diff --git a/.gitignore b/.gitignore index 2813ecc..074c3af 100644 --- a/.gitignore +++ b/.gitignore @@ -101,4 +101,10 @@ TODO.txt *.lck *.log -.claude \ No newline at end of file +.claude +*.report.md +*.claude.md +diff + +*rinjae* +*gf63* \ No newline at end of file diff --git a/BOOTRUN_SETUP_GUIDE.md b/BOOTRUN_SETUP_GUIDE.md new file mode 100644 index 0000000..a61affb --- /dev/null +++ b/BOOTRUN_SETUP_GUIDE.md @@ -0,0 +1,173 @@ +# eapim-portal 로컬 개발 환경 실행 가이드 + +> Eclipse Terminal에서 `gradlew bootRun`으로 기동하는 방법 + +--- + +## 환경 구성 개요 + +| 항목 | 내용 | +|------|------| +| DB | 로컬 Docker Oracle 19c | +| 포트 | 8080 | +| 프로파일 | dev | +| 실행 방법 | Eclipse Terminal → `gradlew.bat bootRun` | + +--- + +## 1. 사전 준비: 로그 디렉토리 생성 + +최초 1회만 실행 (없으면 기동 실패) + +```cmd +mkdir C:\Log\eapim\portal +mkdir C:\Log\App\eapim +``` + +--- + +## 2. Docker Oracle 기동 + +```cmd +cd D:\eActive\workspaces\elink-djbank\docker\ekjb +docker-compose up -d +``` + +Oracle 완전 기동 확인 (처음 기동 시 3~5분 소요): + +```cmd +docker logs -f oracle +``` + +`DATABASE IS READY TO USE!` 메시지 확인 후 다음 단계 진행. + +--- + +## 3. Eclipse Terminal에서 bootRun 실행 + +### Eclipse Terminal 열기 + +`Window` → `Show View` → `Terminal` +→ 우측 상단 **+** 클릭 → **Local Terminal** 선택 + +### 실행 명령어 + +```cmd +cd D:\eActive\workspaces\elink-djbank\eapim-portal +gradlew.bat bootRun +``` + +### 기동 성공 확인 + +``` +##### PortalApplication Ready ##### +``` + +브라우저에서 `http://localhost:8080` 접속 확인. + +--- + +## 4. 수정된 파일 목록 + +### 4-1. `src/main/resources/application-dev.yml` + +DB 호스트를 원격 서버에서 로컬 Docker로 변경: + +```yaml +ems: + datasource: + jdbc-url: jdbc:oracle:thin:@//localhost:1599/DAPM # 변경 + username: EMSAPP + password: elink1234 + +gateway: + datasource: + jdbc-url: jdbc:oracle:thin:@//localhost:1599/DAPM # 변경 + username: AGWAPP + password: elink1234 +``` + +### 4-2. `build.gradle` + +`bootRun` 태스크에 `sourceResources` 추가: + +```groovy +bootRun { + sourceResources sourceSets.main // processResources 필터 우회 (dev yml 직접 참조) + args = ["--spring.profiles.active=dev"] +} +``` + +> **이유**: `processResources`가 `application-dev.yml`을 빌드 출력에서 제외하도록 설정되어 있어, +> `sourceResources sourceSets.main`을 추가해야 `bootRun` 실행 시 소스 디렉토리를 직접 참조함. + +### 4-3. `src/main/resources/logback-spring.xml` + +`dev` 프로파일에 CONSOLE appender 추가 (콘솔 로그 출력): + +```xml + + + + + + +``` + +### 4-4. `.classpath` + +MapStruct 등 Gradle annotation processor 생성 소스를 Eclipse 소스 폴더로 추가: + +```xml + + + + + + +``` + +> **이유**: Eclipse는 Gradle annotation processing을 자동 실행하지 않으므로, +> `gradlew.bat compileJava` 실행 후 생성된 소스 경로를 Eclipse 빌드 경로에 수동 추가 필요. + +--- + +## 5. Annotation Processor 소스 재생성 + +소스 구조 변경(매퍼 추가 등) 시 재실행 필요: + +```cmd +cd D:\eActive\workspaces\elink-djbank\eapim-portal +gradlew.bat compileJava +``` + +이후 Eclipse에서 `Project` → `Clean` 실행. + +--- + +## 6. Eclipse에서 직접 기동이 안 되는 이유 + +Eclipse는 참조 프로젝트(`elink-online-core-jpa`)의 test scope 의존성을 구분하지 않고 +런타임 클래스패스에 모두 포함시킵니다. + +이로 인해 Spring Boot 버전 충돌 발생: + +| 출처 | jar | 충돌 대상 | +|------|-----|-----------| +| `elink-online-core-jpa` (test) | `spring-boot-autoconfigure:2.6.15` | eapim-portal `2.7.18` | +| `elink-online-core-jpa` (test) | `spring-core:5.3.27` | eapim-portal `5.3.31` | + +→ `gradlew bootRun`은 Gradle의 dependency management(BOM)로 버전을 통일하므로 이 문제가 없음. + +--- + +## 7. Docker Oracle 계정 정보 + +| 항목 | 값 | +|------|----| +| Host | localhost | +| Port | 1599 | +| SID | DAPM | +| EMS 계정 | EMSAPP / elink1234 | +| GW 계정 | AGWAPP / elink1234 | diff --git a/CLAUDE.md b/CLAUDE.md index fd952b8..5c589d1 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -14,6 +14,46 @@ - Spring Security (커스텀 인증) - JPA/Hibernate 5.6.15 with Envers (감사 추적) - MapStruct (DTO 매핑), Lombok (보일러플레이트 제거) +- SASS/SCSS (CSS 전처리기) - 스타일 수정 시 반드시 SASS 파일 수정 + +**CSS/SASS 가이드:** +- CSS 파일(`src/main/resources/static/css/main.css`)은 SASS에서 컴파일된 결과물 +- 스타일 수정 시 반드시 SASS 파일(`src/main/resources/static/sass/`)을 수정해야 함 +- CSS 파일 직접 수정 금지 - SASS 파일 수정 후 컴파일 필요 +- SASS 컴파일: `sass src/main/resources/static/sass/main.scss src/main/resources/static/css/main.css` + +## Git 브랜치 전략 + +**저장소 정보:** +- 기본 저장소: `ssh://git@192.168.240.178:18081/eapim/eapim-portal.git` +- 대체 저장소: `https://git.eactive.synology.me:8090/kjb-eapim/eapim-portal.git` + +**브랜치:** +- **jenkins_with_weblogic**: Jenkins 빌드와 WebLogic 테스트를 위한 브랜치 (기본 개발 브랜치) + - Jenkins CI/CD 파이프라인 설정 포함 + - WebLogic 배포 및 테스트 환경 설정 + - 일상적인 개발 작업은 이 브랜치에서 수행 + - 기능 개발, 버그 수정, 테스트 등 모든 개발 활동의 기본 브랜치 + +- **master**: 완벽히 동작하는 검증된 코드 저장소 (안정 브랜치) + - 프로덕션 배포 가능한 안정적인 코드만 포함 + - jenkins_with_weblogic 브랜치에서 충분히 테스트된 코드만 병합 + - 직접 커밋 금지, Pull Request를 통한 병합만 허용 + +**브랜치 사용 가이드:** +```bash +# 개발 시작 시 jenkins_with_weblogic 브랜치에서 작업 +git checkout jenkins_with_weblogic + +# 기능 개발 후 커밋 +git add . +git commit -m "기능 설명" + +# jenkins_with_weblogic 브랜치에 푸시 +git push origin jenkins_with_weblogic + +# 충분한 테스트 완료 후 master로 병합 (Pull Request 사용) +``` ## 빌드 명령어 @@ -433,7 +473,7 @@ gradle bootWar ./build_docker.sh # 컨테이너 실행 -docker run -p 8080:8080 \ +docker run -p 30200:30200 \ -e JAVA_OPTS="-Dspring.profiles.active=prod" \ -v /Log:/Log \ eapim-portal:latest diff --git a/README.md b/README.md index 64b2e56..1417139 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,55 @@ 광주은행 엔터프라이즈 API 포털 관리 시스템 +## 프로젝트 클론 (Git Clone) + +아래 스크립트를 복사하여 한 번에 실행하면 모든 프로젝트를 클론합니다: + +```bash +# 변수 설정 (환경에 맞게 수정하세요) +GIT_URL="ssh://git@192.168.240.178:18081/eapim" +PROJECT_HOME="/c/eactive/workspaces/kjb-eapim-devPortal" +DEFAULT_BRANCH="jenkins_with_weblogic" + +# 프로젝트 홈 디렉토리 생성 및 이동 +mkdir -p "$PROJECT_HOME" +cd "$PROJECT_HOME" + +# 메인 프로젝트 클론 +git clone -b "$DEFAULT_BRANCH" "$GIT_URL/kjb-eapim.git" kjb-eapim + +# 의존 모듈 클론 (kjb-eapim 폴더와 동일 레벨) +mkdir -p eapim-online +git clone -b "$DEFAULT_BRANCH" "$GIT_URL/elink-online-core-jpa.git" eapim-online/elink-online-core-jpa +git clone -b "$DEFAULT_BRANCH" "$GIT_URL/elink-portal-common.git" elink-portal-common +git clone -b "$DEFAULT_BRANCH" "$GIT_URL/kjb-safedb.git" kjb-safedb + +# 클론 후 폴더 구조 확인 +echo "=== 프로젝트 구조 ===" +ls -la "$PROJECT_HOME" +``` + +### 클론 후 폴더 구조 + +``` +$PROJECT_HOME/ +├── kjb-eapim/ # 메인 프로젝트 +│ └── eapim-portal/ # 이 프로젝트 (Portal) +├── eapim-online/ # Gateway 데이터 모델 +│ └── elink-online-core-jpa/ # JPA 엔티티 모듈 +├── elink-portal-common/ # 공통 유틸리티 +└── kjb-safedb/ # SafeDB 암호화 라이브러리 +``` + +### 의존 관계 (settings.gradle 참조) + +``` +eapim-portal +├── elink-online-core-jpa → ../eapim-online/elink-online-core-jpa +├── elink-portal-common → ../elink-portal-common +└── kjb-safedb → ../kjb-safedb +``` + ## 프로젝트 개요 **EAPIM Portal**은 광주은행의 API 서비스를 관리하고, 사용자 등록, API 키 발급, 문서화, 테스트 기능을 제공하는 웹 기반 플랫폼입니다. @@ -16,23 +65,255 @@ - JPA/Hibernate 5.6.15 with Envers - MapStruct, Lombok +## 초기 세팅 가이드 + +### 사전 요구사항 + +- JDK 8 이상 +- Gradle 8.7 +- Git +- Oracle 19c (또는 개발 환경에 따라 접근 가능한 DB) + +### Git Bash에서 초기 세팅 + +```bash +# ==================================== +# 1. 변수 설정 (프로젝트 경로 및 브랜치) +# ==================================== +export WORKSPACE_DIR="/c/eactive/workspaces/kjb-eapim" +export PROJECT_BRANCH="jenkins_with_weblogic" + +# Git Repository URL 설정 +# 기본: ssh://git@192.168.240.178:18081/eapim +# 대체: https://git.eactive.synology.me:8090/kjb-eapim +export GIT_REPO_BASE="ssh://git@192.168.240.178:18081/eapim" +# export GIT_REPO_BASE="https://git.eactive.synology.me:8090/kjb-eapim" + +# Gradle 명령어 설정 +# 일반 환경: gradle +# msi-gf63 환경: /c/eactive/workspaces/shell-scripts/kjb-gradle.sh +export GRADLE_CMD="gradle" +# export GRADLE_CMD="/c/eactive/workspaces/shell-scripts/kjb-gradle.sh" + +# ==================================== +# 2. 작업 디렉토리 생성 +# ==================================== +mkdir -p $WORKSPACE_DIR +cd $WORKSPACE_DIR + +# ==================================== +# 3. eapim-portal 프로젝트 클론 +# ==================================== +git clone ${GIT_REPO_BASE}/eapim-portal.git +cd eapim-portal + +# 프로젝트 브랜치로 체크아웃 +git checkout $PROJECT_BRANCH + +# ==================================== +# 4. eapim-online 프로젝트 클론 (elink-online-core-jpa 포함) +# ==================================== +cd $WORKSPACE_DIR +git clone ${GIT_REPO_BASE}/eapim-online.git +cd eapim-online +git checkout $PROJECT_BRANCH + +# ==================================== +# 5. 의존 모듈 클론 +# ==================================== +cd $WORKSPACE_DIR + +# elink-portal-common 클론 +git clone ${GIT_REPO_BASE}/elink-portal-common.git +cd elink-portal-common +git checkout master # 또는 필요한 브랜치 + +# kjb-safedb 클론 +cd $WORKSPACE_DIR +git clone ${GIT_REPO_BASE}/kjb-safedb.git +cd kjb-safedb +git checkout master # 또는 필요한 브랜치 + +# ==================================== +# 6. 최종 폴더 구조 확인 +# ==================================== +# kjb-eapim/ +# ├── eapim-portal/ +# ├── eapim-online/ +# │ └── elink-online-core-jpa/ +# ├── elink-portal-common/ +# └── kjb-safedb/ + +# ==================================== +# 7. eapim-portal 프로젝트로 이동 및 디펜던시 다운로드 +# ==================================== +cd $WORKSPACE_DIR/eapim-portal + +# Gradle 디펜던시 다운로드 +$GRADLE_CMD dependencies +``` + +### Windows CMD에서 초기 세팅 + +```cmd +REM ==================================== +REM 1. 변수 설정 (프로젝트 경로 및 브랜치) +REM ==================================== +set WORKSPACE_DIR=C:\eactive\workspaces\kjb-eapim +set PROJECT_BRANCH=jenkins_with_weblogic + +REM Git Repository URL 설정 +REM 기본: ssh://git@192.168.240.178:18081/eapim +REM 대체: https://git.eactive.synology.me:8090/kjb-eapim +set GIT_REPO_BASE=ssh://git@192.168.240.178:18081/eapim +REM set GIT_REPO_BASE=https://git.eactive.synology.me:8090/kjb-eapim + +REM Gradle 명령어 설정 +REM 일반 환경: gradle +REM msi-gf63 환경: C:\eactive\workspaces\shell-scripts\kjb-gradle.sh +set GRADLE_CMD=gradle +REM set GRADLE_CMD=C:\eactive\workspaces\shell-scripts\kjb-gradle.sh + +REM ==================================== +REM 2. 작업 디렉토리 생성 +REM ==================================== +if not exist "%WORKSPACE_DIR%" mkdir "%WORKSPACE_DIR%" +cd /d "%WORKSPACE_DIR%" + +REM ==================================== +REM 3. eapim-portal 프로젝트 클론 +REM ==================================== +git clone %GIT_REPO_BASE%/eapim-portal.git +cd eapim-portal + +REM 프로젝트 브랜치로 체크아웃 +git checkout %PROJECT_BRANCH% + +REM ==================================== +REM 4. eapim-online 프로젝트 클론 (elink-online-core-jpa 포함) +REM ==================================== +cd /d "%WORKSPACE_DIR%" +git clone %GIT_REPO_BASE%/eapim-online.git +cd eapim-online +git checkout %PROJECT_BRANCH% + +REM ==================================== +REM 5. 의존 모듈 클론 +REM ==================================== +cd /d "%WORKSPACE_DIR%" + +REM elink-portal-common 클론 +git clone %GIT_REPO_BASE%/elink-portal-common.git +cd elink-portal-common +git checkout master + +REM kjb-safedb 클론 +cd /d "%WORKSPACE_DIR%" +git clone %GIT_REPO_BASE%/kjb-safedb.git +cd kjb-safedb +git checkout master + +REM ==================================== +REM 6. 최종 폴더 구조 확인 +REM ==================================== +REM kjb-eapim\ +REM ├── eapim-portal\ +REM ├── eapim-online\ +REM │ └── elink-online-core-jpa\ +REM ├── elink-portal-common\ +REM └── kjb-safedb\ + +REM ==================================== +REM 7. eapim-portal 프로젝트로 이동 및 디펜던시 다운로드 +REM ==================================== +cd /d "%WORKSPACE_DIR%\eapim-portal" + +REM Gradle 디펜던시 다운로드 +%GRADLE_CMD% dependencies +``` + +### 환경별 설정 파일 + +프로젝트 클론 후 필요한 환경 설정을 수정하세요: + +- `src/main/resources/application-dev.yml` - 개발 환경 +- `src/main/resources/application-stage.yml` - 스테이징 환경 +- `src/main/resources/application-prod.yml` - 운영 환경 +- `src/main/resources/application-gf63.yml` - 개인 개발 환경 + +### 데이터베이스 설정 + +개발 환경(dev)에서는 `application-dev.yml`에 데이터베이스 설정이 포함되어 있습니다. +스테이징/운영 환경에서는 JNDI 데이터소스가 필요합니다: + +- `jdbc/dsOBP_EMS` - Portal 데이터베이스 +- `jdbc/dsOBP_AGW` - Gateway 데이터베이스 + +### 주의사항 + +- **gradlew 사용 금지**: 반드시 시스템에 설치된 gradle을 직접 사용하거나 환경에 맞는 스크립트를 사용하세요 +- **Gradle 명령어 변수화**: 위의 `GRADLE_CMD` 변수를 설정하면 환경별로 다른 gradle 명령어 사용 가능 +- **SafeDB**: 암호화 기능이 필요한 경우 kjb-safedb 모듈 설정 확인 + ## 빌드 및 실행 +### Gradle 명령어 설정 + +사용 환경에 따라 적절한 gradle 명령어를 설정하세요: + +**Git Bash:** +```bash +# 일반 환경 +export GRADLE_CMD="gradle" + +# msi-gf63 환경 +export GRADLE_CMD="/c/eactive/workspaces/shell-scripts/kjb-gradle.sh" +``` + +**Windows CMD:** +```cmd +REM 일반 환경 +set GRADLE_CMD=gradle + +REM msi-gf63 환경 +set GRADLE_CMD=C:\eactive\workspaces\shell-scripts\kjb-gradle.sh +``` + +### 빌드 및 실행 명령어 + ```bash # 개발 환경 실행 -gradle bootRun +$GRADLE_CMD bootRun +# 또는: %GRADLE_CMD% bootRun (CMD) # WAR 빌드 -gradle bootWar +$GRADLE_CMD bootWar # 테스트 -gradle test +$GRADLE_CMD test + +# 컴파일만 수행 +$GRADLE_CMD classes + +# 전체 빌드 +$GRADLE_CMD clean build ``` **주의**: gradlew 사용 금지 - offline gradle 단독 실행 ## 프로젝트 구조 +### 전체 프로젝트 구조 +``` +kjb-eapim/ +├── eapim-portal/ # Portal 웹 애플리케이션 (현재 프로젝트) +├── eapim-online/ # Online 프로젝트 +│ └── elink-online-core-jpa/ # Gateway 데이터 모델 +├── elink-portal-common/ # 공통 유틸리티 +└── kjb-safedb/ # SafeDB 암호화 라이브러리 +``` + +### eapim-portal 내부 구조 ``` src/main/java/com/eactive/apim/portal/ ├── apps/ # 기능 모듈 (user, app, approval, apis, etc.) @@ -43,10 +324,153 @@ src/main/java/com/eactive/apim/portal/ ## 외부 모듈 -이 프로젝트는 다음 모듈에 의존합니다: -- `elink-online-core-jpa`: Gateway 데이터 모델 -- `elink-portal-common`: 공통 유틸리티 -- `kjb-safedb`: SafeDB 암호화 라이브러리 +이 프로젝트는 다음 모듈에 의존합니다 (settings.gradle 참조): +- `eapim-online/elink-online-core-jpa`: Gateway 데이터 모델 (../eapim-online/elink-online-core-jpa) +- `elink-portal-common`: 공통 유틸리티 (../elink-portal-common) +- `kjb-safedb`: SafeDB 암호화 라이브러리 (../kjb-safedb) + +## SASS / CSS 스타일 개발 + +### 개요 + +이 프로젝트는 **Dart Sass**를 사용하여 CSS를 관리합니다. +`src/main/resources/static/css/main.css`는 SASS에서 컴파일된 결과물이므로 **직접 수정하면 안 됩니다.** +스타일 변경은 반드시 `src/main/resources/static/sass/` 하위 파일을 수정하세요. + +### 설치 + +```bash +# Node.js가 설치된 상태에서 +npm install +``` + +### 빌드 명령어 + +```bash +# 1회 컴파일 (개발용, 들여쓰기 유지) +npm run sass:build + +# 1회 컴파일 (배포용, minified) +npm run sass:build:minified + +# 파일 변경 감지 후 자동 컴파일 (개발 중 권장) +npm run sass:watch + +# 빌드 + minified 동시 생성 +npm run build + +# 단축 스크립트 (npm 없이 sass 직접 실행) +./sass-build.sh +``` + +### SASS 파일 구조 + +``` +src/main/resources/static/sass/ +├── main.scss # 진입점 - 모든 파일을 @use로 조합 +│ +├── abstracts/ # CSS 출력 없는 헬퍼 (변수, 믹스인 등) +│ ├── _variables.scss # 색상, 타이포그래피, 그림자 변수 +│ ├── _mixins.scss # 재사용 믹스인 +│ └── _color-functions.scss # Dart Sass 3.0 color 유틸리티 +│ +├── base/ # 전역 기본 스타일 +│ ├── _reset.scss +│ ├── _typography.scss +│ ├── _animations.scss +│ ├── _kjb-font.scss # 광주은행 전용 폰트 +│ └── _utilities.scss # 유틸리티 클래스 +│ +├── layout/ # 페이지 레이아웃 구조 +│ ├── _header.scss +│ ├── _footer.scss +│ ├── _grid.scss +│ └── _container.scss +│ +├── components/ # 재사용 UI 컴포넌트 +│ ├── _buttons.scss +│ ├── _forms.scss +│ ├── _modals.scss # customPopup 모달 스타일 +│ ├── _tables.scss +│ ├── _badges.scss +│ ├── _cards.scss +│ ├── _navigation.scss +│ ├── _pagination.scss +│ └── ... +│ +├── pages/ # 페이지별 스타일 +│ ├── _mypage.scss +│ ├── _login.scss +│ ├── _apikey-list.scss +│ └── ... +│ +├── themes/ +│ └── _dark.scss # 다크 테마 (예약) +│ +└── vendors/ + └── _overrides.scss # 외부 라이브러리 스타일 덮어쓰기 +``` + +### 새 스타일 추가하는 방법 + +#### 1. 기존 컴포넌트/페이지 파일에 추가 +해당 `_*.scss` 파일을 직접 수정합니다. + +```scss +// components/_buttons.scss 예시 +.btn-withdrawal { + color: $text-gray; + border: 1px solid $border-gray; + + &:hover { + color: $accent-orange; + } +} +``` + +#### 2. 새 페이지 스타일 파일 추가 + +```bash +# 1. 파일 생성 (언더스코어 접두사 필수) +touch src/main/resources/static/sass/pages/_new-page.scss + +# 2. main.scss 에 @use 추가 +``` + +```scss +// main.scss 에 추가 +@use 'pages/new-page' as *; +``` + +#### 3. 변수 활용 예시 + +```scss +// abstracts/_variables.scss 에 정의된 변수 사용 +.my-component { + color: $primary-blue; // #0049b4 (광주은행 블루) + background: $gray-bg; // #F8FAFC + border: 1px solid $border-gray; // #E2E8F0 + box-shadow: $shadow-md; + font-family: $font-family-primary; +} +``` + +### 컴파일 결과 확인 + +```bash +# 컴파일 후 출력 파일 확인 +ls -lh src/main/resources/static/css/main.css +ls -lh src/main/resources/static/css/main.min.css # minified +``` + +### 주의사항 + +- `src/main/resources/static/css/main.css` **직접 수정 금지** — SASS 재컴파일 시 덮어씌워짐 +- `style2.css` 는 레거시 파일(구 디자인 시스템)이며 `main.css` 와 **별도로 관리됨** +- `@import` 대신 **`@use`** 사용 (Dart Sass 3.0 호환) +- 파일명 앞에 언더스코어(`_`) 필수 — `_filename.scss` 형식이어야 단독 컴파일되지 않음 + +--- ## AI 코딩 어시스턴트 지침 파일 @@ -127,16 +551,16 @@ docker run -p 8080:8080 eapim-portal:latest ## 관련 프로젝트 -- `../eapim-admin/` - Admin 포털 -- `../eapim-online/` - Online 포털 -- `../kjb-eapim-sql/` - SQL 스크립트 +프로젝트 구조 (kjb-eapim 디렉토리 기준): +- `eapim-portal/` - Portal 웹 애플리케이션 (현재 프로젝트) +- `eapim-online/` - Online 프로젝트 (elink-online-core-jpa 포함) +- `elink-portal-common/` - 공통 유틸리티 라이브러리 +- `kjb-safedb/` - SafeDB 암호화 라이브러리 +- `eapim-admin/` - Admin 포털 (선택사항) +- `kjb-eapim-sql/` - SQL 스크립트 (선택사항) ## 문서 - **프로젝트 상세 지침**: `CLAUDE.md` (한글) - **사용자 가이드**: `개발자포탈.md` (한글) - **빌드 스크립트**: `build-gf63.sh`, `deploy_portal.sh` - -## 라이선스 - -© 광주은행 (Kwangju Bank) diff --git a/build.gradle b/build.gradle index 779fd1e..918045e 100644 --- a/build.gradle +++ b/build.gradle @@ -14,6 +14,14 @@ targetCompatibility = "1.8" def nexusUrl = "https://nexus.eactive.synology.me:8090" +allprojects { + repositories { + maven { + url "${nexusUrl}/repository/maven-public/" + allowInsecureProtocol = true + } + } +} dependencies { annotationProcessor "org.projectlombok:lombok:1.18.28" @@ -27,7 +35,7 @@ dependencies { implementation project(':elink-online-core-jpa') implementation project(':elink-portal-common') - implementation project(':kjb-safedb') +// implementation project(':kjb-safedb') implementation('org.springframework.boot:spring-boot-starter') implementation 'org.springframework.boot:spring-boot-starter-jta-atomikos' @@ -79,6 +87,11 @@ dependencies { implementation 'net.bytebuddy:byte-buddy:1.14.5' + // Commons FileUpload (WAS 독립적인 multipart 처리) + implementation 'commons-fileupload:commons-fileupload:1.5' + implementation 'commons-io:commons-io:2.15.1' + + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.0' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.0' testImplementation 'org.mockito:mockito-inline:3.11.2' @@ -110,6 +123,7 @@ bootRun { // 디버깅 사용시에는 아래 내용 주석 해제 하세요 // jvmArgs '-Xdebug', '-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005' + sourceResources sourceSets.main // processResources 필터 우회 (dev yml 직접 참조) args = ["--spring.profiles.active=dev"] } @@ -132,6 +146,15 @@ compileJava { options.compilerArgs += ["-parameters"] options.encoding = 'UTF-8' } + +processResources { + exclude { details -> + details.file.name.startsWith('application-') && + details.file.name.endsWith('.yml') && + !(details.file.name in ['application-stage.yml', 'application-prod.yml']) + } +} + test { useJUnitPlatform() enabled = true @@ -158,4 +181,20 @@ task printSourceSets { println " Output dir : ${srcSet.output.classesDirs.asPath}" } } +} + +// CryptoCli 실행 task (bcrypt 지원) +task cryptoCli(type: JavaExec) { + mainClass = 'com.eactive.ext.kjb.safedb.CryptoCli' + classpath = sourceSets.main.runtimeClasspath + standardInput = System.in + + // SafeDB 라이브러리 경로 추가 (리눅스) + classpath += files("/safedb/JavaAPI/config") + classpath += fileTree(dir: "/safedb/JavaAPI/lib", include: ["*.jar"]) + + // CLI 인자 전달: ./gradlew cryptoCli --args="bcrypt hash password123" + if (project.hasProperty('args')) { + args project.args.split('\\s+') + } } \ No newline at end of file diff --git a/email_template/email_common_template.html b/email_template/email_common_template.html new file mode 100644 index 0000000..5d14a50 --- /dev/null +++ b/email_template/email_common_template.html @@ -0,0 +1,260 @@ + + + + + + + 광주은행 API Portal + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + +
+ + + diff --git a/email_template/email_logo_bottom.png b/email_template/email_logo_bottom.png new file mode 100644 index 0000000..d0f80e4 Binary files /dev/null and b/email_template/email_logo_bottom.png differ diff --git a/email_template/email_logo_top.png b/email_template/email_logo_top.png new file mode 100644 index 0000000..43544b9 Binary files /dev/null and b/email_template/email_logo_top.png differ diff --git a/email_template/email_otp_password.html b/email_template/email_otp_password.html new file mode 100644 index 0000000..adecb47 --- /dev/null +++ b/email_template/email_otp_password.html @@ -0,0 +1,255 @@ + + + + + + + 일회용 비밀번호 - 광주은행 API Portal + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + +
+ + + diff --git a/email_template/email_password_changed.html b/email_template/email_password_changed.html new file mode 100644 index 0000000..58213cf --- /dev/null +++ b/email_template/email_password_changed.html @@ -0,0 +1,234 @@ + + + + + + + 비밀번호 변경 완료 안내 - 광주은행 API Portal + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + +
+ + + diff --git a/email_template/email_verification.html b/email_template/email_verification.html new file mode 100644 index 0000000..42bc740 --- /dev/null +++ b/email_template/email_verification.html @@ -0,0 +1,235 @@ + + + + + + + 이메일 인증 - 광주은행 API Portal + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + +
+ + + \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 8bea23a..20db9ad 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=gradle-8.7-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/kjb-docs/biz-reg-no-duplicate-check-plan.md b/kjb-docs/biz-reg-no-duplicate-check-plan.md new file mode 100644 index 0000000..7a8e070 --- /dev/null +++ b/kjb-docs/biz-reg-no-duplicate-check-plan.md @@ -0,0 +1,113 @@ +# 사업자등록번호 중복 체크 구현 작업 계획서 + +## 1. 현황 분석 + +### 문제점 +- 법인 등록 시 사업자등록번호(compRegNo) 중복 검증이 없음 +- `PortalOrgRepository.findByCompRegNo()` 메서드는 존재하나 사용되지 않음 +- 동일한 사업자등록번호로 여러 법인이 등록될 수 있는 상태 + +### 검증 시점 +1. **실시간 검증**: 사용자가 사업자등록번호 입력 시 `/check-business-number` API 호출 +2. **등록 시 검증**: 실제 법인 생성 시점 (`registerOrgFromDTOWithFile`) + +--- + +## 2. 수정 대상 파일 + +| 파일 경로 | 수정 내용 | +|-----------|-----------| +| `src/main/java/.../apps/user/service/PortalOrgService.java` | 사업자등록번호 중복 체크 메서드 추가 | +| `src/main/java/.../apps/user/facade/OrgRegisterFacadeImpl.java` | `checkBusinessNumber()` 중복 체크 로직 추가, 등록 전 중복 체크 추가 | + +--- + +## 3. 구현 상세 + +### 3.1 PortalOrgService.java + +```java +// 추가할 메서드 +public boolean existsByCompRegNo(String compRegNo) { + return portalOrgRepository.findByCompRegNo(compRegNo).isPresent(); +} +``` + +### 3.2 OrgRegisterFacadeImpl.java + +#### checkBusinessNumber 메서드 수정 +- 형식 검증 + 중복 검증 동시 수행 +- 중복 시 "이미 등록된 사업자등록번호입니다." 메시지 반환 + +```java +@Override +public ResponseEntity checkBusinessNumber(String compRegNo) { + // 1. 형식 검증 + boolean isValidFormat = validationService.isValidBusinessNumber(compRegNo); + if (!isValidFormat) { + return ResponseEntity.ok(new ValidationResponse(false, "유효하지 않은 사업자 등록번호입니다.")); + } + + // 2. 중복 검증 + boolean isDuplicate = portalOrgService.existsByCompRegNo(compRegNo); + if (isDuplicate) { + return ResponseEntity.ok(new ValidationResponse(false, "이미 등록된 사업자등록번호입니다.")); + } + + return ResponseEntity.ok(new ValidationResponse(true, "사용 가능한 사업자 등록번호입니다.")); +} +``` + +#### 법인 생성 메서드들에 중복 체크 추가 +- `registerNewCorporateUser()` +- `convertExistingUserToCorporate()` +- `registerNewCorporateUserWithExistingData()` + +각 메서드에서 `portalOrgService.registerOrgFromDTOWithFile()` 호출 전 중복 체크 수행 + +--- + +## 4. 검증 흐름 + +``` +[사용자 입력] + ↓ +[실시간 검증] /check-business-number API + ├── 형식 검증 (10자리 숫자) + └── 중복 검증 (DB 조회) + ↓ +[등록 요청] + ↓ +[등록 시 검증] registerOrgFromDTOWithFile 호출 전 + └── 중복 체크 (이중 방어) + ↓ +[법인 생성] +``` + +--- + +## 5. 기존 중복 데이터 처리 + +- 기존에 중복 등록된 데이터는 그대로 유지 +- 신규 등록 시에만 중복 체크 적용 +- 필요시 관리자 화면에서 중복 데이터 조회/정리 기능 별도 개발 가능 + +--- + +## 6. 테스트 시나리오 + +| 시나리오 | 예상 결과 | +|----------|-----------| +| 형식이 잘못된 사업자등록번호 입력 | "유효하지 않은 사업자 등록번호입니다." | +| 이미 등록된 사업자등록번호 입력 | "이미 등록된 사업자등록번호입니다." | +| 사용 가능한 사업자등록번호 입력 | "사용 가능한 사업자 등록번호입니다." | +| 중복된 번호로 법인 등록 시도 | 등록 실패, 에러 메시지 표시 | + +--- + +## 7. 작업 완료 + +- [x] PortalOrgService에 existsByCompRegNo 메서드 추가 +- [x] OrgRegisterFacadeImpl.checkBusinessNumber 수정 +- [x] 법인 등록 메서드들에 중복 체크 로직 추가 +- [x] 빌드 검증 완료 diff --git a/kjb-docs/design-survey-guide.md b/kjb-docs/design-survey-guide.md new file mode 100644 index 0000000..34ddb6d --- /dev/null +++ b/kjb-docs/design-survey-guide.md @@ -0,0 +1,165 @@ +# 네비게이션 바 디자인 Survey 기능 가이드 + +## 개요 + +직원 대상 네비게이션 바 디자인 선호도 조사를 위한 UI 기능입니다. +사용자가 A, B, C 등의 버튼을 클릭하면 실시간으로 네비게이션 바 디자인이 변경됩니다. + +## 기능 활성화/비활성화 + +### PortalProperty 설정 + +DB의 `ptl_property` 테이블에서 `ui.design-survey` 값을 설정합니다. + +```sql +-- 활성화 +UPDATE ptl_property +SET property_value = 'true' +WHERE property_group_name = 'Portal' AND property_name = 'ui.design-survey'; + +-- 비활성화 +UPDATE ptl_property +SET property_value = 'false' +WHERE property_group_name = 'Portal' AND property_name = 'ui.design-survey'; +``` + +> 최초 페이지 로드 시 속성이 자동 생성됩니다 (기본값: `false`) + +--- + +## 디자인 옵션 추가/수정 방법 + +### 파일 위치 +`src/main/resources/templates/views/fragment/kjbank/header_container.html` + +### DESIGN_OPTIONS 배열 + +디자인 옵션은 JavaScript의 `DESIGN_OPTIONS` 배열에서 관리됩니다: + +```javascript +const DESIGN_OPTIONS = [ + { + id: 'A', // 고유 식별자 (필수) + label: 'A (현재)', // 버튼에 표시될 텍스트 (필수) + isDefault: true, // 기본 선택 옵션 (선택, 하나만 true) + styles: {} // 적용할 CSS 스타일 (필수) + }, + { + id: 'B', + label: 'B', + styles: { + '.logo-text': { 'font-size': '18px' }, + '.nav-link': { 'margin': '0 20px' } + } + }, + { + id: 'C', + label: 'C', + styles: { + '.nav-link': { 'margin': '0 20px', 'font-weight': 'bold' } + } + } +]; +``` + +### 새 디자인 옵션 추가 예시 + +```javascript +// D 옵션 추가 +{ + id: 'D', + label: 'D', + styles: { + '.logo-text': { + 'font-size': '16px', + 'color': '#333333' + }, + '.nav-link': { + 'padding': '10px 24px', + 'font-weight': '600' + } + } +} +``` + +### styles 객체 구조 + +```javascript +styles: { + 'CSS 셀렉터': { + 'CSS 속성': '값', + 'CSS 속성2': '값2' + }, + '다른 셀렉터': { + 'CSS 속성': '값' + } +} +``` + +--- + +## 자주 사용되는 CSS 셀렉터 + +| 셀렉터 | 설명 | +|--------|------| +| `.logo-text` | 로고 텍스트 "API Portal" | +| `.nav-link` | 네비게이션 메뉴 링크 | +| `.nav-menu` | 네비게이션 메뉴 컨테이너 | +| `.header-content` | 헤더 콘텐츠 영역 | +| `.global-header` | 전체 헤더 | +| `.logo-wrapper` | 로고 래퍼 (이미지 + 텍스트) | + +## 자주 사용되는 CSS 속성 + +| 속성 | 예시 값 | 설명 | +|------|---------|------| +| `font-size` | `18px`, `1.2rem` | 글자 크기 | +| `font-weight` | `400`, `500`, `600`, `bold` | 글자 두께 | +| `margin` | `0 20px`, `10px 15px` | 바깥 여백 | +| `padding` | `8px 16px` | 안쪽 여백 | +| `color` | `#333333`, `#0049b4` | 글자 색상 | +| `background` | `#ffffff`, `#f5f5f5` | 배경 색상 | +| `gap` | `12px`, `20px` | flex 아이템 간격 | + +--- + +## 사용자 선택 저장 + +사용자의 디자인 선택은 브라우저의 `localStorage`에 저장됩니다. + +- Key: `design-survey-selection` +- Value: 선택한 디자인 ID (예: `A`, `B`, `C`) + +```javascript +// 저장된 선택 확인 (개발자 도구 콘솔) +localStorage.getItem('design-survey-selection') + +// 선택 초기화 +localStorage.removeItem('design-survey-selection') +``` + +--- + +## 관련 파일 + +| 파일 | 역할 | +|------|------| +| `GlobalControllerAdvice.java` | `designSurveyEnabled` ModelAttribute 제공 | +| `header_container.html` | Survey 바 HTML 및 DESIGN_OPTIONS 설정 | +| `_header.scss` | Survey 바 스타일 | + +--- + +## 주의사항 + +1. **디자인 옵션 ID는 고유해야 합니다** - 중복 ID 사용 시 오동작 +2. **isDefault는 하나의 옵션에만 설정** - 여러 개 설정 시 첫 번째만 적용 +3. **CSS 속성명은 kebab-case 사용** - `fontSize` (X) → `font-size` (O) +4. **스타일 적용 시 `!important` 자동 추가** - 기존 스타일 덮어씀 +5. **Survey 기능 종료 후** - `ui.design-survey`를 `false`로 설정하여 비활성화 + +--- + +## 문의 + +기능 관련 문의는 개발팀에 연락해주세요. diff --git a/kjb-docs/invitation-code-input-implementation-plan.md b/kjb-docs/invitation-code-input-implementation-plan.md new file mode 100644 index 0000000..cd156df --- /dev/null +++ b/kjb-docs/invitation-code-input-implementation-plan.md @@ -0,0 +1,1466 @@ +# 초대 코드 직접 입력 기능 구현 계획 + +**작성일**: 2025-11-24 +**목적**: 폐쇄망 환경에서 이메일 링크를 사용할 수 없는 사용자를 위한 8자리 초대코드 직접 입력 기능 추가 + +--- + +## 📌 개요 + +### 배경 +- 현재 초대 시스템은 이메일 링크 방식만 지원 +- 폐쇄망 환경에서는 이메일 링크 클릭이 불가능 +- 8자리 토큰(예: K7MNPQ2R)을 생성하고 있지만 직접 입력 UI가 없음 + +### 목표 +1. 로그인 페이지에 "초대를 받으셨나요?" 링크 추가 +2. 초대코드 직접 입력 페이지 생성 +3. 기존 회원 로그인 시 자동으로 초대 확인 및 리다이렉트 + +--- + +## 🎯 구현 대상 사용자 플로우 + +### 신규 회원 (미가입자) +``` +로그인 페이지 → "초대를 받으셨나요?" 클릭 → 초대코드 입력 → 검증 성공 → 회원가입 페이지 +``` + +### 기존 회원 (ROLE_USER) +``` +방법1: 로그인 페이지 → "초대를 받으셨나요?" 클릭 → 로그인 유도 → 로그인 → 자동 초대 확인 → 초대 수락 페이지 +방법2: 로그인 페이지 → 로그인 → 자동 초대 확인 → 초대 수락 페이지 +``` + +--- + +## 🛠 작업 목록 + +### 0. 마이페이지 초대 알림 (재방문 사용자 대응) + +로그인 후 자동 리다이렉트를 놓친 사용자나, 페이지를 벗어났다가 다시 돌아온 사용자를 위해 마이페이지에서 초대 알림을 표시합니다. + +#### 0.1 AccountController 수정 +**파일**: `src/main/java/com/eactive/apim/portal/apps/user/controller/AccountController.java` + +**GET /mypage 메서드 수정** (119-149라인): + +```java +@GetMapping("/mypage") +public ModelAndView mypage() { + ModelAndView mav = new ModelAndView(); + + try { + PortalAuthenticatedUser currentUser = SecurityUtil.getPortalAuthenticatedUser(); + PortalUserDTO user = userFacade.findById(currentUser.getId()); + + // ===== 새로 추가: 초대 확인 로직 ===== + // ROLE_USER만 초대 확인 (법인 회원은 제외) + if (currentUser.getRoleCode() == RoleCode.ROLE_USER) { + Optional pendingInvitation = + userInvitationRepository.findFirstByInvitationEmailAndStatus( + currentUser.getLoginId(), InvitationStatus.PENDING); + + if (pendingInvitation.isPresent()) { + // 초대 정보를 모델에 추가 + mav.addObject("hasPendingInvitation", true); + + // 초대한 법인명 조회 + String orgId = pendingInvitation.get().getOrgId(); + Optional org = portalOrgRepository.findById(orgId); + if (org.isPresent()) { + mav.addObject("invitationOrgName", org.get().getOrgName()); + } + } + } + // ================================ + + // 기존 로직 + mav.addObject("loginId", user.getLoginId()); + mav.addObject("userName", user.getUserName()); + mav.addObject("mobileNumber", user.getMobileNumber()); + mav.addObject("user", user); + + // ... 기존 코드 계속 + } +} +``` + +**추가 필드**: +```java +private final UserInvitationRepository userInvitationRepository; +private final PortalOrgRepository portalOrgRepository; +``` + +**예상 작업량**: 20-25줄 추가 + +--- + +#### 0.2 마이페이지 레이아웃 수정 (옵션 1: 공통 알림) +**파일**: `src/main/resources/templates/views/layout/kbank_mypage_layout.html` + +**헤더 다음에 알림 배너 추가**: + +```html +
+ + +
+
+
+ 📩 +
+ 법인명에서 귀하를 법인회원으로 초대하였습니다. +
+ + 초대 확인하기 + +
+
+
+ +
+
+``` + +**CSS 스타일**: +```css +.invitation-alert-banner { + background: #e3f2fd; + border-bottom: 2px solid #2196F3; + padding: 15px 0; +} + +.invitation-banner { + max-width: 1200px; + margin: 0 auto; + display: flex; + align-items: center; + padding: 12px 20px; + background: white; + border-radius: 8px; + box-shadow: 0 2px 4px rgba(0,0,0,0.1); +} + +.invitation-content { + display: flex; + align-items: center; + gap: 15px; + width: 100%; +} + +.invitation-content .icon { + font-size: 24px; +} + +.invitation-content .message { + flex: 1; + font-size: 14px; + color: #333; +} + +.invitation-content .btn { + white-space: nowrap; +} +``` + +**예상 작업량**: 50-60줄 (HTML + CSS) + +--- + +#### 0.3 내 정보 관리 페이지 알림 (옵션 2: 페이지별 알림) +**파일**: `src/main/resources/templates/views/apps/mypage/updatePersonalUser.html` + +**페이지 상단에 알림 추가** (8-10라인 다음): + +```html +
+

내 정보 관리

+
+ + +
+

📩 법인회원 초대

+

+ 법인명에서 귀하를 법인회원으로 초대하였습니다. +

+ +
+ +
+ +
+``` + +**CSS 스타일**: +```css +.invitation-alert { + background: #e3f2fd; + border: 2px solid #2196F3; + border-radius: 8px; + padding: 20px; + margin: 20px 0; + text-align: center; +} + +.invitation-alert h4 { + color: #1976D2; + margin-bottom: 10px; + font-size: 18px; +} + +.invitation-alert p { + color: #333; + margin-bottom: 15px; + font-size: 14px; +} +``` + +**예상 작업량**: 40-50줄 (HTML + CSS) + +--- + +#### 0.4 선택 기준 + +**옵션 1 (레이아웃 수정)** 추천: +- ✅ 모든 마이페이지에서 초대 알림 표시 +- ✅ 일관된 UX +- ✅ 수정 파일 적음 (1개 레이아웃 파일) + +**옵션 2 (개별 페이지 수정)**: +- ✅ 페이지별 커스터마이징 가능 +- ❌ 여러 파일 수정 필요 (updatePersonalUser, updateCorporateUser, updateCorporateManager 등) +- ❌ 일부 페이지에서 놓칠 가능성 + +**권장**: 옵션 1 (레이아웃 수정) + +--- + +#### 0.5 테스트 시나리오 + +**시나리오 1: 로그인 후 초대 자동 확인 놓침** +1. 로그인 시 초대 자동 리다이렉트 발생 +2. 사용자가 뒤로가기나 다른 페이지 이동 +3. 마이페이지 접속 +4. 상단에 초대 알림 배너 표시 ✅ +5. "초대 확인하기" 클릭 → /signup/decision_process 이동 + +**시나리오 2: 세션 만료 후 재로그인** +1. 로그인 세션 만료 +2. 재로그인 +3. 자동 리다이렉트 없음 (세션 초기화) +4. 마이페이지 접속 +5. 초대 알림 표시 ✅ + +**시나리오 3: 초대 수락 후 알림 제거** +1. 마이페이지에서 초대 알림 확인 +2. "초대 확인하기" 클릭 → 초대 수락 +3. 마이페이지 재방문 +4. 초대 알림 없음 ✅ (PENDING 상태 아님) + +**시나리오 4: 법인 회원은 알림 표시 안 됨** +1. ROLE_CORP_USER로 로그인 +2. 마이페이지 접속 +3. 초대 알림 없음 ✅ (ROLE_USER만 표시) + +--- + +### 1. 프론트엔드 (Thymeleaf Templates) + +#### 1.1 로그인 페이지 수정 +**파일**: `src/main/resources/templates/views/apps/login/login.html` + +**수정 위치**: 53-73라인 `login_list` 영역 + +**작업 내용**: +```html +
  • + + 초대를 받으셨나요? +
  • +
  • + | +
  • +``` + +**예상 작업량**: 10줄 추가 + +--- + +#### 1.2 초대코드 입력 페이지 생성 (신규) +**파일**: `src/main/resources/templates/views/apps/register/invitationCodeInput.html` + +**작업 내용**: +- 8자리 초대코드 입력 폼 +- 대문자 자동 변환 (JavaScript) +- CSRF 토큰 포함 +- 검증 실패 시 에러 메시지 표시 + +**UI 요구사항**: +- 입력 필드: 8자리 영문+숫자 (예: K7MNPQ2R) +- 실시간 대문자 변환 +- 제출 버튼: "확인" +- 취소 버튼: "돌아가기" (로그인 페이지로) + +**예상 작업량**: 100-120줄 + +**예시 구조**: +```html +
    + + +
    +
    +

    초대코드

    +
    + +
    +
    +
    + + +
    +``` + +**JavaScript 기능**: +- 자동 대문자 변환 +- 8자리 제한 +- 영문+숫자만 입력 허용 + +--- + +### 2. 백엔드 (Java Controller) + +#### 2.1 UserRegisterController 수정 +**파일**: `src/main/java/com/eactive/apim/portal/apps/user/controller/UserRegisterController.java` + +**추가 엔드포인트**: + +##### 2.1.1 GET /signup/invitation-code +```java +@GetMapping("/signup/invitation-code") +public String showInvitationCodeInput(HttpSession session, Model model) { + // 초대코드 입력 페이지 표시 + return "apps/register/invitationCodeInput"; +} +``` + +##### 2.1.2 POST /signup/invitation-code +```java +@PostMapping("/signup/invitation-code") +public String processInvitationCode( + @RequestParam("invitationCode") String invitationCode, + HttpSession session, + RedirectAttributes redirectAttributes, + Model model) { + + // 1. 입력값 검증 (8자리, 영문+숫자) + if (!invitationCode.matches("[A-Z0-9]{8}")) { + model.addAttribute("error", "올바른 형식의 초대코드를 입력해주세요."); + return "apps/register/invitationCodeInput"; + } + + // 2. DB에서 토큰 조회 + Optional invitation = + userInvitationRepository.findByToken(invitationCode); + + // 3. 유효성 검증 + if (!invitation.isPresent() || + invitation.get().getStatus() != UserInvitationEnums.InvitationStatus.PENDING) { + model.addAttribute("error", "유효하지 않은 초대코드입니다."); + return "apps/register/invitationCodeInput"; + } + + // 4. 만료 확인 + if (invitation.get().getExpiresOn().isBefore(LocalDateTime.now())) { + model.addAttribute("error", "만료된 초대코드입니다."); + return "apps/register/invitationCodeInput"; + } + + // 5. 사용자 존재 확인 + String email = invitation.get().getInvitationEmail(); + Optional existingUser = + portalUserRepository.findPortalUserByEmailAddr(email); + + if (existingUser.isPresent()) { + // 기존 회원 → 로그인 유도 + session.setAttribute("invitationCodeForLogin", invitationCode); + redirectAttributes.addFlashAttribute("message", + "로그인하여 초대를 수락해주세요."); + return "redirect:/login"; + } else { + // 신규 회원 → 가입 페이지 + return "redirect:/signup/portalUser?invitation=" + invitationCode; + } +} +``` + +**예상 작업량**: 50-60줄 + +--- + +#### 2.2 PortalAuthenticationSuccessHandler 수정 +**파일**: `src/main/java/com/eactive/apim/portal/config/PortalAuthenticationSuccessHandler.java` + +**수정 위치**: `onAuthenticationSuccess()` 메서드 (38-83라인) + +**추가 로직**: +```java +@Override +public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, + Authentication authentication) throws IOException, ServletException { + String username = request.getParameter("id"); + PortalUser user = portalUserRepository.findPortalUserByEmailAddr(username).orElse(null); + + // ... 기존 로직 (이메일 인증, 휴면 계정, 비밀번호 만료 체크) + + // ===== 새로 추가 ===== + // 초대 코드 자동 확인 (ROLE_USER 회원만) + if (user.getRoleCode() == RoleCode.ROLE_USER) { + Optional pendingInvitation = + userInvitationRepository.findFirstByInvitationEmailAndStatus( + username, InvitationStatus.PENDING); + + if (pendingInvitation.isPresent()) { + // 초대가 있으면 자동으로 초대 수락 페이지로 리다이렉트 + response.sendRedirect(contextPath + "/signup/decision_process"); + return; + } + } + // ================== + + // 기존 decisionToken 체크 로직 + String decisionToken = (String) request.getSession().getAttribute("decisionToken"); + if (decisionToken != null) { + response.sendRedirect(contextPath + "/signup/decision_process"); + } else { + response.sendRedirect(contextPath + "/"); + } +} +``` + +**추가 필드**: +```java +private final UserInvitationRepository userInvitationRepository; +``` + +**예상 작업량**: 15줄 추가 + +--- + +### 3. 보안 설정 (선택사항) + +#### 3.1 PortalConfigSecurity 확인 +**파일**: `src/main/java/com/eactive/apim/portal/config/PortalConfigSecurity.java` + +**확인 사항**: +- `/signup/invitation-code` 경로가 인증 없이 접근 가능한지 확인 +- CSRF 토큰이 적용되는지 확인 + +**예상 설정** (이미 `/signup/**`이 허용되어 있을 가능성 높음): +```java +.antMatchers("/signup/**").permitAll() +``` + +--- + +## 🔍 테스트 계획 + +### 단위 테스트 + +#### 3.1 Controller 테스트 +```java +@SpringBootTest +@AutoConfigureMockMvc +class InvitationCodeInputTest extends BaseWebTest { + + @Test + void testValidInvitationCode_NewUser() { + // 신규 회원용 유효한 코드 입력 → 가입 페이지로 리다이렉트 + } + + @Test + void testValidInvitationCode_ExistingUser() { + // 기존 회원용 유효한 코드 입력 → 로그인 페이지로 리다이렉트 + } + + @Test + void testInvalidInvitationCode() { + // 존재하지 않는 코드 → 에러 메시지 + } + + @Test + void testExpiredInvitationCode() { + // 만료된 코드 → 에러 메시지 + } + + @Test + void testAlreadyProcessedCode() { + // 이미 처리된 코드(COMPLETED) → 에러 메시지 + } +} +``` + +#### 3.2 AuthenticationSuccessHandler 테스트 +```java +@Test +void testAutoRedirectWithPendingInvitation() { + // ROLE_USER 로그인 시 초대가 있으면 자동 리다이렉트 +} + +@Test +void testNoAutoRedirectForCorpUser() { + // ROLE_CORP_USER는 자동 리다이렉트 안됨 +} +``` + +### 통합 테스트 시나리오 + +#### 시나리오 1: 신규 회원 초대코드 입력 +1. 로그인 페이지 접속 +2. "초대를 받으셨나요?" 클릭 +3. 초대코드 입력 (예: K7MNPQ2R) +4. 회원가입 페이지로 이동 +5. 가입 완료 + +**예상 결과**: ROLE_CORP_USER로 가입 완료 + +--- + +#### 시나리오 2: 기존 회원 초대코드 입력 후 로그인 +1. 로그인 페이지 접속 +2. "초대를 받으셨나요?" 클릭 +3. 초대코드 입력 (예: K7MNPQ2R) +4. "로그인하여 초대를 수락해주세요" 메시지 +5. 로그인 페이지로 리다이렉트 +6. 로그인 +7. 자동으로 초대 수락 페이지로 이동 +8. 약관 동의 후 수락 +9. 로그아웃 후 재로그인 + +**예상 결과**: ROLE_USER → ROLE_CORP_USER 전환 완료 + +--- + +#### 시나리오 3: 기존 회원 직접 로그인 (초대 자동 확인) +1. 로그인 페이지 접속 +2. 로그인 (초대받은 이메일로) +3. 자동으로 초대 수락 페이지로 리다이렉트 +4. 약관 동의 후 수락 + +**예상 결과**: 초대 수락 완료 + +--- + +#### 시나리오 4: 유효하지 않은 초대코드 +1. 초대코드 입력 페이지 접속 +2. 존재하지 않는 코드 입력 (예: INVALID1) +3. "유효하지 않은 초대코드입니다" 에러 메시지 +4. 입력 페이지 유지 + +**예상 결과**: 에러 메시지 표시, 재입력 가능 + +--- + +#### 시나리오 5: 만료된 초대코드 +1. 초대코드 입력 페이지 접속 +2. 7일 이상 경과한 코드 입력 +3. "만료된 초대코드입니다" 에러 메시지 + +**예상 결과**: 에러 메시지 표시 + +--- + +## ⚠️ 주의사항 및 고려사항 + +### 보안 +- [ ] CSRF 토큰 적용 확인 +- [ ] SQL Injection 방지 (JPA 사용으로 기본 방어) +- [ ] **초대코드 Brute Force 공격 방지 (세션 기반 실패 횟수 제한)** - 아래 상세 구현 방법 참조 +- [ ] 세션 하이재킹 방지 (기존 Spring Security로 대응) +- [ ] 이메일 형식 검증 (프론트엔드 + 백엔드 이중 검증) ✅ 구현 완료 + +### 사용성 +- [ ] 입력 필드 자동 대문자 변환 +- [ ] 8자리 제한 및 유효성 검증 (클라이언트 + 서버) +- [ ] 명확한 에러 메시지 +- [ ] 모바일 반응형 디자인 + +### 우선순위 충돌 처리 +`PortalAuthenticationSuccessHandler`에서 여러 조건 체크 순서: + +1. 이메일 인증 필요 (`READY` 상태) +2. 휴면 계정 (`DORMANT` 상태) +3. 임시 비밀번호 로그인 +4. 비밀번호 만료 (90일) +5. **→ [새로 추가] 초대 코드 확인** ← ROLE_USER만 해당 +6. decisionToken 세션 체크 +7. 일반 홈페이지 + +**결정**: 비밀번호 관련 체크 이후, 일반 홈 이동 전에 초대 확인 + +--- + +## 📊 작업 규모 요약 + +| 항목 | 파일 수 | 예상 코드량 | 난이도 | 예상 시간 | +|------|---------|------------|--------|----------| +| **마이페이지 초대 알림** | 2 | 70-85줄 | ⭐⭐ 보통 | 1시간 | +| HTML 수정 (로그인) | 1 | 10줄 | ⭐ 쉬움 | 15분 | +| HTML 신규 (초대코드 입력) | 1 | 100-120줄 | ⭐⭐ 보통 | 1-1.5시간 | +| Controller 수정 (UserRegister) | 1 | 50-60줄 | ⭐⭐ 보통 | 1시간 | +| Handler 수정 | 1 | 15줄 | ⭐⭐ 보통 | 30분 | +| 단위 테스트 | 2 | 100줄 | ⭐⭐ 보통 | 1시간 | +| 통합 테스트 | - | 수동 | ⭐⭐⭐ 중요 | 2-2.5시간 | + +**총 예상 시간**: 6.5-8시간 + +--- + +## 📝 체크리스트 + +### 개발 전 +- [ ] 기존 초대 플로우 완전 이해 +- [ ] DB 스키마 확인 (UserInvitation 테이블) +- [ ] 기존 테스트 케이스 리뷰 + +### 개발 중 +- [ ] **초대 만료일 동적 설정** 🔧 개선 + - [ ] DurationParser 유틸리티 클래스 생성 + - [ ] UserManFacade에 PortalPropertyService 의존성 추가 + - [ ] createInvitation() 메서드 수정 (동적 만료일) + - [ ] PTL_PROPERTY 테이블에 설정 추가 +- [ ] **중복 초대 방지 추가** ⚠️ 필수 + - [ ] UserInvitationRepository 메서드 추가 + - [ ] UserManFacade.sendInvitation()에 중복 체크 로직 추가 + - [ ] 같은 법인 중복 초대 차단 + - [ ] 만료된 초대 자동 취소 후 재생성 + - [ ] 다른 법인 중복 초대 차단 (비즈니스 정책 결정 필요) +- [ ] **마이페이지 초대 알림 추가** (재방문 사용자 대응) + - [ ] AccountController에 초대 확인 로직 추가 + - [ ] kbank_mypage_layout.html에 알림 배너 추가 + - [ ] CSS 스타일 작성 +- [ ] 로그인 페이지 "초대를 받으셨나요?" 링크 추가 +- [ ] 초대코드 입력 페이지 생성 +- [ ] UserRegisterController 엔드포인트 구현 + - [ ] GET /signup/invitation-code + - [ ] POST /signup/invitation-code (Brute Force 방지 포함) +- [ ] AuthenticationSuccessHandler 수정 (자동 초대 확인) +- [ ] CSRF 토큰 적용 확인 +- [ ] 에러 처리 구현 + +### 테스트 +- [ ] 단위 테스트 작성 및 통과 +- [ ] **초대 만료일 동적 설정 테스트** 🔧 + - [ ] 기본값 7일 테스트 + - [ ] 2시간 설정 테스트 + - [ ] 30분 설정 테스트 + - [ ] 14일 설정 테스트 + - [ ] 잘못된 형식 → 기본값 fallback 테스트 +- [ ] **중복 초대 방지 테스트** ⚠️ 필수 + - [ ] 같은 법인 중복 초대 차단 + - [ ] 만료된 초대 재발송 + - [ ] 다른 법인 중복 초대 차단 + - [ ] 초대 수락 후 중복 차단 +- [ ] **마이페이지 초대 알림 테스트** + - [ ] 로그인 후 자동 확인 놓친 경우 + - [ ] 세션 만료 후 재로그인 + - [ ] 초대 수락 후 알림 제거 확인 + - [ ] 법인 회원은 알림 표시 안 됨 +- [ ] 신규 회원 플로우 테스트 +- [ ] 기존 회원 플로우 테스트 +- [ ] 에러 케이스 테스트 +- [ ] 만료된 코드 테스트 +- [ ] Brute Force 방지 테스트 (5회 실패 시나리오) +- [ ] 우선순위 충돌 테스트 + +### 배포 전 +- [ ] 코드 리뷰 +- [ ] 통합 테스트 완료 +- [ ] 로그 확인 (Hibernate SQL, 애플리케이션 로그) +- [ ] 스테이징 환경 배포 및 검증 +- [ ] 운영 배포 계획 수립 + +--- + +## 🔗 관련 파일 + +### 주요 소스 +- `UserRegisterController.java:149-191` - 기존 초대 처리 로직 +- `UserManFacade.java:124-163` - 초대 생성 및 토큰 생성 +- `PortalAuthenticationSuccessHandler.java:38-83` - 로그인 성공 처리 +- `userDecisionProcess.html` - 초대 수락 페이지 (약관 포함) + +### 참고 문서 +- `kjb-docs/mermaid/invite-flow-integrated.mermaid` - 통합 플로우차트 +- `kjb-docs/mermaid/invite-sequence.mermaid` - 시퀀스 다이어그램 +- `CLAUDE.md` - 프로젝트 아키텍처 + +--- + +## 📅 일정 (예상) + +| 단계 | 작업 내용 | 소요 시간 | 담당자 | +|------|----------|----------|--------| +| 0단계 | 마이페이지 초대 알림 구현 | 1시간 | - | +| 1단계 | HTML 작업 (로그인, 초대코드 입력) | 1.5시간 | - | +| 2단계 | Controller 구현 (Brute Force 방지 포함) | 1.5시간 | - | +| 3단계 | AuthenticationSuccessHandler 수정 | 0.5시간 | - | +| 4단계 | 단위 테스트 작성 | 1시간 | - | +| 5단계 | 통합 테스트 (수동) | 2.5시간 | - | +| 총 | | **8시간** | | + +--- + +## 📌 Brute Force 방지 구현 (세션 기반) + +### 개요 +초대코드 입력 실패 시 세션 기반으로 실패 횟수를 추적하여, 일정 횟수 이상 실패 시 일시적으로 입력을 차단합니다. + +### 구현 방법 + +#### 1. 세션 속성 정의 +```java +// 세션에 저장할 키 +public static final String INVITATION_CODE_FAILURE_COUNT = "invitationCodeFailureCount"; +public static final String INVITATION_CODE_LOCKED_UNTIL = "invitationCodeLockedUntil"; + +// 설정값 +public static final int MAX_FAILURE_COUNT = 5; // 최대 실패 허용 횟수 +public static final int LOCK_DURATION_MINUTES = 15; // 차단 시간 (분) +``` + +#### 2. UserRegisterController 수정 +**파일**: `UserRegisterController.java` + +**POST /signup/invitation-code 메서드 수정**: + +```java +@PostMapping("/signup/invitation-code") +public String processInvitationCode( + @RequestParam("invitationCode") String invitationCode, + HttpSession session, + RedirectAttributes redirectAttributes, + Model model) { + + // 1. 차단 상태 확인 + LocalDateTime lockedUntil = (LocalDateTime) session.getAttribute(INVITATION_CODE_LOCKED_UNTIL); + if (lockedUntil != null && LocalDateTime.now().isBefore(lockedUntil)) { + long minutesLeft = ChronoUnit.MINUTES.between(LocalDateTime.now(), lockedUntil); + model.addAttribute("error", + String.format("너무 많은 시도로 인해 %d분간 초대코드 입력이 차단되었습니다.", minutesLeft + 1)); + return "apps/register/invitationCodeInput"; + } + + // 2. 입력값 검증 + if (!invitationCode.matches("[A-Z0-9]{8}")) { + incrementFailureCount(session); + model.addAttribute("error", "올바른 형식의 초대코드를 입력해주세요."); + return "apps/register/invitationCodeInput"; + } + + // 3. DB에서 토큰 조회 및 검증 + Optional invitation = + userInvitationRepository.findByToken(invitationCode); + + if (!invitation.isPresent() || + invitation.get().getStatus() != UserInvitationEnums.InvitationStatus.PENDING || + invitation.get().getExpiresOn().isBefore(LocalDateTime.now())) { + + // 실패 처리 + int failureCount = incrementFailureCount(session); + + if (failureCount >= MAX_FAILURE_COUNT) { + // 차단 처리 + LocalDateTime lockTime = LocalDateTime.now().plusMinutes(LOCK_DURATION_MINUTES); + session.setAttribute(INVITATION_CODE_LOCKED_UNTIL, lockTime); + model.addAttribute("error", + "너무 많은 시도로 인해 15분간 초대코드 입력이 차단되었습니다."); + } else { + int remainingAttempts = MAX_FAILURE_COUNT - failureCount; + model.addAttribute("error", + String.format("유효하지 않은 초대코드입니다. (남은 시도: %d회)", remainingAttempts)); + } + + return "apps/register/invitationCodeInput"; + } + + // 4. 성공 시 실패 카운트 초기화 + session.removeAttribute(INVITATION_CODE_FAILURE_COUNT); + session.removeAttribute(INVITATION_CODE_LOCKED_UNTIL); + + // 5. 기존 로직 (사용자 확인 및 리다이렉트) + String email = invitation.get().getInvitationEmail(); + Optional existingUser = + portalUserRepository.findPortalUserByEmailAddr(email); + + if (existingUser.isPresent()) { + session.setAttribute("invitationCodeForLogin", invitationCode); + redirectAttributes.addFlashAttribute("message", "로그인하여 초대를 수락해주세요."); + return "redirect:/login"; + } else { + return "redirect:/signup/portalUser?invitation=" + invitationCode; + } +} + +// Helper 메서드 +private int incrementFailureCount(HttpSession session) { + Integer failureCount = (Integer) session.getAttribute(INVITATION_CODE_FAILURE_COUNT); + if (failureCount == null) { + failureCount = 0; + } + failureCount++; + session.setAttribute(INVITATION_CODE_FAILURE_COUNT, failureCount); + return failureCount; +} +``` + +#### 3. 프론트엔드 UI 개선 +**invitationCodeInput.html에 추가**: + +```html + +
    + ⚠️ 보안 경고 +

    +

    잠시 후 다시 시도해주세요.

    +
    + + +
    +

    +
    +``` + +#### 4. 장점 +- ✅ **DB 사용 불필요**: 세션 메모리만 사용하여 간단하고 빠름 +- ✅ **자동 만료**: 세션 타임아웃(15분) 시 자동으로 초기화 +- ✅ **사용자별 독립적**: 각 세션마다 독립적으로 카운트 +- ✅ **성능 영향 최소**: 추가 DB 쿼리 없음 + +#### 5. 주의사항 +- 세션이 만료되면 실패 카운트도 초기화됨 (긍정적 효과) +- 다른 브라우저/디바이스에서는 별도 카운트 (IP 기반 차단 아님) +- 로그아웃 시 세션 초기화로 카운트 리셋 가능 (큰 문제 아님) + +#### 6. 테스트 시나리오 +``` +시나리오 1: 5회 실패 후 차단 +1. 잘못된 코드 입력 (1회) → "남은 시도: 4회" +2. 잘못된 코드 입력 (2회) → "남은 시도: 3회" +3. 잘못된 코드 입력 (3회) → "남은 시도: 2회" +4. 잘못된 코드 입력 (4회) → "남은 시도: 1회" +5. 잘못된 코드 입력 (5회) → "15분간 차단되었습니다" +6. 올바른 코드 입력 → 차단 메시지 표시, 입력 불가 + +시나리오 2: 차단 후 시간 경과 +1. 5회 실패로 차단 상태 +2. 15분 대기 +3. 올바른 코드 입력 → 정상 처리 + +시나리오 3: 실패 후 성공 +1. 잘못된 코드 입력 (2회) → "남은 시도: 3회" +2. 올바른 코드 입력 → 성공, 카운트 초기화 +3. 다시 잘못된 코드 입력 → "남은 시도: 4회" (초기화됨) +``` + +--- + +## 📌 초대 만료일 동적 설정 (PortalProperty) 🔧 개선 + +### 현재 상황 + +**UserManFacade.createInvitation()** (149-150라인): +```java +// Set expiration date (e.g., 7 days from now) +newInvitation.setExpiresOn(LocalDateTime.now().plusDays(7)); +``` + +**문제점**: +- 만료일이 하드코딩되어 있음 (7일 고정) +- 운영 중 변경 시 코드 수정 및 재배포 필요 +- 환경별 다른 설정 불가 + +--- + +### 개선 방안: PTL_PROPERTY 테이블 활용 + +**PTL_PROPERTY 테이블**에서 초대 만료 기간을 읽어와 동적으로 설정합니다. + +#### 1. 시간 표현 유틸리티 클래스 생성 (신규) + +**파일**: `src/main/java/com/eactive/apim/portal/common/util/DurationParser.java` + +```java +package com.eactive.apim.portal.common.util; + +import java.time.Duration; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * 시간 표현 문자열을 Duration으로 파싱하는 유틸리티 + * 지원 형식: 5m (5분), 2h (2시간), 7d (7일) + */ +public class DurationParser { + + private static final Pattern DURATION_PATTERN = Pattern.compile("^(\\d+)([mhd])$"); + + /** + * 시간 표현 문자열을 Duration으로 변환 + * + * @param durationStr 시간 문자열 (예: "5m", "2h", "7d") + * @return Duration 객체 + * @throws IllegalArgumentException 형식이 올바르지 않을 경우 + */ + public static Duration parse(String durationStr) { + if (durationStr == null || durationStr.trim().isEmpty()) { + throw new IllegalArgumentException("Duration string cannot be null or empty"); + } + + String normalized = durationStr.trim().toLowerCase(); + Matcher matcher = DURATION_PATTERN.matcher(normalized); + + if (!matcher.matches()) { + throw new IllegalArgumentException( + "Invalid duration format: " + durationStr + + ". Expected format: (e.g., 5m, 2h, 7d)" + ); + } + + long value = Long.parseLong(matcher.group(1)); + String unit = matcher.group(2); + + switch (unit) { + case "m": + return Duration.ofMinutes(value); + case "h": + return Duration.ofHours(value); + case "d": + return Duration.ofDays(value); + default: + throw new IllegalArgumentException("Unsupported time unit: " + unit); + } + } + + /** + * 시간 표현 문자열을 일(day) 단위로 변환 + * + * @param durationStr 시간 문자열 (예: "5m", "2h", "7d") + * @return 일(day) 단위 long 값 + */ + public static long parseToDays(String durationStr) { + return parse(durationStr).toDays(); + } + + /** + * 기본값을 제공하는 안전한 파싱 + * + * @param durationStr 시간 문자열 + * @param defaultValue 파싱 실패 시 기본값 + * @return Duration 객체 + */ + public static Duration parseOrDefault(String durationStr, Duration defaultValue) { + try { + return parse(durationStr); + } catch (Exception e) { + return defaultValue; + } + } +} +``` + +**예상 작업량**: 70줄 + +--- + +#### 2. UserManFacade 수정 + +**파일**: `UserManFacade.java` + +**변경 전** (149-150라인): +```java +// Set expiration date (e.g., 7 days from now) +newInvitation.setExpiresOn(LocalDateTime.now().plusDays(7)); +``` + +**변경 후**: +```java +// ===== 새로 추가 ===== +private final PortalPropertyService portalPropertyService; + +public String createInvitation(PortalUser sender, String emailAddr) { + UserInvitation newInvitation = new UserInvitation(); + newInvitation.setAdminId(sender.getId()); + newInvitation.setOrgId(sender.getPortalOrg().getId()); + newInvitation.setInvitationEmail(emailAddr); + + // Set additional required fields + newInvitation.setInvitationDate(LocalDateTime.now()); + newInvitation.setStatus(InvitationStatus.PENDING); + + // Generate 8-character token + String token = generateEightCharToken(); + newInvitation.setToken(token); + + // ===== 변경: 동적 만료일 설정 ===== + // PTL_PROPERTY에서 초대 만료 기간 조회 + Map properties = portalPropertyService.getPortalPropertiesAsMap("Portal"); + String expirationDuration = properties.getOrDefault("invitation.expiration", "7d"); + + // 시간 문자열 파싱 (예: "7d" → 7일, "2h" → 2시간, "30m" → 30분) + Duration duration = DurationParser.parseOrDefault(expirationDuration, Duration.ofDays(7)); + + // 만료일 설정 + newInvitation.setExpiresOn(LocalDateTime.now().plus(duration)); + // ================================ + + // Save the invitation + userInvitationRepository.save(newInvitation); + return token; +} +``` + +**추가 필드**: +```java +private final PortalPropertyService portalPropertyService; +``` + +**예상 작업량**: 10줄 추가 + +--- + +### PTL_PROPERTY 테이블 설정 + +#### 데이터 삽입 SQL + +```sql +-- 초대 만료 기간 설정 (기본값: 7일) +INSERT INTO PTL_PROPERTY (PROP_GROUP, PROP_KEY, PROP_VALUE, DESCRIPTION) +VALUES ('Portal', 'invitation.expiration', '7d', '초대 만료 기간 (형식: 숫자+단위, 예: 5m, 2h, 7d)'); + +-- 다른 예시들 +-- 30분: '30m' +-- 2시간: '2h' +-- 14일: '14d' +``` + +#### 설정 예시 + +| PROP_GROUP | PROP_KEY | PROP_VALUE | 설명 | +|------------|----------|------------|------| +| Portal | invitation.expiration | 7d | 7일 후 만료 (기본값) | +| Portal | invitation.expiration | 2h | 2시간 후 만료 (테스트용) | +| Portal | invitation.expiration | 30m | 30분 후 만료 (데모용) | +| Portal | invitation.expiration | 14d | 14일 후 만료 (운영용) | + +--- + +### 지원하는 시간 단위 + +| 단위 | 설명 | 예시 | 실제 기간 | +|------|------|------|----------| +| `m` | 분 (minutes) | `5m` | 5분 | +| `m` | 분 (minutes) | `30m` | 30분 | +| `h` | 시간 (hours) | `2h` | 2시간 | +| `h` | 시간 (hours) | `24h` | 24시간 (1일) | +| `d` | 일 (days) | `7d` | 7일 | +| `d` | 일 (days) | `30d` | 30일 | + +**형식**: `<숫자><단위>` (예: `5m`, `2h`, `7d`) + +--- + +### 에러 처리 + +#### 잘못된 형식 입력 시 + +```java +// PTL_PROPERTY에 잘못된 값이 있는 경우 +// 예: "invalid", "7", "d7", "7dd" + +Duration duration = DurationParser.parseOrDefault(expirationDuration, Duration.ofDays(7)); +// → 파싱 실패 시 기본값 7일 사용 +``` + +**로그 출력** (선택사항): +```java +try { + duration = DurationParser.parse(expirationDuration); +} catch (IllegalArgumentException e) { + log.warn("Invalid invitation expiration duration '{}', using default 7 days", + expirationDuration, e); + duration = Duration.ofDays(7); +} +``` + +--- + +### 테스트 시나리오 + +#### 테스트 1: 기본값 7일 +``` +PTL_PROPERTY: 설정 없음 또는 "7d" +초대 생성: 2025-11-24 10:00 +만료일: 2025-12-01 10:00 (7일 후) ✅ +``` + +#### 테스트 2: 2시간 (테스트용) +``` +PTL_PROPERTY: "2h" +초대 생성: 2025-11-24 10:00 +만료일: 2025-11-24 12:00 (2시간 후) ✅ +``` + +#### 테스트 3: 30분 (데모용) +``` +PTL_PROPERTY: "30m" +초대 생성: 2025-11-24 10:00 +만료일: 2025-11-24 10:30 (30분 후) ✅ +``` + +#### 테스트 4: 14일 (운영용) +``` +PTL_PROPERTY: "14d" +초대 생성: 2025-11-24 10:00 +만료일: 2025-12-08 10:00 (14일 후) ✅ +``` + +#### 테스트 5: 잘못된 형식 → 기본값 +``` +PTL_PROPERTY: "invalid" 또는 빈 값 +초대 생성: 2025-11-24 10:00 +만료일: 2025-12-01 10:00 (기본 7일) ✅ +로그: "Invalid invitation expiration duration 'invalid', using default 7 days" +``` + +--- + +### 장점 + +- ✅ **재배포 불필요**: DB 설정만 변경하면 즉시 적용 +- ✅ **환경별 설정**: 개발/스테이징/운영 환경마다 다른 만료일 설정 가능 +- ✅ **유연성**: 분/시간/일 단위 모두 지원 +- ✅ **안전성**: 잘못된 설정 시 기본값(7일) 사용 +- ✅ **가독성**: "7d"가 "7"보다 의미 명확 + +--- + +## 📌 중복 초대 방지 구현 ⚠️ 필수 + +### 현재 문제점 + +**UserManFacade.sendInvitation()** (91-132라인)에서: +- ✅ 이미 법인 회원인지 체크함 (108-110라인) +- ❌ **PENDING 상태의 초대가 이미 있는지 체크 안 함** + +**결과**: 같은 이메일에 여러 번 초대 가능 → 중복 초대 생성 ❌ + +--- + +### 구현 방법 + +#### 1. UserManFacade.sendInvitation() 수정 +**파일**: `src/main/java/com/eactive/apim/portal/apps/user/facade/UserManFacade.java` + +**수정 위치**: 103-111라인 (기존 사용자 확인 로직 다음) + +```java +public void sendInvitation(PortalUser sender, String emailAddr) { + // 이메일 형식 검증 + if (emailAddr == null || emailAddr.trim().isEmpty()) { + throw new IllegalArgumentException("이메일 주소를 입력해주세요."); + } + + // 이메일 정규식 검증 + String emailPattern = "^[a-zA-Z0-9._%+\\-]+@[a-zA-Z0-9.\\-]+\\.[a-zA-Z]{2,}$"; + if (!emailAddr.matches(emailPattern)) { + throw new IllegalArgumentException("올바른 이메일 형식을 입력해주세요."); + } + + // 기존 사용자인지 확인 + Optional existingUser = portalUserRepository.findPortalUserByEmailAddr(emailAddr); + + if (existingUser.isPresent()) { + PortalUser user = existingUser.get(); + if (user.getPortalOrg() != null && !user.getRoleCode().equals(RoleCode.ROLE_USER)) { + throw new IllegalArgumentException("기관에 등록된 이용자 입니다."); + } + } + + // ===== 새로 추가: 중복 초대 확인 ===== + // 1. 현재 법인의 PENDING 초대 확인 + Optional existingInvitation = + userInvitationRepository.findFirstByInvitationEmailAndOrgIdAndStatus( + emailAddr, + sender.getPortalOrg().getId(), + InvitationStatus.PENDING + ); + + if (existingInvitation.isPresent()) { + UserInvitation invitation = existingInvitation.get(); + + // 만료된 초대인 경우 → 기존 초대 취소 후 새로 생성 + if (invitation.getExpiresOn().isBefore(LocalDateTime.now())) { + invitation.setStatus(InvitationStatus.CANCELED); + userInvitationRepository.save(invitation); + // 계속 진행하여 새 초대 생성 + } else { + // 아직 유효한 초대가 있는 경우 → 에러 + throw new IllegalArgumentException( + "이미 초대 중인 사용자입니다. 초대 만료일: " + + invitation.getExpiresOn().format(DateTimeFormatter.ofPattern("yyyy-MM-dd")) + ); + } + } + + // 2. 다른 법인의 PENDING 초대 확인 (선택사항) + Optional otherOrgInvitation = + userInvitationRepository.findFirstByInvitationEmailAndStatus( + emailAddr, + InvitationStatus.PENDING + ); + + if (otherOrgInvitation.isPresent() && + !otherOrgInvitation.get().getOrgId().equals(sender.getPortalOrg().getId())) { + + // 다른 법인에서 초대 중 → 경고 메시지 (선택사항) + // 비즈니스 정책에 따라 차단 또는 허용 + throw new IllegalArgumentException( + "해당 사용자는 다른 법인에서 초대 중입니다." + ); + } + // ==================================== + + String token = createInvitation(sender, emailAddr); + + // ... 나머지 기존 로직 +} +``` + +**예상 작업량**: 30-35줄 추가 + +--- + +#### 2. UserInvitationRepository 메서드 추가 (필요 시) + +현재 `findFirstByInvitationEmailAndStatus` 메서드는 있지만, +**같은 법인 내 중복 체크**를 위해 새 메서드가 필요할 수 있습니다. + +**파일**: `UserInvitationRepository.java` + +```java +public interface UserInvitationRepository extends JpaRepository { + + // 기존 메서드 + Optional findFirstByInvitationEmailAndStatus( + String invitationEmail, + InvitationStatus status + ); + + // 새로 추가: 같은 법인 내 중복 체크 + Optional findFirstByInvitationEmailAndOrgIdAndStatus( + String invitationEmail, + String orgId, + InvitationStatus status + ); +} +``` + +**예상 작업량**: 5줄 추가 (메서드 시그니처만) + +--- + +### 중복 초대 시나리오 및 처리 방침 + +#### 시나리오 1: 같은 법인에서 중복 초대 +``` +상황: A법인 관리자가 user@example.com에게 초대 +상태: PENDING (만료일: 2025-12-01) +재시도: A법인 관리자가 동일 이메일에 다시 초대 시도 +결과: ❌ "이미 초대 중인 사용자입니다. 초대 만료일: 2025-12-01" +``` + +**정책**: 차단 (같은 법인 내 중복 초대 불가) + +--- + +#### 시나리오 2: 같은 법인, 만료된 초대 +``` +상황: A법인 관리자가 user@example.com에게 초대 +상태: PENDING (만료일: 2025-11-20, 이미 만료됨) +재시도: A법인 관리자가 동일 이메일에 다시 초대 시도 +처리: + 1. 기존 초대 상태 → CANCELED + 2. 새로운 PENDING 초대 생성 + 3. 새 토큰 발급 +결과: ✅ 새 초대 이메일 발송 +``` + +**정책**: 허용 (만료된 초대는 자동 취소 후 재생성) + +--- + +#### 시나리오 3: 다른 법인에서 중복 초대 (비즈니스 정책 필요) + +**옵션 A: 차단** (추천) +``` +상황: A법인에서 user@example.com에게 초대 (PENDING) +재시도: B법인 관리자가 동일 이메일에 초대 시도 +결과: ❌ "해당 사용자는 다른 법인에서 초대 중입니다." +``` + +**옵션 B: 허용** +``` +상황: A법인에서 user@example.com에게 초대 (PENDING) +재시도: B법인 관리자가 동일 이메일에 초대 시도 +결과: ✅ 두 초대 모두 PENDING 상태 유지 +선택: 사용자가 먼저 수락한 초대로 가입 +``` + +**권장**: 옵션 A (차단) - 사용자 혼란 방지 + +--- + +#### 시나리오 4: 초대 수락 후 다른 법인 초대 +``` +상황: user@example.com이 A법인 초대 수락 (ROLE_CORP_USER) +재시도: B법인 관리자가 동일 이메일에 초대 시도 +결과: ❌ "기관에 등록된 이용자 입니다." (기존 로직) +``` + +**정책**: 차단 (이미 구현됨, 108-110라인) + +--- + +### 프론트엔드 에러 메시지 처리 + +**파일**: `userList.html` (sendInvitation 함수, 223-235라인) + +현재 에러 처리 코드: +```javascript +.fail(function (jqXHR, textStatus, errorThrown) { + let errorMessage; + + try { + const errorResponse = JSON.parse(jqXHR.responseText); + errorMessage = errorResponse.msg || '이용자 초대 중 오류가 발생했습니다'; + } catch (e) { + errorMessage = '이용자 초대 중 오류가 발생했습니다: ' + errorThrown; + } + customPopups.showAlert(errorMessage); +}); +``` + +**변경 필요 없음**: 이미 서버 에러 메시지를 그대로 표시하므로 중복 초대 에러도 자동으로 표시됨 ✅ + +--- + +### 테스트 시나리오 + +#### 테스트 1: 같은 법인 중복 초대 차단 +``` +1. A법인 관리자 로그인 +2. user@example.com에게 초대 발송 → 성공 +3. 다시 user@example.com에게 초대 시도 +4. 에러: "이미 초대 중인 사용자입니다. 초대 만료일: 2025-12-01" +5. 초대 목록에서 1개만 표시 ✅ +``` + +#### 테스트 2: 만료된 초대 재발송 +``` +1. A법인 관리자 로그인 +2. 만료된 초대 존재 (만료일: 2025-11-20) +3. user@example.com에게 다시 초대 시도 +4. 성공: 기존 초대 CANCELED, 새 초대 PENDING +5. 새 토큰으로 이메일 발송 ✅ +``` + +#### 테스트 3: 다른 법인 중복 초대 차단 +``` +1. A법인 관리자가 user@example.com에게 초대 (PENDING) +2. B법인 관리자 로그인 +3. user@example.com에게 초대 시도 +4. 에러: "해당 사용자는 다른 법인에서 초대 중입니다." +5. B법인 초대 목록에 표시 안 됨 ✅ +``` + +#### 테스트 4: 초대 수락 후 중복 차단 +``` +1. user@example.com이 A법인 초대 수락 (ROLE_CORP_USER) +2. B법인 관리자가 user@example.com에게 초대 시도 +3. 에러: "기관에 등록된 이용자 입니다." (기존 로직) +4. 초대 생성 안 됨 ✅ +``` + +--- + +### 구현 우선순위 + +| 시나리오 | 우선순위 | 이유 | +|----------|---------|------| +| 같은 법인 중복 초대 차단 | 🔴 **필수** | 데이터 정합성 문제, 사용자 혼란 | +| 만료된 초대 재생성 | 🟡 **권장** | UX 개선 (수동 취소 불필요) | +| 다른 법인 중복 초대 차단 | 🟡 **권장** | 사용자 혼란 방지 | + +--- + +## 📌 이메일 검증 구현 ✅ 완료 + +### 구현 내역 + +#### 1. 프론트엔드 (userList.html) +- ✅ `` 적용 +- ✅ HTML5 pattern 속성 추가: `[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}` +- ✅ JavaScript 정규식 검증 추가 + +#### 2. 백엔드 (UserManRestController.java) +- ✅ 이메일 null/empty 체크 +- ✅ 정규식 검증: `^[a-zA-Z0-9._%+\\-]+@[a-zA-Z0-9.\\-]+\\.[a-zA-Z]{2,}$` +- ✅ 검증 실패 시 400 에러 반환 + +#### 3. Facade (UserManFacade.java) +- ✅ 이메일 null/empty 체크 +- ✅ 정규식 검증 +- ✅ `IllegalArgumentException` 발생 + +### 적용된 파일 +- `src/main/resources/templates/views/apps/users/userList.html` +- `src/main/java/com/eactive/apim/portal/apps/user/controller/UserManRestController.java` +- `src/main/java/com/eactive/apim/portal/apps/user/facade/UserManFacade.java` + +--- + +## 📌 후속 작업 (선택사항) + +1. ~~**Brute Force 방지**: 초대코드 입력 실패 횟수 제한~~ ✅ 상세 구현 계획 추가 (세션 기반) +2. ~~**이메일 검증**: 이메일 형식 검증~~ ✅ 구현 완료 +3. ~~**중복 초대 방지**: PENDING 초대 중복 체크~~ ✅ 상세 구현 계획 추가 +4. ~~**초대 만료일 동적 설정**: PTL_PROPERTY 기반 만료일 설정~~ ✅ 상세 구현 계획 추가 +5. **관리자 알림**: 초대코드 입력 시도 로그 기록 +6. **통계**: 초대코드 직접 입력 vs 이메일 링크 사용 비율 추적 +7. **QR 코드**: 초대코드를 QR 코드로 제공 (모바일 스캔) +8. **초대 재발송**: 만료 전 초대 재발송 기능 (UI 버튼) +9. **만료일 연장**: 주 단위(w), 월 단위(M), 년 단위(y) 지원 + +--- + +**문서 버전**: 1.4 +**최종 수정**: 2025-11-24 +**작성자**: Claude Code +**변경 이력**: +- v1.4 (2025-11-24): 초대 만료일 동적 설정 (PTL_PROPERTY 기반, 시간 표현 파싱 유틸리티) +- v1.3 (2025-11-24): 중복 초대 방지 기능 추가 (4가지 시나리오 및 구현 방법) +- v1.2 (2025-11-24): 마이페이지 초대 알림 기능 추가 (재방문 사용자 대응), 작업 규모 재산정 (6→8시간) +- v1.1 (2025-11-24): Brute Force 방지 세션 기반 구현 방법 추가, 이메일 검증 구현 완료 표시 +- v1.0 (2025-11-24): 초기 작성 diff --git a/kjb-docs/mermaid/invite-flow-integrated.mermaid b/kjb-docs/mermaid/invite-flow-integrated.mermaid new file mode 100644 index 0000000..3c36d86 --- /dev/null +++ b/kjb-docs/mermaid/invite-flow-integrated.mermaid @@ -0,0 +1,105 @@ +flowchart TD + Start([법인 관리자가 사용자 초대]) --> GenerateToken[8글자 토큰 생성
    예: K7MNPQ2R
    UserInvitation 저장
    상태: PENDING] + + GenerateToken --> SendEmail[이메일 발송
    - 링크 포함
    - 8글자 토큰 표시] + + SendEmail --> UserType{사용자 유형} + + %% ======================================== + %% 신규 회원 플로우 + %% ======================================== + UserType -->|신규 회원
    미가입자| NewUserPath{초대 확인 방법} + + NewUserPath -->|이메일 링크| EmailLink1[이메일 링크 클릭
    /signup/portalUser?invitation=CODE] + NewUserPath -->|일반 회원가입| DirectSignup[일반 회원가입 진행
    ROLE_USER로 생성] + + EmailLink1 --> SignupPage1[회원가입 페이지
    - 이메일 자동 입력됨
    - 법인명 표시] + SignupPage1 --> RegisterForm[가입 정보 입력
    초대 수락] + RegisterForm --> RegisterComplete[회원가입 완료
    ROLE_CORP_USER로 생성
    상태: COMPLETED] + RegisterComplete --> NewUserEnd([가입 완료 안내]) + + DirectSignup --> Login4[로그인] + Login4 --> AfterLogin4[로그인 성공] + AfterLogin4 --> CheckInvitation + + %% ======================================== + %% 기존 회원 플로우 + %% ======================================== + UserType -->|기존 회원
    ROLE_USER| ExistingUserPath{초대 확인 방법} + + ExistingUserPath -->|이메일 링크
    비로그인 상태| EmailLink2[이메일 링크 클릭
    /signup/decision?invitation=CODE] + ExistingUserPath -->|이메일 링크
    로그인 상태| EmailLinkLoggedIn[이메일 링크 클릭
    /signup/decision?invitation=CODE] + ExistingUserPath -->|직접 로그인| DirectLogin[로그인 페이지에서 로그인] + + %% 비로그인 상태에서 이메일 링크 클릭 + EmailLink2 --> DecisionPagePublic[초대 수락 페이지
    비로그인 상태] + DecisionPagePublic --> RedirectLogin2[로그인 유도] + RedirectLogin2 --> Login2[로그인] + Login2 --> AfterLogin2[로그인 성공] + + %% 로그인 상태에서 이메일 링크 클릭 → 바로 수락 페이지 + EmailLinkLoggedIn --> DecisionPageDirect[초대 수락 페이지
    /signup/decision_process
    초대 수락] + DecisionPageDirect --> UserDecision + + %% 직접 로그인 + DirectLogin --> Login3[로그인] + Login3 --> AfterLogin3[로그인 성공] + + %% ======================================== + %% 로그인 후 초대 확인 (PortalAuthenticationSuccessHandler) + %% ======================================== + AfterLogin2 --> CheckInvitation[PortalAuthenticationSuccessHandler
    PENDING 초대 확인
    findFirstByInvitationEmailAndStatus] + AfterLogin3 --> CheckInvitation + + CheckInvitation --> HasInvitation{PENDING 초대 존재?} + + HasInvitation -->|없음| NormalHome[메인 페이지 이동] + HasInvitation -->|있음| SaveToSession[세션에 초대 정보 저장
    pendingInvitation=true
    pendingInvitationToken
    pendingInvitationOrgName] + + SaveToSession --> RedirectHome[메인 페이지로 이동] + + %% ======================================== + %% 메인 페이지 팝업 (index.html) + %% ======================================== + RedirectHome --> ShowPopup[팝업 표시
    OOO에서 초대가 도착했습니다
    초대를 확인하시겠습니까?] + + ShowPopup --> PopupChoice{사용자 선택} + + PopupChoice -->|확인| GoDecisionPage[초대 수락 페이지 이동
    /signup/decision?invitation=TOKEN] + PopupChoice -->|취소| StayHome[메인 페이지 유지
    다음 로그인 시 팝업 재표시] + + StayHome --> NextLogin[다음 로그인] + NextLogin --> CheckInvitation + + GoDecisionPage --> DecisionPage[초대 수락 페이지
    /signup/decision_process
    초대 수락] + + DecisionPage --> UserDecision{사용자 선택} + + UserDecision -->|수락| AcceptProcess[초대 수락 처리
    1. PortalOrg 변경
    2. ROLE_CORP_USER 전환
    3. 약관 재동의
    상태: COMPLETED] + UserDecision -->|거절| RejectProcess[초대 거절 처리
    상태: REJECTED] + + AcceptProcess --> LogoutPrompt[로그아웃 후 재로그인 안내] + LogoutPrompt --> ExistingUserEnd([초대 수락 완료]) + + RejectProcess --> RejectEnd([초대 거절 완료
    다음 로그인 시 팝업 안 뜸]) + + %% ======================================== + %% 스타일링 + %% ======================================== + style Start fill:#e1f5e1 + style NewUserEnd fill:#c8e6c9 + style ExistingUserEnd fill:#c8e6c9 + style RejectEnd fill:#fff9c4 + style NormalHome fill:#e3f2fd + + style GenerateToken fill:#bbdefb + style AcceptProcess fill:#c5cae9 + style RejectProcess fill:#ffecb3 + + style CheckInvitation fill:#ffe0b2 + style SaveToSession fill:#ffe0b2 + style ShowPopup fill:#f8bbd0 + style PopupChoice fill:#f8bbd0 + + style StayHome fill:#e1bee7 + style NextLogin fill:#e1bee7 diff --git a/kjb-docs/mermaid/invite-flow.mermaid b/kjb-docs/mermaid/invite-flow.mermaid new file mode 100644 index 0000000..618e4b6 --- /dev/null +++ b/kjb-docs/mermaid/invite-flow.mermaid @@ -0,0 +1,44 @@ +flowchart TD + Start([법인 관리자가 사용자 초대 시작]) --> CheckUser{가입된 사용자인가?} + + CheckUser -->|아니오| Error1[에러: 가입된 사용자만 초대 가능] + CheckUser -->|예| CheckRole{이미 법인 사용자인가?} + + CheckRole -->|예| Error2[에러: 이미 기관에 등록된 사용자] + CheckRole -->|아니오 ROLE_USER| CreateInvitation[8글자 토큰 생성
    UserInvitation 저장
    상태: PENDING] + + CreateInvitation --> SendEmail[이메일 발송
    UserInvitationEvent] + + SendEmail --> UserReceive{사용자가 토큰 받음} + + UserReceive -->|폐쇄망| ManualInput[8글자 토큰 직접 입력
    예: K7MNPQ2R] + UserReceive -->|이메일 링크| EmailLink[Base64 인코딩된
    링크 클릭] + + ManualInput --> DecodeToken[토큰 디코딩
    decodeInvitationToken] + EmailLink --> DecodeToken + + DecodeToken --> ValidateToken{토큰 유효성 검증} + + ValidateToken -->|무효| Error3[에러: 유효하지 않은 초대] + ValidateToken -->|유효 & PENDING| ShowDecision[약관 동의 페이지
    /signup/decision] + + ShowDecision --> UserDecision{사용자 선택} + + UserDecision -->|수락| ProcessAccept[processInvitation: accept] + UserDecision -->|거절| ProcessReject[processInvitation: reject] + + ProcessAccept --> UpdateUser[1. PortalOrg 변경
    2. ROLE_CORP_USER로 전환
    3. 약관 재동의 PRIVACY_COLLECT_ORG] + UpdateUser --> CompleteInvitation[Invitation 상태:
    COMPLETED + completeDate] + CompleteInvitation --> Success([초대 수락 완료
    로그아웃 후 재로그인]) + + ProcessReject --> RejectInvitation[Invitation 상태:
    REJECTED + completeDate] + RejectInvitation --> End([초대 거절 완료]) + + style Start fill:#e1f5e1 + style Success fill:#c8e6c9 + style End fill:#fff9c4 + style Error1 fill:#ffcdd2 + style Error2 fill:#ffcdd2 + style Error3 fill:#ffcdd2 + style CreateInvitation fill:#bbdefb + style UpdateUser fill:#c5cae9 diff --git a/kjb-docs/mermaid/invite-sequence.mermaid b/kjb-docs/mermaid/invite-sequence.mermaid new file mode 100644 index 0000000..5204e6e --- /dev/null +++ b/kjb-docs/mermaid/invite-sequence.mermaid @@ -0,0 +1,122 @@ +sequenceDiagram + actor 법인관리자 + participant Portal as Portal UI + participant UserManFacade + participant UserInvitationRepo as UserInvitation Repository + participant MessageService as Message Handler + participant Email as Email System + actor 초대받은사용자 + participant AuthHandler as PortalAuthenticationSuccessHandler + participant IndexController + participant MainPage as 메인 페이지 (index.html) + participant UserRegisterController + participant UserRegisterFacade + participant PortalUserRepo as PortalUser Repository + participant PortalOrgRepo as PortalOrg Repository + + %% ======================================== + %% 1. 초대 발송 + %% ======================================== + 법인관리자->>Portal: 사용자 이메일 입력
    초대 요청 + Portal->>UserManFacade: sendInvitation(sender, emailAddr) + + UserManFacade->>PortalUserRepo: findByEmailAddr(emailAddr) + alt 사용자가 없거나 이미 법인 사용자 + UserManFacade-->>Portal: 에러 반환 + Portal-->>법인관리자: 초대 불가 알림 + else 일반 사용자 ROLE_USER 또는 비회원 + UserManFacade->>UserManFacade: generateEightCharToken()
    예: K7MNPQ2R + UserManFacade->>UserInvitationRepo: save(UserInvitation)
    status: PENDING
    expiresOn: 7일 후 + UserManFacade->>MessageService: publishEvent(UserInvitationEvent) + MessageService->>Email: 초대 이메일 발송
    (법인명, 관리자명, 토큰) + UserManFacade-->>Portal: 초대 완료 + Portal-->>법인관리자: 초대 성공 알림 + end + + Email->>초대받은사용자: 초대 이메일 수신 + + %% ======================================== + %% 2-A. 로그인 상태에서 이메일 링크 클릭 (바로 수락 페이지) + %% ======================================== + alt 로그인 상태에서 이메일 링크 클릭 + 초대받은사용자->>Portal: 이메일 링크 클릭
    /signup/decision?invitation=TOKEN + Portal->>UserRegisterController: showDecisionPage(invitationToken) + UserRegisterController->>UserInvitationRepo: findByToken(decodedToken) + UserRegisterController-->>Portal: 약관 동의 페이지 + Portal-->>초대받은사용자: 초대 수락/거절 화면
    (바로 표시, 팝업 없음) + Note over 초대받은사용자: 6. 초대 수락/거절 처리로 이동 + end + + %% ======================================== + %% 2-B. 로그인 (기존 회원 또는 신규 가입 후) + %% ======================================== + alt 기존 회원 (비로그인 상태) + 초대받은사용자->>Portal: 로그인 + else 비회원이 일반 회원가입 후 + 초대받은사용자->>Portal: 회원가입 (ROLE_USER) + 초대받은사용자->>Portal: 로그인 + end + + %% ======================================== + %% 3. 로그인 성공 처리 (AuthenticationSuccessHandler) + %% ======================================== + Portal->>AuthHandler: onAuthenticationSuccess() + AuthHandler->>AuthHandler: 이메일 인증, 휴면계정,
    비밀번호 만료 체크 + + AuthHandler->>UserInvitationRepo: findFirstByInvitationEmailAndStatus
    (loginId, PENDING) + + alt PENDING 초대 없음 + AuthHandler->>Portal: redirect to / + Portal-->>초대받은사용자: 메인 페이지 (팝업 없음) + else PENDING 초대 있음 & 만료 안됨 + AuthHandler->>PortalOrgRepo: findById(invitation.orgId) + PortalOrgRepo-->>AuthHandler: PortalOrg (기관명) + AuthHandler->>AuthHandler: session.setAttribute
    ("pendingInvitation", true)
    ("pendingInvitationToken", token)
    ("pendingInvitationOrgName", orgName) + AuthHandler->>Portal: redirect to / + end + + %% ======================================== + %% 4. 메인 페이지 팝업 표시 + %% ======================================== + Portal->>IndexController: GET / + IndexController->>MainPage: render index.html + + MainPage->>MainPage: 세션에서 pendingInvitation 확인 + + alt pendingInvitation == true + MainPage-->>초대받은사용자: 팝업 표시
    "[기관명]에서 초대가 도착했습니다.
    초대를 확인하시겠습니까?" + end + + %% ======================================== + %% 5. 사용자 팝업 선택 + %% ======================================== + alt 팝업에서 "확인" 클릭 + 초대받은사용자->>Portal: /signup/decision?invitation=TOKEN + Portal->>UserRegisterController: showDecisionPage(invitationToken) + UserRegisterController->>UserInvitationRepo: findByToken(decodedToken) + UserRegisterController-->>Portal: 약관 동의 페이지 + Portal-->>초대받은사용자: 초대 수락/거절 화면 + else 팝업에서 "취소" 클릭 + Note over 초대받은사용자,MainPage: 메인 페이지 유지
    다음 로그인 시 팝업 재표시 + end + + %% ======================================== + %% 6. 초대 수락/거절 처리 + %% ======================================== + 초대받은사용자->>Portal: 수락 or 거절 선택 + Portal->>UserRegisterController: processInvitation(action) + UserRegisterController->>UserRegisterFacade: processInvitation(action, invitation) + + alt 수락 + UserRegisterFacade->>PortalUserRepo: findByEmailAddr() + UserRegisterFacade->>PortalUserRepo: updateUserToCorpUser()
    (PortalOrg 변경
    ROLE_CORP_USER 전환) + UserRegisterFacade->>UserInvitationRepo: save(invitation)
    status: COMPLETED
    completeDate: now() + UserRegisterFacade-->>Portal: 수락 성공 + Portal-->>초대받은사용자: 성공 메시지
    로그아웃 후 재로그인 안내 + Note over 초대받은사용자: 다음 로그인 시
    PENDING 초대 없음 → 팝업 안 뜸 + else 거절 + UserRegisterFacade->>UserInvitationRepo: save(invitation)
    status: REJECTED
    completeDate: now() + UserRegisterFacade-->>Portal: 거절 처리 완료 + Portal-->>초대받은사용자: 거절 완료 메시지 + Note over 초대받은사용자: 다음 로그인 시
    REJECTED 상태 → 팝업 안 뜸 + end diff --git a/kjb-docs/mermaid/invite-state.mermaid b/kjb-docs/mermaid/invite-state.mermaid new file mode 100644 index 0000000..e9a551c --- /dev/null +++ b/kjb-docs/mermaid/invite-state.mermaid @@ -0,0 +1,44 @@ +stateDiagram-v2 + [*] --> 초대생성: 법인관리자가 초대 시작 + + 초대생성 --> PENDING: 토큰 생성 및 저장 + + PENDING --> 이메일발송: 초대 이메일 발송 + + state 로그인후팝업 { + 이메일발송 --> 로그인: 사용자 로그인 + 로그인 --> 초대확인: AuthSuccessHandler + 초대확인 --> 세션저장: 세션에 초대 정보 저장 + 세션저장 --> 팝업표시: 메인 페이지 팝업 + 팝업표시 --> 팝업확인: 확인 클릭 + 팝업표시 --> 팝업취소: 취소 클릭 + 팝업취소 --> 로그인: 다음 로그인 시 재표시 + } + + 팝업확인 --> 초대수락: 수락 페이지 이동 + + 초대수락 --> COMPLETED: 수락 + 초대수락 --> REJECTED: 거절 + + COMPLETED --> [*]: 초대 완료 + REJECTED --> [*]: 초대 거절 + PENDING --> CANCELED: 관리자 취소 + PENDING --> EXPIRED: 만료일 경과 + CANCELED --> [*]: 초대 취소 + EXPIRED --> [*]: 초대 만료 + + note right of PENDING + PENDING 상태일 때만 + 로그인 시 팝업 표시 + end note + + note right of REJECTED + REJECTED 상태가 되면 + 더 이상 팝업 표시 안 함 + end note + + note right of COMPLETED + COMPLETED 상태가 되면 + 더 이상 팝업 표시 안 함 + ROLE_CORP_USER로 전환됨 + end note diff --git a/kjb-docs/tmp/intro-1.png b/kjb-docs/tmp/intro-1.png new file mode 100644 index 0000000..57f9f0f Binary files /dev/null and b/kjb-docs/tmp/intro-1.png differ diff --git a/kjb-docs/tmp/intro-all.png b/kjb-docs/tmp/intro-all.png new file mode 100644 index 0000000..6072c5b Binary files /dev/null and b/kjb-docs/tmp/intro-all.png differ diff --git a/kjb-docs/tmp/service-intro-1.html b/kjb-docs/tmp/service-intro-1.html new file mode 100644 index 0000000..0706c02 --- /dev/null +++ b/kjb-docs/tmp/service-intro-1.html @@ -0,0 +1,68 @@ + + + + + + + + +
    +

    광주은행 API 포탈

    +

     

    +
    +

    광주은행이 제공하는 오픈API를 활용하여
    핀테크 기업들이 새로운 금융서비스를 편리하게 개발할 수 있는 환경입니다.

    +

    오픈뱅크 플랫폼이 제공하는 다양한 API를 활용해 핀테크 업체는 아이디어를 손쉽게 실현시킬 수 있습니다.
    특히 JB금융의 과감한 투자와 제휴를 통해 핀테크 업체는 맞춤형 API를 제공받고 새로운 금융서비스를 신속하게 개발할 수 있습니다.

    +
    +
     
    +
     
    +
    + + \ No newline at end of file diff --git a/kjb-docs/tmp/service-intro-2.html b/kjb-docs/tmp/service-intro-2.html new file mode 100644 index 0000000..854631e --- /dev/null +++ b/kjb-docs/tmp/service-intro-2.html @@ -0,0 +1,84 @@ + + + + + + + + +
    +

    회원 가입 안내

    +

    광주은행 오픈 API 개발자 포탈에 방문해 주셔서 감사합니다.
    광주은행 오픈 API 사용을 위해서는 다음과 같은 이용절차로 진행하여야 합니다.

    +

    +

    회원은 제휴사, 스타트업, 개인(개발자)분으로 구분되며, 개발자 포털에서 온라인으로 회원가입 신청을 합니다.

    +

    ※ 포털 사용은 운영 담당자의 승인 후 가능합니다.

    +
    + + + + + + + + + + + + + + + + +

    ※ 제휴사와 스타트업의 관리자 회원 승인 이후, 포털을 실제 사용할 직원(개발자)을 추가 등록해 주시기 바랍니다.

    +
    +
    + + \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 28056c5..9c14fc4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,834 @@ { - "name": "eactive-portal", + "name": "eapim-portal", + "version": "1.0.0", "lockfileVersion": 2, "requires": true, - "packages": {} + "packages": { + "": { + "name": "eapim-portal", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "playwright": "^1.57.0" + }, + "devDependencies": { + "sass": "^1.69.5" + } + }, + "node_modules/@parcel/watcher": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz", + "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^1.0.3", + "is-glob": "^4.0.3", + "micromatch": "^4.0.5", + "node-addon-api": "^7.0.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.5.1", + "@parcel/watcher-darwin-arm64": "2.5.1", + "@parcel/watcher-darwin-x64": "2.5.1", + "@parcel/watcher-freebsd-x64": "2.5.1", + "@parcel/watcher-linux-arm-glibc": "2.5.1", + "@parcel/watcher-linux-arm-musl": "2.5.1", + "@parcel/watcher-linux-arm64-glibc": "2.5.1", + "@parcel/watcher-linux-arm64-musl": "2.5.1", + "@parcel/watcher-linux-x64-glibc": "2.5.1", + "@parcel/watcher-linux-x64-musl": "2.5.1", + "@parcel/watcher-win32-arm64": "2.5.1", + "@parcel/watcher-win32-ia32": "2.5.1", + "@parcel/watcher-win32-x64": "2.5.1" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz", + "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz", + "integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz", + "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz", + "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz", + "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz", + "integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz", + "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz", + "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz", + "integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz", + "integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz", + "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz", + "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz", + "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/immutable": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.4.tgz", + "integrity": "sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/playwright": { + "version": "1.57.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.57.0.tgz", + "integrity": "sha512-ilYQj1s8sr2ppEJ2YVadYBN0Mb3mdo9J0wQ+UuDhzYqURwSoW4n1Xs5vs7ORwgDGmyEh33tRMeS8KhdkMoLXQw==", + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.57.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.57.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.57.0.tgz", + "integrity": "sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==", + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/sass": { + "version": "1.97.1", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.97.1.tgz", + "integrity": "sha512-uf6HoO8fy6ClsrShvMgaKUn14f2EHQLQRtpsZZLeU/Mv0Q1K5P0+x2uvH6Cub39TVVbWNSrraUhDAoFph6vh0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^4.0.0", + "immutable": "^5.0.2", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + }, + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + } + }, + "dependencies": { + "@parcel/watcher": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz", + "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==", + "dev": true, + "optional": true, + "requires": { + "@parcel/watcher-android-arm64": "2.5.1", + "@parcel/watcher-darwin-arm64": "2.5.1", + "@parcel/watcher-darwin-x64": "2.5.1", + "@parcel/watcher-freebsd-x64": "2.5.1", + "@parcel/watcher-linux-arm-glibc": "2.5.1", + "@parcel/watcher-linux-arm-musl": "2.5.1", + "@parcel/watcher-linux-arm64-glibc": "2.5.1", + "@parcel/watcher-linux-arm64-musl": "2.5.1", + "@parcel/watcher-linux-x64-glibc": "2.5.1", + "@parcel/watcher-linux-x64-musl": "2.5.1", + "@parcel/watcher-win32-arm64": "2.5.1", + "@parcel/watcher-win32-ia32": "2.5.1", + "@parcel/watcher-win32-x64": "2.5.1", + "detect-libc": "^1.0.3", + "is-glob": "^4.0.3", + "micromatch": "^4.0.5", + "node-addon-api": "^7.0.0" + } + }, + "@parcel/watcher-android-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz", + "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==", + "dev": true, + "optional": true + }, + "@parcel/watcher-darwin-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz", + "integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==", + "dev": true, + "optional": true + }, + "@parcel/watcher-darwin-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz", + "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==", + "dev": true, + "optional": true + }, + "@parcel/watcher-freebsd-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz", + "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==", + "dev": true, + "optional": true + }, + "@parcel/watcher-linux-arm-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz", + "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==", + "dev": true, + "optional": true + }, + "@parcel/watcher-linux-arm-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz", + "integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==", + "dev": true, + "optional": true + }, + "@parcel/watcher-linux-arm64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz", + "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==", + "dev": true, + "optional": true + }, + "@parcel/watcher-linux-arm64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz", + "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==", + "dev": true, + "optional": true + }, + "@parcel/watcher-linux-x64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz", + "integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==", + "dev": true, + "optional": true + }, + "@parcel/watcher-linux-x64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz", + "integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==", + "dev": true, + "optional": true + }, + "@parcel/watcher-win32-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz", + "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==", + "dev": true, + "optional": true + }, + "@parcel/watcher-win32-ia32": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz", + "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==", + "dev": true, + "optional": true + }, + "@parcel/watcher-win32-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz", + "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==", + "dev": true, + "optional": true + }, + "braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "optional": true, + "requires": { + "fill-range": "^7.1.1" + } + }, + "chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "requires": { + "readdirp": "^4.0.1" + } + }, + "detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "dev": true, + "optional": true + }, + "fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "optional": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "optional": true + }, + "immutable": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.4.tgz", + "integrity": "sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==", + "dev": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "optional": true + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "optional": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "optional": true + }, + "micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "optional": true, + "requires": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + } + }, + "node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "dev": true, + "optional": true + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "optional": true + }, + "playwright": { + "version": "1.57.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.57.0.tgz", + "integrity": "sha512-ilYQj1s8sr2ppEJ2YVadYBN0Mb3mdo9J0wQ+UuDhzYqURwSoW4n1Xs5vs7ORwgDGmyEh33tRMeS8KhdkMoLXQw==", + "requires": { + "fsevents": "2.3.2", + "playwright-core": "1.57.0" + } + }, + "playwright-core": { + "version": "1.57.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.57.0.tgz", + "integrity": "sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==" + }, + "readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true + }, + "sass": { + "version": "1.97.1", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.97.1.tgz", + "integrity": "sha512-uf6HoO8fy6ClsrShvMgaKUn14f2EHQLQRtpsZZLeU/Mv0Q1K5P0+x2uvH6Cub39TVVbWNSrraUhDAoFph6vh0A==", + "dev": true, + "requires": { + "@parcel/watcher": "^2.4.1", + "chokidar": "^4.0.0", + "immutable": "^5.0.2", + "source-map-js": ">=0.6.2 <2.0.0" + } + }, + "source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "optional": true, + "requires": { + "is-number": "^7.0.0" + } + } + } } diff --git a/package.json b/package.json new file mode 100644 index 0000000..6fc2c3c --- /dev/null +++ b/package.json @@ -0,0 +1,20 @@ +{ + "name": "eapim-portal", + "version": "1.0.0", + "description": "SASS build system for EAPIM Portal", + "scripts": { + "sass:build": "sass src/main/resources/static/sass/main.scss:src/main/resources/static/css/main.css --style=expanded", + "sass:build:minified": "sass src/main/resources/static/sass/main.scss:src/main/resources/static/css/main.min.css --style=compressed", + "sass:watch": "sass --watch src/main/resources/static/sass/main.scss:src/main/resources/static/css/main.css --style=expanded", + "build": "npm run sass:build && npm run sass:build:minified", + "dev": "npm run sass:watch" + }, + "devDependencies": { + "sass": "^1.69.5" + }, + "author": "", + "license": "ISC", + "dependencies": { + "playwright": "^1.57.0" + } +} diff --git a/sass-build.sh b/sass-build.sh new file mode 100644 index 0000000..e386def --- /dev/null +++ b/sass-build.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +sass src/main/resources/static/sass/main.scss src/main/resources/static/css/main.css \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index 43bae24..6406d23 100644 --- a/settings.gradle +++ b/settings.gradle @@ -7,5 +7,5 @@ project (':elink-online-core-jpa').projectDir = new File(settingsDir, "../eapim- project (':elink-portal-common').projectDir = new File(settingsDir, "../elink-portal-common") -include 'kjb-safedb' -project (':kjb-safedb').projectDir = new File(settingsDir, "../kjb-safedb") \ No newline at end of file +//include 'kjb-safedb' +//project (':kjb-safedb').projectDir = new File(settingsDir, "../kjb-safedb") \ No newline at end of file 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 6b7f9a9..c24f459 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,7 @@ import org.springframework.web.bind.annotation.RequestParam; @Controller @RequestMapping("/agreements") public class AgreementsController { - public static final String TERMS_AGREEMENTS = "fragment/kbank/terms_agreements"; + public static final String TERMS_AGREEMENTS = "fragment/kjbank/terms_agreements"; private final AgreementsFacade agreementsFacade; diff --git a/src/main/java/com/eactive/apim/portal/apps/agreements/dto/AgreementsDTO.java b/src/main/java/com/eactive/apim/portal/apps/agreements/dto/AgreementsDTO.java index a782879..bfdf0e0 100644 --- a/src/main/java/com/eactive/apim/portal/apps/agreements/dto/AgreementsDTO.java +++ b/src/main/java/com/eactive/apim/portal/apps/agreements/dto/AgreementsDTO.java @@ -35,6 +35,12 @@ public class AgreementsDTO { private LocalDateTime updatedDate; + /* 첨부파일 ID */ + private String attachedFileId; + + /* 첨부파일명 (확장자 포함) */ + private String attachedFileName; + // 기본 생성자 추가 public AgreementsDTO() { this.contents = ""; // null 방지를 위한 기본값 설정 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 65d1c18..82cac26 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 @@ -7,30 +7,48 @@ import com.eactive.apim.portal.apps.agreements.dto.AgreementsDTO; import com.eactive.apim.portal.apps.agreements.mapper.AgreementsMapper; import com.eactive.apim.portal.apps.user.service.PortalUserPrivacyAgreementService; import com.eactive.apim.portal.common.exception.NotFoundException; +import com.eactive.apim.portal.file.entity.FileDetail; +import com.eactive.apim.portal.file.service.FileService; import java.time.LocalDateTime; import java.util.Collections; import java.util.List; import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import java.util.stream.Collectors; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringEscapeUtils; +import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +@Slf4j @Service public class AgreementsFacadeImpl implements AgreementsFacade { + // 플레이스홀더 패턴: {{IMG:fileId:fileSn}} 또는 {{IMG:fileId:fileSn|속성}} + private static final Pattern PLACEHOLDER_PATTERN = + Pattern.compile("\\{\\{IMG:([a-f0-9\\-]+):(\\d+)(\\|[^}]+)?\\}\\}"); + private final AgreementsService agreementsService; private final AgreementsMapper agreementsMapper; private final PortalUserPrivacyAgreementService userAgreementService; + private final FileService fileService; + + @Value("${editor.image.base-url:/file/view}") + private String editorImageBaseUrl; @Autowired public AgreementsFacadeImpl(AgreementsService agreementsService, AgreementsMapper agreementsMapper, - PortalUserPrivacyAgreementService userAgreementService) { + PortalUserPrivacyAgreementService userAgreementService, + FileService fileService) { this.agreementsService = agreementsService; this.agreementsMapper = agreementsMapper; this.userAgreementService = userAgreementService; + this.fileService = fileService; } @Override @@ -71,15 +89,79 @@ public class AgreementsFacadeImpl implements AgreementsFacade { } } - // HTML 언이스케이프 처리 + // HTML 언이스케이프 및 이미지 플레이스홀더 변환 처리 private AgreementsDTO convertToDTO(Agreements agreement) { AgreementsDTO dto = agreementsMapper.map(agreement); if (dto.getContents() != null) { - dto.setContents(StringEscapeUtils.unescapeHtml4(dto.getContents())); + String contents = StringEscapeUtils.unescapeHtml4(dto.getContents()); + // 이미지 플레이스홀더를 HTML img 태그로 변환 + contents = convertPlaceholdersToHtml(contents); + dto.setContents(contents); + } + // 첨부파일 정보 설정 + if (StringUtils.isNotBlank(agreement.getAttachedFileId())) { + dto.setAttachedFileId(agreement.getAttachedFileId()); + try { + FileDetail fileDetail = fileService.getFileDetailByIds(agreement.getAttachedFileId(), 1); + String fileName = fileDetail.getOriginalFileName(); + if (StringUtils.isNotBlank(fileDetail.getFileExtension())) { + fileName += "." + fileDetail.getFileExtension(); + } + dto.setAttachedFileName(fileName); + } catch (Exception e) { + log.warn("첨부파일 정보 조회 실패: fileId={}", agreement.getAttachedFileId(), e); + } } return dto; } + /** + * 플레이스홀더를 실제 이미지 태그로 변환 + * {{IMG:fileId:fileSn}} 또는 {{IMG:fileId:fileSn|width=300|height=200|style=...}} + * → + */ + private String convertPlaceholdersToHtml(String contents) { + if (contents == null) { + return null; + } + + Matcher matcher = PLACEHOLDER_PATTERN.matcher(contents); + StringBuffer sb = new StringBuffer(); + + while (matcher.find()) { + String fileId = matcher.group(1); + String fileSn = matcher.group(2); + String attrs = matcher.group(3); + + StringBuilder img = new StringBuilder(); + img.append(""); + matcher.appendReplacement(sb, Matcher.quoteReplacement(img.toString())); + } + matcher.appendTail(sb); + + return sb.toString(); + } + @Override public AgreementsDTO getAgreement(String agreementsTypeCode) { LocalDateTime date = LocalDateTime.now(); @@ -142,15 +224,7 @@ public class AgreementsFacadeImpl implements AgreementsFacade { return Collections.singletonList(createDefaultAgreement(String.valueOf(agreementTypeCode))); } return agreementsList.stream() - .map(agreement -> { - AgreementsDTO dto = agreementsMapper.map(agreement); - - // HTML 언이스케이프 처리 - if (dto.getContents() != null) { - dto.setContents(StringEscapeUtils.unescapeHtml4(dto.getContents())); - } - return dto; - }) + .map(this::convertToDTO) .collect(Collectors.toList()); } diff --git a/src/main/java/com/eactive/apim/portal/apps/apis/controller/ApiAjaxController.java b/src/main/java/com/eactive/apim/portal/apps/apis/controller/ApiAjaxController.java index c2cec8f..1913452 100644 --- a/src/main/java/com/eactive/apim/portal/apps/apis/controller/ApiAjaxController.java +++ b/src/main/java/com/eactive/apim/portal/apps/apis/controller/ApiAjaxController.java @@ -4,18 +4,11 @@ package com.eactive.apim.portal.apps.apis.controller; 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; -import com.eactive.apim.portal.apps.apiservice.dto.ApiServiceApiDTO; import com.eactive.apim.portal.apps.apiservice.dto.ApiServiceDTO; import com.eactive.apim.portal.apps.apiservice.service.ApiServiceService; -import com.eactive.apim.portal.apps.app.dto.ClientDTO; -import com.eactive.apim.portal.apps.app.service.AppServiceFacade; -import com.eactive.apim.portal.common.user.PortalAuthenticatedUser; -import com.eactive.apim.portal.common.util.SecurityUtil; -import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.springframework.security.access.annotation.Secured; import org.springframework.web.bind.annotation.GetMapping; @@ -28,11 +21,8 @@ import org.springframework.web.bind.annotation.RestController; @RequiredArgsConstructor public class ApiAjaxController { - private final static String STG_SERVER = "STG"; - private final ApiService apiService; private final ApiServiceService apiServiceService; - private final AppServiceFacade appServiceFacade; public Map getMainIconsFromServiceDtos(List apiServices) { Map mainIconsMap = new HashMap<>(); @@ -54,53 +44,15 @@ public class ApiAjaxController { Map mainIconsMap = getMainIconsFromServiceDtos(apiServices); List apiSpecInfoDtos = apiService.findApis(); - for (ApiSpecInfoDto spec : apiSpecInfoDtos) { - ApiServiceDTO serviceDTO = mainIconsMap.get(spec.getApiId()); - if (serviceDTO != null) { - String mainIcon = serviceDTO.getMainIcon(); - String service = serviceDTO.getGroupName(); - spec.setMainIcon(mainIcon); - spec.setService(service); - } - } - return apiSpecInfoDtos; - } - - @GetMapping("/for_request_prod") - @Secured("ROLE_API_KEY_REQUEST") - public List apiRequestProd(@ModelAttribute ApiGroupSearch search) { - - List apiServices = apiServiceService.searchApiGroups(search); - Map mainIconsMap = getMainIconsFromServiceDtos(apiServices); - - PortalAuthenticatedUser user = SecurityUtil.getPortalAuthenticatedUser(); - - List apikeys = appServiceFacade.getApiKeyList(user.getPortalOrg(), STG_SERVER); - - List apis = apikeys.stream() - .flatMap(client -> client.getApiList().stream()) // flatten all apiLists into a single stream of ApiServiceApiDTO - .distinct() // remove duplicates - .collect(Collectors.toList()); // collect to List - - List apiSpecInfoDtos = new ArrayList<>(); - - apis.forEach(api -> { - ApiSpecInfoDto specInfoDto = new ApiSpecInfoDto(); - specInfoDto.setApiId(api.getApiId()); - specInfoDto.setApiName(api.getApiDesc()); - apiSpecInfoDtos.add(specInfoDto); - }); - - for (ApiSpecInfoDto spec : apiSpecInfoDtos) { - ApiServiceDTO serviceDTO = mainIconsMap.get(spec.getApiId()); - if (serviceDTO != null) { - String mainIcon = serviceDTO.getMainIcon(); - String service = serviceDTO.getGroupName(); - spec.setMainIcon(mainIcon); - spec.setService(service); - } - } - return apiSpecInfoDtos; + // Filter APIs that have matching service information + return apiSpecInfoDtos.stream() + .filter(spec -> mainIconsMap.containsKey(spec.getApiId())) + .peek(spec -> { + ApiServiceDTO serviceDTO = mainIconsMap.get(spec.getApiId()); + spec.setMainIcon(serviceDTO.getMainIcon()); + spec.setService(serviceDTO.getGroupName()); + }) + .collect(java.util.stream.Collectors.toList()); } } 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 a00e750..649f1a2 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 @@ -1,17 +1,17 @@ package com.eactive.apim.portal.apps.apis.controller; -import com.eactive.apim.portal.apps.apis.dto.ApiSearchData; import com.eactive.apim.portal.apps.apis.dto.ApiSpecInfoDto; import com.eactive.apim.portal.apps.apis.service.ApiSearchFacade; import com.eactive.apim.portal.apps.apis.service.ApiService; 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 java.util.*; - import com.eactive.apim.portal.common.exception.NotFoundException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; 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 a95204a..473792b 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 @@ -81,7 +81,7 @@ public class ApiTesterFilter implements Filter { String scope = params.getOrDefault("scope", "default"); String token = "{\n" + - " \"access_token\": \"kbank_gw_sample_token\",\n" + + " \"access_token\": \"kjbank_gw_sample_token\",\n" + " \"token_type\": \"bearer\",\n" + " \"expires_in\": 86400,\n" + " \"scope\": \""+scope +"\",\n" + @@ -94,7 +94,7 @@ public class ApiTesterFilter implements Filter { } else { ApiSpecInfoDto apiSpecInfoDto = apiSpecInfoDtoService.selectDetailByURLAndMethod(parseUri(url), httpServletRequest.getMethod()); - if (apiSpecInfoDto.getResponseType().equals("sample")) { + if (apiSpecInfoDto.getResponseType() == null || apiSpecInfoDto.getResponseType().equals("sample")) { response.setContentType("application/json"); response.getWriter().println(apiSpecInfoDto.getSampleResponse()); } else { diff --git a/src/main/java/com/eactive/apim/portal/apps/app/controller/ApiKeyRestController.java b/src/main/java/com/eactive/apim/portal/apps/app/controller/ApiKeyRestController.java deleted file mode 100644 index 73ff405..0000000 --- a/src/main/java/com/eactive/apim/portal/apps/app/controller/ApiKeyRestController.java +++ /dev/null @@ -1,112 +0,0 @@ -package com.eactive.apim.portal.apps.app.controller; - -import com.eactive.apim.portal.apprequest.entity.AppRequestType; -import com.eactive.apim.portal.apps.apis.dto.ApiSearchData; -import com.eactive.apim.portal.apps.apis.service.ApiSearchFacade; -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.dto.ClientSimpleDTO; -import com.eactive.apim.portal.apps.app.mapper.CredentialMapper; -import com.eactive.apim.portal.apps.app.service.AppServiceFacade; -import com.eactive.apim.portal.common.dto.ResponseDTO; -import com.eactive.apim.portal.common.user.PortalAuthenticatedUser; -import com.eactive.apim.portal.common.util.EncryptionUtil; -import com.eactive.apim.portal.common.util.SecurityUtil; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.util.List; -import java.util.stream.Collectors; -import javax.crypto.BadPaddingException; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.NoSuchPaddingException; -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Pageable; -import org.springframework.data.web.PageableDefault; -import org.springframework.security.access.annotation.Secured; -import org.springframework.ui.Model; -import org.springframework.validation.ValidationUtils; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -@RestController -@RequestMapping("/myapikey") -@RequiredArgsConstructor -@Secured("ROLE_API_KEY_REQUEST") -public class ApiKeyRestController { - - private final static String PROD_SERVER = "PROD"; - private final static String STG_SERVER = "STG"; - - private final AppServiceFacade appServiceFacade; - private final CredentialMapper credentialMapper; - private final EncryptionUtil encryptionUtil; - private final ApiSearchFacade apiSearchFacade; - - @PostMapping("/api_key_request") - public ResponseDTO apiRequest(@RequestBody AppRequestDTO appRequest) { - appRequest.setOrg(SecurityUtil.getUserOrg()); - if (appRequest.getType() == null) { - appRequest.setType(AppRequestType.NEW); - } - AppRequestDTO appRequestDTO = appServiceFacade.createAppRequest(appRequest); - appServiceFacade.beginApproval(appRequestDTO.getApproval().getId()); - return new ResponseDTO(200, "SUCCESS", appRequestDTO.getType().getDescription() + " 신청이 완료되었습니다."); - } - - @PostMapping("/api_key_request_prod") - public ResponseDTO requestProdApiKey(@RequestBody AppRequestDTO appRequest) { - appRequest.setOrg(SecurityUtil.getUserOrg()); - - if (appRequest.getClientId() != null && !appRequest.getClientId().isEmpty()) { - try { - appRequest.setClientId(encryptionUtil.decrypt(appRequest.getClientId())); - } catch (NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException e) { - return new ResponseDTO(400, "FAILURE", "API 키 정보가 올바르지 않습니다."); - } - appRequest.setType(AppRequestType.PROD_MODIFY); - } else { - appRequest.setType(AppRequestType.PROD_NEW); - } - - AppRequestDTO appRequestDTO = appServiceFacade.createAppRequest(appRequest); - appServiceFacade.beginApproval(appRequestDTO.getApproval().getId()); - - return new ResponseDTO(200, "SUCCESS", appRequestDTO.getType().getDescription() + " 신청이 완료되었습니다."); - } - - @GetMapping("/dev") - public List apiKeyList() { - PortalAuthenticatedUser user = SecurityUtil.getPortalAuthenticatedUser(); - List apikeys = appServiceFacade.getApiKeyList(user.getPortalOrg(), STG_SERVER); - - ApiSearchData apiSearchData = apiSearchFacade.loadAllApiData(); - for (ClientDTO apikey : apikeys) { - apikey.getApiList().forEach(api -> { - api.setService(apiSearchData.getServicesByApiId().get(api.getApiId()).getGroupName()); - }); - } - - return apikeys.stream().map(credentialMapper::mapToClientSimpleDTO).collect(Collectors.toList()); - } - - @PostMapping("/prod") - public ClientSimpleDTO apiKeyProdList(@RequestBody AppRequestDTO appRequest) { - PortalAuthenticatedUser user = SecurityUtil.getPortalAuthenticatedUser(); - - try { - ClientDTO apiKey = appServiceFacade.getApiKey(user.getPortalOrg().getId(), encryptionUtil.decrypt(appRequest.getClientId()), PROD_SERVER); - ApiSearchData apiSearchData = apiSearchFacade.loadAllApiData(); - apiKey.getApiList().forEach(api -> { - api.setService(apiSearchData.getServicesByApiId().get(api.getApiId()).getGroupName()); - }); - apiKey.setClientid(encryptionUtil.encrypt(apiKey.getClientid())); - return credentialMapper.mapToClientSimpleDTO(apiKey); - } catch (NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException e) { - return new ClientSimpleDTO(); - } - } -} 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 1f15bfc..c01fec2 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,25 +1,25 @@ package com.eactive.apim.portal.apps.app.controller; +import com.eactive.apim.portal.apprequest.entity.AppRequest; 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; +import com.eactive.apim.portal.apps.apiservice.dto.ApiServiceApiDTO; import com.eactive.apim.portal.apps.apiservice.dto.ApiServiceDTO; import com.eactive.apim.portal.apps.apiservice.service.ApiServiceService; +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.common.user.PortalAuthenticatedUser; import com.eactive.apim.portal.common.util.ApiServiceHelper; -import com.eactive.apim.portal.common.util.EncryptionUtil; import com.eactive.apim.portal.common.util.SecurityUtil; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.util.HashMap; +import com.eactive.apim.portal.file.service.FileTypeDetector; +import java.util.ArrayList; import java.util.List; import java.util.Map; -import javax.crypto.BadPaddingException; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.NoSuchPaddingException; +import javax.servlet.http.HttpSession; +import javax.validation.Valid; import lombok.RequiredArgsConstructor; import org.apache.commons.lang3.StringUtils; import org.springframework.data.domain.Page; @@ -29,210 +29,320 @@ import org.springframework.data.web.PageableDefault; 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.validation.FieldError; 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.RequestBody; 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.multipart.MultipartFile; import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.mvc.support.RedirectAttributes; @Controller @RequestMapping("/myapikey") @RequiredArgsConstructor +@SessionAttributes({"apiKeyRegistration", "apiKeyModification"}) public class MyAppController { - private final static String PROD_SERVER = "PROD"; - private final static String STG_SERVER = "STG"; - public static final String API_KEY_LIST = "apps/mypage/apiKeyList"; - public static final String API_KEY_DETAIL = "apps/mypage/apiKeyDetail"; + public static final String APP_REQUEST_DETAIL = "apps/mypage/appRequestDetail"; - public static final String API_KEY_PROD_LIST = "apps/mypage/apiKeyProdList"; - - public static final String API_KEY_PROD_DETAIL = "apps/mypage/apiKeyProdDetail"; + public static final String CREDENTIAL_DETAIL = "apps/mypage/credentialDetail"; public static final String API_KEY_REQUEST_HISTORY = "apps/mypage/apiRequestHistory"; public static final String API_KEY_REQUEST_DETAIL = "apps/mypage/apiRequestDetail"; - public static final String API_KEY_REQUEST_PROD_HISTORY = "apps/mypage/apiRequestProdHistory"; + public static final String API_KEY_REGISTER_STEP1 = "apps/mypage/apiKeyRegisterStep1"; - public static final String API_KEY_REQUEST_PROD_DETAIL = "apps/mypage/apiRequestProdDetail"; + public static final String API_KEY_REGISTER_STEP2 = "apps/mypage/apiKeyRegisterStep2"; + + public static final String API_KEY_REGISTER_STEP3 = "apps/mypage/apiKeyRegisterStep3"; + + public static final String API_KEY_MODIFY_STEP1 = "apps/mypage/apiKeyModifyStep1"; + + public static final String API_KEY_MODIFY_STEP2 = "apps/mypage/apiKeyModifyStep2"; + + public static final String API_KEY_MODIFY_STEP3 = "apps/mypage/apiKeyModifyStep3"; private final AppServiceFacade appServiceFacade; private final ApiServiceService apiServiceService; private final ApiService apiService; - private final EncryptionUtil encryptionUtil; private final ApiServiceHelper apiServiceHelper; + private final FileTypeDetector fileTypeDetector; + private static final long MAX_APP_ICON_BYTES = 2L * 1024 * 1024; // 2MB + + // ========== API Key 조회 및 관리 ========== + + /** + * API Key 목록 페이지를 표시합니다. + * 대기 중인 요청 목록과 승인된 API Key 목록을 함께 보여줍니다. + */ @GetMapping @Secured("ROLE_APP") - public ModelAndView apiKeyList(@PageableDefault Pageable pageable, Model model) { + public ModelAndView apiKeyList(Model model) { PortalAuthenticatedUser user = SecurityUtil.getPortalAuthenticatedUser(); - Page apikeys = appServiceFacade.getApiKeyPageForServer(pageable, user.getPortalOrg(), STG_SERVER); - model.addAttribute("apiKeys", apikeys.getContent()); - model.addAttribute("page", apikeys); + List appRequests = appServiceFacade.getPendingApiKeyList(user.getPortalOrg()); + List apikeys = appServiceFacade.getApikeyList(user.getPortalOrg()); + + model.addAttribute("appRequests", appRequests); + model.addAttribute("apiKeys", apikeys); return new ModelAndView(API_KEY_LIST); } - @GetMapping("/api_key_prod_page") + /** + * AppRequest(대기 중인 요청)의 상세 정보를 표시합니다. + * Step 1과 Step 2에서 입력한 모든 정보를 보여줍니다. + * + * @param id AppRequest ID + */ + @GetMapping("/app_request_detail") @Secured("ROLE_APP") - public ModelAndView apiKeyProdList(@PageableDefault Pageable pageable, Model model) { - PortalAuthenticatedUser user = SecurityUtil.getPortalAuthenticatedUser(); - Page apikeys = appServiceFacade.getApiKeyPageForServer(pageable, user.getPortalOrg(), PROD_SERVER); - model.addAttribute("apiKeys", apikeys.getContent()); - model.addAttribute("page", apikeys); - return new ModelAndView(API_KEY_PROD_LIST); - } - - @GetMapping("/api_key_prod_detail") - @Secured("ROLE_APP") - public ModelAndView apiKeyProdDetail(@RequestParam(value = "id", required = false) String id, Model model) { - - if(id == null) { - return new ModelAndView("redirect:/myapikey/api_key_prod_page"); - } - ClientDTO apiKey = null; - try { - apiKey = appServiceFacade.getApiKey(SecurityUtil.getPortalAuthenticatedUser().getPortalOrg().getId(), encryptionUtil.decrypt(id), PROD_SERVER); - } catch (NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException e) { - return new ModelAndView("/"); - } - - if (apiKey == null) { - return new ModelAndView("/"); - } else { - ApiGroupSearch search = new ApiGroupSearch(); - List apiServices = apiServiceService.searchApiGroups(search); - Map mainIconsMap = apiServiceHelper.getMainIconsFromServiceDtos(apiServices); - - apiKey.getApiList().forEach(api -> { - ApiServiceDTO serviceDTO = mainIconsMap.get(api.getApiId()); - api.setService(serviceDTO.getGroupName()); - }); - - model.addAttribute("apiKey", apiKey); - return new ModelAndView(API_KEY_PROD_DETAIL); - } - } - - @GetMapping("/api_key_detail") - @Secured("ROLE_APP") - public ModelAndView apiKeyDetail(@RequestParam(value = "id", required = false) String id, Model model) { - - if(id == null) { + public ModelAndView appRequestDetail(@RequestParam(value = "id", required = false) String id, Model model) { + if (id == null) { return new ModelAndView("redirect:/myapikey"); } - ClientDTO apiKey = appServiceFacade.getApiKey(SecurityUtil.getPortalAuthenticatedUser().getPortalOrg().getId(), id, STG_SERVER); - if (apiKey == null) { - return new ModelAndView("/"); - } else { - ApiGroupSearch search = new ApiGroupSearch(); - List apiServices = apiServiceService.searchApiGroups(search); - Map mainIconsMap = apiServiceHelper.getMainIconsFromServiceDtos(apiServices); + PortalAuthenticatedUser user = SecurityUtil.getPortalAuthenticatedUser(); + AppRequestDTO appRequest = appServiceFacade.getAppRequestById(id, user.getPortalOrg()); - apiKey.getApiList().forEach(api -> { - ApiServiceDTO serviceDTO = mainIconsMap.get(api.getApiId()); - api.setService(serviceDTO.getGroupName()); - }); - - List prodHistory = appServiceFacade.getProdApiRequestHistory(apiKey.getClientid()); - model.addAttribute("apiKey", apiKey); - model.addAttribute("prodhistory", prodHistory); - return new ModelAndView(API_KEY_DETAIL); + if (appRequest == null) { + return new ModelAndView("redirect:/myapikey"); } + + // API 목록 조회 및 설정 + if (!StringUtils.isEmpty(appRequest.getApiList())) { + String[] apiIds = appRequest.getApiList().split(","); + List apiSpecList = new ArrayList<>(); + + for (String apiId : apiIds) { + ApiSpecInfoDto apiSpec = apiService.selectDetail(apiId.trim()); + if (apiSpec != null) { + apiSpecList.add(apiSpec); + } + } + appRequest.setApiSpecList(apiSpecList); + } + + model.addAttribute("appRequest", appRequest); + return new ModelAndView(APP_REQUEST_DETAIL); } + /** + * Credential(승인된 API Key)의 상세 정보를 표시합니다. + * API Key에 연결된 API 목록과 서비스 정보를 함께 보여줍니다. + * + * @param id Client ID + */ + @GetMapping("/credential_detail") + @Secured("ROLE_APP") + public ModelAndView credentialDetail(@RequestParam(value = "id", required = false) String id, Model model) { + if (id == null) { + return new ModelAndView("redirect:/myapikey"); + } + + PortalAuthenticatedUser user = SecurityUtil.getPortalAuthenticatedUser(); + ClientDTO apiKey = appServiceFacade.getApiKey(user.getPortalOrg().getId(), id); + + if (apiKey == null) { + return new ModelAndView("redirect:/myapikey"); + } + + // API 목록에 서비스 정보 추가 + ApiGroupSearch search = new ApiGroupSearch(); + List apiServices = apiServiceService.searchApiGroups(search); + Map mainIconsMap = apiServiceHelper.getMainIconsFromServiceDtos(apiServices); + + apiKey.getApiList().forEach(api -> { + ApiServiceDTO serviceDTO = mainIconsMap.get(api.getApiId()); + if (serviceDTO != null) { + api.setService(serviceDTO.getGroupName()); + } + }); + + model.addAttribute("apiKey", apiKey); + + return new ModelAndView(CREDENTIAL_DETAIL); + } + + /** + * 특정 API Key의 상세 정보를 표시합니다. (기존 메서드 - 하위 호환성 유지) + * API Key에 연결된 API 목록과 서비스 정보를 함께 보여줍니다. + * + * @param id API Key ID + * @deprecated Use {@link #credentialDetail(String, Model)} instead + */ + @GetMapping("/api_key_detail") + @Secured("ROLE_APP") + @Deprecated + public ModelAndView apiKeyDetail(@RequestParam(value = "id", required = false) String id, Model model) { + // 기존 기능 유지를 위해 credentialDetail로 리다이렉트 + return credentialDetail(id, model); + } + + // ========== API Key 요청 관리 ========== + + /** + * API Key 요청을 취소합니다. + * AJAX 요청을 지원하기 위해 @ResponseBody를 사용하여 JSON 응답 반환 + * + * @param id 취소할 요청 ID + * @return 성공/실패 결과를 담은 Map + */ @PostMapping("/api_key_request/cancel") @Secured("ROLE_API_KEY_REQUEST") - public String apiRequestCancel(@RequestParam String id) { - appServiceFacade.cancelApiRequest(id, SecurityUtil.getPortalAuthenticatedUser().getPortalOrg()); - return "redirect:/myapikey/api_key_request/history"; + @org.springframework.web.bind.annotation.ResponseBody + public Map apiRequestCancel(@RequestParam String id) { + Map result = new java.util.HashMap<>(); + + try { + appServiceFacade.cancelApiRequest(id, SecurityUtil.getPortalAuthenticatedUser().getPortalOrg()); + result.put("success", true); + result.put("message", "신청이 취소되었습니다."); + } catch (Exception e) { + result.put("success", false); + result.put("message", "신청 취소 중 오류가 발생했습니다: " + e.getMessage()); + } + + return result; } + /** + * API Key를 삭제합니다. + * AJAX 요청을 지원하기 위해 @ResponseBody를 사용하여 JSON 응답 반환 + * + * @param requestData 요청 데이터 (clientId와 type 포함) + * @return 성공/실패 결과를 담은 Map + */ + @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) { + result.put("success", false); + result.put("msg", "삭제 요청 중 오류가 발생했습니다: " + e.getMessage()); + } + + return result; + } + + /** + * 사용자 비밀번호를 검증합니다. + * Client Secret 조회 시 본인 확인을 위해 사용됩니다. + * + * @param password 검증할 비밀번호 + * @return 성공/실패 결과를 담은 Map + */ + @PostMapping("/verify-password") + @Secured("ROLE_APP") + @org.springframework.web.bind.annotation.ResponseBody + public Map verifyPassword(@RequestParam String password) { + Map result = new java.util.HashMap<>(); + + try { + PortalAuthenticatedUser user = SecurityUtil.getPortalAuthenticatedUser(); + boolean isValid = appServiceFacade.verifyUserPassword(user, password); + + result.put("success", isValid); + if (!isValid) { + result.put("message", "비밀번호가 일치하지 않습니다."); + } + } catch (Exception e) { + result.put("success", false); + result.put("message", "비밀번호 확인 중 오류가 발생했습니다."); + } + + return result; + } + + /** + * API Key 요청 이력을 페이지 형태로 조회합니다. + * 각 요청의 내용을 사용자 친화적인 형식으로 포맷팅하여 표시합니다. + */ @GetMapping("/api_key_request/history") @Secured("ROLE_API_KEY_REQUEST_VIEW") - public ModelAndView apiRequestHistory(@PageableDefault(sort = "createdDate", direction = Sort.Direction.DESC) Pageable pageable, Model model) { + public ModelAndView apiRequestHistory( + @PageableDefault(sort = "createdDate", direction = Sort.Direction.DESC) Pageable pageable, + Model model) { + Page apiRequests = appServiceFacade.getApiRequestHistory(pageable, SecurityUtil.getUserOrg()); - for (AppRequestDTO request : apiRequests.getContent()) { - - if (!StringUtils.isEmpty(request.getApiGroupList())) { - String[] apiGroupList = request.getApiGroupList().split(","); - - ApiServiceDTO firstService = apiServiceService.getApiGroupById(apiGroupList[0]); - - if (apiGroupList.length == 1) { - request.setContents(firstService.getGroupName()); - } else { - request.setContents(firstService.getGroupName() + " 외 " + (apiGroupList.length - 1) + "개의 API 그룹"); - } - } - - if (StringUtils.isEmpty(request.getContents())) { - if (!StringUtils.isEmpty(request.getApiList())) { - String[] apiList = request.getApiList().split(","); - - ApiSpecInfoDto firstApi = apiService.selectDetail(apiList[0]); - - if (apiList.length == 1) { - request.setContents(firstApi.getApiName()); - } else { - request.setContents(firstApi.getApiName() + " 외 " + (apiList.length - 1) + "개의 API"); - } - } - } - } + // 각 요청의 내용을 사용자 친화적인 형식으로 포맷팅 + apiRequests.getContent().forEach(this::formatApiRequestContent); model.addAttribute("requests", apiRequests.getContent()); model.addAttribute("page", apiRequests); return new ModelAndView(API_KEY_REQUEST_HISTORY); } - @GetMapping("/api_key_request/prod_history") - @Secured("ROLE_API_KEY_REQUEST_VIEW") - public ModelAndView apiRequestProdHistory(@PageableDefault(sort = "createdDate", direction = Sort.Direction.DESC) Pageable pageable, Model model) { - Page apiRequests = appServiceFacade.getApiRequestProdHistory(pageable, SecurityUtil.getUserOrg()); + /** + * API 요청 내용을 사용자 친화적인 형식으로 포맷팅합니다. + * API 그룹 또는 개별 API 목록을 "첫번째 외 N개" 형식으로 변환합니다. + * + * @param request 포맷팅할 AppRequest DTO + */ + private void formatApiRequestContent(AppRequestDTO request) { + // API 그룹 목록 포맷팅 + if (!StringUtils.isEmpty(request.getApiGroupList())) { + String[] apiGroupList = request.getApiGroupList().split(","); + ApiServiceDTO firstService = apiServiceService.getApiGroupById(apiGroupList[0]); - for (AppRequestDTO request : apiRequests.getContent()) { - - if (!StringUtils.isEmpty(request.getApiGroupList())) { - String[] apiGroupList = request.getApiGroupList().split(","); - - ApiServiceDTO firstService = apiServiceService.getApiGroupById(apiGroupList[0]); - - if (apiGroupList.length == 1) { - request.setContents(firstService.getGroupName()); - } else { - request.setContents(firstService.getGroupName() + " 외 " + (apiGroupList.length - 1) + "개의 API 그룹"); - } - } - - if (StringUtils.isEmpty(request.getContents())) { - if (!StringUtils.isEmpty(request.getApiList())) { - String[] apiList = request.getApiList().split(","); - - ApiSpecInfoDto firstApi = apiService.selectDetail(apiList[0]); - - if (apiList.length == 1) { - request.setContents(firstApi.getApiName()); - } else { - request.setContents(firstApi.getApiName() + " 외 " + (apiList.length - 1) + "개의 API"); - } - } + if (apiGroupList.length == 1) { + request.setContents(firstService.getGroupName()); + } else { + request.setContents(firstService.getGroupName() + " 외 " + (apiGroupList.length - 1) + "개의 API 그룹"); } + return; } - model.addAttribute("requests", apiRequests.getContent()); - model.addAttribute("page", apiRequests); - return new ModelAndView(API_KEY_REQUEST_PROD_HISTORY); + // 개별 API 목록 포맷팅 + if (!StringUtils.isEmpty(request.getApiList())) { + String[] apiList = request.getApiList().split(","); + ApiSpecInfoDto firstApi = apiService.selectDetail(apiList[0]); + + if (apiList.length == 1) { + request.setContents(firstApi.getApiName()); + } else { + request.setContents(firstApi.getApiName() + " 외 " + (apiList.length - 1) + "개의 API"); + } + } } + /** + * 특정 API Key 요청의 상세 정보를 표시합니다. + * + * @param id 요청 ID + */ @GetMapping("/api_key_request/detail") @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"); } @@ -244,20 +354,593 @@ public class MyAppController { return new ModelAndView(API_KEY_REQUEST_DETAIL); } - @GetMapping("/api_key_request/prod_detail") - @Secured("ROLE_API_KEY_REQUEST_VIEW") - public ModelAndView apiRequestProdDetail(@RequestParam(value = "id", required = false) String id, Model model) { + /** + * 세션 등록 객체 초기화 + */ + @ModelAttribute("apiKeyRegistration") + public ApiKeyRegistrationDTO getApiKeyRegistration() { + return new ApiKeyRegistrationDTO(); + } - if(id == null) { - return new ModelAndView("redirect:/myapikey/api_key_request/prod_history"); + /** + * 세션 수정 객체 초기화 + */ + @ModelAttribute("apiKeyModification") + public ApiKeyRegistrationDTO getApiKeyModification() { + return new ApiKeyRegistrationDTO(); + } + + /** + * API Key 등록 - 1단계: 기본 정보 입력 + * + * @param clear - 새로운 등록을 시작할 때 세션을 초기화하는 선택적 파라미터 + */ + @GetMapping("/register/step1") + @Secured("ROLE_API_KEY_REQUEST") + public ModelAndView registerStep1( + @RequestParam(value = "clear", required = false, defaultValue = "false") boolean clear, + @ModelAttribute("apiKeyRegistration") ApiKeyRegistrationDTO registration, + SessionStatus sessionStatus, + Model model) { + PortalAuthenticatedUser user = SecurityUtil.getPortalAuthenticatedUser(); + + // 명시적으로 요청된 경우에만 세션 초기화 (새 등록 시작) + // 페이지 새로고침이나 뒤로가기 시에는 세션 데이터를 보존 + if (clear) { + sessionStatus.setComplete(); + // 새로운 깨끗한 등록 객체 생성 + ApiKeyRegistrationDTO newRegistration = new ApiKeyRegistrationDTO(); + model.addAttribute("apiKeyRegistration", newRegistration); + } + + // 등록 프로세스를 위한 세션 데이터 초기화 + setupStepModel(model, 1); + model.addAttribute("userOrg", user.getPortalOrg()); + + return new ModelAndView(API_KEY_REGISTER_STEP1); + } + + /** + * API Key 등록 - 1단계 처리 및 2단계로 이동 + */ + @PostMapping("/register/step1") + @Secured("ROLE_API_KEY_REQUEST") + public ModelAndView processStep1( + @RequestParam(value = "ipWhitelist", required = false) String ipWhitelist, + @Valid @ModelAttribute("apiKeyRegistration") ApiKeyRegistrationDTO registration, + BindingResult bindingResult, + Model model) { + + try { + // 앱 아이콘 파일 검증 + MultipartFile appIcon = registration.getAppIcon(); + boolean hasExistingIcon = registration.getAppIconData() != null && registration.getAppIconData().length > 0; + validateAppIcon(appIcon, hasExistingIcon, bindingResult); + + // 검증 오류가 있는 경우 Step 1로 돌아가기 + if (bindingResult.hasErrors()) { + setupStepModel(model, 1); + model.addAttribute("error", buildErrorMessage(bindingResult)); + return new ModelAndView(API_KEY_REGISTER_STEP1); + } + + // 새 업로드가 있는 경우 세션에 파일 데이터 저장 + boolean hasNewUpload = appIcon != null && !appIcon.isEmpty(); + if (hasNewUpload) { + registration.setAppIconFileName(appIcon.getOriginalFilename()); + registration.setAppIconData(appIcon.getBytes()); + registration.setAppIconContentType(appIcon.getContentType()); + } + + // 텍스트 필드 공백 제거 및 설정 (널 안전) + registration.setAppName(StringUtils.trimToEmpty(registration.getAppName())); + registration.setAppDescription(StringUtils.trimToEmpty(registration.getAppDescription())); + registration.setCallbackUrl(StringUtils.trimToEmpty(registration.getCallbackUrl())); + + // IP 화이트리스트 설정 + if (ipWhitelist != null && !ipWhitelist.trim().isEmpty()) { + registration.setIpWhitelistFromString(ipWhitelist); + } + + return new ModelAndView("redirect:/myapikey/register/step2"); + + } catch (Exception e) { + setupStepModel(model, 1); + model.addAttribute("error", "파일 처리 중 오류가 발생했습니다."); + return new ModelAndView(API_KEY_REGISTER_STEP1); + } + } + + /** + * 업로드된 앱 아이콘 파일의 유효성을 검증합니다. + * 아이콘은 선택 사항이므로 업로드된 경우에만 검증합니다. + * - 파일 크기 검증 (최대 2MB) + * - 파일 형식 검증 (PNG, JPG만 허용) + * - Magic Number 기반 실제 파일 타입 검증 + * + * @param appIcon 업로드된 파일 (선택 사항) + * @param hasExistingIcon 세션에 기존 아이콘이 있는지 여부 + * @param bindingResult 검증 오류를 저장할 BindingResult + */ + private void validateAppIcon(MultipartFile appIcon, boolean hasExistingIcon, BindingResult bindingResult) { + boolean hasNewUpload = appIcon != null && !appIcon.isEmpty(); + + // 아이콘은 선택 사항이므로, 새 업로드가 없으면 검증 종료 + if (!hasNewUpload) { + return; + } + + // 파일 크기 검증 (최대 2MB) + if (appIcon.getSize() > MAX_APP_ICON_BYTES) { + bindingResult.rejectValue("appIconFileName", "error.appIcon.size", + "파일 크기는 2MB를 초과할 수 없습니다."); + return; + } + + // 파일 내용 기반 실제 파일 타입 검증 (Magic Number 검증) + try { + byte[] fileBytes = appIcon.getBytes(); + String detectedType = fileTypeDetector.detectFileType(fileBytes); + String normalizedType = detectedType != null ? detectedType.toLowerCase() : ""; + + // PNG, JPG/JPEG만 허용 (SVG 제외) + if (!"png".equals(normalizedType) && !"jpg".equals(normalizedType) && !"jpeg".equals(normalizedType)) { + bindingResult.rejectValue("appIconFileName", "error.appIcon.type", + "PNG, JPG/JPEG 형식만 업로드 가능합니다. 업로드된 파일은 " + detectedType + " 형식입니다."); + } + } catch (Exception e) { + bindingResult.rejectValue("appIconFileName", "error.appIcon.invalid", + "파일 형식을 확인할 수 없습니다. 올바른 이미지 파일을 선택해주세요."); + } + } + + /** + * API Key 등록 - 2단계: API 선택 + */ + @GetMapping("/register/step2") + @Secured("ROLE_API_KEY_REQUEST") + public ModelAndView registerStep2( + @ModelAttribute("apiKeyRegistration") ApiKeyRegistrationDTO registration, + RedirectAttributes redirectAttributes, + Model model) { + + // 1단계가 완료되었는지 검증 + if (!registration.isStep1Complete()) { + redirectAttributes.addFlashAttribute("error", "먼저 기본 정보를 입력해주세요."); + return new ModelAndView("redirect:/myapikey/register/step1"); + } + + // 서비스 카테고리만 가져오기 (API는 AJAX로 로드됨) + ApiGroupSearch search = new ApiGroupSearch(); + List apiServices = apiServiceService.searchApiGroups(search); + + setupStepModel(model, 2); + model.addAttribute("apiServices", apiServices); + model.addAttribute("registration", registration); + + return new ModelAndView(API_KEY_REGISTER_STEP2); + } + + /** + * API Key 등록 - 2단계 선택 저장 (뒤로가기 네비게이션용) + */ + @PostMapping("/register/step2/save") + @Secured("ROLE_API_KEY_REQUEST") + public ModelAndView saveStep2( + @RequestParam(value = "selectedApis", required = false) List selectedApis, + @ModelAttribute("apiKeyRegistration") ApiKeyRegistrationDTO registration) { + + // 선택된 API를 세션에 저장 (부분 진행 상황을 보존하기 위해 비어있어도 저장) + if (selectedApis != null) { + registration.setSelectedApis(selectedApis); + } else { + registration.setSelectedApis(new ArrayList<>()); + } + + // 1단계로 리다이렉트 + return new ModelAndView("redirect:/myapikey/register/step1"); + } + + /** + * API Key 등록 - 2단계 처리 및 3단계로 이동 + * 실제 AppRequest 생성 및 승인 프로세스 시작 + */ + @PostMapping("/register/step2") + @Secured("ROLE_API_KEY_REQUEST") + public ModelAndView processStep2( + @RequestParam(value = "selectedApis", required = false) List selectedApis, + @ModelAttribute("apiKeyRegistration") ApiKeyRegistrationDTO registration, + SessionStatus sessionStatus, + RedirectAttributes redirectAttributes) { + + // 1단계가 완료되었는지 검증 + if (!registration.isStep1Complete()) { + redirectAttributes.addFlashAttribute("error", "먼저 기본 정보를 입력해주세요."); + return new ModelAndView("redirect:/myapikey/register/step1"); + } + + // API 선택 검증 + if (selectedApis == null || selectedApis.isEmpty()) { + redirectAttributes.addFlashAttribute("error", "최소 1개 이상의 API를 선택해주세요."); + return new ModelAndView("redirect:/myapikey/register/step2"); + } + + // 선택된 API를 세션에 저장 + registration.setSelectedApis(selectedApis); + + // 등록이 완료되었는지 최종 검증 + if (!registration.isComplete()) { + redirectAttributes.addFlashAttribute("error", "등록 정보가 완전하지 않습니다."); + return new ModelAndView("redirect:/myapikey/register/step1"); } PortalAuthenticatedUser user = SecurityUtil.getPortalAuthenticatedUser(); - AppRequestDTO appRequest = appServiceFacade.getApiRequestProdByIdAndPortalOrg(id, user.getPortalOrg()); + try { + // AppRequest 생성 및 승인 프로세스 시작 + AppRequestDTO appRequestDTO = appServiceFacade.createAppRequestFromRegistration(registration, user.getPortalOrg()); + appServiceFacade.beginApproval(appRequestDTO.getApproval().getId()); - model.addAttribute("appRequest", appRequest); - return new ModelAndView(API_KEY_REQUEST_PROD_DETAIL); + // 성공 메시지와 함께 step3로 리다이렉트 + redirectAttributes.addFlashAttribute("registrationSuccess", true); + redirectAttributes.addFlashAttribute("appName", registration.getAppName()); + + // 성공적으로 완료된 후 세션 초기화 + sessionStatus.setComplete(); + + return new ModelAndView("redirect:/myapikey/register/step3"); + + } catch (Exception e) { + // 실패 시 에러 메시지와 함께 step2로 돌아감 + redirectAttributes.addFlashAttribute("error", "API Key 등록 중 오류가 발생했습니다. 다시 시도해주세요."); + return new ModelAndView("redirect:/myapikey/register/step2"); + } } + /** + * API Key 등록 - 3단계: 결과 확인 페이지 + * Step 2에서 이미 처리가 완료되었으므로 결과만 표시 + */ + @GetMapping("/register/step3") + @Secured("ROLE_API_KEY_REQUEST") + public ModelAndView registerStep3(Model model) { + + // Step 2 POST에서 전달된 성공 플래그 확인 + Boolean registrationSuccess = (Boolean) model.asMap().get("registrationSuccess"); + String appName = (String) model.asMap().get("appName"); + + // 직접 접근 방지: Step 2 POST를 거치지 않고 직접 접근한 경우 + if (registrationSuccess == null || !registrationSuccess) { + return new ModelAndView("redirect:/myapikey"); + } + + // 결과 페이지 표시용 속성 설정 + setupStepModel(model, 3); + model.addAttribute("registrationComplete", true); + model.addAttribute("appName", appName); + + return new ModelAndView(API_KEY_REGISTER_STEP3); + } + + /** + * API Key 등록 취소 및 목록으로 돌아가기 + */ + @GetMapping("/register/cancel") + @Secured("ROLE_API_KEY_REQUEST") + public String cancelRegistration(SessionStatus sessionStatus) { + // 등록과 관련된 세션 데이터 초기화 + sessionStatus.setComplete(); + return "redirect:/myapikey"; + } + + /** + * 등록 단계별 공통 모델 속성을 설정합니다. + * + * @param model 모델 객체 + * @param currentStep 현재 단계 번호 + */ + private void setupStepModel(Model model, int currentStep) { + model.addAttribute("currentStep", currentStep); + model.addAttribute("totalSteps", 3); + model.addAttribute("userOrg", SecurityUtil.getPortalAuthenticatedUser().getPortalOrg()); + } + + /** + * BindingResult의 모든 에러 메시지를 하나의 문자열로 결합합니다. + * + * @param bindingResult 검증 결과 + * @return 줄바꿈으로 구분된 에러 메시지 + */ + private String buildErrorMessage(BindingResult bindingResult) { + StringBuilder errorMessage = new StringBuilder(); + for (FieldError error : bindingResult.getFieldErrors()) { + if (errorMessage.length() > 0) { + errorMessage.append("\n"); + } + errorMessage.append(error.getDefaultMessage()); + } + return errorMessage.toString(); + } + + // ========== API Key 수정 관련 엔드포인트 ========== + + /** + * API Key 수정 - 1단계: 기본 정보 수정 + * Credential을 ApiKeyRegistrationDTO로 변환하여 세션에 저장 + * + * @param clientId 수정할 API Key의 Client ID + */ + @GetMapping("/modify/step1") + @Secured("ROLE_API_KEY_REQUEST") + public ModelAndView modifyStep1( + @RequestParam(value = "clientId", required = false) String clientId, + @ModelAttribute("apiKeyModification") ApiKeyRegistrationDTO modification, + SessionStatus sessionStatus, + Model model, + RedirectAttributes redirectAttributes) { + + if (clientId == null) { + return new ModelAndView("redirect:/myapikey"); + } + + PortalAuthenticatedUser user = SecurityUtil.getPortalAuthenticatedUser(); + + // 기존 API Key 정보 조회 + ClientDTO apiKey = appServiceFacade.getApiKey(user.getPortalOrg().getId(), clientId); + if (apiKey == null) { + return new ModelAndView("redirect:/myapikey"); + } + + // 새로운 수정 세션 시작시에만 초기화 + // clientId가 다르거나, clientId는 같지만 다른 필드가 비어있는 경우 (세션이 클리어된 경우) + if (modification.getClientId() == null || + !modification.getClientId().equals(clientId) || + modification.getAppName() == null) { + modification = new ApiKeyRegistrationDTO(); + + // ClientDTO를 ApiKeyRegistrationDTO로 변환 + modification.setClientId(clientId); + modification.setAppName(apiKey.getClientname()); + modification.setAppDescription(apiKey.getAppDescription()); + modification.setCallbackUrl(apiKey.getRedirecturi()); + + // IP 화이트리스트 변환 + if (apiKey.getAllowedips() != null && !apiKey.getAllowedips().isEmpty()) { + modification.setIpWhitelistFromString(apiKey.getAllowedips()); + } + + // 앱 아이콘 정보 설정 + if (apiKey.getAppIconFileId() != null) { + // 파일 ID만 설정하면 뷰에서 /file/download로 직접 조회 + modification.setAppIconFileId(apiKey.getAppIconFileId()); + } + + // 기존 API 목록 설정 + List apiIds = new ArrayList<>(); + if (apiKey.getApiList() != null) { + for (ApiServiceApiDTO api : apiKey.getApiList()) { + apiIds.add(api.getApiId()); + } + } + modification.setSelectedApis(apiIds); + + // 수정 타입 설정 + modification.setRequestType("MODIFY"); + + model.addAttribute("apiKeyModification", modification); + } + + // Step 모델 설정 + setupStepModel(model, 1); + model.addAttribute("userOrg", user.getPortalOrg()); + + return new ModelAndView(API_KEY_MODIFY_STEP1); + } + + /** + * API Key 수정 - 1단계 처리 및 2단계로 이동 + */ + @PostMapping("/modify/step1") + @Secured("ROLE_API_KEY_REQUEST") + public ModelAndView processModifyStep1( + @RequestParam(value = "ipWhitelist", required = false) String ipWhitelist, + @Valid @ModelAttribute("apiKeyModification") ApiKeyRegistrationDTO modification, + BindingResult bindingResult, + Model model) { + + try { + // 앱 아이콘 파일 검증 + MultipartFile appIcon = modification.getAppIcon(); + // 수정 시에는 appIconFileId가 있으면 기존 아이콘이 있는 것으로 처리 + boolean hasExistingIcon = (modification.getAppIconData() != null && modification.getAppIconData().length > 0) + || (modification.getAppIconFileId() != null && !modification.getAppIconFileId().isEmpty()); + validateAppIcon(appIcon, hasExistingIcon, bindingResult); + + // 검증 오류가 있는 경우 Step 1로 돌아가기 + if (bindingResult.hasErrors()) { + setupStepModel(model, 1); + model.addAttribute("error", buildErrorMessage(bindingResult)); + return new ModelAndView(API_KEY_MODIFY_STEP1); + } + + // 새 업로드가 있는 경우 세션에 파일 데이터 저장 + boolean hasNewUpload = appIcon != null && !appIcon.isEmpty(); + if (hasNewUpload) { + modification.setAppIconFileName(appIcon.getOriginalFilename()); + modification.setAppIconData(appIcon.getBytes()); + modification.setAppIconContentType(appIcon.getContentType()); + } + + // 텍스트 필드 공백 제거 및 설정 + modification.setAppName(StringUtils.trimToEmpty(modification.getAppName())); + modification.setAppDescription(StringUtils.trimToEmpty(modification.getAppDescription())); + modification.setCallbackUrl(StringUtils.trimToEmpty(modification.getCallbackUrl())); + + // IP 화이트리스트 설정 + if (ipWhitelist != null && !ipWhitelist.trim().isEmpty()) { + modification.setIpWhitelistFromString(ipWhitelist); + } + + return new ModelAndView("redirect:/myapikey/modify/step2"); + + } catch (Exception e) { + setupStepModel(model, 1); + model.addAttribute("error", "파일 처리 중 오류가 발생했습니다."); + return new ModelAndView(API_KEY_MODIFY_STEP1); + } + } + + /** + * API Key 수정 - 2단계: API 선택 + */ + @GetMapping("/modify/step2") + @Secured("ROLE_API_KEY_REQUEST") + public ModelAndView modifyStep2( + @ModelAttribute("apiKeyModification") ApiKeyRegistrationDTO modification, + RedirectAttributes redirectAttributes, + Model model) { + + // 1단계가 완료되었는지 검증 + if (!modification.isStep1Complete()) { + redirectAttributes.addFlashAttribute("error", "먼저 기본 정보를 입력해주세요."); + return new ModelAndView("redirect:/myapikey/modify/step1?clientId=" + modification.getClientId()); + } + + // 서비스 카테고리와 API 목록 가져오기 + ApiGroupSearch search = new ApiGroupSearch(); + List apiServices = apiServiceService.searchApiGroups(search); + + setupStepModel(model, 2); + model.addAttribute("apiServices", apiServices); + model.addAttribute("modification", modification); + + return new ModelAndView(API_KEY_MODIFY_STEP2); + } + + /** + * API Key 수정 - 2단계 선택 저장 (뒤로가기용) + */ + @PostMapping("/modify/step2/save") + @Secured("ROLE_API_KEY_REQUEST") + public ModelAndView saveModifyStep2( + @RequestParam(value = "selectedApis", required = false) List selectedApis, + @ModelAttribute("apiKeyModification") ApiKeyRegistrationDTO modification) { + + // 선택된 API를 세션에 저장 + if (selectedApis != null) { + modification.setSelectedApis(selectedApis); + } else { + modification.setSelectedApis(new ArrayList<>()); + } + + // 1단계로 리다이렉트 + return new ModelAndView("redirect:/myapikey/modify/step1?clientId=" + modification.getClientId()); + } + + /** + * API Key 수정 - 2단계 처리 및 3단계로 이동 + * 실제 수정 요청 생성 및 승인 프로세스 시작 + */ + @PostMapping("/modify/step2") + @Secured("ROLE_API_KEY_REQUEST") + public ModelAndView processModifyStep2( + @RequestParam(value = "selectedApis", required = false) List selectedApis, + @ModelAttribute("apiKeyModification") ApiKeyRegistrationDTO modification, + SessionStatus sessionStatus, + RedirectAttributes redirectAttributes) { + + // 1단계가 완료되었는지 검증 + if (!modification.isStep1Complete()) { + redirectAttributes.addFlashAttribute("error", "먼저 기본 정보를 입력해주세요."); + return new ModelAndView("redirect:/myapikey/modify/step1?clientId=" + modification.getClientId()); + } + + // API 선택 검증 + if (selectedApis == null || selectedApis.isEmpty()) { + redirectAttributes.addFlashAttribute("error", "최소 1개 이상의 API를 선택해주세요."); + return new ModelAndView("redirect:/myapikey/modify/step2"); + } + + // 선택된 API를 세션에 저장 + modification.setSelectedApis(selectedApis); + + // 등록이 완료되었는지 최종 검증 + if (!modification.isComplete()) { + redirectAttributes.addFlashAttribute("error", "수정 정보가 완전하지 않습니다."); + return new ModelAndView("redirect:/myapikey/modify/step1?clientId=" + modification.getClientId()); + } + + PortalAuthenticatedUser user = SecurityUtil.getPortalAuthenticatedUser(); + + try { + // AppRequest 생성 (MODIFY 타입으로) 및 승인 프로세스 시작 + AppRequestDTO appRequestDTO = appServiceFacade.createModifyRequestFromRegistration(modification, user.getPortalOrg()); + appServiceFacade.beginApproval(appRequestDTO.getApproval().getId()); + + // 성공 메시지와 함께 step3로 리다이렉트 + redirectAttributes.addFlashAttribute("modificationComplete", true); + redirectAttributes.addFlashAttribute("appName", modification.getAppName()); + + // 성공적으로 완료된 후 세션 초기화 + sessionStatus.setComplete(); + + return new ModelAndView("redirect:/myapikey/modify/step3"); + + } catch (Exception e) { + // 실패 시 에러 메시지와 함께 step2로 돌아감 + redirectAttributes.addFlashAttribute("error", "API Key 수정 요청 중 오류가 발생했습니다. 다시 시도해주세요."); + return new ModelAndView("redirect:/myapikey/modify/step2"); + } + } + + /** + * API Key 수정 - 3단계: 결과 확인 페이지 + */ + @GetMapping("/modify/step3") + @Secured("ROLE_API_KEY_REQUEST") + public ModelAndView modifyStep3(Model model) { + + // Step 2 POST에서 전달된 성공 플래그 확인 + Boolean modificationComplete = (Boolean) model.asMap().get("modificationComplete"); + String appName = (String) model.asMap().get("appName"); + + // 직접 접근 방지: Step 2 POST를 거치지 않고 직접 접근한 경우 + if (modificationComplete == null || !modificationComplete) { + return new ModelAndView("redirect:/myapikey"); + } + + // 결과 페이지 표시용 속성 설정 + setupStepModel(model, 3); + model.addAttribute("modificationComplete", true); + model.addAttribute("appName", appName); + + return new ModelAndView(API_KEY_MODIFY_STEP3); + } + + /** + * API Key 수정 취소 및 상세 페이지로 돌아가기 + */ + @GetMapping("/modify/cancel") + @Secured("ROLE_API_KEY_REQUEST") + public String cancelModification( + @RequestParam(value = "clientId", required = false) String clientId, + SessionStatus sessionStatus, + HttpSession session) { + + // 세션에서 직접 apiKeyModification 제거 + session.removeAttribute("apiKeyModification"); + + // Spring의 세션 관리도 초기화 + sessionStatus.setComplete(); + + // clientId가 있으면 상세 페이지로, 없으면 목록으로 + if (clientId != null && !clientId.isEmpty()) { + return "redirect:/myapikey/credential_detail?id=" + clientId; + } else { + return "redirect:/myapikey"; + } + } + + + } 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 new file mode 100644 index 0000000..2672c3a --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/apps/app/dto/ApiKeyRegistrationDTO.java @@ -0,0 +1,104 @@ +package com.eactive.apim.portal.apps.app.dto; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Base64; +import java.util.List; +import lombok.Data; +import org.hibernate.validator.constraints.Length; +import org.hibernate.validator.constraints.NotBlank; +import org.springframework.web.multipart.MultipartFile; + +/** + * API Key Registration Session Data + * Holds data across multiple registration steps + */ +@Data +public class ApiKeyRegistrationDTO implements Serializable { + + private static final long serialVersionUID = 1L; + + // Client ID for modification operations + private String clientId; + + // Request type for tracking operation type + private String requestType; // "NEW" or "MODIFY" + + // Existing app icon file ID (for modifications) + private String appIconFileId; + + // Step 1: Basic Information + // Transient field for file upload (not serialized in session) + private transient MultipartFile appIcon; + + // Persisted file data + private String appIconFileName; + private byte[] appIconData; + private String appIconContentType; + + @NotBlank(message = "앱 이름을 입력해주세요.") + @Length(max = 100, message = "앱 이름은 100자를 초과할 수 없습니다.") + private String appName; + + @NotBlank(message = "앱 설명을 입력해주세요.") + @Length(max = 500, message = "앱 설명은 500자를 초과할 수 없습니다.") + private String appDescription; + + private String callbackUrl; + + private List ipWhitelist = new ArrayList<>(); + + // Step 2: API Selection + private List selectedApis = new ArrayList<>(); + + // Helper method to add IP to whitelist + public void addIpAddress(String ip) { + if (ip != null && !ip.trim().isEmpty() && !ipWhitelist.contains(ip)) { + ipWhitelist.add(ip); + } + } + + // Helper method to set IP whitelist from comma-separated string + public void setIpWhitelistFromString(String ipString) { + ipWhitelist.clear(); + if (ipString != null && !ipString.trim().isEmpty()) { + String[] ips = ipString.split(","); + for (String ip : ips) { + String trimmedIp = ip.trim(); + if (!trimmedIp.isEmpty()) { + ipWhitelist.add(trimmedIp); + } + } + } + } + + // Helper method to get IP whitelist as comma-separated string + public String getIpWhitelistAsString() { + return String.join(",", ipWhitelist); + } + + // Helper method to get app icon as base64 data URI for image display + public String getAppIconBase64() { + if (appIconData == null || appIconData.length == 0) { + return null; + } + String base64Data = Base64.getEncoder().encodeToString(appIconData); + String contentType = appIconContentType != null ? appIconContentType : "image/png"; + return "data:" + contentType + ";base64," + base64Data; + } + + // Validation helpers + public boolean isStep1Complete() { + // callbackUrl and appIcon are optional, so we only check required fields + return appName != null && !appName.trim().isEmpty() && + appDescription != null && !appDescription.trim().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/apps/app/dto/AppRequestDTO.java b/src/main/java/com/eactive/apim/portal/apps/app/dto/AppRequestDTO.java index c034fe9..2889f4c 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 @@ -44,6 +44,14 @@ public class AppRequestDTO { private String reason; + private String appDescription; + + private String callbackUrl; + + private String ipWhitelist; + + private String appIconFileId; + private LocalDateTime createdDate; private String prevApiList; diff --git a/src/main/java/com/eactive/apim/portal/apps/app/dto/ClientDTO.java b/src/main/java/com/eactive/apim/portal/apps/app/dto/ClientDTO.java index 552eb51..016a0bb 100644 --- a/src/main/java/com/eactive/apim/portal/apps/app/dto/ClientDTO.java +++ b/src/main/java/com/eactive/apim/portal/apps/app/dto/ClientDTO.java @@ -45,6 +45,10 @@ public class ClientDTO { private String appstatus; + private String appDescription; + + private String appIconFileId; + private List apiList = new ArrayList<>(); } diff --git a/src/main/java/com/eactive/apim/portal/apps/app/mapper/AppRequestMapper.java b/src/main/java/com/eactive/apim/portal/apps/app/mapper/AppRequestMapper.java index 0be97f5..d6318c1 100644 --- a/src/main/java/com/eactive/apim/portal/apps/app/mapper/AppRequestMapper.java +++ b/src/main/java/com/eactive/apim/portal/apps/app/mapper/AppRequestMapper.java @@ -4,6 +4,7 @@ package com.eactive.apim.portal.apps.app.mapper; import com.eactive.apim.portal.apprequest.entity.AppRequest; import com.eactive.apim.portal.apps.apiservice.mapper.ApiServiceApiMapper; import com.eactive.apim.portal.apps.app.dto.AppRequestDTO; +import com.eactive.apim.portal.apps.app.dto.ClientDTO; import com.eactive.apim.portal.apps.approval.mapper.ApprovalMapper; import com.eactive.apim.portal.apps.user.mapper.PortalOrgMapper; import com.eactive.eai.data.mapper.BaseMapperConfig; @@ -13,4 +14,53 @@ import org.mapstruct.Mapper; @Mapper(config = BaseMapperConfig.class, uses = {PortalOrgMapper.class, ApiServiceApiMapper.class, ApprovalMapper.class}) public interface AppRequestMapper extends GenericMapper { + /** + * AppRequest를 ClientDTO로 간단하게 매핑합니다. + * 뷰에서 AppRequest를 ClientDTO와 동일한 형식으로 표시하기 위해 사용됩니다. + * + * @param appRequest 앱 요청 엔티티 + * @return ClientDTO 표시용 DTO + */ + default ClientDTO mapToClientDTO(AppRequest appRequest) { + if (appRequest == null) { + return null; + } + + ClientDTO dto = new ClientDTO(); + + // 기본 식별 정보 + dto.setClientid(appRequest.getId()); // AppRequest ID를 임시 client ID로 사용 + dto.setClientname(appRequest.getClientName()); + + // 앱 상세 정보 + dto.setAppDescription(appRequest.getAppDescription()); + dto.setAppIconFileId(appRequest.getAppIconFileId()); + + // 네트워크 설정 + dto.setRedirecturi(appRequest.getCallbackUrl()); + dto.setAllowedips(appRequest.getIpWhitelist()); + + // 상태 정보 (ApprovalStatus → appstatus 매핑) + if (appRequest.getStatus() != null) { + switch (appRequest.getStatus()) { + case APPROVED: + dto.setAppstatus("1"); // 승인됨 = 활성화 + break; + case DENIED: + dto.setAppstatus("0"); // 거부됨 = 비활성화 + break; + default: + dto.setAppstatus("2"); // 대기중/진행중 = 보류 + break; + } + } + + // 수정 날짜 (createdDate를 modifiedon으로 사용) + dto.setModifiedon(appRequest.getCreatedDate()); + + // API 목록은 별도 처리 필요 (콤마로 구분된 문자열 → List) + // 필요시 컨트롤러에서 추가 처리 + + return dto; + } } 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 d4828df..b765fec 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,12 +7,14 @@ 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.ProcessingState; +import com.eactive.apim.portal.approval.statemachine.RequestedState; 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; import com.eactive.apim.portal.apps.apiservice.dto.ApiServiceDTO; -import com.eactive.apim.portal.apps.apiservice.dto.ApiServiceApiDTO; import com.eactive.apim.portal.apps.apiservice.service.ApiServiceService; +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.mapper.AppRequestMapper; @@ -21,16 +23,24 @@ import com.eactive.apim.portal.apps.approval.service.ApprovalService; import com.eactive.apim.portal.apps.user.dto.PortalOrgDTO; import com.eactive.apim.portal.apps.user.mapper.PortalOrgMapper; import com.eactive.apim.portal.common.exception.NotFoundException; +import com.eactive.apim.portal.common.user.PortalAuthenticatedUser; import com.eactive.apim.portal.common.util.ApiServiceHelper; +import com.eactive.apim.portal.file.entity.FileInfo; +import com.eactive.apim.portal.file.service.FileService; import com.eactive.apim.portal.portalorg.entity.PortalOrg; +import java.io.IOException; import java.time.LocalDateTime; -import java.util.*; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Optional; import java.util.stream.Collectors; import javax.transaction.Transactional; import lombok.RequiredArgsConstructor; import org.apache.commons.lang3.StringUtils; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; @Service @@ -47,53 +57,64 @@ public class AppServiceFacade { private final AppRequestMapper appRequestMapper; private final PortalOrgMapper portalOrgMapper; private final ApiServiceHelper apiServiceHelper; + private final FileService fileService; + private final PasswordEncoder passwordEncoder; - public Page getApiKeyPageForServer(Pageable pageable, PortalOrg portalOrg, String server) { - Page clients = credentialRepository.findAllByOrgidAndServer(pageable, portalOrg.getId(), server); - return clients.map(credentialMapper::toVo); - } + public List getApikeyList(PortalOrg portalOrg) { - public List getApiKeyList(PortalOrg portalOrg, String server) { - List clients = credentialRepository.findAllByOrgidAndServer(portalOrg.getId(), server); + List clients = credentialRepository.findAllByOrgid(portalOrg.getId()); return clients.stream().map(credentialMapper::toVo).collect(Collectors.toList()); } - public ClientDTO getApiKey(String orgid, String clientId, String server) { - return credentialRepository.findByClientidAndOrgidAndServer(clientId, orgid, server).map(credentialMapper::toVo).orElse(null); + public List getPendingApiKeyList(PortalOrg portalOrg) { + List appRequests = appRequestRepository.findAllByOrgAndTypeIsInAndApproval_ApprovalStatusIn(portalOrg, Arrays.asList(AppRequestType.NEW, AppRequestType.MODIFY, AppRequestType.DELETE), + Arrays.asList(new ProcessingState(), new RequestedState())); + + return appRequests; + } + + public ClientDTO getApiKey(String orgid, String clientId) { + return credentialRepository.findByClientidAndOrgid(clientId, orgid).map(credentialMapper::toVo).orElse(null); + } + + /** + * AppRequest ID와 조직 정보로 AppRequest를 조회합니다. + * API 그룹 및 API 목록 정보를 함께 처리하여 반환합니다. + * + * @param id AppRequest ID + * @param portalOrg 포털 조직 + * @return AppRequest DTO (없으면 null) + */ + public AppRequestDTO getAppRequestById(String id, PortalOrg portalOrg) { + return getApiRequestByIdAndPortalOrg(id, portalOrg); } public Page getApiRequestHistory(Pageable pageable, PortalOrgDTO portalOrg) { - return appRequestRepository.findAllByOrgAndTypeIsIn(pageable, portalOrgMapper.toEntity(portalOrg), Arrays.asList(AppRequestType.NEW,AppRequestType.MODIFY, AppRequestType.DELETE)).map(appRequestMapper::toVo); - } - - public Page getApiRequestProdHistory(Pageable pageable, PortalOrgDTO portalOrg) { - return appRequestRepository.findAllByOrgAndTypeIsIn(pageable, portalOrgMapper.toEntity(portalOrg), Arrays.asList(AppRequestType.PROD_NEW,AppRequestType.PROD_MODIFY, AppRequestType.PROD_DELETE)).map(appRequestMapper::toVo); + return appRequestRepository.findAllByOrgAndTypeIsIn(pageable, portalOrgMapper.toEntity(portalOrg), Arrays.asList(AppRequestType.NEW, AppRequestType.MODIFY, AppRequestType.DELETE)).map(appRequestMapper::toVo); } public AppRequestDTO createAppRequest(AppRequestDTO appRequest) { - setApiGroupsFromApiList(appRequest); AppRequest request = appRequestRepository.save(appRequestMapper.toEntity(appRequest)); - if (request.getType() != null && (request.getType().equals(AppRequestType.MODIFY) || request.getType().equals(AppRequestType.DELETE) || - request.getType().equals(AppRequestType.PROD_MODIFY) || request.getType().equals(AppRequestType.PROD_DELETE))) { + if (request.getType() != null && (request.getType().equals(AppRequestType.MODIFY) || request.getType().equals(AppRequestType.DELETE))) { Optional client = credentialRepository.findById(request.getClientId()); if (client.isPresent()) { request.setClientName(client.get().getClientname()); - if(client.get().getApiList() != null) { + if (client.get().getApiList() != null) { // 기존 API 목록을 저장 String existingApiList = client.get().getApiList().stream() - .map(ApiSpecInfo::getApiId) - .collect(Collectors.joining(",")); + .map(ApiSpecInfo::getApiId) + .collect(Collectors.joining(",")); request.setPrevApiList(existingApiList); } - if(AppRequestType.DELETE.equals(request.getType()) || AppRequestType.PROD_DELETE.equals(request.getType())) { + if (AppRequestType.DELETE.equals(request.getType())) { request.setApiList(request.getPrevApiList()); } } else { @@ -108,22 +129,6 @@ public class AppServiceFacade { return appRequestMapper.toVo(request); } - private void setApiGroupsFromApiList(AppRequestDTO appRequest) { - if (StringUtils.isEmpty(appRequest.getApiList())) { - return; - } - - Set uniqueGroupIds = Arrays.stream(appRequest.getApiList().split(",")) - .map(apiId -> apiServiceService.findApiServiceByApiId(apiId)) - .filter(Objects::nonNull) - .flatMap(apiService -> apiService.getApiGroupApiList().stream()) - .map(ApiServiceApiDTO::getApiGroupId) - .collect(Collectors.toSet()); - - appRequest.setApiGroupList(String.join(",", uniqueGroupIds)); - } - - public void beginApproval(String approvalId) { approvalService.beginApproval(approvalId); } @@ -132,27 +137,11 @@ public class AppServiceFacade { appRequestRepository.findByIdAndOrg(id, portalOrg).ifPresent(approvalService::cancelAppApproval); } - public List getProdApiRequestHistory(String clientid) { - return appRequestRepository.findAllByClientIdsContainsAndTypeIsIn(clientid,Arrays.asList(AppRequestType.PROD_NEW,AppRequestType.PROD_MODIFY, AppRequestType.PROD_DELETE)).stream().map(appRequestMapper::toVo).collect(Collectors.toList()); - } - - - public AppRequestDTO getApiRequestProdByIdAndPortalOrg(String id, PortalOrg portalOrg) { - AppRequestDTO appRequest = appRequestRepository.findByIdAndOrgAndTypeIsIn(id, portalOrg, - Arrays.asList(AppRequestType.PROD_NEW,AppRequestType.PROD_MODIFY, AppRequestType.PROD_DELETE)).map(this::convertToAppRequestDTO).orElse(null); - - if(appRequest != null) { - processApiGroups(appRequest); - processApiList(appRequest); - } - return appRequest; - } public AppRequestDTO getApiRequestByIdAndPortalOrg(String id, PortalOrg org) { AppRequestDTO appRequest = appRequestRepository.findByIdAndOrg(id, org).map(this::convertToAppRequestDTO).orElse(null); - if(appRequest != null) { - processApiGroups(appRequest); + if (appRequest != null) { processApiList(appRequest); } return appRequest; @@ -167,16 +156,6 @@ public class AppServiceFacade { return dto; } - private void processApiGroups(AppRequestDTO appRequest) { - - if (!StringUtils.isEmpty(appRequest.getApiGroupList())) { - String[] apiGroupList = appRequest.getApiGroupList().split(","); - for (String apiGroupId : apiGroupList) { - appRequest.getApiServiceDTOList().add(apiServiceService.getApiGroupById(apiGroupId)); - } - } - } - private void processApiList(AppRequestDTO appRequest) { if (!StringUtils.isEmpty(appRequest.getApiList())) { @@ -192,4 +171,140 @@ public class AppServiceFacade { } } } + + /** + * ApiKeyRegistrationDTO를 기반으로 AppRequest를 생성합니다. + * + * @param registration API Key 등록 정보 + * @param portalOrg 포털 조직 + * @return 생성된 AppRequest DTO + * @throws IOException 파일 처리 중 오류 발생 시 + */ + public AppRequestDTO createAppRequestFromRegistration(ApiKeyRegistrationDTO registration, PortalOrg portalOrg) throws IOException { + // 1. 앱 아이콘 파일 저장 + String appIconFileId = null; + + if (registration.getAppIconData() != null && registration.getAppIconData().length > 0) { + FileInfo fileInfo = fileService.createSingleFileFromBytes(registration.getAppIconFileName(), registration.getAppIconContentType(), registration.getAppIconData()); + appIconFileId = fileInfo.getFileId(); + } + + // 2. AppRequestDTO 생성 및 필드 매핑 + AppRequestDTO appRequestDTO = new AppRequestDTO(); + appRequestDTO.setOrg(portalOrgMapper.toVo(portalOrg)); + appRequestDTO.setType(AppRequestType.NEW); + + // 기본 정보 매핑 + appRequestDTO.setClientName(registration.getAppName()); + appRequestDTO.setAppDescription(registration.getAppDescription()); + appRequestDTO.setCallbackUrl(registration.getCallbackUrl()); + appRequestDTO.setAppIconFileId(appIconFileId); + + // IP 화이트리스트 매핑 (List → comma-separated String) + if (registration.getIpWhitelist() != null && !registration.getIpWhitelist().isEmpty()) { + appRequestDTO.setIpWhitelist(String.join(",", registration.getIpWhitelist())); + } + + // 선택된 API 목록 매핑 (List → comma-separated String) + if (registration.getSelectedApis() != null && !registration.getSelectedApis().isEmpty()) { + appRequestDTO.setApiList(String.join(",", registration.getSelectedApis())); + } + + // 3. 기존 createAppRequest 메소드 호출하여 승인 프로세스 시작 + return createAppRequest(appRequestDTO); + } + + /** + * ApiKeyRegistrationDTO를 기반으로 수정 AppRequest를 생성합니다. + * + * @param registration API Key 수정 정보 + * @param portalOrg 포털 조직 + * @return 생성된 AppRequest DTO + * @throws IOException 파일 처리 중 오류 발생 시 + */ + public AppRequestDTO createModifyRequestFromRegistration(ApiKeyRegistrationDTO registration, PortalOrg portalOrg) throws IOException { + // 1. 기존 Credential 정보 조회 + ClientDTO existingClient = getApiKey(portalOrg.getId(), registration.getClientId()); + if (existingClient == null) { + throw new NotFoundException("Client not found: " + registration.getClientId()); + } + + // 2. 앱 아이콘 파일 저장 (변경된 경우) + String appIconFileId = null; + + // 새로운 아이콘 데이터가 있으면 저장 + if (registration.getAppIconData() != null && registration.getAppIconData().length > 0) { + FileInfo fileInfo = fileService.createSingleFileFromBytes(registration.getAppIconFileName(), registration.getAppIconContentType(), registration.getAppIconData()); + appIconFileId = fileInfo.getFileId(); + } + // 새로운 아이콘이 없으면 기존 아이콘 ID 사용 (registration에서 가져오거나 기존 클라이언트에서 가져옴) + else if (registration.getAppIconFileId() != null) { + appIconFileId = registration.getAppIconFileId(); + } else { + appIconFileId = existingClient.getAppIconFileId(); + } + + // 3. AppRequestDTO 생성 및 필드 매핑 + AppRequestDTO appRequestDTO = new AppRequestDTO(); + appRequestDTO.setOrg(portalOrgMapper.toVo(portalOrg)); + appRequestDTO.setType(AppRequestType.MODIFY); + appRequestDTO.setClientId(registration.getClientId()); // 수정할 클라이언트 ID 설정 + + // 기본 정보 매핑 + appRequestDTO.setClientName(registration.getAppName()); + appRequestDTO.setAppDescription(registration.getAppDescription()); + appRequestDTO.setCallbackUrl(registration.getCallbackUrl()); + appRequestDTO.setAppIconFileId(appIconFileId); + + // IP 화이트리스트 매핑 (List → comma-separated String) + if (registration.getIpWhitelist() != null && !registration.getIpWhitelist().isEmpty()) { + appRequestDTO.setIpWhitelist(String.join(",", registration.getIpWhitelist())); + } + + // 선택된 API 목록 매핑 (List → comma-separated String) + if (registration.getSelectedApis() != null && !registration.getSelectedApis().isEmpty()) { + appRequestDTO.setApiList(String.join(",", registration.getSelectedApis())); + } + + // 4. 기존 createAppRequest 메소드 호출하여 승인 프로세스 시작 + // createAppRequest 내부에서 MODIFY 타입인 경우 기존 API 목록을 prevApiList에 저장함 + return createAppRequest(appRequestDTO); + } + + /** + * 사용자의 비밀번호를 검증합니다. + * Client Secret 조회 등 민감한 작업 수행 시 본인 확인을 위해 사용됩니다. + * + * @param user 인증된 사용자 정보 + * @param password 검증할 비밀번호 (평문) + * @return 비밀번호 일치 여부 + */ + public boolean verifyUserPassword(PortalAuthenticatedUser user, String password) { + if (user == null || password == null) { + return false; + } + + try { + // PasswordEncoder를 사용하여 평문 비밀번호와 저장된 해시를 비교 + return passwordEncoder.matches(password, user.getPassword()); + } catch (Exception e) { + // 비밀번호 검증 중 오류 발생 시 false 반환 (보안상 구체적인 오류 노출 방지) + return false; + } + } + + /** + * API Key(Credential)를 즉시 삭제합니다. + * 승인 프로세스 없이 바로 삭제 처리됩니다. + * + * @param orgId 조직 ID + * @param clientId 삭제할 클라이언트 ID + * @throws NotFoundException 클라이언트를 찾을 수 없는 경우 + */ + public void deleteApp(String orgId, String clientId) { + Credential credential = credentialRepository.findByClientidAndOrgid(clientId, orgId) + .orElseThrow(() -> new NotFoundException("Client not found: " + clientId)); + + credentialRepository.delete(credential); + } } 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 22a1981..fc1ac66 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 @@ -2,7 +2,7 @@ package com.eactive.apim.portal.apps.auth.service; public interface AuthNumberService { - void sendRequestAuthNumber(String recipientKey, String msgType); + String sendRequestAuthNumber(String recipientKey, String msgType); 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 99285ed..3510e60 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 @@ -40,10 +40,11 @@ public class AuthNumberServiceImpl implements AuthNumberService { /** * @param recipientKey * @param msgType + * @return 생성된 인증번호 */ @Override @Transactional(noRollbackFor = AuthNumberException.class) - public void sendRequestAuthNumber(String recipientKey, String msgType) { + public String sendRequestAuthNumber(String recipientKey, String msgType) { logger.info("Sending auth number to: {} via {}", recipientKey, msgType); validateResendTime(recipientKey); @@ -55,6 +56,8 @@ public class AuthNumberServiceImpl implements AuthNumberService { storage.saveAuthNumber(recipientKey, authNumber, LocalDateTime.now().plusSeconds(authNumberExpirationTime)); + + return authNumber; } @Override 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 new file mode 100755 index 0000000..62df1d0 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/apps/commission/constant/Constants.java @@ -0,0 +1,26 @@ +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 new file mode 100755 index 0000000..3fb0963 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/apps/commission/controller/CommissionController.java @@ -0,0 +1,320 @@ +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 new file mode 100755 index 0000000..52281a3 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/apps/commission/dto/CommissionDTO.java @@ -0,0 +1,28 @@ +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 new file mode 100755 index 0000000..d967539 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/apps/commission/dto/CommissionList.java @@ -0,0 +1,32 @@ +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 new file mode 100755 index 0000000..70b1e78 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/apps/commission/dto/CommissionSearch.java @@ -0,0 +1,17 @@ +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 new file mode 100755 index 0000000..ce0f6db --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/apps/commission/exception/ErrorUtil.java @@ -0,0 +1,27 @@ +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 new file mode 100755 index 0000000..8a00cd6 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/apps/commission/exception/ErrorVM.java @@ -0,0 +1,43 @@ +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 new file mode 100755 index 0000000..7b5315f --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/apps/commission/exception/FieldErrorVM.java @@ -0,0 +1,33 @@ +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 new file mode 100755 index 0000000..e55e506 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/apps/commission/service/ExternalService.java @@ -0,0 +1,80 @@ +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/partnershipapplication/controller/PartnershipApplicationController.java b/src/main/java/com/eactive/apim/portal/apps/community/partnership/controller/PartnershipApplicationController.java similarity index 79% rename from src/main/java/com/eactive/apim/portal/apps/community/partnershipapplication/controller/PartnershipApplicationController.java rename to src/main/java/com/eactive/apim/portal/apps/community/partnership/controller/PartnershipApplicationController.java index 46c7e2a..4b053a5 100644 --- a/src/main/java/com/eactive/apim/portal/apps/community/partnershipapplication/controller/PartnershipApplicationController.java +++ b/src/main/java/com/eactive/apim/portal/apps/community/partnership/controller/PartnershipApplicationController.java @@ -1,8 +1,9 @@ -package com.eactive.apim.portal.apps.community.partnershipapplication.controller; +package com.eactive.apim.portal.apps.community.partnership.controller; -import com.eactive.apim.portal.apps.community.partnershipapplication.dto.PartnershipApplicationDTO; -import com.eactive.apim.portal.apps.community.partnershipapplication.service.PartnershipApplicationFacade; +import com.eactive.apim.portal.apps.community.partnership.dto.PartnershipApplicationDTO; +import com.eactive.apim.portal.apps.community.partnership.service.PartnershipApplicationFacade; import com.eactive.apim.portal.common.util.SecurityUtil; +import com.eactive.apim.portal.common.util.UserTypeUtil; import java.io.IOException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; @@ -38,13 +39,14 @@ public class PartnershipApplicationController { } model.addAttribute("partnership", new PartnershipApplicationDTO()); + model.addAttribute("isInternalUser", UserTypeUtil.isCurrentUserInternal()); return "apps/community/mainPartnershipForm"; } @PostMapping public String createPartnershipApplication(@Valid @ModelAttribute PartnershipApplicationDTO partnershipApplicationDTO, - BindingResult bindingResult, HttpServletRequest httpServletRequest, RedirectAttributes redirectAttributes) throws IOException { + BindingResult bindingResult, HttpServletRequest httpServletRequest, RedirectAttributes redirectAttributes) throws IOException { if (bindingResult.hasErrors()) { return "apps/community/mainPartnershipForm"; } diff --git a/src/main/java/com/eactive/apim/portal/apps/community/partnershipapplication/dto/PartnershipApplicationDTO.java b/src/main/java/com/eactive/apim/portal/apps/community/partnership/dto/PartnershipApplicationDTO.java similarity index 88% rename from src/main/java/com/eactive/apim/portal/apps/community/partnershipapplication/dto/PartnershipApplicationDTO.java rename to src/main/java/com/eactive/apim/portal/apps/community/partnership/dto/PartnershipApplicationDTO.java index 72a08d5..7866253 100644 --- a/src/main/java/com/eactive/apim/portal/apps/community/partnershipapplication/dto/PartnershipApplicationDTO.java +++ b/src/main/java/com/eactive/apim/portal/apps/community/partnership/dto/PartnershipApplicationDTO.java @@ -1,4 +1,4 @@ -package com.eactive.apim.portal.apps.community.partnershipapplication.dto; +package com.eactive.apim.portal.apps.community.partnership.dto; import java.util.List; import lombok.AllArgsConstructor; diff --git a/src/main/java/com/eactive/apim/portal/apps/community/partnershipapplication/mapper/PartnershipApplicationMapper.java b/src/main/java/com/eactive/apim/portal/apps/community/partnership/mapper/PartnershipApplicationMapper.java similarity index 61% rename from src/main/java/com/eactive/apim/portal/apps/community/partnershipapplication/mapper/PartnershipApplicationMapper.java rename to src/main/java/com/eactive/apim/portal/apps/community/partnership/mapper/PartnershipApplicationMapper.java index 529d32c..cf45014 100644 --- a/src/main/java/com/eactive/apim/portal/apps/community/partnershipapplication/mapper/PartnershipApplicationMapper.java +++ b/src/main/java/com/eactive/apim/portal/apps/community/partnership/mapper/PartnershipApplicationMapper.java @@ -1,7 +1,7 @@ -package com.eactive.apim.portal.apps.community.partnershipapplication.mapper; +package com.eactive.apim.portal.apps.community.partnership.mapper; -import com.eactive.apim.portal.apps.community.partnershipapplication.dto.PartnershipApplicationDTO; +import com.eactive.apim.portal.apps.community.partnership.dto.PartnershipApplicationDTO; import com.eactive.apim.portal.common.mapper.CommonMapper; import com.eactive.apim.portal.partnershipapplication.entity.PartnershipApplication; import org.mapstruct.Mapper; @@ -9,8 +9,8 @@ import org.mapstruct.ReportingPolicy; import org.springframework.stereotype.Component; @Mapper(componentModel = "spring", - uses = {CommonMapper.class}, - unmappedTargetPolicy = ReportingPolicy.IGNORE) + uses = {CommonMapper.class}, + unmappedTargetPolicy = ReportingPolicy.IGNORE) @Component public interface PartnershipApplicationMapper { diff --git a/src/main/java/com/eactive/apim/portal/apps/community/partnershipapplication/repository/PartnershipApplicationRepository.java b/src/main/java/com/eactive/apim/portal/apps/community/partnership/repository/PartnershipApplicationRepository.java similarity index 86% rename from src/main/java/com/eactive/apim/portal/apps/community/partnershipapplication/repository/PartnershipApplicationRepository.java rename to src/main/java/com/eactive/apim/portal/apps/community/partnership/repository/PartnershipApplicationRepository.java index 8f52467..726f4af 100644 --- a/src/main/java/com/eactive/apim/portal/apps/community/partnershipapplication/repository/PartnershipApplicationRepository.java +++ b/src/main/java/com/eactive/apim/portal/apps/community/partnership/repository/PartnershipApplicationRepository.java @@ -1,4 +1,4 @@ -package com.eactive.apim.portal.apps.community.partnershipapplication.repository; +package com.eactive.apim.portal.apps.community.partnership.repository; import com.eactive.apim.portal.partnershipapplication.entity.PartnershipApplication; import com.eactive.eai.rms.data.EMSDataSource; diff --git a/src/main/java/com/eactive/apim/portal/apps/community/partnershipapplication/service/PartnershipApplicationFacade.java b/src/main/java/com/eactive/apim/portal/apps/community/partnership/service/PartnershipApplicationFacade.java similarity index 51% rename from src/main/java/com/eactive/apim/portal/apps/community/partnershipapplication/service/PartnershipApplicationFacade.java rename to src/main/java/com/eactive/apim/portal/apps/community/partnership/service/PartnershipApplicationFacade.java index 6410d69..4d2289d 100644 --- a/src/main/java/com/eactive/apim/portal/apps/community/partnershipapplication/service/PartnershipApplicationFacade.java +++ b/src/main/java/com/eactive/apim/portal/apps/community/partnership/service/PartnershipApplicationFacade.java @@ -1,6 +1,6 @@ -package com.eactive.apim.portal.apps.community.partnershipapplication.service; +package com.eactive.apim.portal.apps.community.partnership.service; -import com.eactive.apim.portal.apps.community.partnershipapplication.dto.PartnershipApplicationDTO; +import com.eactive.apim.portal.apps.community.partnership.dto.PartnershipApplicationDTO; import java.io.IOException; public interface PartnershipApplicationFacade { diff --git a/src/main/java/com/eactive/apim/portal/apps/community/partnershipapplication/service/PartnershipApplicationFacadeImpl.java b/src/main/java/com/eactive/apim/portal/apps/community/partnership/service/PartnershipApplicationFacadeImpl.java similarity index 59% rename from src/main/java/com/eactive/apim/portal/apps/community/partnershipapplication/service/PartnershipApplicationFacadeImpl.java rename to src/main/java/com/eactive/apim/portal/apps/community/partnership/service/PartnershipApplicationFacadeImpl.java index 38284e8..0cc7d4b 100644 --- a/src/main/java/com/eactive/apim/portal/apps/community/partnershipapplication/service/PartnershipApplicationFacadeImpl.java +++ b/src/main/java/com/eactive/apim/portal/apps/community/partnership/service/PartnershipApplicationFacadeImpl.java @@ -1,7 +1,10 @@ -package com.eactive.apim.portal.apps.community.partnershipapplication.service; +package com.eactive.apim.portal.apps.community.partnership.service; -import com.eactive.apim.portal.apps.community.partnershipapplication.dto.PartnershipApplicationDTO; -import com.eactive.apim.portal.apps.community.partnershipapplication.mapper.PartnershipApplicationMapper; +import com.eactive.apim.portal.apps.community.partnership.dto.PartnershipApplicationDTO; +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.file.entity.FileInfo; import com.eactive.apim.portal.file.service.FileService; import com.eactive.apim.portal.file.service.FileTypeContext; @@ -25,8 +28,15 @@ public class PartnershipApplicationFacadeImpl implements PartnershipApplicationF FileInfo file = null; if (!partnershipApplicationDTO.getFiles().isEmpty()) { - FileTypeContext.setFileType("business"); - file = fileService.createFile(partnershipApplicationDTO.getFiles()); + try { + PortalAuthenticatedUser user = SecurityUtil.getPortalAuthenticatedUser(); + FileService.setInternalUserContext(UserTypeUtil.isInternalUser(user)); + + FileTypeContext.setFileType("business"); + file = fileService.createFile(partnershipApplicationDTO.getFiles()); + } finally { + FileService.clearInternalUserContext(); + } } PartnershipApplication partnershipApplication = partnershipApplicationMapper.toEntity(partnershipApplicationDTO); if (file != null) { diff --git a/src/main/java/com/eactive/apim/portal/apps/community/partnershipapplication/service/PartnershipApplicationService.java b/src/main/java/com/eactive/apim/portal/apps/community/partnership/service/PartnershipApplicationService.java similarity index 80% rename from src/main/java/com/eactive/apim/portal/apps/community/partnershipapplication/service/PartnershipApplicationService.java rename to src/main/java/com/eactive/apim/portal/apps/community/partnership/service/PartnershipApplicationService.java index a00277a..8f76844 100644 --- a/src/main/java/com/eactive/apim/portal/apps/community/partnershipapplication/service/PartnershipApplicationService.java +++ b/src/main/java/com/eactive/apim/portal/apps/community/partnership/service/PartnershipApplicationService.java @@ -1,6 +1,6 @@ -package com.eactive.apim.portal.apps.community.partnershipapplication.service; +package com.eactive.apim.portal.apps.community.partnership.service; -import com.eactive.apim.portal.apps.community.partnershipapplication.repository.PartnershipApplicationRepository; +import com.eactive.apim.portal.apps.community.partnership.repository.PartnershipApplicationRepository; import com.eactive.apim.portal.partnershipapplication.entity.PartnershipApplication; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; 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 6b67b90..5e99a18 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 @@ -5,9 +5,9 @@ import com.eactive.apim.portal.apps.community.qna.dto.InquirySearch; 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 java.io.IOException; -import com.eactive.apim.portal.file.exception.InvalidFileException; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; @@ -68,6 +68,7 @@ public class InquiryController { @GetMapping("/new") public String newInquiryForm(Model model) { model.addAttribute("inquiry", new InquiryDTO()); + model.addAttribute("isInternalUser", UserTypeUtil.isCurrentUserInternal()); return APPS_COMMUNITY_MAIN_INQUIRY_FORM; } @@ -78,6 +79,7 @@ public class InquiryController { public String editInquiryForm(@RequestParam("id") String id, Model model) { InquiryDTO inquiry = inquiryFacade.getMyInquiry(SecurityUtil.getPortalAuthenticatedUser(), id); model.addAttribute("inquiry", inquiry); + model.addAttribute("isInternalUser", UserTypeUtil.isCurrentUserInternal()); return APPS_COMMUNITY_MAIN_INQUIRY_FORM; } @@ -123,11 +125,4 @@ public class InquiryController { redirectAttributes.addFlashAttribute("success", "삭제 되었습니다."); return REDIRECT_INQUIRY; } - - @ExceptionHandler(InvalidFileException.class) - public String handleInvalidFileException(InvalidFileException e, Model model) { - model.addAttribute("inquiry", new InquiryDTO()); - model.addAttribute("error", e.getMessage()); - return "redirect:/inquiry/new"; - } } 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 9f84e7c..5b7af77 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,6 +6,7 @@ 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.file.entity.FileInfo; import com.eactive.apim.portal.file.service.FileService; import com.eactive.apim.portal.file.service.FileTypeContext; @@ -83,8 +84,15 @@ public class InquiryFacadeImpl implements InquiryFacade { FileInfo file = null; if (!inquiryDTO.getFiles().isEmpty()) { - FileTypeContext.setFileType("qna"); - file = fileService.createFile(inquiryDTO.getFiles()); + try { + PortalAuthenticatedUser user = SecurityUtil.getPortalAuthenticatedUser(); + FileService.setInternalUserContext(UserTypeUtil.isInternalUser(user)); + + FileTypeContext.setFileType("qna"); + file = fileService.createFile(inquiryDTO.getFiles()); + } finally { + FileService.clearInternalUserContext(); + } } Inquiry inquiry = inquiryMapper.toEntity(inquiryDTO); @@ -104,12 +112,17 @@ public class InquiryFacadeImpl implements InquiryFacade { 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()) { - FileTypeContext.setFileType("qna"); - FileInfo file = fileService.createFile(inquiryDTO.getFiles()); - if(file != null) { - inquiryDTO.setAttachFile(file.getFileId()); + 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()); diff --git a/src/main/java/com/eactive/apim/portal/apps/dashboard/controller/DashboardHandler.java b/src/main/java/com/eactive/apim/portal/apps/dashboard/controller/DashboardHandler.java index ade2417..89aa474 100644 --- a/src/main/java/com/eactive/apim/portal/apps/dashboard/controller/DashboardHandler.java +++ b/src/main/java/com/eactive/apim/portal/apps/dashboard/controller/DashboardHandler.java @@ -4,7 +4,6 @@ import com.eactive.apim.portal.apps.dashboard.service.DashboardService; import com.eactive.apim.portal.common.pagerouter.PageHandler; import com.eactive.apim.portal.common.util.SecurityUtil; import com.eactive.apim.portal.portalorg.entity.PortalOrg; -import java.util.List; import javax.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; @@ -22,9 +21,7 @@ public class DashboardHandler implements PageHandler { if (SecurityUtil.getPortalAuthenticatedUser() != null && SecurityUtil.getPortalAuthenticatedUser().getPortalOrg() != null) { PortalOrg org = SecurityUtil.getPortalAuthenticatedUser().getPortalOrg(); model.addAttribute("pendingKeyCount", dashboardService.getPendingApiKeyRequestCount(org)); - model.addAttribute("testKeyCount", dashboardService.getTestApiKeyCount(org)); - model.addAttribute("pendingProdCount", dashboardService.getProdApiKeyRequestCount(org)); - model.addAttribute("prodKeyCount", dashboardService.getProdApiKeyCount(org)); + model.addAttribute("testKeyCount", dashboardService.getApiKeyCount(org)); model.addAttribute("totalApi", dashboardService.getTotalApi(org)); model.addAttribute("dailyTotal", dashboardService.getDailyTotalApiUsage(org)); diff --git a/src/main/java/com/eactive/apim/portal/apps/dashboard/service/DashboardService.java b/src/main/java/com/eactive/apim/portal/apps/dashboard/service/DashboardService.java index d227516..b033eda 100644 --- a/src/main/java/com/eactive/apim/portal/apps/dashboard/service/DashboardService.java +++ b/src/main/java/com/eactive/apim/portal/apps/dashboard/service/DashboardService.java @@ -5,24 +5,18 @@ import com.eactive.apim.portal.app.entity.Credential; import com.eactive.apim.portal.app.repository.CredentialRepository; import com.eactive.apim.portal.apprequest.entity.AppRequestType; import com.eactive.apim.portal.apprequest.repository.AppRequestRepository; -import com.eactive.apim.portal.approval.entity.ApprovalStatus; import com.eactive.apim.portal.approval.statemachine.ProcessingState; import com.eactive.apim.portal.approval.statemachine.RequestedState; -import com.eactive.apim.portal.apps.approval.service.ApprovalService; import com.eactive.apim.portal.apps.dashboard.ApiDailyStatistics; import com.eactive.apim.portal.apps.dashboard.repository.PortalStatisticsRepository; import com.eactive.apim.portal.apps.dashboard.repository.PortalStatisticsRepository.TopApiUsage; import com.eactive.apim.portal.common.util.DateUtil; import com.eactive.apim.portal.portalorg.entity.PortalOrg; -import java.text.SimpleDateFormat; import java.time.LocalDate; -import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Arrays; -import java.util.Calendar; import java.util.Collections; -import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -39,15 +33,12 @@ import org.springframework.transaction.annotation.Transactional; @Service public class DashboardService { - private final static String PROD_SERVER = "PROD"; - private final static String STG_SERVER = "STG"; - private final AppRequestRepository appRequestRepository; private final CredentialRepository credentialRepository; private final PortalStatisticsRepository portalStatisticsRepository; - public int getTestApiKeyCount(PortalOrg org) { - return credentialRepository.countAllByOrgidAndServer(org.getId(), STG_SERVER); + public int getApiKeyCount(PortalOrg org) { + return credentialRepository.countAllByOrgid(org.getId()); } public int getPendingApiKeyRequestCount(PortalOrg org) { @@ -55,18 +46,9 @@ public class DashboardService { Arrays.asList(new ProcessingState(), new RequestedState())); } - public int getProdApiKeyRequestCount(PortalOrg org) { - return appRequestRepository.countAllByOrgAndTypeIsInAndApproval_ApprovalStatusIn(org, Arrays.asList(AppRequestType.PROD_NEW, AppRequestType.PROD_MODIFY, AppRequestType.PROD_DELETE), - Arrays.asList(new ProcessingState(), new RequestedState())); - } - - public int getProdApiKeyCount(PortalOrg org) { - return credentialRepository.findAllByOrgidAndServer(org.getId(), PROD_SERVER).size(); - } - public int getTotalApi(PortalOrg org) { - List credentials = credentialRepository.findAllByOrgidAndServer(org.getId(), PROD_SERVER); + List credentials = credentialRepository.findAllByOrgid(org.getId()); return (int) credentials.stream() .flatMap(credential -> credential.getApiList().stream()) .map(ApiSpecInfo::getApiId) diff --git a/src/main/java/com/eactive/apim/portal/apps/file/controller/FileDownloadController.java b/src/main/java/com/eactive/apim/portal/apps/file/controller/FileDownloadController.java index 2ff632d..a9ee9ea 100644 --- a/src/main/java/com/eactive/apim/portal/apps/file/controller/FileDownloadController.java +++ b/src/main/java/com/eactive/apim/portal/apps/file/controller/FileDownloadController.java @@ -39,4 +39,54 @@ public class FileDownloadController { IOUtils.write(fileContents, response.getOutputStream()); response.flushBuffer(); } + + /** + * 이미지 인라인 표시용 (브라우저에서 직접 렌더링) + * 약관 등에서 이미지를 표시할 때 사용 + */ + @GetMapping(value = "/view") + public void viewImage(@RequestParam(value = "fileId") String fileId, + @RequestParam(value = "fileSn", defaultValue = "1") int fileSn, + HttpServletResponse response) throws IOException { + + FileDetail fileDetail = fileService.getFileDetailByIds(fileId, fileSn); + + // Content-Type 설정 (이미지용) + String contentType = getImageContentType(fileDetail.getFileExtension()); + response.setContentType(contentType); + response.setContentLength(fileDetail.getFileSize()); + + // inline으로 설정 (브라우저에서 직접 표시) + response.setHeader("Content-Disposition", "inline"); + + // 캐시 설정 (1일) + response.setHeader("Cache-Control", "public, max-age=86400"); + + byte[] fileContents = fileService.retrieveFileContent(fileDetail); + IOUtils.write(fileContents, response.getOutputStream()); + response.flushBuffer(); + } + + private String getImageContentType(String extension) { + if (extension == null) { + return "application/octet-stream"; + } + switch (extension.toLowerCase()) { + case "jpg": + case "jpeg": + return "image/jpeg"; + case "png": + return "image/png"; + case "gif": + return "image/gif"; + case "bmp": + return "image/bmp"; + case "webp": + return "image/webp"; + case "svg": + return "image/svg+xml"; + default: + return "application/octet-stream"; + } + } } 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 433870d..c254ed9 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 @@ -6,6 +6,7 @@ import com.eactive.apim.portal.apps.user.dto.PortalUserDTO; import com.eactive.apim.portal.apps.user.service.PortalUserAuthService; import java.util.HashMap; +import java.util.List; import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -79,7 +80,7 @@ public class AccountRecoveryController { } try { - PortalUserDTO foundId = portalUserAuthService.findUsersByNameAndMobile(findIdDTO.getUserName(), findIdDTO.getMobileNumber()); + List foundUsers = portalUserAuthService.findAllUsersByNameAndMobile(findIdDTO.getUserName(), findIdDTO.getMobileNumber()); // 인증 관련 세션 정보 제거 session.removeAttribute("mobileNumber"); @@ -87,7 +88,7 @@ public class AccountRecoveryController { //redirect 가 아닌 경우에는 model 을 사용해서 페이지 렌더링 처리 model.addAttribute("findIdDTO", findIdDTO); - model.addAttribute("foundId", foundId); + model.addAttribute("foundUsers", foundUsers); return ACCOUNT_RECOVERY_RESULT; } catch (UserNotFoundException e) { @@ -112,14 +113,23 @@ public class AccountRecoveryController { // 세션에서 인증 여부 확인 Boolean isVerified = (Boolean) session.getAttribute("isVerified"); + String sessionMobileNumber = (String) session.getAttribute("mobileNumber"); + if (isVerified == null || !isVerified) { setModelForError(redirectAttributes, "resetPassword", null, "휴대폰 번호 인증을 먼저 진행해주세요.", null, passwordReset); return "redirect:/account_recovery?tab=resetPassword"; } + // 인증된 전화번호와 입력된 전화번호 일치 여부 확인 + if (passwordReset.getMobileNumber() == null || + !passwordReset.getMobileNumber().replace("-", "").equals(sessionMobileNumber)) { + setModelForError(redirectAttributes, "resetPassword", null, "인증된 휴대폰 번호와 입력한 번호가 일치하지 않습니다.", null, passwordReset); + return "redirect:/account_recovery?tab=resetPassword"; + } + try { // 사용자 정보 확인 - portalUserAuthService.initializePassword( + portalUserAuthService.resetPassword( passwordReset.getLoginId(), passwordReset.getUserName(), passwordReset.getMobileNumber() 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 2a51fab..1f6716b 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,37 +1,27 @@ package com.eactive.apim.portal.apps.main.controller; -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.apps.apiservice.dto.ApiServiceApiDTO; import com.eactive.apim.portal.apps.apiservice.dto.ApiServiceDTO; -import com.eactive.apim.portal.apps.community.notice.dto.PortalNoticeDTO; -import com.eactive.apim.portal.apps.community.notice.service.PortalNoticeFacade; +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.util.SecurityUtil; - -import java.util.*; -import java.util.stream.Collectors; - +import java.util.List; 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.RequestParam; + @Controller @RequiredArgsConstructor public class IndexController { - private final ApiSearchFacade apiSearchFacade; private final MainApiFacade mainApiFacade; - private final PortalNoticeFacade portalNoticeFacade; + private final IndexStatisticsService indexStatisticsService; /** * 메인 페이지 API는 Portal Property 에 main.service.list 에 등록된 그룹을 기준으로 API를 조회함. * 프로퍼티에 등록되어 있어도 어드민의 그룹 표시 설정이 N이면 표시 되지 않음. - * 어드민의 API 스펙의 표시 설정이 N이면 표시되지 않음. - * 그룹 또는 권한이 지정된 경우, 해당사용자의 권한 또는 기관이 일치해야 표시됨. + * 어드민의 API 스펙의 표시 설정이 N이면 표시되지 않음. 그룹 또는 권한이 지정된 경우, 해당사용자의 권한 또는 기관이 일치해야 표시됨. *

    * 검색어는 Portal Property 또는 메인 관리 화면에 main.keyword.list 에 등록된 내용이 표시 됨. * @@ -40,147 +30,28 @@ public class IndexController { */ @GetMapping("/") public String index(Model model) { - // Step 1: Fetch and sort API services - List apiServices = getSortedApiServices(null); - // Step 2: Create a map of API ID to ApiServiceDTO for icon mapping - Map mainIconsMap = createMainIconsMap(apiServices); - - // Step 3: Merge all API lists from apiServices - List allApis = getAllApis(apiServices); - - // Step 4: Enrich API spec info DTOs with main icon and service info - List apiSpecInfoDtos = enrichApiSpecInfo(apiServices, allApis, mainIconsMap); - - // Step 5: Calculate total API count - int totalApiCount = calculateTotalApiCount(apiServices); - - // Step 6: Fetch portal notices and hashtags - List portalNotices = portalNoticeFacade.getLatestNotices(); + List services = mainApiFacade.getOpenApiServices(); List hashTags = mainApiFacade.getHashTags(); - ApiGroupSearch search = new ApiGroupSearch(); - Map searchResult = apiSearchFacade.searchApis(search); + // 인덱스 페이지 하단 통계 조회 (캐싱 적용) + IndexStatisticsDTO statistics = indexStatisticsService.getStatistics(); - int selectedApiCount = (int) searchResult.get("selectedApiCount"); - - // 서비스 이용 수 - int serviceCount = ((List) searchResult.get("services")).size(); - - // 승인된 기업 건수 - int approvedOrgCount = mainApiFacade.getApprovedOrgCount(); - - // Step 7: Add attributes to model - addAttributesToModel(model, apiServices, apiSearchFacade.sortApisByService(apiSpecInfoDtos, apiServices), totalApiCount, portalNotices, hashTags, serviceCount, approvedOrgCount, selectedApiCount); + addAttributesToModel(model, services, hashTags, statistics); return "apps/main/index"; } - private List getSortedApiServices(String apiId) { - List apiServices = mainApiFacade.getOpenApiServices(apiId); - apiServices.sort(Comparator.comparing(ApiServiceDTO::getDisplayOrder)); - return apiServices; - } - - private Map createMainIconsMap(List apiServices) { - Map mainIconsMap = new HashMap<>(); - apiServices.forEach(service -> - service.getApiGroupApiList().forEach(api -> - mainIconsMap.put(api.getApiId(), service) - ) - ); - return mainIconsMap; - } - - private List getAllApis(List apiServices) { - return apiServices.stream() - .flatMap(service -> service.getApiGroupApiList().stream()) - .collect(Collectors.toList()); - } - - private List enrichApiSpecInfo(List services, List allApis, Map mainIconsMap) { - List apiIds = allApis.stream().map(ApiServiceApiDTO::getApiId).collect(Collectors.toList()); - List apiSpecInfoDtos = mainApiFacade.getOpenApis(apiIds); - apiSpecInfoDtos.forEach(spec -> { - ApiServiceDTO serviceDTO = mainIconsMap.get(spec.getApiId()); - if (serviceDTO != null) { - spec.setMainIcon(serviceDTO.getMainIcon()); - spec.setService(serviceDTO.getGroupName()); - } - }); - - boolean isAuthenticated = SecurityUtil.isAuthenticated(); - String roleCode; - String org; - - if (isAuthenticated) { - roleCode = SecurityUtil.getPortalAuthenticatedUser().getRoleCode().toString(); - org = SecurityUtil.getUserOrg().getId(); - } else { - roleCode = null; - org = null; - } - - List filteredApiSpecInfoDtos = apiSpecInfoDtos.stream() - .filter(spec -> { - if (!isAuthenticated) { - return spec.getDisplayOrg() == null && spec.getDisplayRoleCode() == null; - } - boolean orgMatch = false; - if (org != null) { - orgMatch = spec.getDisplayOrg() == null || spec.getDisplayOrg().isEmpty() || spec.getDisplayOrg().contains(org); - } - boolean roleMatch = spec.getDisplayRoleCode() == null || spec.getDisplayRoleCode().isEmpty() || spec.getDisplayRoleCode().contains(roleCode); - return orgMatch || roleMatch; - }) - .collect(Collectors.toList()); - - Set filteredApiIds = filteredApiSpecInfoDtos.stream() - .map(ApiSpecInfoDto::getApiId) - .collect(Collectors.toSet()); - - services.forEach(service -> { - if (service.getApiGroupApiList() != null) { - List filteredApiList = service.getApiGroupApiList().stream() - .filter(api -> filteredApiIds.contains(api.getApiId())) - .collect(Collectors.toList()); - service.setApiGroupApiList(filteredApiList); - } - }); - - return filteredApiSpecInfoDtos.stream().collect(Collectors.toList()); - } - - private int calculateTotalApiCount(List apiServices) { - return apiServices.stream() - .mapToInt(service -> service.getApiGroupApiList().size()) - .sum(); - } - - private void addAttributesToModel(Model model, List apiServices, - List apiSpecInfoDtos, int totalApiCount, - List portalNotices, List hashTags, int serviceCount, int approvedOrgCount, int selectedApiCount) { + private void addAttributesToModel(Model model, List apiServices, List hashTags, IndexStatisticsDTO statistics) { model.addAttribute("services", apiServices); - model.addAttribute("apis", apiSpecInfoDtos); - model.addAttribute("totalApiCount", totalApiCount); - model.addAttribute("portalNotices", portalNotices); model.addAttribute("hashtags", hashTags); - model.addAttribute("serviceCount", serviceCount); - model.addAttribute("approvedOrgCount", approvedOrgCount); - model.addAttribute("selectedApiCount", selectedApiCount); + // 인덱스 페이지 하단 통계 + model.addAttribute("statisticsVisible", statistics.isVisible()); + model.addAttribute("approvedOrgCount", statistics.getApprovedOrgCount()); + model.addAttribute("serviceCount", statistics.getActiveAppCount()); + model.addAttribute("totalApiCount", statistics.getTotalApiCount()); } - @GetMapping("/api_fragments") - public String getApiFragments(@RequestParam(value = "id", required = false) String id, Model model) { - List apiServices = getSortedApiServices(id); - Map mainIconsMap = createMainIconsMap(apiServices); - List allApis = getAllApis(apiServices); - List apiSpecInfoDtos = enrichApiSpecInfo(apiServices, allApis, mainIconsMap); - model.addAttribute("apis", apiSpecInfoDtos); - return "apps/main/apiListFragment :: apiListFragment"; - } - - } diff --git a/src/main/java/com/eactive/apim/portal/apps/main/dto/IndexStatisticsDTO.java b/src/main/java/com/eactive/apim/portal/apps/main/dto/IndexStatisticsDTO.java new file mode 100644 index 0000000..03039d6 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/apps/main/dto/IndexStatisticsDTO.java @@ -0,0 +1,44 @@ +package com.eactive.apim.portal.apps.main.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 인덱스 페이지 하단 통계 DTO + * - API 활용 기업: 법인으로 등록된 수의 합계 (정상 상태) + * - 서비스 이용 수: 전체 법인이 생성한 앱의 합계 (이용 가능 상태) + * - API 이용 건수: 전체 법인이 생성한 앱의 API 수의 합계 (이용 가능 상태) + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class IndexStatisticsDTO { + + /** + * 통계 섹션 노출 여부 + * PortalProperty의 main.statistics.display 값으로 설정 (기본값: true) + */ + @Builder.Default + private boolean visible = true; + + /** + * API 활용 기업 수 + * 법인으로 등록되고 정상 상태인 기업의 수 + */ + private int approvedOrgCount; + + /** + * 서비스 이용 수 + * 정상 상태 법인이 생성한 앱 중 이용 가능 상태인 앱의 수 + */ + private int activeAppCount; + + /** + * API 이용 건수 + * 정상 상태 법인이 생성한 이용 가능 앱에 연결된 API의 총 수 + */ + private int totalApiCount; +} diff --git a/src/main/java/com/eactive/apim/portal/apps/main/service/IndexStatisticsService.java b/src/main/java/com/eactive/apim/portal/apps/main/service/IndexStatisticsService.java new file mode 100644 index 0000000..609e3d1 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/apps/main/service/IndexStatisticsService.java @@ -0,0 +1,161 @@ +package com.eactive.apim.portal.apps.main.service; + +import com.eactive.apim.portal.app.entity.Credential; +import com.eactive.apim.portal.app.repository.CredentialRepository; +import com.eactive.apim.portal.apps.main.dto.IndexStatisticsDTO; +import com.eactive.apim.portal.apps.user.repository.PortalOrgRepository; +import com.eactive.apim.portal.portalorg.entity.PortalOrgEnums.ApprovalStatus; +import com.eactive.apim.portal.portalorg.entity.PortalOrgEnums.OrgStatus; +import com.eactive.apim.portal.portalproperty.service.PortalPropertyService; +import java.time.LocalDateTime; +import java.util.List; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +/** + * 인덱스 페이지 통계 서비스 + * 메모리 캐싱을 통해 DB 조회를 최소화 (1시간 TTL) + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class IndexStatisticsService { + + private static final long CACHE_TTL_HOURS = 1; + private static final String APP_STATUS_ACTIVE = "1"; + private static final String PORTAL_PROPERTY_GROUP = "Portal"; + private static final String STATISTICS_DISPLAY_KEY = "main.statistics.display"; + private static final String DISPLAY_YES = "Y"; + + private final PortalOrgRepository portalOrgRepository; + private final CredentialRepository credentialRepository; + private final PortalPropertyService portalPropertyService; + + // 캐시된 통계 데이터 + private volatile IndexStatisticsDTO cachedStatistics; + // 마지막 집계 시간 + private volatile LocalDateTime lastAggregatedTime; + + /** + * 인덱스 페이지 통계 조회 + * 캐시가 없거나 1시간이 경과한 경우 재집계 + */ + @Transactional(readOnly = true) + public IndexStatisticsDTO getStatistics() { + if (shouldRefreshCache()) { + synchronized (this) { + // Double-check locking + if (shouldRefreshCache()) { + refreshStatistics(); + } + } + } + return cachedStatistics; + } + + /** + * 캐시 갱신이 필요한지 확인 + */ + private boolean shouldRefreshCache() { + if (cachedStatistics == null || lastAggregatedTime == null) { + return true; + } + return LocalDateTime.now().isAfter(lastAggregatedTime.plusHours(CACHE_TTL_HOURS)); + } + + /** + * 통계 재집계 + */ + private void refreshStatistics() { + log.info("인덱스 페이지 통계 집계 시작"); + long startTime = System.currentTimeMillis(); + + try { + // 0. 노출 여부 확인 (PortalProperty에서 조회, 기본값: Y) + boolean visible = isStatisticsVisible(); + + // 1. API 활용 기업 수: 정상 상태이고 승인 완료된 기관 수 + int approvedOrgCount = (int) portalOrgRepository.countByOrgStatusAndApprovalStatus( + OrgStatus.ACTIVE, ApprovalStatus.COMPLETED); + + // 2. 정상 상태 기관의 ID 목록 조회 + List activeOrgIds = portalOrgRepository.findIdsByOrgStatusAndApprovalStatus( + OrgStatus.ACTIVE, ApprovalStatus.COMPLETED); + + int activeAppCount = 0; + int totalApiCount = 0; + + if (!activeOrgIds.isEmpty()) { + // 3. 서비스 이용 수: 정상 기관의 이용 가능 앱 수 + activeAppCount = (int) credentialRepository.countActiveAppsByOrgIds(activeOrgIds); + + // 4. API 이용 건수: 정상 기관의 이용 가능 앱에 연결된 API 수 + List activeApps = credentialRepository.findActiveAppsByOrgIds(activeOrgIds); + totalApiCount = activeApps.stream() + .mapToInt(credential -> credential.getApiList() != null ? credential.getApiList().size() : 0) + .sum(); + } + + cachedStatistics = IndexStatisticsDTO.builder() + .visible(visible) + .approvedOrgCount(approvedOrgCount) + .activeAppCount(activeAppCount) + .totalApiCount(totalApiCount) + .build(); + + lastAggregatedTime = LocalDateTime.now(); + + long elapsed = System.currentTimeMillis() - startTime; + log.info("인덱스 페이지 통계 집계 완료 - 소요시간: {}ms, 노출: {}, 기업: {}, 앱: {}, API: {}", + elapsed, visible, approvedOrgCount, activeAppCount, totalApiCount); + + } catch (Exception e) { + log.error("인덱스 페이지 통계 집계 실패", e); + // 기존 캐시가 없는 경우 기본값 설정 + if (cachedStatistics == null) { + cachedStatistics = IndexStatisticsDTO.builder() + .visible(true) + .approvedOrgCount(0) + .activeAppCount(0) + .totalApiCount(0) + .build(); + } + } + } + + /** + * PortalProperty에서 통계 노출 여부 조회 + * 프로퍼티가 없으면 기본값 "Y"를 DB에 저장 후 반환 + */ + private boolean isStatisticsVisible() { + String displayValue = portalPropertyService.getOrCreateProperty( + PORTAL_PROPERTY_GROUP, + STATISTICS_DISPLAY_KEY, + DISPLAY_YES, + "인덱스 페이지 하단 통계 섹션 노출 여부 (Y: 노출, N: 숨김)" + ); + return DISPLAY_YES.equalsIgnoreCase(displayValue); + } + + /** + * 캐시 강제 갱신 (관리자용) + */ + public void forceRefresh() { + synchronized (this) { + lastAggregatedTime = null; + refreshStatistics(); + } + } + + /** + * 캐시 만료 시간 조회 + */ + public LocalDateTime getCacheExpireTime() { + if (lastAggregatedTime == null) { + return null; + } + return lastAggregatedTime.plusHours(CACHE_TTL_HOURS); + } +} diff --git a/src/main/java/com/eactive/apim/portal/apps/main/service/MainApiFacade.java b/src/main/java/com/eactive/apim/portal/apps/main/service/MainApiFacade.java index f0b07ba..d802bd4 100644 --- a/src/main/java/com/eactive/apim/portal/apps/main/service/MainApiFacade.java +++ b/src/main/java/com/eactive/apim/portal/apps/main/service/MainApiFacade.java @@ -7,17 +7,13 @@ import com.eactive.apim.portal.apps.apiservice.dto.ApiServiceDTO; import com.eactive.apim.portal.apps.apiservice.service.ApiServiceService; import com.eactive.apim.portal.apps.user.service.PortalOrgService; import com.eactive.apim.portal.portalproperty.service.PortalPropertyService; - import java.util.Arrays; -import java.util.Collections; import java.util.List; import java.util.Map; import java.util.stream.Collectors; - import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import org.springframework.util.StringUtils; @Transactional @Service @@ -34,17 +30,13 @@ public class MainApiFacade { return portalOrgService.getActiveApprovedOrgCount(); } - public List getOpenApiServices(String serviceId) { + public List getOpenApiServices() { ApiGroupSearch search = new ApiGroupSearch(); Map property = portalPropertyService.getPortalPropertiesAsMap(PORTAL_PROPERTY); String services = property.getOrDefault("main.service.list", ""); - if (StringUtils.hasText(serviceId) && services.contains(serviceId)) { - search.setGroupIds(Collections.singletonList(serviceId)); - } else { - search.setGroupIds(Arrays.asList(services.split(","))); - } + search.setGroupIds(Arrays.asList(services.replace(" ", "").split(","))); return apiServiceService.searchApiGroups(search); } diff --git a/src/main/java/com/eactive/apim/portal/apps/proxy/ApimProxyController.java b/src/main/java/com/eactive/apim/portal/apps/proxy/ApimProxyController.java deleted file mode 100644 index 13b5b8d..0000000 --- a/src/main/java/com/eactive/apim/portal/apps/proxy/ApimProxyController.java +++ /dev/null @@ -1,146 +0,0 @@ -package com.eactive.apim.portal.apps.proxy; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.ObjectNode; -import java.net.URI; -import java.nio.charset.StandardCharsets; -import java.time.Duration; -import javax.servlet.http.HttpServletRequest; -import lombok.extern.slf4j.Slf4j; -import org.springframework.boot.web.client.RestTemplateBuilder; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestHeader; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.client.HttpStatusCodeException; -import org.springframework.web.client.RestTemplate; -import org.springframework.web.util.UriComponentsBuilder; - -@RestController -@RequestMapping("/_proxy") // /proxy 대신 /_proxy 사용 -@Slf4j -public class ApimProxyController { - - private final RestTemplate restTemplate; - private final ProxyProperties proxyProperties; - private final ObjectMapper objectMapper; - private final MediaType JSON_UTF8 = new MediaType(MediaType.APPLICATION_JSON, StandardCharsets.UTF_8); - - - public ApimProxyController( - RestTemplateBuilder restTemplateBuilder, - ProxyProperties proxyProperties, - ObjectMapper objectMapper) { - this.proxyProperties = proxyProperties; - this.objectMapper = objectMapper; - this.restTemplate = restTemplateBuilder - .setConnectTimeout(Duration.ofMillis(proxyProperties.getTimeout().getConnect())) - .setReadTimeout(Duration.ofMillis(proxyProperties.getTimeout().getRead())) - .build(); - } - - private ResponseEntity createJsonResponse(HttpStatus status, String message) { - ObjectNode jsonResponse = objectMapper.createObjectNode() - .put("status", status.value()) - .put("message", message); - return ResponseEntity - .status(status) - .contentType(JSON_UTF8) - .body(jsonResponse.toString()); - } - - @RequestMapping(value = "/**") - public ResponseEntity proxyRequest( - HttpServletRequest request, - @RequestBody(required = false) String body, - @RequestHeader HttpHeaders headers) { - - String targetKey = headers.getFirst("target_host"); - if (targetKey == null || targetKey.trim().isEmpty()) { - log.error("Missing or empty target_host header"); - return createJsonResponse(HttpStatus.BAD_REQUEST, "Error: target_host header is required"); - } - - String targetUrl = proxyProperties.getTargets().get(targetKey.trim().toLowerCase()); - if (targetUrl == null) { - log.error("Invalid target_host: {}. Available targets: {}", - targetKey, proxyProperties.getTargets().keySet()); - return createJsonResponse(HttpStatus.BAD_REQUEST, - "Error: Invalid target_host. Must be one of: " + - String.join(", ", proxyProperties.getTargets().keySet())); - } - - try { - String requestUrl = request.getRequestURI().replace("/_proxy", ""); - URI uri = UriComponentsBuilder - .fromHttpUrl(targetUrl + requestUrl) - .query(request.getQueryString()) - .build() - .toUri(); - - HttpMethod method = HttpMethod.valueOf(request.getMethod()); - - HttpHeaders proxyHeaders = new HttpHeaders(); - headers.forEach((key, value) -> { - if (!key.equalsIgnoreCase("host") && - !key.equalsIgnoreCase("target_host")) { - proxyHeaders.addAll(key, value); - } - }); - - HttpEntity httpEntity = new HttpEntity<>(body, proxyHeaders); - - log.debug("Forwarding request to: {} ({})", targetKey, uri); - ResponseEntity response = restTemplate.exchange( - uri, - method, - httpEntity, - String.class - ); - log.debug("Received response status: {}", response.getStatusCode()); - - // Create JSON response with the original response data - ObjectNode jsonResponse = objectMapper.createObjectNode() - .put("status", response.getStatusCode().value()); - - // Try to parse the response body as JSON, if it fails, include it as a string - try { - jsonResponse.set("data", objectMapper.readTree(response.getBody())); - } catch (Exception e) { - jsonResponse.put("data", response.getBody()); - } - - return ResponseEntity - .status(response.getStatusCode()) - .contentType(JSON_UTF8) - .body(jsonResponse.toString()); - - } catch (HttpStatusCodeException e) { - log.error("Target server returned error: {} for host: {}", e.getStatusCode(), targetKey); - ObjectNode jsonResponse = objectMapper.createObjectNode() - .put("status", e.getStatusCode().value()); - - // Try to parse the error response body as JSON, if it fails, include it as a string - try { - jsonResponse.set("data", objectMapper.readTree(e.getResponseBodyAsString())); - } catch (Exception ex) { - jsonResponse.put("data", e.getResponseBodyAsString()); - } - - return ResponseEntity - .status(e.getStatusCode()) - .contentType(JSON_UTF8) - .body(jsonResponse.toString()); - - } catch (Exception e) { - log.error("Error during proxy request to: {}", targetKey, e); - return createJsonResponse(HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage()); - } - } -} diff --git a/src/main/java/com/eactive/apim/portal/apps/proxy/ProxyProperties.java b/src/main/java/com/eactive/apim/portal/apps/proxy/ProxyProperties.java deleted file mode 100644 index 3f021d9..0000000 --- a/src/main/java/com/eactive/apim/portal/apps/proxy/ProxyProperties.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.eactive.apim.portal.apps.proxy; - -import java.util.Map; -import lombok.Getter; -import lombok.Setter; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.context.annotation.Configuration; - -@Configuration -@ConfigurationProperties(prefix = "proxy") -@Getter -@Setter -public class ProxyProperties { - private Map targets; - private Timeout timeout = new Timeout(); - - @Getter - @Setter - public static class Timeout { - private int connect = 5000; - private int read = 5000; - } -} 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 new file mode 100644 index 0000000..1c3ad11 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/apps/statistics/controller/ApiStatisticsController.java @@ -0,0 +1,112 @@ +package com.eactive.apim.portal.apps.statistics.controller; + +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.service.ApiStatisticsService; +import com.eactive.apim.portal.common.util.SecurityUtil; +import com.eactive.apim.portal.portalorg.entity.PortalOrg; +import java.io.IOException; +import java.time.LocalDate; +import javax.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +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.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; + +/** + * API 통계 Controller + */ +@Slf4j +@Controller +@RequestMapping("/statistics/api") +@RequiredArgsConstructor +public class ApiStatisticsController { + + private final ApiStatisticsService apiStatisticsService; + + /** + * 통계 페이지 진입 + */ + @GetMapping + public String statisticsPage(Model model) { + PortalOrg org = getPortalOrg(); + if (org == null) { + return "redirect:/login"; + } + + String orgId = org.getId(); + + // 앱 목록 조회 (선택용) + model.addAttribute("appList", apiStatisticsService.getAppListByOrg(orgId)); + + // 기본 조회 (7일 전 ~ 오늘, 전체 앱) + ApiStatisticsSearchDto searchDto = new ApiStatisticsSearchDto(); + searchDto.setStartDate(LocalDate.now().minusDays(7)); + searchDto.setEndDate(LocalDate.now()); + searchDto.setClientId(null); + + ApiStatisticsResultDto result = apiStatisticsService.getStatistics(orgId, searchDto); + model.addAttribute("summary", result.getSummary()); + model.addAttribute("details", result.getDetails()); + model.addAttribute("searchDto", searchDto); + + return "apps/statistics/apiStatistics"; + } + + /** + * 통계 조회 (AJAX) + */ + @PostMapping("/search") + @ResponseBody + public ResponseEntity searchStatistics(@RequestBody ApiStatisticsSearchDto searchDto) { + PortalOrg org = getPortalOrg(); + if (org == null) { + return ResponseEntity.status(401).build(); + } + + // 날짜 범위 검증 (최대 40일) + if (!searchDto.isValidDateRange()) { + return ResponseEntity.badRequest() + .body(java.util.Collections.singletonMap("error", + "조회 기간은 최대 " + ApiStatisticsSearchDto.MAX_DATE_RANGE_DAYS + "일까지 가능합니다.")); + } + + String orgId = org.getId(); + ApiStatisticsResultDto result = apiStatisticsService.getStatistics(orgId, searchDto); + return ResponseEntity.ok(result); + } + + /** + * CSV 다운로드 + */ + @GetMapping("/download") + public void downloadCsv(ApiStatisticsSearchDto searchDto, HttpServletResponse response) throws IOException { + PortalOrg org = getPortalOrg(); + if (org == null) { + response.sendError(401, "Unauthorized"); + return; + } + + // 날짜 범위 검증 (최대 40일) + if (!searchDto.isValidDateRange()) { + response.sendError(400, "조회 기간은 최대 " + ApiStatisticsSearchDto.MAX_DATE_RANGE_DAYS + "일까지 가능합니다."); + return; + } + + String orgId = org.getId(); + apiStatisticsService.downloadCsv(orgId, searchDto, response); + } + + private PortalOrg getPortalOrg() { + if (SecurityUtil.getPortalAuthenticatedUser() != null) { + return SecurityUtil.getPortalAuthenticatedUser().getPortalOrg(); + } + return null; + } +} 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 new file mode 100644 index 0000000..16e6038 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/apps/statistics/dto/ApiStatisticsDetailDto.java @@ -0,0 +1,54 @@ +package com.eactive.apim.portal.apps.statistics.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 개별 API 통계 상세 DTO + */ +@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; // 실패율 (%) + + /** + * Repository 조회용 생성자 + */ + public ApiStatisticsDetailDto(String apiId, String apiName, Long attemptedCount, Long completedCount) { + this.apiId = apiId; + this.apiName = apiName; + this.attemptedCount = attemptedCount != null ? attemptedCount : 0L; + this.completedCount = completedCount != null ? completedCount : 0L; + } + + /** + * 실패건, 성공률, 실패율 계산 + */ + public void calculateRates() { + if (attemptedCount == null) { + attemptedCount = 0L; + } + if (completedCount == null) { + completedCount = 0L; + } + + 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; + } else { + this.successRate = 0.0; + this.failureRate = 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 new file mode 100644 index 0000000..b254cb6 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/apps/statistics/dto/ApiStatisticsResultDto.java @@ -0,0 +1,29 @@ +package com.eactive.apim.portal.apps.statistics.dto; + +import java.util.ArrayList; +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * API 통계 조회 결과 DTO + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ApiStatisticsResultDto { + + private ApiStatisticsSummaryDto summary; + private List details; + + /** + * 빈 결과 생성 + */ + public static ApiStatisticsResultDto empty() { + return new ApiStatisticsResultDto( + ApiStatisticsSummaryDto.empty(), + 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 new file mode 100644 index 0000000..87de30e --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/apps/statistics/dto/ApiStatisticsSearchDto.java @@ -0,0 +1,66 @@ +package com.eactive.apim.portal.apps.statistics.dto; + +import java.time.LocalDate; +import java.time.temporal.ChronoUnit; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +/** + * API 통계 검색 조건 DTO + */ +@Data +public class ApiStatisticsSearchDto { + + /** + * 최대 조회 가능 일수 + */ + public static final int MAX_DATE_RANGE_DAYS = 40; + + @DateTimeFormat(pattern = "yyyy-MM-dd") + private LocalDate startDate; + + @DateTimeFormat(pattern = "yyyy-MM-dd") + private LocalDate endDate; + + /** + * 선택된 앱의 clientId (null 또는 빈값이면 전체 앱) + */ + private String clientId; + + public ApiStatisticsSearchDto() { + this.startDate = LocalDate.now().minusDays(7); + this.endDate = LocalDate.now(); + this.clientId = null; + } + + /** + * 특정 앱이 선택되었는지 여부 + */ + public boolean hasSelectedApp() { + return clientId != null && !clientId.trim().isEmpty(); + } + + /** + * 날짜 범위가 유효한지 검증 (최대 40일) + */ + public boolean isValidDateRange() { + if (startDate == null || endDate == null) { + return false; + } + if (startDate.isAfter(endDate)) { + return false; + } + long daysBetween = ChronoUnit.DAYS.between(startDate, endDate); + return daysBetween <= MAX_DATE_RANGE_DAYS; + } + + /** + * 조회 기간 일수 반환 + */ + public long getDateRangeDays() { + if (startDate == null || endDate == null) { + return 0; + } + return ChronoUnit.DAYS.between(startDate, endDate); + } +} 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 new file mode 100644 index 0000000..bc4411f --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/apps/statistics/dto/ApiStatisticsSummaryDto.java @@ -0,0 +1,59 @@ +package com.eactive.apim.portal.apps.statistics.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * API 전체 통계 요약 DTO + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ApiStatisticsSummaryDto { + + private Long totalAttempted; // 전체 호출 건 + private Long totalCompleted; // 전체 성공 건 + private Long totalFailed; // 전체 실패 건 + private Double successRate; // 성공율 (%) + private Double failureRate; // 실패율 (%) + + /** + * Repository 조회용 생성자 (attempted, completed만) + */ + public ApiStatisticsSummaryDto(Long totalAttempted, Long totalCompleted) { + this.totalAttempted = totalAttempted != null ? totalAttempted : 0L; + this.totalCompleted = totalCompleted != null ? totalCompleted : 0L; + } + + /** + * 실패건, 성공률, 실패율 계산 + */ + public void calculateRates() { + if (totalAttempted == null) { + totalAttempted = 0L; + } + if (totalCompleted == null) { + totalCompleted = 0L; + } + + 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; + } else { + this.successRate = 0.0; + this.failureRate = 0.0; + } + } + + /** + * 빈 통계 생성 + */ + public static ApiStatisticsSummaryDto empty() { + ApiStatisticsSummaryDto dto = new ApiStatisticsSummaryDto(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 new file mode 100644 index 0000000..2c688e2 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/apps/statistics/repository/ApiStatisticsRepository.java @@ -0,0 +1,81 @@ +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/service/ApiStatisticsService.java b/src/main/java/com/eactive/apim/portal/apps/statistics/service/ApiStatisticsService.java new file mode 100644 index 0000000..a12b869 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/apps/statistics/service/ApiStatisticsService.java @@ -0,0 +1,172 @@ +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.portal.apps.statistics.dto.ApiStatisticsDetailDto; +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 java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.Collections; +import java.util.List; +import javax.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +/** + * API 통계 Service + */ +@Slf4j +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class ApiStatisticsService { + + private final CredentialRepository credentialRepository; + private final ApiStatisticsRepository apiStatisticsRepository; + + /** + * 로그인 사용자 법인의 앱 목록 조회 (전체) + */ + public List getAppListByOrg(String orgId) { + return credentialRepository.findAllByOrgid(orgId); + } + + /** + * 통계 조회 + */ + public ApiStatisticsResultDto getStatistics(String orgId, ApiStatisticsSearchDto searchDto) { + LocalDateTime startTime = searchDto.getStartDate().atStartOfDay(); + LocalDateTime endTime = searchDto.getEndDate().atTime(LocalTime.MAX); + + ApiStatisticsSummaryDto summary; + List details; + + if (searchDto.hasSelectedApp()) { + // 특정 앱 통계 + String clientId = searchDto.getClientId(); + + // 해당 앱이 조직 소속인지 검증 + if (!isAppBelongsToOrg(clientId, orgId)) { + return ApiStatisticsResultDto.empty(); + } + + 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); + } + + // null 체크 및 빈 결과 처리 + if (summary == null) { + summary = ApiStatisticsSummaryDto.empty(); + } else { + summary.calculateRates(); + } + + if (details == null) { + details = Collections.emptyList(); + } else { + details.forEach(ApiStatisticsDetailDto::calculateRates); + } + + return new ApiStatisticsResultDto(summary, details); + } + + /** + * 앱이 해당 조직 소속인지 검증 + */ + private boolean isAppBelongsToOrg(String clientId, String orgId) { + return credentialRepository.findByClientidAndOrgid(clientId, orgId).isPresent(); + } + + /** + * CSV 다운로드 + */ + public void downloadCsv(String orgId, ApiStatisticsSearchDto searchDto, HttpServletResponse response) throws IOException { + ApiStatisticsResultDto result = getStatistics(orgId, searchDto); + + String fileName = String.format("API_Statistics_%s_%s.csv", + searchDto.getStartDate().toString(), + searchDto.getEndDate().toString()); + + response.setContentType("text/csv; charset=UTF-8"); + response.setHeader("Content-Disposition", "attachment; filename=\"" + URLEncoder.encode(fileName, StandardCharsets.UTF_8.name()) + "\""); + + // BOM for Excel UTF-8 recognition + response.getOutputStream().write(0xEF); + response.getOutputStream().write(0xBB); + response.getOutputStream().write(0xBF); + + try (PrintWriter writer = new PrintWriter(new OutputStreamWriter(response.getOutputStream(), StandardCharsets.UTF_8), true)) { + // 조회 조건 + writer.println("조회 기간," + searchDto.getStartDate() + " ~ " + searchDto.getEndDate()); + + String appName = "전체 앱"; + if (searchDto.hasSelectedApp()) { + appName = credentialRepository.findById(searchDto.getClientId()) + .map(Credential::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(); + + // API별 통계 헤더 + writer.println("[API별 통계]"); + writer.println("API명,호출 건,성공 건,성공율,실패 건,실패율"); + + // API별 통계 데이터 + for (ApiStatisticsDetailDto detail : result.getDetails()) { + writer.println(String.format("%s,%d,%d,%s%%,%d,%s%%", + escapeCsv(detail.getApiName()), + detail.getAttemptedCount(), + detail.getCompletedCount(), + detail.getSuccessRate(), + detail.getFailedCount(), + detail.getFailureRate())); + } + + // 전체 합계 행 + writer.println(String.format("전체,%d,%d,%s%%,%d,%s%%", + summary.getTotalAttempted(), + summary.getTotalCompleted(), + summary.getSuccessRate(), + summary.getTotalFailed(), + summary.getFailureRate())); + } + } + + /** + * CSV 값 이스케이프 (쉼표, 따옴표 처리) + */ + private String escapeCsv(String value) { + if (value == null) return ""; + if (value.contains(",") || value.contains("\"") || value.contains("\n")) { + return "\"" + value.replace("\"", "\"\"") + "\""; + } + return value; + } +} 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 a3e49dc..e7dcd34 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 @@ -10,10 +10,15 @@ 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.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; @@ -33,6 +38,7 @@ 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.servlet.ModelAndView; import org.springframework.web.servlet.mvc.support.RedirectAttributes; @@ -47,6 +53,7 @@ public class AccountController { private final OrgRegisterFacade orgRegisterFacade; private final AgreementsFacade agreementsFacade; private final com.eactive.apim.portal.apps.user.facade.AuthFacade authFacade; + private final UserInvitationRepository userInvitationRepository; @PostMapping("/confirm_password") @@ -122,7 +129,8 @@ public class AccountController { try { // 현재 로그인된 사용자의 정보를 가져옴 - PortalUserDTO user = userFacade.findById(SecurityUtil.getPortalAuthenticatedUser().getId()); + PortalAuthenticatedUser currentUser = SecurityUtil.getPortalAuthenticatedUser(); + PortalUserDTO user = userFacade.findById(currentUser.getId()); // 개별 필드들을 모델에 추가 mav.addObject("loginId", user.getLoginId()); @@ -132,6 +140,20 @@ public class AccountController { // 기존 user 객체도 유지 (다른 곳에서 필요할 수 있으므로) mav.addObject("user", user); + // ROLE_USER인 경우 초대 여부 확인 + if (currentUser.getRoleCode() == RoleCode.ROLE_USER) { + java.util.Optional pendingInvitation = + userInvitationRepository.findFirstByInvitationEmailAndStatus( + user.getEmailAddr(), InvitationStatus.PENDING); + + if (pendingInvitation.isPresent()) { + mav.addObject("hasPendingInvitation", true); + mav.addObject("invitationToken", pendingInvitation.get().getToken()); + } else { + mav.addObject("hasPendingInvitation", false); + } + } + // IP 목록을 분리하여 모델에 추가 (법인 관리자인 경우만) if (user.getRoleCode() == PortalUserEnums.RoleCode.ROLE_CORP_MANAGER && user.getPortalOrg() != null @@ -143,7 +165,7 @@ public class AccountController { } // 사용자의 RoleCode에 따라 다른 페이지로 이동 - switch (SecurityUtil.getPortalAuthenticatedUser().getRoleCode()) { + switch (currentUser.getRoleCode()) { case ROLE_USER: mav.setViewName("apps/mypage/updatePersonalUser"); break; @@ -252,18 +274,20 @@ public class AccountController { @PostMapping("/mypage/org-transfer") - public String processOrgTransfer( + @ResponseBody + public Map processOrgTransfer( @ModelAttribute PortalOrgRegistrationDTO orgDTO, @ModelAttribute UserAgreementDTO agreementDTO, HttpServletRequest request, - HttpServletResponse response, - RedirectAttributes redirectAttributes) { + HttpServletResponse response) { + Map result = new HashMap<>(); try { var currentUser = SecurityUtil.getPortalAuthenticatedUser(); if (currentUser.getRoleCode() != PortalUserEnums.RoleCode.ROLE_USER) { - redirectAttributes.addFlashAttribute("error", "잘못된 접근입니다."); - return "redirect:/mypage"; + result.put("status", "ERROR"); + result.put("message", "잘못된 접근입니다."); + return result; } ValidationResponse validationResponse = orgRegisterFacade.convertToOrgUser( @@ -291,16 +315,20 @@ public class AccountController { } } - redirectAttributes.addFlashAttribute("success", validationResponse.getMessage()); - return "apps/mypage/UserConversionCompleted"; + result.put("status", "SUCCESS"); + result.put("message", validationResponse.getMessage()); + result.put("redirectUrl", "/"); + return result; } else { - redirectAttributes.addFlashAttribute("error", validationResponse.getMessage()); - return "redirect:/mypage/org-transfer"; + result.put("status", "ERROR"); + result.put("message", validationResponse.getMessage()); + return result; } } catch (Exception e) { - redirectAttributes.addFlashAttribute("error", "법인회원 전환 중 오류가 발생했습니다: " + e.getMessage()); - return "redirect:/mypage/org-transfer"; + result.put("status", "ERROR"); + result.put("message", "법인회원 전환 중 오류가 발생했습니다: " + e.getMessage()); + return result; } } 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 066525f..f64ae36 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 @@ -43,8 +43,20 @@ public class UserManRestController { //5. register new user @PostMapping("/invite") public ResponseDTO sendInvitation(@RequestBody PortalUserDTO newUser) { + String email = newUser.getEmailAddr(); - userManFacade.sendInvitation(SecurityUtil.getPortalAuthenticatedUser(), newUser.getEmailAddr()); + // 이메일 형식 검증 + if (email == null || email.trim().isEmpty()) { + 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", "올바른 이메일 형식을 입력해주세요."); + } + + userManFacade.sendInvitation(SecurityUtil.getPortalAuthenticatedUser(), email); return new ResponseDTO(200, "SUCCESS", "요청 메일이 발송되었습니다."); } @@ -56,4 +68,11 @@ public class UserManRestController { return new ResponseDTO(200, "SUCCESS", "변경되었습니다. 확인 버튼을 누르시면 계정을 로그아웃 합니다."); } + + // 6. Resend invitation + @PostMapping("/resend-invitation") + public ResponseDTO resendInvitation(@RequestBody PortalUserDTO user) { + userManFacade.resendInvitation(SecurityUtil.getPortalAuthenticatedUser(), user.getId()); + return new ResponseDTO(200, "SUCCESS", "초대가 재발송되었습니다."); + } } 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 caf0524..f6f872c 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 @@ -71,7 +71,8 @@ public class UserRegisterController { String[] parts = null; if (invitationToken != null) { - String decodedToken = new String(Base64.decode(invitationToken)); + // 8글자 토큰 사용 + String decodedToken = decodeInvitationToken(invitationToken); session.setAttribute("invitationToken", decodedToken); Optional invitation = userInvitationRepository.findByToken(decodedToken); if (invitation.isPresent() && invitation.get().getStatus() == UserInvitationEnums.InvitationStatus.PENDING) { @@ -112,6 +113,7 @@ public class UserRegisterController { @Valid @ModelAttribute("portalUser") PortalUserRegistrationDTO portalUserRegistrationDTO, BindingResult bindingResult, HttpSession session, + RedirectAttributes redirectAttributes, Model model) { try { if (bindingResult.hasErrors()) { @@ -134,10 +136,10 @@ public class UserRegisterController { setModelForError(session, model, agreement, portalUserRegistrationDTO, response.getMessage()); return MAIN_USER_REGISTER; } - // 성공 시 + // 성공 시 PRG 패턴 적용: 결과 페이지로 리다이렉트 session.removeAttribute("invitationToken"); - model.addAttribute("message", "회원가입이 완료되었습니다."); - return invitationToken != null ? MAIN_EMAIL_COMPLETED : MAIN_REGISTER_RESULT; + redirectAttributes.addFlashAttribute("message", "회원가입이 완료되었습니다."); + return invitationToken != null ? "redirect:/signup/complete/corporate" : "redirect:/signup/complete"; } catch (Exception e) { setModelForError(session, model, agreement, portalUserRegistrationDTO, "처리 중 오류가 발생했습니다: " + e.getMessage()); @@ -145,6 +147,28 @@ public class UserRegisterController { } } + /** + * 개인회원 가입 완료 페이지 + */ + @GetMapping("/signup/complete") + public String showRegistrationComplete(Model model) { + if (!model.containsAttribute("message")) { + return "redirect:/"; + } + return MAIN_REGISTER_RESULT; + } + + /** + * 법인회원 가입 완료 페이지 + */ + @GetMapping("/signup/complete/corporate") + public String showCorporateRegistrationComplete(Model model) { + if (!model.containsAttribute("message")) { + return "redirect:/"; + } + return MAIN_EMAIL_COMPLETED; + } + @GetMapping("/signup/decision") public String showDecisionPage(@RequestParam(name = "invitation", required = false) String invitationToken, HttpSession session, @@ -153,7 +177,8 @@ public class UserRegisterController { return "redirect:/"; } - String decodedToken = new String(Base64.decode(invitationToken)); + // 8글자 토큰 사용 + String decodedToken = decodeInvitationToken(invitationToken); session.setAttribute("decisionToken", decodedToken); Optional invitation = userInvitationRepository.findByToken(decodedToken); @@ -183,6 +208,7 @@ public class UserRegisterController { // 이메일 대신 사용자 이름을 모델에 추가 model.addAttribute("userName", portalUser.get().getUserName()); model.addAttribute("orgName", org.get().getOrgName()); + model.addAttribute("invitationCode", decodedToken); model.addAttribute("isInvited", true); return "apps/register/userDecisionAccept"; @@ -213,6 +239,7 @@ public class UserRegisterController { model.addAttribute("userName", SecurityUtil.getPortalAuthenticatedUser().getUsername()); 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("registrationType", "corporate"); @@ -228,6 +255,7 @@ public class UserRegisterController { BindingResult bindingResult, @RequestParam(name = "invitationToken") String invitationToken, RedirectAttributes redirectAttributes, + HttpSession session, Model model) { agreementValidator.validate(agreement, bindingResult); @@ -249,6 +277,12 @@ public class UserRegisterController { } else { ValidationResponse response = userRegisterFacade.processInvitation(action, invitation.get()); + // 초대 처리 완료 후 세션에서 초대 관련 속성 제거 + session.removeAttribute("pendingInvitation"); + session.removeAttribute("pendingInvitationToken"); + session.removeAttribute("pendingInvitationOrgName"); + session.removeAttribute("decisionToken"); + redirectAttributes.addFlashAttribute("success", response.getMessage()); return "redirect:/"; } @@ -294,4 +328,124 @@ public class UserRegisterController { model.addAttribute("privacyCollect", agreementsFacade.getAgreement("PRIVACY_COLLECT_IND")); model.addAttribute("msgType", "sms"); } + + /** + * 초대 토큰 반환 + * @param invitationToken 원본 토큰 (8글자 토큰) + * @return 토큰 + */ + private String decodeInvitationToken(String invitationToken) { + return invitationToken; + } + + /** + * 초대 코드 입력 페이지 표시 + */ + @GetMapping("/signup/invitation-code") + public String showInvitationCodeInput(HttpSession session, Model model) { + // 브루트포스 방지: 세션에서 시도 횟수 확인 + Integer failedAttempts = (Integer) session.getAttribute("invitationCodeAttempts"); + Long lockoutUntil = (Long) session.getAttribute("invitationCodeLockoutUntil"); + + if (lockoutUntil != null && lockoutUntil > System.currentTimeMillis()) { + // 잠금 상태: 남은 시간 계산 + long remainingSeconds = (lockoutUntil - System.currentTimeMillis()) / 1000; + long remainingMinutes = remainingSeconds / 60; + long seconds = remainingSeconds % 60; + String lockoutTime = String.format("%d분 %d초", remainingMinutes, seconds); + model.addAttribute("lockoutTime", lockoutTime); + model.addAttribute("attemptsRemaining", 0); + } else { + // 잠금 해제: 시도 횟수 표시 + if (failedAttempts == null) { + failedAttempts = 0; + } + int attemptsRemaining = 5 - failedAttempts; + model.addAttribute("attemptsRemaining", attemptsRemaining); + } + + return "apps/register/invitationCodeInput"; + } + + /** + * 초대 코드 입력 처리 + */ + @PostMapping("/signup/invitation-code") + public String processInvitationCode( + @RequestParam("invitationCode") String invitationCode, + HttpSession session, + RedirectAttributes redirectAttributes, + Model model) { + + // 브루트포스 방지: 잠금 상태 확인 + Long lockoutUntil = (Long) session.getAttribute("invitationCodeLockoutUntil"); + if (lockoutUntil != null && lockoutUntil > System.currentTimeMillis()) { + long remainingSeconds = (lockoutUntil - System.currentTimeMillis()) / 1000; + long remainingMinutes = remainingSeconds / 60; + long seconds = remainingSeconds % 60; + String lockoutTime = String.format("%d분 %d초", remainingMinutes, seconds); + model.addAttribute("lockoutTime", lockoutTime); + model.addAttribute("error", "잠금 해제 시간을 기다려 주세요."); + return "apps/register/invitationCodeInput"; + } + + // 초대 코드 정규화 (대문자 변환 및 공백 제거) + String normalizedCode = invitationCode.toUpperCase().trim(); + + // 초대 코드 검증 + Optional invitationOpt = userInvitationRepository.findByToken(normalizedCode); + + if (!invitationOpt.isPresent() || + invitationOpt.get().getStatus() != UserInvitationEnums.InvitationStatus.PENDING) { + + // 실패 처리: 시도 횟수 증가 + Integer failedAttempts = (Integer) session.getAttribute("invitationCodeAttempts"); + if (failedAttempts == null) { + failedAttempts = 0; + } + failedAttempts++; + session.setAttribute("invitationCodeAttempts", failedAttempts); + + if (failedAttempts >= 5) { + // 5회 실패: 15분 잠금 + long lockoutTime = System.currentTimeMillis() + (15 * 60 * 1000); // 15분 + session.setAttribute("invitationCodeLockoutUntil", lockoutTime); + session.setAttribute("invitationCodeAttempts", 0); // 시도 횟수 초기화 + + model.addAttribute("lockoutTime", "15분 0초"); + model.addAttribute("error", "잘못된 초대 코드를 5회 입력하여 15분간 잠금되었습니다."); + } else { + // 실패 메시지와 남은 시도 횟수 표시 + int attemptsRemaining = 5 - failedAttempts; + model.addAttribute("attemptsRemaining", attemptsRemaining); + model.addAttribute("error", "유효하지 않은 초대 코드입니다. 다시 확인해 주세요."); + } + + return "apps/register/invitationCodeInput"; + } + + // 성공: 초대 코드 검증 완료 + UserInvitation invitation = invitationOpt.get(); + + // 만료 확인 + if (invitation.getExpiresOn().isBefore(java.time.LocalDateTime.now())) { + model.addAttribute("error", "만료된 초대 코드입니다. 관리자에게 재발송을 요청해 주세요."); + return "apps/register/invitationCodeInput"; + } + + // 시도 횟수 초기화 + session.removeAttribute("invitationCodeAttempts"); + session.removeAttribute("invitationCodeLockoutUntil"); + + // 기존 사용자 여부 확인 + Optional existingUser = portalUserRepository.findPortalUserByEmailAddr(invitation.getInvitationEmail()); + + if (existingUser.isPresent()) { + // 기존 사용자: 초대 수락 페이지로 이동 + return "redirect:/signup/decision?invitation=" + normalizedCode; + } else { + // 신규 사용자: 회원가입 페이지로 이동 + return "redirect:/signup/portalUser?invitation=" + normalizedCode; + } + } } diff --git a/src/main/java/com/eactive/apim/portal/apps/user/dto/PortalOrgRegistrationDTO.java b/src/main/java/com/eactive/apim/portal/apps/user/dto/PortalOrgRegistrationDTO.java index 1181062..c3ebe2d 100644 --- a/src/main/java/com/eactive/apim/portal/apps/user/dto/PortalOrgRegistrationDTO.java +++ b/src/main/java/com/eactive/apim/portal/apps/user/dto/PortalOrgRegistrationDTO.java @@ -30,11 +30,9 @@ public class PortalOrgRegistrationDTO extends PortalUserRegistrationDTO { private String orgName; //대표자 성명 - @NotEmpty(message = "{field.required}") private String ceoName; //기관사업장소재지 - @NotEmpty(message = "{field.required}") private String orgAddr; //기관업태 @@ -44,15 +42,12 @@ public class PortalOrgRegistrationDTO extends PortalUserRegistrationDTO { private String orgIndustryType; //서비스명 - @NotEmpty(message = "{field.required}") private String serviceName; //고객센터연락처 - @NotEmpty(message = "{field.required}") private String scPhoneNumber; //기관전화번호 - @NotEmpty(message = "{field.required}") @PhoneNumber private String orgPhoneNumber; diff --git a/src/main/java/com/eactive/apim/portal/apps/user/dto/ValidationResponse.java b/src/main/java/com/eactive/apim/portal/apps/user/dto/ValidationResponse.java index 2826f65..8dbe37d 100644 --- a/src/main/java/com/eactive/apim/portal/apps/user/dto/ValidationResponse.java +++ b/src/main/java/com/eactive/apim/portal/apps/user/dto/ValidationResponse.java @@ -18,6 +18,8 @@ public class ValidationResponse { private boolean isPersonalAccount; private boolean isCorporateAccount; + private String authNumber; // 테스트 환경 인증번호 + public ValidationResponse(boolean isValid, String message) { this.isValid = isValid; this.message = message; 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 0b84483..371f87d 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 @@ -41,9 +41,10 @@ public class AuthFacadeImpl implements AuthFacade { } try { - authNumberService.sendRequestAuthNumber(recipientKey, msgType); + String generatedAuthNumber = authNumberService.sendRequestAuthNumber(recipientKey, msgType); response.setValid(true); response.setMessage("인증번호를 발송하였습니다."); + 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/OrgRegisterFacadeImpl.java b/src/main/java/com/eactive/apim/portal/apps/user/facade/OrgRegisterFacadeImpl.java index b23c960..f2f39c7 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 @@ -76,7 +76,7 @@ public class OrgRegisterFacadeImpl implements OrgRegisterFacade { // 2025.10.20 - 휴대폰 번호 중복 무시 // PortalUser existingUser = portalUserRepository.findByUserNameAndMobileNumber(orgDTO.getUserName(), orgDTO.getMobileNumber()); - Optional existingUser = portalUserRepository.findByUserName(orgDTO.getUserName()); + Optional existingUser = portalUserRepository.findByLoginId(orgDTO.getLoginId()); if(existingUser.isPresent()) { return new ValidationResponse(false, "가입된 계정이 이미 존재합니다."); @@ -181,6 +181,11 @@ public class OrgRegisterFacadeImpl implements OrgRegisterFacade { PortalOrgRegistrationDTO orgDTO, FileInfo uploadedFile) { + // 사업자등록번호 중복 체크 (이중 방어) + if (portalOrgService.existsByCompRegNo(orgDTO.getCompRegNo())) { + return new ValidationResponse(false, "이미 등록된 사업자등록번호입니다."); + } + // PortalOrgService를 통한 기관 등록 PortalOrg newOrg = portalOrgService.registerOrgFromDTOWithFile(orgDTO, uploadedFile); @@ -190,7 +195,8 @@ public class OrgRegisterFacadeImpl implements OrgRegisterFacade { agreementsFacade.saveUserAgreements(newUser.getId(), AgreementType.PRIVACY_COLLECT_ORG); approvalService.createUserApproval(newUser); - sendActivationEmail(newUser); + // 11.28 - 회원 가입단계가 아닌 로그인 단계로 이메일 인증 이동 +// sendActivationEmail(newUser); return new ValidationResponse(true, "법인 사용자 등록 신청이 완료되었습니다."); } @@ -218,6 +224,11 @@ public class OrgRegisterFacadeImpl implements OrgRegisterFacade { PortalOrgRegistrationDTO orgDTO, FileInfo uploadedFile) { + // 사업자등록번호 중복 체크 (이중 방어) + if (portalOrgService.existsByCompRegNo(orgDTO.getCompRegNo())) { + return new ValidationResponse(false, "이미 등록된 사업자등록번호입니다."); + } + // PortalOrgService를 통한 기관 등록 PortalOrg newOrg = portalOrgService.registerOrgFromDTOWithFile(orgDTO, uploadedFile); @@ -243,12 +254,18 @@ public class OrgRegisterFacadeImpl implements OrgRegisterFacade { String newEmail, FileInfo uploadedFile) { + // 사업자등록번호 중복 체크 (이중 방어) + if (portalOrgService.existsByCompRegNo(orgDTO.getCompRegNo())) { + return new ValidationResponse(false, "이미 등록된 사업자등록번호입니다."); + } + // PortalOrgService를 통한 기관 등록 PortalOrg newOrg = portalOrgService.registerOrgFromDTOWithFile(orgDTO, uploadedFile); - // 새 사용자 생성 - existingUser.setLoginId(newEmail); - existingUser.setEmailAddr(newEmail); + // 새 사용자 생성 (이메일 소문자 변환 적용) + String normalizedNewEmail = newEmail != null ? newEmail.toLowerCase() : null; + existingUser.setLoginId(normalizedNewEmail); + existingUser.setEmailAddr(normalizedNewEmail); existingUser.setPortalOrg(newOrg); existingUser.setRoleCode(PortalUserEnums.RoleCode.ROLE_CORP_MANAGER); existingUser.setUserStatus(PortalUserEnums.UserStatus.READY); @@ -285,9 +302,19 @@ public class OrgRegisterFacadeImpl implements OrgRegisterFacade { @Override public ResponseEntity checkBusinessNumber(String compRegNo) { - boolean isValid = validationService.isValidBusinessNumber(compRegNo); - String message = isValid ? "사용 가능한 사업자 등록번호입니다." : "유효하지 않은 사업자 등록번호입니다."; - return ResponseEntity.ok(new ValidationResponse(isValid, message)); + // 1. 형식 검증 + boolean isValidFormat = validationService.isValidBusinessNumber(compRegNo); + if (!isValidFormat) { + return ResponseEntity.ok(new ValidationResponse(false, "유효하지 않은 사업자 등록번호입니다.")); + } + + // 2. 중복 검증 + boolean isDuplicate = portalOrgService.existsByCompRegNo(compRegNo); + if (isDuplicate) { + return ResponseEntity.ok(new ValidationResponse(false, "이미 등록된 사업자등록번호입니다.")); + } + + return ResponseEntity.ok(new ValidationResponse(true, "사용 가능한 사업자 등록번호입니다.")); } @Override 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 961578f..371700a 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 @@ -6,6 +6,7 @@ import com.eactive.apim.portal.apps.user.mapper.PortalOrgMapper; 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.SecurityUtil; import com.eactive.apim.portal.common.util.StringMaskingUtil; import com.eactive.apim.portal.invitation.entity.UserInvitation; @@ -18,23 +19,25 @@ 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.portaluser.repository.PortalUserRepository; +import com.eactive.apim.portal.portalproperty.service.PortalPropertyService; import com.eactive.apim.portal.template.service.MessageHandlerService; import com.eactive.apim.portal.template.service.MessageRecipient; import com.eactive.apim.portal.user.entity.UserLog; import com.eactive.apim.portal.user.repository.UserLogRepository; -import java.nio.charset.StandardCharsets; import lombok.RequiredArgsConstructor; -import org.apache.xerces.impl.dv.util.Base64; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import javax.sound.midi.Receiver; +import java.security.SecureRandom; +import java.time.Duration; import java.time.LocalDateTime; import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Optional; -import java.util.UUID; import java.util.stream.Collectors; @Service @@ -48,6 +51,7 @@ public class UserManFacade { private final UserInvitationRepository userInvitationRepository; private final MessageHandlerService messageHandlerService; private final UserLogRepository userLogRepository; + private final PortalPropertyService portalPropertyService; public Page getUsers(PortalOrgDTO userOrg, Pageable pageable) { return portalUserRepository @@ -91,6 +95,19 @@ public class UserManFacade { } public void sendInvitation(PortalUser sender, String emailAddr) { + // 이메일 형식 검증 + if (emailAddr == null || emailAddr.trim().isEmpty()) { + throw new IllegalArgumentException("이메일 주소를 입력해주세요."); + } + + // 이메일 정규식 검증 + String emailPattern = "^[a-zA-Z0-9._%+\\-]+@[a-zA-Z0-9.\\-]+\\.[a-zA-Z]{2,}$"; + if (!emailAddr.matches(emailPattern)) { + throw new IllegalArgumentException("올바른 이메일 형식을 입력해주세요."); + } + + // 중복 초대 확인 + checkDuplicateInvitation(sender.getPortalOrg().getId(), emailAddr); //기존 사용자인지 확인 Optional existingUser = portalUserRepository.findPortalUserByEmailAddr(emailAddr); @@ -104,16 +121,32 @@ public class UserManFacade { String token = createInvitation(sender, emailAddr); - MessageRecipient recipient = new MessageRecipient(); - recipient.setUserId(emailAddr); - HashMap params = new HashMap<>(); - params.put("manager", sender.getUserName()); - params.put("company", sender.getPortalOrg().getOrgName()); + // {"CRPT_NM":"%corpName%", "MNGR_NM":"%managerName%", "CUST_ID":"%loginId%", "AUTH_NO": "%authNumber%", "NAME": "%loginId%"} + MessageRecipient recipient; + HashMap params = new HashMap<>(); + params.put("managerName", sender.getUserName()); + params.put("corpName", sender.getPortalOrg().getOrgName()); + params.put("authNumber", token); // 8-character invitation code + + // 기존 사용자인 경우 회원 이름 사용, 비회원인 경우 이메일 사용 if (existingUser.isPresent()) { - params.put("url", "signup/decision?invitation=" + Base64.encode(token.getBytes(StandardCharsets.UTF_8))); + PortalUser user = existingUser.get(); + params.put("loginId", user.getUserName()); + params.put("NAME", user.getUserName()); + params.put("url", "signup/decision?invitation=" + token); + + recipient = MessageRecipient.of(user); } else { - params.put("url", "signup/portalUser?invitation=" + Base64.encode(token.getBytes(StandardCharsets.UTF_8))); + params.put("loginId", emailAddr); + params.put("NAME", emailAddr); + params.put("url", "signup/portalUser?invitation=" + token); + + String emailUsername = emailAddr.contains("@") ? emailAddr.substring(0, emailAddr.indexOf("@")) : emailAddr; + recipient = MessageRecipient.builder() + .userId(emailAddr) + .username(emailUsername) + .build(); } messageHandlerService.publishEvent(UserInvitationEvent.KEY, recipient, params); @@ -129,18 +162,38 @@ public class UserManFacade { newInvitation.setInvitationDate(LocalDateTime.now()); newInvitation.setStatus(InvitationStatus.PENDING); - // Generate a unique token (you might want to use a more secure method) - String token = UUID.randomUUID().toString(); + // Generate 8-character token for manual entry in closed network + // Excludes confusing characters: O, 0, I, 1 + String token = generateEightCharToken(); newInvitation.setToken(token); - // Set expiration date (e.g., 7 days from now) - newInvitation.setExpiresOn(LocalDateTime.now().plusDays(7)); + // Set expiration date from PTL_PROPERTY (default: 7 days) + Duration expiration = getInvitationExpiration(); + newInvitation.setExpiresOn(LocalDateTime.now().plus(expiration)); // Save the invitation userInvitationRepository.save(newInvitation); return token; } + /** + * 폐쇄망에서 사람이 직접 입력할 수 있는 8글자 토큰 생성 + * 혼동되기 쉬운 문자(O, 0, I, 1)를 제외한 대문자 영문자와 숫자 조합 + */ + private String generateEightCharToken() { + // 혼동되기 쉬운 문자 제외: O, 0, I, 1 + String chars = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"; + SecureRandom random = new SecureRandom(); + StringBuilder token = new StringBuilder(8); + + for (int i = 0; i < 8; i++) { + int index = random.nextInt(chars.length()); + token.append(chars.charAt(index)); + } + + return token.toString(); + } + public void assignManager(PortalAuthenticatedUser portalAuthenticatedUser, String targetUserId) { PortalUser targetUser = portalUserRepository.findById(targetUserId) .orElseThrow(() -> new NotFoundException("ID [" + targetUserId + "] 사용자를 찾을 수 없습니다. ")); @@ -251,4 +304,78 @@ public class UserManFacade { }) .collect(Collectors.toList()); } + + /** + * 초대 재발송 + * 기존 PENDING 또는 EXPIRED 상태의 초대를 취소하고 새로운 초대를 생성합니다. + */ + public void resendInvitation(PortalUser sender, String invitationId) { + UserInvitation existingInvitation = userInvitationRepository.findById(invitationId) + .orElseThrow(() -> new NotFoundException("초대를 찾을 수 없습니다: " + invitationId)); + + // 권한 확인: 같은 기관의 초대인지 확인 + if (!existingInvitation.getOrgId().equals(sender.getPortalOrg().getId())) { + throw new IllegalArgumentException("초대를 재발송할 권한이 없습니다."); + } + + // 재발송 가능한 상태인지 확인 + if (existingInvitation.getStatus() != InvitationStatus.PENDING + && existingInvitation.getStatus() != InvitationStatus.EXPIRED) { + throw new IllegalStateException("재발송 가능한 상태가 아닙니다. 현재 상태: " + existingInvitation.getStatus().getDescription()); + } + + // 기존 초대를 취소 처리 + existingInvitation.setStatus(InvitationStatus.CANCELED); + userInvitationRepository.save(existingInvitation); + + // 새로운 초대 생성 및 발송 + sendInvitation(sender, existingInvitation.getInvitationEmail()); + } + + /** + * 중복 초대 확인 + * 같은 기관에서 같은 이메일로 이미 PENDING 상태인 초대가 있는지 확인 + */ + private void checkDuplicateInvitation(String orgId, String emailAddr) { + // 1. 같은 기관에서 PENDING 상태인 초대가 있는지 확인 + Optional pendingInvitation = + userInvitationRepository.findFirstByInvitationEmailAndOrgIdAndStatus( + emailAddr, orgId, InvitationStatus.PENDING); + + if (pendingInvitation.isPresent()) { + throw new IllegalArgumentException("이미 초대가 진행 중인 이메일 주소입니다."); + } + + // 2. 다른 기관에서 PENDING 상태인 초대가 있는지 확인 + Optional otherOrgPendingInvitation = + userInvitationRepository.findFirstByInvitationEmailAndStatus( + emailAddr, InvitationStatus.PENDING); + + if (otherOrgPendingInvitation.isPresent() + && !otherOrgPendingInvitation.get().getOrgId().equals(orgId)) { + throw new IllegalArgumentException("해당 이메일은 다른 기관에서 초대 진행 중입니다."); + } + } + + /** + * PTL_PROPERTY에서 초대 만료 기간을 가져옵니다. + * 설정이 없으면 기본값 7일을 반환합니다. + * + * @return 초대 만료 기간 + */ + private Duration getInvitationExpiration() { + try { + Map properties = portalPropertyService.getPortalPropertiesAsMap("invitation"); + String expirationStr = properties.get("expiration"); + + if (expirationStr != null && !expirationStr.trim().isEmpty()) { + return DurationParser.parseOrDefault(expirationStr, Duration.ofDays(7)); + } + } catch (Exception e) { + // 설정을 가져오는 데 실패하면 기본값 사용 + } + + // 기본값: 7일 + return Duration.ofDays(7); + } } 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 f28e68c..c34f9a8 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 @@ -132,6 +132,11 @@ public class UserRegisterFacadeImpl implements UserRegisterFacade { return new ValidationResponse(false,"입력값을 확인해 주세요."); } + // 이메일 중복 체크 + if (portalUserService.existsByLoginId(registrationDTO.getLoginId())) { + return new ValidationResponse(false, "이미 사용 중인 이메일입니다."); + } + // 25.10.01 - 휴대폰 번호 중복이여도 가입 가능 // PortalUser existingUser = portalUserRepository.findByUserNameAndMobileNumber(registrationDTO.getUserName(), registrationDTO.getMobileNumber()); // diff --git a/src/main/java/com/eactive/apim/portal/apps/user/repository/PortalOrgRepository.java b/src/main/java/com/eactive/apim/portal/apps/user/repository/PortalOrgRepository.java index 63fd9aa..38916a4 100644 --- a/src/main/java/com/eactive/apim/portal/apps/user/repository/PortalOrgRepository.java +++ b/src/main/java/com/eactive/apim/portal/apps/user/repository/PortalOrgRepository.java @@ -5,15 +5,31 @@ import com.eactive.apim.portal.portalorg.entity.PortalOrgEnums; import com.eactive.eai.data.jpa.BaseRepository; import com.eactive.eai.rms.data.EMSDataSource; +import java.util.List; import java.util.Optional; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; @EMSDataSource public interface PortalOrgRepository extends BaseRepository { Optional findByCompRegNo(String compRegNo); + /** + * 사업자등록번호 존재 여부 확인 (중복 데이터가 있어도 안전하게 처리) + */ + boolean existsByCompRegNo(String compRegNo); + long countByOrgStatusAndApprovalStatus(PortalOrgEnums.OrgStatus status, PortalOrgEnums.ApprovalStatus approvalStatus); Optional findById(String orgId); + + /** + * 정상 상태이고 승인 완료된 기관의 ID 목록 조회 + */ + @Query("SELECT o.id FROM PortalOrg o WHERE o.orgStatus = :status AND o.approvalStatus = :approvalStatus") + List findIdsByOrgStatusAndApprovalStatus( + @Param("status") PortalOrgEnums.OrgStatus status, + @Param("approvalStatus") PortalOrgEnums.ApprovalStatus approvalStatus); } diff --git a/src/main/java/com/eactive/apim/portal/apps/user/service/PortalOrgService.java b/src/main/java/com/eactive/apim/portal/apps/user/service/PortalOrgService.java index ee2cae8..560fa51 100644 --- a/src/main/java/com/eactive/apim/portal/apps/user/service/PortalOrgService.java +++ b/src/main/java/com/eactive/apim/portal/apps/user/service/PortalOrgService.java @@ -58,4 +58,9 @@ public class PortalOrgService { org.setOrgStatus(OrgStatus.READY); org.setApprovalStatus(ApprovalStatus.PENDING); } + + // 사업자등록번호 중복 체크 (Spring Data JPA의 existsBy 쿼리 사용 - 중복 데이터에도 안전) + public boolean existsByCompRegNo(String compRegNo) { + return portalOrgRepository.existsByCompRegNo(compRegNo); + } } \ No newline at end of file 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 3ec73a5..d62fd1c 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 @@ -18,6 +18,16 @@ import com.eactive.apim.portal.template.entity.MessageRequest; import com.eactive.apim.portal.template.repository.MessageRequestRepository; import com.eactive.apim.portal.template.service.MessageHandlerService; import com.eactive.apim.portal.template.service.MessageRecipient; +import java.nio.charset.StandardCharsets; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.time.format.DateTimeFormatter; +import java.util.HashMap; +import java.util.List; +import java.util.stream.Collectors; +import javax.crypto.BadPaddingException; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; import lombok.RequiredArgsConstructor; import org.apache.xerces.impl.dv.util.Base64; import org.springframework.security.core.authority.SimpleGrantedAuthority; @@ -28,16 +38,6 @@ import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import javax.crypto.BadPaddingException; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.NoSuchPaddingException; -import java.nio.charset.StandardCharsets; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.time.format.DateTimeFormatter; -import java.util.HashMap; -import java.util.List; - @Service @Transactional @RequiredArgsConstructor @@ -55,7 +55,9 @@ public class PortalUserAuthService implements UserDetailsService { @Transactional(noRollbackFor = UsernameNotFoundException.class) public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { try { - PortalUser portalUser = findByEmailAddr(username); + // 이메일 소문자 변환 적용 (대소문자 구분 없이 로그인 가능하도록) + 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); @@ -71,17 +73,22 @@ public class PortalUserAuthService implements UserDetailsService { } } - public PortalUserDTO findUsersByNameAndMobile(String userName, String mobileNumber) { + public List findAllUsersByNameAndMobile(String userName, String mobileNumber) { try { - PortalUser user = portalUserRepository.findByUserNameAndMobileNumber(userName, mobileNumber); - if (user == null) { + if (mobileNumber == null || !mobileNumber.matches("^\\d{2,3}-\\d{3,4}-\\d{4}$")) { + throw new UserNotFoundException("유효하지 않은 휴대폰 번호 형식입니다."); + } + List users = portalUserRepository.findAllByUserNameAndMobileNumber(userName, mobileNumber); + if (users == null || users.isEmpty()) { throw new UserNotFoundException("입력하신 사용자 정보가 올바르지 않습니다. 다시 확인해 주세요."); } - PortalUserDTO dto = portalUserMapper.toDTO(user); - dto.setMaskedEmailAddr(StringMaskingUtil.maskEmail(dto.getLoginId())); - dto.setLoginId(null); - return dto; + return users.stream().map(user -> { + PortalUserDTO dto = portalUserMapper.toDTO(user); + dto.setMaskedEmailAddr(StringMaskingUtil.maskEmail(dto.getLoginId())); + dto.setLoginId(null); + return dto; + }).collect(Collectors.toList()); } catch (UserNotFoundException e) { throw e; @@ -94,13 +101,19 @@ public class PortalUserAuthService implements UserDetailsService { return portalUserRepository.findPortalUserByEmailAddr(emailAddr).orElseThrow(() -> new UserNotFoundException("USER_NOT_FOUND_MESSAGE")); } - public void initializePassword(String loginId, String userName, String mobileNumber) { + public void resetPassword(String loginId, String userName, String mobileNumber) { + if (mobileNumber == null || !mobileNumber.matches("^\\d{2,3}-\\d{3,4}-\\d{4}$")) { + throw new UserNotFoundException("유효하지 않은 휴대폰 번호 형식입니다."); + } PortalUser portalUser = portalUserRepository.findByLoginIdAndUserNameAndMobileNumber(loginId, userName, mobileNumber) .orElseThrow(() -> new UserNotFoundException("일치하는 사용자 정보를 찾을 수 없습니다.")); - String tempPassword = EncryptionUtil.generateNewPasswordForKjbank(loginId); + String tempPassword = EncryptionUtil.generateNewPassword(); portalUser.setPasswordHash(passwordEncoder.encode(tempPassword)); + if ("Y".equalsIgnoreCase(portalUser.getAccountLockYn())) { + portalUser.setAccountLockYn("N"); + } portalUserRepository.save(portalUser); HashMap params = new HashMap<>(); 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 d8b17c5..20921e5 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 @@ -46,11 +46,15 @@ public class PortalUserService { } public Optional findByLoginId(String loginId) { - return portalUserRepository.findByLoginId(loginId); + // 이메일 소문자 변환 적용 + String normalizedLoginId = loginId != null ? loginId.toLowerCase() : null; + return portalUserRepository.findByLoginId(normalizedLoginId); } public PortalUser findByEmailAddr(String emailAddr) { - return portalUserRepository.findPortalUserByEmailAddr(emailAddr) + // 이메일 소문자 변환 적용 + String normalizedEmail = emailAddr != null ? emailAddr.toLowerCase() : null; + return portalUserRepository.findPortalUserByEmailAddr(normalizedEmail) .orElseThrow(() -> new IllegalArgumentException("해당 이메일 주소의 사용자를 찾을 수 없습니다.")); } @@ -59,7 +63,9 @@ public class PortalUserService { } public boolean existsByLoginId(String loginId) { - return portalUserRepository.existsByLoginId(loginId); + // 이메일 소문자 변환 적용 + String normalizedLoginId = loginId != null ? loginId.toLowerCase() : null; + return portalUserRepository.existsByLoginId(normalizedLoginId); } public boolean checkOrgHasOtherUsers(PortalOrg portalOrg) { @@ -137,11 +143,13 @@ public class PortalUserService { } private void mapDtoToEntity(PortalUser user, PortalUserRegistrationDTO dto) { - user.setLoginId(dto.getLoginId()); + // 이메일 소문자 변환 적용 (대소문자 구분 없이 로그인 가능하도록) + String normalizedEmail = dto.getLoginId() != null ? dto.getLoginId().toLowerCase() : null; + user.setLoginId(normalizedEmail); user.setUserName(dto.getUserName()); user.setPasswordHash(passwordEncoder.encode(dto.getPassword())); user.setMobileNumber(dto.getMobileNumber()); - user.setEmailAddr(dto.getLoginId()); + user.setEmailAddr(normalizedEmail); } /** @@ -176,7 +184,9 @@ public class PortalUserService { } public boolean checkPassword(String loginId, String inputPassword) { - PortalUser user = portalUserRepository.findByLoginId(loginId) + // 이메일 소문자 변환 적용 + String normalizedLoginId = loginId != null ? loginId.toLowerCase() : null; + PortalUser user = portalUserRepository.findByLoginId(normalizedLoginId) .orElseThrow(() -> new IllegalArgumentException("해당 사용자를 찾을 수 없습니다.")); return passwordEncoder.matches(inputPassword, user.getPasswordHash()); } diff --git a/src/main/java/com/eactive/apim/portal/apps/user/service/UserRegistrationValidationService.java b/src/main/java/com/eactive/apim/portal/apps/user/service/UserRegistrationValidationService.java index 30fd379..ad6478b 100644 --- a/src/main/java/com/eactive/apim/portal/apps/user/service/UserRegistrationValidationService.java +++ b/src/main/java/com/eactive/apim/portal/apps/user/service/UserRegistrationValidationService.java @@ -24,17 +24,26 @@ public class UserRegistrationValidationService { } private boolean isValidOrgInfo(PortalOrgRegistrationDTO orgDTO) { - return basicValidationService.isValidBusinessNumber(orgDTO.getCompRegNo()) && - basicValidationService.isValidCorpRegNo(orgDTO.getCorpRegNo()) && - basicValidationService.isValidPhoneNumber(orgDTO.getOrgPhoneNumber()) && - basicValidationService.isValidPhoneNumber(orgDTO.getScPhoneNumber()); + // 사업자등록번호, 법인등록번호는 필수 + if (!basicValidationService.isValidBusinessNumber(orgDTO.getCompRegNo()) || + !basicValidationService.isValidCorpRegNo(orgDTO.getCorpRegNo())) { + return false; + } + // 회사 전화번호, 고객센터 전화번호는 선택 (입력된 경우에만 형식 검증) + if (basicValidationService.isNotEmpty(orgDTO.getOrgPhoneNumber()) && + !basicValidationService.isValidPhoneNumber(orgDTO.getOrgPhoneNumber())) { + return false; + } + if (basicValidationService.isNotEmpty(orgDTO.getScPhoneNumber()) && + !basicValidationService.isValidPhoneNumber(orgDTO.getScPhoneNumber())) { + return false; + } + return true; } private boolean isValidRequiredFields(PortalOrgRegistrationDTO orgDTO) { - return basicValidationService.isNotEmpty(orgDTO.getOrgName()) && - basicValidationService.isNotEmpty(orgDTO.getCeoName()) && - basicValidationService.isNotEmpty(orgDTO.getOrgAddr()) && - basicValidationService.isNotEmpty(orgDTO.getServiceName()); + // 법인명만 필수, 나머지(대표자 성명, 소재지, 서비스명)는 선택 + return basicValidationService.isNotEmpty(orgDTO.getOrgName()); } private boolean isValidIpWhitelistIfExists(PortalOrgRegistrationDTO orgDTO) { 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 c6cf86d..e1090c4 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 @@ -6,6 +6,8 @@ import javax.servlet.http.HttpServletRequest; import org.springframework.beans.factory.annotation.Autowired; 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; @ControllerAdvice public class GlobalControllerAdvice { @@ -13,6 +15,12 @@ public class GlobalControllerAdvice { @Autowired private PageService pageService; + @Autowired + private PortalPropertyService portalPropertyService; + + @Autowired + private PortalProperties portalProperties; + @ModelAttribute("breadcrumb") public List addBreadcrumbToModel(HttpServletRequest request) { String currentPath = request.getRequestURI(); @@ -24,4 +32,30 @@ public class GlobalControllerAdvice { String currentPath = request.getRequestURI(); 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(); + } + + @ModelAttribute("testAuthNumber") + public String getTestAuthNumber() { + if (portalProperties.isTestAuthNoticeEnabled()) { + String authVirtualCode = portalProperties.getAuthVirtualCode(); + // 고정 인증번호가 있으면 반환, 없으면 "random" 표시 + return (authVirtualCode != null && !authVirtualCode.isEmpty()) ? authVirtualCode : "random"; + } + 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 0325693..e848f6d 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 @@ -7,8 +7,11 @@ import java.util.Map; import java.util.stream.Collectors; import javax.servlet.http.HttpServletRequest; +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; @@ -28,9 +31,11 @@ import org.springframework.web.servlet.mvc.support.RedirectAttributes; */ @Order(2) @ControllerAdvice(annotations = Controller.class) +@RequiredArgsConstructor public class PortalGlobalExceptionHandler { private final Logger log = LoggerFactory.getLogger(getClass()); + private final PortalProperties portalProperties; @ExceptionHandler(value = NotFoundException.class) public ModelAndView handleINotFoundException(HttpServletRequest request, NotFoundException ex) { @@ -62,7 +67,7 @@ public class PortalGlobalExceptionHandler { log.error(ex.getMessage()); ModelAndView modelAndView = new ModelAndView(); modelAndView.addObject("errorMessage", ex.getMessage()); - modelAndView.setViewName("common/error/error"); + modelAndView.setViewName("error"); return modelAndView; } @@ -79,7 +84,7 @@ public class PortalGlobalExceptionHandler { log.error(ex.getMessage()); ModelAndView modelAndView = new ModelAndView(); modelAndView.addObject("errorMessage", ex.getMessage()); - modelAndView.setViewName("common/error/error"); + modelAndView.setViewName("error"); return modelAndView; } @@ -100,7 +105,7 @@ public class PortalGlobalExceptionHandler { log.error("Exception occurred: ", ex); ModelAndView modelAndView = new ModelAndView(); modelAndView.addObject("errorMessage", ex.getMessage()); - modelAndView.setViewName("common/error/error"); + modelAndView.setViewName("error"); return modelAndView; } @@ -111,4 +116,15 @@ public class PortalGlobalExceptionHandler { modelAndView.setViewName("redirect:" + request.getRequestURI()); return modelAndView; } + + @ExceptionHandler(value = MaxUploadSizeExceededException.class) + public ModelAndView handleMaxUploadSizeExceededException(HttpServletRequest request, RedirectAttributes redirectAttributes, MaxUploadSizeExceededException ex) { + log.warn("파일 업로드 용량 초과: {}", ex.getMessage()); + String maxSize = portalProperties.getFile().getMaxSize(); + String errorMessage = "파일 크기가 허용된 최대 용량(" + maxSize + ")을 초과했습니다."; + redirectAttributes.addFlashAttribute("error", errorMessage); + ModelAndView modelAndView = new ModelAndView(); + modelAndView.setViewName("redirect:" + request.getRequestURI()); + return 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 c527a60..1b69b4f 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 @@ -2,20 +2,28 @@ package com.eactive.apim.portal.common.exception; import com.eactive.apim.portal.common.dto.ResponseDTO; +import com.eactive.apim.portal.config.PortalProperties; import com.eactive.apim.portal.portaluser.service.AuthNumberException; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.core.annotation.Order; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.multipart.MaxUploadSizeExceededException; import javax.servlet.http.HttpServletRequest; +@Slf4j @Order(1) @RestControllerAdvice(annotations = RestController.class) +@RequiredArgsConstructor public class PortalRestExceptionHandler { + private final PortalProperties portalProperties; + @ExceptionHandler(value = IntegrationException.class) public ResponseEntity handleHttpRequestMethodNotSupportedException(HttpServletRequest request, IntegrationException ex) { @@ -51,4 +59,13 @@ public class PortalRestExceptionHandler { ResponseDTO response = new ResponseDTO(500, "500", ex.getMessage()); return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR); } + + @ExceptionHandler(value = MaxUploadSizeExceededException.class) + public ResponseEntity handleMaxUploadSizeExceededException(HttpServletRequest request, MaxUploadSizeExceededException ex) { + log.warn("파일 업로드 용량 초과 (REST): {}", ex.getMessage()); + String maxSize = portalProperties.getFile().getMaxSize(); + String errorMessage = "파일 크기가 허용된 최대 용량(" + maxSize + ")을 초과했습니다."; + ResponseDTO response = new ResponseDTO(413, "413", errorMessage); + return new ResponseEntity<>(response, HttpStatus.PAYLOAD_TOO_LARGE); + } } diff --git a/src/main/java/com/eactive/apim/portal/common/util/DurationParser.java b/src/main/java/com/eactive/apim/portal/common/util/DurationParser.java new file mode 100644 index 0000000..d6fd033 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/common/util/DurationParser.java @@ -0,0 +1,76 @@ +package com.eactive.apim.portal.common.util; + +import java.time.Duration; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * 시간 표현 문자열을 Duration으로 파싱하는 유틸리티 + * 지원 형식: 5m (5분), 2h (2시간), 7d (7일) + */ +public class DurationParser { + + private static final Pattern DURATION_PATTERN = Pattern.compile("^(\\d+)([mhd])$"); + + /** + * 시간 표현 문자열을 Duration으로 변환 + * + * @param durationStr 시간 문자열 (예: "5m", "2h", "7d") + * @return Duration 객체 + * @throws IllegalArgumentException 형식이 올바르지 않을 경우 + */ + public static Duration parse(String durationStr) { + if (durationStr == null || durationStr.trim().isEmpty()) { + throw new IllegalArgumentException("Duration string cannot be null or empty"); + } + + String normalized = durationStr.trim().toLowerCase(); + Matcher matcher = DURATION_PATTERN.matcher(normalized); + + if (!matcher.matches()) { + throw new IllegalArgumentException( + "Invalid duration format: " + durationStr + + ". Expected format: (e.g., 5m, 2h, 7d)" + ); + } + + long value = Long.parseLong(matcher.group(1)); + String unit = matcher.group(2); + + switch (unit) { + case "m": + return Duration.ofMinutes(value); + case "h": + return Duration.ofHours(value); + case "d": + return Duration.ofDays(value); + default: + throw new IllegalArgumentException("Unsupported time unit: " + unit); + } + } + + /** + * 시간 표현 문자열을 일(day) 단위로 변환 + * + * @param durationStr 시간 문자열 (예: "5m", "2h", "7d") + * @return 일(day) 단위 long 값 + */ + public static long parseToDays(String durationStr) { + return parse(durationStr).toDays(); + } + + /** + * 기본값을 제공하는 안전한 파싱 + * + * @param durationStr 시간 문자열 + * @param defaultValue 파싱 실패 시 기본값 + * @return Duration 객체 + */ + public static Duration parseOrDefault(String durationStr, Duration defaultValue) { + try { + return parse(durationStr); + } catch (Exception e) { + return defaultValue; + } + } +} diff --git a/src/main/java/com/eactive/apim/portal/common/util/HttpRequestUtil.java b/src/main/java/com/eactive/apim/portal/common/util/HttpRequestUtil.java new file mode 100644 index 0000000..db8b518 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/common/util/HttpRequestUtil.java @@ -0,0 +1,99 @@ +package com.eactive.apim.portal.common.util; + +import javax.servlet.http.HttpServletRequest; + +/** + * HTTP 요청 관련 유틸리티 클래스 + */ +public class HttpRequestUtil { + + private static final String[] IP_HEADER_CANDIDATES = { + "X-Forwarded-For", + "Proxy-Client-IP", + "WL-Proxy-Client-IP", + "HTTP_X_FORWARDED_FOR", + "HTTP_X_FORWARDED", + "HTTP_X_CLUSTER_CLIENT_IP", + "HTTP_CLIENT_IP", + "HTTP_FORWARDED_FOR", + "HTTP_FORWARDED", + "HTTP_VIA", + "REMOTE_ADDR", + "X-Real-IP" + }; + + /** + * Proxy를 통한 접속도 고려하여 실제 클라이언트 IP 주소를 반환 + * + * @param request HttpServletRequest 객체 + * @return 클라이언트 IP 주소 + */ + public static String getClientIpAddress(HttpServletRequest request) { + if (request == null) { + return "unknown"; + } + + String ip = null; + + // 헤더들을 순회하면서 IP 주소 찾기 + for (String header : IP_HEADER_CANDIDATES) { + ip = request.getHeader(header); + if (ip != null && !ip.isEmpty() && !"unknown".equalsIgnoreCase(ip)) { + break; + } + } + + // 헤더에서 IP를 찾지 못한 경우 기본 메서드 사용 + if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) { + ip = request.getRemoteAddr(); + } + + // X-Forwarded-For 헤더는 여러 IP를 포함할 수 있음 (쉼표로 구분) + // 첫 번째 IP가 실제 클라이언트 IP + if (ip != null && ip.contains(",")) { + ip = ip.split(",")[0].trim(); + } + + return ip; + } + + /** + * Proxy를 통한 접속도 고려하여 실제 클라이언트 호스트명을 반환 + * + * @param request HttpServletRequest 객체 + * @return 클라이언트 호스트명 + */ + public static String getClientHost(HttpServletRequest request) { + if (request == null) { + return "unknown"; + } + + String host = request.getHeader("X-Forwarded-Host"); + if (host == null || host.isEmpty() || "unknown".equalsIgnoreCase(host)) { + host = request.getRemoteHost(); + } + + return host; + } + + /** + * 요청이 Proxy를 통해 들어왔는지 확인 + * + * @param request HttpServletRequest 객체 + * @return Proxy를 통한 접속이면 true, 아니면 false + */ + public static boolean isProxied(HttpServletRequest request) { + if (request == null) { + return false; + } + + for (String header : IP_HEADER_CANDIDATES) { + String value = request.getHeader(header); + if (value != null && !value.isEmpty() && !"unknown".equalsIgnoreCase(value)) { + return true; + } + } + + return false; + } +} diff --git a/src/main/java/com/eactive/apim/portal/common/util/SecurityUtil.java b/src/main/java/com/eactive/apim/portal/common/util/SecurityUtil.java index f94d127..3c6520d 100644 --- a/src/main/java/com/eactive/apim/portal/common/util/SecurityUtil.java +++ b/src/main/java/com/eactive/apim/portal/common/util/SecurityUtil.java @@ -49,9 +49,11 @@ public final class SecurityUtil { public static PortalOrgDTO getUserOrg() { PortalOrgDTO portalOrgDTO = new PortalOrgDTO(); - if (Objects.requireNonNull(getPortalAuthenticatedUser()).getPortalOrg() != null) { - portalOrgDTO.setOrgName(getPortalAuthenticatedUser().getPortalOrg().getOrgName()); - portalOrgDTO.setId(getPortalAuthenticatedUser().getPortalOrg().getId()); + PortalAuthenticatedUser portalAuthenticatedUser = getPortalAuthenticatedUser(); + if (Objects.requireNonNull(portalAuthenticatedUser).getPortalOrg() != null) { + portalOrgDTO.setOrgName(portalAuthenticatedUser.getPortalOrg().getOrgName()); + portalOrgDTO.setId(portalAuthenticatedUser.getPortalOrg().getId()); + portalOrgDTO.setOrgCode(portalAuthenticatedUser.getPortalOrg().getOrgCode()); } return portalOrgDTO; diff --git a/src/main/java/com/eactive/apim/portal/common/util/StringRepeatUtil.java b/src/main/java/com/eactive/apim/portal/common/util/StringRepeatUtil.java new file mode 100644 index 0000000..b1259d3 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/common/util/StringRepeatUtil.java @@ -0,0 +1,43 @@ +package com.eactive.apim.portal.common.util; + +/** + * JDK 8 호환성을 위한 문자열 반복 유틸리티 + */ +public class StringRepeatUtil { + + /** + * 문자를 지정된 횟수만큼 반복하여 문자열을 생성 + * + * @param ch 반복할 문자 + * @param count 반복 횟수 + * @return 반복된 문자열 + */ + public static String repeat(char ch, int count) { + if (count <= 0) { + return ""; + } + char[] chars = new char[count]; + for (int i = 0; i < count; i++) { + chars[i] = ch; + } + return new String(chars); + } + + /** + * 문자열을 지정된 횟수만큼 반복 + * + * @param str 반복할 문자열 + * @param count 반복 횟수 + * @return 반복된 문자열 + */ + public static String repeat(String str, int count) { + if (str == null || count <= 0) { + return ""; + } + StringBuilder sb = new StringBuilder(str.length() * count); + for (int i = 0; i < count; i++) { + sb.append(str); + } + return sb.toString(); + } +} 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 new file mode 100644 index 0000000..221d879 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/common/util/UserTypeUtil.java @@ -0,0 +1,38 @@ +package com.eactive.apim.portal.common.util; + +import com.eactive.apim.portal.common.user.PortalAuthenticatedUser; +import org.apache.commons.lang3.StringUtils; + +/** + * 사용자 유형 판별 유틸리티 + * 내부 운영자와 외부 사용자를 구분하는 공통 로직 제공 + */ +public final class UserTypeUtil { + + private UserTypeUtil() { + throw new IllegalStateException("Utility class"); + } + + private static final String INTERNAL_EMAIL_DOMAIN = "@kjbank.com"; + + /** + * 현재 사용자가 내부 운영자인지 확인 + * @param user 인증된 사용자 정보 + * @return 내부 운영자 여부 + */ + public static boolean isInternalUser(PortalAuthenticatedUser user) { + if (user == null || StringUtils.isEmpty(user.getEmailAddr())) { + return false; + } + return user.getEmailAddr().toLowerCase().endsWith(INTERNAL_EMAIL_DOMAIN); + } + + /** + * 현재 로그인한 사용자가 내부 운영자인지 확인 + * @return 내부 운영자 여부 + */ + public static boolean isCurrentUserInternal() { + PortalAuthenticatedUser user = SecurityUtil.getPortalAuthenticatedUser(); + return isInternalUser(user); + } +} diff --git a/src/main/java/com/eactive/apim/portal/config/MultipartConfig.java b/src/main/java/com/eactive/apim/portal/config/MultipartConfig.java new file mode 100644 index 0000000..41a5f27 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/config/MultipartConfig.java @@ -0,0 +1,47 @@ +package com.eactive.apim.portal.config; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.multipart.commons.CommonsMultipartResolver; + +/** + * WAS(WebLogic, JEUS 등) 독립적인 Multipart 파일 업로드 설정. + *

    + * Spring Boot 내장 Tomcat의 spring.servlet.multipart 설정은 외부 WAS에서 동작하지 않으므로, + * CommonsMultipartResolver를 사용하여 모든 환경에서 일관된 파일 업로드 처리를 보장합니다. + *

    + */ +@Slf4j +@Configuration +@RequiredArgsConstructor +public class MultipartConfig { + + private final PortalProperties portalProperties; + + /** + * Commons FileUpload 기반 MultipartResolver 설정. + *

    + * Bean 이름을 "filterMultipartResolver"로 지정하여 + * PortalConfigWebDispatcherServlet의 MultipartFilter와 연동합니다. + *

    + * + * @return CommonsMultipartResolver 인스턴스 + */ + @Bean(name = "filterMultipartResolver") + public CommonsMultipartResolver filterMultipartResolver() { + CommonsMultipartResolver resolver = new CommonsMultipartResolver(); + + long maxSizeBytes = portalProperties.getFile().getMaxSizeBytes(); + resolver.setMaxUploadSize(maxSizeBytes); + resolver.setMaxUploadSizePerFile(maxSizeBytes); + resolver.setMaxInMemorySize((int) Math.min(maxSizeBytes, Integer.MAX_VALUE)); + resolver.setDefaultEncoding("UTF-8"); + + log.info("CommonsMultipartResolver configured with maxUploadSize: {} bytes ({})", + maxSizeBytes, portalProperties.getFile().getMaxSize()); + + return resolver; + } +} 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 c6d6f2e..acf4c30 100644 --- a/src/main/java/com/eactive/apim/portal/config/PortalAuthenticationFailureHandler.java +++ b/src/main/java/com/eactive/apim/portal/config/PortalAuthenticationFailureHandler.java @@ -3,6 +3,8 @@ package com.eactive.apim.portal.config; import com.eactive.apim.portal.apps.login.constants.LoginConstants; import com.eactive.apim.portal.apps.user.service.PortalUserLogService; import com.eactive.apim.portal.common.exception.UserNotFoundException; +import com.eactive.apim.portal.common.util.HttpRequestUtil; +import com.eactive.apim.portal.common.util.StringRepeatUtil; import com.eactive.apim.portal.portaluser.entity.PortalUser; import com.eactive.apim.portal.portaluser.repository.PortalUserRepository; import com.eactive.apim.portal.template.entity.MessageCode; @@ -23,6 +25,8 @@ 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; /** * Created by Sungpil Hyun @@ -32,6 +36,9 @@ import java.io.IOException; public class PortalAuthenticationFailureHandler implements AuthenticationFailureHandler { private static final Logger logger = LoggerFactory.getLogger(PortalAuthenticationFailureHandler.class); + 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 PortalUserLogService userLogService; private final MessageHandlerService messageHandlerService; @@ -49,6 +56,8 @@ public class PortalAuthenticationFailureHandler implements AuthenticationFailure @Transactional(propagation = Propagation.REQUIRES_NEW, noRollbackFor = {AuthenticationException.class, UserNotFoundException.class}) public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException { String username = request.getParameter("id"); + // 이메일 소문자 변환 적용 + String normalizedUsername = username != null ? username.toLowerCase() : null; String ip = request.getRemoteAddr(); String sessionId = request.getSession().getId(); @@ -56,8 +65,8 @@ public class PortalAuthenticationFailureHandler implements AuthenticationFailure logger.error(exception.getLocalizedMessage()); } else if (!(exception instanceof UsernameNotFoundException)) { try { - PortalUser user = portalUserRepository.findPortalUserByEmailAddr(username) - .orElseThrow(() -> new UserNotFoundException(username)); + PortalUser user = portalUserRepository.findPortalUserByEmailAddr(normalizedUsername) + .orElseThrow(() -> new UserNotFoundException(normalizedUsername)); user.setLoginFailureCount(user.getLoginFailureCount() + 1); if (user.getLoginFailureCount() >= 5) { @@ -80,10 +89,56 @@ public class PortalAuthenticationFailureHandler implements AuthenticationFailure userLogService.logFailure(username, ip, sessionId); + // 로그인 실패 시 세션 정보 로깅 + logLoginFailure(request, username, exception); + HttpSession session = request.getSession(); session.setAttribute(LoginConstants.LOGIN_MESSAGE, exception.getLocalizedMessage()); session.setAttribute("loginId", username); String contextPath = request.getContextPath(); response.sendRedirect(contextPath + "/login"); } + + 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("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 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.warn(logMessage.toString()); + } } 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 0806640..2935022 100644 --- a/src/main/java/com/eactive/apim/portal/config/PortalAuthenticationSuccessHandler.java +++ b/src/main/java/com/eactive/apim/portal/config/PortalAuthenticationSuccessHandler.java @@ -1,6 +1,13 @@ 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.portaluser.entity.PortalUser; import com.eactive.apim.portal.portaluser.entity.PortalUserEnums; import com.eactive.apim.portal.portaluser.entity.UserPasswordHistory; @@ -10,6 +17,8 @@ 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.web.authentication.AuthenticationSuccessHandler; import org.springframework.stereotype.Service; @@ -21,6 +30,7 @@ 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; @@ -29,17 +39,24 @@ import java.util.Optional; @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; @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { String username = request.getParameter("id"); - PortalUser user = portalUserRepository.findPortalUserByEmailAddr(username).orElse(null); + // 이메일 소문자 변환 적용 + 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; @@ -74,6 +91,30 @@ public class PortalAuthenticationSuccessHandler implements AuthenticationSuccess session.setAttribute("redirectUrl", contextPath + "/new_password"); } + // 초대 코드 확인 - ROLE_USER만 확인 (세션에 저장하여 메인 페이지에서 팝업으로 표시) + if (user.getRoleCode() == PortalUserEnums.RoleCode.ROLE_USER) { + Optional pendingInvitation = + userInvitationRepository.findFirstByInvitationEmailAndStatus( + user.getLoginId(), InvitationStatus.PENDING); + + 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); + } + } + } + + // 로그인 성공 시 세션 정보 로깅 + logLoginSuccess(request, session, username); + String decisionToken = (String) request.getSession().getAttribute("decisionToken"); if (decisionToken != null) { response.sendRedirect(contextPath + "/signup/decision_process"); @@ -82,6 +123,48 @@ public class PortalAuthenticationSuccessHandler implements AuthenticationSuccess } } + 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 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 dc3b892..f79ec29 100644 --- a/src/main/java/com/eactive/apim/portal/config/PortalConfigLog.java +++ b/src/main/java/com/eactive/apim/portal/config/PortalConfigLog.java @@ -1,5 +1,6 @@ package com.eactive.apim.portal.config; +import com.eactive.apim.portal.common.util.HttpRequestUtil; import com.eactive.apim.portal.common.util.SecurityUtil; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; @@ -29,7 +30,7 @@ public class PortalConfigLog { String method = request.getMethod(); String path = request.getRequestURI(); String query = request.getQueryString(); - String ip = request.getRemoteAddr(); + String ip = HttpRequestUtil.getClientIpAddress(request); String user = SecurityUtil.getCurrentLoginId(); diff --git a/src/main/java/com/eactive/apim/portal/config/PortalConfigMessageSource.java b/src/main/java/com/eactive/apim/portal/config/PortalConfigMessageSource.java index 0e2bf58..0539cb0 100644 --- a/src/main/java/com/eactive/apim/portal/config/PortalConfigMessageSource.java +++ b/src/main/java/com/eactive/apim/portal/config/PortalConfigMessageSource.java @@ -24,6 +24,7 @@ public class PortalConfigMessageSource { @Bean public ReloadableResourceBundleMessageSource messageSource() { PortalReloadableResourceBundleMessageSource reloadableResourceBundleMessageSource = new PortalReloadableResourceBundleMessageSource(); + reloadableResourceBundleMessageSource.setDefaultEncoding("UTF-8"); reloadableResourceBundleMessageSource.setEgovBaseNames(Arrays.asList("classpath*:message/**/*")); return reloadableResourceBundleMessageSource; } 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 0191d6f..43bc85b 100644 --- a/src/main/java/com/eactive/apim/portal/config/PortalConfigSecurity.java +++ b/src/main/java/com/eactive/apim/portal/config/PortalConfigSecurity.java @@ -31,13 +31,17 @@ public class PortalConfigSecurity { private final PortalAuthenticationManager portalAuthenticationManager; + private final PortalLogoutSuccessHandler logoutSuccessHandler; + @Autowired public PortalConfigSecurity(PortalAuthenticationFailureHandler authenticationFailureHandler, PortalAuthenticationSuccessHandler authenticationSuccessHandler, - PortalAuthenticationManager portalAuthenticationManager) { + PortalAuthenticationManager portalAuthenticationManager, + PortalLogoutSuccessHandler logoutSuccessHandler) { this.authenticationFailureHandler = authenticationFailureHandler; this.authenticationSuccessHandler = authenticationSuccessHandler; this.portalAuthenticationManager = portalAuthenticationManager; + this.logoutSuccessHandler = logoutSuccessHandler; } @Bean @@ -71,7 +75,7 @@ public class PortalConfigSecurity { .successHandler(authenticationSuccessHandler)) .logout(logout -> logout .logoutRequestMatcher(new AntPathRequestMatcher("/actionLogout.do", "GET")) - .logoutSuccessUrl("/")) + .logoutSuccessHandler(logoutSuccessHandler)) .csrf(csrf -> csrf .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) .ignoringRequestMatchers(new AntPathRequestMatcher("/_proxy/**/*")) diff --git a/src/main/java/com/eactive/apim/portal/config/PortalLogoutSuccessHandler.java b/src/main/java/com/eactive/apim/portal/config/PortalLogoutSuccessHandler.java new file mode 100644 index 0000000..27214a3 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/config/PortalLogoutSuccessHandler.java @@ -0,0 +1,103 @@ +package com.eactive.apim.portal.config; + +import com.eactive.apim.portal.common.util.HttpRequestUtil; +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.LogoutSuccessHandler; +import org.springframework.stereotype.Component; + +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.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Enumeration; + +/** + * 로그아웃 성공 시 세션 정보를 로깅하는 핸들러 + */ +@Component +public class PortalLogoutSuccessHandler implements 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"); + + @Override + public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, + Authentication authentication) throws IOException, ServletException { + HttpSession session = request.getSession(false); + + if (session != null) { + 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("Logout At: ").append(LocalDateTime.now().format(formatter)).append("\n"); + + if (authentication != null) { + logMessage.append("Username: ").append(authentication.getName()).append("\n"); + logMessage.append("Authenticated: ").append(authentication.isAuthenticated()).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("\n"); + logMessage.append(StringRepeatUtil.repeat('-', 80)).append("\n"); + logMessage.append("SESSION ATTRIBUTES (Before Invalidation)\n"); + logMessage.append(StringRepeatUtil.repeat('-', 80)).append("\n"); + + try { + Enumeration attributeNames = session.getAttributeNames(); + while (attributeNames.hasMoreElements()) { + String attrName = attributeNames.nextElement(); + Object attrValue = session.getAttribute(attrName); + String valueStr = attrValue != null ? attrValue.toString() : "null"; + if (valueStr.length() > 100) { + valueStr = valueStr.substring(0, 97) + "..."; + } + logMessage.append(String.format(" %-30s : %s\n", attrName, valueStr)); + } + } catch (Exception e) { + logMessage.append(" Unable to retrieve session attributes\n"); + } + + logMessage.append(StringRepeatUtil.repeat('=', 80)).append("\n"); + + sessionLogger.info(logMessage.toString()); + } + + 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 51beca9..f1306b5 100644 --- a/src/main/java/com/eactive/apim/portal/config/PortalProperties.java +++ b/src/main/java/com/eactive/apim/portal/config/PortalProperties.java @@ -25,5 +25,34 @@ public class PortalProperties { private String authVirtualCode = ""; + private boolean testAuthNoticeEnabled = false; + private Map> portalSecurity; + + private FileProperties file = new FileProperties(); + + /** + * 파일 업로드 관련 설정 + */ + @Data + public static class FileProperties { + private String maxSize = "8MB"; + private String allowedExtensions = "pdf,doc,docx,xls,xlsx,ppt,pptx,hwp,gif,jpg,jpeg,png"; + + /** + * maxSize 문자열을 bytes로 변환 + * "8MB" -> 8388608, "10MB" -> 10485760 + */ + public long getMaxSizeBytes() { + String size = maxSize.toUpperCase().trim(); + if (size.endsWith("MB")) { + return Long.parseLong(size.replace("MB", "").trim()) * 1024 * 1024; + } else if (size.endsWith("KB")) { + return Long.parseLong(size.replace("KB", "").trim()) * 1024; + } else if (size.endsWith("GB")) { + return Long.parseLong(size.replace("GB", "").trim()) * 1024 * 1024 * 1024; + } + return Long.parseLong(size); // bytes로 간주 + } + } } diff --git a/src/main/java/com/eactive/apim/portal/config/RestTemplateConfig.java b/src/main/java/com/eactive/apim/portal/config/RestTemplateConfig.java new file mode 100644 index 0000000..314d12d --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/config/RestTemplateConfig.java @@ -0,0 +1,35 @@ +package com.eactive.apim.portal.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.client.SimpleClientHttpRequestFactory; +import org.springframework.web.client.RestTemplate; + +/** + * RestTemplate 설정 클래스 + * 외부 API 호출을 위한 RestTemplate Bean을 제공합니다. + * + * @author sungpilhyun + * @since 2025-01-16 + */ +@Configuration +public class RestTemplateConfig { + + /** + * RestTemplate Bean 생성 + * + * @return 설정된 RestTemplate 인스턴스 + */ + @Bean + public RestTemplate restTemplate() { + SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory(); + + // 연결 타임아웃: 10초 + factory.setConnectTimeout(10000); + + // 읽기 타임아웃: 30초 + factory.setReadTimeout(30000); + + return new RestTemplate(factory); + } +} diff --git a/src/main/java/com/eactive/apim/portal/config/SessionLoggingListener.java b/src/main/java/com/eactive/apim/portal/config/SessionLoggingListener.java new file mode 100644 index 0000000..91154df --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/config/SessionLoggingListener.java @@ -0,0 +1,134 @@ +package com.eactive.apim.portal.config; + +import com.eactive.apim.portal.common.util.HttpRequestUtil; +import com.eactive.apim.portal.common.util.StringRepeatUtil; +import lombok.extern.slf4j.Slf4j; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; +import javax.servlet.http.HttpSessionEvent; +import javax.servlet.http.HttpSessionListener; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Enumeration; + +/** + * HttpSession 생성 시 요청 헤더 정보를 로깅하는 리스너 + */ +@Component +public class SessionLoggingListener implements HttpSessionListener { + + private static final Logger sessionLogger = LoggerFactory.getLogger("eapim.portal.session"); + private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"); + + @Override + public void sessionCreated(HttpSessionEvent se) { + HttpSession session = se.getSession(); + + // HttpServletRequest 가져오기 + HttpServletRequest request = (HttpServletRequest) session.getAttribute("org.springframework.web.context.request.RequestContextListener.REQUEST_ATTRIBUTES"); + + // Spring의 RequestContextHolder를 통해 현재 요청 가져오기 + try { + org.springframework.web.context.request.RequestAttributes requestAttributes = + org.springframework.web.context.request.RequestContextHolder.getRequestAttributes(); + + if (requestAttributes instanceof org.springframework.web.context.request.ServletRequestAttributes) { + request = ((org.springframework.web.context.request.ServletRequestAttributes) requestAttributes).getRequest(); + } + } catch (Exception e) { + // RequestContextHolder를 통해 요청을 가져올 수 없는 경우 무시 + } + + StringBuilder logMessage = new StringBuilder(); + logMessage.append("\n"); + 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("Created At: ").append(LocalDateTime.now().format(formatter)).append("\n"); + logMessage.append("Max Inactive Interval: ").append(session.getMaxInactiveInterval()).append(" seconds\n"); + + if (request != null) { + 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("Remote Port: ").append(request.getRemotePort()).append("\n"); + logMessage.append("Request Method: ").append(request.getMethod()).append("\n"); + logMessage.append("Request URI: ").append(request.getRequestURI()).append("\n"); + logMessage.append("Request URL: ").append(request.getRequestURL()).append("\n"); + logMessage.append("Query String: ").append(request.getQueryString()).append("\n"); + logMessage.append("Protocol: ").append(request.getProtocol()).append("\n"); + logMessage.append("Scheme: ").append(request.getScheme()).append("\n"); + logMessage.append("Server Name: ").append(request.getServerName()).append("\n"); + logMessage.append("Server Port: ").append(request.getServerPort()).append("\n"); + logMessage.append("Context Path: ").append(request.getContextPath()).append("\n"); + + logMessage.append("\n"); + logMessage.append(StringRepeatUtil.repeat('-', 80)).append("\n"); + logMessage.append("REQUEST HEADERS\n"); + logMessage.append(StringRepeatUtil.repeat('-', 80)).append("\n"); + + Enumeration headerNames = request.getHeaderNames(); + while (headerNames.hasMoreElements()) { + String headerName = headerNames.nextElement(); + Enumeration headerValues = request.getHeaders(headerName); + while (headerValues.hasMoreElements()) { + String headerValue = headerValues.nextElement(); + logMessage.append(String.format(" %-30s : %s\n", headerName, headerValue)); + } + } + } else { + logMessage.append("\n"); + logMessage.append("WARNING: HttpServletRequest not available\n"); + } + + logMessage.append(StringRepeatUtil.repeat('=', 80)).append("\n"); + + sessionLogger.info(logMessage.toString()); + } + + @Override + public void sessionDestroyed(HttpSessionEvent se) { + HttpSession session = se.getSession(); + + StringBuilder logMessage = new StringBuilder(); + logMessage.append("\n"); + 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("Destroyed At: ").append(LocalDateTime.now().format(formatter)).append("\n"); + logMessage.append("Max Inactive Interval: ").append(session.getMaxInactiveInterval()).append(" seconds\n"); + + // 세션 속성 정보 로깅 + try { + logMessage.append("\n"); + logMessage.append(StringRepeatUtil.repeat('-', 80)).append("\n"); + logMessage.append("SESSION ATTRIBUTES\n"); + logMessage.append(StringRepeatUtil.repeat('-', 80)).append("\n"); + + java.util.Enumeration attributeNames = session.getAttributeNames(); + while (attributeNames.hasMoreElements()) { + String attrName = attributeNames.nextElement(); + Object attrValue = session.getAttribute(attrName); + logMessage.append(String.format(" %-30s : %s\n", attrName, attrValue)); + } + } catch (Exception e) { + logMessage.append(" Unable to retrieve session attributes\n"); + } + + logMessage.append(StringRepeatUtil.repeat('=', 80)).append("\n"); + + sessionLogger.info(logMessage.toString()); + } +} diff --git a/src/main/java/com/eactive/apim/portal/custom/config/KjbPasswordEncoder.java b/src/main/java/com/eactive/apim/portal/custom/config/KjbPasswordEncoder.java index 2be694d..c404f92 100644 --- a/src/main/java/com/eactive/apim/portal/custom/config/KjbPasswordEncoder.java +++ b/src/main/java/com/eactive/apim/portal/custom/config/KjbPasswordEncoder.java @@ -1,19 +1,26 @@ package com.eactive.apim.portal.custom.config; -import com.eactive.ext.kjb.safedb.KjbSafedbWrapper; +//import com.eactive.ext.kjb.safedb.KjbSafedbWrapper; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; public class KjbPasswordEncoder implements PasswordEncoder { + + private final BCryptPasswordEncoder bcryptEncoder = new BCryptPasswordEncoder(); + @Override public String encode(CharSequence rawPassword) { - KjbSafedbWrapper safedb = KjbSafedbWrapper.getInstance(); - return safedb.encryptPassword(rawPassword.toString()); + String bcryptHash = bcryptEncoder.encode(rawPassword); +// KjbSafedbWrapper safedb = KjbSafedbWrapper.getInstance(); +// return safedb.encryptNotRnnoString(bcryptHash); + return bcryptHash; } @Override public boolean matches(CharSequence rawPassword, String encodedPassword) { - KjbSafedbWrapper safedb = KjbSafedbWrapper.getInstance(); - String encoded = safedb.encryptPassword(rawPassword.toString()); - return encoded.equals(encodedPassword); + return bcryptEncoder.matches(rawPassword, encodedPassword); +// KjbSafedbWrapper safedb = KjbSafedbWrapper.getInstance(); +// String bcryptHash = safedb.decryptNotRnno(encodedPassword); +// return bcryptEncoder.matches(rawPassword, bcryptHash); } } diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index 09dae30..376e3fb 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -13,6 +13,7 @@ spring: devtools: restart: enabled: false + additional-exclude: logback*.xml livereload: enabled: true thymeleaf: @@ -20,23 +21,26 @@ spring: ems: datasource: - jdbc-url: jdbc:oracle:thin:@//192.168.240.177:1599/DAPM + jdbc-url: jdbc:oracle:thin:@//localhost:1599/DAPM +# jdbc-url: jdbc:oracle:thin:@//192.168.240.177:1599/DAPM # jdbc-url: jdbc:oracle:thin:@//192.168.240.177:1599/PAPM driver-class-name: oracle.jdbc.OracleDriver username: EMSAPP - password: EMSAPP123! + password: elink1234 gateway: datasource: - jdbc-url: jdbc:oracle:thin:@//192.168.240.177:1599/DAPM + jdbc-url: jdbc:oracle:thin:@//localhost:1599/DAPM +# jdbc-url: jdbc:oracle:thin:@//192.168.240.177:1599/DAPM # jdbc-url: jdbc:oracle:thin:@//192.168.240.177:1599/PAPM driver-class-name: oracle.jdbc.OracleDriver username: AGWAPP - password: AGWAPP123! + password: elink1234 portal: auth-virtual-code: 654321 + test-auth-notice-enabled: true proxy: targets: diff --git a/src/main/resources/application-gf63.yml b/src/main/resources/application-gf63.yml deleted file mode 100644 index d0ad9e3..0000000 --- a/src/main/resources/application-gf63.yml +++ /dev/null @@ -1,51 +0,0 @@ -server.port: '30200' - -spring: - jta: - enabled: true - jpa: - properties: - hibernate: - show_sql: false - format_sql: true - use_sql_comments: true - devtools: - restart: - enabled: false - livereload: - enabled: true - thymeleaf: - cache: false - -ems: - datasource: - jdbc-url: jdbc:oracle:thin:@//192.168.240.177:1599/DAPM -# jdbc-url: jdbc:oracle:thin:@//192.168.240.177:1599/PAPM - driver-class-name: oracle.jdbc.OracleDriver - username: EMSAPP - password: EMSAPP123! - - -gateway: - datasource: - jdbc-url: jdbc:oracle:thin:@//192.168.240.177:1599/DAPM -# jdbc-url: jdbc:oracle:thin:@//192.168.240.177:1599/PAPM - driver-class-name: oracle.jdbc.OracleDriver - username: AGWAPP - password: AGWAPP123! - -portal: - logging: - log-path: d:/kjb-logs - auth-virtual-code: 654321 - -proxy: - targets: - stg: http://localhost:10000/monitoring - prod: http://localhost:10000/monitoring - - # stg: http://inter-ndapiap01.k-bank.com:7090/monitoring - # prod: http://inter-ndapiap01.k-bank.com:7090/monitoring - timeout: - connect: 5000 - read: 5000 diff --git a/src/main/resources/application-kjb_rinjae.yml b/src/main/resources/application-kjb_rinjae.yml deleted file mode 100644 index 17b7fbe..0000000 --- a/src/main/resources/application-kjb_rinjae.yml +++ /dev/null @@ -1,51 +0,0 @@ -server.port: '30200' - -spring: - jta: - enabled: true - jpa: - properties: - hibernate: - show_sql: false - format_sql: true - use_sql_comments: true - devtools: - restart: - enabled: false - livereload: - enabled: true - thymeleaf: - cache: false - -ems: - datasource: -# jdbc-url: jdbc:oracle:thin:@//192.168.240.177:1599/DAPM - jdbc-url: jdbc:oracle:thin:@//10.14.8.30:11521/ELINKDB - driver-class-name: oracle.jdbc.OracleDriver - username: EMSAPP - password: EMSAPP123! - - -gateway: - datasource: -# jdbc-url: jdbc:oracle:thin:@//192.168.240.177:1599/DAPM - jdbc-url: jdbc:oracle:thin:@//10.14.8.30:11521/ELINKDB - driver-class-name: oracle.jdbc.OracleDriver - username: AGWAPP - password: AGWAPP123! - -portal: - logging: - log-path: C:\eactive\logs - auth-virtual-code: 654321 - -proxy: - targets: - stg: http://localhost:10000/monitoring - prod: http://localhost:10000/monitoring - - # stg: http://inter-ndapiap01.k-bank.com:7090/monitoring - # prod: http://inter-ndapiap01.k-bank.com:7090/monitoring - timeout: - connect: 5000 - read: 5000 diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml index 1532889..333b9ba 100644 --- a/src/main/resources/application-prod.yml +++ b/src/main/resources/application-prod.yml @@ -4,6 +4,13 @@ spring: config: activate: on-profile: prod + web: + resources: + cache: + period: 86400 # 1일 (운영 환경 - 캐시 활성화) + cachecontrol: + max-age: 86400 + public: true jpa: properties: hibernate: @@ -18,10 +25,5 @@ spring: thymeleaf: cache: false -proxy: - targets: - stg: http://inter-dapiwas01.k-bank.com:7090/monitoring - prod: http://inter-apiwas00.k-bank.com:7090/monitoring - timeout: - connect: 5000 - read: 5000 \ No newline at end of file +portal: + test-auth-notice-enabled: false # prod 환경: UI 안내 미표시 \ No newline at end of file diff --git a/src/main/resources/application-stage.yml b/src/main/resources/application-stage.yml index d9a4f86..6cf4377 100644 --- a/src/main/resources/application-stage.yml +++ b/src/main/resources/application-stage.yml @@ -1,6 +1,12 @@ spring: jta: enabled: true + web: + resources: + cache: + period: 3600 # 1시간 (스테이징 환경) + cachecontrol: + max-age: 3600 jpa: properties: hibernate: @@ -16,7 +22,9 @@ spring: cache: false portal: - auth-virtual-code: 654321 + test-auth-notice-enabled: true +# 검증 단계에선 사용하지 않음 +# auth-virtual-code: 654321 ems: @@ -37,10 +45,3 @@ gateway: hibernate-physical-naming-strategy: com.eactive.apim.portal.common.entity.CustomPhysicalNamingStrategy entity-package: com.eactive.eai.data.entity.onl -#proxy: -# targets: -# stg: http://inter-dapiwas01.k-bank.com:7090/monitoring -# prod: http://inter-dapiwas01.k-bank.com:7090/monitoring -# timeout: -# connect: 5000 -# read: 5000 \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 1bced55..f3582d9 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -25,6 +25,15 @@ spring: jpa: open-in-view: false + web: + resources: + cache: + period: 0 # 캐시 비활성화 (개발환경) + cachecontrol: + max-age: 0 + must-revalidate: true + no-cache: true + jta: enabled: true atomikos: @@ -40,11 +49,6 @@ spring: cache: false main: allow-bean-definition-overriding: true - servlet: - multipart: - max-file-size: 8MB - max-request-size: 8MB - security: basic: enabled: false @@ -56,6 +60,7 @@ ems: datasource: jndi-name: jdbc/dsOBP_EMS schema: EMSADM + driver-class-name: oracle.jdbc.OracleDriver hibernate-dialect: org.hibernate.dialect.Oracle12cDialect hibernate-physical-naming-strategy: com.eactive.apim.portal.common.entity.CustomPhysicalNamingStrategy @@ -65,18 +70,24 @@ gateway: datasource: jndi-name: jdbc/dsOBP_AGW schema: AGWADM + driver-class-name: oracle.jdbc.OracleDriver 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 portal: + file: + max-size: 10MB + allowed-extensions: pdf,doc,docx,xls,xlsx,ppt,pptx,hwp,gif,jpg,jpeg,png + logging: log-path: /Log/App/eapim/ auth-ttl: 300 auth: resend_limit_seconds: 30 + test-auth-notice-enabled: true # 기본값: true (대부분의 개발환경에서 UI 안내 표시) user-approval: true password-expiration-days: 90 @@ -156,6 +167,14 @@ portal: method: GET view-name: apps/main/intro + - path-pattern: /service/intro + method: GET + view-name: apps/service/intro + + - path-pattern: /service/guide + method: GET + view-name: apps/service/guide + - path-pattern: /dashboard method: GET view-name: apps/mypage/dashboard @@ -212,6 +231,13 @@ page: corp: name: "법인회원가입" path: "/signup/portalOrg" + agreements: + name: 약관 + path: "#" + children: + terms: + name: "이용약관 및 개인정보처리방침" + path: "/agreements/terms" about: name: 안내 path: "#" @@ -222,6 +248,16 @@ page: guide: name: "이용안내" path: "/guide" + service: + name: 서비스 소개 + path: "#" + children: + intro: + name: "API 포탈 소개" + path: "/service/intro" + guide: + name: "회원가입 안내" + path: "/service/guide" apis: name: "API" path: "#" @@ -229,48 +265,9 @@ page: apis: name: "API 목록" path: "/apis" - api_common: - name: "API 상세보기" - path: "/apis/common" - api_KAPAP004U3: - name: "API 상세보기" - path: "/apis/KAPAP004U3" - api_KAPAP004U4: - name: "API 상세보기" - path: "/apis/KAPAP004U4" - api_KAPAP004U5: - name: "API 상세보기" - path: "/apis/KAPAP004U5" - api_KAPAP004U6: - name: "API 상세보기" - path: "/apis/KAPAP004U6" - api_KAPAP004U7: - name: "API 상세보기" - path: "/apis/KAPAP004U7" - api_KAPAP004U8: - name: "API 상세보기" - path: "/apis/KAPAP004U8" - api_KAPAP004U9: - name: "API 상세보기" - path: "/apis/KAPAP004U9" - api_KAPAP004U10: - name: "API 상세보기" - path: "/apis/KAPAP004U10" - api_token_spec: - name: "API 상세보기" - path: "/apis/token-spec" api_detail: name: "API 상세보기" path: "/apis/detail" - api_key_new: - name: "API 키 신청" - path: "/apis/apikey/new" - api_key_prod: - name: "운영 API 키 신청" - path: "/apis/apikey/prod" - testbed: - name: 테스트 베드 - path: "/apis/testbed" community: name: 고객지원 path: "#" @@ -306,39 +303,53 @@ page: mypage: name: "내 정보 관리" path: "/mypage" - dashboard: - name: 대시보드 - path: "/dashboard" user_list: name: "이용자 관리" path: "/users" user_detail: - name: "이용자 관리" + name: "이용자 정보" path: "/users/detail" - app_request: - name: "인증키 관리" - path: "/myapikey/api_key_request/history" - app_request_prod: - name: "인증키 관리" - path: "/myapikey/api_key_request/prod_history" - app_request_detail: - name: "인증키 관리" - path: "/myapikey/api_key_request/detail" - app_request_prod_detail: - name: "인증키 관리" - path: "/myapikey/api_key_request/prod_detail" apikey: name: "인증키 관리" path: "/myapikey" - apikey_prod: - name: "인증키 관리" - path: "/myapikey/api_key_prod_page" - apikey_detail: - name: "인증키 관리" - path: "/myapikey/api_key_detail" - apikey_prod_detail: - name: "인증키 관리" - path: "/myapikey/api_key_prod_detail" + app_request_detail: + name: "인증키 신청 상세" + path: "/myapikey/app_request_detail" + credential_detail: + name: "인증키 정보" + path: "/myapikey/credential_detail" change_password: name: "비밀번호 변경" path: "/change_password" + verify_current_password: + name: "비밀번호 변경" + path: "/verify_current_password" + myapikey_register_step1: + name: "앱 생성 (기본 정보)" + path: "/myapikey/register/step1" + myapikey_register_step2: + name: "앱 생성 (API 선택)" + path: "/myapikey/register/step2" + myapikey_register_step3: + name: "앱 생성 요청 완료" + path: "/myapikey/register/step3" + myapikey_modify_step1: + name: "앱 수정 (기본 정보)" + path: "/myapikey/modify/step1" + myapikey_modify_step2: + name: "앱 수정 (API 선택)" + path: "/myapikey/modify/step2" + myapikey_modify_step3: + name: "앱 수정 요청 완료" + path: "/myapikey/modify/step3" + commission: + name: "과금 관리" + path: "/commission/manage" + api_statistics: + name: "이용 통계" + path: "/statistics/api" + +# 에디터 이미지 설정 (약관 이미지 표시용) +editor: + image: + base-url: /file/view diff --git a/src/main/resources/logback-debug.xml b/src/main/resources/logback-debug.xml index 3892104..db0b928 100644 --- a/src/main/resources/logback-debug.xml +++ b/src/main/resources/logback-debug.xml @@ -46,6 +46,17 @@ + + ${LOG_PATH}/http-session.log + + ${LOG_PATH}/backup/http-session.%d{yyyy-MM-dd}.log + 30 + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level - %msg%n + + + @@ -79,4 +90,8 @@ + + + + \ No newline at end of file diff --git a/src/main/resources/logback-gf63.xml b/src/main/resources/logback-gf63.xml index 27f5050..849fd81 100644 --- a/src/main/resources/logback-gf63.xml +++ b/src/main/resources/logback-gf63.xml @@ -30,6 +30,17 @@ + + ${LOG_PATH}/http-session.log + + ${LOG_PATH}/backup/http-session.%d{yyyy-MM-dd}.log + 30 + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level - %msg%n + + + @@ -65,4 +76,8 @@ + + + + \ No newline at end of file diff --git a/src/main/resources/logback-rinjae.xml b/src/main/resources/logback-rinjae.xml index 59120c4..ac69065 100644 --- a/src/main/resources/logback-rinjae.xml +++ b/src/main/resources/logback-rinjae.xml @@ -38,6 +38,17 @@ + + ${LOG_PATH}/http-session.log + + ${LOG_PATH}/backup/http-session.%d{yyyy-MM-dd}.log + 30 + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level - %msg%n + + + @@ -79,4 +90,8 @@ + + + + \ No newline at end of file diff --git a/src/main/resources/logback-spring.xml b/src/main/resources/logback-spring.xml index 5698dc6..c595f5a 100644 --- a/src/main/resources/logback-spring.xml +++ b/src/main/resources/logback-spring.xml @@ -1,4 +1,6 @@ - + + + @@ -13,8 +15,9 @@ ${LOG_PATH}/hibernate.log - - ${LOG_PATH}/backup/hibernate.%d{yyyy-MM-dd}.log + + ${LOG_PATH}/backup/hibernate.%d{yyyy-MM-dd}.%i.log + 500MB 7 @@ -24,8 +27,9 @@ ${LOG_PATH}/portal.log - - ${LOG_PATH}/backup/portal.%d{yyyy-MM-dd}.log + + ${LOG_PATH}/backup/portal.%d{yyyy-MM-dd}.%i.log + 500MB 7 @@ -35,8 +39,9 @@ ${LOG_PATH}/need-fix.log - - ${LOG_PATH}/backup/need-fix.%d{yyyy-MM-dd}.log + + ${LOG_PATH}/backup/need-fix.%d{yyyy-MM-dd}.%i.log + 500MB 90 @@ -44,6 +49,18 @@ + + ${LOG_PATH}/http-session.log + + ${LOG_PATH}/backup/http-session.%d{yyyy-MM-dd}.%i.log + 500MB + 30 + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level - %msg%n + + + ${CONSOLE_PATTERN} @@ -53,6 +70,10 @@ + + + + @@ -61,6 +82,7 @@ + diff --git a/src/main/resources/message/apim/message_en.properties b/src/main/resources/message/apim/message_en.properties index c979e33..5deccc9 100644 --- a/src/main/resources/message/apim/message_en.properties +++ b/src/main/resources/message/apim/message_en.properties @@ -1,4 +1,4 @@ -title.html=Developer Portal +title.html=KJBank API Portal Portal password.not.match=Password is not matched authnumber.not.match=Auth number is not matched. apim.route.register.file=File diff --git a/src/main/resources/message/apim/message_ko.properties b/src/main/resources/message/apim/message_ko.properties index fe38a41..176e88b 100644 --- a/src/main/resources/message/apim/message_ko.properties +++ b/src/main/resources/message/apim/message_ko.properties @@ -1,4 +1,4 @@ -title.html=Kbank API Portal +title.html=\uAD11\uC8FC\uC740\uD589 API Portal apim.route.register.file=\uCCA8\uBD80\uD30C\uC77C field.required=\uD544\uC218 \uC785\uB825\uD56D\uBAA9\uC785\uB2C8\uB2E4. companyName=\uD68C\uC0AC\uBA85 diff --git a/src/main/resources/static/css/api-statistics.css b/src/main/resources/static/css/api-statistics.css new file mode 100644 index 0000000..9e8581f --- /dev/null +++ b/src/main/resources/static/css/api-statistics.css @@ -0,0 +1,442 @@ +/* API Statistics Page Styles */ + +.api-statistics-container { + /*max-width: 1200px;*/ + margin: 0 auto; + padding: 48px 0px; +} + +/* Search Section */ +.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: transparent; + 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 0.2s; +} + +.btn-search:hover { + background: #003d99; +} + +.btn-search svg { + stroke: #fff; +} + +/* Summary Card */ +.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 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); +} + +.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; +} + +/* Donut Chart */ +.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 0.3s ease, stroke-dashoffset 0.3s ease; +} + +/* Rate Display */ +.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 */ +.statistics-table-card { + background: #fff; + border-radius: 16px; + overflow: hidden; + box-shadow: 0 2px 8px rgba(0, 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: rgba(255, 255, 255, 0.2); + border-radius: 20px; + color: #fff; + text-decoration: none; + font-size: 14px; + transition: background 0.2s; +} + +.btn-download:hover { + background: rgba(255, 255, 255, 0.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; +} + +.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 Bar */ +.progress-cell { + min-width: 200px; +} + +.progress-bar { + display: flex; + height: 12px; + background: #e0e0e0; + border-radius: 6px; + overflow: hidden; +} + +.progress-success { + background: #4A90D9; + 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/editor-content.css b/src/main/resources/static/css/editor-content.css new file mode 100644 index 0000000..c144110 --- /dev/null +++ b/src/main/resources/static/css/editor-content.css @@ -0,0 +1,334 @@ +/** + * Editor Content Styles (editor-content.css) + * + * Summernote 에디터로 작성된 HTML 콘텐츠의 공통 스타일 + * 관리자포탈/개발자포탈 양쪽에서 동일하게 사용 + * + * 사용법: + * - 관리자포탈: Summernote .note-editable에 .editor-content 클래스 추가 + * - 개발자포탈: 콘텐츠 wrapper에 .editor-content 클래스 추가 + * + * @version 1.0.0 + * @date 2025-12-30 + */ + +/* ========================================================================== + CSS Reset + 기본 설정 + ========================================================================== */ + +.editor-content { + all: revert; + font-family: 'Noto Sans KR', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif !important; + font-size: 16px !important; + font-weight: 400 !important; + line-height: 1.8 !important; + color: #1A1A2E !important; + word-wrap: break-word; + text-align: left !important; +} + +/* ========================================================================== + 헤딩 (Headings) + ========================================================================== */ + +.editor-content h1 { + font-size: 20px !important; + font-weight: 700 !important; + margin: 32px 0 16px !important; + line-height: 1.4 !important; + color: #1A1A2E !important; +} + +.editor-content h1:first-child { + margin-top: 0 !important; +} + +.editor-content h2 { + font-size: 18px !important; + font-weight: 700 !important; + margin: 24px 0 8px !important; + line-height: 1.4 !important; + color: #1A1A2E !important; +} + +.editor-content h3 { + font-size: 16px !important; + font-weight: 600 !important; + margin: 16px 0 8px !important; + line-height: 1.4 !important; + color: #1A1A2E !important; +} + +.editor-content h4, +.editor-content h5, +.editor-content h6 { + font-size: 16px !important; + font-weight: 600 !important; + margin: 16px 0 8px !important; + line-height: 1.4 !important; + color: #1A1A2E !important; +} + +/* ========================================================================== + 단락 (Paragraphs) + ========================================================================== */ + +.editor-content p { + margin-bottom: 16px !important; + line-height: 1.8 !important; + font-size: 16px !important; +} + +.editor-content p:last-child { + margin-bottom: 0 !important; +} + +/* ========================================================================== + 리스트 (Lists) - 글로벌 reset 대응 + ========================================================================== */ + +.editor-content ul { + list-style-type: disc !important; + padding-left: 32px !important; + margin: 16px 0 !important; +} + +.editor-content ol { + list-style-type: decimal !important; + padding-left: 32px !important; + margin: 16px 0 !important; +} + +.editor-content li { + margin-bottom: 8px !important; + line-height: 1.8 !important; + font-size: 16px !important; +} + +.editor-content li:last-child { + margin-bottom: 0 !important; +} + +/* 중첩 리스트 */ +.editor-content ul ul { + list-style-type: circle !important; + margin: 8px 0 !important; +} + +.editor-content ul ul ul { + list-style-type: square !important; +} + +.editor-content ol ol { + list-style-type: lower-alpha !important; + margin: 8px 0 !important; +} + +.editor-content ol ol ol { + list-style-type: lower-roman !important; +} + +/* ========================================================================== + 테이블 (Tables) + ========================================================================== */ + +.editor-content table { + width: 100% !important; + border-collapse: collapse !important; + margin: 24px 0 !important; + border: 1px solid #E2E8F0 !important; + border-radius: 8px !important; + overflow: hidden !important; + font-size: 14px !important; +} + +.editor-content th, +.editor-content td { + border: 1px solid #E2E8F0 !important; + padding: 8px 16px !important; + text-align: left !important; + vertical-align: top !important; +} + +.editor-content th { + background: #EFF6FF !important; + font-weight: 600 !important; + color: #1A1A2E !important; +} + +.editor-content tr:hover { + background: #F8FAFC !important; +} + +/* ========================================================================== + 링크 (Links) + ========================================================================== */ + +.editor-content a { + color: #0049b4 !important; + text-decoration: underline !important; + transition: color 0.3s ease !important; +} + +.editor-content a:hover { + color: #003080 !important; +} + +/* ========================================================================== + 인용문 (Blockquote) + ========================================================================== */ + +.editor-content blockquote { + border-left: 4px solid #0049b4 !important; + padding: 16px 24px !important; + margin: 24px 0 !important; + background: #F8FAFC !important; + font-style: italic !important; + color: #64748B !important; + border-radius: 0 8px 8px 0 !important; +} + +.editor-content blockquote p { + margin-bottom: 0 !important; +} + +/* ========================================================================== + 이미지 (Images) + ========================================================================== */ + +.editor-content img { + max-width: 100% !important; + height: auto !important; + border-radius: 8px !important; + margin: 16px 0 !important; + display: block !important; +} + +/* ========================================================================== + 코드 (Code) + ========================================================================== */ + +.editor-content code { + background: #F8FAFC !important; + padding: 2px 6px !important; + border-radius: 4px !important; + font-family: 'Fira Code', 'Courier New', monospace !important; + font-size: 0.9em !important; + color: #0049b4 !important; +} + +.editor-content pre { + background: #F8FAFC !important; + border: 1px solid #E2E8F0 !important; + border-radius: 8px !important; + padding: 16px !important; + overflow-x: auto !important; + margin: 16px 0 !important; +} + +.editor-content pre code { + background: transparent !important; + padding: 0 !important; + color: #1A1A2E !important; +} + +/* ========================================================================== + 구분선 (Horizontal Rule) + ========================================================================== */ + +.editor-content hr { + border: none !important; + border-top: 1px solid #E2E8F0 !important; + margin: 32px 0 !important; +} + +/* ========================================================================== + 텍스트 강조 (Text Emphasis) + ========================================================================== */ + +.editor-content strong, +.editor-content b { + font-weight: 700 !important; +} + +.editor-content em, +.editor-content i { + font-style: italic !important; +} + +.editor-content u { + text-decoration: underline !important; +} + +.editor-content s, +.editor-content strike, +.editor-content del { + text-decoration: line-through !important; +} + +.editor-content mark { + background-color: #FEF3C7 !important; + padding: 0 2px !important; +} + +.editor-content sub { + vertical-align: sub !important; + font-size: 0.8em !important; +} + +.editor-content sup { + vertical-align: super !important; + font-size: 0.8em !important; +} + +/* ========================================================================== + 반응형 (Responsive) + ========================================================================== */ + +@media (max-width: 768px) { + .editor-content { + font-size: 14px !important; + } + + .editor-content h1 { + font-size: 18px !important; + } + + .editor-content h2 { + font-size: 16px !important; + } + + .editor-content h3, + .editor-content h4, + .editor-content h5, + .editor-content h6 { + font-size: 14px !important; + } + + .editor-content p, + .editor-content li { + font-size: 14px !important; + } + + .editor-content table { + font-size: 12px !important; + } + + .editor-content th, + .editor-content td { + padding: 4px 8px !important; + } + + .editor-content ul, + .editor-content ol { + padding-left: 24px !important; + } + + .editor-content blockquote { + padding: 8px 16px !important; + } + + .editor-content pre { + padding: 8px !important; + } +} diff --git a/src/main/resources/static/css/main.css b/src/main/resources/static/css/main.css index 1b38ef5..9c60c7a 100644 --- a/src/main/resources/static/css/main.css +++ b/src/main/resources/static/css/main.css @@ -1,4 +1,9 @@ @charset "UTF-8"; +:root { + --font-weight-primary-regular: 400; + --font-weight-primary-bold: 700; +} + * { margin: 0; padding: 0; @@ -13,7 +18,7 @@ html { } body { - font-family: "Pretendard", -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans KR", 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; @@ -73,15 +78,22 @@ section, summary { } ::selection { - background-color: rgba(75, 155, 255, 0.2); + background-color: rgba(0, 73, 180, 0.2); color: #1A1A2E; } ::-moz-selection { - background-color: rgba(75, 155, 255, 0.2); + background-color: rgba(0, 73, 180, 0.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; @@ -157,33 +169,12 @@ p:last-child { margin-bottom: 0; } -.text-gradient { - background: linear-gradient(135deg, #4B9BFF 0%, #2E7FF7 100%); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; -} - -.text-gradient-accent { - background: linear-gradient(135deg, #00D4FF 0%, #4B9BFF 100%); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; -} - -.text-gradient-warm { - background: linear-gradient(135deg, #FFD93D 0%, #FF6B6B 100%); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; -} - .text-primary { - color: #4B9BFF; + color: #0049b4; } .text-secondary { - color: #2E7FF7; + color: #c3dfea; } .text-dark { @@ -275,16 +266,16 @@ p:last-child { } a { - color: #4B9BFF; + color: #0049b4; } a:hover { - color: #2E7FF7; + color: #c3dfea; } blockquote { padding: 16px; margin: 16px 0; - border-left: 4px solid #4B9BFF; + border-left: 4px solid #0049b4; background: #F8FAFC; font-style: italic; } @@ -570,7 +561,6 @@ hr { left: -50%; width: 200%; height: 200%; - background: linear-gradient(45deg, transparent 30%, rgba(255, 255, 255, 0.2) 50%, transparent 70%); transform: rotate(45deg); transition: transform 0.6s ease; transform: translateX(-200%); @@ -579,6 +569,44 @@ 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"); +} +@font-face {} +/* SpoqaHanSans */ +@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"); +} +/* HGGGothicssi */ +@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; @@ -594,15 +622,81 @@ hr { --gray-bg: #F8FAFC; --light-bg: #EFF6FF; --border-gray: #E2E8F0; - --gradient-primary: linear-gradient(135deg, #4B9BFF 0%, #2E7FF7 100%); - --gradient-accent: linear-gradient(135deg, #00D4FF 0%, #4B9BFF 100%); - --gradient-warm: linear-gradient(135deg, #FFD93D 0%, #FF6B6B 100%); --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, 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; + } +} + .blind { position: absolute; width: 1px; @@ -616,25 +710,30 @@ hr { } .global-header { - position: fixed; - top: 0; - left: 0; - right: 0; - background: rgba(255, 255, 255, 0.95); - backdrop-filter: blur(10px); - -webkit-backdrop-filter: blur(10px); - box-shadow: var(--shadow-sm); - z-index: 1000; - height: 80px; + position: relative; + background-color: #ffffff; + z-index: 350; + height: 112px; transition: all 0.3s ease; + border-bottom: 1px solid #dddddd; +} +.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: 80px; - max-width: 1280px; + height: 65px; + max-width: 1920px; margin: 0 auto; padding: 0 20px; } @@ -644,8 +743,8 @@ hr { align-items: center; } .header-left .logo { - height: 45px; - width: auto; + height: 22px; + width: 114px; } .logo-wrapper { @@ -658,7 +757,7 @@ hr { display: flex; align-items: center; gap: 12px; - height: 45px; + height: 22px; z-index: 10; } .logo a { @@ -669,29 +768,23 @@ hr { text-decoration: none; } .logo img { - height: 45px; - width: auto; + height: 22px; + width: 114px; } .logo-text { - font-size: 16px; - font-weight: 700; - color: var(--primary-blue); - padding: 4px 12px; - background: var(--light-bg); - border-radius: 20px; + font-size: 22px; + font-weight: 600; + color: var(--text-dark); text-decoration: none; display: inline-block; transition: all 0.3s ease; } .logo-text:hover { - background: var(--primary-blue); - color: var(--white); - transform: translateY(-1px); - box-shadow: var(--shadow-md); + color: var(--primary-blue); } .logo-text:visited { - color: var(--primary-blue); + color: var(--text-dark); } .logo-link, @@ -759,14 +852,34 @@ hr { } } +.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: 32px; + 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 { - flex: 1; - text-align: center; + display: none; } .mobile-center .mobile-title { font-size: 18px; @@ -775,9 +888,13 @@ hr { margin: 0; } +.mobile-right { + display: flex; + align-items: center; +} .mobile-right .mobile-menu-btn { - width: 40px; - height: 40px; + width: clamp(24px, 6.4vw, 32px); + height: clamp(24px, 6.4vw, 32px); background: transparent; border: none; cursor: pointer; @@ -785,29 +902,29 @@ hr { flex-direction: column; justify-content: center; align-items: center; - gap: 4px; - padding: 8px; - border-radius: 8px; + gap: clamp(3px, 0.8vw, 5px); + padding: clamp(4px, 1.07vw, 6px); + border-radius: 4px; transition: background-color 0.3s ease; } .mobile-right .mobile-menu-btn:hover { - background: var(--gray-bg); + background: rgba(0, 0, 0, 0.05); } .mobile-right .mobile-menu-btn .hamburger-line { - width: 24px; + width: clamp(16px, 4.27vw, 22px); height: 2px; - background: var(--text-dark); + background: #515961; transition: all 0.3s ease; border-radius: 1px; } .mobile-right .mobile-menu-btn.active .hamburger-line:nth-child(1) { - transform: rotate(45deg) translateY(6px); + 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(-6px); + transform: rotate(-45deg) translateY(-5px); } .mobile-drawer { @@ -839,147 +956,282 @@ hr { bottom: 0; width: 100%; height: 100%; - background: var(--white); + background: #ffffff; transform: translateY(100%); transition: transform 0.3s ease; display: flex; flex-direction: column; + overflow-y: auto; } .mobile-drawer .drawer-header { display: flex; align-items: center; justify-content: space-between; - padding: 20px; - border-bottom: 2px solid var(--border-gray); - background: var(--white); - box-shadow: var(--shadow-sm); - min-height: 60px; + height: 44px; + min-height: 44px; + padding: 0 16px 0 20px; + background: #ffffff; } -.mobile-drawer .drawer-header .drawer-logo { +.mobile-drawer .drawer-header .drawer-header-left { display: flex; align-items: center; - gap: 12px; + gap: clamp(8px, 2.13vw, 12px); } -.mobile-drawer .drawer-header .drawer-logo .logo { - height: 32px; +.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-logo .logo-text { - font-size: 16px; +.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: var(--primary-blue); + 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: 0.7; } .mobile-drawer .drawer-header .drawer-close { - width: 40px; - height: 40px; + width: 24px; + height: 24px; background: transparent; border: none; cursor: pointer; display: flex; align-items: center; justify-content: center; - border-radius: 8px; - font-size: 18px; - color: var(--text-gray); - transition: all 0.3s ease; + padding: 0; +} +.mobile-drawer .drawer-header .drawer-close svg { + width: 24px; + height: 24px; } .mobile-drawer .drawer-header .drawer-close:hover { - background: var(--white); - color: var(--text-dark); + opacity: 0.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 0.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: #ffffff; + 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 0.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: #ffffff; + border-top: 1px solid #dadada; +} +.mobile-drawer .drawer-footer .drawer-logout-btn { + display: flex; + align-items: center; + gap: 4px; + padding: 10px; + background: transparent; + 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: 0.8; } .mobile-drawer .drawer-nav { flex: 1; - padding: 0; - overflow-y: auto; - background: var(--white); + padding: 0 20px; + background: #ffffff; } -.mobile-drawer .drawer-nav .drawer-menu { +.mobile-drawer .drawer-nav .drawer-menu-list { list-style: none; margin: 0; - padding: 20px 0; + padding: 0; } -.mobile-drawer .drawer-nav .drawer-menu li { - margin-bottom: 8px; +.mobile-drawer .drawer-nav .drawer-menu-item { + border-bottom: 1px solid #dadada; } -.mobile-drawer .drawer-nav .drawer-menu li .drawer-link { - display: block; - padding: 16px 24px; - margin: 0 20px; - color: var(--text-dark); - text-decoration: none; - font-size: 18px; - font-weight: 500; - border-radius: 12px; - transition: all 0.3s ease; -} -.mobile-drawer .drawer-nav .drawer-menu li .drawer-link:hover, .mobile-drawer .drawer-nav .drawer-menu li .drawer-link:active { - background: var(--light-bg); - color: var(--primary-blue); - transform: translateX(4px); - box-shadow: var(--shadow-sm); -} -.mobile-drawer .drawer-actions { - padding: 24px; - border-top: 2px solid var(--border-gray); - background: var(--white); - box-shadow: 0 -4px 12px rgba(0, 0, 0, 0.05); -} -.mobile-drawer .drawer-actions .drawer-login-btn { - display: block; - text-align: center; - padding: 14px 20px; - color: var(--text-dark); - text-decoration: none; - border: 2px solid var(--primary-blue); - border-radius: 12px; - font-weight: 600; - font-size: 16px; - margin-bottom: 12px; - transition: all 0.3s ease; -} -.mobile-drawer .drawer-actions .drawer-login-btn:hover { - background: var(--light-bg); - color: var(--primary-blue); -} -.mobile-drawer .drawer-actions .drawer-signup-btn { +.mobile-drawer .drawer-nav .drawer-menu-item .drawer-menu-btn { display: flex; align-items: center; - justify-content: center; - gap: 8px; - padding: 14px 20px; - background: var(--gradient-primary); - color: var(--white); - text-decoration: none; - border-radius: 12px; - font-weight: 600; - font-size: 16px; - margin-bottom: 12px; - transition: all 0.3s ease; - box-shadow: var(--shadow-md); -} -.mobile-drawer .drawer-actions .drawer-signup-btn:hover { - transform: translateY(-2px); - box-shadow: var(--shadow-lg); -} -.mobile-drawer .drawer-actions .drawer-search-btn { - display: flex; - align-items: center; - justify-content: center; - gap: 8px; + justify-content: space-between; width: 100%; - padding: 14px 20px; - background: var(--gray-bg); - color: var(--text-dark); + height: 56px; + padding: 0 8px; + background: transparent; border: none; - border-radius: 12px; - font-weight: 500; - font-size: 16px; cursor: pointer; - transition: all 0.3s ease; + 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-actions .drawer-search-btn:hover { - background: var(--light-bg); - color: var(--primary-blue); +.mobile-drawer .drawer-nav .drawer-menu-item .drawer-menu-btn .accordion-icon { + transition: transform 0.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 0.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 { @@ -1100,7 +1352,7 @@ hr { } .main-nav.m-nav .nav-list .nav-item .sub-menu a:hover { background: #FFFFFF; - color: #4B9BFF; + color: #0049b4; } .main-nav.m-nav .nav-list .nav-item.expanded .sub-menu { display: block; @@ -1119,6 +1371,52 @@ hr { 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 0.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 0.3s ease; +} +.nav-menu > li .sub-menu li a:hover { + background: var(--light-bg); + color: var(--primary-blue); +} .nav-link { display: flex; @@ -1127,15 +1425,13 @@ hr { text-decoration: none; color: var(--text-dark); font-weight: 500; - font-size: 15px; + font-size: 20px; padding: 8px 16px; border-radius: 8px; transition: all 0.3s ease; } .nav-link:hover { - background: var(--light-bg); color: var(--primary-blue); - transform: translateY(-2px); } .nav-icon { @@ -1147,7 +1443,6 @@ hr { align-items: center; gap: 8px; text-decoration: none; - background: var(--gradient-primary); color: var(--white); padding: 12px 24px; border-radius: 50px; @@ -1160,278 +1455,349 @@ hr { box-shadow: var(--shadow-lg); } -body { - padding-top: 80px; +@media (max-width: 768px) { + .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-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 0.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 0.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 0.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) { - .global-header { - height: 60px; - } - .global-header .container .header-content { - padding: 0 16px; - height: 60px; - } - body { - padding-top: 60px; - } -} -@media (max-width: 480px) { - .global-header .header-content { - padding: 0 12px; - } - .logo .logo-text { + .header-user-info { display: none; } } .global-footer { - background: #1A1A2E; - color: #FFFFFF; - padding: 80px 0 40px; + background: #FFFFFF; + border-top: 1px solid #E5E7EB; } -@media (max-width: 768px) { - .global-footer { - padding: 48px 0 32px; - } -} - -.footer-top { - display: grid; - grid-template-columns: 2fr 3fr; - gap: 80px; - margin-bottom: 64px; - max-width: 1280px; - margin-left: auto; - margin-right: auto; - padding: 0 24px; -} -@media (max-width: 1024px) { - .footer-top { - grid-template-columns: 1fr; - gap: 40px; - } -} - -.footer-brand { - max-width: 360px; -} -.footer-brand .footer-logo { - height: 48px; - margin-bottom: 24px; -} -.footer-brand .footer-desc { - font-size: 14px; - line-height: 1.8; - color: rgba(255, 255, 255, 0.7); - margin-bottom: 24px; -} -.footer-brand .footer-newsletter { - margin-bottom: 32px; -} -.footer-brand .footer-newsletter h4 { - font-size: 18px; - font-weight: 600; - margin-bottom: 16px; -} -.footer-brand .footer-newsletter .newsletter-form { - display: flex; - gap: 8px; -} -.footer-brand .footer-newsletter .newsletter-form input { - flex: 1; - padding: 8px 16px; - background: rgba(255, 255, 255, 0.1); - border: 1px solid rgba(255, 255, 255, 0.2); - border-radius: 8px; - color: #FFFFFF; - font-size: 14px; -} -.footer-brand .footer-newsletter .newsletter-form input::placeholder { - color: rgba(255, 255, 255, 0.5); -} -.footer-brand .footer-newsletter .newsletter-form input:focus { - background: rgba(255, 255, 255, 0.15); - border-color: #4B9BFF; - outline: none; -} -.footer-brand .footer-newsletter .newsletter-form button { - padding: 8px 24px; - background: linear-gradient(135deg, #4B9BFF 0%, #2E7FF7 100%); - color: #FFFFFF; - border-radius: 8px; - font-weight: 600; - font-size: 14px; - transition: all 0.3s ease; -} -.footer-brand .footer-newsletter .newsletter-form button:hover { - transform: translateY(-2px); - box-shadow: 0 4px 12px rgba(75, 155, 255, 0.1); -} - -.social-links { - display: flex; - gap: 16px; -} -.social-links a { - width: 44px; - height: 44px; - background: rgba(255, 255, 255, 0.1); - border-radius: 12px; - display: flex; - justify-content: center; - align-items: center; - color: #FFFFFF; - transition: all 0.3s ease; - font-size: 20px; -} -.social-links a:hover { - background: #4B9BFF; - transform: translateY(-3px); -} -.social-links a.facebook:hover { - background: #1877f2; -} -.social-links a.twitter:hover { - background: #1da1f2; -} -.social-links a.linkedin:hover { - background: #0077b5; -} -.social-links a.github:hover { - background: #333; -} -.social-links a.youtube:hover { - background: #ff0000; -} - -.footer-links { - display: grid; - grid-template-columns: repeat(4, 1fr); - gap: 40px; -} -@media (max-width: 1024px) { - .footer-links { - grid-template-columns: repeat(2, 1fr); - } -} -@media (max-width: 768px) { - .footer-links { - grid-template-columns: 1fr; - text-align: center; - } -} - -.footer-column h4 { - font-size: 16px; - font-weight: 700; - margin-bottom: 24px; - color: #FFFFFF; -} -.footer-column ul { - list-style: none; -} -.footer-column ul li { - margin-bottom: 16px; -} -.footer-column ul li a { - color: rgba(255, 255, 255, 0.7); - font-size: 14px; - transition: all 0.3s ease; - position: relative; -} -.footer-column ul li a::after { - content: ""; - position: absolute; - bottom: -2px; - left: 0; - width: 0; - height: 1px; - background: #00D4FF; - transition: all 0.3s ease; -} -.footer-column ul li a:hover { - color: #00D4FF; -} -.footer-column ul li a:hover::after { - width: 100%; -} -.footer-column .footer-badge { - display: inline-flex; - align-items: center; - gap: 4px; - padding: 4px 8px; - background: rgba(107, 207, 127, 0.2); - color: #6BCF7F; - border-radius: 50px; - font-size: 12px; - font-weight: 600; - margin-left: 8px; -} - -.footer-bottom { - padding-top: 40px; - border-top: 1px solid rgba(255, 255, 255, 0.1); - max-width: 1280px; +.global-footer .container { + max-width: 1200px; margin: 0 auto; - padding-left: 24px; - padding-right: 24px; + padding: 0 20px; } -.footer-legal { +.footer-content { display: flex; justify-content: space-between; align-items: center; + min-height: 200px; + padding: 40px 0; + gap: 40px; } -@media (max-width: 768px) { - .footer-legal { +@media (max-width: 1024px) { + .footer-content { flex-direction: column; - gap: 24px; - text-align: center; + align-items: flex-start; + gap: 32px; + padding: 32px 0; } } -.footer-legal p { - color: rgba(255, 255, 255, 0.5); - font-size: 14px; - margin: 0; -} -.footer-legal .legal-links { + +.footer-left { display: flex; - gap: 24px; + flex-direction: column; + gap: 16px; } -.footer-legal .legal-links a { - color: rgba(255, 255, 255, 0.7); +.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: #5F666C; + text-decoration: none; + transition: color 0.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: "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: #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; } -.footer-legal .legal-links a:hover { - color: #FFFFFF; +.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, 0.1); +} +@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: #1F2937; + margin: 0; + line-height: 1.2; +} +@media (max-width: 1024px) { + .footer-right .footer-contact { + font-size: 16px; + } } -.footer-decoration { - position: relative; - overflow: hidden; +@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; + } } -.footer-decoration::before { - content: ""; - position: absolute; - top: -100px; - right: -100px; - width: 300px; - height: 300px; - background: radial-gradient(circle, rgba(75, 155, 255, 0.1) 0%, transparent 70%); - border-radius: 50%; +@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; + } } -.footer-decoration::after { - content: ""; - position: absolute; - bottom: -150px; - left: -150px; - width: 400px; - height: 400px; - background: radial-gradient(circle, rgba(0, 212, 255, 0.05) 0%, transparent 70%); - border-radius: 50%; -} - .grid { display: grid; gap: 24px; @@ -1707,7 +2073,7 @@ body { .container { max-width: 1280px; margin: 0 auto; - padding: 0 24px; + padding: 0 26px; } @media (max-width: 768px) { .container { @@ -1752,14 +2118,7 @@ body { .section-bg-light { background: #EFF6FF; } -.section-bg-gradient { - background: linear-gradient(180deg, #FFFFFF 0%, #EFF6FF 100%); -} -.section-bg-gradient-reverse { - background: linear-gradient(180deg, #EFF6FF 0%, #FFFFFF 100%); -} .section-bg-primary { - background: linear-gradient(135deg, #4B9BFF 0%, #2E7FF7 100%); color: #FFFFFF; } .section-bg-dark { @@ -1782,7 +2141,7 @@ body { gap: 4px; padding: 4px 16px; background: #EFF6FF; - color: #4B9BFF; + color: #0049b4; border-radius: 50px; font-size: 14px; font-weight: 600; @@ -1799,12 +2158,6 @@ body { font-size: 32px; } } -.section-header .section-title .title-highlight { - background: linear-gradient(135deg, #00D4FF 0%, #4B9BFF 100%); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; -} .section-header .section-subtitle { font-size: 18px; color: #64748B; @@ -1876,7 +2229,7 @@ body { font-size: 20px; } .btn-primary { - background: linear-gradient(135deg, #4B9BFF 0%, #2E7FF7 100%); + background: #0049b4; color: #FFFFFF; box-shadow: 0 4px 12px rgba(75, 155, 255, 0.1); } @@ -1888,16 +2241,14 @@ body { transform: translateY(-1px); } .btn-secondary { - background: #FFFFFF; - color: #4B9BFF; - border: 2px solid #4B9BFF; + background: #E5E7EB; + color: #5F666C; } .btn-secondary:hover { background: #EFF6FF; transform: translateY(-3px); } .btn-accent { - background: linear-gradient(135deg, #00D4FF 0%, #4B9BFF 100%); color: #FFFFFF; box-shadow: 0 4px 12px rgba(75, 155, 255, 0.1); } @@ -1906,7 +2257,6 @@ body { box-shadow: 0 16px 40px rgba(75, 155, 255, 0.2); } .btn-warm { - background: linear-gradient(135deg, #FFD93D 0%, #FF6B6B 100%); color: #FFFFFF; box-shadow: 0 4px 12px rgba(75, 155, 255, 0.1); } @@ -1919,7 +2269,7 @@ body { color: #FFFFFF; } .btn-success:hover { - background: rgb(68.4897959184, 194.5102040816, 93.693877551); + background: rgb(83.2897959184, 199.3102040816, 106.493877551); transform: translateY(-3px); } .btn-danger { @@ -1927,17 +2277,17 @@ body { color: #FFFFFF; } .btn-danger:hover { - background: #ff3838; + background: rgb(255, 70.8, 70.8); transform: translateY(-3px); } .btn-ghost { background: transparent; - color: #4B9BFF; + color: #0049b4; border: 2px solid transparent; } .btn-ghost:hover { - background: rgba(75, 155, 255, 0.1); - border-color: #4B9BFF; + background: rgba(0, 73, 180, 0.1); + border-color: #0049b4; } .btn-outline { background: transparent; @@ -1945,8 +2295,8 @@ body { border: 2px solid #E2E8F0; } .btn-outline:hover { - border-color: #4B9BFF; - color: #4B9BFF; + border-color: #0049b4; + color: #0049b4; transform: translateY(-3px); } .btn:disabled, .btn.disabled { @@ -2015,7 +2365,6 @@ body { right: 32px; width: 60px; height: 60px; - background: linear-gradient(135deg, #4B9BFF 0%, #2E7FF7 100%); border: none; border-radius: 50%; color: #FFFFFF; @@ -2070,7 +2419,7 @@ body { } .btn-cta-primary { background: #FFFFFF; - color: #4B9BFF; + color: #0049b4; padding: 16px 40px; font-size: 18px; box-shadow: 0 8px 24px rgba(75, 155, 255, 0.15); @@ -2107,6 +2456,408 @@ body { 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 0.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: #FFFFFF; +} +.action-btn-new:hover, +.action-btn-primary:hover { + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(75, 155, 255, 0.1); +} +.action-btn-new:active, +.action-btn-primary:active { + transform: translateY(0); +} + +.action-btn-secondary { + background: #FFFFFF; + 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: #FFFFFF; +} +.action-btn-edit:hover { + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(75, 155, 255, 0.1); + background: #c3dfea; +} +.action-btn-edit:active { + transform: translateY(0); +} + +.action-btn-delete { + background: #FF6B6B; + color: #FFFFFF; +} +.action-btn-delete:hover { + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(75, 155, 255, 0.1); + background: rgb(255, 88.9, 88.9); +} +.action-btn-delete:active { + transform: translateY(0); +} + +.action-btn.btn-list { + background: #FFFFFF; + color: #1A1A2E; + border: 1px solid #E2E8F0; +} +.action-btn.btn-list:hover { + background: #F8FAFC; + border-color: #0049b4; + color: #0049b4; +} +.action-btn.btn-edit { + color: #FFFFFF; +} +.action-btn.btn-edit:hover { + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(75, 155, 255, 0.1); +} +.action-btn.btn-delete { + background: #FF6B6B; + color: #FFFFFF; +} +.action-btn.btn-delete:hover { + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(75, 155, 255, 0.1); +} + +.btn-copy { + padding: 8px 16px; + background: rgba(0, 73, 180, 0.1); + color: #0049b4; + border: 1px solid rgba(0, 73, 180, 0.2); + border-radius: 6px; + font-size: 12px; + font-weight: 500; + cursor: pointer; + transition: all 0.3s ease; + display: inline-flex; + align-items: center; + gap: 4px; +} +.btn-copy:hover { + background: rgba(0, 73, 180, 0.2); + border-color: #0049b4; +} +.btn-copy i { + font-size: 12px; +} + +.btn-action { + padding: 8px 24px; + color: #FFFFFF; + border: none; + border-radius: 8px; + font-size: 14px; + font-weight: 500; + cursor: pointer; + transition: all 0.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, 0.1); +} +.btn-action i { + font-size: 14px; +} + +.btn-cancel { + padding: 8px 24px; + background: #FFFFFF; + color: #64748B; + border: 1px solid #E2E8F0; + border-radius: 8px; + font-size: 14px; + font-weight: 500; + cursor: pointer; + transition: all 0.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: #FFFFFF; + cursor: pointer; + transition: all 0.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, 0.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, 0.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: #FFFFFF; + border: none; + border-radius: 8px; + font-size: 14px; + font-weight: 600; + cursor: pointer; + transition: background-color 0.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 0.3s ease; +} +.status-badge.status-pending { + background: rgba(255, 107, 107, 0.1); + color: #FF6B6B; +} +.status-badge.status-completed { + background: rgba(107, 207, 127, 0.1); + color: #6BCF7F; +} +.status-badge.status-active { + background: rgba(0, 73, 180, 0.1); + color: #0049b4; +} +.status-badge.status-inactive { + background: rgba(100, 116, 139, 0.1); + color: #64748B; +} +.status-badge.status-processing { + background: rgba(255, 217, 61, 0.1); + color: rgb(221.2, 177.8721649485, 0); +} +.status-badge.status-failed { + background: rgba(255, 107, 107, 0.1); + color: #FF6B6B; +} +.status-badge.status-success { + background: rgba(107, 207, 127, 0.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 0.3s ease; +} +.status-badge-header.status-pending { + background: rgba(255, 107, 107, 0.1); + color: #FF6B6B; +} +.status-badge-header.status-completed { + background: rgba(107, 207, 127, 0.1); + color: #6BCF7F; +} +.status-badge-header.status-active { + background: rgba(0, 73, 180, 0.1); + color: #0049b4; +} +.status-badge-header.status-inactive { + background: rgba(100, 116, 139, 0.1); + color: #64748B; +} +.status-badge-header.status-processing { + background: rgba(255, 217, 61, 0.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: transparent; + 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: 0.9em; +} + +.notification-badge { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 20px; + height: 20px; + padding: 0 4px; + background: #FF6B6B; + color: #FFFFFF; + 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: #FFFFFF; border-radius: 20px; @@ -2114,29 +2865,12 @@ body { transition: all 0.3s ease; } .card:hover { - transform: translateY(-5px); box-shadow: 0 8px 24px rgba(75, 155, 255, 0.15); } .card { position: relative; overflow: hidden; } -.card::before { - content: ""; - position: absolute; - top: -50%; - left: -50%; - width: 200%; - height: 200%; - background: linear-gradient(45deg, transparent 30%, rgba(75, 155, 255, 0.03) 50%, transparent 70%); - transform: rotate(45deg); - transition: all 0.5s ease; - opacity: 0; -} -.card:hover::before { - animation: shimmer 0.6s ease; - opacity: 1; -} .api-card { background: #FFFFFF; @@ -2145,7 +2879,6 @@ body { transition: all 0.3s ease; } .api-card:hover { - transform: translateY(-5px); box-shadow: 0 8px 24px rgba(75, 155, 255, 0.15); } .api-card { @@ -2153,16 +2886,13 @@ body { border: 2px solid #E2E8F0; } .api-card:hover { - border-color: #4B9BFF; - transform: translateY(-8px); + border-color: #0049b4; } .api-card-popular { border-color: #FFD93D; - background: linear-gradient(180deg, rgba(255, 217, 61, 0.05) 0%, #FFFFFF 100%); } .api-card-new { border-color: #6BCF7F; - background: linear-gradient(180deg, rgba(107, 207, 127, 0.05) 0%, #FFFFFF 100%); } .api-card .api-badge { position: absolute; @@ -2193,13 +2923,12 @@ body { align-items: center; font-size: 28px; background: #EFF6FF; - color: #4B9BFF; + color: #0049b4; border-radius: 16px; transition: all 0.3s ease; } .api-card:hover .api-icon { transform: scale(1.1); - background: linear-gradient(135deg, #4B9BFF 0%, #2E7FF7 100%); color: #FFFFFF; } .api-card .api-name { @@ -2233,14 +2962,14 @@ body { display: inline-flex; align-items: center; gap: 4px; - color: #4B9BFF; + color: #0049b4; font-weight: 600; font-size: 14px; transition: all 0.3s ease; } .api-card .api-link:hover { gap: 8px; - color: #2E7FF7; + color: #c3dfea; } .experience-card { @@ -2250,7 +2979,6 @@ body { transition: all 0.3s ease; } .experience-card:hover { - transform: translateY(-5px); box-shadow: 0 8px 24px rgba(75, 155, 255, 0.15); } .experience-card { @@ -2264,7 +2992,6 @@ body { justify-content: center; align-items: center; font-size: 24px; - background: linear-gradient(135deg, #4B9BFF 0%, #2E7FF7 100%); color: #FFFFFF; border-radius: 16px; } @@ -2290,7 +3017,7 @@ body { .experience-card .card-metric .metric-value { font-size: 24px; font-weight: 800; - color: #4B9BFF; + color: #0049b4; } .experience-card .card-metric .metric-label { font-size: 12px; @@ -2305,7 +3032,6 @@ body { transition: all 0.3s ease; } .feature-card:hover { - transform: translateY(-5px); box-shadow: 0 8px 24px rgba(75, 155, 255, 0.15); } .feature-card { @@ -2320,7 +3046,6 @@ body { display: flex; justify-content: center; align-items: center; - background: linear-gradient(135deg, #4B9BFF 0%, #2E7FF7 100%); color: #FFFFFF; border-radius: 12px; font-size: 24px; @@ -2347,7 +3072,6 @@ body { transition: all 0.3s ease; } .testimonial-card:hover { - transform: translateY(-5px); box-shadow: 0 8px 24px rgba(75, 155, 255, 0.15); } .testimonial-card { @@ -2359,7 +3083,7 @@ body { top: 16px; left: 16px; font-size: 60px; - color: #4B9BFF; + color: #0049b4; opacity: 0.1; font-weight: 700; } @@ -2400,7 +3124,6 @@ body { transition: all 0.3s ease; } .pricing-card:hover { - transform: translateY(-5px); box-shadow: 0 8px 24px rgba(75, 155, 255, 0.15); } .pricing-card { @@ -2408,7 +3131,7 @@ body { position: relative; } .pricing-card.featured { - border: 2px solid #4B9BFF; + border: 2px solid #0049b4; transform: scale(1.05); } .pricing-card.featured .pricing-badge { @@ -2417,7 +3140,6 @@ body { left: 50%; transform: translateX(-50%); padding: 4px 16px; - background: linear-gradient(135deg, #4B9BFF 0%, #2E7FF7 100%); color: #FFFFFF; border-radius: 50px; font-size: 12px; @@ -2448,10 +3170,6 @@ body { .pricing-card .pricing-header .price .amount { font-size: 48px; font-weight: 800; - background: linear-gradient(135deg, #4B9BFF 0%, #2E7FF7 100%); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; } .pricing-card .pricing-header .price .period { font-size: 16px; @@ -2485,6 +3203,497 @@ body { 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: transparent; + 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: "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: #000000; + 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: #000000; + 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: #FFFFFF; + 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: 0.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 0.2s ease, transform 0.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: #FFFFFF; +} +.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: #FFFFFF; +} +.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: #FFFFFF; + border: 1px solid #E2E8F0; + border-radius: 6px; + font-size: 14px; + color: #1A1A2E; + cursor: pointer; + transition: all 0.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: #FFFFFF; +} +@media (max-width: 768px) { + .apikey-btn-copy, .btn-copy { + align-self: stretch; + justify-content: center; + } +} + +.apikey-detail-section, .detail-section { + background: #FFFFFF; + border-radius: 12px; + padding: 64px; + margin-bottom: 24px; + box-shadow: 0 4px 12px rgba(75, 155, 255, 0.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: #FFFFFF; + transition: all 0.2s; +} +.apikey-api-item:hover, .api-list-item:hover { + border-color: #0049b4; + box-shadow: 0 2px 4px rgba(0, 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; } @@ -2517,7 +3726,7 @@ body { width: 100%; padding: 12px 16px; font-size: 16px; - font-family: "Pretendard", -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans KR", 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; @@ -2529,8 +3738,8 @@ body { } .form-control:focus { outline: none; - border-color: #4B9BFF; - 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.1); } .form-control:disabled { background: #F8FAFC; @@ -2624,7 +3833,7 @@ select.form-control { transition: all 0.3s ease; } .form-switch .switch.active { - background: #4B9BFF; + background: #0049b4; } .form-switch .switch.active::after { transform: translateX(24px); @@ -2684,20 +3893,626 @@ select.form-control { } } +.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 0.3s ease; +} +.file-upload-wrapper .file-name-display.has-file { + color: #1A1A2E; + background: rgba(0, 73, 180, 0.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, 0.1); +} +.file-upload-wrapper .file-upload-btn { + padding: 16px 24px; + color: #FFFFFF; + border-radius: 8px; + font-size: 14px; + font-weight: 500; + cursor: pointer; + transition: all 0.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, 0.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: #FFFFFF; + border-radius: 8px; + border: none; + cursor: pointer; + transition: all 0.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, 0.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: #000000; +} +@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: #FFFFFF; + 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: #FFFFFF; + transition: all 0.3s ease; +} +.form-input:focus { + outline: none; + border-color: #0049b4; + box-shadow: 0 0 0 3px rgba(0, 73, 180, 0.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: #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; + padding-right: 48px; +} +.form-select:focus { + outline: none; + border-color: #0049b4; + box-shadow: 0 0 0 3px rgba(0, 73, 180, 0.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 0.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: #FFFFFF !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: #FFFFFF; + 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: transparent; + 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: transparent; + border: none; + cursor: pointer; + color: #515961; + transition: all 0.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, 0.1); +} + +.notice-content-box { + padding: 24px; + background: #FFFFFF; + 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 0.3s ease; +} +.attachment-link:hover { + background: rgba(0, 73, 180, 0.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: rgba(0, 0, 0, 0.5); + background: #385670; z-index: 400; - opacity: 0; + opacity: 0.9; visibility: hidden; transition: all 0.3s ease; } .modal-backdrop.show { - opacity: 1; + opacity: 0.9; visibility: visible; } @@ -2705,8 +4520,8 @@ select.form-control { position: fixed; top: 0; left: 0; - right: 0; - bottom: 0; + width: 100%; + height: 100vh; z-index: 500; display: flex; align-items: center; @@ -2714,37 +4529,100 @@ select.form-control { padding: 24px; opacity: 0; visibility: hidden; - transition: all 0.3s ease; + transition: opacity 0.3s ease, visibility 0.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: #FFFFFF; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + z-index: 500; + transition: all 0.3s ease; + box-shadow: 0 8px 24px rgba(75, 155, 255, 0.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: #FFFFFF; - border-radius: 16px; - box-shadow: 0 16px 40px rgba(75, 155, 255, 0.2); - max-width: 500px; - width: 100%; - max-height: 90vh; + padding: 0 24px; + border-radius: 12px; + box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3); + max-width: 700px; + width: calc(100% - 48px); + max-height: 85vh; display: flex; flex-direction: column; transform: scale(0.95); - transition: all 0.3s ease; -} -.modal-dialog.modal-sm { - max-width: 400px; -} -.modal-dialog.modal-lg { - max-width: 800px; -} -.modal-dialog.modal-xl { - max-width: 1200px; + transition: transform 0.3s ease; + z-index: 500; + margin-left: auto; + margin-right: auto; } .modal-dialog.modal-fullscreen { max-width: 100%; @@ -2753,67 +4631,121 @@ select.form-control { margin: 0; border-radius: 0; } +@media (max-width: 768px) { + .modal-dialog { + padding: 0; + } +} .modal-header { - padding: 24px; - border-bottom: 1px solid #E2E8F0; + padding: 30px 50px 20px; display: flex; align-items: center; justify-content: space-between; + border-bottom: 1px solid black; + border-top-left-radius: 12px; + border-top-right-radius: 12px; } .modal-header .modal-title { font-size: 24px; font-weight: 600; - color: #1A1A2E; + color: #212529; margin: 0; } -.modal-header .modal-close { - width: 32px; - height: 32px; - display: flex; - justify-content: center; - align-items: center; - background: transparent; - border: none; - border-radius: 8px; - color: #64748B; - font-size: 24px; - cursor: pointer; - transition: all 0.3s ease; -} -.modal-header .modal-close:hover { - background: #F8FAFC; - color: #1A1A2E; +@media (max-width: 768px) { + .modal-header { + padding: 20px 24px 16px; + } + .modal-header .modal-title { + font-size: 18px; + } } .modal-body { - padding: 24px; + padding: 30px 50px; flex: 1; overflow-y: auto; - color: #1A1A2E; + 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: 24px; - border-top: 1px solid #E2E8F0; + padding: 20px 50px 40px; display: flex; - gap: 16px; - justify-content: flex-end; -} -.modal-footer.modal-footer-center { + 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; + } +} -.modal-alert .modal-dialog { - max-width: 400px; +.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 0.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, 0.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, 0.1); +} + +.modal-alert .modal-container { + max-width: 500px; } .modal-alert .modal-body { text-align: center; - padding: 32px; + padding: 32px 50px; } .modal-alert .modal-body .alert-icon { width: 64px; @@ -2838,8 +4770,8 @@ select.form-control { color: #FF6B6B; } .modal-alert .modal-body .alert-icon.alert-info { - background: rgba(75, 155, 255, 0.1); - color: #4B9BFF; + background: rgba(0, 73, 180, 0.1); + color: #0049b4; } .modal-alert .modal-body .alert-title { font-size: 24px; @@ -2849,6 +4781,7 @@ select.form-control { } .modal-alert .modal-body .alert-message { color: #64748B; + font-size: 16px; } .breadcrumb { @@ -2869,7 +4802,7 @@ select.form-control { transition: all 0.3s ease; } .breadcrumb .breadcrumb-item a:hover { - color: #4B9BFF; + color: #0049b4; } .breadcrumb .breadcrumb-item.active { color: #1A1A2E; @@ -2905,8 +4838,8 @@ select.form-control { color: #1A1A2E; } .nav-tabs .nav-link.active { - color: #4B9BFF; - border-bottom-color: #4B9BFF; + color: #0049b4; + border-bottom-color: #0049b4; } .nav-tabs .nav-link .badge { padding: 2px 6px; @@ -2937,7 +4870,7 @@ select.form-control { } .nav-pills .nav-link.active { background: #FFFFFF; - color: #4B9BFF; + color: #0049b4; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); } @@ -2978,18 +4911,18 @@ select.form-control { } .sidebar-nav .nav-menu .nav-link:hover { background: #F8FAFC; - color: #4B9BFF; + color: #0049b4; } .sidebar-nav .nav-menu .nav-link:hover .nav-icon { - color: #4B9BFF; + color: #0049b4; } .sidebar-nav .nav-menu .nav-link.active { background: #EFF6FF; - color: #4B9BFF; + color: #0049b4; font-weight: 500; } .sidebar-nav .nav-menu .nav-link.active .nav-icon { - color: #4B9BFF; + color: #0049b4; } .sidebar-nav .nav-menu .nav-submenu { margin-left: 40px; @@ -3012,9 +4945,9 @@ select.form-control { pointer-events: none; } .pagination .page-item.active .page-link { - background: #4B9BFF; + background: #0049b4; color: #FFFFFF; - border-color: #4B9BFF; + border-color: #0049b4; } .pagination .page-link { display: flex; @@ -3033,8 +4966,8 @@ select.form-control { } .pagination .page-link:hover { background: #F8FAFC; - border-color: #4B9BFF; - color: #4B9BFF; + border-color: #0049b4; + color: #0049b4; } .pagination .page-link.page-prev, .pagination .page-link.page-next { font-size: 18px; @@ -3052,916 +4985,3470 @@ select.form-control { .final-cta { position: relative; padding: 150px 0; - background: linear-gradient(135deg, #4B9BFF 0%, #2E7FF7 100%); overflow: hidden; } -.hero-section { +#passwordInputPopup .pop_input_group { + text-align: left; + margin: 0; +} +#passwordInputPopup .error-message { + color: #FF6B6B; + font-size: 12px; + margin-top: 4px; + display: none; + animation: fadeInDown 0.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 0.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: "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; +} +.login-btn-box .user-icon { + width: 18px; + height: 18px; + flex-shrink: 0; +} + +.user-profile-dropdown { position: relative; - padding: 160px 0 100px; - background: linear-gradient(180deg, #EFF6FF 0%, #FFFFFF 100%); +} +.user-profile-dropdown .user-profile-btn { + display: flex; + align-items: center; + gap: 8px; + padding: 10px 20px; + background: #EFF6FF; + border: 2px solid transparent; + border-radius: 50px; + cursor: pointer; + transition: all 0.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 0.15s ease; +} +.user-profile-dropdown .user-profile-btn:hover { + background: #FFFFFF; + border-color: #0049b4; + box-shadow: 0 4px 12px rgba(75, 155, 255, 0.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: #FFFFFF; + border: 1px solid #E2E8F0; + border-radius: 12px; + box-shadow: 0 16px 40px rgba(75, 155, 255, 0.2); + visibility: hidden; + opacity: 0; + transform: translateY(-10px); + transition: all all 0.3s ease; + z-index: 100; overflow: hidden; } +.user-profile-dropdown .profile-header { + padding: 24px; + color: #FFFFFF; + 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: 0.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 0.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 0.3s ease; +} +.user-profile-dropdown .profile-footer .logout-btn i { + font-size: 14px; +} +.user-profile-dropdown .profile-footer .logout-btn:hover { + background: #FF6B6B; + color: #FFFFFF; + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(75, 155, 255, 0.1); +} + +.drawer-user-info { + padding: 24px; + border-bottom: 1px solid rgba(255, 255, 255, 0.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: rgba(255, 255, 255, 0.2); + border-radius: 50%; + color: #FFFFFF; +} +.drawer-user-info .drawer-profile .profile-avatar i { + font-size: 32px; +} +.drawer-user-info .drawer-profile .profile-info { + flex: 1; + color: #FFFFFF; +} +.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: 0.9; +} + +.drawer-logout-btn { + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + width: 100%; + padding: 14px 20px; + background: #FF6B6B; + color: #FFFFFF; + text-decoration: none; + border-radius: 12px; + font-weight: 600; + font-size: 16px; + transition: all 0.3s ease; + box-shadow: 0 4px 12px rgba(75, 155, 255, 0.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, 0.15); +} + @media (max-width: 768px) { - .hero-section { - padding: 120px 0 60px; + .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: #FFFFFF; + border-radius: 12px; + overflow: hidden; + box-shadow: 0 2px 4px rgba(0, 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, 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 0.3s ease; +} +.data-table table tbody tr:last-child { + border-bottom: none; +} +.data-table table tbody tr:hover { + background-color: rgba(0, 73, 180, 0.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 0.3s ease; +} +.data-table table tbody td a:hover { + color: #0049b4; +} + +.notice-table { + width: 100%; + background: #FFFFFF; + border-radius: 12px; + overflow: hidden; + box-shadow: 0 2px 4px rgba(0, 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 0.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, 0.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 0.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: 0.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; } } -.hero-patterns { +.table-empty { + padding: 80px 24px; + text-align: center; + background: #FFFFFF; + border-radius: 12px; +} +.table-empty .empty-icon { + margin-bottom: 24px; +} +.table-empty .empty-icon img { + max-width: 200px; + opacity: 0.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: #FFFFFF; + border-radius: 12px; + overflow: hidden; + border: 1px solid #E5E7EB; + box-shadow: 0 1px 3px rgba(0, 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: #FFFFFF; + text-align: center; + letter-spacing: 0.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 0.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: #FFFFFF; +} +.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 0.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: #000000; + border: none; + cursor: pointer; + transition: all 0.3s ease; + white-space: nowrap; +} +.list-table-btn:hover { + transform: translateY(-1px); + box-shadow: 0 2px 4px rgba(0, 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: rgb(187.8846153846, 197.2807692308, 234.8653846154); +} + +.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 0.3s ease; +} +.table-pagination .pagination-btn:hover { + color: #0049b4; +} +.table-pagination .pagination-btn:disabled { + opacity: 0.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 0.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 0.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, 0.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: #FFFFFF; + border: 1px solid #dadada; + border-radius: 8px; + position: relative; + } + .search-field input { + flex: 1; + height: 100%; + padding: 0 40px 0 16px; + border: none; + background: transparent; + 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: #000000; + 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: transparent; + 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: transparent !important; + border-bottom: 1px solid #dadada; + } + .list-table .list-table-row:hover { + background: rgba(0, 73, 180, 0.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: #FFFFFF; + border-radius: 0; + } +} +.faq-accordion .faq-item { + border-bottom: 1px solid #DADADA; + transition: all 0.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 0.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 0.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, 0.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 0.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: #FFFFFF; + } +} +@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: #ffffff; + 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: 0.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: transparent; + 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: transparent; + 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: #FFFFFF; + border-radius: 12px; + box-shadow: 0 2px 4px rgba(0, 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, 0.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 0.3s ease; +} +@media (max-width: 768px) { + .accordion .accordion-header { + padding: 16px 24px; + font-size: 14px; + } +} +.accordion .accordion-header:hover { + background: rgba(0, 73, 180, 0.02); +} +.accordion .accordion-header .accordion-title { + flex: 1; +} +.accordion .accordion-header .accordion-icon { + flex-shrink: 0; + font-size: 20px; + color: #64748B; + transition: all 0.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 0.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; - pointer-events: none; + 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: 0.5; } -.hero-patterns .pattern-circle { - position: absolute; - border-radius: 50%; - opacity: 0.1; -} -.hero-patterns .pattern-circle.pattern-1 { - width: 400px; - height: 400px; - background: #00D4FF; - border-radius: 50%; - opacity: 0.1; - position: absolute; - top: -200px; - right: -100px; - animation: float 20s ease-in-out infinite; -} -.hero-patterns .pattern-circle.pattern-2 { - width: 300px; - height: 300px; - background: #FFD93D; - border-radius: 50%; - opacity: 0.1; - position: absolute; - bottom: -150px; - left: -50px; - animation: float 15s ease-in-out infinite reverse; -} -.hero-patterns .pattern-dots { - position: absolute; - top: 50%; - left: 10%; - width: 100px; - height: 100px; - background-image: radial-gradient(circle, #A78BFA 2px, transparent 2px); - background-size: 20px 20px; - opacity: 0.3; -} - -.hero-content { - text-align: center; - position: relative; - z-index: 1; - max-width: 1280px; - margin: 0 auto; - padding: 0 24px; -} - -.hero-badge { - display: inline-flex; - align-items: center; - gap: 8px; - padding: 8px 24px; - background: #FFFFFF; - border: 2px solid #FFD93D; - border-radius: 50px; - margin-bottom: 24px; - font-size: 14px; - font-weight: 600; - color: #1A1A2E; - animation: slideInDown 0.6s ease-out; -} -.hero-badge .badge-icon { - font-size: 20px; - animation: bounce 2s infinite; -} - -.main-headline { - font-size: 56px; - font-weight: 800; - line-height: 1.2; - margin-bottom: 24px; - color: #1A1A2E; - animation: slideInUp 0.8s ease-out; -} -@media (max-width: 768px) { - .main-headline { - font-size: 40px; - } -} -@media (max-width: 480px) { - .main-headline { - font-size: 32px; - } -} -.main-headline .headline-highlight { - background: linear-gradient(135deg, #00D4FF 0%, #4B9BFF 100%); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; -} - -.sub-headline { - font-size: 20px; - font-weight: 400; - line-height: 1.6; - margin-bottom: 40px; - color: #64748B; - animation: slideInUp 0.9s ease-out; -} -@media (max-width: 768px) { - .sub-headline { - font-size: 16px; - } -} - -.hero-buttons { - display: flex; - gap: 16px; - justify-content: center; - margin-bottom: 64px; - animation: slideInUp 1s ease-out; -} -@media (max-width: 768px) { - .hero-buttons { - flex-direction: column; - align-items: center; - } -} -.hero-buttons .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 0.3s ease; - cursor: pointer; - border: none; -} -.hero-buttons .btn:hover { - transform: translateY(-3px); -} -.hero-buttons .btn.btn-primary { - background: linear-gradient(135deg, #4B9BFF 0%, #2E7FF7 100%); - color: #FFFFFF; - box-shadow: 0 4px 12px rgba(75, 155, 255, 0.1); -} -.hero-buttons .btn.btn-primary:hover { - box-shadow: 0 16px 40px rgba(75, 155, 255, 0.2); -} -.hero-buttons .btn.btn-secondary { - background: #FFFFFF; - color: #4B9BFF; - border: 2px solid #4B9BFF; -} -.hero-buttons .btn.btn-secondary:hover { - background: #EFF6FF; -} -@media (max-width: 480px) { - .hero-buttons .btn { - width: 100%; - justify-content: center; - } -} - -.hero-stats { - display: flex; - justify-content: center; - gap: 64px; - animation: fadeIn 1.2s ease-out; -} -@media (max-width: 768px) { - .hero-stats { - flex-direction: column; - gap: 24px; - } -} -.hero-stats .stat-item { - display: flex; - flex-direction: column; - align-items: center; -} -.hero-stats .stat-item .stat-number { - font-size: 40px; - font-weight: 800; - background: linear-gradient(135deg, #4B9BFF 0%, #2E7FF7 100%); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; -} -.hero-stats .stat-item .stat-label { - font-size: 14px; - color: #64748B; - font-weight: 500; -} - -.hero-image { - position: relative; - margin-top: 64px; - text-align: center; -} -.hero-image img { - max-width: 100%; - height: auto; - border-radius: 16px; - box-shadow: 0 16px 40px rgba(75, 155, 255, 0.2); -} -.hero-image .hero-decoration { - position: absolute; - width: 100%; - height: 100%; - top: 0; - left: 0; - pointer-events: none; -} -.hero-image .hero-decoration::before, .hero-image .hero-decoration::after { - content: ""; - position: absolute; - border-radius: 50%; -} -.hero-image .hero-decoration::before { - width: 60px; - height: 60px; - background: linear-gradient(135deg, #FFD93D 0%, #FF6B6B 100%); - top: -30px; - right: 10%; - animation: bounce 2s infinite; -} -.hero-image .hero-decoration::after { - width: 40px; - height: 40px; - background: linear-gradient(135deg, #00D4FF 0%, #4B9BFF 100%); - bottom: -20px; - left: 15%; - animation: bounce 2s infinite 0.5s; -} - -.api-search-section { - padding: 64px 0; - background: linear-gradient(180deg, #FFFFFF 0%, #EFF6FF 100%); - position: relative; -} -.api-search-section::before { - content: ""; +.page-title-banner .title-image { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); - width: 600px; - height: 600px; - background: radial-gradient(circle, rgba(75, 155, 255, 0.03) 0%, transparent 70%); - pointer-events: none; -} -.api-search-section .search-container { - max-width: 800px; - margin: 0 auto; - text-align: center; - position: relative; + height: 220px; + width: auto; z-index: 1; } -.api-search-section .search-form { - margin-bottom: 32px; - animation: slideInUp 0.8s ease-out; -} -.api-search-section .search-wrapper { - display: flex; - gap: 16px; - padding: 0 24px; +.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) { - .api-search-section .search-wrapper { + .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, 0.1); + border: 1px solid rgba(255, 217, 61, 0.3); + color: #B89900; +} +.alert.alert-error { + background: rgba(255, 107, 107, 0.1); + border: 1px solid rgba(255, 107, 107, 0.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, 0.1); + border: 1px solid rgba(107, 207, 127, 0.3); + color: rgb(61.5183673469, 189.6816326531, 87.1510204082); +} +.alert.alert-info { + background: rgba(0, 73, 180, 0.1); + border: 1px solid rgba(0, 73, 180, 0.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 0.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 0.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 0.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 0.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 0.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 0.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 0.6s ease-in-out, visibility 0.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: "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: "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: #000000; + 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: #FFFFFF; + font-family: "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 0.3s ease; +} +.btn-hero-signup:hover { + background: rgb(0, 65.7, 162); + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(0, 73, 180, 0.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: rgba(255, 255, 255, 0.5); + border: none; + border-radius: 50px; + cursor: pointer; + display: none; + align-items: center; + justify-content: center; + z-index: 10; + transition: all 0.3s ease; +} +.hero-nav-btn:hover { + background: rgba(255, 255, 255, 0.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 0.3s ease; +} +.hero-indicator.active { + background: #0049B4; +} +.hero-indicator:hover:not(.active) { + background: rgba(0, 0, 0, 0.5); +} +@media (max-width: 768px) { + .hero-indicator { + width: 6px; + height: 6px; + border-radius: 50%; + background: transparent; + 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 0.3s ease; + position: relative; +} +.hero-autoplay-toggle svg { + position: absolute; + opacity: 0; + transition: opacity 0.3s ease; +} +.hero-autoplay-toggle svg.active { + opacity: 1; +} +.hero-autoplay-toggle:hover { + background: rgb(255, 255, 255); + border-color: #0049B4; + box-shadow: 0 2px 8px rgba(0, 73, 180, 0.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: "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: #000000; + 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: #FFFFFF; + 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; - padding: 16px 32px; - font-size: 16px; - border: 2px solid #E2E8F0; - border-radius: 50px; - background: #FFFFFF; - transition: all 0.3s ease; + width: 100%; + height: 100%; + border: none; outline: none; + background: transparent; + font-family: "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; } .api-search-section .search-input::placeholder { - color: #94A3B8; + color: #B3B3B3; + font-weight: 400; } -.api-search-section .search-input:focus { - border-color: #4B9BFF; - box-shadow: 0 0 0 4px rgba(75, 155, 255, 0.1); +@media (max-width: 1024px) { + .api-search-section .search-input { + font-size: 15px; + padding: 0 12px; + } } @media (max-width: 768px) { .api-search-section .search-input { - width: 100%; - padding: 16px 24px; + font-size: 11px; + font-weight: 500; + padding: 0 8px; + } + .api-search-section .search-input::placeholder { + color: #8C959F; + font-weight: 500; } } -.api-search-section .search-button { - padding: 16px 40px; - background: linear-gradient(135deg, #4B9BFF 0%, #2E7FF7 100%); - color: #FFFFFF; +.api-search-section .search-icon-button { + background: none; border: none; - border-radius: 50px; - font-size: 16px; - font-weight: 600; cursor: pointer; - transition: all 0.3s ease; - display: flex; - align-items: center; - gap: 8px; - box-shadow: 0 4px 12px rgba(75, 155, 255, 0.1); - white-space: nowrap; -} -.api-search-section .search-button i { - font-size: 20px; -} -.api-search-section .search-button:hover { - transform: translateY(-2px); - box-shadow: 0 16px 40px rgba(75, 155, 255, 0.2); - background: linear-gradient(135deg, rgb(49.5, 140.8333333333, 255) 0%, rgb(21.4400921659, 111.9585253456, 246.0599078341) 100%); -} -.api-search-section .search-button:active { - transform: translateY(0); - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); -} -@media (max-width: 768px) { - .api-search-section .search-button { - width: 100%; - justify-content: center; - padding: 16px 32px; - } -} -.api-search-section .hashtag-suggestions { + padding: 0; display: flex; align-items: center; justify-content: center; - gap: 16px; - flex-wrap: wrap; - padding: 0 24px; - animation: fadeIn 0.8s ease-out 0.2s both; + width: 40px; + height: 40px; + flex-shrink: 0; + transition: transform 0.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 .hashtag-suggestions { + .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; - justify-content: flex-start; + 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-size: 14px; - color: #64748B; - font-weight: 500; + font-family: "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 { - width: 100%; - margin-bottom: 4px; + font-size: 12px; + color: #212529; } } -.api-search-section .hashtag { - display: inline-flex; +.api-search-section .hashtag-list { + display: flex; align-items: center; - padding: 4px 24px; - background: #FFFFFF; - border: 1px solid #E2E8F0; - border-radius: 50px; - font-size: 14px; - color: #1A1A2E; - text-decoration: none; - transition: all 0.3s ease; - position: relative; - overflow: hidden; + gap: 14px; + flex-wrap: wrap; } -.api-search-section .hashtag::before { - content: ""; - position: absolute; - top: 0; - left: -100%; - width: 100%; - height: 100%; - background: linear-gradient(135deg, #4B9BFF 0%, #2E7FF7 100%); - transition: left 0.3s ease; - z-index: 0; -} -.api-search-section .hashtag:hover { - border-color: #4B9BFF; - transform: translateY(-2px); - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); - color: #FFFFFF; - position: relative; - z-index: 1; -} -.api-search-section .hashtag:hover::before { - left: 0; +@media (max-width: 1024px) { + .api-search-section .hashtag-list { + justify-content: center; + gap: 10px; + } } @media (max-width: 768px) { - .api-search-section .hashtag { - padding: 4px 16px; + .api-search-section .hashtag-list { + gap: 8px; + justify-content: left; + } +} +.api-search-section .hashtag-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: #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: "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; } } .api-showcase { - padding: 100px 0; + position: relative; + display: flex; + align-items: center; + padding: 75px 0 100px; background: #FFFFFF; + 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: "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: #000000; + 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: "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: "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; + 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 0.3s ease; +} +.api-showcase .btn-all-apis svg path { + stroke: #FFFFFF; +} +@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: #FFFFFF; + border: 1px solid #DDDDDD; + border-radius: 20px; + padding: 36px 38px; + display: flex; + flex-direction: column; + align-items: flex-start; + position: relative; + transition: all 0.3s ease; + 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; +} +@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, 0.3); + gap: 20px; + } +} +.api-showcase .card-title { + font-family: "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: #000000; + margin: 0 0 12px 0; + } +} +.api-showcase .card-description { + font-family: "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: #FFFFFF; - position: relative; + overflow: hidden; + height: 740px; + display: flex; + align-items: center; } -.info-section::before { - content: ""; +@media (max-width: 768px) { + .info-section { + min-height: 392px; + padding: 75px 20px 20px; + } +} +.info-section .info-background { position: absolute; - top: 0; - left: 0; - right: 0; - height: 1px; - background: linear-gradient(90deg, transparent 0%, #E2E8F0 20%, #E2E8F0 80%, transparent 100%); + 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; - flex-direction: column; align-items: center; - gap: 48px; + justify-content: space-between; + gap: 80px; } -.info-section .image-container { - width: 100%; - max-width: 600px; - margin: 0 auto; -} -.info-section .image-placeholder { - position: relative; - width: 100%; - height: 300px; - background: linear-gradient(135deg, #EFF6FF 0%, rgba(75, 155, 255, 0.1) 100%); - border-radius: 20px; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - gap: 16px; - box-shadow: 0 4px 12px rgba(75, 155, 255, 0.1); - overflow: hidden; -} -.info-section .image-placeholder::before { - content: ""; - position: absolute; - top: -50%; - left: -50%; - width: 200%; - height: 200%; - background: radial-gradient(circle at center, rgba(0, 212, 255, 0.1) 0%, transparent 50%); - animation: rotate 20s linear infinite; -} -.info-section .image-placeholder::after { - content: ""; - position: absolute; - bottom: -20px; - right: -20px; - width: 100px; - height: 100px; - background: linear-gradient(135deg, #00D4FF 0%, #4B9BFF 100%); - border-radius: 50%; - opacity: 0.3; - filter: blur(40px); -} -.info-section .image-placeholder i { - font-size: 64px; - color: #4B9BFF; - z-index: 1; -} -.info-section .image-placeholder span { - font-size: 24px; - font-weight: 600; - color: #1A1A2E; - z-index: 1; +@media (max-width: 1024px) { + .info-section .info-wrapper { + flex-direction: column; + gap: 64px; + } } @media (max-width: 768px) { - .info-section .image-placeholder { - height: 200px; - } - .info-section .image-placeholder i { - font-size: 48px; - } - .info-section .image-placeholder span { - font-size: 20px; + .info-section .info-wrapper { + display: block; + flex-direction: column; + gap: 20px; + position: relative; } } .info-section .info-content { - text-align: center; - max-width: 800px; - margin: 0 auto; -} -.info-section .highlight-text { - font-size: 24px; - line-height: 1.8; - color: #1A1A2E; - margin-bottom: 48px; - font-weight: 500; -} -.info-section .highlight-text .text-accent { - color: #4B9BFF; - font-weight: 600; + flex: 1; + max-width: 600px; } @media (max-width: 1024px) { - .info-section .highlight-text { - font-size: 20px; + .info-section .info-content { + text-align: center; + max-width: 100%; } } @media (max-width: 768px) { - .info-section .highlight-text { + .info-section .info-content { + text-align: left; + max-width: 100%; + order: 1; + } +} +.info-section .info-title { + font-family: "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; +} +@media (max-width: 1024px) { + .info-section .info-title { + font-size: 36px; + } +} +@media (max-width: 768px) { + .info-section .info-title { font-size: 16px; - padding: 0 24px; - margin-bottom: 40px; + 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: "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; +} +@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; - justify-content: center; - flex-wrap: wrap; +} +@media (max-width: 1024px) { + .info-section .action-buttons { + justify-content: center; + } } @media (max-width: 768px) { .info-section .action-buttons { - flex-direction: column; - align-items: center; - padding: 0 24px; + flex-direction: row; + align-items: stretch; + gap: 15px; + width: 100%; } } .info-section .action-btn { - display: flex; - align-items: center; - gap: 16px; - padding: 16px 32px; - background: #FFFFFF; - border: 2px solid #E2E8F0; - border-radius: 50px; - text-decoration: none; - transition: all 0.3s ease; - min-width: 280px; - position: relative; - overflow: hidden; -} -.info-section .action-btn::before { - content: ""; - position: absolute; - top: 0; - left: 0; - width: 0; - height: 100%; - background: linear-gradient(135deg, #4B9BFF 0%, #2E7FF7 100%); - transition: width 0.3s ease; - z-index: 0; -} -.info-section .action-btn:hover { - border-color: #4B9BFF; - transform: translateY(-3px); - box-shadow: 0 8px 24px rgba(75, 155, 255, 0.15); -} -.info-section .action-btn:hover::before { - width: 100%; -} -.info-section .action-btn:hover .btn-number { - background: #FFFFFF; - color: #4B9BFF; -} -.info-section .action-btn:hover .btn-text, -.info-section .action-btn:hover i { - color: #FFFFFF; -} -.info-section .action-btn .btn-number { - position: relative; - z-index: 1; - width: 32px; - height: 32px; - display: flex; + display: inline-flex; align-items: center; justify-content: center; - background: linear-gradient(135deg, #4B9BFF 0%, #2E7FF7 100%); - color: #FFFFFF; - border-radius: 50%; + padding: 18px 52px; + font-family: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-size: 24px; font-weight: 700; - font-size: 16px; - flex-shrink: 0; - transition: all 0.3s ease; -} -.info-section .action-btn .btn-text { - position: relative; - z-index: 1; - font-size: 16px; - font-weight: 500; - color: #1A1A2E; - transition: color 0.3s ease; -} -.info-section .action-btn i { - position: relative; - z-index: 1; - font-size: 14px; - color: #94A3B8; - margin-left: auto; + line-height: 56px; + border-radius: 20px; + text-decoration: none; + white-space: nowrap; transition: all 0.3s ease; + min-width: 310px; + height: 128px; } @media (max-width: 768px) { .info-section .action-btn { - min-width: 100%; - justify-content: flex-start; + 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, 0.15); +} +.info-section .action-btn:active { + transform: translateY(0); +} +.info-section .action-btn-primary { + background: #0049B4; + color: #FFFFFF; +} +.info-section .action-btn-primary:hover { + background: rgb(0, 65.7, 162); +} +.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; + 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; } } -@keyframes rotate { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); +.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 { - padding: 100px 0; - background: #F8FAFC; +.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; text-align: center; - margin-bottom: 64px; +} +@media (max-width: 768px) { + .support-center .section-header { + margin-bottom: 24px; + text-align: left; + } } .support-center .section-header .section-title { - font-size: 48px; - font-weight: 700; - color: #1A1A2E; - margin-bottom: 16px; + font-family: "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; + 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: 40px; + font-size: 16px; + line-height: 1; + letter-spacing: -0.32px; } } -.support-center .section-header .section-title .title-highlight { - background: linear-gradient(135deg, #4B9BFF 0%, #2E7FF7 100%); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; +@media (max-width: 768px) { + .support-center .section-header .section-title .title-regular { + font-weight: 500; + } } -.support-center .section-header .section-subtitle { - font-size: 20px; - color: #64748B; - line-height: 1.8; +.support-center .section-header .section-title .title-bold { + font-size: 38px; + font-weight: 700; } @media (max-width: 768px) { - .support-center .section-header .section-subtitle { - font-size: 16px; + .support-center .section-header .section-title .title-bold { + font-size: 20px; + letter-spacing: -0.4px; } } .support-center .support-grid { - display: grid; - grid-template-columns: repeat(3, 1fr); + display: flex; gap: 32px; - max-width: 1000px; + justify-content: center; + align-items: stretch; + max-width: 1228px; + height: 320px; margin: 0 auto; } @media (max-width: 1024px) { .support-center .support-grid { - grid-template-columns: 1fr; - gap: 24px; - max-width: 500px; + flex-direction: column; + align-items: center; + gap: 20px; } } @media (max-width: 768px) { .support-center .support-grid { - gap: 16px; - padding: 0 16px; + flex-direction: row; + justify-content: space-between; + align-items: stretch; + gap: 12px; + max-width: 100%; } } .support-center .support-card { - position: relative; - display: flex; - flex-direction: column; - align-items: center; - padding: 40px 32px; background: #FFFFFF; - border-radius: 16px; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); + border-radius: 20px; text-decoration: none; transition: all 0.3s ease; - overflow: hidden; -} -.support-center .support-card::before { - content: ""; - position: absolute; - top: 0; - left: 0; - right: 0; - height: 4px; - background: linear-gradient(135deg, #4B9BFF 0%, #2E7FF7 100%); - transform: scaleX(0); - transition: transform 0.3s ease; -} -.support-center .support-card:hover { - transform: translateY(-8px); - box-shadow: 0 16px 40px rgba(75, 155, 255, 0.2); -} -.support-center .support-card:hover::before { - transform: scaleX(1); -} -.support-center .support-card:hover .card-icon { - transform: scale(1.1) rotate(5deg); - background: linear-gradient(135deg, #4B9BFF 0%, #2E7FF7 100%); - color: #FFFFFF; -} -.support-center .support-card:hover .card-arrow { - transform: translateX(5px); - color: #4B9BFF; -} -.support-center .support-card .card-icon { - width: 80px; - height: 80px; display: flex; - align-items: center; - justify-content: center; - background: #EFF6FF; - border-radius: 50px; - margin-bottom: 24px; - transition: all 0.3s ease; + box-sizing: border-box; + position: relative; + overflow: hidden; + height: 320px; } -.support-center .support-card .card-icon i { - font-size: 36px; - color: #4B9BFF; -} -.support-center .support-card h3 { - font-size: 24px; - font-weight: 600; - color: #1A1A2E; - margin-bottom: 8px; - text-align: center; -} -.support-center .support-card p { - font-size: 16px; - color: #64748B; - text-align: center; - line-height: 1.8; - margin-bottom: 24px; -} -.support-center .support-card .card-arrow { - margin-top: auto; - color: #94A3B8; - transition: all 0.3s ease; -} -.support-center .support-card .card-arrow i { - font-size: 20px; +@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 { - padding: 32px 24px; + 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, 0.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: 70px; - height: 70px; - } - .support-center .support-card .card-icon i { - font-size: 30px; - } - .support-center .support-card h3 { - font-size: 20px; - } - .support-center .support-card p { - font-size: 14px; + width: 48px !important; + height: 48px !important; + margin-bottom: 0 !important; } } - -.api-stats-section { - padding: 100px 0; - background: linear-gradient(180deg, #FFFFFF 0%, #EFF6FF 100%); - position: relative; - overflow: hidden; +.support-center .support-card .card-content { + display: flex; + flex-direction: column; + justify-content: center; } -.api-stats-section::before { - content: ""; - position: absolute; - top: 50%; - left: -100px; - width: 200px; - height: 200px; - background: radial-gradient(circle, #00D4FF 0%, transparent 70%); - opacity: 0.1; - border-radius: 50%; - transform: translateY(-50%); +@media (max-width: 768px) { + .support-center .support-card .card-content { + align-items: center; + text-align: center; + gap: 4px; + } } -.api-stats-section::after { - content: ""; - position: absolute; - top: 50%; - right: -100px; - width: 300px; - height: 300px; - background: radial-gradient(circle, #4B9BFF 0%, transparent 70%); - opacity: 0.08; - border-radius: 50%; - transform: translateY(-50%); -} -.api-stats-section .section-header { - text-align: center; - margin-bottom: 80px; - position: relative; - z-index: 1; -} -.api-stats-section .section-header .section-title { - font-size: 40px; +.support-center .support-card .card-content h3 { + font-family: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-size: 32px; font-weight: 700; - color: #1A1A2E; - line-height: 1.8; - margin-bottom: 40px; + color: #212529; + margin: 0 0 10px 0; + line-height: 1.3; } @media (max-width: 1024px) { - .api-stats-section .section-header .section-title { + .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: "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: #FFFFFF; + font-size: 30px; +} +@media (max-width: 1024px) { + .support-center .support-card--notice .card-content h3 { font-size: 32px; } } @media (max-width: 768px) { - .api-stats-section .section-header .section-title { - font-size: 24px; - padding: 0 24px; + .support-center .support-card--notice .card-content h3 { + font-size: 15px; + color: #FFFFFF; } } -.api-stats-section .section-header .section-title .title-highlight { - background: linear-gradient(135deg, #4B9BFF 0%, #2E7FF7 100%); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; - font-weight: 800; +.support-center .support-card--notice .card-content p { + color: rgba(255, 255, 255, 0.9); + font-size: 22px; } -.api-stats-section .stats-cards { - display: grid; - grid-template-columns: repeat(3, 1fr); - gap: 40px; - max-width: 1100px; - margin: 0 auto; +@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: rgba(255, 255, 255, 0.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: "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 { - grid-template-columns: 1fr; - gap: 32px; - max-width: 400px; + flex-direction: column; + gap: 40px; } } @media (max-width: 768px) { .api-stats-section .stats-cards { - padding: 0 24px; + 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 { - background: #FFFFFF; - border-radius: 16px; - padding: 40px; - box-shadow: 0 4px 12px rgba(75, 155, 255, 0.1); - border: 1px solid rgba(75, 155, 255, 0.1); - position: relative; - overflow: hidden; - transition: all 0.4s ease; + display: flex; + flex-direction: column; + align-items: center; + gap: 10px; + width: 145px; } -.api-stats-section .stat-card::before { - content: ""; - position: absolute; - top: -2px; - left: -2px; - right: -2px; - bottom: -2px; - background: linear-gradient(135deg, #4B9BFF 0%, #2E7FF7 100%); - border-radius: 16px; - opacity: 0; - z-index: -1; - transition: opacity 0.4s ease; +@media (max-width: 1024px) { + .api-stats-section .stat-card { + width: auto; + } } -.api-stats-section .stat-card:hover { - transform: translateY(-10px) scale(1.02); - box-shadow: 0 16px 40px rgba(75, 155, 255, 0.2); +@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:hover::before { - opacity: 1; -} -.api-stats-section .stat-card:hover .stat-icon { - transform: rotate(360deg) scale(1.1); - background: linear-gradient(135deg, #4B9BFF 0%, #2E7FF7 100%); -} -.api-stats-section .stat-card:hover .stat-icon i { +.api-stats-section .stat-card .stat-label { + font-family: "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; } -.api-stats-section .stat-card:hover .stat-number { - background: linear-gradient(135deg, #00D4FF 0%, #4B9BFF 100%); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; +@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; @@ -3969,83 +8456,79 @@ select.form-control { display: flex; align-items: center; justify-content: center; - background: #EFF6FF; - border-radius: 50px; - margin-bottom: 24px; - transition: all 0.6s cubic-bezier(0.4, 0, 0.2, 1); + transition: transform 0.3s ease; } -.api-stats-section .stat-card .stat-icon i { - font-size: 32px; - color: #4B9BFF; - transition: color 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-content .stat-number { - font-size: 48px; - font-weight: 800; - color: #1A1A2E; - margin-bottom: 8px; - transition: all 0.3s ease; - display: block; +.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-content .stat-number { + .api-stats-section .stat-card .stat-icon { + width: 40px; + height: 40px; + } +} +.api-stats-section .stat-card .stat-number { + font-family: "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; } } -.api-stats-section .stat-card .stat-content .stat-number[data-formatted]::after { - content: attr(data-formatted); +@media (max-width: 768px) { + .api-stats-section .stat-card .stat-number { + font-size: 16px; + line-height: 1; + } } -.api-stats-section .stat-card .stat-content .stat-label { - font-size: 20px; - font-weight: 600; - color: #1A1A2E; - margin-bottom: 4px; +.api-stats-section .stat-card:hover .stat-icon { + transform: scale(1.1); } -.api-stats-section .stat-card .stat-content .stat-description { - font-size: 14px; - color: #64748B; - line-height: 1.6; +.api-stats-section .stats-divider { + width: 1px; + height: 200px; + margin: 0 60px; + flex-shrink: 0; + background-color: #ffffff; } -.api-stats-section .stat-card:nth-child(1) .stat-icon { - background: rgba(0, 212, 255, 0.1); +@media (max-width: 1024px) { + .api-stats-section .stats-divider { + display: none; + } } -.api-stats-section .stat-card:nth-child(1) .stat-icon i { - color: #00D4FF; -} -.api-stats-section .stat-card:nth-child(1):hover .stat-icon { - background: linear-gradient(135deg, #00D4FF 0%, #4B9BFF 100%); -} -.api-stats-section .stat-card:nth-child(2) .stat-icon { - background: rgba(107, 207, 127, 0.1); -} -.api-stats-section .stat-card:nth-child(2) .stat-icon i { - color: #6BCF7F; -} -.api-stats-section .stat-card:nth-child(2):hover .stat-icon { - background: linear-gradient(135deg, #6BCF7F 0%, rgb(68.4897959184, 194.5102040816, 93.693877551) 100%); -} -.api-stats-section .stat-card:nth-child(3) .stat-icon { - background: rgba(167, 139, 250, 0.1); -} -.api-stats-section .stat-card:nth-child(3) .stat-icon i { - color: #A78BFA; -} -.api-stats-section .stat-card:nth-child(3):hover .stat-icon { - background: linear-gradient(135deg, #A78BFA 0%, rgb(129.9090909091, 90.1074380165, 247.8925619835) 100%); +@media (min-width: 1280px) { + .api-stats-section .stats-divider { + margin: 0 120px; + } } @media (max-width: 768px) { - .api-stats-section .stat-card { - padding: 32px; - } - .api-stats-section .stat-card .stat-icon { - width: 60px; - height: 60px; - } - .api-stats-section .stat-card .stat-icon i { - font-size: 28px; + .api-stats-section .stats-divider { + display: block !important; + width: 1px; + height: 108px; + margin: 0; + background-color: rgb(250, 250, 250); } } +.stat-number { + animation: countUp 0.6s ease-out forwards; +} + @keyframes countUp { from { opacity: 0; @@ -4056,27 +8539,129 @@ select.form-control { transform: translateY(0); } } -.stat-number { - animation: countUp 0.6s ease-out forwards; +.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: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-size: 40px; + font-weight: 700; + color: #000000; + 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: 0.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: #FFFFFF; + font-family: "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 0.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, 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; + 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: #DDDDDD; } .api-market-container { display: flex; min-height: 100vh; - background-color: #F8FAFC; + background-color: #FFFFFF; position: relative; + margin-bottom: 20px; } .api-market-sidebar { - width: 280px; - background-color: #FFFFFF; - border-right: 1px solid #E2E8F0; + width: 283px; + background-color: #F8FBFD; + border-radius: 20px; padding: 40px 24px; position: sticky; - top: 0; - height: 100vh; - overflow-y: auto; + top: 24px; + height: calc(100vh - 32px); flex-shrink: 0; + margin: 16px; } @media (max-width: 1024px) { .api-market-sidebar { @@ -4093,33 +8678,50 @@ select.form-control { box-shadow: 0 8px 24px rgba(75, 155, 255, 0.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: 4px; + margin-bottom: 0; + border-bottom: 1px solid #DDDDDD; +} +.api-sidebar-nav .menu-section:last-child { + border-bottom: none; } .api-sidebar-nav .menu-title { - padding: 12px 16px; - font-size: 14px; - font-weight: 600; - color: #2E7FF7; + padding: 16px 10px; + font-size: 20px; + font-weight: 400; + color: #1A1A2E; cursor: pointer; - border-radius: 8px; transition: all 0.3s ease; display: flex; align-items: center; justify-content: space-between; + position: relative; } .api-sidebar-nav .menu-title:hover { - background-color: #F8FAFC; + background-color: rgba(0, 73, 180, 0.05); } .api-sidebar-nav .menu-title.active { - background-color: #EFF6FF; - color: #2E7FF7; + font-weight: 700; + color: #0049b4; } .api-sidebar-nav .menu-title .api-count { display: inline-flex; @@ -4128,19 +8730,54 @@ select.form-control { min-width: 24px; height: 24px; padding: 0 8px; - background-color: rgba(75, 155, 255, 0.1); - color: #4B9BFF; + 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: #4B9BFF; + background-color: #0049b4; color: #FFFFFF; } +.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 0.3s ease; +} +.api-sidebar-nav .accordion-section .menu-title.accordion-trigger .accordion-icon i { + font-size: 12px; + color: #64748B; + transition: all 0.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 0.3s ease, padding 0.3s ease, margin 0.3s ease; +} +.api-sidebar-nav .api-list.accordion-content.expanded { + max-height: 500px; margin-top: 4px; margin-bottom: 8px; } @@ -4159,11 +8796,11 @@ select.form-control { .api-sidebar-nav .api-item::before { content: "•"; margin-right: 8px; - color: #4B9BFF; + color: #0049b4; opacity: 0.5; } .api-sidebar-nav .api-item:hover { - background-color: rgba(75, 155, 255, 0.05); + background-color: rgba(0, 73, 180, 0.05); color: #1A1A2E; padding-left: 20px; } @@ -4171,8 +8808,8 @@ select.form-control { opacity: 1; } .api-sidebar-nav .api-item.active { - background-color: rgba(75, 155, 255, 0.1); - color: #4B9BFF; + background-color: rgba(0, 73, 180, 0.1); + color: #0049b4; font-weight: 500; } .api-sidebar-nav .api-item.active::before { @@ -4187,7 +8824,9 @@ select.form-control { .api-market-content { flex: 1; - padding: 40px 48px; + padding-top: 80px; + padding-left: 8px; + padding-right: 8px; min-height: 100vh; display: flex; flex-direction: column; @@ -4197,6 +8836,15 @@ select.form-control { padding: 24px 16px; } } +.api-market-content .api-result-count { + font-size: 20px; + color: #000000; + white-space: nowrap; +} +.api-market-content .api-result-count strong { + color: #0049b4; + font-weight: 600; +} .api-mobile-toggle { display: none; @@ -4205,7 +8853,6 @@ select.form-control { right: 24px; width: 56px; height: 56px; - background: linear-gradient(135deg, #4B9BFF 0%, #2E7FF7 100%); border-radius: 50%; border: none; color: #FFFFFF; @@ -4214,6 +8861,7 @@ select.form-control { 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); @@ -4234,7 +8882,7 @@ select.form-control { display: flex; justify-content: space-between; align-items: center; - margin-bottom: 40px; + margin-bottom: 24px; } @media (max-width: 768px) { .api-market-header { @@ -4280,8 +8928,8 @@ select.form-control { transition: all 0.3s ease; } .api-market-search input:focus { - border-color: #4B9BFF; - 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.1); } .api-market-search input::placeholder { color: #94A3B8; @@ -4305,7 +8953,7 @@ select.form-control { } .api-market-search .search-submit-btn:hover { background: #EFF6FF; - color: #4B9BFF; + color: #0049b4; transform: translateY(-50%) scale(1.05); } .api-market-search .search-submit-btn:active { @@ -4316,21 +8964,16 @@ select.form-control { display: block; } -.api-result-count { - font-size: 16px; - color: #64748B; - margin-bottom: 32px; -} -.api-result-count strong { - color: #4B9BFF; - font-weight: 600; -} - .api-card-grid { display: grid; - grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); + grid-template-columns: repeat(3, 1fr); gap: 24px; } +@media (max-width: 1024px) { + .api-card-grid { + grid-template-columns: repeat(2, 1fr); + } +} @media (max-width: 768px) { .api-card-grid { grid-template-columns: 1fr; @@ -4342,111 +8985,74 @@ select.form-control { border-radius: 12px; padding: 32px; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); - transition: all 0.3s ease; cursor: pointer; display: flex; flex-direction: column; - justify-content: space-between; - min-height: 220px; + gap: 16px; + min-height: 180px; position: relative; - overflow: hidden; -} -.api-card::before { - content: ""; - position: absolute; - top: 0; - left: 0; - right: 0; - height: 4px; - background: linear-gradient(135deg, #4B9BFF 0%, #2E7FF7 100%); - transform: scaleX(0); - transform-origin: left; - transition: transform 0.3s ease; -} -.api-card:hover { - box-shadow: 0 8px 24px rgba(75, 155, 255, 0.15); - transform: translateY(-5px); -} -.api-card:hover::before { - transform: scaleX(1); -} -.api-card:hover .api-card-icon { - transform: scale(1.1); + border: 1px solid #E2E8F0; } .api-card:active { - transform: translateY(-3px); + transform: scale(0.98); } -.api-card-header { +.api-card-group { display: flex; align-items: center; gap: 8px; - margin-bottom: 16px; } -.api-card-badge { - display: inline-flex; +.api-card-group-icon { + width: 32px; + height: 32px; + display: flex; align-items: center; - gap: 6px; - padding: 6px 12px; - background-color: #F8FAFC; - border-radius: 6px; - font-size: 12px; + 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-badge::before { - content: "◉"; - color: #4B9BFF; - font-size: 10px; -} -.api-card-title { - font-size: 18px; +.api-card-name { + font-size: 20px; font-weight: 600; color: #1A1A2E; - margin-bottom: 12px; 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; -} - -.api-card-icon { - text-align: center; - margin-top: 24px; - font-size: 48px; - opacity: 0.8; - transition: transform 0.3s ease; -} -.api-card-icon img { - max-width: 60px; - max-height: 60px; - object-fit: contain; -} - -.api-card:nth-child(3n+1) .api-card-icon { - color: #60a5fa; -} - -.api-card:nth-child(3n+2) .api-card-icon { - color: #34d399; -} - -.api-card:nth-child(3n+3) .api-card-icon { - color: #818cf8; + text-align: left; } .api-empty-state { @@ -4480,7 +9086,7 @@ select.form-control { width: 48px; height: 48px; border: 4px solid #E2E8F0; - border-top-color: #4B9BFF; + border-top-color: #0049b4; border-radius: 50%; animation: spin 1s linear infinite; } @@ -4539,13 +9145,13 @@ select.form-control { bottom: -2px; } .api-detail-tabs .tab-button:hover { - color: #4B9BFF; - background-color: rgba(75, 155, 255, 0.05); + color: #0049b4; + background-color: rgba(0, 73, 180, 0.05); } .api-detail-tabs .tab-button.active { - color: #4B9BFF; + color: #0049b4; font-weight: 600; - border-bottom-color: #4B9BFF; + border-bottom-color: #0049b4; } .tab-content { @@ -4559,10 +9165,10 @@ select.form-control { } .api-overview-card { - background: #FFFFFF; - border-radius: 12px; - padding: 32px; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); + background: transparent; + border-radius: 0; + padding: 0; + padding-bottom: 32px; display: flex; flex-direction: column; gap: 24px; @@ -4580,12 +9186,13 @@ select.form-control { } } .api-overview-card .api-method-badge { + background: #0049b4; display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; padding: 8px 16px; - background: linear-gradient(135deg, #4B9BFF 0%, #2E7FF7 100%); + height: 100%; color: #FFFFFF; font-size: 14px; font-weight: 600; @@ -4631,22 +9238,23 @@ select.form-control { } .api-detail-card { - background: #FFFFFF; - border-radius: 12px; - padding: 32px; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); - transition: all 0.3s ease; + background: transparent; + border-radius: 0; + padding: 0; + padding-bottom: 32px; + border-bottom: 1px solid #E2E8F0; } -.api-detail-card:hover { - box-shadow: 0 4px 12px rgba(75, 155, 255, 0.1); +.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: 8px; - border-bottom: 2px solid #EFF6FF; + padding-bottom: 0; + border-bottom: none; } .api-detail-card .detail-content { font-size: 14px; @@ -4687,7 +9295,7 @@ select.form-control { font-weight: 600; color: #1A1A2E; border-bottom: 2px solid #E2E8F0; - background-color: rgba(75, 155, 255, 0.05); + background-color: rgba(0, 73, 180, 0.05); } .api-detail-card .detail-content table th:not(:last-child) { border-right: 1px solid #E2E8F0; @@ -4708,7 +9316,7 @@ select.form-control { border-bottom: none; } .api-detail-card .detail-content table tr:hover { - background-color: rgba(75, 155, 255, 0.02); + background-color: rgba(0, 73, 180, 0.02); } .api-detail-card .detail-content table tbody tr:nth-child(even) { background-color: rgba(248, 250, 252, 0.3); @@ -4745,221 +9353,330 @@ select.form-control { border-radius: 6px; font-family: "Courier New", monospace; font-size: 12px; - color: #4B9BFF; + 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 { - min-height: calc(100vh - 140px); display: flex; align-items: center; justify-content: center; - background: linear-gradient(135deg, var(--light-bg) 0%, var(--gray-bg) 100%); + background: #EDF9FE; padding: 40px 20px; position: relative; - overflow: hidden; -} -.login-page::before { - content: ""; - position: absolute; - top: -50%; - right: -20%; - width: 600px; - height: 600px; - background: radial-gradient(circle, var(--accent-cyan) 0%, transparent 70%); - opacity: 0.1; - border-radius: 50%; -} -.login-page::after { - content: ""; - position: absolute; - bottom: -30%; - left: -10%; - width: 400px; - height: 400px; - background: radial-gradient(circle, var(--primary-blue) 0%, transparent 70%); - opacity: 0.1; - border-radius: 50%; + border-radius: 12px; + margin-top: 60px; + margin-bottom: 60px; } .login-container { width: 100%; - max-width: 480px; + max-width: 504px; margin: 0 auto; position: relative; - z-index: 1; } .login-card { - background: var(--white); - border-radius: 24px; - box-shadow: var(--shadow-xl); - padding: 48px 40px; - backdrop-filter: blur(10px); - border: 1px solid rgba(255, 255, 255, 0.8); + background: transparent; + padding: 0; + display: flex; + flex-direction: column; + align-items: center; } -@media (max-width: 480px) { +@media (max-width: 576px) { .login-card { - padding: 32px 24px; - border-radius: 16px; + padding: 0 20px; } } -.login-header { +.login-logo { + width: 90px; + height: 90px; + margin-bottom: 30px; +} +.login-logo img { + width: 100%; + height: 100%; + object-fit: contain; +} + +.login-message { + font-family: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-size: 20px; + font-weight: 400; + color: #000000; text-align: center; margin-bottom: 40px; -} -.login-header .login-logo { - display: inline-flex; - align-items: center; - gap: 12px; - margin-bottom: 24px; -} -.login-header .login-logo img { - height: 48px; - width: auto; -} -.login-header .login-logo .logo-text { - font-size: 20px; - font-weight: 700; - color: var(--primary-blue); - padding: 6px 16px; - background: var(--light-bg); - border-radius: 24px; -} -.login-header .login-title { - font-size: 28px; - font-weight: 700; - color: var(--text-dark); - margin-bottom: 8px; -} -.login-header .login-subtitle { - font-size: 15px; - color: var(--text-gray); + line-height: 1; } .login-form { - margin-bottom: 24px; -} -.login-form .form-group { - margin-bottom: 20px; -} -.login-form .form-group:last-child { + width: 100%; margin-bottom: 0; } -.login-form .form-label { - display: block; - font-size: 14px; - font-weight: 600; - color: var(--text-dark); - margin-bottom: 8px; +.login-form .form-group { + margin-bottom: 24px; +} +.login-form .form-group:last-of-type { + margin-bottom: 0; } .login-form .form-input { width: 100%; - padding: 14px 16px; - font-size: 15px; - border: 2px solid var(--border-gray); + height: 70px; + padding: 0 32px; + 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; - background: var(--white); - transition: all 0.3s ease; outline: none; + transition: all 0.3s ease; } .login-form .form-input::placeholder { - color: var(--text-light); + color: #5F666C; } .login-form .form-input:focus { - border-color: var(--primary-blue); - box-shadow: 0 0 0 4px rgba(75, 155, 255, 0.1); + border-color: #0049B4; } .login-form .form-input.error { - border-color: var(--accent-orange); -} -.login-form .form-input.error:focus { - box-shadow: 0 0 0 4px rgba(255, 107, 107, 0.1); + border-color: #FF6B6B; } .login-form .form-checkbox { display: flex; align-items: center; - gap: 8px; - margin-top: 16px; + justify-content: flex-end; + gap: 7px; + margin-top: 24px; + margin-bottom: 22px; + margin-right: 10px; } .login-form .form-checkbox input[type=checkbox] { - width: 18px; - height: 18px; - accent-color: var(--primary-blue); + width: 20px; + height: 20px; + border: 1.5px solid #000000; + border-radius: 4px; cursor: pointer; + appearance: none; + background: #FFFFFF; + position: relative; + flex-shrink: 0; +} +.login-form .form-checkbox input[type=checkbox]:checked { + background: #FFFFFF; + 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-size: 14px; - color: var(--text-gray); + 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; cursor: pointer; user-select: none; + line-height: 24px; + white-space: nowrap; } .login-button { width: 100%; - padding: 16px 24px; - font-size: 16px; - font-weight: 600; - color: var(--white); - background: var(--gradient-primary); + height: 80px; + padding: 10px; + font-family: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-size: 20px; + font-weight: 700; + color: #FFFFFF; + background: #0049B4; border: none; border-radius: 12px; cursor: pointer; transition: all 0.3s ease; - box-shadow: var(--shadow-md); + margin-bottom: 40px; + line-height: 1; } .login-button:hover { - transform: translateY(-2px); - box-shadow: var(--shadow-lg); + background: rgb(0, 69.35, 171); } .login-button:active { - transform: translateY(0); + background: rgb(0, 65.7, 162); } .login-button:disabled { opacity: 0.6; cursor: not-allowed; - transform: none; } .login-links { display: flex; justify-content: center; align-items: center; - gap: 16px; - margin-top: 32px; - padding-top: 32px; - border-top: 1px solid var(--border-gray); + gap: 26px; flex-wrap: wrap; } -@media (max-width: 480px) { - .login-links { - gap: 12px; - margin-top: 24px; - padding-top: 24px; - } -} .login-links a { - font-size: 14px; - color: var(--text-gray); + 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; text-decoration: none; transition: color 0.3s ease; - display: flex; - align-items: center; - gap: 6px; + line-height: 1; } .login-links a:hover { - color: var(--primary-blue); -} -.login-links a i { - font-size: 16px; + color: #0049B4; + text-decoration: underline; } .login-links .link-separator { - color: var(--border-gray); - font-size: 14px; + color: #000000; + font-size: 16px; } .login-alert { - margin-bottom: 20px; + width: 100%; + margin-bottom: 24px; padding: 12px 16px; border-radius: 8px; font-size: 14px; @@ -4969,34 +9686,33 @@ select.form-control { } .login-alert.alert-error { background: rgba(255, 107, 107, 0.1); - color: var(--accent-orange); + color: #FF6B6B; border: 1px solid rgba(255, 107, 107, 0.2); } .login-alert.alert-success { background: rgba(107, 207, 127, 0.1); - color: var(--accent-green); + color: #6BCF7F; border: 1px solid rgba(107, 207, 127, 0.2); } .login-alert.alert-info { - background: rgba(75, 155, 255, 0.1); - color: var(--primary-blue); - border: 1px solid rgba(75, 155, 255, 0.2); + background: rgba(0, 73, 180, 0.1); + color: #0049B4; + border: 1px solid rgba(0, 73, 180, 0.2); } .login-alert i { font-size: 18px; } .login-loading { - position: absolute; + position: fixed; top: 0; left: 0; right: 0; bottom: 0; - background: rgba(255, 255, 255, 0.9); + background: rgba(237, 249, 254, 0.9); display: flex; align-items: center; justify-content: center; - border-radius: 24px; z-index: 10; opacity: 0; pointer-events: none; @@ -5009,8 +9725,8 @@ select.form-control { .login-loading .spinner { width: 40px; height: 40px; - border: 3px solid var(--border-gray); - border-top-color: var(--primary-blue); + border: 3px solid #DDDDDD; + border-top-color: #0049B4; border-radius: 50%; animation: spin 1s linear infinite; } @@ -5020,17 +9736,9280 @@ select.form-control { transform: rotate(360deg); } } -.container { - max-width: 1280px; +@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: transparent; + 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: transparent; + 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: transparent; + 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: "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 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; +} +.account-recovery-tabs .tab-link:hover { + color: #3ba4ed; + background: #e4e8ed; +} +.account-recovery-tabs .tab-link.active { + color: #FFFFFF; + 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: "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 0.3s ease; +} +.account-alert i { + font-size: 18px; + flex-shrink: 0; +} +.account-alert.alert-error { + background: rgba(255, 107, 107, 0.1); + color: #FF6B6B; + border: 1px solid rgba(255, 107, 107, 0.3); +} +.account-alert.alert-success { + background: rgba(107, 207, 127, 0.1); + color: #6BCF7F; + border: 1px solid rgba(107, 207, 127, 0.3); +} +.account-alert.alert-info { + background: rgba(0, 73, 180, 0.1); + color: #0049B4; + border: 1px solid rgba(0, 73, 180, 0.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: "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: "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; + outline: none; + transition: all 0.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, 0.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: "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; + outline: none; + cursor: pointer; + transition: all 0.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, 0.1); +} +.account-recovery-form .form-select:disabled { + background-color: #F8F9FA; + border-color: #E2E8F0; + color: #94A3B8; + cursor: not-allowed; + opacity: 0.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: "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: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-size: 14px; + font-weight: 700; + color: #FFFFFF; + background: #a4d6ea; + border: none; + border-radius: 8px; + cursor: pointer; + transition: all 0.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: 0.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: transparent; +} +.account-recovery-card .form-actions .cancel-button, +.account-recovery-card .form-actions .submit-button { + width: 160px; + height: 40px; + padding: 8px; + font-family: "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 0.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: rgb(215.8869565217, 218.8956521739, 224.9130434783); +} +.account-recovery-card .form-actions .cancel-button:active { + background: rgb(202.7739130435, 206.7913043478, 214.8260869565); +} +.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); +} +.account-recovery-card .form-actions .submit-button:active { + background: rgb(0, 65.7, 162); +} +.account-recovery-card .form-actions .submit-button:disabled { + opacity: 0.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, 0.9); + display: none; + align-items: center; + justify-content: center; + z-index: 1000; + transition: opacity 0.3s ease; +} +.loading-overlay.active { + display: flex; +} +.loading-overlay .spinner { + width: 40px; + height: 40px; + border: 3px solid #DDDDDD; + 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: "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: "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: #FFFFFF; + border: 1px solid #dadada; + border-radius: 12px; + padding: 20px 24px; + margin-bottom: 12px; + transition: all 0.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, 0.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, 0.05); + border: 1px solid rgba(0, 73, 180, 0.2); + border-radius: 12px; + padding: 16px 24px; +} +.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 0.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: 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; + } + .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: #ffffff; + } + .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: transparent; + 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: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-size: 20px; + font-weight: 400; + color: #000000; + 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: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-size: 20px; + font-weight: 700; + color: #FFFFFF; + text-decoration: none; + border-radius: 12px; + cursor: pointer; + transition: all 0.3s ease; + line-height: 1; + position: relative; +} +.signup-btn:hover { + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); +} +.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 { + display: flex; + justify-content: center; + align-items: center; + gap: 26px; + flex-wrap: wrap; +} +.signup-navigation .signup-nav-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: #000000; + text-decoration: none; + transition: color 0.3s ease; + line-height: 1; +} +.signup-navigation .signup-nav-link:hover { + color: #0049B4; + text-decoration: underline; +} +.signup-navigation .signup-nav-separator { + color: #000000; + 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: 0.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: transparent; + 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: #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; +} +@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: #FFFFFF; + } +} + +.register-form-container .form-input { + width: 100%; + padding: 8px 12px; + border: 1px solid #dadada; + border-radius: 8px; + background-color: #FFFFFF; + font-size: 14px; + color: #1A1A2E; + outline: none; + transition: border-color 0.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: #FFFFFF; + font-size: 14px; + color: #1A1A2E; + outline: none; + resize: vertical; + transition: border-color 0.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: #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; +} +.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: #FFFFFF; + border: none; + border-radius: 8px; + font-size: 14px; + font-weight: 600; + cursor: pointer; + transition: background-color 0.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: transparent; +} +.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: transparent; +} +@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: #FFFFFF; + 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 0.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) { - .container { + .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: #FFFFFF; + border: 2px solid #E2E8F0; + display: flex; + align-items: center; + justify-content: center; + font-weight: 600; + color: #64748B; + transition: all 0.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: #FFFFFF; + box-shadow: 0 0 0 4px rgba(0, 73, 180, 0.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: #FFFFFF; +} +.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 0.3s ease; + box-shadow: 0 8px 24px rgba(75, 155, 255, 0.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 0.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, 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: #FFFFFF; + font-size: 24px; + cursor: pointer; + z-index: 501; + transition: all 0.3s ease; +} +.apikey-mobile-toggle:hover { + transform: scale(1.05); + box-shadow: 0 16px 40px rgba(75, 155, 255, 0.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, 0.5); + z-index: 499; + opacity: 0; + transition: opacity 0.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: #FFFFFF; + font-size: 14px; + font-family: inherit; + outline: none; + transition: all 0.3s ease; +} +.api-search-box .search-input:focus { + border-color: #0049b4; + box-shadow: 0 0 0 3px rgba(0, 73, 180, 0.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: 0.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: #FFFFFF; + border: 1px solid #dadada; + border-radius: 12px; + transition: all 0.3s ease; + cursor: pointer; + position: relative; +} +.api-selection-card:hover { + border-color: #0049b4; + box-shadow: 0 4px 12px rgba(75, 155, 255, 0.1); +} +.api-selection-card.selected { + border-color: #0049b4; + background-color: rgba(0, 73, 180, 0.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: #FFFFFF; + 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: #FFFFFF; + border-radius: 12px; + padding: 40px; + box-shadow: 0 2px 4px rgba(0, 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 0.3s ease; + background: #FFFFFF; +} +.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-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 0.3s ease; +} +.register-form .icon-upload-wrapper .icon-preview:hover { + border-color: #0049b4; + background: rgba(0, 73, 180, 0.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: 0.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: rgba(255, 255, 255, 0.95); + border: 1px solid #E2E8F0; + border-radius: 50%; + color: #64748B; + cursor: pointer; + transition: all 0.3s ease; + display: none; + align-items: center; + justify-content: center; + box-shadow: 0 2px 4px rgba(0, 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: #FFFFFF; + transform: scale(1.1); + box-shadow: 0 4px 12px rgba(75, 155, 255, 0.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: #FFFFFF; + border: 1px solid #E2E8F0; + border-radius: 8px; + font-size: 14px; + font-weight: 500; + color: #1A1A2E; + cursor: pointer; + transition: all 0.3s ease; +} +.register-form .icon-upload-wrapper .btn-icon-select:hover { + background: #0049b4; + border-color: #0049b4; + color: #FFFFFF; +} +.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: #FFFFFF; + font-size: 14px; + font-weight: 500; + cursor: pointer; + transition: all 0.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, 0.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 0.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: #FFFFFF; + border: 1px solid #E2E8F0; + border-radius: 6px; + color: #64748B; + cursor: pointer; + transition: all 0.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: transparent; + } + .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: #FFFFFF !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: transparent; + 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, 0.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: #FFFFFF; + border-radius: 12px; + padding: 32px; + box-shadow: 0 4px 12px rgba(75, 155, 255, 0.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: #FFFFFF; + border: 1px solid #E2E8F0; + border-radius: 6px; + cursor: pointer; + transition: all 0.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: #FFFFFF; +} +.key-info-card .info-row .status-badge.active { + padding: 4px 12px; + background: rgba(107, 207, 127, 0.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: #FFFFFF; + 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 0.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: #FFFFFF; + box-shadow: 0 4px 12px rgba(75, 155, 255, 0.1); +} +.result-actions .btn-primary:hover { + transform: translateY(-2px); + box-shadow: 0 8px 24px rgba(75, 155, 255, 0.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: #FFFFFF; + 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: #FFFFFF; + display: flex; + align-items: center; + justify-content: space-between; + cursor: pointer; + box-shadow: 0 16px 40px rgba(75, 155, 255, 0.2); + transition: all 0.3s ease; + z-index: 300; +} +.floating-cart-btn .cart-label { + font-size: 14px; + font-weight: 700; + color: #FFFFFF; + white-space: nowrap; +} +.floating-cart-btn .cart-badge { + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + background: #FFFFFF; + 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, 0.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 0.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: #000000; + 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: transparent; + border: none; + border-radius: 50%; + color: #212529; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.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, 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: #FFFFFF; +} +.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: #FFFFFF; + cursor: pointer; + white-space: nowrap; + transition: background 0.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: #FFFFFF; + cursor: pointer; + transition: background 0.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: #FFFFFF; + 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: #FFFFFF; + } + .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: #FFFFFF; + } + .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: transparent; + } + .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: #FFFFFF !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: transparent; + border-radius: 0; + padding: 0 0 6px 0; + margin-bottom: 0; + border-bottom: 2px solid #1A1A2E; + } +} + +.app-list-title { + font-family: "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: transparent; + 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 0.2s ease; +} +.app-list-item:last-child { + border-bottom: none; +} +.app-list-item:hover { + background-color: rgba(255, 255, 255, 0.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: transparent; + } + .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: #FFFFFF; +} +.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: transparent; + 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: #FFFFFF; + border-radius: 12px; + box-shadow: 0 4px 12px rgba(75, 155, 255, 0.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 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; +} +.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: #FFFFFF; + 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: "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 { + background: #FFFFFF; + border-radius: 12px; + box-shadow: 0 4px 12px rgba(75, 155, 255, 0.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 0.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 0.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: 0.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: transparent; + 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: #FFFFFF; + border-radius: 12px; + box-shadow: 0 2px 4px rgba(0, 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 0.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 0.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 0.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: transparent; + 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: #ffffff; + 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 0.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: rgb(215.8869565217, 218.8956521739, 224.9130434783); +} +.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: #FFFFFF; + cursor: pointer; + transition: all 0.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: #FFFFFF; + cursor: pointer; + transition: all 0.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: #FFFFFF; + 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: #FFFFFF; + cursor: pointer; + transition: all 0.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: #FFFFFF; + cursor: pointer; + transition: all 0.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: transparent; + padding: 16px 0; + margin-bottom: 0; + } + .inquiry-form-container .register-form-container { + box-shadow: none !important; + border: none !important; + background: transparent !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: rgb(215.8869565217, 218.8956521739, 224.9130434783); + } + .inquiry-form-container .form-actions .btn-primary { + background: #0049b4; + color: #FFFFFF; + 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: transparent; + 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: #FFFFFF; + border-radius: 12px; + box-shadow: 0 4px 12px rgba(75, 155, 255, 0.1); + overflow: hidden; + margin-bottom: 32px; +} + +.inquiry-table { + width: 100%; + background: #FFFFFF; + 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 0.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, 0.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 0.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: 0.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: #FFFFFF; + border-radius: 12px; + box-shadow: 0 4px 12px rgba(75, 155, 255, 0.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 0.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 0.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: #FFFFFF; + border-radius: 12px; + box-shadow: 0 4px 12px rgba(75, 155, 255, 0.1); + overflow: hidden; + margin-bottom: 32px; + border-left: 4px solid #6BCF7F; +} +.inquiry-response-card .response-header { + padding: 32px 40px; + background: rgba(107, 207, 127, 0.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 0.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: #FFFFFF; + 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: #FFFFFF; +} +.inquiry-detail-actions .action-btn.btn-edit:hover { + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(75, 155, 255, 0.1); +} +.inquiry-detail-actions .action-btn.btn-delete { + background: #FF6B6B; + color: #FFFFFF; +} +.inquiry-detail-actions .action-btn.btn-delete:hover { + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(75, 155, 255, 0.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: #FFFFFF; + border-radius: 12px; + box-shadow: 0 4px 12px rgba(75, 155, 255, 0.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 0.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, 0.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 0.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, 0.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: rgba(255, 255, 255, 0.2); + color: #FFFFFF; + 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: #FFFFFF; + 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 0.3s ease; + background: #FFFFFF; +} +.org-form-input:focus { + outline: none; + border-color: #0049b4; + box-shadow: 0 0 0 3px rgba(75, 155, 255, 0.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: #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; + padding-right: 48px; +} +.org-form-select:focus { + outline: none; + border-color: #0049b4; + box-shadow: 0 0 0 3px rgba(75, 155, 255, 0.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 0.3s ease; + border: none; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 8px; + white-space: nowrap; +} +.org-btn:disabled { + opacity: 0.5; + cursor: not-allowed; +} +.org-btn:not(:disabled):hover { + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(75, 155, 255, 0.1); +} +.org-btn:not(:disabled):active { + transform: translateY(0); +} + +.org-btn-primary { + color: #FFFFFF; +} + +.org-btn-secondary { + background: #FFFFFF; + 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: #FFFFFF; + 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: #FFFFFF; +} +@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, 0.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: #FFFFFF; + border: none; + border-radius: 8px; + font-size: 14px; + cursor: pointer; + transition: all 0.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: #ffffff; + border: none; + } + .org-action-buttons .btn-primary:hover { + background: #003d99; + color: #ffffff; + } +} + +.org-validation-message { + margin-top: 8px; + padding: 8px 16px; + border-radius: 6px; + font-size: 14px; +} +.org-validation-message.success { + background: rgba(107, 207, 127, 0.1); + color: #6BCF7F; +} +.org-validation-message.error { + background: rgba(255, 107, 107, 0.1); + color: #FF6B6B; +} + +.org-loading-overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(26, 26, 46, 0.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 rgba(255, 255, 255, 0.2); + border-top-color: #FFFFFF; + 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: #FFFFFF; + border: 1px solid #DDDDDD; + 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: #DDDDDD; + 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, 0.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 0.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: transparent; + color: #64748B; + cursor: pointer; + transition: all 0.3s ease; + padding: 8px 4px 10px 7px; +} +.agreement-toggle-icon i { + font-size: 12px; + transition: transform 0.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: #FFFFFF; + border-radius: 16px; + box-shadow: 0 8px 24px rgba(75, 155, 255, 0.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, 0.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, 0.1); + outline: none; +} +.partnership-card textarea.form-control:focus { + box-shadow: 0 0 0 4px rgba(0, 73, 180, 0.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 0.3s ease; +} +.user-management-container .list-table .row-cell a:hover { + color: #0049b4; +} +@media (max-width: 768px) { + .user-management-container .table-controls { + background-color: #FFFFFF; + 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, 0.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: 0.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: #FFFFFF; + margin-bottom: 32px; + position: relative; + overflow: hidden; +} +.user-profile-card::before { + content: ""; + position: absolute; + top: -50%; + right: -20%; + width: 400px; + height: 400px; + background: rgba(255, 255, 255, 0.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: rgba(255, 255, 255, 0.2); + color: #FFFFFF; + border: 4px solid rgba(255, 255, 255, 0.3); + box-shadow: 0 8px 24px rgba(75, 155, 255, 0.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: 0.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: rgba(255, 255, 255, 0.2); + color: #FFFFFF; + backdrop-filter: blur(10px); + border: 1px solid rgba(255, 255, 255, 0.3); +} + +.detail-section { + background: #FFFFFF; + border-radius: 12px; + padding: 32px; + box-shadow: 0 2px 4px rgba(0, 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, 0.1); + color: rgb(83.2897959184, 199.3102040816, 106.493877551); +} +.status-indicator.status-active .status-dot { + background-color: #6BCF7F; +} +.status-indicator.status-inactive { + background-color: rgba(100, 116, 139, 0.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: 0.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: #FFFFFF; + 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: #000000; + 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: #FFFFFF; + 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: #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) { + 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: #FFFFFF; + } + .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; + 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: transparent !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: transparent; + border: none; + border-radius: 6px; + color: #5f666c; + cursor: pointer; + transition: all 0.3s ease; +} +.dropdown-wrapper .dropdown-toggle:hover { + background: rgba(0, 73, 180, 0.1); + color: #0049b4; +} +.dropdown-wrapper .dropdown-toggle:focus { + outline: none; + background: rgba(0, 73, 180, 0.1); +} +.dropdown-wrapper .dropdown-toggle svg { + width: 20px; + height: 20px; +} +.dropdown-wrapper .dropdown-menu { + position: absolute; + top: 100%; + right: 0; + min-width: 140px; + background: #FFFFFF; + border-radius: 8px; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15); + z-index: 100; + opacity: 0; + visibility: hidden; + transform: translateY(-8px); + transition: all 0.2s ease; + overflow: hidden; +} +.dropdown-wrapper .dropdown-menu .dropdown-item { + display: block; + width: 100%; + padding: 10px 16px; + background: transparent; + border: none; + text-align: left; + font-size: 14px; + font-weight: 400; + color: #212529; + cursor: pointer; + transition: all 0.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, 0.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: #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: "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; + 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: #FFFFFF; + border-radius: 12px; + box-shadow: 0 4px 12px rgba(75, 155, 255, 0.1); + overflow: hidden; +} + +.terms-tabs { + display: flex; + gap: 0; + background: transparent; + 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 0.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: #FFFFFF; + 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: #FFFFFF; + 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 0.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: #FFFFFF; + border: 1px solid #0049b4; + border-radius: 6px; + color: #0049b4; + font-size: 14px; + font-weight: 500; + text-decoration: none; + transition: all 0.3s ease; +} +.terms-content .terms-attachment .attachment-link:hover { + background: #0049b4; + color: #FFFFFF; +} +.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 0.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: 0.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: transparent; + 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: 64px 20px 80px; + background-color: #f9fafb; +} +.service-intro__header { + margin-bottom: 48px; +} +.service-intro__title { + font-family: "Noto Sans KR", sans-serif; + font-size: 48px; + font-weight: 700; + line-height: 1; + color: #0049b4; + margin: 0 0 22px 0; +} +.service-intro__underline { + width: 96px; + height: 4px; + background-color: #0049b4; +} +.service-intro__content { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 48px; + margin-bottom: 48px; +} +.service-intro__text-column { + display: flex; + flex-direction: column; + gap: 32px; +} +.service-intro__card { + background: #fff; + border: 0.8px solid #f3f4f6; + border-radius: 16px; + padding: 30px 33px; + box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px -1px rgba(0, 0, 0, 0.1); +} +.service-intro__card--highlight { + background: linear-gradient(180deg, #0049b4 0%, #0066dd 100%); + border: none; + box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1); + padding: 29px 32px 32px; +} +.service-intro__desc { + font-family: "Noto Sans KR", sans-serif; + font-size: 20px; + font-weight: 400; + line-height: 1.625; + color: #1e2939; + margin: 0; +} +.service-intro__desc--white { + color: #fff; +} +.service-intro__diagram-column { + display: flex; + align-items: stretch; +} +.service-intro__diagram-card { + background: #fff; + border: 0.8px solid #f3f4f6; + border-radius: 16px; + box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1); + padding: 33px; + width: 100%; + display: flex; + align-items: center; + justify-content: center; +} +.service-intro__diagram-img { + width: 100%; + height: auto; + object-fit: contain; +} +.service-intro__features { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 32px; +} +.service-intro__feature-card { + background: #fff; + border: 0.8px solid #f3f4f6; + border-radius: 16px; + padding: 33px; + box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px -1px rgba(0, 0, 0, 0.1); +} +.service-intro__feature-icon { + width: 64px; + height: 64px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + margin-bottom: 12px; +} +.service-intro__feature-icon i { + font-size: 24px; + color: #fff; +} +.service-intro__feature-icon--fast { + background-color: #0049b4; +} +.service-intro__feature-icon--custom { + background-color: #00acdd; +} +.service-intro__feature-icon--stable { + background-color: #008ae2; +} +.service-intro__feature-title { + font-family: "Noto Sans KR", sans-serif; + font-size: 24px; + font-weight: 700; + line-height: 1.333; + color: #101828; + margin: 0 0 14px 0; +} +.service-intro__feature-desc { + font-family: "Noto Sans KR", sans-serif; + font-size: 16px; + font-weight: 400; + line-height: 1.625; + color: #4a5565; + margin: 0; +} + +@media (max-width: 1024px) { + .service-intro { + padding: 48px 24px 64px; + } + .service-intro__title { + font-size: 36px; + } + .service-intro__content { + grid-template-columns: 1fr; + gap: 32px; + } + .service-intro__text-column { + order: 2; + } + .service-intro__diagram-column { + order: 1; + } + .service-intro__features { + grid-template-columns: 1fr; + gap: 24px; + } +} +@media (max-width: 768px) { + .service-intro { + padding: 32px 16px 48px; + } + .service-intro__header { + margin-bottom: 32px; + } + .service-intro__title { + font-size: 28px; + } + .service-intro__underline { + width: 64px; + height: 3px; + } + .service-intro__card { + padding: 24px; + } + .service-intro__desc { + font-size: 16px; + line-height: 1.75; + } + .service-intro__diagram-card { + padding: 20px; + } + .service-intro__feature-card { + padding: 24px; + } + .service-intro__feature-icon { + width: 56px; + height: 56px; + } + .service-intro__feature-icon i { + font-size: 20px; + } + .service-intro__feature-title { + font-size: 20px; + } + .service-intro__feature-desc { + font-size: 14px; + } +} +.service_intro_wrap { + max-width: 1280px; + margin: 0 auto; + padding: 48px 40px 80px; +} + +.service_intro_header { + margin-bottom: 32px; +} +.service_intro_header .main_title { + font-family: "Pretendard", sans-serif; + font-size: 28px; + font-weight: 700; + color: #0066CC; + margin-bottom: 16px; +} +.service_intro_header .title_underline { + width: 50px; + height: 3px; + background: #0066CC; +} + +.service_intro_content { + display: flex; + flex-direction: row; + align-items: flex-start; + gap: 48px; + margin-bottom: 64px; +} + +.intro_text_area { + flex: 1; +} +.intro_text_area .intro_desc { + font-family: "Pretendard", sans-serif; + font-size: 20px; + line-height: 2; + color: #333; + margin-bottom: 24px; +} +.intro_text_area .intro_highlight_box { + background: #0066CC; + border-radius: 16px; + padding: 36px 32px; + margin-top: 28px; +} +.intro_text_area .intro_highlight_box p { + font-family: "Pretendard", sans-serif; + font-size: 17px; + line-height: 1.85; + color: #fff; + margin: 0; +} + +.intro_diagram_area { + flex: 0 0 480px; +} +.intro_diagram_area img { + width: 100%; + height: auto; +} + +.service_feature_cards { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 32px; + padding-top: 48px; + border-top: 1px solid #e9ecef; +} + +.feature_card { + padding: 32px 28px; + background: #fff; + border: 1px solid #e9ecef; + border-radius: 8px; + text-align: left; +} +.feature_card .feature_icon { + width: 64px; + height: 64px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + margin-bottom: 24px; +} +.feature_card .feature_icon i { + font-size: 26px; + color: #fff; +} +.feature_card .feature_icon.feature_icon_fast { + background: linear-gradient(135deg, #4A90D9 0%, #357ABD 100%); +} +.feature_card .feature_icon.feature_icon_custom { + background: linear-gradient(135deg, #F5A623 0%, #E8940C 100%); +} +.feature_card .feature_icon.feature_icon_stable { + background: linear-gradient(135deg, #4A90D9 0%, #357ABD 100%); +} +.feature_card .feature_title { + font-family: "Pretendard", sans-serif; + font-size: 18px; + font-weight: 700; + color: #333; + margin-bottom: 12px; +} +.feature_card .feature_desc { + font-family: "Pretendard", sans-serif; + font-size: 13px; + line-height: 1.7; + color: #666; + margin: 0; +} + +.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-family: "Noto Sans KR", sans-serif; + 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-family: "Noto Sans KR", sans-serif; + 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, 0.1), 0 4px 6px -4px rgba(0, 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-family: "Arial", sans-serif; + font-size: 14px; + font-weight: 400; + color: #6a7282; + line-height: 20px; +} +.signup-guide__step-text { + font-family: "Noto Sans KR", sans-serif; + 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-family: "Noto Sans KR", sans-serif; + 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-family: "Noto Sans KR", sans-serif; + font-size: 18px; + font-weight: 600; + border-radius: 8px; + text-decoration: none; + transition: background 0.2s ease, transform 0.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-family: "Pretendard", sans-serif; + 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-family: "Pretendard", sans-serif; + 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, 0.1); +} + +.service_guide_info { + padding: 24px 16px; + text-align: center; +} +.service_guide_info .info_text { + font-family: "Pretendard", sans-serif; + 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-family: "Pretendard", sans-serif; + 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-family: "Pretendard", sans-serif; + 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-family: "Pretendard", sans-serif; + 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-family: "Pretendard", sans-serif; + font-size: 16px; + font-weight: 600; + border-radius: 8px; + text-decoration: none; + transition: background 0.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; + } +} +.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: transparent; + 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 0.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 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); +} + +.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 0.3s ease, stroke-dashoffset 0.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, 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: rgba(255, 255, 255, 0.2); + border-radius: 20px; + color: #fff; + text-decoration: none; + font-size: 14px; + transition: background 0.2s; +} +.btn-download:hover { + background: rgba(255, 255, 255, 0.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 0.3s ease; +} + +.progress-failure { + background: #F5A3B5; + transition: width 0.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; } @@ -5758,11 +19737,11 @@ select.form-control { } .bg-primary { - background: #4B9BFF; + background: #0049b4; } .bg-secondary { - background: #2E7FF7; + background: #c3dfea; } .bg-white { @@ -5777,18 +19756,6 @@ select.form-control { background: #EFF6FF; } -.bg-gradient-primary { - background: linear-gradient(135deg, #4B9BFF 0%, #2E7FF7 100%); -} - -.bg-gradient-accent { - background: linear-gradient(135deg, #00D4FF 0%, #4B9BFF 100%); -} - -.bg-gradient-warm { - background: linear-gradient(135deg, #FFD93D 0%, #FF6B6B 100%); -} - .border { border: 1px solid #E2E8F0; } @@ -5814,7 +19781,7 @@ select.form-control { } .border-primary { - border-color: #4B9BFF; + border-color: #0049b4; } .rounded-0 { @@ -5935,6 +19902,24 @@ select.form-control { } } +.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; } @@ -5983,4 +19968,16 @@ select.form-control { 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.css.map */ diff --git a/src/main/resources/static/css/main.css.map b/src/main/resources/static/css/main.css.map index 5af179c..a39a471 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/layout/_header.scss","../sass/layout/_footer.scss","../sass/layout/_grid.scss","../sass/layout/_container.scss","../sass/components/_buttons.scss","../sass/components/_cards.scss","../sass/components/_forms.scss","../sass/components/_modals.scss","../sass/components/_navigation.scss","../sass/components/_partners.scss","../sass/components/_cta.scss","../sass/pages/_index.scss","../sass/pages/_api-market.scss","../sass/pages/_login.scss","../sass/base/_utilities.scss"],"names":[],"mappings":";AAIA;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;;;AAGF;EACE,aCqBoB;EDpBpB,aC4CmB;ED3CnB,OCJU;EDKV,kBCFM;EDGN;;;AAIF;EACE;;;AAIF;EACE;EACA;EACA,YC4EgB;;;ADxElB;EACE;EACA;EACA;;;AAIF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;;AAIF;AAAA;AAAA;EAGE;EACA;EACA;EACA;EACA;;;AAIF;EACE;EACA;;;AAIF;EACE,aChBiB;EDiBjB,aCbkB;;;ADiBpB;AAAA;AAAA;EAGE;;;AAIF;EACE;EACA,OCxEU;;;AD2EZ;EACE;EACA,OC7EU;;;ACXZ;EACE,WDuCe;ECtCf,aDgDoB;EC/CpB,ODQU;;;ACJZ;EACE,WDuCc;ECtCd,aD6CsB;EC5CtB,eDuDW;;AE7DT;EDGJ;IAMI,WDgCY;;;AE3CZ;EDKJ;IAUI,WD2BY;;;;ACvBhB;EACE,WDwBc;ECvBd,aD8BiB;EC7BjB,eDwCW;;AE5DT;EDiBJ;IAMI,WDiBY;;;AE1CZ;EDmBJ;IAUI,WDYW;;;;ACRf;EACE,WDSc;ECRd,aDgBiB;ECfjB,eD0BW;;AE5DT;ED+BJ;IAMI,WDEW;;;;ACEf;EACE,WDFc;ECGd,aDKqB;ECJrB,eDeW;;;ACZb;EACE,WDTa;ECUb,aDDqB;ECErB,eDSW;;;ACNb;EACE,WDhBa;ECiBb,aDPqB;ECQrB,eDGW;;;ACCb;EACE,eDDW;ECEX,aDRmB;;ACUnB;EACE;;;AAKJ;ECTE,YF1CiB;EE2CjB;EACA;EACA;;;ADUF;ECbE,YFzCgB;EE0ChB;EACA;EACA;;;ADcF;ECjBE,YFxCc;EEyCd;EACA;EACA;;;ADkBF;EACE,ODrFa;;;ACwFf;EACE,ODxFe;;;AC2FjB;EACE,ODpFU;;;ACuFZ;EACE,ODvFU;;;AC0FZ;EACE,OD1FW;;;AC6Fb;EACE,OD7FM;;;ACiGR;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAIF;EACE,aD3EoB;;;AC8EtB;EACE,aD9EmB;;;ACiFrB;EACE,aDjFqB;;;ACoFvB;EACE,aDpFiB;;;ACuFnB;EACE,aDvFsB;;;AC2FxB;EACE,WD5Ga;;;AC+Gf;EACE,WD/Ga;;;ACkHf;EACE,WDlHe;;;ACqHjB;EACE,WDpHa;;;ACuHf;EACE,WDvHa;;;AC2Hf;EACE,WD7Ha;EC8Hb,aDtHoB;ECuHpB,aD9GkB;EC+GlB,OD9JU;;;ACiKZ;EACE,WDvIa;ECwIb,ODnKU;;;ACsKZ;EACE,aDhJiB;ECiJjB,WD7Ia;EC8Ib,YDtKQ;ECuKR;EACA,eD7GiB;;;ACiHnB;EACE,OD1La;;AC4Lb;EACE,OD5La;;;ACiMjB;EACE,SDrIW;ECsIX;EACA;EACA,YDzLQ;EC0LR;;AAEA;EACE;;;AAKJ;EACE;EACA;EACA;;;AEpNF;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,YHjQQ;;;AGqQZ;EACE;;AAEA;EACE;;;AAIJ;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EAMA;EACA;EACA;;AAGF;EACE;;;AClUJ;EAEE;EACA;EACA;EACA;EACA;EACA;EACA;EAGA;EACA;EACA;EACA;EACA;EACA;EACA;EAGA;EACA;EACA;EAGA;EACA;EACA;EACA;;;AAIF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAMF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;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;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGA;EACE;EACA;EACA;EACA;;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;;;;AAQF;EACE;EACA;;;AAIJ;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;;;AAKF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;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;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;EACA;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAKN;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAEA;EACE;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAOV;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAIJ;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;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;;AAMR;EACE;EACA,OJxZW;EIyZX;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA,YJ5dQ;EI6dR;EACA;EACA;;AAEA;EAEE;EACA;EACA;EACA;EACA;EACA,YJxeM;EIyeN;;AAGF;EAAY;;AACZ;EAAW;;AAGb;EACE;;AACA;EAAY;EAAQ;;AACpB;EAAW;EAAQ;;;AAOvB;EAEE;EACA;EACA;EACA;EACA;EACA,YJ9fM;EI+fN,SJ1ac;EI2ad;EACA;EACA;EACA;EACA;;AAKA;EACE,SJvdS;EIwdT,YJ1gBM;EI2gBN;;AAGF;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAEA;EACE;EACA;EACA,SJxeK;EIyeL,OJ/hBI;EIgiBJ,WJlgBO;EImgBP,aJzfa;EI0fb;EACA;;AAGF;EACE;EACA;EACA,OJnfK;EIofL;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA,YJxjBE;;AI0jBF;EACE;EACA;EACA,OJhkBE;EIikBF,WJtiBK;EIuiBL;;AAEA;EACE,YJnkBJ;EIokBI,OJhlBG;;AIqlBT;EACE;;AAKN;EACE;EACA,YJjlBI;EIklBJ;EACA;;;AAQJ;EACE;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;;AAIJ;EACE;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;;AAqGJ;EACE;;;AAMF;EACE;IACE;;EAEA;IACE;IACA;;EAIJ;IACE;;;AAIJ;EAEI;IACE;;EAKF;IACE;;;AC5xBN;EACE,YLWU;EKVV,OLaM;EKZN;;AHEE;EGLJ;IAMI;;;;AAIJ;EACE;EACA;EACA,KL0DY;EKzDZ,eLwDY;EKvDZ,WLqEoB;EKpEpB;EACA;EACA;;AHXE;EGGJ;IAWI;IACA,KL8CU;;;;AKzCd;EACE;;AAEA;EACE;EACA,eLkCS;;AK/BX;EACE,WLIW;EKHX,aLuBgB;EKtBhB;EACA,eL2BS;;AKxBX;EACE,eLwBS;;AKtBT;EACE,WLJS;EKKT,aLMiB;EKLjB,eLiBO;;AKdT;EACE;EACA,KLWO;;AKTP;EACE;EACA;EACA;EACA;EACA,eLgBW;EKfX,OL9CA;EK+CA,WLtBO;;AKwBP;EACE;;AAGF;EACE;EACA,cLnEK;EKoEL;;AAIJ;EACE;EACA,YLrDW;EKsDX,OL/DA;EKgEA,eLHW;EKIX,aL3Be;EK4Bf,WLzCO;EK0CP,YLwBU;;AKtBV;EACE;EACA,YLxDE;;;AKgEZ;EACE;EACA,KL/BW;;AKiCX;EACE;EACA;EACA;EACA,eLzBe;EExDjB;EACA;EACA;EGiFE,OLzFI;EK0FJ,YLCc;EKAd,WL/DW;;AKiEX;EACE,YL1GS;EK2GT;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;;AAMN;EACE;EACA;EACA,KLvEY;;AE7DV;EGiIJ;IAMI;;;AHzIA;EGmIJ;IAUI;IACA;;;;AAKF;EACE,WLhHa;EKiHb,aLpGe;EKqGf,eLzFS;EK0FT,OL7II;;AKgJN;EACE;;AAEA;EACE,eLlGO;;AKoGP;EACE;EACA,WL/HO;EKgIP,YL9DU;EK+DV;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA,YL7KI;EK8KJ,YLzEQ;;AK4EV;EACE,OLlLI;;AKoLJ;EACE;;AAQV;EACE;EACA;EACA,KLtIS;EKuIT;EACA;EACA,OLhMW;EKiMX,eLzHiB;EK0HjB,WLnKW;EKoKX,aLtJmB;EKuJnB,aL5IS;;;AKiJb;EACE,aL9IY;EK+IZ;EACA,WLhIoB;EKiIpB;EACA,cLpJW;EKqJX,eLrJW;;;AKwJb;EACE;EACA;EACA;;AHxNE;EGqNJ;IAMI;IACA,KL/JS;IKgKT;;;AAGF;EACE;EACA,WL/LW;EKgMX;;AAGF;EACE;EACA,KL3KS;;AK6KT;EACE;EACA,WLzMS;EK0MT,YLxIY;;AK0IZ;EACE,OLtOA;;;AK6OR;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AClRJ;EACE;EACA,KN+DW;;AM5DX;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAIF;EACE;;AAGF;EACE;;AAGF;EACE;;AAIF;EACE;;AAGF;EACE,KNWS;;AMRX;EACE,KNQS;;AMLX;EACE,KNKS;;AMFX;EACE,KNES;;AE5DT;EI+DA;IACE;;EAGF;IACE;;;AJtEF;EI2EA;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,KNrEW;;AE3DT;EI6HJ;IAMI;;;AJrIA;EI+HJ;IAUI;;;;AAKJ;EACE;EACA;EACA,KNpFW;;AE7DT;EI8IJ;IAMI;;;;AAKJ;EACE;EACA;EACA,KN7FY;EM8FZ;;AJ7JE;EIyJJ;IAOI;;;;AAKJ;EACE;EACA;EACA,KN1GW;;AE9DT;EIqKJ;IAMI;;;;AAKJ;EACE;EACA;EACA,KNpHY;EMqHZ;;AJlLE;EI8KJ;IAOI;;;;AAKJ;EACE;EACA;EACA,KN7HY;;AEhEV;EI0LJ;IAMI;IACA,KNpIU;;;AMuIZ;EACE;EACA;EACA,KN1IU;;AE7DV;EIoMF;IAMI;;;AJ5MF;EIsMF;IAUI;IACA;;;;AAMN;EACE;EACA;EACA,KN7JW;EM8JX;;AJzNE;EIqNJ;IAOI;IACA,KNjKS;;;;AMsKb;EACE;EACA;EACA,KN1KW;;AE7DT;EIoOJ;IAMI;;;;AAKJ;EACE;EACA;EACA,KNpLW;EMqLX;;AJjPE;EI6OJ;IAOI;;;AJtPA;EI+OJ;IAWI;;;;AAKJ;EACE;EACA;EACA,KNlMY;EMmMZ;;AJjQE;EI6PJ;IAOI;IACA,KNzMS;;;AM4MX;EACE;;AAEA;EACE;;AJ5QF;EIwQF;IAQI;;;;AAMN;EACE;EACA;EACA,KN7NW;;AE5DT;EIsRJ;IAMI;;;;AClSJ;EACE,WPkFoB;EOjFpB;EACA;;ALCE;EKJJ;IAMI;;;AAIF;EACE;EACA;;AAGF;EACE;;AAGF;EACE;;;AAKJ;EACE;;ALtBE;EKqBJ;IAII;;;AAIF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;;AAMF;EACE,YPtCM;;AOyCR;EACE,YPzCO;;AO4CT;EACE;;AAGF;EACE;;AAGF;EACE,YP9Ce;EO+Cf,OPxDI;;AO2DN;EACE,YP/DQ;EOgER,OP7DI;;;AOkER;EACE;EACA,ePbY;;AEjEV;EK4EJ;IAKI,ePjBU;;;AOoBZ;EACE;EACA;EACA,KP7BS;EO8BT;EACA,YP7EO;EO8EP,OP5FW;EO6FX,ePhBiB;EOiBjB,WPzDW;EO0DX,aP7CmB;EO8CnB,ePlCS;;AOqCX;EACE,WPzDY;EO0DZ,aPlDe;EOmDf,ePxCS;EOyCT,OP9FQ;;AEPR;EKiGF;IAOI,WPhEU;;;AOmEZ;EL9CF,YFzCgB;EE0ChB;EACA;EACA;;AKgDA;EACE,WP5EW;EO6EX,OP1GQ;EO2GR,aP7DiB;EO8DjB;EACA;;ALrHA;EKgHF;IAQI,WPpFW;;;;AO0FjB;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA,aPtDY;;;AO2DhB;EACE;EACA;;;AAIF;EACE;EACA;;;ACvJF;ENiCE;EACA;EACA,KF4BW;EE3BX;EACA,eFuCiB;EEtCjB,aFcqB;EEbrB,WFCe;EEAf;EACA,YFgEgB;EE/DhB;EACA;;AAEA;EACE;;AM9CJ;EAEE;EACA;;AAGA;EACE;EACA,WR+BW;;AQ5Bb;EACE;EACA,WR2Ba;;AQxBf;EACE;EACA,WRuBW;;AQpBb;EACE;EACA,WRmBW;;AQfb;EACE,YRLe;EQMf,ORfI;EQgBJ,YRDQ;;AQGR;EACE;EACA,YRHM;;AQMR;EACE;;AAIJ;EACE,YR7BI;EQ8BJ,OR1CW;EQ2CX;;AAEA;EACE,YRhCK;EQiCL;;AAIJ;EACE,YR9Bc;EQ+Bd,ORzCI;EQ0CJ,YR3BQ;;AQ6BR;EACE;EACA,YR7BM;;AQiCV;EACE,YRxCY;EQyCZ,ORpDI;EQqDJ,YRtCQ;;AQwCR;EACE;EACA,YRxCM;;AQ4CV;EACE,YRrEW;EQsEX,OR/DI;;AQiEJ;EACE;EACA;;AAIJ;EACE,YRhFY;EQiFZ,ORzEI;;AQ2EJ;EACE;EACA;;AAIJ;EACE;EACA,OR/FW;EQgGX;;AAEA;EACE;EACA,cRpGS;;AQwGb;EACE;EACA,ORjGQ;EQkGR;;AAEA;EACE,cR9GS;EQ+GT,OR/GS;EQgHT;;AAKJ;EAEE;EACA;EACA;;AAGF;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;ENtDJ;EACA;EACA;EACA;EMqDI;EACA;EACA;EACA;;AAKJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA,eRvEmB;;AQyEnB;EACE;EACA;;AAGF;EACE;EACA;;AAKJ;EACE;EACA;;AAIF;EACE;EACA;;AAEA;EACE;;AAEA;EACE,wBRvGW;EQwGX,2BRxGW;;AQ2Gb;EACE,yBR5GW;EQ6GX,4BR7GW;;;AQoHnB;EACE;EACA,QRhIW;EQiIX,ORjIW;EQkIX;EACA;EACA,YR/KiB;EQgLjB;EACA,eRxHqB;EQyHrB,OR3LM;EQ4LN,WR/Ja;EQgKb;EACA,YR9KU;EQ+KV,YRpGgB;EQqGhB,SR9Gc;EE5Ed;EACA;EACA;;AM2LA;EACE;EACA,YRpLQ;;AQsLR;EACE;EACA;;AAIJ;EACE;EACA;EACA;EACA;EACA,YRrNQ;EQsNR,ORnNI;EQoNJ;EACA,eRxJe;EQyJf,WR7LW;EQ8LX;EACA;EACA,YR9Hc;EQ+Hd;;;AAMF;EN7MA;EACA;EACA,KF4BW;EE3BX;EACA,eFuCiB;EEtCjB,aFcqB;EEbrB,WFCe;EEAf;EACA,YFgEgB;EE/DhB;EACA;;AAEA;EACE;;AMgMF;EAEE,YRlOI;EQmOJ,OR/OW;EQgPX;EACA,WR1MW;EQ2MX,YRtNQ;;AQwNR;EACE;EACA,YRzNM;;AQ6NV;EN3NA;EACA;EACA,KF4BW;EE3BX;EACA,eFuCiB;EEtCjB,aFcqB;EEbrB,WFCe;EEAf;EACA,YFgEgB;EE/DhB;EACA;;AAEA;EACE;;AM8MF;EAEE;EACA,ORjPI;EQkPJ;EACA;EACA,WRzNW;;AQ2NX;EACE;EACA;;;ACtQN;EPoDE,YFtCM;EEuCN,eFyBkB;EExBlB,SF0Da;EEzDb,YFkDgB;;AEhDhB;EACE;EACA,YF7BQ;;AS9BZ;EAEE;EACA;;AAGA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EAMA;EACA,YTuFc;EStFd;;AAGF;EACE;EACA;;;AAKJ;EPqBE,YFtCM;EEuCN,eFyBkB;EExBlB,SF0Da;EEzDb,YFkDgB;;AEhDhB;EACE;EACA,YF7BQ;;ASCZ;EAEE;EACA;;AAEA;EACE,cTnCW;ESoCX;;AAIF;EACE,cTtCY;ESuCZ;;AAIF;EACE,cT1CW;ES2CX;;AAIF;EACE;EACA;EACA;EACA;EACA,YTtDY;ESuDZ,OTjDQ;ESkDR,eTiBgB;EShBhB,WTxBW;ESyBX,aTVe;ESWf;;AAEA;EACE,YT5DS;ES6DT,OTtDE;;ASyDJ;EACE,YThEU;ESiEV,OT3DE;;ASgEN;EACE;EACA;EACA;EP7DF;EACA;EACA;EO6DE;EACA,YTpEO;ESqEP,OTnFW;ESoFX,eTTe;ESUf,YTkBc;;ASfhB;EACE;EACA,YTrEe;ESsEf,OT/EI;;ASmFN;EACE,WTxDW;ESyDX,aT9Ce;ES+Cf,eTpCS;ESqCT,OT1FQ;;AS6FV;EACE,OT7FQ;ES8FR,aThDiB;ESiDjB,eT1CS;ES2CT,WTrEW;;ASyEb;EACE;EACA;EACA,KTpDS;ESqDT,eTnDS;ESoDT;;AAEA;EACE;EACA,YT1GI;ES2GJ,OT9GM;ES+GN,eT7Cc;ES8Cd,WTtFS;ESuFT,aT1Ee;;AS+EnB;EACE;EACA;EACA,KTvES;ESwET,OTpIW;ESqIX,aTnFmB;ESoFnB,WTjGW;ESkGX,YThCc;;ASkCd;EACE,KT7EO;ES8EP,OT1IW;;;ASgJjB;EP/FE,YFtCM;EEuCN,eFyBkB;EExBlB,SF0Da;EEzDb,YFkDgB;;AEhDhB;EACE;EACA,YF7BQ;;ASqHZ;EAEE;;AAEA;EACE;EACA;EACA;EPtIF;EACA;EACA;EOsIE,WTjHW;ESkHX,YTtIe;ESuIf,OThJI;ESiJJ,eTlFe;;ASqFjB;EACE,WTzHW;ES0HX,aT/Ge;ESgHf,eTrGS;ESsGT,OT3JQ;;AS8JV;EACE,OT9JQ;ES+JR,aTjHiB;ESkHjB,eT3GS;ES4GT,WTtIW;;AS0Ib;EACE;EACA;EACA;EACA,aTpHS;ESqHT;;AAEA;EACE,WT9IS;ES+IT,aTpIkB;ESqIlB,OTzLS;;AS4LX;EACE,WTzJS;ES0JT,OTpLM;ESqLN,aT9Ie;;;ASoJrB;EPnJE,YFtCM;EEuCN,eFyBkB;EExBlB,SF0Da;EEzDb,YFkDgB;;AEhDhB;EACE;EACA,YF7BQ;;ASyKZ;EAEE;EACA;EACA,KT1IW;;AS4IX;EACE;EACA;EACA;EP5LF;EACA;EACA;EO4LE,YT3Le;ES4Lf,OTrMI;ESsMJ,eTxIe;ESyIf,WT1KW;;AS6Kb;EACE;;AAEA;EACE,WTnLS;ESoLT,aTzKiB;ES0KjB,eT/JO;ESgKP,OTpNM;;ASuNR;EACE,OTvNM;ESwNN,aT1Ke;ES2Kf,WT9LS;;;ASoMf;EPvLE,YFtCM;EEuCN,eFyBkB;EExBlB,SF0Da;EEzDb,YFkDgB;;AEhDhB;EACE;EACA,YF7BQ;;AS6MZ;EAEE;;AAEA;EACE;EACA;EACA,KTlLS;ESmLT,MTnLS;ESoLT;EACA,OTnPW;ESoPX;EACA,aTlMe;;ASqMjB;EACE;EACA;EACA,WTrNa;ESsNb,aTnMgB;ESoMhB,OTpPQ;ESqPR,eT/LS;ESgMT;;AAGF;EACE;EACA;EACA,KTvMS;;ASyMT;EACE;EACA;EACA,eT5LiB;ES6LjB;;AAIA;EACE,aT9Ne;ES+Nf,OTxQI;ESyQJ;;AAGF;EACE,WTjPO;ESkPP,OT7QI;;;ASoRZ;EP5OE,YFtCM;EEuCN,eFyBkB;EExBlB,SF0Da;EEzDb,YFkDgB;;AEhDhB;EACE;EACA,YF7BQ;;ASkQZ;EAEE;EACA;;AAEA;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA,YTxRa;ESyRb,OTlSE;ESmSF,eTlOe;ESmOf,WT5QS;ES6QT,aT9Pa;ES+Pb;;AAIJ;EACE,gBTxPS;ESyPT;EACA,eT1PS;;AS4PT;EACE,WTnRS;ESoRT,aT1Qa;ES2Qb,eTjQO;ESkQP,OTtTM;;ASyTR;EACE;EACA;EACA;EACA,KT1QO;;AS4QP;EACE,WTjSO;ESkSP,OThUI;;ASmUN;EACE,WTlSQ;ESmSR,aT3RgB;EEWtB,YF1CiB;EE2CjB;EACA;EACA;;AOiRI;EACE,WT9SS;ES+ST,OT3UI;;ASgVV;EACE;EACA,eT7RS;;AS+RT;EACE;EACA,OTtVM;ESuVN,WT5TS;ES6TT;EACA;EACA,KTvSO;;ASySP;EACE;EACA,OTnWO;ESoWP,aTtTW;;ASyTb;EACE;;AAEA;EACE;EACA,OTtWG;;AS4WX;EACE;;;AC1XJ;EACE,eVgEW;;AU9DX;EACE;EACA,WVkCW;EUjCX,aV6CiB;EU5CjB,OVIQ;EUHR,eVuDS;;AUrDT;EACE,OVLU;EUMV;;AAIJ;EACE,WVqBW;EUpBX,OVNQ;EUOR,YV2CS;;AUxCX;EACE,WVeW;EUdX,OVlBY;EUmBZ,YVqCS;EUpCT;EACA;EACA,KVkCS;;;AU7Bb;EACE;EACA;EACA,WVIe;EUHf,aVHoB;EUIpB,OV3BU;EU4BV,YVzBM;EU0BN;EACA,eVkCiB;EUjCjB,YV+DgB;;AU7DhB;EACE,OVhCS;;AUmCX;EACE;EACA,cVhDW;EUiDX;;AAGF;EACE,YVxCM;EUyCN,OV5CQ;EU6CR;;AAIF;EACE;EACA,WVxBW;;AU2Bb;EACE;EACA,WV3BW;;AU+Bb;EACE,cVlEW;;AUoEX;EACE;;AAIJ;EACE,cV3EY;;AU6EZ;EACE;;;AAMN;EACE;EACA;;;AAIF;EACE;EACA;EACA;EACA;EACA;EACA,eVnCY;;;AUuCd;EACE;EACA;EACA,eV7CW;;AU+CX;AAAA;EAEE;EACA;EACA,cVpDS;EUqDT;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;;AAEA;AAAA;EAEE;;;AAMN;EACE;EACA;EACA,KV5EW;;AU8EX;EACE;EACA;EACA;EACA,YVjIU;EUkIV,eVpEiB;EUqEjB;EACA,YV5Cc;;AU8Cd;EACE;EACA;EACA;EACA;EACA;EACA;EACA,YVhJE;EUiJF,eV/EiB;EUgFjB,YVvDY;;AU0Dd;EACE,YVlKS;;AUoKT;EACE;;;AAOR;EACE;EACA;;AAEA;EACE;EACA,WV5Ia;EU6Ib,OVzKQ;EU0KR,YVvKM;EUwKN;EACA;EACA;;AAGF;EACE;;AAIA;EACE;EACA;EACA;;AAGF;EACE;;;AAMN;EACE;EACA;EACA,KVjJW;;AUmJX;EACE;;ARhNA;EQ0MJ;IAUI;IACA;;EAEA;IACE,eV5JO;;;;AUkKb;EACE;EACA;EACA,KVpKW;;AE7DT;EQ8NJ;IAMI;;;;ACxOJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA,SX2FuB;EW1FvB;EACA;EACA,YX+FgB;;AW7FhB;EACE;EACA;;;AAKJ;EACE;EACA;EACA;EACA;EACA;EACA,SX0Ec;EWzEd;EACA;EACA;EACA,SXoCW;EWnCX;EACA;EACA,YXyEgB;;AWvEhB;EACE;EACA;;AAEA;EACE;;;AAMN;EACE;EACA,YXjCM;EWkCN,eX6BiB;EW5BjB,YXlBU;EWmBV;EACA;EACA;EACA;EACA;EACA;EACA,YXiDgB;;AW9ChB;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA;;;AAKJ;EACE,SXjBW;EWkBX;EACA;EACA;EACA;;AAEA;EACE,WX9CW;EW+CX,aXtCmB;EWuCnB,OXhFQ;EWiFR;;AAGF;EACE;EACA;ET7EF;EACA;EACA;ES6EE;EACA;EACA,eX1Be;EW2Bf,OX1FQ;EW2FR,WX5DW;EW6DX;EACA;;AAEA;EACE,YX7FI;EW8FJ,OXlGM;;;AWwGZ;EACE,SXnDW;EWoDX;EACA;EACA,OX5GU;EW6GV,aX9DmB;;;AWkErB;EACE,SX5DW;EW6DX;EACA;EACA,KXhEW;EWiEX;;AAEA;EACE;;AAGF;EACE;;;AAMF;EACE;;AAGF;EACE;EACA,SXlFS;;AWoFT;EACE;EACA;EACA;ETrIJ;EACA;EACA;ESqII,WX/GU;EWgHV,eX5EiB;;AW8EjB;EACE;EACA,OXzJO;;AW4JT;EACE;EACA,OXhKQ;;AWmKV;EACE;EACA,OXpKQ;;AWuKV;EACE;EACA,OX7KO;;AWiLX;EACE,WXzIS;EW0IT,aXjIiB;EWkIjB,eXvHO;EWwHP,OX5KM;;AW+KR;EACE,OX/KM;;;AYZZ;EACE;EACA;EACA,KZ4DW;EY3DX;EACA,WZkCa;;AYhCb;EACE;EACA;EACA,KZqDS;EYpDT,OZCQ;;AYCR;EACE,OZFM;EYGN,YZ0FY;;AYxFZ;EACE,OZhBO;;AYoBX;EACE,OZZM;EYaN,aZ2Be;;AYxBjB;EACE;EACA,OZhBO;EYiBP,aZiCO;;;AY3Bb;EACE;EACA,KZwBW;EYvBX;EACA,eZ0BW;;AYxBX;EACE;;AAGF;EACE;EACA;EACA,KZcS;EYbT;EACA,OZvCQ;EYwCR,aZDiB;EYEjB;EACA;EACA,YZkDc;;AYhDd;EACE,OZ/CM;;AYkDR;EACE,OZ5DS;EY6DT,qBZ7DS;;AYgEX;EACE;EACA,YZrDI;EYsDJ,OZzDM;EY0DN,eZSe;EYRf;EACA,aZpBiB;;;AY0BvB;EACE;EACA,KZjBW;EYkBX,SZnBW;EYoBX,YZnEQ;EYoER,eZPiB;;AYSjB;EACE;EACA,OZ3EQ;EY4ER,aZrCiB;EYsCjB,eZde;EYef,YZec;;AYbd;EACE,OZlFM;EYmFN;;AAGF;EACE,YZpFE;EYqFF,OZjGS;EYkGT,YZxEM;;;AY+EV;EACE,eZ1CS;;AY4CT;EACE,WZzES;EY0ET,aZ5DiB;EY6DjB;EACA,OZtGM;EYuGN;EACA;;AAIJ;EACE;;AAEA;EACE,eZ9DO;;AYiET;EACE;EACA;EACA,KZlEO;EYmEP;EACA,OZzHM;EY0HN,eZ1Da;EY2Db,YZ7BY;;AY+BZ;EACE;EACA;EVtHN;EACA;EACA;EUsHM,WZnGO;EYoGP,OZjII;;AYoIN;EACE,YZlIE;EYmIF,OZhJO;;AYkJP;EACE,OZnJK;;AYuJT;EACE,YZ1IG;EY2IH,OZzJO;EY0JP,aZzGa;;AY2Gb;EACE,OZ7JK;;AYmKX;EACE,aZnGQ;EYoGR,YZzGO;EY0GP;;AAEA;EACE,WZpIO;EYqIP;;;AAOR;EACE;EACA;EACA,KZxHW;;AY4HP;EACE;EACA;EACA;;AAKF;EACE,YZjMO;EYkMP,OZtLA;EYuLA,cZnMO;;AYwMb;EACE;EACA;EACA;EACA;EACA;EACA;EACA,YZnMI;EYoMJ;EACA,eZxIe;EYyIf,OZzMQ;EY0MR,WZ9KW;EY+KX,aZnKiB;EYoKjB,YZ9Gc;;AYgHd;EACE,YZ3MI;EY4MJ,cZzNS;EY0NT,OZ1NS;;AY6NX;EAEE,WZxLS;;AY4Lb;EACE;EACA,OZ3NQ;;;AabZ;EACE;EACA,YbaM;;;AcfR;EACE;EACA;EACA,YdqBiB;EcpBjB;;;ACJF;EACE;EACA;EACA;EACA;;AbCE;EaLJ;IAOI;;;;AAKJ;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAEA;Eb4GF,Oa3G4B;Eb4G5B,Qa5G4B;Eb6G5B,YFlIY;EEmIZ;EACA;EACA;Ea/GI;EACA;EbmEJ;;Aa/DE;EbqGF,OapG4B;EbqG5B,QarG4B;EbsG5B,YFjIc;EEkId;EACA;EACA;EaxGI;EACA;EACA;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAKJ;EACE;EACA;EACA;EACA,Wf2BoB;Ee1BpB;EACA;;;AAIF;EACE;EACA;EACA,KfFW;EeGX;EACA,YfrDM;EesDN;EACA,efUmB;EeTnB,efLW;EeMX,WfhCa;EeiCb,afpBqB;EeqBrB,Of9DU;EE2FV;;Aa1BA;EACE,WfnCW;EEwDb;;;AafF;EACE,WfrCc;EesCd,af/BsB;EegCtB,af7BkB;Ee8BlB,eftBW;EeuBX,Of7EU;EE+FV;;AAtGE;Ea+EJ;IASI,Wf/CY;;;AE3CZ;EaiFJ;IAaI,WfpDY;;;AeuDd;EblCA,YFzCgB;EE0ChB;EACA;EACA;;;AaqCF;EACE,WfhEa;EeiEb,afzDoB;Ee0DpB,aflDmB;EemDnB,ef1CY;Ee2CZ,OflGU;EemGV;;Ab3GE;EaqGJ;IASI,Wf1Ea;;;;Ae+EjB;EACE;EACA,KfzDW;Ee0DX;EACA,eftDY;EeuDZ;;AbxHE;EamHJ;IAQI;IACA;;;AAGF;EblGA;EACA;EACA,KF4BW;EE3BX;EACA,eFuCiB;EEtCjB,aFcqB;EEbrB,WFCe;EEAf;EACA,YFgEgB;EE/DhB;EACA;;AAEA;EACE;;AawFA;EACE,YfhHa;EeiHb,Of1HE;Ee2HF,Yf5GM;;Ae8GN;EACE,Yf7GI;;AeiHR;EACE,YfnIE;EeoIF,OfhJS;EeiJT;;AAEA;EACE,YftIG;;AEdP;EaiIF;IAwBI;IACA;;;;AAMN;EACE;EACA;EACA,KfhGY;EEyCZ;;AA1GE;Ea8JJ;IAOI;IACA,KfzGS;;;Ae4GX;EACE;EACA;EACA;;AAEA;EACE,WftIU;EeuIV,af9HkB;EEWtB,YF1CiB;EE2CjB;EACA;EACA;;AaoHE;EACE,WflJS;EemJT,Of9KM;Ee+KN,afxIe;;;Ae8IrB;EACE;EACA,Yf9HY;Ee+HZ;;AAEA;EACE;EACA;EACA,ef5He;Ee6Hf,Yf3KQ;;Ae8KV;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EAEE;EACA;EACA;;AAGF;EACE;EACA;EACA,YftMU;EeuMV;EACA;Eb/HJ;;AamIE;EACE;EACA;EACA,YfhNY;EeiNZ;EACA;EACA;;;AAWN;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE,ef9MS;EEwCX;;Aa0KA;EACE;EACA,KftNS;EeuNT;;AbnRA;EagRF;IAMI;IACA,Kf5NO;;;AegOX;EACE;EACA;EACA,Wf1Pa;Ee2Pb;EACA,efrNiB;EesNjB,YfvRI;EewRJ;EACA;;AAEA;EACE,Of7RO;;AegST;EACE,cf5SS;Ee6ST;;Ab3SF;Ea2RF;IAoBI;IACA;;;AAIJ;EACE;EACA,YfnSe;EeoSf,Of7SI;Ee8SJ;EACA,ef9OiB;Ee+OjB,WftRa;EeuRb,af3QmB;Ee4QnB;EACA;EACA;EACA;EACA,KfrQS;EesQT,YfxSQ;EeySR;;AAEA;EACE,Wf/RS;;AekSX;EACE;EACA,Yf/SM;EegTN;;AAGF;EACE;EACA,YfxTM;;AExBR;EaoTF;IAgCI;IACA;IACA;;;AAIJ;EACE;EACA;EACA;EACA,KflSS;EemST;EACA;EACA;;AbjWA;Ea0VF;IAUI,KfzSO;Ie0SP;;;AAIJ;EACE,WfvUW;EewUX,OfnWQ;EeoWR,af7TiB;;AE/CjB;EayWF;IAMI;IACA,eftTO;;;Ae0TX;EACE;EACA;EACA;EACA,Yf9WI;Ee+WJ;EACA,ef/SiB;EegTjB,WfxVW;EeyVX,OfrXQ;EesXR;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA,YftXa;EeuXb;EACA;;AAGF;EACE,cfjZS;EekZT;EACA,YfzXM;Ee0XN,OfxYE;EeyYF;EACA;;AAEA;EACE;;AbvZJ;EaoXF;IAwCI;IACA,Wf3XS;;;;AeqYf;EACE;EACA,Yf/ZM;;;AeuaR;EACE;EACA,YfzaM;Ee0aN;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAQF;EACE;EACA;EACA;EACA,KfzYU;;Ae4YZ;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA,ef7YgB;Ee8YhB;EACA;EACA;EACA;EACA,KfhaS;EeiaT,YfpcQ;EeqcR;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EAKA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA,YfleY;EemeZ;EACA;EACA;;AAGF;EACE;EACA,OfhgBS;EeigBT;;AAGF;EACE,Wf5dS;Ee6dT,afpdiB;EeqdjB,Of9fM;Ee+fN;;AbtgBF;EakdF;IAwDI;;EAEA;IACE;;EAGF;IACE,Wf3eO;;;Aegfb;EACE;EACA;EACA;;AAGF;EACE,WftfW;EeufX,afvegB;EewehB,OfxhBQ;EeyhBR,efheU;EeieV,aflfiB;;AeofjB;EACE,OftiBS;EeuiBT,afrfiB;;AE9CnB;Ea0hBF;IAaI,WfngBS;;;AEtCX;Ea4hBF;IAiBI,WfzgBW;Ie0gBX;IACA,efhfQ;;;AeofZ;EACE;EACA,KfxfS;EeyfT;EACA;;AbvjBA;EamjBF;IAOI;IACA;IACA;;;AAIJ;EACE;EACA;EACA,KfvgBS;EewgBT;EACA,Yf3jBI;Ee4jBJ;EACA,ef5fiB;Ee6fjB;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA,YflkBa;EemkBb;EACA;;AAGF;EACE,cf7lBS;Ee8lBT;EACA,YfnkBM;;AeqkBN;EACE;;AAGF;EACE,Yf1lBA;Ee2lBA,OfvmBO;;Ae0mBT;AAAA;EAEE,OfhmBA;;AeomBJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA,YfnmBa;EeomBb,Of7mBE;Ee8mBF;EACA,afxkBa;EeykBb,WftlBW;EeulBX;EACA;;AAGF;EACE;EACA;EACA,Wf9lBW;Ee+lBX,afplBe;EeqlBf,Of7nBM;Ee8nBN;;AAGF;EACE;EACA;EACA,WfxmBS;EeymBT,OfnoBO;EeooBP;EACA;;Ab9oBF;EagkBF;IAkFI;IACA;;;;AAKN;EACE;IACE;;EAEF;IACE;;;AASJ;EACE;EACA,Yf7pBQ;;Ae+pBR;EACE;EACA,ef3mBU;;Ae6mBV;EACE,WfroBU;EesoBV,af/nBa;EegoBb,Of1qBM;Ee2qBN,eftnBO;;AE5DT;Ea8qBA;IAOI,Wf5oBQ;;;Ae+oBV;Eb3nBJ,YF1CiB;EE2CjB;EACA;EACA;;Aa6nBE;EACE,WfxpBS;EeypBT,OfvrBM;EewrBN,afzoBc;;AEvDhB;Ea6rBA;IAMI,Wf/pBS;;;AeoqBf;EACE;EACA;EACA,Kf7oBS;Ee8oBT;EACA;;Ab3sBA;EassBF;IAQI;IACA,KfppBO;IeqpBP;;;AbltBF;EawsBF;IAcI,Kf1pBO;Ie2pBP;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA,YfvtBI;EewtBJ,efzpBe;Ee0pBf,Yf3sBQ;Ee4sBR;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA,Yf5tBa;Ee6tBb;EACA;;AAGF;EACE;EACA,Yf3tBM;;Ae6tBN;EACE;;AAGF;EACE;EACA,Yf3uBW;Ee4uBX,OfrvBA;;AewvBF;EACE;EACA,OftwBO;;Ae0wBX;EACE;EACA;EACA;EACA;EACA;EACA,YflwBK;EemwBL,efpsBe;EeqsBf,efntBO;EeotBP;;AAEA;EACE;EACA,OfvxBO;;Ae2xBX;EACE,WfnvBS;EeovBT,af3uBiB;Ee4uBjB,OfrxBM;EesxBN,efluBO;EemuBP;;AAGF;EACE,Wf9vBW;Ee+vBX,Of3xBM;Ee4xBN;EACA,af9uBc;Ee+uBd,efzuBO;;Ae4uBT;EACE;EACA,OflyBO;EemyBP;;AAEA;EACE,WfzwBO;;AEtCX;Ea2tBF;IAyFI;;EAEA;IACE;IACA;;EAEA;IACE;;EAIJ;IACE,Wf1xBO;;Ee6xBT;IACE,WfjyBO;;;;Ae4yBf;EACE;EACA;EACA;EACA;;AAGA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA,efhzBU;EeizBV;EACA;;AAEA;EACE,Wf90BU;Ee+0BV,afv0Ba;Eew0Bb,Ofl3BM;Eem3BN,afn0Bc;Eeo0Bd,ef5zBQ;;AE7DV;Eao3BA;IAQI,Wft1BQ;;;AExCZ;Eas3BA;IAYI,Wf31BO;Ie41BP;;;AAGF;Ebz0BJ,YF1CiB;EE2CjB;EACA;EACA;Eaw0BM,aft1BgB;;Ae21BtB;EACE;EACA;EACA,Kfj1BU;Eek1BV;EACA;EACA;EACA;;Abl5BA;Ea24BF;IAUI;IACA,Kf11BO;Ie21BP;;;Abz5BF;Ea64BF;IAgBI;;;AAIJ;EACE,Yfx5BI;Eey5BJ,ef11Be;Ee21Bf,Sfr2BU;Ees2BV,Yf54BQ;Ee64BR;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA,Yf/5Ba;Eeg6Bb,ef12Ba;Ee22Bb;EACA;EACA;;AAGF;EACE;EACA,Yfh6BM;;Aek6BN;EACE;;AAGF;EACE;EACA,Yfh7BW;;Aek7BX;EACE,Of57BF;;Aeg8BF;Eb74BJ,YFzCgB;EE0ChB;EACA;EACA;;Aa+4BE;EACE;EACA;EACA;EACA;EACA;EACA,Yfz8BK;Ee08BL,ef34Be;Ee44Bf,ef15BO;Ee25BP;;AAEA;EACE;EACA,Of99BO;Ee+9BP;;AAKF;EACE,Wfz7BQ;Ee07BR,afl7BgB;Eem7BhB,Of99BI;Ee+9BJ,ef36BK;Ee46BL;EACA;;Abx+BJ;Eak+BE;IASI,Wfl8BM;;;Aes8BR;EACE;;AAIJ;EACE,Wf/8BO;Eeg9BP,aft8Be;Eeu8Bf,Ofh/BI;Eei/BJ,ef97BK;;Aei8BP;EACE,Wfz9BO;Ee09BP,Ofr/BI;Ees/BJ,afx8Ba;;Ae88Bf;EACE;;AAEA;EACE,OfxgCI;;Ae4gCR;EACE;;AAKF;EACE;;AAEA;EACE,OfnhCK;;AeuhCT;EACE;;AAKF;EACE;;AAEA;EACE,OfhiCM;;AeoiCV;EACE;;AbziCJ;Eai6BF;IA6II,Sfh/BO;;Eek/BP;IACE;IACA;;EAEA;IACE;;;;AAQV;EACE;IACE;IACA;;EAEF;IACE;IACA;;;AAIJ;EACE;;;AC7kCF;EACE;EACA;EACA,kBhBYQ;EgBXR;;;AAIF;EACE;EACA,kBhBIM;EgBHN;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EAXF;IAYI;;;AAGF;EAfF;IAgBI;IACA;IACA;IACA;IACA;IACA,YhBCQ;IgBAR,ShBqEY;IgBpEZ;;EAEA;IACE;;;;AAMJ;EACE,ehBqBS;;AgBlBX;EACE;EACA,WhBPW;EgBQX,ahBKmB;EgBJnB,OhB7Ca;EgB8Cb;EACA,ehByBe;EgBxBf,YhBsDc;EgBrDd;EACA;EACA;;AAEA;EACE,kBhB1CI;;AgB6CN;EACE,kBhB7CK;EgB8CL,OhB3DW;;AgB8Db;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA,OhBvES;EgBwET,WhBpCS;EgBqCT,ahBvBiB;EgBwBjB,ehBGe;EgBFf,YhB4BY;;AgBzBd;EACE,kBhB/ES;EgBgFT,OhBpEE;;AgByEN;EACE,chBxBS;EgByBT,YhB3BS;EgB4BT,ehB3BS;;AgB8BX;EACE;EACA,WhBxDW;EgByDX,OhBpFQ;EgBqFR;EACA,ehBxBe;EgByBf,YhBMc;EgBLd;EACA;EACA;EACA;;AAEA;EACE;EACA,chB5CO;EgB6CP,OhB1GS;EgB2GT;;AAGF;EACE;EACA,OhBvGM;EgBwGN;;AAEA;EACE;;AAIJ;EACE;EACA,OhB1HS;EgB2HT,ahB1Ee;;AgB4Ef;EACE;;AAIJ;EACE;EACA;EACA;EACA;;;AAMN;EACE;EACA;EACA;EACA;EACA;;AAEA;EAPF;IAQI;;;;AAKJ;EACE;EACA;EACA,QhB7FW;EgB8FX,OhB9FW;EgB+FX;EACA;EACA,YhB3IiB;EgB4IjB,ehBnFqB;EgBoFrB;EACA,OhBvJM;EgBwJN;EACA;EACA,YhB1IU;EgB2IV;EACA,YhBjEgB;;AgBmEhB;EACE;EACA,YhB/IQ;;AgBkJV;EACE;;AAGF;EA1BF;IA2BI;IACA;IACA;;;;AAKJ;EACE;EACA;EACA;EACA,ehB9HY;;AgBgIZ;EANF;IAOI;IACA;IACA,KhBrIS;;;;AgB0IX;EACE,WhBrKW;EgBsKX,OhBjMQ;EgBkMR,ahB5JkB;EgB6JlB,ehBjJS;;AgBoJX;EACE,WhBvKY;EgBwKZ,ahB/Je;EgBgKf,OhB1MQ;;;AgB+MZ;EACE;EACA;;AAEA;EAJF;IAKI;;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA,ehBhKe;EgBiKf,WhBtMW;EgBuMX;EACA,YhBtIc;;AgBwId;EACE,chBhPS;EgBiPT;;AAGF;EACE,OhB1OO;;AgB8OX;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,ehBzLe;EgB0Lf;EACA;EACA;EACA;EACA,YhBhKc;EgBiKd,OhB9PQ;;AgBgQR;EACE,YhB7PK;EgB8PL,OhB5QS;EgB6QT;;AAGF;EACE;;AAGF;EACE;EACA;;;AAMN;EACE,WhBvPe;EgBwPf,OhBpRU;EgBqRV,ehB/NW;;AgBiOX;EACE,OhBlSW;EgBmSX,ahBjPmB;;;AgBsPvB;EACE;EACA;EACA,KhB5OW;;AgB8OX;EALF;IAMI;;;;AAKJ;EACE,YhBxSM;EgBySN,ehB3OiB;EgB4OjB,ShBtPW;EgBuPX,YhB7RU;EgB8RV,YhBjNgB;EgBkNhB;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA,YhBnTe;EgBoTf;EACA;EACA;;AAGF;EACE,YhBnTQ;EgBoTR;;AAEA;EACE;;AAGF;EACE;;AAIJ;EACE;;;AAIJ;EACE;EACA;EACA,KhBtSW;EgBuSX,ehBtSW;;;AgBySb;EACE;EACA;EACA;EACA;EACA,kBhB/VQ;EgBgWR,ehBrSiB;EgBsSjB,WhB1Ua;EgB2Ub,OhBrWU;EgBsWV,ahB/TmB;;AgBiUnB;EACE;EACA,OhBpXW;EgBqXX;;;AAIJ;EACE,WhBnVa;EgBoVb,ahBzUqB;EgB0UrB,OhBnXU;EgBoXV;EACA,ahBvUkB;EgBwUlB;EACA;EACA;EACA;;;AAGF;EACE,WhBjWa;EgBkWb,OhB7XU;EgB8XV,ahBhVmB;EgBiVnB;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA,YhBnVW;EgBoVX;EACA;EACA;;AAEA;EACE;EACA;EACA;;;AAKJ;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAIF;EACE;EACA;EACA,OhBraU;;AgBuaV;EACE;EACA,ehBpXS;EgBqXT;;AAGF;EACE,WhB/YW;EgBgZX,ahBvYmB;EgBwYnB,OhBjbQ;EgBkbR,ehB7XS;;AgBgYX;EACE,WhBzZa;EgB0Zb,OhBtbQ;;;AgB2bZ;EACE;EACA;EACA;EACA,ShBrYY;;AgBuYZ;EACE;EACA;EACA;EACA,kBhB/cW;EgBgdX,ehBlYmB;EgBmYnB;;;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,KhBvbW;;;AgB2bb;EACE;EACA,KhBhcW;EgBicX;EACA,ehB/bW;;AgBicX;EACE;EACA;EACA;EACA;EACA,WhBhea;EgBieb,ahBtdiB;EgBudjB,OhB9fQ;EgB+fR;EACA,YhBnac;EgBoad;EACA;;AAEA;EACE,OhB/gBS;EgBghBT;;AAGF;EACE,OhBphBS;EgBqhBT,ahBneiB;EgBoejB,qBhBthBS;;;AgB4hBf;EACE;EACA;EACA,KhB/dW;EgBgeX;;AAEA;EACE;;;AAKJ;EACE,YhB7hBM;EgB8hBN,ehBheiB;EgBiejB,ShB3eW;EgB4eX,YhBlhBU;EgBmhBV;EACA;EACA,KhBhfW;;AgBkfX;EACE;EACA;EACA;EACA,KhBvfS;;AgByfT;EANF;IAOI;IACA;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA,YhB9iBe;EgB+iBf,OhBxjBI;EgByjBJ,WhBhiBW;EgBiiBX,ahBphBmB;EgBqhBnB,ehB/fe;EgBggBf;EACA;;AAGF;EACE;;AAEA;EACE;EACA;EACA,kBhBrkBI;EgBskBJ;EACA,ehB3gBa;EgB4gBb,WhB9iBS;EgB+iBT;EACA,OhB9kBM;EgB+kBN;;AAIJ;EACE;EACA;;AAEA;EACE,WhB3jBW;EgB4jBX,OhBxlBM;EgBylBN,ahB3iBe;EgB4iBf;;AAKF;EACE,WhBnkBS;EgBokBT,ahBzjBiB;EgB0jBjB,OhBnmBM;EgBomBN,ehB/iBO;;;AgBqjBb;EACE;EACA;EACA,KhBvjBW;;;AgB2jBb;EACE,YhB/mBM;EgBgnBN,ehBljBiB;EgBmjBjB,ShB7jBW;EgB8jBX,YhBpmBU;EgBqmBV,YhBxhBgB;;AgB0hBhB;EACE,YhBvmBQ;;AgB0mBV;EACE,WhB9lBW;EgB+lBX,ahBrlBmB;EgBslBnB,OhB/nBQ;EgBgoBR,ehB3kBS;EgB4kBT,gBhB7kBS;EgB8kBT;;AAGF;EACE,WhB1mBW;EgB2mBX,OhBtoBQ;EgBuoBR,ahBzlBiB;;AgB2lBjB;EACE,kBhBvoBI;EgBwoBJ;EACA,ehB7kBa;EgB8kBb,ShBzlBO;EgB0lBP;EACA;;AAEA;EACE;EACA,WhBxnBO;EgBynBP,OhBrpBI;EgBspBJ;EACA;;AAKJ;EACE;EACA;EACA;EACA,kBhB7pBE;EgB8pBF;EACA,ehBlmBa;EgBmmBb;;AAEA;EACE,kBhBjqBG;;AgBoqBL;EACE;EACA;EACA,WhBhpBO;EgBipBP,ahBpoBe;EgBqoBf,OhB9qBI;EgB+qBJ;EACA;;AAEA;EACE;;AAIJ;EACE;EACA,WhB7pBO;EgB8pBP,OhBzrBI;EgB0rBJ;;AAEA;EACE;;AAIJ;EACE,YhBrmBU;;AgBumBV;EACE;;AAGF;EACE;;AAIJ;EACE;;;AAOR;EACE;EACA;;AAEA;EACE;;AAEA;EACE;;AAIJ;EACE;EACA;EACA,WhBzsBW;EgB0sBX,ahB7rBmB;EgB8rBnB,OhBvuBQ;EgBwuBR;EACA,kBhBruBM;;AgBwuBR;EACE;EACA,WhBltBW;EgBmtBX,OhB9uBQ;;AgBgvBR;EACE;EACA;EACA,kBhBhvBI;EgBivBJ,ehBtrBa;EgBurBb;EACA,WhB5tBS;EgB6tBT,OhBjwBS;;;AiBHf;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAIJ;EACE;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EARF;IASI;IACA;;;;AAIJ;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;AAIJ;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;;;AAIJ;EACE;;AAEA;EACE;;AAEA;EACE;;AAIJ;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;EACA;;AAGF;EACE;;AAEA;EACE;;AAKN;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;;AAKN;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;;AAGF;EACE;EACA;EACA;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EAVF;IAWI;IACA;IACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;;AAIJ;EACE;EACA;;;AAKJ;EACE;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;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;;AAIJ;EACE;IACE;;;AC5SJ;EACE,WlBkFoB;EkBjFpB;EACA;;AhBCE;EgBJJ;IAMI;;;;AAKJ;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAIF;EhBhBE;EACA;EACA;;;AgBkBF;EhBdE;EACA;EACA;;;AgBgBF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAIF;EACE,KlBxBW;;;AkB2Bb;EACE,KlB3BW;;;AkB8Bb;EACE,KlB9BW;;;AkBiCb;EACE,KlBjCW;;;AkBoCb;EACE,KlBpCW;;;AkBoDX;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,YlB/Pa;;;AkBkQf;EACE,YlBlQe;;;AkBqQjB;EACE,YlB3PM;;;AkB8PR;EACE,YlB9PQ;;;AkBiQV;EACE,YlBjQS;;;AkBoQX;EACE,YlB9PiB;;;AkBiQnB;EACE,YlBjQgB;;;AkBoQlB;EACE,YlBpQc;;;AkBwQhB;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE,clBxTa;;;AkB4Tf;EACE;;;AAGF;EACE,elBzPiB;;;AkB4PnB;EACE,elB5PiB;;;AkB+PnB;EACE,elB/PiB;;;AkBkQnB;EACE,elBlQiB;;;AkBqQnB;EACE,elBpQmB;;;AkBuQrB;EACE,elBvQqB;;;AkB2QvB;EACE;;;AAGF;EACE,YlBpUU;;;AkBuUZ;EACE,YlBvUU;;;AkB0UZ;EACE,YlB1UU;;;AkB6UZ;EACE,YlB7UU;;;AkBiVZ;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAIF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AhB1ZE;EgB+ZF;IACE;;;AhB9ZA;EgBmaF;IACE;;;AAKF;EADF;IAEI;;;;AhB5aA;EgBgbJ;IAEI;;;;AAKJ;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAIF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAIF;EACE,YlBrXgB;;;AkBwXlB;EACE,YlBxXgB;;;AkB2XlB;EACE,YlB3XgB;;;AkB8XlB;EACE","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/_kjb-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;AAMA;AACA;EACE;EACA;EACA;;AAIF;EACE;EACA;EACA;;AAKF;AACA;EACE;EACA;EACA;;AAIF;EACE;EACA;EACA;;AAIF;EACE;EACA;EACA;;ACzDF;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;;;AAIJ;EACE;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,aLjRkB;EKkRlB;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,aLtac;EKuad;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,aLnfgB;EKofhB;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE,aL7fgB;EK8fhB;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAIF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA,aLthBgB;EKuhBhB;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAKJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA,aL3iBgB;EK4iBhB;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAKJ;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;EACA;;AAIF;EACE,aL5kBc;EK6kBd;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,aLnnBc;EKonBd;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,aL1pBc;EK2pBd;EACA;EACA;EACA;;AAEA;EACE;;AAKJ;EACE;EACA;EACA;EACA;EACA;;AAGE;EACE;EACA;EACA,aLjrBU;EKkrBV;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAKJ;EACE;EACA;;AAOJ;EACE;;AAEA;EACE;;AAIJ;EACE;;;AAOV;EACE;EACA,OL5rBW;EK6rBX;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA,YL5vBQ;EK6vBR;EACA;EACA;;AAEA;EAEE;EACA;EACA;EACA;EACA;EACA,YLxwBM;EKywBN;;AAGF;EAAY;;AACZ;EAAW;;AAGb;EACE;;AACA;EAAY;EAAQ;;AACpB;EAAW;EAAQ;;;AAOvB;EAEE;EACA;EACA;EACA;EACA;EACA,YL9xBM;EK+xBN,SL9sBc;EK+sBd;EACA;EACA;EACA;EACA;;AAKA;EACE,SL3vBS;EK4vBT,YL1yBM;EK2yBN;;AAGF;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAEA;EACE;EACA;EACA,SL5wBK;EK6wBL,OL/zBI;EKg0BJ,WLtyBO;EKuyBP,aL7xBa;EK8xBb;EACA;;AAGF;EACE;EACA;EACA,OLvxBK;EKwxBL;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA,YLx1BE;;AK01BF;EACE;EACA;EACA,OLh2BE;EKi2BF,WL10BK;EK20BL;;AAEA;EACE,YLn2BJ;EKo2BI,OLl3BG;;AKu3BT;EACE;;AAKN;EACE;EACA,YLj3BI;EKk3BJ;EACA;;;AAQJ;EACE;EACA;EACA;EACA;EACA;;AAGA;EACE;;AAGE;EACE;EACA;;AAGF;EACE;EACA;EACA;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA,eLh2Ba;EKi2Bb;EACA;EACA;EACA;EACA;EACA;EACA,SLr1Ba;EKs1Bb;EACA;;AAEA;EACE;;AAEA;EACE;EACA;EACA;EACA;EACA,WLx5BK;EKy5BL,aL74BW;EK84BX;;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,aL5kCgB;EK6kChB;EACA;EACA;EACA;;AAIJ;EACE;EACA;EACA;;AAGF;EACE,aL3lCkB;EK4lClB;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,eLllCa;EKmlCb;EACA;EACA;EACA;EACA;EACA;EACA,SLvkCa;;AKykCb;EACE;EACA;EACA;;AAEA;EACE;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA,WL/oCG;EKgpCH,aLpoCS;EKqoCT;;AAEA;EACE;EACA;EACA;;AAGF;EACE;EACA;;AAOV;EACE;EACA;EACA;;;AAMN;EACE;IACE;;;AC/sCJ;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;;;A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;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,OZ3hBQ;EY4hBR;;AAGF;EACE;EACA,eZpee;EYqef,WZ1gBW;EY2gBX,aZ9fmB;;AYggBnB;EACE;EACA;;AAGF;EACE;EACA;;;AAQN;EACE;EACA;EACA,KZtgBW;EYugBX;EACA,YZngBY;EYogBZ,aZpgBY;EYqgBZ;;AAEA;EATF;IAUI;IACA;;;AAIA;EADF;IAEI;;;;AC7kBN;EACE,eb0DW;;AaxDX;EACE;EACA,Wb4BW;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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;;;AAOR;EACE;EACA;EACA,Kf9HW;;AekIP;EACE;EACA;EACA;;AAKF;EACE,YfrMO;EesMP,OfxLA;EeyLA,cfvMO;;Ae4Mb;EACE;EACA;EACA;EACA;EACA;EACA;EACA,YfrMI;EesMJ;EACA,ef9Ie;Ee+If,Of3MQ;Ee4MR,WfpLW;EeqLX,afzKiB;Ee0KjB,YfpHc;;AesHd;EACE,Yf7MI;Ee8MJ,cf7NS;Ee8NT,Of9NS;;AeiOX;EAEE,Wf9LS;;AekMb;EACE;EACA,Of7NQ;;;AgBXZ;EACE;EACA,YhBWM;;;AiBbR;EACE;EACA;EAEA;;;ACKA;EACE;EACA;;AAGF;EACE,OlBZY;EkBaZ,WlBiBW;EkBhBX,YlBwCS;EkBvCT;EACA;;AAEA;EACE;;;AAMN;EACE;IACE;IACA;;EAEF;IACE;IACA;;;AC/BJ;EACE;EACA;EACA,KnBoDW;;AmBlDX;EACE;;;AAOJ;EACE;EACA;EACA;EACA,OnBXU;EmBYV;EACA;EACA,enBkDmB;EmBjDnB,anBsBqB;EmBrBrB,WnBQa;EmBPb,YnByEgB;;AmBtEhB;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,anBZkB;EmBalB,anBKmB;EmBJnB;EACA;EACA,YnBuDc;;AmBrDd;EACE;EACA;EACA;;;AAQN;EACE;;AAEA;EACE;EACA;EACA,KnBNS;EmBOT;EACA,YnBnDO;EmBoDP;EACA,enBMiB;EmBLjB;EACA,YnB8Bc;EmB7Bd,anB1CkB;EmB2ClB,WnBtCW;;AmBwCX;EACE,OnBjEM;EmBkEN,anB7BiB;;AmBgCnB;EACE,OnBrEM;EmBsEN;EACA;;AAGF;EACE,YnBzEE;EmB0EF,cnBxFS;EmByFT,YnBhEM;;AmBkEN;EACE,OnB5FO;;AmB+FT;EACE,OnBhGO;;AmBsGX;EACE;;AAGF;EACE;EACA;EACA;;AAIJ;EACE;EACA;EACA;EACA;EACA,YnBxGI;EmByGJ;EACA,enBhDe;EmBiDf,YnB9FQ;EmB+FR;EACA;EACA;EACA;EACA,SnBpCe;EmBqCf;;AAGF;EACE,SnBtES;EmBwET,OnBvHI;EmBwHJ;;AAEA;EACE;EACA,WnBrGS;EmBsGT,anB1Fa;;AmB6Ff;EACE,WnB7GS;EmB8GT;;AAIJ;EACE;EACA;EACA;;AAEA;EACE;;AAEA;EACE;EACA;EACA;EACA;EACA,OnBtJI;EmBuJJ;EACA,WnBhIO;EmBiIP,YnB9DU;;AmBgEV;EACE;EACA,OnB5JE;EmB6JF;;AAGF;EACE,YnB7JC;EmB8JD,OnB9KK;;AmBgLL;EACE,OnBjLG;;AmBwLb;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA,KnBrIO;EmBsIP;EACA;EACA,YnBpLI;EmBqLJ,OnBzLM;EmB0LN;EACA,enB/Ha;EmBgIb,anBvJiB;EmBwJjB,WnBrKS;EmBsKT,YnBpGY;;AmBsGZ;EACE;;AAGF;EACE,YnB5MQ;EmB6MR,OnBnMA;EmBoMA;EACA,YnB1LI;;;AmBmMZ;EACE,SnBhKW;EmBkKX;;AAEA;EACE;EACA;EACA,KnBxKS;;AmB0KT;EACE;EACA;EACA;EACA;EACA;EACA;EACA,enBjKiB;EmBkKjB,OnBhOE;;AmBkOF;EACE;;AAIJ;EACE;EACA,OnBzOE;;AmB2OF;EACE;EACA,WnBtNO;EmBuNP,anB3MW;;AmB8Mb;EACE,WnB9NO;EmB+NP;;;AASR;EACE;EACA;EACA;EACA,KnBnNW;EmBoNX;EACA;EACA,YnB7Qc;EmB8Qd,OnBpQM;EmBqQN;EACA,enB5MiB;EmB6MjB,anBrOqB;EmBsOrB,WnBlPe;EmBmPf,YnBlLgB;EmBmLhB,YnB/PU;;AmBiQV;EACE;;AAGF;EACE;EACA;EACA,YnBvQQ;;;AmB8QZ;EAEI;IACE;IACA;;;AAKN;EAEI;IACE;;EAEA;IACE;IACA;IACA;IACA;;EAIJ;IACE;IACA;;;AC9TN;EACE;EACA,YpBUM;EoBTN,epBmEiB;EoBlEjB;EACA,YpBiBU;;AoBdV;EACE;EACA,epBkDS;;AoBhDT;EAJF;IAKI,epByDa;IoBxDb,YpBQM;;;AoBJV;EACE;EACA;;AAEA;EACE,YpBTK;;AoBWL;EACE;;AAGF;EACE;EACA;EACA,WpBCO;EoBAP,apBae;EoBZf,OpBzBI;EoB0BJ;;AAEA;EARF;IASI;IACA,WpBPK;;;AoBaT;EACE;EACA,YpBoDU;;AoBlDV;EACE;;AAGF;EACE;;AAIJ;EACE;EACA,WpB3BO;EoB4BP,OpBnDI;;AoBqDJ;EALF;IAMI;IACA,WpBjCK;;;AoBoCP;EACE,OpB5DE;EoB6DF;EACA,YpB4BQ;;AoB1BR;EACE,OpB5EG;;;AoBqFf;EACE;EACA,YpBzEM;EoB0EN,epBhBiB;EoBiBjB;EACA,YpBlEU;;AoBoEV;EACE;;AAEA;EAHF;IAII;IACA;;;AAKJ;EACE;EACA;EACA,KpB7CS;EoB8CT;EACA,YpB3FO;EoB4FP;EACA,WpB1EW;EoB2EX,apB9DmB;EoB+DnB,OpBpGQ;;AoBsGR;EAXF;IAYI;IACA;IACA,WpBlFS;;;AoBqFX;EAjBF;IAkBI;;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAKJ;EACE;EACA;EACA,KpBhFS;EoBiFT,SpBhFS;EoBiFT;EACA,YpB1Cc;EoB2Cd;;AAEA;EATF;IAUI;IACA,SpBxFO;;;AoB2FT;EAdF;IAeI;IACA,KpB9FO;IoB+FP,SpB9FO;;;AoBiGT;EACE;;AAGF;EACE;;AAGF;EACE;EACA;EACA;EACA,WpBtIS;EoBuIT,OpB9JM;EoB+JN,apB5He;;AoB8Hf;EARF;IASI;;;AAIJ;EACE;EACA;EACA,WpBjJW;EoBkJX,OpB3KM;EoB4KN,apBxIe;;AoB0If;EAPF;IAQI,WpBvJO;;;AoB0JT;EACE;EACA;EACA;EACA;EACA,KpBvIK;EoBwIL,YpB9FU;;AoBgGV;EACE,OpBtMK;;AoB0MT;EACE;EACA;EACA,apBlJK;EoBmJL;;AAEA;EACE;EACA;;AAKN;EACE;EACA;EACA;EACA,WpBxLS;EoByLT,OpB/MO;;AoBiNP;EAPF;IAQI;IACA,WpB9LO;IoB+LP,apBvKK;;;AoB6KX;EACE;IACE;IACA,OpB9NM;IoB+NN,cpBjLO;;;;AoBuLb;EACE;EACA;EACA,YpBtOM;EoBuON,epB7KiB;;AoB+KjB;EACE,epB3LS;;AoB6LT;EACE;EACA;;AAEA;EAJF;IAKI;;;AAKN;EACE,WpBjOa;EoBkOb,OpB1PQ;;AoB4PR;EAJF;IAKI,WpBtOS;;;;AoBgPf;EACE;EACA,YpBvQM;EoBwQN,epB9MiB;EoB+MjB;EACA;EACA;;AAEA;EACE;EACA,epB/NS;;AoBmOX;EACE;EACA;EACA;EACA;EACA;;AAEA;EAPF;IAQI;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA,apBlQiB;EoBmQjB,OpBrSE;EoBsSF;EACA;;AAKJ;EACE;EACA;;AAIF;EACE;EACA;EACA;EACA;EACA,YpB/Nc;EoBgOd;;AAEA;EACE;;AAIF;EACE;;AAGF;EACE,kBpBpUE;;AoBuUJ;EACE;;AAGF;EAzBF;IA0BI;IACA;IACA,KpBjSO;IoBkSP,SpBjSO;;;AoBoST;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EAVF;IAWI,WpBxUO;IoByUP;;EAEA;IACE;IACA,apBhUa;IoBiUb,OpBrWE;IoBsWF,cpBvTG;IoBwTH;IACA;;;AAKJ;EACE;;AAEA;EACE;IACE;;;AAIJ;EACE;EACA;EACA,KpB1UG;EoB2UH;EACA;EACA,YpBnSQ;;AoBqSR;EACE,OpB3YG;;AoB+YL;EACE;EACA,apBlWS;EoBmWT,OpBtYA;EoBuYA;;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,apBhYK;;;;AoBuYb;EACE;EACA;EACA;EACA;EACA;EACA;EACA,epBlYiB;EoBmYjB,WpBtae;EoBuaf,apB7ZoB;EoB8ZpB;EACA;EACA;EACA,YpB1WgB;EoB2WhB;;AAEA;EACE;EACA,YpB5bQ;;AoB+bV;EACE;;AAIF;EACE;;AAEA;EACE;;AAIJ;EACE;;AAEA;EACE;;AAIJ;EACE;;AAEA;EACE;;;AAQN;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,YpBpac;;AoBsad;EACE,OpB5gBS;;AoB+gBX;EACE;EACA;;AAGF;EACE,WpBjfW;;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;;EAEA;IACE;;EAOR;IACE;IACA;;EAEA;IACE;IACA;;EAEA;IACE;IACA;IACA;;EAIJ;IACE;;EAGF;IACE;IACA;IACA;IACA;IACA,apBhzBgB;;EoBkzBhB;IACE,apBhzBW;IoBizBX;;;AC/1BR;EACE;EACA,erBmEiB;EqBlEjB;EACA,erBwDY;;AE3DV;EmBDJ;IAOI;IACA;;;AAGF;EACE;EACA,YrBqFc;;AqBnFd;EACE;;AAME;EACE;;AAIJ;EACE;EACA;;AAMN;EACE;EACA;EACA;EACA;EACA;EACA,arBKe;EqBJf;EACA;EACA,YrBsDc;EqBrDd;EACA;;AAEA;EAbF;IAcI;IACA;IACA,KrBOO;;;AqBJT;EACE;;AAGF;EACE;EACA,arBTe;;AqBajB;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAMN;EACE;EACA;EACA;EACA;;AnBrFA;EmBiFF;IAOI;;;AAGF;EAVF;IAWI;;;AAGF;EACE;EACA,arBvDgB;EqBwDhB;EACA;EACA,SrB3CO;EqB4CP,arBlDc;;AqBoDd;EARF;IASI;;;AAIF;EACE,erBrDK;;AqBuDL;EACE;;AAIJ;EACE;EACA,crB7DK;;AqBgEP;EACE,erBpEK;;AqBuEP;EACE,OrBlIO;EqBmIP;;AAEA;EACE,OrBrIO;;AqByIX;EACE,YrB5HE;EqB6HF;EACA,erBvEW;EqBwEX,arB/GW;EqBgHX;EACA,OrBhJO;;AqBmJT;EACE,YrBrIE;EqBsIF;EACA,erB/EW;EqBgFX,SrB3FK;EqB4FL;EACA;;AAEA;EACE;EACA;EACA,OrBnJE;;;AqB2JZ;EACE;EACA,YrBzJQ;EqB0JR;;AAEA;EALF;IAMI;;;;AAIJ;EACE,WrBlGoB;EqBmGpB;;;AAGF;EACE,erBtHY;;AqBwHZ;EAHF;IAII,erB1HU;;;AqB6HZ;EACE,WrBpJY;EqBqJZ,arB7Ie;EqB8If,OrBpLQ;EqBqLR,erBpIS;EqBsIT;EACA;EACA;;AAEA;EAVF;IAWI,WrB/JU;;;AqBmKd;EACE,WrBxKa;EqByKb,OrBjMQ;EqBkMR,arBxJiB;;AqB0JjB;EALF;IAMI,WrB9KS;;;;AqBoLf;EACE;IACE;IACA;;EAEF;IACE;IACA;;;AAKJ;EACE,YrBtNM;EqBuNN,erB7JiB;EqB8JjB,YrB9MU;EqB+MV;;AAEA;EACE;;AAEA;EACE;;AAIA;EACE;EACA,OrBnPO;;AqBqPP;EACE;;AAMR;EACE;EACA;EACA;EACA;EACA,WrB7Na;EqB8Nb,arBnNiB;EqBoNjB,OrBxPQ;EqByPR;EACA,YrBhKc;;AqBkKd;EAXF;IAYI;IACA,WrBtOS;;;AqByOX;EACE;;AAGF;EACE;;AAGF;EACE;EACA;EACA,OrB3QM;EqB4QN,YrBnLY;;AqBuLhB;EACE;EACA;EACA,WrB5PW;EqB6PX,OrBpRQ;EqBqRR,arB3OiB;EqB4OjB;;AAEA;EARF;IASI;;;AAGF;EACE;EACA;;;ACzSN;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,WtBMY;EsBLZ,atBae;EsBZf,OtB1BQ;EsB2BR;EACA;EACA;;ApBpCA;EoBHJ;IA4CI;IACA,etBgBS;;EsBdT;IACE;;EAGF;IACE,WtBdS;;;;AuBrCf;EACE;EACA;EACA,KvBuDW;EuBtDX;EACA,evBgEiB;EuB/DjB,evBsDW;;AuBpDX;EACE;EACA;;AAGF;EACE;EACA;;AAEA;EACE,avB4BiB;;AuBvBrB;EACE;EACA;EACA;;AAIF;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;EACA,WvBTS;EuBUT,avBEe;;AuBGnB;EACE;EACA;EACA;;AAIF;EACE;EACA;EACA,OvB3Da;;;AwBSjB;EACE;EACA;EACA;EACA,KAXe;;AAcf;AAAA;AAAA;AAAA;EAIE;EACA;EACA;EACA,OApBmB;EAqBnB,QArBmB;EAsBnB,OApBqB;EAqBrB;EACA;EACA,YxByEc;;AwBvEd;AAAA;AAAA;AAAA;EACE,OxBhCS;;AwBmCX;AAAA;AAAA;AAAA;EACE,OA5BqB;EA6BrB;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;EyBwMJ;IAeI;IACA;;;AzB1NA;EyB0MJ;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;;AzBpQA;EyByOJ;IAgCI;;;;AAIJ;EACE;;AAEA;EACE;;AzBrRA;EyBiRJ;IAQI;;;AzBvRA;EyB+QJ;IAYI;;;AzBzRA;EyB6QJ;IAkBI;;;;AAIJ;EACE;;AzBxSE;EyBuSJ;IAII;;;AzBzSA;EyBqSJ;IAQI;;;AzB3SA;EyBmSJ;IAcI;;;;AAKJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AzBjUE;EyB0TJ;IAWI;IACA;IACA;IACA;IACA;IACA;IACA;IACA;;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;;AzB/VA;EyBgVJ;IAsBI;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;;AzB/YA;EyBwXJ;IA4BI;;;;AAQJ;EACE;EACA;EAEA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;AzBlbA;EyB4aF;IASI;IACA;IACA;;;AzBzbF;EyB8aF;IAgBI;IACA;IACA;IACA;IACA;;;AAIJ;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;;AzB5cF;EyBocF;IAYI;IACA;;;AzBndF;EyBscF;IAkBI;IACA;;;AAIJ;EACE;;AzB5dA;EyB2dF;IAII;;;AzBjeF;EyB6dF;IASI;;;AAIJ;EACE,a3BjdkB;E2BkdlB;EACA;EACA;EACA;EACA;;AzB9eA;EyBweF;IASI;;;AzBnfF;EyB0eF;IAcI;IACA;IACA;;;AAIJ;EACE;EACA;EACA;EACA;EACA;;AzBjgBA;EyB4fF;IAQI;;;AzBtgBF;EyB8fF;IAaI;IACA;;;AAIJ;EACE;EACA;;AASF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AzBliBA;EyByhBF;IAYI;IACA;IACA;;;AzBziBF;EyB2hBF;IAmBI;IACA;IACA;IACA;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA,a3BliBkB;E2BmiBlB;EACA;EACA;EACA;;AAEA;EACE;EACA;;AzBlkBF;EyBmjBF;IAmBI;IACA;;;AzBzkBF;EyBqjBF;IAyBI;IACA;IACA;;EAEA;IACE;IACA;;;AAKN;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;;AAGF;EACE;EACA;;AzB9mBF;EyB4mBA;IAKI;IACA;;;AzBpnBJ;EyB8mBA;IAWI;IACA;;;AzB1nBJ;EyBylBF;IAsCI;IACA;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AzBzoBA;EyBkoBF;IAUI;IACA;IACA;IACA;IACA;;;AzBlpBF;EyBooBF;IAmBI;IACA;IACA;IACA;IACA;;;AAIJ;EACE,a3BtoBkB;E2BuoBlB;EACA;EACA;EACA;;AzBpqBA;EyB+pBF;IASI;IACA;;;AAIJ;EACE;EACA;EACA;EACA;;AzB/qBA;EyB2qBF;IAOI;IACA;;;AzBrrBF;EyB6qBF;IAaI;IACA;;;AAIJ;EACE,a3BtqBkB;E2BuqBlB;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AzBxsBF;EyB6rBF;IAeI;;;AzB9sBF;EyB+rBF;IAoBI;IACA;;;AAIJ;EACE,a3B/rBkB;E2BgsBlB;EACA;EACA;;AzB1tBA;EyBstBF;IAOI;;;AzB/tBF;EyBwtBF;IAYI;IACA;IACA;IACA;IACA;;;;AAUN;EACE;EACA;EACA;EACA;EACA,Y3B7uBM;E2B8uBN;EACA;;AzBzvBE;EyBkvBJ;IAUI;IAEA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EAEA;EACA;EACA;;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;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AzBv0CF;EyB40CE;IACE;IACA;IACA;IACA;IACA;IACA;;;AAKN;EACE;EACA;EACA;EACA;EACA;EACA;;AzB71CA;EyBu1CF;IASI;IACA;IACA;;;AAIJ;EACE;EACA;;AzBx2CA;EyBs2CF;IAMI;IACA;;;AAGF;EACE,a3Bv1CgB;E2Bw1ChB;E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AAEA;EAbF;IAcI;;;AAGF;EACE;EACA;;;AAKJ;EACE;EACA;EACA,K5BteW;;;A4B0eb;EACE;EACA,K5B/eW;E4BgfX;EACA,e5B9eW;;A4BgfX;EACE;EACA;EACA;EACA;EACA,W5B/gBa;E4BghBb,a5BrgBiB;E4BsgBjB,O5BziBQ;E4B0iBR;EACA,Y5Bldc;E4Bmdd;EACA;;AAEA;EACE,O5B5jBS;E4B6jBT;;AAGF;EACE,O5BjkBS;E4BkkBT,a5BlhBiB;E4BmhBjB,qB5BnkBS;;;A4BykBf;EACE;EACA;EACA,K5B9gBW;E4B+gBX;;AAEA;EACE;;;AAKJ;EACE;EACA;EACA;EACA,gB5B3hBW;E4B4hBX;EACA;EACA,K5B/hBW;;A4BiiBX;EACE;EACA;EACA;EACA,K5BtiBS;;A4BwiBT;EANF;IAOI;IACA;;;AAIJ;EACE,Y5B3mBW;E4B4mBX;EACA;EACA;EACA;EACA;EACA;EAEA,O5BrmBI;E4BsmBJ,W5BjlBW;E4BklBX,a5BrkBmB;E4BskBnB,e5BhjBe;E4BijBf;EACA;;AAGF;EACE;;AAEA;EACE;EACA;EACA,kB5BlnBI;E4BmnBJ;EACA,e5B5jBa;E4B6jBb,W5B/lBS;E4BgmBT;EACA,O5B3nBM;E4B4nBN;;AAIJ;EACE;EACA;;AAEA;EACE,W5B5mBW;E4B6mBX,O5BroBM;E4BsoBN,a5B5lBe;E4B6lBf;;AAKF;EACE,W5BpnBS;E4BqnBT,a5B1mBiB;E4B2mBjB,O5BhpBM;E4BipBN,e5BhmBO;;;A4BsmBb;EACE;EACA;EACA,K5BxmBW;;;A4B4mBb;EACE;EACA;EACA;EACA,gB5B/mBW;E4BgnBX;;AAEA;EACE;EACA;;AAGF;EACE,W5BhpBW;E4BipBX,a5BvoBmB;E4BwoBnB,O5B7qBQ;E4B8qBR,e5B7nBS;E4B8nBT;EACA;;AAGF;EACE,W5B5pBW;E4B6pBX,O5BprBQ;E4BqrBR,a5B3oBiB;;A4B6oBjB;EACE,kB5BrrBI;E4BsrBJ;EACA,e5B/nBa;E4BgoBb,S5B3oBO;E4B4oBP;EACA;;AAEA;EACE;EACA,W5B1qBO;E4B2qBP,O5BnsBI;E4BosBJ;EACA;;AAKJ;EACE;EACA;EACA;EACA,kB5B3sBE;E4B4sBF;EACA,e5BppBa;E4BqpBb;;AAEA;EACE,kB5B/sBG;;A4BktBL;EACE;EACA;EACA,W5BlsBO;E4BmsBP,a5BtrBe;E4BurBf,O5B5tBI;E4B6tBJ;EACA;;AAEA;EACE;;AAIJ;EACE;EACA,W5B/sBO;E4BgtBP,O5BvuBI;E4BwuBJ;;AAEA;EACE;;AAIJ;EACE,Y5BvpBU;;A4BypBV;EACE;;AAGF;EACE;;AAIJ;EACE;;;AAOR;EACE;EACA;;AAEA;EACE;;AAEA;EACE;;AAIJ;EACE;EACA;EACA,W5B3vBW;E4B4vBX,a5B/uBmB;E4BgvBnB,O5BrxBQ;E4BsxBR;EACA,kB5BnxBM;;A4BsxBR;EACE;EACA,W5BpwBW;E4BqwBX,O5B5xBQ;;A4B8xBR;EACE;EACA;EACA,kB5B9xBI;E4B+xBJ,e5BxuBa;E4ByuBb;EACA,W5B9wBS;E4B+wBT,O5BjzBS;;;A4ByzBf;EAEE;IACE;IACA,e5BhwBS;;E4BkwBT;IACE;IACA;IACA,W5B/xBS;I4BgyBT;;EAKJ;IACE;IACA;IACA,K5B/wBS;I4BgxBT,gB5B/wBS;;E4BixBT;IACE;IACA;IACA;IACA,K5BvxBO;I4BwxBP;;EAGF;IACE;IACA,W5BtzBS;I4BuzBT;IACA;;EAGF;IACE;IACA;;EAEA;IACE;IACA;IACA,W5Bj0BO;I4Bk0BP;IACA;;EAIJ;IACE;IACA,Y5BjzBO;;E4BmzBP;IACE,W5B50BO;I4B60BP,e5BrzBK;;E4BwzBP;IACE;;EAIJ;IACE;IACA;;EAEA;IACE,W5B11BO;;E4B81BX;IACE;;EAKJ;IACE,gB5B30BS;;E4B60BT;IACE,W5Bv2BW;I4Bw2BX,e5Bj1BO;;E4Bq1BT;IACE,W5B/2BS;I4Bg3BT;IACA;;EAGA;IACE;IACA;;EAEA;IACE;IACA,W5B13BK;I4B23BL;;EAKJ;IACE,S5Bx2BK;I4By2BL;IACA;;EAEA;IACE,W5Bt4BK;I4Bu4BL;IACA;;EAOR;IACE;IACA;IACA;;EAEA;IACE;;EAGF;IACE;IACA,W5B15BS;I4B25BT;IACA;IACA;;EAGF;IACE;IACA,W5Bl6BS;;E4Bo6BT;IACE;IACA;;EAMN;IACE,K5Bn5BS;;E4Bu5BX;IACE,K5Bv5BS;I4Bw5BT;IACA;IACA;;EAIF;IACE;IACA;;EAIF;IACE;IACA;;;ACl+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;IACA;;EAGF;IAEE;IACA;IACA;IACA;IACA;;EAGF;IAEE;IACA;IACA;;EAGF;IAEE;IACA;IACA;IACA;IACA;IACA;IACA;;EAEA;IACE;IACA;IACA;;EAGF;IACE;;EAGF;IACE;IACA;IACA;;EAIJ;IAEE;;EAEA;IACE;IACA;IACA;;EAGF;IACE;IACA;;;AC3NN;EACE,YhCyDY;EgCxDZ,ahCsDW;;AgCpDX;EACE;;;ACDJ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qCQ;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,KjCvuD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;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;;;;AAMN;EAEE;EACA;EACA;;AjCxGE;EiCoGJ;IAQI;IACA;;;AAGF;EAEE;;AAGF;EAEE;;;AAKJ;EACE;EACA;EACA;EACA;EACA;;AjCjIE;EiC4HJ;IASI;;;;AAIJ;EACE;EACA;EACA,KnCxEiB;;AEpEf;EiCyIJ;IAOI;IACA;;;;AjCjJA;EiCsJJ;IAKI;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA,anCtHe;ImCuHf;IA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;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;EoCuhBN;;AAEA;EATF;IAUI,WpCpgBO;;;AoCugBT;EACE,OpC1iBO;;AoC8iBX;EACE;EACA,WpC5gBW;EoC6gBX,OpCtiBM;EoCuiBN,YpC7cY;;AoC+cZ;EANF;IAOI,WpClhBO;;;AoCshBX;EACE,WpCvhBS;EoCwhBT,OpC9iBO;EoC+iBP;EACA;;AAEA;EANF;IAOI,WpC9hBO;IoC+hBP;;;;AASR;EACE;EACA;EACA;;AAEA;EALF;IAMI;;;;AAKJ;EACE,gBpC1hBW;EoC2hBX;EACA,epC1hBW;;;AoC6hBb;EACE;EACA;EACA;EACA,epCniBW;;AoCqiBX;EANF;IAOI;IACA;IACA,KpCziBS;;;;AoC6iBb;EACE;EACA,apCzjBiB;EoC0jBjB;EACA;EACA;;AAEA;EAPF;IAQI;;;;AAIJ;EACE;EACA,apCxkBoB;EoCykBpB;;AAEA;EALF;IAMI;;;;AAKJ;EACE,epCnkBW;;AoCqkBX;EACE;EACA;EACA,KpC1kBS;;AoC6kBX;EACE;EACA;;;AAIJ;EACE;EACA;EACA,KpCvlBW;EoCwlBX;EACA;EACA;EACA,YpChjBgB;;AoCkjBhB;EATF;IAUI;;;AAGF;EACE,OpC5pBW;;AoC+pBb;EACE;EACA;;AAGF;EACE,OpCrqBW;;AoCwqBb;EACE,cpC7mBS;;AoCgnBX;EACE;;;AAKJ;EACE,epCpnBY;;;AoCunBd;EACE;EACA;EACA,SpC1nBY;EoC2nBZ;EACA,WpCvpBe;EoCwpBf,OpCjrBU;EoCkrBV,apCtoBkB;EoCuoBlB;;AAEA;EAVF;IAWI,SpCpoBS;IoCqoBT;IACA;;;AAIF;EAjBF;IAkBI;IACA;IACA;IACA;;;AAGF;EACE,epCnpBS;;AoCqpBT;EACE;;AAIJ;EACE;EACA;EACA,epClpBe;EoCmpBf;;AAGF;EACE,OpC9tBW;EoC+tBX;;AAEA;EACE,OpCjuBW;;;AoCuuBjB;EACE,epC1qBY;;;AoC6qBd;EACE;EACA;EACA;EACA,gBpCprBW;EoCqrBX;EACA,epCrrBW;;AoCurBX;EARF;IASI;IACA;IACA,KpC5rBS;;;;AoCgsBb;EACE;EACA,apC5sBiB;EoC6sBjB;;AAEA;EALF;IAMI;;;;AAIJ;EACE;EACA,apCztBoB;EoC0tBpB;;AAEA;EALF;IAMI;;;;AAIJ;EACE;EACA;EACA;EACA,SpCptBY;EoCqtBZ;EACA,WpCjvBe;EoCkvBf,OpC3wBU;EoC4wBV,apChuBkB;EoCiuBlB;;AAEA;EAXF;IAYI,SpC9tBS;IoC+tBT;IACA;;;;AAKJ;EACE;EACA;EACA;EACA,KpC1uBW;;AoC4uBX;EANF;IAOI;IACA;IACA,KpChvBS;;;AoCmvBX;EACE;EACA,KpCpvBS;;AoCsvBT;EAJF;IAKI,KpCxvBO;;;AoC4vBX;EACE;EACA,KpC7vBS;;AoC+vBT;EAJF;IAKI,KpCjwBO;;;;AoCuwBb;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,apC7yBoB;EoC8yBpB;EACA,apC5xBiB;EoC6xBjB;EACA;EACA,YpC3uBgB;;AoC6uBhB;EAhBF;IAiBI;IACA;IACA;IACA;IACA;IACA;;;AAGF;EACE;;AAGF;EACE;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,apC/0BoB;EoCg1BpB;EACA,apC9zBiB;EoC+zBjB,OpCl2BM;EoCm2BN;EACA,YpC7wBgB;;AoC+wBhB;EAhBF;IAiBI;IACA;IACA;IACA;IACA;IACA;;;AAGF;EACE;;AAGF;EACE;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,apCj3BoB;EoCk3BpB;EACA,apCh2BiB;EoCi2BjB,OpCp4BM;EoCq4BN;EACA,YpC/yBgB;;AoCizBhB;EAhBF;IAiBI;IACA;IACA;IACA;IACA;IACA;;;AAGF;EACE;;AAGF;EACE;;;AAOJ;EACE;EACA;EACA;;AAEA;EALF;IAMI;;;;AAMJ;EACE;EACA;EACA;;AAEA;EALF;IAMI;IACA;IACA,KpCn4BS;;;AoCu4BX;EACE;EACA;EACA;EACA;EACA;EACA;EACA,YpC57BI;EoC67BJ;EACA;EACA;;AAEA;EACE;;AAIJ;EACE;EACA,apCx7BkB;EoCy7BlB,WpCn7Ba;EoCo7Bb;EACA;EACA;EACA;;AAEA;EACE,OpCn9BM;;AoCw9BV;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,OpC99BI;EoC+9BJ;EACA,YpCz4Bc;EoC04Bd;EACA,apCr7BS;;AoCu7BT;EACE;;AAGF;EACE;EACA;;AAKJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,apC1+BkB;EoC2+BlB;EACA,apC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E,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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E,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;IAOI;;;;AAKJ;EACE;EACA;EACA;EACA,exC4CY;;AwC1CZ;EANF;IAOI;IACA;IACA,KxCqCS;;;;AwChCX;EACE,WxCKW;EwCJX,OxCnBQ;EwCoBR,axCckB;EwCblB,exCyBS;;AwCtBX;EACE,WxCGY;EwCFZ,axCWe;EwCVf,OxC5BQ;;;AwCgCZ;EACE;EACA,KxCeW;;;AwCZb;EACE;EACA,WxCba;EwCcb,YxCUW;;;AwCCX;EACE;;AAGA;EACE,OxCxDM;EwCyDN;EACA,YxCgCY;;AwC9BZ;EACE,OxCxEO;;AEIX;EsCyEF;IAII,kBxCnEE;IwCoEF;;;AAIA;EACE;;;AAOR;EACE;EACA;EACA;EACA;EACA;EACA;EACA,OxC/Fc;EwCgGd,exChCiB;EwCiCjB,WxCpEa;EwCqEb,axCzDmB;EwC0DnB;;AAGA;EAdF;IAeI;;;;AAKJ;EACE;EACA;EACA,OxCzGU;;AwC2GV;EACE;EACA,exC5DS;EwC6DT;;AAGF;EACE,WxCvFW;EwCwFX,axC/EmB;EwCgFnB,OxCrHQ;EwCsHR,exCrES;;AwCwEX;EACE,WxCjGa;EwCkGb,OxC1HQ;;;AwCkIZ;EACE;EACA;EACA,SxClFY;;AwCoFZ;EALF;IAMI;;;;AAKJ;EACE,OxC5IM;EwC6IN,exC7FW;EwC8FX;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA,exC3FmB;;AwC8FrB;EACE;;;AAIJ;EACE;EACA;EACA,KxCpHW;EwCqHX;EACA;;AAEA;EAPF;IAQI;IACA;;;;AAIJ;EACE;;;AAGF;EACE;EACA;EACA,exCvHqB;EwCwHrB;EACA;EACA;EACA,WxC9Jc;EwC+Jd,axCvJiB;EwCwJjB;EACA,OxC5LM;EwC6LN;EACA,YxClLU;;;AwCqLZ;EACE;;AAEA;EACE,WxC3KY;EwC4KZ,axCnKe;EwCoKf,exC1JS;;AwC6JX;EACE,WxCrLa;EwCsLb;EACA,exC/JS;;;AwCmKb;EACE;EACA,KxCtKW;EwCuKX;;AAEA;EALF;IAMI;;;AAGF;AAAA;EAEE;EACA,OxC7NI;EwC8NJ;EACA;;;AAKJ;EACE,YxCrOM;EwCsON,exC5KiB;EwC6KjB,SxCvLW;EwCwLX,YxC9NU;EwC+NV,exC1LW;;AwC4LX;EACE,WxCpNW;EwCqNX,axC3MmB;EwC4MnB,OxCjPQ;EwCkPR,exChMS;EwCiMT,gBxCnMS;EwCoMT;;;AAIJ;EACE;EACA;EACA,KxCzMW;;AwC2MX;EALF;IAMI;;;;AAIJ;EACE;EACA;EACA,KxCtNW;;AwCwNX;EACE;;;AAIJ;EACE,WxCrPa;EwCsPb,axCzOqB;EwC0OrB,OxC9QU;EwC+QV;EACA;EACA,KxCnOW;;AwCqOX;EACE,OxChSW;EwCiSX,WxC9PW;;;AwCkQf;EACE,WxClQe;EwCmQf,OxC5RU;EwC6RV;;;AAGF;EACE;EACA;EACA,KxCpPW;EwCqPX;EACA,exC1OiB;EwC2OjB,WxC9Qa;EwC+Qb,axCnQmB;;AwCqQnB;EACE;EACA;;AAEA;EACE,kBxCpTS;;AwCwTb;EACE;EACA,OxCnTQ;;AwCqTR;EACE,kBxCtTM;;;AwC2TZ;EACE;EACA;EACA,exC9PqB;EwC+PrB;;;AAGF;EACE;IACE;;EAEF;IACE;;;AAKJ;EACE;EACA,KxC9RW;EwC+RX;EACA,axC/RW;;AwCiSX;EANF;IAOI;;;;AAOJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA,axC7TiB;EwC8TjB,OxCjWM;EwCkWN;;AAGA;EACE;;AAIF;EACE;;;AAKJ;EACE;EACA;EACA,YxClUY;;;AwCwUd;EACE;EACA,KxC/UW;EwCgVX;;;AAGF;EACE;;;AAOF;EAEE;IACE;;EAGF;IACE;;EAGF;IACE;IACA;IACA,KxCrWS;IwCsWT;IACA;;EAGA;IACE;;EAEA;IACE;IACA;IACA;IACA,OxC5ZA;IwC6ZA;IACA;;EAEA;IACE;IACA,axCnYS;IwCoYT;;EAKJ;IACE;IACA;;EAEA;IACE;IACA;IACA;IACA;IACA;;EAMN;IACE;IACA,YxC7bE;IwC8bF,exCpYa;IwCqYb;;EAGA;IACE;IACA;IACA;IACA;IACA;IACA;;EAEA;IACE;IACA,axCzaS;IwC0aT,OxC7cF;IwC8cE;IACA;;EAGA;IACE;;EAIF;IACE;IACA;IACA;;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;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KJ;EACE;EACA;EACA;EACA;EACA,OA1EgB;EA2EhB;;AAEA;EACE;;AAKJ;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA,YAxFqB;EAyFrB;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAIF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA,YAnHkB;;AAsHpB;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE,kBA7IiB;;AAgJnB;EACE,kBA/Ic;;AAkJhB;EACE,kBAlJc;;AAsJlB;EACE;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA,OAjKgB;EAkKhB;;;AAQJ;EACE;IACE;;EAEA;IACE;;EAGF;IACE;IACA;;EAGF;IACE;;EAGF;IACE;;EAGF;IACE;IACA;;;AAKN;EACE;IACE;;EAEA;IACE;;EAGF;IACE;;EAGF;IACE;IACA;;EAGF;IACE;;EAGF;IACE;IACA;;EAGF;IACE;;EAGF;IACE;;EAGF;IACE;IACA;;EAEA;IACE;;EAIJ;IACE;;EAGF;IACE;;;AASN;EACE;EACA;EACA;;;AAIF;EACE;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;;;AAKJ;EACE;EACA;EACA;EACA;EACA;;;AAIF;EACE;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;;AAMN;EACE;;AAEA;EACE;EACA;;;AAKJ;EACE;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAIJ;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;;AASJ;EACE;EACA;EACA;EACA,kBA9YW;;AAiZX;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA,OA9ZmB;EA+ZnB;;AAGF;EACE;EACA;EACA,kBAramB;EAsanB;;AAIA;EACE;EACA;EACA;EACA;EACA,OA1ac;EA2ad;;AAKJ;EACE;EACA;EACA;EACA,YAhbqB;EAibrB;;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,kBAremB;EAsenB;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA,OA3fgB;EA4fhB;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAIF;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA,OAxhBgB;EAyhBhB;;AAEA;EACE;;AAIJ;EACE;EACA;;AAIF;EACE;EACA;;AAGF;EACE;EACA;EACA,YAnjBmB;EAojBnB;EACA;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;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;;AAKN;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;;AAIJ;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAIJ;EACE;EACA;EACA;EACA;;;AAIJ;EACE;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;;AAGF;AAAA;EAEE;;;AAKN;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;;AAIJ;EACE;EACA;;AAEA;EACE;EACA;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;;;ACx7BN;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 diff --git a/src/main/resources/static/css/main.min.css b/src/main/resources/static/css/main.min.css index 9497a3a..154efc4 100644 --- a/src/main/resources/static/css/main.min.css +++ b/src/main/resources/static/css/main.min.css @@ -1 +1 @@ -*{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:"Pretendard",-apple-system,BlinkMacSystemFont,"Segoe UI","Noto Sans KR",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(75,155,255,.2);color:#1a1a2e}::-moz-selection{background-color:rgba(75,155,255,.2);color:#1a1a2e}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-gradient{background:linear-gradient(135deg, #4B9BFF 0%, #2E7FF7 100%);-webkit-background-clip:text;-webkit-text-fill-color:rgba(0,0,0,0);background-clip:text}.text-gradient-accent{background:linear-gradient(135deg, #00D4FF 0%, #4B9BFF 100%);-webkit-background-clip:text;-webkit-text-fill-color:rgba(0,0,0,0);background-clip:text}.text-gradient-warm{background:linear-gradient(135deg, #FFD93D 0%, #FF6B6B 100%);-webkit-background-clip:text;-webkit-text-fill-color:rgba(0,0,0,0);background-clip:text}.text-primary{color:#4b9bff}.text-secondary{color:#2e7ff7}.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:#4b9bff}a:hover{color:#2e7ff7}blockquote{padding:16px;margin:16px 0;border-left:4px solid #4b9bff;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%;background:linear-gradient(45deg, transparent 30%, rgba(255, 255, 255, 0.2) 50%, transparent 70%);transform:rotate(45deg);transition:transform .6s ease;transform:translateX(-200%)}.hover-shine:hover::before{transform:translateX(200%) rotate(45deg)}: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;--gradient-primary: linear-gradient(135deg, #4B9BFF 0%, #2E7FF7 100%);--gradient-accent: linear-gradient(135deg, #00D4FF 0%, #4B9BFF 100%);--gradient-warm: linear-gradient(135deg, #FFD93D 0%, #FF6B6B 100%);--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)}.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;right:0;background:hsla(0,0%,100%,.95);backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);box-shadow:var(--shadow-sm);z-index:1000;height:80px;transition:all .3s ease}.header-content{display:flex;align-items:center;justify-content:space-between;height:80px;max-width:1280px;margin:0 auto;padding:0 20px}.header-left{display:flex;align-items:center}.header-left .logo{height:45px;width:auto}.logo-wrapper{display:flex;align-items:center;gap:12px}.logo{display:flex;align-items:center;gap:12px;height:45px;z-index:10}.logo a{display:flex;align-items:center;gap:12px;height:100%;text-decoration:none}.logo img{height:45px;width:auto}.logo-text{font-size:16px;font-weight:700;color:var(--primary-blue);padding:4px 12px;background:var(--light-bg);border-radius:20px;text-decoration:none;display:inline-block;transition:all .3s ease}.logo-text:hover{background:var(--primary-blue);color:var(--white);transform:translateY(-1px);box-shadow:var(--shadow-md)}.logo-text:visited{color:var(--primary-blue)}.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 .mobile-logo{height:32px;width:auto}.mobile-center{flex:1;text-align:center}.mobile-center .mobile-title{font-size:18px;font-weight:700;color:var(--text-dark);margin:0}.mobile-right .mobile-menu-btn{width:40px;height:40px;background:rgba(0,0,0,0);border:none;cursor:pointer;display:flex;flex-direction:column;justify-content:center;align-items:center;gap:4px;padding:8px;border-radius:8px;transition:background-color .3s ease}.mobile-right .mobile-menu-btn:hover{background:var(--gray-bg)}.mobile-right .mobile-menu-btn .hamburger-line{width:24px;height:2px;background:var(--text-dark);transition:all .3s ease;border-radius:1px}.mobile-right .mobile-menu-btn.active .hamburger-line:nth-child(1){transform:rotate(45deg) translateY(6px)}.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(-6px)}.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:var(--white);transform:translateY(100%);transition:transform .3s ease;display:flex;flex-direction:column}.mobile-drawer .drawer-header{display:flex;align-items:center;justify-content:space-between;padding:20px;border-bottom:2px solid var(--border-gray);background:var(--white);box-shadow:var(--shadow-sm);min-height:60px}.mobile-drawer .drawer-header .drawer-logo{display:flex;align-items:center;gap:12px}.mobile-drawer .drawer-header .drawer-logo .logo{height:32px;width:auto}.mobile-drawer .drawer-header .drawer-logo .logo-text{font-size:16px;font-weight:700;color:var(--primary-blue)}.mobile-drawer .drawer-header .drawer-close{width:40px;height:40px;background:rgba(0,0,0,0);border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;border-radius:8px;font-size:18px;color:var(--text-gray);transition:all .3s ease}.mobile-drawer .drawer-header .drawer-close:hover{background:var(--white);color:var(--text-dark)}.mobile-drawer .drawer-nav{flex:1;padding:0;overflow-y:auto;background:var(--white)}.mobile-drawer .drawer-nav .drawer-menu{list-style:none;margin:0;padding:20px 0}.mobile-drawer .drawer-nav .drawer-menu li{margin-bottom:8px}.mobile-drawer .drawer-nav .drawer-menu li .drawer-link{display:block;padding:16px 24px;margin:0 20px;color:var(--text-dark);text-decoration:none;font-size:18px;font-weight:500;border-radius:12px;transition:all .3s ease}.mobile-drawer .drawer-nav .drawer-menu li .drawer-link:hover,.mobile-drawer .drawer-nav .drawer-menu li .drawer-link:active{background:var(--light-bg);color:var(--primary-blue);transform:translateX(4px);box-shadow:var(--shadow-sm)}.mobile-drawer .drawer-actions{padding:24px;border-top:2px solid var(--border-gray);background:var(--white);box-shadow:0 -4px 12px rgba(0,0,0,.05)}.mobile-drawer .drawer-actions .drawer-login-btn{display:block;text-align:center;padding:14px 20px;color:var(--text-dark);text-decoration:none;border:2px solid var(--primary-blue);border-radius:12px;font-weight:600;font-size:16px;margin-bottom:12px;transition:all .3s ease}.mobile-drawer .drawer-actions .drawer-login-btn:hover{background:var(--light-bg);color:var(--primary-blue)}.mobile-drawer .drawer-actions .drawer-signup-btn{display:flex;align-items:center;justify-content:center;gap:8px;padding:14px 20px;background:var(--gradient-primary);color:var(--white);text-decoration:none;border-radius:12px;font-weight:600;font-size:16px;margin-bottom:12px;transition:all .3s ease;box-shadow:var(--shadow-md)}.mobile-drawer .drawer-actions .drawer-signup-btn:hover{transform:translateY(-2px);box-shadow:var(--shadow-lg)}.mobile-drawer .drawer-actions .drawer-search-btn{display:flex;align-items:center;justify-content:center;gap:8px;width:100%;padding:14px 20px;background:var(--gray-bg);color:var(--text-dark);border:none;border-radius:12px;font-weight:500;font-size:16px;cursor:pointer;transition:all .3s ease}.mobile-drawer .drawer-actions .drawer-search-btn:hover{background:var(--light-bg);color:var(--primary-blue)}.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:#4b9bff}.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-link{display:flex;align-items:center;gap:6px;text-decoration:none;color:var(--text-dark);font-weight:500;font-size:15px;padding:8px 16px;border-radius:8px;transition:all .3s ease}.nav-link:hover{background:var(--light-bg);color:var(--primary-blue);transform:translateY(-2px)}.nav-icon{font-size:18px}.signup-btn{display:flex;align-items:center;gap:8px;text-decoration:none;background:var(--gradient-primary);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)}body{padding-top:80px}@media(max-width: 768px){.global-header{height:60px}.global-header .container .header-content{padding:0 16px;height:60px}body{padding-top:60px}}@media(max-width: 480px){.global-header .header-content{padding:0 12px}.logo .logo-text{display:none}}.global-footer{background:#1a1a2e;color:#fff;padding:80px 0 40px}@media(max-width: 768px){.global-footer{padding:48px 0 32px}}.footer-top{display:grid;grid-template-columns:2fr 3fr;gap:80px;margin-bottom:64px;max-width:1280px;margin-left:auto;margin-right:auto;padding:0 24px}@media(max-width: 1024px){.footer-top{grid-template-columns:1fr;gap:40px}}.footer-brand{max-width:360px}.footer-brand .footer-logo{height:48px;margin-bottom:24px}.footer-brand .footer-desc{font-size:14px;line-height:1.8;color:hsla(0,0%,100%,.7);margin-bottom:24px}.footer-brand .footer-newsletter{margin-bottom:32px}.footer-brand .footer-newsletter h4{font-size:18px;font-weight:600;margin-bottom:16px}.footer-brand .footer-newsletter .newsletter-form{display:flex;gap:8px}.footer-brand .footer-newsletter .newsletter-form input{flex:1;padding:8px 16px;background:hsla(0,0%,100%,.1);border:1px solid hsla(0,0%,100%,.2);border-radius:8px;color:#fff;font-size:14px}.footer-brand .footer-newsletter .newsletter-form input::placeholder{color:hsla(0,0%,100%,.5)}.footer-brand .footer-newsletter .newsletter-form input:focus{background:hsla(0,0%,100%,.15);border-color:#4b9bff;outline:none}.footer-brand .footer-newsletter .newsletter-form button{padding:8px 24px;background:linear-gradient(135deg, #4B9BFF 0%, #2E7FF7 100%);color:#fff;border-radius:8px;font-weight:600;font-size:14px;transition:all .3s ease}.footer-brand .footer-newsletter .newsletter-form button:hover{transform:translateY(-2px);box-shadow:0 4px 12px rgba(75,155,255,.1)}.social-links{display:flex;gap:16px}.social-links a{width:44px;height:44px;background:hsla(0,0%,100%,.1);border-radius:12px;display:flex;justify-content:center;align-items:center;color:#fff;transition:all .3s ease;font-size:20px}.social-links a:hover{background:#4b9bff;transform:translateY(-3px)}.social-links a.facebook:hover{background:#1877f2}.social-links a.twitter:hover{background:#1da1f2}.social-links a.linkedin:hover{background:#0077b5}.social-links a.github:hover{background:#333}.social-links a.youtube:hover{background:red}.footer-links{display:grid;grid-template-columns:repeat(4, 1fr);gap:40px}@media(max-width: 1024px){.footer-links{grid-template-columns:repeat(2, 1fr)}}@media(max-width: 768px){.footer-links{grid-template-columns:1fr;text-align:center}}.footer-column h4{font-size:16px;font-weight:700;margin-bottom:24px;color:#fff}.footer-column ul{list-style:none}.footer-column ul li{margin-bottom:16px}.footer-column ul li a{color:hsla(0,0%,100%,.7);font-size:14px;transition:all .3s ease;position:relative}.footer-column ul li a::after{content:"";position:absolute;bottom:-2px;left:0;width:0;height:1px;background:#00d4ff;transition:all .3s ease}.footer-column ul li a:hover{color:#00d4ff}.footer-column ul li a:hover::after{width:100%}.footer-column .footer-badge{display:inline-flex;align-items:center;gap:4px;padding:4px 8px;background:rgba(107,207,127,.2);color:#6bcf7f;border-radius:50px;font-size:12px;font-weight:600;margin-left:8px}.footer-bottom{padding-top:40px;border-top:1px solid hsla(0,0%,100%,.1);max-width:1280px;margin:0 auto;padding-left:24px;padding-right:24px}.footer-legal{display:flex;justify-content:space-between;align-items:center}@media(max-width: 768px){.footer-legal{flex-direction:column;gap:24px;text-align:center}}.footer-legal p{color:hsla(0,0%,100%,.5);font-size:14px;margin:0}.footer-legal .legal-links{display:flex;gap:24px}.footer-legal .legal-links a{color:hsla(0,0%,100%,.7);font-size:14px;transition:all .3s ease}.footer-legal .legal-links a:hover{color:#fff}.footer-decoration{position:relative;overflow:hidden}.footer-decoration::before{content:"";position:absolute;top:-100px;right:-100px;width:300px;height:300px;background:radial-gradient(circle, rgba(75, 155, 255, 0.1) 0%, transparent 70%);border-radius:50%}.footer-decoration::after{content:"";position:absolute;bottom:-150px;left:-150px;width:400px;height:400px;background:radial-gradient(circle, rgba(0, 212, 255, 0.05) 0%, transparent 70%);border-radius:50%}.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 24px}@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-gradient{background:linear-gradient(180deg, #FFFFFF 0%, #EFF6FF 100%)}.section-bg-gradient-reverse{background:linear-gradient(180deg, #EFF6FF 0%, #FFFFFF 100%)}.section-bg-primary{background:linear-gradient(135deg, #4B9BFF 0%, #2E7FF7 100%);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:#4b9bff;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-title .title-highlight{background:linear-gradient(135deg, #00D4FF 0%, #4B9BFF 100%);-webkit-background-clip:text;-webkit-text-fill-color:rgba(0,0,0,0);background-clip:text}.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:linear-gradient(135deg, #4B9BFF 0%, #2E7FF7 100%);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:#fff;color:#4b9bff;border:2px solid #4b9bff}.btn-secondary:hover{background:#eff6ff;transform:translateY(-3px)}.btn-accent{background:linear-gradient(135deg, #00D4FF 0%, #4B9BFF 100%);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{background:linear-gradient(135deg, #FFD93D 0%, #FF6B6B 100%);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%,51.568627451%);transform:translateY(-3px)}.btn-danger{background:#ff6b6b;color:#fff}.btn-danger:hover{background:#ff3838;transform:translateY(-3px)}.btn-ghost{background:rgba(0,0,0,0);color:#4b9bff;border:2px solid rgba(0,0,0,0)}.btn-ghost:hover{background:rgba(75,155,255,.1);border-color:#4b9bff}.btn-outline{background:rgba(0,0,0,0);color:#1a1a2e;border:2px solid #e2e8f0}.btn-outline:hover{border-color:#4b9bff;color:#4b9bff;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;background:linear-gradient(135deg, #4B9BFF 0%, #2E7FF7 100%);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:#4b9bff;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)}.card{background:#fff;border-radius:20px;padding:32px;transition:all .3s ease}.card:hover{transform:translateY(-5px);box-shadow:0 8px 24px rgba(75,155,255,.15)}.card{position:relative;overflow:hidden}.card::before{content:"";position:absolute;top:-50%;left:-50%;width:200%;height:200%;background:linear-gradient(45deg, transparent 30%, rgba(75, 155, 255, 0.03) 50%, transparent 70%);transform:rotate(45deg);transition:all .5s ease;opacity:0}.card:hover::before{animation:shimmer .6s ease;opacity:1}.api-card{background:#fff;border-radius:20px;padding:32px;transition:all .3s ease}.api-card:hover{transform:translateY(-5px);box-shadow:0 8px 24px rgba(75,155,255,.15)}.api-card{text-align:center;border:2px solid #e2e8f0}.api-card:hover{border-color:#4b9bff;transform:translateY(-8px)}.api-card-popular{border-color:#ffd93d;background:linear-gradient(180deg, rgba(255, 217, 61, 0.05) 0%, #FFFFFF 100%)}.api-card-new{border-color:#6bcf7f;background:linear-gradient(180deg, rgba(107, 207, 127, 0.05) 0%, #FFFFFF 100%)}.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:#4b9bff;border-radius:16px;transition:all .3s ease}.api-card:hover .api-icon{transform:scale(1.1);background:linear-gradient(135deg, #4B9BFF 0%, #2E7FF7 100%);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:#4b9bff;font-weight:600;font-size:14px;transition:all .3s ease}.api-card .api-link:hover{gap:8px;color:#2e7ff7}.experience-card{background:#fff;border-radius:20px;padding:32px;transition:all .3s ease}.experience-card:hover{transform:translateY(-5px);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;background:linear-gradient(135deg, #4B9BFF 0%, #2E7FF7 100%);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:#4b9bff}.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{transform:translateY(-5px);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;background:linear-gradient(135deg, #4B9BFF 0%, #2E7FF7 100%);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{transform:translateY(-5px);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:#4b9bff;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{transform:translateY(-5px);box-shadow:0 8px 24px rgba(75,155,255,.15)}.pricing-card{text-align:center;position:relative}.pricing-card.featured{border:2px solid #4b9bff;transform:scale(1.05)}.pricing-card.featured .pricing-badge{position:absolute;top:-14px;left:50%;transform:translateX(-50%);padding:4px 16px;background:linear-gradient(135deg, #4B9BFF 0%, #2E7FF7 100%);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;background:linear-gradient(135deg, #4B9BFF 0%, #2E7FF7 100%);-webkit-background-clip:text;-webkit-text-fill-color:rgba(0,0,0,0);background-clip:text}.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}.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:"Pretendard",-apple-system,BlinkMacSystemFont,"Segoe UI","Noto Sans KR",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:#4b9bff;box-shadow:0 0 0 3px rgba(75,155,255,.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:#4b9bff}.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}}.modal-backdrop{position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,.5);z-index:400;opacity:0;visibility:hidden;transition:all .3s ease}.modal-backdrop.show{opacity:1;visibility:visible}.modal{position:fixed;top:0;left:0;right:0;bottom:0;z-index:500;display:flex;align-items:center;justify-content:center;padding:24px;opacity:0;visibility:hidden;transition:all .3s ease}.modal.show{opacity:1;visibility:visible}.modal.show .modal-dialog{transform:scale(1)}.modal-dialog{position:relative;background:#fff;border-radius:16px;box-shadow:0 16px 40px rgba(75,155,255,.2);max-width:500px;width:100%;max-height:90vh;display:flex;flex-direction:column;transform:scale(0.95);transition:all .3s ease}.modal-dialog.modal-sm{max-width:400px}.modal-dialog.modal-lg{max-width:800px}.modal-dialog.modal-xl{max-width:1200px}.modal-dialog.modal-fullscreen{max-width:100%;max-height:100%;height:100%;margin:0;border-radius:0}.modal-header{padding:24px;border-bottom:1px solid #e2e8f0;display:flex;align-items:center;justify-content:space-between}.modal-header .modal-title{font-size:24px;font-weight:600;color:#1a1a2e;margin:0}.modal-header .modal-close{width:32px;height:32px;display:flex;justify-content:center;align-items:center;background:rgba(0,0,0,0);border:none;border-radius:8px;color:#64748b;font-size:24px;cursor:pointer;transition:all .3s ease}.modal-header .modal-close:hover{background:#f8fafc;color:#1a1a2e}.modal-body{padding:24px;flex:1;overflow-y:auto;color:#1a1a2e;line-height:1.6}.modal-footer{padding:24px;border-top:1px solid #e2e8f0;display:flex;gap:16px;justify-content:flex-end}.modal-footer.modal-footer-center{justify-content:center}.modal-footer.modal-footer-between{justify-content:space-between}.modal-alert .modal-dialog{max-width:400px}.modal-alert .modal-body{text-align:center;padding:32px}.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(75,155,255,.1);color:#4b9bff}.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}.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:#4b9bff}.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:#4b9bff;border-bottom-color:#4b9bff}.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:#4b9bff;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:#4b9bff}.sidebar-nav .nav-menu .nav-link:hover .nav-icon{color:#4b9bff}.sidebar-nav .nav-menu .nav-link.active{background:#eff6ff;color:#4b9bff;font-weight:500}.sidebar-nav .nav-menu .nav-link.active .nav-icon{color:#4b9bff}.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:#4b9bff;color:#fff;border-color:#4b9bff}.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:#4b9bff;color:#4b9bff}.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;background:linear-gradient(135deg, #4B9BFF 0%, #2E7FF7 100%);overflow:hidden}.hero-section{position:relative;padding:160px 0 100px;background:linear-gradient(180deg, #EFF6FF 0%, #FFFFFF 100%);overflow:hidden}@media(max-width: 768px){.hero-section{padding:120px 0 60px}}.hero-patterns{position:absolute;top:0;left:0;right:0;bottom:0;pointer-events:none}.hero-patterns .pattern-circle{position:absolute;border-radius:50%;opacity:.1}.hero-patterns .pattern-circle.pattern-1{width:400px;height:400px;background:#00d4ff;border-radius:50%;opacity:.1;position:absolute;top:-200px;right:-100px;animation:float 20s ease-in-out infinite}.hero-patterns .pattern-circle.pattern-2{width:300px;height:300px;background:#ffd93d;border-radius:50%;opacity:.1;position:absolute;bottom:-150px;left:-50px;animation:float 15s ease-in-out infinite reverse}.hero-patterns .pattern-dots{position:absolute;top:50%;left:10%;width:100px;height:100px;background-image:radial-gradient(circle, #A78BFA 2px, transparent 2px);background-size:20px 20px;opacity:.3}.hero-content{text-align:center;position:relative;z-index:1;max-width:1280px;margin:0 auto;padding:0 24px}.hero-badge{display:inline-flex;align-items:center;gap:8px;padding:8px 24px;background:#fff;border:2px solid #ffd93d;border-radius:50px;margin-bottom:24px;font-size:14px;font-weight:600;color:#1a1a2e;animation:slideInDown .6s ease-out}.hero-badge .badge-icon{font-size:20px;animation:bounce 2s infinite}.main-headline{font-size:56px;font-weight:800;line-height:1.2;margin-bottom:24px;color:#1a1a2e;animation:slideInUp .8s ease-out}@media(max-width: 768px){.main-headline{font-size:40px}}@media(max-width: 480px){.main-headline{font-size:32px}}.main-headline .headline-highlight{background:linear-gradient(135deg, #00D4FF 0%, #4B9BFF 100%);-webkit-background-clip:text;-webkit-text-fill-color:rgba(0,0,0,0);background-clip:text}.sub-headline{font-size:20px;font-weight:400;line-height:1.6;margin-bottom:40px;color:#64748b;animation:slideInUp .9s ease-out}@media(max-width: 768px){.sub-headline{font-size:16px}}.hero-buttons{display:flex;gap:16px;justify-content:center;margin-bottom:64px;animation:slideInUp 1s ease-out}@media(max-width: 768px){.hero-buttons{flex-direction:column;align-items:center}}.hero-buttons .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}.hero-buttons .btn:hover{transform:translateY(-3px)}.hero-buttons .btn.btn-primary{background:linear-gradient(135deg, #4B9BFF 0%, #2E7FF7 100%);color:#fff;box-shadow:0 4px 12px rgba(75,155,255,.1)}.hero-buttons .btn.btn-primary:hover{box-shadow:0 16px 40px rgba(75,155,255,.2)}.hero-buttons .btn.btn-secondary{background:#fff;color:#4b9bff;border:2px solid #4b9bff}.hero-buttons .btn.btn-secondary:hover{background:#eff6ff}@media(max-width: 480px){.hero-buttons .btn{width:100%;justify-content:center}}.hero-stats{display:flex;justify-content:center;gap:64px;animation:fadeIn 1.2s ease-out}@media(max-width: 768px){.hero-stats{flex-direction:column;gap:24px}}.hero-stats .stat-item{display:flex;flex-direction:column;align-items:center}.hero-stats .stat-item .stat-number{font-size:40px;font-weight:800;background:linear-gradient(135deg, #4B9BFF 0%, #2E7FF7 100%);-webkit-background-clip:text;-webkit-text-fill-color:rgba(0,0,0,0);background-clip:text}.hero-stats .stat-item .stat-label{font-size:14px;color:#64748b;font-weight:500}.hero-image{position:relative;margin-top:64px;text-align:center}.hero-image img{max-width:100%;height:auto;border-radius:16px;box-shadow:0 16px 40px rgba(75,155,255,.2)}.hero-image .hero-decoration{position:absolute;width:100%;height:100%;top:0;left:0;pointer-events:none}.hero-image .hero-decoration::before,.hero-image .hero-decoration::after{content:"";position:absolute;border-radius:50%}.hero-image .hero-decoration::before{width:60px;height:60px;background:linear-gradient(135deg, #FFD93D 0%, #FF6B6B 100%);top:-30px;right:10%;animation:bounce 2s infinite}.hero-image .hero-decoration::after{width:40px;height:40px;background:linear-gradient(135deg, #00D4FF 0%, #4B9BFF 100%);bottom:-20px;left:15%;animation:bounce 2s infinite .5s}.api-search-section{padding:64px 0;background:linear-gradient(180deg, #FFFFFF 0%, #EFF6FF 100%);position:relative}.api-search-section::before{content:"";position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);width:600px;height:600px;background:radial-gradient(circle, rgba(75, 155, 255, 0.03) 0%, transparent 70%);pointer-events:none}.api-search-section .search-container{max-width:800px;margin:0 auto;text-align:center;position:relative;z-index:1}.api-search-section .search-form{margin-bottom:32px;animation:slideInUp .8s ease-out}.api-search-section .search-wrapper{display:flex;gap:16px;padding:0 24px}@media(max-width: 768px){.api-search-section .search-wrapper{flex-direction:column;gap:8px}}.api-search-section .search-input{flex:1;padding:16px 32px;font-size:16px;border:2px solid #e2e8f0;border-radius:50px;background:#fff;transition:all .3s ease;outline:none}.api-search-section .search-input::placeholder{color:#94a3b8}.api-search-section .search-input:focus{border-color:#4b9bff;box-shadow:0 0 0 4px rgba(75,155,255,.1)}@media(max-width: 768px){.api-search-section .search-input{width:100%;padding:16px 24px}}.api-search-section .search-button{padding:16px 40px;background:linear-gradient(135deg, #4B9BFF 0%, #2E7FF7 100%);color:#fff;border:none;border-radius:50px;font-size:16px;font-weight:600;cursor:pointer;transition:all .3s ease;display:flex;align-items:center;gap:8px;box-shadow:0 4px 12px rgba(75,155,255,.1);white-space:nowrap}.api-search-section .search-button i{font-size:20px}.api-search-section .search-button:hover{transform:translateY(-2px);box-shadow:0 16px 40px rgba(75,155,255,.2);background:linear-gradient(135deg, rgb(49.5, 140.8333333333, 255) 0%, rgb(21.4400921659, 111.9585253456, 246.0599078341) 100%)}.api-search-section .search-button:active{transform:translateY(0);box-shadow:0 2px 4px rgba(0,0,0,.05)}@media(max-width: 768px){.api-search-section .search-button{width:100%;justify-content:center;padding:16px 32px}}.api-search-section .hashtag-suggestions{display:flex;align-items:center;justify-content:center;gap:16px;flex-wrap:wrap;padding:0 24px;animation:fadeIn .8s ease-out .2s both}@media(max-width: 768px){.api-search-section .hashtag-suggestions{gap:8px;justify-content:flex-start}}.api-search-section .hashtag-label{font-size:14px;color:#64748b;font-weight:500}@media(max-width: 768px){.api-search-section .hashtag-label{width:100%;margin-bottom:4px}}.api-search-section .hashtag{display:inline-flex;align-items:center;padding:4px 24px;background:#fff;border:1px solid #e2e8f0;border-radius:50px;font-size:14px;color:#1a1a2e;text-decoration:none;transition:all .3s ease;position:relative;overflow:hidden}.api-search-section .hashtag::before{content:"";position:absolute;top:0;left:-100%;width:100%;height:100%;background:linear-gradient(135deg, #4B9BFF 0%, #2E7FF7 100%);transition:left .3s ease;z-index:0}.api-search-section .hashtag:hover{border-color:#4b9bff;transform:translateY(-2px);box-shadow:0 2px 4px rgba(0,0,0,.05);color:#fff;position:relative;z-index:1}.api-search-section .hashtag:hover::before{left:0}@media(max-width: 768px){.api-search-section .hashtag{padding:4px 16px;font-size:12px}}.api-showcase{padding:100px 0;background:#fff}.info-section{padding:100px 0;background:#fff;position:relative}.info-section::before{content:"";position:absolute;top:0;left:0;right:0;height:1px;background:linear-gradient(90deg, transparent 0%, #E2E8F0 20%, #E2E8F0 80%, transparent 100%)}.info-section .info-wrapper{display:flex;flex-direction:column;align-items:center;gap:48px}.info-section .image-container{width:100%;max-width:600px;margin:0 auto}.info-section .image-placeholder{position:relative;width:100%;height:300px;background:linear-gradient(135deg, #EFF6FF 0%, rgba(75, 155, 255, 0.1) 100%);border-radius:20px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:16px;box-shadow:0 4px 12px rgba(75,155,255,.1);overflow:hidden}.info-section .image-placeholder::before{content:"";position:absolute;top:-50%;left:-50%;width:200%;height:200%;background:radial-gradient(circle at center, rgba(0, 212, 255, 0.1) 0%, transparent 50%);animation:rotate 20s linear infinite}.info-section .image-placeholder::after{content:"";position:absolute;bottom:-20px;right:-20px;width:100px;height:100px;background:linear-gradient(135deg, #00D4FF 0%, #4B9BFF 100%);border-radius:50%;opacity:.3;filter:blur(40px)}.info-section .image-placeholder i{font-size:64px;color:#4b9bff;z-index:1}.info-section .image-placeholder span{font-size:24px;font-weight:600;color:#1a1a2e;z-index:1}@media(max-width: 768px){.info-section .image-placeholder{height:200px}.info-section .image-placeholder i{font-size:48px}.info-section .image-placeholder span{font-size:20px}}.info-section .info-content{text-align:center;max-width:800px;margin:0 auto}.info-section .highlight-text{font-size:24px;line-height:1.8;color:#1a1a2e;margin-bottom:48px;font-weight:500}.info-section .highlight-text .text-accent{color:#4b9bff;font-weight:600}@media(max-width: 1024px){.info-section .highlight-text{font-size:20px}}@media(max-width: 768px){.info-section .highlight-text{font-size:16px;padding:0 24px;margin-bottom:40px}}.info-section .action-buttons{display:flex;gap:24px;justify-content:center;flex-wrap:wrap}@media(max-width: 768px){.info-section .action-buttons{flex-direction:column;align-items:center;padding:0 24px}}.info-section .action-btn{display:flex;align-items:center;gap:16px;padding:16px 32px;background:#fff;border:2px solid #e2e8f0;border-radius:50px;text-decoration:none;transition:all .3s ease;min-width:280px;position:relative;overflow:hidden}.info-section .action-btn::before{content:"";position:absolute;top:0;left:0;width:0;height:100%;background:linear-gradient(135deg, #4B9BFF 0%, #2E7FF7 100%);transition:width .3s ease;z-index:0}.info-section .action-btn:hover{border-color:#4b9bff;transform:translateY(-3px);box-shadow:0 8px 24px rgba(75,155,255,.15)}.info-section .action-btn:hover::before{width:100%}.info-section .action-btn:hover .btn-number{background:#fff;color:#4b9bff}.info-section .action-btn:hover .btn-text,.info-section .action-btn:hover i{color:#fff}.info-section .action-btn .btn-number{position:relative;z-index:1;width:32px;height:32px;display:flex;align-items:center;justify-content:center;background:linear-gradient(135deg, #4B9BFF 0%, #2E7FF7 100%);color:#fff;border-radius:50%;font-weight:700;font-size:16px;flex-shrink:0;transition:all .3s ease}.info-section .action-btn .btn-text{position:relative;z-index:1;font-size:16px;font-weight:500;color:#1a1a2e;transition:color .3s ease}.info-section .action-btn i{position:relative;z-index:1;font-size:14px;color:#94a3b8;margin-left:auto;transition:all .3s ease}@media(max-width: 768px){.info-section .action-btn{min-width:100%;justify-content:flex-start}}@keyframes rotate{from{transform:rotate(0deg)}to{transform:rotate(360deg)}}.support-center{padding:100px 0;background:#f8fafc}.support-center .section-header{text-align:center;margin-bottom:64px}.support-center .section-header .section-title{font-size:48px;font-weight:700;color:#1a1a2e;margin-bottom:16px}@media(max-width: 768px){.support-center .section-header .section-title{font-size:40px}}.support-center .section-header .section-title .title-highlight{background:linear-gradient(135deg, #4B9BFF 0%, #2E7FF7 100%);-webkit-background-clip:text;-webkit-text-fill-color:rgba(0,0,0,0);background-clip:text}.support-center .section-header .section-subtitle{font-size:20px;color:#64748b;line-height:1.8}@media(max-width: 768px){.support-center .section-header .section-subtitle{font-size:16px}}.support-center .support-grid{display:grid;grid-template-columns:repeat(3, 1fr);gap:32px;max-width:1000px;margin:0 auto}@media(max-width: 1024px){.support-center .support-grid{grid-template-columns:1fr;gap:24px;max-width:500px}}@media(max-width: 768px){.support-center .support-grid{gap:16px;padding:0 16px}}.support-center .support-card{position:relative;display:flex;flex-direction:column;align-items:center;padding:40px 32px;background:#fff;border-radius:16px;box-shadow:0 2px 4px rgba(0,0,0,.05);text-decoration:none;transition:all .3s ease;overflow:hidden}.support-center .support-card::before{content:"";position:absolute;top:0;left:0;right:0;height:4px;background:linear-gradient(135deg, #4B9BFF 0%, #2E7FF7 100%);transform:scaleX(0);transition:transform .3s ease}.support-center .support-card:hover{transform:translateY(-8px);box-shadow:0 16px 40px rgba(75,155,255,.2)}.support-center .support-card:hover::before{transform:scaleX(1)}.support-center .support-card:hover .card-icon{transform:scale(1.1) rotate(5deg);background:linear-gradient(135deg, #4B9BFF 0%, #2E7FF7 100%);color:#fff}.support-center .support-card:hover .card-arrow{transform:translateX(5px);color:#4b9bff}.support-center .support-card .card-icon{width:80px;height:80px;display:flex;align-items:center;justify-content:center;background:#eff6ff;border-radius:50px;margin-bottom:24px;transition:all .3s ease}.support-center .support-card .card-icon i{font-size:36px;color:#4b9bff}.support-center .support-card h3{font-size:24px;font-weight:600;color:#1a1a2e;margin-bottom:8px;text-align:center}.support-center .support-card p{font-size:16px;color:#64748b;text-align:center;line-height:1.8;margin-bottom:24px}.support-center .support-card .card-arrow{margin-top:auto;color:#94a3b8;transition:all .3s ease}.support-center .support-card .card-arrow i{font-size:20px}@media(max-width: 768px){.support-center .support-card{padding:32px 24px}.support-center .support-card .card-icon{width:70px;height:70px}.support-center .support-card .card-icon i{font-size:30px}.support-center .support-card h3{font-size:20px}.support-center .support-card p{font-size:14px}}.api-stats-section{padding:100px 0;background:linear-gradient(180deg, #FFFFFF 0%, #EFF6FF 100%);position:relative;overflow:hidden}.api-stats-section::before{content:"";position:absolute;top:50%;left:-100px;width:200px;height:200px;background:radial-gradient(circle, #00D4FF 0%, transparent 70%);opacity:.1;border-radius:50%;transform:translateY(-50%)}.api-stats-section::after{content:"";position:absolute;top:50%;right:-100px;width:300px;height:300px;background:radial-gradient(circle, #4B9BFF 0%, transparent 70%);opacity:.08;border-radius:50%;transform:translateY(-50%)}.api-stats-section .section-header{text-align:center;margin-bottom:80px;position:relative;z-index:1}.api-stats-section .section-header .section-title{font-size:40px;font-weight:700;color:#1a1a2e;line-height:1.8;margin-bottom:40px}@media(max-width: 1024px){.api-stats-section .section-header .section-title{font-size:32px}}@media(max-width: 768px){.api-stats-section .section-header .section-title{font-size:24px;padding:0 24px}}.api-stats-section .section-header .section-title .title-highlight{background:linear-gradient(135deg, #4B9BFF 0%, #2E7FF7 100%);-webkit-background-clip:text;-webkit-text-fill-color:rgba(0,0,0,0);background-clip:text;font-weight:800}.api-stats-section .stats-cards{display:grid;grid-template-columns:repeat(3, 1fr);gap:40px;max-width:1100px;margin:0 auto;position:relative;z-index:1}@media(max-width: 1024px){.api-stats-section .stats-cards{grid-template-columns:1fr;gap:32px;max-width:400px}}@media(max-width: 768px){.api-stats-section .stats-cards{padding:0 24px}}.api-stats-section .stat-card{background:#fff;border-radius:16px;padding:40px;box-shadow:0 4px 12px rgba(75,155,255,.1);border:1px solid rgba(75,155,255,.1);position:relative;overflow:hidden;transition:all .4s ease}.api-stats-section .stat-card::before{content:"";position:absolute;top:-2px;left:-2px;right:-2px;bottom:-2px;background:linear-gradient(135deg, #4B9BFF 0%, #2E7FF7 100%);border-radius:16px;opacity:0;z-index:-1;transition:opacity .4s ease}.api-stats-section .stat-card:hover{transform:translateY(-10px) scale(1.02);box-shadow:0 16px 40px rgba(75,155,255,.2)}.api-stats-section .stat-card:hover::before{opacity:1}.api-stats-section .stat-card:hover .stat-icon{transform:rotate(360deg) scale(1.1);background:linear-gradient(135deg, #4B9BFF 0%, #2E7FF7 100%)}.api-stats-section .stat-card:hover .stat-icon i{color:#fff}.api-stats-section .stat-card:hover .stat-number{background:linear-gradient(135deg, #00D4FF 0%, #4B9BFF 100%);-webkit-background-clip:text;-webkit-text-fill-color:rgba(0,0,0,0);background-clip:text}.api-stats-section .stat-card .stat-icon{width:70px;height:70px;display:flex;align-items:center;justify-content:center;background:#eff6ff;border-radius:50px;margin-bottom:24px;transition:all .6s cubic-bezier(0.4, 0, 0.2, 1)}.api-stats-section .stat-card .stat-icon i{font-size:32px;color:#4b9bff;transition:color .3s ease}.api-stats-section .stat-card .stat-content .stat-number{font-size:48px;font-weight:800;color:#1a1a2e;margin-bottom:8px;transition:all .3s ease;display:block}@media(max-width: 768px){.api-stats-section .stat-card .stat-content .stat-number{font-size:40px}}.api-stats-section .stat-card .stat-content .stat-number[data-formatted]::after{content:attr(data-formatted)}.api-stats-section .stat-card .stat-content .stat-label{font-size:20px;font-weight:600;color:#1a1a2e;margin-bottom:4px}.api-stats-section .stat-card .stat-content .stat-description{font-size:14px;color:#64748b;line-height:1.6}.api-stats-section .stat-card:nth-child(1) .stat-icon{background:rgba(0,212,255,.1)}.api-stats-section .stat-card:nth-child(1) .stat-icon i{color:#00d4ff}.api-stats-section .stat-card:nth-child(1):hover .stat-icon{background:linear-gradient(135deg, #00D4FF 0%, #4B9BFF 100%)}.api-stats-section .stat-card:nth-child(2) .stat-icon{background:rgba(107,207,127,.1)}.api-stats-section .stat-card:nth-child(2) .stat-icon i{color:#6bcf7f}.api-stats-section .stat-card:nth-child(2):hover .stat-icon{background:linear-gradient(135deg, #6BCF7F 0%, rgb(68.4897959184, 194.5102040816, 93.693877551) 100%)}.api-stats-section .stat-card:nth-child(3) .stat-icon{background:rgba(167,139,250,.1)}.api-stats-section .stat-card:nth-child(3) .stat-icon i{color:#a78bfa}.api-stats-section .stat-card:nth-child(3):hover .stat-icon{background:linear-gradient(135deg, #A78BFA 0%, rgb(129.9090909091, 90.1074380165, 247.8925619835) 100%)}@media(max-width: 768px){.api-stats-section .stat-card{padding:32px}.api-stats-section .stat-card .stat-icon{width:60px;height:60px}.api-stats-section .stat-card .stat-icon i{font-size:28px}}@keyframes countUp{from{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}.stat-number{animation:countUp .6s ease-out forwards}.api-market-container{display:flex;min-height:100vh;background-color:#f8fafc;position:relative}.api-market-sidebar{width:280px;background-color:#fff;border-right:1px solid #e2e8f0;padding:40px 24px;position:sticky;top:0;height:100vh;overflow-y:auto;flex-shrink:0}@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)}.api-market-sidebar.mobile-open{transform:translateX(0)}}.api-sidebar-nav .menu-section{margin-bottom:4px}.api-sidebar-nav .menu-title{padding:12px 16px;font-size:14px;font-weight:600;color:#2e7ff7;cursor:pointer;border-radius:8px;transition:all .3s ease;display:flex;align-items:center;justify-content:space-between}.api-sidebar-nav .menu-title:hover{background-color:#f8fafc}.api-sidebar-nav .menu-title.active{background-color:#eff6ff;color:#2e7ff7}.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(75,155,255,.1);color:#4b9bff;font-size:12px;font-weight:600;border-radius:50px;transition:all .3s ease}.api-sidebar-nav .menu-title.active .api-count{background-color:#4b9bff;color:#fff}.api-sidebar-nav .api-list{padding-left:16px;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:#4b9bff;opacity:.5}.api-sidebar-nav .api-item:hover{background-color:rgba(75,155,255,.05);color:#1a1a2e;padding-left:20px}.api-sidebar-nav .api-item:hover::before{opacity:1}.api-sidebar-nav .api-item.active{background-color:rgba(75,155,255,.1);color:#4b9bff;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:40px 48px;min-height:100vh;display:flex;flex-direction:column}@media(max-width: 768px){.api-market-content{padding:24px 16px}}.api-mobile-toggle{display:none;position:fixed;bottom:24px;right:24px;width:56px;height:56px;background:linear-gradient(135deg, #4B9BFF 0%, #2E7FF7 100%);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}.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:40px}@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:#4b9bff;box-shadow:0 0 0 3px rgba(75,155,255,.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:#4b9bff;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-result-count{font-size:16px;color:#64748b;margin-bottom:32px}.api-result-count strong{color:#4b9bff;font-weight:600}.api-card-grid{display:grid;grid-template-columns:repeat(auto-fill, minmax(320px, 1fr));gap:24px}@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);transition:all .3s ease;cursor:pointer;display:flex;flex-direction:column;justify-content:space-between;min-height:220px;position:relative;overflow:hidden}.api-card::before{content:"";position:absolute;top:0;left:0;right:0;height:4px;background:linear-gradient(135deg, #4B9BFF 0%, #2E7FF7 100%);transform:scaleX(0);transform-origin:left;transition:transform .3s ease}.api-card:hover{box-shadow:0 8px 24px rgba(75,155,255,.15);transform:translateY(-5px)}.api-card:hover::before{transform:scaleX(1)}.api-card:hover .api-card-icon{transform:scale(1.1)}.api-card:active{transform:translateY(-3px)}.api-card-header{display:flex;align-items:center;gap:8px;margin-bottom:16px}.api-card-badge{display:inline-flex;align-items:center;gap:6px;padding:6px 12px;background-color:#f8fafc;border-radius:6px;font-size:12px;color:#64748b;font-weight:500}.api-card-badge::before{content:"◉";color:#4b9bff;font-size:10px}.api-card-title{font-size:18px;font-weight:600;color:#1a1a2e;margin-bottom:12px;line-height:1.2;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.api-card-description{font-size:14px;color:#64748b;line-height:1.6;flex-grow:1;display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden}.api-card-icon{text-align:center;margin-top:24px;font-size:48px;opacity:.8;transition:transform .3s ease}.api-card-icon img{max-width:60px;max-height:60px;object-fit:contain}.api-card:nth-child(3n+1) .api-card-icon{color:#60a5fa}.api-card:nth-child(3n+2) .api-card-icon{color:#34d399}.api-card:nth-child(3n+3) .api-card-icon{color:#818cf8}.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:#4b9bff;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:#4b9bff;background-color:rgba(75,155,255,.05)}.api-detail-tabs .tab-button.active{color:#4b9bff;font-weight:600;border-bottom-color:#4b9bff}.tab-content{display:none;flex-direction:column;gap:32px;min-height:200px}.tab-content.active{display:flex}.api-overview-card{background:#fff;border-radius:12px;padding:32px;box-shadow:0 2px 4px rgba(0,0,0,.05);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{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;padding:8px 16px;background:linear-gradient(135deg, #4B9BFF 0%, #2E7FF7 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:#fff;border-radius:12px;padding:32px;box-shadow:0 2px 4px rgba(0,0,0,.05);transition:all .3s ease}.api-detail-card:hover{box-shadow:0 4px 12px rgba(75,155,255,.1)}.api-detail-card h3{font-size:20px;font-weight:600;color:#1a1a2e;margin-bottom:16px;padding-bottom:8px;border-bottom:2px solid #eff6ff}.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(75,155,255,.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(75,155,255,.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:#4b9bff}.login-page{min-height:calc(100vh - 140px);display:flex;align-items:center;justify-content:center;background:linear-gradient(135deg, var(--light-bg) 0%, var(--gray-bg) 100%);padding:40px 20px;position:relative;overflow:hidden}.login-page::before{content:"";position:absolute;top:-50%;right:-20%;width:600px;height:600px;background:radial-gradient(circle, var(--accent-cyan) 0%, transparent 70%);opacity:.1;border-radius:50%}.login-page::after{content:"";position:absolute;bottom:-30%;left:-10%;width:400px;height:400px;background:radial-gradient(circle, var(--primary-blue) 0%, transparent 70%);opacity:.1;border-radius:50%}.login-container{width:100%;max-width:480px;margin:0 auto;position:relative;z-index:1}.login-card{background:var(--white);border-radius:24px;box-shadow:var(--shadow-xl);padding:48px 40px;backdrop-filter:blur(10px);border:1px solid hsla(0,0%,100%,.8)}@media(max-width: 480px){.login-card{padding:32px 24px;border-radius:16px}}.login-header{text-align:center;margin-bottom:40px}.login-header .login-logo{display:inline-flex;align-items:center;gap:12px;margin-bottom:24px}.login-header .login-logo img{height:48px;width:auto}.login-header .login-logo .logo-text{font-size:20px;font-weight:700;color:var(--primary-blue);padding:6px 16px;background:var(--light-bg);border-radius:24px}.login-header .login-title{font-size:28px;font-weight:700;color:var(--text-dark);margin-bottom:8px}.login-header .login-subtitle{font-size:15px;color:var(--text-gray)}.login-form{margin-bottom:24px}.login-form .form-group{margin-bottom:20px}.login-form .form-group:last-child{margin-bottom:0}.login-form .form-label{display:block;font-size:14px;font-weight:600;color:var(--text-dark);margin-bottom:8px}.login-form .form-input{width:100%;padding:14px 16px;font-size:15px;border:2px solid var(--border-gray);border-radius:12px;background:var(--white);transition:all .3s ease;outline:none}.login-form .form-input::placeholder{color:var(--text-light)}.login-form .form-input:focus{border-color:var(--primary-blue);box-shadow:0 0 0 4px rgba(75,155,255,.1)}.login-form .form-input.error{border-color:var(--accent-orange)}.login-form .form-input.error:focus{box-shadow:0 0 0 4px rgba(255,107,107,.1)}.login-form .form-checkbox{display:flex;align-items:center;gap:8px;margin-top:16px}.login-form .form-checkbox input[type=checkbox]{width:18px;height:18px;accent-color:var(--primary-blue);cursor:pointer}.login-form .form-checkbox label{font-size:14px;color:var(--text-gray);cursor:pointer;user-select:none}.login-button{width:100%;padding:16px 24px;font-size:16px;font-weight:600;color:var(--white);background:var(--gradient-primary);border:none;border-radius:12px;cursor:pointer;transition:all .3s ease;box-shadow:var(--shadow-md)}.login-button:hover{transform:translateY(-2px);box-shadow:var(--shadow-lg)}.login-button:active{transform:translateY(0)}.login-button:disabled{opacity:.6;cursor:not-allowed;transform:none}.login-links{display:flex;justify-content:center;align-items:center;gap:16px;margin-top:32px;padding-top:32px;border-top:1px solid var(--border-gray);flex-wrap:wrap}@media(max-width: 480px){.login-links{gap:12px;margin-top:24px;padding-top:24px}}.login-links a{font-size:14px;color:var(--text-gray);text-decoration:none;transition:color .3s ease;display:flex;align-items:center;gap:6px}.login-links a:hover{color:var(--primary-blue)}.login-links a i{font-size:16px}.login-links .link-separator{color:var(--border-gray);font-size:14px}.login-alert{margin-bottom:20px;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:var(--accent-orange);border:1px solid rgba(255,107,107,.2)}.login-alert.alert-success{background:rgba(107,207,127,.1);color:var(--accent-green);border:1px solid rgba(107,207,127,.2)}.login-alert.alert-info{background:rgba(75,155,255,.1);color:var(--primary-blue);border:1px solid rgba(75,155,255,.2)}.login-alert i{font-size:18px}.login-loading{position:absolute;top:0;left:0;right:0;bottom:0;background:hsla(0,0%,100%,.9);display:flex;align-items:center;justify-content:center;border-radius:24px;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 var(--border-gray);border-top-color:var(--primary-blue);border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}.container{max-width:1280px;margin:0 auto;padding:0 24px}@media(max-width: 768px){.container{padding:0 16px}}.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:#4b9bff}.bg-secondary{background:#2e7ff7}.bg-white{background:#fff}.bg-gray{background:#f8fafc}.bg-light{background:#eff6ff}.bg-gradient-primary{background:linear-gradient(135deg, #4B9BFF 0%, #2E7FF7 100%)}.bg-gradient-accent{background:linear-gradient(135deg, #00D4FF 0%, #4B9BFF 100%)}.bg-gradient-warm{background:linear-gradient(135deg, #FFD93D 0%, #FF6B6B 100%)}.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:#4b9bff}.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}}.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}/*# 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}@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-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)}.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:22px;width:114px}.logo-wrapper{display:flex;align-items:center;gap:12px}.logo{display:flex;align-items:center;gap:12px;height:22px;z-index:10}.logo a{display:flex;align-items:center;gap:12px;height:100%;text-decoration:none}.logo img{height:22px;width:114px}.logo-text{font-size:22px;font-weight:600;color:var(--text-dark);text-decoration:none;display:inline-block;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:"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,52.3166666667,129)}.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: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:"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: 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:"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:"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:#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:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:30px;font-weight:700;color:#1f2937;margin:0;line-height:1.2}@media(max-width: 1024px){.footer-right .footer-contact{font-size:24px}}@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%,51.568627451%);transform:translateY(-3px)}.btn-danger{background:#ff6b6b;color:#fff}.btn-danger:hover{background:#ff3838;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,81.5,81.5)}.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(122.5625,195.3303571429,224.4375);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:8px;height:60px;padding:10px 24px;background-color:#3ba4ed;color:#fff;border:none;border-radius:12px;font-size:18px;font-weight:700;cursor:pointer;transition:background-color .2s ease}.btn-upload:hover{background-color:#2b94dd}@media(max-width: 1024px){.btn-upload{font-size:16px;height:50px}}.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(163,131.0721649485,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(163,131.0721649485,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-result,.register-form-container{background-color:#f6f9fb;border-radius:12px;padding:30px 45px}@media(max-width: 768px){.common-section-box,.app-list-container,.register-result,.register-form-container{padding:24px}}.common-title-bar,.user-management-container .table-controls,.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,.user-management-container .table-controls,.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,.user-management-container .table-controls .common-title,.app-list-title-section .common-title,.register-title-bar .common-title{font-family:"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){.common-title-bar .common-title,.user-management-container .table-controls .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,.user-management-container .table-controls .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,.user-management-container .table-controls .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: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:"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,81.5,81.5)}.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:40px;margin-bottom:50px}@media(max-width: 1024px){.form-row{flex-direction:column;gap:12px;margin-bottom:30px}}.form-label-wrapper{display:flex;align-items:center;gap:5px;min-width:214px;width:214px;flex-shrink:0}@media(max-width: 1024px){.form-label-wrapper{padding-top:0;width:auto}}.form-label-wrapper.label-offset{margin-top:15px}@media(max-width: 1024px){.form-label-wrapper.label-offset{margin-top:0}}.form-label-text{font-size:18px;font-weight:700;color:#000}@media(max-width: 1024px){.form-label-text{font-size:16px}}.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%}}.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 .phone-number{flex:1;width:100%}.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(208.9090909091,231.9545454545,242.5909090909)}.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,52.3166666667,129)}.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:"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:22px;display:inline-flex;align-items:center;justify-content:center;gap:4px;padding:4px 10px;color:#212529;text-decoration:none;border:1px solid #212529;border-radius:6px;font-family:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-weight:600;font-size:14px;background:rgba(0,0,0,0);transition:all .3s ease}.login-btn-box .user-icon{width:16px;height:16px;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:#ff3838;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:hsl(0,0%,80.4901960784%)}.list-table-btn--primary{background-color:#a4d6ea}.list-table-btn--primary:hover{background-color:rgb(143.28125,204.6651785714,229.21875)}.list-table-btn--secondary{background-color:#cdd4f0}.list-table-btn--secondary:hover{background-color:hsl(228,53.8461538462%,82.2549019608%)}.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:#ff3838;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%,41.568627451%)}.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:"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")}.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:"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:"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:"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,52.3166666667,129);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:"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:"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:"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:"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:"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:"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:"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:"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,106.8584070796,175)}.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:"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:"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:"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:"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:"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,52.3166666667,129)}.info-section .action-btn-secondary{background:#00acdd;color:#fff}.info-section .action-btn-secondary:hover{background:rgb(0,132.3076923077,170)}.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:"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:"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:"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,62.6583333333,154.5)}.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:"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:"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:"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:"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:"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,106.8584070796,175);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, 1fr);gap:24px}@media(max-width: 1024px){.api-card-grid{grid-template-columns:repeat(2, 1fr)}}@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:"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:"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:"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:"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,62.6583333333,154.5)}.login-button:active{background:rgb(0,52.3166666667,129)}.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:"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:1228px;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:18px 24px;font-family:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:22px;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:14px 16px;font-size:16px}}.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:#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:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:20px;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:16px}}.account-recovery-form .form-input{flex:1;height:60px;padding:0 20px;font-family:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:20px;font-weight:400;color:#212529;background:#fff;border:1px solid #dadada;border-radius:12px;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:60px;padding:0 40px 0 20px;font-family:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:20px;font-weight:400;color:#515151;background:#fff;border:1px solid #dadada;border-radius:12px;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 16px 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:12px;font-size:16px}.phone-input-group{display:flex;align-items:center;gap:14px;flex:1}.phone-input-group .phone-prefix{width:180px;flex-shrink:0}.phone-input-group .phone-middle,.phone-input-group .phone-last{width:180px;flex-shrink:0}.phone-input-group .phone-separator{display:flex;align-items:center;justify-content:center;width:14px;height:1px;background:#515151;flex-shrink:0}@media(max-width: 768px){.phone-input-group{flex-wrap:wrap;gap:10px}.phone-input-group .phone-prefix,.phone-input-group .phone-middle,.phone-input-group .phone-last{width:calc(33% - 20px);min-width:80px}.phone-input-group .phone-separator{width:10px}}@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:80px}.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:20px;font-weight:400;color:#ed5b5b;pointer-events:none}@media(max-width: 576px){.auth-input-group .auth-timer{font-size:16px;right:16px}}.auth-request-button,.auth-verify-button{width:172px;height:60px;padding:10px;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;background:#a4d6ea;border:none;border-radius:12px;cursor:pointer;transition:all .3s ease;margin:0;flex-shrink:0;line-height:1}.auth-request-button:hover,.auth-verify-button:hover{background:rgb(143.28125,204.6651785714,229.21875)}.auth-request-button:active,.auth-verify-button:active{background:rgb(122.5625,195.3303571429,224.4375)}.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:50px;font-size:16px;margin-top:10px}}.account-recovery-card .form-actions,.account-recovery-card .form-actions-center{display:flex;justify-content:center;gap:20px;margin-top:40px;padding:40px 0;border-top:none;background:rgba(0,0,0,0)}.account-recovery-card .form-actions .cancel-button,.account-recovery-card .form-actions-center .cancel-button,.account-recovery-card .form-actions .submit-button,.account-recovery-card .form-actions-center .submit-button{width:200px;height:60px;padding:10px;font-family:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:18px;font-weight:700;border:none;border-radius:12px;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,.account-recovery-card .form-actions-center .cancel-button{color:#5f666c;background:#e5e7eb}.account-recovery-card .form-actions .cancel-button:hover,.account-recovery-card .form-actions-center .cancel-button:hover{background:hsl(220,13.0434782609%,85.9803921569%)}.account-recovery-card .form-actions .cancel-button:active,.account-recovery-card .form-actions-center .cancel-button:active{background:hsl(220,13.0434782609%,80.9803921569%)}.account-recovery-card .form-actions .submit-button,.account-recovery-card .form-actions-center .submit-button{color:#fff;background:#0049b4}.account-recovery-card .form-actions .submit-button:hover,.account-recovery-card .form-actions-center .submit-button:hover{background:rgb(0,62.6583333333,154.5)}.account-recovery-card .form-actions .submit-button:active,.account-recovery-card .form-actions-center .submit-button:active{background:rgb(0,52.3166666667,129)}.account-recovery-card .form-actions .submit-button:disabled,.account-recovery-card .form-actions-center .submit-button:disabled{opacity:.6;cursor:not-allowed}@media(max-width: 576px){.account-recovery-card .form-actions,.account-recovery-card .form-actions-center{flex-direction:column;gap:12px;padding:24px 0}.account-recovery-card .form-actions .cancel-button,.account-recovery-card .form-actions-center .cancel-button,.account-recovery-card .form-actions .submit-button,.account-recovery-card .form-actions-center .submit-button{width:100%;height:50px;font-size:16px}}.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:"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:"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-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,52.3166666667,129)}@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,.account-recovery-card .form-actions-center{flex-direction:row;gap:21px;margin-top:24px;padding:24px 0}.account-recovery-card .form-actions .cancel-button,.account-recovery-card .form-actions-center .cancel-button,.account-recovery-card .form-actions .submit-button,.account-recovery-card .form-actions-center .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,.account-recovery-card .form-actions-center .cancel-button{background:#e5e7eb;color:#5f666c}.account-recovery-card .form-actions .submit-button,.account-recovery-card .form-actions-center .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:60px 20px;position:relative;border-radius:12px;margin-top:60px;margin-bottom:60px}.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:30px}.signup-logo img{width:100%;height:100%;object-fit:contain}.signup-message{font-family:"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}.signup-buttons{width:100%;max-width:504px;display:flex;flex-direction:column;gap:40px;margin-bottom:60px}.signup-btn{width:100%;height:80px;display:flex;align-items:center;justify-content:center;gap:10px;padding:10px;font-family:"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:"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:40px}@media(max-width: 768px){.register-progress{margin-bottom:30px;padding:0}}.progress-steps{display:flex;align-items:flex-start;gap:50px}@media(max-width: 768px){.progress-steps{gap:16px;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:62px;height:62px;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:44px;height:44px}}.progress-step-item .step-icon .step-icon-img{width:36px;height:36px}@media(max-width: 768px){.progress-step-item .step-icon .step-icon-img{width:24px;height:24px}}.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:50px 45px}@media(max-width: 1024px){.register-form-container{padding:30px 20px}}.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%;height:60px;padding:0 20px;border:1px solid #dadada;border-radius:12px;background-color:#fff;font-size:20px;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{height:50px;font-size:16px}}.register-form-container .form-textarea{width:100%;min-height:190px;padding:18px 20px;border:1px solid #dadada;border-radius:12px;background-color:#fff;font-size:20px;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:50px 40px;display:flex;flex-direction:column;align-items:center;gap:26px}@media(max-width: 1024px){.icon-upload-box{padding:30px 20px}}.icon-preview-area{width:100px;height:100px;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:16px;font-weight:700;color:#515961;margin:0 0 11px}.icon-upload-info .upload-hint{font-size:14px;font-weight:400;color:#515961;margin:0}.icon-upload-info .upload-hint strong{font-weight:700}.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:158px;height:60px;background-color:#3ba4ed;color:#fff;border:none;border-radius:12px;font-size:20px;font-weight:700;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:100%;font-size:16px;height:50px}}@media(max-width: 768px){.btn-add-ip{width:89px;height:40px;font-size:12px;border-radius:8px;padding:0}}.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,.register-form-container .form-actions-center{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,.register-form-container .form-actions-center{padding:0 16px 16px}}@media(max-width: 768px){.apikey-register-container>.form-actions,.apikey-register-container>.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)}.apikey-register-container>.form-actions .btn-submit,.apikey-register-container>.form-actions-center .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,.apikey-register-container>.form-actions-center .btn-submit.btn-secondary{background-color:#e5e7eb !important;color:#5f666c !important;border:none !important}.apikey-register-container>.form-actions .btn-submit.btn-primary,.apikey-register-container>.form-actions-center .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:32px;padding:0;padding-top:27px;margin-bottom:64px}@media(max-width: 768px){.app-info-card{flex-direction:column;align-items:center;text-align:center;padding:24px 0;gap:24px}}.app-info-icon{flex-shrink:0;width:160px;height:160px;border-radius:16px;overflow:hidden;background:#f8fafc;display:flex;align-items:center;justify-content:center;margin-left:87px}@media(max-width: 768px){.app-info-icon{width:120px;height:120px}}.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:16px}.app-status-badge{display:inline-block;padding:4px 24px;border-radius:50px;font-size:14px;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:24px;font-weight:700;color:#1a1a2e;margin:0}@media(max-width: 768px){.app-info-name{font-size:20px}}.app-info-description{font-size:18px;color:#515151;margin:0;line-height:1.5}@media(max-width: 768px){.app-info-description{font-size:16px}}.apikey-info-section{background:#f6f9fb;border-radius:12px;padding:48px 40px;margin-bottom:40px}@media(max-width: 768px){.apikey-info-section{padding:24px}}.info-row{display:flex;align-items:center;gap:32px;margin-bottom:32px}.info-row:last-child{margin-bottom:0}@media(max-width: 768px){.info-row{flex-direction:column;align-items:flex-start;gap:8px}}.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:20px;font-weight:700;color:#212529}@media(max-width: 768px){.info-label{width:100%;font-size:16px}}.info-value{flex:1;font-size:16px;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:60px;padding:0 24px;background:#ddd;border-radius:12px;font-size:20px;color:#515151;text-align:center}@media(max-width: 768px){.info-value-box{height:50px;font-size:16px;padding:0 16px}}.info-value-box.with-copy{flex:1}.info-row-with-action .info-value{display:flex;gap:16px;align-items:center}.btn-copy-action{display:flex;align-items:center;justify-content:center;gap:4px;height:60px;width:158px;padding:0 24px;background:#a4d6ea;border:none;border-radius:12px;font-size:20px;font-weight:700;color:#fff;cursor:pointer;white-space:nowrap;transition:background .2s ease}.btn-copy-action:hover{background:rgb(122.5625,195.3303571429,224.4375)}@media(max-width: 768px){.btn-copy-action{height:50px;font-size:16px;padding:0 16px}}.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(20.6074766355,140.8177570093,224.3925233645)}.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(122.5625,195.3303571429,224.4375)}.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(20.6074766355,140.8177570093,224.3925233645)}#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:"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:"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}}.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:"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%,85.9803921569%)}.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:"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%,85.9803921569%)}.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,62.6583333333,154.5)}.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(210.9493670886,36.5506329114,53.2594936709)}.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(189.2151898734,32.7848101266,47.7721518987)}.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(21.6317757009,146.6504672897,233.5682242991)}.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,.inquiry-form-container .form-actions-center{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,.inquiry-form-container .form-actions-center .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,.inquiry-form-container .form-actions-center .btn-secondary{background:#e5e7eb;color:#5f666c;border:none}.inquiry-form-container .form-actions .btn-secondary:hover,.inquiry-form-container .form-actions-center .btn-secondary:hover{background:hsl(220,13.0434782609%,85.9803921569%)}.inquiry-form-container .form-actions .btn-primary,.inquiry-form-container .form-actions-center .btn-primary{background:#0049b4;color:#fff;border:none}.inquiry-form-container .form-actions .btn-primary:hover,.inquiry-form-container .form-actions-center .btn-primary:hover{background:rgb(0,62.6583333333,154.5)}.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:"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}}@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:20px;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:8px;display:flex;align-items:flex-start;gap:24px}.org-form-group:last-child{margin-bottom:0}@media(max-width: 768px){.org-form-group{flex-direction:column;gap:16px}}.org-form-label{display:flex;align-items:center;gap:8px;font-size:16px;font-weight:600;color:#1a1a2e;min-width:180px;padding-top:16px}.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:16px 24px;border:2px solid #e2e8f0;border-radius:8px;font-size:16px;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 .phone-number{flex:1;width:100%}.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:16px 24px;border:2px solid #e2e8f0;border-radius:8px;font-size:16px;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:16px 24px;width:172px;background:#a4d6ea;color:#fff;border:2px solid #a4d6ea;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:16px 24px;border:2px dashed #e2e8f0;border-radius:8px;background:#f8fafc;color:#94a3b8;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{cursor:pointer;background:#3ba4ed;border-radius:12px;border:2px solid #3ba4ed}.org-file-label input[type=file]{display:none}.org-file-remove{padding:16px;background:#ff6b6b;color:#fff;border:none;border-radius:8px;cursor:pointer;transition:all .3s ease}.org-file-remove:hover{background:#ff3838}.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,.form-actions-center{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%,51.568627451%)}.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 .register-title-bar,.org-register-container .app-list-title-section,.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 .register-title-bar h3,.org-register-container .app-list-title-section 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,.org-register-container .form-actions-center{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,.org-register-container .form-actions-center .form-actions-buttons{width:100%;display:flex;justify-content:center}.org-register-container .form-actions .form-actions-buttons .btn,.org-register-container .form-actions-center .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:"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,.terms-container .register-title-bar,.terms-container .app-list-title-section,.terms-container .user-management-container .table-controls,.user-management-container .terms-container .table-controls{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:"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;min-height:400px;background-color:#f6f9fb}@media(max-width: 768px){.terms-content{padding:24px 24px;min-height:300px}}.terms-content .content-body{font-size:16px;color:#1a1a2e;line-height:1.8;word-wrap:break-word}@media(max-width: 768px){.terms-content .content-body{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}.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 7f79bca..ad19d3a 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/layout/_header.scss","../sass/layout/_footer.scss","../sass/layout/_grid.scss","../sass/layout/_container.scss","../sass/components/_buttons.scss","../sass/components/_cards.scss","../sass/components/_forms.scss","../sass/components/_modals.scss","../sass/components/_navigation.scss","../sass/components/_partners.scss","../sass/components/_cta.scss","../sass/pages/_index.scss","../sass/pages/_api-market.scss","../sass/pages/_login.scss","../sass/base/_utilities.scss"],"names":[],"mappings":"CAIA,EACE,SACA,UACA,sBAGF,KACE,eACA,mCACA,kCACA,kCAGF,KACE,YCqBoB,mFDpBpB,YC4CmB,ID3CnB,MCJU,QDKV,iBCFM,KDGN,kBAIF,MACE,gBAIF,EACE,qBACA,cACA,WC4EgB,aDxElB,IACE,eACA,YACA,cAIF,OACE,oBACA,kBACA,oBACA,eACA,yBACA,YACA,UAIF,sBAGE,oBACA,kBACA,oBACA,YACA,aAIF,MACE,yBACA,iBAIF,kBACE,YChBiB,IDiBjB,YCbkB,IDiBpB,2FAGE,cAIF,YACE,qCACA,MCxEU,QD2EZ,iBACE,qCACA,MC7EU,QCXZ,KACE,UDuCe,KCtCf,YDgDoB,IC/CpB,MDQU,QCJZ,OACE,UDuCc,KCtCd,YD6CsB,IC5CtB,cDuDW,KE7DT,yBDGJ,OAMI,UDgCY,ME3CZ,yBDKJ,OAUI,UD2BY,MCvBhB,OACE,UDwBc,KCvBd,YD8BiB,IC7BjB,cDwCW,KE5DT,yBDiBJ,OAMI,UDiBY,ME1CZ,yBDmBJ,OAUI,UDYW,MCRf,OACE,UDSc,KCRd,YDgBiB,ICfjB,cD0BW,KE5DT,yBD+BJ,OAMI,UDEW,MCEf,OACE,UDFc,KCGd,YDKqB,ICJrB,cDeW,ICZb,OACE,UDTa,KCUb,YDDqB,ICErB,cDSW,ICNb,OACE,UDhBa,KCiBb,YDPqB,ICQrB,cDGW,ICCb,EACE,cDDW,KCEX,YDRmB,ICUnB,aACE,gBAKJ,eCTE,WF1CiB,kDE2CjB,6BACA,sCACA,qBDUF,sBCbE,WFzCgB,kDE0ChB,6BACA,sCACA,qBDcF,oBCjBE,WFxCc,kDEyCd,6BACA,sCACA,qBDkBF,cACE,MDrFa,QCwFf,gBACE,MDxFe,QC2FjB,WACE,MDpFU,QCuFZ,WACE,MDvFU,QC0FZ,YACE,MD1FW,QC6Fb,YACE,MD7FM,KCiGR,WACE,gBAGF,aACE,kBAGF,YACE,iBAIF,cACE,YD3EoB,IC8EtB,aACE,YD9EmB,ICiFrB,eACE,YDjFqB,ICoFvB,WACE,YDpFiB,ICuFnB,gBACE,YDvFsB,IC2FxB,SACE,UD5Ga,KC+Gf,SACE,UD/Ga,KCkHf,WACE,UDlHe,KCqHjB,SACE,UDpHa,KCuHf,SACE,UDvHa,KC2Hf,MACE,UD7Ha,KC8Hb,YDtHoB,ICuHpB,YD9GkB,IC+GlB,MD9JU,QCiKZ,SACE,UDvIa,KCwIb,MDnKU,QCsKZ,MACE,YDhJiB,oCCiJjB,UD7Ia,KC8Ib,WDtKQ,QCuKR,gBACA,cD7GiB,ICiHnB,EACE,MD1La,QC4Lb,QACE,MD5La,QCiMjB,WACE,QDrIW,KCsIX,cACA,8BACA,WDzLQ,QC0LR,kBAEA,aACE,gBAKJ,GACE,YACA,6BACA,cEpNF,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,WHjQQ,gCGqQZ,cACE,8BAEA,oBACE,uBAIJ,aACE,kBACA,gBAEA,qBACE,WACA,kBACA,SACA,UACA,WACA,YACA,kGAMA,wBACA,8BACA,4BAGF,2BACE,yCClUJ,MAEE,wBACA,0BACA,uBACA,yBACA,yBACA,wBACA,yBAGA,qBACA,qBACA,sBACA,iBACA,mBACA,oBACA,uBAGA,sEACA,qEACA,mEAGA,2CACA,gDACA,iDACA,iDAIF,OACE,kBACA,UACA,WACA,UACA,YACA,gBACA,sBACA,mBACA,SAMF,eACE,eACA,MACA,OACA,QACA,+BACA,2BACA,mCACA,4BACA,aACA,YACA,wBAGF,gBACE,aACA,mBACA,8BACA,YACA,iBACA,cACA,eAQF,aACE,aACA,mBAEA,mBACE,YACA,WAKJ,cACE,aACA,mBACA,SAIF,MACE,aACA,mBACA,SACA,YACA,WAEA,QACE,aACA,mBACA,SACA,YACA,qBAGF,UACE,YACA,WAIJ,WACE,eACA,gBACA,0BACA,iBACA,2BACA,mBACA,qBACA,qBACA,wBAGA,iBACE,+BACA,mBACA,2BACA,4BAGF,mBACE,0BAKJ,+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,cAQF,0BACE,YACA,WAIJ,eACE,OACA,kBAEA,6BACE,eACA,gBACA,uBACA,SAKF,+BACE,WACA,YACA,yBACA,YACA,eACA,aACA,sBACA,uBACA,mBACA,QACA,YACA,kBACA,qCAEA,qCACE,0BAGF,+CACE,WACA,WACA,4BACA,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,wBACA,2BACA,8BACA,aACA,sBAGF,8BACE,aACA,mBACA,8BACA,aACA,2CACA,wBACA,4BACA,gBAEA,2CACE,aACA,mBACA,SAEA,iDACE,YACA,WAGF,sDACE,eACA,gBACA,0BAIJ,4CACE,WACA,YACA,yBACA,YACA,eACA,aACA,mBACA,uBACA,kBACA,eACA,uBACA,wBAEA,kDACE,wBACA,uBAKN,2BACE,OACA,UACA,gBACA,wBAEA,wCACE,gBACA,SACA,eAEA,2CACE,kBAEA,wDACE,cACA,kBACA,cACA,uBACA,qBACA,eACA,gBACA,mBACA,wBAEA,6HACE,2BACA,0BACA,0BACA,4BAOV,+BACE,aACA,wCACA,wBACA,uCAEA,iDACE,cACA,kBACA,kBACA,uBACA,qBACA,qCACA,mBACA,gBACA,eACA,mBACA,wBAEA,uDACE,2BACA,0BAIJ,kDACE,aACA,mBACA,uBACA,QACA,kBACA,mCACA,mBACA,qBACA,mBACA,gBACA,eACA,mBACA,wBACA,4BAEA,wDACE,2BACA,4BAIJ,kDACE,aACA,mBACA,uBACA,QACA,WACA,kBACA,0BACA,uBACA,YACA,mBACA,gBACA,eACA,eACA,wBAEA,wDACE,2BACA,0BAMR,gBACE,kBACA,MJxZW,KIyZX,QACA,2BACA,WACA,YACA,yBACA,YACA,eACA,YACA,YAEA,qBACE,cACA,WACA,WACA,WJ5dQ,QI6dR,kBACA,6BACA,oBAEA,yDAEE,WACA,kBACA,OACA,WACA,WACA,WJxeM,QIyeN,6BAGF,sCACA,oCAGF,wBACE,yBACA,8DACA,8DAOJ,gBAEE,eACA,MACA,OACA,QACA,SACA,WJ9fM,KI+fN,QJ1ac,II2ad,YACA,gBACA,gBACA,aACA,sBAKA,4BACE,QJvdS,KIwdT,WJ1gBM,QI2gBN,gCAGF,0BACE,gBACA,SACA,UACA,YAEA,oCACE,gCAEA,sCACE,aACA,mBACA,QJxeK,KIyeL,MJ/hBI,QIgiBJ,UJlgBO,KImgBP,YJzfa,II0fb,qBACA,kBAGF,yDACE,WACA,kBACA,MJnfK,KIofL,QACA,UACA,WACA,+BACA,gCACA,yCACA,mCAGF,sDACE,2CAGF,8CACE,aACA,gBACA,UACA,SACA,WJxjBE,QI0jBF,gDACE,cACA,4BACA,MJhkBE,QIikBF,UJtiBK,KIuiBL,qBAEA,sDACE,WJnkBJ,KIokBI,MJhlBG,QIqlBT,uDACE,cAKN,2BACE,kBACA,WJjlBI,KIklBJ,6BACA,gBAQJ,UACE,aACA,gBACA,QACA,SACA,UAGF,UACE,aACA,mBACA,QACA,qBACA,uBACA,gBACA,eACA,iBACA,kBACA,wBAEA,gBACE,2BACA,0BACA,2BAIJ,UACE,eAGF,YACE,aACA,mBACA,QACA,qBACA,mCACA,mBACA,kBACA,mBACA,gBACA,wBACA,4BAEA,kBACE,2BACA,4BAqGJ,KACE,iBAMF,yBACE,eACE,YAEA,0CACE,eACA,YAIJ,KACE,kBAIJ,yBAEI,+BACE,eAKF,iBACE,cC5xBN,eACE,WLWU,QKVV,MLaM,KKZN,oBHEE,yBGLJ,eAMI,qBAIJ,YACE,aACA,8BACA,IL0DY,KKzDZ,cLwDY,KKvDZ,ULqEoB,OKpEpB,iBACA,kBACA,eHXE,0BGGJ,YAWI,0BACA,IL8CU,MKzCd,cACE,gBAEA,2BACE,YACA,cLkCS,KK/BX,2BACE,ULIW,KKHX,YLuBgB,IKtBhB,yBACA,cL2BS,KKxBX,iCACE,cLwBS,KKtBT,oCACE,ULJS,KKKT,YLMiB,IKLjB,cLiBO,KKdT,kDACE,aACA,ILWO,IKTP,wDACE,OACA,iBACA,8BACA,oCACA,cLgBW,IKfX,ML9CA,KK+CA,ULtBO,KKwBP,qEACE,yBAGF,8DACE,+BACA,aLnEK,QKoEL,aAIJ,yDACE,iBACA,WLrDW,kDKsDX,ML/DA,KKgEA,cLHW,IKIX,YL3Be,IK4Bf,ULzCO,KK0CP,WLwBU,aKtBV,+DACE,2BACA,WLxDE,+BKgEZ,cACE,aACA,IL/BW,KKiCX,gBACE,WACA,YACA,8BACA,cLzBe,KExDjB,aACA,uBACA,mBGiFE,MLzFI,KK0FJ,WLCc,uBA/DH,KKiEX,sBACE,WL1GS,QK2GT,2BAGF,+BACE,mBAGF,8BACE,mBAGF,+BACE,mBAGF,6BACE,gBAGF,8BACE,eAMN,cACE,aACA,qCACA,ILvEY,KE7DV,0BGiIJ,cAMI,sCHzIA,yBGmIJ,cAUI,0BACA,mBAKF,kBACE,ULhHa,KKiHb,YLpGe,IKqGf,cLzFS,KK0FT,ML7II,KKgJN,kBACE,gBAEA,qBACE,cLlGO,KKoGP,uBACE,yBACA,UL/HO,KKgIP,WL9DU,aK+DV,kBAEA,8BACE,WACA,kBACA,YACA,OACA,QACA,WACA,WL7KI,QK8KJ,WLzEQ,aK4EV,6BACE,MLlLI,QKoLJ,oCACE,WAQV,6BACE,oBACA,mBACA,ILtIS,IKuIT,gBACA,gCACA,MLhMW,QKiMX,cLzHiB,KK0HjB,ULnKW,KKoKX,YLtJmB,IKuJnB,YL5IS,IKiJb,eACE,YL9IY,KK+IZ,wCACA,ULhIoB,OKiIpB,cACA,aLpJW,KKqJX,cLrJW,KKwJb,cACE,aACA,8BACA,mBHxNE,yBGqNJ,cAMI,sBACA,IL/JS,KKgKT,mBAGF,gBACE,yBACA,UL/LW,KKgMX,SAGF,2BACE,aACA,IL3KS,KK6KT,6BACE,yBACA,ULzMS,KK0MT,WLxIY,aK0IZ,mCACE,MLtOA,KK6OR,mBACE,kBACA,gBAEA,2BACE,WACA,kBACA,WACA,aACA,YACA,aACA,gFACA,kBAGF,0BACE,WACA,kBACA,cACA,YACA,YACA,aACA,gFACA,kBClRJ,MACE,aACA,IN+DW,KM5DX,aACE,0BAGF,aACE,qCAGF,aACE,qCAGF,aACE,qCAGF,aACE,qCAGF,aACE,qCAGF,cACE,sCAIF,WACE,2DAGF,cACE,2DAGF,cACE,2DAIF,YACE,MAGF,aACE,INWS,IMRX,aACE,INQS,KMLX,aACE,INKS,KMFX,aACE,INES,KE5DT,0BI+DA,gBACE,0BAGF,gBACE,sCJtEF,yBI2EA,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,INrEW,KE3DT,0BI6HJ,UAMI,sCJrIA,yBI+HJ,UAUI,2BAKJ,iBACE,aACA,2DACA,INpFW,KE7DT,yBI8IJ,iBAMI,2BAKJ,eACE,aACA,2DACA,IN7FY,KM8FZ,mBJ7JE,yBIyJJ,eAOI,sCAKJ,cACE,aACA,qCACA,IN1GW,KE9DT,yBIqKJ,cAMI,2BAKJ,cACE,aACA,8BACA,INpHY,KMqHZ,kBJlLE,0BI8KJ,cAOI,2BAKJ,aACE,aACA,8BACA,IN7HY,KEhEV,0BI0LJ,aAMI,0BACA,INpIU,MMuIZ,2BACE,aACA,qCACA,IN1IU,KE7DV,0BIoMF,2BAMI,sCJ5MF,yBIsMF,2BAUI,0BACA,mBAMN,cACE,aACA,qCACA,IN7JW,KM8JX,mBJzNE,0BIqNJ,cAOI,0BACA,INjKS,MMsKb,kBACE,aACA,2DACA,IN1KW,KE7DT,yBIoOJ,kBAMI,2BAKJ,YACE,aACA,qCACA,INpLW,KMqLX,kBJjPE,0BI6OJ,YAOI,sCJtPA,yBI+OJ,YAWI,2BAKJ,YACE,aACA,8BACA,INlMY,KMmMZ,mBJjQE,0BI6PJ,YAOI,0BACA,INzMS,MM4MX,oBACE,cAEA,sBACE,cJ5QF,0BIwQF,oBAQI,eAMN,cACE,aACA,qCACA,IN7NW,KE5DT,0BIsRJ,cAMI,2BClSJ,WACE,UPkFoB,OOjFpB,cACA,eLCE,yBKJJ,WAMI,gBAIF,iBACE,eACA,eAGF,kBACE,gBAGF,gBACE,iBAKJ,SACE,gBLtBE,yBKqBJ,SAII,gBAIF,YACE,eAGF,YACE,gBAGF,gBACE,cAGF,mBACE,iBAMF,iBACE,WPtCM,QOyCR,kBACE,WPzCO,QO4CT,qBACE,6DAGF,6BACE,6DAGF,oBACE,WP9Ce,kDO+Cf,MPxDI,KO2DN,iBACE,WP/DQ,QOgER,MP7DI,KOkER,gBACE,kBACA,cPbY,KEjEV,yBK4EJ,gBAKI,cPjBU,MOoBZ,+BACE,oBACA,mBACA,IP7BS,IO8BT,iBACA,WP7EO,QO8EP,MP5FW,QO6FX,cPhBiB,KOiBjB,UPzDW,KO0DX,YP7CmB,IO8CnB,cPlCS,KOqCX,+BACE,UPzDY,KO0DZ,YPlDe,IOmDf,cPxCS,KOyCT,MP9FQ,QEPR,yBKiGF,+BAOI,UPhEU,MOmEZ,gDL9CF,WFzCgB,kDE0ChB,6BACA,sCACA,qBKgDA,kCACE,UP5EW,KO6EX,MP1GQ,QO2GR,YP7DiB,IO8DjB,gBACA,cLrHA,yBKgHF,kCAQI,UPpFW,MO0FjB,iBACE,kBACA,iBACA,aACA,sBAEA,+BACE,OACA,YPtDY,KO2DhB,cACE,kBACA,gBAIF,eACE,kBACA,UCvJF,KNiCE,oBACA,mBACA,IF4BW,IE3BX,kBACA,cFuCiB,KEtCjB,YFcqB,IEbrB,UFCe,0BECf,WFgEgB,aE/DhB,eACA,YAEA,WACE,2BM9CJ,KAEE,kBACA,gBAGA,QACE,iBACA,UR+BW,KQ5Bb,QACE,kBACA,UR2Ba,KQxBf,QACE,kBACA,URuBW,KQpBb,QACE,kBACA,URmBW,KQfb,aACE,WRLe,kDQMf,MRfI,KQgBJ,WRDQ,+BQGR,mBACE,2BACA,WRHM,gCQMR,oBACE,2BAIJ,eACE,WR7BI,KQ8BJ,MR1CW,QQ2CX,yBAEA,qBACE,WRhCK,QQiCL,2BAIJ,YACE,WR9Bc,kDQ+Bd,MRzCI,KQ0CJ,WR3BQ,+BQ6BR,kBACE,2BACA,WR7BM,gCQiCV,UACE,WRxCY,kDQyCZ,MRpDI,KQqDJ,WRtCQ,+BQwCR,gBACE,2BACA,WRxCM,gCQ4CV,aACE,WRrEW,QQsEX,MR/DI,KQiEJ,mBACE,iDACA,2BAIJ,YACE,WRhFY,QQiFZ,MRzEI,KQ2EJ,kBACE,mBACA,2BAIJ,WACE,yBACA,MR/FW,QQgGX,+BAEA,iBACE,+BACA,aRpGS,QQwGb,aACE,yBACA,MRjGQ,QQkGR,yBAEA,mBACE,aR9GS,QQ+GT,MR/GS,QQgHT,2BAKJ,4BAEE,WACA,mBACA,oBAGF,aACE,oBACA,oBAEA,oBACE,WACA,kBACA,WACA,YNtDJ,kBACA,QACA,SACA,gCMqDI,sBACA,kBACA,+BACA,mCAKJ,UACE,oBACA,mBACA,uBACA,WACA,YACA,UACA,cRvEmB,IQyEnB,iBACE,WACA,YAGF,iBACE,WACA,YAKJ,WACE,WACA,uBAIF,WACE,oBACA,SAEA,gBACE,gBAEA,4BACE,uBRvGW,KQwGX,0BRxGW,KQ2Gb,2BACE,wBR5GW,KQ6GX,2BR7GW,KQoHnB,KACE,eACA,ORhIW,KQiIX,MRjIW,KQkIX,WACA,YACA,WR/KiB,kDQgLjB,YACA,cRxHqB,IQyHrB,MR3LM,KQ4LN,UR/Ja,KQgKb,eACA,WR9KU,gCQ+KV,WRpGgB,aQqGhB,QR9Gc,IE5Ed,aACA,uBACA,mBM2LA,WACE,qBACA,WRpLQ,gCQsLR,sBACE,UACA,4BAIJ,gBACE,kBACA,WACA,QACA,2BACA,WRrNQ,QQsNR,MRnNI,KQoNJ,iBACA,cRxJe,IQyJf,UR7LW,KQ8LX,mBACA,UACA,WR9Hc,aQ+Hd,oBAMF,iBN7MA,oBACA,mBACA,IF4BW,IE3BX,kBACA,cFuCiB,KEtCjB,YFcqB,IEbrB,UFCe,0BECf,WFgEgB,aE/DhB,eACA,YAEA,uBACE,2BMgMF,iBAEE,WRlOI,KQmOJ,MR/OW,QQgPX,kBACA,UR1MW,KQ2MX,WRtNQ,gCQwNR,uBACE,2BACA,WRzNM,gCQ6NV,mBN3NA,oBACA,mBACA,IF4BW,IE3BX,kBACA,cFuCiB,KEtCjB,YFcqB,IEbrB,UFCe,0BECf,WFgEgB,aE/DhB,eACA,YAEA,yBACE,2BM8MF,mBAEE,yBACA,MRjPI,KQkPJ,sBACA,kBACA,URzNW,KQ2NX,yBACE,8BACA,2BCtQN,MPoDE,WFtCM,KEuCN,cFyBkB,KExBlB,QF0Da,KEzDb,WFkDgB,aEhDhB,YACE,2BACA,WF7BQ,gCS9BZ,MAEE,kBACA,gBAGA,cACE,WACA,kBACA,SACA,UACA,WACA,YACA,kGAMA,wBACA,WTuFc,aStFd,UAGF,oBACE,2BACA,UAKJ,UPqBE,WFtCM,KEuCN,cFyBkB,KExBlB,QF0Da,KEzDb,WFkDgB,aEhDhB,gBACE,2BACA,WF7BQ,gCSCZ,UAEE,kBACA,yBAEA,gBACE,aTnCW,QSoCX,2BAIF,kBACE,aTtCY,QSuCZ,8EAIF,cACE,aT1CW,QS2CX,+EAIF,qBACE,kBACA,UACA,WACA,iBACA,WTtDY,QSuDZ,MTjDQ,QSkDR,cTiBgB,KShBhB,UTxBW,KSyBX,YTVe,ISWf,yBAEA,yBACE,WT5DS,QS6DT,MTtDE,KSyDJ,0BACE,WThEU,QSiEV,MT3DE,KSgEN,oBACE,WACA,YACA,mBP7DF,aACA,uBACA,mBO6DE,eACA,WTpEO,QSqEP,MTnFW,QSoFX,cTTe,KSUf,WTkBc,aSfhB,0BACE,qBACA,WTrEe,kDSsEf,MT/EI,KSmFN,oBACE,UTxDW,KSyDX,YT9Ce,IS+Cf,cTpCS,KSqCT,MT1FQ,QS6FV,2BACE,MT7FQ,QS8FR,YThDiB,ISiDjB,cT1CS,KS2CT,UTrEW,KSyEb,wBACE,aACA,uBACA,ITpDS,ISqDT,cTnDS,KSoDT,eAEA,qCACE,gBACA,WT1GI,QS2GJ,MT9GM,QS+GN,cT7Cc,KS8Cd,UTtFS,KSuFT,YT1Ee,IS+EnB,oBACE,oBACA,mBACA,ITvES,ISwET,MTpIW,QSqIX,YTnFmB,ISoFnB,UTjGW,KSkGX,WThCc,aSkCd,0BACE,IT7EO,IS8EP,MT1IW,QSgJjB,iBP/FE,WFtCM,KEuCN,cFyBkB,KExBlB,QF0Da,KEzDb,WFkDgB,aEhDhB,uBACE,2BACA,WF7BQ,gCSqHZ,iBAEE,kBAEA,4BACE,WACA,YACA,mBPtIF,aACA,uBACA,mBOsIE,UTjHW,KSkHX,WTtIe,kDSuIf,MThJI,KSiJJ,cTlFe,KSqFjB,oBACE,UTzHW,KS0HX,YT/Ge,ISgHf,cTrGS,KSsGT,MT3JQ,QS8JV,mBACE,MT9JQ,QS+JR,YTjHiB,ISkHjB,cT3GS,KS4GT,UTtIW,KS0Ib,8BACE,aACA,sBACA,mBACA,YTpHS,KSqHT,6BAEA,4CACE,UT9IS,KS+IT,YTpIkB,ISqIlB,MTzLS,QS4LX,4CACE,UTzJS,KS0JT,MTpLM,QSqLN,YT9Ie,ISoJrB,cPnJE,WFtCM,KEuCN,cFyBkB,KExBlB,QF0Da,KEzDb,WFkDgB,aEhDhB,oBACE,2BACA,WF7BQ,gCSyKZ,cAEE,aACA,uBACA,IT1IW,KS4IX,4BACE,cACA,WACA,YP5LF,aACA,uBACA,mBO4LE,WT3Le,kDS4Lf,MTrMI,KSsMJ,cTxIe,KSyIf,UT1KW,KS6Kb,+BACE,OAEA,kCACE,UTnLS,KSoLT,YTzKiB,IS0KjB,cT/JO,ISgKP,MTpNM,QSuNR,iCACE,MTvNM,QSwNN,YT1Ke,IS2Kf,UT9LS,KSoMf,kBPvLE,WFtCM,KEuCN,cFyBkB,KExBlB,QF0Da,KEzDb,WFkDgB,aEhDhB,wBACE,2BACA,WF7BQ,gCS6MZ,kBAEE,kBAEA,0BACE,YACA,kBACA,ITlLS,KSmLT,KTnLS,KSoLT,eACA,MTnPW,QSoPX,WACA,YTlMe,ISqMjB,uCACE,kBACA,UACA,UTrNa,KSsNb,YTnMgB,ISoMhB,MTpPQ,QSqPR,cT/LS,KSgMT,kBAGF,sCACE,aACA,mBACA,ITvMS,KSyMT,qDACE,WACA,YACA,cT5LiB,IS6LjB,iBAIA,gEACE,YT9Ne,IS+Nf,MTxQI,QSyQJ,kBAGF,iEACE,UTjPO,KSkPP,MT7QI,QSoRZ,cP5OE,WFtCM,KEuCN,cFyBkB,KExBlB,QF0Da,KEzDb,WFkDgB,aEhDhB,oBACE,2BACA,WF7BQ,gCSkQZ,cAEE,kBACA,kBAEA,uBACE,yBACA,sBAEA,sCACE,kBACA,UACA,SACA,2BACA,iBACA,WTxRa,kDSyRb,MTlSE,KSmSF,cTlOe,KSmOf,UT5QS,KS6QT,YT9Pa,IS+Pb,yBAIJ,8BACE,eTxPS,KSyPT,gCACA,cT1PS,KS4PT,iCACE,UTnRS,KSoRT,YT1Qa,IS2Qb,cTjQO,ISkQP,MTtTM,QSyTR,qCACE,aACA,qBACA,uBACA,IT1QO,IS4QP,+CACE,UTjSO,KSkSP,MThUI,QSmUN,6CACE,UTlSQ,KSmSR,YT3RgB,IEWtB,WF1CiB,kDE2CjB,6BACA,sCACA,qBOiRI,6CACE,UT9SS,KS+ST,MT3UI,QSgVV,gCACE,gBACA,cT7RS,KS+RT,mCACE,cACA,MTtVM,QSuVN,UT5TS,KS6TT,aACA,mBACA,ITvSO,ISySP,2CACE,YACA,MTnWO,QSoWP,YTtTW,ISyTb,4CACE,WAEA,oDACE,YACA,MTtWG,QS4WX,2BACE,gBC1XJ,YACE,cVgEW,KU9DX,kBACE,cACA,UVkCW,KUjCX,YV6CiB,IU5CjB,MVIQ,QUHR,cVuDS,IUrDT,4BACE,MVLU,QUMV,gBAIJ,uBACE,UVqBW,KUpBX,MVNQ,QUOR,WV2CS,IUxCX,wBACE,UVeW,KUdX,MVlBY,QUmBZ,WVqCS,IUpCT,aACA,mBACA,IVkCS,IU7Bb,cACE,WACA,kBACA,UVIe,KUHf,YVHoB,mFUIpB,MV3BU,QU4BV,WVzBM,KU0BN,yBACA,cVkCiB,IUjCjB,WV+DgB,aU7DhB,2BACE,MVhCS,QUmCX,oBACE,aACA,aVhDW,QUiDX,yCAGF,uBACE,WVxCM,QUyCN,MV5CQ,QU6CR,mBAIF,iBACE,iBACA,UVxBW,KU2Bb,iBACE,kBACA,UV3BW,KU+Bb,uBACE,aVlEW,QUoEX,6BACE,0CAIJ,yBACE,aV3EY,QU6EZ,+BACE,0CAMN,sBACE,iBACA,gBAIF,oBACE,gBACA,qKACA,4BACA,sCACA,qBACA,cVnCY,KUuCd,YACE,aACA,mBACA,cV7CW,KU+CX,+DAEE,WACA,YACA,aVpDS,IUqDT,eAGF,kBACE,gBACA,eACA,iBAGF,qBACE,WACA,mBAEA,sDAEE,mBAMN,aACE,aACA,mBACA,IV5EW,KU8EX,qBACE,kBACA,WACA,YACA,WVjIU,QUkIV,cVpEiB,KUqEjB,eACA,WV5Cc,aU8Cd,4BACE,WACA,kBACA,QACA,SACA,WACA,YACA,WVhJE,KUiJF,cV/EiB,IUgFjB,WVvDY,aU0Dd,4BACE,WVlKS,QUoKT,mCACE,2BAOR,aACE,aACA,WAEA,+BACE,kBACA,UV5Ia,KU6Ib,MVzKQ,QU0KR,WVvKM,QUwKN,yBACA,0BACA,kBAGF,2BACE,0BAIA,kDACE,0BACA,+BACA,iBAGF,8CACE,0BAMN,aACE,aACA,qBACA,IVjJW,KUmJX,yBACE,gBRhNA,yBQ0MJ,aAUI,sBACA,oBAEA,yBACE,cV5JO,MUkKb,UACE,aACA,qCACA,IVpKW,KE7DT,yBQ8NJ,UAMI,2BCxOJ,gBACE,eACA,MACA,OACA,QACA,SACA,0BACA,QX2FuB,IW1FvB,UACA,kBACA,WX+FgB,aW7FhB,qBACE,UACA,mBAKJ,OACE,eACA,MACA,OACA,QACA,SACA,QX0Ec,IWzEd,aACA,mBACA,uBACA,QXoCW,KWnCX,UACA,kBACA,WXyEgB,aWvEhB,YACE,UACA,mBAEA,0BACE,mBAMN,cACE,kBACA,WXjCM,KWkCN,cX6BiB,KW5BjB,WXlBU,gCWmBV,gBACA,WACA,gBACA,aACA,sBACA,sBACA,WXiDgB,aW9ChB,uBACE,gBAGF,uBACE,gBAGF,uBACE,iBAGF,+BACE,eACA,gBACA,YACA,SACA,gBAKJ,cACE,QXjBW,KWkBX,gCACA,aACA,mBACA,8BAEA,2BACE,UX9CW,KW+CX,YXtCmB,IWuCnB,MXhFQ,QWiFR,SAGF,2BACE,WACA,YT7EF,aACA,uBACA,mBS6EE,yBACA,YACA,cX1Be,IW2Bf,MX1FQ,QW2FR,UX5DW,KW6DX,eACA,wBAEA,iCACE,WX7FI,QW8FJ,MXlGM,QWwGZ,YACE,QXnDW,KWoDX,OACA,gBACA,MX5GU,QW6GV,YX9DmB,IWkErB,cACE,QX5DW,KW6DX,6BACA,aACA,IXhEW,KWiEX,yBAEA,kCACE,uBAGF,mCACE,8BAMF,2BACE,gBAGF,yBACE,kBACA,QXlFS,KWoFT,qCACE,WACA,YACA,mBTrIJ,aACA,uBACA,mBSqII,UX/GU,KWgHV,cX5EiB,IW8EjB,mDACE,gCACA,MXzJO,QW4JT,mDACE,+BACA,MXhKQ,QWmKV,kDACE,gCACA,MXpKQ,QWuKV,gDACE,+BACA,MX7KO,QWiLX,sCACE,UXzIS,KW0IT,YXjIiB,IWkIjB,cXvHO,IWwHP,MX5KM,QW+KR,wCACE,MX/KM,QYZZ,YACE,aACA,mBACA,IZ4DW,IY3DX,eACA,UZkCa,KYhCb,6BACE,aACA,mBACA,IZqDS,IYpDT,MZCQ,QYCR,+BACE,MZFM,QYGN,WZ0FY,aYxFZ,qCACE,MZhBO,QYoBX,oCACE,MZZM,QYaN,YZ2Be,IYxBjB,qDACE,YACA,MZhBO,QYiBP,YZiCO,IY3Bb,UACE,aACA,IZwBW,IYvBX,gCACA,cZ0BW,KYxBX,oBACE,kBAGF,oBACE,aACA,mBACA,IZcS,IYbT,kBACA,MZvCQ,QYwCR,YZDiB,IYEjB,sCACA,mBACA,WZkDc,aYhDd,0BACE,MZ/CM,QYkDR,2BACE,MZ5DS,QY6DT,oBZ7DS,QYgEX,2BACE,gBACA,WZrDI,QYsDJ,MZzDM,QY0DN,cZSe,KYRf,eACA,YZpBiB,IY0BvB,WACE,aACA,IZjBW,IYkBX,QZnBW,IYoBX,WZnEQ,QYoER,cZPiB,KYSjB,qBACE,iBACA,MZ3EQ,QY4ER,YZrCiB,IYsCjB,cZde,IYef,WZec,aYbd,2BACE,MZlFM,QYmFN,8BAGF,4BACE,WZpFE,KYqFF,MZjGS,QYkGT,WZxEM,0BY+EV,0BACE,cZ1CS,KY4CT,qCACE,UZzES,KY0ET,YZ5DiB,IY6DjB,yBACA,MZtGM,QYuGN,iBACA,qBAIJ,uBACE,gBAEA,iCACE,cZ9DO,IYiET,iCACE,aACA,mBACA,IZlEO,KYmEP,iBACA,MZzHM,QY0HN,cZ1Da,IY2Db,WZ7BY,aY+BZ,2CACE,WACA,YVtHN,aACA,uBACA,mBUsHM,UZnGO,KYoGP,MZjII,QYoIN,uCACE,WZlIE,QYmIF,MZhJO,QYkJP,iDACE,MZnJK,QYuJT,wCACE,WZ1IG,QY2IH,MZzJO,QY0JP,YZzGa,IY2Gb,kDACE,MZ7JK,QYmKX,oCACE,YZnGQ,KYoGR,WZzGO,IY0GP,gBAEA,8CACE,UZpIO,KYqIP,iBAOR,YACE,aACA,mBACA,IZxHW,IY4HP,2CACE,WACA,mBACA,oBAKF,yCACE,WZjMO,QYkMP,MZtLA,KYuLA,aZnMO,QYwMb,uBACE,aACA,mBACA,uBACA,eACA,YACA,cACA,WZnMI,KYoMJ,yBACA,cZxIe,IYyIf,MZzMQ,QY0MR,UZ9KW,KY+KX,YZnKiB,IYoKjB,WZ9Gc,aYgHd,6BACE,WZ3MI,QY4MJ,aZzNS,QY0NT,MZ1NS,QY6NX,kEAEE,UZxLS,KY4Lb,uBACE,cACA,MZ3NQ,QabZ,UACE,eACA,WbaM,KcfR,WACE,kBACA,gBACA,WdqBiB,kDcpBjB,gBCJF,cACE,kBACA,sBACA,6DACA,gBbCE,yBaLJ,cAOI,sBAKJ,eACE,kBACA,MACA,OACA,QACA,SACA,oBAEA,+BACE,kBACA,kBACA,WAEA,yCb4GF,Ma3G4B,Mb4G5B,Oa5G4B,Mb6G5B,WFlIY,QEmIZ,kBACA,WACA,kBa/GI,WACA,abmEJ,yCa/DE,yCbqGF,MapG4B,MbqG5B,OarG4B,MbsG5B,WFjIc,QEkId,kBACA,WACA,kBaxGI,cACA,WACA,iDAIJ,6BACE,kBACA,QACA,SACA,YACA,aACA,uEACA,0BACA,WAKJ,cACE,kBACA,kBACA,UACA,Uf2BoB,Oe1BpB,cACA,eAIF,YACE,oBACA,mBACA,IfFW,IeGX,iBACA,WfrDM,KesDN,yBACA,cfUmB,KeTnB,cfLW,KeMX,UfhCa,KeiCb,YfpBqB,IeqBrB,Mf9DU,QE2FV,mCa1BA,wBACE,UfnCW,KEwDb,6BafF,eACE,UfrCc,KesCd,Yf/BsB,IegCtB,Yf7BkB,Ie8BlB,cftBW,KeuBX,Mf7EU,QE+FV,iCAtGE,yBa+EJ,eASI,Uf/CY,ME3CZ,yBaiFJ,eAaI,UfpDY,MeuDd,mCblCA,WFzCgB,kDE0ChB,6BACA,sCACA,qBaqCF,cACE,UfhEa,KeiEb,YfzDoB,Ie0DpB,YflDmB,IemDnB,cf1CY,Ke2CZ,MflGU,QemGV,iCb3GE,yBaqGJ,cASI,Uf1Ea,Me+EjB,cACE,aACA,IfzDW,Ke0DX,uBACA,cftDY,KeuDZ,gCbxHE,yBamHJ,cAQI,sBACA,oBAGF,mBblGA,oBACA,mBACA,IF4BW,IE3BX,kBACA,cFuCiB,KEtCjB,YFcqB,IEbrB,UFCe,0BECf,WFgEgB,aE/DhB,eACA,YAEA,yBACE,2BawFA,+BACE,WfhHa,kDeiHb,Mf1HE,Ke2HF,Wf5GM,+Be8GN,qCACE,Wf7GI,gCeiHR,iCACE,WfnIE,KeoIF,MfhJS,QeiJT,yBAEA,uCACE,WftIG,QEdP,yBaiIF,mBAwBI,WACA,wBAMN,YACE,aACA,uBACA,IfhGY,KEyCZ,+BA1GE,yBa8JJ,YAOI,sBACA,IfzGS,Me4GX,uBACE,aACA,sBACA,mBAEA,oCACE,UftIU,KeuIV,Yf9HkB,IEWtB,WF1CiB,kDE2CjB,6BACA,sCACA,qBaoHE,mCACE,UflJS,KemJT,Mf9KM,Qe+KN,YfxIe,Ie8IrB,YACE,kBACA,Wf9HY,Ke+HZ,kBAEA,gBACE,eACA,YACA,cf5He,Ke6Hf,Wf3KQ,gCe8KV,6BACE,kBACA,WACA,YACA,MACA,OACA,oBAEA,yEAEE,WACA,kBACA,kBAGF,qCACE,WACA,YACA,WftMU,kDeuMV,UACA,Ub/HJ,6BamIE,oCACE,WACA,YACA,WfhNY,kD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Bb,Mfl3BM,Qem3BN,Yfn0Bc,Ieo0Bd,cf5zBQ,KE7DV,0Bao3BA,kDAQI,Uft1BQ,MExCZ,yBas3BA,kDAYI,Uf31BO,Ke41BP,gBAGF,mEbz0BJ,WF1CiB,kDE2CjB,6BACA,sCACA,qBaw0BM,Yft1BgB,Ie21BtB,gCACE,aACA,qCACA,Ifj1BU,Kek1BV,iBACA,cACA,kBACA,Ubl5BA,0Ba24BF,gCAUI,0BACA,If11BO,Ke21BP,iBbz5BF,yBa64BF,gCAgBI,gBAIJ,8BACE,Wfx5BI,Key5BJ,cf11Be,Ke21Bf,Qfr2BU,Kes2BV,Wf54BQ,+Be64BR,qCACA,kBACA,gBACA,wBAEA,sCACE,WACA,kBACA,SACA,UACA,WACA,YACA,Wf/5Ba,kDeg6Bb,cf12Ba,Ke22Bb,UACA,WACA,4BAGF,oCACE,wCACA,Wfh6BM,gCek6BN,4CACE,UAGF,+CACE,oCACA,Wfh7BW,kDek7BX,iDACE,Mf57BF,Keg8BF,iDb74BJ,WFzCgB,kDE0ChB,6BACA,sCACA,qBa+4BE,yCACE,WACA,YACA,aACA,mBACA,uBACA,Wfz8BK,Qe08BL,cf34Be,Ke44Bf,cf15BO,Ke25BP,gDAEA,2CACE,eACA,Mf99BO,Qe+9BP,0BAKF,yDACE,Ufz7BQ,Ke07BR,Yfl7BgB,Iem7BhB,Mf99BI,Qe+9BJ,cf36BK,Ie46BL,wBACA,cbx+BJ,yBak+BE,yDASI,Ufl8BM,Mes8BR,gFACE,6BAIJ,wDACE,Uf/8BO,Keg9BP,Yft8Be,Ieu8Bf,Mfh/BI,Qei/BJ,cf97BK,Iei8BP,8DACE,Ufz9BO,Ke09BP,Mfr/BI,Qes/BJ,Yfx8Ba,Ie88Bf,sDACE,8BAEA,wDACE,MfxgCI,Qe4gCR,4DACE,6DAKF,sDACE,gCAEA,wDACE,MfnhCK,QeuhCT,4DACE,sGAKF,sDACE,gCAEA,wDACE,MfhiCM,QeoiCV,4DACE,wGbziCJ,yBai6BF,8BA6II,Qfh/BO,Kek/BP,yCACE,WACA,YAEA,2CACE,gBAQV,mBACE,KACE,UACA,2BAEF,GACE,UACA,yBAIJ,aACE,wCC7kCF,sBACE,aACA,iBACA,iBhBYQ,QgBXR,kBAIF,oBAC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nkBS,KgBokBT,YhBzjBiB,IgB0jBjB,MhBnmBM,QgBomBN,chB/iBO,KgBqjBb,kBACE,aACA,sBACA,IhBvjBW,KgB2jBb,iBACE,WhB/mBM,KgBgnBN,chBljBiB,KgBmjBjB,QhB7jBW,KgB8jBX,WhBpmBU,0BgBqmBV,WhBxhBgB,agB0hBhB,uBACE,WhBvmBQ,+BgB0mBV,oBACE,UhB9lBW,KgB+lBX,YhBrlBmB,IgBslBnB,MhB/nBQ,QgBgoBR,chB3kBS,KgB4kBT,ehB7kBS,IgB8kBT,gCAGF,iCACE,UhB1mBW,KgB2mBX,MhBtoBQ,QgBuoBR,YhBzlBiB,IgB2lBjB,qCACE,iBhBvoBI,QgBwoBJ,yBACA,chB7kBa,IgB8kBb,QhBzlBO,KgB0lBP,gBACA,SAEA,0CACE,oCACA,UhBxnBO,KgBynBP,MhBrpBI,QgBspBJ,qBACA,sBAKJ,uCACE,WACA,yBACA,cACA,iBhB7pBE,KgB8pBF,yBACA,chBlmBa,IgBmmBb,gBAEA,6CACE,iBhBjqBG,QgBoqBL,0CACE,gBACA,iBACA,UhBhpBO,KgBipBP,YhBpoBe,IgBqoBf,MhB9qBI,QgB+qBJ,gCACA,sCAEA,2DACE,+BAIJ,0CACE,iBACA,UhB7pBO,KgB8pBP,MhBzrBI,QgB0rBJ,gCAEA,2DACE,+BAIJ,0CACE,WhBrmBU,agBumBV,wDACE,mBAGF,gDACE,sCAIJ,gEACE,sCAOR,gBACE,WACA,yBAEA,mBACE,gCAEA,8BACE,mBAIJ,mBACE,gBACA,iBACA,UhBzsBW,KgB0sBX,YhB7rBmB,IgB8rBnB,MhBvuBQ,QgBwuBR,UACA,iBhBruBM,QgBwuBR,mBACE,iBACA,UhBltBW,KgBmtBX,MhB9uBQ,QgBgvBR,wBACE,qBACA,gBACA,iBhBhvBI,QgBivBJ,chBtrBa,IgBurBb,oCACA,UhB5tBS,KgB6tBT,MhBjwBS,QiBHf,YACE,+BACA,aACA,mBACA,uBACA,4EACA,kBACA,kBACA,gBAGA,oBACE,WACA,kBACA,SACA,WACA,YACA,aACA,2EACA,WACA,kBAGF,mBACE,WACA,kBACA,YACA,UACA,YACA,aACA,4EACA,WACA,kBAIJ,iBACE,WACA,gBACA,cACA,kBACA,UAGF,YACE,wBACA,mBACA,4BACA,kBACA,2BACA,oCAEA,yBARF,YASI,kBACA,oBAIJ,cACE,kBACA,mBAEA,0BACE,oBACA,mBACA,SACA,mBAEA,8BACE,YACA,WAGF,qCACE,eACA,gBACA,0BACA,iBACA,2BACA,mBAIJ,2BACE,eACA,gBACA,uBACA,kBAGF,8BACE,eACA,uBAIJ,YACE,mBAEA,wBACE,mBAEA,mCACE,gBAIJ,wBACE,cACA,eACA,gBACA,uBACA,kBAGF,wBACE,WACA,kBACA,eACA,oCACA,mBACA,wBACA,wBACA,aAEA,qCACE,wBAGF,8BACE,iCACA,yCAGF,8BACE,kCAEA,oCACE,0CAKN,2BACE,aACA,mBACA,QACA,gBAEA,gDACE,WACA,YACA,iCACA,eAGF,iCACE,eACA,uBACA,eACA,iBAKN,cACE,WACA,kBACA,eACA,gBACA,mBACA,mCACA,YACA,mBACA,eACA,wBACA,4BAEA,oBACE,2BACA,4BAGF,qBACE,wBAGF,uBACE,WACA,mBACA,eAIJ,aACE,aACA,uBACA,mBACA,SACA,gBACA,iBACA,wCACA,eAEA,yBAVF,aAWI,SACA,gBACA,kBAGF,eACE,eACA,uBACA,qBACA,0BACA,aACA,mBACA,QAEA,qBACE,0BAGF,iBACE,eAIJ,6BACE,yBACA,eAKJ,aACE,mBACA,kBACA,kBACA,eACA,aACA,mBACA,QAEA,yBACE,gCACA,2BACA,sCAGF,2BACE,gCACA,0BACA,sCAGF,wBACE,+BACA,0BACA,qCAGF,eACE,eAKJ,eACE,kBACA,MACA,OACA,QACA,SACA,8BACA,aACA,mBACA,uBACA,mBACA,WACA,UACA,oBACA,4BAEA,sBACE,UACA,mBAGF,wBACE,WACA,YACA,oCACA,qCACA,kBACA,kCAIJ,gBACE,GACE,0BC5SJ,WACE,UlBkFoB,OkBjFpB,cACA,ehBCE,yBgBJJ,WAMI,gBAKJ,QACE,wBAGF,SACE,yBAGF,gBACE,gCAGF,QACE,wBAGF,eACE,+BAGF,QACE,wBAIF,ahBhBE,aACA,uBACA,mBgBkBF,chBdE,aACA,8BACA,mBgBgBF,aACE,sBAGF,WACE,eAGF,cACE,mBAGF,aACE,uBAGF,WACE,qBAGF,gBACE,uBAGF,eACE,2BAGF,aACE,yBAGF,iBACE,8BAGF,gBACE,6BAIF,QACE,IlBxBW,IkB2Bb,QACE,IlB3BW,IkB8Bb,QACE,IlB9BW,KkBiCb,QACE,IlBjCW,KkBoCb,QACE,IlBpCW,KkBoDX,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,WlB/Pa,QkBkQf,cACE,WlBlQe,QkBqQjB,UACE,WlB3PM,KkB8PR,SACE,WlB9PQ,QkBiQV,UACE,WlBjQS,QkBoQX,qBACE,WlB9PiB,kDkBiQnB,oBACE,WlBjQgB,kDkBoQlB,kBACE,WlBpQc,kDkBwQhB,QACE,yBAGF,UACE,uBAGF,YACE,6BAGF,eACE,gCAGF,aACE,8BAGF,cACE,+BAGF,gBACE,alBxTa,QkB4Tf,WACE,gBAGF,YACE,clBzPiB,IkB4PnB,SACE,clB5PiB,IkB+PnB,YACE,clB/PiB,KkBkQnB,YACE,clBlQiB,KkBqQnB,cACE,clBpQmB,KkBuQrB,gBACE,clBvQqB,IkB2QvB,aACE,2BAGF,WACE,WlBpUU,0BkBuUZ,QACE,WlBvUU,+BkB0UZ,WACE,WlB1UU,gCkB6UZ,WACE,WlB7UU,gCkBiVZ,iBACE,gBAGF,eACE,cAGF,mBACE,kBAGF,mBACE,kBAGF,iBACE,gBAGF,iBACE,gBAIF,KACE,UAGF,MACE,WAGF,MACE,WAGF,MACE,WAGF,MACE,WAGF,MACE,WhB1ZE,yBgB+ZF,SACE,yBhB9ZA,0BgBmaF,SACE,yBAKF,yBADF,aAEI,yBhB5aA,yBgBgbJ,cAEI,yBAKJ,gBACE,eAGF,gBACE,eAGF,oBACE,mBAIF,WACE,UAGF,YACE,YAGF,YACE,WAGF,YACE,YAGF,aACE,UAIF,YACE,WlBrXgB,akBwXlB,iBACE,WlBxXgB,ckB2XlB,iBACE,WlB3XgB,akB8XlB,iBACE","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/_kjb-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/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/base/_utilities.scss"],"names":[],"mappings":"CAIA,MACE,mCACA,gCAGF,EACE,SACA,UACA,sBAGF,KACE,eACA,mCACA,kCACA,kCAGF,KACE,YCcoB,0GDbpB,YCqCmB,IDpCnB,MCPU,QDQV,iBCLM,KDMN,kBAIF,MACE,gBAIF,EACE,qBACA,cACA,WCqEgB,aDjElB,IACE,eACA,YACA,cAIF,OACE,oBACA,kBACA,oBACA,eACA,yBACA,YACA,UAIF,sBAGE,oBACA,kBACA,oBACA,YACA,aAIF,MACE,yBACA,iBAIF,kBACE,YCvBiB,IDwBjB,YCpBkB,IDwBpB,2FAGE,cAIF,YACE,mCACA,MC3EU,QD8EZ,iBACE,mCACA,MChFU,QCbZ,WACE,2BACA,qEACA,oBACA,kBACA,kBAIF,KACE,UD4Be,KC3Bf,YDqCoB,ICpCpB,MDCU,QCGZ,OACE,UD4Bc,KC3Bd,YDkCsB,ICjCtB,cD4CW,KE3DT,yBDYJ,OAMI,UDqBY,MEzCZ,yBDcJ,OAUI,UDgBY,MCZhB,OACE,UDac,KCZd,YDmBiB,IClBjB,cD6BW,KE1DT,yBD0BJ,OAMI,UDMY,MExCZ,yBD4BJ,OAUI,UDCW,MCGf,OACE,UDFc,KCGd,YDKiB,ICJjB,cDeW,KE1DT,yBDwCJ,OAMI,UDTW,MCaf,OACE,UDbc,KCcd,YDNqB,ICOrB,cDIW,ICDb,OACE,UDpBa,KCqBb,YDZqB,ICarB,cDFW,ICKb,OACE,UD3Ba,KC4Bb,YDlBqB,ICmBrB,cDRW,ICYb,EACE,cDZW,KCaX,YDnBmB,ICqBnB,aACE,gBAIJ,cACE,MDjFa,QCoFf,gBACE,MDpFe,QCuFjB,WACE,MD9EU,QCiFZ,WACE,MDjFU,QCoFZ,YACE,MDpFW,QCuFb,YACE,MDvFM,KC2FR,WACE,gBAGF,aACE,kBAGF,YACE,iBAIF,cACE,YDzEoB,IC4EtB,aACE,YD5EmB,IC+ErB,eACE,YD/EqB,ICkFvB,WACE,YDlFiB,ICqFnB,gBACE,YDrFsB,ICyFxB,SACE,UD1Ga,KC6Gf,SACE,UD7Ga,KCgHf,WACE,UDhHe,KCmHjB,SACE,UDlHa,KCqHf,SACE,UDrHa,KCyHf,MACE,UD3Ha,KC4Hb,YDpHoB,ICqHpB,YD5GkB,IC6GlB,MDxJU,QC2JZ,SACE,UDrIa,KCsIb,MD7JU,QCgKZ,MACE,YD9IiB,oCC+IjB,UD3Ia,KC4Ib,WDhKQ,QCiKR,gBACA,cD3GiB,IC+GnB,EACE,MDtLa,QCwLb,QACE,MDxLa,QC6LjB,WACE,QDnIW,KCoIX,cACA,8BACA,WDnLQ,QCoLR,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,WHnQQ,gCGuQZ,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,oOAQF,YAOA,WACE,2BACA,gBACA,oLAIF,WACE,+BACA,gBACA,2KAMF,WACE,2BACA,gBACA,wKAIF,WACE,2BACA,gBACA,wKAIF,WACE,2BACA,gBACA,wKCzDF,MAEE,wBACA,0BACA,uBACA,yBACA,yBACA,wBACA,yBAGA,qBACA,qBACA,sBACA,iBACA,mBACA,oBACA,uBAGA,2CACA,gDACA,iDACA,iDAIF,OACE,kBACA,UACA,WACA,UACA,YACA,gBACA,sBACA,mBACA,SAMF,eACE,kBACA,sBACA,QLkDe,IKjDf,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,aACA,mBACA,SACA,YACA,WAEA,QACE,aACA,mBACA,SACA,YACA,qBAGF,UACE,YACA,YAIJ,WACE,eACA,gBACA,uBACA,qBACA,qBACA,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,YLnMkB,0GKoMlB,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,YLxVc,0GKyVd,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,YLragB,0GKsahB,eACA,gBACA,cACA,iBACA,iBACA,kBAGF,6CACE,YL/agB,0GKgbhB,eACA,gBACA,cACA,iBACA,kBACA,kBAGF,gDACE,aACA,SACA,WACA,gBAIF,kDACE,OACA,aACA,mBACA,uBACA,YACA,mBACA,cACA,YLxcgB,0GKychB,eACA,gBACA,kBACA,qBACA,wBAEA,wDACE,mBAKJ,iDACE,OACA,aACA,mBACA,uBACA,YACA,mBACA,WACA,YL7dgB,0GK8dhB,eACA,gBACA,kBACA,qBACA,wBAEA,uDACE,oCAKJ,6CACE,mBACA,iBACA,kBACA,SACA,2BAEA,gEACE,gBACA,cAGF,gEACE,aACA,sBACA,QAIF,gEACE,YL9fc,0GK+fd,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,YLriBc,0GKsiBd,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,YL5kBc,0GK6kBd,eACA,gBACA,cACA,gBAEA,8EACE,8BAKJ,6DACE,aACA,gBACA,SACA,eACA,mBAGE,kEACE,cACA,0BACA,YLnmBU,0GKomBV,eACA,gBACA,cACA,qBACA,iBACA,0BAEA,wEACE,cAKJ,oFACE,gBACA,cAOJ,mEACE,gBAEA,mFACE,yBAIJ,kEACE,cAOV,gBACE,kBACA,ML9mBW,KK+mBX,QACA,2BACA,WACA,YACA,yBACA,YACA,eACA,YACA,YAEA,qBACE,cACA,WACA,WACA,WL9qBQ,QK+qBR,kBACA,6BACA,oBAEA,yDAEE,WACA,kBACA,OACA,WACA,WACA,WL1rBM,QK2rBN,6BAGF,sCACA,oCAGF,wBACE,yBACA,8DACA,8DAOJ,gBAEE,eACA,MACA,OACA,QACA,SACA,WLhtBM,KKitBN,QLhoBc,IKioBd,YACA,gBACA,gBACA,aACA,sBAKA,4BACE,QL7qBS,KK8qBT,WL5tBM,QK6tBN,gCAGF,0BACE,gBACA,SACA,UACA,YAEA,oCACE,gCAEA,sCACE,aACA,mBACA,QL9rBK,KK+rBL,MLjvBI,QKkvBJ,ULxtBO,KKytBP,YL/sBa,IKgtBb,qBACA,kBAGF,yDACE,WACA,kBACA,MLzsBK,KK0sBL,QACA,UACA,WACA,+BACA,gCACA,yCACA,mCAGF,sDACE,2CAGF,8CACE,aACA,gBACA,UACA,SACA,WL1wBE,QK4wBF,gDACE,cACA,4BACA,MLlxBE,QKmxBF,UL5vBK,KK6vBL,qBAEA,sDACE,WLrxBJ,KKsxBI,MLpyBG,QKyyBT,uDACE,cAKN,2BACE,kBACA,WLnyBI,KKoyBJ,6BACA,gBAQJ,UACE,aACA,gBACA,QACA,SACA,UAGA,aACE,kBAGE,oCACE,2BACA,0BAGF,oCACE,mBACA,UACA,wBAIJ,uBACE,kBACA,qBACA,OACA,gBACA,wBACA,oCACA,cLlxBa,KKmxBb,4BACA,cACA,kBACA,UACA,4BACA,wBACA,QLvwBa,IKwwBb,gBACA,SAEA,0BACE,SAEA,4BACE,cACA,iBACA,uBACA,qBACA,UL10BK,KK20BL,YL/zBW,IKg0BX,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,YL9/BgB,0GK+/BhB,eACA,gBACA,cACA,mBAIJ,2BACE,cACA,uBACA,cAGF,+BACE,YL7gCkB,0GK8gClB,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,cLpgCa,KKqgCb,4BACA,cACA,kBACA,UACA,4BACA,wBACA,QLz/Ba,IK2/Bb,2EACE,gBACA,SACA,UAEA,8EACE,SAEA,gFACE,aACA,mBACA,QACA,iBACA,uBACA,qBACA,ULjkCG,KKkkCH,YLtjCS,IKujCT,wBAEA,kFACE,eACA,WACA,kBAGF,sFACE,2BACA,0BAOV,gEACE,mBACA,UACA,wBAMN,yBACE,kBACE,cCroCJ,eACE,gBACA,6BAEA,0BACE,iBACA,cACA,eAIJ,gBACE,aACA,8BACA,mBACA,iBACA,eACA,SJVE,0BIIJ,gBASI,sBACA,uBACA,SACA,gBAKJ,aACE,aACA,sBACA,SAEA,0BACE,YACA,YACA,mBAGF,2BACE,aACA,mBACA,QAEA,wCACE,YNZgB,0GMahB,eACA,gBACA,cACA,qBACA,0BAEA,8CACE,cACA,0BAIJ,6CACE,cACA,eACA,iBAIJ,+BACE,YNjCkB,0GMkClB,eACA,gBACA,cACA,SACA,gBAKJ,cACE,aACA,sBACA,SACA,qBJzEE,0BIqEJ,cAOI,uBACA,YAIA,0DACE,YACA,YACA,eACA,YN3DgB,0GM4DhB,eACA,gBACA,cACA,gBACA,yBACA,kBACA,eACA,wBACA,gBACA,uQACA,4BACA,sCACA,mBAEA,gEACE,qBAGF,gEACE,aACA,qBACA,uCJ3GJ,0BIiFA,0DA8BI,YAKN,8BACE,YN3FkB,0GM4FlB,eACA,gBACA,cACA,SACA,gBJ1HA,0BIoHF,8BASI,gBJ/HF,yBIuIA,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,SJpNF,yBI2NF,gBACE,aACA,sBAGA,6BACE,iBAEA,0CACE,QAGF,2CACE,QAGF,+CACE,QAIJ,8BACE,iBAEA,oDACE,QACA,WAGF,8CACE,SC7PR,MACE,aACA,IP6DW,KO1DX,aACE,0BAGF,aACE,qCAGF,aACE,qCAGF,aACE,qCAGF,aACE,qCAGF,aACE,qCAGF,cACE,sCAIF,WACE,2DAGF,cACE,2DAGF,cACE,2DAIF,YACE,MAGF,aACE,IPSS,IONX,aACE,IPMS,KOHX,aACE,IPGS,kBOCT,SL1DA,0BK+DA,gBACE,0BAGF,gBACE,sCLtEF,yBK2EA,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,IPvEW,KEzDT,0BK6HJ,UAMI,sCLrIA,yBK+HJ,UAUI,2BAKJ,iBACE,aACA,2DACA,IPtFW,KE3DT,yBK8IJ,iBAMI,2BAKJ,eACE,aACA,2DACA,IP/FY,KOgGZ,mBL7JE,yBKyJJ,eAOI,sCAKJ,cACE,aACA,qCACA,IP5GW,KE5DT,yBKqKJ,cAMI,2BAKJ,cACE,aACA,8BACA,IPtHY,KOuHZ,kBLlLE,0BK8KJ,cAOI,2BAKJ,aACE,aACA,8BACA,IP/HY,KE9DV,0BK0LJ,aAMI,0BACA,IPtIU,MOyIZ,2BACE,aACA,qCACA,IP5IU,KE3DV,0BKoMF,2BAMI,sCL5MF,yBKsMF,2BAUI,0BACA,mBAMN,cACE,aACA,qCACA,IP/JW,KOgKX,mBLzNE,0BKqNJ,cAOI,0BACA,IPnKS,MOwKb,kBACE,aACA,2DACA,IP5KW,KE3DT,yBKoOJ,kBAMI,2BAKJ,YACE,aACA,qCACA,IPtLW,KOuLX,kBLjPE,0BK6OJ,YAOI,sCLtPA,yBK+OJ,YAWI,2BAKJ,YACE,aACA,8BACA,IPpMY,KOqMZ,mBLjQE,0BK6PJ,YAOI,0BACA,IP3MS,MO8MX,oBACE,cAEA,sBACE,cL5QF,0BKwQF,oBAQI,eAMN,cACE,aACA,qCACA,IP/NW,KE1DT,0BKsRJ,cAMI,2BClSJ,WACE,URgFoB,OQ/EpB,cACA,eNCE,yBMJJ,WAMI,gBAIF,iBACE,eACA,eAGF,kBACE,gBAGF,gBACE,iBAKJ,SACE,gBNtBE,yBMqBJ,SAII,gBAIF,YACE,eAGF,YACE,gBAGF,gBACE,cAGF,mBACE,iBAMF,iBACE,WRpCM,QQuCR,kBACE,WRvCO,QQ0CT,oBACE,MR7CI,KQgDN,iBACE,WRpDQ,QQqDR,MRlDI,KQuDR,gBACE,kBACA,cRNY,KE/DV,yBMmEJ,gBAKI,cRVU,MQaZ,+BACE,oBACA,mBACA,IRtBS,IQuBT,iBACA,WRlEO,QQmEP,MRnFW,QQoFX,cRTiB,KQUjB,URlDW,KQmDX,YRtCmB,IQuCnB,cR3BS,KQ8BX,+BACE,URlDY,KQmDZ,YR3Ce,IQ4Cf,cRjCS,KQkCT,MRnFQ,QETR,yBMwFF,+BAOI,URzDU,MQgEd,kCACE,URpEW,KQqEX,MR9FQ,QQ+FR,YRrDiB,IQsDjB,gBACA,cN3GA,yBMsGF,kCAQI,UR5EW,MQkFjB,iBACE,kBACA,iBACA,aACA,sBAEA,+BACE,OACA,YR9CY,KQmDhB,cACE,kBACA,gBAIF,eACE,kBACA,UC7IF,KPiCE,oBACA,mBACA,IF0BW,IEzBX,kBACA,cFqCiB,KEpCjB,YFYqB,IEXrB,UFDe,KEEf,qBACA,WF8DgB,aE7DhB,eACA,YAEA,WACE,2BO9CJ,KAEE,kBACA,gBAGA,QACE,iBACA,UT6BW,KS1Bb,QACE,kBACA,UTyBa,KStBf,QACE,kBACA,UTqBW,KSlBb,QACE,kBACA,UTiBW,KSbb,aACE,WT1BW,QS2BX,MTbI,KScJ,WTHQ,+BSKR,mBACE,2BACA,WTLM,gCSQR,oBACE,2BAIJ,eACE,mBACA,cAEA,qBACE,WT7BK,QS8BL,2BAIJ,YACE,MTrCI,KSsCJ,WT3BQ,+BS6BR,kBACE,2BACA,WT7BM,gCSiCV,UACE,MT/CI,KSgDJ,WTrCQ,+BSuCR,gBACE,2BACA,WTvCM,gCS2CV,aACE,WTlEW,QSmEX,MT1DI,KS4DJ,mBACE,iDACA,2BAIJ,YACE,WT7EY,QS8EZ,MTpEI,KSsEJ,kBACE,mBACA,2BAIJ,WACE,yBACA,MT5FW,QS6FX,+BAEA,iBACE,6BACA,aTjGS,QSqGb,aACE,yBACA,MT5FQ,QS6FR,yBAEA,mBACE,aT3GS,QS4GT,MT5GS,QS6GT,2BAKJ,4BAEE,WACA,mBACA,oBAGF,aACE,oBACA,oBAEA,oBACE,WACA,kBACA,WACA,YPnDJ,kBACA,QACA,SACA,gCOkDI,sBACA,kBACA,+BACA,mCAKJ,UACE,oBACA,mBACA,uBACA,WACA,YACA,UACA,cTtEmB,ISwEnB,iBACE,WACA,YAGF,iBACE,WACA,YAKJ,WACE,WACA,uBAIF,WACE,oBACA,SAEA,gBACE,gBAEA,4BACE,uBTtGW,KSuGX,0BTvGW,KS0Gb,2BACE,wBT3GW,KS4GX,2BT5GW,KSmHnB,KACE,eACA,OT/HW,KSgIX,MThIW,KSiIX,WACA,YAEA,YACA,cTvHqB,ISwHrB,MTtLM,KSuLN,UT9Ja,KS+Jb,eACA,WT7KU,gCS8KV,WTnGgB,aSoGhB,QT7Gc,IE1Ed,aACA,uBACA,mBOwLA,WACE,qBACA,WTnLQ,gCSqLR,sBACE,UACA,4BAIJ,gBACE,kBACA,WACA,QACA,2BACA,WThNQ,QSiNR,MT9MI,KS+MJ,iBACA,cTvJe,ISwJf,UT5LW,KS6LX,mBACA,UACA,WT7Hc,aS8Hd,oBAMF,iBP1MA,oBACA,mBACA,IF0BW,IEzBX,kBACA,cFqCiB,KEpCjB,YFYqB,IEXrB,UFDe,KEEf,qBACA,WF8DgB,aE7DhB,eACA,YAEA,uBACE,2BO6LF,iBAEE,WT7NI,KS8NJ,MT5OW,QS6OX,kBACA,UTzMW,KS0MX,WTrNQ,gCSuNR,uBACE,2BACA,WTxNM,gCS4NV,mBPxNA,oBACA,mBACA,IF0BW,IEzBX,kBACA,cFqCiB,KEpCjB,YFYqB,IEXrB,UFDe,KEEf,qBACA,WF8DgB,aE7DhB,eACA,YAEA,yBACE,2BO2MF,mBAEE,yBACA,MT5OI,KS6OJ,sBACA,kBACA,UTxNW,KS0NX,yBACE,8BACA,2BAUN,0GAME,kBACA,cT3MiB,IS4MjB,UT/Oe,KSgPf,YTrOmB,ISsOnB,qBACA,WTjLgB,aSkLhB,oBACA,mBACA,uBACA,IT/NW,ISgOX,eACA,YAEA,yBAnBF,0GAoBI,kBACA,UT7PW,MSgQb,sHACE,eAKJ,oCAGE,MT9RM,KSgSN,gDACE,2BACA,WTvRQ,+BS0RV,kDACE,wBAKJ,sBACE,WT5SM,KS6SN,MThTU,QSiTV,yBAEA,4BACE,WThTM,QSiTN,aThUW,QSiUX,MTjUW,QSoUb,6BACE,sBAKJ,iBACE,WT3Ua,QS4Ub,MT9TM,KSgUN,uBACE,2BACA,WTvTQ,+BSwTR,WThVa,QSmVf,wBACE,wBAKJ,mBACE,WTvVc,QSwVd,MT9UM,KSgVN,yBACE,2BACA,WTvUQ,+BSwUR,8BAGF,0BACE,wBAMF,qBACE,WT9VI,KS+VJ,MTlWQ,QSmWR,yBAEA,2BACE,WTlWI,QSmWJ,aTlXS,QSmXT,MTnXS,QSuXb,qBAEE,MT3WI,KS6WJ,2BACE,2BACA,WTpWM,+BSwWV,uBACE,WT9XY,QS+XZ,MTrXI,KSuXJ,6BACE,2BACA,WT9WM,+BSoXZ,UACE,iBACA,6BACA,MThZa,QSiZb,mCACA,cT5UiB,IS6UjB,UTjXa,KSkXb,YTrWmB,ISsWnB,eACA,WTjTgB,aSkThB,oBACA,mBACA,IT/VW,ISiWX,gBACE,6BACA,aT7ZW,QSgab,YACE,eAIJ,YACE,iBAEA,MT1ZM,KS2ZN,YACA,cTnWiB,ISoWjB,UTxYa,KSyYb,YT7XmB,IS8XnB,eACA,WTzUgB,aS0UhB,oBACA,mBACA,ITvXW,ISyXX,kBACE,2BACA,WT5ZQ,+BS+ZV,cACE,eAIJ,YACE,iBACA,WTjbM,KSkbN,MTpbU,QSqbV,yBACA,cT3XiB,IS4XjB,UThaa,KSiab,YTrZmB,ISsZnB,eACA,WTjWgB,aSkWhB,oBACA,mBACA,IT/YW,ISiZX,kBACE,aT5cW,QS6cX,MT7cW,QSgdb,cACE,eAQJ,kBACE,cACA,oBACA,mBACA,uBACA,gBACA,YACA,kBACA,YACA,cT1ZiB,KS2ZjB,UT7ba,KS8bb,YTnbiB,ISobjB,MTvdM,KSwdN,eACA,WTlYgB,aSmYhB,mBAGA,6BACE,mBAEA,mCACE,iDACA,2BACA,WTxdM,+BS2dR,oCACE,wBAKJ,2BACE,mBAEA,iCACE,WT7fW,QS8fX,2BACA,WTveM,+BS0eR,kCACE,wBAGF,oCACE,WTvfQ,QSwfR,mBACA,eACA,gBAIJ,yBAtDF,kBAuDI,WACA,eACA,YACA,UT/ea,MSufjB,YACE,oBACA,mBACA,uBACA,QACA,YACA,kBACA,yBACA,MTrhBM,KSshBN,YACA,cT7diB,KS8djB,eACA,YTtfiB,ISufjB,eACA,qCAEA,kBACE,yBPxiBA,0BOuhBJ,YAqBI,eACA,aCnjBJ,cACE,oBACA,mBACA,uBACA,iBACA,cVwEmB,KUvEnB,UV8Ba,KU7Bb,YV2CqB,IU1CrB,mBACA,WV8FgB,aU3FhB,6BACE,gCACA,MVRY,QUWd,+BACE,gCACA,MVZW,QUeb,4BACE,6BACA,MVtBW,QUyBb,8BACE,gCACA,MVfQ,QUkBV,gCACE,+BACA,gCAGF,4BACE,gCACA,MVjCY,QUoCd,6BACE,gCACA,MVrCW,QU0Cf,qBACE,oBACA,mBACA,uBACA,iBACA,cVuBmB,KUtBnB,UVnBa,KUoBb,YVNqB,IUOrB,cVKW,KUJX,WV6CgB,aU3ChB,oCACE,gCACA,MVxDY,QU2Dd,sCACE,gCACA,MV5DW,QU+Db,mCACE,6BACA,MVtEW,QUyEb,qCACE,gCACA,MV/DQ,QUkEV,uCACE,+BACA,gCAKJ,UACE,gBACA,eACA,cVlBiB,IUsBnB,UACE,iBACA,UV3Da,KU4Db,cVxBiB,IU4BnB,eACE,yBACA,8BAEA,8BACE,MVpGY,QUqGZ,aVrGY,QUwGd,gCACE,MVxGW,QUyGX,aVzGW,QU4Gb,6BACE,MVlHW,QUmHX,aVnHW,QUsHb,+BACE,MV3GQ,QU4GR,aV5GQ,QUkHV,cACE,aVrES,IUsET,eAKJ,oBACE,oBACA,mBACA,uBACA,eACA,YACA,cACA,WVxIc,QUyId,MV/HM,KUgIN,cVlEqB,IUmErB,eACA,YV/FiB,IUgGjB,cAEA,kCACE,WVpJW,QUuJb,kCACE,WVnJW,QUsJb,kCACE,WVzJY,QU0JZ,MVlJQ,QUqJV,iCACE,WV7JY,QWNhB,MToDE,WFpCM,KEqCN,cFuBkB,KEtBlB,QFwDa,KEvDb,WFgDgB,aE9ChB,YAEE,WF/BQ,gCW5BZ,MAEE,kBACA,gBAIF,UT6CE,WFpCM,KEqCN,cFuBkB,KEtBlB,QFwDa,KEvDb,WFgDgB,aE9ChB,gBAEE,WF/BQ,gCWrBZ,UAEE,kBACA,yBAEA,gBACE,aXXW,QWgBb,kBACE,aXdY,QWkBd,cACE,aXjBW,QWqBb,qBACE,kBACA,UACA,WACA,iBACA,WX5BY,QW6BZ,MXrBQ,QWsBR,cXyCgB,KWxChB,eACA,YXce,IWbf,yBAEA,yBACE,WXlCS,QWmCT,MX1BE,KW6BJ,0BACE,WXtCU,QWuCV,MX/BE,KWoCN,oBACE,WACA,YACA,mBTnCF,aACA,uBACA,mBSmCE,eACA,WXxCO,QWyCP,MXzDW,QW0DX,cXee,KWdf,WX0Cc,aWvChB,0BACE,qBAEA,MXnDI,KWuDN,oBACE,UXhCW,KWiCX,YXtBe,IWuBf,cXZS,KWaT,MX9DQ,QWiEV,2BACE,MXjEQ,QWkER,YXxBiB,IWyBjB,cXlBS,KWmBT,UX7CW,KWiDb,wBACE,aACA,uBACA,IX5BS,IW6BT,cX3BS,KW4BT,eAEA,qCACE,gBACA,WX9EI,QW+EJ,MXlFM,QWmFN,cXrBc,KWsBd,UX9DS,KW+DT,YXlDe,IWuDnB,oBACE,oBACA,mBACA,IX/CS,IWgDT,MX1GW,QW2GX,YX3DmB,IW4DnB,UXzEW,KW0EX,WXRc,aWUd,0BACE,IXrDO,IWsDP,MXhHW,QWsHjB,iBTrEE,WFpCM,KEqCN,cFuBkB,KEtBlB,QFwDa,KEvDb,WFgDgB,aE9ChB,uBAEE,WF/BQ,gCW6FZ,iBAEE,kBAEA,4BACE,WACA,YACA,mBT5GF,aACA,uBACA,mBS4GE,UXzFW,KW2FX,MXpHI,KWqHJ,cX1De,KW6DjB,oBACE,UXjGW,KWkGX,YXvFe,IWwFf,cX7ES,KW8ET,MX/HQ,QWkIV,mBACE,MXlIQ,QWmIR,YXzFiB,IW0FjB,cXnFS,KWoFT,UX9GW,KWkHb,8BACE,aACA,sBACA,mBACA,YX5FS,KW6FT,6BAEA,4CACE,UXtHS,KWuHT,YX5GkB,IW6GlB,MX/JS,QWkKX,4CACE,UXjIS,KWkIT,MXxJM,QWyJN,YXtHe,IW4HrB,cTzHE,WFpCM,KEqCN,cFuBkB,KEtBlB,QFwDa,KEvDb,WFgDgB,aE9ChB,oBAEE,WF/BQ,gCWiJZ,cAEE,aACA,uBACA,IXlHW,KWoHX,4BACE,cACA,WACA,YTlKF,aACA,uBACA,mBSmKE,MXzKI,KW0KJ,cXhHe,KWiHf,UXlJW,KWqJb,+BACE,OAEA,kCACE,UX3JS,KW4JT,YXjJiB,IWkJjB,cXvIO,IWwIP,MXxLM,QW2LR,iCACE,MX3LM,QW4LN,YXlJe,IWmJf,UXtKS,KW4Kf,kBT7JE,WFpCM,KEqCN,cFuBkB,KEtBlB,QFwDa,KEvDb,WFgDgB,aE9ChB,wBAEE,WF/BQ,gCWqLZ,kBAEE,kBAEA,0BACE,YACA,kBACA,IX1JS,KW2JT,KX3JS,KW4JT,eACA,MXzNW,QW0NX,WACA,YX1Ke,IW6KjB,uCACE,kBACA,UACA,UX7La,KW8Lb,YX3KgB,IW4KhB,MXxNQ,QWyNR,cXvKS,KWwKT,kBAGF,sCACE,aACA,mBACA,IX/KS,KWiLT,qDACE,WACA,YACA,cXpKiB,IWqKjB,iBAIA,gEACE,YXtMe,IWuMf,MX5OI,QW6OJ,kBAGF,iEACE,UXzNO,KW0NP,MXjPI,QWwPZ,cTlNE,WFpCM,KEqCN,cFuBkB,KEtBlB,QFwDa,KEvDb,WFgDgB,aE9ChB,oBAEE,WF/BQ,gCW0OZ,cAEE,kBACA,kBAEA,uBACE,yBACA,sBAEA,sCACE,kBACA,UACA,SACA,2BACA,iBAEA,MXtQE,KWuQF,cX1Me,KW2Mf,UXpPS,KWqPT,YXtOa,IWuOb,yBAIJ,8BACE,eXhOS,KWiOT,gCACA,cXlOS,KWoOT,iCACE,UX3PS,KW4PT,YXlPa,IWmPb,cXzOO,IW0OP,MX1RM,QW6RR,qCACE,aACA,qBACA,uBACA,IXlPO,IWoPP,+CACE,UXzQO,KW0QP,MXpSI,QWuSN,6CACE,UX1QQ,KW2QR,YXnQgB,IWsQlB,6CACE,UXrRS,KWsRT,MX9SI,QWmTV,gCACE,gBACA,cXpQS,KWsQT,mCACE,cACA,MXzTM,QW0TN,UXnSS,KWoST,aACA,mBACA,IX9QO,IWgRP,2CACE,YACA,MXxUO,QWyUP,YX7RW,IWgSb,4CACE,WAEA,oDACE,YACA,MXzUG,QW+UX,2BACE,gBChVJ,wHACE,UZiEoB,OYhEpB,cACA,eVdE,yBUWJ,wHAMI,wBAOJ,kFACE,iBAxBgB,QAyBhB,cZ4CiB,KY3CjB,kBV3BE,yBUwBJ,kFAMI,QZ6BS,MYtBb,yGACE,iBArCgB,QAsChB,cZ+BiB,KY9BjB,kBACA,mBVzCE,yBUqCJ,yGASI,+BACA,gBACA,oBACA,gBACA,mCAGF,iKACE,YZ1BkB,0GY2BlB,eACA,YZTe,IYUf,MZhDQ,QYiDR,SV1DA,yBUqDF,iKAQI,eACA,WACA,kBACA,gBAIJ,0BACE,gBACA,mBACA,gBACA,gCACA,cZbS,KYeT,6BACE,eACA,YZ9Ba,IY+Bb,cACA,SAGF,0CACE,aVnFF,yBUoEF,0BAmBI,aACA,oBACA,gBACA,kCAEA,6BACE,eACA,WACA,gBAKN,6KACE,eACA,YZ1DkB,IY2DlB,cVvGA,yBUoGF,6KAMI,eACA,cACA,eACA,iBAQN,qDACE,oBACA,mBACA,uBACA,iBACA,mBACA,YZ/FoB,0GYgGpB,eACA,YZ9EiB,IY+EjB,MZlHM,KYmHN,mBAEA,kGACE,yBAGF,oMAEE,iBArIkB,QAwIpB,qGACE,yBAGF,0MAEE,yBAKA,yIACE,yBACA,cAGF,kRAEE,yBACA,cAGF,wRAEE,yBACA,cAQN,2CACE,oBACA,mBACA,IZrHW,IYsHX,iBACA,cZzGiB,KY0GjB,UZhJa,KYiJb,YZpIqB,IYsIrB,mEACE,UACA,WACA,cZ7GmB,IY8GnB,8BAGF,uEACE,yBACA,cAGF,2EACE,yBACA,cAOJ,sDACE,YACA,aAEA,8EACE,WACA,YV/MA,yBU6MF,8EAKI,WACA,aAIJ,8EACE,YACA,aAIJ,kEACE,WACA,YACA,iBACA,cZ3JiB,KY4JjB,yBAGF,oFACE,WACA,YACA,aACA,mBACA,uBACA,yBACA,cZtKiB,KYuKjB,cACA,eAEA,6EACE,eAOJ,kEACE,aACA,sBACA,mBACA,uBACA,kBACA,kBAEA,kHACE,eACA,cZvMS,KYwMT,WAGF,8EACE,YZ3OkB,0GY4OlB,UZnOW,KYoOX,YZ1Ne,IY2Nf,MZjQQ,QYkQR,gBAGF,0EACE,YZnPkB,0GYoPlB,UZ9Oa,KY+Ob,MA7QoB,QA8QpB,SAOJ,+CACE,oBACA,mBACA,uBACA,YACA,YACA,YACA,cZ1NiB,KY2NjB,YZrQoB,0GYsQpB,eACA,YZpPiB,IYqPjB,eACA,qBACA,wDAEA,iEACE,2BAGF,oEACE,wBAGF,mFACE,iBA/SkB,QAgTlB,MZtSI,KYwSJ,qGACE,yBAIJ,yFACE,yBACA,cAEA,2GACE,yBAIJ,gFACE,iBAhUoB,QAiUpB,MZxTI,KY0TJ,kGACE,yBVvUF,yBUyRJ,+CAmDI,WACA,gBACA,YACA,UZ7Sa,MYoTjB,2BACE,oBACA,mBACA,IZjSW,IYkSX,iBACA,WZ/UM,KYgVN,yBACA,cZzRiB,IY0RjB,UZ7Ta,KY8Tb,MZtVU,QYuVV,eACA,WZ9PgB,aYgQhB,mCACE,WACA,YAGF,uCACE,WZ3WW,QY4WX,aZ5WW,QY6WX,MZ/VI,KYkWN,yBAxBF,2BAyBI,mBACA,wBAOJ,uCACE,WZ5WM,KY6WN,cZnTiB,KYoTjB,QZ3TY,KY4TZ,cZhUW,KYiUX,WZrWU,+BYuWV,yBAPF,uCAQI,QZnUS,MYsUX,qEACE,UZ9VW,KY+VX,YZrVe,IYsVf,MZ5XQ,QY6XR,cZ3US,KY4UT,eZ7US,KYoVb,iCACE,aACA,qCACA,IZtVW,KYwVX,yBALF,iCAMI,2BAIJ,iCACE,aACA,sBACA,IZlWW,IYoWX,uDACE,iBAGF,6DACE,UZjYW,KYkYX,YZrXmB,IYsXnB,MZ1ZQ,QY6ZV,6DACE,UZtYa,KYuYb,MZhaQ,QYiaR,sBAOJ,yBACE,aACA,eACA,IZ3XW,IY8Xb,uBACE,qBACA,iBACA,iBZ5aS,QY6aT,MZ5be,QY6bf,cZxXiB,IYyXjB,UZ5Za,KY6Zb,YZjaiB,oCYuanB,yCACE,qBACA,iBACA,iBZ1bQ,QY2bR,yBACA,cZrYiB,IYsYjB,YZ7aiB,oCY8ajB,UZ1aa,KY2ab,MZ7ce,QYmdjB,+BACE,aACA,mBACA,IZ3ZW,KY6ZX,yBALF,+BAMI,sBACA,wBAOJ,kCACE,aACA,sBACA,IZ1aW,IY6ab,gCACE,aACA,mBACA,IZ/aW,KYgbX,kBACA,WZ/dM,KYgeN,mBAEA,4CACE,aZjfW,QYkfX,WZ1dQ,0BY6dV,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,iBZxgBQ,QYygBR,MZ/gBM,QYmhBV,oDACE,OACA,UZ5fa,KY6fb,YZlfiB,IYmfjB,MZvhBQ,QYwhBR,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,YC7kBN,YACE,cb8DW,Ka5DX,kBACE,cACA,UbgCW,Ka/BX,Yb2CiB,Ia1CjB,MbMQ,QaLR,c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wDN,cfOe,KeNf,eACA,YftBiB,Ie4BvB,WACE,aACA,IfnBW,IeoBX,QfrBW,IesBX,WfjEQ,QekER,cfTiB,KeWjB,qBACE,iBACA,MfzEQ,Qe0ER,YfvCiB,IewCjB,cfhBe,IeiBf,Wfac,aeXd,2BACE,MfhFM,QeiFN,8BAGF,4BACE,WflFE,KemFF,MfjGS,QekGT,Wf1EM,0BeiFV,0BACE,cf5CS,Ke8CT,qCACE,Uf3ES,Ke4ET,Yf9DiB,Ie+DjB,yBACA,MfpGM,QeqGN,iBACA,qBAIJ,uBACE,gBAEA,iCACE,cfhEO,IemET,iCACE,aACA,mBACA,IfpEO,KeqEP,iBACA,MfvHM,QewHN,cf5Da,Ie6Db,Wf/BY,aeiCZ,2CACE,WACA,YbtHN,aACA,uBACA,mBasHM,UfrGO,KesGP,Mf/HI,QekIN,uCACE,WfhIE,QeiIF,MfhJO,QekJP,iDACE,MfnJK,QeuJT,wCACE,WfxIG,QeyIH,MfzJO,Qe0JP,Yf3Ga,Ie6Gb,kDACE,Mf7JK,QemKX,oCACE,YfrGQ,KesGR,Wf3GO,Ie4GP,gBAEA,8CACE,UftIO,KeuIP,iBAOR,YACE,aACA,mBACA,If1HW,Ie8HP,2CACE,WACA,mBACA,oBAKF,yCACE,WfjMO,QekMP,MfpLA,KeqLA,afnMO,QewMb,uBACE,aACA,mBACA,uBACA,eACA,YACA,cACA,WfjMI,KekMJ,yBACA,cf1Ie,Ie2If,MfvMQ,QewMR,UfhLW,KeiLX,YfrKiB,IesKjB,WfhHc,aekHd,6BACE,WfzMI,Qe0MJ,afzNS,Qe0NT,Mf1NS,Qe6NX,kEAEE,Uf1LS,Ke8Lb,uBACE,cACA,MfzNQ,QgBfZ,UACE,eACA,WhBeM,KiBjBR,WACE,kBACA,gBAEA,gBCKA,qCACE,gBACA,SAGF,mCACE,MlBRY,QkBSZ,UlBqBW,KkBpBX,WlB4CS,IkB3CT,aACA,8BAEA,wCACE,cAMN,sBACE,KACE,UACA,4BAEF,GACE,UACA,yBC/BJ,YACE,aACA,mBACA,InBwDW,KmBtDX,0BACE,MAOJ,WACE,oBACA,mBACA,kBACA,MnBPU,QmBQV,qBACA,yBACA,cnBsDmB,KmBrDnB,YnB0BqB,ImBzBrB,UnBYa,KmBXb,WnB6EgB,amB1EhB,eACE,YACA,oBACA,mBACA,uBACA,QACA,iBACA,cACA,qBACA,yBACA,kBACA,YnBRkB,0GmBSlB,YnBSmB,ImBRnB,UnBLW,KmBMX,yBACA,WnB2Dc,amBzDd,0BACE,WACA,YACA,cAQN,uBACE,kBAEA,yCACE,aACA,mBACA,InBFS,ImBGT,kBACA,WnB/CO,QmBgDP,+BACA,cnBUiB,KmBTjB,eACA,WnBkCc,amBjCd,YnBtCkB,0GmBuClB,UnBlCW,KmBoCX,oDACE,MnB7DM,QmB8DN,YnBzBiB,ImB4BnB,2CACE,MnBjEM,QmBkEN,eACA,mCAGF,+CACE,WnBrEE,KmBsEF,anBpFS,QmBqFT,WnB5DM,+BmB8DN,0DACE,MnBxFO,QmB2FT,iDACE,MnB5FO,QmBkGX,kDACE,yBAGF,qDACE,mBACA,UACA,wBAIJ,8CACE,kBACA,sBACA,QACA,YACA,WnBpGI,KmBqGJ,yBACA,cnB5Ce,KmB6Cf,WnB1FQ,gCmB2FR,kBACA,UACA,4BACA,4BACA,QnBhCe,ImBiCf,gBAGF,uCACE,QnBlES,KmBoET,MnBnHI,KmBoHJ,kBAEA,sDACE,iBACA,UnBjGS,KmBkGT,YnBtFa,ImByFf,qDACE,UnBzGS,KmB0GT,WAIJ,0CACE,gBACA,SACA,cAEA,6CACE,SAEA,+CACE,aACA,mBACA,SACA,kBACA,MnBlJI,QmBmJJ,qBACA,UnB5HO,KmB6HP,WnB1DU,cmB4DV,iDACE,WACA,MnBxJE,QmByJF,eAGF,qDACE,WnBzJC,QmB0JD,MnB1KK,QmB4KL,uDACE,MnB7KG,QmBoLb,uCACE,sBACA,6BAEA,mDACE,aACA,mBACA,uBACA,InBjIO,ImBkIP,WACA,aACA,WnBhLI,QmBiLJ,MnBrLM,QmBsLN,qBACA,cnB3Ha,ImB4Hb,YnBnJiB,ImBoJjB,UnBjKS,KmBkKT,WnBhGY,amBkGZ,qDACE,eAGF,yDACE,WnBxMQ,QmByMR,MnB/LA,KmBgMA,2BACA,WnBtLI,+BmB+LZ,kBACE,QnB5JW,KmB8JX,2CAEA,kCACE,aACA,mBACA,InBpKS,KmBsK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AGF,kCACE,OACA,gBACA,iBACA,yBACA,cpBzhBa,IoB0hBb,UpB9jBS,KoB+jBT,WpB7fY,aoB+fZ,yBATF,kCAUI,gBAGF,wCACE,aACA,apB1mBO,QoB2mBP,uCAGF,+CACE,MpBlmBK,QoBsoBb,yBAEE,cACE,aACA,mBACA,WACA,YACA,WpB5oBI,KoB6oBJ,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,MpBnsBE,KoBosBF,mBACA,kCACA,gBAEA,oCACE,cACA,YpB3qBW,IoB4qBX,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,YpBluBc,IoBmuBd,cAEA,qDACE,aAGF,gEACE,aACA,mBACA,QACA,cACA,qBAEA,+EACE,aAGF,2EACE,oBACA,mBACA,cACA,gBAEA,+EACE,WACA,YAOR,iDACE,cACA,WACA,UACA,eACA,YpBxwBc,IoBywBd,cACA,yBAEA,yDACE,aAOR,YACE,SACA,eAEA,4BACE,WACA,YAEA,8DACE,eACA,WACA,YAIJ,gCACE,SAGF,+BACE,eACA,YACA,eACA,cACA,YpB5yBgB,IoB8yBhB,sCACE,YpB5yBW,IoB6yBX,eC/1BR,eACE,mBACA,crBuEiB,KqBtEjB,gBACA,crB4DY,KE7DV,yBmBHJ,eAOI,gBACA,iBAGF,yBACE,gCACA,WrByFc,aqBvFd,oCACE,mBAME,wDACE,yBAIJ,4CACE,cACA,6BAMN,6BACE,aACA,mBACA,8BACA,kBACA,eACA,YrBSe,IqBRf,cACA,eACA,WrB0Dc,aqBzDd,SACA,gBA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,0BAWI,SACA,WACA,YACA,UACA,eACA,mBACA,QACA,oBAIJ,gBACE,WACA,YACA,kBACA,mBACA,YACA,eACA,UACA,wBAEA,uBACE,mBAGF,mCACE,0BxB7VA,yBwB8UJ,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,uCxB7YA,yBwBsXJ,sBA4BI,cAQJ,oBACE,kBACA,UAEA,gBACA,mBACA,aAEA,uCACE,kBACA,QACA,mDACA,sBACA,2BACA,4BACA,oBAGF,4CACE,kBACA,aACA,mBACA,uBACA,SACA,UxBhbA,0BwB0aF,4CASI,sBACA,SACA,kBxBvbF,yBwB4aF,4CAgBI,mBACA,eACA,QACA,iBACA,wBAIJ,sCACE,YACA,aACA,cAEA,0CACE,WACA,YACA,mBxB1cF,0BwBkcF,sCAYI,YACA,cxBjdF,yBwBocF,sCAkBI,WACA,aAIJ,yCACE,gBxB1dA,0BwBydF,yCAII,mBxB/dF,yBwB2dF,yCASI,iBAIJ,kCACE,Y1B7ckB,0G0B8clB,eACA,gBACA,gBACA,WACA,SxB5eA,0BwBseF,kCASI,gBxBjfF,yBwBweF,kCAcI,eACA,gBACA,eAIJ,0CACE,kBACA,aACA,uBACA,mBACA,UxB/fA,0BwB0fF,0CAQI,wBxBpgBF,yBwB4fF,0CAaI,2BACA,4BAIJ,iCACE,WACA,gBASF,gCACE,kBACA,WACA,YACA,gBACA,yBACA,mBACA,aACA,mBACA,exBhiBA,0BwBuhBF,gCAYI,YACA,yBACA,gBxBviBF,yBwByhBF,gCAmBI,YACA,yBACA,mBACA,gBAIJ,kCACE,OACA,WACA,YACA,YACA,aACA,yBACA,Y1B9hBkB,0G0B+hBlB,eACA,gBACA,WACA,eAEA,+CACE,cACA,gBxBhkBF,0BwBijBF,kCAmBI,eACA,gBxBvkBF,yBwBmjBF,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,YxB5mBF,0BwB0mBA,4CAKI,WACA,axBlnBJ,yBwB4mBA,4CAWI,WACA,axBxnBJ,yBwBulBF,wCAsCI,WACA,aAIJ,qCACE,kBACA,aACA,mBACA,uBACA,SACA,mBACA,UxBvoBA,0BwBgoBF,qCAUI,sBACA,mBACA,uBACA,QACA,qBxBhpBF,yBwBkoBF,qCAmBI,mBACA,eACA,SACA,oBACA,wBAIJ,mCACE,Y1BloBkB,0G0BmoBlB,eACA,gBACA,WACA,mBxBlqBA,yBwB6pBF,mCASI,eACA,eAIJ,kCACE,aACA,mBACA,SACA,exB7qBA,0BwByqBF,kCAOI,uBACA,UxBnrBF,yBwB2qBF,kCAaI,QACA,sBAIJ,kCACE,Y1BlqBkB,0G0BmqBlB,eACA,gBACA,WACA,qBACA,mBACA,0BAEA,wCACE,cACA,0BxBtsBF,0BwB2rBF,kCAeI,gBxB5sBF,yBwB6rBF,kCAoBI,eACA,eAIJ,uCACE,Y1B3rBkB,0G0B4rBlB,eACA,WACA,iBxBxtBA,0BwBotBF,uCAOI,gBxB7tBF,yBwBstBF,uCAYI,eACA,cACA,aACA,mBACA,aAUN,cACE,kBACA,aACA,mBACA,qBACA,W1BzuBM,K0B0uBN,gBACA,axBvvBE,yBwBgvBJ,cAUI,YAEA,qBAGF,mCACE,kBACA,QACA,wDACA,sBACA,2BACA,4BACA,oBAGF,gCACE,iBACA,cAEA,kBACA,WACA,UAGF,8BACE,kBACA,mBACA,kBACA,aACA,sBxBrxBA,0BwBgxBF,8BAQI,iBxB1xBF,yBwBkxBF,8BAaI,aACA,mBACA,uBACA,gBACA,mBACA,mBAIJ,6BACE,Y1B7wBkB,0G0B8wBlB,eACA,gBACA,gBACA,WACA,SxB5yBA,0BwBsyBF,6BASI,gBxBjzBF,yBwBwyBF,6BAcI,eACA,cACA,uBAIJ,gCACE,Y1BjyBkB,0G0BkyBlB,eACA,gBACA,gBACA,cACA,mBxBh0BA,0BwB0zBF,gCASI,gBxBr0BF,yBwB4zBF,gCAeI,cAIJ,4BACE,oBACA,oBACA,mBACA,uBACA,SACA,kBACA,Y1B1zBkB,0G0B2zBlB,eACA,gBACA,WACA,mBACA,qBACA,mBACA,wBACA,YACA,YACA,mBxB91BA,0BwB60BF,4BAoBI,gBACA,eACA,oBxBr2BF,yBwB+0BF,4BA2BI,kBACA,QACA,QACA,2BACA,SACA,WACA,YACA,aACA,eACA,gBACA,kBACA,UAGF,gCACE,WACA,YACA,8BAEA,qCACE,YxB93BJ,yBwBw3BA,gCAWI,UACA,YAIJ,kCACE,qCAEA,sCACE,0BAKN,mCACE,aACA,SACA,uBxBl5BA,0BwB+4BF,mCAUI,eACA,SACA,wBxB75BF,yBwBi5BF,mCAiBI,aACA,qCACA,eAIJ,wBACE,YACA,aACA,gBACA,sBACA,mBACA,kBACA,aACA,sBACA,uBACA,kBACA,wBACA,QACA,eAEA,8BACE,2BACA,qCACA,qBxBx7BF,0BwBs6BF,wBAsBI,uBACA,iBxB/7BF,yBwBw6BF,wBA4BI,WACA,YACA,gBACA,iBACA,kBACA,mBACA,yBACA,gDACA,UAIJ,0BACE,Y1Br7BkB,0G0Bs7BlB,eACA,gBACA,iBACA,cACA,kBxBt9BA,yBwBg9BF,0BAUI,eACA,cACA,WACA,mBAIJ,gCACE,Y1Bt8BkB,0G0Bu8BlB,eACA,gBACA,iBACA,cACA,SACA,OACA,gBxBz+BA,yBwBi+BF,gCAYI,eACA,gBACA,eAIJ,iCACE,WACA,YACA,aACA,mBACA,uBACA,gBACA,oBAEA,qCACE,WACA,YACA,mBxB//BF,yBwBm/BF,iCAiBI,WACA,aAaN,cACE,kBACA,gBACA,gBACA,gBACA,aACA,aACA,mBxBzhCE,yBwBkhCJ,cAWI,iBACA,wBAGF,+BACE,kBACA,QACA,8CACA,sBACA,2BACA,oBAYF,yBACE,kBACA,UxBrjCA,yBwBmjCF,yBAMI,SACA,UACA,eACA,YAIJ,4BACE,aACA,mBACA,8BACA,I1BpgCU,KE9DV,0BwB8jCF,4BAOI,sBACA,I1BzgCQ,ME/DV,yBwBgkCF,4BAaI,cACA,sBACA,SACA,mBAIJ,4BACE,OACA,gBxBplCA,0BwBklCF,4BAKI,kBACA,gBxB1lCF,yBwBolCF,4BAWI,gBACA,eACA,SAIJ,0BACE,Y1B1kCkB,0G0B2kClB,eACA,gBACA,gBACA,WACA,kBxBzmCA,0BwBmmCF,0BASI,gBxB9mCF,yBwBqmCF,0BAcI,eACA,gBACA,cAEA,cACA,iBAEA,+BACE,oBAIJ,2CACE,gBACA,cACA,exBloCF,yBwB+nCA,2CAOI,eACA,cACA,sBACA,gBAKN,gCACE,Y1BnnCkB,0G0BonClB,eACA,gBACA,gBACA,WACA,kBxBlpCA,0BwB4oCF,gCASI,gBxBvpCF,yBwB8oCF,gCAcI,eACA,gBACA,iBACA,cACA,sBAIJ,8BACE,aACA,I1B3mCS,KEzDT,0BwBkqCF,8BAKI,wBxBzqCF,yBwBoqCF,8BAUI,mBACA,oBACA,SACA,YAIJ,0BACE,oBACA,mBACA,uBACA,kBACA,Y1B9pCkB,0G0B+pClB,eACA,gBACA,iBACA,mBACA,qBACA,mBACA,wBACA,gBACA,axBnsCA,yBwBqrCF,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,oCAIJ,oCACE,mBACA,WAEA,0CACE,qCAIJ,0BACE,OACA,gBACA,aACA,mBACA,uBAEA,8BACE,WACA,YACA,mBxB5vCF,0BwBkvCF,0BAcI,gBxBlwCF,yBwBovCF,0BAmBI,kBACA,UACA,QACA,YACA,aACA,gBACA,UAEA,8BACE,YACA,cAiBR,gBACE,kBACA,UACA,kBACA,qBACA,mBACA,gBACA,axBzyCE,yBwBkyCJ,gBAWI,iBACA,mBAGF,oCACE,kBACA,QACA,SACA,gCACA,WACA,YACA,iBACA,oBAEA,4CACE,WACA,kBACA,SACA,SACA,YACA,aACA,mDACA,sBACA,4BACA,WxBr0CF,yBwB00CE,4CACE,MACA,UACA,QACA,YACA,aACA,WAKN,2BACE,kBACA,UACA,iBACA,cACA,eACA,axB31CA,yBwBq1CF,2BASI,UACA,eACA,YAIJ,gCACE,mBACA,kBxBt2CA,yBwBo2CF,gCAMI,mBACA,iBAGF,+CACE,Y1Bn1CgB,0G0Bo1ChB,eACA,gBACA,gBACA,WACA,kBACA,gBxBn3CF,0BwB42CA,+CAUI,eACA,iBxBz3CJ,yBwB82CA,+CAgBI,eACA,cACA,wBxBh4CJ,yBwBm4CE,8DAGI,iBAIJ,2DACE,eACA,gBxB54CJ,yBwB04CE,2DAMI,eACA,uBAMR,8BACE,aACA,SACA,uBACA,oBACA,iBACA,aACA,cxB55CA,0BwBq5CF,8BAUI,sBACA,mBACA,UxBn6CF,yBwBu5CF,8BAiBI,mBACA,8BACA,oBACA,SACA,gBAKJ,8BACE,gBACA,mBACA,qBACA,wBACA,aACA,sBACA,kBACA,gBACA,axBx7CA,0BwB+6CF,8BAYI,WACA,gBACA,YACA,kBxBh8CF,yBwBi7CF,8BAoBI,0BACA,YACA,aACA,iBACA,eACA,mBACA,oBACA,sBACA,mBACA,2BACA,UAGF,oCACE,2BACA,0CxBp9CF,yBwBk9CA,oCAKI,4BAIJ,yCACE,aACA,mBACA,uBAEA,6CACE,WACA,YACA,mBxBn+CJ,yBwB29CA,yCAaI,sBACA,uBACA,4BAIJ,4CACE,aACA,sBACA,uBxBj/CF,yBwB8+CA,4CAMI,mBACA,kBACA,SAGF,+CACE,Y1B99Cc,0G0B+9Cd,eACA,gBACA,cACA,kBACA,gBxB7/CJ,0BwBu/CE,+CASI,gBxBlgDN,yBwBy/CE,+CAcI,eACA,SACA,eAIJ,8CACE,Y1Bl/Cc,0G0Bm/Cd,eACA,gBACA,cACA,SACA,gBxBjhDJ,0BwB2gDE,8CASI,gBxBthDN,yBwB6gDE,8CAcI,eACA,eAMN,sCACE,YACA,cACA,sBACA,mBACA,kBACA,mBxBtiDF,0BwBgiDA,sCASI,WACA,gBACA,mBxB7iDJ,yBwBkiDA,sCAgBI,0BACA,oBACA,UAGF,iDACE,YACA,aACA,mBAGF,oDACE,mBACA,kBAEA,uDACE,WACA,exBjkDN,0BwB+jDI,uDAKI,gBxBtkDR,yBwBikDI,uDASI,eACA,YAIJ,sDACE,yBACA,exB/kDN,0BwB6kDI,sDAKI,gBxBplDR,yBwB+kDI,sDASI,eACA,0BAKN,4CACE,sCAKJ,sEACE,YACA,cACA,sBACA,mBACA,kBxBvmDF,0BwBkmDA,sEAQI,WACA,gBACA,cxB9mDJ,yBwBomDA,sEAeI,0BACA,qBAGF,4FACE,YACA,aACA,mBAIF,kGACE,mBACA,kBACA,UAEA,wGACE,exBloDN,0BwBioDI,wGAII,gBxBvoDR,yBwBmoDI,wGASI,gBAIJ,sGACE,exB/oDN,0BwB8oDI,sGAII,gBxBppDR,yBwBgpDI,sGASI,gBAOR,mCACE,mBAEA,oDACE,cAKJ,mCACE,mBAEA,oDACE,cAWR,mBACE,kBACA,aACA,UACA,kBACA,qBACA,gBACA,aACA,mBACA,uBxB/rDE,0BwBsrDJ,mBAYI,iBACA,gBxBrsDA,yBwBwrDJ,mBAkBI,iBACA,kBACA,wBAKJ,kBACE,kBACA,QACA,mDACA,sBACA,2BACA,4BACA,gBAIA,8BACE,kBACA,UxB9tDA,yBwB4tDF,8BAKI,aACA,sBACA,SACA,WACA,eACA,WAIJ,mCACE,kBACA,axB5uDA,yBwB0uDF,mCAMI,gBACA,iBAIJ,kCACE,Y1B1tDkB,0G0B2tDlB,gBACA,SAEA,6CACE,cACA,eACA,gBACA,cxB5vDF,0BwBwvDA,6CAOI,gBxBjwDJ,yBwB0vDA,6CAYI,eACA,gBACA,uBACA,iBAIJ,mDACE,eACA,gBACA,cxB9wDF,0BwB2wDA,mDAMI,gBxBnxDJ,yBwB6wDA,mDAWI,gBAnCN,kCAwCE,eACA,gBACA,cxB7xDA,0BwBmvDF,kCA6CI,gBxBlyDF,yBwBqvDF,kCAkDI,eACA,gBACA,cACA,uBAKJ,gCACE,aACA,mBACA,uBACA,MACA,iBACA,cxBnzDA,0BwB6yDF,gCASI,sBACA,UxBzzDF,yBwB+yDF,gCAeI,8BACA,uBACA,mBACA,iBACA,UACA,aACA,eACA,UAKJ,8BACE,aACA,sBACA,mBACA,SACA,YxB70DA,0BwBw0DF,8BAQI,YxBl1DF,yBwB00DF,8BAaI,SACA,kBACA,aACA,uBACA,aAGF,0CACE,Y1Bn0DgB,0G0Bo0DhB,eACA,gBACA,WACA,iBACA,SACA,mBxBr2DF,yBwB81DA,0CAWI,eACA,gBACA,cACA,UAIJ,yCACE,WACA,YACA,aACA,mBACA,uBACA,8BAEA,6CACE,WACA,YACA,iDAGF,6CACE,WACA,YACA,mBxBj4DJ,yBwBg3DA,yCAsBI,WACA,aAIJ,2CACE,Y1Bh3DgB,0G0Bi3DhB,eACA,gBACA,cACA,iBACA,SACA,kBACA,mBxBj5DF,0BwBy4DA,2CAWI,gBxBt5DJ,yBwB24DA,2CAgBI,eACA,eAMF,+CACE,qBAMN,kCACE,UACA,aACA,cACA,cACA,sBxB56DA,0BwBu6DF,kCAQI,cxB76DF,0BwBq6DF,kCAaI,gBxBt7DF,yBwBy6DF,kCAkBI,yBACA,UACA,aACA,SACA,0BAMN,aACE,wCAGF,mBACE,KACE,UACA,2BAEF,GACE,UACA,yBAOJ,oBACE,kBACA,iBACA,aACA,mBACA,uBACA,gBxB79DE,yBwBu9DJ,oBAUI,iBACA,gBAGF,oCACE,kBACA,QACA,8CACA,sBACA,2BACA,4BACA,gBAGF,+BACE,kBACA,UAGF,iCACE,aACA,sBACA,mBACA,SACA,kBxBz/DA,yBwBo/DF,iCASI,UAIJ,+BACE,Y1Bt+DkB,0G0Bu+DlB,eACA,gBACA,WACA,mBACA,SxBrgEA,0BwB+/DF,+BASI,gBxB1gEF,yBwBigEF,+BAcI,eACA,gBACA,cACA,uBACA,mBAIJ,oCACE,oBACA,mBACA,uBACA,aACA,gBACA,YACA,mBACA,WACA,Y1BpgEkB,0G0BqgElB,eACA,gBACA,mBACA,qBACA,wBAEA,0CACE,qCACA,2BACA,yCAGF,2CACE,wBxB9iEF,yBwBuhEF,oCA4BI,YACA,gBACA,kBACA,eACA,gBACA,kBACA,kBAIJ,iCACE,kBACA,SACA,OACA,QACA,WACA,gBCvkEJ,sBACE,aACA,iBACA,iB3BaM,K2BZN,kBACA,mBAIF,oBACE,YACA,yBACA,mBACA,kBACA,gBACA,I3BgDW,K2B/CX,0BACA,cACA,O3B4CW,K2B1CX,0BAXF,oBAYI,aAGF,yBAfF,oBAgBI,eACA,OACA,SACA,4BA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,gBACA,WACA,eACA,iBACA,iBACA,mBAKN,cACE,WACA,YACA,aACA,Y5BlHoB,0G4BmHpB,eACA,gBACA,WACA,mBACA,YACA,mBACA,eACA,wBACA,mBACA,cAEA,oBACE,sCAGF,qBACE,oCAGF,uBACE,WACA,mBAIJ,aACE,aACA,uBACA,mBACA,SACA,eAEA,eACE,Y5BpJkB,0G4BqJlB,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,0B1BvQA,yB0B6QF,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,iBACA,cACA,kBACA,Q7B+CW,K6B3Cb,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,Y7B3BkB,0G6B4BlB,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,Y7BvEoB,0G6BwEpB,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,Y7BxIkB,0G6ByIlB,eACA,gBACA,cACA,gBAEA,6CACE,cACA,gBAGF,yBAhBF,mCAiBI,WACA,gBAIJ,mCACE,OACA,YACA,eACA,Y7B7JkB,0G6B8JlB,eACA,gBACA,cACA,gBACA,yBACA,mBACA,aACA,wBAEA,gDACE,cAGF,yCACE,qBAGF,yCACE,qBACA,yCAGF,4CACE,mBACA,qBACA,cACA,mBAGF,yCACE,qBAIJ,oCACE,YACA,sBACA,Y7BnMkB,0G6BoMlB,eACA,gBACA,cACA,gBACA,yBACA,mBACA,aACA,eACA,wBACA,gBACA,wBACA,qBACA,wLACA,4BACA,sCACA,yBAEA,0CACE,qBAGF,0CACE,qBACA,yCAGF,6CACE,yBACA,qBACA,cACA,mBACA,WAGF,2CACE,aACA,eAMN,mBACE,aACA,mBACA,SACA,OAEA,iCACE,YACA,cAGF,gEAEE,YACA,cAGF,oCACE,aACA,mBACA,uBACA,WACA,WACA,mBACA,cAGF,yBA3BF,mBA4BI,eACA,SAEA,iGAGE,uBACA,eAGF,oCACE,YAIJ,yBACE,iGAGE,WAGF,oCACE,cAMN,mBACE,aAGF,kBACE,kBACA,aACA,mBACA,OAEA,8BACE,OACA,mBAGF,8BACE,kBACA,WACA,QACA,2BACA,Y7B3TkB,0G6B4TlB,eACA,gBACA,cACA,oBAEA,yBAXF,8BAYI,eACA,YAMN,yCAEE,YACA,YACA,aACA,Y7B9UoB,0G6B+UpB,eACA,gBACA,WACA,mBACA,YACA,mBACA,eACA,wBACA,SACA,cACA,cAEA,qDACE,mDAGF,uDACE,iDAGF,2DACE,WACA,mBAGF,yBA/BF,yCAgCI,WACA,YACA,eACA,iBAKJ,iFACE,aACA,uBACA,SACA,gBACA,eACA,gBACA,yBAEA,8NAEE,YACA,YACA,aACA,Y7B/XkB,0G6BgYlB,eACA,gBACA,YACA,mBACA,eACA,wBACA,cAEA,aACA,mBACA,uBACA,qBAGF,+GACE,cACA,mBAEA,2HACE,kDAGF,6HACE,kDAIJ,+GACE,WACA,mBAEA,2HACE,sCAGF,6HACE,oCAGF,iIACE,WACA,mBAIJ,yBA5DF,iFA6DI,sBACA,SACA,eAEA,8NAEE,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,Y7BnhBgB,0G6BohBhB,eACA,gBACA,cACA,mBAEA,yBAPF,sDAQI,gBAIJ,4DACE,Y7B/hBgB,0G6BgiBhB,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,Y7BrlBgB,0G6BslBhB,eACA,gBACA,cAEA,yBANF,+CAOI,gBAIJ,8CACE,Y7BhmBgB,0G6BimBhB,eACA,gBACA,cAEA,yBANF,8CAOI,gBAOR,iBACE,gBACA,cACA,8BACA,mCACA,mBACA,kBAEA,4BACE,Y7BtnBkB,0G6BunBlB,eACA,gBACA,cACA,SACA,aACA,mBACA,uBACA,QACA,eAEA,8BACE,cACA,eAGF,uCACE,cACA,gBACA,0BACA,0BAEA,6CACE,+BAIJ,yBA5BF,4BA6BI,eACA,mB3B/qBF,yB2BwrBF,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,iFACE,mBACA,SACA,gBACA,eAEA,8NAEE,OACA,gBACA,YACA,eACA,gBACA,kBAIF,+GACE,mBACA,cAIF,+GACE,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,gBCr5BN,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,Y9BZoB,0G8BapB,eACA,gBACA,WACA,kBACA,mBACA,cAGF,gBACE,WACA,gBACA,aACA,sBACA,SACA,mBAGF,YACE,WACA,YACA,aACA,mBACA,uBACA,SACA,aACA,Y9BtCoB,0G8BuCpB,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,Y9BtFkB,0G8BuFlB,eACA,gBACA,WACA,qBACA,0BACA,cAEA,0CACE,cACA,0BAIJ,yCACE,WACA,eACA,c5BnIA,yB4ByIF,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,W/B6DY,K+B5DZ,Y/B0DW,K+BxDX,uCACE,WCFJ,oBAEE,kBACA,aACA,qBACA,S9BLE,8C8BQA,sBACA,QACA,mB9BZA,yB8BEJ,oBAeI,mBACA,eACA,qBACA,QACA,mBACA,+BACA,gBACA,gCACA,oBAGF,oCACE,eACA,YhCiBe,IgChBf,MhCtBQ,QgCuBR,S9B9BA,0B8B0BF,oCAOI,gB9BnCF,yB8B4BF,oCAYI,eACA,YhCIe,4CgCCjB,eACA,YhCHkB,IgCIlB,c9B9CA,0B8B2CF,uCAMI,gB9BnDF,yB8B6CF,uCAWI,gBAMN,mBACE,aACA,uBACA,mB9BjEE,yB8B8DJ,mBAOI,mBACA,WAIJ,gBACE,aACA,uBACA,S9B7EE,yB8B0EJ,gBAOI,SACA,oBAKJ,WACE,aACA,QAEA,gBACE,UACA,WACA,chCpBmB,IgCqBnB,yB9B/FA,yB8BuFJ,WAaI,aACA,mBACA,mBACA,QACA,WACA,YAEA,gBACE,UACA,WACA,chCpCiB,IgCqCjB,0BAMN,oBACE,aACA,sBACA,mBACA,QAEA,uCACE,a9B5HA,yB8B2HF,uCAKI,cAIJ,+BACE,WACA,YACA,chC7DmB,IgC8DnB,yBACA,aACA,mBACA,uBACA,W9B5IA,yB8BoIF,+BAYI,WACA,aAGF,8CACE,WACA,Y9BtJF,yB8BoJA,8CAMI,WACA,aAKN,sCACE,yBACA,MhCtJI,KgCyJN,gCACE,eACA,YhC3HkB,IgC4HlB,cACA,kBACA,mB9B1KA,yB8BqKF,gCASI,gBAIJ,uCACE,YhCpIe,IgCqIf,c9BpLA,yB8BkLF,uCAMI,gBAMN,yBAEE,kB9B9LE,0B8B4LJ,yBAKI,mBAIF,sCACE,aACA,MACA,aACA,iB9B3MA,yB8BuMF,sCAOI,sBACA,UACA,iBhCpME,MgC6MN,qCACE,WACA,YACA,eACA,yBACA,chCxJe,KgCyJf,iBhCnNI,KgCoNJ,eACA,MhCxNQ,QgCyNR,aACA,iCAEA,kDACE,cAGF,2CACE,qB9BxOF,0B8BuNF,qCAqBI,YACA,gBAIJ,wCACE,WACA,iBACA,kBACA,yBACA,chClLe,KgCmLf,iBhC7OI,KgC8OJ,eACA,MhClPQ,QgCmPR,aACA,gBACA,iCAEA,qDACE,cAGF,8CACE,qB9BnQF,0B8BiPF,wCAsBI,iBACA,gBAQN,iBACE,iBhCvQM,KgCwQN,yBACA,chC/MiB,KgCgNjB,kBACA,aACA,sBACA,mBACA,S9BxRE,0B8BgRJ,iBAWI,mBAIJ,mBACE,YACA,aACA,kBAEA,uBACE,WACA,YACA,iBACA,chCrOe,IgCyOnB,kBACE,WACA,YACA,aACA,mBACA,uBACA,chC/OiB,IgCgPjB,WAGF,kCACE,kBACA,SACA,WACA,WACA,YACA,chCpPqB,IgCqPrB,yBACA,MhCpTM,KgCqTN,YACA,eACA,aACA,mBACA,uBACA,qCAEA,wCACE,yBAIJ,kBACE,kBAEA,gCACE,eACA,YhCnSe,IgCoSf,cACA,gBAGF,+BACE,eACA,YhC7SkB,IgC8SlB,cACA,SAEA,sCACE,YhC/Sa,IgCuTnB,cACE,aACA,SAEA,wBACE,O9BzWA,0B8BoWJ,cASI,sBACA,U9BhXA,yB8BsWJ,cAeI,mBACA,SAEA,wBACE,OACA,YACA,YACA,eACA,kBACA,gBAKN,YACE,YACA,YACA,yBACA,MhC3XM,KgC4XN,YACA,chCnUiB,KgCoUjB,eACA,YhC5ViB,IgC6VjB,eACA,qCACA,cAEA,kBACE,yB9B/YA,0B8BiYJ,YAkBI,WACA,eACA,a9BvZA,yB8BmYJ,YAyBI,WACA,YACA,eACA,kBACA,WAKJ,kCACE,gBACA,YACA,yBAEA,4CACE,aACA,sBACA,SACA,gBAGF,2CACE,aACA,SACA,UACA,mBAEA,iDACE,yB9BtbF,0B8B+aF,2CAWI,sBACA,U9B7bF,yB8BibF,2CAiBI,mBACA,UAGF,uDACE,OACA,YACA,eACA,yBACA,chCtYa,IgCuYb,iBhChcE,KgCicF,aACA,mBACA,eACA,MhCvcM,QETR,yB8BscA,uDAcI,YACA,eACA,eACA,kBACA,aAIJ,0DACE,uBACA,uBACA,oCACA,yBACA,uBACA,8BACA,0BACA,YhCrba,IgCsbb,eACA,qCACA,cACA,wBACA,mBACA,uB9BxeF,0B8B0dA,0DAiBI,sBACA,0BACA,wB9B/eJ,yB8B4dA,0DAwBI,sBACA,uBACA,0BACA,6BACA,sBAgBR,2BAEE,eAEA,yBAJF,2BAKI,mBAIF,wCACE,aACA,+BACA,kBACA,UAKJ,mBACE,chC7dY,KgC8dZ,eAEA,yBAJF,mBAKI,chCneS,KgCoeT,WhCveS,KgC0eX,mCACE,aACA,mBACA,uBACA,kBAGF,kCACE,aACA,sBACA,mBACA,kBACA,UAEA,+CACE,WACA,YACA,chC1eiB,IgC2ejB,WhCziBE,KgC0iBF,yBACA,aACA,mBACA,uBACA,YhC5gBiB,IgC6gBjB,MhCjjBM,QgCkjBN,WhCzdY,agC0dZ,chCpgBO,IgCugBT,8CACE,UhChiBS,KgCiiBT,MhCxjBM,QgCyjBN,mBAIA,sDACE,WhC1kBO,QgC2kBP,ahC3kBO,QgC4kBP,MhC9jBA,KgC+jBA,uCAGF,qDACE,MhCjlBO,QgCklBP,YhCniBa,IgCwiBf,yDACE,WhCnlBO,QgColBP,ahCplBO,QgCqlBP,MhC5kBA,KgC+kBF,wDACE,MhCnlBI,QgCwlBV,kCACE,OACA,WACA,WhCrlBU,QgCslBV,cACA,mBACA,kBAEA,yCACE,WhCvmBS,QgC6mBf,iBACE,kBACA,chCpjBY,KgCqjBZ,eAEA,oBACE,UhChlBY,KgCilBZ,YhCxkBe,IgCykBf,MhC/mBQ,QgCgnBR,chChkBS,IgCmkBX,oBACE,UhCxlBW,KgCylBX,YhChlBmB,IgCilBnB,MhCjoBW,QgCkoBX,chCtkBS,KgCykBX,qCACE,UhClmBa,KgCmmBb,MhC3nBQ,QgC4nBR,gBACA,cAKJ,yBACE,YACA,yBACA,chCzkBiB,KgC0kBjB,kBACA,gBACA,MACA,mBACA,+BACA,gBACA,cAEA,0BAZF,yBAaI,aAGF,yBAhBF,yBAiBI,eACA,OACA,SACA,4BACA,8BACA,WhC1oBQ,gCgC2oBR,QhCtkBY,IgCukBZ,0BACA,gBACA,SACA,gBAEA,qCACE,yBAOJ,mCACE,eACA,YhCpoBe,IgCqoBf,cACA,kBACA,uBAGF,kCACE,gBACA,gCAEA,6CACE,mBAIJ,gCACE,kBACA,eACA,YhCzpBkB,IgC0pBlB,cACA,eACA,WhCrmBc,agCsmBd,aACA,mBACA,8BACA,SAEA,sCACE,iCAGF,uCACE,YhCpqBa,IgCqqBb,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,UhCpuBa,KgCquBb,MhC7vBQ,QgC8vBR,chC5sBS,KgC8sBT,kDACE,MhC7wBS,QgC8wBT,YhC9tBiB,IgCkuBrB,yBAnBF,yBAoBI,WAKJ,sBACE,aACA,eACA,OhC9tBW,KgC+tBX,MhC/tBW,KgCguBX,WACA,YACA,iBhC/xBa,QgCgyBb,chCptBqB,IgCqtBrB,YACA,MhCpxBM,KgCqxBN,eACA,eACA,YACA,WhCjsBgB,agCmsBhB,4BACE,sBACA,WhC/wBQ,gCgCkxBV,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,chCvxBW,KgCwxBX,IhC1xBW,KgC4xBX,yBAPF,mBAQI,yBAIJ,gBACE,kBACA,YACA,YACA,cACA,iBAEA,yBAPF,gBAQI,WACA,eAGF,8BACE,WACA,YACA,sBACA,yBACA,mBACA,iBhCj2BI,KgCk2BJ,eACA,oBACA,aACA,WhC9wBc,agCgxBd,oCACE,ahCt3BS,QgCu3BT,uCAGF,2CACE,cAKJ,oBACE,kBACA,WACA,QACA,2BACA,oBAIJ,mBACE,UhCt2Be,KgCu2Bf,MhC/3BU,QgCg4BV,mBAEA,kCACE,YhC/1BmB,IgCg2BnB,MhCh5BW,QgCq5Bf,mBACE,aACA,mBACA,8BACA,IhC51BW,KgC61BX,chC71BW,KgC+1BX,yBAPF,mBAQI,sBACA,oBACA,IhCn2BS,MgCs2BX,uCACE,aACA,mBACA,OAEA,yBALF,uCAMI,SAIJ,qCACE,aACA,mBACA,IhCp3BS,IgCq3BT,eACA,UhC74Ba,KgC84Bb,MhCv6BQ,QgCw6BR,iBAEA,2CACE,MhCt7BS,QgC07Bb,wCACE,WACA,YACA,eACA,ahC97BW,QgCg8BX,sDACE,WAKF,yBADF,mCAEI,SAQN,yBACE,aACA,qCACA,IhCr5BW,KgCs5BX,chCr5BW,KgCw5BX,0BAPF,yBAQI,qCACA,IhC55BS,MgCg6BX,yBAbF,yBAcI,qCACA,IhCl6BS,MgCy6Bb,iBACE,qBACA,WACA,YACA,cACA,eACA,oDACA,wBACA,4BACA,2BACA,kBAIF,8CACE,yBACA,kDAKA,mCACE,iBAKJ,oBACE,WhCn/BM,KgCo/BN,yBACA,mBACA,WhC/5BgB,agCg6BhB,eACA,kBAEA,0BACE,ahCzgCW,QgC0gCX,WhCj/BQ,+BgCo/BV,6BACE,qBACA,oCAGF,sCACE,aACA,sBACA,SACA,kBACA,eAGA,uDACE,aACA,8BACA,mBACA,SAEA,0EACE,eACA,YhCp/Bc,IgCq/Bd,cACA,OAGF,yEACE,cAEA,uFACE,WACA,YACA,eACA,qBAMN,gDACE,eACA,YhCrgCa,IgCsgCb,cACA,SACA,cAIF,uDACE,eACA,YhCjhCgB,IgCkhChB,cACA,SACA,gBACA,oBACA,qBACA,4BACA,gBAIF,qDACE,aACA,yBACA,gBAEA,yDACE,WACA,YACA,mBAGF,uDACE,WACA,YACA,aACA,mBACA,uBACA,eACA,WAOR,kBACE,WhCplCS,QgCqlCT,chC7hCiB,KgC8hCjB,QhCziCW,KgC0iCX,chCziCW,KgC2iCX,qBACE,UhCrkCW,KgCskCX,YhC3jCmB,IgC4jCnB,MhCjmCQ,QgCkmCR,chCjjCS,KgCojCX,iCACE,aACA,eACA,IhCxjCS,IgC0jCT,gDACE,oBACA,mBACA,IhC9jCO,IgC+jCP,iBACA,WhC5mCE,KgC6mCF,yBACA,chCjjCe,KgCkjCf,UhC1lCS,KgC2lCT,MhC9nCS,QgCgoCT,6DACE,gBACA,YACA,MhCnoCO,QgCooCP,eACA,eACA,UACA,gBAEA,mEACE,MhCtoCM,QgC8oChB,iBAEE,QhCplCY,KgCwlCd,eACE,QhCzlCY,KgC0lCZ,kBACA,MhC/oCU,QgCipCV,gCACE,WACA,YACA,mBACA,yBACA,iBhClqCW,QgCmqCX,chCvlCmB,IgCwlCnB,kCAGF,iBACE,UhCpoCa,KgCqoCb,MhC7pCQ,QgCiqCZ,gBACE,GACE,0BAKJ,eACE,WACA,iBACA,cAEA,0BACE,WhC5qCI,KgC6qCJ,chCnnCe,KgConCf,QhC7nCU,KgC8nCV,WhCrqCQ,0BgCsqCR,chChoCS,KgCmoCX,2BACE,chCpoCS,KgCsoCT,sCACE,gBAIJ,2BACE,cACA,UhCxqCW,KgCyqCX,YhC5pCmB,IgC6pCnB,MhClsCQ,QgCmsCR,chCnpCS,IgCqpCT,mDACE,MhC7sCU,QgC8sCV,gBAIJ,qFAGE,WACA,kBACA,yBACA,chCrpCe,IgCspCf,UhCzrCa,KgC0rCb,WhCznCc,agC0nCd,WhCjtCI,KgCmtCJ,uGACE,aACA,ahCnuCS,QgCouCT,uCAGF,4HACE,MhC3tCO,QgC8tCT,gHACE,yBAIJ,8BACE,gBACA,iBACA,oBAGF,0BACE,cACA,UhCttCW,KgCutCX,MhC7uCQ,QgC8uCR,WhChsCS,IgCksCT,wCACE,YACA,MhCjvCO,QgCqvCX,4BACE,aACA,IhCvsCS,KgCwsCT,eAEA,yCACE,aACA,mBACA,eAEA,2DACE,ahCltCK,IgCqtCP,qDACE,UhC7uCS,KgC8uCT,MhCvwCI,QgC6wCV,oCACE,aACA,mBACA,IhC9tCS,KgCguCT,kDACE,YACA,aACA,0BACA,chCztCa,KgC0tCb,aACA,mBACA,uBACA,WhCtxCI,QgCuxCJ,gBACA,kBACA,WhCnsCY,agCqsCZ,wDACE,ahC3yCO,QgC4yCP,8BAGF,oEACE,aACA,sBACA,mBACA,uBACA,IhCzvCK,IgC0vCL,MhCzyCI,QgC0yCJ,kBACA,QhC3vCK,KgC6vCL,wEACE,WAGF,yEACE,UhC5xCK,KgC6xCL,YhChxCW,IgCoxCf,sDACE,WACA,YACA,iBAGF,mEACE,kBACA,QACA,UACA,WACA,YACA,UACA,+BACA,yBACA,chCtwCe,IgCuwCf,MhCv0CI,QgCw0CJ,eACA,WhChvCU,agCivCV,aACA,mBACA,uBACA,WhCj0CI,0BgCk0CJ,WAEA,uEACE,WACA,YAGF,yEACE,WhC91CM,QgC+1CN,ahC/1CM,QgCg2CN,MhCt1CF,KgCu1CE,qBACA,WhC70CE,+BgCg1CJ,0EACE,sBAKN,gDACE,aAGF,qDACE,kBACA,WhCv2CE,KgCw2CF,yBACA,chChzCa,IgCizCb,UhCr1CS,KgCs1CT,YhC10Ce,IgC20Cf,MhC/2CM,QgCg3CN,eACA,WhCvxCY,agCyxCZ,2DACE,WhC/3CO,QgCg4CP,ahCh4CO,QgCi4CP,MhCn3CA,KgCs3CF,4DACE,sBAMN,+BACE,aACA,IhCl1CS,IgCm1CT,chCl1CS,KgCo1CT,2CACE,OAGF,2CACE,oBACA,mBACA,IhC71CO,IgC81CP,kBACA,WhCz5CS,QgC05CT,YACA,chCp1Ca,IgCq1Cb,MhC94CE,KgC+4CF,UhC13CS,KgC23CT,YhC/2Ce,IgCg3Cf,eACA,WhC3zCY,agC4zCZ,mBAEA,+CACE,cAGF,iDACE,WhCv6CS,QgCw6CT,2BACA,WhCj5CI,+BgCo5CN,kDACE,wBAKN,wBACE,WhCx3CS,KgCy3CT,gBAEA,wCACE,QhC53CO,KgC63CP,WhC16CI,QgC26CJ,gCAEA,kDACE,UhC15CO,KgC25CP,MhCl7CI,QgCo7CJ,yDACE,MhCj8CK,QgCk8CL,YhCl5Ca,IgCu5CnB,kCACE,iBACA,gBAEA,qDACE,UAGF,2DACE,WhCj8CE,QgCo8CJ,2DACE,WhCn8CM,QgCo8CN,kBAEA,iEACE,WhC38CG,QgCg9CT,iCACE,aACA,mBACA,8BACA,gCACA,WhC73CY,agC+3CZ,4CACE,mBAGF,uCACE,WhC19CE,QgC69CJ,6CACE,YhC98CW,oCgC+8CX,UhC38CO,KgC48CP,MhCp+CI,QgCq+CJ,YhCj8Ca,IgCo8Cf,gDACE,oBACA,mBACA,uBACA,WACA,YACA,UACA,WhC5+CA,KgC6+CA,yBACA,chCt7CW,IgCu7CX,MhCj/CI,QgCk/CJ,eACA,WhC15CU,agC45CV,oDACE,WACA,YAGF,uDACE,sBASV,qFACE,8BACA,oBACA,WhCp9CY,KgCq9CZ,gBACA,cAEA,yBAPF,qFAQI,qB9BthDA,yB8BiiDJ,yFAEI,aACA,8BACA,kCACA,mBACA,SACA,kBACA,aACA,+BAEA,iHACE,uBACA,uBACA,gBACA,4BACA,0BACA,YhCngDa,IgCogDb,6BACA,cAEA,6IACE,oCACA,yBACA,uBAGF,yIACE,oCACA,sBACA,wBAOR,iBAEE,kBACA,cACA,cACA,kB9B3kDE,yB8BskDJ,iBASI,YACA,+BACA,iBACA,+BACA,aACA,sBACA,mBACA,4BAIF,6BAEE,c9B5lDA,yB8B0lDF,6BAMI,yBACA,kBACA,uBACA,WACA,oBAMA,0CACE,YACA,a9B5mDJ,yB8B0mDE,0CAMI,YACA,cAON,oCACE,gCACA,MhCxnDU,QgC4nDd,sCACE,chCnkDS,KE5DT,yB8B8nDF,sCAKI,oBAGF,mDACE,cACA,aACA,mBACA,uBACA,eAIJ,gCACE,chCnlDU,KE7DV,yB8B+oDF,gCAKI,iBAGF,mCACE,UhCnnDS,KgConDT,YhC1mDa,IgC2mDb,MhC5pDS,QgC6pDT,chCjmDO,KE1DT,yB8BupDA,mCAQI,eACA,oBAIJ,oDACE,UhCnoDW,KgCooDX,MhC5pDM,QgC6pDN,c9BvqDF,yB8BoqDA,oDAOI,eACA,gBAEA,+DACE,cAGF,+DACE,eASV,eACE,WhCjrDM,KgCkrDN,chCxnDiB,KgCynDjB,QhCnoDW,KgCooDX,WhCzqDU,+BgC0qDV,chCroDW,KgCsoDX,gBAEA,yBACE,aACA,mBACA,8BACA,eACA,gCAEA,oCACE,mBAGF,qCACE,UhC/qDS,KgCgrDT,MhCvsDM,QgCwsDN,YhCrqDe,IgCsqDf,gBAGF,qCACE,UhCrrDW,KgCsrDX,MhC/sDM,QgCgtDN,YhC5qDe,IgC+qDjB,sCACE,aACA,mBACA,IhCtqDO,IgCuqDP,OACA,yBAEA,iDACE,YhCvsDW,oCgCwsDX,UhCpsDO,KgCqsDP,WhCztDE,QgC0tDF,iBACA,chCpqDW,IgCqqDX,MhC1uDS,QgC2uDT,ahCjrDK,IgCorDP,6GAEE,iBACA,WhCpuDA,KgCquDA,yBACA,chC9qDW,IgC+qDX,eACA,WhCjpDU,agCmpDV,yHACE,WhCzvDK,QgC0vDL,ahC1vDK,QgC2vDL,MhC7uDF,KgCmvDF,8CACE,iBACA,iCACA,cACA,chC1rDa,KgC2rDb,UhCpuDO,KgCquDP,YhCvtDe,IgC6tDvB,uBACE,WhC/vDQ,QgCgwDR,chCvsDiB,KgCwsDjB,QhCntDW,KgCotDX,chCntDW,KgCotDX,gBAEA,0BACE,UhChvDW,KgCivDX,YhCtuDmB,IgCuuDnB,MhC5wDQ,QgC6wDR,chC5tDS,KgC+tDX,kCACE,aACA,eACA,IhCnuDS,IgCouDT,chCnuDS,KgCquDT,4CACE,iBACA,WhCrxDE,KgCsxDF,yBACA,chC1tDe,KgC2tDf,UhCnwDS,KgCowDT,MhCvyDS,QgC2yDb,iCACE,UhC1wDW,KgC2wDX,MhCjyDQ,QgCkyDR,kBAIJ,iBACE,WhCnyDS,QgCoyDT,chC5uDiB,KgC6uDjB,QhCvvDW,KgCwvDX,chCxvDW,KgCyvDX,gBAEA,oBACE,UhCrxDW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CACE,aACA,IjC5ZS,KiC6ZT,mBAIJ,iBACE,aACA,mBACA,uBACA,IjCvaW,IiCwaX,YACA,YACA,eACA,mBACA,YACA,cjC/ZiB,KiCgajB,eACA,YjCxbiB,IiCybjB,MjC5dM,KiC6dN,eACA,mBACA,+BAEA,uBACE,iDAGF,yBAtBF,iBAuBI,YACA,UjCjda,KiCkdb,gBAKJ,iBACE,aACA,mBACA,uBACA,IjCpcW,IiCqcX,WACA,YACA,mBACA,YACA,cjC5biB,KiC6bjB,eACA,YjCrdiB,IiCsdjB,MjCzfM,KiC0fN,eACA,+BAEA,uBACE,4DAGF,qBACE,WACA,YAGF,yBAzBF,iBA0BI,eACA,YACA,UjCnfa,MiCwfjB,cACE,WACA,WjChhBM,KiCihBN,yBACA,cjCxdiB,KiCydjB,QjCpeW,KiCseX,yBAPF,cAQI,eACA,QjCzeS,MiC8eb,eACE,aACA,mBACA,8BACA,eACA,gCAEA,0BACE,mBAGF,8BACE,eACA,WAEA,yBAJF,8BAKI,UjCthBW,MiC0hBf,gCACE,aACA,mBACA,uBACA,gBACA,YACA,eACA,mBACA,cjC3fiB,KiC4fjB,UjCniBa,KiCoiBb,cAEA,yBAZF,gCAaI,eACA,UjCziBS,KiC0iBT,gB/B3kBF,yB+B2lBF,yBACE,qBACA,eAIF,oBACE,aACA,gBACA,gCAEA,oCACE,eACA,YjCzjBa,IiC0jBb,kBAGF,uCACE,eACA,WAKJ,eACE,8BACA,kCACA,2BACA,aACA,SACA,gBACA,WjC9mBI,KiCgnBJ,8BACE,sBACA,uBACA,eACA,cACA,kBAEA,8CACE,WACA,YACA,iBACA,kBAIA,wDACE,WACA,YAKN,iCACE,QAGF,iCACE,iBACA,eACA,mBAEA,iDACE,yBACA,MjCjpBA,KiCqpBJ,8BACE,0BACA,YjCpnBa,IiCunBf,qCACE,0BACA,WACA,gBAKJ,qBACE,mBACA,kBACA,aACA,WAIF,UACE,sBACA,uBACA,QACA,mBAEA,qBACE,gBAIJ,YACE,0BACA,YjCppBe,IiCqpBf,cACA,WAGF,YACE,WAIF,gBACE,uBACA,0BACA,mBACA,yBACA,kBACA,e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X,MnC9WM,QmC+WN,YnCnUc,ImCqUd,yBALF,kDAMI,UnC1VO,MmC8VT,oDACE,cnCtUK,KmCwUL,+DACE,gBAIJ,8TACE,WnC5UK,KmC6UL,cnC/UK,KmCgVL,YnC5Ve,ImC6Vf,MnClYI,QmCqYN,sDACE,eACA,YACA,cnC5UW,ImC6UX,cAGF,oDACE,MnCxZO,QmCyZP,0BAEA,0DACE,MnC3ZO,QmC+ZX,0GACE,cACA,anCpWK,KmCuWP,qDACE,cnC3WK,ImC8WP,6DACE,8BACA,anC9WK,KmC+WL,cACA,MnCjaI,QmCkaJ,kBAGF,uDACE,WnCnaE,QmCoaF,gBACA,cnC9WW,ImC+WX,YnCtZW,oCmCuZX,eACA,MnCvbO,QmC0bT,sDACE,WnC5aE,QmC6aF,yBACA,cnCtXW,ImCuXX,QnClYK,KmCmYL,gBACA,cAEA,2DACE,yBACA,UACA,MnC1bE,QmC8bN,wDACE,WACA,yBACA,cACA,yBACA,cnCvYW,ImCwYX,gBAEA,2DACE,WnClcC,QmCmcD,iBACA,gBACA,YnCraa,ImCsab,gCAGF,2DACE,iBACA,gCAGF,yEACE,mBAQV,gBACE,aACA,uBACA,InC9aW,KmC+aX,cnC3aY,KmC6aZ,yBANF,gBAOI,sBACA,qBAOJ,mBACE,WnCzeM,KmC0eN,cnChbiB,KmCibjB,WnCjeU,0BmCkeV,gBAEA,6BACE,aACA,mBACA,8BACA,kBACA,gCACA,WnC7Zc,amC8Zd,eAEA,yBATF,6BAUI,sBACA,uBACA,kBACA,InC9cO,KmCidT,wCACE,mBAGF,mCACE,WnClgBI,QmCogBJ,8CACE,MnCphBO,QmCwhBX,wCACE,aACA,mBACA,InCheO,ImCieP,UnCzfS,KmC0fT,YnC9ee,ImC+ef,MnClhBM,QmCmhBN,eAEA,yBATF,wCAUI,UnChgBO,MmCmgBT,0CACE,MnCtiBO,QmC0iBX,wCACE,OACA,UnCxgBW,KmCygBX,MnCliBM,QmCmiBN,WnCzcY,amC2cZ,yBANF,wCAOI,UnC9gBO,MmCkhBX,uCACE,UnCnhBS,KmCohBT,MnC1iBO,QmC2iBP,gBACA,iBAEA,yBANF,uCAOI,UnC1hBO,KmC2hBP,iBASR,0BACE,iBACA,cACA,kBAEA,0BALF,0BAMI,mBAKJ,uBACE,enCthBW,KmCuhBX,gCACA,cnCthBW,KmCyhBb,oBACE,aACA,8BACA,mBACA,cnC/hBW,KmCiiBX,0BANF,oBAOI,sBACA,uBACA,InCriBS,KmCyiBb,sBACE,eACA,YnCrjBiB,ImCsjBjB,cACA,gBACA,SAEA,0BAPF,sBAQI,gBAIJ,qBACE,eACA,YnCpkBoB,ImCqkBpB,cAEA,0BALF,qBAMI,gBAKJ,2BACE,cnC/jBW,KmCikBX,4CACE,aACA,sBACA,InCtkBS,ImCykBX,4CACE,aACA,mBAIJ,yBACE,oBACA,mBACA,InCnlBW,ImColBX,cACA,eACA,qBACA,WnC5iBgB,amC8iBhB,0BATF,yBAUI,gBAGF,+BACE,MnCxpBW,QmC2pBb,6BACE,cACA,cAGF,mCACE,MnCjqBW,QmCoqBb,2CACE,anCzmBS,KmC4mBX,8CACE,cAKJ,wBACE,cnChnBY,KmCmnBd,sBACE,mBACA,mBACA,QnCtnBY,KmCunBZ,iBACA,UnCnpBe,KmCopBf,MnC7qBU,QmC8qBV,YnCloBkB,ImCmoBlB,qBAEA,0BAVF,sBAWI,QnChoBS,KmCioBT,iBACA,oBAIF,yBAjBF,sBAkBI,yBACA,gBACA,UACA,iBAGF,wBACE,cnC/oBS,KmCipBT,mCACE,gBAIJ,0BACE,eACA,YACA,cnC9oBe,ImC+oBf,cAGF,wBACE,MnC1tBW,QmC2tBX,0BAEA,8BACE,MnC7tBW,QmCmuBjB,0BACE,cnCtqBY,KmCyqBd,yBACE,aACA,8BACA,mBACA,enChrBW,KmCirBX,gCACA,cnCjrBW,KmCmrBX,0BARF,yBASI,sBACA,uBACA,InCxrBS,KmC4rBb,gBACE,eACA,YnCxsBiB,ImCysBjB,cAEA,0BALF,gBAMI,gBAIJ,eACE,eACA,YnCrtBoB,ImCstBpB,cAEA,0BALF,eAMI,gBAIJ,uBACE,gBACA,yBACA,mBACA,QnChtBY,KmCitBZ,iBACA,UnC7uBe,KmC8uBf,MnCvwBU,QmCwwBV,YnC5tBkB,ImC6tBlB,qBAEA,0BAXF,uBAYI,QnC1tBS,KmC2tBT,iBACA,oBAKJ,wBACE,aACA,8BACA,mBACA,InCtuBW,KmCwuBX,0BANF,wBAOI,mBACA,uBACA,InC5uBS,KmC+uBX,sCACE,aACA,InChvBS,KmCkvBT,0BAJF,sCAKI,InCpvBO,KmCwvBX,uCACE,aACA,InCzvBS,KmC2vBT,0BAJF,uCAKI,InC7vBO,KmCmwBb,kBACE,aACA,mBACA,uBACA,YACA,YACA,mBACA,YACA,mBACA,YnCzyBoB,0GmC0yBpB,eACA,YnCxxBiB,ImCyxBjB,cACA,eACA,WnCvuBgB,amCyuBhB,0BAhBF,kBAiBI,WACA,eACA,eACA,YACA,eACA,mBAGF,wBACE,kDAGF,yBACE,sBAIJ,kBACE,aACA,mBACA,uBACA,YACA,YACA,mBACA,YACA,mBACA,YnC30BoB,0GmC40BpB,eACA,YnC1zBiB,ImC2zBjB,MnC91BM,KmC+1BN,eACA,WnCzwBgB,amC2wBhB,0BAhBF,kBAiBI,WACA,eACA,eACA,YACA,eACA,mBAGF,wBACE,sCAGF,yBACE,sBAIJ,oBACE,aACA,mBACA,uBACA,YACA,YACA,mBACA,YACA,mBACA,YnC72BoB,0GmC82BpB,eACA,YnC51BiB,ImC61BjB,MnCh4BM,KmCi4BN,eACA,WnC3yBgB,amC6yBhB,0BAhBF,oBAiBI,WACA,eACA,eACA,YACA,eACA,mBAGF,0BACE,2DAGF,2BACE,sBAOJ,wBACE,iBACA,cACA,kBAEA,0BALF,wBAMI,mBAMJ,oBACE,aACA,mBACA,SAEA,0BALF,oBAMI,sBACA,oBACA,InC/3BS,MmCm4BX,wCACE,OACA,aACA,mBACA,8BACA,YACA,eACA,WnCx7BI,KmCy7BJ,yBACA,mBACA,YAEA,iDACE,qBAIJ,uCACE,OACA,YnCp7BkB,0GmCq7BlB,UnC/6Ba,KmCg7Bb,cACA,gBACA,uBACA,mBAEA,gDACE,MnC/8BM,QmCo9BV,4CACE,aACA,mBACA,uBACA,WACA,YACA,mBACA,YACA,kBACA,MnC19BI,KmC29BJ,eACA,WnCr4Bc,amCs4Bd,cACA,YnCj7BS,ImCm7BT,kDACE,2DAGF,gDACE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oCsSX,kDACE,aACA,mBACA,IpCnRO,KoCoRP,eACA,UpC/SS,KoCgTT,MpCvUM,QoCyUN,yBARF,kDASI,UpCpTO,KoCqTP,IpC3RK,MoC8RP,6DACE,aACA,mBACA,IpCnSK,IoCqSL,+DACE,MpChWK,QoCmWP,oEACE,MpCzVE,QoC0VF,YpCtTW,IoC6TnB,yCACE,kBACA,WpC/VM,QoCgWN,gCAEA,yBALF,yCAMI,mBAGF,0DACE,aACA,sBACA,IpC7TO,IoC+TP,2EACE,aACA,mBACA,IpClUK,IoCoUL,6EACE,MpCrXE,QoCsXF,qBACA,UpC/VK,KoCgWL,WpC9RQ,aoC+RR,aACA,mBACA,IpC5UG,IoC8UH,yBATF,6EAUI,UpCvWG,MoC0WL,mFACE,MpC7YG,QoC+YH,qFACE,2BAIJ,+EACE,MpCrZG,QoCsZH,WpCjTM,aoCyThB,qCACE,kBACA,iBAEA,yBAJF,qCAKI,mBAGF,mDACE,UpCnYW,KoCoYX,MpC7ZM,QoC8ZN,YpClXc,IoCmXd,qBAEA,yBANF,mDAOI,UpC1YO,MoCiZf,uBACE,WpCvaM,KoCwaN,cpC9WiB,KoC+WjB,WpC9ZU,+BoC+ZV,gBACA,cpC3XW,KoC4XX,8BAEA,wCACE,kBACA,iCACA,gCAEA,yBALF,wCAMI,QpCrYO,MoCwYT,wDACE,aACA,mBACA,IpC7YO,IoC8YP,UpCnaS,KoCoaT,YpC1ZiB,IoC2ZjB,MpChcM,QoCicN,cpCjZO,IoCmZP,0DACE,MpC1cO,QoC8cX,uDACE,UpCjbS,KoCkbT,MpCzcM,QoC2cN,yBAJF,uDAKI,UpCtbO,MoC2bb,yCACE,QpC/ZU,KoCgaV,UpC3ba,KoC4bb,MpCrdQ,QoCsdR,YpC1agB,IoC2ahB,qBAEA,yBAPF,yCAQI,QpCxaO,KoCyaP,UpCncS,MoCycf,wBACE,aACA,uBACA,IpCnbW,KoCobX,cpChbY,KoCibZ,eAEA,yBAPF,wBAQI,mBACA,qBAGF,oCACE,kBACA,cpCnbe,IoCobf,UpCvda,KoCwdb,YpC7ciB,IoC8cjB,qBACA,WpCzZc,aoC0Zd,oBACA,mBACA,uBACA,IpCvcS,IoCwcT,eACA,YAEA,yBAdF,oCAeI,kBACA,UpCreS,MoCweX,6CACE,WpC9fE,KoC+fF,MpClgBM,QoCmgBN,yBAEA,mDACE,WpClgBE,QoCmgBF,apClhBO,QoCshBX,6CACE,MpCzgBE,KoC2gBF,mDACE,2BACA,WpClgBI,+BoCsgBR,+CACE,WpC5hBU,QoC6hBV,MpCnhBE,KoCqhBF,qDACE,2BACA,WpC5gBI,+BoCghBR,sCACE,eAQN,mBACE,+BACA,WpCriBQ,QoCsiBR,kBAEA,yBALF,mBAMI,mBAIJ,wBACE,cAGF,mBACE,WpCnjBM,KoCojBN,cpC1fiB,KoC2fjB,WpC1iBU,+BoC2iBV,QpCpgBY,KoCqgBZ,cpCvgBW,KoCygBX,yBAPF,mBAQI,mBAGF,+BACE,cpC7gBU,KoC+gBV,0CACE,gBAGF,2CACE,cACA,UpChjBW,KoCijBX,YpCriBiB,IoCsiBjB,MpC3kBM,QoC4kBN,cpC3hBO,KoC6hBP,yBAPF,2CAQI,UpCvjBO,MoC0jBT,2DACE,YACA,MpC3lBQ,QoC4lBR,YpCtiBK,IoC0iBT,2CACE,WACA,QpC1iBO,KoC2iBP,yBACA,cpCjiBa,IoCkiBb,UpCrkBW,KoCskBX,WpCrgBY,aoCugBZ,yBARF,2CASI,UpC1kBO,KoC2kBP,kBAGF,iDACE,aACA,apCnnBO,QoConBP,uCAGF,wDACE,MpC3mBK,QoC+mBT,8CACE,WACA,iBACA,QpCnkBO,KoCokBP,yBACA,cpC1jBa,IoC2jBb,UpC9lBW,KoC+lBX,YpCrmBgB,0GoCsmBhB,gBACA,WpChiBY,aoCkiBZ,yBAXF,8CAYI,UpCrmBO,KoCsmBP,iBACA,kBAGF,oDACE,aACA,apC/oBO,QoCgpBP,uCAGF,2DACE,MpCvoBK,QoCgpBb,sBACE,aACA,uBACA,IpCpmBW,KoCqmBX,WpCnmBW,KoCqmBX,yBANF,sBAOI,8BACA,qBCrqBJ,mBACE,iBACA,mCAFF,mBAKI,WAOF,yBAHF,wBAII,mBAKJ,oBACE,UrCsBc,KqCrBd,YrC6BiB,IqC5BjB,MrCVU,QqCWV,kBACA,crCyCY,KE9DV,yBmCgBJ,oBAQI,UrCcY,KqCbZ,crCmCS,MqC9Bb,oBACE,aACA,mBACA,8BACA,kBACA,mBACA,crCiCiB,IqChCjB,crCuBW,KqCrBX,uBACE,UrCJW,KqCKX,YrCGkB,IqCFlB,cACA,SAGF,oCACE,8BACA,MrCpCI,KqCqCJ,iBACA,crCuBiB,KqCtBjB,UrClBW,KqCmBX,YrCPiB,IqCQjB,oBACA,mBACA,IrCCS,IqCCT,4CACE,YACA,MrC1DU,QqC2DV,YrCba,IqCkBjB,+BACE,gBACA,mBACA,gBACA,gCACA,crCVS,KqCYT,kCACE,eACA,YrC3Ba,IqC4Bb,cACA,SAGF,+CACE,anChFF,yBmC8BJ,oBAuDI,kBAEA,uBACE,UrCrDS,KqCwDX,+BACE,kBAEA,kCACE,UrC5DO,MqCmEf,iBACE,crClCiB,IqCmCjB,crC9CW,KqCgDX,oBACE,gBACA,UACA,SAEA,uBACE,kBACA,arCtDO,KqCuDP,MrCxGM,QqCyGN,UrClFS,KqCmFT,gBAEA,+BACE,YACA,kBACA,OACA,MrC5HO,QqC6HP,YrC5EW,IqC+Eb,0BACE,WrCtEK,IqC6Eb,gBACE,crC9EW,IqC+EX,aACA,uBACA,IrC/EW,KqCiFX,2BACE,gBnC7IA,yBmCsIJ,gBAWI,sBACA,IrCxFS,MqC4Fb,gBACE,aACA,mBACA,IrChGW,IqCiGX,UrCxHe,KqCyHf,YrC7GqB,IqC8GrB,MrCnJU,QqCoJV,gBACA,YrCpGW,KqCsGX,gCACE,oBACA,mBACA,uBACA,gBACA,mBACA,MrC1JI,KqC2JJ,eACA,YrC3HiB,IqC4HjB,kBACA,gBnC1KA,yBmCsJJ,gBAwBI,eACA,eAIJ,wBACE,OACA,aACA,sBACA,IrC9HW,IEzDT,yBmCmLJ,wBAOI,YAKJ,eACE,aACA,mBACA,IrCxIW,KqC0IX,uGAGE,OACA,YAGF,6DACE,cnC5MA,yBmC+LJ,eAiBI,sBACA,oBACA,IrCzJS,IqC2JT,uGAGE,WAGF,6DACE,YAMN,gBACE,WACA,kBACA,yBACA,crChKiB,IqCiKjB,UrCpMe,KqCqMf,MrC9NU,QqC+NV,WrCrIgB,aqCsIhB,WrC7NM,KqC+NN,sBACE,aACA,arC/OW,QqCgPX,yCAGF,yBACE,WrCrO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A,MtC3BQ,QsC4BR,YtCciB,IEpDjB,yBoCmCF,+BAMI,UtCPW,MsCUb,0CACE,MtC/CS,QsCgDT,gBACA,kBAEA,iDACE,WACA,kBACA,YACA,OACA,QACA,WACA,kBAOR,4BACE,gBACA,cpCjEE,yBoC+DJ,4BAKI,gBAKJ,kBACE,QtCXY,KsCYZ,WtC/DM,KsCgEN,ctCLiB,KsCMjB,WtCrDU,gCsCsDV,yBpC9EE,yBoCyEJ,kBAQI,kBACA,ctCbe,KsCiBjB,8BACE,ctC1BU,KE7DV,yBoCsFF,8BAII,ctC9BO,MsCiCT,oCACE,aACA,mBACA,ItCvCO,IsCwCP,UtC/DW,KsCgEX,YtCpDiB,IsCqDjB,MtC1FM,QsC2FN,ctC1CO,KsC4CP,sCACE,MtCzGO,QsC0GP,UtCrEO,KsCwET,8CACE,MtC1GQ,QsC2GR,gBAKJ,oDACE,iBACA,gBACA,gBpCrHF,yBoCkHA,oDAMI,kBAMN,6BACE,aACA,uBACA,ItCzES,IsC0ET,WtCzES,IsC0ET,UtClGW,KsCmGX,MtC1HQ,QsC2HR,YtCjFiB,IsCmFjB,+BACE,MtC1IS,QsC2IT,eACA,cAKJ,8BACE,aACA,uBACA,ItC1FS,IsC2FT,WtC1FS,IsC2FT,UtCnHW,KsCoHX,MtCnJY,QsCoJZ,YtClGiB,IsCoGjB,gCACE,eACA,cAKJ,kCACE,WtCtGS,KsCwGT,oCACE,aACA,uBACA,ItC7GO,IsC8GP,aACA,UtCvIS,KsCwIT,MtC7JO,QsC8JP,YtCrHe,IsCuHf,sCACE,MtCzKO,QsC0KP,eACA,cAWJ,mEACE,MtCjLM,QsCkLN,8BACA,atC9LS,QsC+LT,YtChJe,IsCoJnB,yBACE,wDACE,QAGF,0DACE,QAGF,wDACE,SAQJ,sCACE,atCtNW,QsCuNX,uCACA,aAIF,8CACE,wCAKJ,aACE,aACE,ctCtKS,KsCyKX,kBACE,gBACA,yBACA,QtC5KS,KsC+KX,mCACE,aAIA,4EAEE,cCrPN,2BACE,iBACA,cACA,eACA,cvC4DY,KuC1DZ,yBANF,2BAOI,mBAKJ,wBACE,aACA,8BACA,mBACA,cvCgDY,KuC9CZ,yBANF,wBAOI,sBACA,uBACA,IvCyCS,MuCpCX,0BACE,UvCSW,KuCRX,MvCfQ,QuCgBR,YvCkBkB,IuCjBlB,cvC6BS,IuC1BX,0BACE,UvCOY,KuCNZ,YvCee,IuCdf,MvCxBQ,QuC4BZ,yBACE,aACA,IvCmBW,KuChBb,uBACE,kBACA,UvCTa,KuCUb,WvCcW,KuCHX,uCACE,cvCIU,KuCDV,mDACE,MvCpDM,QuCqDN,qBACA,WvCoCY,auClCZ,yDACE,MvCpEO,QEEX,yBqCuEF,2CAII,iBvC/DE,KuCgEF,WAIA,4DACE,YAOR,oBACE,oBACA,YACA,mBACA,QACA,iBACA,sCACA,MvC3Fc,QuC4Fd,cvC5BiB,IuC6BjB,UvChEa,KuCiEb,YvCrDmB,IuCsDnB,gBAGA,yBAdF,oBAeI,cAKJ,kBACE,kBACA,kBACA,MvCrGU,QuCuGV,8BACE,eACA,cvCxDS,KuCyDT,WAGF,qBACE,UvCnFW,KuCoFX,YvC3EmB,IuC4EnB,MvCjHQ,QuCkHR,cvCjES,KuCoEX,oBACE,UvC7Fa,KuC8Fb,MvCtHQ,QuC8HZ,uBACE,gBACA,cACA,QvC9EY,KuCgFZ,yBALF,uBAMI,mBAKJ,mBACE,MvCxIM,KuCyIN,cvCzFW,KuC0FX,kBACA,gBAEA,2BACE,WACA,kBACA,SACA,WACA,YACA,aACA,+BACA,cvCvFmB,IuC0FrB,gCACE,YAIJ,qBACE,aACA,mBACA,IvChHW,KuCiHX,kBACA,UAEA,yBAPF,qBAQI,sBACA,mBAIJ,qBACE,cAGF,qBACE,YACA,aACA,cvCnHqB,IuCoHrB,aACA,mBACA,uBACA,UvC1Jc,KuC2Jd,YvCnJiB,IuCoJjB,8BACA,MvCxLM,KuCyLN,oCACA,WvC9KU,gCuCiLZ,mBACE,OAEA,sBACE,UvCvKY,KuCwKZ,YvC/Je,IuCgKf,cvCtJS,IuCyJX,uCACE,UvCjLa,KuCkLb,WACA,cvC3JS,KuC+Jb,qBACE,aACA,IvClKW,IuCmKX,eAEA,yBALF,qBAMI,wBAGF,8EAEE,8BACA,MvCzNI,KuC0NJ,2BACA,oCAKJ,gBACE,WvCjOM,KuCkON,cvCxKiB,KuCyKjB,QvCnLW,KuCoLX,WvC1NU,0BuC2NV,cvCtLW,KuCwLX,mBACE,UvChNW,KuCiNX,YvCvMmB,IuCwMnB,MvC7OQ,QuC8OR,cvC5LS,KuC6LT,evC/LS,IuCgMT,gCAIJ,aACE,aACA,qCACA,IvCrMW,KuCuMX,yBALF,aAMI,2BAIJ,aACE,aACA,sBACA,IvClNW,IuCoNX,wBACE,iBAIJ,cACE,UvCjPa,KuCkPb,YvCrOqB,IuCsOrB,MvC1QU,QuC2QV,aACA,mBACA,IvC/NW,IuCiOX,gBACE,MvC5RW,QuC6RX,UvC1PW,KuC8Pf,cACE,UvC9Pe,KuC+Pf,MvCxRU,QuCyRV,cAGF,kBACE,oBACA,mBACA,IvChPW,IuCiPX,iBACA,cvCtOiB,IuCuOjB,UvC1Qa,KuC2Qb,YvC/PmB,IuCiQnB,gCACE,sCACA,4CAEA,4CACE,iBvChTS,QuCoTb,kCACE,sCACA,MvC/SQ,QuCiTR,8CACE,iBvClTM,QuCuTZ,YACE,UACA,WACA,cvC1PqB,IuC2PrB,4BAGF,iBACE,QACE,UAEF,IACE,YAKJ,gBACE,aACA,IvC1RW,KuC2RX,uBACA,YvC3RW,KuC6RX,yBANF,gBAOI,uBAOJ,sBACE,oBACA,mBACA,uBACA,YACA,eACA,mBACA,eACA,YvCzTiB,IuC0TjB,MvC7VM,KuC8VN,mBAGA,iCACE,yBAIF,+BACE,yBAKJ,iBACE,aACA,uBACA,WvC9TY,KuCoUd,iBACE,aACA,IvC3UW,IuC4UX,iBAGF,gBACE,aAOF,yBAEE,iBACE,wBAGF,gBACE,wBAGF,2BACE,aACA,sBACA,IvCjWS,KuCkWT,kBACA,gBAGA,2CACE,iBAEA,wDACE,QACA,gBACA,eACA,MvCxZA,KuCyZA,mBACA,kCAEA,+DACE,cACA,YvC/XS,IuCgYT,eAKJ,uDACE,QACA,WAEA,4DACE,aACA,mBACA,uBACA,WACA,YAMN,uCACE,QACA,WvCzbE,KuC0bF,cvChYa,KuCiYb,iBAGA,0DACE,aACA,mBACA,YACA,cACA,mBACA,0BAEA,uEACE,eACA,YvCraS,IuCsaT,MvCzcF,KuC0cE,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,iBvC7gBF,KuCghBA,6DACE,oCAGF,iEACE,eACA,YvCtfY,IuCufZ,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,UvC9kBO,KuCilBT,+CACE,UvCrlBO,KuC0lBX,uCACE,QACA,SACA,eAEA,uDACE,WACA,YAEA,oHACE,eACA,WACA,YAIJ,2DACE,SAGF,0DACE,eACA,YACA,eACA,cACA,YvCxmBc,IuC0mBd,iEACE,YvCxmBS,IuCymBT,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,4QACE,cvCz1BO,KuC21BP,2RACE,UvCn3BO,KuCw3BX,iDACE,2BACA,uBACA,oCACA,qBAIF,mFACE,aACA,8BACA,kCACA,8BACA,iBACA,aAEA,+HACE,WACA,aACA,uBAEA,yIACE,aACA,mBACA,uBACA,WACA,YACA,gBAUV,kBACE,kBACA,oBACA,mBACA,uBAEA,mCACE,aACA,mBACA,uBACA,WACA,YACA,UACA,yBACA,YACA,cvC54Be,IuC64Bf,cACA,eACA,WvCh3Bc,auCk3Bd,yCACE,6BACA,MvCz9BS,QuC49BX,yCACE,aACA,6BAGF,uCACE,WACA,YAIJ,iCACE,kBACA,SACA,QACA,gBACA,WvC99BI,KuC+9BJ,cvCt6Be,IuCu6Bf,sCACA,QvCr5Be,IuCs5Bf,UACA,kBACA,2BACA,wBACA,gBAEA,gDACE,cACA,WACA,kBACA,yBACA,YACA,gBACA,eACA,YvCh9BgB,IuCi9BhB,cACA,eACA,WvC35BY,cuC45BZ,mBAEA,sDACE,mBACA,MvCtgCO,QuCygCT,uDACE,mBAGF,+DACE,0BAOJ,wCACE,6BACA,MvCvhCS,QuC0hCX,sCACE,UACA,mBACA,0BC3hCN,sBACE,iBACA,cAGF,MACE,iBACA,cACA,WxCIM,KwCHN,QxCkDW,KwCjDX,cxC2DiB,IwC1DjB,WxCYU,+BwCTZ,YACE,qBACA,axCyCW,IwCxCX,cxCyCW,KwCrCb,WACE,mBACA,aACA,cxC6CiB,IwC5CjB,mBAEA,mBACE,aACA,mBACA,SACA,eAGF,iBACE,YxCUiB,IwCTjB,MxC3BQ,QwC4BR,eAGF,yBACE,iBACA,yBACA,cxCyBe,IwCxBf,UxCXW,KwCYX,gBAIJ,aACE,aACA,mBACA,SAGA,6BACE,aACA,mBACA,SAEA,0CACE,kBAKN,QACE,iBAEA,kBACE,gBAKJ,UACE,aAIF,mBACE,cACA,gBACA,gBAGF,UACE,gBAIF,WACE,MxC1Fc,QwC6FhB,YACE,cAGF,eACE,cAIF,kBACE,gBACA,gB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GF,cACE,mBAGF,aACE,uBAGF,WACE,qBAGF,gBACE,uBAGF,eACE,2BAGF,aACE,yBAGF,iBACE,8BAGF,gBACE,6BAIF,QACE,I1ChBW,I0CmBb,QACE,I1CnBW,I0CsBb,QACE,I1CtBW,K0CyBb,QACE,I1CzBW,K0C4Bb,QACE,I1C5BW,K0C4CX,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,W1CrPa,Q0CwPf,cACE,W1CxPe,Q0C2PjB,UACE,W1C/OM,K0CkPR,SACE,W1ClPQ,Q0CqPV,UACE,W1CrPS,Q0C8PX,QACE,yBAGF,UACE,uBAGF,YACE,6BAGF,eACE,gCAGF,aACE,8BAGF,cACE,+BAGF,gBACE,a1CvSa,Q0C2Sf,WACE,gBAGF,YACE,c1C1OiB,I0C6OnB,SACE,c1C7OiB,I0CgPnB,YACE,c1ChPiB,K0CmPnB,YACE,c1CnPiB,K0CsPnB,cACE,c1CrPmB,K0CwPrB,gBACE,c1CxPqB,I0C4PvB,aACE,2BAGF,WACE,W1CrTU,0B0CwTZ,QACE,W1CxTU,+B0C2TZ,WACE,W1C3TU,gC0C8TZ,WACE,W1C9TU,gC0CkUZ,iBACE,gBAGF,eACE,cAGF,mBACE,kBAGF,mBACE,kBAGF,iBACE,gBAGF,iBACE,gBAIF,KACE,UAGF,MACE,WAGF,MACE,WAGF,MACE,WAGF,MACE,WAGF,MACE,WxCzYE,yBwC8YF,SACE,yBxC7YA,0BwCkZF,SACE,yBAKF,yBADF,aAEI,yBxC3ZA,yBwC+ZJ,cAEI,yBAKJ,IACE,cxCvaE,yBwCsaJ,IAII,yBAIJ,QACE,axC/aE,yBwC8aJ,QAII,0BAKJ,gBACE,eAGF,gBACE,eAGF,oBACE,mBAIF,WACE,UAGF,YACE,YAGF,YACE,WAGF,YACE,YAGF,aACE,UAIF,YACE,W1CvXgB,a0C0XlB,iBACE,W1C1XgB,c0C6XlB,iBACE,W1C7XgB,a0CgYlB,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/font/NotoSansKR-VariableFont_wght.ttf b/src/main/resources/static/font/NotoSansKR-VariableFont_wght.ttf new file mode 100644 index 0000000..d19eb43 Binary files /dev/null and b/src/main/resources/static/font/NotoSansKR-VariableFont_wght.ttf differ diff --git a/src/main/resources/static/font/Pretendard-Bold.ttf b/src/main/resources/static/font/Pretendard-Bold.ttf deleted file mode 100644 index 7837ae5..0000000 Binary files a/src/main/resources/static/font/Pretendard-Bold.ttf and /dev/null differ diff --git a/src/main/resources/static/font/Pretendard-Bold.woff b/src/main/resources/static/font/Pretendard-Bold.woff deleted file mode 100644 index 7837ae5..0000000 Binary files a/src/main/resources/static/font/Pretendard-Bold.woff and /dev/null differ diff --git a/src/main/resources/static/font/Pretendard-Regular.ttf b/src/main/resources/static/font/Pretendard-Regular.ttf deleted file mode 100644 index e3b3a35..0000000 Binary files a/src/main/resources/static/font/Pretendard-Regular.ttf and /dev/null differ diff --git a/src/main/resources/static/font/Pretendard-Regular.woff b/src/main/resources/static/font/Pretendard-Regular.woff deleted file mode 100644 index e3b3a35..0000000 Binary files a/src/main/resources/static/font/Pretendard-Regular.woff and /dev/null differ diff --git a/src/main/resources/static/font/kjb/HGGGothicssi40g.ttf b/src/main/resources/static/font/kjb/HGGGothicssi40g.ttf new file mode 100644 index 0000000..3891426 Binary files /dev/null and b/src/main/resources/static/font/kjb/HGGGothicssi40g.ttf differ diff --git a/src/main/resources/static/font/kjb/HGGGothicssi40g.woff b/src/main/resources/static/font/kjb/HGGGothicssi40g.woff new file mode 100644 index 0000000..2fd96d3 Binary files /dev/null and b/src/main/resources/static/font/kjb/HGGGothicssi40g.woff differ diff --git a/src/main/resources/static/font/kjb/HGGGothicssi40g.woff2 b/src/main/resources/static/font/kjb/HGGGothicssi40g.woff2 new file mode 100644 index 0000000..c272914 Binary files /dev/null and b/src/main/resources/static/font/kjb/HGGGothicssi40g.woff2 differ diff --git a/src/main/resources/static/font/kjb/HGGGothicssi60g.ttf b/src/main/resources/static/font/kjb/HGGGothicssi60g.ttf new file mode 100644 index 0000000..cc4dc7f Binary files /dev/null and b/src/main/resources/static/font/kjb/HGGGothicssi60g.ttf differ diff --git a/src/main/resources/static/font/kjb/HGGGothicssi60g.woff b/src/main/resources/static/font/kjb/HGGGothicssi60g.woff new file mode 100644 index 0000000..9a6b093 Binary files /dev/null and b/src/main/resources/static/font/kjb/HGGGothicssi60g.woff differ diff --git a/src/main/resources/static/font/kjb/HGGGothicssi60g.woff2 b/src/main/resources/static/font/kjb/HGGGothicssi60g.woff2 new file mode 100644 index 0000000..0eef338 Binary files /dev/null and b/src/main/resources/static/font/kjb/HGGGothicssi60g.woff2 differ diff --git a/src/main/resources/static/font/kjb/HGGGothicssi80g.ttf b/src/main/resources/static/font/kjb/HGGGothicssi80g.ttf new file mode 100644 index 0000000..19d807a Binary files /dev/null and b/src/main/resources/static/font/kjb/HGGGothicssi80g.ttf differ diff --git a/src/main/resources/static/font/kjb/HGGGothicssi80g.woff b/src/main/resources/static/font/kjb/HGGGothicssi80g.woff new file mode 100644 index 0000000..05acd6e Binary files /dev/null and b/src/main/resources/static/font/kjb/HGGGothicssi80g.woff differ diff --git a/src/main/resources/static/font/kjb/HGGGothicssi80g.woff2 b/src/main/resources/static/font/kjb/HGGGothicssi80g.woff2 new file mode 100644 index 0000000..58582a9 Binary files /dev/null and b/src/main/resources/static/font/kjb/HGGGothicssi80g.woff2 differ diff --git a/src/main/resources/static/font/kjb/SpoqaHanSansBold.ttf b/src/main/resources/static/font/kjb/SpoqaHanSansBold.ttf new file mode 100644 index 0000000..00f6e1a Binary files /dev/null and b/src/main/resources/static/font/kjb/SpoqaHanSansBold.ttf differ diff --git a/src/main/resources/static/font/kjb/SpoqaHanSansBold.woff b/src/main/resources/static/font/kjb/SpoqaHanSansBold.woff new file mode 100644 index 0000000..e793d7b Binary files /dev/null and b/src/main/resources/static/font/kjb/SpoqaHanSansBold.woff differ diff --git a/src/main/resources/static/font/kjb/SpoqaHanSansBold.woff2 b/src/main/resources/static/font/kjb/SpoqaHanSansBold.woff2 new file mode 100644 index 0000000..2133aee Binary files /dev/null and b/src/main/resources/static/font/kjb/SpoqaHanSansBold.woff2 differ diff --git a/src/main/resources/static/font/kjb/SpoqaHanSansNeo-Bold.ttf b/src/main/resources/static/font/kjb/SpoqaHanSansNeo-Bold.ttf new file mode 100644 index 0000000..45aa7f6 Binary files /dev/null and b/src/main/resources/static/font/kjb/SpoqaHanSansNeo-Bold.ttf differ diff --git a/src/main/resources/static/font/kjb/SpoqaHanSansNeo-Bold.woff b/src/main/resources/static/font/kjb/SpoqaHanSansNeo-Bold.woff new file mode 100644 index 0000000..6630859 Binary files /dev/null and b/src/main/resources/static/font/kjb/SpoqaHanSansNeo-Bold.woff differ diff --git a/src/main/resources/static/font/kjb/SpoqaHanSansNeo-Bold.woff2 b/src/main/resources/static/font/kjb/SpoqaHanSansNeo-Bold.woff2 new file mode 100644 index 0000000..da1dd60 Binary files /dev/null and b/src/main/resources/static/font/kjb/SpoqaHanSansNeo-Bold.woff2 differ diff --git a/src/main/resources/static/font/kjb/SpoqaHanSansNeo-Regular.ttf b/src/main/resources/static/font/kjb/SpoqaHanSansNeo-Regular.ttf new file mode 100644 index 0000000..ca7bf6d Binary files /dev/null and b/src/main/resources/static/font/kjb/SpoqaHanSansNeo-Regular.ttf differ diff --git a/src/main/resources/static/font/kjb/SpoqaHanSansNeo-Regular.woff b/src/main/resources/static/font/kjb/SpoqaHanSansNeo-Regular.woff new file mode 100644 index 0000000..9a2041b Binary files /dev/null and b/src/main/resources/static/font/kjb/SpoqaHanSansNeo-Regular.woff differ diff --git a/src/main/resources/static/font/kjb/SpoqaHanSansNeo-Regular.woff2 b/src/main/resources/static/font/kjb/SpoqaHanSansNeo-Regular.woff2 new file mode 100644 index 0000000..1572319 Binary files /dev/null and b/src/main/resources/static/font/kjb/SpoqaHanSansNeo-Regular.woff2 differ diff --git a/src/main/resources/static/font/kjb/SpoqaHanSansRegular.ttf b/src/main/resources/static/font/kjb/SpoqaHanSansRegular.ttf new file mode 100644 index 0000000..2959c8e Binary files /dev/null and b/src/main/resources/static/font/kjb/SpoqaHanSansRegular.ttf differ diff --git a/src/main/resources/static/font/kjb/SpoqaHanSansRegular.woff b/src/main/resources/static/font/kjb/SpoqaHanSansRegular.woff new file mode 100644 index 0000000..0389d83 Binary files /dev/null and b/src/main/resources/static/font/kjb/SpoqaHanSansRegular.woff differ diff --git a/src/main/resources/static/font/kjb/SpoqaHanSansRegular.woff2 b/src/main/resources/static/font/kjb/SpoqaHanSansRegular.woff2 new file mode 100644 index 0000000..4e63efa Binary files /dev/null and b/src/main/resources/static/font/kjb/SpoqaHanSansRegular.woff2 differ diff --git a/src/main/resources/static/img/api_icon_finance.png b/src/main/resources/static/img/api_icon_finance.png new file mode 100644 index 0000000..ec776c4 Binary files /dev/null and b/src/main/resources/static/img/api_icon_finance.png differ diff --git a/src/main/resources/static/img/api_icon_loan.png b/src/main/resources/static/img/api_icon_loan.png new file mode 100644 index 0000000..3377abd Binary files /dev/null and b/src/main/resources/static/img/api_icon_loan.png differ diff --git a/src/main/resources/static/img/api_icon_p2p.png b/src/main/resources/static/img/api_icon_p2p.png new file mode 100644 index 0000000..a1267f4 Binary files /dev/null and b/src/main/resources/static/img/api_icon_p2p.png differ diff --git a/src/main/resources/static/img/api_icon_remittance.png b/src/main/resources/static/img/api_icon_remittance.png new file mode 100644 index 0000000..46facc4 Binary files /dev/null and b/src/main/resources/static/img/api_icon_remittance.png differ diff --git a/src/main/resources/static/img/api_sidebar.png b/src/main/resources/static/img/api_sidebar.png new file mode 100644 index 0000000..efb5193 Binary files /dev/null and b/src/main/resources/static/img/api_sidebar.png differ diff --git a/src/main/resources/static/img/apikey_step1.png b/src/main/resources/static/img/apikey_step1.png new file mode 100644 index 0000000..09a53b6 Binary files /dev/null and b/src/main/resources/static/img/apikey_step1.png differ diff --git a/src/main/resources/static/img/apikey_step2.png b/src/main/resources/static/img/apikey_step2.png new file mode 100644 index 0000000..212eee1 Binary files /dev/null and b/src/main/resources/static/img/apikey_step2.png differ diff --git a/src/main/resources/static/img/apikey_step3.png b/src/main/resources/static/img/apikey_step3.png new file mode 100644 index 0000000..221d1a1 Binary files /dev/null and b/src/main/resources/static/img/apikey_step3.png differ diff --git a/src/main/resources/static/img/bg_api.png b/src/main/resources/static/img/bg_api.png deleted file mode 100644 index 0a0d033..0000000 Binary files a/src/main/resources/static/img/bg_api.png and /dev/null differ diff --git a/src/main/resources/static/img/bg_api_intro.svg b/src/main/resources/static/img/bg_api_intro.svg new file mode 100644 index 0000000..de39a48 --- /dev/null +++ b/src/main/resources/static/img/bg_api_intro.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/main/resources/static/img/bg_api_visual.png b/src/main/resources/static/img/bg_api_visual.png deleted file mode 100644 index 977b21a..0000000 Binary files a/src/main/resources/static/img/bg_api_visual.png and /dev/null differ diff --git a/src/main/resources/static/img/bg_api_visual02.png b/src/main/resources/static/img/bg_api_visual02.png deleted file mode 100644 index b86071a..0000000 Binary files a/src/main/resources/static/img/bg_api_visual02.png and /dev/null differ diff --git a/src/main/resources/static/img/bg_api_visual03.png b/src/main/resources/static/img/bg_api_visual03.png deleted file mode 100644 index c741645..0000000 Binary files a/src/main/resources/static/img/bg_api_visual03.png and /dev/null differ diff --git a/src/main/resources/static/img/bg_main_api_usage.png b/src/main/resources/static/img/bg_main_api_usage.png new file mode 100644 index 0000000..42331d1 Binary files /dev/null and b/src/main/resources/static/img/bg_main_api_usage.png differ diff --git a/src/main/resources/static/img/bg_main_intersect.png b/src/main/resources/static/img/bg_main_intersect.png new file mode 100644 index 0000000..9c0348a Binary files /dev/null and b/src/main/resources/static/img/bg_main_intersect.png differ diff --git a/src/main/resources/static/img/bg_main_intersect.svg b/src/main/resources/static/img/bg_main_intersect.svg new file mode 100644 index 0000000..acd996c --- /dev/null +++ b/src/main/resources/static/img/bg_main_intersect.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/main/resources/static/img/bg_main_join.png b/src/main/resources/static/img/bg_main_join.png new file mode 100644 index 0000000..9778af7 Binary files /dev/null and b/src/main/resources/static/img/bg_main_join.png differ diff --git a/src/main/resources/static/img/bg_main_recommend_apis.png b/src/main/resources/static/img/bg_main_recommend_apis.png new file mode 100644 index 0000000..ae7d41a Binary files /dev/null and b/src/main/resources/static/img/bg_main_recommend_apis.png differ diff --git a/src/main/resources/static/img/bg_portal_info01.png b/src/main/resources/static/img/bg_portal_info01.png deleted file mode 100644 index 6558029..0000000 Binary files a/src/main/resources/static/img/bg_portal_info01.png and /dev/null differ diff --git a/src/main/resources/static/img/bg_portal_info02.png b/src/main/resources/static/img/bg_portal_info02.png deleted file mode 100644 index e0e8f68..0000000 Binary files a/src/main/resources/static/img/bg_portal_info02.png and /dev/null differ diff --git a/src/main/resources/static/img/bg_portal_info03.png b/src/main/resources/static/img/bg_portal_info03.png deleted file mode 100644 index e994230..0000000 Binary files a/src/main/resources/static/img/bg_portal_info03.png and /dev/null differ diff --git a/src/main/resources/static/img/bg_portal_info04.png b/src/main/resources/static/img/bg_portal_info04.png deleted file mode 100644 index 74d0eec..0000000 Binary files a/src/main/resources/static/img/bg_portal_info04.png and /dev/null differ diff --git a/src/main/resources/static/img/bg_portal_info05.png b/src/main/resources/static/img/bg_portal_info05.png deleted file mode 100644 index f79eee7..0000000 Binary files a/src/main/resources/static/img/bg_portal_info05.png and /dev/null differ diff --git a/src/main/resources/static/img/bg_portal_info06.png b/src/main/resources/static/img/bg_portal_info06.png deleted file mode 100644 index d41a21f..0000000 Binary files a/src/main/resources/static/img/bg_portal_info06.png and /dev/null differ diff --git a/src/main/resources/static/img/bg_portal_sinfo01.png b/src/main/resources/static/img/bg_portal_sinfo01.png deleted file mode 100644 index c09eedc..0000000 Binary files a/src/main/resources/static/img/bg_portal_sinfo01.png and /dev/null differ diff --git a/src/main/resources/static/img/bg_portal_sinfo02.png b/src/main/resources/static/img/bg_portal_sinfo02.png deleted file mode 100644 index 4141dbf..0000000 Binary files a/src/main/resources/static/img/bg_portal_sinfo02.png and /dev/null differ diff --git a/src/main/resources/static/img/bg_portal_sinfo03.png b/src/main/resources/static/img/bg_portal_sinfo03.png deleted file mode 100644 index da830fa..0000000 Binary files a/src/main/resources/static/img/bg_portal_sinfo03.png and /dev/null differ diff --git a/src/main/resources/static/img/bg_portal_sinfo04.png b/src/main/resources/static/img/bg_portal_sinfo04.png deleted file mode 100644 index 9c4f4b9..0000000 Binary files a/src/main/resources/static/img/bg_portal_sinfo04.png and /dev/null differ diff --git a/src/main/resources/static/img/bg_support_center.png b/src/main/resources/static/img/bg_support_center.png new file mode 100644 index 0000000..e36b03e Binary files /dev/null and b/src/main/resources/static/img/bg_support_center.png differ diff --git a/src/main/resources/static/img/btn_withdrawal.png b/src/main/resources/static/img/btn_withdrawal.png new file mode 100644 index 0000000..cf00c29 Binary files /dev/null and b/src/main/resources/static/img/btn_withdrawal.png differ diff --git a/src/main/resources/static/img/checkbox-checked.png b/src/main/resources/static/img/checkbox-checked.png new file mode 100644 index 0000000..468fc4f Binary files /dev/null and b/src/main/resources/static/img/checkbox-checked.png differ diff --git a/src/main/resources/static/img/checkbox-checked.svg b/src/main/resources/static/img/checkbox-checked.svg new file mode 100644 index 0000000..47ef9ad --- /dev/null +++ b/src/main/resources/static/img/checkbox-checked.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/main/resources/static/img/checkbox-unchecked.png b/src/main/resources/static/img/checkbox-unchecked.png new file mode 100644 index 0000000..2400ad1 Binary files /dev/null and b/src/main/resources/static/img/checkbox-unchecked.png differ diff --git a/src/main/resources/static/img/checkbox-unchecked.svg b/src/main/resources/static/img/checkbox-unchecked.svg new file mode 100644 index 0000000..28a2e74 --- /dev/null +++ b/src/main/resources/static/img/checkbox-unchecked.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/main/resources/static/img/error_3d.png b/src/main/resources/static/img/error_3d.png new file mode 100644 index 0000000..67ea2a6 Binary files /dev/null and b/src/main/resources/static/img/error_3d.png differ diff --git a/src/main/resources/static/img/hero_image_1.png b/src/main/resources/static/img/hero_image_1.png new file mode 100644 index 0000000..c0ec1c4 Binary files /dev/null and b/src/main/resources/static/img/hero_image_1.png differ diff --git a/src/main/resources/static/img/hero_image_2.png b/src/main/resources/static/img/hero_image_2.png new file mode 100644 index 0000000..ccf69bc Binary files /dev/null and b/src/main/resources/static/img/hero_image_2.png differ diff --git a/src/main/resources/static/img/hero_image_3.png b/src/main/resources/static/img/hero_image_3.png new file mode 100644 index 0000000..a400f48 Binary files /dev/null and b/src/main/resources/static/img/hero_image_3.png differ diff --git a/src/main/resources/static/img/icon/icon_api_info01.png b/src/main/resources/static/img/icon/icon_api_info01.png deleted file mode 100644 index c60ced6..0000000 Binary files a/src/main/resources/static/img/icon/icon_api_info01.png and /dev/null differ diff --git a/src/main/resources/static/img/icon/icon_api_info01_hover.png b/src/main/resources/static/img/icon/icon_api_info01_hover.png deleted file mode 100644 index e988419..0000000 Binary files a/src/main/resources/static/img/icon/icon_api_info01_hover.png and /dev/null differ diff --git a/src/main/resources/static/img/icon/icon_api_info02.png b/src/main/resources/static/img/icon/icon_api_info02.png deleted file mode 100644 index 1c70a58..0000000 Binary files a/src/main/resources/static/img/icon/icon_api_info02.png and /dev/null differ diff --git a/src/main/resources/static/img/icon/icon_api_info02_hover.png b/src/main/resources/static/img/icon/icon_api_info02_hover.png deleted file mode 100644 index 1673183..0000000 Binary files a/src/main/resources/static/img/icon/icon_api_info02_hover.png and /dev/null differ diff --git a/src/main/resources/static/img/icon/icon_api_info03.png b/src/main/resources/static/img/icon/icon_api_info03.png deleted file mode 100644 index 16a7a5a..0000000 Binary files a/src/main/resources/static/img/icon/icon_api_info03.png and /dev/null differ diff --git a/src/main/resources/static/img/icon/icon_api_info03_hover.png b/src/main/resources/static/img/icon/icon_api_info03_hover.png deleted file mode 100644 index d66f92c..0000000 Binary files a/src/main/resources/static/img/icon/icon_api_info03_hover.png and /dev/null differ diff --git a/src/main/resources/static/img/icon/icon_api_info04.png b/src/main/resources/static/img/icon/icon_api_info04.png deleted file mode 100644 index 9e96af4..0000000 Binary files a/src/main/resources/static/img/icon/icon_api_info04.png and /dev/null differ diff --git a/src/main/resources/static/img/icon/icon_api_info04_hover.png b/src/main/resources/static/img/icon/icon_api_info04_hover.png deleted file mode 100644 index 1b6235e..0000000 Binary files a/src/main/resources/static/img/icon/icon_api_info04_hover.png and /dev/null differ diff --git a/src/main/resources/static/img/icon/icon_api_info05.png b/src/main/resources/static/img/icon/icon_api_info05.png deleted file mode 100644 index a3ea4b0..0000000 Binary files a/src/main/resources/static/img/icon/icon_api_info05.png and /dev/null differ diff --git a/src/main/resources/static/img/icon/icon_api_info05_hover.png b/src/main/resources/static/img/icon/icon_api_info05_hover.png deleted file mode 100644 index c799c3e..0000000 Binary files a/src/main/resources/static/img/icon/icon_api_info05_hover.png and /dev/null differ diff --git a/src/main/resources/static/img/icon/icon_api_info06.png b/src/main/resources/static/img/icon/icon_api_info06.png deleted file mode 100644 index 938031e..0000000 Binary files a/src/main/resources/static/img/icon/icon_api_info06.png and /dev/null differ diff --git a/src/main/resources/static/img/icon/icon_api_info06_hover.png b/src/main/resources/static/img/icon/icon_api_info06_hover.png deleted file mode 100644 index d9a3229..0000000 Binary files a/src/main/resources/static/img/icon/icon_api_info06_hover.png and /dev/null differ diff --git a/src/main/resources/static/img/icon/icon_api_info07.png b/src/main/resources/static/img/icon/icon_api_info07.png deleted file mode 100644 index 624fa78..0000000 Binary files a/src/main/resources/static/img/icon/icon_api_info07.png and /dev/null differ diff --git a/src/main/resources/static/img/icon/icon_api_info07_hover.png b/src/main/resources/static/img/icon/icon_api_info07_hover.png deleted file mode 100644 index 209b235..0000000 Binary files a/src/main/resources/static/img/icon/icon_api_info07_hover.png and /dev/null differ diff --git a/src/main/resources/static/img/icon/icon_api_info08.png b/src/main/resources/static/img/icon/icon_api_info08.png deleted file mode 100644 index 187a263..0000000 Binary files a/src/main/resources/static/img/icon/icon_api_info08.png and /dev/null differ diff --git a/src/main/resources/static/img/icon/icon_api_info08_hover.png b/src/main/resources/static/img/icon/icon_api_info08_hover.png deleted file mode 100644 index eca41cd..0000000 Binary files a/src/main/resources/static/img/icon/icon_api_info08_hover.png and /dev/null differ diff --git a/src/main/resources/static/img/icon/icon_api_info09.png b/src/main/resources/static/img/icon/icon_api_info09.png deleted file mode 100644 index be29fe4..0000000 Binary files a/src/main/resources/static/img/icon/icon_api_info09.png and /dev/null differ diff --git a/src/main/resources/static/img/icon/icon_api_info09_hover.png b/src/main/resources/static/img/icon/icon_api_info09_hover.png deleted file mode 100644 index bccbc71..0000000 Binary files a/src/main/resources/static/img/icon/icon_api_info09_hover.png and /dev/null differ diff --git a/src/main/resources/static/img/icon/icon_api_info10.png b/src/main/resources/static/img/icon/icon_api_info10.png deleted file mode 100644 index 7ec850a..0000000 Binary files a/src/main/resources/static/img/icon/icon_api_info10.png and /dev/null differ diff --git a/src/main/resources/static/img/icon/icon_api_info10_hover.png b/src/main/resources/static/img/icon/icon_api_info10_hover.png deleted file mode 100644 index dca6918..0000000 Binary files a/src/main/resources/static/img/icon/icon_api_info10_hover.png and /dev/null differ diff --git a/src/main/resources/static/img/icon/icon_api_info11.png b/src/main/resources/static/img/icon/icon_api_info11.png deleted file mode 100644 index fee170d..0000000 Binary files a/src/main/resources/static/img/icon/icon_api_info11.png and /dev/null differ diff --git a/src/main/resources/static/img/icon/icon_api_info11_hover.png b/src/main/resources/static/img/icon/icon_api_info11_hover.png deleted file mode 100644 index 5d5af46..0000000 Binary files a/src/main/resources/static/img/icon/icon_api_info11_hover.png and /dev/null differ diff --git a/src/main/resources/static/img/icon/icon_api_info12.png b/src/main/resources/static/img/icon/icon_api_info12.png deleted file mode 100644 index 5fe7911..0000000 Binary files a/src/main/resources/static/img/icon/icon_api_info12.png and /dev/null differ diff --git a/src/main/resources/static/img/icon/icon_api_info12_hover.png b/src/main/resources/static/img/icon/icon_api_info12_hover.png deleted file mode 100644 index 1c514ad..0000000 Binary files a/src/main/resources/static/img/icon/icon_api_info12_hover.png and /dev/null differ diff --git a/src/main/resources/static/img/icon/icon_petals01.png b/src/main/resources/static/img/icon/icon_petals01.png deleted file mode 100644 index a596d35..0000000 Binary files a/src/main/resources/static/img/icon/icon_petals01.png and /dev/null differ diff --git a/src/main/resources/static/img/icon/icon_petals02.png b/src/main/resources/static/img/icon/icon_petals02.png deleted file mode 100644 index bb4fbf1..0000000 Binary files a/src/main/resources/static/img/icon/icon_petals02.png and /dev/null differ diff --git a/src/main/resources/static/img/icon/icon_petals03.png b/src/main/resources/static/img/icon/icon_petals03.png deleted file mode 100644 index b50d883..0000000 Binary files a/src/main/resources/static/img/icon/icon_petals03.png and /dev/null differ diff --git a/src/main/resources/static/img/icon/icon_portal_circle01.png b/src/main/resources/static/img/icon/icon_portal_circle01.png index 45a2ba7..60c14b2 100644 Binary files a/src/main/resources/static/img/icon/icon_portal_circle01.png and b/src/main/resources/static/img/icon/icon_portal_circle01.png differ diff --git a/src/main/resources/static/img/icon/icon_portal_circle02.png b/src/main/resources/static/img/icon/icon_portal_circle02.png index 47e0580..b877e83 100644 Binary files a/src/main/resources/static/img/icon/icon_portal_circle02.png and b/src/main/resources/static/img/icon/icon_portal_circle02.png differ diff --git a/src/main/resources/static/img/icon/icon_portal_circle03.png b/src/main/resources/static/img/icon/icon_portal_circle03.png index 4036f9e..f0a32bd 100644 Binary files a/src/main/resources/static/img/icon/icon_portal_circle03.png and b/src/main/resources/static/img/icon/icon_portal_circle03.png differ diff --git a/src/main/resources/static/img/icon_faq.png b/src/main/resources/static/img/icon_faq.png new file mode 100644 index 0000000..de506c4 Binary files /dev/null and b/src/main/resources/static/img/icon_faq.png differ diff --git a/src/main/resources/static/img/icon_notice.png b/src/main/resources/static/img/icon_notice.png new file mode 100644 index 0000000..5b180a0 Binary files /dev/null and b/src/main/resources/static/img/icon_notice.png differ diff --git a/src/main/resources/static/img/icon_qna.png b/src/main/resources/static/img/icon_qna.png new file mode 100644 index 0000000..61aebaf Binary files /dev/null and b/src/main/resources/static/img/icon_qna.png differ diff --git a/src/main/resources/static/img/img_api_intro.png b/src/main/resources/static/img/img_api_intro.png new file mode 100644 index 0000000..4a6593e Binary files /dev/null and b/src/main/resources/static/img/img_api_intro.png differ diff --git a/src/main/resources/static/img/img_app_complete.png b/src/main/resources/static/img/img_app_complete.png new file mode 100644 index 0000000..4efd0b7 Binary files /dev/null and b/src/main/resources/static/img/img_app_complete.png differ diff --git a/src/main/resources/static/img/img_con_tit.png b/src/main/resources/static/img/img_con_tit.png deleted file mode 100644 index 0079bed..0000000 Binary files a/src/main/resources/static/img/img_con_tit.png and /dev/null differ diff --git a/src/main/resources/static/img/img_con_tit2.png b/src/main/resources/static/img/img_con_tit2.png deleted file mode 100644 index 80d4fa1..0000000 Binary files a/src/main/resources/static/img/img_con_tit2.png and /dev/null differ diff --git a/src/main/resources/static/img/img_mainslick2_m.png b/src/main/resources/static/img/img_mainslick2_m.png deleted file mode 100644 index 0e5b291..0000000 Binary files a/src/main/resources/static/img/img_mainslick2_m.png and /dev/null differ diff --git a/src/main/resources/static/img/img_mainslick2_pc.png b/src/main/resources/static/img/img_mainslick2_pc.png deleted file mode 100644 index 38fa528..0000000 Binary files a/src/main/resources/static/img/img_mainslick2_pc.png and /dev/null differ diff --git a/src/main/resources/static/img/img_mainslick3_m.png b/src/main/resources/static/img/img_mainslick3_m.png deleted file mode 100644 index ac7980b..0000000 Binary files a/src/main/resources/static/img/img_mainslick3_m.png and /dev/null differ diff --git a/src/main/resources/static/img/img_mainslick3_pc.png b/src/main/resources/static/img/img_mainslick3_pc.png deleted file mode 100644 index c0e3f0a..0000000 Binary files a/src/main/resources/static/img/img_mainslick3_pc.png and /dev/null differ diff --git a/src/main/resources/static/img/img_mainslick_m.png b/src/main/resources/static/img/img_mainslick_m.png deleted file mode 100644 index c16e3eb..0000000 Binary files a/src/main/resources/static/img/img_mainslick_m.png and /dev/null differ diff --git a/src/main/resources/static/img/img_mainslick_pc.png b/src/main/resources/static/img/img_mainslick_pc.png deleted file mode 100644 index 5f85a35..0000000 Binary files a/src/main/resources/static/img/img_mainslick_pc.png and /dev/null differ diff --git a/src/main/resources/static/img/img_mask_group.png b/src/main/resources/static/img/img_mask_group.png deleted file mode 100644 index 6827368..0000000 Binary files a/src/main/resources/static/img/img_mask_group.png and /dev/null differ diff --git a/src/main/resources/static/img/img_mask_group2.png b/src/main/resources/static/img/img_mask_group2.png deleted file mode 100644 index 1bd5477..0000000 Binary files a/src/main/resources/static/img/img_mask_group2.png and /dev/null differ diff --git a/src/main/resources/static/img/img_nodata.png b/src/main/resources/static/img/img_nodata.png deleted file mode 100644 index 8ca6040..0000000 Binary files a/src/main/resources/static/img/img_nodata.png and /dev/null differ diff --git a/src/main/resources/static/img/img_nodata2.png b/src/main/resources/static/img/img_nodata2.png deleted file mode 100644 index 38052ba..0000000 Binary files a/src/main/resources/static/img/img_nodata2.png and /dev/null differ diff --git a/src/main/resources/static/img/img_search_character.png b/src/main/resources/static/img/img_search_character.png new file mode 100644 index 0000000..40eb171 Binary files /dev/null and b/src/main/resources/static/img/img_search_character.png differ diff --git a/src/main/resources/static/img/img_title_bg.png b/src/main/resources/static/img/img_title_bg.png new file mode 100644 index 0000000..af0fa1d Binary files /dev/null and b/src/main/resources/static/img/img_title_bg.png differ diff --git a/src/main/resources/static/img/logo/img_email_logo.png b/src/main/resources/static/img/logo/img_email_logo.png deleted file mode 100644 index 87d27d4..0000000 Binary files a/src/main/resources/static/img/logo/img_email_logo.png and /dev/null differ diff --git a/src/main/resources/static/img/logo/img_email_logo2.png b/src/main/resources/static/img/logo/img_email_logo2.png deleted file mode 100644 index 61664ef..0000000 Binary files a/src/main/resources/static/img/logo/img_email_logo2.png and /dev/null differ diff --git a/src/main/resources/static/img/logo/img_logo.png b/src/main/resources/static/img/logo/img_logo.png deleted file mode 100644 index ff3ff5d..0000000 Binary files a/src/main/resources/static/img/logo/img_logo.png and /dev/null differ diff --git a/src/main/resources/static/img/logo/img_logo2.png b/src/main/resources/static/img/logo/img_logo2.png deleted file mode 100644 index 87d27d4..0000000 Binary files a/src/main/resources/static/img/logo/img_logo2.png and /dev/null differ diff --git a/src/main/resources/static/img/logo/img_logo3.png b/src/main/resources/static/img/logo/img_logo3.png deleted file mode 100644 index 812bfd0..0000000 Binary files a/src/main/resources/static/img/logo/img_logo3.png and /dev/null differ diff --git a/src/main/resources/static/img/logo/img_logo4.png b/src/main/resources/static/img/logo/img_logo4.png deleted file mode 100644 index ff3ff5d..0000000 Binary files a/src/main/resources/static/img/logo/img_logo4.png and /dev/null differ diff --git a/src/main/resources/static/img/logo/img_logo5.png b/src/main/resources/static/img/logo/img_logo5.png deleted file mode 100644 index e10e5c7..0000000 Binary files a/src/main/resources/static/img/logo/img_logo5.png and /dev/null differ diff --git a/src/main/resources/static/img/logo/img_mlogo.png b/src/main/resources/static/img/logo/img_mlogo.png deleted file mode 100644 index f7888e8..0000000 Binary files a/src/main/resources/static/img/logo/img_mlogo.png and /dev/null differ diff --git a/src/main/resources/static/img/logo/img_mlogo2.png b/src/main/resources/static/img/logo/img_mlogo2.png deleted file mode 100644 index 647d99b..0000000 Binary files a/src/main/resources/static/img/logo/img_mlogo2.png and /dev/null differ diff --git a/src/main/resources/static/img/logo/logo.png b/src/main/resources/static/img/logo/logo.png index d84af84..a165d0c 100644 Binary files a/src/main/resources/static/img/logo/logo.png and b/src/main/resources/static/img/logo/logo.png differ diff --git a/src/main/resources/static/img/logo/logo_box.png b/src/main/resources/static/img/logo/logo_box.png new file mode 100644 index 0000000..7fd87f3 Binary files /dev/null and b/src/main/resources/static/img/logo/logo_box.png differ diff --git a/src/main/resources/static/img/m_keyvisual_img.png b/src/main/resources/static/img/m_keyvisual_img.png deleted file mode 100644 index 932bc15..0000000 Binary files a/src/main/resources/static/img/m_keyvisual_img.png and /dev/null differ diff --git a/src/main/resources/static/img/pc_keyvisual_img.png b/src/main/resources/static/img/pc_keyvisual_img.png deleted file mode 100644 index 490fe09..0000000 Binary files a/src/main/resources/static/img/pc_keyvisual_img.png and /dev/null differ diff --git a/src/main/resources/static/img/service/guide_1.jpg b/src/main/resources/static/img/service/guide_1.jpg new file mode 100644 index 0000000..b09d488 Binary files /dev/null and b/src/main/resources/static/img/service/guide_1.jpg differ diff --git a/src/main/resources/static/img/service/intro_1.jpg b/src/main/resources/static/img/service/intro_1.jpg new file mode 100644 index 0000000..5b94f74 Binary files /dev/null and b/src/main/resources/static/img/service/intro_1.jpg differ diff --git a/src/main/resources/static/img/service/intro_diagram.png b/src/main/resources/static/img/service/intro_diagram.png new file mode 100644 index 0000000..57f9f0f Binary files /dev/null and b/src/main/resources/static/img/service/intro_diagram.png differ diff --git a/src/main/resources/static/img/user_icon.svg b/src/main/resources/static/img/user_icon.svg new file mode 100644 index 0000000..c2ccbf1 --- /dev/null +++ b/src/main/resources/static/img/user_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/main/resources/static/img/user_icon_blue.svg b/src/main/resources/static/img/user_icon_blue.svg new file mode 100644 index 0000000..d030791 --- /dev/null +++ b/src/main/resources/static/img/user_icon_blue.svg @@ -0,0 +1,6 @@ + + + + diff --git a/src/main/resources/static/js/common.js b/src/main/resources/static/js/common.js index 1a2a3dd..425ae80 100644 --- a/src/main/resources/static/js/common.js +++ b/src/main/resources/static/js/common.js @@ -64,6 +64,86 @@ $(function(){ }); }); +/** + * 파일 검증 유틸리티 + * PORTAL_CONFIG.file 설정값 사용 + */ +const FileValidator = { + /** + * 파일 크기 검증 + * @param {File} file - 검증할 파일 + * @returns {boolean} 유효 여부 + */ + validateFileSize: function(file) { + const maxSizeBytes = window.PORTAL_CONFIG?.file?.maxSizeBytes || 8388608; + if (file.size > maxSizeBytes) { + const maxSize = window.PORTAL_CONFIG?.file?.maxSize || '8MB'; + customPopups.showAlert('파일 크기는 ' + maxSize + '를 초과할 수 없습니다.\n선택한 파일: ' + this.formatFileSize(file.size)); + return false; + } + return true; + }, + + /** + * 파일 확장자 검증 + * @param {File} file - 검증할 파일 + * @returns {boolean} 유효 여부 + */ + validateFileExtension: function(file) { + const allowedExtensions = (window.PORTAL_CONFIG?.file?.allowedExtensions || '').split(','); + const fileExt = file.name.split('.').pop().toLowerCase(); + if (!allowedExtensions.includes(fileExt)) { + customPopups.showAlert('허용되지 않는 파일 형식입니다.\n허용된 형식: ' + allowedExtensions.join(', ')); + return false; + } + return true; + }, + + /** + * 파일 전체 검증 (크기 + 확장자) + * @param {File} file - 검증할 파일 + * @returns {boolean} 유효 여부 + */ + validateFile: function(file) { + return this.validateFileExtension(file) && this.validateFileSize(file); + }, + + /** + * 여러 파일 검증 + * @param {FileList} files - 검증할 파일 목록 + * @returns {boolean} 모두 유효 여부 + */ + validateFiles: function(files) { + for (let i = 0; i < files.length; i++) { + if (!this.validateFile(files[i])) { + return false; + } + } + return true; + }, + + /** + * 바이트를 읽기 쉬운 형식으로 변환 + * @param {number} bytes + * @returns {string} + */ + formatFileSize: function(bytes) { + if (bytes === 0) return '0 Bytes'; + const k = 1024; + const sizes = ['Bytes', 'KB', 'MB', 'GB']; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; + }, + + /** + * 최대 허용 용량 반환 (표시용) + * @returns {string} + */ + getMaxSizeDisplay: function() { + return window.PORTAL_CONFIG?.file?.maxSize || '8MB'; + } +}; + class FormValidator { constructor(selector, validateFunction, message, extraValidation) { this.form = document.querySelector(selector); @@ -88,3 +168,204 @@ class FormValidator { } } } + +/** + * Summernote 에디터 공통 초기화 함수 + * - 이미지 붙여넣기 시 data-uri(Base64) 방식으로 처리 + * - 이미지 리사이징 지원 + * @param {string} selector - 에디터 selector (예: '#contents') + * @param {object} customOptions - 추가 옵션 (선택사항) + */ +function initSummernote(selector, customOptions) { + customOptions = customOptions || {}; + + // 기본 설정 + var defaultOptions = { + placeholder: customOptions.placeholder || '여기에 내용을 입력하세요.', + height: customOptions.height || 300, + lang: 'ko-KR', + toolbar: customOptions.toolbar || [ + ['style', ['style']], + ['font', ['bold', 'underline', 'clear']], + ['color', ['color']], + ['para', ['ul', 'ol', 'paragraph']], + ['table', ['table']], + ['insert', ['link', 'picture']], + ['view', ['fullscreen', 'codeview', 'help']] + ], + // 이미지 팝오버 설정 - 리사이징 지원 + popover: { + image: [ + ['resize', ['resizeFull', 'resizeHalf', 'resizeQuarter', 'resizeNone']], + ['float', ['floatLeft', 'floatRight', 'floatNone']], + ['remove', ['removeMedia']] + ] + }, + callbacks: { + onInit: function() { + var $editable = $(selector).next('.note-editor').find('.note-editable'); + $editable.addClass('editor-content'); + $editable.on('keydown', function(e) { + if (e.keyCode === 8) { + e.stopPropagation(); + } + }); + + // 커스텀 onInit 콜백 호출 + if (customOptions.callbacks && customOptions.callbacks.onInit) { + customOptions.callbacks.onInit.call(this); + } + }, + // 이미지 업로드 처리 - data-uri(Base64) 방식 + onImageUpload: function(files) { + for (var i = 0; i < files.length; i++) { + insertImageAsDataUri(files[i], $(selector)); + } + }, + // 붙여넣기 시 이미지 처리 (MS Office 등에서 복사한 이미지 포함) + onPaste: function(e) { + var clipboardData = e.originalEvent.clipboardData; + if (!clipboardData || !clipboardData.items) return; + + var items = clipboardData.items; + var imageFile = null; + + // 1. 먼저 순수 이미지 파일이 있는지 확인 + for (var i = 0; i < items.length; i++) { + if (items[i].type.indexOf('image') !== -1) { + imageFile = items[i].getAsFile(); + break; + } + } + + // 2. 이미지 파일이 있으면 data-uri로 변환하여 삽입 + if (imageFile) { + e.preventDefault(); + e.stopPropagation(); + insertImageAsDataUri(imageFile, $(selector)); + return; + } + + // 3. HTML 붙여넣기인 경우 (MS Office 등) - 이미지 태그 정리 + var htmlData = clipboardData.getData('text/html'); + if (htmlData && htmlData.indexOf(' maxSize) { + alert('이미지 크기는 5MB를 초과할 수 없습니다.'); + return; + } + + // 이미지 타입 확인 + if (!file.type.match(/image\/(jpeg|jpg|png|gif|bmp|webp)/i)) { + alert('지원하지 않는 이미지 형식입니다. (jpeg, png, gif, bmp, webp만 지원)'); + return; + } + + var reader = new FileReader(); + reader.onload = function(e) { + var dataUri = e.target.result; + $editor.summernote('insertImage', dataUri, function($image) { + // 이미지 스타일 설정 + $image.css({ + 'max-width': '100%', + 'height': 'auto' + }); + $image.attr('data-filename', file.name); + }); + }; + reader.onerror = function() { + alert('이미지 읽기에 실패했습니다.'); + }; + reader.readAsDataURL(file); +} + +/** + * 붙여넣기된 HTML 정리 (MS Office 등에서 복사한 내용) + * - 불필요한 Office 속성 제거 + * - 이미지 태그 정리 (alt, v:shapes 등 제거) + * @param {string} html - 원본 HTML + * @returns {string} 정리된 HTML + */ +function cleanPastedHtml(html) { + // 임시 div에서 HTML 파싱 + var $temp = $('
    ').html(html); + + // 이미지 태그 정리 + $temp.find('img').each(function() { + var $img = $(this); + var src = $img.attr('src'); + + // src가 없거나 file:// 프로토콜이면 제거 + if (!src || src.indexOf('file://') === 0) { + $img.remove(); + return; + } + + // 불필요한 속성 제거 (Office 관련) + var attributesToRemove = ['alt', 'v:shapes', 'o:title', 'style', 'class', 'id', 'name']; + attributesToRemove.forEach(function(attr) { + $img.removeAttr(attr); + }); + + // 깨끗한 스타일만 적용 + $img.css({ + 'max-width': '100%', + 'height': 'auto' + }); + }); + + // VML 태그 제거 (v:, o:, w: 등) + var cleanedHtml = $temp.html(); + cleanedHtml = cleanedHtml.replace(/]*>[\s\S]*?<\/v:[^>]*>/gi, ''); + cleanedHtml = cleanedHtml.replace(/]*>[\s\S]*?<\/o:[^>]*>/gi, ''); + cleanedHtml = cleanedHtml.replace(/]*>[\s\S]*?<\/w:[^>]*>/gi, ''); + cleanedHtml = cleanedHtml.replace(/]*>[\s\S]*?/gi, ''); + + // Office 네임스페이스 속성 제거 + cleanedHtml = cleanedHtml.replace(/\s*v:[a-z]+="[^"]*"/gi, ''); + cleanedHtml = cleanedHtml.replace(/\s*o:[a-z]+="[^"]*"/gi, ''); + + return cleanedHtml; +} diff --git a/src/main/resources/static/js/front2.js b/src/main/resources/static/js/front2.js index 645acf1..79dec57 100644 --- a/src/main/resources/static/js/front2.js +++ b/src/main/resources/static/js/front2.js @@ -297,16 +297,6 @@ const createCustomSelect = (targetElement, options = {}) => { }; }; -function activateSelect() { - const customSelects = document.querySelectorAll('.custom_select'); - customSelects.forEach(select => { - let selector = createCustomSelect(select); - console.log(selector); - }); -} - -/* 디자인 select box */ -// document.addEventListener('DOMContentLoaded',activateSelect); /* footer */ function adjustLayout() { diff --git a/src/main/resources/static/js/json-lint.js b/src/main/resources/static/js/json-lint.js index ebc3cc3..0f37911 100644 --- a/src/main/resources/static/js/json-lint.js +++ b/src/main/resources/static/js/json-lint.js @@ -415,18 +415,4 @@ var jsonlint = (function(){ if (typeof require !== 'undefined' && typeof exports !== 'undefined') { exports.parser = jsonlint; exports.parse = function () { return jsonlint.parse.apply(jsonlint, arguments); } - exports.main = function commonjsMain(args) { - if (!args[1]) - throw new Error('Usage: '+args[0]+' FILE'); - if (typeof process !== 'undefined') { - var source = require('fs').readFileSync(require('path').join(process.cwd(), args[1]), "utf8"); - } else { - var cwd = require("file").path(require("file").cwd()); - var source = cwd.join(args[1]).read({charset: "utf-8"}); - } - return exports.parser.parse(source); - } - if (typeof module !== 'undefined' && require.main === module) { - exports.main(typeof process !== 'undefined' ? process.argv.slice(1) : require("system").args); - } } \ No newline at end of file diff --git a/src/main/resources/static/js/lodash.js b/src/main/resources/static/js/lodash.js index c8b3569..4131e93 100644 --- a/src/main/resources/static/js/lodash.js +++ b/src/main/resources/static/js/lodash.js @@ -8,17105 +8,17202 @@ */ ;(function() { - /** Used as a safe reference for `undefined` in pre-ES5 environments. */ - var undefined; + /** Used as a safe reference for `undefined` in pre-ES5 environments. */ + var undefined; - /** Used as the semantic version number. */ - var VERSION = '4.17.15'; + /** Used as the semantic version number. */ + var VERSION = '4.17.21'; - /** Used as the size to enable large array optimizations. */ - var LARGE_ARRAY_SIZE = 200; + /** Used as the size to enable large array optimizations. */ + var LARGE_ARRAY_SIZE = 200; - /** Error message constants. */ - var CORE_ERROR_TEXT = 'Unsupported core-js use. Try https://npms.io/search?q=ponyfill.', - FUNC_ERROR_TEXT = 'Expected a function'; + /** Error message constants. */ + var CORE_ERROR_TEXT = 'Unsupported core-js use. Try https://npms.io/search?q=ponyfill.', + FUNC_ERROR_TEXT = 'Expected a function', + INVALID_TEMPL_VAR_ERROR_TEXT = 'Invalid `variable` option passed into `_.template`'; - /** Used to stand-in for `undefined` hash values. */ - var HASH_UNDEFINED = '__lodash_hash_undefined__'; + /** Used to stand-in for `undefined` hash values. */ + var HASH_UNDEFINED = '__lodash_hash_undefined__'; - /** Used as the maximum memoize cache size. */ - var MAX_MEMOIZE_SIZE = 500; + /** Used as the maximum memoize cache size. */ + var MAX_MEMOIZE_SIZE = 500; - /** Used as the internal argument placeholder. */ - var PLACEHOLDER = '__lodash_placeholder__'; + /** Used as the internal argument placeholder. */ + var PLACEHOLDER = '__lodash_placeholder__'; - /** Used to compose bitmasks for cloning. */ - var CLONE_DEEP_FLAG = 1, - CLONE_FLAT_FLAG = 2, - CLONE_SYMBOLS_FLAG = 4; + /** Used to compose bitmasks for cloning. */ + var CLONE_DEEP_FLAG = 1, + CLONE_FLAT_FLAG = 2, + CLONE_SYMBOLS_FLAG = 4; - /** Used to compose bitmasks for value comparisons. */ - var COMPARE_PARTIAL_FLAG = 1, - COMPARE_UNORDERED_FLAG = 2; + /** Used to compose bitmasks for value comparisons. */ + var COMPARE_PARTIAL_FLAG = 1, + COMPARE_UNORDERED_FLAG = 2; - /** Used to compose bitmasks for function metadata. */ - var WRAP_BIND_FLAG = 1, - WRAP_BIND_KEY_FLAG = 2, - WRAP_CURRY_BOUND_FLAG = 4, - WRAP_CURRY_FLAG = 8, - WRAP_CURRY_RIGHT_FLAG = 16, - WRAP_PARTIAL_FLAG = 32, - WRAP_PARTIAL_RIGHT_FLAG = 64, - WRAP_ARY_FLAG = 128, - WRAP_REARG_FLAG = 256, - WRAP_FLIP_FLAG = 512; + /** Used to compose bitmasks for function metadata. */ + var WRAP_BIND_FLAG = 1, + WRAP_BIND_KEY_FLAG = 2, + WRAP_CURRY_BOUND_FLAG = 4, + WRAP_CURRY_FLAG = 8, + WRAP_CURRY_RIGHT_FLAG = 16, + WRAP_PARTIAL_FLAG = 32, + WRAP_PARTIAL_RIGHT_FLAG = 64, + WRAP_ARY_FLAG = 128, + WRAP_REARG_FLAG = 256, + WRAP_FLIP_FLAG = 512; - /** Used as default options for `_.truncate`. */ - var DEFAULT_TRUNC_LENGTH = 30, - DEFAULT_TRUNC_OMISSION = '...'; + /** Used as default options for `_.truncate`. */ + var DEFAULT_TRUNC_LENGTH = 30, + DEFAULT_TRUNC_OMISSION = '...'; - /** Used to detect hot functions by number of calls within a span of milliseconds. */ - var HOT_COUNT = 800, - HOT_SPAN = 16; + /** Used to detect hot functions by number of calls within a span of milliseconds. */ + var HOT_COUNT = 800, + HOT_SPAN = 16; - /** Used to indicate the type of lazy iteratees. */ - var LAZY_FILTER_FLAG = 1, - LAZY_MAP_FLAG = 2, - LAZY_WHILE_FLAG = 3; + /** Used to indicate the type of lazy iteratees. */ + var LAZY_FILTER_FLAG = 1, + LAZY_MAP_FLAG = 2, + LAZY_WHILE_FLAG = 3; - /** Used as references for various `Number` constants. */ - var INFINITY = 1 / 0, - MAX_SAFE_INTEGER = 9007199254740991, - MAX_INTEGER = 1.7976931348623157e+308, - NAN = 0 / 0; + /** Used as references for various `Number` constants. */ + var INFINITY = 1 / 0, + MAX_SAFE_INTEGER = 9007199254740991, + MAX_INTEGER = 1.7976931348623157e+308, + NAN = 0 / 0; - /** Used as references for the maximum length and index of an array. */ - var MAX_ARRAY_LENGTH = 4294967295, - MAX_ARRAY_INDEX = MAX_ARRAY_LENGTH - 1, - HALF_MAX_ARRAY_LENGTH = MAX_ARRAY_LENGTH >>> 1; + /** Used as references for the maximum length and index of an array. */ + var MAX_ARRAY_LENGTH = 4294967295, + MAX_ARRAY_INDEX = MAX_ARRAY_LENGTH - 1, + HALF_MAX_ARRAY_LENGTH = MAX_ARRAY_LENGTH >>> 1; - /** Used to associate wrap methods with their bit flags. */ - var wrapFlags = [ - ['ary', WRAP_ARY_FLAG], - ['bind', WRAP_BIND_FLAG], - ['bindKey', WRAP_BIND_KEY_FLAG], - ['curry', WRAP_CURRY_FLAG], - ['curryRight', WRAP_CURRY_RIGHT_FLAG], - ['flip', WRAP_FLIP_FLAG], - ['partial', WRAP_PARTIAL_FLAG], - ['partialRight', WRAP_PARTIAL_RIGHT_FLAG], - ['rearg', WRAP_REARG_FLAG] - ]; + /** Used to associate wrap methods with their bit flags. */ + var wrapFlags = [ + ['ary', WRAP_ARY_FLAG], + ['bind', WRAP_BIND_FLAG], + ['bindKey', WRAP_BIND_KEY_FLAG], + ['curry', WRAP_CURRY_FLAG], + ['curryRight', WRAP_CURRY_RIGHT_FLAG], + ['flip', WRAP_FLIP_FLAG], + ['partial', WRAP_PARTIAL_FLAG], + ['partialRight', WRAP_PARTIAL_RIGHT_FLAG], + ['rearg', WRAP_REARG_FLAG] + ]; - /** `Object#toString` result references. */ - var argsTag = '[object Arguments]', - arrayTag = '[object Array]', - asyncTag = '[object AsyncFunction]', - boolTag = '[object Boolean]', - dateTag = '[object Date]', - domExcTag = '[object DOMException]', - errorTag = '[object Error]', - funcTag = '[object Function]', - genTag = '[object GeneratorFunction]', - mapTag = '[object Map]', - numberTag = '[object Number]', - nullTag = '[object Null]', - objectTag = '[object Object]', - promiseTag = '[object Promise]', - proxyTag = '[object Proxy]', - regexpTag = '[object RegExp]', - setTag = '[object Set]', - stringTag = '[object String]', - symbolTag = '[object Symbol]', - undefinedTag = '[object Undefined]', - weakMapTag = '[object WeakMap]', - weakSetTag = '[object WeakSet]'; + /** `Object#toString` result references. */ + var argsTag = '[object Arguments]', + arrayTag = '[object Array]', + asyncTag = '[object AsyncFunction]', + boolTag = '[object Boolean]', + dateTag = '[object Date]', + domExcTag = '[object DOMException]', + errorTag = '[object Error]', + funcTag = '[object Function]', + genTag = '[object GeneratorFunction]', + mapTag = '[object Map]', + numberTag = '[object Number]', + nullTag = '[object Null]', + objectTag = '[object Object]', + promiseTag = '[object Promise]', + proxyTag = '[object Proxy]', + regexpTag = '[object RegExp]', + setTag = '[object Set]', + stringTag = '[object String]', + symbolTag = '[object Symbol]', + undefinedTag = '[object Undefined]', + weakMapTag = '[object WeakMap]', + weakSetTag = '[object WeakSet]'; - var arrayBufferTag = '[object ArrayBuffer]', - dataViewTag = '[object DataView]', - float32Tag = '[object Float32Array]', - float64Tag = '[object Float64Array]', - int8Tag = '[object Int8Array]', - int16Tag = '[object Int16Array]', - int32Tag = '[object Int32Array]', - uint8Tag = '[object Uint8Array]', - uint8ClampedTag = '[object Uint8ClampedArray]', - uint16Tag = '[object Uint16Array]', - uint32Tag = '[object Uint32Array]'; + var arrayBufferTag = '[object ArrayBuffer]', + dataViewTag = '[object DataView]', + float32Tag = '[object Float32Array]', + float64Tag = '[object Float64Array]', + int8Tag = '[object Int8Array]', + int16Tag = '[object Int16Array]', + int32Tag = '[object Int32Array]', + uint8Tag = '[object Uint8Array]', + uint8ClampedTag = '[object Uint8ClampedArray]', + uint16Tag = '[object Uint16Array]', + uint32Tag = '[object Uint32Array]'; - /** Used to match empty string literals in compiled template source. */ - var reEmptyStringLeading = /\b__p \+= '';/g, - reEmptyStringMiddle = /\b(__p \+=) '' \+/g, - reEmptyStringTrailing = /(__e\(.*?\)|\b__t\)) \+\n'';/g; + /** Used to match empty string literals in compiled template source. */ + var reEmptyStringLeading = /\b__p \+= '';/g, + reEmptyStringMiddle = /\b(__p \+=) '' \+/g, + reEmptyStringTrailing = /(__e\(.*?\)|\b__t\)) \+\n'';/g; - /** Used to match HTML entities and HTML characters. */ - var reEscapedHtml = /&(?:amp|lt|gt|quot|#39);/g, - reUnescapedHtml = /[&<>"']/g, - reHasEscapedHtml = RegExp(reEscapedHtml.source), - reHasUnescapedHtml = RegExp(reUnescapedHtml.source); + /** Used to match HTML entities and HTML characters. */ + var reEscapedHtml = /&(?:amp|lt|gt|quot|#39);/g, + reUnescapedHtml = /[&<>"']/g, + reHasEscapedHtml = RegExp(reEscapedHtml.source), + reHasUnescapedHtml = RegExp(reUnescapedHtml.source); - /** Used to match template delimiters. */ - var reEscape = /<%-([\s\S]+?)%>/g, - reEvaluate = /<%([\s\S]+?)%>/g, - reInterpolate = /<%=([\s\S]+?)%>/g; + /** Used to match template delimiters. */ + var reEscape = /<%-([\s\S]+?)%>/g, + reEvaluate = /<%([\s\S]+?)%>/g, + reInterpolate = /<%=([\s\S]+?)%>/g; - /** Used to match property names within property paths. */ - var reIsDeepProp = /\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/, - reIsPlainProp = /^\w*$/, - rePropName = /[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g; + /** Used to match property names within property paths. */ + var reIsDeepProp = /\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/, + reIsPlainProp = /^\w*$/, + rePropName = /[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g; - /** - * Used to match `RegExp` - * [syntax characters](http://ecma-international.org/ecma-262/7.0/#sec-patterns). - */ - var reRegExpChar = /[\\^$.*+?()[\]{}|]/g, - reHasRegExpChar = RegExp(reRegExpChar.source); + /** + * Used to match `RegExp` + * [syntax characters](http://ecma-international.org/ecma-262/7.0/#sec-patterns). + */ + var reRegExpChar = /[\\^$.*+?()[\]{}|]/g, + reHasRegExpChar = RegExp(reRegExpChar.source); - /** Used to match leading and trailing whitespace. */ - var reTrim = /^\s+|\s+$/g, - reTrimStart = /^\s+/, - reTrimEnd = /\s+$/; + /** Used to match leading whitespace. */ + var reTrimStart = /^\s+/; - /** Used to match wrap detail comments. */ - var reWrapComment = /\{(?:\n\/\* \[wrapped with .+\] \*\/)?\n?/, - reWrapDetails = /\{\n\/\* \[wrapped with (.+)\] \*/, - reSplitDetails = /,? & /; + /** Used to match a single whitespace character. */ + var reWhitespace = /\s/; - /** Used to match words composed of alphanumeric characters. */ - var reAsciiWord = /[^\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]+/g; + /** Used to match wrap detail comments. */ + var reWrapComment = /\{(?:\n\/\* \[wrapped with .+\] \*\/)?\n?/, + reWrapDetails = /\{\n\/\* \[wrapped with (.+)\] \*/, + reSplitDetails = /,? & /; - /** Used to match backslashes in property paths. */ - var reEscapeChar = /\\(\\)?/g; + /** Used to match words composed of alphanumeric characters. */ + var reAsciiWord = /[^\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]+/g; - /** - * Used to match - * [ES template delimiters](http://ecma-international.org/ecma-262/7.0/#sec-template-literal-lexical-components). - */ - var reEsTemplate = /\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g; + /** + * Used to validate the `validate` option in `_.template` variable. + * + * Forbids characters which could potentially change the meaning of the function argument definition: + * - "()," (modification of function parameters) + * - "=" (default value) + * - "[]{}" (destructuring of function parameters) + * - "/" (beginning of a comment) + * - whitespace + */ + var reForbiddenIdentifierChars = /[()=,{}\[\]\/\s]/; - /** Used to match `RegExp` flags from their coerced string values. */ - var reFlags = /\w*$/; + /** Used to match backslashes in property paths. */ + var reEscapeChar = /\\(\\)?/g; - /** Used to detect bad signed hexadecimal string values. */ - var reIsBadHex = /^[-+]0x[0-9a-f]+$/i; + /** + * Used to match + * [ES template delimiters](http://ecma-international.org/ecma-262/7.0/#sec-template-literal-lexical-components). + */ + var reEsTemplate = /\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g; - /** Used to detect binary string values. */ - var reIsBinary = /^0b[01]+$/i; + /** Used to match `RegExp` flags from their coerced string values. */ + var reFlags = /\w*$/; - /** Used to detect host constructors (Safari). */ - var reIsHostCtor = /^\[object .+?Constructor\]$/; + /** Used to detect bad signed hexadecimal string values. */ + var reIsBadHex = /^[-+]0x[0-9a-f]+$/i; - /** Used to detect octal string values. */ - var reIsOctal = /^0o[0-7]+$/i; + /** Used to detect binary string values. */ + var reIsBinary = /^0b[01]+$/i; - /** Used to detect unsigned integer values. */ - var reIsUint = /^(?:0|[1-9]\d*)$/; + /** Used to detect host constructors (Safari). */ + var reIsHostCtor = /^\[object .+?Constructor\]$/; - /** Used to match Latin Unicode letters (excluding mathematical operators). */ - var reLatin = /[\xc0-\xd6\xd8-\xf6\xf8-\xff\u0100-\u017f]/g; + /** Used to detect octal string values. */ + var reIsOctal = /^0o[0-7]+$/i; - /** Used to ensure capturing order of template delimiters. */ - var reNoMatch = /($^)/; + /** Used to detect unsigned integer values. */ + var reIsUint = /^(?:0|[1-9]\d*)$/; - /** Used to match unescaped characters in compiled string literals. */ - var reUnescapedString = /['\n\r\u2028\u2029\\]/g; + /** Used to match Latin Unicode letters (excluding mathematical operators). */ + var reLatin = /[\xc0-\xd6\xd8-\xf6\xf8-\xff\u0100-\u017f]/g; - /** Used to compose unicode character classes. */ - var rsAstralRange = '\\ud800-\\udfff', - rsComboMarksRange = '\\u0300-\\u036f', - reComboHalfMarksRange = '\\ufe20-\\ufe2f', - rsComboSymbolsRange = '\\u20d0-\\u20ff', - rsComboRange = rsComboMarksRange + reComboHalfMarksRange + rsComboSymbolsRange, - rsDingbatRange = '\\u2700-\\u27bf', - rsLowerRange = 'a-z\\xdf-\\xf6\\xf8-\\xff', - rsMathOpRange = '\\xac\\xb1\\xd7\\xf7', - rsNonCharRange = '\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf', - rsPunctuationRange = '\\u2000-\\u206f', - rsSpaceRange = ' \\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', - rsUpperRange = 'A-Z\\xc0-\\xd6\\xd8-\\xde', - rsVarRange = '\\ufe0e\\ufe0f', - rsBreakRange = rsMathOpRange + rsNonCharRange + rsPunctuationRange + rsSpaceRange; + /** Used to ensure capturing order of template delimiters. */ + var reNoMatch = /($^)/; - /** Used to compose unicode capture groups. */ - var rsApos = "['\u2019]", - rsAstral = '[' + rsAstralRange + ']', - rsBreak = '[' + rsBreakRange + ']', - rsCombo = '[' + rsComboRange + ']', - rsDigits = '\\d+', - rsDingbat = '[' + rsDingbatRange + ']', - rsLower = '[' + rsLowerRange + ']', - rsMisc = '[^' + rsAstralRange + rsBreakRange + rsDigits + rsDingbatRange + rsLowerRange + rsUpperRange + ']', - rsFitz = '\\ud83c[\\udffb-\\udfff]', - rsModifier = '(?:' + rsCombo + '|' + rsFitz + ')', - rsNonAstral = '[^' + rsAstralRange + ']', - rsRegional = '(?:\\ud83c[\\udde6-\\uddff]){2}', - rsSurrPair = '[\\ud800-\\udbff][\\udc00-\\udfff]', - rsUpper = '[' + rsUpperRange + ']', - rsZWJ = '\\u200d'; + /** Used to match unescaped characters in compiled string literals. */ + var reUnescapedString = /['\n\r\u2028\u2029\\]/g; - /** Used to compose unicode regexes. */ - var rsMiscLower = '(?:' + rsLower + '|' + rsMisc + ')', - rsMiscUpper = '(?:' + rsUpper + '|' + rsMisc + ')', - rsOptContrLower = '(?:' + rsApos + '(?:d|ll|m|re|s|t|ve))?', - rsOptContrUpper = '(?:' + rsApos + '(?:D|LL|M|RE|S|T|VE))?', - reOptMod = rsModifier + '?', - rsOptVar = '[' + rsVarRange + ']?', - rsOptJoin = '(?:' + rsZWJ + '(?:' + [rsNonAstral, rsRegional, rsSurrPair].join('|') + ')' + rsOptVar + reOptMod + ')*', - rsOrdLower = '\\d*(?:1st|2nd|3rd|(?![123])\\dth)(?=\\b|[A-Z_])', - rsOrdUpper = '\\d*(?:1ST|2ND|3RD|(?![123])\\dTH)(?=\\b|[a-z_])', - rsSeq = rsOptVar + reOptMod + rsOptJoin, - rsEmoji = '(?:' + [rsDingbat, rsRegional, rsSurrPair].join('|') + ')' + rsSeq, - rsSymbol = '(?:' + [rsNonAstral + rsCombo + '?', rsCombo, rsRegional, rsSurrPair, rsAstral].join('|') + ')'; + /** Used to compose unicode character classes. */ + var rsAstralRange = '\\ud800-\\udfff', + rsComboMarksRange = '\\u0300-\\u036f', + reComboHalfMarksRange = '\\ufe20-\\ufe2f', + rsComboSymbolsRange = '\\u20d0-\\u20ff', + rsComboRange = rsComboMarksRange + reComboHalfMarksRange + rsComboSymbolsRange, + rsDingbatRange = '\\u2700-\\u27bf', + rsLowerRange = 'a-z\\xdf-\\xf6\\xf8-\\xff', + rsMathOpRange = '\\xac\\xb1\\xd7\\xf7', + rsNonCharRange = '\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf', + rsPunctuationRange = '\\u2000-\\u206f', + rsSpaceRange = ' \\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', + rsUpperRange = 'A-Z\\xc0-\\xd6\\xd8-\\xde', + rsVarRange = '\\ufe0e\\ufe0f', + rsBreakRange = rsMathOpRange + rsNonCharRange + rsPunctuationRange + rsSpaceRange; - /** Used to match apostrophes. */ - var reApos = RegExp(rsApos, 'g'); + /** Used to compose unicode capture groups. */ + var rsApos = "['\u2019]", + rsAstral = '[' + rsAstralRange + ']', + rsBreak = '[' + rsBreakRange + ']', + rsCombo = '[' + rsComboRange + ']', + rsDigits = '\\d+', + rsDingbat = '[' + rsDingbatRange + ']', + rsLower = '[' + rsLowerRange + ']', + rsMisc = '[^' + rsAstralRange + rsBreakRange + rsDigits + rsDingbatRange + rsLowerRange + rsUpperRange + ']', + rsFitz = '\\ud83c[\\udffb-\\udfff]', + rsModifier = '(?:' + rsCombo + '|' + rsFitz + ')', + rsNonAstral = '[^' + rsAstralRange + ']', + rsRegional = '(?:\\ud83c[\\udde6-\\uddff]){2}', + rsSurrPair = '[\\ud800-\\udbff][\\udc00-\\udfff]', + rsUpper = '[' + rsUpperRange + ']', + rsZWJ = '\\u200d'; - /** - * Used to match [combining diacritical marks](https://en.wikipedia.org/wiki/Combining_Diacritical_Marks) and - * [combining diacritical marks for symbols](https://en.wikipedia.org/wiki/Combining_Diacritical_Marks_for_Symbols). - */ - var reComboMark = RegExp(rsCombo, 'g'); + /** Used to compose unicode regexes. */ + var rsMiscLower = '(?:' + rsLower + '|' + rsMisc + ')', + rsMiscUpper = '(?:' + rsUpper + '|' + rsMisc + ')', + rsOptContrLower = '(?:' + rsApos + '(?:d|ll|m|re|s|t|ve))?', + rsOptContrUpper = '(?:' + rsApos + '(?:D|LL|M|RE|S|T|VE))?', + reOptMod = rsModifier + '?', + rsOptVar = '[' + rsVarRange + ']?', + rsOptJoin = '(?:' + rsZWJ + '(?:' + [rsNonAstral, rsRegional, rsSurrPair].join('|') + ')' + rsOptVar + reOptMod + ')*', + rsOrdLower = '\\d*(?:1st|2nd|3rd|(?![123])\\dth)(?=\\b|[A-Z_])', + rsOrdUpper = '\\d*(?:1ST|2ND|3RD|(?![123])\\dTH)(?=\\b|[a-z_])', + rsSeq = rsOptVar + reOptMod + rsOptJoin, + rsEmoji = '(?:' + [rsDingbat, rsRegional, rsSurrPair].join('|') + ')' + rsSeq, + rsSymbol = '(?:' + [rsNonAstral + rsCombo + '?', rsCombo, rsRegional, rsSurrPair, rsAstral].join('|') + ')'; - /** Used to match [string symbols](https://mathiasbynens.be/notes/javascript-unicode). */ - var reUnicode = RegExp(rsFitz + '(?=' + rsFitz + ')|' + rsSymbol + rsSeq, 'g'); + /** Used to match apostrophes. */ + var reApos = RegExp(rsApos, 'g'); - /** Used to match complex or compound words. */ - var reUnicodeWord = RegExp([ - rsUpper + '?' + rsLower + '+' + rsOptContrLower + '(?=' + [rsBreak, rsUpper, '$'].join('|') + ')', - rsMiscUpper + '+' + rsOptContrUpper + '(?=' + [rsBreak, rsUpper + rsMiscLower, '$'].join('|') + ')', - rsUpper + '?' + rsMiscLower + '+' + rsOptContrLower, - rsUpper + '+' + rsOptContrUpper, - rsOrdUpper, - rsOrdLower, - rsDigits, - rsEmoji - ].join('|'), 'g'); + /** + * Used to match [combining diacritical marks](https://en.wikipedia.org/wiki/Combining_Diacritical_Marks) and + * [combining diacritical marks for symbols](https://en.wikipedia.org/wiki/Combining_Diacritical_Marks_for_Symbols). + */ + var reComboMark = RegExp(rsCombo, 'g'); - /** Used to detect strings with [zero-width joiners or code points from the astral planes](http://eev.ee/blog/2015/09/12/dark-corners-of-unicode/). */ - var reHasUnicode = RegExp('[' + rsZWJ + rsAstralRange + rsComboRange + rsVarRange + ']'); + /** Used to match [string symbols](https://mathiasbynens.be/notes/javascript-unicode). */ + var reUnicode = RegExp(rsFitz + '(?=' + rsFitz + ')|' + rsSymbol + rsSeq, 'g'); - /** Used to detect strings that need a more robust regexp to match words. */ - var reHasUnicodeWord = /[a-z][A-Z]|[A-Z]{2}[a-z]|[0-9][a-zA-Z]|[a-zA-Z][0-9]|[^a-zA-Z0-9 ]/; + /** Used to match complex or compound words. */ + var reUnicodeWord = RegExp([ + rsUpper + '?' + rsLower + '+' + rsOptContrLower + '(?=' + [rsBreak, rsUpper, '$'].join('|') + ')', + rsMiscUpper + '+' + rsOptContrUpper + '(?=' + [rsBreak, rsUpper + rsMiscLower, '$'].join('|') + ')', + rsUpper + '?' + rsMiscLower + '+' + rsOptContrLower, + rsUpper + '+' + rsOptContrUpper, + rsOrdUpper, + rsOrdLower, + rsDigits, + rsEmoji + ].join('|'), 'g'); - /** Used to assign default `context` object properties. */ - var contextProps = [ - 'Array', 'Buffer', 'DataView', 'Date', 'Error', 'Float32Array', 'Float64Array', - 'Function', 'Int8Array', 'Int16Array', 'Int32Array', 'Map', 'Math', 'Object', - 'Promise', 'RegExp', 'Set', 'String', 'Symbol', 'TypeError', 'Uint8Array', - 'Uint8ClampedArray', 'Uint16Array', 'Uint32Array', 'WeakMap', - '_', 'clearTimeout', 'isFinite', 'parseInt', 'setTimeout' - ]; + /** Used to detect strings with [zero-width joiners or code points from the astral planes](http://eev.ee/blog/2015/09/12/dark-corners-of-unicode/). */ + var reHasUnicode = RegExp('[' + rsZWJ + rsAstralRange + rsComboRange + rsVarRange + ']'); - /** Used to make template sourceURLs easier to identify. */ - var templateCounter = -1; + /** Used to detect strings that need a more robust regexp to match words. */ + var reHasUnicodeWord = /[a-z][A-Z]|[A-Z]{2}[a-z]|[0-9][a-zA-Z]|[a-zA-Z][0-9]|[^a-zA-Z0-9 ]/; - /** Used to identify `toStringTag` values of typed arrays. */ - var typedArrayTags = {}; - typedArrayTags[float32Tag] = typedArrayTags[float64Tag] = - typedArrayTags[int8Tag] = typedArrayTags[int16Tag] = - typedArrayTags[int32Tag] = typedArrayTags[uint8Tag] = - typedArrayTags[uint8ClampedTag] = typedArrayTags[uint16Tag] = - typedArrayTags[uint32Tag] = true; - typedArrayTags[argsTag] = typedArrayTags[arrayTag] = - typedArrayTags[arrayBufferTag] = typedArrayTags[boolTag] = - typedArrayTags[dataViewTag] = typedArrayTags[dateTag] = - typedArrayTags[errorTag] = typedArrayTags[funcTag] = - typedArrayTags[mapTag] = typedArrayTags[numberTag] = - typedArrayTags[objectTag] = typedArrayTags[regexpTag] = - typedArrayTags[setTag] = typedArrayTags[stringTag] = - typedArrayTags[weakMapTag] = false; + /** Used to assign default `context` object properties. */ + var contextProps = [ + 'Array', 'Buffer', 'DataView', 'Date', 'Error', 'Float32Array', 'Float64Array', + 'Function', 'Int8Array', 'Int16Array', 'Int32Array', 'Map', 'Math', 'Object', + 'Promise', 'RegExp', 'Set', 'String', 'Symbol', 'TypeError', 'Uint8Array', + 'Uint8ClampedArray', 'Uint16Array', 'Uint32Array', 'WeakMap', + '_', 'clearTimeout', 'isFinite', 'parseInt', 'setTimeout' + ]; - /** Used to identify `toStringTag` values supported by `_.clone`. */ - var cloneableTags = {}; - cloneableTags[argsTag] = cloneableTags[arrayTag] = - cloneableTags[arrayBufferTag] = cloneableTags[dataViewTag] = - cloneableTags[boolTag] = cloneableTags[dateTag] = - cloneableTags[float32Tag] = cloneableTags[float64Tag] = - cloneableTags[int8Tag] = cloneableTags[int16Tag] = - cloneableTags[int32Tag] = cloneableTags[mapTag] = - cloneableTags[numberTag] = cloneableTags[objectTag] = - cloneableTags[regexpTag] = cloneableTags[setTag] = - cloneableTags[stringTag] = cloneableTags[symbolTag] = - cloneableTags[uint8Tag] = cloneableTags[uint8ClampedTag] = - cloneableTags[uint16Tag] = cloneableTags[uint32Tag] = true; - cloneableTags[errorTag] = cloneableTags[funcTag] = - cloneableTags[weakMapTag] = false; + /** Used to make template sourceURLs easier to identify. */ + var templateCounter = -1; - /** Used to map Latin Unicode letters to basic Latin letters. */ - var deburredLetters = { - // Latin-1 Supplement block. - '\xc0': 'A', '\xc1': 'A', '\xc2': 'A', '\xc3': 'A', '\xc4': 'A', '\xc5': 'A', - '\xe0': 'a', '\xe1': 'a', '\xe2': 'a', '\xe3': 'a', '\xe4': 'a', '\xe5': 'a', - '\xc7': 'C', '\xe7': 'c', - '\xd0': 'D', '\xf0': 'd', - '\xc8': 'E', '\xc9': 'E', '\xca': 'E', '\xcb': 'E', - '\xe8': 'e', '\xe9': 'e', '\xea': 'e', '\xeb': 'e', - '\xcc': 'I', '\xcd': 'I', '\xce': 'I', '\xcf': 'I', - '\xec': 'i', '\xed': 'i', '\xee': 'i', '\xef': 'i', - '\xd1': 'N', '\xf1': 'n', - '\xd2': 'O', '\xd3': 'O', '\xd4': 'O', '\xd5': 'O', '\xd6': 'O', '\xd8': 'O', - '\xf2': 'o', '\xf3': 'o', '\xf4': 'o', '\xf5': 'o', '\xf6': 'o', '\xf8': 'o', - '\xd9': 'U', '\xda': 'U', '\xdb': 'U', '\xdc': 'U', - '\xf9': 'u', '\xfa': 'u', '\xfb': 'u', '\xfc': 'u', - '\xdd': 'Y', '\xfd': 'y', '\xff': 'y', - '\xc6': 'Ae', '\xe6': 'ae', - '\xde': 'Th', '\xfe': 'th', - '\xdf': 'ss', - // Latin Extended-A block. - '\u0100': 'A', '\u0102': 'A', '\u0104': 'A', - '\u0101': 'a', '\u0103': 'a', '\u0105': 'a', - '\u0106': 'C', '\u0108': 'C', '\u010a': 'C', '\u010c': 'C', - '\u0107': 'c', '\u0109': 'c', '\u010b': 'c', '\u010d': 'c', - '\u010e': 'D', '\u0110': 'D', '\u010f': 'd', '\u0111': 'd', - '\u0112': 'E', '\u0114': 'E', '\u0116': 'E', '\u0118': 'E', '\u011a': 'E', - '\u0113': 'e', '\u0115': 'e', '\u0117': 'e', '\u0119': 'e', '\u011b': 'e', - '\u011c': 'G', '\u011e': 'G', '\u0120': 'G', '\u0122': 'G', - '\u011d': 'g', '\u011f': 'g', '\u0121': 'g', '\u0123': 'g', - '\u0124': 'H', '\u0126': 'H', '\u0125': 'h', '\u0127': 'h', - '\u0128': 'I', '\u012a': 'I', '\u012c': 'I', '\u012e': 'I', '\u0130': 'I', - '\u0129': 'i', '\u012b': 'i', '\u012d': 'i', '\u012f': 'i', '\u0131': 'i', - '\u0134': 'J', '\u0135': 'j', - '\u0136': 'K', '\u0137': 'k', '\u0138': 'k', - '\u0139': 'L', '\u013b': 'L', '\u013d': 'L', '\u013f': 'L', '\u0141': 'L', - '\u013a': 'l', '\u013c': 'l', '\u013e': 'l', '\u0140': 'l', '\u0142': 'l', - '\u0143': 'N', '\u0145': 'N', '\u0147': 'N', '\u014a': 'N', - '\u0144': 'n', '\u0146': 'n', '\u0148': 'n', '\u014b': 'n', - '\u014c': 'O', '\u014e': 'O', '\u0150': 'O', - '\u014d': 'o', '\u014f': 'o', '\u0151': 'o', - '\u0154': 'R', '\u0156': 'R', '\u0158': 'R', - '\u0155': 'r', '\u0157': 'r', '\u0159': 'r', - '\u015a': 'S', '\u015c': 'S', '\u015e': 'S', '\u0160': 'S', - '\u015b': 's', '\u015d': 's', '\u015f': 's', '\u0161': 's', - '\u0162': 'T', '\u0164': 'T', '\u0166': 'T', - '\u0163': 't', '\u0165': 't', '\u0167': 't', - '\u0168': 'U', '\u016a': 'U', '\u016c': 'U', '\u016e': 'U', '\u0170': 'U', '\u0172': 'U', - '\u0169': 'u', '\u016b': 'u', '\u016d': 'u', '\u016f': 'u', '\u0171': 'u', '\u0173': 'u', - '\u0174': 'W', '\u0175': 'w', - '\u0176': 'Y', '\u0177': 'y', '\u0178': 'Y', - '\u0179': 'Z', '\u017b': 'Z', '\u017d': 'Z', - '\u017a': 'z', '\u017c': 'z', '\u017e': 'z', - '\u0132': 'IJ', '\u0133': 'ij', - '\u0152': 'Oe', '\u0153': 'oe', - '\u0149': "'n", '\u017f': 's' + /** Used to identify `toStringTag` values of typed arrays. */ + var typedArrayTags = {}; + typedArrayTags[float32Tag] = typedArrayTags[float64Tag] = + typedArrayTags[int8Tag] = typedArrayTags[int16Tag] = + typedArrayTags[int32Tag] = typedArrayTags[uint8Tag] = + typedArrayTags[uint8ClampedTag] = typedArrayTags[uint16Tag] = + typedArrayTags[uint32Tag] = true; + typedArrayTags[argsTag] = typedArrayTags[arrayTag] = + typedArrayTags[arrayBufferTag] = typedArrayTags[boolTag] = + typedArrayTags[dataViewTag] = typedArrayTags[dateTag] = + typedArrayTags[errorTag] = typedArrayTags[funcTag] = + typedArrayTags[mapTag] = typedArrayTags[numberTag] = + typedArrayTags[objectTag] = typedArrayTags[regexpTag] = + typedArrayTags[setTag] = typedArrayTags[stringTag] = + typedArrayTags[weakMapTag] = false; + + /** Used to identify `toStringTag` values supported by `_.clone`. */ + var cloneableTags = {}; + cloneableTags[argsTag] = cloneableTags[arrayTag] = + cloneableTags[arrayBufferTag] = cloneableTags[dataViewTag] = + cloneableTags[boolTag] = cloneableTags[dateTag] = + cloneableTags[float32Tag] = cloneableTags[float64Tag] = + cloneableTags[int8Tag] = cloneableTags[int16Tag] = + cloneableTags[int32Tag] = cloneableTags[mapTag] = + cloneableTags[numberTag] = cloneableTags[objectTag] = + cloneableTags[regexpTag] = cloneableTags[setTag] = + cloneableTags[stringTag] = cloneableTags[symbolTag] = + cloneableTags[uint8Tag] = cloneableTags[uint8ClampedTag] = + cloneableTags[uint16Tag] = cloneableTags[uint32Tag] = true; + cloneableTags[errorTag] = cloneableTags[funcTag] = + cloneableTags[weakMapTag] = false; + + /** Used to map Latin Unicode letters to basic Latin letters. */ + var deburredLetters = { + // Latin-1 Supplement block. + '\xc0': 'A', '\xc1': 'A', '\xc2': 'A', '\xc3': 'A', '\xc4': 'A', '\xc5': 'A', + '\xe0': 'a', '\xe1': 'a', '\xe2': 'a', '\xe3': 'a', '\xe4': 'a', '\xe5': 'a', + '\xc7': 'C', '\xe7': 'c', + '\xd0': 'D', '\xf0': 'd', + '\xc8': 'E', '\xc9': 'E', '\xca': 'E', '\xcb': 'E', + '\xe8': 'e', '\xe9': 'e', '\xea': 'e', '\xeb': 'e', + '\xcc': 'I', '\xcd': 'I', '\xce': 'I', '\xcf': 'I', + '\xec': 'i', '\xed': 'i', '\xee': 'i', '\xef': 'i', + '\xd1': 'N', '\xf1': 'n', + '\xd2': 'O', '\xd3': 'O', '\xd4': 'O', '\xd5': 'O', '\xd6': 'O', '\xd8': 'O', + '\xf2': 'o', '\xf3': 'o', '\xf4': 'o', '\xf5': 'o', '\xf6': 'o', '\xf8': 'o', + '\xd9': 'U', '\xda': 'U', '\xdb': 'U', '\xdc': 'U', + '\xf9': 'u', '\xfa': 'u', '\xfb': 'u', '\xfc': 'u', + '\xdd': 'Y', '\xfd': 'y', '\xff': 'y', + '\xc6': 'Ae', '\xe6': 'ae', + '\xde': 'Th', '\xfe': 'th', + '\xdf': 'ss', + // Latin Extended-A block. + '\u0100': 'A', '\u0102': 'A', '\u0104': 'A', + '\u0101': 'a', '\u0103': 'a', '\u0105': 'a', + '\u0106': 'C', '\u0108': 'C', '\u010a': 'C', '\u010c': 'C', + '\u0107': 'c', '\u0109': 'c', '\u010b': 'c', '\u010d': 'c', + '\u010e': 'D', '\u0110': 'D', '\u010f': 'd', '\u0111': 'd', + '\u0112': 'E', '\u0114': 'E', '\u0116': 'E', '\u0118': 'E', '\u011a': 'E', + '\u0113': 'e', '\u0115': 'e', '\u0117': 'e', '\u0119': 'e', '\u011b': 'e', + '\u011c': 'G', '\u011e': 'G', '\u0120': 'G', '\u0122': 'G', + '\u011d': 'g', '\u011f': 'g', '\u0121': 'g', '\u0123': 'g', + '\u0124': 'H', '\u0126': 'H', '\u0125': 'h', '\u0127': 'h', + '\u0128': 'I', '\u012a': 'I', '\u012c': 'I', '\u012e': 'I', '\u0130': 'I', + '\u0129': 'i', '\u012b': 'i', '\u012d': 'i', '\u012f': 'i', '\u0131': 'i', + '\u0134': 'J', '\u0135': 'j', + '\u0136': 'K', '\u0137': 'k', '\u0138': 'k', + '\u0139': 'L', '\u013b': 'L', '\u013d': 'L', '\u013f': 'L', '\u0141': 'L', + '\u013a': 'l', '\u013c': 'l', '\u013e': 'l', '\u0140': 'l', '\u0142': 'l', + '\u0143': 'N', '\u0145': 'N', '\u0147': 'N', '\u014a': 'N', + '\u0144': 'n', '\u0146': 'n', '\u0148': 'n', '\u014b': 'n', + '\u014c': 'O', '\u014e': 'O', '\u0150': 'O', + '\u014d': 'o', '\u014f': 'o', '\u0151': 'o', + '\u0154': 'R', '\u0156': 'R', '\u0158': 'R', + '\u0155': 'r', '\u0157': 'r', '\u0159': 'r', + '\u015a': 'S', '\u015c': 'S', '\u015e': 'S', '\u0160': 'S', + '\u015b': 's', '\u015d': 's', '\u015f': 's', '\u0161': 's', + '\u0162': 'T', '\u0164': 'T', '\u0166': 'T', + '\u0163': 't', '\u0165': 't', '\u0167': 't', + '\u0168': 'U', '\u016a': 'U', '\u016c': 'U', '\u016e': 'U', '\u0170': 'U', '\u0172': 'U', + '\u0169': 'u', '\u016b': 'u', '\u016d': 'u', '\u016f': 'u', '\u0171': 'u', '\u0173': 'u', + '\u0174': 'W', '\u0175': 'w', + '\u0176': 'Y', '\u0177': 'y', '\u0178': 'Y', + '\u0179': 'Z', '\u017b': 'Z', '\u017d': 'Z', + '\u017a': 'z', '\u017c': 'z', '\u017e': 'z', + '\u0132': 'IJ', '\u0133': 'ij', + '\u0152': 'Oe', '\u0153': 'oe', + '\u0149': "'n", '\u017f': 's' + }; + + /** Used to map characters to HTML entities. */ + var htmlEscapes = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''' + }; + + /** Used to map HTML entities to characters. */ + var htmlUnescapes = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + ''': "'" + }; + + /** Used to escape characters for inclusion in compiled string literals. */ + var stringEscapes = { + '\\': '\\', + "'": "'", + '\n': 'n', + '\r': 'r', + '\u2028': 'u2028', + '\u2029': 'u2029' + }; + + /** Built-in method references without a dependency on `root`. */ + var freeParseFloat = parseFloat, + freeParseInt = parseInt; + + /** Detect free variable `global` from Node.js. */ + var freeGlobal = typeof global == 'object' && global && global.Object === Object && global; + + /** Detect free variable `self`. */ + var freeSelf = typeof self == 'object' && self && self.Object === Object && self; + + /** Used as a reference to the global object. */ + var root = freeGlobal || freeSelf || Function('return this')(); + + /** Detect free variable `exports`. */ + var freeExports = typeof exports == 'object' && exports && !exports.nodeType && exports; + + /** Detect free variable `module`. */ + var freeModule = freeExports && typeof module == 'object' && module && !module.nodeType && module; + + /** Detect the popular CommonJS extension `module.exports`. */ + var moduleExports = freeModule && freeModule.exports === freeExports; + + /** Detect free variable `process` from Node.js. */ + var freeProcess = moduleExports && freeGlobal.process; + + /** Used to access faster Node.js helpers. */ + var nodeUtil = (function() { + try { + // Use `util.types` for Node.js 10+. + var types = freeModule && freeModule.require && freeModule.require('util').types; + + if (types) { + return types; + } + + // Legacy `process.binding('util')` for Node.js < 10. + return freeProcess && freeProcess.binding && freeProcess.binding('util'); + } catch (e) {} + }()); + + /* Node.js helper references. */ + var nodeIsArrayBuffer = nodeUtil && nodeUtil.isArrayBuffer, + nodeIsDate = nodeUtil && nodeUtil.isDate, + nodeIsMap = nodeUtil && nodeUtil.isMap, + nodeIsRegExp = nodeUtil && nodeUtil.isRegExp, + nodeIsSet = nodeUtil && nodeUtil.isSet, + nodeIsTypedArray = nodeUtil && nodeUtil.isTypedArray; + + /*--------------------------------------------------------------------------*/ + + /** + * A faster alternative to `Function#apply`, this function invokes `func` + * with the `this` binding of `thisArg` and the arguments of `args`. + * + * @private + * @param {Function} func The function to invoke. + * @param {*} thisArg The `this` binding of `func`. + * @param {Array} args The arguments to invoke `func` with. + * @returns {*} Returns the result of `func`. + */ + function apply(func, thisArg, args) { + switch (args.length) { + case 0: return func.call(thisArg); + case 1: return func.call(thisArg, args[0]); + case 2: return func.call(thisArg, args[0], args[1]); + case 3: return func.call(thisArg, args[0], args[1], args[2]); + } + return func.apply(thisArg, args); + } + + /** + * A specialized version of `baseAggregator` for arrays. + * + * @private + * @param {Array} [array] The array to iterate over. + * @param {Function} setter The function to set `accumulator` values. + * @param {Function} iteratee The iteratee to transform keys. + * @param {Object} accumulator The initial aggregated object. + * @returns {Function} Returns `accumulator`. + */ + function arrayAggregator(array, setter, iteratee, accumulator) { + var index = -1, + length = array == null ? 0 : array.length; + + while (++index < length) { + var value = array[index]; + setter(accumulator, value, iteratee(value), array); + } + return accumulator; + } + + /** + * A specialized version of `_.forEach` for arrays without support for + * iteratee shorthands. + * + * @private + * @param {Array} [array] The array to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @returns {Array} Returns `array`. + */ + function arrayEach(array, iteratee) { + var index = -1, + length = array == null ? 0 : array.length; + + while (++index < length) { + if (iteratee(array[index], index, array) === false) { + break; + } + } + return array; + } + + /** + * A specialized version of `_.forEachRight` for arrays without support for + * iteratee shorthands. + * + * @private + * @param {Array} [array] The array to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @returns {Array} Returns `array`. + */ + function arrayEachRight(array, iteratee) { + var length = array == null ? 0 : array.length; + + while (length--) { + if (iteratee(array[length], length, array) === false) { + break; + } + } + return array; + } + + /** + * A specialized version of `_.every` for arrays without support for + * iteratee shorthands. + * + * @private + * @param {Array} [array] The array to iterate over. + * @param {Function} predicate The function invoked per iteration. + * @returns {boolean} Returns `true` if all elements pass the predicate check, + * else `false`. + */ + function arrayEvery(array, predicate) { + var index = -1, + length = array == null ? 0 : array.length; + + while (++index < length) { + if (!predicate(array[index], index, array)) { + return false; + } + } + return true; + } + + /** + * A specialized version of `_.filter` for arrays without support for + * iteratee shorthands. + * + * @private + * @param {Array} [array] The array to iterate over. + * @param {Function} predicate The function invoked per iteration. + * @returns {Array} Returns the new filtered array. + */ + function arrayFilter(array, predicate) { + var index = -1, + length = array == null ? 0 : array.length, + resIndex = 0, + result = []; + + while (++index < length) { + var value = array[index]; + if (predicate(value, index, array)) { + result[resIndex++] = value; + } + } + return result; + } + + /** + * A specialized version of `_.includes` for arrays without support for + * specifying an index to search from. + * + * @private + * @param {Array} [array] The array to inspect. + * @param {*} target The value to search for. + * @returns {boolean} Returns `true` if `target` is found, else `false`. + */ + function arrayIncludes(array, value) { + var length = array == null ? 0 : array.length; + return !!length && baseIndexOf(array, value, 0) > -1; + } + + /** + * This function is like `arrayIncludes` except that it accepts a comparator. + * + * @private + * @param {Array} [array] The array to inspect. + * @param {*} target The value to search for. + * @param {Function} comparator The comparator invoked per element. + * @returns {boolean} Returns `true` if `target` is found, else `false`. + */ + function arrayIncludesWith(array, value, comparator) { + var index = -1, + length = array == null ? 0 : array.length; + + while (++index < length) { + if (comparator(value, array[index])) { + return true; + } + } + return false; + } + + /** + * A specialized version of `_.map` for arrays without support for iteratee + * shorthands. + * + * @private + * @param {Array} [array] The array to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @returns {Array} Returns the new mapped array. + */ + function arrayMap(array, iteratee) { + var index = -1, + length = array == null ? 0 : array.length, + result = Array(length); + + while (++index < length) { + result[index] = iteratee(array[index], index, array); + } + return result; + } + + /** + * Appends the elements of `values` to `array`. + * + * @private + * @param {Array} array The array to modify. + * @param {Array} values The values to append. + * @returns {Array} Returns `array`. + */ + function arrayPush(array, values) { + var index = -1, + length = values.length, + offset = array.length; + + while (++index < length) { + array[offset + index] = values[index]; + } + return array; + } + + /** + * A specialized version of `_.reduce` for arrays without support for + * iteratee shorthands. + * + * @private + * @param {Array} [array] The array to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @param {*} [accumulator] The initial value. + * @param {boolean} [initAccum] Specify using the first element of `array` as + * the initial value. + * @returns {*} Returns the accumulated value. + */ + function arrayReduce(array, iteratee, accumulator, initAccum) { + var index = -1, + length = array == null ? 0 : array.length; + + if (initAccum && length) { + accumulator = array[++index]; + } + while (++index < length) { + accumulator = iteratee(accumulator, array[index], index, array); + } + return accumulator; + } + + /** + * A specialized version of `_.reduceRight` for arrays without support for + * iteratee shorthands. + * + * @private + * @param {Array} [array] The array to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @param {*} [accumulator] The initial value. + * @param {boolean} [initAccum] Specify using the last element of `array` as + * the initial value. + * @returns {*} Returns the accumulated value. + */ + function arrayReduceRight(array, iteratee, accumulator, initAccum) { + var length = array == null ? 0 : array.length; + if (initAccum && length) { + accumulator = array[--length]; + } + while (length--) { + accumulator = iteratee(accumulator, array[length], length, array); + } + return accumulator; + } + + /** + * A specialized version of `_.some` for arrays without support for iteratee + * shorthands. + * + * @private + * @param {Array} [array] The array to iterate over. + * @param {Function} predicate The function invoked per iteration. + * @returns {boolean} Returns `true` if any element passes the predicate check, + * else `false`. + */ + function arraySome(array, predicate) { + var index = -1, + length = array == null ? 0 : array.length; + + while (++index < length) { + if (predicate(array[index], index, array)) { + return true; + } + } + return false; + } + + /** + * Gets the size of an ASCII `string`. + * + * @private + * @param {string} string The string inspect. + * @returns {number} Returns the string size. + */ + var asciiSize = baseProperty('length'); + + /** + * Converts an ASCII `string` to an array. + * + * @private + * @param {string} string The string to convert. + * @returns {Array} Returns the converted array. + */ + function asciiToArray(string) { + return string.split(''); + } + + /** + * Splits an ASCII `string` into an array of its words. + * + * @private + * @param {string} The string to inspect. + * @returns {Array} Returns the words of `string`. + */ + function asciiWords(string) { + return string.match(reAsciiWord) || []; + } + + /** + * The base implementation of methods like `_.findKey` and `_.findLastKey`, + * without support for iteratee shorthands, which iterates over `collection` + * using `eachFunc`. + * + * @private + * @param {Array|Object} collection The collection to inspect. + * @param {Function} predicate The function invoked per iteration. + * @param {Function} eachFunc The function to iterate over `collection`. + * @returns {*} Returns the found element or its key, else `undefined`. + */ + function baseFindKey(collection, predicate, eachFunc) { + var result; + eachFunc(collection, function(value, key, collection) { + if (predicate(value, key, collection)) { + result = key; + return false; + } + }); + return result; + } + + /** + * The base implementation of `_.findIndex` and `_.findLastIndex` without + * support for iteratee shorthands. + * + * @private + * @param {Array} array The array to inspect. + * @param {Function} predicate The function invoked per iteration. + * @param {number} fromIndex The index to search from. + * @param {boolean} [fromRight] Specify iterating from right to left. + * @returns {number} Returns the index of the matched value, else `-1`. + */ + function baseFindIndex(array, predicate, fromIndex, fromRight) { + var length = array.length, + index = fromIndex + (fromRight ? 1 : -1); + + while ((fromRight ? index-- : ++index < length)) { + if (predicate(array[index], index, array)) { + return index; + } + } + return -1; + } + + /** + * The base implementation of `_.indexOf` without `fromIndex` bounds checks. + * + * @private + * @param {Array} array The array to inspect. + * @param {*} value The value to search for. + * @param {number} fromIndex The index to search from. + * @returns {number} Returns the index of the matched value, else `-1`. + */ + function baseIndexOf(array, value, fromIndex) { + return value === value + ? strictIndexOf(array, value, fromIndex) + : baseFindIndex(array, baseIsNaN, fromIndex); + } + + /** + * This function is like `baseIndexOf` except that it accepts a comparator. + * + * @private + * @param {Array} array The array to inspect. + * @param {*} value The value to search for. + * @param {number} fromIndex The index to search from. + * @param {Function} comparator The comparator invoked per element. + * @returns {number} Returns the index of the matched value, else `-1`. + */ + function baseIndexOfWith(array, value, fromIndex, comparator) { + var index = fromIndex - 1, + length = array.length; + + while (++index < length) { + if (comparator(array[index], value)) { + return index; + } + } + return -1; + } + + /** + * The base implementation of `_.isNaN` without support for number objects. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is `NaN`, else `false`. + */ + function baseIsNaN(value) { + return value !== value; + } + + /** + * The base implementation of `_.mean` and `_.meanBy` without support for + * iteratee shorthands. + * + * @private + * @param {Array} array The array to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @returns {number} Returns the mean. + */ + function baseMean(array, iteratee) { + var length = array == null ? 0 : array.length; + return length ? (baseSum(array, iteratee) / length) : NAN; + } + + /** + * The base implementation of `_.property` without support for deep paths. + * + * @private + * @param {string} key The key of the property to get. + * @returns {Function} Returns the new accessor function. + */ + function baseProperty(key) { + return function(object) { + return object == null ? undefined : object[key]; }; + } - /** Used to map characters to HTML entities. */ - var htmlEscapes = { - '&': '&', - '<': '<', - '>': '>', - '"': '"', - "'": ''' + /** + * The base implementation of `_.propertyOf` without support for deep paths. + * + * @private + * @param {Object} object The object to query. + * @returns {Function} Returns the new accessor function. + */ + function basePropertyOf(object) { + return function(key) { + return object == null ? undefined : object[key]; }; + } - /** Used to map HTML entities to characters. */ - var htmlUnescapes = { - '&': '&', - '<': '<', - '>': '>', - '"': '"', - ''': "'" + /** + * The base implementation of `_.reduce` and `_.reduceRight`, without support + * for iteratee shorthands, which iterates over `collection` using `eachFunc`. + * + * @private + * @param {Array|Object} collection The collection to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @param {*} accumulator The initial value. + * @param {boolean} initAccum Specify using the first or last element of + * `collection` as the initial value. + * @param {Function} eachFunc The function to iterate over `collection`. + * @returns {*} Returns the accumulated value. + */ + function baseReduce(collection, iteratee, accumulator, initAccum, eachFunc) { + eachFunc(collection, function(value, index, collection) { + accumulator = initAccum + ? (initAccum = false, value) + : iteratee(accumulator, value, index, collection); + }); + return accumulator; + } + + /** + * The base implementation of `_.sortBy` which uses `comparer` to define the + * sort order of `array` and replaces criteria objects with their corresponding + * values. + * + * @private + * @param {Array} array The array to sort. + * @param {Function} comparer The function to define sort order. + * @returns {Array} Returns `array`. + */ + function baseSortBy(array, comparer) { + var length = array.length; + + array.sort(comparer); + while (length--) { + array[length] = array[length].value; + } + return array; + } + + /** + * The base implementation of `_.sum` and `_.sumBy` without support for + * iteratee shorthands. + * + * @private + * @param {Array} array The array to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @returns {number} Returns the sum. + */ + function baseSum(array, iteratee) { + var result, + index = -1, + length = array.length; + + while (++index < length) { + var current = iteratee(array[index]); + if (current !== undefined) { + result = result === undefined ? current : (result + current); + } + } + return result; + } + + /** + * The base implementation of `_.times` without support for iteratee shorthands + * or max array length checks. + * + * @private + * @param {number} n The number of times to invoke `iteratee`. + * @param {Function} iteratee The function invoked per iteration. + * @returns {Array} Returns the array of results. + */ + function baseTimes(n, iteratee) { + var index = -1, + result = Array(n); + + while (++index < n) { + result[index] = iteratee(index); + } + return result; + } + + /** + * The base implementation of `_.toPairs` and `_.toPairsIn` which creates an array + * of key-value pairs for `object` corresponding to the property names of `props`. + * + * @private + * @param {Object} object The object to query. + * @param {Array} props The property names to get values for. + * @returns {Object} Returns the key-value pairs. + */ + function baseToPairs(object, props) { + return arrayMap(props, function(key) { + return [key, object[key]]; + }); + } + + /** + * The base implementation of `_.trim`. + * + * @private + * @param {string} string The string to trim. + * @returns {string} Returns the trimmed string. + */ + function baseTrim(string) { + return string + ? string.slice(0, trimmedEndIndex(string) + 1).replace(reTrimStart, '') + : string; + } + + /** + * The base implementation of `_.unary` without support for storing metadata. + * + * @private + * @param {Function} func The function to cap arguments for. + * @returns {Function} Returns the new capped function. + */ + function baseUnary(func) { + return function(value) { + return func(value); }; + } - /** Used to escape characters for inclusion in compiled string literals. */ - var stringEscapes = { - '\\': '\\', - "'": "'", - '\n': 'n', - '\r': 'r', - '\u2028': 'u2028', - '\u2029': 'u2029' + /** + * The base implementation of `_.values` and `_.valuesIn` which creates an + * array of `object` property values corresponding to the property names + * of `props`. + * + * @private + * @param {Object} object The object to query. + * @param {Array} props The property names to get values for. + * @returns {Object} Returns the array of property values. + */ + function baseValues(object, props) { + return arrayMap(props, function(key) { + return object[key]; + }); + } + + /** + * Checks if a `cache` value for `key` exists. + * + * @private + * @param {Object} cache The cache to query. + * @param {string} key The key of the entry to check. + * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. + */ + function cacheHas(cache, key) { + return cache.has(key); + } + + /** + * Used by `_.trim` and `_.trimStart` to get the index of the first string symbol + * that is not found in the character symbols. + * + * @private + * @param {Array} strSymbols The string symbols to inspect. + * @param {Array} chrSymbols The character symbols to find. + * @returns {number} Returns the index of the first unmatched string symbol. + */ + function charsStartIndex(strSymbols, chrSymbols) { + var index = -1, + length = strSymbols.length; + + while (++index < length && baseIndexOf(chrSymbols, strSymbols[index], 0) > -1) {} + return index; + } + + /** + * Used by `_.trim` and `_.trimEnd` to get the index of the last string symbol + * that is not found in the character symbols. + * + * @private + * @param {Array} strSymbols The string symbols to inspect. + * @param {Array} chrSymbols The character symbols to find. + * @returns {number} Returns the index of the last unmatched string symbol. + */ + function charsEndIndex(strSymbols, chrSymbols) { + var index = strSymbols.length; + + while (index-- && baseIndexOf(chrSymbols, strSymbols[index], 0) > -1) {} + return index; + } + + /** + * Gets the number of `placeholder` occurrences in `array`. + * + * @private + * @param {Array} array The array to inspect. + * @param {*} placeholder The placeholder to search for. + * @returns {number} Returns the placeholder count. + */ + function countHolders(array, placeholder) { + var length = array.length, + result = 0; + + while (length--) { + if (array[length] === placeholder) { + ++result; + } + } + return result; + } + + /** + * Used by `_.deburr` to convert Latin-1 Supplement and Latin Extended-A + * letters to basic Latin letters. + * + * @private + * @param {string} letter The matched letter to deburr. + * @returns {string} Returns the deburred letter. + */ + var deburrLetter = basePropertyOf(deburredLetters); + + /** + * Used by `_.escape` to convert characters to HTML entities. + * + * @private + * @param {string} chr The matched character to escape. + * @returns {string} Returns the escaped character. + */ + var escapeHtmlChar = basePropertyOf(htmlEscapes); + + /** + * Used by `_.template` to escape characters for inclusion in compiled string literals. + * + * @private + * @param {string} chr The matched character to escape. + * @returns {string} Returns the escaped character. + */ + function escapeStringChar(chr) { + return '\\' + stringEscapes[chr]; + } + + /** + * Gets the value at `key` of `object`. + * + * @private + * @param {Object} [object] The object to query. + * @param {string} key The key of the property to get. + * @returns {*} Returns the property value. + */ + function getValue(object, key) { + return object == null ? undefined : object[key]; + } + + /** + * Checks if `string` contains Unicode symbols. + * + * @private + * @param {string} string The string to inspect. + * @returns {boolean} Returns `true` if a symbol is found, else `false`. + */ + function hasUnicode(string) { + return reHasUnicode.test(string); + } + + /** + * Checks if `string` contains a word composed of Unicode symbols. + * + * @private + * @param {string} string The string to inspect. + * @returns {boolean} Returns `true` if a word is found, else `false`. + */ + function hasUnicodeWord(string) { + return reHasUnicodeWord.test(string); + } + + /** + * Converts `iterator` to an array. + * + * @private + * @param {Object} iterator The iterator to convert. + * @returns {Array} Returns the converted array. + */ + function iteratorToArray(iterator) { + var data, + result = []; + + while (!(data = iterator.next()).done) { + result.push(data.value); + } + return result; + } + + /** + * Converts `map` to its key-value pairs. + * + * @private + * @param {Object} map The map to convert. + * @returns {Array} Returns the key-value pairs. + */ + function mapToArray(map) { + var index = -1, + result = Array(map.size); + + map.forEach(function(value, key) { + result[++index] = [key, value]; + }); + return result; + } + + /** + * Creates a unary function that invokes `func` with its argument transformed. + * + * @private + * @param {Function} func The function to wrap. + * @param {Function} transform The argument transform. + * @returns {Function} Returns the new function. + */ + function overArg(func, transform) { + return function(arg) { + return func(transform(arg)); }; + } - /** Built-in method references without a dependency on `root`. */ - var freeParseFloat = parseFloat, - freeParseInt = parseInt; + /** + * Replaces all `placeholder` elements in `array` with an internal placeholder + * and returns an array of their indexes. + * + * @private + * @param {Array} array The array to modify. + * @param {*} placeholder The placeholder to replace. + * @returns {Array} Returns the new array of placeholder indexes. + */ + function replaceHolders(array, placeholder) { + var index = -1, + length = array.length, + resIndex = 0, + result = []; - /** Detect free variable `global` from Node.js. */ - var freeGlobal = typeof global == 'object' && global && global.Object === Object && global; + while (++index < length) { + var value = array[index]; + if (value === placeholder || value === PLACEHOLDER) { + array[index] = PLACEHOLDER; + result[resIndex++] = index; + } + } + return result; + } - /** Detect free variable `self`. */ - var freeSelf = typeof self == 'object' && self && self.Object === Object && self; + /** + * Converts `set` to an array of its values. + * + * @private + * @param {Object} set The set to convert. + * @returns {Array} Returns the values. + */ + function setToArray(set) { + var index = -1, + result = Array(set.size); - /** Used as a reference to the global object. */ - var root = freeGlobal || freeSelf || Function('return this')(); + set.forEach(function(value) { + result[++index] = value; + }); + return result; + } - /** Detect free variable `exports`. */ - var freeExports = typeof exports == 'object' && exports && !exports.nodeType && exports; + /** + * Converts `set` to its value-value pairs. + * + * @private + * @param {Object} set The set to convert. + * @returns {Array} Returns the value-value pairs. + */ + function setToPairs(set) { + var index = -1, + result = Array(set.size); - /** Detect free variable `module`. */ - var freeModule = freeExports && typeof module == 'object' && module && !module.nodeType && module; + set.forEach(function(value) { + result[++index] = [value, value]; + }); + return result; + } - /** Detect the popular CommonJS extension `module.exports`. */ - var moduleExports = freeModule && freeModule.exports === freeExports; + /** + * A specialized version of `_.indexOf` which performs strict equality + * comparisons of values, i.e. `===`. + * + * @private + * @param {Array} array The array to inspect. + * @param {*} value The value to search for. + * @param {number} fromIndex The index to search from. + * @returns {number} Returns the index of the matched value, else `-1`. + */ + function strictIndexOf(array, value, fromIndex) { + var index = fromIndex - 1, + length = array.length; - /** Detect free variable `process` from Node.js. */ - var freeProcess = moduleExports && freeGlobal.process; + while (++index < length) { + if (array[index] === value) { + return index; + } + } + return -1; + } - /** Used to access faster Node.js helpers. */ - var nodeUtil = (function() { - try { - // Use `util.types` for Node.js 10+. - var types = freeModule && freeModule.require && freeModule.require('util').types; + /** + * A specialized version of `_.lastIndexOf` which performs strict equality + * comparisons of values, i.e. `===`. + * + * @private + * @param {Array} array The array to inspect. + * @param {*} value The value to search for. + * @param {number} fromIndex The index to search from. + * @returns {number} Returns the index of the matched value, else `-1`. + */ + function strictLastIndexOf(array, value, fromIndex) { + var index = fromIndex + 1; + while (index--) { + if (array[index] === value) { + return index; + } + } + return index; + } - if (types) { - return types; - } + /** + * Gets the number of symbols in `string`. + * + * @private + * @param {string} string The string to inspect. + * @returns {number} Returns the string size. + */ + function stringSize(string) { + return hasUnicode(string) + ? unicodeSize(string) + : asciiSize(string); + } - // Legacy `process.binding('util')` for Node.js < 10. - return freeProcess && freeProcess.binding && freeProcess.binding('util'); - } catch (e) {} + /** + * Converts `string` to an array. + * + * @private + * @param {string} string The string to convert. + * @returns {Array} Returns the converted array. + */ + function stringToArray(string) { + return hasUnicode(string) + ? unicodeToArray(string) + : asciiToArray(string); + } + + /** + * Used by `_.trim` and `_.trimEnd` to get the index of the last non-whitespace + * character of `string`. + * + * @private + * @param {string} string The string to inspect. + * @returns {number} Returns the index of the last non-whitespace character. + */ + function trimmedEndIndex(string) { + var index = string.length; + + while (index-- && reWhitespace.test(string.charAt(index))) {} + return index; + } + + /** + * Used by `_.unescape` to convert HTML entities to characters. + * + * @private + * @param {string} chr The matched character to unescape. + * @returns {string} Returns the unescaped character. + */ + var unescapeHtmlChar = basePropertyOf(htmlUnescapes); + + /** + * Gets the size of a Unicode `string`. + * + * @private + * @param {string} string The string inspect. + * @returns {number} Returns the string size. + */ + function unicodeSize(string) { + var result = reUnicode.lastIndex = 0; + while (reUnicode.test(string)) { + ++result; + } + return result; + } + + /** + * Converts a Unicode `string` to an array. + * + * @private + * @param {string} string The string to convert. + * @returns {Array} Returns the converted array. + */ + function unicodeToArray(string) { + return string.match(reUnicode) || []; + } + + /** + * Splits a Unicode `string` into an array of its words. + * + * @private + * @param {string} The string to inspect. + * @returns {Array} Returns the words of `string`. + */ + function unicodeWords(string) { + return string.match(reUnicodeWord) || []; + } + + /*--------------------------------------------------------------------------*/ + + /** + * Create a new pristine `lodash` function using the `context` object. + * + * @static + * @memberOf _ + * @since 1.1.0 + * @category Util + * @param {Object} [context=root] The context object. + * @returns {Function} Returns a new `lodash` function. + * @example + * + * _.mixin({ 'foo': _.constant('foo') }); + * + * var lodash = _.runInContext(); + * lodash.mixin({ 'bar': lodash.constant('bar') }); + * + * _.isFunction(_.foo); + * // => true + * _.isFunction(_.bar); + * // => false + * + * lodash.isFunction(lodash.foo); + * // => false + * lodash.isFunction(lodash.bar); + * // => true + * + * // Create a suped-up `defer` in Node.js. + * var defer = _.runInContext({ 'setTimeout': setImmediate }).defer; + */ + var runInContext = (function runInContext(context) { + context = context == null ? root : _.defaults(root.Object(), context, _.pick(root, contextProps)); + + /** Built-in constructor references. */ + var Array = context.Array, + Date = context.Date, + Error = context.Error, + Function = context.Function, + Math = context.Math, + Object = context.Object, + RegExp = context.RegExp, + String = context.String, + TypeError = context.TypeError; + + /** Used for built-in method references. */ + var arrayProto = Array.prototype, + funcProto = Function.prototype, + objectProto = Object.prototype; + + /** Used to detect overreaching core-js shims. */ + var coreJsData = context['__core-js_shared__']; + + /** Used to resolve the decompiled source of functions. */ + var funcToString = funcProto.toString; + + /** Used to check objects for own properties. */ + var hasOwnProperty = objectProto.hasOwnProperty; + + /** Used to generate unique IDs. */ + var idCounter = 0; + + /** Used to detect methods masquerading as native. */ + var maskSrcKey = (function() { + var uid = /[^.]+$/.exec(coreJsData && coreJsData.keys && coreJsData.keys.IE_PROTO || ''); + return uid ? ('Symbol(src)_1.' + uid) : ''; }()); - /* Node.js helper references. */ - var nodeIsArrayBuffer = nodeUtil && nodeUtil.isArrayBuffer, - nodeIsDate = nodeUtil && nodeUtil.isDate, - nodeIsMap = nodeUtil && nodeUtil.isMap, - nodeIsRegExp = nodeUtil && nodeUtil.isRegExp, - nodeIsSet = nodeUtil && nodeUtil.isSet, - nodeIsTypedArray = nodeUtil && nodeUtil.isTypedArray; + /** + * Used to resolve the + * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring) + * of values. + */ + var nativeObjectToString = objectProto.toString; - /*--------------------------------------------------------------------------*/ + /** Used to infer the `Object` constructor. */ + var objectCtorString = funcToString.call(Object); + + /** Used to restore the original `_` reference in `_.noConflict`. */ + var oldDash = root._; + + /** Used to detect if a method is native. */ + var reIsNative = RegExp('^' + + funcToString.call(hasOwnProperty).replace(reRegExpChar, '\\$&') + .replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, '$1.*?') + '$' + ); + + /** Built-in value references. */ + var Buffer = moduleExports ? context.Buffer : undefined, + Symbol = context.Symbol, + Uint8Array = context.Uint8Array, + allocUnsafe = Buffer ? Buffer.allocUnsafe : undefined, + getPrototype = overArg(Object.getPrototypeOf, Object), + objectCreate = Object.create, + propertyIsEnumerable = objectProto.propertyIsEnumerable, + splice = arrayProto.splice, + spreadableSymbol = Symbol ? Symbol.isConcatSpreadable : undefined, + symIterator = Symbol ? Symbol.iterator : undefined, + symToStringTag = Symbol ? Symbol.toStringTag : undefined; + + var defineProperty = (function() { + try { + var func = getNative(Object, 'defineProperty'); + func({}, '', {}); + return func; + } catch (e) {} + }()); + + /** Mocked built-ins. */ + var ctxClearTimeout = context.clearTimeout !== root.clearTimeout && context.clearTimeout, + ctxNow = Date && Date.now !== root.Date.now && Date.now, + ctxSetTimeout = context.setTimeout !== root.setTimeout && context.setTimeout; + + /* Built-in method references for those with the same name as other `lodash` methods. */ + var nativeCeil = Math.ceil, + nativeFloor = Math.floor, + nativeGetSymbols = Object.getOwnPropertySymbols, + nativeIsBuffer = Buffer ? Buffer.isBuffer : undefined, + nativeIsFinite = context.isFinite, + nativeJoin = arrayProto.join, + nativeKeys = overArg(Object.keys, Object), + nativeMax = Math.max, + nativeMin = Math.min, + nativeNow = Date.now, + nativeParseInt = context.parseInt, + nativeRandom = Math.random, + nativeReverse = arrayProto.reverse; + + /* Built-in method references that are verified to be native. */ + var DataView = getNative(context, 'DataView'), + Map = getNative(context, 'Map'), + Promise = getNative(context, 'Promise'), + Set = getNative(context, 'Set'), + WeakMap = getNative(context, 'WeakMap'), + nativeCreate = getNative(Object, 'create'); + + /** Used to store function metadata. */ + var metaMap = WeakMap && new WeakMap; + + /** Used to lookup unminified function names. */ + var realNames = {}; + + /** Used to detect maps, sets, and weakmaps. */ + var dataViewCtorString = toSource(DataView), + mapCtorString = toSource(Map), + promiseCtorString = toSource(Promise), + setCtorString = toSource(Set), + weakMapCtorString = toSource(WeakMap); + + /** Used to convert symbols to primitives and strings. */ + var symbolProto = Symbol ? Symbol.prototype : undefined, + symbolValueOf = symbolProto ? symbolProto.valueOf : undefined, + symbolToString = symbolProto ? symbolProto.toString : undefined; + + /*------------------------------------------------------------------------*/ /** - * A faster alternative to `Function#apply`, this function invokes `func` - * with the `this` binding of `thisArg` and the arguments of `args`. + * Creates a `lodash` object which wraps `value` to enable implicit method + * chain sequences. Methods that operate on and return arrays, collections, + * and functions can be chained together. Methods that retrieve a single value + * or may return a primitive value will automatically end the chain sequence + * and return the unwrapped value. Otherwise, the value must be unwrapped + * with `_#value`. * - * @private - * @param {Function} func The function to invoke. - * @param {*} thisArg The `this` binding of `func`. - * @param {Array} args The arguments to invoke `func` with. - * @returns {*} Returns the result of `func`. + * Explicit chain sequences, which must be unwrapped with `_#value`, may be + * enabled using `_.chain`. + * + * The execution of chained methods is lazy, that is, it's deferred until + * `_#value` is implicitly or explicitly called. + * + * Lazy evaluation allows several methods to support shortcut fusion. + * Shortcut fusion is an optimization to merge iteratee calls; this avoids + * the creation of intermediate arrays and can greatly reduce the number of + * iteratee executions. Sections of a chain sequence qualify for shortcut + * fusion if the section is applied to an array and iteratees accept only + * one argument. The heuristic for whether a section qualifies for shortcut + * fusion is subject to change. + * + * Chaining is supported in custom builds as long as the `_#value` method is + * directly or indirectly included in the build. + * + * In addition to lodash methods, wrappers have `Array` and `String` methods. + * + * The wrapper `Array` methods are: + * `concat`, `join`, `pop`, `push`, `shift`, `sort`, `splice`, and `unshift` + * + * The wrapper `String` methods are: + * `replace` and `split` + * + * The wrapper methods that support shortcut fusion are: + * `at`, `compact`, `drop`, `dropRight`, `dropWhile`, `filter`, `find`, + * `findLast`, `head`, `initial`, `last`, `map`, `reject`, `reverse`, `slice`, + * `tail`, `take`, `takeRight`, `takeRightWhile`, `takeWhile`, and `toArray` + * + * The chainable wrapper methods are: + * `after`, `ary`, `assign`, `assignIn`, `assignInWith`, `assignWith`, `at`, + * `before`, `bind`, `bindAll`, `bindKey`, `castArray`, `chain`, `chunk`, + * `commit`, `compact`, `concat`, `conforms`, `constant`, `countBy`, `create`, + * `curry`, `debounce`, `defaults`, `defaultsDeep`, `defer`, `delay`, + * `difference`, `differenceBy`, `differenceWith`, `drop`, `dropRight`, + * `dropRightWhile`, `dropWhile`, `extend`, `extendWith`, `fill`, `filter`, + * `flatMap`, `flatMapDeep`, `flatMapDepth`, `flatten`, `flattenDeep`, + * `flattenDepth`, `flip`, `flow`, `flowRight`, `fromPairs`, `functions`, + * `functionsIn`, `groupBy`, `initial`, `intersection`, `intersectionBy`, + * `intersectionWith`, `invert`, `invertBy`, `invokeMap`, `iteratee`, `keyBy`, + * `keys`, `keysIn`, `map`, `mapKeys`, `mapValues`, `matches`, `matchesProperty`, + * `memoize`, `merge`, `mergeWith`, `method`, `methodOf`, `mixin`, `negate`, + * `nthArg`, `omit`, `omitBy`, `once`, `orderBy`, `over`, `overArgs`, + * `overEvery`, `overSome`, `partial`, `partialRight`, `partition`, `pick`, + * `pickBy`, `plant`, `property`, `propertyOf`, `pull`, `pullAll`, `pullAllBy`, + * `pullAllWith`, `pullAt`, `push`, `range`, `rangeRight`, `rearg`, `reject`, + * `remove`, `rest`, `reverse`, `sampleSize`, `set`, `setWith`, `shuffle`, + * `slice`, `sort`, `sortBy`, `splice`, `spread`, `tail`, `take`, `takeRight`, + * `takeRightWhile`, `takeWhile`, `tap`, `throttle`, `thru`, `toArray`, + * `toPairs`, `toPairsIn`, `toPath`, `toPlainObject`, `transform`, `unary`, + * `union`, `unionBy`, `unionWith`, `uniq`, `uniqBy`, `uniqWith`, `unset`, + * `unshift`, `unzip`, `unzipWith`, `update`, `updateWith`, `values`, + * `valuesIn`, `without`, `wrap`, `xor`, `xorBy`, `xorWith`, `zip`, + * `zipObject`, `zipObjectDeep`, and `zipWith` + * + * The wrapper methods that are **not** chainable by default are: + * `add`, `attempt`, `camelCase`, `capitalize`, `ceil`, `clamp`, `clone`, + * `cloneDeep`, `cloneDeepWith`, `cloneWith`, `conformsTo`, `deburr`, + * `defaultTo`, `divide`, `each`, `eachRight`, `endsWith`, `eq`, `escape`, + * `escapeRegExp`, `every`, `find`, `findIndex`, `findKey`, `findLast`, + * `findLastIndex`, `findLastKey`, `first`, `floor`, `forEach`, `forEachRight`, + * `forIn`, `forInRight`, `forOwn`, `forOwnRight`, `get`, `gt`, `gte`, `has`, + * `hasIn`, `head`, `identity`, `includes`, `indexOf`, `inRange`, `invoke`, + * `isArguments`, `isArray`, `isArrayBuffer`, `isArrayLike`, `isArrayLikeObject`, + * `isBoolean`, `isBuffer`, `isDate`, `isElement`, `isEmpty`, `isEqual`, + * `isEqualWith`, `isError`, `isFinite`, `isFunction`, `isInteger`, `isLength`, + * `isMap`, `isMatch`, `isMatchWith`, `isNaN`, `isNative`, `isNil`, `isNull`, + * `isNumber`, `isObject`, `isObjectLike`, `isPlainObject`, `isRegExp`, + * `isSafeInteger`, `isSet`, `isString`, `isUndefined`, `isTypedArray`, + * `isWeakMap`, `isWeakSet`, `join`, `kebabCase`, `last`, `lastIndexOf`, + * `lowerCase`, `lowerFirst`, `lt`, `lte`, `max`, `maxBy`, `mean`, `meanBy`, + * `min`, `minBy`, `multiply`, `noConflict`, `noop`, `now`, `nth`, `pad`, + * `padEnd`, `padStart`, `parseInt`, `pop`, `random`, `reduce`, `reduceRight`, + * `repeat`, `result`, `round`, `runInContext`, `sample`, `shift`, `size`, + * `snakeCase`, `some`, `sortedIndex`, `sortedIndexBy`, `sortedLastIndex`, + * `sortedLastIndexBy`, `startCase`, `startsWith`, `stubArray`, `stubFalse`, + * `stubObject`, `stubString`, `stubTrue`, `subtract`, `sum`, `sumBy`, + * `template`, `times`, `toFinite`, `toInteger`, `toJSON`, `toLength`, + * `toLower`, `toNumber`, `toSafeInteger`, `toString`, `toUpper`, `trim`, + * `trimEnd`, `trimStart`, `truncate`, `unescape`, `uniqueId`, `upperCase`, + * `upperFirst`, `value`, and `words` + * + * @name _ + * @constructor + * @category Seq + * @param {*} value The value to wrap in a `lodash` instance. + * @returns {Object} Returns the new `lodash` wrapper instance. + * @example + * + * function square(n) { + * return n * n; + * } + * + * var wrapped = _([1, 2, 3]); + * + * // Returns an unwrapped value. + * wrapped.reduce(_.add); + * // => 6 + * + * // Returns a wrapped value. + * var squares = wrapped.map(square); + * + * _.isArray(squares); + * // => false + * + * _.isArray(squares.value()); + * // => true */ - function apply(func, thisArg, args) { - switch (args.length) { - case 0: return func.call(thisArg); - case 1: return func.call(thisArg, args[0]); - case 2: return func.call(thisArg, args[0], args[1]); - case 3: return func.call(thisArg, args[0], args[1], args[2]); + function lodash(value) { + if (isObjectLike(value) && !isArray(value) && !(value instanceof LazyWrapper)) { + if (value instanceof LodashWrapper) { + return value; } - return func.apply(thisArg, args); + if (hasOwnProperty.call(value, '__wrapped__')) { + return wrapperClone(value); + } + } + return new LodashWrapper(value); } /** - * A specialized version of `baseAggregator` for arrays. + * The base implementation of `_.create` without support for assigning + * properties to the created object. * * @private - * @param {Array} [array] The array to iterate over. + * @param {Object} proto The object to inherit from. + * @returns {Object} Returns the new object. + */ + var baseCreate = (function() { + function object() {} + return function(proto) { + if (!isObject(proto)) { + return {}; + } + if (objectCreate) { + return objectCreate(proto); + } + object.prototype = proto; + var result = new object; + object.prototype = undefined; + return result; + }; + }()); + + /** + * The function whose prototype chain sequence wrappers inherit from. + * + * @private + */ + function baseLodash() { + // No operation performed. + } + + /** + * The base constructor for creating `lodash` wrapper objects. + * + * @private + * @param {*} value The value to wrap. + * @param {boolean} [chainAll] Enable explicit method chain sequences. + */ + function LodashWrapper(value, chainAll) { + this.__wrapped__ = value; + this.__actions__ = []; + this.__chain__ = !!chainAll; + this.__index__ = 0; + this.__values__ = undefined; + } + + /** + * By default, the template delimiters used by lodash are like those in + * embedded Ruby (ERB) as well as ES2015 template strings. Change the + * following template settings to use alternative delimiters. + * + * @static + * @memberOf _ + * @type {Object} + */ + lodash.templateSettings = { + + /** + * Used to detect `data` property values to be HTML-escaped. + * + * @memberOf _.templateSettings + * @type {RegExp} + */ + 'escape': reEscape, + + /** + * Used to detect code to be evaluated. + * + * @memberOf _.templateSettings + * @type {RegExp} + */ + 'evaluate': reEvaluate, + + /** + * Used to detect `data` property values to inject. + * + * @memberOf _.templateSettings + * @type {RegExp} + */ + 'interpolate': reInterpolate, + + /** + * Used to reference the data object in the template text. + * + * @memberOf _.templateSettings + * @type {string} + */ + 'variable': '', + + /** + * Used to import variables into the compiled template. + * + * @memberOf _.templateSettings + * @type {Object} + */ + 'imports': { + + /** + * A reference to the `lodash` function. + * + * @memberOf _.templateSettings.imports + * @type {Function} + */ + '_': lodash + } + }; + + // Ensure wrappers are instances of `baseLodash`. + lodash.prototype = baseLodash.prototype; + lodash.prototype.constructor = lodash; + + LodashWrapper.prototype = baseCreate(baseLodash.prototype); + LodashWrapper.prototype.constructor = LodashWrapper; + + /*------------------------------------------------------------------------*/ + + /** + * Creates a lazy wrapper object which wraps `value` to enable lazy evaluation. + * + * @private + * @constructor + * @param {*} value The value to wrap. + */ + function LazyWrapper(value) { + this.__wrapped__ = value; + this.__actions__ = []; + this.__dir__ = 1; + this.__filtered__ = false; + this.__iteratees__ = []; + this.__takeCount__ = MAX_ARRAY_LENGTH; + this.__views__ = []; + } + + /** + * Creates a clone of the lazy wrapper object. + * + * @private + * @name clone + * @memberOf LazyWrapper + * @returns {Object} Returns the cloned `LazyWrapper` object. + */ + function lazyClone() { + var result = new LazyWrapper(this.__wrapped__); + result.__actions__ = copyArray(this.__actions__); + result.__dir__ = this.__dir__; + result.__filtered__ = this.__filtered__; + result.__iteratees__ = copyArray(this.__iteratees__); + result.__takeCount__ = this.__takeCount__; + result.__views__ = copyArray(this.__views__); + return result; + } + + /** + * Reverses the direction of lazy iteration. + * + * @private + * @name reverse + * @memberOf LazyWrapper + * @returns {Object} Returns the new reversed `LazyWrapper` object. + */ + function lazyReverse() { + if (this.__filtered__) { + var result = new LazyWrapper(this); + result.__dir__ = -1; + result.__filtered__ = true; + } else { + result = this.clone(); + result.__dir__ *= -1; + } + return result; + } + + /** + * Extracts the unwrapped value from its lazy wrapper. + * + * @private + * @name value + * @memberOf LazyWrapper + * @returns {*} Returns the unwrapped value. + */ + function lazyValue() { + var array = this.__wrapped__.value(), + dir = this.__dir__, + isArr = isArray(array), + isRight = dir < 0, + arrLength = isArr ? array.length : 0, + view = getView(0, arrLength, this.__views__), + start = view.start, + end = view.end, + length = end - start, + index = isRight ? end : (start - 1), + iteratees = this.__iteratees__, + iterLength = iteratees.length, + resIndex = 0, + takeCount = nativeMin(length, this.__takeCount__); + + if (!isArr || (!isRight && arrLength == length && takeCount == length)) { + return baseWrapperValue(array, this.__actions__); + } + var result = []; + + outer: + while (length-- && resIndex < takeCount) { + index += dir; + + var iterIndex = -1, + value = array[index]; + + while (++iterIndex < iterLength) { + var data = iteratees[iterIndex], + iteratee = data.iteratee, + type = data.type, + computed = iteratee(value); + + if (type == LAZY_MAP_FLAG) { + value = computed; + } else if (!computed) { + if (type == LAZY_FILTER_FLAG) { + continue outer; + } else { + break outer; + } + } + } + result[resIndex++] = value; + } + return result; + } + + // Ensure `LazyWrapper` is an instance of `baseLodash`. + LazyWrapper.prototype = baseCreate(baseLodash.prototype); + LazyWrapper.prototype.constructor = LazyWrapper; + + /*------------------------------------------------------------------------*/ + + /** + * Creates a hash object. + * + * @private + * @constructor + * @param {Array} [entries] The key-value pairs to cache. + */ + function Hash(entries) { + var index = -1, + length = entries == null ? 0 : entries.length; + + this.clear(); + while (++index < length) { + var entry = entries[index]; + this.set(entry[0], entry[1]); + } + } + + /** + * Removes all key-value entries from the hash. + * + * @private + * @name clear + * @memberOf Hash + */ + function hashClear() { + this.__data__ = nativeCreate ? nativeCreate(null) : {}; + this.size = 0; + } + + /** + * Removes `key` and its value from the hash. + * + * @private + * @name delete + * @memberOf Hash + * @param {Object} hash The hash to modify. + * @param {string} key The key of the value to remove. + * @returns {boolean} Returns `true` if the entry was removed, else `false`. + */ + function hashDelete(key) { + var result = this.has(key) && delete this.__data__[key]; + this.size -= result ? 1 : 0; + return result; + } + + /** + * Gets the hash value for `key`. + * + * @private + * @name get + * @memberOf Hash + * @param {string} key The key of the value to get. + * @returns {*} Returns the entry value. + */ + function hashGet(key) { + var data = this.__data__; + if (nativeCreate) { + var result = data[key]; + return result === HASH_UNDEFINED ? undefined : result; + } + return hasOwnProperty.call(data, key) ? data[key] : undefined; + } + + /** + * Checks if a hash value for `key` exists. + * + * @private + * @name has + * @memberOf Hash + * @param {string} key The key of the entry to check. + * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. + */ + function hashHas(key) { + var data = this.__data__; + return nativeCreate ? (data[key] !== undefined) : hasOwnProperty.call(data, key); + } + + /** + * Sets the hash `key` to `value`. + * + * @private + * @name set + * @memberOf Hash + * @param {string} key The key of the value to set. + * @param {*} value The value to set. + * @returns {Object} Returns the hash instance. + */ + function hashSet(key, value) { + var data = this.__data__; + this.size += this.has(key) ? 0 : 1; + data[key] = (nativeCreate && value === undefined) ? HASH_UNDEFINED : value; + return this; + } + + // Add methods to `Hash`. + Hash.prototype.clear = hashClear; + Hash.prototype['delete'] = hashDelete; + Hash.prototype.get = hashGet; + Hash.prototype.has = hashHas; + Hash.prototype.set = hashSet; + + /*------------------------------------------------------------------------*/ + + /** + * Creates an list cache object. + * + * @private + * @constructor + * @param {Array} [entries] The key-value pairs to cache. + */ + function ListCache(entries) { + var index = -1, + length = entries == null ? 0 : entries.length; + + this.clear(); + while (++index < length) { + var entry = entries[index]; + this.set(entry[0], entry[1]); + } + } + + /** + * Removes all key-value entries from the list cache. + * + * @private + * @name clear + * @memberOf ListCache + */ + function listCacheClear() { + this.__data__ = []; + this.size = 0; + } + + /** + * Removes `key` and its value from the list cache. + * + * @private + * @name delete + * @memberOf ListCache + * @param {string} key The key of the value to remove. + * @returns {boolean} Returns `true` if the entry was removed, else `false`. + */ + function listCacheDelete(key) { + var data = this.__data__, + index = assocIndexOf(data, key); + + if (index < 0) { + return false; + } + var lastIndex = data.length - 1; + if (index == lastIndex) { + data.pop(); + } else { + splice.call(data, index, 1); + } + --this.size; + return true; + } + + /** + * Gets the list cache value for `key`. + * + * @private + * @name get + * @memberOf ListCache + * @param {string} key The key of the value to get. + * @returns {*} Returns the entry value. + */ + function listCacheGet(key) { + var data = this.__data__, + index = assocIndexOf(data, key); + + return index < 0 ? undefined : data[index][1]; + } + + /** + * Checks if a list cache value for `key` exists. + * + * @private + * @name has + * @memberOf ListCache + * @param {string} key The key of the entry to check. + * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. + */ + function listCacheHas(key) { + return assocIndexOf(this.__data__, key) > -1; + } + + /** + * Sets the list cache `key` to `value`. + * + * @private + * @name set + * @memberOf ListCache + * @param {string} key The key of the value to set. + * @param {*} value The value to set. + * @returns {Object} Returns the list cache instance. + */ + function listCacheSet(key, value) { + var data = this.__data__, + index = assocIndexOf(data, key); + + if (index < 0) { + ++this.size; + data.push([key, value]); + } else { + data[index][1] = value; + } + return this; + } + + // Add methods to `ListCache`. + ListCache.prototype.clear = listCacheClear; + ListCache.prototype['delete'] = listCacheDelete; + ListCache.prototype.get = listCacheGet; + ListCache.prototype.has = listCacheHas; + ListCache.prototype.set = listCacheSet; + + /*------------------------------------------------------------------------*/ + + /** + * Creates a map cache object to store key-value pairs. + * + * @private + * @constructor + * @param {Array} [entries] The key-value pairs to cache. + */ + function MapCache(entries) { + var index = -1, + length = entries == null ? 0 : entries.length; + + this.clear(); + while (++index < length) { + var entry = entries[index]; + this.set(entry[0], entry[1]); + } + } + + /** + * Removes all key-value entries from the map. + * + * @private + * @name clear + * @memberOf MapCache + */ + function mapCacheClear() { + this.size = 0; + this.__data__ = { + 'hash': new Hash, + 'map': new (Map || ListCache), + 'string': new Hash + }; + } + + /** + * Removes `key` and its value from the map. + * + * @private + * @name delete + * @memberOf MapCache + * @param {string} key The key of the value to remove. + * @returns {boolean} Returns `true` if the entry was removed, else `false`. + */ + function mapCacheDelete(key) { + var result = getMapData(this, key)['delete'](key); + this.size -= result ? 1 : 0; + return result; + } + + /** + * Gets the map value for `key`. + * + * @private + * @name get + * @memberOf MapCache + * @param {string} key The key of the value to get. + * @returns {*} Returns the entry value. + */ + function mapCacheGet(key) { + return getMapData(this, key).get(key); + } + + /** + * Checks if a map value for `key` exists. + * + * @private + * @name has + * @memberOf MapCache + * @param {string} key The key of the entry to check. + * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. + */ + function mapCacheHas(key) { + return getMapData(this, key).has(key); + } + + /** + * Sets the map `key` to `value`. + * + * @private + * @name set + * @memberOf MapCache + * @param {string} key The key of the value to set. + * @param {*} value The value to set. + * @returns {Object} Returns the map cache instance. + */ + function mapCacheSet(key, value) { + var data = getMapData(this, key), + size = data.size; + + data.set(key, value); + this.size += data.size == size ? 0 : 1; + return this; + } + + // Add methods to `MapCache`. + MapCache.prototype.clear = mapCacheClear; + MapCache.prototype['delete'] = mapCacheDelete; + MapCache.prototype.get = mapCacheGet; + MapCache.prototype.has = mapCacheHas; + MapCache.prototype.set = mapCacheSet; + + /*------------------------------------------------------------------------*/ + + /** + * + * Creates an array cache object to store unique values. + * + * @private + * @constructor + * @param {Array} [values] The values to cache. + */ + function SetCache(values) { + var index = -1, + length = values == null ? 0 : values.length; + + this.__data__ = new MapCache; + while (++index < length) { + this.add(values[index]); + } + } + + /** + * Adds `value` to the array cache. + * + * @private + * @name add + * @memberOf SetCache + * @alias push + * @param {*} value The value to cache. + * @returns {Object} Returns the cache instance. + */ + function setCacheAdd(value) { + this.__data__.set(value, HASH_UNDEFINED); + return this; + } + + /** + * Checks if `value` is in the array cache. + * + * @private + * @name has + * @memberOf SetCache + * @param {*} value The value to search for. + * @returns {number} Returns `true` if `value` is found, else `false`. + */ + function setCacheHas(value) { + return this.__data__.has(value); + } + + // Add methods to `SetCache`. + SetCache.prototype.add = SetCache.prototype.push = setCacheAdd; + SetCache.prototype.has = setCacheHas; + + /*------------------------------------------------------------------------*/ + + /** + * Creates a stack cache object to store key-value pairs. + * + * @private + * @constructor + * @param {Array} [entries] The key-value pairs to cache. + */ + function Stack(entries) { + var data = this.__data__ = new ListCache(entries); + this.size = data.size; + } + + /** + * Removes all key-value entries from the stack. + * + * @private + * @name clear + * @memberOf Stack + */ + function stackClear() { + this.__data__ = new ListCache; + this.size = 0; + } + + /** + * Removes `key` and its value from the stack. + * + * @private + * @name delete + * @memberOf Stack + * @param {string} key The key of the value to remove. + * @returns {boolean} Returns `true` if the entry was removed, else `false`. + */ + function stackDelete(key) { + var data = this.__data__, + result = data['delete'](key); + + this.size = data.size; + return result; + } + + /** + * Gets the stack value for `key`. + * + * @private + * @name get + * @memberOf Stack + * @param {string} key The key of the value to get. + * @returns {*} Returns the entry value. + */ + function stackGet(key) { + return this.__data__.get(key); + } + + /** + * Checks if a stack value for `key` exists. + * + * @private + * @name has + * @memberOf Stack + * @param {string} key The key of the entry to check. + * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. + */ + function stackHas(key) { + return this.__data__.has(key); + } + + /** + * Sets the stack `key` to `value`. + * + * @private + * @name set + * @memberOf Stack + * @param {string} key The key of the value to set. + * @param {*} value The value to set. + * @returns {Object} Returns the stack cache instance. + */ + function stackSet(key, value) { + var data = this.__data__; + if (data instanceof ListCache) { + var pairs = data.__data__; + if (!Map || (pairs.length < LARGE_ARRAY_SIZE - 1)) { + pairs.push([key, value]); + this.size = ++data.size; + return this; + } + data = this.__data__ = new MapCache(pairs); + } + data.set(key, value); + this.size = data.size; + return this; + } + + // Add methods to `Stack`. + Stack.prototype.clear = stackClear; + Stack.prototype['delete'] = stackDelete; + Stack.prototype.get = stackGet; + Stack.prototype.has = stackHas; + Stack.prototype.set = stackSet; + + /*------------------------------------------------------------------------*/ + + /** + * Creates an array of the enumerable property names of the array-like `value`. + * + * @private + * @param {*} value The value to query. + * @param {boolean} inherited Specify returning inherited property names. + * @returns {Array} Returns the array of property names. + */ + function arrayLikeKeys(value, inherited) { + var isArr = isArray(value), + isArg = !isArr && isArguments(value), + isBuff = !isArr && !isArg && isBuffer(value), + isType = !isArr && !isArg && !isBuff && isTypedArray(value), + skipIndexes = isArr || isArg || isBuff || isType, + result = skipIndexes ? baseTimes(value.length, String) : [], + length = result.length; + + for (var key in value) { + if ((inherited || hasOwnProperty.call(value, key)) && + !(skipIndexes && ( + // Safari 9 has enumerable `arguments.length` in strict mode. + key == 'length' || + // Node.js 0.10 has enumerable non-index properties on buffers. + (isBuff && (key == 'offset' || key == 'parent')) || + // PhantomJS 2 has enumerable non-index properties on typed arrays. + (isType && (key == 'buffer' || key == 'byteLength' || key == 'byteOffset')) || + // Skip index properties. + isIndex(key, length) + ))) { + result.push(key); + } + } + return result; + } + + /** + * A specialized version of `_.sample` for arrays. + * + * @private + * @param {Array} array The array to sample. + * @returns {*} Returns the random element. + */ + function arraySample(array) { + var length = array.length; + return length ? array[baseRandom(0, length - 1)] : undefined; + } + + /** + * A specialized version of `_.sampleSize` for arrays. + * + * @private + * @param {Array} array The array to sample. + * @param {number} n The number of elements to sample. + * @returns {Array} Returns the random elements. + */ + function arraySampleSize(array, n) { + return shuffleSelf(copyArray(array), baseClamp(n, 0, array.length)); + } + + /** + * A specialized version of `_.shuffle` for arrays. + * + * @private + * @param {Array} array The array to shuffle. + * @returns {Array} Returns the new shuffled array. + */ + function arrayShuffle(array) { + return shuffleSelf(copyArray(array)); + } + + /** + * This function is like `assignValue` except that it doesn't assign + * `undefined` values. + * + * @private + * @param {Object} object The object to modify. + * @param {string} key The key of the property to assign. + * @param {*} value The value to assign. + */ + function assignMergeValue(object, key, value) { + if ((value !== undefined && !eq(object[key], value)) || + (value === undefined && !(key in object))) { + baseAssignValue(object, key, value); + } + } + + /** + * Assigns `value` to `key` of `object` if the existing value is not equivalent + * using [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) + * for equality comparisons. + * + * @private + * @param {Object} object The object to modify. + * @param {string} key The key of the property to assign. + * @param {*} value The value to assign. + */ + function assignValue(object, key, value) { + var objValue = object[key]; + if (!(hasOwnProperty.call(object, key) && eq(objValue, value)) || + (value === undefined && !(key in object))) { + baseAssignValue(object, key, value); + } + } + + /** + * Gets the index at which the `key` is found in `array` of key-value pairs. + * + * @private + * @param {Array} array The array to inspect. + * @param {*} key The key to search for. + * @returns {number} Returns the index of the matched value, else `-1`. + */ + function assocIndexOf(array, key) { + var length = array.length; + while (length--) { + if (eq(array[length][0], key)) { + return length; + } + } + return -1; + } + + /** + * Aggregates elements of `collection` on `accumulator` with keys transformed + * by `iteratee` and values set by `setter`. + * + * @private + * @param {Array|Object} collection The collection to iterate over. * @param {Function} setter The function to set `accumulator` values. * @param {Function} iteratee The iteratee to transform keys. * @param {Object} accumulator The initial aggregated object. * @returns {Function} Returns `accumulator`. */ - function arrayAggregator(array, setter, iteratee, accumulator) { - var index = -1, - length = array == null ? 0 : array.length; - - while (++index < length) { - var value = array[index]; - setter(accumulator, value, iteratee(value), array); - } - return accumulator; + function baseAggregator(collection, setter, iteratee, accumulator) { + baseEach(collection, function(value, key, collection) { + setter(accumulator, value, iteratee(value), collection); + }); + return accumulator; } /** - * A specialized version of `_.forEach` for arrays without support for - * iteratee shorthands. + * The base implementation of `_.assign` without support for multiple sources + * or `customizer` functions. * * @private - * @param {Array} [array] The array to iterate over. - * @param {Function} iteratee The function invoked per iteration. - * @returns {Array} Returns `array`. + * @param {Object} object The destination object. + * @param {Object} source The source object. + * @returns {Object} Returns `object`. */ - function arrayEach(array, iteratee) { - var index = -1, - length = array == null ? 0 : array.length; - - while (++index < length) { - if (iteratee(array[index], index, array) === false) { - break; - } - } - return array; + function baseAssign(object, source) { + return object && copyObject(source, keys(source), object); } /** - * A specialized version of `_.forEachRight` for arrays without support for - * iteratee shorthands. + * The base implementation of `_.assignIn` without support for multiple sources + * or `customizer` functions. * * @private - * @param {Array} [array] The array to iterate over. - * @param {Function} iteratee The function invoked per iteration. - * @returns {Array} Returns `array`. + * @param {Object} object The destination object. + * @param {Object} source The source object. + * @returns {Object} Returns `object`. */ - function arrayEachRight(array, iteratee) { - var length = array == null ? 0 : array.length; - - while (length--) { - if (iteratee(array[length], length, array) === false) { - break; - } - } - return array; + function baseAssignIn(object, source) { + return object && copyObject(source, keysIn(source), object); } /** - * A specialized version of `_.every` for arrays without support for - * iteratee shorthands. + * The base implementation of `assignValue` and `assignMergeValue` without + * value checks. * * @private - * @param {Array} [array] The array to iterate over. - * @param {Function} predicate The function invoked per iteration. - * @returns {boolean} Returns `true` if all elements pass the predicate check, - * else `false`. + * @param {Object} object The object to modify. + * @param {string} key The key of the property to assign. + * @param {*} value The value to assign. */ - function arrayEvery(array, predicate) { - var index = -1, - length = array == null ? 0 : array.length; - - while (++index < length) { - if (!predicate(array[index], index, array)) { - return false; - } - } - return true; - } - - /** - * A specialized version of `_.filter` for arrays without support for - * iteratee shorthands. - * - * @private - * @param {Array} [array] The array to iterate over. - * @param {Function} predicate The function invoked per iteration. - * @returns {Array} Returns the new filtered array. - */ - function arrayFilter(array, predicate) { - var index = -1, - length = array == null ? 0 : array.length, - resIndex = 0, - result = []; - - while (++index < length) { - var value = array[index]; - if (predicate(value, index, array)) { - result[resIndex++] = value; - } - } - return result; - } - - /** - * A specialized version of `_.includes` for arrays without support for - * specifying an index to search from. - * - * @private - * @param {Array} [array] The array to inspect. - * @param {*} target The value to search for. - * @returns {boolean} Returns `true` if `target` is found, else `false`. - */ - function arrayIncludes(array, value) { - var length = array == null ? 0 : array.length; - return !!length && baseIndexOf(array, value, 0) > -1; - } - - /** - * This function is like `arrayIncludes` except that it accepts a comparator. - * - * @private - * @param {Array} [array] The array to inspect. - * @param {*} target The value to search for. - * @param {Function} comparator The comparator invoked per element. - * @returns {boolean} Returns `true` if `target` is found, else `false`. - */ - function arrayIncludesWith(array, value, comparator) { - var index = -1, - length = array == null ? 0 : array.length; - - while (++index < length) { - if (comparator(value, array[index])) { - return true; - } - } - return false; - } - - /** - * A specialized version of `_.map` for arrays without support for iteratee - * shorthands. - * - * @private - * @param {Array} [array] The array to iterate over. - * @param {Function} iteratee The function invoked per iteration. - * @returns {Array} Returns the new mapped array. - */ - function arrayMap(array, iteratee) { - var index = -1, - length = array == null ? 0 : array.length, - result = Array(length); - - while (++index < length) { - result[index] = iteratee(array[index], index, array); - } - return result; - } - - /** - * Appends the elements of `values` to `array`. - * - * @private - * @param {Array} array The array to modify. - * @param {Array} values The values to append. - * @returns {Array} Returns `array`. - */ - function arrayPush(array, values) { - var index = -1, - length = values.length, - offset = array.length; - - while (++index < length) { - array[offset + index] = values[index]; - } - return array; - } - - /** - * A specialized version of `_.reduce` for arrays without support for - * iteratee shorthands. - * - * @private - * @param {Array} [array] The array to iterate over. - * @param {Function} iteratee The function invoked per iteration. - * @param {*} [accumulator] The initial value. - * @param {boolean} [initAccum] Specify using the first element of `array` as - * the initial value. - * @returns {*} Returns the accumulated value. - */ - function arrayReduce(array, iteratee, accumulator, initAccum) { - var index = -1, - length = array == null ? 0 : array.length; - - if (initAccum && length) { - accumulator = array[++index]; - } - while (++index < length) { - accumulator = iteratee(accumulator, array[index], index, array); - } - return accumulator; - } - - /** - * A specialized version of `_.reduceRight` for arrays without support for - * iteratee shorthands. - * - * @private - * @param {Array} [array] The array to iterate over. - * @param {Function} iteratee The function invoked per iteration. - * @param {*} [accumulator] The initial value. - * @param {boolean} [initAccum] Specify using the last element of `array` as - * the initial value. - * @returns {*} Returns the accumulated value. - */ - function arrayReduceRight(array, iteratee, accumulator, initAccum) { - var length = array == null ? 0 : array.length; - if (initAccum && length) { - accumulator = array[--length]; - } - while (length--) { - accumulator = iteratee(accumulator, array[length], length, array); - } - return accumulator; - } - - /** - * A specialized version of `_.some` for arrays without support for iteratee - * shorthands. - * - * @private - * @param {Array} [array] The array to iterate over. - * @param {Function} predicate The function invoked per iteration. - * @returns {boolean} Returns `true` if any element passes the predicate check, - * else `false`. - */ - function arraySome(array, predicate) { - var index = -1, - length = array == null ? 0 : array.length; - - while (++index < length) { - if (predicate(array[index], index, array)) { - return true; - } - } - return false; - } - - /** - * Gets the size of an ASCII `string`. - * - * @private - * @param {string} string The string inspect. - * @returns {number} Returns the string size. - */ - var asciiSize = baseProperty('length'); - - /** - * Converts an ASCII `string` to an array. - * - * @private - * @param {string} string The string to convert. - * @returns {Array} Returns the converted array. - */ - function asciiToArray(string) { - return string.split(''); - } - - /** - * Splits an ASCII `string` into an array of its words. - * - * @private - * @param {string} The string to inspect. - * @returns {Array} Returns the words of `string`. - */ - function asciiWords(string) { - return string.match(reAsciiWord) || []; - } - - /** - * The base implementation of methods like `_.findKey` and `_.findLastKey`, - * without support for iteratee shorthands, which iterates over `collection` - * using `eachFunc`. - * - * @private - * @param {Array|Object} collection The collection to inspect. - * @param {Function} predicate The function invoked per iteration. - * @param {Function} eachFunc The function to iterate over `collection`. - * @returns {*} Returns the found element or its key, else `undefined`. - */ - function baseFindKey(collection, predicate, eachFunc) { - var result; - eachFunc(collection, function(value, key, collection) { - if (predicate(value, key, collection)) { - result = key; - return false; - } + function baseAssignValue(object, key, value) { + if (key == '__proto__' && defineProperty) { + defineProperty(object, key, { + 'configurable': true, + 'enumerable': true, + 'value': value, + 'writable': true }); + } else { + object[key] = value; + } + } + + /** + * The base implementation of `_.at` without support for individual paths. + * + * @private + * @param {Object} object The object to iterate over. + * @param {string[]} paths The property paths to pick. + * @returns {Array} Returns the picked elements. + */ + function baseAt(object, paths) { + var index = -1, + length = paths.length, + result = Array(length), + skip = object == null; + + while (++index < length) { + result[index] = skip ? undefined : get(object, paths[index]); + } + return result; + } + + /** + * The base implementation of `_.clamp` which doesn't coerce arguments. + * + * @private + * @param {number} number The number to clamp. + * @param {number} [lower] The lower bound. + * @param {number} upper The upper bound. + * @returns {number} Returns the clamped number. + */ + function baseClamp(number, lower, upper) { + if (number === number) { + if (upper !== undefined) { + number = number <= upper ? number : upper; + } + if (lower !== undefined) { + number = number >= lower ? number : lower; + } + } + return number; + } + + /** + * The base implementation of `_.clone` and `_.cloneDeep` which tracks + * traversed objects. + * + * @private + * @param {*} value The value to clone. + * @param {boolean} bitmask The bitmask flags. + * 1 - Deep clone + * 2 - Flatten inherited properties + * 4 - Clone symbols + * @param {Function} [customizer] The function to customize cloning. + * @param {string} [key] The key of `value`. + * @param {Object} [object] The parent object of `value`. + * @param {Object} [stack] Tracks traversed objects and their clone counterparts. + * @returns {*} Returns the cloned value. + */ + function baseClone(value, bitmask, customizer, key, object, stack) { + var result, + isDeep = bitmask & CLONE_DEEP_FLAG, + isFlat = bitmask & CLONE_FLAT_FLAG, + isFull = bitmask & CLONE_SYMBOLS_FLAG; + + if (customizer) { + result = object ? customizer(value, key, object, stack) : customizer(value); + } + if (result !== undefined) { return result; - } - - /** - * The base implementation of `_.findIndex` and `_.findLastIndex` without - * support for iteratee shorthands. - * - * @private - * @param {Array} array The array to inspect. - * @param {Function} predicate The function invoked per iteration. - * @param {number} fromIndex The index to search from. - * @param {boolean} [fromRight] Specify iterating from right to left. - * @returns {number} Returns the index of the matched value, else `-1`. - */ - function baseFindIndex(array, predicate, fromIndex, fromRight) { - var length = array.length, - index = fromIndex + (fromRight ? 1 : -1); - - while ((fromRight ? index-- : ++index < length)) { - if (predicate(array[index], index, array)) { - return index; - } + } + if (!isObject(value)) { + return value; + } + var isArr = isArray(value); + if (isArr) { + result = initCloneArray(value); + if (!isDeep) { + return copyArray(value, result); } - return -1; - } + } else { + var tag = getTag(value), + isFunc = tag == funcTag || tag == genTag; - /** - * The base implementation of `_.indexOf` without `fromIndex` bounds checks. - * - * @private - * @param {Array} array The array to inspect. - * @param {*} value The value to search for. - * @param {number} fromIndex The index to search from. - * @returns {number} Returns the index of the matched value, else `-1`. - */ - function baseIndexOf(array, value, fromIndex) { - return value === value - ? strictIndexOf(array, value, fromIndex) - : baseFindIndex(array, baseIsNaN, fromIndex); - } - - /** - * This function is like `baseIndexOf` except that it accepts a comparator. - * - * @private - * @param {Array} array The array to inspect. - * @param {*} value The value to search for. - * @param {number} fromIndex The index to search from. - * @param {Function} comparator The comparator invoked per element. - * @returns {number} Returns the index of the matched value, else `-1`. - */ - function baseIndexOfWith(array, value, fromIndex, comparator) { - var index = fromIndex - 1, - length = array.length; - - while (++index < length) { - if (comparator(array[index], value)) { - return index; - } + if (isBuffer(value)) { + return cloneBuffer(value, isDeep); } - return -1; + if (tag == objectTag || tag == argsTag || (isFunc && !object)) { + result = (isFlat || isFunc) ? {} : initCloneObject(value); + if (!isDeep) { + return isFlat + ? copySymbolsIn(value, baseAssignIn(result, value)) + : copySymbols(value, baseAssign(result, value)); + } + } else { + if (!cloneableTags[tag]) { + return object ? value : {}; + } + result = initCloneByTag(value, tag, isDeep); + } + } + // Check for circular references and return its corresponding clone. + stack || (stack = new Stack); + var stacked = stack.get(value); + if (stacked) { + return stacked; + } + stack.set(value, result); + + if (isSet(value)) { + value.forEach(function(subValue) { + result.add(baseClone(subValue, bitmask, customizer, subValue, value, stack)); + }); + } else if (isMap(value)) { + value.forEach(function(subValue, key) { + result.set(key, baseClone(subValue, bitmask, customizer, key, value, stack)); + }); + } + + var keysFunc = isFull + ? (isFlat ? getAllKeysIn : getAllKeys) + : (isFlat ? keysIn : keys); + + var props = isArr ? undefined : keysFunc(value); + arrayEach(props || value, function(subValue, key) { + if (props) { + key = subValue; + subValue = value[key]; + } + // Recursively populate clone (susceptible to call stack limits). + assignValue(result, key, baseClone(subValue, bitmask, customizer, key, value, stack)); + }); + return result; } /** - * The base implementation of `_.isNaN` without support for number objects. + * The base implementation of `_.conforms` which doesn't clone `source`. * * @private - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is `NaN`, else `false`. + * @param {Object} source The object of property predicates to conform to. + * @returns {Function} Returns the new spec function. */ - function baseIsNaN(value) { - return value !== value; + function baseConforms(source) { + var props = keys(source); + return function(object) { + return baseConformsTo(object, source, props); + }; } /** - * The base implementation of `_.mean` and `_.meanBy` without support for - * iteratee shorthands. + * The base implementation of `_.conformsTo` which accepts `props` to check. * * @private - * @param {Array} array The array to iterate over. - * @param {Function} iteratee The function invoked per iteration. - * @returns {number} Returns the mean. + * @param {Object} object The object to inspect. + * @param {Object} source The object of property predicates to conform to. + * @returns {boolean} Returns `true` if `object` conforms, else `false`. */ - function baseMean(array, iteratee) { - var length = array == null ? 0 : array.length; - return length ? (baseSum(array, iteratee) / length) : NAN; + function baseConformsTo(object, source, props) { + var length = props.length; + if (object == null) { + return !length; + } + object = Object(object); + while (length--) { + var key = props[length], + predicate = source[key], + value = object[key]; + + if ((value === undefined && !(key in object)) || !predicate(value)) { + return false; + } + } + return true; } /** - * The base implementation of `_.property` without support for deep paths. + * The base implementation of `_.delay` and `_.defer` which accepts `args` + * to provide to `func`. * * @private - * @param {string} key The key of the property to get. - * @returns {Function} Returns the new accessor function. + * @param {Function} func The function to delay. + * @param {number} wait The number of milliseconds to delay invocation. + * @param {Array} args The arguments to provide to `func`. + * @returns {number|Object} Returns the timer id or timeout object. */ - function baseProperty(key) { - return function(object) { - return object == null ? undefined : object[key]; - }; + function baseDelay(func, wait, args) { + if (typeof func != 'function') { + throw new TypeError(FUNC_ERROR_TEXT); + } + return setTimeout(function() { func.apply(undefined, args); }, wait); } /** - * The base implementation of `_.propertyOf` without support for deep paths. + * The base implementation of methods like `_.difference` without support + * for excluding multiple arrays or iteratee shorthands. * * @private - * @param {Object} object The object to query. - * @returns {Function} Returns the new accessor function. + * @param {Array} array The array to inspect. + * @param {Array} values The values to exclude. + * @param {Function} [iteratee] The iteratee invoked per element. + * @param {Function} [comparator] The comparator invoked per element. + * @returns {Array} Returns the new array of filtered values. */ - function basePropertyOf(object) { - return function(key) { - return object == null ? undefined : object[key]; - }; + function baseDifference(array, values, iteratee, comparator) { + var index = -1, + includes = arrayIncludes, + isCommon = true, + length = array.length, + result = [], + valuesLength = values.length; + + if (!length) { + return result; + } + if (iteratee) { + values = arrayMap(values, baseUnary(iteratee)); + } + if (comparator) { + includes = arrayIncludesWith; + isCommon = false; + } + else if (values.length >= LARGE_ARRAY_SIZE) { + includes = cacheHas; + isCommon = false; + values = new SetCache(values); + } + outer: + while (++index < length) { + var value = array[index], + computed = iteratee == null ? value : iteratee(value); + + value = (comparator || value !== 0) ? value : 0; + if (isCommon && computed === computed) { + var valuesIndex = valuesLength; + while (valuesIndex--) { + if (values[valuesIndex] === computed) { + continue outer; + } + } + result.push(value); + } + else if (!includes(values, computed, comparator)) { + result.push(value); + } + } + return result; } /** - * The base implementation of `_.reduce` and `_.reduceRight`, without support - * for iteratee shorthands, which iterates over `collection` using `eachFunc`. + * The base implementation of `_.forEach` without support for iteratee shorthands. * * @private * @param {Array|Object} collection The collection to iterate over. * @param {Function} iteratee The function invoked per iteration. - * @param {*} accumulator The initial value. - * @param {boolean} initAccum Specify using the first or last element of - * `collection` as the initial value. - * @param {Function} eachFunc The function to iterate over `collection`. - * @returns {*} Returns the accumulated value. + * @returns {Array|Object} Returns `collection`. */ - function baseReduce(collection, iteratee, accumulator, initAccum, eachFunc) { - eachFunc(collection, function(value, index, collection) { - accumulator = initAccum - ? (initAccum = false, value) - : iteratee(accumulator, value, index, collection); - }); - return accumulator; - } + var baseEach = createBaseEach(baseForOwn); /** - * The base implementation of `_.sortBy` which uses `comparer` to define the - * sort order of `array` and replaces criteria objects with their corresponding - * values. + * The base implementation of `_.forEachRight` without support for iteratee shorthands. * * @private - * @param {Array} array The array to sort. - * @param {Function} comparer The function to define sort order. - * @returns {Array} Returns `array`. + * @param {Array|Object} collection The collection to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @returns {Array|Object} Returns `collection`. */ - function baseSortBy(array, comparer) { - var length = array.length; + var baseEachRight = createBaseEach(baseForOwnRight, true); - array.sort(comparer); - while (length--) { - array[length] = array[length].value; - } - return array; + /** + * The base implementation of `_.every` without support for iteratee shorthands. + * + * @private + * @param {Array|Object} collection The collection to iterate over. + * @param {Function} predicate The function invoked per iteration. + * @returns {boolean} Returns `true` if all elements pass the predicate check, + * else `false` + */ + function baseEvery(collection, predicate) { + var result = true; + baseEach(collection, function(value, index, collection) { + result = !!predicate(value, index, collection); + return result; + }); + return result; } /** - * The base implementation of `_.sum` and `_.sumBy` without support for - * iteratee shorthands. + * The base implementation of methods like `_.max` and `_.min` which accepts a + * `comparator` to determine the extremum value. * * @private * @param {Array} array The array to iterate over. - * @param {Function} iteratee The function invoked per iteration. - * @returns {number} Returns the sum. + * @param {Function} iteratee The iteratee invoked per iteration. + * @param {Function} comparator The comparator used to compare values. + * @returns {*} Returns the extremum value. */ - function baseSum(array, iteratee) { - var result, - index = -1, - length = array.length; + function baseExtremum(array, iteratee, comparator) { + var index = -1, + length = array.length; - while (++index < length) { - var current = iteratee(array[index]); - if (current !== undefined) { - result = result === undefined ? current : (result + current); - } + while (++index < length) { + var value = array[index], + current = iteratee(value); + + if (current != null && (computed === undefined + ? (current === current && !isSymbol(current)) + : comparator(current, computed) + )) { + var computed = current, + result = value; } - return result; + } + return result; } /** - * The base implementation of `_.times` without support for iteratee shorthands - * or max array length checks. + * The base implementation of `_.fill` without an iteratee call guard. * * @private - * @param {number} n The number of times to invoke `iteratee`. - * @param {Function} iteratee The function invoked per iteration. - * @returns {Array} Returns the array of results. + * @param {Array} array The array to fill. + * @param {*} value The value to fill `array` with. + * @param {number} [start=0] The start position. + * @param {number} [end=array.length] The end position. + * @returns {Array} Returns `array`. */ - function baseTimes(n, iteratee) { - var index = -1, - result = Array(n); + function baseFill(array, value, start, end) { + var length = array.length; - while (++index < n) { - result[index] = iteratee(index); - } - return result; + start = toInteger(start); + if (start < 0) { + start = -start > length ? 0 : (length + start); + } + end = (end === undefined || end > length) ? length : toInteger(end); + if (end < 0) { + end += length; + } + end = start > end ? 0 : toLength(end); + while (start < end) { + array[start++] = value; + } + return array; } /** - * The base implementation of `_.toPairs` and `_.toPairsIn` which creates an array - * of key-value pairs for `object` corresponding to the property names of `props`. + * The base implementation of `_.filter` without support for iteratee shorthands. + * + * @private + * @param {Array|Object} collection The collection to iterate over. + * @param {Function} predicate The function invoked per iteration. + * @returns {Array} Returns the new filtered array. + */ + function baseFilter(collection, predicate) { + var result = []; + baseEach(collection, function(value, index, collection) { + if (predicate(value, index, collection)) { + result.push(value); + } + }); + return result; + } + + /** + * The base implementation of `_.flatten` with support for restricting flattening. + * + * @private + * @param {Array} array The array to flatten. + * @param {number} depth The maximum recursion depth. + * @param {boolean} [predicate=isFlattenable] The function invoked per iteration. + * @param {boolean} [isStrict] Restrict to values that pass `predicate` checks. + * @param {Array} [result=[]] The initial result value. + * @returns {Array} Returns the new flattened array. + */ + function baseFlatten(array, depth, predicate, isStrict, result) { + var index = -1, + length = array.length; + + predicate || (predicate = isFlattenable); + result || (result = []); + + while (++index < length) { + var value = array[index]; + if (depth > 0 && predicate(value)) { + if (depth > 1) { + // Recursively flatten arrays (susceptible to call stack limits). + baseFlatten(value, depth - 1, predicate, isStrict, result); + } else { + arrayPush(result, value); + } + } else if (!isStrict) { + result[result.length] = value; + } + } + return result; + } + + /** + * The base implementation of `baseForOwn` which iterates over `object` + * properties returned by `keysFunc` and invokes `iteratee` for each property. + * Iteratee functions may exit iteration early by explicitly returning `false`. + * + * @private + * @param {Object} object The object to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @param {Function} keysFunc The function to get the keys of `object`. + * @returns {Object} Returns `object`. + */ + var baseFor = createBaseFor(); + + /** + * This function is like `baseFor` except that it iterates over properties + * in the opposite order. + * + * @private + * @param {Object} object The object to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @param {Function} keysFunc The function to get the keys of `object`. + * @returns {Object} Returns `object`. + */ + var baseForRight = createBaseFor(true); + + /** + * The base implementation of `_.forOwn` without support for iteratee shorthands. + * + * @private + * @param {Object} object The object to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @returns {Object} Returns `object`. + */ + function baseForOwn(object, iteratee) { + return object && baseFor(object, iteratee, keys); + } + + /** + * The base implementation of `_.forOwnRight` without support for iteratee shorthands. + * + * @private + * @param {Object} object The object to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @returns {Object} Returns `object`. + */ + function baseForOwnRight(object, iteratee) { + return object && baseForRight(object, iteratee, keys); + } + + /** + * The base implementation of `_.functions` which creates an array of + * `object` function property names filtered from `props`. + * + * @private + * @param {Object} object The object to inspect. + * @param {Array} props The property names to filter. + * @returns {Array} Returns the function names. + */ + function baseFunctions(object, props) { + return arrayFilter(props, function(key) { + return isFunction(object[key]); + }); + } + + /** + * The base implementation of `_.get` without support for default values. * * @private * @param {Object} object The object to query. - * @param {Array} props The property names to get values for. - * @returns {Object} Returns the key-value pairs. + * @param {Array|string} path The path of the property to get. + * @returns {*} Returns the resolved value. */ - function baseToPairs(object, props) { - return arrayMap(props, function(key) { - return [key, object[key]]; - }); + function baseGet(object, path) { + path = castPath(path, object); + + var index = 0, + length = path.length; + + while (object != null && index < length) { + object = object[toKey(path[index++])]; + } + return (index && index == length) ? object : undefined; } /** - * The base implementation of `_.unary` without support for storing metadata. - * - * @private - * @param {Function} func The function to cap arguments for. - * @returns {Function} Returns the new capped function. - */ - function baseUnary(func) { - return function(value) { - return func(value); - }; - } - - /** - * The base implementation of `_.values` and `_.valuesIn` which creates an - * array of `object` property values corresponding to the property names - * of `props`. + * The base implementation of `getAllKeys` and `getAllKeysIn` which uses + * `keysFunc` and `symbolsFunc` to get the enumerable property names and + * symbols of `object`. * * @private * @param {Object} object The object to query. - * @param {Array} props The property names to get values for. - * @returns {Object} Returns the array of property values. + * @param {Function} keysFunc The function to get the keys of `object`. + * @param {Function} symbolsFunc The function to get the symbols of `object`. + * @returns {Array} Returns the array of property names and symbols. */ - function baseValues(object, props) { - return arrayMap(props, function(key) { - return object[key]; - }); + function baseGetAllKeys(object, keysFunc, symbolsFunc) { + var result = keysFunc(object); + return isArray(object) ? result : arrayPush(result, symbolsFunc(object)); } /** - * Checks if a `cache` value for `key` exists. + * The base implementation of `getTag` without fallbacks for buggy environments. * * @private - * @param {Object} cache The cache to query. - * @param {string} key The key of the entry to check. - * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. + * @param {*} value The value to query. + * @returns {string} Returns the `toStringTag`. */ - function cacheHas(cache, key) { - return cache.has(key); + function baseGetTag(value) { + if (value == null) { + return value === undefined ? undefinedTag : nullTag; + } + return (symToStringTag && symToStringTag in Object(value)) + ? getRawTag(value) + : objectToString(value); } /** - * Used by `_.trim` and `_.trimStart` to get the index of the first string symbol - * that is not found in the character symbols. + * The base implementation of `_.gt` which doesn't coerce arguments. * * @private - * @param {Array} strSymbols The string symbols to inspect. - * @param {Array} chrSymbols The character symbols to find. - * @returns {number} Returns the index of the first unmatched string symbol. + * @param {*} value The value to compare. + * @param {*} other The other value to compare. + * @returns {boolean} Returns `true` if `value` is greater than `other`, + * else `false`. */ - function charsStartIndex(strSymbols, chrSymbols) { - var index = -1, - length = strSymbols.length; - - while (++index < length && baseIndexOf(chrSymbols, strSymbols[index], 0) > -1) {} - return index; + function baseGt(value, other) { + return value > other; } /** - * Used by `_.trim` and `_.trimEnd` to get the index of the last string symbol - * that is not found in the character symbols. - * - * @private - * @param {Array} strSymbols The string symbols to inspect. - * @param {Array} chrSymbols The character symbols to find. - * @returns {number} Returns the index of the last unmatched string symbol. - */ - function charsEndIndex(strSymbols, chrSymbols) { - var index = strSymbols.length; - - while (index-- && baseIndexOf(chrSymbols, strSymbols[index], 0) > -1) {} - return index; - } - - /** - * Gets the number of `placeholder` occurrences in `array`. - * - * @private - * @param {Array} array The array to inspect. - * @param {*} placeholder The placeholder to search for. - * @returns {number} Returns the placeholder count. - */ - function countHolders(array, placeholder) { - var length = array.length, - result = 0; - - while (length--) { - if (array[length] === placeholder) { - ++result; - } - } - return result; - } - - /** - * Used by `_.deburr` to convert Latin-1 Supplement and Latin Extended-A - * letters to basic Latin letters. - * - * @private - * @param {string} letter The matched letter to deburr. - * @returns {string} Returns the deburred letter. - */ - var deburrLetter = basePropertyOf(deburredLetters); - - /** - * Used by `_.escape` to convert characters to HTML entities. - * - * @private - * @param {string} chr The matched character to escape. - * @returns {string} Returns the escaped character. - */ - var escapeHtmlChar = basePropertyOf(htmlEscapes); - - /** - * Used by `_.template` to escape characters for inclusion in compiled string literals. - * - * @private - * @param {string} chr The matched character to escape. - * @returns {string} Returns the escaped character. - */ - function escapeStringChar(chr) { - return '\\' + stringEscapes[chr]; - } - - /** - * Gets the value at `key` of `object`. + * The base implementation of `_.has` without support for deep paths. * * @private * @param {Object} [object] The object to query. - * @param {string} key The key of the property to get. - * @returns {*} Returns the property value. + * @param {Array|string} key The key to check. + * @returns {boolean} Returns `true` if `key` exists, else `false`. */ - function getValue(object, key) { - return object == null ? undefined : object[key]; + function baseHas(object, key) { + return object != null && hasOwnProperty.call(object, key); } /** - * Checks if `string` contains Unicode symbols. + * The base implementation of `_.hasIn` without support for deep paths. * * @private - * @param {string} string The string to inspect. - * @returns {boolean} Returns `true` if a symbol is found, else `false`. + * @param {Object} [object] The object to query. + * @param {Array|string} key The key to check. + * @returns {boolean} Returns `true` if `key` exists, else `false`. */ - function hasUnicode(string) { - return reHasUnicode.test(string); + function baseHasIn(object, key) { + return object != null && key in Object(object); } /** - * Checks if `string` contains a word composed of Unicode symbols. + * The base implementation of `_.inRange` which doesn't coerce arguments. * * @private - * @param {string} string The string to inspect. - * @returns {boolean} Returns `true` if a word is found, else `false`. + * @param {number} number The number to check. + * @param {number} start The start of the range. + * @param {number} end The end of the range. + * @returns {boolean} Returns `true` if `number` is in the range, else `false`. */ - function hasUnicodeWord(string) { - return reHasUnicodeWord.test(string); + function baseInRange(number, start, end) { + return number >= nativeMin(start, end) && number < nativeMax(start, end); } /** - * Converts `iterator` to an array. + * The base implementation of methods like `_.intersection`, without support + * for iteratee shorthands, that accepts an array of arrays to inspect. * * @private - * @param {Object} iterator The iterator to convert. - * @returns {Array} Returns the converted array. + * @param {Array} arrays The arrays to inspect. + * @param {Function} [iteratee] The iteratee invoked per element. + * @param {Function} [comparator] The comparator invoked per element. + * @returns {Array} Returns the new array of shared values. */ - function iteratorToArray(iterator) { - var data, - result = []; + function baseIntersection(arrays, iteratee, comparator) { + var includes = comparator ? arrayIncludesWith : arrayIncludes, + length = arrays[0].length, + othLength = arrays.length, + othIndex = othLength, + caches = Array(othLength), + maxLength = Infinity, + result = []; - while (!(data = iterator.next()).done) { - result.push(data.value); + while (othIndex--) { + var array = arrays[othIndex]; + if (othIndex && iteratee) { + array = arrayMap(array, baseUnary(iteratee)); } - return result; + maxLength = nativeMin(array.length, maxLength); + caches[othIndex] = !comparator && (iteratee || (length >= 120 && array.length >= 120)) + ? new SetCache(othIndex && array) + : undefined; + } + array = arrays[0]; + + var index = -1, + seen = caches[0]; + + outer: + while (++index < length && result.length < maxLength) { + var value = array[index], + computed = iteratee ? iteratee(value) : value; + + value = (comparator || value !== 0) ? value : 0; + if (!(seen + ? cacheHas(seen, computed) + : includes(result, computed, comparator) + )) { + othIndex = othLength; + while (--othIndex) { + var cache = caches[othIndex]; + if (!(cache + ? cacheHas(cache, computed) + : includes(arrays[othIndex], computed, comparator)) + ) { + continue outer; + } + } + if (seen) { + seen.push(computed); + } + result.push(value); + } + } + return result; } /** - * Converts `map` to its key-value pairs. + * The base implementation of `_.invert` and `_.invertBy` which inverts + * `object` with values transformed by `iteratee` and set by `setter`. * * @private - * @param {Object} map The map to convert. - * @returns {Array} Returns the key-value pairs. + * @param {Object} object The object to iterate over. + * @param {Function} setter The function to set `accumulator` values. + * @param {Function} iteratee The iteratee to transform values. + * @param {Object} accumulator The initial inverted object. + * @returns {Function} Returns `accumulator`. */ - function mapToArray(map) { - var index = -1, - result = Array(map.size); + function baseInverter(object, setter, iteratee, accumulator) { + baseForOwn(object, function(value, key, object) { + setter(accumulator, iteratee(value), key, object); + }); + return accumulator; + } - map.forEach(function(value, key) { - result[++index] = [key, value]; + /** + * The base implementation of `_.invoke` without support for individual + * method arguments. + * + * @private + * @param {Object} object The object to query. + * @param {Array|string} path The path of the method to invoke. + * @param {Array} args The arguments to invoke the method with. + * @returns {*} Returns the result of the invoked method. + */ + function baseInvoke(object, path, args) { + path = castPath(path, object); + object = parent(object, path); + var func = object == null ? object : object[toKey(last(path))]; + return func == null ? undefined : apply(func, object, args); + } + + /** + * The base implementation of `_.isArguments`. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an `arguments` object, + */ + function baseIsArguments(value) { + return isObjectLike(value) && baseGetTag(value) == argsTag; + } + + /** + * The base implementation of `_.isArrayBuffer` without Node.js optimizations. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an array buffer, else `false`. + */ + function baseIsArrayBuffer(value) { + return isObjectLike(value) && baseGetTag(value) == arrayBufferTag; + } + + /** + * The base implementation of `_.isDate` without Node.js optimizations. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a date object, else `false`. + */ + function baseIsDate(value) { + return isObjectLike(value) && baseGetTag(value) == dateTag; + } + + /** + * The base implementation of `_.isEqual` which supports partial comparisons + * and tracks traversed objects. + * + * @private + * @param {*} value The value to compare. + * @param {*} other The other value to compare. + * @param {boolean} bitmask The bitmask flags. + * 1 - Unordered comparison + * 2 - Partial comparison + * @param {Function} [customizer] The function to customize comparisons. + * @param {Object} [stack] Tracks traversed `value` and `other` objects. + * @returns {boolean} Returns `true` if the values are equivalent, else `false`. + */ + function baseIsEqual(value, other, bitmask, customizer, stack) { + if (value === other) { + return true; + } + if (value == null || other == null || (!isObjectLike(value) && !isObjectLike(other))) { + return value !== value && other !== other; + } + return baseIsEqualDeep(value, other, bitmask, customizer, baseIsEqual, stack); + } + + /** + * A specialized version of `baseIsEqual` for arrays and objects which performs + * deep comparisons and tracks traversed objects enabling objects with circular + * references to be compared. + * + * @private + * @param {Object} object The object to compare. + * @param {Object} other The other object to compare. + * @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details. + * @param {Function} customizer The function to customize comparisons. + * @param {Function} equalFunc The function to determine equivalents of values. + * @param {Object} [stack] Tracks traversed `object` and `other` objects. + * @returns {boolean} Returns `true` if the objects are equivalent, else `false`. + */ + function baseIsEqualDeep(object, other, bitmask, customizer, equalFunc, stack) { + var objIsArr = isArray(object), + othIsArr = isArray(other), + objTag = objIsArr ? arrayTag : getTag(object), + othTag = othIsArr ? arrayTag : getTag(other); + + objTag = objTag == argsTag ? objectTag : objTag; + othTag = othTag == argsTag ? objectTag : othTag; + + var objIsObj = objTag == objectTag, + othIsObj = othTag == objectTag, + isSameTag = objTag == othTag; + + if (isSameTag && isBuffer(object)) { + if (!isBuffer(other)) { + return false; + } + objIsArr = true; + objIsObj = false; + } + if (isSameTag && !objIsObj) { + stack || (stack = new Stack); + return (objIsArr || isTypedArray(object)) + ? equalArrays(object, other, bitmask, customizer, equalFunc, stack) + : equalByTag(object, other, objTag, bitmask, customizer, equalFunc, stack); + } + if (!(bitmask & COMPARE_PARTIAL_FLAG)) { + var objIsWrapped = objIsObj && hasOwnProperty.call(object, '__wrapped__'), + othIsWrapped = othIsObj && hasOwnProperty.call(other, '__wrapped__'); + + if (objIsWrapped || othIsWrapped) { + var objUnwrapped = objIsWrapped ? object.value() : object, + othUnwrapped = othIsWrapped ? other.value() : other; + + stack || (stack = new Stack); + return equalFunc(objUnwrapped, othUnwrapped, bitmask, customizer, stack); + } + } + if (!isSameTag) { + return false; + } + stack || (stack = new Stack); + return equalObjects(object, other, bitmask, customizer, equalFunc, stack); + } + + /** + * The base implementation of `_.isMap` without Node.js optimizations. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a map, else `false`. + */ + function baseIsMap(value) { + return isObjectLike(value) && getTag(value) == mapTag; + } + + /** + * The base implementation of `_.isMatch` without support for iteratee shorthands. + * + * @private + * @param {Object} object The object to inspect. + * @param {Object} source The object of property values to match. + * @param {Array} matchData The property names, values, and compare flags to match. + * @param {Function} [customizer] The function to customize comparisons. + * @returns {boolean} Returns `true` if `object` is a match, else `false`. + */ + function baseIsMatch(object, source, matchData, customizer) { + var index = matchData.length, + length = index, + noCustomizer = !customizer; + + if (object == null) { + return !length; + } + object = Object(object); + while (index--) { + var data = matchData[index]; + if ((noCustomizer && data[2]) + ? data[1] !== object[data[0]] + : !(data[0] in object) + ) { + return false; + } + } + while (++index < length) { + data = matchData[index]; + var key = data[0], + objValue = object[key], + srcValue = data[1]; + + if (noCustomizer && data[2]) { + if (objValue === undefined && !(key in object)) { + return false; + } + } else { + var stack = new Stack; + if (customizer) { + var result = customizer(objValue, srcValue, key, object, source, stack); + } + if (!(result === undefined + ? baseIsEqual(srcValue, objValue, COMPARE_PARTIAL_FLAG | COMPARE_UNORDERED_FLAG, customizer, stack) + : result + )) { + return false; + } + } + } + return true; + } + + /** + * The base implementation of `_.isNative` without bad shim checks. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a native function, + * else `false`. + */ + function baseIsNative(value) { + if (!isObject(value) || isMasked(value)) { + return false; + } + var pattern = isFunction(value) ? reIsNative : reIsHostCtor; + return pattern.test(toSource(value)); + } + + /** + * The base implementation of `_.isRegExp` without Node.js optimizations. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a regexp, else `false`. + */ + function baseIsRegExp(value) { + return isObjectLike(value) && baseGetTag(value) == regexpTag; + } + + /** + * The base implementation of `_.isSet` without Node.js optimizations. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a set, else `false`. + */ + function baseIsSet(value) { + return isObjectLike(value) && getTag(value) == setTag; + } + + /** + * The base implementation of `_.isTypedArray` without Node.js optimizations. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a typed array, else `false`. + */ + function baseIsTypedArray(value) { + return isObjectLike(value) && + isLength(value.length) && !!typedArrayTags[baseGetTag(value)]; + } + + /** + * The base implementation of `_.iteratee`. + * + * @private + * @param {*} [value=_.identity] The value to convert to an iteratee. + * @returns {Function} Returns the iteratee. + */ + function baseIteratee(value) { + // Don't store the `typeof` result in a variable to avoid a JIT bug in Safari 9. + // See https://bugs.webkit.org/show_bug.cgi?id=156034 for more details. + if (typeof value == 'function') { + return value; + } + if (value == null) { + return identity; + } + if (typeof value == 'object') { + return isArray(value) + ? baseMatchesProperty(value[0], value[1]) + : baseMatches(value); + } + return property(value); + } + + /** + * The base implementation of `_.keys` which doesn't treat sparse arrays as dense. + * + * @private + * @param {Object} object The object to query. + * @returns {Array} Returns the array of property names. + */ + function baseKeys(object) { + if (!isPrototype(object)) { + return nativeKeys(object); + } + var result = []; + for (var key in Object(object)) { + if (hasOwnProperty.call(object, key) && key != 'constructor') { + result.push(key); + } + } + return result; + } + + /** + * The base implementation of `_.keysIn` which doesn't treat sparse arrays as dense. + * + * @private + * @param {Object} object The object to query. + * @returns {Array} Returns the array of property names. + */ + function baseKeysIn(object) { + if (!isObject(object)) { + return nativeKeysIn(object); + } + var isProto = isPrototype(object), + result = []; + + for (var key in object) { + if (!(key == 'constructor' && (isProto || !hasOwnProperty.call(object, key)))) { + result.push(key); + } + } + return result; + } + + /** + * The base implementation of `_.lt` which doesn't coerce arguments. + * + * @private + * @param {*} value The value to compare. + * @param {*} other The other value to compare. + * @returns {boolean} Returns `true` if `value` is less than `other`, + * else `false`. + */ + function baseLt(value, other) { + return value < other; + } + + /** + * The base implementation of `_.map` without support for iteratee shorthands. + * + * @private + * @param {Array|Object} collection The collection to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @returns {Array} Returns the new mapped array. + */ + function baseMap(collection, iteratee) { + var index = -1, + result = isArrayLike(collection) ? Array(collection.length) : []; + + baseEach(collection, function(value, key, collection) { + result[++index] = iteratee(value, key, collection); + }); + return result; + } + + /** + * The base implementation of `_.matches` which doesn't clone `source`. + * + * @private + * @param {Object} source The object of property values to match. + * @returns {Function} Returns the new spec function. + */ + function baseMatches(source) { + var matchData = getMatchData(source); + if (matchData.length == 1 && matchData[0][2]) { + return matchesStrictComparable(matchData[0][0], matchData[0][1]); + } + return function(object) { + return object === source || baseIsMatch(object, source, matchData); + }; + } + + /** + * The base implementation of `_.matchesProperty` which doesn't clone `srcValue`. + * + * @private + * @param {string} path The path of the property to get. + * @param {*} srcValue The value to match. + * @returns {Function} Returns the new spec function. + */ + function baseMatchesProperty(path, srcValue) { + if (isKey(path) && isStrictComparable(srcValue)) { + return matchesStrictComparable(toKey(path), srcValue); + } + return function(object) { + var objValue = get(object, path); + return (objValue === undefined && objValue === srcValue) + ? hasIn(object, path) + : baseIsEqual(srcValue, objValue, COMPARE_PARTIAL_FLAG | COMPARE_UNORDERED_FLAG); + }; + } + + /** + * The base implementation of `_.merge` without support for multiple sources. + * + * @private + * @param {Object} object The destination object. + * @param {Object} source The source object. + * @param {number} srcIndex The index of `source`. + * @param {Function} [customizer] The function to customize merged values. + * @param {Object} [stack] Tracks traversed source values and their merged + * counterparts. + */ + function baseMerge(object, source, srcIndex, customizer, stack) { + if (object === source) { + return; + } + baseFor(source, function(srcValue, key) { + stack || (stack = new Stack); + if (isObject(srcValue)) { + baseMergeDeep(object, source, key, srcIndex, baseMerge, customizer, stack); + } + else { + var newValue = customizer + ? customizer(safeGet(object, key), srcValue, (key + ''), object, source, stack) + : undefined; + + if (newValue === undefined) { + newValue = srcValue; + } + assignMergeValue(object, key, newValue); + } + }, keysIn); + } + + /** + * A specialized version of `baseMerge` for arrays and objects which performs + * deep merges and tracks traversed objects enabling objects with circular + * references to be merged. + * + * @private + * @param {Object} object The destination object. + * @param {Object} source The source object. + * @param {string} key The key of the value to merge. + * @param {number} srcIndex The index of `source`. + * @param {Function} mergeFunc The function to merge values. + * @param {Function} [customizer] The function to customize assigned values. + * @param {Object} [stack] Tracks traversed source values and their merged + * counterparts. + */ + function baseMergeDeep(object, source, key, srcIndex, mergeFunc, customizer, stack) { + var objValue = safeGet(object, key), + srcValue = safeGet(source, key), + stacked = stack.get(srcValue); + + if (stacked) { + assignMergeValue(object, key, stacked); + return; + } + var newValue = customizer + ? customizer(objValue, srcValue, (key + ''), object, source, stack) + : undefined; + + var isCommon = newValue === undefined; + + if (isCommon) { + var isArr = isArray(srcValue), + isBuff = !isArr && isBuffer(srcValue), + isTyped = !isArr && !isBuff && isTypedArray(srcValue); + + newValue = srcValue; + if (isArr || isBuff || isTyped) { + if (isArray(objValue)) { + newValue = objValue; + } + else if (isArrayLikeObject(objValue)) { + newValue = copyArray(objValue); + } + else if (isBuff) { + isCommon = false; + newValue = cloneBuffer(srcValue, true); + } + else if (isTyped) { + isCommon = false; + newValue = cloneTypedArray(srcValue, true); + } + else { + newValue = []; + } + } + else if (isPlainObject(srcValue) || isArguments(srcValue)) { + newValue = objValue; + if (isArguments(objValue)) { + newValue = toPlainObject(objValue); + } + else if (!isObject(objValue) || isFunction(objValue)) { + newValue = initCloneObject(srcValue); + } + } + else { + isCommon = false; + } + } + if (isCommon) { + // Recursively merge objects and arrays (susceptible to call stack limits). + stack.set(srcValue, newValue); + mergeFunc(newValue, srcValue, srcIndex, customizer, stack); + stack['delete'](srcValue); + } + assignMergeValue(object, key, newValue); + } + + /** + * The base implementation of `_.nth` which doesn't coerce arguments. + * + * @private + * @param {Array} array The array to query. + * @param {number} n The index of the element to return. + * @returns {*} Returns the nth element of `array`. + */ + function baseNth(array, n) { + var length = array.length; + if (!length) { + return; + } + n += n < 0 ? length : 0; + return isIndex(n, length) ? array[n] : undefined; + } + + /** + * The base implementation of `_.orderBy` without param guards. + * + * @private + * @param {Array|Object} collection The collection to iterate over. + * @param {Function[]|Object[]|string[]} iteratees The iteratees to sort by. + * @param {string[]} orders The sort orders of `iteratees`. + * @returns {Array} Returns the new sorted array. + */ + function baseOrderBy(collection, iteratees, orders) { + if (iteratees.length) { + iteratees = arrayMap(iteratees, function(iteratee) { + if (isArray(iteratee)) { + return function(value) { + return baseGet(value, iteratee.length === 1 ? iteratee[0] : iteratee); + } + } + return iteratee; }); - return result; + } else { + iteratees = [identity]; + } + + var index = -1; + iteratees = arrayMap(iteratees, baseUnary(getIteratee())); + + var result = baseMap(collection, function(value, key, collection) { + var criteria = arrayMap(iteratees, function(iteratee) { + return iteratee(value); + }); + return { 'criteria': criteria, 'index': ++index, 'value': value }; + }); + + return baseSortBy(result, function(object, other) { + return compareMultiple(object, other, orders); + }); } /** - * Creates a unary function that invokes `func` with its argument transformed. + * The base implementation of `_.pick` without support for individual + * property identifiers. * * @private - * @param {Function} func The function to wrap. - * @param {Function} transform The argument transform. - * @returns {Function} Returns the new function. + * @param {Object} object The source object. + * @param {string[]} paths The property paths to pick. + * @returns {Object} Returns the new object. */ - function overArg(func, transform) { - return function(arg) { - return func(transform(arg)); - }; + function basePick(object, paths) { + return basePickBy(object, paths, function(value, path) { + return hasIn(object, path); + }); } /** - * Replaces all `placeholder` elements in `array` with an internal placeholder - * and returns an array of their indexes. + * The base implementation of `_.pickBy` without support for iteratee shorthands. + * + * @private + * @param {Object} object The source object. + * @param {string[]} paths The property paths to pick. + * @param {Function} predicate The function invoked per property. + * @returns {Object} Returns the new object. + */ + function basePickBy(object, paths, predicate) { + var index = -1, + length = paths.length, + result = {}; + + while (++index < length) { + var path = paths[index], + value = baseGet(object, path); + + if (predicate(value, path)) { + baseSet(result, castPath(path, object), value); + } + } + return result; + } + + /** + * A specialized version of `baseProperty` which supports deep paths. + * + * @private + * @param {Array|string} path The path of the property to get. + * @returns {Function} Returns the new accessor function. + */ + function basePropertyDeep(path) { + return function(object) { + return baseGet(object, path); + }; + } + + /** + * The base implementation of `_.pullAllBy` without support for iteratee + * shorthands. * * @private * @param {Array} array The array to modify. - * @param {*} placeholder The placeholder to replace. - * @returns {Array} Returns the new array of placeholder indexes. + * @param {Array} values The values to remove. + * @param {Function} [iteratee] The iteratee invoked per element. + * @param {Function} [comparator] The comparator invoked per element. + * @returns {Array} Returns `array`. */ - function replaceHolders(array, placeholder) { - var index = -1, - length = array.length, - resIndex = 0, - result = []; + function basePullAll(array, values, iteratee, comparator) { + var indexOf = comparator ? baseIndexOfWith : baseIndexOf, + index = -1, + length = values.length, + seen = array; - while (++index < length) { - var value = array[index]; - if (value === placeholder || value === PLACEHOLDER) { - array[index] = PLACEHOLDER; - result[resIndex++] = index; - } + if (array === values) { + values = copyArray(values); + } + if (iteratee) { + seen = arrayMap(array, baseUnary(iteratee)); + } + while (++index < length) { + var fromIndex = 0, + value = values[index], + computed = iteratee ? iteratee(value) : value; + + while ((fromIndex = indexOf(seen, computed, fromIndex, comparator)) > -1) { + if (seen !== array) { + splice.call(seen, fromIndex, 1); + } + splice.call(array, fromIndex, 1); } - return result; + } + return array; } /** - * Converts `set` to an array of its values. + * The base implementation of `_.pullAt` without support for individual + * indexes or capturing the removed elements. * * @private - * @param {Object} set The set to convert. - * @returns {Array} Returns the values. + * @param {Array} array The array to modify. + * @param {number[]} indexes The indexes of elements to remove. + * @returns {Array} Returns `array`. */ - function setToArray(set) { - var index = -1, - result = Array(set.size); + function basePullAt(array, indexes) { + var length = array ? indexes.length : 0, + lastIndex = length - 1; - set.forEach(function(value) { - result[++index] = value; - }); - return result; + while (length--) { + var index = indexes[length]; + if (length == lastIndex || index !== previous) { + var previous = index; + if (isIndex(index)) { + splice.call(array, index, 1); + } else { + baseUnset(array, index); + } + } + } + return array; } /** - * Converts `set` to its value-value pairs. + * The base implementation of `_.random` without support for returning + * floating-point numbers. * * @private - * @param {Object} set The set to convert. - * @returns {Array} Returns the value-value pairs. + * @param {number} lower The lower bound. + * @param {number} upper The upper bound. + * @returns {number} Returns the random number. */ - function setToPairs(set) { - var index = -1, - result = Array(set.size); - - set.forEach(function(value) { - result[++index] = [value, value]; - }); - return result; + function baseRandom(lower, upper) { + return lower + nativeFloor(nativeRandom() * (upper - lower + 1)); } /** - * A specialized version of `_.indexOf` which performs strict equality - * comparisons of values, i.e. `===`. + * The base implementation of `_.range` and `_.rangeRight` which doesn't + * coerce arguments. + * + * @private + * @param {number} start The start of the range. + * @param {number} end The end of the range. + * @param {number} step The value to increment or decrement by. + * @param {boolean} [fromRight] Specify iterating from right to left. + * @returns {Array} Returns the range of numbers. + */ + function baseRange(start, end, step, fromRight) { + var index = -1, + length = nativeMax(nativeCeil((end - start) / (step || 1)), 0), + result = Array(length); + + while (length--) { + result[fromRight ? length : ++index] = start; + start += step; + } + return result; + } + + /** + * The base implementation of `_.repeat` which doesn't coerce arguments. + * + * @private + * @param {string} string The string to repeat. + * @param {number} n The number of times to repeat the string. + * @returns {string} Returns the repeated string. + */ + function baseRepeat(string, n) { + var result = ''; + if (!string || n < 1 || n > MAX_SAFE_INTEGER) { + return result; + } + // Leverage the exponentiation by squaring algorithm for a faster repeat. + // See https://en.wikipedia.org/wiki/Exponentiation_by_squaring for more details. + do { + if (n % 2) { + result += string; + } + n = nativeFloor(n / 2); + if (n) { + string += string; + } + } while (n); + + return result; + } + + /** + * The base implementation of `_.rest` which doesn't validate or coerce arguments. + * + * @private + * @param {Function} func The function to apply a rest parameter to. + * @param {number} [start=func.length-1] The start position of the rest parameter. + * @returns {Function} Returns the new function. + */ + function baseRest(func, start) { + return setToString(overRest(func, start, identity), func + ''); + } + + /** + * The base implementation of `_.sample`. + * + * @private + * @param {Array|Object} collection The collection to sample. + * @returns {*} Returns the random element. + */ + function baseSample(collection) { + return arraySample(values(collection)); + } + + /** + * The base implementation of `_.sampleSize` without param guards. + * + * @private + * @param {Array|Object} collection The collection to sample. + * @param {number} n The number of elements to sample. + * @returns {Array} Returns the random elements. + */ + function baseSampleSize(collection, n) { + var array = values(collection); + return shuffleSelf(array, baseClamp(n, 0, array.length)); + } + + /** + * The base implementation of `_.set`. + * + * @private + * @param {Object} object The object to modify. + * @param {Array|string} path The path of the property to set. + * @param {*} value The value to set. + * @param {Function} [customizer] The function to customize path creation. + * @returns {Object} Returns `object`. + */ + function baseSet(object, path, value, customizer) { + if (!isObject(object)) { + return object; + } + path = castPath(path, object); + + var index = -1, + length = path.length, + lastIndex = length - 1, + nested = object; + + while (nested != null && ++index < length) { + var key = toKey(path[index]), + newValue = value; + + if (key === '__proto__' || key === 'constructor' || key === 'prototype') { + return object; + } + + if (index != lastIndex) { + var objValue = nested[key]; + newValue = customizer ? customizer(objValue, key, nested) : undefined; + if (newValue === undefined) { + newValue = isObject(objValue) + ? objValue + : (isIndex(path[index + 1]) ? [] : {}); + } + } + assignValue(nested, key, newValue); + nested = nested[key]; + } + return object; + } + + /** + * The base implementation of `setData` without support for hot loop shorting. + * + * @private + * @param {Function} func The function to associate metadata with. + * @param {*} data The metadata. + * @returns {Function} Returns `func`. + */ + var baseSetData = !metaMap ? identity : function(func, data) { + metaMap.set(func, data); + return func; + }; + + /** + * The base implementation of `setToString` without support for hot loop shorting. + * + * @private + * @param {Function} func The function to modify. + * @param {Function} string The `toString` result. + * @returns {Function} Returns `func`. + */ + var baseSetToString = !defineProperty ? identity : function(func, string) { + return defineProperty(func, 'toString', { + 'configurable': true, + 'enumerable': false, + 'value': constant(string), + 'writable': true + }); + }; + + /** + * The base implementation of `_.shuffle`. + * + * @private + * @param {Array|Object} collection The collection to shuffle. + * @returns {Array} Returns the new shuffled array. + */ + function baseShuffle(collection) { + return shuffleSelf(values(collection)); + } + + /** + * The base implementation of `_.slice` without an iteratee call guard. + * + * @private + * @param {Array} array The array to slice. + * @param {number} [start=0] The start position. + * @param {number} [end=array.length] The end position. + * @returns {Array} Returns the slice of `array`. + */ + function baseSlice(array, start, end) { + var index = -1, + length = array.length; + + if (start < 0) { + start = -start > length ? 0 : (length + start); + } + end = end > length ? length : end; + if (end < 0) { + end += length; + } + length = start > end ? 0 : ((end - start) >>> 0); + start >>>= 0; + + var result = Array(length); + while (++index < length) { + result[index] = array[index + start]; + } + return result; + } + + /** + * The base implementation of `_.some` without support for iteratee shorthands. + * + * @private + * @param {Array|Object} collection The collection to iterate over. + * @param {Function} predicate The function invoked per iteration. + * @returns {boolean} Returns `true` if any element passes the predicate check, + * else `false`. + */ + function baseSome(collection, predicate) { + var result; + + baseEach(collection, function(value, index, collection) { + result = predicate(value, index, collection); + return !result; + }); + return !!result; + } + + /** + * The base implementation of `_.sortedIndex` and `_.sortedLastIndex` which + * performs a binary search of `array` to determine the index at which `value` + * should be inserted into `array` in order to maintain its sort order. + * + * @private + * @param {Array} array The sorted array to inspect. + * @param {*} value The value to evaluate. + * @param {boolean} [retHighest] Specify returning the highest qualified index. + * @returns {number} Returns the index at which `value` should be inserted + * into `array`. + */ + function baseSortedIndex(array, value, retHighest) { + var low = 0, + high = array == null ? low : array.length; + + if (typeof value == 'number' && value === value && high <= HALF_MAX_ARRAY_LENGTH) { + while (low < high) { + var mid = (low + high) >>> 1, + computed = array[mid]; + + if (computed !== null && !isSymbol(computed) && + (retHighest ? (computed <= value) : (computed < value))) { + low = mid + 1; + } else { + high = mid; + } + } + return high; + } + return baseSortedIndexBy(array, value, identity, retHighest); + } + + /** + * The base implementation of `_.sortedIndexBy` and `_.sortedLastIndexBy` + * which invokes `iteratee` for `value` and each element of `array` to compute + * their sort ranking. The iteratee is invoked with one argument; (value). + * + * @private + * @param {Array} array The sorted array to inspect. + * @param {*} value The value to evaluate. + * @param {Function} iteratee The iteratee invoked per element. + * @param {boolean} [retHighest] Specify returning the highest qualified index. + * @returns {number} Returns the index at which `value` should be inserted + * into `array`. + */ + function baseSortedIndexBy(array, value, iteratee, retHighest) { + var low = 0, + high = array == null ? 0 : array.length; + if (high === 0) { + return 0; + } + + value = iteratee(value); + var valIsNaN = value !== value, + valIsNull = value === null, + valIsSymbol = isSymbol(value), + valIsUndefined = value === undefined; + + while (low < high) { + var mid = nativeFloor((low + high) / 2), + computed = iteratee(array[mid]), + othIsDefined = computed !== undefined, + othIsNull = computed === null, + othIsReflexive = computed === computed, + othIsSymbol = isSymbol(computed); + + if (valIsNaN) { + var setLow = retHighest || othIsReflexive; + } else if (valIsUndefined) { + setLow = othIsReflexive && (retHighest || othIsDefined); + } else if (valIsNull) { + setLow = othIsReflexive && othIsDefined && (retHighest || !othIsNull); + } else if (valIsSymbol) { + setLow = othIsReflexive && othIsDefined && !othIsNull && (retHighest || !othIsSymbol); + } else if (othIsNull || othIsSymbol) { + setLow = false; + } else { + setLow = retHighest ? (computed <= value) : (computed < value); + } + if (setLow) { + low = mid + 1; + } else { + high = mid; + } + } + return nativeMin(high, MAX_ARRAY_INDEX); + } + + /** + * The base implementation of `_.sortedUniq` and `_.sortedUniqBy` without + * support for iteratee shorthands. * * @private * @param {Array} array The array to inspect. - * @param {*} value The value to search for. - * @param {number} fromIndex The index to search from. - * @returns {number} Returns the index of the matched value, else `-1`. + * @param {Function} [iteratee] The iteratee invoked per element. + * @returns {Array} Returns the new duplicate free array. */ - function strictIndexOf(array, value, fromIndex) { - var index = fromIndex - 1, - length = array.length; + function baseSortedUniq(array, iteratee) { + var index = -1, + length = array.length, + resIndex = 0, + result = []; - while (++index < length) { - if (array[index] === value) { - return index; - } + while (++index < length) { + var value = array[index], + computed = iteratee ? iteratee(value) : value; + + if (!index || !eq(computed, seen)) { + var seen = computed; + result[resIndex++] = value === 0 ? 0 : value; } - return -1; + } + return result; } /** - * A specialized version of `_.lastIndexOf` which performs strict equality - * comparisons of values, i.e. `===`. + * The base implementation of `_.toNumber` which doesn't ensure correct + * conversions of binary, hexadecimal, or octal string values. + * + * @private + * @param {*} value The value to process. + * @returns {number} Returns the number. + */ + function baseToNumber(value) { + if (typeof value == 'number') { + return value; + } + if (isSymbol(value)) { + return NAN; + } + return +value; + } + + /** + * The base implementation of `_.toString` which doesn't convert nullish + * values to empty strings. + * + * @private + * @param {*} value The value to process. + * @returns {string} Returns the string. + */ + function baseToString(value) { + // Exit early for strings to avoid a performance hit in some environments. + if (typeof value == 'string') { + return value; + } + if (isArray(value)) { + // Recursively convert values (susceptible to call stack limits). + return arrayMap(value, baseToString) + ''; + } + if (isSymbol(value)) { + return symbolToString ? symbolToString.call(value) : ''; + } + var result = (value + ''); + return (result == '0' && (1 / value) == -INFINITY) ? '-0' : result; + } + + /** + * The base implementation of `_.uniqBy` without support for iteratee shorthands. * * @private * @param {Array} array The array to inspect. - * @param {*} value The value to search for. - * @param {number} fromIndex The index to search from. - * @returns {number} Returns the index of the matched value, else `-1`. + * @param {Function} [iteratee] The iteratee invoked per element. + * @param {Function} [comparator] The comparator invoked per element. + * @returns {Array} Returns the new duplicate free array. */ - function strictLastIndexOf(array, value, fromIndex) { - var index = fromIndex + 1; + function baseUniq(array, iteratee, comparator) { + var index = -1, + includes = arrayIncludes, + length = array.length, + isCommon = true, + result = [], + seen = result; + + if (comparator) { + isCommon = false; + includes = arrayIncludesWith; + } + else if (length >= LARGE_ARRAY_SIZE) { + var set = iteratee ? null : createSet(array); + if (set) { + return setToArray(set); + } + isCommon = false; + includes = cacheHas; + seen = new SetCache; + } + else { + seen = iteratee ? [] : result; + } + outer: + while (++index < length) { + var value = array[index], + computed = iteratee ? iteratee(value) : value; + + value = (comparator || value !== 0) ? value : 0; + if (isCommon && computed === computed) { + var seenIndex = seen.length; + while (seenIndex--) { + if (seen[seenIndex] === computed) { + continue outer; + } + } + if (iteratee) { + seen.push(computed); + } + result.push(value); + } + else if (!includes(seen, computed, comparator)) { + if (seen !== result) { + seen.push(computed); + } + result.push(value); + } + } + return result; + } + + /** + * The base implementation of `_.unset`. + * + * @private + * @param {Object} object The object to modify. + * @param {Array|string} path The property path to unset. + * @returns {boolean} Returns `true` if the property is deleted, else `false`. + */ + function baseUnset(object, path) { + path = castPath(path, object); + object = parent(object, path); + return object == null || delete object[toKey(last(path))]; + } + + /** + * The base implementation of `_.update`. + * + * @private + * @param {Object} object The object to modify. + * @param {Array|string} path The path of the property to update. + * @param {Function} updater The function to produce the updated value. + * @param {Function} [customizer] The function to customize path creation. + * @returns {Object} Returns `object`. + */ + function baseUpdate(object, path, updater, customizer) { + return baseSet(object, path, updater(baseGet(object, path)), customizer); + } + + /** + * The base implementation of methods like `_.dropWhile` and `_.takeWhile` + * without support for iteratee shorthands. + * + * @private + * @param {Array} array The array to query. + * @param {Function} predicate The function invoked per iteration. + * @param {boolean} [isDrop] Specify dropping elements instead of taking them. + * @param {boolean} [fromRight] Specify iterating from right to left. + * @returns {Array} Returns the slice of `array`. + */ + function baseWhile(array, predicate, isDrop, fromRight) { + var length = array.length, + index = fromRight ? length : -1; + + while ((fromRight ? index-- : ++index < length) && + predicate(array[index], index, array)) {} + + return isDrop + ? baseSlice(array, (fromRight ? 0 : index), (fromRight ? index + 1 : length)) + : baseSlice(array, (fromRight ? index + 1 : 0), (fromRight ? length : index)); + } + + /** + * The base implementation of `wrapperValue` which returns the result of + * performing a sequence of actions on the unwrapped `value`, where each + * successive action is supplied the return value of the previous. + * + * @private + * @param {*} value The unwrapped value. + * @param {Array} actions Actions to perform to resolve the unwrapped value. + * @returns {*} Returns the resolved value. + */ + function baseWrapperValue(value, actions) { + var result = value; + if (result instanceof LazyWrapper) { + result = result.value(); + } + return arrayReduce(actions, function(result, action) { + return action.func.apply(action.thisArg, arrayPush([result], action.args)); + }, result); + } + + /** + * The base implementation of methods like `_.xor`, without support for + * iteratee shorthands, that accepts an array of arrays to inspect. + * + * @private + * @param {Array} arrays The arrays to inspect. + * @param {Function} [iteratee] The iteratee invoked per element. + * @param {Function} [comparator] The comparator invoked per element. + * @returns {Array} Returns the new array of values. + */ + function baseXor(arrays, iteratee, comparator) { + var length = arrays.length; + if (length < 2) { + return length ? baseUniq(arrays[0]) : []; + } + var index = -1, + result = Array(length); + + while (++index < length) { + var array = arrays[index], + othIndex = -1; + + while (++othIndex < length) { + if (othIndex != index) { + result[index] = baseDifference(result[index] || array, arrays[othIndex], iteratee, comparator); + } + } + } + return baseUniq(baseFlatten(result, 1), iteratee, comparator); + } + + /** + * This base implementation of `_.zipObject` which assigns values using `assignFunc`. + * + * @private + * @param {Array} props The property identifiers. + * @param {Array} values The property values. + * @param {Function} assignFunc The function to assign values. + * @returns {Object} Returns the new object. + */ + function baseZipObject(props, values, assignFunc) { + var index = -1, + length = props.length, + valsLength = values.length, + result = {}; + + while (++index < length) { + var value = index < valsLength ? values[index] : undefined; + assignFunc(result, props[index], value); + } + return result; + } + + /** + * Casts `value` to an empty array if it's not an array like object. + * + * @private + * @param {*} value The value to inspect. + * @returns {Array|Object} Returns the cast array-like object. + */ + function castArrayLikeObject(value) { + return isArrayLikeObject(value) ? value : []; + } + + /** + * Casts `value` to `identity` if it's not a function. + * + * @private + * @param {*} value The value to inspect. + * @returns {Function} Returns cast function. + */ + function castFunction(value) { + return typeof value == 'function' ? value : identity; + } + + /** + * Casts `value` to a path array if it's not one. + * + * @private + * @param {*} value The value to inspect. + * @param {Object} [object] The object to query keys on. + * @returns {Array} Returns the cast property path array. + */ + function castPath(value, object) { + if (isArray(value)) { + return value; + } + return isKey(value, object) ? [value] : stringToPath(toString(value)); + } + + /** + * A `baseRest` alias which can be replaced with `identity` by module + * replacement plugins. + * + * @private + * @type {Function} + * @param {Function} func The function to apply a rest parameter to. + * @returns {Function} Returns the new function. + */ + var castRest = baseRest; + + /** + * Casts `array` to a slice if it's needed. + * + * @private + * @param {Array} array The array to inspect. + * @param {number} start The start position. + * @param {number} [end=array.length] The end position. + * @returns {Array} Returns the cast slice. + */ + function castSlice(array, start, end) { + var length = array.length; + end = end === undefined ? length : end; + return (!start && end >= length) ? array : baseSlice(array, start, end); + } + + /** + * A simple wrapper around the global [`clearTimeout`](https://mdn.io/clearTimeout). + * + * @private + * @param {number|Object} id The timer id or timeout object of the timer to clear. + */ + var clearTimeout = ctxClearTimeout || function(id) { + return root.clearTimeout(id); + }; + + /** + * Creates a clone of `buffer`. + * + * @private + * @param {Buffer} buffer The buffer to clone. + * @param {boolean} [isDeep] Specify a deep clone. + * @returns {Buffer} Returns the cloned buffer. + */ + function cloneBuffer(buffer, isDeep) { + if (isDeep) { + return buffer.slice(); + } + var length = buffer.length, + result = allocUnsafe ? allocUnsafe(length) : new buffer.constructor(length); + + buffer.copy(result); + return result; + } + + /** + * Creates a clone of `arrayBuffer`. + * + * @private + * @param {ArrayBuffer} arrayBuffer The array buffer to clone. + * @returns {ArrayBuffer} Returns the cloned array buffer. + */ + function cloneArrayBuffer(arrayBuffer) { + var result = new arrayBuffer.constructor(arrayBuffer.byteLength); + new Uint8Array(result).set(new Uint8Array(arrayBuffer)); + return result; + } + + /** + * Creates a clone of `dataView`. + * + * @private + * @param {Object} dataView The data view to clone. + * @param {boolean} [isDeep] Specify a deep clone. + * @returns {Object} Returns the cloned data view. + */ + function cloneDataView(dataView, isDeep) { + var buffer = isDeep ? cloneArrayBuffer(dataView.buffer) : dataView.buffer; + return new dataView.constructor(buffer, dataView.byteOffset, dataView.byteLength); + } + + /** + * Creates a clone of `regexp`. + * + * @private + * @param {Object} regexp The regexp to clone. + * @returns {Object} Returns the cloned regexp. + */ + function cloneRegExp(regexp) { + var result = new regexp.constructor(regexp.source, reFlags.exec(regexp)); + result.lastIndex = regexp.lastIndex; + return result; + } + + /** + * Creates a clone of the `symbol` object. + * + * @private + * @param {Object} symbol The symbol object to clone. + * @returns {Object} Returns the cloned symbol object. + */ + function cloneSymbol(symbol) { + return symbolValueOf ? Object(symbolValueOf.call(symbol)) : {}; + } + + /** + * Creates a clone of `typedArray`. + * + * @private + * @param {Object} typedArray The typed array to clone. + * @param {boolean} [isDeep] Specify a deep clone. + * @returns {Object} Returns the cloned typed array. + */ + function cloneTypedArray(typedArray, isDeep) { + var buffer = isDeep ? cloneArrayBuffer(typedArray.buffer) : typedArray.buffer; + return new typedArray.constructor(buffer, typedArray.byteOffset, typedArray.length); + } + + /** + * Compares values to sort them in ascending order. + * + * @private + * @param {*} value The value to compare. + * @param {*} other The other value to compare. + * @returns {number} Returns the sort order indicator for `value`. + */ + function compareAscending(value, other) { + if (value !== other) { + var valIsDefined = value !== undefined, + valIsNull = value === null, + valIsReflexive = value === value, + valIsSymbol = isSymbol(value); + + var othIsDefined = other !== undefined, + othIsNull = other === null, + othIsReflexive = other === other, + othIsSymbol = isSymbol(other); + + if ((!othIsNull && !othIsSymbol && !valIsSymbol && value > other) || + (valIsSymbol && othIsDefined && othIsReflexive && !othIsNull && !othIsSymbol) || + (valIsNull && othIsDefined && othIsReflexive) || + (!valIsDefined && othIsReflexive) || + !valIsReflexive) { + return 1; + } + if ((!valIsNull && !valIsSymbol && !othIsSymbol && value < other) || + (othIsSymbol && valIsDefined && valIsReflexive && !valIsNull && !valIsSymbol) || + (othIsNull && valIsDefined && valIsReflexive) || + (!othIsDefined && valIsReflexive) || + !othIsReflexive) { + return -1; + } + } + return 0; + } + + /** + * Used by `_.orderBy` to compare multiple properties of a value to another + * and stable sort them. + * + * If `orders` is unspecified, all values are sorted in ascending order. Otherwise, + * specify an order of "desc" for descending or "asc" for ascending sort order + * of corresponding values. + * + * @private + * @param {Object} object The object to compare. + * @param {Object} other The other object to compare. + * @param {boolean[]|string[]} orders The order to sort by for each property. + * @returns {number} Returns the sort order indicator for `object`. + */ + function compareMultiple(object, other, orders) { + var index = -1, + objCriteria = object.criteria, + othCriteria = other.criteria, + length = objCriteria.length, + ordersLength = orders.length; + + while (++index < length) { + var result = compareAscending(objCriteria[index], othCriteria[index]); + if (result) { + if (index >= ordersLength) { + return result; + } + var order = orders[index]; + return result * (order == 'desc' ? -1 : 1); + } + } + // Fixes an `Array#sort` bug in the JS engine embedded in Adobe applications + // that causes it, under certain circumstances, to provide the same value for + // `object` and `other`. See https://github.com/jashkenas/underscore/pull/1247 + // for more details. + // + // This also ensures a stable sort in V8 and other engines. + // See https://bugs.chromium.org/p/v8/issues/detail?id=90 for more details. + return object.index - other.index; + } + + /** + * Creates an array that is the composition of partially applied arguments, + * placeholders, and provided arguments into a single array of arguments. + * + * @private + * @param {Array} args The provided arguments. + * @param {Array} partials The arguments to prepend to those provided. + * @param {Array} holders The `partials` placeholder indexes. + * @params {boolean} [isCurried] Specify composing for a curried function. + * @returns {Array} Returns the new array of composed arguments. + */ + function composeArgs(args, partials, holders, isCurried) { + var argsIndex = -1, + argsLength = args.length, + holdersLength = holders.length, + leftIndex = -1, + leftLength = partials.length, + rangeLength = nativeMax(argsLength - holdersLength, 0), + result = Array(leftLength + rangeLength), + isUncurried = !isCurried; + + while (++leftIndex < leftLength) { + result[leftIndex] = partials[leftIndex]; + } + while (++argsIndex < holdersLength) { + if (isUncurried || argsIndex < argsLength) { + result[holders[argsIndex]] = args[argsIndex]; + } + } + while (rangeLength--) { + result[leftIndex++] = args[argsIndex++]; + } + return result; + } + + /** + * This function is like `composeArgs` except that the arguments composition + * is tailored for `_.partialRight`. + * + * @private + * @param {Array} args The provided arguments. + * @param {Array} partials The arguments to append to those provided. + * @param {Array} holders The `partials` placeholder indexes. + * @params {boolean} [isCurried] Specify composing for a curried function. + * @returns {Array} Returns the new array of composed arguments. + */ + function composeArgsRight(args, partials, holders, isCurried) { + var argsIndex = -1, + argsLength = args.length, + holdersIndex = -1, + holdersLength = holders.length, + rightIndex = -1, + rightLength = partials.length, + rangeLength = nativeMax(argsLength - holdersLength, 0), + result = Array(rangeLength + rightLength), + isUncurried = !isCurried; + + while (++argsIndex < rangeLength) { + result[argsIndex] = args[argsIndex]; + } + var offset = argsIndex; + while (++rightIndex < rightLength) { + result[offset + rightIndex] = partials[rightIndex]; + } + while (++holdersIndex < holdersLength) { + if (isUncurried || argsIndex < argsLength) { + result[offset + holders[holdersIndex]] = args[argsIndex++]; + } + } + return result; + } + + /** + * Copies the values of `source` to `array`. + * + * @private + * @param {Array} source The array to copy values from. + * @param {Array} [array=[]] The array to copy values to. + * @returns {Array} Returns `array`. + */ + function copyArray(source, array) { + var index = -1, + length = source.length; + + array || (array = Array(length)); + while (++index < length) { + array[index] = source[index]; + } + return array; + } + + /** + * Copies properties of `source` to `object`. + * + * @private + * @param {Object} source The object to copy properties from. + * @param {Array} props The property identifiers to copy. + * @param {Object} [object={}] The object to copy properties to. + * @param {Function} [customizer] The function to customize copied values. + * @returns {Object} Returns `object`. + */ + function copyObject(source, props, object, customizer) { + var isNew = !object; + object || (object = {}); + + var index = -1, + length = props.length; + + while (++index < length) { + var key = props[index]; + + var newValue = customizer + ? customizer(object[key], source[key], key, object, source) + : undefined; + + if (newValue === undefined) { + newValue = source[key]; + } + if (isNew) { + baseAssignValue(object, key, newValue); + } else { + assignValue(object, key, newValue); + } + } + return object; + } + + /** + * Copies own symbols of `source` to `object`. + * + * @private + * @param {Object} source The object to copy symbols from. + * @param {Object} [object={}] The object to copy symbols to. + * @returns {Object} Returns `object`. + */ + function copySymbols(source, object) { + return copyObject(source, getSymbols(source), object); + } + + /** + * Copies own and inherited symbols of `source` to `object`. + * + * @private + * @param {Object} source The object to copy symbols from. + * @param {Object} [object={}] The object to copy symbols to. + * @returns {Object} Returns `object`. + */ + function copySymbolsIn(source, object) { + return copyObject(source, getSymbolsIn(source), object); + } + + /** + * Creates a function like `_.groupBy`. + * + * @private + * @param {Function} setter The function to set accumulator values. + * @param {Function} [initializer] The accumulator object initializer. + * @returns {Function} Returns the new aggregator function. + */ + function createAggregator(setter, initializer) { + return function(collection, iteratee) { + var func = isArray(collection) ? arrayAggregator : baseAggregator, + accumulator = initializer ? initializer() : {}; + + return func(collection, setter, getIteratee(iteratee, 2), accumulator); + }; + } + + /** + * Creates a function like `_.assign`. + * + * @private + * @param {Function} assigner The function to assign values. + * @returns {Function} Returns the new assigner function. + */ + function createAssigner(assigner) { + return baseRest(function(object, sources) { + var index = -1, + length = sources.length, + customizer = length > 1 ? sources[length - 1] : undefined, + guard = length > 2 ? sources[2] : undefined; + + customizer = (assigner.length > 3 && typeof customizer == 'function') + ? (length--, customizer) + : undefined; + + if (guard && isIterateeCall(sources[0], sources[1], guard)) { + customizer = length < 3 ? undefined : customizer; + length = 1; + } + object = Object(object); + while (++index < length) { + var source = sources[index]; + if (source) { + assigner(object, source, index, customizer); + } + } + return object; + }); + } + + /** + * Creates a `baseEach` or `baseEachRight` function. + * + * @private + * @param {Function} eachFunc The function to iterate over a collection. + * @param {boolean} [fromRight] Specify iterating from right to left. + * @returns {Function} Returns the new base function. + */ + function createBaseEach(eachFunc, fromRight) { + return function(collection, iteratee) { + if (collection == null) { + return collection; + } + if (!isArrayLike(collection)) { + return eachFunc(collection, iteratee); + } + var length = collection.length, + index = fromRight ? length : -1, + iterable = Object(collection); + + while ((fromRight ? index-- : ++index < length)) { + if (iteratee(iterable[index], index, iterable) === false) { + break; + } + } + return collection; + }; + } + + /** + * Creates a base function for methods like `_.forIn` and `_.forOwn`. + * + * @private + * @param {boolean} [fromRight] Specify iterating from right to left. + * @returns {Function} Returns the new base function. + */ + function createBaseFor(fromRight) { + return function(object, iteratee, keysFunc) { + var index = -1, + iterable = Object(object), + props = keysFunc(object), + length = props.length; + + while (length--) { + var key = props[fromRight ? length : ++index]; + if (iteratee(iterable[key], key, iterable) === false) { + break; + } + } + return object; + }; + } + + /** + * Creates a function that wraps `func` to invoke it with the optional `this` + * binding of `thisArg`. + * + * @private + * @param {Function} func The function to wrap. + * @param {number} bitmask The bitmask flags. See `createWrap` for more details. + * @param {*} [thisArg] The `this` binding of `func`. + * @returns {Function} Returns the new wrapped function. + */ + function createBind(func, bitmask, thisArg) { + var isBind = bitmask & WRAP_BIND_FLAG, + Ctor = createCtor(func); + + function wrapper() { + var fn = (this && this !== root && this instanceof wrapper) ? Ctor : func; + return fn.apply(isBind ? thisArg : this, arguments); + } + return wrapper; + } + + /** + * Creates a function like `_.lowerFirst`. + * + * @private + * @param {string} methodName The name of the `String` case method to use. + * @returns {Function} Returns the new case function. + */ + function createCaseFirst(methodName) { + return function(string) { + string = toString(string); + + var strSymbols = hasUnicode(string) + ? stringToArray(string) + : undefined; + + var chr = strSymbols + ? strSymbols[0] + : string.charAt(0); + + var trailing = strSymbols + ? castSlice(strSymbols, 1).join('') + : string.slice(1); + + return chr[methodName]() + trailing; + }; + } + + /** + * Creates a function like `_.camelCase`. + * + * @private + * @param {Function} callback The function to combine each word. + * @returns {Function} Returns the new compounder function. + */ + function createCompounder(callback) { + return function(string) { + return arrayReduce(words(deburr(string).replace(reApos, '')), callback, ''); + }; + } + + /** + * Creates a function that produces an instance of `Ctor` regardless of + * whether it was invoked as part of a `new` expression or by `call` or `apply`. + * + * @private + * @param {Function} Ctor The constructor to wrap. + * @returns {Function} Returns the new wrapped function. + */ + function createCtor(Ctor) { + return function() { + // Use a `switch` statement to work with class constructors. See + // http://ecma-international.org/ecma-262/7.0/#sec-ecmascript-function-objects-call-thisargument-argumentslist + // for more details. + var args = arguments; + switch (args.length) { + case 0: return new Ctor; + case 1: return new Ctor(args[0]); + case 2: return new Ctor(args[0], args[1]); + case 3: return new Ctor(args[0], args[1], args[2]); + case 4: return new Ctor(args[0], args[1], args[2], args[3]); + case 5: return new Ctor(args[0], args[1], args[2], args[3], args[4]); + case 6: return new Ctor(args[0], args[1], args[2], args[3], args[4], args[5]); + case 7: return new Ctor(args[0], args[1], args[2], args[3], args[4], args[5], args[6]); + } + var thisBinding = baseCreate(Ctor.prototype), + result = Ctor.apply(thisBinding, args); + + // Mimic the constructor's `return` behavior. + // See https://es5.github.io/#x13.2.2 for more details. + return isObject(result) ? result : thisBinding; + }; + } + + /** + * Creates a function that wraps `func` to enable currying. + * + * @private + * @param {Function} func The function to wrap. + * @param {number} bitmask The bitmask flags. See `createWrap` for more details. + * @param {number} arity The arity of `func`. + * @returns {Function} Returns the new wrapped function. + */ + function createCurry(func, bitmask, arity) { + var Ctor = createCtor(func); + + function wrapper() { + var length = arguments.length, + args = Array(length), + index = length, + placeholder = getHolder(wrapper); + while (index--) { - if (array[index] === value) { - return index; - } + args[index] = arguments[index]; } - return index; + var holders = (length < 3 && args[0] !== placeholder && args[length - 1] !== placeholder) + ? [] + : replaceHolders(args, placeholder); + + length -= holders.length; + if (length < arity) { + return createRecurry( + func, bitmask, createHybrid, wrapper.placeholder, undefined, + args, holders, undefined, undefined, arity - length); + } + var fn = (this && this !== root && this instanceof wrapper) ? Ctor : func; + return apply(fn, this, args); + } + return wrapper; } /** - * Gets the number of symbols in `string`. + * Creates a `_.find` or `_.findLast` function. * * @private - * @param {string} string The string to inspect. - * @returns {number} Returns the string size. + * @param {Function} findIndexFunc The function to find the collection index. + * @returns {Function} Returns the new find function. */ - function stringSize(string) { - return hasUnicode(string) - ? unicodeSize(string) - : asciiSize(string); + function createFind(findIndexFunc) { + return function(collection, predicate, fromIndex) { + var iterable = Object(collection); + if (!isArrayLike(collection)) { + var iteratee = getIteratee(predicate, 3); + collection = keys(collection); + predicate = function(key) { return iteratee(iterable[key], key, iterable); }; + } + var index = findIndexFunc(collection, predicate, fromIndex); + return index > -1 ? iterable[iteratee ? collection[index] : index] : undefined; + }; } /** - * Converts `string` to an array. + * Creates a `_.flow` or `_.flowRight` function. * * @private - * @param {string} string The string to convert. - * @returns {Array} Returns the converted array. + * @param {boolean} [fromRight] Specify iterating from right to left. + * @returns {Function} Returns the new flow function. */ - function stringToArray(string) { - return hasUnicode(string) - ? unicodeToArray(string) - : asciiToArray(string); + function createFlow(fromRight) { + return flatRest(function(funcs) { + var length = funcs.length, + index = length, + prereq = LodashWrapper.prototype.thru; + + if (fromRight) { + funcs.reverse(); + } + while (index--) { + var func = funcs[index]; + if (typeof func != 'function') { + throw new TypeError(FUNC_ERROR_TEXT); + } + if (prereq && !wrapper && getFuncName(func) == 'wrapper') { + var wrapper = new LodashWrapper([], true); + } + } + index = wrapper ? index : length; + while (++index < length) { + func = funcs[index]; + + var funcName = getFuncName(func), + data = funcName == 'wrapper' ? getData(func) : undefined; + + if (data && isLaziable(data[0]) && + data[1] == (WRAP_ARY_FLAG | WRAP_CURRY_FLAG | WRAP_PARTIAL_FLAG | WRAP_REARG_FLAG) && + !data[4].length && data[9] == 1 + ) { + wrapper = wrapper[getFuncName(data[0])].apply(wrapper, data[3]); + } else { + wrapper = (func.length == 1 && isLaziable(func)) + ? wrapper[funcName]() + : wrapper.thru(func); + } + } + return function() { + var args = arguments, + value = args[0]; + + if (wrapper && args.length == 1 && isArray(value)) { + return wrapper.plant(value).value(); + } + var index = 0, + result = length ? funcs[index].apply(this, args) : value; + + while (++index < length) { + result = funcs[index].call(this, result); + } + return result; + }; + }); } /** - * Used by `_.unescape` to convert HTML entities to characters. + * Creates a function that wraps `func` to invoke it with optional `this` + * binding of `thisArg`, partial application, and currying. * * @private - * @param {string} chr The matched character to unescape. - * @returns {string} Returns the unescaped character. + * @param {Function|string} func The function or method name to wrap. + * @param {number} bitmask The bitmask flags. See `createWrap` for more details. + * @param {*} [thisArg] The `this` binding of `func`. + * @param {Array} [partials] The arguments to prepend to those provided to + * the new function. + * @param {Array} [holders] The `partials` placeholder indexes. + * @param {Array} [partialsRight] The arguments to append to those provided + * to the new function. + * @param {Array} [holdersRight] The `partialsRight` placeholder indexes. + * @param {Array} [argPos] The argument positions of the new function. + * @param {number} [ary] The arity cap of `func`. + * @param {number} [arity] The arity of `func`. + * @returns {Function} Returns the new wrapped function. */ - var unescapeHtmlChar = basePropertyOf(htmlUnescapes); + function createHybrid(func, bitmask, thisArg, partials, holders, partialsRight, holdersRight, argPos, ary, arity) { + var isAry = bitmask & WRAP_ARY_FLAG, + isBind = bitmask & WRAP_BIND_FLAG, + isBindKey = bitmask & WRAP_BIND_KEY_FLAG, + isCurried = bitmask & (WRAP_CURRY_FLAG | WRAP_CURRY_RIGHT_FLAG), + isFlip = bitmask & WRAP_FLIP_FLAG, + Ctor = isBindKey ? undefined : createCtor(func); + + function wrapper() { + var length = arguments.length, + args = Array(length), + index = length; + + while (index--) { + args[index] = arguments[index]; + } + if (isCurried) { + var placeholder = getHolder(wrapper), + holdersCount = countHolders(args, placeholder); + } + if (partials) { + args = composeArgs(args, partials, holders, isCurried); + } + if (partialsRight) { + args = composeArgsRight(args, partialsRight, holdersRight, isCurried); + } + length -= holdersCount; + if (isCurried && length < arity) { + var newHolders = replaceHolders(args, placeholder); + return createRecurry( + func, bitmask, createHybrid, wrapper.placeholder, thisArg, + args, newHolders, argPos, ary, arity - length + ); + } + var thisBinding = isBind ? thisArg : this, + fn = isBindKey ? thisBinding[func] : func; + + length = args.length; + if (argPos) { + args = reorder(args, argPos); + } else if (isFlip && length > 1) { + args.reverse(); + } + if (isAry && ary < length) { + args.length = ary; + } + if (this && this !== root && this instanceof wrapper) { + fn = Ctor || createCtor(fn); + } + return fn.apply(thisBinding, args); + } + return wrapper; + } /** - * Gets the size of a Unicode `string`. + * Creates a function like `_.invertBy`. * * @private - * @param {string} string The string inspect. - * @returns {number} Returns the string size. + * @param {Function} setter The function to set accumulator values. + * @param {Function} toIteratee The function to resolve iteratees. + * @returns {Function} Returns the new inverter function. */ - function unicodeSize(string) { - var result = reUnicode.lastIndex = 0; - while (reUnicode.test(string)) { - ++result; + function createInverter(setter, toIteratee) { + return function(object, iteratee) { + return baseInverter(object, setter, toIteratee(iteratee), {}); + }; + } + + /** + * Creates a function that performs a mathematical operation on two values. + * + * @private + * @param {Function} operator The function to perform the operation. + * @param {number} [defaultValue] The value used for `undefined` arguments. + * @returns {Function} Returns the new mathematical operation function. + */ + function createMathOperation(operator, defaultValue) { + return function(value, other) { + var result; + if (value === undefined && other === undefined) { + return defaultValue; + } + if (value !== undefined) { + result = value; + } + if (other !== undefined) { + if (result === undefined) { + return other; + } + if (typeof value == 'string' || typeof other == 'string') { + value = baseToString(value); + other = baseToString(other); + } else { + value = baseToNumber(value); + other = baseToNumber(other); + } + result = operator(value, other); } return result; + }; } /** - * Converts a Unicode `string` to an array. + * Creates a function like `_.over`. + * + * @private + * @param {Function} arrayFunc The function to iterate over iteratees. + * @returns {Function} Returns the new over function. + */ + function createOver(arrayFunc) { + return flatRest(function(iteratees) { + iteratees = arrayMap(iteratees, baseUnary(getIteratee())); + return baseRest(function(args) { + var thisArg = this; + return arrayFunc(iteratees, function(iteratee) { + return apply(iteratee, thisArg, args); + }); + }); + }); + } + + /** + * Creates the padding for `string` based on `length`. The `chars` string + * is truncated if the number of characters exceeds `length`. + * + * @private + * @param {number} length The padding length. + * @param {string} [chars=' '] The string used as padding. + * @returns {string} Returns the padding for `string`. + */ + function createPadding(length, chars) { + chars = chars === undefined ? ' ' : baseToString(chars); + + var charsLength = chars.length; + if (charsLength < 2) { + return charsLength ? baseRepeat(chars, length) : chars; + } + var result = baseRepeat(chars, nativeCeil(length / stringSize(chars))); + return hasUnicode(chars) + ? castSlice(stringToArray(result), 0, length).join('') + : result.slice(0, length); + } + + /** + * Creates a function that wraps `func` to invoke it with the `this` binding + * of `thisArg` and `partials` prepended to the arguments it receives. + * + * @private + * @param {Function} func The function to wrap. + * @param {number} bitmask The bitmask flags. See `createWrap` for more details. + * @param {*} thisArg The `this` binding of `func`. + * @param {Array} partials The arguments to prepend to those provided to + * the new function. + * @returns {Function} Returns the new wrapped function. + */ + function createPartial(func, bitmask, thisArg, partials) { + var isBind = bitmask & WRAP_BIND_FLAG, + Ctor = createCtor(func); + + function wrapper() { + var argsIndex = -1, + argsLength = arguments.length, + leftIndex = -1, + leftLength = partials.length, + args = Array(leftLength + argsLength), + fn = (this && this !== root && this instanceof wrapper) ? Ctor : func; + + while (++leftIndex < leftLength) { + args[leftIndex] = partials[leftIndex]; + } + while (argsLength--) { + args[leftIndex++] = arguments[++argsIndex]; + } + return apply(fn, isBind ? thisArg : this, args); + } + return wrapper; + } + + /** + * Creates a `_.range` or `_.rangeRight` function. + * + * @private + * @param {boolean} [fromRight] Specify iterating from right to left. + * @returns {Function} Returns the new range function. + */ + function createRange(fromRight) { + return function(start, end, step) { + if (step && typeof step != 'number' && isIterateeCall(start, end, step)) { + end = step = undefined; + } + // Ensure the sign of `-0` is preserved. + start = toFinite(start); + if (end === undefined) { + end = start; + start = 0; + } else { + end = toFinite(end); + } + step = step === undefined ? (start < end ? 1 : -1) : toFinite(step); + return baseRange(start, end, step, fromRight); + }; + } + + /** + * Creates a function that performs a relational operation on two values. + * + * @private + * @param {Function} operator The function to perform the operation. + * @returns {Function} Returns the new relational operation function. + */ + function createRelationalOperation(operator) { + return function(value, other) { + if (!(typeof value == 'string' && typeof other == 'string')) { + value = toNumber(value); + other = toNumber(other); + } + return operator(value, other); + }; + } + + /** + * Creates a function that wraps `func` to continue currying. + * + * @private + * @param {Function} func The function to wrap. + * @param {number} bitmask The bitmask flags. See `createWrap` for more details. + * @param {Function} wrapFunc The function to create the `func` wrapper. + * @param {*} placeholder The placeholder value. + * @param {*} [thisArg] The `this` binding of `func`. + * @param {Array} [partials] The arguments to prepend to those provided to + * the new function. + * @param {Array} [holders] The `partials` placeholder indexes. + * @param {Array} [argPos] The argument positions of the new function. + * @param {number} [ary] The arity cap of `func`. + * @param {number} [arity] The arity of `func`. + * @returns {Function} Returns the new wrapped function. + */ + function createRecurry(func, bitmask, wrapFunc, placeholder, thisArg, partials, holders, argPos, ary, arity) { + var isCurry = bitmask & WRAP_CURRY_FLAG, + newHolders = isCurry ? holders : undefined, + newHoldersRight = isCurry ? undefined : holders, + newPartials = isCurry ? partials : undefined, + newPartialsRight = isCurry ? undefined : partials; + + bitmask |= (isCurry ? WRAP_PARTIAL_FLAG : WRAP_PARTIAL_RIGHT_FLAG); + bitmask &= ~(isCurry ? WRAP_PARTIAL_RIGHT_FLAG : WRAP_PARTIAL_FLAG); + + if (!(bitmask & WRAP_CURRY_BOUND_FLAG)) { + bitmask &= ~(WRAP_BIND_FLAG | WRAP_BIND_KEY_FLAG); + } + var newData = [ + func, bitmask, thisArg, newPartials, newHolders, newPartialsRight, + newHoldersRight, argPos, ary, arity + ]; + + var result = wrapFunc.apply(undefined, newData); + if (isLaziable(func)) { + setData(result, newData); + } + result.placeholder = placeholder; + return setWrapToString(result, func, bitmask); + } + + /** + * Creates a function like `_.round`. + * + * @private + * @param {string} methodName The name of the `Math` method to use when rounding. + * @returns {Function} Returns the new round function. + */ + function createRound(methodName) { + var func = Math[methodName]; + return function(number, precision) { + number = toNumber(number); + precision = precision == null ? 0 : nativeMin(toInteger(precision), 292); + if (precision && nativeIsFinite(number)) { + // Shift with exponential notation to avoid floating-point issues. + // See [MDN](https://mdn.io/round#Examples) for more details. + var pair = (toString(number) + 'e').split('e'), + value = func(pair[0] + 'e' + (+pair[1] + precision)); + + pair = (toString(value) + 'e').split('e'); + return +(pair[0] + 'e' + (+pair[1] - precision)); + } + return func(number); + }; + } + + /** + * Creates a set object of `values`. + * + * @private + * @param {Array} values The values to add to the set. + * @returns {Object} Returns the new set. + */ + var createSet = !(Set && (1 / setToArray(new Set([,-0]))[1]) == INFINITY) ? noop : function(values) { + return new Set(values); + }; + + /** + * Creates a `_.toPairs` or `_.toPairsIn` function. + * + * @private + * @param {Function} keysFunc The function to get the keys of a given object. + * @returns {Function} Returns the new pairs function. + */ + function createToPairs(keysFunc) { + return function(object) { + var tag = getTag(object); + if (tag == mapTag) { + return mapToArray(object); + } + if (tag == setTag) { + return setToPairs(object); + } + return baseToPairs(object, keysFunc(object)); + }; + } + + /** + * Creates a function that either curries or invokes `func` with optional + * `this` binding and partially applied arguments. + * + * @private + * @param {Function|string} func The function or method name to wrap. + * @param {number} bitmask The bitmask flags. + * 1 - `_.bind` + * 2 - `_.bindKey` + * 4 - `_.curry` or `_.curryRight` of a bound function + * 8 - `_.curry` + * 16 - `_.curryRight` + * 32 - `_.partial` + * 64 - `_.partialRight` + * 128 - `_.rearg` + * 256 - `_.ary` + * 512 - `_.flip` + * @param {*} [thisArg] The `this` binding of `func`. + * @param {Array} [partials] The arguments to be partially applied. + * @param {Array} [holders] The `partials` placeholder indexes. + * @param {Array} [argPos] The argument positions of the new function. + * @param {number} [ary] The arity cap of `func`. + * @param {number} [arity] The arity of `func`. + * @returns {Function} Returns the new wrapped function. + */ + function createWrap(func, bitmask, thisArg, partials, holders, argPos, ary, arity) { + var isBindKey = bitmask & WRAP_BIND_KEY_FLAG; + if (!isBindKey && typeof func != 'function') { + throw new TypeError(FUNC_ERROR_TEXT); + } + var length = partials ? partials.length : 0; + if (!length) { + bitmask &= ~(WRAP_PARTIAL_FLAG | WRAP_PARTIAL_RIGHT_FLAG); + partials = holders = undefined; + } + ary = ary === undefined ? ary : nativeMax(toInteger(ary), 0); + arity = arity === undefined ? arity : toInteger(arity); + length -= holders ? holders.length : 0; + + if (bitmask & WRAP_PARTIAL_RIGHT_FLAG) { + var partialsRight = partials, + holdersRight = holders; + + partials = holders = undefined; + } + var data = isBindKey ? undefined : getData(func); + + var newData = [ + func, bitmask, thisArg, partials, holders, partialsRight, holdersRight, + argPos, ary, arity + ]; + + if (data) { + mergeData(newData, data); + } + func = newData[0]; + bitmask = newData[1]; + thisArg = newData[2]; + partials = newData[3]; + holders = newData[4]; + arity = newData[9] = newData[9] === undefined + ? (isBindKey ? 0 : func.length) + : nativeMax(newData[9] - length, 0); + + if (!arity && bitmask & (WRAP_CURRY_FLAG | WRAP_CURRY_RIGHT_FLAG)) { + bitmask &= ~(WRAP_CURRY_FLAG | WRAP_CURRY_RIGHT_FLAG); + } + if (!bitmask || bitmask == WRAP_BIND_FLAG) { + var result = createBind(func, bitmask, thisArg); + } else if (bitmask == WRAP_CURRY_FLAG || bitmask == WRAP_CURRY_RIGHT_FLAG) { + result = createCurry(func, bitmask, arity); + } else if ((bitmask == WRAP_PARTIAL_FLAG || bitmask == (WRAP_BIND_FLAG | WRAP_PARTIAL_FLAG)) && !holders.length) { + result = createPartial(func, bitmask, thisArg, partials); + } else { + result = createHybrid.apply(undefined, newData); + } + var setter = data ? baseSetData : setData; + return setWrapToString(setter(result, newData), func, bitmask); + } + + /** + * Used by `_.defaults` to customize its `_.assignIn` use to assign properties + * of source objects to the destination object for all destination properties + * that resolve to `undefined`. + * + * @private + * @param {*} objValue The destination value. + * @param {*} srcValue The source value. + * @param {string} key The key of the property to assign. + * @param {Object} object The parent object of `objValue`. + * @returns {*} Returns the value to assign. + */ + function customDefaultsAssignIn(objValue, srcValue, key, object) { + if (objValue === undefined || + (eq(objValue, objectProto[key]) && !hasOwnProperty.call(object, key))) { + return srcValue; + } + return objValue; + } + + /** + * Used by `_.defaultsDeep` to customize its `_.merge` use to merge source + * objects into destination objects that are passed thru. + * + * @private + * @param {*} objValue The destination value. + * @param {*} srcValue The source value. + * @param {string} key The key of the property to merge. + * @param {Object} object The parent object of `objValue`. + * @param {Object} source The parent object of `srcValue`. + * @param {Object} [stack] Tracks traversed source values and their merged + * counterparts. + * @returns {*} Returns the value to assign. + */ + function customDefaultsMerge(objValue, srcValue, key, object, source, stack) { + if (isObject(objValue) && isObject(srcValue)) { + // Recursively merge objects and arrays (susceptible to call stack limits). + stack.set(srcValue, objValue); + baseMerge(objValue, srcValue, undefined, customDefaultsMerge, stack); + stack['delete'](srcValue); + } + return objValue; + } + + /** + * Used by `_.omit` to customize its `_.cloneDeep` use to only clone plain + * objects. + * + * @private + * @param {*} value The value to inspect. + * @param {string} key The key of the property to inspect. + * @returns {*} Returns the uncloned value or `undefined` to defer cloning to `_.cloneDeep`. + */ + function customOmitClone(value) { + return isPlainObject(value) ? undefined : value; + } + + /** + * A specialized version of `baseIsEqualDeep` for arrays with support for + * partial deep comparisons. + * + * @private + * @param {Array} array The array to compare. + * @param {Array} other The other array to compare. + * @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details. + * @param {Function} customizer The function to customize comparisons. + * @param {Function} equalFunc The function to determine equivalents of values. + * @param {Object} stack Tracks traversed `array` and `other` objects. + * @returns {boolean} Returns `true` if the arrays are equivalent, else `false`. + */ + function equalArrays(array, other, bitmask, customizer, equalFunc, stack) { + var isPartial = bitmask & COMPARE_PARTIAL_FLAG, + arrLength = array.length, + othLength = other.length; + + if (arrLength != othLength && !(isPartial && othLength > arrLength)) { + return false; + } + // Check that cyclic values are equal. + var arrStacked = stack.get(array); + var othStacked = stack.get(other); + if (arrStacked && othStacked) { + return arrStacked == other && othStacked == array; + } + var index = -1, + result = true, + seen = (bitmask & COMPARE_UNORDERED_FLAG) ? new SetCache : undefined; + + stack.set(array, other); + stack.set(other, array); + + // Ignore non-index properties. + while (++index < arrLength) { + var arrValue = array[index], + othValue = other[index]; + + if (customizer) { + var compared = isPartial + ? customizer(othValue, arrValue, index, other, array, stack) + : customizer(arrValue, othValue, index, array, other, stack); + } + if (compared !== undefined) { + if (compared) { + continue; + } + result = false; + break; + } + // Recursively compare arrays (susceptible to call stack limits). + if (seen) { + if (!arraySome(other, function(othValue, othIndex) { + if (!cacheHas(seen, othIndex) && + (arrValue === othValue || equalFunc(arrValue, othValue, bitmask, customizer, stack))) { + return seen.push(othIndex); + } + })) { + result = false; + break; + } + } else if (!( + arrValue === othValue || + equalFunc(arrValue, othValue, bitmask, customizer, stack) + )) { + result = false; + break; + } + } + stack['delete'](array); + stack['delete'](other); + return result; + } + + /** + * A specialized version of `baseIsEqualDeep` for comparing objects of + * the same `toStringTag`. + * + * **Note:** This function only supports comparing values with tags of + * `Boolean`, `Date`, `Error`, `Number`, `RegExp`, or `String`. + * + * @private + * @param {Object} object The object to compare. + * @param {Object} other The other object to compare. + * @param {string} tag The `toStringTag` of the objects to compare. + * @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details. + * @param {Function} customizer The function to customize comparisons. + * @param {Function} equalFunc The function to determine equivalents of values. + * @param {Object} stack Tracks traversed `object` and `other` objects. + * @returns {boolean} Returns `true` if the objects are equivalent, else `false`. + */ + function equalByTag(object, other, tag, bitmask, customizer, equalFunc, stack) { + switch (tag) { + case dataViewTag: + if ((object.byteLength != other.byteLength) || + (object.byteOffset != other.byteOffset)) { + return false; + } + object = object.buffer; + other = other.buffer; + + case arrayBufferTag: + if ((object.byteLength != other.byteLength) || + !equalFunc(new Uint8Array(object), new Uint8Array(other))) { + return false; + } + return true; + + case boolTag: + case dateTag: + case numberTag: + // Coerce booleans to `1` or `0` and dates to milliseconds. + // Invalid dates are coerced to `NaN`. + return eq(+object, +other); + + case errorTag: + return object.name == other.name && object.message == other.message; + + case regexpTag: + case stringTag: + // Coerce regexes to strings and treat strings, primitives and objects, + // as equal. See http://www.ecma-international.org/ecma-262/7.0/#sec-regexp.prototype.tostring + // for more details. + return object == (other + ''); + + case mapTag: + var convert = mapToArray; + + case setTag: + var isPartial = bitmask & COMPARE_PARTIAL_FLAG; + convert || (convert = setToArray); + + if (object.size != other.size && !isPartial) { + return false; + } + // Assume cyclic values are equal. + var stacked = stack.get(object); + if (stacked) { + return stacked == other; + } + bitmask |= COMPARE_UNORDERED_FLAG; + + // Recursively compare objects (susceptible to call stack limits). + stack.set(object, other); + var result = equalArrays(convert(object), convert(other), bitmask, customizer, equalFunc, stack); + stack['delete'](object); + return result; + + case symbolTag: + if (symbolValueOf) { + return symbolValueOf.call(object) == symbolValueOf.call(other); + } + } + return false; + } + + /** + * A specialized version of `baseIsEqualDeep` for objects with support for + * partial deep comparisons. + * + * @private + * @param {Object} object The object to compare. + * @param {Object} other The other object to compare. + * @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details. + * @param {Function} customizer The function to customize comparisons. + * @param {Function} equalFunc The function to determine equivalents of values. + * @param {Object} stack Tracks traversed `object` and `other` objects. + * @returns {boolean} Returns `true` if the objects are equivalent, else `false`. + */ + function equalObjects(object, other, bitmask, customizer, equalFunc, stack) { + var isPartial = bitmask & COMPARE_PARTIAL_FLAG, + objProps = getAllKeys(object), + objLength = objProps.length, + othProps = getAllKeys(other), + othLength = othProps.length; + + if (objLength != othLength && !isPartial) { + return false; + } + var index = objLength; + while (index--) { + var key = objProps[index]; + if (!(isPartial ? key in other : hasOwnProperty.call(other, key))) { + return false; + } + } + // Check that cyclic values are equal. + var objStacked = stack.get(object); + var othStacked = stack.get(other); + if (objStacked && othStacked) { + return objStacked == other && othStacked == object; + } + var result = true; + stack.set(object, other); + stack.set(other, object); + + var skipCtor = isPartial; + while (++index < objLength) { + key = objProps[index]; + var objValue = object[key], + othValue = other[key]; + + if (customizer) { + var compared = isPartial + ? customizer(othValue, objValue, key, other, object, stack) + : customizer(objValue, othValue, key, object, other, stack); + } + // Recursively compare objects (susceptible to call stack limits). + if (!(compared === undefined + ? (objValue === othValue || equalFunc(objValue, othValue, bitmask, customizer, stack)) + : compared + )) { + result = false; + break; + } + skipCtor || (skipCtor = key == 'constructor'); + } + if (result && !skipCtor) { + var objCtor = object.constructor, + othCtor = other.constructor; + + // Non `Object` object instances with different constructors are not equal. + if (objCtor != othCtor && + ('constructor' in object && 'constructor' in other) && + !(typeof objCtor == 'function' && objCtor instanceof objCtor && + typeof othCtor == 'function' && othCtor instanceof othCtor)) { + result = false; + } + } + stack['delete'](object); + stack['delete'](other); + return result; + } + + /** + * A specialized version of `baseRest` which flattens the rest array. + * + * @private + * @param {Function} func The function to apply a rest parameter to. + * @returns {Function} Returns the new function. + */ + function flatRest(func) { + return setToString(overRest(func, undefined, flatten), func + ''); + } + + /** + * Creates an array of own enumerable property names and symbols of `object`. + * + * @private + * @param {Object} object The object to query. + * @returns {Array} Returns the array of property names and symbols. + */ + function getAllKeys(object) { + return baseGetAllKeys(object, keys, getSymbols); + } + + /** + * Creates an array of own and inherited enumerable property names and + * symbols of `object`. + * + * @private + * @param {Object} object The object to query. + * @returns {Array} Returns the array of property names and symbols. + */ + function getAllKeysIn(object) { + return baseGetAllKeys(object, keysIn, getSymbolsIn); + } + + /** + * Gets metadata for `func`. + * + * @private + * @param {Function} func The function to query. + * @returns {*} Returns the metadata for `func`. + */ + var getData = !metaMap ? noop : function(func) { + return metaMap.get(func); + }; + + /** + * Gets the name of `func`. + * + * @private + * @param {Function} func The function to query. + * @returns {string} Returns the function name. + */ + function getFuncName(func) { + var result = (func.name + ''), + array = realNames[result], + length = hasOwnProperty.call(realNames, result) ? array.length : 0; + + while (length--) { + var data = array[length], + otherFunc = data.func; + if (otherFunc == null || otherFunc == func) { + return data.name; + } + } + return result; + } + + /** + * Gets the argument placeholder value for `func`. + * + * @private + * @param {Function} func The function to inspect. + * @returns {*} Returns the placeholder value. + */ + function getHolder(func) { + var object = hasOwnProperty.call(lodash, 'placeholder') ? lodash : func; + return object.placeholder; + } + + /** + * Gets the appropriate "iteratee" function. If `_.iteratee` is customized, + * this function returns the custom method, otherwise it returns `baseIteratee`. + * If arguments are provided, the chosen function is invoked with them and + * its result is returned. + * + * @private + * @param {*} [value] The value to convert to an iteratee. + * @param {number} [arity] The arity of the created iteratee. + * @returns {Function} Returns the chosen function or its result. + */ + function getIteratee() { + var result = lodash.iteratee || iteratee; + result = result === iteratee ? baseIteratee : result; + return arguments.length ? result(arguments[0], arguments[1]) : result; + } + + /** + * Gets the data for `map`. + * + * @private + * @param {Object} map The map to query. + * @param {string} key The reference key. + * @returns {*} Returns the map data. + */ + function getMapData(map, key) { + var data = map.__data__; + return isKeyable(key) + ? data[typeof key == 'string' ? 'string' : 'hash'] + : data.map; + } + + /** + * Gets the property names, values, and compare flags of `object`. + * + * @private + * @param {Object} object The object to query. + * @returns {Array} Returns the match data of `object`. + */ + function getMatchData(object) { + var result = keys(object), + length = result.length; + + while (length--) { + var key = result[length], + value = object[key]; + + result[length] = [key, value, isStrictComparable(value)]; + } + return result; + } + + /** + * Gets the native function at `key` of `object`. + * + * @private + * @param {Object} object The object to query. + * @param {string} key The key of the method to get. + * @returns {*} Returns the function if it's native, else `undefined`. + */ + function getNative(object, key) { + var value = getValue(object, key); + return baseIsNative(value) ? value : undefined; + } + + /** + * A specialized version of `baseGetTag` which ignores `Symbol.toStringTag` values. + * + * @private + * @param {*} value The value to query. + * @returns {string} Returns the raw `toStringTag`. + */ + function getRawTag(value) { + var isOwn = hasOwnProperty.call(value, symToStringTag), + tag = value[symToStringTag]; + + try { + value[symToStringTag] = undefined; + var unmasked = true; + } catch (e) {} + + var result = nativeObjectToString.call(value); + if (unmasked) { + if (isOwn) { + value[symToStringTag] = tag; + } else { + delete value[symToStringTag]; + } + } + return result; + } + + /** + * Creates an array of the own enumerable symbols of `object`. + * + * @private + * @param {Object} object The object to query. + * @returns {Array} Returns the array of symbols. + */ + var getSymbols = !nativeGetSymbols ? stubArray : function(object) { + if (object == null) { + return []; + } + object = Object(object); + return arrayFilter(nativeGetSymbols(object), function(symbol) { + return propertyIsEnumerable.call(object, symbol); + }); + }; + + /** + * Creates an array of the own and inherited enumerable symbols of `object`. + * + * @private + * @param {Object} object The object to query. + * @returns {Array} Returns the array of symbols. + */ + var getSymbolsIn = !nativeGetSymbols ? stubArray : function(object) { + var result = []; + while (object) { + arrayPush(result, getSymbols(object)); + object = getPrototype(object); + } + return result; + }; + + /** + * Gets the `toStringTag` of `value`. + * + * @private + * @param {*} value The value to query. + * @returns {string} Returns the `toStringTag`. + */ + var getTag = baseGetTag; + + // Fallback for data views, maps, sets, and weak maps in IE 11 and promises in Node.js < 6. + if ((DataView && getTag(new DataView(new ArrayBuffer(1))) != dataViewTag) || + (Map && getTag(new Map) != mapTag) || + (Promise && getTag(Promise.resolve()) != promiseTag) || + (Set && getTag(new Set) != setTag) || + (WeakMap && getTag(new WeakMap) != weakMapTag)) { + getTag = function(value) { + var result = baseGetTag(value), + Ctor = result == objectTag ? value.constructor : undefined, + ctorString = Ctor ? toSource(Ctor) : ''; + + if (ctorString) { + switch (ctorString) { + case dataViewCtorString: return dataViewTag; + case mapCtorString: return mapTag; + case promiseCtorString: return promiseTag; + case setCtorString: return setTag; + case weakMapCtorString: return weakMapTag; + } + } + return result; + }; + } + + /** + * Gets the view, applying any `transforms` to the `start` and `end` positions. + * + * @private + * @param {number} start The start of the view. + * @param {number} end The end of the view. + * @param {Array} transforms The transformations to apply to the view. + * @returns {Object} Returns an object containing the `start` and `end` + * positions of the view. + */ + function getView(start, end, transforms) { + var index = -1, + length = transforms.length; + + while (++index < length) { + var data = transforms[index], + size = data.size; + + switch (data.type) { + case 'drop': start += size; break; + case 'dropRight': end -= size; break; + case 'take': end = nativeMin(end, start + size); break; + case 'takeRight': start = nativeMax(start, end - size); break; + } + } + return { 'start': start, 'end': end }; + } + + /** + * Extracts wrapper details from the `source` body comment. + * + * @private + * @param {string} source The source to inspect. + * @returns {Array} Returns the wrapper details. + */ + function getWrapDetails(source) { + var match = source.match(reWrapDetails); + return match ? match[1].split(reSplitDetails) : []; + } + + /** + * Checks if `path` exists on `object`. + * + * @private + * @param {Object} object The object to query. + * @param {Array|string} path The path to check. + * @param {Function} hasFunc The function to check properties. + * @returns {boolean} Returns `true` if `path` exists, else `false`. + */ + function hasPath(object, path, hasFunc) { + path = castPath(path, object); + + var index = -1, + length = path.length, + result = false; + + while (++index < length) { + var key = toKey(path[index]); + if (!(result = object != null && hasFunc(object, key))) { + break; + } + object = object[key]; + } + if (result || ++index != length) { + return result; + } + length = object == null ? 0 : object.length; + return !!length && isLength(length) && isIndex(key, length) && + (isArray(object) || isArguments(object)); + } + + /** + * Initializes an array clone. + * + * @private + * @param {Array} array The array to clone. + * @returns {Array} Returns the initialized clone. + */ + function initCloneArray(array) { + var length = array.length, + result = new array.constructor(length); + + // Add properties assigned by `RegExp#exec`. + if (length && typeof array[0] == 'string' && hasOwnProperty.call(array, 'index')) { + result.index = array.index; + result.input = array.input; + } + return result; + } + + /** + * Initializes an object clone. + * + * @private + * @param {Object} object The object to clone. + * @returns {Object} Returns the initialized clone. + */ + function initCloneObject(object) { + return (typeof object.constructor == 'function' && !isPrototype(object)) + ? baseCreate(getPrototype(object)) + : {}; + } + + /** + * Initializes an object clone based on its `toStringTag`. + * + * **Note:** This function only supports cloning values with tags of + * `Boolean`, `Date`, `Error`, `Map`, `Number`, `RegExp`, `Set`, or `String`. + * + * @private + * @param {Object} object The object to clone. + * @param {string} tag The `toStringTag` of the object to clone. + * @param {boolean} [isDeep] Specify a deep clone. + * @returns {Object} Returns the initialized clone. + */ + function initCloneByTag(object, tag, isDeep) { + var Ctor = object.constructor; + switch (tag) { + case arrayBufferTag: + return cloneArrayBuffer(object); + + case boolTag: + case dateTag: + return new Ctor(+object); + + case dataViewTag: + return cloneDataView(object, isDeep); + + case float32Tag: case float64Tag: + case int8Tag: case int16Tag: case int32Tag: + case uint8Tag: case uint8ClampedTag: case uint16Tag: case uint32Tag: + return cloneTypedArray(object, isDeep); + + case mapTag: + return new Ctor; + + case numberTag: + case stringTag: + return new Ctor(object); + + case regexpTag: + return cloneRegExp(object); + + case setTag: + return new Ctor; + + case symbolTag: + return cloneSymbol(object); + } + } + + /** + * Inserts wrapper `details` in a comment at the top of the `source` body. + * + * @private + * @param {string} source The source to modify. + * @returns {Array} details The details to insert. + * @returns {string} Returns the modified source. + */ + function insertWrapDetails(source, details) { + var length = details.length; + if (!length) { + return source; + } + var lastIndex = length - 1; + details[lastIndex] = (length > 1 ? '& ' : '') + details[lastIndex]; + details = details.join(length > 2 ? ', ' : ' '); + return source.replace(reWrapComment, '{\n/* [wrapped with ' + details + '] */\n'); + } + + /** + * Checks if `value` is a flattenable `arguments` object or array. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is flattenable, else `false`. + */ + function isFlattenable(value) { + return isArray(value) || isArguments(value) || + !!(spreadableSymbol && value && value[spreadableSymbol]); + } + + /** + * Checks if `value` is a valid array-like index. + * + * @private + * @param {*} value The value to check. + * @param {number} [length=MAX_SAFE_INTEGER] The upper bounds of a valid index. + * @returns {boolean} Returns `true` if `value` is a valid index, else `false`. + */ + function isIndex(value, length) { + var type = typeof value; + length = length == null ? MAX_SAFE_INTEGER : length; + + return !!length && + (type == 'number' || + (type != 'symbol' && reIsUint.test(value))) && + (value > -1 && value % 1 == 0 && value < length); + } + + /** + * Checks if the given arguments are from an iteratee call. + * + * @private + * @param {*} value The potential iteratee value argument. + * @param {*} index The potential iteratee index or key argument. + * @param {*} object The potential iteratee object argument. + * @returns {boolean} Returns `true` if the arguments are from an iteratee call, + * else `false`. + */ + function isIterateeCall(value, index, object) { + if (!isObject(object)) { + return false; + } + var type = typeof index; + if (type == 'number' + ? (isArrayLike(object) && isIndex(index, object.length)) + : (type == 'string' && index in object) + ) { + return eq(object[index], value); + } + return false; + } + + /** + * Checks if `value` is a property name and not a property path. + * + * @private + * @param {*} value The value to check. + * @param {Object} [object] The object to query keys on. + * @returns {boolean} Returns `true` if `value` is a property name, else `false`. + */ + function isKey(value, object) { + if (isArray(value)) { + return false; + } + var type = typeof value; + if (type == 'number' || type == 'symbol' || type == 'boolean' || + value == null || isSymbol(value)) { + return true; + } + return reIsPlainProp.test(value) || !reIsDeepProp.test(value) || + (object != null && value in Object(object)); + } + + /** + * Checks if `value` is suitable for use as unique object key. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is suitable, else `false`. + */ + function isKeyable(value) { + var type = typeof value; + return (type == 'string' || type == 'number' || type == 'symbol' || type == 'boolean') + ? (value !== '__proto__') + : (value === null); + } + + /** + * Checks if `func` has a lazy counterpart. + * + * @private + * @param {Function} func The function to check. + * @returns {boolean} Returns `true` if `func` has a lazy counterpart, + * else `false`. + */ + function isLaziable(func) { + var funcName = getFuncName(func), + other = lodash[funcName]; + + if (typeof other != 'function' || !(funcName in LazyWrapper.prototype)) { + return false; + } + if (func === other) { + return true; + } + var data = getData(other); + return !!data && func === data[0]; + } + + /** + * Checks if `func` has its source masked. + * + * @private + * @param {Function} func The function to check. + * @returns {boolean} Returns `true` if `func` is masked, else `false`. + */ + function isMasked(func) { + return !!maskSrcKey && (maskSrcKey in func); + } + + /** + * Checks if `func` is capable of being masked. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `func` is maskable, else `false`. + */ + var isMaskable = coreJsData ? isFunction : stubFalse; + + /** + * Checks if `value` is likely a prototype object. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a prototype, else `false`. + */ + function isPrototype(value) { + var Ctor = value && value.constructor, + proto = (typeof Ctor == 'function' && Ctor.prototype) || objectProto; + + return value === proto; + } + + /** + * Checks if `value` is suitable for strict equality comparisons, i.e. `===`. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` if suitable for strict + * equality comparisons, else `false`. + */ + function isStrictComparable(value) { + return value === value && !isObject(value); + } + + /** + * A specialized version of `matchesProperty` for source values suitable + * for strict equality comparisons, i.e. `===`. + * + * @private + * @param {string} key The key of the property to get. + * @param {*} srcValue The value to match. + * @returns {Function} Returns the new spec function. + */ + function matchesStrictComparable(key, srcValue) { + return function(object) { + if (object == null) { + return false; + } + return object[key] === srcValue && + (srcValue !== undefined || (key in Object(object))); + }; + } + + /** + * A specialized version of `_.memoize` which clears the memoized function's + * cache when it exceeds `MAX_MEMOIZE_SIZE`. + * + * @private + * @param {Function} func The function to have its output memoized. + * @returns {Function} Returns the new memoized function. + */ + function memoizeCapped(func) { + var result = memoize(func, function(key) { + if (cache.size === MAX_MEMOIZE_SIZE) { + cache.clear(); + } + return key; + }); + + var cache = result.cache; + return result; + } + + /** + * Merges the function metadata of `source` into `data`. + * + * Merging metadata reduces the number of wrappers used to invoke a function. + * This is possible because methods like `_.bind`, `_.curry`, and `_.partial` + * may be applied regardless of execution order. Methods like `_.ary` and + * `_.rearg` modify function arguments, making the order in which they are + * executed important, preventing the merging of metadata. However, we make + * an exception for a safe combined case where curried functions have `_.ary` + * and or `_.rearg` applied. + * + * @private + * @param {Array} data The destination metadata. + * @param {Array} source The source metadata. + * @returns {Array} Returns `data`. + */ + function mergeData(data, source) { + var bitmask = data[1], + srcBitmask = source[1], + newBitmask = bitmask | srcBitmask, + isCommon = newBitmask < (WRAP_BIND_FLAG | WRAP_BIND_KEY_FLAG | WRAP_ARY_FLAG); + + var isCombo = + ((srcBitmask == WRAP_ARY_FLAG) && (bitmask == WRAP_CURRY_FLAG)) || + ((srcBitmask == WRAP_ARY_FLAG) && (bitmask == WRAP_REARG_FLAG) && (data[7].length <= source[8])) || + ((srcBitmask == (WRAP_ARY_FLAG | WRAP_REARG_FLAG)) && (source[7].length <= source[8]) && (bitmask == WRAP_CURRY_FLAG)); + + // Exit early if metadata can't be merged. + if (!(isCommon || isCombo)) { + return data; + } + // Use source `thisArg` if available. + if (srcBitmask & WRAP_BIND_FLAG) { + data[2] = source[2]; + // Set when currying a bound function. + newBitmask |= bitmask & WRAP_BIND_FLAG ? 0 : WRAP_CURRY_BOUND_FLAG; + } + // Compose partial arguments. + var value = source[3]; + if (value) { + var partials = data[3]; + data[3] = partials ? composeArgs(partials, value, source[4]) : value; + data[4] = partials ? replaceHolders(data[3], PLACEHOLDER) : source[4]; + } + // Compose partial right arguments. + value = source[5]; + if (value) { + partials = data[5]; + data[5] = partials ? composeArgsRight(partials, value, source[6]) : value; + data[6] = partials ? replaceHolders(data[5], PLACEHOLDER) : source[6]; + } + // Use source `argPos` if available. + value = source[7]; + if (value) { + data[7] = value; + } + // Use source `ary` if it's smaller. + if (srcBitmask & WRAP_ARY_FLAG) { + data[8] = data[8] == null ? source[8] : nativeMin(data[8], source[8]); + } + // Use source `arity` if one is not provided. + if (data[9] == null) { + data[9] = source[9]; + } + // Use source `func` and merge bitmasks. + data[0] = source[0]; + data[1] = newBitmask; + + return data; + } + + /** + * This function is like + * [`Object.keys`](http://ecma-international.org/ecma-262/7.0/#sec-object.keys) + * except that it includes inherited enumerable properties. + * + * @private + * @param {Object} object The object to query. + * @returns {Array} Returns the array of property names. + */ + function nativeKeysIn(object) { + var result = []; + if (object != null) { + for (var key in Object(object)) { + result.push(key); + } + } + return result; + } + + /** + * Converts `value` to a string using `Object.prototype.toString`. + * + * @private + * @param {*} value The value to convert. + * @returns {string} Returns the converted string. + */ + function objectToString(value) { + return nativeObjectToString.call(value); + } + + /** + * A specialized version of `baseRest` which transforms the rest array. + * + * @private + * @param {Function} func The function to apply a rest parameter to. + * @param {number} [start=func.length-1] The start position of the rest parameter. + * @param {Function} transform The rest array transform. + * @returns {Function} Returns the new function. + */ + function overRest(func, start, transform) { + start = nativeMax(start === undefined ? (func.length - 1) : start, 0); + return function() { + var args = arguments, + index = -1, + length = nativeMax(args.length - start, 0), + array = Array(length); + + while (++index < length) { + array[index] = args[start + index]; + } + index = -1; + var otherArgs = Array(start + 1); + while (++index < start) { + otherArgs[index] = args[index]; + } + otherArgs[start] = transform(array); + return apply(func, this, otherArgs); + }; + } + + /** + * Gets the parent value at `path` of `object`. + * + * @private + * @param {Object} object The object to query. + * @param {Array} path The path to get the parent value of. + * @returns {*} Returns the parent value. + */ + function parent(object, path) { + return path.length < 2 ? object : baseGet(object, baseSlice(path, 0, -1)); + } + + /** + * Reorder `array` according to the specified indexes where the element at + * the first index is assigned as the first element, the element at + * the second index is assigned as the second element, and so on. + * + * @private + * @param {Array} array The array to reorder. + * @param {Array} indexes The arranged array indexes. + * @returns {Array} Returns `array`. + */ + function reorder(array, indexes) { + var arrLength = array.length, + length = nativeMin(indexes.length, arrLength), + oldArray = copyArray(array); + + while (length--) { + var index = indexes[length]; + array[length] = isIndex(index, arrLength) ? oldArray[index] : undefined; + } + return array; + } + + /** + * Gets the value at `key`, unless `key` is "__proto__" or "constructor". + * + * @private + * @param {Object} object The object to query. + * @param {string} key The key of the property to get. + * @returns {*} Returns the property value. + */ + function safeGet(object, key) { + if (key === 'constructor' && typeof object[key] === 'function') { + return; + } + + if (key == '__proto__') { + return; + } + + return object[key]; + } + + /** + * Sets metadata for `func`. + * + * **Note:** If this function becomes hot, i.e. is invoked a lot in a short + * period of time, it will trip its breaker and transition to an identity + * function to avoid garbage collection pauses in V8. See + * [V8 issue 2070](https://bugs.chromium.org/p/v8/issues/detail?id=2070) + * for more details. + * + * @private + * @param {Function} func The function to associate metadata with. + * @param {*} data The metadata. + * @returns {Function} Returns `func`. + */ + var setData = shortOut(baseSetData); + + /** + * A simple wrapper around the global [`setTimeout`](https://mdn.io/setTimeout). + * + * @private + * @param {Function} func The function to delay. + * @param {number} wait The number of milliseconds to delay invocation. + * @returns {number|Object} Returns the timer id or timeout object. + */ + var setTimeout = ctxSetTimeout || function(func, wait) { + return root.setTimeout(func, wait); + }; + + /** + * Sets the `toString` method of `func` to return `string`. + * + * @private + * @param {Function} func The function to modify. + * @param {Function} string The `toString` result. + * @returns {Function} Returns `func`. + */ + var setToString = shortOut(baseSetToString); + + /** + * Sets the `toString` method of `wrapper` to mimic the source of `reference` + * with wrapper details in a comment at the top of the source body. + * + * @private + * @param {Function} wrapper The function to modify. + * @param {Function} reference The reference function. + * @param {number} bitmask The bitmask flags. See `createWrap` for more details. + * @returns {Function} Returns `wrapper`. + */ + function setWrapToString(wrapper, reference, bitmask) { + var source = (reference + ''); + return setToString(wrapper, insertWrapDetails(source, updateWrapDetails(getWrapDetails(source), bitmask))); + } + + /** + * Creates a function that'll short out and invoke `identity` instead + * of `func` when it's called `HOT_COUNT` or more times in `HOT_SPAN` + * milliseconds. + * + * @private + * @param {Function} func The function to restrict. + * @returns {Function} Returns the new shortable function. + */ + function shortOut(func) { + var count = 0, + lastCalled = 0; + + return function() { + var stamp = nativeNow(), + remaining = HOT_SPAN - (stamp - lastCalled); + + lastCalled = stamp; + if (remaining > 0) { + if (++count >= HOT_COUNT) { + return arguments[0]; + } + } else { + count = 0; + } + return func.apply(undefined, arguments); + }; + } + + /** + * A specialized version of `_.shuffle` which mutates and sets the size of `array`. + * + * @private + * @param {Array} array The array to shuffle. + * @param {number} [size=array.length] The size of `array`. + * @returns {Array} Returns `array`. + */ + function shuffleSelf(array, size) { + var index = -1, + length = array.length, + lastIndex = length - 1; + + size = size === undefined ? length : size; + while (++index < size) { + var rand = baseRandom(index, lastIndex), + value = array[rand]; + + array[rand] = array[index]; + array[index] = value; + } + array.length = size; + return array; + } + + /** + * Converts `string` to a property path array. * * @private * @param {string} string The string to convert. - * @returns {Array} Returns the converted array. + * @returns {Array} Returns the property path array. */ - function unicodeToArray(string) { - return string.match(reUnicode) || []; - } + var stringToPath = memoizeCapped(function(string) { + var result = []; + if (string.charCodeAt(0) === 46 /* . */) { + result.push(''); + } + string.replace(rePropName, function(match, number, quote, subString) { + result.push(quote ? subString.replace(reEscapeChar, '$1') : (number || match)); + }); + return result; + }); /** - * Splits a Unicode `string` into an array of its words. + * Converts `value` to a string key if it's not a string or symbol. * * @private - * @param {string} The string to inspect. - * @returns {Array} Returns the words of `string`. + * @param {*} value The value to inspect. + * @returns {string|symbol} Returns the key. */ - function unicodeWords(string) { - return string.match(reUnicodeWord) || []; + function toKey(value) { + if (typeof value == 'string' || isSymbol(value)) { + return value; + } + var result = (value + ''); + return (result == '0' && (1 / value) == -INFINITY) ? '-0' : result; } - /*--------------------------------------------------------------------------*/ + /** + * Converts `func` to its source code. + * + * @private + * @param {Function} func The function to convert. + * @returns {string} Returns the source code. + */ + function toSource(func) { + if (func != null) { + try { + return funcToString.call(func); + } catch (e) {} + try { + return (func + ''); + } catch (e) {} + } + return ''; + } /** - * Create a new pristine `lodash` function using the `context` object. + * Updates wrapper `details` based on `bitmask` flags. + * + * @private + * @returns {Array} details The details to modify. + * @param {number} bitmask The bitmask flags. See `createWrap` for more details. + * @returns {Array} Returns `details`. + */ + function updateWrapDetails(details, bitmask) { + arrayEach(wrapFlags, function(pair) { + var value = '_.' + pair[0]; + if ((bitmask & pair[1]) && !arrayIncludes(details, value)) { + details.push(value); + } + }); + return details.sort(); + } + + /** + * Creates a clone of `wrapper`. + * + * @private + * @param {Object} wrapper The wrapper to clone. + * @returns {Object} Returns the cloned wrapper. + */ + function wrapperClone(wrapper) { + if (wrapper instanceof LazyWrapper) { + return wrapper.clone(); + } + var result = new LodashWrapper(wrapper.__wrapped__, wrapper.__chain__); + result.__actions__ = copyArray(wrapper.__actions__); + result.__index__ = wrapper.__index__; + result.__values__ = wrapper.__values__; + return result; + } + + /*------------------------------------------------------------------------*/ + + /** + * Creates an array of elements split into groups the length of `size`. + * If `array` can't be split evenly, the final chunk will be the remaining + * elements. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category Array + * @param {Array} array The array to process. + * @param {number} [size=1] The length of each chunk + * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. + * @returns {Array} Returns the new array of chunks. + * @example + * + * _.chunk(['a', 'b', 'c', 'd'], 2); + * // => [['a', 'b'], ['c', 'd']] + * + * _.chunk(['a', 'b', 'c', 'd'], 3); + * // => [['a', 'b', 'c'], ['d']] + */ + function chunk(array, size, guard) { + if ((guard ? isIterateeCall(array, size, guard) : size === undefined)) { + size = 1; + } else { + size = nativeMax(toInteger(size), 0); + } + var length = array == null ? 0 : array.length; + if (!length || size < 1) { + return []; + } + var index = 0, + resIndex = 0, + result = Array(nativeCeil(length / size)); + + while (index < length) { + result[resIndex++] = baseSlice(array, index, (index += size)); + } + return result; + } + + /** + * Creates an array with all falsey values removed. The values `false`, `null`, + * `0`, `""`, `undefined`, and `NaN` are falsey. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Array + * @param {Array} array The array to compact. + * @returns {Array} Returns the new array of filtered values. + * @example + * + * _.compact([0, 1, false, 2, '', 3]); + * // => [1, 2, 3] + */ + function compact(array) { + var index = -1, + length = array == null ? 0 : array.length, + resIndex = 0, + result = []; + + while (++index < length) { + var value = array[index]; + if (value) { + result[resIndex++] = value; + } + } + return result; + } + + /** + * Creates a new array concatenating `array` with any additional arrays + * and/or values. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Array + * @param {Array} array The array to concatenate. + * @param {...*} [values] The values to concatenate. + * @returns {Array} Returns the new concatenated array. + * @example + * + * var array = [1]; + * var other = _.concat(array, 2, [3], [[4]]); + * + * console.log(other); + * // => [1, 2, 3, [4]] + * + * console.log(array); + * // => [1] + */ + function concat() { + var length = arguments.length; + if (!length) { + return []; + } + var args = Array(length - 1), + array = arguments[0], + index = length; + + while (index--) { + args[index - 1] = arguments[index]; + } + return arrayPush(isArray(array) ? copyArray(array) : [array], baseFlatten(args, 1)); + } + + /** + * Creates an array of `array` values not included in the other given arrays + * using [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) + * for equality comparisons. The order and references of result values are + * determined by the first array. + * + * **Note:** Unlike `_.pullAll`, this method returns a new array. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Array + * @param {Array} array The array to inspect. + * @param {...Array} [values] The values to exclude. + * @returns {Array} Returns the new array of filtered values. + * @see _.without, _.xor + * @example + * + * _.difference([2, 1], [2, 3]); + * // => [1] + */ + var difference = baseRest(function(array, values) { + return isArrayLikeObject(array) + ? baseDifference(array, baseFlatten(values, 1, isArrayLikeObject, true)) + : []; + }); + + /** + * This method is like `_.difference` except that it accepts `iteratee` which + * is invoked for each element of `array` and `values` to generate the criterion + * by which they're compared. The order and references of result values are + * determined by the first array. The iteratee is invoked with one argument: + * (value). + * + * **Note:** Unlike `_.pullAllBy`, this method returns a new array. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Array + * @param {Array} array The array to inspect. + * @param {...Array} [values] The values to exclude. + * @param {Function} [iteratee=_.identity] The iteratee invoked per element. + * @returns {Array} Returns the new array of filtered values. + * @example + * + * _.differenceBy([2.1, 1.2], [2.3, 3.4], Math.floor); + * // => [1.2] + * + * // The `_.property` iteratee shorthand. + * _.differenceBy([{ 'x': 2 }, { 'x': 1 }], [{ 'x': 1 }], 'x'); + * // => [{ 'x': 2 }] + */ + var differenceBy = baseRest(function(array, values) { + var iteratee = last(values); + if (isArrayLikeObject(iteratee)) { + iteratee = undefined; + } + return isArrayLikeObject(array) + ? baseDifference(array, baseFlatten(values, 1, isArrayLikeObject, true), getIteratee(iteratee, 2)) + : []; + }); + + /** + * This method is like `_.difference` except that it accepts `comparator` + * which is invoked to compare elements of `array` to `values`. The order and + * references of result values are determined by the first array. The comparator + * is invoked with two arguments: (arrVal, othVal). + * + * **Note:** Unlike `_.pullAllWith`, this method returns a new array. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Array + * @param {Array} array The array to inspect. + * @param {...Array} [values] The values to exclude. + * @param {Function} [comparator] The comparator invoked per element. + * @returns {Array} Returns the new array of filtered values. + * @example + * + * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }]; + * + * _.differenceWith(objects, [{ 'x': 1, 'y': 2 }], _.isEqual); + * // => [{ 'x': 2, 'y': 1 }] + */ + var differenceWith = baseRest(function(array, values) { + var comparator = last(values); + if (isArrayLikeObject(comparator)) { + comparator = undefined; + } + return isArrayLikeObject(array) + ? baseDifference(array, baseFlatten(values, 1, isArrayLikeObject, true), undefined, comparator) + : []; + }); + + /** + * Creates a slice of `array` with `n` elements dropped from the beginning. + * + * @static + * @memberOf _ + * @since 0.5.0 + * @category Array + * @param {Array} array The array to query. + * @param {number} [n=1] The number of elements to drop. + * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. + * @returns {Array} Returns the slice of `array`. + * @example + * + * _.drop([1, 2, 3]); + * // => [2, 3] + * + * _.drop([1, 2, 3], 2); + * // => [3] + * + * _.drop([1, 2, 3], 5); + * // => [] + * + * _.drop([1, 2, 3], 0); + * // => [1, 2, 3] + */ + function drop(array, n, guard) { + var length = array == null ? 0 : array.length; + if (!length) { + return []; + } + n = (guard || n === undefined) ? 1 : toInteger(n); + return baseSlice(array, n < 0 ? 0 : n, length); + } + + /** + * Creates a slice of `array` with `n` elements dropped from the end. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category Array + * @param {Array} array The array to query. + * @param {number} [n=1] The number of elements to drop. + * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. + * @returns {Array} Returns the slice of `array`. + * @example + * + * _.dropRight([1, 2, 3]); + * // => [1, 2] + * + * _.dropRight([1, 2, 3], 2); + * // => [1] + * + * _.dropRight([1, 2, 3], 5); + * // => [] + * + * _.dropRight([1, 2, 3], 0); + * // => [1, 2, 3] + */ + function dropRight(array, n, guard) { + var length = array == null ? 0 : array.length; + if (!length) { + return []; + } + n = (guard || n === undefined) ? 1 : toInteger(n); + n = length - n; + return baseSlice(array, 0, n < 0 ? 0 : n); + } + + /** + * Creates a slice of `array` excluding elements dropped from the end. + * Elements are dropped until `predicate` returns falsey. The predicate is + * invoked with three arguments: (value, index, array). + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category Array + * @param {Array} array The array to query. + * @param {Function} [predicate=_.identity] The function invoked per iteration. + * @returns {Array} Returns the slice of `array`. + * @example + * + * var users = [ + * { 'user': 'barney', 'active': true }, + * { 'user': 'fred', 'active': false }, + * { 'user': 'pebbles', 'active': false } + * ]; + * + * _.dropRightWhile(users, function(o) { return !o.active; }); + * // => objects for ['barney'] + * + * // The `_.matches` iteratee shorthand. + * _.dropRightWhile(users, { 'user': 'pebbles', 'active': false }); + * // => objects for ['barney', 'fred'] + * + * // The `_.matchesProperty` iteratee shorthand. + * _.dropRightWhile(users, ['active', false]); + * // => objects for ['barney'] + * + * // The `_.property` iteratee shorthand. + * _.dropRightWhile(users, 'active'); + * // => objects for ['barney', 'fred', 'pebbles'] + */ + function dropRightWhile(array, predicate) { + return (array && array.length) + ? baseWhile(array, getIteratee(predicate, 3), true, true) + : []; + } + + /** + * Creates a slice of `array` excluding elements dropped from the beginning. + * Elements are dropped until `predicate` returns falsey. The predicate is + * invoked with three arguments: (value, index, array). + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category Array + * @param {Array} array The array to query. + * @param {Function} [predicate=_.identity] The function invoked per iteration. + * @returns {Array} Returns the slice of `array`. + * @example + * + * var users = [ + * { 'user': 'barney', 'active': false }, + * { 'user': 'fred', 'active': false }, + * { 'user': 'pebbles', 'active': true } + * ]; + * + * _.dropWhile(users, function(o) { return !o.active; }); + * // => objects for ['pebbles'] + * + * // The `_.matches` iteratee shorthand. + * _.dropWhile(users, { 'user': 'barney', 'active': false }); + * // => objects for ['fred', 'pebbles'] + * + * // The `_.matchesProperty` iteratee shorthand. + * _.dropWhile(users, ['active', false]); + * // => objects for ['pebbles'] + * + * // The `_.property` iteratee shorthand. + * _.dropWhile(users, 'active'); + * // => objects for ['barney', 'fred', 'pebbles'] + */ + function dropWhile(array, predicate) { + return (array && array.length) + ? baseWhile(array, getIteratee(predicate, 3), true) + : []; + } + + /** + * Fills elements of `array` with `value` from `start` up to, but not + * including, `end`. + * + * **Note:** This method mutates `array`. + * + * @static + * @memberOf _ + * @since 3.2.0 + * @category Array + * @param {Array} array The array to fill. + * @param {*} value The value to fill `array` with. + * @param {number} [start=0] The start position. + * @param {number} [end=array.length] The end position. + * @returns {Array} Returns `array`. + * @example + * + * var array = [1, 2, 3]; + * + * _.fill(array, 'a'); + * console.log(array); + * // => ['a', 'a', 'a'] + * + * _.fill(Array(3), 2); + * // => [2, 2, 2] + * + * _.fill([4, 6, 8, 10], '*', 1, 3); + * // => [4, '*', '*', 10] + */ + function fill(array, value, start, end) { + var length = array == null ? 0 : array.length; + if (!length) { + return []; + } + if (start && typeof start != 'number' && isIterateeCall(array, value, start)) { + start = 0; + end = length; + } + return baseFill(array, value, start, end); + } + + /** + * This method is like `_.find` except that it returns the index of the first + * element `predicate` returns truthy for instead of the element itself. * * @static * @memberOf _ * @since 1.1.0 - * @category Util - * @param {Object} [context=root] The context object. - * @returns {Function} Returns a new `lodash` function. + * @category Array + * @param {Array} array The array to inspect. + * @param {Function} [predicate=_.identity] The function invoked per iteration. + * @param {number} [fromIndex=0] The index to search from. + * @returns {number} Returns the index of the found element, else `-1`. * @example * - * _.mixin({ 'foo': _.constant('foo') }); + * var users = [ + * { 'user': 'barney', 'active': false }, + * { 'user': 'fred', 'active': false }, + * { 'user': 'pebbles', 'active': true } + * ]; * - * var lodash = _.runInContext(); - * lodash.mixin({ 'bar': lodash.constant('bar') }); + * _.findIndex(users, function(o) { return o.user == 'barney'; }); + * // => 0 * - * _.isFunction(_.foo); - * // => true - * _.isFunction(_.bar); - * // => false + * // The `_.matches` iteratee shorthand. + * _.findIndex(users, { 'user': 'fred', 'active': false }); + * // => 1 * - * lodash.isFunction(lodash.foo); - * // => false - * lodash.isFunction(lodash.bar); - * // => true + * // The `_.matchesProperty` iteratee shorthand. + * _.findIndex(users, ['active', false]); + * // => 0 * - * // Create a suped-up `defer` in Node.js. - * var defer = _.runInContext({ 'setTimeout': setImmediate }).defer; + * // The `_.property` iteratee shorthand. + * _.findIndex(users, 'active'); + * // => 2 */ - var runInContext = (function runInContext(context) { - context = context == null ? root : _.defaults(root.Object(), context, _.pick(root, contextProps)); - - /** Built-in constructor references. */ - var Array = context.Array, - Date = context.Date, - Error = context.Error, - Function = context.Function, - Math = context.Math, - Object = context.Object, - RegExp = context.RegExp, - String = context.String, - TypeError = context.TypeError; - - /** Used for built-in method references. */ - var arrayProto = Array.prototype, - funcProto = Function.prototype, - objectProto = Object.prototype; - - /** Used to detect overreaching core-js shims. */ - var coreJsData = context['__core-js_shared__']; - - /** Used to resolve the decompiled source of functions. */ - var funcToString = funcProto.toString; - - /** Used to check objects for own properties. */ - var hasOwnProperty = objectProto.hasOwnProperty; - - /** Used to generate unique IDs. */ - var idCounter = 0; - - /** Used to detect methods masquerading as native. */ - var maskSrcKey = (function() { - var uid = /[^.]+$/.exec(coreJsData && coreJsData.keys && coreJsData.keys.IE_PROTO || ''); - return uid ? ('Symbol(src)_1.' + uid) : ''; - }()); - - /** - * Used to resolve the - * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring) - * of values. - */ - var nativeObjectToString = objectProto.toString; - - /** Used to infer the `Object` constructor. */ - var objectCtorString = funcToString.call(Object); - - /** Used to restore the original `_` reference in `_.noConflict`. */ - var oldDash = root._; - - /** Used to detect if a method is native. */ - var reIsNative = RegExp('^' + - funcToString.call(hasOwnProperty).replace(reRegExpChar, '\\$&') - .replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, '$1.*?') + '$' - ); - - /** Built-in value references. */ - var Buffer = moduleExports ? context.Buffer : undefined, - Symbol = context.Symbol, - Uint8Array = context.Uint8Array, - allocUnsafe = Buffer ? Buffer.allocUnsafe : undefined, - getPrototype = overArg(Object.getPrototypeOf, Object), - objectCreate = Object.create, - propertyIsEnumerable = objectProto.propertyIsEnumerable, - splice = arrayProto.splice, - spreadableSymbol = Symbol ? Symbol.isConcatSpreadable : undefined, - symIterator = Symbol ? Symbol.iterator : undefined, - symToStringTag = Symbol ? Symbol.toStringTag : undefined; - - var defineProperty = (function() { - try { - var func = getNative(Object, 'defineProperty'); - func({}, '', {}); - return func; - } catch (e) {} - }()); - - /** Mocked built-ins. */ - var ctxClearTimeout = context.clearTimeout !== root.clearTimeout && context.clearTimeout, - ctxNow = Date && Date.now !== root.Date.now && Date.now, - ctxSetTimeout = context.setTimeout !== root.setTimeout && context.setTimeout; - - /* Built-in method references for those with the same name as other `lodash` methods. */ - var nativeCeil = Math.ceil, - nativeFloor = Math.floor, - nativeGetSymbols = Object.getOwnPropertySymbols, - nativeIsBuffer = Buffer ? Buffer.isBuffer : undefined, - nativeIsFinite = context.isFinite, - nativeJoin = arrayProto.join, - nativeKeys = overArg(Object.keys, Object), - nativeMax = Math.max, - nativeMin = Math.min, - nativeNow = Date.now, - nativeParseInt = context.parseInt, - nativeRandom = Math.random, - nativeReverse = arrayProto.reverse; - - /* Built-in method references that are verified to be native. */ - var DataView = getNative(context, 'DataView'), - Map = getNative(context, 'Map'), - Promise = getNative(context, 'Promise'), - Set = getNative(context, 'Set'), - WeakMap = getNative(context, 'WeakMap'), - nativeCreate = getNative(Object, 'create'); - - /** Used to store function metadata. */ - var metaMap = WeakMap && new WeakMap; - - /** Used to lookup unminified function names. */ - var realNames = {}; - - /** Used to detect maps, sets, and weakmaps. */ - var dataViewCtorString = toSource(DataView), - mapCtorString = toSource(Map), - promiseCtorString = toSource(Promise), - setCtorString = toSource(Set), - weakMapCtorString = toSource(WeakMap); - - /** Used to convert symbols to primitives and strings. */ - var symbolProto = Symbol ? Symbol.prototype : undefined, - symbolValueOf = symbolProto ? symbolProto.valueOf : undefined, - symbolToString = symbolProto ? symbolProto.toString : undefined; - - /*------------------------------------------------------------------------*/ - - /** - * Creates a `lodash` object which wraps `value` to enable implicit method - * chain sequences. Methods that operate on and return arrays, collections, - * and functions can be chained together. Methods that retrieve a single value - * or may return a primitive value will automatically end the chain sequence - * and return the unwrapped value. Otherwise, the value must be unwrapped - * with `_#value`. - * - * Explicit chain sequences, which must be unwrapped with `_#value`, may be - * enabled using `_.chain`. - * - * The execution of chained methods is lazy, that is, it's deferred until - * `_#value` is implicitly or explicitly called. - * - * Lazy evaluation allows several methods to support shortcut fusion. - * Shortcut fusion is an optimization to merge iteratee calls; this avoids - * the creation of intermediate arrays and can greatly reduce the number of - * iteratee executions. Sections of a chain sequence qualify for shortcut - * fusion if the section is applied to an array and iteratees accept only - * one argument. The heuristic for whether a section qualifies for shortcut - * fusion is subject to change. - * - * Chaining is supported in custom builds as long as the `_#value` method is - * directly or indirectly included in the build. - * - * In addition to lodash methods, wrappers have `Array` and `String` methods. - * - * The wrapper `Array` methods are: - * `concat`, `join`, `pop`, `push`, `shift`, `sort`, `splice`, and `unshift` - * - * The wrapper `String` methods are: - * `replace` and `split` - * - * The wrapper methods that support shortcut fusion are: - * `at`, `compact`, `drop`, `dropRight`, `dropWhile`, `filter`, `find`, - * `findLast`, `head`, `initial`, `last`, `map`, `reject`, `reverse`, `slice`, - * `tail`, `take`, `takeRight`, `takeRightWhile`, `takeWhile`, and `toArray` - * - * The chainable wrapper methods are: - * `after`, `ary`, `assign`, `assignIn`, `assignInWith`, `assignWith`, `at`, - * `before`, `bind`, `bindAll`, `bindKey`, `castArray`, `chain`, `chunk`, - * `commit`, `compact`, `concat`, `conforms`, `constant`, `countBy`, `create`, - * `curry`, `debounce`, `defaults`, `defaultsDeep`, `defer`, `delay`, - * `difference`, `differenceBy`, `differenceWith`, `drop`, `dropRight`, - * `dropRightWhile`, `dropWhile`, `extend`, `extendWith`, `fill`, `filter`, - * `flatMap`, `flatMapDeep`, `flatMapDepth`, `flatten`, `flattenDeep`, - * `flattenDepth`, `flip`, `flow`, `flowRight`, `fromPairs`, `functions`, - * `functionsIn`, `groupBy`, `initial`, `intersection`, `intersectionBy`, - * `intersectionWith`, `invert`, `invertBy`, `invokeMap`, `iteratee`, `keyBy`, - * `keys`, `keysIn`, `map`, `mapKeys`, `mapValues`, `matches`, `matchesProperty`, - * `memoize`, `merge`, `mergeWith`, `method`, `methodOf`, `mixin`, `negate`, - * `nthArg`, `omit`, `omitBy`, `once`, `orderBy`, `over`, `overArgs`, - * `overEvery`, `overSome`, `partial`, `partialRight`, `partition`, `pick`, - * `pickBy`, `plant`, `property`, `propertyOf`, `pull`, `pullAll`, `pullAllBy`, - * `pullAllWith`, `pullAt`, `push`, `range`, `rangeRight`, `rearg`, `reject`, - * `remove`, `rest`, `reverse`, `sampleSize`, `set`, `setWith`, `shuffle`, - * `slice`, `sort`, `sortBy`, `splice`, `spread`, `tail`, `take`, `takeRight`, - * `takeRightWhile`, `takeWhile`, `tap`, `throttle`, `thru`, `toArray`, - * `toPairs`, `toPairsIn`, `toPath`, `toPlainObject`, `transform`, `unary`, - * `union`, `unionBy`, `unionWith`, `uniq`, `uniqBy`, `uniqWith`, `unset`, - * `unshift`, `unzip`, `unzipWith`, `update`, `updateWith`, `values`, - * `valuesIn`, `without`, `wrap`, `xor`, `xorBy`, `xorWith`, `zip`, - * `zipObject`, `zipObjectDeep`, and `zipWith` - * - * The wrapper methods that are **not** chainable by default are: - * `add`, `attempt`, `camelCase`, `capitalize`, `ceil`, `clamp`, `clone`, - * `cloneDeep`, `cloneDeepWith`, `cloneWith`, `conformsTo`, `deburr`, - * `defaultTo`, `divide`, `each`, `eachRight`, `endsWith`, `eq`, `escape`, - * `escapeRegExp`, `every`, `find`, `findIndex`, `findKey`, `findLast`, - * `findLastIndex`, `findLastKey`, `first`, `floor`, `forEach`, `forEachRight`, - * `forIn`, `forInRight`, `forOwn`, `forOwnRight`, `get`, `gt`, `gte`, `has`, - * `hasIn`, `head`, `identity`, `includes`, `indexOf`, `inRange`, `invoke`, - * `isArguments`, `isArray`, `isArrayBuffer`, `isArrayLike`, `isArrayLikeObject`, - * `isBoolean`, `isBuffer`, `isDate`, `isElement`, `isEmpty`, `isEqual`, - * `isEqualWith`, `isError`, `isFinite`, `isFunction`, `isInteger`, `isLength`, - * `isMap`, `isMatch`, `isMatchWith`, `isNaN`, `isNative`, `isNil`, `isNull`, - * `isNumber`, `isObject`, `isObjectLike`, `isPlainObject`, `isRegExp`, - * `isSafeInteger`, `isSet`, `isString`, `isUndefined`, `isTypedArray`, - * `isWeakMap`, `isWeakSet`, `join`, `kebabCase`, `last`, `lastIndexOf`, - * `lowerCase`, `lowerFirst`, `lt`, `lte`, `max`, `maxBy`, `mean`, `meanBy`, - * `min`, `minBy`, `multiply`, `noConflict`, `noop`, `now`, `nth`, `pad`, - * `padEnd`, `padStart`, `parseInt`, `pop`, `random`, `reduce`, `reduceRight`, - * `repeat`, `result`, `round`, `runInContext`, `sample`, `shift`, `size`, - * `snakeCase`, `some`, `sortedIndex`, `sortedIndexBy`, `sortedLastIndex`, - * `sortedLastIndexBy`, `startCase`, `startsWith`, `stubArray`, `stubFalse`, - * `stubObject`, `stubString`, `stubTrue`, `subtract`, `sum`, `sumBy`, - * `template`, `times`, `toFinite`, `toInteger`, `toJSON`, `toLength`, - * `toLower`, `toNumber`, `toSafeInteger`, `toString`, `toUpper`, `trim`, - * `trimEnd`, `trimStart`, `truncate`, `unescape`, `uniqueId`, `upperCase`, - * `upperFirst`, `value`, and `words` - * - * @name _ - * @constructor - * @category Seq - * @param {*} value The value to wrap in a `lodash` instance. - * @returns {Object} Returns the new `lodash` wrapper instance. - * @example - * - * function square(n) { - * return n * n; - * } - * - * var wrapped = _([1, 2, 3]); - * - * // Returns an unwrapped value. - * wrapped.reduce(_.add); - * // => 6 - * - * // Returns a wrapped value. - * var squares = wrapped.map(square); - * - * _.isArray(squares); - * // => false - * - * _.isArray(squares.value()); - * // => true - */ - function lodash(value) { - if (isObjectLike(value) && !isArray(value) && !(value instanceof LazyWrapper)) { - if (value instanceof LodashWrapper) { - return value; - } - if (hasOwnProperty.call(value, '__wrapped__')) { - return wrapperClone(value); - } - } - return new LodashWrapper(value); - } - - /** - * The base implementation of `_.create` without support for assigning - * properties to the created object. - * - * @private - * @param {Object} proto The object to inherit from. - * @returns {Object} Returns the new object. - */ - var baseCreate = (function() { - function object() {} - return function(proto) { - if (!isObject(proto)) { - return {}; - } - if (objectCreate) { - return objectCreate(proto); - } - object.prototype = proto; - var result = new object; - object.prototype = undefined; - return result; - }; - }()); - - /** - * The function whose prototype chain sequence wrappers inherit from. - * - * @private - */ - function baseLodash() { - // No operation performed. - } - - /** - * The base constructor for creating `lodash` wrapper objects. - * - * @private - * @param {*} value The value to wrap. - * @param {boolean} [chainAll] Enable explicit method chain sequences. - */ - function LodashWrapper(value, chainAll) { - this.__wrapped__ = value; - this.__actions__ = []; - this.__chain__ = !!chainAll; - this.__index__ = 0; - this.__values__ = undefined; - } - - /** - * By default, the template delimiters used by lodash are like those in - * embedded Ruby (ERB) as well as ES2015 template strings. Change the - * following template settings to use alternative delimiters. - * - * @static - * @memberOf _ - * @type {Object} - */ - lodash.templateSettings = { - - /** - * Used to detect `data` property values to be HTML-escaped. - * - * @memberOf _.templateSettings - * @type {RegExp} - */ - 'escape': reEscape, - - /** - * Used to detect code to be evaluated. - * - * @memberOf _.templateSettings - * @type {RegExp} - */ - 'evaluate': reEvaluate, - - /** - * Used to detect `data` property values to inject. - * - * @memberOf _.templateSettings - * @type {RegExp} - */ - 'interpolate': reInterpolate, - - /** - * Used to reference the data object in the template text. - * - * @memberOf _.templateSettings - * @type {string} - */ - 'variable': '', - - /** - * Used to import variables into the compiled template. - * - * @memberOf _.templateSettings - * @type {Object} - */ - 'imports': { - - /** - * A reference to the `lodash` function. - * - * @memberOf _.templateSettings.imports - * @type {Function} - */ - '_': lodash - } - }; - - // Ensure wrappers are instances of `baseLodash`. - lodash.prototype = baseLodash.prototype; - lodash.prototype.constructor = lodash; - - LodashWrapper.prototype = baseCreate(baseLodash.prototype); - LodashWrapper.prototype.constructor = LodashWrapper; - - /*------------------------------------------------------------------------*/ - - /** - * Creates a lazy wrapper object which wraps `value` to enable lazy evaluation. - * - * @private - * @constructor - * @param {*} value The value to wrap. - */ - function LazyWrapper(value) { - this.__wrapped__ = value; - this.__actions__ = []; - this.__dir__ = 1; - this.__filtered__ = false; - this.__iteratees__ = []; - this.__takeCount__ = MAX_ARRAY_LENGTH; - this.__views__ = []; - } - - /** - * Creates a clone of the lazy wrapper object. - * - * @private - * @name clone - * @memberOf LazyWrapper - * @returns {Object} Returns the cloned `LazyWrapper` object. - */ - function lazyClone() { - var result = new LazyWrapper(this.__wrapped__); - result.__actions__ = copyArray(this.__actions__); - result.__dir__ = this.__dir__; - result.__filtered__ = this.__filtered__; - result.__iteratees__ = copyArray(this.__iteratees__); - result.__takeCount__ = this.__takeCount__; - result.__views__ = copyArray(this.__views__); - return result; - } - - /** - * Reverses the direction of lazy iteration. - * - * @private - * @name reverse - * @memberOf LazyWrapper - * @returns {Object} Returns the new reversed `LazyWrapper` object. - */ - function lazyReverse() { - if (this.__filtered__) { - var result = new LazyWrapper(this); - result.__dir__ = -1; - result.__filtered__ = true; - } else { - result = this.clone(); - result.__dir__ *= -1; - } - return result; - } - - /** - * Extracts the unwrapped value from its lazy wrapper. - * - * @private - * @name value - * @memberOf LazyWrapper - * @returns {*} Returns the unwrapped value. - */ - function lazyValue() { - var array = this.__wrapped__.value(), - dir = this.__dir__, - isArr = isArray(array), - isRight = dir < 0, - arrLength = isArr ? array.length : 0, - view = getView(0, arrLength, this.__views__), - start = view.start, - end = view.end, - length = end - start, - index = isRight ? end : (start - 1), - iteratees = this.__iteratees__, - iterLength = iteratees.length, - resIndex = 0, - takeCount = nativeMin(length, this.__takeCount__); - - if (!isArr || (!isRight && arrLength == length && takeCount == length)) { - return baseWrapperValue(array, this.__actions__); - } - var result = []; - - outer: - while (length-- && resIndex < takeCount) { - index += dir; - - var iterIndex = -1, - value = array[index]; - - while (++iterIndex < iterLength) { - var data = iteratees[iterIndex], - iteratee = data.iteratee, - type = data.type, - computed = iteratee(value); - - if (type == LAZY_MAP_FLAG) { - value = computed; - } else if (!computed) { - if (type == LAZY_FILTER_FLAG) { - continue outer; - } else { - break outer; - } - } - } - result[resIndex++] = value; - } - return result; - } - - // Ensure `LazyWrapper` is an instance of `baseLodash`. - LazyWrapper.prototype = baseCreate(baseLodash.prototype); - LazyWrapper.prototype.constructor = LazyWrapper; - - /*------------------------------------------------------------------------*/ - - /** - * Creates a hash object. - * - * @private - * @constructor - * @param {Array} [entries] The key-value pairs to cache. - */ - function Hash(entries) { - var index = -1, - length = entries == null ? 0 : entries.length; - - this.clear(); - while (++index < length) { - var entry = entries[index]; - this.set(entry[0], entry[1]); - } - } - - /** - * Removes all key-value entries from the hash. - * - * @private - * @name clear - * @memberOf Hash - */ - function hashClear() { - this.__data__ = nativeCreate ? nativeCreate(null) : {}; - this.size = 0; - } - - /** - * Removes `key` and its value from the hash. - * - * @private - * @name delete - * @memberOf Hash - * @param {Object} hash The hash to modify. - * @param {string} key The key of the value to remove. - * @returns {boolean} Returns `true` if the entry was removed, else `false`. - */ - function hashDelete(key) { - var result = this.has(key) && delete this.__data__[key]; - this.size -= result ? 1 : 0; - return result; - } - - /** - * Gets the hash value for `key`. - * - * @private - * @name get - * @memberOf Hash - * @param {string} key The key of the value to get. - * @returns {*} Returns the entry value. - */ - function hashGet(key) { - var data = this.__data__; - if (nativeCreate) { - var result = data[key]; - return result === HASH_UNDEFINED ? undefined : result; - } - return hasOwnProperty.call(data, key) ? data[key] : undefined; - } - - /** - * Checks if a hash value for `key` exists. - * - * @private - * @name has - * @memberOf Hash - * @param {string} key The key of the entry to check. - * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. - */ - function hashHas(key) { - var data = this.__data__; - return nativeCreate ? (data[key] !== undefined) : hasOwnProperty.call(data, key); - } - - /** - * Sets the hash `key` to `value`. - * - * @private - * @name set - * @memberOf Hash - * @param {string} key The key of the value to set. - * @param {*} value The value to set. - * @returns {Object} Returns the hash instance. - */ - function hashSet(key, value) { - var data = this.__data__; - this.size += this.has(key) ? 0 : 1; - data[key] = (nativeCreate && value === undefined) ? HASH_UNDEFINED : value; - return this; - } - - // Add methods to `Hash`. - Hash.prototype.clear = hashClear; - Hash.prototype['delete'] = hashDelete; - Hash.prototype.get = hashGet; - Hash.prototype.has = hashHas; - Hash.prototype.set = hashSet; - - /*------------------------------------------------------------------------*/ - - /** - * Creates an list cache object. - * - * @private - * @constructor - * @param {Array} [entries] The key-value pairs to cache. - */ - function ListCache(entries) { - var index = -1, - length = entries == null ? 0 : entries.length; - - this.clear(); - while (++index < length) { - var entry = entries[index]; - this.set(entry[0], entry[1]); - } - } - - /** - * Removes all key-value entries from the list cache. - * - * @private - * @name clear - * @memberOf ListCache - */ - function listCacheClear() { - this.__data__ = []; - this.size = 0; - } - - /** - * Removes `key` and its value from the list cache. - * - * @private - * @name delete - * @memberOf ListCache - * @param {string} key The key of the value to remove. - * @returns {boolean} Returns `true` if the entry was removed, else `false`. - */ - function listCacheDelete(key) { - var data = this.__data__, - index = assocIndexOf(data, key); - - if (index < 0) { - return false; - } - var lastIndex = data.length - 1; - if (index == lastIndex) { - data.pop(); - } else { - splice.call(data, index, 1); - } - --this.size; - return true; - } - - /** - * Gets the list cache value for `key`. - * - * @private - * @name get - * @memberOf ListCache - * @param {string} key The key of the value to get. - * @returns {*} Returns the entry value. - */ - function listCacheGet(key) { - var data = this.__data__, - index = assocIndexOf(data, key); - - return index < 0 ? undefined : data[index][1]; - } - - /** - * Checks if a list cache value for `key` exists. - * - * @private - * @name has - * @memberOf ListCache - * @param {string} key The key of the entry to check. - * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. - */ - function listCacheHas(key) { - return assocIndexOf(this.__data__, key) > -1; - } - - /** - * Sets the list cache `key` to `value`. - * - * @private - * @name set - * @memberOf ListCache - * @param {string} key The key of the value to set. - * @param {*} value The value to set. - * @returns {Object} Returns the list cache instance. - */ - function listCacheSet(key, value) { - var data = this.__data__, - index = assocIndexOf(data, key); - - if (index < 0) { - ++this.size; - data.push([key, value]); - } else { - data[index][1] = value; - } - return this; - } - - // Add methods to `ListCache`. - ListCache.prototype.clear = listCacheClear; - ListCache.prototype['delete'] = listCacheDelete; - ListCache.prototype.get = listCacheGet; - ListCache.prototype.has = listCacheHas; - ListCache.prototype.set = listCacheSet; - - /*------------------------------------------------------------------------*/ - - /** - * Creates a map cache object to store key-value pairs. - * - * @private - * @constructor - * @param {Array} [entries] The key-value pairs to cache. - */ - function MapCache(entries) { - var index = -1, - length = entries == null ? 0 : entries.length; - - this.clear(); - while (++index < length) { - var entry = entries[index]; - this.set(entry[0], entry[1]); - } - } - - /** - * Removes all key-value entries from the map. - * - * @private - * @name clear - * @memberOf MapCache - */ - function mapCacheClear() { - this.size = 0; - this.__data__ = { - 'hash': new Hash, - 'map': new (Map || ListCache), - 'string': new Hash - }; - } - - /** - * Removes `key` and its value from the map. - * - * @private - * @name delete - * @memberOf MapCache - * @param {string} key The key of the value to remove. - * @returns {boolean} Returns `true` if the entry was removed, else `false`. - */ - function mapCacheDelete(key) { - var result = getMapData(this, key)['delete'](key); - this.size -= result ? 1 : 0; - return result; - } - - /** - * Gets the map value for `key`. - * - * @private - * @name get - * @memberOf MapCache - * @param {string} key The key of the value to get. - * @returns {*} Returns the entry value. - */ - function mapCacheGet(key) { - return getMapData(this, key).get(key); - } - - /** - * Checks if a map value for `key` exists. - * - * @private - * @name has - * @memberOf MapCache - * @param {string} key The key of the entry to check. - * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. - */ - function mapCacheHas(key) { - return getMapData(this, key).has(key); - } - - /** - * Sets the map `key` to `value`. - * - * @private - * @name set - * @memberOf MapCache - * @param {string} key The key of the value to set. - * @param {*} value The value to set. - * @returns {Object} Returns the map cache instance. - */ - function mapCacheSet(key, value) { - var data = getMapData(this, key), - size = data.size; - - data.set(key, value); - this.size += data.size == size ? 0 : 1; - return this; - } - - // Add methods to `MapCache`. - MapCache.prototype.clear = mapCacheClear; - MapCache.prototype['delete'] = mapCacheDelete; - MapCache.prototype.get = mapCacheGet; - MapCache.prototype.has = mapCacheHas; - MapCache.prototype.set = mapCacheSet; - - /*------------------------------------------------------------------------*/ - - /** - * - * Creates an array cache object to store unique values. - * - * @private - * @constructor - * @param {Array} [values] The values to cache. - */ - function SetCache(values) { - var index = -1, - length = values == null ? 0 : values.length; - - this.__data__ = new MapCache; - while (++index < length) { - this.add(values[index]); - } - } - - /** - * Adds `value` to the array cache. - * - * @private - * @name add - * @memberOf SetCache - * @alias push - * @param {*} value The value to cache. - * @returns {Object} Returns the cache instance. - */ - function setCacheAdd(value) { - this.__data__.set(value, HASH_UNDEFINED); - return this; - } - - /** - * Checks if `value` is in the array cache. - * - * @private - * @name has - * @memberOf SetCache - * @param {*} value The value to search for. - * @returns {number} Returns `true` if `value` is found, else `false`. - */ - function setCacheHas(value) { - return this.__data__.has(value); - } - - // Add methods to `SetCache`. - SetCache.prototype.add = SetCache.prototype.push = setCacheAdd; - SetCache.prototype.has = setCacheHas; - - /*------------------------------------------------------------------------*/ - - /** - * Creates a stack cache object to store key-value pairs. - * - * @private - * @constructor - * @param {Array} [entries] The key-value pairs to cache. - */ - function Stack(entries) { - var data = this.__data__ = new ListCache(entries); - this.size = data.size; - } - - /** - * Removes all key-value entries from the stack. - * - * @private - * @name clear - * @memberOf Stack - */ - function stackClear() { - this.__data__ = new ListCache; - this.size = 0; - } - - /** - * Removes `key` and its value from the stack. - * - * @private - * @name delete - * @memberOf Stack - * @param {string} key The key of the value to remove. - * @returns {boolean} Returns `true` if the entry was removed, else `false`. - */ - function stackDelete(key) { - var data = this.__data__, - result = data['delete'](key); - - this.size = data.size; - return result; - } - - /** - * Gets the stack value for `key`. - * - * @private - * @name get - * @memberOf Stack - * @param {string} key The key of the value to get. - * @returns {*} Returns the entry value. - */ - function stackGet(key) { - return this.__data__.get(key); - } - - /** - * Checks if a stack value for `key` exists. - * - * @private - * @name has - * @memberOf Stack - * @param {string} key The key of the entry to check. - * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. - */ - function stackHas(key) { - return this.__data__.has(key); - } - - /** - * Sets the stack `key` to `value`. - * - * @private - * @name set - * @memberOf Stack - * @param {string} key The key of the value to set. - * @param {*} value The value to set. - * @returns {Object} Returns the stack cache instance. - */ - function stackSet(key, value) { - var data = this.__data__; - if (data instanceof ListCache) { - var pairs = data.__data__; - if (!Map || (pairs.length < LARGE_ARRAY_SIZE - 1)) { - pairs.push([key, value]); - this.size = ++data.size; - return this; - } - data = this.__data__ = new MapCache(pairs); - } - data.set(key, value); - this.size = data.size; - return this; - } - - // Add methods to `Stack`. - Stack.prototype.clear = stackClear; - Stack.prototype['delete'] = stackDelete; - Stack.prototype.get = stackGet; - Stack.prototype.has = stackHas; - Stack.prototype.set = stackSet; - - /*------------------------------------------------------------------------*/ - - /** - * Creates an array of the enumerable property names of the array-like `value`. - * - * @private - * @param {*} value The value to query. - * @param {boolean} inherited Specify returning inherited property names. - * @returns {Array} Returns the array of property names. - */ - function arrayLikeKeys(value, inherited) { - var isArr = isArray(value), - isArg = !isArr && isArguments(value), - isBuff = !isArr && !isArg && isBuffer(value), - isType = !isArr && !isArg && !isBuff && isTypedArray(value), - skipIndexes = isArr || isArg || isBuff || isType, - result = skipIndexes ? baseTimes(value.length, String) : [], - length = result.length; - - for (var key in value) { - if ((inherited || hasOwnProperty.call(value, key)) && - !(skipIndexes && ( - // Safari 9 has enumerable `arguments.length` in strict mode. - key == 'length' || - // Node.js 0.10 has enumerable non-index properties on buffers. - (isBuff && (key == 'offset' || key == 'parent')) || - // PhantomJS 2 has enumerable non-index properties on typed arrays. - (isType && (key == 'buffer' || key == 'byteLength' || key == 'byteOffset')) || - // Skip index properties. - isIndex(key, length) - ))) { - result.push(key); - } - } - return result; - } - - /** - * A specialized version of `_.sample` for arrays. - * - * @private - * @param {Array} array The array to sample. - * @returns {*} Returns the random element. - */ - function arraySample(array) { - var length = array.length; - return length ? array[baseRandom(0, length - 1)] : undefined; - } - - /** - * A specialized version of `_.sampleSize` for arrays. - * - * @private - * @param {Array} array The array to sample. - * @param {number} n The number of elements to sample. - * @returns {Array} Returns the random elements. - */ - function arraySampleSize(array, n) { - return shuffleSelf(copyArray(array), baseClamp(n, 0, array.length)); - } - - /** - * A specialized version of `_.shuffle` for arrays. - * - * @private - * @param {Array} array The array to shuffle. - * @returns {Array} Returns the new shuffled array. - */ - function arrayShuffle(array) { - return shuffleSelf(copyArray(array)); - } - - /** - * This function is like `assignValue` except that it doesn't assign - * `undefined` values. - * - * @private - * @param {Object} object The object to modify. - * @param {string} key The key of the property to assign. - * @param {*} value The value to assign. - */ - function assignMergeValue(object, key, value) { - if ((value !== undefined && !eq(object[key], value)) || - (value === undefined && !(key in object))) { - baseAssignValue(object, key, value); - } - } - - /** - * Assigns `value` to `key` of `object` if the existing value is not equivalent - * using [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) - * for equality comparisons. - * - * @private - * @param {Object} object The object to modify. - * @param {string} key The key of the property to assign. - * @param {*} value The value to assign. - */ - function assignValue(object, key, value) { - var objValue = object[key]; - if (!(hasOwnProperty.call(object, key) && eq(objValue, value)) || - (value === undefined && !(key in object))) { - baseAssignValue(object, key, value); - } - } - - /** - * Gets the index at which the `key` is found in `array` of key-value pairs. - * - * @private - * @param {Array} array The array to inspect. - * @param {*} key The key to search for. - * @returns {number} Returns the index of the matched value, else `-1`. - */ - function assocIndexOf(array, key) { - var length = array.length; - while (length--) { - if (eq(array[length][0], key)) { - return length; - } - } - return -1; - } - - /** - * Aggregates elements of `collection` on `accumulator` with keys transformed - * by `iteratee` and values set by `setter`. - * - * @private - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} setter The function to set `accumulator` values. - * @param {Function} iteratee The iteratee to transform keys. - * @param {Object} accumulator The initial aggregated object. - * @returns {Function} Returns `accumulator`. - */ - function baseAggregator(collection, setter, iteratee, accumulator) { - baseEach(collection, function(value, key, collection) { - setter(accumulator, value, iteratee(value), collection); - }); - return accumulator; - } - - /** - * The base implementation of `_.assign` without support for multiple sources - * or `customizer` functions. - * - * @private - * @param {Object} object The destination object. - * @param {Object} source The source object. - * @returns {Object} Returns `object`. - */ - function baseAssign(object, source) { - return object && copyObject(source, keys(source), object); - } - - /** - * The base implementation of `_.assignIn` without support for multiple sources - * or `customizer` functions. - * - * @private - * @param {Object} object The destination object. - * @param {Object} source The source object. - * @returns {Object} Returns `object`. - */ - function baseAssignIn(object, source) { - return object && copyObject(source, keysIn(source), object); - } - - /** - * The base implementation of `assignValue` and `assignMergeValue` without - * value checks. - * - * @private - * @param {Object} object The object to modify. - * @param {string} key The key of the property to assign. - * @param {*} value The value to assign. - */ - function baseAssignValue(object, key, value) { - if (key == '__proto__' && defineProperty) { - defineProperty(object, key, { - 'configurable': true, - 'enumerable': true, - 'value': value, - 'writable': true - }); - } else { - object[key] = value; - } - } - - /** - * The base implementation of `_.at` without support for individual paths. - * - * @private - * @param {Object} object The object to iterate over. - * @param {string[]} paths The property paths to pick. - * @returns {Array} Returns the picked elements. - */ - function baseAt(object, paths) { - var index = -1, - length = paths.length, - result = Array(length), - skip = object == null; - - while (++index < length) { - result[index] = skip ? undefined : get(object, paths[index]); - } - return result; - } - - /** - * The base implementation of `_.clamp` which doesn't coerce arguments. - * - * @private - * @param {number} number The number to clamp. - * @param {number} [lower] The lower bound. - * @param {number} upper The upper bound. - * @returns {number} Returns the clamped number. - */ - function baseClamp(number, lower, upper) { - if (number === number) { - if (upper !== undefined) { - number = number <= upper ? number : upper; - } - if (lower !== undefined) { - number = number >= lower ? number : lower; - } - } - return number; - } - - /** - * The base implementation of `_.clone` and `_.cloneDeep` which tracks - * traversed objects. - * - * @private - * @param {*} value The value to clone. - * @param {boolean} bitmask The bitmask flags. - * 1 - Deep clone - * 2 - Flatten inherited properties - * 4 - Clone symbols - * @param {Function} [customizer] The function to customize cloning. - * @param {string} [key] The key of `value`. - * @param {Object} [object] The parent object of `value`. - * @param {Object} [stack] Tracks traversed objects and their clone counterparts. - * @returns {*} Returns the cloned value. - */ - function baseClone(value, bitmask, customizer, key, object, stack) { - var result, - isDeep = bitmask & CLONE_DEEP_FLAG, - isFlat = bitmask & CLONE_FLAT_FLAG, - isFull = bitmask & CLONE_SYMBOLS_FLAG; - - if (customizer) { - result = object ? customizer(value, key, object, stack) : customizer(value); - } - if (result !== undefined) { - return result; - } - if (!isObject(value)) { - return value; - } - var isArr = isArray(value); - if (isArr) { - result = initCloneArray(value); - if (!isDeep) { - return copyArray(value, result); - } - } else { - var tag = getTag(value), - isFunc = tag == funcTag || tag == genTag; - - if (isBuffer(value)) { - return cloneBuffer(value, isDeep); - } - if (tag == objectTag || tag == argsTag || (isFunc && !object)) { - result = (isFlat || isFunc) ? {} : initCloneObject(value); - if (!isDeep) { - return isFlat - ? copySymbolsIn(value, baseAssignIn(result, value)) - : copySymbols(value, baseAssign(result, value)); - } - } else { - if (!cloneableTags[tag]) { - return object ? value : {}; - } - result = initCloneByTag(value, tag, isDeep); - } - } - // Check for circular references and return its corresponding clone. - stack || (stack = new Stack); - var stacked = stack.get(value); - if (stacked) { - return stacked; - } - stack.set(value, result); - - if (isSet(value)) { - value.forEach(function(subValue) { - result.add(baseClone(subValue, bitmask, customizer, subValue, value, stack)); - }); - } else if (isMap(value)) { - value.forEach(function(subValue, key) { - result.set(key, baseClone(subValue, bitmask, customizer, key, value, stack)); - }); - } - - var keysFunc = isFull - ? (isFlat ? getAllKeysIn : getAllKeys) - : (isFlat ? keysIn : keys); - - var props = isArr ? undefined : keysFunc(value); - arrayEach(props || value, function(subValue, key) { - if (props) { - key = subValue; - subValue = value[key]; - } - // Recursively populate clone (susceptible to call stack limits). - assignValue(result, key, baseClone(subValue, bitmask, customizer, key, value, stack)); - }); - return result; - } - - /** - * The base implementation of `_.conforms` which doesn't clone `source`. - * - * @private - * @param {Object} source The object of property predicates to conform to. - * @returns {Function} Returns the new spec function. - */ - function baseConforms(source) { - var props = keys(source); - return function(object) { - return baseConformsTo(object, source, props); - }; - } - - /** - * The base implementation of `_.conformsTo` which accepts `props` to check. - * - * @private - * @param {Object} object The object to inspect. - * @param {Object} source The object of property predicates to conform to. - * @returns {boolean} Returns `true` if `object` conforms, else `false`. - */ - function baseConformsTo(object, source, props) { - var length = props.length; - if (object == null) { - return !length; - } - object = Object(object); - while (length--) { - var key = props[length], - predicate = source[key], - value = object[key]; - - if ((value === undefined && !(key in object)) || !predicate(value)) { - return false; - } - } - return true; - } - - /** - * The base implementation of `_.delay` and `_.defer` which accepts `args` - * to provide to `func`. - * - * @private - * @param {Function} func The function to delay. - * @param {number} wait The number of milliseconds to delay invocation. - * @param {Array} args The arguments to provide to `func`. - * @returns {number|Object} Returns the timer id or timeout object. - */ - function baseDelay(func, wait, args) { - if (typeof func != 'function') { - throw new TypeError(FUNC_ERROR_TEXT); - } - return setTimeout(function() { func.apply(undefined, args); }, wait); - } - - /** - * The base implementation of methods like `_.difference` without support - * for excluding multiple arrays or iteratee shorthands. - * - * @private - * @param {Array} array The array to inspect. - * @param {Array} values The values to exclude. - * @param {Function} [iteratee] The iteratee invoked per element. - * @param {Function} [comparator] The comparator invoked per element. - * @returns {Array} Returns the new array of filtered values. - */ - function baseDifference(array, values, iteratee, comparator) { - var index = -1, - includes = arrayIncludes, - isCommon = true, - length = array.length, - result = [], - valuesLength = values.length; - - if (!length) { - return result; - } - if (iteratee) { - values = arrayMap(values, baseUnary(iteratee)); - } - if (comparator) { - includes = arrayIncludesWith; - isCommon = false; - } - else if (values.length >= LARGE_ARRAY_SIZE) { - includes = cacheHas; - isCommon = false; - values = new SetCache(values); - } - outer: - while (++index < length) { - var value = array[index], - computed = iteratee == null ? value : iteratee(value); - - value = (comparator || value !== 0) ? value : 0; - if (isCommon && computed === computed) { - var valuesIndex = valuesLength; - while (valuesIndex--) { - if (values[valuesIndex] === computed) { - continue outer; - } - } - result.push(value); - } - else if (!includes(values, computed, comparator)) { - result.push(value); - } - } - return result; - } - - /** - * The base implementation of `_.forEach` without support for iteratee shorthands. - * - * @private - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} iteratee The function invoked per iteration. - * @returns {Array|Object} Returns `collection`. - */ - var baseEach = createBaseEach(baseForOwn); - - /** - * The base implementation of `_.forEachRight` without support for iteratee shorthands. - * - * @private - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} iteratee The function invoked per iteration. - * @returns {Array|Object} Returns `collection`. - */ - var baseEachRight = createBaseEach(baseForOwnRight, true); - - /** - * The base implementation of `_.every` without support for iteratee shorthands. - * - * @private - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} predicate The function invoked per iteration. - * @returns {boolean} Returns `true` if all elements pass the predicate check, - * else `false` - */ - function baseEvery(collection, predicate) { - var result = true; - baseEach(collection, function(value, index, collection) { - result = !!predicate(value, index, collection); - return result; - }); - return result; - } - - /** - * The base implementation of methods like `_.max` and `_.min` which accepts a - * `comparator` to determine the extremum value. - * - * @private - * @param {Array} array The array to iterate over. - * @param {Function} iteratee The iteratee invoked per iteration. - * @param {Function} comparator The comparator used to compare values. - * @returns {*} Returns the extremum value. - */ - function baseExtremum(array, iteratee, comparator) { - var index = -1, - length = array.length; - - while (++index < length) { - var value = array[index], - current = iteratee(value); - - if (current != null && (computed === undefined - ? (current === current && !isSymbol(current)) - : comparator(current, computed) - )) { - var computed = current, - result = value; - } - } - return result; - } - - /** - * The base implementation of `_.fill` without an iteratee call guard. - * - * @private - * @param {Array} array The array to fill. - * @param {*} value The value to fill `array` with. - * @param {number} [start=0] The start position. - * @param {number} [end=array.length] The end position. - * @returns {Array} Returns `array`. - */ - function baseFill(array, value, start, end) { - var length = array.length; - - start = toInteger(start); - if (start < 0) { - start = -start > length ? 0 : (length + start); - } - end = (end === undefined || end > length) ? length : toInteger(end); - if (end < 0) { - end += length; - } - end = start > end ? 0 : toLength(end); - while (start < end) { - array[start++] = value; - } - return array; - } - - /** - * The base implementation of `_.filter` without support for iteratee shorthands. - * - * @private - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} predicate The function invoked per iteration. - * @returns {Array} Returns the new filtered array. - */ - function baseFilter(collection, predicate) { - var result = []; - baseEach(collection, function(value, index, collection) { - if (predicate(value, index, collection)) { - result.push(value); - } - }); - return result; - } - - /** - * The base implementation of `_.flatten` with support for restricting flattening. - * - * @private - * @param {Array} array The array to flatten. - * @param {number} depth The maximum recursion depth. - * @param {boolean} [predicate=isFlattenable] The function invoked per iteration. - * @param {boolean} [isStrict] Restrict to values that pass `predicate` checks. - * @param {Array} [result=[]] The initial result value. - * @returns {Array} Returns the new flattened array. - */ - function baseFlatten(array, depth, predicate, isStrict, result) { - var index = -1, - length = array.length; - - predicate || (predicate = isFlattenable); - result || (result = []); - - while (++index < length) { - var value = array[index]; - if (depth > 0 && predicate(value)) { - if (depth > 1) { - // Recursively flatten arrays (susceptible to call stack limits). - baseFlatten(value, depth - 1, predicate, isStrict, result); - } else { - arrayPush(result, value); - } - } else if (!isStrict) { - result[result.length] = value; - } - } - return result; - } - - /** - * The base implementation of `baseForOwn` which iterates over `object` - * properties returned by `keysFunc` and invokes `iteratee` for each property. - * Iteratee functions may exit iteration early by explicitly returning `false`. - * - * @private - * @param {Object} object The object to iterate over. - * @param {Function} iteratee The function invoked per iteration. - * @param {Function} keysFunc The function to get the keys of `object`. - * @returns {Object} Returns `object`. - */ - var baseFor = createBaseFor(); - - /** - * This function is like `baseFor` except that it iterates over properties - * in the opposite order. - * - * @private - * @param {Object} object The object to iterate over. - * @param {Function} iteratee The function invoked per iteration. - * @param {Function} keysFunc The function to get the keys of `object`. - * @returns {Object} Returns `object`. - */ - var baseForRight = createBaseFor(true); - - /** - * The base implementation of `_.forOwn` without support for iteratee shorthands. - * - * @private - * @param {Object} object The object to iterate over. - * @param {Function} iteratee The function invoked per iteration. - * @returns {Object} Returns `object`. - */ - function baseForOwn(object, iteratee) { - return object && baseFor(object, iteratee, keys); - } - - /** - * The base implementation of `_.forOwnRight` without support for iteratee shorthands. - * - * @private - * @param {Object} object The object to iterate over. - * @param {Function} iteratee The function invoked per iteration. - * @returns {Object} Returns `object`. - */ - function baseForOwnRight(object, iteratee) { - return object && baseForRight(object, iteratee, keys); - } - - /** - * The base implementation of `_.functions` which creates an array of - * `object` function property names filtered from `props`. - * - * @private - * @param {Object} object The object to inspect. - * @param {Array} props The property names to filter. - * @returns {Array} Returns the function names. - */ - function baseFunctions(object, props) { - return arrayFilter(props, function(key) { - return isFunction(object[key]); - }); - } - - /** - * The base implementation of `_.get` without support for default values. - * - * @private - * @param {Object} object The object to query. - * @param {Array|string} path The path of the property to get. - * @returns {*} Returns the resolved value. - */ - function baseGet(object, path) { - path = castPath(path, object); - - var index = 0, - length = path.length; - - while (object != null && index < length) { - object = object[toKey(path[index++])]; - } - return (index && index == length) ? object : undefined; - } - - /** - * The base implementation of `getAllKeys` and `getAllKeysIn` which uses - * `keysFunc` and `symbolsFunc` to get the enumerable property names and - * symbols of `object`. - * - * @private - * @param {Object} object The object to query. - * @param {Function} keysFunc The function to get the keys of `object`. - * @param {Function} symbolsFunc The function to get the symbols of `object`. - * @returns {Array} Returns the array of property names and symbols. - */ - function baseGetAllKeys(object, keysFunc, symbolsFunc) { - var result = keysFunc(object); - return isArray(object) ? result : arrayPush(result, symbolsFunc(object)); - } - - /** - * The base implementation of `getTag` without fallbacks for buggy environments. - * - * @private - * @param {*} value The value to query. - * @returns {string} Returns the `toStringTag`. - */ - function baseGetTag(value) { - if (value == null) { - return value === undefined ? undefinedTag : nullTag; - } - return (symToStringTag && symToStringTag in Object(value)) - ? getRawTag(value) - : objectToString(value); - } - - /** - * The base implementation of `_.gt` which doesn't coerce arguments. - * - * @private - * @param {*} value The value to compare. - * @param {*} other The other value to compare. - * @returns {boolean} Returns `true` if `value` is greater than `other`, - * else `false`. - */ - function baseGt(value, other) { - return value > other; - } - - /** - * The base implementation of `_.has` without support for deep paths. - * - * @private - * @param {Object} [object] The object to query. - * @param {Array|string} key The key to check. - * @returns {boolean} Returns `true` if `key` exists, else `false`. - */ - function baseHas(object, key) { - return object != null && hasOwnProperty.call(object, key); - } - - /** - * The base implementation of `_.hasIn` without support for deep paths. - * - * @private - * @param {Object} [object] The object to query. - * @param {Array|string} key The key to check. - * @returns {boolean} Returns `true` if `key` exists, else `false`. - */ - function baseHasIn(object, key) { - return object != null && key in Object(object); - } - - /** - * The base implementation of `_.inRange` which doesn't coerce arguments. - * - * @private - * @param {number} number The number to check. - * @param {number} start The start of the range. - * @param {number} end The end of the range. - * @returns {boolean} Returns `true` if `number` is in the range, else `false`. - */ - function baseInRange(number, start, end) { - return number >= nativeMin(start, end) && number < nativeMax(start, end); - } - - /** - * The base implementation of methods like `_.intersection`, without support - * for iteratee shorthands, that accepts an array of arrays to inspect. - * - * @private - * @param {Array} arrays The arrays to inspect. - * @param {Function} [iteratee] The iteratee invoked per element. - * @param {Function} [comparator] The comparator invoked per element. - * @returns {Array} Returns the new array of shared values. - */ - function baseIntersection(arrays, iteratee, comparator) { - var includes = comparator ? arrayIncludesWith : arrayIncludes, - length = arrays[0].length, - othLength = arrays.length, - othIndex = othLength, - caches = Array(othLength), - maxLength = Infinity, - result = []; - - while (othIndex--) { - var array = arrays[othIndex]; - if (othIndex && iteratee) { - array = arrayMap(array, baseUnary(iteratee)); - } - maxLength = nativeMin(array.length, maxLength); - caches[othIndex] = !comparator && (iteratee || (length >= 120 && array.length >= 120)) - ? new SetCache(othIndex && array) - : undefined; - } - array = arrays[0]; - - var index = -1, - seen = caches[0]; - - outer: - while (++index < length && result.length < maxLength) { - var value = array[index], - computed = iteratee ? iteratee(value) : value; - - value = (comparator || value !== 0) ? value : 0; - if (!(seen - ? cacheHas(seen, computed) - : includes(result, computed, comparator) - )) { - othIndex = othLength; - while (--othIndex) { - var cache = caches[othIndex]; - if (!(cache - ? cacheHas(cache, computed) - : includes(arrays[othIndex], computed, comparator)) - ) { - continue outer; - } - } - if (seen) { - seen.push(computed); - } - result.push(value); - } - } - return result; - } - - /** - * The base implementation of `_.invert` and `_.invertBy` which inverts - * `object` with values transformed by `iteratee` and set by `setter`. - * - * @private - * @param {Object} object The object to iterate over. - * @param {Function} setter The function to set `accumulator` values. - * @param {Function} iteratee The iteratee to transform values. - * @param {Object} accumulator The initial inverted object. - * @returns {Function} Returns `accumulator`. - */ - function baseInverter(object, setter, iteratee, accumulator) { - baseForOwn(object, function(value, key, object) { - setter(accumulator, iteratee(value), key, object); - }); - return accumulator; - } - - /** - * The base implementation of `_.invoke` without support for individual - * method arguments. - * - * @private - * @param {Object} object The object to query. - * @param {Array|string} path The path of the method to invoke. - * @param {Array} args The arguments to invoke the method with. - * @returns {*} Returns the result of the invoked method. - */ - function baseInvoke(object, path, args) { - path = castPath(path, object); - object = parent(object, path); - var func = object == null ? object : object[toKey(last(path))]; - return func == null ? undefined : apply(func, object, args); - } - - /** - * The base implementation of `_.isArguments`. - * - * @private - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is an `arguments` object, - */ - function baseIsArguments(value) { - return isObjectLike(value) && baseGetTag(value) == argsTag; - } - - /** - * The base implementation of `_.isArrayBuffer` without Node.js optimizations. - * - * @private - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is an array buffer, else `false`. - */ - function baseIsArrayBuffer(value) { - return isObjectLike(value) && baseGetTag(value) == arrayBufferTag; - } - - /** - * The base implementation of `_.isDate` without Node.js optimizations. - * - * @private - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a date object, else `false`. - */ - function baseIsDate(value) { - return isObjectLike(value) && baseGetTag(value) == dateTag; - } - - /** - * The base implementation of `_.isEqual` which supports partial comparisons - * and tracks traversed objects. - * - * @private - * @param {*} value The value to compare. - * @param {*} other The other value to compare. - * @param {boolean} bitmask The bitmask flags. - * 1 - Unordered comparison - * 2 - Partial comparison - * @param {Function} [customizer] The function to customize comparisons. - * @param {Object} [stack] Tracks traversed `value` and `other` objects. - * @returns {boolean} Returns `true` if the values are equivalent, else `false`. - */ - function baseIsEqual(value, other, bitmask, customizer, stack) { - if (value === other) { - return true; - } - if (value == null || other == null || (!isObjectLike(value) && !isObjectLike(other))) { - return value !== value && other !== other; - } - return baseIsEqualDeep(value, other, bitmask, customizer, baseIsEqual, stack); - } - - /** - * A specialized version of `baseIsEqual` for arrays and objects which performs - * deep comparisons and tracks traversed objects enabling objects with circular - * references to be compared. - * - * @private - * @param {Object} object The object to compare. - * @param {Object} other The other object to compare. - * @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details. - * @param {Function} customizer The function to customize comparisons. - * @param {Function} equalFunc The function to determine equivalents of values. - * @param {Object} [stack] Tracks traversed `object` and `other` objects. - * @returns {boolean} Returns `true` if the objects are equivalent, else `false`. - */ - function baseIsEqualDeep(object, other, bitmask, customizer, equalFunc, stack) { - var objIsArr = isArray(object), - othIsArr = isArray(other), - objTag = objIsArr ? arrayTag : getTag(object), - othTag = othIsArr ? arrayTag : getTag(other); - - objTag = objTag == argsTag ? objectTag : objTag; - othTag = othTag == argsTag ? objectTag : othTag; - - var objIsObj = objTag == objectTag, - othIsObj = othTag == objectTag, - isSameTag = objTag == othTag; - - if (isSameTag && isBuffer(object)) { - if (!isBuffer(other)) { - return false; - } - objIsArr = true; - objIsObj = false; - } - if (isSameTag && !objIsObj) { - stack || (stack = new Stack); - return (objIsArr || isTypedArray(object)) - ? equalArrays(object, other, bitmask, customizer, equalFunc, stack) - : equalByTag(object, other, objTag, bitmask, customizer, equalFunc, stack); - } - if (!(bitmask & COMPARE_PARTIAL_FLAG)) { - var objIsWrapped = objIsObj && hasOwnProperty.call(object, '__wrapped__'), - othIsWrapped = othIsObj && hasOwnProperty.call(other, '__wrapped__'); - - if (objIsWrapped || othIsWrapped) { - var objUnwrapped = objIsWrapped ? object.value() : object, - othUnwrapped = othIsWrapped ? other.value() : other; - - stack || (stack = new Stack); - return equalFunc(objUnwrapped, othUnwrapped, bitmask, customizer, stack); - } - } - if (!isSameTag) { - return false; - } - stack || (stack = new Stack); - return equalObjects(object, other, bitmask, customizer, equalFunc, stack); - } - - /** - * The base implementation of `_.isMap` without Node.js optimizations. - * - * @private - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a map, else `false`. - */ - function baseIsMap(value) { - return isObjectLike(value) && getTag(value) == mapTag; - } - - /** - * The base implementation of `_.isMatch` without support for iteratee shorthands. - * - * @private - * @param {Object} object The object to inspect. - * @param {Object} source The object of property values to match. - * @param {Array} matchData The property names, values, and compare flags to match. - * @param {Function} [customizer] The function to customize comparisons. - * @returns {boolean} Returns `true` if `object` is a match, else `false`. - */ - function baseIsMatch(object, source, matchData, customizer) { - var index = matchData.length, - length = index, - noCustomizer = !customizer; - - if (object == null) { - return !length; - } - object = Object(object); - while (index--) { - var data = matchData[index]; - if ((noCustomizer && data[2]) - ? data[1] !== object[data[0]] - : !(data[0] in object) - ) { - return false; - } - } - while (++index < length) { - data = matchData[index]; - var key = data[0], - objValue = object[key], - srcValue = data[1]; - - if (noCustomizer && data[2]) { - if (objValue === undefined && !(key in object)) { - return false; - } - } else { - var stack = new Stack; - if (customizer) { - var result = customizer(objValue, srcValue, key, object, source, stack); - } - if (!(result === undefined - ? baseIsEqual(srcValue, objValue, COMPARE_PARTIAL_FLAG | COMPARE_UNORDERED_FLAG, customizer, stack) - : result - )) { - return false; - } - } - } - return true; - } - - /** - * The base implementation of `_.isNative` without bad shim checks. - * - * @private - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a native function, - * else `false`. - */ - function baseIsNative(value) { - if (!isObject(value) || isMasked(value)) { - return false; - } - var pattern = isFunction(value) ? reIsNative : reIsHostCtor; - return pattern.test(toSource(value)); - } - - /** - * The base implementation of `_.isRegExp` without Node.js optimizations. - * - * @private - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a regexp, else `false`. - */ - function baseIsRegExp(value) { - return isObjectLike(value) && baseGetTag(value) == regexpTag; - } - - /** - * The base implementation of `_.isSet` without Node.js optimizations. - * - * @private - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a set, else `false`. - */ - function baseIsSet(value) { - return isObjectLike(value) && getTag(value) == setTag; - } - - /** - * The base implementation of `_.isTypedArray` without Node.js optimizations. - * - * @private - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a typed array, else `false`. - */ - function baseIsTypedArray(value) { - return isObjectLike(value) && - isLength(value.length) && !!typedArrayTags[baseGetTag(value)]; - } - - /** - * The base implementation of `_.iteratee`. - * - * @private - * @param {*} [value=_.identity] The value to convert to an iteratee. - * @returns {Function} Returns the iteratee. - */ - function baseIteratee(value) { - // Don't store the `typeof` result in a variable to avoid a JIT bug in Safari 9. - // See https://bugs.webkit.org/show_bug.cgi?id=156034 for more details. - if (typeof value == 'function') { - return value; - } - if (value == null) { - return identity; - } - if (typeof value == 'object') { - return isArray(value) - ? baseMatchesProperty(value[0], value[1]) - : baseMatches(value); - } - return property(value); - } - - /** - * The base implementation of `_.keys` which doesn't treat sparse arrays as dense. - * - * @private - * @param {Object} object The object to query. - * @returns {Array} Returns the array of property names. - */ - function baseKeys(object) { - if (!isPrototype(object)) { - return nativeKeys(object); - } - var result = []; - for (var key in Object(object)) { - if (hasOwnProperty.call(object, key) && key != 'constructor') { - result.push(key); - } - } - return result; - } - - /** - * The base implementation of `_.keysIn` which doesn't treat sparse arrays as dense. - * - * @private - * @param {Object} object The object to query. - * @returns {Array} Returns the array of property names. - */ - function baseKeysIn(object) { - if (!isObject(object)) { - return nativeKeysIn(object); - } - var isProto = isPrototype(object), - result = []; - - for (var key in object) { - if (!(key == 'constructor' && (isProto || !hasOwnProperty.call(object, key)))) { - result.push(key); - } - } - return result; - } - - /** - * The base implementation of `_.lt` which doesn't coerce arguments. - * - * @private - * @param {*} value The value to compare. - * @param {*} other The other value to compare. - * @returns {boolean} Returns `true` if `value` is less than `other`, - * else `false`. - */ - function baseLt(value, other) { - return value < other; - } - - /** - * The base implementation of `_.map` without support for iteratee shorthands. - * - * @private - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} iteratee The function invoked per iteration. - * @returns {Array} Returns the new mapped array. - */ - function baseMap(collection, iteratee) { - var index = -1, - result = isArrayLike(collection) ? Array(collection.length) : []; - - baseEach(collection, function(value, key, collection) { - result[++index] = iteratee(value, key, collection); - }); - return result; - } - - /** - * The base implementation of `_.matches` which doesn't clone `source`. - * - * @private - * @param {Object} source The object of property values to match. - * @returns {Function} Returns the new spec function. - */ - function baseMatches(source) { - var matchData = getMatchData(source); - if (matchData.length == 1 && matchData[0][2]) { - return matchesStrictComparable(matchData[0][0], matchData[0][1]); - } - return function(object) { - return object === source || baseIsMatch(object, source, matchData); - }; - } - - /** - * The base implementation of `_.matchesProperty` which doesn't clone `srcValue`. - * - * @private - * @param {string} path The path of the property to get. - * @param {*} srcValue The value to match. - * @returns {Function} Returns the new spec function. - */ - function baseMatchesProperty(path, srcValue) { - if (isKey(path) && isStrictComparable(srcValue)) { - return matchesStrictComparable(toKey(path), srcValue); - } - return function(object) { - var objValue = get(object, path); - return (objValue === undefined && objValue === srcValue) - ? hasIn(object, path) - : baseIsEqual(srcValue, objValue, COMPARE_PARTIAL_FLAG | COMPARE_UNORDERED_FLAG); - }; - } - - /** - * The base implementation of `_.merge` without support for multiple sources. - * - * @private - * @param {Object} object The destination object. - * @param {Object} source The source object. - * @param {number} srcIndex The index of `source`. - * @param {Function} [customizer] The function to customize merged values. - * @param {Object} [stack] Tracks traversed source values and their merged - * counterparts. - */ - function baseMerge(object, source, srcIndex, customizer, stack) { - if (object === source) { - return; - } - baseFor(source, function(srcValue, key) { - stack || (stack = new Stack); - if (isObject(srcValue)) { - baseMergeDeep(object, source, key, srcIndex, baseMerge, customizer, stack); - } - else { - var newValue = customizer - ? customizer(safeGet(object, key), srcValue, (key + ''), object, source, stack) - : undefined; - - if (newValue === undefined) { - newValue = srcValue; - } - assignMergeValue(object, key, newValue); - } - }, keysIn); - } - - /** - * A specialized version of `baseMerge` for arrays and objects which performs - * deep merges and tracks traversed objects enabling objects with circular - * references to be merged. - * - * @private - * @param {Object} object The destination object. - * @param {Object} source The source object. - * @param {string} key The key of the value to merge. - * @param {number} srcIndex The index of `source`. - * @param {Function} mergeFunc The function to merge values. - * @param {Function} [customizer] The function to customize assigned values. - * @param {Object} [stack] Tracks traversed source values and their merged - * counterparts. - */ - function baseMergeDeep(object, source, key, srcIndex, mergeFunc, customizer, stack) { - var objValue = safeGet(object, key), - srcValue = safeGet(source, key), - stacked = stack.get(srcValue); - - if (stacked) { - assignMergeValue(object, key, stacked); - return; - } - var newValue = customizer - ? customizer(objValue, srcValue, (key + ''), object, source, stack) - : undefined; - - var isCommon = newValue === undefined; - - if (isCommon) { - var isArr = isArray(srcValue), - isBuff = !isArr && isBuffer(srcValue), - isTyped = !isArr && !isBuff && isTypedArray(srcValue); - - newValue = srcValue; - if (isArr || isBuff || isTyped) { - if (isArray(objValue)) { - newValue = objValue; - } - else if (isArrayLikeObject(objValue)) { - newValue = copyArray(objValue); - } - else if (isBuff) { - isCommon = false; - newValue = cloneBuffer(srcValue, true); - } - else if (isTyped) { - isCommon = false; - newValue = cloneTypedArray(srcValue, true); - } - else { - newValue = []; - } - } - else if (isPlainObject(srcValue) || isArguments(srcValue)) { - newValue = objValue; - if (isArguments(objValue)) { - newValue = toPlainObject(objValue); - } - else if (!isObject(objValue) || isFunction(objValue)) { - newValue = initCloneObject(srcValue); - } - } - else { - isCommon = false; - } - } - if (isCommon) { - // Recursively merge objects and arrays (susceptible to call stack limits). - stack.set(srcValue, newValue); - mergeFunc(newValue, srcValue, srcIndex, customizer, stack); - stack['delete'](srcValue); - } - assignMergeValue(object, key, newValue); - } - - /** - * The base implementation of `_.nth` which doesn't coerce arguments. - * - * @private - * @param {Array} array The array to query. - * @param {number} n The index of the element to return. - * @returns {*} Returns the nth element of `array`. - */ - function baseNth(array, n) { - var length = array.length; - if (!length) { - return; - } - n += n < 0 ? length : 0; - return isIndex(n, length) ? array[n] : undefined; - } - - /** - * The base implementation of `_.orderBy` without param guards. - * - * @private - * @param {Array|Object} collection The collection to iterate over. - * @param {Function[]|Object[]|string[]} iteratees The iteratees to sort by. - * @param {string[]} orders The sort orders of `iteratees`. - * @returns {Array} Returns the new sorted array. - */ - function baseOrderBy(collection, iteratees, orders) { - var index = -1; - iteratees = arrayMap(iteratees.length ? iteratees : [identity], baseUnary(getIteratee())); - - var result = baseMap(collection, function(value, key, collection) { - var criteria = arrayMap(iteratees, function(iteratee) { - return iteratee(value); - }); - return { 'criteria': criteria, 'index': ++index, 'value': value }; - }); - - return baseSortBy(result, function(object, other) { - return compareMultiple(object, other, orders); - }); - } - - /** - * The base implementation of `_.pick` without support for individual - * property identifiers. - * - * @private - * @param {Object} object The source object. - * @param {string[]} paths The property paths to pick. - * @returns {Object} Returns the new object. - */ - function basePick(object, paths) { - return basePickBy(object, paths, function(value, path) { - return hasIn(object, path); - }); - } - - /** - * The base implementation of `_.pickBy` without support for iteratee shorthands. - * - * @private - * @param {Object} object The source object. - * @param {string[]} paths The property paths to pick. - * @param {Function} predicate The function invoked per property. - * @returns {Object} Returns the new object. - */ - function basePickBy(object, paths, predicate) { - var index = -1, - length = paths.length, - result = {}; - - while (++index < length) { - var path = paths[index], - value = baseGet(object, path); - - if (predicate(value, path)) { - baseSet(result, castPath(path, object), value); - } - } - return result; - } - - /** - * A specialized version of `baseProperty` which supports deep paths. - * - * @private - * @param {Array|string} path The path of the property to get. - * @returns {Function} Returns the new accessor function. - */ - function basePropertyDeep(path) { - return function(object) { - return baseGet(object, path); - }; - } - - /** - * The base implementation of `_.pullAllBy` without support for iteratee - * shorthands. - * - * @private - * @param {Array} array The array to modify. - * @param {Array} values The values to remove. - * @param {Function} [iteratee] The iteratee invoked per element. - * @param {Function} [comparator] The comparator invoked per element. - * @returns {Array} Returns `array`. - */ - function basePullAll(array, values, iteratee, comparator) { - var indexOf = comparator ? baseIndexOfWith : baseIndexOf, - index = -1, - length = values.length, - seen = array; - - if (array === values) { - values = copyArray(values); - } - if (iteratee) { - seen = arrayMap(array, baseUnary(iteratee)); - } - while (++index < length) { - var fromIndex = 0, - value = values[index], - computed = iteratee ? iteratee(value) : value; - - while ((fromIndex = indexOf(seen, computed, fromIndex, comparator)) > -1) { - if (seen !== array) { - splice.call(seen, fromIndex, 1); - } - splice.call(array, fromIndex, 1); - } - } - return array; - } - - /** - * The base implementation of `_.pullAt` without support for individual - * indexes or capturing the removed elements. - * - * @private - * @param {Array} array The array to modify. - * @param {number[]} indexes The indexes of elements to remove. - * @returns {Array} Returns `array`. - */ - function basePullAt(array, indexes) { - var length = array ? indexes.length : 0, - lastIndex = length - 1; - - while (length--) { - var index = indexes[length]; - if (length == lastIndex || index !== previous) { - var previous = index; - if (isIndex(index)) { - splice.call(array, index, 1); - } else { - baseUnset(array, index); - } - } - } - return array; - } - - /** - * The base implementation of `_.random` without support for returning - * floating-point numbers. - * - * @private - * @param {number} lower The lower bound. - * @param {number} upper The upper bound. - * @returns {number} Returns the random number. - */ - function baseRandom(lower, upper) { - return lower + nativeFloor(nativeRandom() * (upper - lower + 1)); - } - - /** - * The base implementation of `_.range` and `_.rangeRight` which doesn't - * coerce arguments. - * - * @private - * @param {number} start The start of the range. - * @param {number} end The end of the range. - * @param {number} step The value to increment or decrement by. - * @param {boolean} [fromRight] Specify iterating from right to left. - * @returns {Array} Returns the range of numbers. - */ - function baseRange(start, end, step, fromRight) { - var index = -1, - length = nativeMax(nativeCeil((end - start) / (step || 1)), 0), - result = Array(length); - - while (length--) { - result[fromRight ? length : ++index] = start; - start += step; - } - return result; - } - - /** - * The base implementation of `_.repeat` which doesn't coerce arguments. - * - * @private - * @param {string} string The string to repeat. - * @param {number} n The number of times to repeat the string. - * @returns {string} Returns the repeated string. - */ - function baseRepeat(string, n) { - var result = ''; - if (!string || n < 1 || n > MAX_SAFE_INTEGER) { - return result; - } - // Leverage the exponentiation by squaring algorithm for a faster repeat. - // See https://en.wikipedia.org/wiki/Exponentiation_by_squaring for more details. - do { - if (n % 2) { - result += string; - } - n = nativeFloor(n / 2); - if (n) { - string += string; - } - } while (n); - - return result; - } - - /** - * The base implementation of `_.rest` which doesn't validate or coerce arguments. - * - * @private - * @param {Function} func The function to apply a rest parameter to. - * @param {number} [start=func.length-1] The start position of the rest parameter. - * @returns {Function} Returns the new function. - */ - function baseRest(func, start) { - return setToString(overRest(func, start, identity), func + ''); - } - - /** - * The base implementation of `_.sample`. - * - * @private - * @param {Array|Object} collection The collection to sample. - * @returns {*} Returns the random element. - */ - function baseSample(collection) { - return arraySample(values(collection)); - } - - /** - * The base implementation of `_.sampleSize` without param guards. - * - * @private - * @param {Array|Object} collection The collection to sample. - * @param {number} n The number of elements to sample. - * @returns {Array} Returns the random elements. - */ - function baseSampleSize(collection, n) { - var array = values(collection); - return shuffleSelf(array, baseClamp(n, 0, array.length)); - } - - /** - * The base implementation of `_.set`. - * - * @private - * @param {Object} object The object to modify. - * @param {Array|string} path The path of the property to set. - * @param {*} value The value to set. - * @param {Function} [customizer] The function to customize path creation. - * @returns {Object} Returns `object`. - */ - function baseSet(object, path, value, customizer) { - if (!isObject(object)) { - return object; - } - path = castPath(path, object); - - var index = -1, - length = path.length, - lastIndex = length - 1, - nested = object; - - while (nested != null && ++index < length) { - var key = toKey(path[index]), - newValue = value; - - if (index != lastIndex) { - var objValue = nested[key]; - newValue = customizer ? customizer(objValue, key, nested) : undefined; - if (newValue === undefined) { - newValue = isObject(objValue) - ? objValue - : (isIndex(path[index + 1]) ? [] : {}); - } - } - assignValue(nested, key, newValue); - nested = nested[key]; - } - return object; - } - - /** - * The base implementation of `setData` without support for hot loop shorting. - * - * @private - * @param {Function} func The function to associate metadata with. - * @param {*} data The metadata. - * @returns {Function} Returns `func`. - */ - var baseSetData = !metaMap ? identity : function(func, data) { - metaMap.set(func, data); - return func; - }; - - /** - * The base implementation of `setToString` without support for hot loop shorting. - * - * @private - * @param {Function} func The function to modify. - * @param {Function} string The `toString` result. - * @returns {Function} Returns `func`. - */ - var baseSetToString = !defineProperty ? identity : function(func, string) { - return defineProperty(func, 'toString', { - 'configurable': true, - 'enumerable': false, - 'value': constant(string), - 'writable': true - }); - }; - - /** - * The base implementation of `_.shuffle`. - * - * @private - * @param {Array|Object} collection The collection to shuffle. - * @returns {Array} Returns the new shuffled array. - */ - function baseShuffle(collection) { - return shuffleSelf(values(collection)); - } - - /** - * The base implementation of `_.slice` without an iteratee call guard. - * - * @private - * @param {Array} array The array to slice. - * @param {number} [start=0] The start position. - * @param {number} [end=array.length] The end position. - * @returns {Array} Returns the slice of `array`. - */ - function baseSlice(array, start, end) { - var index = -1, - length = array.length; - - if (start < 0) { - start = -start > length ? 0 : (length + start); - } - end = end > length ? length : end; - if (end < 0) { - end += length; - } - length = start > end ? 0 : ((end - start) >>> 0); - start >>>= 0; - - var result = Array(length); - while (++index < length) { - result[index] = array[index + start]; - } - return result; - } - - /** - * The base implementation of `_.some` without support for iteratee shorthands. - * - * @private - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} predicate The function invoked per iteration. - * @returns {boolean} Returns `true` if any element passes the predicate check, - * else `false`. - */ - function baseSome(collection, predicate) { - var result; - - baseEach(collection, function(value, index, collection) { - result = predicate(value, index, collection); - return !result; - }); - return !!result; - } - - /** - * The base implementation of `_.sortedIndex` and `_.sortedLastIndex` which - * performs a binary search of `array` to determine the index at which `value` - * should be inserted into `array` in order to maintain its sort order. - * - * @private - * @param {Array} array The sorted array to inspect. - * @param {*} value The value to evaluate. - * @param {boolean} [retHighest] Specify returning the highest qualified index. - * @returns {number} Returns the index at which `value` should be inserted - * into `array`. - */ - function baseSortedIndex(array, value, retHighest) { - var low = 0, - high = array == null ? low : array.length; - - if (typeof value == 'number' && value === value && high <= HALF_MAX_ARRAY_LENGTH) { - while (low < high) { - var mid = (low + high) >>> 1, - computed = array[mid]; - - if (computed !== null && !isSymbol(computed) && - (retHighest ? (computed <= value) : (computed < value))) { - low = mid + 1; - } else { - high = mid; - } - } - return high; - } - return baseSortedIndexBy(array, value, identity, retHighest); - } - - /** - * The base implementation of `_.sortedIndexBy` and `_.sortedLastIndexBy` - * which invokes `iteratee` for `value` and each element of `array` to compute - * their sort ranking. The iteratee is invoked with one argument; (value). - * - * @private - * @param {Array} array The sorted array to inspect. - * @param {*} value The value to evaluate. - * @param {Function} iteratee The iteratee invoked per element. - * @param {boolean} [retHighest] Specify returning the highest qualified index. - * @returns {number} Returns the index at which `value` should be inserted - * into `array`. - */ - function baseSortedIndexBy(array, value, iteratee, retHighest) { - value = iteratee(value); - - var low = 0, - high = array == null ? 0 : array.length, - valIsNaN = value !== value, - valIsNull = value === null, - valIsSymbol = isSymbol(value), - valIsUndefined = value === undefined; - - while (low < high) { - var mid = nativeFloor((low + high) / 2), - computed = iteratee(array[mid]), - othIsDefined = computed !== undefined, - othIsNull = computed === null, - othIsReflexive = computed === computed, - othIsSymbol = isSymbol(computed); - - if (valIsNaN) { - var setLow = retHighest || othIsReflexive; - } else if (valIsUndefined) { - setLow = othIsReflexive && (retHighest || othIsDefined); - } else if (valIsNull) { - setLow = othIsReflexive && othIsDefined && (retHighest || !othIsNull); - } else if (valIsSymbol) { - setLow = othIsReflexive && othIsDefined && !othIsNull && (retHighest || !othIsSymbol); - } else if (othIsNull || othIsSymbol) { - setLow = false; - } else { - setLow = retHighest ? (computed <= value) : (computed < value); - } - if (setLow) { - low = mid + 1; - } else { - high = mid; - } - } - return nativeMin(high, MAX_ARRAY_INDEX); - } - - /** - * The base implementation of `_.sortedUniq` and `_.sortedUniqBy` without - * support for iteratee shorthands. - * - * @private - * @param {Array} array The array to inspect. - * @param {Function} [iteratee] The iteratee invoked per element. - * @returns {Array} Returns the new duplicate free array. - */ - function baseSortedUniq(array, iteratee) { - var index = -1, - length = array.length, - resIndex = 0, - result = []; - - while (++index < length) { - var value = array[index], - computed = iteratee ? iteratee(value) : value; - - if (!index || !eq(computed, seen)) { - var seen = computed; - result[resIndex++] = value === 0 ? 0 : value; - } - } - return result; - } - - /** - * The base implementation of `_.toNumber` which doesn't ensure correct - * conversions of binary, hexadecimal, or octal string values. - * - * @private - * @param {*} value The value to process. - * @returns {number} Returns the number. - */ - function baseToNumber(value) { - if (typeof value == 'number') { - return value; - } - if (isSymbol(value)) { - return NAN; - } - return +value; - } - - /** - * The base implementation of `_.toString` which doesn't convert nullish - * values to empty strings. - * - * @private - * @param {*} value The value to process. - * @returns {string} Returns the string. - */ - function baseToString(value) { - // Exit early for strings to avoid a performance hit in some environments. - if (typeof value == 'string') { - return value; - } - if (isArray(value)) { - // Recursively convert values (susceptible to call stack limits). - return arrayMap(value, baseToString) + ''; - } - if (isSymbol(value)) { - return symbolToString ? symbolToString.call(value) : ''; - } - var result = (value + ''); - return (result == '0' && (1 / value) == -INFINITY) ? '-0' : result; - } - - /** - * The base implementation of `_.uniqBy` without support for iteratee shorthands. - * - * @private - * @param {Array} array The array to inspect. - * @param {Function} [iteratee] The iteratee invoked per element. - * @param {Function} [comparator] The comparator invoked per element. - * @returns {Array} Returns the new duplicate free array. - */ - function baseUniq(array, iteratee, comparator) { - var index = -1, - includes = arrayIncludes, - length = array.length, - isCommon = true, - result = [], - seen = result; - - if (comparator) { - isCommon = false; - includes = arrayIncludesWith; - } - else if (length >= LARGE_ARRAY_SIZE) { - var set = iteratee ? null : createSet(array); - if (set) { - return setToArray(set); - } - isCommon = false; - includes = cacheHas; - seen = new SetCache; - } - else { - seen = iteratee ? [] : result; - } - outer: - while (++index < length) { - var value = array[index], - computed = iteratee ? iteratee(value) : value; - - value = (comparator || value !== 0) ? value : 0; - if (isCommon && computed === computed) { - var seenIndex = seen.length; - while (seenIndex--) { - if (seen[seenIndex] === computed) { - continue outer; - } - } - if (iteratee) { - seen.push(computed); - } - result.push(value); - } - else if (!includes(seen, computed, comparator)) { - if (seen !== result) { - seen.push(computed); - } - result.push(value); - } - } - return result; - } - - /** - * The base implementation of `_.unset`. - * - * @private - * @param {Object} object The object to modify. - * @param {Array|string} path The property path to unset. - * @returns {boolean} Returns `true` if the property is deleted, else `false`. - */ - function baseUnset(object, path) { - path = castPath(path, object); - object = parent(object, path); - return object == null || delete object[toKey(last(path))]; - } - - /** - * The base implementation of `_.update`. - * - * @private - * @param {Object} object The object to modify. - * @param {Array|string} path The path of the property to update. - * @param {Function} updater The function to produce the updated value. - * @param {Function} [customizer] The function to customize path creation. - * @returns {Object} Returns `object`. - */ - function baseUpdate(object, path, updater, customizer) { - return baseSet(object, path, updater(baseGet(object, path)), customizer); - } - - /** - * The base implementation of methods like `_.dropWhile` and `_.takeWhile` - * without support for iteratee shorthands. - * - * @private - * @param {Array} array The array to query. - * @param {Function} predicate The function invoked per iteration. - * @param {boolean} [isDrop] Specify dropping elements instead of taking them. - * @param {boolean} [fromRight] Specify iterating from right to left. - * @returns {Array} Returns the slice of `array`. - */ - function baseWhile(array, predicate, isDrop, fromRight) { - var length = array.length, - index = fromRight ? length : -1; - - while ((fromRight ? index-- : ++index < length) && - predicate(array[index], index, array)) {} - - return isDrop - ? baseSlice(array, (fromRight ? 0 : index), (fromRight ? index + 1 : length)) - : baseSlice(array, (fromRight ? index + 1 : 0), (fromRight ? length : index)); - } - - /** - * The base implementation of `wrapperValue` which returns the result of - * performing a sequence of actions on the unwrapped `value`, where each - * successive action is supplied the return value of the previous. - * - * @private - * @param {*} value The unwrapped value. - * @param {Array} actions Actions to perform to resolve the unwrapped value. - * @returns {*} Returns the resolved value. - */ - function baseWrapperValue(value, actions) { - var result = value; - if (result instanceof LazyWrapper) { - result = result.value(); - } - return arrayReduce(actions, function(result, action) { - return action.func.apply(action.thisArg, arrayPush([result], action.args)); - }, result); - } - - /** - * The base implementation of methods like `_.xor`, without support for - * iteratee shorthands, that accepts an array of arrays to inspect. - * - * @private - * @param {Array} arrays The arrays to inspect. - * @param {Function} [iteratee] The iteratee invoked per element. - * @param {Function} [comparator] The comparator invoked per element. - * @returns {Array} Returns the new array of values. - */ - function baseXor(arrays, iteratee, comparator) { - var length = arrays.length; - if (length < 2) { - return length ? baseUniq(arrays[0]) : []; - } - var index = -1, - result = Array(length); - - while (++index < length) { - var array = arrays[index], - othIndex = -1; - - while (++othIndex < length) { - if (othIndex != index) { - result[index] = baseDifference(result[index] || array, arrays[othIndex], iteratee, comparator); - } - } - } - return baseUniq(baseFlatten(result, 1), iteratee, comparator); - } - - /** - * This base implementation of `_.zipObject` which assigns values using `assignFunc`. - * - * @private - * @param {Array} props The property identifiers. - * @param {Array} values The property values. - * @param {Function} assignFunc The function to assign values. - * @returns {Object} Returns the new object. - */ - function baseZipObject(props, values, assignFunc) { - var index = -1, - length = props.length, - valsLength = values.length, - result = {}; - - while (++index < length) { - var value = index < valsLength ? values[index] : undefined; - assignFunc(result, props[index], value); - } - return result; - } - - /** - * Casts `value` to an empty array if it's not an array like object. - * - * @private - * @param {*} value The value to inspect. - * @returns {Array|Object} Returns the cast array-like object. - */ - function castArrayLikeObject(value) { - return isArrayLikeObject(value) ? value : []; - } - - /** - * Casts `value` to `identity` if it's not a function. - * - * @private - * @param {*} value The value to inspect. - * @returns {Function} Returns cast function. - */ - function castFunction(value) { - return typeof value == 'function' ? value : identity; - } - - /** - * Casts `value` to a path array if it's not one. - * - * @private - * @param {*} value The value to inspect. - * @param {Object} [object] The object to query keys on. - * @returns {Array} Returns the cast property path array. - */ - function castPath(value, object) { - if (isArray(value)) { - return value; - } - return isKey(value, object) ? [value] : stringToPath(toString(value)); - } - - /** - * A `baseRest` alias which can be replaced with `identity` by module - * replacement plugins. - * - * @private - * @type {Function} - * @param {Function} func The function to apply a rest parameter to. - * @returns {Function} Returns the new function. - */ - var castRest = baseRest; - - /** - * Casts `array` to a slice if it's needed. - * - * @private - * @param {Array} array The array to inspect. - * @param {number} start The start position. - * @param {number} [end=array.length] The end position. - * @returns {Array} Returns the cast slice. - */ - function castSlice(array, start, end) { - var length = array.length; - end = end === undefined ? length : end; - return (!start && end >= length) ? array : baseSlice(array, start, end); - } - - /** - * A simple wrapper around the global [`clearTimeout`](https://mdn.io/clearTimeout). - * - * @private - * @param {number|Object} id The timer id or timeout object of the timer to clear. - */ - var clearTimeout = ctxClearTimeout || function(id) { - return root.clearTimeout(id); - }; - - /** - * Creates a clone of `buffer`. - * - * @private - * @param {Buffer} buffer The buffer to clone. - * @param {boolean} [isDeep] Specify a deep clone. - * @returns {Buffer} Returns the cloned buffer. - */ - function cloneBuffer(buffer, isDeep) { - if (isDeep) { - return buffer.slice(); - } - var length = buffer.length, - result = allocUnsafe ? allocUnsafe(length) : new buffer.constructor(length); - - buffer.copy(result); - return result; - } - - /** - * Creates a clone of `arrayBuffer`. - * - * @private - * @param {ArrayBuffer} arrayBuffer The array buffer to clone. - * @returns {ArrayBuffer} Returns the cloned array buffer. - */ - function cloneArrayBuffer(arrayBuffer) { - var result = new arrayBuffer.constructor(arrayBuffer.byteLength); - new Uint8Array(result).set(new Uint8Array(arrayBuffer)); - return result; - } - - /** - * Creates a clone of `dataView`. - * - * @private - * @param {Object} dataView The data view to clone. - * @param {boolean} [isDeep] Specify a deep clone. - * @returns {Object} Returns the cloned data view. - */ - function cloneDataView(dataView, isDeep) { - var buffer = isDeep ? cloneArrayBuffer(dataView.buffer) : dataView.buffer; - return new dataView.constructor(buffer, dataView.byteOffset, dataView.byteLength); - } - - /** - * Creates a clone of `regexp`. - * - * @private - * @param {Object} regexp The regexp to clone. - * @returns {Object} Returns the cloned regexp. - */ - function cloneRegExp(regexp) { - var result = new regexp.constructor(regexp.source, reFlags.exec(regexp)); - result.lastIndex = regexp.lastIndex; - return result; - } - - /** - * Creates a clone of the `symbol` object. - * - * @private - * @param {Object} symbol The symbol object to clone. - * @returns {Object} Returns the cloned symbol object. - */ - function cloneSymbol(symbol) { - return symbolValueOf ? Object(symbolValueOf.call(symbol)) : {}; - } - - /** - * Creates a clone of `typedArray`. - * - * @private - * @param {Object} typedArray The typed array to clone. - * @param {boolean} [isDeep] Specify a deep clone. - * @returns {Object} Returns the cloned typed array. - */ - function cloneTypedArray(typedArray, isDeep) { - var buffer = isDeep ? cloneArrayBuffer(typedArray.buffer) : typedArray.buffer; - return new typedArray.constructor(buffer, typedArray.byteOffset, typedArray.length); - } - - /** - * Compares values to sort them in ascending order. - * - * @private - * @param {*} value The value to compare. - * @param {*} other The other value to compare. - * @returns {number} Returns the sort order indicator for `value`. - */ - function compareAscending(value, other) { - if (value !== other) { - var valIsDefined = value !== undefined, - valIsNull = value === null, - valIsReflexive = value === value, - valIsSymbol = isSymbol(value); - - var othIsDefined = other !== undefined, - othIsNull = other === null, - othIsReflexive = other === other, - othIsSymbol = isSymbol(other); - - if ((!othIsNull && !othIsSymbol && !valIsSymbol && value > other) || - (valIsSymbol && othIsDefined && othIsReflexive && !othIsNull && !othIsSymbol) || - (valIsNull && othIsDefined && othIsReflexive) || - (!valIsDefined && othIsReflexive) || - !valIsReflexive) { - return 1; - } - if ((!valIsNull && !valIsSymbol && !othIsSymbol && value < other) || - (othIsSymbol && valIsDefined && valIsReflexive && !valIsNull && !valIsSymbol) || - (othIsNull && valIsDefined && valIsReflexive) || - (!othIsDefined && valIsReflexive) || - !othIsReflexive) { - return -1; - } - } - return 0; - } - - /** - * Used by `_.orderBy` to compare multiple properties of a value to another - * and stable sort them. - * - * If `orders` is unspecified, all values are sorted in ascending order. Otherwise, - * specify an order of "desc" for descending or "asc" for ascending sort order - * of corresponding values. - * - * @private - * @param {Object} object The object to compare. - * @param {Object} other The other object to compare. - * @param {boolean[]|string[]} orders The order to sort by for each property. - * @returns {number} Returns the sort order indicator for `object`. - */ - function compareMultiple(object, other, orders) { - var index = -1, - objCriteria = object.criteria, - othCriteria = other.criteria, - length = objCriteria.length, - ordersLength = orders.length; - - while (++index < length) { - var result = compareAscending(objCriteria[index], othCriteria[index]); - if (result) { - if (index >= ordersLength) { - return result; - } - var order = orders[index]; - return result * (order == 'desc' ? -1 : 1); - } - } - // Fixes an `Array#sort` bug in the JS engine embedded in Adobe applications - // that causes it, under certain circumstances, to provide the same value for - // `object` and `other`. See https://github.com/jashkenas/underscore/pull/1247 - // for more details. - // - // This also ensures a stable sort in V8 and other engines. - // See https://bugs.chromium.org/p/v8/issues/detail?id=90 for more details. - return object.index - other.index; - } - - /** - * Creates an array that is the composition of partially applied arguments, - * placeholders, and provided arguments into a single array of arguments. - * - * @private - * @param {Array} args The provided arguments. - * @param {Array} partials The arguments to prepend to those provided. - * @param {Array} holders The `partials` placeholder indexes. - * @params {boolean} [isCurried] Specify composing for a curried function. - * @returns {Array} Returns the new array of composed arguments. - */ - function composeArgs(args, partials, holders, isCurried) { - var argsIndex = -1, - argsLength = args.length, - holdersLength = holders.length, - leftIndex = -1, - leftLength = partials.length, - rangeLength = nativeMax(argsLength - holdersLength, 0), - result = Array(leftLength + rangeLength), - isUncurried = !isCurried; - - while (++leftIndex < leftLength) { - result[leftIndex] = partials[leftIndex]; - } - while (++argsIndex < holdersLength) { - if (isUncurried || argsIndex < argsLength) { - result[holders[argsIndex]] = args[argsIndex]; - } - } - while (rangeLength--) { - result[leftIndex++] = args[argsIndex++]; - } - return result; - } - - /** - * This function is like `composeArgs` except that the arguments composition - * is tailored for `_.partialRight`. - * - * @private - * @param {Array} args The provided arguments. - * @param {Array} partials The arguments to append to those provided. - * @param {Array} holders The `partials` placeholder indexes. - * @params {boolean} [isCurried] Specify composing for a curried function. - * @returns {Array} Returns the new array of composed arguments. - */ - function composeArgsRight(args, partials, holders, isCurried) { - var argsIndex = -1, - argsLength = args.length, - holdersIndex = -1, - holdersLength = holders.length, - rightIndex = -1, - rightLength = partials.length, - rangeLength = nativeMax(argsLength - holdersLength, 0), - result = Array(rangeLength + rightLength), - isUncurried = !isCurried; - - while (++argsIndex < rangeLength) { - result[argsIndex] = args[argsIndex]; - } - var offset = argsIndex; - while (++rightIndex < rightLength) { - result[offset + rightIndex] = partials[rightIndex]; - } - while (++holdersIndex < holdersLength) { - if (isUncurried || argsIndex < argsLength) { - result[offset + holders[holdersIndex]] = args[argsIndex++]; - } - } - return result; - } - - /** - * Copies the values of `source` to `array`. - * - * @private - * @param {Array} source The array to copy values from. - * @param {Array} [array=[]] The array to copy values to. - * @returns {Array} Returns `array`. - */ - function copyArray(source, array) { - var index = -1, - length = source.length; - - array || (array = Array(length)); - while (++index < length) { - array[index] = source[index]; - } - return array; - } - - /** - * Copies properties of `source` to `object`. - * - * @private - * @param {Object} source The object to copy properties from. - * @param {Array} props The property identifiers to copy. - * @param {Object} [object={}] The object to copy properties to. - * @param {Function} [customizer] The function to customize copied values. - * @returns {Object} Returns `object`. - */ - function copyObject(source, props, object, customizer) { - var isNew = !object; - object || (object = {}); - - var index = -1, - length = props.length; - - while (++index < length) { - var key = props[index]; - - var newValue = customizer - ? customizer(object[key], source[key], key, object, source) - : undefined; - - if (newValue === undefined) { - newValue = source[key]; - } - if (isNew) { - baseAssignValue(object, key, newValue); - } else { - assignValue(object, key, newValue); - } - } - return object; - } - - /** - * Copies own symbols of `source` to `object`. - * - * @private - * @param {Object} source The object to copy symbols from. - * @param {Object} [object={}] The object to copy symbols to. - * @returns {Object} Returns `object`. - */ - function copySymbols(source, object) { - return copyObject(source, getSymbols(source), object); - } - - /** - * Copies own and inherited symbols of `source` to `object`. - * - * @private - * @param {Object} source The object to copy symbols from. - * @param {Object} [object={}] The object to copy symbols to. - * @returns {Object} Returns `object`. - */ - function copySymbolsIn(source, object) { - return copyObject(source, getSymbolsIn(source), object); - } - - /** - * Creates a function like `_.groupBy`. - * - * @private - * @param {Function} setter The function to set accumulator values. - * @param {Function} [initializer] The accumulator object initializer. - * @returns {Function} Returns the new aggregator function. - */ - function createAggregator(setter, initializer) { - return function(collection, iteratee) { - var func = isArray(collection) ? arrayAggregator : baseAggregator, - accumulator = initializer ? initializer() : {}; - - return func(collection, setter, getIteratee(iteratee, 2), accumulator); - }; - } - - /** - * Creates a function like `_.assign`. - * - * @private - * @param {Function} assigner The function to assign values. - * @returns {Function} Returns the new assigner function. - */ - function createAssigner(assigner) { - return baseRest(function(object, sources) { - var index = -1, - length = sources.length, - customizer = length > 1 ? sources[length - 1] : undefined, - guard = length > 2 ? sources[2] : undefined; - - customizer = (assigner.length > 3 && typeof customizer == 'function') - ? (length--, customizer) - : undefined; - - if (guard && isIterateeCall(sources[0], sources[1], guard)) { - customizer = length < 3 ? undefined : customizer; - length = 1; - } - object = Object(object); - while (++index < length) { - var source = sources[index]; - if (source) { - assigner(object, source, index, customizer); - } - } - return object; - }); - } - - /** - * Creates a `baseEach` or `baseEachRight` function. - * - * @private - * @param {Function} eachFunc The function to iterate over a collection. - * @param {boolean} [fromRight] Specify iterating from right to left. - * @returns {Function} Returns the new base function. - */ - function createBaseEach(eachFunc, fromRight) { - return function(collection, iteratee) { - if (collection == null) { - return collection; - } - if (!isArrayLike(collection)) { - return eachFunc(collection, iteratee); - } - var length = collection.length, - index = fromRight ? length : -1, - iterable = Object(collection); - - while ((fromRight ? index-- : ++index < length)) { - if (iteratee(iterable[index], index, iterable) === false) { - break; - } - } - return collection; - }; - } - - /** - * Creates a base function for methods like `_.forIn` and `_.forOwn`. - * - * @private - * @param {boolean} [fromRight] Specify iterating from right to left. - * @returns {Function} Returns the new base function. - */ - function createBaseFor(fromRight) { - return function(object, iteratee, keysFunc) { - var index = -1, - iterable = Object(object), - props = keysFunc(object), - length = props.length; - - while (length--) { - var key = props[fromRight ? length : ++index]; - if (iteratee(iterable[key], key, iterable) === false) { - break; - } - } - return object; - }; - } - - /** - * Creates a function that wraps `func` to invoke it with the optional `this` - * binding of `thisArg`. - * - * @private - * @param {Function} func The function to wrap. - * @param {number} bitmask The bitmask flags. See `createWrap` for more details. - * @param {*} [thisArg] The `this` binding of `func`. - * @returns {Function} Returns the new wrapped function. - */ - function createBind(func, bitmask, thisArg) { - var isBind = bitmask & WRAP_BIND_FLAG, - Ctor = createCtor(func); - - function wrapper() { - var fn = (this && this !== root && this instanceof wrapper) ? Ctor : func; - return fn.apply(isBind ? thisArg : this, arguments); - } - return wrapper; - } - - /** - * Creates a function like `_.lowerFirst`. - * - * @private - * @param {string} methodName The name of the `String` case method to use. - * @returns {Function} Returns the new case function. - */ - function createCaseFirst(methodName) { - return function(string) { - string = toString(string); - - var strSymbols = hasUnicode(string) - ? stringToArray(string) - : undefined; - - var chr = strSymbols - ? strSymbols[0] - : string.charAt(0); - - var trailing = strSymbols - ? castSlice(strSymbols, 1).join('') - : string.slice(1); - - return chr[methodName]() + trailing; - }; - } - - /** - * Creates a function like `_.camelCase`. - * - * @private - * @param {Function} callback The function to combine each word. - * @returns {Function} Returns the new compounder function. - */ - function createCompounder(callback) { - return function(string) { - return arrayReduce(words(deburr(string).replace(reApos, '')), callback, ''); - }; - } - - /** - * Creates a function that produces an instance of `Ctor` regardless of - * whether it was invoked as part of a `new` expression or by `call` or `apply`. - * - * @private - * @param {Function} Ctor The constructor to wrap. - * @returns {Function} Returns the new wrapped function. - */ - function createCtor(Ctor) { - return function() { - // Use a `switch` statement to work with class constructors. See - // http://ecma-international.org/ecma-262/7.0/#sec-ecmascript-function-objects-call-thisargument-argumentslist - // for more details. - var args = arguments; - switch (args.length) { - case 0: return new Ctor; - case 1: return new Ctor(args[0]); - case 2: return new Ctor(args[0], args[1]); - case 3: return new Ctor(args[0], args[1], args[2]); - case 4: return new Ctor(args[0], args[1], args[2], args[3]); - case 5: return new Ctor(args[0], args[1], args[2], args[3], args[4]); - case 6: return new Ctor(args[0], args[1], args[2], args[3], args[4], args[5]); - case 7: return new Ctor(args[0], args[1], args[2], args[3], args[4], args[5], args[6]); - } - var thisBinding = baseCreate(Ctor.prototype), - result = Ctor.apply(thisBinding, args); - - // Mimic the constructor's `return` behavior. - // See https://es5.github.io/#x13.2.2 for more details. - return isObject(result) ? result : thisBinding; - }; - } - - /** - * Creates a function that wraps `func` to enable currying. - * - * @private - * @param {Function} func The function to wrap. - * @param {number} bitmask The bitmask flags. See `createWrap` for more details. - * @param {number} arity The arity of `func`. - * @returns {Function} Returns the new wrapped function. - */ - function createCurry(func, bitmask, arity) { - var Ctor = createCtor(func); - - function wrapper() { - var length = arguments.length, - args = Array(length), - index = length, - placeholder = getHolder(wrapper); - - while (index--) { - args[index] = arguments[index]; - } - var holders = (length < 3 && args[0] !== placeholder && args[length - 1] !== placeholder) - ? [] - : replaceHolders(args, placeholder); - - length -= holders.length; - if (length < arity) { - return createRecurry( - func, bitmask, createHybrid, wrapper.placeholder, undefined, - args, holders, undefined, undefined, arity - length); - } - var fn = (this && this !== root && this instanceof wrapper) ? Ctor : func; - return apply(fn, this, args); - } - return wrapper; - } - - /** - * Creates a `_.find` or `_.findLast` function. - * - * @private - * @param {Function} findIndexFunc The function to find the collection index. - * @returns {Function} Returns the new find function. - */ - function createFind(findIndexFunc) { - return function(collection, predicate, fromIndex) { - var iterable = Object(collection); - if (!isArrayLike(collection)) { - var iteratee = getIteratee(predicate, 3); - collection = keys(collection); - predicate = function(key) { return iteratee(iterable[key], key, iterable); }; - } - var index = findIndexFunc(collection, predicate, fromIndex); - return index > -1 ? iterable[iteratee ? collection[index] : index] : undefined; - }; - } - - /** - * Creates a `_.flow` or `_.flowRight` function. - * - * @private - * @param {boolean} [fromRight] Specify iterating from right to left. - * @returns {Function} Returns the new flow function. - */ - function createFlow(fromRight) { - return flatRest(function(funcs) { - var length = funcs.length, - index = length, - prereq = LodashWrapper.prototype.thru; - - if (fromRight) { - funcs.reverse(); - } - while (index--) { - var func = funcs[index]; - if (typeof func != 'function') { - throw new TypeError(FUNC_ERROR_TEXT); - } - if (prereq && !wrapper && getFuncName(func) == 'wrapper') { - var wrapper = new LodashWrapper([], true); - } - } - index = wrapper ? index : length; - while (++index < length) { - func = funcs[index]; - - var funcName = getFuncName(func), - data = funcName == 'wrapper' ? getData(func) : undefined; - - if (data && isLaziable(data[0]) && - data[1] == (WRAP_ARY_FLAG | WRAP_CURRY_FLAG | WRAP_PARTIAL_FLAG | WRAP_REARG_FLAG) && - !data[4].length && data[9] == 1 - ) { - wrapper = wrapper[getFuncName(data[0])].apply(wrapper, data[3]); - } else { - wrapper = (func.length == 1 && isLaziable(func)) - ? wrapper[funcName]() - : wrapper.thru(func); - } - } - return function() { - var args = arguments, - value = args[0]; - - if (wrapper && args.length == 1 && isArray(value)) { - return wrapper.plant(value).value(); - } - var index = 0, - result = length ? funcs[index].apply(this, args) : value; - - while (++index < length) { - result = funcs[index].call(this, result); - } - return result; - }; - }); - } - - /** - * Creates a function that wraps `func` to invoke it with optional `this` - * binding of `thisArg`, partial application, and currying. - * - * @private - * @param {Function|string} func The function or method name to wrap. - * @param {number} bitmask The bitmask flags. See `createWrap` for more details. - * @param {*} [thisArg] The `this` binding of `func`. - * @param {Array} [partials] The arguments to prepend to those provided to - * the new function. - * @param {Array} [holders] The `partials` placeholder indexes. - * @param {Array} [partialsRight] The arguments to append to those provided - * to the new function. - * @param {Array} [holdersRight] The `partialsRight` placeholder indexes. - * @param {Array} [argPos] The argument positions of the new function. - * @param {number} [ary] The arity cap of `func`. - * @param {number} [arity] The arity of `func`. - * @returns {Function} Returns the new wrapped function. - */ - function createHybrid(func, bitmask, thisArg, partials, holders, partialsRight, holdersRight, argPos, ary, arity) { - var isAry = bitmask & WRAP_ARY_FLAG, - isBind = bitmask & WRAP_BIND_FLAG, - isBindKey = bitmask & WRAP_BIND_KEY_FLAG, - isCurried = bitmask & (WRAP_CURRY_FLAG | WRAP_CURRY_RIGHT_FLAG), - isFlip = bitmask & WRAP_FLIP_FLAG, - Ctor = isBindKey ? undefined : createCtor(func); - - function wrapper() { - var length = arguments.length, - args = Array(length), - index = length; - - while (index--) { - args[index] = arguments[index]; - } - if (isCurried) { - var placeholder = getHolder(wrapper), - holdersCount = countHolders(args, placeholder); - } - if (partials) { - args = composeArgs(args, partials, holders, isCurried); - } - if (partialsRight) { - args = composeArgsRight(args, partialsRight, holdersRight, isCurried); - } - length -= holdersCount; - if (isCurried && length < arity) { - var newHolders = replaceHolders(args, placeholder); - return createRecurry( - func, bitmask, createHybrid, wrapper.placeholder, thisArg, - args, newHolders, argPos, ary, arity - length - ); - } - var thisBinding = isBind ? thisArg : this, - fn = isBindKey ? thisBinding[func] : func; - - length = args.length; - if (argPos) { - args = reorder(args, argPos); - } else if (isFlip && length > 1) { - args.reverse(); - } - if (isAry && ary < length) { - args.length = ary; - } - if (this && this !== root && this instanceof wrapper) { - fn = Ctor || createCtor(fn); - } - return fn.apply(thisBinding, args); - } - return wrapper; - } - - /** - * Creates a function like `_.invertBy`. - * - * @private - * @param {Function} setter The function to set accumulator values. - * @param {Function} toIteratee The function to resolve iteratees. - * @returns {Function} Returns the new inverter function. - */ - function createInverter(setter, toIteratee) { - return function(object, iteratee) { - return baseInverter(object, setter, toIteratee(iteratee), {}); - }; - } - - /** - * Creates a function that performs a mathematical operation on two values. - * - * @private - * @param {Function} operator The function to perform the operation. - * @param {number} [defaultValue] The value used for `undefined` arguments. - * @returns {Function} Returns the new mathematical operation function. - */ - function createMathOperation(operator, defaultValue) { - return function(value, other) { - var result; - if (value === undefined && other === undefined) { - return defaultValue; - } - if (value !== undefined) { - result = value; - } - if (other !== undefined) { - if (result === undefined) { - return other; - } - if (typeof value == 'string' || typeof other == 'string') { - value = baseToString(value); - other = baseToString(other); - } else { - value = baseToNumber(value); - other = baseToNumber(other); - } - result = operator(value, other); - } - return result; - }; - } - - /** - * Creates a function like `_.over`. - * - * @private - * @param {Function} arrayFunc The function to iterate over iteratees. - * @returns {Function} Returns the new over function. - */ - function createOver(arrayFunc) { - return flatRest(function(iteratees) { - iteratees = arrayMap(iteratees, baseUnary(getIteratee())); - return baseRest(function(args) { - var thisArg = this; - return arrayFunc(iteratees, function(iteratee) { - return apply(iteratee, thisArg, args); - }); - }); - }); - } - - /** - * Creates the padding for `string` based on `length`. The `chars` string - * is truncated if the number of characters exceeds `length`. - * - * @private - * @param {number} length The padding length. - * @param {string} [chars=' '] The string used as padding. - * @returns {string} Returns the padding for `string`. - */ - function createPadding(length, chars) { - chars = chars === undefined ? ' ' : baseToString(chars); - - var charsLength = chars.length; - if (charsLength < 2) { - return charsLength ? baseRepeat(chars, length) : chars; - } - var result = baseRepeat(chars, nativeCeil(length / stringSize(chars))); - return hasUnicode(chars) - ? castSlice(stringToArray(result), 0, length).join('') - : result.slice(0, length); - } - - /** - * Creates a function that wraps `func` to invoke it with the `this` binding - * of `thisArg` and `partials` prepended to the arguments it receives. - * - * @private - * @param {Function} func The function to wrap. - * @param {number} bitmask The bitmask flags. See `createWrap` for more details. - * @param {*} thisArg The `this` binding of `func`. - * @param {Array} partials The arguments to prepend to those provided to - * the new function. - * @returns {Function} Returns the new wrapped function. - */ - function createPartial(func, bitmask, thisArg, partials) { - var isBind = bitmask & WRAP_BIND_FLAG, - Ctor = createCtor(func); - - function wrapper() { - var argsIndex = -1, - argsLength = arguments.length, - leftIndex = -1, - leftLength = partials.length, - args = Array(leftLength + argsLength), - fn = (this && this !== root && this instanceof wrapper) ? Ctor : func; - - while (++leftIndex < leftLength) { - args[leftIndex] = partials[leftIndex]; - } - while (argsLength--) { - args[leftIndex++] = arguments[++argsIndex]; - } - return apply(fn, isBind ? thisArg : this, args); - } - return wrapper; - } - - /** - * Creates a `_.range` or `_.rangeRight` function. - * - * @private - * @param {boolean} [fromRight] Specify iterating from right to left. - * @returns {Function} Returns the new range function. - */ - function createRange(fromRight) { - return function(start, end, step) { - if (step && typeof step != 'number' && isIterateeCall(start, end, step)) { - end = step = undefined; - } - // Ensure the sign of `-0` is preserved. - start = toFinite(start); - if (end === undefined) { - end = start; - start = 0; - } else { - end = toFinite(end); - } - step = step === undefined ? (start < end ? 1 : -1) : toFinite(step); - return baseRange(start, end, step, fromRight); - }; - } - - /** - * Creates a function that performs a relational operation on two values. - * - * @private - * @param {Function} operator The function to perform the operation. - * @returns {Function} Returns the new relational operation function. - */ - function createRelationalOperation(operator) { - return function(value, other) { - if (!(typeof value == 'string' && typeof other == 'string')) { - value = toNumber(value); - other = toNumber(other); - } - return operator(value, other); - }; - } - - /** - * Creates a function that wraps `func` to continue currying. - * - * @private - * @param {Function} func The function to wrap. - * @param {number} bitmask The bitmask flags. See `createWrap` for more details. - * @param {Function} wrapFunc The function to create the `func` wrapper. - * @param {*} placeholder The placeholder value. - * @param {*} [thisArg] The `this` binding of `func`. - * @param {Array} [partials] The arguments to prepend to those provided to - * the new function. - * @param {Array} [holders] The `partials` placeholder indexes. - * @param {Array} [argPos] The argument positions of the new function. - * @param {number} [ary] The arity cap of `func`. - * @param {number} [arity] The arity of `func`. - * @returns {Function} Returns the new wrapped function. - */ - function createRecurry(func, bitmask, wrapFunc, placeholder, thisArg, partials, holders, argPos, ary, arity) { - var isCurry = bitmask & WRAP_CURRY_FLAG, - newHolders = isCurry ? holders : undefined, - newHoldersRight = isCurry ? undefined : holders, - newPartials = isCurry ? partials : undefined, - newPartialsRight = isCurry ? undefined : partials; - - bitmask |= (isCurry ? WRAP_PARTIAL_FLAG : WRAP_PARTIAL_RIGHT_FLAG); - bitmask &= ~(isCurry ? WRAP_PARTIAL_RIGHT_FLAG : WRAP_PARTIAL_FLAG); - - if (!(bitmask & WRAP_CURRY_BOUND_FLAG)) { - bitmask &= ~(WRAP_BIND_FLAG | WRAP_BIND_KEY_FLAG); - } - var newData = [ - func, bitmask, thisArg, newPartials, newHolders, newPartialsRight, - newHoldersRight, argPos, ary, arity - ]; - - var result = wrapFunc.apply(undefined, newData); - if (isLaziable(func)) { - setData(result, newData); - } - result.placeholder = placeholder; - return setWrapToString(result, func, bitmask); - } - - /** - * Creates a function like `_.round`. - * - * @private - * @param {string} methodName The name of the `Math` method to use when rounding. - * @returns {Function} Returns the new round function. - */ - function createRound(methodName) { - var func = Math[methodName]; - return function(number, precision) { - number = toNumber(number); - precision = precision == null ? 0 : nativeMin(toInteger(precision), 292); - if (precision && nativeIsFinite(number)) { - // Shift with exponential notation to avoid floating-point issues. - // See [MDN](https://mdn.io/round#Examples) for more details. - var pair = (toString(number) + 'e').split('e'), - value = func(pair[0] + 'e' + (+pair[1] + precision)); - - pair = (toString(value) + 'e').split('e'); - return +(pair[0] + 'e' + (+pair[1] - precision)); - } - return func(number); - }; - } - - /** - * Creates a set object of `values`. - * - * @private - * @param {Array} values The values to add to the set. - * @returns {Object} Returns the new set. - */ - var createSet = !(Set && (1 / setToArray(new Set([,-0]))[1]) == INFINITY) ? noop : function(values) { - return new Set(values); - }; - - /** - * Creates a `_.toPairs` or `_.toPairsIn` function. - * - * @private - * @param {Function} keysFunc The function to get the keys of a given object. - * @returns {Function} Returns the new pairs function. - */ - function createToPairs(keysFunc) { - return function(object) { - var tag = getTag(object); - if (tag == mapTag) { - return mapToArray(object); - } - if (tag == setTag) { - return setToPairs(object); - } - return baseToPairs(object, keysFunc(object)); - }; - } - - /** - * Creates a function that either curries or invokes `func` with optional - * `this` binding and partially applied arguments. - * - * @private - * @param {Function|string} func The function or method name to wrap. - * @param {number} bitmask The bitmask flags. - * 1 - `_.bind` - * 2 - `_.bindKey` - * 4 - `_.curry` or `_.curryRight` of a bound function - * 8 - `_.curry` - * 16 - `_.curryRight` - * 32 - `_.partial` - * 64 - `_.partialRight` - * 128 - `_.rearg` - * 256 - `_.ary` - * 512 - `_.flip` - * @param {*} [thisArg] The `this` binding of `func`. - * @param {Array} [partials] The arguments to be partially applied. - * @param {Array} [holders] The `partials` placeholder indexes. - * @param {Array} [argPos] The argument positions of the new function. - * @param {number} [ary] The arity cap of `func`. - * @param {number} [arity] The arity of `func`. - * @returns {Function} Returns the new wrapped function. - */ - function createWrap(func, bitmask, thisArg, partials, holders, argPos, ary, arity) { - var isBindKey = bitmask & WRAP_BIND_KEY_FLAG; - if (!isBindKey && typeof func != 'function') { - throw new TypeError(FUNC_ERROR_TEXT); - } - var length = partials ? partials.length : 0; - if (!length) { - bitmask &= ~(WRAP_PARTIAL_FLAG | WRAP_PARTIAL_RIGHT_FLAG); - partials = holders = undefined; - } - ary = ary === undefined ? ary : nativeMax(toInteger(ary), 0); - arity = arity === undefined ? arity : toInteger(arity); - length -= holders ? holders.length : 0; - - if (bitmask & WRAP_PARTIAL_RIGHT_FLAG) { - var partialsRight = partials, - holdersRight = holders; - - partials = holders = undefined; - } - var data = isBindKey ? undefined : getData(func); - - var newData = [ - func, bitmask, thisArg, partials, holders, partialsRight, holdersRight, - argPos, ary, arity - ]; - - if (data) { - mergeData(newData, data); - } - func = newData[0]; - bitmask = newData[1]; - thisArg = newData[2]; - partials = newData[3]; - holders = newData[4]; - arity = newData[9] = newData[9] === undefined - ? (isBindKey ? 0 : func.length) - : nativeMax(newData[9] - length, 0); - - if (!arity && bitmask & (WRAP_CURRY_FLAG | WRAP_CURRY_RIGHT_FLAG)) { - bitmask &= ~(WRAP_CURRY_FLAG | WRAP_CURRY_RIGHT_FLAG); - } - if (!bitmask || bitmask == WRAP_BIND_FLAG) { - var result = createBind(func, bitmask, thisArg); - } else if (bitmask == WRAP_CURRY_FLAG || bitmask == WRAP_CURRY_RIGHT_FLAG) { - result = createCurry(func, bitmask, arity); - } else if ((bitmask == WRAP_PARTIAL_FLAG || bitmask == (WRAP_BIND_FLAG | WRAP_PARTIAL_FLAG)) && !holders.length) { - result = createPartial(func, bitmask, thisArg, partials); - } else { - result = createHybrid.apply(undefined, newData); - } - var setter = data ? baseSetData : setData; - return setWrapToString(setter(result, newData), func, bitmask); - } - - /** - * Used by `_.defaults` to customize its `_.assignIn` use to assign properties - * of source objects to the destination object for all destination properties - * that resolve to `undefined`. - * - * @private - * @param {*} objValue The destination value. - * @param {*} srcValue The source value. - * @param {string} key The key of the property to assign. - * @param {Object} object The parent object of `objValue`. - * @returns {*} Returns the value to assign. - */ - function customDefaultsAssignIn(objValue, srcValue, key, object) { - if (objValue === undefined || - (eq(objValue, objectProto[key]) && !hasOwnProperty.call(object, key))) { - return srcValue; - } - return objValue; - } - - /** - * Used by `_.defaultsDeep` to customize its `_.merge` use to merge source - * objects into destination objects that are passed thru. - * - * @private - * @param {*} objValue The destination value. - * @param {*} srcValue The source value. - * @param {string} key The key of the property to merge. - * @param {Object} object The parent object of `objValue`. - * @param {Object} source The parent object of `srcValue`. - * @param {Object} [stack] Tracks traversed source values and their merged - * counterparts. - * @returns {*} Returns the value to assign. - */ - function customDefaultsMerge(objValue, srcValue, key, object, source, stack) { - if (isObject(objValue) && isObject(srcValue)) { - // Recursively merge objects and arrays (susceptible to call stack limits). - stack.set(srcValue, objValue); - baseMerge(objValue, srcValue, undefined, customDefaultsMerge, stack); - stack['delete'](srcValue); - } - return objValue; - } - - /** - * Used by `_.omit` to customize its `_.cloneDeep` use to only clone plain - * objects. - * - * @private - * @param {*} value The value to inspect. - * @param {string} key The key of the property to inspect. - * @returns {*} Returns the uncloned value or `undefined` to defer cloning to `_.cloneDeep`. - */ - function customOmitClone(value) { - return isPlainObject(value) ? undefined : value; - } - - /** - * A specialized version of `baseIsEqualDeep` for arrays with support for - * partial deep comparisons. - * - * @private - * @param {Array} array The array to compare. - * @param {Array} other The other array to compare. - * @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details. - * @param {Function} customizer The function to customize comparisons. - * @param {Function} equalFunc The function to determine equivalents of values. - * @param {Object} stack Tracks traversed `array` and `other` objects. - * @returns {boolean} Returns `true` if the arrays are equivalent, else `false`. - */ - function equalArrays(array, other, bitmask, customizer, equalFunc, stack) { - var isPartial = bitmask & COMPARE_PARTIAL_FLAG, - arrLength = array.length, - othLength = other.length; - - if (arrLength != othLength && !(isPartial && othLength > arrLength)) { - return false; - } - // Assume cyclic values are equal. - var stacked = stack.get(array); - if (stacked && stack.get(other)) { - return stacked == other; - } - var index = -1, - result = true, - seen = (bitmask & COMPARE_UNORDERED_FLAG) ? new SetCache : undefined; - - stack.set(array, other); - stack.set(other, array); - - // Ignore non-index properties. - while (++index < arrLength) { - var arrValue = array[index], - othValue = other[index]; - - if (customizer) { - var compared = isPartial - ? customizer(othValue, arrValue, index, other, array, stack) - : customizer(arrValue, othValue, index, array, other, stack); - } - if (compared !== undefined) { - if (compared) { - continue; - } - result = false; - break; - } - // Recursively compare arrays (susceptible to call stack limits). - if (seen) { - if (!arraySome(other, function(othValue, othIndex) { - if (!cacheHas(seen, othIndex) && - (arrValue === othValue || equalFunc(arrValue, othValue, bitmask, customizer, stack))) { - return seen.push(othIndex); - } - })) { - result = false; - break; - } - } else if (!( - arrValue === othValue || - equalFunc(arrValue, othValue, bitmask, customizer, stack) - )) { - result = false; - break; - } - } - stack['delete'](array); - stack['delete'](other); - return result; - } - - /** - * A specialized version of `baseIsEqualDeep` for comparing objects of - * the same `toStringTag`. - * - * **Note:** This function only supports comparing values with tags of - * `Boolean`, `Date`, `Error`, `Number`, `RegExp`, or `String`. - * - * @private - * @param {Object} object The object to compare. - * @param {Object} other The other object to compare. - * @param {string} tag The `toStringTag` of the objects to compare. - * @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details. - * @param {Function} customizer The function to customize comparisons. - * @param {Function} equalFunc The function to determine equivalents of values. - * @param {Object} stack Tracks traversed `object` and `other` objects. - * @returns {boolean} Returns `true` if the objects are equivalent, else `false`. - */ - function equalByTag(object, other, tag, bitmask, customizer, equalFunc, stack) { - switch (tag) { - case dataViewTag: - if ((object.byteLength != other.byteLength) || - (object.byteOffset != other.byteOffset)) { - return false; - } - object = object.buffer; - other = other.buffer; - - case arrayBufferTag: - if ((object.byteLength != other.byteLength) || - !equalFunc(new Uint8Array(object), new Uint8Array(other))) { - return false; - } - return true; - - case boolTag: - case dateTag: - case numberTag: - // Coerce booleans to `1` or `0` and dates to milliseconds. - // Invalid dates are coerced to `NaN`. - return eq(+object, +other); - - case errorTag: - return object.name == other.name && object.message == other.message; - - case regexpTag: - case stringTag: - // Coerce regexes to strings and treat strings, primitives and objects, - // as equal. See http://www.ecma-international.org/ecma-262/7.0/#sec-regexp.prototype.tostring - // for more details. - return object == (other + ''); - - case mapTag: - var convert = mapToArray; - - case setTag: - var isPartial = bitmask & COMPARE_PARTIAL_FLAG; - convert || (convert = setToArray); - - if (object.size != other.size && !isPartial) { - return false; - } - // Assume cyclic values are equal. - var stacked = stack.get(object); - if (stacked) { - return stacked == other; - } - bitmask |= COMPARE_UNORDERED_FLAG; - - // Recursively compare objects (susceptible to call stack limits). - stack.set(object, other); - var result = equalArrays(convert(object), convert(other), bitmask, customizer, equalFunc, stack); - stack['delete'](object); - return result; - - case symbolTag: - if (symbolValueOf) { - return symbolValueOf.call(object) == symbolValueOf.call(other); - } - } - return false; - } - - /** - * A specialized version of `baseIsEqualDeep` for objects with support for - * partial deep comparisons. - * - * @private - * @param {Object} object The object to compare. - * @param {Object} other The other object to compare. - * @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details. - * @param {Function} customizer The function to customize comparisons. - * @param {Function} equalFunc The function to determine equivalents of values. - * @param {Object} stack Tracks traversed `object` and `other` objects. - * @returns {boolean} Returns `true` if the objects are equivalent, else `false`. - */ - function equalObjects(object, other, bitmask, customizer, equalFunc, stack) { - var isPartial = bitmask & COMPARE_PARTIAL_FLAG, - objProps = getAllKeys(object), - objLength = objProps.length, - othProps = getAllKeys(other), - othLength = othProps.length; - - if (objLength != othLength && !isPartial) { - return false; - } - var index = objLength; - while (index--) { - var key = objProps[index]; - if (!(isPartial ? key in other : hasOwnProperty.call(other, key))) { - return false; - } - } - // Assume cyclic values are equal. - var stacked = stack.get(object); - if (stacked && stack.get(other)) { - return stacked == other; - } - var result = true; - stack.set(object, other); - stack.set(other, object); - - var skipCtor = isPartial; - while (++index < objLength) { - key = objProps[index]; - var objValue = object[key], - othValue = other[key]; - - if (customizer) { - var compared = isPartial - ? customizer(othValue, objValue, key, other, object, stack) - : customizer(objValue, othValue, key, object, other, stack); - } - // Recursively compare objects (susceptible to call stack limits). - if (!(compared === undefined - ? (objValue === othValue || equalFunc(objValue, othValue, bitmask, customizer, stack)) - : compared - )) { - result = false; - break; - } - skipCtor || (skipCtor = key == 'constructor'); - } - if (result && !skipCtor) { - var objCtor = object.constructor, - othCtor = other.constructor; - - // Non `Object` object instances with different constructors are not equal. - if (objCtor != othCtor && - ('constructor' in object && 'constructor' in other) && - !(typeof objCtor == 'function' && objCtor instanceof objCtor && - typeof othCtor == 'function' && othCtor instanceof othCtor)) { - result = false; - } - } - stack['delete'](object); - stack['delete'](other); - return result; - } - - /** - * A specialized version of `baseRest` which flattens the rest array. - * - * @private - * @param {Function} func The function to apply a rest parameter to. - * @returns {Function} Returns the new function. - */ - function flatRest(func) { - return setToString(overRest(func, undefined, flatten), func + ''); - } - - /** - * Creates an array of own enumerable property names and symbols of `object`. - * - * @private - * @param {Object} object The object to query. - * @returns {Array} Returns the array of property names and symbols. - */ - function getAllKeys(object) { - return baseGetAllKeys(object, keys, getSymbols); - } - - /** - * Creates an array of own and inherited enumerable property names and - * symbols of `object`. - * - * @private - * @param {Object} object The object to query. - * @returns {Array} Returns the array of property names and symbols. - */ - function getAllKeysIn(object) { - return baseGetAllKeys(object, keysIn, getSymbolsIn); - } - - /** - * Gets metadata for `func`. - * - * @private - * @param {Function} func The function to query. - * @returns {*} Returns the metadata for `func`. - */ - var getData = !metaMap ? noop : function(func) { - return metaMap.get(func); - }; - - /** - * Gets the name of `func`. - * - * @private - * @param {Function} func The function to query. - * @returns {string} Returns the function name. - */ - function getFuncName(func) { - var result = (func.name + ''), - array = realNames[result], - length = hasOwnProperty.call(realNames, result) ? array.length : 0; - - while (length--) { - var data = array[length], - otherFunc = data.func; - if (otherFunc == null || otherFunc == func) { - return data.name; - } - } - return result; - } - - /** - * Gets the argument placeholder value for `func`. - * - * @private - * @param {Function} func The function to inspect. - * @returns {*} Returns the placeholder value. - */ - function getHolder(func) { - var object = hasOwnProperty.call(lodash, 'placeholder') ? lodash : func; - return object.placeholder; - } - - /** - * Gets the appropriate "iteratee" function. If `_.iteratee` is customized, - * this function returns the custom method, otherwise it returns `baseIteratee`. - * If arguments are provided, the chosen function is invoked with them and - * its result is returned. - * - * @private - * @param {*} [value] The value to convert to an iteratee. - * @param {number} [arity] The arity of the created iteratee. - * @returns {Function} Returns the chosen function or its result. - */ - function getIteratee() { - var result = lodash.iteratee || iteratee; - result = result === iteratee ? baseIteratee : result; - return arguments.length ? result(arguments[0], arguments[1]) : result; - } - - /** - * Gets the data for `map`. - * - * @private - * @param {Object} map The map to query. - * @param {string} key The reference key. - * @returns {*} Returns the map data. - */ - function getMapData(map, key) { - var data = map.__data__; - return isKeyable(key) - ? data[typeof key == 'string' ? 'string' : 'hash'] - : data.map; - } - - /** - * Gets the property names, values, and compare flags of `object`. - * - * @private - * @param {Object} object The object to query. - * @returns {Array} Returns the match data of `object`. - */ - function getMatchData(object) { - var result = keys(object), - length = result.length; - - while (length--) { - var key = result[length], - value = object[key]; - - result[length] = [key, value, isStrictComparable(value)]; - } - return result; - } - - /** - * Gets the native function at `key` of `object`. - * - * @private - * @param {Object} object The object to query. - * @param {string} key The key of the method to get. - * @returns {*} Returns the function if it's native, else `undefined`. - */ - function getNative(object, key) { - var value = getValue(object, key); - return baseIsNative(value) ? value : undefined; - } - - /** - * A specialized version of `baseGetTag` which ignores `Symbol.toStringTag` values. - * - * @private - * @param {*} value The value to query. - * @returns {string} Returns the raw `toStringTag`. - */ - function getRawTag(value) { - var isOwn = hasOwnProperty.call(value, symToStringTag), - tag = value[symToStringTag]; - - try { - value[symToStringTag] = undefined; - var unmasked = true; - } catch (e) {} - - var result = nativeObjectToString.call(value); - if (unmasked) { - if (isOwn) { - value[symToStringTag] = tag; - } else { - delete value[symToStringTag]; - } - } - return result; - } - - /** - * Creates an array of the own enumerable symbols of `object`. - * - * @private - * @param {Object} object The object to query. - * @returns {Array} Returns the array of symbols. - */ - var getSymbols = !nativeGetSymbols ? stubArray : function(object) { - if (object == null) { - return []; - } - object = Object(object); - return arrayFilter(nativeGetSymbols(object), function(symbol) { - return propertyIsEnumerable.call(object, symbol); - }); - }; - - /** - * Creates an array of the own and inherited enumerable symbols of `object`. - * - * @private - * @param {Object} object The object to query. - * @returns {Array} Returns the array of symbols. - */ - var getSymbolsIn = !nativeGetSymbols ? stubArray : function(object) { - var result = []; - while (object) { - arrayPush(result, getSymbols(object)); - object = getPrototype(object); - } - return result; - }; - - /** - * Gets the `toStringTag` of `value`. - * - * @private - * @param {*} value The value to query. - * @returns {string} Returns the `toStringTag`. - */ - var getTag = baseGetTag; - - // Fallback for data views, maps, sets, and weak maps in IE 11 and promises in Node.js < 6. - if ((DataView && getTag(new DataView(new ArrayBuffer(1))) != dataViewTag) || - (Map && getTag(new Map) != mapTag) || - (Promise && getTag(Promise.resolve()) != promiseTag) || - (Set && getTag(new Set) != setTag) || - (WeakMap && getTag(new WeakMap) != weakMapTag)) { - getTag = function(value) { - var result = baseGetTag(value), - Ctor = result == objectTag ? value.constructor : undefined, - ctorString = Ctor ? toSource(Ctor) : ''; - - if (ctorString) { - switch (ctorString) { - case dataViewCtorString: return dataViewTag; - case mapCtorString: return mapTag; - case promiseCtorString: return promiseTag; - case setCtorString: return setTag; - case weakMapCtorString: return weakMapTag; - } - } - return result; - }; - } - - /** - * Gets the view, applying any `transforms` to the `start` and `end` positions. - * - * @private - * @param {number} start The start of the view. - * @param {number} end The end of the view. - * @param {Array} transforms The transformations to apply to the view. - * @returns {Object} Returns an object containing the `start` and `end` - * positions of the view. - */ - function getView(start, end, transforms) { - var index = -1, - length = transforms.length; - - while (++index < length) { - var data = transforms[index], - size = data.size; - - switch (data.type) { - case 'drop': start += size; break; - case 'dropRight': end -= size; break; - case 'take': end = nativeMin(end, start + size); break; - case 'takeRight': start = nativeMax(start, end - size); break; - } - } - return { 'start': start, 'end': end }; - } - - /** - * Extracts wrapper details from the `source` body comment. - * - * @private - * @param {string} source The source to inspect. - * @returns {Array} Returns the wrapper details. - */ - function getWrapDetails(source) { - var match = source.match(reWrapDetails); - return match ? match[1].split(reSplitDetails) : []; - } - - /** - * Checks if `path` exists on `object`. - * - * @private - * @param {Object} object The object to query. - * @param {Array|string} path The path to check. - * @param {Function} hasFunc The function to check properties. - * @returns {boolean} Returns `true` if `path` exists, else `false`. - */ - function hasPath(object, path, hasFunc) { - path = castPath(path, object); - - var index = -1, - length = path.length, - result = false; - - while (++index < length) { - var key = toKey(path[index]); - if (!(result = object != null && hasFunc(object, key))) { - break; - } - object = object[key]; - } - if (result || ++index != length) { - return result; - } - length = object == null ? 0 : object.length; - return !!length && isLength(length) && isIndex(key, length) && - (isArray(object) || isArguments(object)); - } - - /** - * Initializes an array clone. - * - * @private - * @param {Array} array The array to clone. - * @returns {Array} Returns the initialized clone. - */ - function initCloneArray(array) { - var length = array.length, - result = new array.constructor(length); - - // Add properties assigned by `RegExp#exec`. - if (length && typeof array[0] == 'string' && hasOwnProperty.call(array, 'index')) { - result.index = array.index; - result.input = array.input; - } - return result; - } - - /** - * Initializes an object clone. - * - * @private - * @param {Object} object The object to clone. - * @returns {Object} Returns the initialized clone. - */ - function initCloneObject(object) { - return (typeof object.constructor == 'function' && !isPrototype(object)) - ? baseCreate(getPrototype(object)) - : {}; - } - - /** - * Initializes an object clone based on its `toStringTag`. - * - * **Note:** This function only supports cloning values with tags of - * `Boolean`, `Date`, `Error`, `Map`, `Number`, `RegExp`, `Set`, or `String`. - * - * @private - * @param {Object} object The object to clone. - * @param {string} tag The `toStringTag` of the object to clone. - * @param {boolean} [isDeep] Specify a deep clone. - * @returns {Object} Returns the initialized clone. - */ - function initCloneByTag(object, tag, isDeep) { - var Ctor = object.constructor; - switch (tag) { - case arrayBufferTag: - return cloneArrayBuffer(object); - - case boolTag: - case dateTag: - return new Ctor(+object); - - case dataViewTag: - return cloneDataView(object, isDeep); - - case float32Tag: case float64Tag: - case int8Tag: case int16Tag: case int32Tag: - case uint8Tag: case uint8ClampedTag: case uint16Tag: case uint32Tag: - return cloneTypedArray(object, isDeep); - - case mapTag: - return new Ctor; - - case numberTag: - case stringTag: - return new Ctor(object); - - case regexpTag: - return cloneRegExp(object); - - case setTag: - return new Ctor; - - case symbolTag: - return cloneSymbol(object); - } - } - - /** - * Inserts wrapper `details` in a comment at the top of the `source` body. - * - * @private - * @param {string} source The source to modify. - * @returns {Array} details The details to insert. - * @returns {string} Returns the modified source. - */ - function insertWrapDetails(source, details) { - var length = details.length; - if (!length) { - return source; - } - var lastIndex = length - 1; - details[lastIndex] = (length > 1 ? '& ' : '') + details[lastIndex]; - details = details.join(length > 2 ? ', ' : ' '); - return source.replace(reWrapComment, '{\n/* [wrapped with ' + details + '] */\n'); - } - - /** - * Checks if `value` is a flattenable `arguments` object or array. - * - * @private - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is flattenable, else `false`. - */ - function isFlattenable(value) { - return isArray(value) || isArguments(value) || - !!(spreadableSymbol && value && value[spreadableSymbol]); - } - - /** - * Checks if `value` is a valid array-like index. - * - * @private - * @param {*} value The value to check. - * @param {number} [length=MAX_SAFE_INTEGER] The upper bounds of a valid index. - * @returns {boolean} Returns `true` if `value` is a valid index, else `false`. - */ - function isIndex(value, length) { - var type = typeof value; - length = length == null ? MAX_SAFE_INTEGER : length; - - return !!length && - (type == 'number' || - (type != 'symbol' && reIsUint.test(value))) && - (value > -1 && value % 1 == 0 && value < length); - } - - /** - * Checks if the given arguments are from an iteratee call. - * - * @private - * @param {*} value The potential iteratee value argument. - * @param {*} index The potential iteratee index or key argument. - * @param {*} object The potential iteratee object argument. - * @returns {boolean} Returns `true` if the arguments are from an iteratee call, - * else `false`. - */ - function isIterateeCall(value, index, object) { - if (!isObject(object)) { - return false; - } - var type = typeof index; - if (type == 'number' - ? (isArrayLike(object) && isIndex(index, object.length)) - : (type == 'string' && index in object) - ) { - return eq(object[index], value); - } - return false; - } - - /** - * Checks if `value` is a property name and not a property path. - * - * @private - * @param {*} value The value to check. - * @param {Object} [object] The object to query keys on. - * @returns {boolean} Returns `true` if `value` is a property name, else `false`. - */ - function isKey(value, object) { - if (isArray(value)) { - return false; - } - var type = typeof value; - if (type == 'number' || type == 'symbol' || type == 'boolean' || - value == null || isSymbol(value)) { - return true; - } - return reIsPlainProp.test(value) || !reIsDeepProp.test(value) || - (object != null && value in Object(object)); - } - - /** - * Checks if `value` is suitable for use as unique object key. - * - * @private - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is suitable, else `false`. - */ - function isKeyable(value) { - var type = typeof value; - return (type == 'string' || type == 'number' || type == 'symbol' || type == 'boolean') - ? (value !== '__proto__') - : (value === null); - } - - /** - * Checks if `func` has a lazy counterpart. - * - * @private - * @param {Function} func The function to check. - * @returns {boolean} Returns `true` if `func` has a lazy counterpart, - * else `false`. - */ - function isLaziable(func) { - var funcName = getFuncName(func), - other = lodash[funcName]; - - if (typeof other != 'function' || !(funcName in LazyWrapper.prototype)) { - return false; - } - if (func === other) { - return true; - } - var data = getData(other); - return !!data && func === data[0]; - } - - /** - * Checks if `func` has its source masked. - * - * @private - * @param {Function} func The function to check. - * @returns {boolean} Returns `true` if `func` is masked, else `false`. - */ - function isMasked(func) { - return !!maskSrcKey && (maskSrcKey in func); - } - - /** - * Checks if `func` is capable of being masked. - * - * @private - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `func` is maskable, else `false`. - */ - var isMaskable = coreJsData ? isFunction : stubFalse; - - /** - * Checks if `value` is likely a prototype object. - * - * @private - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a prototype, else `false`. - */ - function isPrototype(value) { - var Ctor = value && value.constructor, - proto = (typeof Ctor == 'function' && Ctor.prototype) || objectProto; - - return value === proto; - } - - /** - * Checks if `value` is suitable for strict equality comparisons, i.e. `===`. - * - * @private - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` if suitable for strict - * equality comparisons, else `false`. - */ - function isStrictComparable(value) { - return value === value && !isObject(value); - } - - /** - * A specialized version of `matchesProperty` for source values suitable - * for strict equality comparisons, i.e. `===`. - * - * @private - * @param {string} key The key of the property to get. - * @param {*} srcValue The value to match. - * @returns {Function} Returns the new spec function. - */ - function matchesStrictComparable(key, srcValue) { - return function(object) { - if (object == null) { - return false; - } - return object[key] === srcValue && - (srcValue !== undefined || (key in Object(object))); - }; - } - - /** - * A specialized version of `_.memoize` which clears the memoized function's - * cache when it exceeds `MAX_MEMOIZE_SIZE`. - * - * @private - * @param {Function} func The function to have its output memoized. - * @returns {Function} Returns the new memoized function. - */ - function memoizeCapped(func) { - var result = memoize(func, function(key) { - if (cache.size === MAX_MEMOIZE_SIZE) { - cache.clear(); - } - return key; - }); - - var cache = result.cache; - return result; - } - - /** - * Merges the function metadata of `source` into `data`. - * - * Merging metadata reduces the number of wrappers used to invoke a function. - * This is possible because methods like `_.bind`, `_.curry`, and `_.partial` - * may be applied regardless of execution order. Methods like `_.ary` and - * `_.rearg` modify function arguments, making the order in which they are - * executed important, preventing the merging of metadata. However, we make - * an exception for a safe combined case where curried functions have `_.ary` - * and or `_.rearg` applied. - * - * @private - * @param {Array} data The destination metadata. - * @param {Array} source The source metadata. - * @returns {Array} Returns `data`. - */ - function mergeData(data, source) { - var bitmask = data[1], - srcBitmask = source[1], - newBitmask = bitmask | srcBitmask, - isCommon = newBitmask < (WRAP_BIND_FLAG | WRAP_BIND_KEY_FLAG | WRAP_ARY_FLAG); - - var isCombo = - ((srcBitmask == WRAP_ARY_FLAG) && (bitmask == WRAP_CURRY_FLAG)) || - ((srcBitmask == WRAP_ARY_FLAG) && (bitmask == WRAP_REARG_FLAG) && (data[7].length <= source[8])) || - ((srcBitmask == (WRAP_ARY_FLAG | WRAP_REARG_FLAG)) && (source[7].length <= source[8]) && (bitmask == WRAP_CURRY_FLAG)); - - // Exit early if metadata can't be merged. - if (!(isCommon || isCombo)) { - return data; - } - // Use source `thisArg` if available. - if (srcBitmask & WRAP_BIND_FLAG) { - data[2] = source[2]; - // Set when currying a bound function. - newBitmask |= bitmask & WRAP_BIND_FLAG ? 0 : WRAP_CURRY_BOUND_FLAG; - } - // Compose partial arguments. - var value = source[3]; - if (value) { - var partials = data[3]; - data[3] = partials ? composeArgs(partials, value, source[4]) : value; - data[4] = partials ? replaceHolders(data[3], PLACEHOLDER) : source[4]; - } - // Compose partial right arguments. - value = source[5]; - if (value) { - partials = data[5]; - data[5] = partials ? composeArgsRight(partials, value, source[6]) : value; - data[6] = partials ? replaceHolders(data[5], PLACEHOLDER) : source[6]; - } - // Use source `argPos` if available. - value = source[7]; - if (value) { - data[7] = value; - } - // Use source `ary` if it's smaller. - if (srcBitmask & WRAP_ARY_FLAG) { - data[8] = data[8] == null ? source[8] : nativeMin(data[8], source[8]); - } - // Use source `arity` if one is not provided. - if (data[9] == null) { - data[9] = source[9]; - } - // Use source `func` and merge bitmasks. - data[0] = source[0]; - data[1] = newBitmask; - - return data; - } - - /** - * This function is like - * [`Object.keys`](http://ecma-international.org/ecma-262/7.0/#sec-object.keys) - * except that it includes inherited enumerable properties. - * - * @private - * @param {Object} object The object to query. - * @returns {Array} Returns the array of property names. - */ - function nativeKeysIn(object) { - var result = []; - if (object != null) { - for (var key in Object(object)) { - result.push(key); - } - } - return result; - } - - /** - * Converts `value` to a string using `Object.prototype.toString`. - * - * @private - * @param {*} value The value to convert. - * @returns {string} Returns the converted string. - */ - function objectToString(value) { - return nativeObjectToString.call(value); - } - - /** - * A specialized version of `baseRest` which transforms the rest array. - * - * @private - * @param {Function} func The function to apply a rest parameter to. - * @param {number} [start=func.length-1] The start position of the rest parameter. - * @param {Function} transform The rest array transform. - * @returns {Function} Returns the new function. - */ - function overRest(func, start, transform) { - start = nativeMax(start === undefined ? (func.length - 1) : start, 0); - return function() { - var args = arguments, - index = -1, - length = nativeMax(args.length - start, 0), - array = Array(length); - - while (++index < length) { - array[index] = args[start + index]; - } - index = -1; - var otherArgs = Array(start + 1); - while (++index < start) { - otherArgs[index] = args[index]; - } - otherArgs[start] = transform(array); - return apply(func, this, otherArgs); - }; - } - - /** - * Gets the parent value at `path` of `object`. - * - * @private - * @param {Object} object The object to query. - * @param {Array} path The path to get the parent value of. - * @returns {*} Returns the parent value. - */ - function parent(object, path) { - return path.length < 2 ? object : baseGet(object, baseSlice(path, 0, -1)); - } - - /** - * Reorder `array` according to the specified indexes where the element at - * the first index is assigned as the first element, the element at - * the second index is assigned as the second element, and so on. - * - * @private - * @param {Array} array The array to reorder. - * @param {Array} indexes The arranged array indexes. - * @returns {Array} Returns `array`. - */ - function reorder(array, indexes) { - var arrLength = array.length, - length = nativeMin(indexes.length, arrLength), - oldArray = copyArray(array); - - while (length--) { - var index = indexes[length]; - array[length] = isIndex(index, arrLength) ? oldArray[index] : undefined; - } - return array; - } - - /** - * Gets the value at `key`, unless `key` is "__proto__" or "constructor". - * - * @private - * @param {Object} object The object to query. - * @param {string} key The key of the property to get. - * @returns {*} Returns the property value. - */ - function safeGet(object, key) { - if (key === 'constructor' && typeof object[key] === 'function') { - return; - } - - if (key == '__proto__') { - return; - } - - return object[key]; - } - - /** - * Sets metadata for `func`. - * - * **Note:** If this function becomes hot, i.e. is invoked a lot in a short - * period of time, it will trip its breaker and transition to an identity - * function to avoid garbage collection pauses in V8. See - * [V8 issue 2070](https://bugs.chromium.org/p/v8/issues/detail?id=2070) - * for more details. - * - * @private - * @param {Function} func The function to associate metadata with. - * @param {*} data The metadata. - * @returns {Function} Returns `func`. - */ - var setData = shortOut(baseSetData); - - /** - * A simple wrapper around the global [`setTimeout`](https://mdn.io/setTimeout). - * - * @private - * @param {Function} func The function to delay. - * @param {number} wait The number of milliseconds to delay invocation. - * @returns {number|Object} Returns the timer id or timeout object. - */ - var setTimeout = ctxSetTimeout || function(func, wait) { - return root.setTimeout(func, wait); - }; - - /** - * Sets the `toString` method of `func` to return `string`. - * - * @private - * @param {Function} func The function to modify. - * @param {Function} string The `toString` result. - * @returns {Function} Returns `func`. - */ - var setToString = shortOut(baseSetToString); - - /** - * Sets the `toString` method of `wrapper` to mimic the source of `reference` - * with wrapper details in a comment at the top of the source body. - * - * @private - * @param {Function} wrapper The function to modify. - * @param {Function} reference The reference function. - * @param {number} bitmask The bitmask flags. See `createWrap` for more details. - * @returns {Function} Returns `wrapper`. - */ - function setWrapToString(wrapper, reference, bitmask) { - var source = (reference + ''); - return setToString(wrapper, insertWrapDetails(source, updateWrapDetails(getWrapDetails(source), bitmask))); - } - - /** - * Creates a function that'll short out and invoke `identity` instead - * of `func` when it's called `HOT_COUNT` or more times in `HOT_SPAN` - * milliseconds. - * - * @private - * @param {Function} func The function to restrict. - * @returns {Function} Returns the new shortable function. - */ - function shortOut(func) { - var count = 0, - lastCalled = 0; - - return function() { - var stamp = nativeNow(), - remaining = HOT_SPAN - (stamp - lastCalled); - - lastCalled = stamp; - if (remaining > 0) { - if (++count >= HOT_COUNT) { - return arguments[0]; - } - } else { - count = 0; - } - return func.apply(undefined, arguments); - }; - } - - /** - * A specialized version of `_.shuffle` which mutates and sets the size of `array`. - * - * @private - * @param {Array} array The array to shuffle. - * @param {number} [size=array.length] The size of `array`. - * @returns {Array} Returns `array`. - */ - function shuffleSelf(array, size) { - var index = -1, - length = array.length, - lastIndex = length - 1; - - size = size === undefined ? length : size; - while (++index < size) { - var rand = baseRandom(index, lastIndex), - value = array[rand]; - - array[rand] = array[index]; - array[index] = value; - } - array.length = size; - return array; - } - - /** - * Converts `string` to a property path array. - * - * @private - * @param {string} string The string to convert. - * @returns {Array} Returns the property path array. - */ - var stringToPath = memoizeCapped(function(string) { - var result = []; - if (string.charCodeAt(0) === 46 /* . */) { - result.push(''); - } - string.replace(rePropName, function(match, number, quote, subString) { - result.push(quote ? subString.replace(reEscapeChar, '$1') : (number || match)); - }); - return result; - }); - - /** - * Converts `value` to a string key if it's not a string or symbol. - * - * @private - * @param {*} value The value to inspect. - * @returns {string|symbol} Returns the key. - */ - function toKey(value) { - if (typeof value == 'string' || isSymbol(value)) { - return value; - } - var result = (value + ''); - return (result == '0' && (1 / value) == -INFINITY) ? '-0' : result; - } - - /** - * Converts `func` to its source code. - * - * @private - * @param {Function} func The function to convert. - * @returns {string} Returns the source code. - */ - function toSource(func) { - if (func != null) { - try { - return funcToString.call(func); - } catch (e) {} - try { - return (func + ''); - } catch (e) {} - } - return ''; - } - - /** - * Updates wrapper `details` based on `bitmask` flags. - * - * @private - * @returns {Array} details The details to modify. - * @param {number} bitmask The bitmask flags. See `createWrap` for more details. - * @returns {Array} Returns `details`. - */ - function updateWrapDetails(details, bitmask) { - arrayEach(wrapFlags, function(pair) { - var value = '_.' + pair[0]; - if ((bitmask & pair[1]) && !arrayIncludes(details, value)) { - details.push(value); - } - }); - return details.sort(); - } - - /** - * Creates a clone of `wrapper`. - * - * @private - * @param {Object} wrapper The wrapper to clone. - * @returns {Object} Returns the cloned wrapper. - */ - function wrapperClone(wrapper) { - if (wrapper instanceof LazyWrapper) { - return wrapper.clone(); - } - var result = new LodashWrapper(wrapper.__wrapped__, wrapper.__chain__); - result.__actions__ = copyArray(wrapper.__actions__); - result.__index__ = wrapper.__index__; - result.__values__ = wrapper.__values__; - return result; - } - - /*------------------------------------------------------------------------*/ - - /** - * Creates an array of elements split into groups the length of `size`. - * If `array` can't be split evenly, the final chunk will be the remaining - * elements. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category Array - * @param {Array} array The array to process. - * @param {number} [size=1] The length of each chunk - * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. - * @returns {Array} Returns the new array of chunks. - * @example - * - * _.chunk(['a', 'b', 'c', 'd'], 2); - * // => [['a', 'b'], ['c', 'd']] - * - * _.chunk(['a', 'b', 'c', 'd'], 3); - * // => [['a', 'b', 'c'], ['d']] - */ - function chunk(array, size, guard) { - if ((guard ? isIterateeCall(array, size, guard) : size === undefined)) { - size = 1; - } else { - size = nativeMax(toInteger(size), 0); - } - var length = array == null ? 0 : array.length; - if (!length || size < 1) { - return []; - } - var index = 0, - resIndex = 0, - result = Array(nativeCeil(length / size)); - - while (index < length) { - result[resIndex++] = baseSlice(array, index, (index += size)); - } - return result; - } - - /** - * Creates an array with all falsey values removed. The values `false`, `null`, - * `0`, `""`, `undefined`, and `NaN` are falsey. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Array - * @param {Array} array The array to compact. - * @returns {Array} Returns the new array of filtered values. - * @example - * - * _.compact([0, 1, false, 2, '', 3]); - * // => [1, 2, 3] - */ - function compact(array) { - var index = -1, - length = array == null ? 0 : array.length, - resIndex = 0, - result = []; - - while (++index < length) { - var value = array[index]; - if (value) { - result[resIndex++] = value; - } - } - return result; - } - - /** - * Creates a new array concatenating `array` with any additional arrays - * and/or values. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Array - * @param {Array} array The array to concatenate. - * @param {...*} [values] The values to concatenate. - * @returns {Array} Returns the new concatenated array. - * @example - * - * var array = [1]; - * var other = _.concat(array, 2, [3], [[4]]); - * - * console.log(other); - * // => [1, 2, 3, [4]] - * - * console.log(array); - * // => [1] - */ - function concat() { - var length = arguments.length; - if (!length) { - return []; - } - var args = Array(length - 1), - array = arguments[0], - index = length; - - while (index--) { - args[index - 1] = arguments[index]; - } - return arrayPush(isArray(array) ? copyArray(array) : [array], baseFlatten(args, 1)); - } - - /** - * Creates an array of `array` values not included in the other given arrays - * using [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) - * for equality comparisons. The order and references of result values are - * determined by the first array. - * - * **Note:** Unlike `_.pullAll`, this method returns a new array. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Array - * @param {Array} array The array to inspect. - * @param {...Array} [values] The values to exclude. - * @returns {Array} Returns the new array of filtered values. - * @see _.without, _.xor - * @example - * - * _.difference([2, 1], [2, 3]); - * // => [1] - */ - var difference = baseRest(function(array, values) { - return isArrayLikeObject(array) - ? baseDifference(array, baseFlatten(values, 1, isArrayLikeObject, true)) - : []; - }); - - /** - * This method is like `_.difference` except that it accepts `iteratee` which - * is invoked for each element of `array` and `values` to generate the criterion - * by which they're compared. The order and references of result values are - * determined by the first array. The iteratee is invoked with one argument: - * (value). - * - * **Note:** Unlike `_.pullAllBy`, this method returns a new array. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Array - * @param {Array} array The array to inspect. - * @param {...Array} [values] The values to exclude. - * @param {Function} [iteratee=_.identity] The iteratee invoked per element. - * @returns {Array} Returns the new array of filtered values. - * @example - * - * _.differenceBy([2.1, 1.2], [2.3, 3.4], Math.floor); - * // => [1.2] - * - * // The `_.property` iteratee shorthand. - * _.differenceBy([{ 'x': 2 }, { 'x': 1 }], [{ 'x': 1 }], 'x'); - * // => [{ 'x': 2 }] - */ - var differenceBy = baseRest(function(array, values) { - var iteratee = last(values); - if (isArrayLikeObject(iteratee)) { - iteratee = undefined; - } - return isArrayLikeObject(array) - ? baseDifference(array, baseFlatten(values, 1, isArrayLikeObject, true), getIteratee(iteratee, 2)) - : []; - }); - - /** - * This method is like `_.difference` except that it accepts `comparator` - * which is invoked to compare elements of `array` to `values`. The order and - * references of result values are determined by the first array. The comparator - * is invoked with two arguments: (arrVal, othVal). - * - * **Note:** Unlike `_.pullAllWith`, this method returns a new array. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Array - * @param {Array} array The array to inspect. - * @param {...Array} [values] The values to exclude. - * @param {Function} [comparator] The comparator invoked per element. - * @returns {Array} Returns the new array of filtered values. - * @example - * - * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }]; - * - * _.differenceWith(objects, [{ 'x': 1, 'y': 2 }], _.isEqual); - * // => [{ 'x': 2, 'y': 1 }] - */ - var differenceWith = baseRest(function(array, values) { - var comparator = last(values); - if (isArrayLikeObject(comparator)) { - comparator = undefined; - } - return isArrayLikeObject(array) - ? baseDifference(array, baseFlatten(values, 1, isArrayLikeObject, true), undefined, comparator) - : []; - }); - - /** - * Creates a slice of `array` with `n` elements dropped from the beginning. - * - * @static - * @memberOf _ - * @since 0.5.0 - * @category Array - * @param {Array} array The array to query. - * @param {number} [n=1] The number of elements to drop. - * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. - * @returns {Array} Returns the slice of `array`. - * @example - * - * _.drop([1, 2, 3]); - * // => [2, 3] - * - * _.drop([1, 2, 3], 2); - * // => [3] - * - * _.drop([1, 2, 3], 5); - * // => [] - * - * _.drop([1, 2, 3], 0); - * // => [1, 2, 3] - */ - function drop(array, n, guard) { - var length = array == null ? 0 : array.length; - if (!length) { - return []; - } - n = (guard || n === undefined) ? 1 : toInteger(n); - return baseSlice(array, n < 0 ? 0 : n, length); - } - - /** - * Creates a slice of `array` with `n` elements dropped from the end. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category Array - * @param {Array} array The array to query. - * @param {number} [n=1] The number of elements to drop. - * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. - * @returns {Array} Returns the slice of `array`. - * @example - * - * _.dropRight([1, 2, 3]); - * // => [1, 2] - * - * _.dropRight([1, 2, 3], 2); - * // => [1] - * - * _.dropRight([1, 2, 3], 5); - * // => [] - * - * _.dropRight([1, 2, 3], 0); - * // => [1, 2, 3] - */ - function dropRight(array, n, guard) { - var length = array == null ? 0 : array.length; - if (!length) { - return []; - } - n = (guard || n === undefined) ? 1 : toInteger(n); - n = length - n; - return baseSlice(array, 0, n < 0 ? 0 : n); - } - - /** - * Creates a slice of `array` excluding elements dropped from the end. - * Elements are dropped until `predicate` returns falsey. The predicate is - * invoked with three arguments: (value, index, array). - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category Array - * @param {Array} array The array to query. - * @param {Function} [predicate=_.identity] The function invoked per iteration. - * @returns {Array} Returns the slice of `array`. - * @example - * - * var users = [ - * { 'user': 'barney', 'active': true }, - * { 'user': 'fred', 'active': false }, - * { 'user': 'pebbles', 'active': false } - * ]; - * - * _.dropRightWhile(users, function(o) { return !o.active; }); - * // => objects for ['barney'] - * - * // The `_.matches` iteratee shorthand. - * _.dropRightWhile(users, { 'user': 'pebbles', 'active': false }); - * // => objects for ['barney', 'fred'] - * - * // The `_.matchesProperty` iteratee shorthand. - * _.dropRightWhile(users, ['active', false]); - * // => objects for ['barney'] - * - * // The `_.property` iteratee shorthand. - * _.dropRightWhile(users, 'active'); - * // => objects for ['barney', 'fred', 'pebbles'] - */ - function dropRightWhile(array, predicate) { - return (array && array.length) - ? baseWhile(array, getIteratee(predicate, 3), true, true) - : []; - } - - /** - * Creates a slice of `array` excluding elements dropped from the beginning. - * Elements are dropped until `predicate` returns falsey. The predicate is - * invoked with three arguments: (value, index, array). - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category Array - * @param {Array} array The array to query. - * @param {Function} [predicate=_.identity] The function invoked per iteration. - * @returns {Array} Returns the slice of `array`. - * @example - * - * var users = [ - * { 'user': 'barney', 'active': false }, - * { 'user': 'fred', 'active': false }, - * { 'user': 'pebbles', 'active': true } - * ]; - * - * _.dropWhile(users, function(o) { return !o.active; }); - * // => objects for ['pebbles'] - * - * // The `_.matches` iteratee shorthand. - * _.dropWhile(users, { 'user': 'barney', 'active': false }); - * // => objects for ['fred', 'pebbles'] - * - * // The `_.matchesProperty` iteratee shorthand. - * _.dropWhile(users, ['active', false]); - * // => objects for ['pebbles'] - * - * // The `_.property` iteratee shorthand. - * _.dropWhile(users, 'active'); - * // => objects for ['barney', 'fred', 'pebbles'] - */ - function dropWhile(array, predicate) { - return (array && array.length) - ? baseWhile(array, getIteratee(predicate, 3), true) - : []; - } - - /** - * Fills elements of `array` with `value` from `start` up to, but not - * including, `end`. - * - * **Note:** This method mutates `array`. - * - * @static - * @memberOf _ - * @since 3.2.0 - * @category Array - * @param {Array} array The array to fill. - * @param {*} value The value to fill `array` with. - * @param {number} [start=0] The start position. - * @param {number} [end=array.length] The end position. - * @returns {Array} Returns `array`. - * @example - * - * var array = [1, 2, 3]; - * - * _.fill(array, 'a'); - * console.log(array); - * // => ['a', 'a', 'a'] - * - * _.fill(Array(3), 2); - * // => [2, 2, 2] - * - * _.fill([4, 6, 8, 10], '*', 1, 3); - * // => [4, '*', '*', 10] - */ - function fill(array, value, start, end) { - var length = array == null ? 0 : array.length; - if (!length) { - return []; - } - if (start && typeof start != 'number' && isIterateeCall(array, value, start)) { - start = 0; - end = length; - } - return baseFill(array, value, start, end); - } - - /** - * This method is like `_.find` except that it returns the index of the first - * element `predicate` returns truthy for instead of the element itself. - * - * @static - * @memberOf _ - * @since 1.1.0 - * @category Array - * @param {Array} array The array to inspect. - * @param {Function} [predicate=_.identity] The function invoked per iteration. - * @param {number} [fromIndex=0] The index to search from. - * @returns {number} Returns the index of the found element, else `-1`. - * @example - * - * var users = [ - * { 'user': 'barney', 'active': false }, - * { 'user': 'fred', 'active': false }, - * { 'user': 'pebbles', 'active': true } - * ]; - * - * _.findIndex(users, function(o) { return o.user == 'barney'; }); - * // => 0 - * - * // The `_.matches` iteratee shorthand. - * _.findIndex(users, { 'user': 'fred', 'active': false }); - * // => 1 - * - * // The `_.matchesProperty` iteratee shorthand. - * _.findIndex(users, ['active', false]); - * // => 0 - * - * // The `_.property` iteratee shorthand. - * _.findIndex(users, 'active'); - * // => 2 - */ - function findIndex(array, predicate, fromIndex) { - var length = array == null ? 0 : array.length; - if (!length) { - return -1; - } - var index = fromIndex == null ? 0 : toInteger(fromIndex); - if (index < 0) { - index = nativeMax(length + index, 0); - } - return baseFindIndex(array, getIteratee(predicate, 3), index); - } - - /** - * This method is like `_.findIndex` except that it iterates over elements - * of `collection` from right to left. - * - * @static - * @memberOf _ - * @since 2.0.0 - * @category Array - * @param {Array} array The array to inspect. - * @param {Function} [predicate=_.identity] The function invoked per iteration. - * @param {number} [fromIndex=array.length-1] The index to search from. - * @returns {number} Returns the index of the found element, else `-1`. - * @example - * - * var users = [ - * { 'user': 'barney', 'active': true }, - * { 'user': 'fred', 'active': false }, - * { 'user': 'pebbles', 'active': false } - * ]; - * - * _.findLastIndex(users, function(o) { return o.user == 'pebbles'; }); - * // => 2 - * - * // The `_.matches` iteratee shorthand. - * _.findLastIndex(users, { 'user': 'barney', 'active': true }); - * // => 0 - * - * // The `_.matchesProperty` iteratee shorthand. - * _.findLastIndex(users, ['active', false]); - * // => 2 - * - * // The `_.property` iteratee shorthand. - * _.findLastIndex(users, 'active'); - * // => 0 - */ - function findLastIndex(array, predicate, fromIndex) { - var length = array == null ? 0 : array.length; - if (!length) { - return -1; - } - var index = length - 1; - if (fromIndex !== undefined) { - index = toInteger(fromIndex); - index = fromIndex < 0 - ? nativeMax(length + index, 0) - : nativeMin(index, length - 1); - } - return baseFindIndex(array, getIteratee(predicate, 3), index, true); - } - - /** - * Flattens `array` a single level deep. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Array - * @param {Array} array The array to flatten. - * @returns {Array} Returns the new flattened array. - * @example - * - * _.flatten([1, [2, [3, [4]], 5]]); - * // => [1, 2, [3, [4]], 5] - */ - function flatten(array) { - var length = array == null ? 0 : array.length; - return length ? baseFlatten(array, 1) : []; - } - - /** - * Recursively flattens `array`. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category Array - * @param {Array} array The array to flatten. - * @returns {Array} Returns the new flattened array. - * @example - * - * _.flattenDeep([1, [2, [3, [4]], 5]]); - * // => [1, 2, 3, 4, 5] - */ - function flattenDeep(array) { - var length = array == null ? 0 : array.length; - return length ? baseFlatten(array, INFINITY) : []; - } - - /** - * Recursively flatten `array` up to `depth` times. - * - * @static - * @memberOf _ - * @since 4.4.0 - * @category Array - * @param {Array} array The array to flatten. - * @param {number} [depth=1] The maximum recursion depth. - * @returns {Array} Returns the new flattened array. - * @example - * - * var array = [1, [2, [3, [4]], 5]]; - * - * _.flattenDepth(array, 1); - * // => [1, 2, [3, [4]], 5] - * - * _.flattenDepth(array, 2); - * // => [1, 2, 3, [4], 5] - */ - function flattenDepth(array, depth) { - var length = array == null ? 0 : array.length; - if (!length) { - return []; - } - depth = depth === undefined ? 1 : toInteger(depth); - return baseFlatten(array, depth); - } - - /** - * The inverse of `_.toPairs`; this method returns an object composed - * from key-value `pairs`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Array - * @param {Array} pairs The key-value pairs. - * @returns {Object} Returns the new object. - * @example - * - * _.fromPairs([['a', 1], ['b', 2]]); - * // => { 'a': 1, 'b': 2 } - */ - function fromPairs(pairs) { - var index = -1, - length = pairs == null ? 0 : pairs.length, - result = {}; - - while (++index < length) { - var pair = pairs[index]; - result[pair[0]] = pair[1]; - } - return result; - } - - /** - * Gets the first element of `array`. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @alias first - * @category Array - * @param {Array} array The array to query. - * @returns {*} Returns the first element of `array`. - * @example - * - * _.head([1, 2, 3]); - * // => 1 - * - * _.head([]); - * // => undefined - */ - function head(array) { - return (array && array.length) ? array[0] : undefined; - } - - /** - * Gets the index at which the first occurrence of `value` is found in `array` - * using [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) - * for equality comparisons. If `fromIndex` is negative, it's used as the - * offset from the end of `array`. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Array - * @param {Array} array The array to inspect. - * @param {*} value The value to search for. - * @param {number} [fromIndex=0] The index to search from. - * @returns {number} Returns the index of the matched value, else `-1`. - * @example - * - * _.indexOf([1, 2, 1, 2], 2); - * // => 1 - * - * // Search from the `fromIndex`. - * _.indexOf([1, 2, 1, 2], 2, 2); - * // => 3 - */ - function indexOf(array, value, fromIndex) { - var length = array == null ? 0 : array.length; - if (!length) { - return -1; - } - var index = fromIndex == null ? 0 : toInteger(fromIndex); - if (index < 0) { - index = nativeMax(length + index, 0); - } - return baseIndexOf(array, value, index); - } - - /** - * Gets all but the last element of `array`. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Array - * @param {Array} array The array to query. - * @returns {Array} Returns the slice of `array`. - * @example - * - * _.initial([1, 2, 3]); - * // => [1, 2] - */ - function initial(array) { - var length = array == null ? 0 : array.length; - return length ? baseSlice(array, 0, -1) : []; - } - - /** - * Creates an array of unique values that are included in all given arrays - * using [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) - * for equality comparisons. The order and references of result values are - * determined by the first array. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Array - * @param {...Array} [arrays] The arrays to inspect. - * @returns {Array} Returns the new array of intersecting values. - * @example - * - * _.intersection([2, 1], [2, 3]); - * // => [2] - */ - var intersection = baseRest(function(arrays) { - var mapped = arrayMap(arrays, castArrayLikeObject); - return (mapped.length && mapped[0] === arrays[0]) - ? baseIntersection(mapped) - : []; - }); - - /** - * This method is like `_.intersection` except that it accepts `iteratee` - * which is invoked for each element of each `arrays` to generate the criterion - * by which they're compared. The order and references of result values are - * determined by the first array. The iteratee is invoked with one argument: - * (value). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Array - * @param {...Array} [arrays] The arrays to inspect. - * @param {Function} [iteratee=_.identity] The iteratee invoked per element. - * @returns {Array} Returns the new array of intersecting values. - * @example - * - * _.intersectionBy([2.1, 1.2], [2.3, 3.4], Math.floor); - * // => [2.1] - * - * // The `_.property` iteratee shorthand. - * _.intersectionBy([{ 'x': 1 }], [{ 'x': 2 }, { 'x': 1 }], 'x'); - * // => [{ 'x': 1 }] - */ - var intersectionBy = baseRest(function(arrays) { - var iteratee = last(arrays), - mapped = arrayMap(arrays, castArrayLikeObject); - - if (iteratee === last(mapped)) { - iteratee = undefined; - } else { - mapped.pop(); - } - return (mapped.length && mapped[0] === arrays[0]) - ? baseIntersection(mapped, getIteratee(iteratee, 2)) - : []; - }); - - /** - * This method is like `_.intersection` except that it accepts `comparator` - * which is invoked to compare elements of `arrays`. The order and references - * of result values are determined by the first array. The comparator is - * invoked with two arguments: (arrVal, othVal). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Array - * @param {...Array} [arrays] The arrays to inspect. - * @param {Function} [comparator] The comparator invoked per element. - * @returns {Array} Returns the new array of intersecting values. - * @example - * - * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }]; - * var others = [{ 'x': 1, 'y': 1 }, { 'x': 1, 'y': 2 }]; - * - * _.intersectionWith(objects, others, _.isEqual); - * // => [{ 'x': 1, 'y': 2 }] - */ - var intersectionWith = baseRest(function(arrays) { - var comparator = last(arrays), - mapped = arrayMap(arrays, castArrayLikeObject); - - comparator = typeof comparator == 'function' ? comparator : undefined; - if (comparator) { - mapped.pop(); - } - return (mapped.length && mapped[0] === arrays[0]) - ? baseIntersection(mapped, undefined, comparator) - : []; - }); - - /** - * Converts all elements in `array` into a string separated by `separator`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Array - * @param {Array} array The array to convert. - * @param {string} [separator=','] The element separator. - * @returns {string} Returns the joined string. - * @example - * - * _.join(['a', 'b', 'c'], '~'); - * // => 'a~b~c' - */ - function join(array, separator) { - return array == null ? '' : nativeJoin.call(array, separator); - } - - /** - * Gets the last element of `array`. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Array - * @param {Array} array The array to query. - * @returns {*} Returns the last element of `array`. - * @example - * - * _.last([1, 2, 3]); - * // => 3 - */ - function last(array) { - var length = array == null ? 0 : array.length; - return length ? array[length - 1] : undefined; - } - - /** - * This method is like `_.indexOf` except that it iterates over elements of - * `array` from right to left. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Array - * @param {Array} array The array to inspect. - * @param {*} value The value to search for. - * @param {number} [fromIndex=array.length-1] The index to search from. - * @returns {number} Returns the index of the matched value, else `-1`. - * @example - * - * _.lastIndexOf([1, 2, 1, 2], 2); - * // => 3 - * - * // Search from the `fromIndex`. - * _.lastIndexOf([1, 2, 1, 2], 2, 2); - * // => 1 - */ - function lastIndexOf(array, value, fromIndex) { - var length = array == null ? 0 : array.length; - if (!length) { - return -1; - } - var index = length; - if (fromIndex !== undefined) { - index = toInteger(fromIndex); - index = index < 0 ? nativeMax(length + index, 0) : nativeMin(index, length - 1); - } - return value === value - ? strictLastIndexOf(array, value, index) - : baseFindIndex(array, baseIsNaN, index, true); - } - - /** - * Gets the element at index `n` of `array`. If `n` is negative, the nth - * element from the end is returned. - * - * @static - * @memberOf _ - * @since 4.11.0 - * @category Array - * @param {Array} array The array to query. - * @param {number} [n=0] The index of the element to return. - * @returns {*} Returns the nth element of `array`. - * @example - * - * var array = ['a', 'b', 'c', 'd']; - * - * _.nth(array, 1); - * // => 'b' - * - * _.nth(array, -2); - * // => 'c'; - */ - function nth(array, n) { - return (array && array.length) ? baseNth(array, toInteger(n)) : undefined; - } - - /** - * Removes all given values from `array` using - * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) - * for equality comparisons. - * - * **Note:** Unlike `_.without`, this method mutates `array`. Use `_.remove` - * to remove elements from an array by predicate. - * - * @static - * @memberOf _ - * @since 2.0.0 - * @category Array - * @param {Array} array The array to modify. - * @param {...*} [values] The values to remove. - * @returns {Array} Returns `array`. - * @example - * - * var array = ['a', 'b', 'c', 'a', 'b', 'c']; - * - * _.pull(array, 'a', 'c'); - * console.log(array); - * // => ['b', 'b'] - */ - var pull = baseRest(pullAll); - - /** - * This method is like `_.pull` except that it accepts an array of values to remove. - * - * **Note:** Unlike `_.difference`, this method mutates `array`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Array - * @param {Array} array The array to modify. - * @param {Array} values The values to remove. - * @returns {Array} Returns `array`. - * @example - * - * var array = ['a', 'b', 'c', 'a', 'b', 'c']; - * - * _.pullAll(array, ['a', 'c']); - * console.log(array); - * // => ['b', 'b'] - */ - function pullAll(array, values) { - return (array && array.length && values && values.length) - ? basePullAll(array, values) - : array; - } - - /** - * This method is like `_.pullAll` except that it accepts `iteratee` which is - * invoked for each element of `array` and `values` to generate the criterion - * by which they're compared. The iteratee is invoked with one argument: (value). - * - * **Note:** Unlike `_.differenceBy`, this method mutates `array`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Array - * @param {Array} array The array to modify. - * @param {Array} values The values to remove. - * @param {Function} [iteratee=_.identity] The iteratee invoked per element. - * @returns {Array} Returns `array`. - * @example - * - * var array = [{ 'x': 1 }, { 'x': 2 }, { 'x': 3 }, { 'x': 1 }]; - * - * _.pullAllBy(array, [{ 'x': 1 }, { 'x': 3 }], 'x'); - * console.log(array); - * // => [{ 'x': 2 }] - */ - function pullAllBy(array, values, iteratee) { - return (array && array.length && values && values.length) - ? basePullAll(array, values, getIteratee(iteratee, 2)) - : array; - } - - /** - * This method is like `_.pullAll` except that it accepts `comparator` which - * is invoked to compare elements of `array` to `values`. The comparator is - * invoked with two arguments: (arrVal, othVal). - * - * **Note:** Unlike `_.differenceWith`, this method mutates `array`. - * - * @static - * @memberOf _ - * @since 4.6.0 - * @category Array - * @param {Array} array The array to modify. - * @param {Array} values The values to remove. - * @param {Function} [comparator] The comparator invoked per element. - * @returns {Array} Returns `array`. - * @example - * - * var array = [{ 'x': 1, 'y': 2 }, { 'x': 3, 'y': 4 }, { 'x': 5, 'y': 6 }]; - * - * _.pullAllWith(array, [{ 'x': 3, 'y': 4 }], _.isEqual); - * console.log(array); - * // => [{ 'x': 1, 'y': 2 }, { 'x': 5, 'y': 6 }] - */ - function pullAllWith(array, values, comparator) { - return (array && array.length && values && values.length) - ? basePullAll(array, values, undefined, comparator) - : array; - } - - /** - * Removes elements from `array` corresponding to `indexes` and returns an - * array of removed elements. - * - * **Note:** Unlike `_.at`, this method mutates `array`. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category Array - * @param {Array} array The array to modify. - * @param {...(number|number[])} [indexes] The indexes of elements to remove. - * @returns {Array} Returns the new array of removed elements. - * @example - * - * var array = ['a', 'b', 'c', 'd']; - * var pulled = _.pullAt(array, [1, 3]); - * - * console.log(array); - * // => ['a', 'c'] - * - * console.log(pulled); - * // => ['b', 'd'] - */ - var pullAt = flatRest(function(array, indexes) { - var length = array == null ? 0 : array.length, - result = baseAt(array, indexes); - - basePullAt(array, arrayMap(indexes, function(index) { - return isIndex(index, length) ? +index : index; - }).sort(compareAscending)); - - return result; - }); - - /** - * Removes all elements from `array` that `predicate` returns truthy for - * and returns an array of the removed elements. The predicate is invoked - * with three arguments: (value, index, array). - * - * **Note:** Unlike `_.filter`, this method mutates `array`. Use `_.pull` - * to pull elements from an array by value. - * - * @static - * @memberOf _ - * @since 2.0.0 - * @category Array - * @param {Array} array The array to modify. - * @param {Function} [predicate=_.identity] The function invoked per iteration. - * @returns {Array} Returns the new array of removed elements. - * @example - * - * var array = [1, 2, 3, 4]; - * var evens = _.remove(array, function(n) { - * return n % 2 == 0; - * }); - * - * console.log(array); - * // => [1, 3] - * - * console.log(evens); - * // => [2, 4] - */ - function remove(array, predicate) { - var result = []; - if (!(array && array.length)) { - return result; - } - var index = -1, - indexes = [], - length = array.length; - - predicate = getIteratee(predicate, 3); - while (++index < length) { - var value = array[index]; - if (predicate(value, index, array)) { - result.push(value); - indexes.push(index); - } - } - basePullAt(array, indexes); - return result; - } - - /** - * Reverses `array` so that the first element becomes the last, the second - * element becomes the second to last, and so on. - * - * **Note:** This method mutates `array` and is based on - * [`Array#reverse`](https://mdn.io/Array/reverse). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Array - * @param {Array} array The array to modify. - * @returns {Array} Returns `array`. - * @example - * - * var array = [1, 2, 3]; - * - * _.reverse(array); - * // => [3, 2, 1] - * - * console.log(array); - * // => [3, 2, 1] - */ - function reverse(array) { - return array == null ? array : nativeReverse.call(array); - } - - /** - * Creates a slice of `array` from `start` up to, but not including, `end`. - * - * **Note:** This method is used instead of - * [`Array#slice`](https://mdn.io/Array/slice) to ensure dense arrays are - * returned. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category Array - * @param {Array} array The array to slice. - * @param {number} [start=0] The start position. - * @param {number} [end=array.length] The end position. - * @returns {Array} Returns the slice of `array`. - */ - function slice(array, start, end) { - var length = array == null ? 0 : array.length; - if (!length) { - return []; - } - if (end && typeof end != 'number' && isIterateeCall(array, start, end)) { - start = 0; - end = length; - } - else { - start = start == null ? 0 : toInteger(start); - end = end === undefined ? length : toInteger(end); - } - return baseSlice(array, start, end); - } - - /** - * Uses a binary search to determine the lowest index at which `value` - * should be inserted into `array` in order to maintain its sort order. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Array - * @param {Array} array The sorted array to inspect. - * @param {*} value The value to evaluate. - * @returns {number} Returns the index at which `value` should be inserted - * into `array`. - * @example - * - * _.sortedIndex([30, 50], 40); - * // => 1 - */ - function sortedIndex(array, value) { - return baseSortedIndex(array, value); - } - - /** - * This method is like `_.sortedIndex` except that it accepts `iteratee` - * which is invoked for `value` and each element of `array` to compute their - * sort ranking. The iteratee is invoked with one argument: (value). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Array - * @param {Array} array The sorted array to inspect. - * @param {*} value The value to evaluate. - * @param {Function} [iteratee=_.identity] The iteratee invoked per element. - * @returns {number} Returns the index at which `value` should be inserted - * into `array`. - * @example - * - * var objects = [{ 'x': 4 }, { 'x': 5 }]; - * - * _.sortedIndexBy(objects, { 'x': 4 }, function(o) { return o.x; }); - * // => 0 - * - * // The `_.property` iteratee shorthand. - * _.sortedIndexBy(objects, { 'x': 4 }, 'x'); - * // => 0 - */ - function sortedIndexBy(array, value, iteratee) { - return baseSortedIndexBy(array, value, getIteratee(iteratee, 2)); - } - - /** - * This method is like `_.indexOf` except that it performs a binary - * search on a sorted `array`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Array - * @param {Array} array The array to inspect. - * @param {*} value The value to search for. - * @returns {number} Returns the index of the matched value, else `-1`. - * @example - * - * _.sortedIndexOf([4, 5, 5, 5, 6], 5); - * // => 1 - */ - function sortedIndexOf(array, value) { - var length = array == null ? 0 : array.length; - if (length) { - var index = baseSortedIndex(array, value); - if (index < length && eq(array[index], value)) { - return index; - } - } - return -1; - } - - /** - * This method is like `_.sortedIndex` except that it returns the highest - * index at which `value` should be inserted into `array` in order to - * maintain its sort order. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category Array - * @param {Array} array The sorted array to inspect. - * @param {*} value The value to evaluate. - * @returns {number} Returns the index at which `value` should be inserted - * into `array`. - * @example - * - * _.sortedLastIndex([4, 5, 5, 5, 6], 5); - * // => 4 - */ - function sortedLastIndex(array, value) { - return baseSortedIndex(array, value, true); - } - - /** - * This method is like `_.sortedLastIndex` except that it accepts `iteratee` - * which is invoked for `value` and each element of `array` to compute their - * sort ranking. The iteratee is invoked with one argument: (value). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Array - * @param {Array} array The sorted array to inspect. - * @param {*} value The value to evaluate. - * @param {Function} [iteratee=_.identity] The iteratee invoked per element. - * @returns {number} Returns the index at which `value` should be inserted - * into `array`. - * @example - * - * var objects = [{ 'x': 4 }, { 'x': 5 }]; - * - * _.sortedLastIndexBy(objects, { 'x': 4 }, function(o) { return o.x; }); - * // => 1 - * - * // The `_.property` iteratee shorthand. - * _.sortedLastIndexBy(objects, { 'x': 4 }, 'x'); - * // => 1 - */ - function sortedLastIndexBy(array, value, iteratee) { - return baseSortedIndexBy(array, value, getIteratee(iteratee, 2), true); - } - - /** - * This method is like `_.lastIndexOf` except that it performs a binary - * search on a sorted `array`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Array - * @param {Array} array The array to inspect. - * @param {*} value The value to search for. - * @returns {number} Returns the index of the matched value, else `-1`. - * @example - * - * _.sortedLastIndexOf([4, 5, 5, 5, 6], 5); - * // => 3 - */ - function sortedLastIndexOf(array, value) { - var length = array == null ? 0 : array.length; - if (length) { - var index = baseSortedIndex(array, value, true) - 1; - if (eq(array[index], value)) { - return index; - } - } - return -1; - } - - /** - * This method is like `_.uniq` except that it's designed and optimized - * for sorted arrays. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Array - * @param {Array} array The array to inspect. - * @returns {Array} Returns the new duplicate free array. - * @example - * - * _.sortedUniq([1, 1, 2]); - * // => [1, 2] - */ - function sortedUniq(array) { - return (array && array.length) - ? baseSortedUniq(array) - : []; - } - - /** - * This method is like `_.uniqBy` except that it's designed and optimized - * for sorted arrays. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Array - * @param {Array} array The array to inspect. - * @param {Function} [iteratee] The iteratee invoked per element. - * @returns {Array} Returns the new duplicate free array. - * @example - * - * _.sortedUniqBy([1.1, 1.2, 2.3, 2.4], Math.floor); - * // => [1.1, 2.3] - */ - function sortedUniqBy(array, iteratee) { - return (array && array.length) - ? baseSortedUniq(array, getIteratee(iteratee, 2)) - : []; - } - - /** - * Gets all but the first element of `array`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Array - * @param {Array} array The array to query. - * @returns {Array} Returns the slice of `array`. - * @example - * - * _.tail([1, 2, 3]); - * // => [2, 3] - */ - function tail(array) { - var length = array == null ? 0 : array.length; - return length ? baseSlice(array, 1, length) : []; - } - - /** - * Creates a slice of `array` with `n` elements taken from the beginning. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Array - * @param {Array} array The array to query. - * @param {number} [n=1] The number of elements to take. - * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. - * @returns {Array} Returns the slice of `array`. - * @example - * - * _.take([1, 2, 3]); - * // => [1] - * - * _.take([1, 2, 3], 2); - * // => [1, 2] - * - * _.take([1, 2, 3], 5); - * // => [1, 2, 3] - * - * _.take([1, 2, 3], 0); - * // => [] - */ - function take(array, n, guard) { - if (!(array && array.length)) { - return []; - } - n = (guard || n === undefined) ? 1 : toInteger(n); - return baseSlice(array, 0, n < 0 ? 0 : n); - } - - /** - * Creates a slice of `array` with `n` elements taken from the end. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category Array - * @param {Array} array The array to query. - * @param {number} [n=1] The number of elements to take. - * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. - * @returns {Array} Returns the slice of `array`. - * @example - * - * _.takeRight([1, 2, 3]); - * // => [3] - * - * _.takeRight([1, 2, 3], 2); - * // => [2, 3] - * - * _.takeRight([1, 2, 3], 5); - * // => [1, 2, 3] - * - * _.takeRight([1, 2, 3], 0); - * // => [] - */ - function takeRight(array, n, guard) { - var length = array == null ? 0 : array.length; - if (!length) { - return []; - } - n = (guard || n === undefined) ? 1 : toInteger(n); - n = length - n; - return baseSlice(array, n < 0 ? 0 : n, length); - } - - /** - * Creates a slice of `array` with elements taken from the end. Elements are - * taken until `predicate` returns falsey. The predicate is invoked with - * three arguments: (value, index, array). - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category Array - * @param {Array} array The array to query. - * @param {Function} [predicate=_.identity] The function invoked per iteration. - * @returns {Array} Returns the slice of `array`. - * @example - * - * var users = [ - * { 'user': 'barney', 'active': true }, - * { 'user': 'fred', 'active': false }, - * { 'user': 'pebbles', 'active': false } - * ]; - * - * _.takeRightWhile(users, function(o) { return !o.active; }); - * // => objects for ['fred', 'pebbles'] - * - * // The `_.matches` iteratee shorthand. - * _.takeRightWhile(users, { 'user': 'pebbles', 'active': false }); - * // => objects for ['pebbles'] - * - * // The `_.matchesProperty` iteratee shorthand. - * _.takeRightWhile(users, ['active', false]); - * // => objects for ['fred', 'pebbles'] - * - * // The `_.property` iteratee shorthand. - * _.takeRightWhile(users, 'active'); - * // => [] - */ - function takeRightWhile(array, predicate) { - return (array && array.length) - ? baseWhile(array, getIteratee(predicate, 3), false, true) - : []; - } - - /** - * Creates a slice of `array` with elements taken from the beginning. Elements - * are taken until `predicate` returns falsey. The predicate is invoked with - * three arguments: (value, index, array). - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category Array - * @param {Array} array The array to query. - * @param {Function} [predicate=_.identity] The function invoked per iteration. - * @returns {Array} Returns the slice of `array`. - * @example - * - * var users = [ - * { 'user': 'barney', 'active': false }, - * { 'user': 'fred', 'active': false }, - * { 'user': 'pebbles', 'active': true } - * ]; - * - * _.takeWhile(users, function(o) { return !o.active; }); - * // => objects for ['barney', 'fred'] - * - * // The `_.matches` iteratee shorthand. - * _.takeWhile(users, { 'user': 'barney', 'active': false }); - * // => objects for ['barney'] - * - * // The `_.matchesProperty` iteratee shorthand. - * _.takeWhile(users, ['active', false]); - * // => objects for ['barney', 'fred'] - * - * // The `_.property` iteratee shorthand. - * _.takeWhile(users, 'active'); - * // => [] - */ - function takeWhile(array, predicate) { - return (array && array.length) - ? baseWhile(array, getIteratee(predicate, 3)) - : []; - } - - /** - * Creates an array of unique values, in order, from all given arrays using - * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) - * for equality comparisons. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Array - * @param {...Array} [arrays] The arrays to inspect. - * @returns {Array} Returns the new array of combined values. - * @example - * - * _.union([2], [1, 2]); - * // => [2, 1] - */ - var union = baseRest(function(arrays) { - return baseUniq(baseFlatten(arrays, 1, isArrayLikeObject, true)); - }); - - /** - * This method is like `_.union` except that it accepts `iteratee` which is - * invoked for each element of each `arrays` to generate the criterion by - * which uniqueness is computed. Result values are chosen from the first - * array in which the value occurs. The iteratee is invoked with one argument: - * (value). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Array - * @param {...Array} [arrays] The arrays to inspect. - * @param {Function} [iteratee=_.identity] The iteratee invoked per element. - * @returns {Array} Returns the new array of combined values. - * @example - * - * _.unionBy([2.1], [1.2, 2.3], Math.floor); - * // => [2.1, 1.2] - * - * // The `_.property` iteratee shorthand. - * _.unionBy([{ 'x': 1 }], [{ 'x': 2 }, { 'x': 1 }], 'x'); - * // => [{ 'x': 1 }, { 'x': 2 }] - */ - var unionBy = baseRest(function(arrays) { - var iteratee = last(arrays); - if (isArrayLikeObject(iteratee)) { - iteratee = undefined; - } - return baseUniq(baseFlatten(arrays, 1, isArrayLikeObject, true), getIteratee(iteratee, 2)); - }); - - /** - * This method is like `_.union` except that it accepts `comparator` which - * is invoked to compare elements of `arrays`. Result values are chosen from - * the first array in which the value occurs. The comparator is invoked - * with two arguments: (arrVal, othVal). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Array - * @param {...Array} [arrays] The arrays to inspect. - * @param {Function} [comparator] The comparator invoked per element. - * @returns {Array} Returns the new array of combined values. - * @example - * - * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }]; - * var others = [{ 'x': 1, 'y': 1 }, { 'x': 1, 'y': 2 }]; - * - * _.unionWith(objects, others, _.isEqual); - * // => [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }, { 'x': 1, 'y': 1 }] - */ - var unionWith = baseRest(function(arrays) { - var comparator = last(arrays); - comparator = typeof comparator == 'function' ? comparator : undefined; - return baseUniq(baseFlatten(arrays, 1, isArrayLikeObject, true), undefined, comparator); - }); - - /** - * Creates a duplicate-free version of an array, using - * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) - * for equality comparisons, in which only the first occurrence of each element - * is kept. The order of result values is determined by the order they occur - * in the array. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Array - * @param {Array} array The array to inspect. - * @returns {Array} Returns the new duplicate free array. - * @example - * - * _.uniq([2, 1, 2]); - * // => [2, 1] - */ - function uniq(array) { - return (array && array.length) ? baseUniq(array) : []; - } - - /** - * This method is like `_.uniq` except that it accepts `iteratee` which is - * invoked for each element in `array` to generate the criterion by which - * uniqueness is computed. The order of result values is determined by the - * order they occur in the array. The iteratee is invoked with one argument: - * (value). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Array - * @param {Array} array The array to inspect. - * @param {Function} [iteratee=_.identity] The iteratee invoked per element. - * @returns {Array} Returns the new duplicate free array. - * @example - * - * _.uniqBy([2.1, 1.2, 2.3], Math.floor); - * // => [2.1, 1.2] - * - * // The `_.property` iteratee shorthand. - * _.uniqBy([{ 'x': 1 }, { 'x': 2 }, { 'x': 1 }], 'x'); - * // => [{ 'x': 1 }, { 'x': 2 }] - */ - function uniqBy(array, iteratee) { - return (array && array.length) ? baseUniq(array, getIteratee(iteratee, 2)) : []; - } - - /** - * This method is like `_.uniq` except that it accepts `comparator` which - * is invoked to compare elements of `array`. The order of result values is - * determined by the order they occur in the array.The comparator is invoked - * with two arguments: (arrVal, othVal). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Array - * @param {Array} array The array to inspect. - * @param {Function} [comparator] The comparator invoked per element. - * @returns {Array} Returns the new duplicate free array. - * @example - * - * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }, { 'x': 1, 'y': 2 }]; - * - * _.uniqWith(objects, _.isEqual); - * // => [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }] - */ - function uniqWith(array, comparator) { - comparator = typeof comparator == 'function' ? comparator : undefined; - return (array && array.length) ? baseUniq(array, undefined, comparator) : []; - } - - /** - * This method is like `_.zip` except that it accepts an array of grouped - * elements and creates an array regrouping the elements to their pre-zip - * configuration. - * - * @static - * @memberOf _ - * @since 1.2.0 - * @category Array - * @param {Array} array The array of grouped elements to process. - * @returns {Array} Returns the new array of regrouped elements. - * @example - * - * var zipped = _.zip(['a', 'b'], [1, 2], [true, false]); - * // => [['a', 1, true], ['b', 2, false]] - * - * _.unzip(zipped); - * // => [['a', 'b'], [1, 2], [true, false]] - */ - function unzip(array) { - if (!(array && array.length)) { - return []; - } - var length = 0; - array = arrayFilter(array, function(group) { - if (isArrayLikeObject(group)) { - length = nativeMax(group.length, length); - return true; - } - }); - return baseTimes(length, function(index) { - return arrayMap(array, baseProperty(index)); - }); - } - - /** - * This method is like `_.unzip` except that it accepts `iteratee` to specify - * how regrouped values should be combined. The iteratee is invoked with the - * elements of each group: (...group). - * - * @static - * @memberOf _ - * @since 3.8.0 - * @category Array - * @param {Array} array The array of grouped elements to process. - * @param {Function} [iteratee=_.identity] The function to combine - * regrouped values. - * @returns {Array} Returns the new array of regrouped elements. - * @example - * - * var zipped = _.zip([1, 2], [10, 20], [100, 200]); - * // => [[1, 10, 100], [2, 20, 200]] - * - * _.unzipWith(zipped, _.add); - * // => [3, 30, 300] - */ - function unzipWith(array, iteratee) { - if (!(array && array.length)) { - return []; - } - var result = unzip(array); - if (iteratee == null) { - return result; - } - return arrayMap(result, function(group) { - return apply(iteratee, undefined, group); - }); - } - - /** - * Creates an array excluding all given values using - * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) - * for equality comparisons. - * - * **Note:** Unlike `_.pull`, this method returns a new array. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Array - * @param {Array} array The array to inspect. - * @param {...*} [values] The values to exclude. - * @returns {Array} Returns the new array of filtered values. - * @see _.difference, _.xor - * @example - * - * _.without([2, 1, 2, 3], 1, 2); - * // => [3] - */ - var without = baseRest(function(array, values) { - return isArrayLikeObject(array) - ? baseDifference(array, values) - : []; - }); - - /** - * Creates an array of unique values that is the - * [symmetric difference](https://en.wikipedia.org/wiki/Symmetric_difference) - * of the given arrays. The order of result values is determined by the order - * they occur in the arrays. - * - * @static - * @memberOf _ - * @since 2.4.0 - * @category Array - * @param {...Array} [arrays] The arrays to inspect. - * @returns {Array} Returns the new array of filtered values. - * @see _.difference, _.without - * @example - * - * _.xor([2, 1], [2, 3]); - * // => [1, 3] - */ - var xor = baseRest(function(arrays) { - return baseXor(arrayFilter(arrays, isArrayLikeObject)); - }); - - /** - * This method is like `_.xor` except that it accepts `iteratee` which is - * invoked for each element of each `arrays` to generate the criterion by - * which by which they're compared. The order of result values is determined - * by the order they occur in the arrays. The iteratee is invoked with one - * argument: (value). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Array - * @param {...Array} [arrays] The arrays to inspect. - * @param {Function} [iteratee=_.identity] The iteratee invoked per element. - * @returns {Array} Returns the new array of filtered values. - * @example - * - * _.xorBy([2.1, 1.2], [2.3, 3.4], Math.floor); - * // => [1.2, 3.4] - * - * // The `_.property` iteratee shorthand. - * _.xorBy([{ 'x': 1 }], [{ 'x': 2 }, { 'x': 1 }], 'x'); - * // => [{ 'x': 2 }] - */ - var xorBy = baseRest(function(arrays) { - var iteratee = last(arrays); - if (isArrayLikeObject(iteratee)) { - iteratee = undefined; - } - return baseXor(arrayFilter(arrays, isArrayLikeObject), getIteratee(iteratee, 2)); - }); - - /** - * This method is like `_.xor` except that it accepts `comparator` which is - * invoked to compare elements of `arrays`. The order of result values is - * determined by the order they occur in the arrays. The comparator is invoked - * with two arguments: (arrVal, othVal). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Array - * @param {...Array} [arrays] The arrays to inspect. - * @param {Function} [comparator] The comparator invoked per element. - * @returns {Array} Returns the new array of filtered values. - * @example - * - * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }]; - * var others = [{ 'x': 1, 'y': 1 }, { 'x': 1, 'y': 2 }]; - * - * _.xorWith(objects, others, _.isEqual); - * // => [{ 'x': 2, 'y': 1 }, { 'x': 1, 'y': 1 }] - */ - var xorWith = baseRest(function(arrays) { - var comparator = last(arrays); - comparator = typeof comparator == 'function' ? comparator : undefined; - return baseXor(arrayFilter(arrays, isArrayLikeObject), undefined, comparator); - }); - - /** - * Creates an array of grouped elements, the first of which contains the - * first elements of the given arrays, the second of which contains the - * second elements of the given arrays, and so on. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Array - * @param {...Array} [arrays] The arrays to process. - * @returns {Array} Returns the new array of grouped elements. - * @example - * - * _.zip(['a', 'b'], [1, 2], [true, false]); - * // => [['a', 1, true], ['b', 2, false]] - */ - var zip = baseRest(unzip); - - /** - * This method is like `_.fromPairs` except that it accepts two arrays, - * one of property identifiers and one of corresponding values. - * - * @static - * @memberOf _ - * @since 0.4.0 - * @category Array - * @param {Array} [props=[]] The property identifiers. - * @param {Array} [values=[]] The property values. - * @returns {Object} Returns the new object. - * @example - * - * _.zipObject(['a', 'b'], [1, 2]); - * // => { 'a': 1, 'b': 2 } - */ - function zipObject(props, values) { - return baseZipObject(props || [], values || [], assignValue); - } - - /** - * This method is like `_.zipObject` except that it supports property paths. - * - * @static - * @memberOf _ - * @since 4.1.0 - * @category Array - * @param {Array} [props=[]] The property identifiers. - * @param {Array} [values=[]] The property values. - * @returns {Object} Returns the new object. - * @example - * - * _.zipObjectDeep(['a.b[0].c', 'a.b[1].d'], [1, 2]); - * // => { 'a': { 'b': [{ 'c': 1 }, { 'd': 2 }] } } - */ - function zipObjectDeep(props, values) { - return baseZipObject(props || [], values || [], baseSet); - } - - /** - * This method is like `_.zip` except that it accepts `iteratee` to specify - * how grouped values should be combined. The iteratee is invoked with the - * elements of each group: (...group). - * - * @static - * @memberOf _ - * @since 3.8.0 - * @category Array - * @param {...Array} [arrays] The arrays to process. - * @param {Function} [iteratee=_.identity] The function to combine - * grouped values. - * @returns {Array} Returns the new array of grouped elements. - * @example - * - * _.zipWith([1, 2], [10, 20], [100, 200], function(a, b, c) { - * return a + b + c; - * }); - * // => [111, 222] - */ - var zipWith = baseRest(function(arrays) { - var length = arrays.length, - iteratee = length > 1 ? arrays[length - 1] : undefined; - - iteratee = typeof iteratee == 'function' ? (arrays.pop(), iteratee) : undefined; - return unzipWith(arrays, iteratee); - }); - - /*------------------------------------------------------------------------*/ - - /** - * Creates a `lodash` wrapper instance that wraps `value` with explicit method - * chain sequences enabled. The result of such sequences must be unwrapped - * with `_#value`. - * - * @static - * @memberOf _ - * @since 1.3.0 - * @category Seq - * @param {*} value The value to wrap. - * @returns {Object} Returns the new `lodash` wrapper instance. - * @example - * - * var users = [ - * { 'user': 'barney', 'age': 36 }, - * { 'user': 'fred', 'age': 40 }, - * { 'user': 'pebbles', 'age': 1 } - * ]; - * - * var youngest = _ - * .chain(users) - * .sortBy('age') - * .map(function(o) { - * return o.user + ' is ' + o.age; - * }) - * .head() - * .value(); - * // => 'pebbles is 1' - */ - function chain(value) { - var result = lodash(value); - result.__chain__ = true; - return result; - } - - /** - * This method invokes `interceptor` and returns `value`. The interceptor - * is invoked with one argument; (value). The purpose of this method is to - * "tap into" a method chain sequence in order to modify intermediate results. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Seq - * @param {*} value The value to provide to `interceptor`. - * @param {Function} interceptor The function to invoke. - * @returns {*} Returns `value`. - * @example - * - * _([1, 2, 3]) - * .tap(function(array) { - * // Mutate input array. - * array.pop(); - * }) - * .reverse() - * .value(); - * // => [2, 1] - */ - function tap(value, interceptor) { - interceptor(value); - return value; - } - - /** - * This method is like `_.tap` except that it returns the result of `interceptor`. - * The purpose of this method is to "pass thru" values replacing intermediate - * results in a method chain sequence. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category Seq - * @param {*} value The value to provide to `interceptor`. - * @param {Function} interceptor The function to invoke. - * @returns {*} Returns the result of `interceptor`. - * @example - * - * _(' abc ') - * .chain() - * .trim() - * .thru(function(value) { - * return [value]; - * }) - * .value(); - * // => ['abc'] - */ - function thru(value, interceptor) { - return interceptor(value); - } - - /** - * This method is the wrapper version of `_.at`. - * - * @name at - * @memberOf _ - * @since 1.0.0 - * @category Seq - * @param {...(string|string[])} [paths] The property paths to pick. - * @returns {Object} Returns the new `lodash` wrapper instance. - * @example - * - * var object = { 'a': [{ 'b': { 'c': 3 } }, 4] }; - * - * _(object).at(['a[0].b.c', 'a[1]']).value(); - * // => [3, 4] - */ - var wrapperAt = flatRest(function(paths) { - var length = paths.length, - start = length ? paths[0] : 0, - value = this.__wrapped__, - interceptor = function(object) { return baseAt(object, paths); }; - - if (length > 1 || this.__actions__.length || - !(value instanceof LazyWrapper) || !isIndex(start)) { - return this.thru(interceptor); - } - value = value.slice(start, +start + (length ? 1 : 0)); - value.__actions__.push({ - 'func': thru, - 'args': [interceptor], - 'thisArg': undefined - }); - return new LodashWrapper(value, this.__chain__).thru(function(array) { - if (length && !array.length) { - array.push(undefined); - } - return array; - }); - }); - - /** - * Creates a `lodash` wrapper instance with explicit method chain sequences enabled. - * - * @name chain - * @memberOf _ - * @since 0.1.0 - * @category Seq - * @returns {Object} Returns the new `lodash` wrapper instance. - * @example - * - * var users = [ - * { 'user': 'barney', 'age': 36 }, - * { 'user': 'fred', 'age': 40 } - * ]; - * - * // A sequence without explicit chaining. - * _(users).head(); - * // => { 'user': 'barney', 'age': 36 } - * - * // A sequence with explicit chaining. - * _(users) - * .chain() - * .head() - * .pick('user') - * .value(); - * // => { 'user': 'barney' } - */ - function wrapperChain() { - return chain(this); - } - - /** - * Executes the chain sequence and returns the wrapped result. - * - * @name commit - * @memberOf _ - * @since 3.2.0 - * @category Seq - * @returns {Object} Returns the new `lodash` wrapper instance. - * @example - * - * var array = [1, 2]; - * var wrapped = _(array).push(3); - * - * console.log(array); - * // => [1, 2] - * - * wrapped = wrapped.commit(); - * console.log(array); - * // => [1, 2, 3] - * - * wrapped.last(); - * // => 3 - * - * console.log(array); - * // => [1, 2, 3] - */ - function wrapperCommit() { - return new LodashWrapper(this.value(), this.__chain__); - } - - /** - * Gets the next value on a wrapped object following the - * [iterator protocol](https://mdn.io/iteration_protocols#iterator). - * - * @name next - * @memberOf _ - * @since 4.0.0 - * @category Seq - * @returns {Object} Returns the next iterator value. - * @example - * - * var wrapped = _([1, 2]); - * - * wrapped.next(); - * // => { 'done': false, 'value': 1 } - * - * wrapped.next(); - * // => { 'done': false, 'value': 2 } - * - * wrapped.next(); - * // => { 'done': true, 'value': undefined } - */ - function wrapperNext() { - if (this.__values__ === undefined) { - this.__values__ = toArray(this.value()); - } - var done = this.__index__ >= this.__values__.length, - value = done ? undefined : this.__values__[this.__index__++]; - - return { 'done': done, 'value': value }; - } - - /** - * Enables the wrapper to be iterable. - * - * @name Symbol.iterator - * @memberOf _ - * @since 4.0.0 - * @category Seq - * @returns {Object} Returns the wrapper object. - * @example - * - * var wrapped = _([1, 2]); - * - * wrapped[Symbol.iterator]() === wrapped; - * // => true - * - * Array.from(wrapped); - * // => [1, 2] - */ - function wrapperToIterator() { - return this; - } - - /** - * Creates a clone of the chain sequence planting `value` as the wrapped value. - * - * @name plant - * @memberOf _ - * @since 3.2.0 - * @category Seq - * @param {*} value The value to plant. - * @returns {Object} Returns the new `lodash` wrapper instance. - * @example - * - * function square(n) { - * return n * n; - * } - * - * var wrapped = _([1, 2]).map(square); - * var other = wrapped.plant([3, 4]); - * - * other.value(); - * // => [9, 16] - * - * wrapped.value(); - * // => [1, 4] - */ - function wrapperPlant(value) { - var result, - parent = this; - - while (parent instanceof baseLodash) { - var clone = wrapperClone(parent); - clone.__index__ = 0; - clone.__values__ = undefined; - if (result) { - previous.__wrapped__ = clone; - } else { - result = clone; - } - var previous = clone; - parent = parent.__wrapped__; - } - previous.__wrapped__ = value; - return result; - } - - /** - * This method is the wrapper version of `_.reverse`. - * - * **Note:** This method mutates the wrapped array. - * - * @name reverse - * @memberOf _ - * @since 0.1.0 - * @category Seq - * @returns {Object} Returns the new `lodash` wrapper instance. - * @example - * - * var array = [1, 2, 3]; - * - * _(array).reverse().value() - * // => [3, 2, 1] - * - * console.log(array); - * // => [3, 2, 1] - */ - function wrapperReverse() { - var value = this.__wrapped__; - if (value instanceof LazyWrapper) { - var wrapped = value; - if (this.__actions__.length) { - wrapped = new LazyWrapper(this); - } - wrapped = wrapped.reverse(); - wrapped.__actions__.push({ - 'func': thru, - 'args': [reverse], - 'thisArg': undefined - }); - return new LodashWrapper(wrapped, this.__chain__); - } - return this.thru(reverse); - } - - /** - * Executes the chain sequence to resolve the unwrapped value. - * - * @name value - * @memberOf _ - * @since 0.1.0 - * @alias toJSON, valueOf - * @category Seq - * @returns {*} Returns the resolved unwrapped value. - * @example - * - * _([1, 2, 3]).value(); - * // => [1, 2, 3] - */ - function wrapperValue() { - return baseWrapperValue(this.__wrapped__, this.__actions__); - } - - /*------------------------------------------------------------------------*/ - - /** - * Creates an object composed of keys generated from the results of running - * each element of `collection` thru `iteratee`. The corresponding value of - * each key is the number of times the key was returned by `iteratee`. The - * iteratee is invoked with one argument: (value). - * - * @static - * @memberOf _ - * @since 0.5.0 - * @category Collection - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} [iteratee=_.identity] The iteratee to transform keys. - * @returns {Object} Returns the composed aggregate object. - * @example - * - * _.countBy([6.1, 4.2, 6.3], Math.floor); - * // => { '4': 1, '6': 2 } - * - * // The `_.property` iteratee shorthand. - * _.countBy(['one', 'two', 'three'], 'length'); - * // => { '3': 2, '5': 1 } - */ - var countBy = createAggregator(function(result, value, key) { - if (hasOwnProperty.call(result, key)) { - ++result[key]; - } else { - baseAssignValue(result, key, 1); - } - }); - - /** - * Checks if `predicate` returns truthy for **all** elements of `collection`. - * Iteration is stopped once `predicate` returns falsey. The predicate is - * invoked with three arguments: (value, index|key, collection). - * - * **Note:** This method returns `true` for - * [empty collections](https://en.wikipedia.org/wiki/Empty_set) because - * [everything is true](https://en.wikipedia.org/wiki/Vacuous_truth) of - * elements of empty collections. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Collection - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} [predicate=_.identity] The function invoked per iteration. - * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. - * @returns {boolean} Returns `true` if all elements pass the predicate check, - * else `false`. - * @example - * - * _.every([true, 1, null, 'yes'], Boolean); - * // => false - * - * var users = [ - * { 'user': 'barney', 'age': 36, 'active': false }, - * { 'user': 'fred', 'age': 40, 'active': false } - * ]; - * - * // The `_.matches` iteratee shorthand. - * _.every(users, { 'user': 'barney', 'active': false }); - * // => false - * - * // The `_.matchesProperty` iteratee shorthand. - * _.every(users, ['active', false]); - * // => true - * - * // The `_.property` iteratee shorthand. - * _.every(users, 'active'); - * // => false - */ - function every(collection, predicate, guard) { - var func = isArray(collection) ? arrayEvery : baseEvery; - if (guard && isIterateeCall(collection, predicate, guard)) { - predicate = undefined; - } - return func(collection, getIteratee(predicate, 3)); - } - - /** - * Iterates over elements of `collection`, returning an array of all elements - * `predicate` returns truthy for. The predicate is invoked with three - * arguments: (value, index|key, collection). - * - * **Note:** Unlike `_.remove`, this method returns a new array. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Collection - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} [predicate=_.identity] The function invoked per iteration. - * @returns {Array} Returns the new filtered array. - * @see _.reject - * @example - * - * var users = [ - * { 'user': 'barney', 'age': 36, 'active': true }, - * { 'user': 'fred', 'age': 40, 'active': false } - * ]; - * - * _.filter(users, function(o) { return !o.active; }); - * // => objects for ['fred'] - * - * // The `_.matches` iteratee shorthand. - * _.filter(users, { 'age': 36, 'active': true }); - * // => objects for ['barney'] - * - * // The `_.matchesProperty` iteratee shorthand. - * _.filter(users, ['active', false]); - * // => objects for ['fred'] - * - * // The `_.property` iteratee shorthand. - * _.filter(users, 'active'); - * // => objects for ['barney'] - */ - function filter(collection, predicate) { - var func = isArray(collection) ? arrayFilter : baseFilter; - return func(collection, getIteratee(predicate, 3)); - } - - /** - * Iterates over elements of `collection`, returning the first element - * `predicate` returns truthy for. The predicate is invoked with three - * arguments: (value, index|key, collection). - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Collection - * @param {Array|Object} collection The collection to inspect. - * @param {Function} [predicate=_.identity] The function invoked per iteration. - * @param {number} [fromIndex=0] The index to search from. - * @returns {*} Returns the matched element, else `undefined`. - * @example - * - * var users = [ - * { 'user': 'barney', 'age': 36, 'active': true }, - * { 'user': 'fred', 'age': 40, 'active': false }, - * { 'user': 'pebbles', 'age': 1, 'active': true } - * ]; - * - * _.find(users, function(o) { return o.age < 40; }); - * // => object for 'barney' - * - * // The `_.matches` iteratee shorthand. - * _.find(users, { 'age': 1, 'active': true }); - * // => object for 'pebbles' - * - * // The `_.matchesProperty` iteratee shorthand. - * _.find(users, ['active', false]); - * // => object for 'fred' - * - * // The `_.property` iteratee shorthand. - * _.find(users, 'active'); - * // => object for 'barney' - */ - var find = createFind(findIndex); - - /** - * This method is like `_.find` except that it iterates over elements of - * `collection` from right to left. - * - * @static - * @memberOf _ - * @since 2.0.0 - * @category Collection - * @param {Array|Object} collection The collection to inspect. - * @param {Function} [predicate=_.identity] The function invoked per iteration. - * @param {number} [fromIndex=collection.length-1] The index to search from. - * @returns {*} Returns the matched element, else `undefined`. - * @example - * - * _.findLast([1, 2, 3, 4], function(n) { - * return n % 2 == 1; - * }); - * // => 3 - */ - var findLast = createFind(findLastIndex); - - /** - * Creates a flattened array of values by running each element in `collection` - * thru `iteratee` and flattening the mapped results. The iteratee is invoked - * with three arguments: (value, index|key, collection). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Collection - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} [iteratee=_.identity] The function invoked per iteration. - * @returns {Array} Returns the new flattened array. - * @example - * - * function duplicate(n) { - * return [n, n]; - * } - * - * _.flatMap([1, 2], duplicate); - * // => [1, 1, 2, 2] - */ - function flatMap(collection, iteratee) { - return baseFlatten(map(collection, iteratee), 1); - } - - /** - * This method is like `_.flatMap` except that it recursively flattens the - * mapped results. - * - * @static - * @memberOf _ - * @since 4.7.0 - * @category Collection - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} [iteratee=_.identity] The function invoked per iteration. - * @returns {Array} Returns the new flattened array. - * @example - * - * function duplicate(n) { - * return [[[n, n]]]; - * } - * - * _.flatMapDeep([1, 2], duplicate); - * // => [1, 1, 2, 2] - */ - function flatMapDeep(collection, iteratee) { - return baseFlatten(map(collection, iteratee), INFINITY); - } - - /** - * This method is like `_.flatMap` except that it recursively flattens the - * mapped results up to `depth` times. - * - * @static - * @memberOf _ - * @since 4.7.0 - * @category Collection - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} [iteratee=_.identity] The function invoked per iteration. - * @param {number} [depth=1] The maximum recursion depth. - * @returns {Array} Returns the new flattened array. - * @example - * - * function duplicate(n) { - * return [[[n, n]]]; - * } - * - * _.flatMapDepth([1, 2], duplicate, 2); - * // => [[1, 1], [2, 2]] - */ - function flatMapDepth(collection, iteratee, depth) { - depth = depth === undefined ? 1 : toInteger(depth); - return baseFlatten(map(collection, iteratee), depth); - } - - /** - * Iterates over elements of `collection` and invokes `iteratee` for each element. - * The iteratee is invoked with three arguments: (value, index|key, collection). - * Iteratee functions may exit iteration early by explicitly returning `false`. - * - * **Note:** As with other "Collections" methods, objects with a "length" - * property are iterated like arrays. To avoid this behavior use `_.forIn` - * or `_.forOwn` for object iteration. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @alias each - * @category Collection - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} [iteratee=_.identity] The function invoked per iteration. - * @returns {Array|Object} Returns `collection`. - * @see _.forEachRight - * @example - * - * _.forEach([1, 2], function(value) { - * console.log(value); - * }); - * // => Logs `1` then `2`. - * - * _.forEach({ 'a': 1, 'b': 2 }, function(value, key) { - * console.log(key); - * }); - * // => Logs 'a' then 'b' (iteration order is not guaranteed). - */ - function forEach(collection, iteratee) { - var func = isArray(collection) ? arrayEach : baseEach; - return func(collection, getIteratee(iteratee, 3)); - } - - /** - * This method is like `_.forEach` except that it iterates over elements of - * `collection` from right to left. - * - * @static - * @memberOf _ - * @since 2.0.0 - * @alias eachRight - * @category Collection - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} [iteratee=_.identity] The function invoked per iteration. - * @returns {Array|Object} Returns `collection`. - * @see _.forEach - * @example - * - * _.forEachRight([1, 2], function(value) { - * console.log(value); - * }); - * // => Logs `2` then `1`. - */ - function forEachRight(collection, iteratee) { - var func = isArray(collection) ? arrayEachRight : baseEachRight; - return func(collection, getIteratee(iteratee, 3)); - } - - /** - * Creates an object composed of keys generated from the results of running - * each element of `collection` thru `iteratee`. The order of grouped values - * is determined by the order they occur in `collection`. The corresponding - * value of each key is an array of elements responsible for generating the - * key. The iteratee is invoked with one argument: (value). - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Collection - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} [iteratee=_.identity] The iteratee to transform keys. - * @returns {Object} Returns the composed aggregate object. - * @example - * - * _.groupBy([6.1, 4.2, 6.3], Math.floor); - * // => { '4': [4.2], '6': [6.1, 6.3] } - * - * // The `_.property` iteratee shorthand. - * _.groupBy(['one', 'two', 'three'], 'length'); - * // => { '3': ['one', 'two'], '5': ['three'] } - */ - var groupBy = createAggregator(function(result, value, key) { - if (hasOwnProperty.call(result, key)) { - result[key].push(value); - } else { - baseAssignValue(result, key, [value]); - } - }); - - /** - * Checks if `value` is in `collection`. If `collection` is a string, it's - * checked for a substring of `value`, otherwise - * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) - * is used for equality comparisons. If `fromIndex` is negative, it's used as - * the offset from the end of `collection`. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Collection - * @param {Array|Object|string} collection The collection to inspect. - * @param {*} value The value to search for. - * @param {number} [fromIndex=0] The index to search from. - * @param- {Object} [guard] Enables use as an iteratee for methods like `_.reduce`. - * @returns {boolean} Returns `true` if `value` is found, else `false`. - * @example - * - * _.includes([1, 2, 3], 1); - * // => true - * - * _.includes([1, 2, 3], 1, 2); - * // => false - * - * _.includes({ 'a': 1, 'b': 2 }, 1); - * // => true - * - * _.includes('abcd', 'bc'); - * // => true - */ - function includes(collection, value, fromIndex, guard) { - collection = isArrayLike(collection) ? collection : values(collection); - fromIndex = (fromIndex && !guard) ? toInteger(fromIndex) : 0; - - var length = collection.length; - if (fromIndex < 0) { - fromIndex = nativeMax(length + fromIndex, 0); - } - return isString(collection) - ? (fromIndex <= length && collection.indexOf(value, fromIndex) > -1) - : (!!length && baseIndexOf(collection, value, fromIndex) > -1); - } - - /** - * Invokes the method at `path` of each element in `collection`, returning - * an array of the results of each invoked method. Any additional arguments - * are provided to each invoked method. If `path` is a function, it's invoked - * for, and `this` bound to, each element in `collection`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Collection - * @param {Array|Object} collection The collection to iterate over. - * @param {Array|Function|string} path The path of the method to invoke or - * the function invoked per iteration. - * @param {...*} [args] The arguments to invoke each method with. - * @returns {Array} Returns the array of results. - * @example - * - * _.invokeMap([[5, 1, 7], [3, 2, 1]], 'sort'); - * // => [[1, 5, 7], [1, 2, 3]] - * - * _.invokeMap([123, 456], String.prototype.split, ''); - * // => [['1', '2', '3'], ['4', '5', '6']] - */ - var invokeMap = baseRest(function(collection, path, args) { - var index = -1, - isFunc = typeof path == 'function', - result = isArrayLike(collection) ? Array(collection.length) : []; - - baseEach(collection, function(value) { - result[++index] = isFunc ? apply(path, value, args) : baseInvoke(value, path, args); - }); - return result; - }); - - /** - * Creates an object composed of keys generated from the results of running - * each element of `collection` thru `iteratee`. The corresponding value of - * each key is the last element responsible for generating the key. The - * iteratee is invoked with one argument: (value). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Collection - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} [iteratee=_.identity] The iteratee to transform keys. - * @returns {Object} Returns the composed aggregate object. - * @example - * - * var array = [ - * { 'dir': 'left', 'code': 97 }, - * { 'dir': 'right', 'code': 100 } - * ]; - * - * _.keyBy(array, function(o) { - * return String.fromCharCode(o.code); - * }); - * // => { 'a': { 'dir': 'left', 'code': 97 }, 'd': { 'dir': 'right', 'code': 100 } } - * - * _.keyBy(array, 'dir'); - * // => { 'left': { 'dir': 'left', 'code': 97 }, 'right': { 'dir': 'right', 'code': 100 } } - */ - var keyBy = createAggregator(function(result, value, key) { - baseAssignValue(result, key, value); - }); - - /** - * Creates an array of values by running each element in `collection` thru - * `iteratee`. The iteratee is invoked with three arguments: - * (value, index|key, collection). - * - * Many lodash methods are guarded to work as iteratees for methods like - * `_.every`, `_.filter`, `_.map`, `_.mapValues`, `_.reject`, and `_.some`. - * - * The guarded methods are: - * `ary`, `chunk`, `curry`, `curryRight`, `drop`, `dropRight`, `every`, - * `fill`, `invert`, `parseInt`, `random`, `range`, `rangeRight`, `repeat`, - * `sampleSize`, `slice`, `some`, `sortBy`, `split`, `take`, `takeRight`, - * `template`, `trim`, `trimEnd`, `trimStart`, and `words` - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Collection - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} [iteratee=_.identity] The function invoked per iteration. - * @returns {Array} Returns the new mapped array. - * @example - * - * function square(n) { - * return n * n; - * } - * - * _.map([4, 8], square); - * // => [16, 64] - * - * _.map({ 'a': 4, 'b': 8 }, square); - * // => [16, 64] (iteration order is not guaranteed) - * - * var users = [ - * { 'user': 'barney' }, - * { 'user': 'fred' } - * ]; - * - * // The `_.property` iteratee shorthand. - * _.map(users, 'user'); - * // => ['barney', 'fred'] - */ - function map(collection, iteratee) { - var func = isArray(collection) ? arrayMap : baseMap; - return func(collection, getIteratee(iteratee, 3)); - } - - /** - * This method is like `_.sortBy` except that it allows specifying the sort - * orders of the iteratees to sort by. If `orders` is unspecified, all values - * are sorted in ascending order. Otherwise, specify an order of "desc" for - * descending or "asc" for ascending sort order of corresponding values. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Collection - * @param {Array|Object} collection The collection to iterate over. - * @param {Array[]|Function[]|Object[]|string[]} [iteratees=[_.identity]] - * The iteratees to sort by. - * @param {string[]} [orders] The sort orders of `iteratees`. - * @param- {Object} [guard] Enables use as an iteratee for methods like `_.reduce`. - * @returns {Array} Returns the new sorted array. - * @example - * - * var users = [ - * { 'user': 'fred', 'age': 48 }, - * { 'user': 'barney', 'age': 34 }, - * { 'user': 'fred', 'age': 40 }, - * { 'user': 'barney', 'age': 36 } - * ]; - * - * // Sort by `user` in ascending order and by `age` in descending order. - * _.orderBy(users, ['user', 'age'], ['asc', 'desc']); - * // => objects for [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 40]] - */ - function orderBy(collection, iteratees, orders, guard) { - if (collection == null) { - return []; - } - if (!isArray(iteratees)) { - iteratees = iteratees == null ? [] : [iteratees]; - } - orders = guard ? undefined : orders; - if (!isArray(orders)) { - orders = orders == null ? [] : [orders]; - } - return baseOrderBy(collection, iteratees, orders); - } - - /** - * Creates an array of elements split into two groups, the first of which - * contains elements `predicate` returns truthy for, the second of which - * contains elements `predicate` returns falsey for. The predicate is - * invoked with one argument: (value). - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category Collection - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} [predicate=_.identity] The function invoked per iteration. - * @returns {Array} Returns the array of grouped elements. - * @example - * - * var users = [ - * { 'user': 'barney', 'age': 36, 'active': false }, - * { 'user': 'fred', 'age': 40, 'active': true }, - * { 'user': 'pebbles', 'age': 1, 'active': false } - * ]; - * - * _.partition(users, function(o) { return o.active; }); - * // => objects for [['fred'], ['barney', 'pebbles']] - * - * // The `_.matches` iteratee shorthand. - * _.partition(users, { 'age': 1, 'active': false }); - * // => objects for [['pebbles'], ['barney', 'fred']] - * - * // The `_.matchesProperty` iteratee shorthand. - * _.partition(users, ['active', false]); - * // => objects for [['barney', 'pebbles'], ['fred']] - * - * // The `_.property` iteratee shorthand. - * _.partition(users, 'active'); - * // => objects for [['fred'], ['barney', 'pebbles']] - */ - var partition = createAggregator(function(result, value, key) { - result[key ? 0 : 1].push(value); - }, function() { return [[], []]; }); - - /** - * Reduces `collection` to a value which is the accumulated result of running - * each element in `collection` thru `iteratee`, where each successive - * invocation is supplied the return value of the previous. If `accumulator` - * is not given, the first element of `collection` is used as the initial - * value. The iteratee is invoked with four arguments: - * (accumulator, value, index|key, collection). - * - * Many lodash methods are guarded to work as iteratees for methods like - * `_.reduce`, `_.reduceRight`, and `_.transform`. - * - * The guarded methods are: - * `assign`, `defaults`, `defaultsDeep`, `includes`, `merge`, `orderBy`, - * and `sortBy` - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Collection - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} [iteratee=_.identity] The function invoked per iteration. - * @param {*} [accumulator] The initial value. - * @returns {*} Returns the accumulated value. - * @see _.reduceRight - * @example - * - * _.reduce([1, 2], function(sum, n) { - * return sum + n; - * }, 0); - * // => 3 - * - * _.reduce({ 'a': 1, 'b': 2, 'c': 1 }, function(result, value, key) { - * (result[value] || (result[value] = [])).push(key); - * return result; - * }, {}); - * // => { '1': ['a', 'c'], '2': ['b'] } (iteration order is not guaranteed) - */ - function reduce(collection, iteratee, accumulator) { - var func = isArray(collection) ? arrayReduce : baseReduce, - initAccum = arguments.length < 3; - - return func(collection, getIteratee(iteratee, 4), accumulator, initAccum, baseEach); - } - - /** - * This method is like `_.reduce` except that it iterates over elements of - * `collection` from right to left. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Collection - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} [iteratee=_.identity] The function invoked per iteration. - * @param {*} [accumulator] The initial value. - * @returns {*} Returns the accumulated value. - * @see _.reduce - * @example - * - * var array = [[0, 1], [2, 3], [4, 5]]; - * - * _.reduceRight(array, function(flattened, other) { - * return flattened.concat(other); - * }, []); - * // => [4, 5, 2, 3, 0, 1] - */ - function reduceRight(collection, iteratee, accumulator) { - var func = isArray(collection) ? arrayReduceRight : baseReduce, - initAccum = arguments.length < 3; - - return func(collection, getIteratee(iteratee, 4), accumulator, initAccum, baseEachRight); - } - - /** - * The opposite of `_.filter`; this method returns the elements of `collection` - * that `predicate` does **not** return truthy for. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Collection - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} [predicate=_.identity] The function invoked per iteration. - * @returns {Array} Returns the new filtered array. - * @see _.filter - * @example - * - * var users = [ - * { 'user': 'barney', 'age': 36, 'active': false }, - * { 'user': 'fred', 'age': 40, 'active': true } - * ]; - * - * _.reject(users, function(o) { return !o.active; }); - * // => objects for ['fred'] - * - * // The `_.matches` iteratee shorthand. - * _.reject(users, { 'age': 40, 'active': true }); - * // => objects for ['barney'] - * - * // The `_.matchesProperty` iteratee shorthand. - * _.reject(users, ['active', false]); - * // => objects for ['fred'] - * - * // The `_.property` iteratee shorthand. - * _.reject(users, 'active'); - * // => objects for ['barney'] - */ - function reject(collection, predicate) { - var func = isArray(collection) ? arrayFilter : baseFilter; - return func(collection, negate(getIteratee(predicate, 3))); - } - - /** - * Gets a random element from `collection`. - * - * @static - * @memberOf _ - * @since 2.0.0 - * @category Collection - * @param {Array|Object} collection The collection to sample. - * @returns {*} Returns the random element. - * @example - * - * _.sample([1, 2, 3, 4]); - * // => 2 - */ - function sample(collection) { - var func = isArray(collection) ? arraySample : baseSample; - return func(collection); - } - - /** - * Gets `n` random elements at unique keys from `collection` up to the - * size of `collection`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Collection - * @param {Array|Object} collection The collection to sample. - * @param {number} [n=1] The number of elements to sample. - * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. - * @returns {Array} Returns the random elements. - * @example - * - * _.sampleSize([1, 2, 3], 2); - * // => [3, 1] - * - * _.sampleSize([1, 2, 3], 4); - * // => [2, 3, 1] - */ - function sampleSize(collection, n, guard) { - if ((guard ? isIterateeCall(collection, n, guard) : n === undefined)) { - n = 1; - } else { - n = toInteger(n); - } - var func = isArray(collection) ? arraySampleSize : baseSampleSize; - return func(collection, n); - } - - /** - * Creates an array of shuffled values, using a version of the - * [Fisher-Yates shuffle](https://en.wikipedia.org/wiki/Fisher-Yates_shuffle). - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Collection - * @param {Array|Object} collection The collection to shuffle. - * @returns {Array} Returns the new shuffled array. - * @example - * - * _.shuffle([1, 2, 3, 4]); - * // => [4, 1, 3, 2] - */ - function shuffle(collection) { - var func = isArray(collection) ? arrayShuffle : baseShuffle; - return func(collection); - } - - /** - * Gets the size of `collection` by returning its length for array-like - * values or the number of own enumerable string keyed properties for objects. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Collection - * @param {Array|Object|string} collection The collection to inspect. - * @returns {number} Returns the collection size. - * @example - * - * _.size([1, 2, 3]); - * // => 3 - * - * _.size({ 'a': 1, 'b': 2 }); - * // => 2 - * - * _.size('pebbles'); - * // => 7 - */ - function size(collection) { - if (collection == null) { - return 0; - } - if (isArrayLike(collection)) { - return isString(collection) ? stringSize(collection) : collection.length; - } - var tag = getTag(collection); - if (tag == mapTag || tag == setTag) { - return collection.size; - } - return baseKeys(collection).length; - } - - /** - * Checks if `predicate` returns truthy for **any** element of `collection`. - * Iteration is stopped once `predicate` returns truthy. The predicate is - * invoked with three arguments: (value, index|key, collection). - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Collection - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} [predicate=_.identity] The function invoked per iteration. - * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. - * @returns {boolean} Returns `true` if any element passes the predicate check, - * else `false`. - * @example - * - * _.some([null, 0, 'yes', false], Boolean); - * // => true - * - * var users = [ - * { 'user': 'barney', 'active': true }, - * { 'user': 'fred', 'active': false } - * ]; - * - * // The `_.matches` iteratee shorthand. - * _.some(users, { 'user': 'barney', 'active': false }); - * // => false - * - * // The `_.matchesProperty` iteratee shorthand. - * _.some(users, ['active', false]); - * // => true - * - * // The `_.property` iteratee shorthand. - * _.some(users, 'active'); - * // => true - */ - function some(collection, predicate, guard) { - var func = isArray(collection) ? arraySome : baseSome; - if (guard && isIterateeCall(collection, predicate, guard)) { - predicate = undefined; - } - return func(collection, getIteratee(predicate, 3)); - } - - /** - * Creates an array of elements, sorted in ascending order by the results of - * running each element in a collection thru each iteratee. This method - * performs a stable sort, that is, it preserves the original sort order of - * equal elements. The iteratees are invoked with one argument: (value). - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Collection - * @param {Array|Object} collection The collection to iterate over. - * @param {...(Function|Function[])} [iteratees=[_.identity]] - * The iteratees to sort by. - * @returns {Array} Returns the new sorted array. - * @example - * - * var users = [ - * { 'user': 'fred', 'age': 48 }, - * { 'user': 'barney', 'age': 36 }, - * { 'user': 'fred', 'age': 40 }, - * { 'user': 'barney', 'age': 34 } - * ]; - * - * _.sortBy(users, [function(o) { return o.user; }]); - * // => objects for [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 40]] - * - * _.sortBy(users, ['user', 'age']); - * // => objects for [['barney', 34], ['barney', 36], ['fred', 40], ['fred', 48]] - */ - var sortBy = baseRest(function(collection, iteratees) { - if (collection == null) { - return []; - } - var length = iteratees.length; - if (length > 1 && isIterateeCall(collection, iteratees[0], iteratees[1])) { - iteratees = []; - } else if (length > 2 && isIterateeCall(iteratees[0], iteratees[1], iteratees[2])) { - iteratees = [iteratees[0]]; - } - return baseOrderBy(collection, baseFlatten(iteratees, 1), []); - }); - - /*------------------------------------------------------------------------*/ - - /** - * Gets the timestamp of the number of milliseconds that have elapsed since - * the Unix epoch (1 January 1970 00:00:00 UTC). - * - * @static - * @memberOf _ - * @since 2.4.0 - * @category Date - * @returns {number} Returns the timestamp. - * @example - * - * _.defer(function(stamp) { - * console.log(_.now() - stamp); - * }, _.now()); - * // => Logs the number of milliseconds it took for the deferred invocation. - */ - var now = ctxNow || function() { - return root.Date.now(); - }; - - /*------------------------------------------------------------------------*/ - - /** - * The opposite of `_.before`; this method creates a function that invokes - * `func` once it's called `n` or more times. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Function - * @param {number} n The number of calls before `func` is invoked. - * @param {Function} func The function to restrict. - * @returns {Function} Returns the new restricted function. - * @example - * - * var saves = ['profile', 'settings']; - * - * var done = _.after(saves.length, function() { - * console.log('done saving!'); - * }); - * - * _.forEach(saves, function(type) { - * asyncSave({ 'type': type, 'complete': done }); - * }); - * // => Logs 'done saving!' after the two async saves have completed. - */ - function after(n, func) { - if (typeof func != 'function') { - throw new TypeError(FUNC_ERROR_TEXT); - } - n = toInteger(n); - return function() { - if (--n < 1) { - return func.apply(this, arguments); - } - }; - } - - /** - * Creates a function that invokes `func`, with up to `n` arguments, - * ignoring any additional arguments. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category Function - * @param {Function} func The function to cap arguments for. - * @param {number} [n=func.length] The arity cap. - * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. - * @returns {Function} Returns the new capped function. - * @example - * - * _.map(['6', '8', '10'], _.ary(parseInt, 1)); - * // => [6, 8, 10] - */ - function ary(func, n, guard) { - n = guard ? undefined : n; - n = (func && n == null) ? func.length : n; - return createWrap(func, WRAP_ARY_FLAG, undefined, undefined, undefined, undefined, n); - } - - /** - * Creates a function that invokes `func`, with the `this` binding and arguments - * of the created function, while it's called less than `n` times. Subsequent - * calls to the created function return the result of the last `func` invocation. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category Function - * @param {number} n The number of calls at which `func` is no longer invoked. - * @param {Function} func The function to restrict. - * @returns {Function} Returns the new restricted function. - * @example - * - * jQuery(element).on('click', _.before(5, addContactToList)); - * // => Allows adding up to 4 contacts to the list. - */ - function before(n, func) { - var result; - if (typeof func != 'function') { - throw new TypeError(FUNC_ERROR_TEXT); - } - n = toInteger(n); - return function() { - if (--n > 0) { - result = func.apply(this, arguments); - } - if (n <= 1) { - func = undefined; - } - return result; - }; - } - - /** - * Creates a function that invokes `func` with the `this` binding of `thisArg` - * and `partials` prepended to the arguments it receives. - * - * The `_.bind.placeholder` value, which defaults to `_` in monolithic builds, - * may be used as a placeholder for partially applied arguments. - * - * **Note:** Unlike native `Function#bind`, this method doesn't set the "length" - * property of bound functions. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Function - * @param {Function} func The function to bind. - * @param {*} thisArg The `this` binding of `func`. - * @param {...*} [partials] The arguments to be partially applied. - * @returns {Function} Returns the new bound function. - * @example - * - * function greet(greeting, punctuation) { - * return greeting + ' ' + this.user + punctuation; - * } - * - * var object = { 'user': 'fred' }; - * - * var bound = _.bind(greet, object, 'hi'); - * bound('!'); - * // => 'hi fred!' - * - * // Bound with placeholders. - * var bound = _.bind(greet, object, _, '!'); - * bound('hi'); - * // => 'hi fred!' - */ - var bind = baseRest(function(func, thisArg, partials) { - var bitmask = WRAP_BIND_FLAG; - if (partials.length) { - var holders = replaceHolders(partials, getHolder(bind)); - bitmask |= WRAP_PARTIAL_FLAG; - } - return createWrap(func, bitmask, thisArg, partials, holders); - }); - - /** - * Creates a function that invokes the method at `object[key]` with `partials` - * prepended to the arguments it receives. - * - * This method differs from `_.bind` by allowing bound functions to reference - * methods that may be redefined or don't yet exist. See - * [Peter Michaux's article](http://peter.michaux.ca/articles/lazy-function-definition-pattern) - * for more details. - * - * The `_.bindKey.placeholder` value, which defaults to `_` in monolithic - * builds, may be used as a placeholder for partially applied arguments. - * - * @static - * @memberOf _ - * @since 0.10.0 - * @category Function - * @param {Object} object The object to invoke the method on. - * @param {string} key The key of the method. - * @param {...*} [partials] The arguments to be partially applied. - * @returns {Function} Returns the new bound function. - * @example - * - * var object = { - * 'user': 'fred', - * 'greet': function(greeting, punctuation) { - * return greeting + ' ' + this.user + punctuation; - * } - * }; - * - * var bound = _.bindKey(object, 'greet', 'hi'); - * bound('!'); - * // => 'hi fred!' - * - * object.greet = function(greeting, punctuation) { - * return greeting + 'ya ' + this.user + punctuation; - * }; - * - * bound('!'); - * // => 'hiya fred!' - * - * // Bound with placeholders. - * var bound = _.bindKey(object, 'greet', _, '!'); - * bound('hi'); - * // => 'hiya fred!' - */ - var bindKey = baseRest(function(object, key, partials) { - var bitmask = WRAP_BIND_FLAG | WRAP_BIND_KEY_FLAG; - if (partials.length) { - var holders = replaceHolders(partials, getHolder(bindKey)); - bitmask |= WRAP_PARTIAL_FLAG; - } - return createWrap(key, bitmask, object, partials, holders); - }); - - /** - * Creates a function that accepts arguments of `func` and either invokes - * `func` returning its result, if at least `arity` number of arguments have - * been provided, or returns a function that accepts the remaining `func` - * arguments, and so on. The arity of `func` may be specified if `func.length` - * is not sufficient. - * - * The `_.curry.placeholder` value, which defaults to `_` in monolithic builds, - * may be used as a placeholder for provided arguments. - * - * **Note:** This method doesn't set the "length" property of curried functions. - * - * @static - * @memberOf _ - * @since 2.0.0 - * @category Function - * @param {Function} func The function to curry. - * @param {number} [arity=func.length] The arity of `func`. - * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. - * @returns {Function} Returns the new curried function. - * @example - * - * var abc = function(a, b, c) { - * return [a, b, c]; - * }; - * - * var curried = _.curry(abc); - * - * curried(1)(2)(3); - * // => [1, 2, 3] - * - * curried(1, 2)(3); - * // => [1, 2, 3] - * - * curried(1, 2, 3); - * // => [1, 2, 3] - * - * // Curried with placeholders. - * curried(1)(_, 3)(2); - * // => [1, 2, 3] - */ - function curry(func, arity, guard) { - arity = guard ? undefined : arity; - var result = createWrap(func, WRAP_CURRY_FLAG, undefined, undefined, undefined, undefined, undefined, arity); - result.placeholder = curry.placeholder; - return result; - } - - /** - * This method is like `_.curry` except that arguments are applied to `func` - * in the manner of `_.partialRight` instead of `_.partial`. - * - * The `_.curryRight.placeholder` value, which defaults to `_` in monolithic - * builds, may be used as a placeholder for provided arguments. - * - * **Note:** This method doesn't set the "length" property of curried functions. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category Function - * @param {Function} func The function to curry. - * @param {number} [arity=func.length] The arity of `func`. - * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. - * @returns {Function} Returns the new curried function. - * @example - * - * var abc = function(a, b, c) { - * return [a, b, c]; - * }; - * - * var curried = _.curryRight(abc); - * - * curried(3)(2)(1); - * // => [1, 2, 3] - * - * curried(2, 3)(1); - * // => [1, 2, 3] - * - * curried(1, 2, 3); - * // => [1, 2, 3] - * - * // Curried with placeholders. - * curried(3)(1, _)(2); - * // => [1, 2, 3] - */ - function curryRight(func, arity, guard) { - arity = guard ? undefined : arity; - var result = createWrap(func, WRAP_CURRY_RIGHT_FLAG, undefined, undefined, undefined, undefined, undefined, arity); - result.placeholder = curryRight.placeholder; - return result; - } - - /** - * Creates a debounced function that delays invoking `func` until after `wait` - * milliseconds have elapsed since the last time the debounced function was - * invoked. The debounced function comes with a `cancel` method to cancel - * delayed `func` invocations and a `flush` method to immediately invoke them. - * Provide `options` to indicate whether `func` should be invoked on the - * leading and/or trailing edge of the `wait` timeout. The `func` is invoked - * with the last arguments provided to the debounced function. Subsequent - * calls to the debounced function return the result of the last `func` - * invocation. - * - * **Note:** If `leading` and `trailing` options are `true`, `func` is - * invoked on the trailing edge of the timeout only if the debounced function - * is invoked more than once during the `wait` timeout. - * - * If `wait` is `0` and `leading` is `false`, `func` invocation is deferred - * until to the next tick, similar to `setTimeout` with a timeout of `0`. - * - * See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/) - * for details over the differences between `_.debounce` and `_.throttle`. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Function - * @param {Function} func The function to debounce. - * @param {number} [wait=0] The number of milliseconds to delay. - * @param {Object} [options={}] The options object. - * @param {boolean} [options.leading=false] - * Specify invoking on the leading edge of the timeout. - * @param {number} [options.maxWait] - * The maximum time `func` is allowed to be delayed before it's invoked. - * @param {boolean} [options.trailing=true] - * Specify invoking on the trailing edge of the timeout. - * @returns {Function} Returns the new debounced function. - * @example - * - * // Avoid costly calculations while the window size is in flux. - * jQuery(window).on('resize', _.debounce(calculateLayout, 150)); - * - * // Invoke `sendMail` when clicked, debouncing subsequent calls. - * jQuery(element).on('click', _.debounce(sendMail, 300, { - * 'leading': true, - * 'trailing': false - * })); - * - * // Ensure `batchLog` is invoked once after 1 second of debounced calls. - * var debounced = _.debounce(batchLog, 250, { 'maxWait': 1000 }); - * var source = new EventSource('/stream'); - * jQuery(source).on('message', debounced); - * - * // Cancel the trailing debounced invocation. - * jQuery(window).on('popstate', debounced.cancel); - */ - function debounce(func, wait, options) { - var lastArgs, - lastThis, - maxWait, - result, - timerId, - lastCallTime, - lastInvokeTime = 0, - leading = false, - maxing = false, - trailing = true; - - if (typeof func != 'function') { - throw new TypeError(FUNC_ERROR_TEXT); - } - wait = toNumber(wait) || 0; - if (isObject(options)) { - leading = !!options.leading; - maxing = 'maxWait' in options; - maxWait = maxing ? nativeMax(toNumber(options.maxWait) || 0, wait) : maxWait; - trailing = 'trailing' in options ? !!options.trailing : trailing; - } - - function invokeFunc(time) { - var args = lastArgs, - thisArg = lastThis; - - lastArgs = lastThis = undefined; - lastInvokeTime = time; - result = func.apply(thisArg, args); - return result; - } - - function leadingEdge(time) { - // Reset any `maxWait` timer. - lastInvokeTime = time; - // Start the timer for the trailing edge. - timerId = setTimeout(timerExpired, wait); - // Invoke the leading edge. - return leading ? invokeFunc(time) : result; - } - - function remainingWait(time) { - var timeSinceLastCall = time - lastCallTime, - timeSinceLastInvoke = time - lastInvokeTime, - timeWaiting = wait - timeSinceLastCall; - - return maxing - ? nativeMin(timeWaiting, maxWait - timeSinceLastInvoke) - : timeWaiting; - } - - function shouldInvoke(time) { - var timeSinceLastCall = time - lastCallTime, - timeSinceLastInvoke = time - lastInvokeTime; - - // Either this is the first call, activity has stopped and we're at the - // trailing edge, the system time has gone backwards and we're treating - // it as the trailing edge, or we've hit the `maxWait` limit. - return (lastCallTime === undefined || (timeSinceLastCall >= wait) || - (timeSinceLastCall < 0) || (maxing && timeSinceLastInvoke >= maxWait)); - } - - function timerExpired() { - var time = now(); - if (shouldInvoke(time)) { - return trailingEdge(time); - } - // Restart the timer. - timerId = setTimeout(timerExpired, remainingWait(time)); - } - - function trailingEdge(time) { - timerId = undefined; - - // Only invoke if we have `lastArgs` which means `func` has been - // debounced at least once. - if (trailing && lastArgs) { - return invokeFunc(time); - } - lastArgs = lastThis = undefined; - return result; - } - - function cancel() { - if (timerId !== undefined) { - clearTimeout(timerId); - } - lastInvokeTime = 0; - lastArgs = lastCallTime = lastThis = timerId = undefined; - } - - function flush() { - return timerId === undefined ? result : trailingEdge(now()); - } - - function debounced() { - var time = now(), - isInvoking = shouldInvoke(time); - - lastArgs = arguments; - lastThis = this; - lastCallTime = time; - - if (isInvoking) { - if (timerId === undefined) { - return leadingEdge(lastCallTime); - } - if (maxing) { - // Handle invocations in a tight loop. - clearTimeout(timerId); - timerId = setTimeout(timerExpired, wait); - return invokeFunc(lastCallTime); - } - } - if (timerId === undefined) { - timerId = setTimeout(timerExpired, wait); - } - return result; - } - debounced.cancel = cancel; - debounced.flush = flush; - return debounced; - } - - /** - * Defers invoking the `func` until the current call stack has cleared. Any - * additional arguments are provided to `func` when it's invoked. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Function - * @param {Function} func The function to defer. - * @param {...*} [args] The arguments to invoke `func` with. - * @returns {number} Returns the timer id. - * @example - * - * _.defer(function(text) { - * console.log(text); - * }, 'deferred'); - * // => Logs 'deferred' after one millisecond. - */ - var defer = baseRest(function(func, args) { - return baseDelay(func, 1, args); - }); - - /** - * Invokes `func` after `wait` milliseconds. Any additional arguments are - * provided to `func` when it's invoked. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Function - * @param {Function} func The function to delay. - * @param {number} wait The number of milliseconds to delay invocation. - * @param {...*} [args] The arguments to invoke `func` with. - * @returns {number} Returns the timer id. - * @example - * - * _.delay(function(text) { - * console.log(text); - * }, 1000, 'later'); - * // => Logs 'later' after one second. - */ - var delay = baseRest(function(func, wait, args) { - return baseDelay(func, toNumber(wait) || 0, args); - }); - - /** - * Creates a function that invokes `func` with arguments reversed. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Function - * @param {Function} func The function to flip arguments for. - * @returns {Function} Returns the new flipped function. - * @example - * - * var flipped = _.flip(function() { - * return _.toArray(arguments); - * }); - * - * flipped('a', 'b', 'c', 'd'); - * // => ['d', 'c', 'b', 'a'] - */ - function flip(func) { - return createWrap(func, WRAP_FLIP_FLAG); - } - - /** - * Creates a function that memoizes the result of `func`. If `resolver` is - * provided, it determines the cache key for storing the result based on the - * arguments provided to the memoized function. By default, the first argument - * provided to the memoized function is used as the map cache key. The `func` - * is invoked with the `this` binding of the memoized function. - * - * **Note:** The cache is exposed as the `cache` property on the memoized - * function. Its creation may be customized by replacing the `_.memoize.Cache` - * constructor with one whose instances implement the - * [`Map`](http://ecma-international.org/ecma-262/7.0/#sec-properties-of-the-map-prototype-object) - * method interface of `clear`, `delete`, `get`, `has`, and `set`. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Function - * @param {Function} func The function to have its output memoized. - * @param {Function} [resolver] The function to resolve the cache key. - * @returns {Function} Returns the new memoized function. - * @example - * - * var object = { 'a': 1, 'b': 2 }; - * var other = { 'c': 3, 'd': 4 }; - * - * var values = _.memoize(_.values); - * values(object); - * // => [1, 2] - * - * values(other); - * // => [3, 4] - * - * object.a = 2; - * values(object); - * // => [1, 2] - * - * // Modify the result cache. - * values.cache.set(object, ['a', 'b']); - * values(object); - * // => ['a', 'b'] - * - * // Replace `_.memoize.Cache`. - * _.memoize.Cache = WeakMap; - */ - function memoize(func, resolver) { - if (typeof func != 'function' || (resolver != null && typeof resolver != 'function')) { - throw new TypeError(FUNC_ERROR_TEXT); - } - var memoized = function() { - var args = arguments, - key = resolver ? resolver.apply(this, args) : args[0], - cache = memoized.cache; - - if (cache.has(key)) { - return cache.get(key); - } - var result = func.apply(this, args); - memoized.cache = cache.set(key, result) || cache; - return result; - }; - memoized.cache = new (memoize.Cache || MapCache); - return memoized; - } - - // Expose `MapCache`. - memoize.Cache = MapCache; - - /** - * Creates a function that negates the result of the predicate `func`. The - * `func` predicate is invoked with the `this` binding and arguments of the - * created function. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category Function - * @param {Function} predicate The predicate to negate. - * @returns {Function} Returns the new negated function. - * @example - * - * function isEven(n) { - * return n % 2 == 0; - * } - * - * _.filter([1, 2, 3, 4, 5, 6], _.negate(isEven)); - * // => [1, 3, 5] - */ - function negate(predicate) { - if (typeof predicate != 'function') { - throw new TypeError(FUNC_ERROR_TEXT); - } - return function() { - var args = arguments; - switch (args.length) { - case 0: return !predicate.call(this); - case 1: return !predicate.call(this, args[0]); - case 2: return !predicate.call(this, args[0], args[1]); - case 3: return !predicate.call(this, args[0], args[1], args[2]); - } - return !predicate.apply(this, args); - }; - } - - /** - * Creates a function that is restricted to invoking `func` once. Repeat calls - * to the function return the value of the first invocation. The `func` is - * invoked with the `this` binding and arguments of the created function. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Function - * @param {Function} func The function to restrict. - * @returns {Function} Returns the new restricted function. - * @example - * - * var initialize = _.once(createApplication); - * initialize(); - * initialize(); - * // => `createApplication` is invoked once - */ - function once(func) { - return before(2, func); - } - - /** - * Creates a function that invokes `func` with its arguments transformed. - * - * @static - * @since 4.0.0 - * @memberOf _ - * @category Function - * @param {Function} func The function to wrap. - * @param {...(Function|Function[])} [transforms=[_.identity]] - * The argument transforms. - * @returns {Function} Returns the new function. - * @example - * - * function doubled(n) { - * return n * 2; - * } - * - * function square(n) { - * return n * n; - * } - * - * var func = _.overArgs(function(x, y) { - * return [x, y]; - * }, [square, doubled]); - * - * func(9, 3); - * // => [81, 6] - * - * func(10, 5); - * // => [100, 10] - */ - var overArgs = castRest(function(func, transforms) { - transforms = (transforms.length == 1 && isArray(transforms[0])) - ? arrayMap(transforms[0], baseUnary(getIteratee())) - : arrayMap(baseFlatten(transforms, 1), baseUnary(getIteratee())); - - var funcsLength = transforms.length; - return baseRest(function(args) { - var index = -1, - length = nativeMin(args.length, funcsLength); - - while (++index < length) { - args[index] = transforms[index].call(this, args[index]); - } - return apply(func, this, args); - }); - }); - - /** - * Creates a function that invokes `func` with `partials` prepended to the - * arguments it receives. This method is like `_.bind` except it does **not** - * alter the `this` binding. - * - * The `_.partial.placeholder` value, which defaults to `_` in monolithic - * builds, may be used as a placeholder for partially applied arguments. - * - * **Note:** This method doesn't set the "length" property of partially - * applied functions. - * - * @static - * @memberOf _ - * @since 0.2.0 - * @category Function - * @param {Function} func The function to partially apply arguments to. - * @param {...*} [partials] The arguments to be partially applied. - * @returns {Function} Returns the new partially applied function. - * @example - * - * function greet(greeting, name) { - * return greeting + ' ' + name; - * } - * - * var sayHelloTo = _.partial(greet, 'hello'); - * sayHelloTo('fred'); - * // => 'hello fred' - * - * // Partially applied with placeholders. - * var greetFred = _.partial(greet, _, 'fred'); - * greetFred('hi'); - * // => 'hi fred' - */ - var partial = baseRest(function(func, partials) { - var holders = replaceHolders(partials, getHolder(partial)); - return createWrap(func, WRAP_PARTIAL_FLAG, undefined, partials, holders); - }); - - /** - * This method is like `_.partial` except that partially applied arguments - * are appended to the arguments it receives. - * - * The `_.partialRight.placeholder` value, which defaults to `_` in monolithic - * builds, may be used as a placeholder for partially applied arguments. - * - * **Note:** This method doesn't set the "length" property of partially - * applied functions. - * - * @static - * @memberOf _ - * @since 1.0.0 - * @category Function - * @param {Function} func The function to partially apply arguments to. - * @param {...*} [partials] The arguments to be partially applied. - * @returns {Function} Returns the new partially applied function. - * @example - * - * function greet(greeting, name) { - * return greeting + ' ' + name; - * } - * - * var greetFred = _.partialRight(greet, 'fred'); - * greetFred('hi'); - * // => 'hi fred' - * - * // Partially applied with placeholders. - * var sayHelloTo = _.partialRight(greet, 'hello', _); - * sayHelloTo('fred'); - * // => 'hello fred' - */ - var partialRight = baseRest(function(func, partials) { - var holders = replaceHolders(partials, getHolder(partialRight)); - return createWrap(func, WRAP_PARTIAL_RIGHT_FLAG, undefined, partials, holders); - }); - - /** - * Creates a function that invokes `func` with arguments arranged according - * to the specified `indexes` where the argument value at the first index is - * provided as the first argument, the argument value at the second index is - * provided as the second argument, and so on. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category Function - * @param {Function} func The function to rearrange arguments for. - * @param {...(number|number[])} indexes The arranged argument indexes. - * @returns {Function} Returns the new function. - * @example - * - * var rearged = _.rearg(function(a, b, c) { - * return [a, b, c]; - * }, [2, 0, 1]); - * - * rearged('b', 'c', 'a') - * // => ['a', 'b', 'c'] - */ - var rearg = flatRest(function(func, indexes) { - return createWrap(func, WRAP_REARG_FLAG, undefined, undefined, undefined, indexes); - }); - - /** - * Creates a function that invokes `func` with the `this` binding of the - * created function and arguments from `start` and beyond provided as - * an array. - * - * **Note:** This method is based on the - * [rest parameter](https://mdn.io/rest_parameters). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Function - * @param {Function} func The function to apply a rest parameter to. - * @param {number} [start=func.length-1] The start position of the rest parameter. - * @returns {Function} Returns the new function. - * @example - * - * var say = _.rest(function(what, names) { - * return what + ' ' + _.initial(names).join(', ') + - * (_.size(names) > 1 ? ', & ' : '') + _.last(names); - * }); - * - * say('hello', 'fred', 'barney', 'pebbles'); - * // => 'hello fred, barney, & pebbles' - */ - function rest(func, start) { - if (typeof func != 'function') { - throw new TypeError(FUNC_ERROR_TEXT); - } - start = start === undefined ? start : toInteger(start); - return baseRest(func, start); - } - - /** - * Creates a function that invokes `func` with the `this` binding of the - * create function and an array of arguments much like - * [`Function#apply`](http://www.ecma-international.org/ecma-262/7.0/#sec-function.prototype.apply). - * - * **Note:** This method is based on the - * [spread operator](https://mdn.io/spread_operator). - * - * @static - * @memberOf _ - * @since 3.2.0 - * @category Function - * @param {Function} func The function to spread arguments over. - * @param {number} [start=0] The start position of the spread. - * @returns {Function} Returns the new function. - * @example - * - * var say = _.spread(function(who, what) { - * return who + ' says ' + what; - * }); - * - * say(['fred', 'hello']); - * // => 'fred says hello' - * - * var numbers = Promise.all([ - * Promise.resolve(40), - * Promise.resolve(36) - * ]); - * - * numbers.then(_.spread(function(x, y) { - * return x + y; - * })); - * // => a Promise of 76 - */ - function spread(func, start) { - if (typeof func != 'function') { - throw new TypeError(FUNC_ERROR_TEXT); - } - start = start == null ? 0 : nativeMax(toInteger(start), 0); - return baseRest(function(args) { - var array = args[start], - otherArgs = castSlice(args, 0, start); - - if (array) { - arrayPush(otherArgs, array); - } - return apply(func, this, otherArgs); - }); - } - - /** - * Creates a throttled function that only invokes `func` at most once per - * every `wait` milliseconds. The throttled function comes with a `cancel` - * method to cancel delayed `func` invocations and a `flush` method to - * immediately invoke them. Provide `options` to indicate whether `func` - * should be invoked on the leading and/or trailing edge of the `wait` - * timeout. The `func` is invoked with the last arguments provided to the - * throttled function. Subsequent calls to the throttled function return the - * result of the last `func` invocation. - * - * **Note:** If `leading` and `trailing` options are `true`, `func` is - * invoked on the trailing edge of the timeout only if the throttled function - * is invoked more than once during the `wait` timeout. - * - * If `wait` is `0` and `leading` is `false`, `func` invocation is deferred - * until to the next tick, similar to `setTimeout` with a timeout of `0`. - * - * See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/) - * for details over the differences between `_.throttle` and `_.debounce`. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Function - * @param {Function} func The function to throttle. - * @param {number} [wait=0] The number of milliseconds to throttle invocations to. - * @param {Object} [options={}] The options object. - * @param {boolean} [options.leading=true] - * Specify invoking on the leading edge of the timeout. - * @param {boolean} [options.trailing=true] - * Specify invoking on the trailing edge of the timeout. - * @returns {Function} Returns the new throttled function. - * @example - * - * // Avoid excessively updating the position while scrolling. - * jQuery(window).on('scroll', _.throttle(updatePosition, 100)); - * - * // Invoke `renewToken` when the click event is fired, but not more than once every 5 minutes. - * var throttled = _.throttle(renewToken, 300000, { 'trailing': false }); - * jQuery(element).on('click', throttled); - * - * // Cancel the trailing throttled invocation. - * jQuery(window).on('popstate', throttled.cancel); - */ - function throttle(func, wait, options) { - var leading = true, - trailing = true; - - if (typeof func != 'function') { - throw new TypeError(FUNC_ERROR_TEXT); - } - if (isObject(options)) { - leading = 'leading' in options ? !!options.leading : leading; - trailing = 'trailing' in options ? !!options.trailing : trailing; - } - return debounce(func, wait, { - 'leading': leading, - 'maxWait': wait, - 'trailing': trailing - }); - } - - /** - * Creates a function that accepts up to one argument, ignoring any - * additional arguments. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Function - * @param {Function} func The function to cap arguments for. - * @returns {Function} Returns the new capped function. - * @example - * - * _.map(['6', '8', '10'], _.unary(parseInt)); - * // => [6, 8, 10] - */ - function unary(func) { - return ary(func, 1); - } - - /** - * Creates a function that provides `value` to `wrapper` as its first - * argument. Any additional arguments provided to the function are appended - * to those provided to the `wrapper`. The wrapper is invoked with the `this` - * binding of the created function. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Function - * @param {*} value The value to wrap. - * @param {Function} [wrapper=identity] The wrapper function. - * @returns {Function} Returns the new function. - * @example - * - * var p = _.wrap(_.escape, function(func, text) { - * return '

    ' + func(text) + '

    '; - * }); - * - * p('fred, barney, & pebbles'); - * // => '

    fred, barney, & pebbles

    ' - */ - function wrap(value, wrapper) { - return partial(castFunction(wrapper), value); - } - - /*------------------------------------------------------------------------*/ - - /** - * Casts `value` as an array if it's not one. - * - * @static - * @memberOf _ - * @since 4.4.0 - * @category Lang - * @param {*} value The value to inspect. - * @returns {Array} Returns the cast array. - * @example - * - * _.castArray(1); - * // => [1] - * - * _.castArray({ 'a': 1 }); - * // => [{ 'a': 1 }] - * - * _.castArray('abc'); - * // => ['abc'] - * - * _.castArray(null); - * // => [null] - * - * _.castArray(undefined); - * // => [undefined] - * - * _.castArray(); - * // => [] - * - * var array = [1, 2, 3]; - * console.log(_.castArray(array) === array); - * // => true - */ - function castArray() { - if (!arguments.length) { - return []; - } - var value = arguments[0]; - return isArray(value) ? value : [value]; - } - - /** - * Creates a shallow clone of `value`. - * - * **Note:** This method is loosely based on the - * [structured clone algorithm](https://mdn.io/Structured_clone_algorithm) - * and supports cloning arrays, array buffers, booleans, date objects, maps, - * numbers, `Object` objects, regexes, sets, strings, symbols, and typed - * arrays. The own enumerable properties of `arguments` objects are cloned - * as plain objects. An empty object is returned for uncloneable values such - * as error objects, functions, DOM nodes, and WeakMaps. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Lang - * @param {*} value The value to clone. - * @returns {*} Returns the cloned value. - * @see _.cloneDeep - * @example - * - * var objects = [{ 'a': 1 }, { 'b': 2 }]; - * - * var shallow = _.clone(objects); - * console.log(shallow[0] === objects[0]); - * // => true - */ - function clone(value) { - return baseClone(value, CLONE_SYMBOLS_FLAG); - } - - /** - * This method is like `_.clone` except that it accepts `customizer` which - * is invoked to produce the cloned value. If `customizer` returns `undefined`, - * cloning is handled by the method instead. The `customizer` is invoked with - * up to four arguments; (value [, index|key, object, stack]). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to clone. - * @param {Function} [customizer] The function to customize cloning. - * @returns {*} Returns the cloned value. - * @see _.cloneDeepWith - * @example - * - * function customizer(value) { - * if (_.isElement(value)) { - * return value.cloneNode(false); - * } - * } - * - * var el = _.cloneWith(document.body, customizer); - * - * console.log(el === document.body); - * // => false - * console.log(el.nodeName); - * // => 'BODY' - * console.log(el.childNodes.length); - * // => 0 - */ - function cloneWith(value, customizer) { - customizer = typeof customizer == 'function' ? customizer : undefined; - return baseClone(value, CLONE_SYMBOLS_FLAG, customizer); - } - - /** - * This method is like `_.clone` except that it recursively clones `value`. - * - * @static - * @memberOf _ - * @since 1.0.0 - * @category Lang - * @param {*} value The value to recursively clone. - * @returns {*} Returns the deep cloned value. - * @see _.clone - * @example - * - * var objects = [{ 'a': 1 }, { 'b': 2 }]; - * - * var deep = _.cloneDeep(objects); - * console.log(deep[0] === objects[0]); - * // => false - */ - function cloneDeep(value) { - return baseClone(value, CLONE_DEEP_FLAG | CLONE_SYMBOLS_FLAG); - } - - /** - * This method is like `_.cloneWith` except that it recursively clones `value`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to recursively clone. - * @param {Function} [customizer] The function to customize cloning. - * @returns {*} Returns the deep cloned value. - * @see _.cloneWith - * @example - * - * function customizer(value) { - * if (_.isElement(value)) { - * return value.cloneNode(true); - * } - * } - * - * var el = _.cloneDeepWith(document.body, customizer); - * - * console.log(el === document.body); - * // => false - * console.log(el.nodeName); - * // => 'BODY' - * console.log(el.childNodes.length); - * // => 20 - */ - function cloneDeepWith(value, customizer) { - customizer = typeof customizer == 'function' ? customizer : undefined; - return baseClone(value, CLONE_DEEP_FLAG | CLONE_SYMBOLS_FLAG, customizer); - } - - /** - * Checks if `object` conforms to `source` by invoking the predicate - * properties of `source` with the corresponding property values of `object`. - * - * **Note:** This method is equivalent to `_.conforms` when `source` is - * partially applied. - * - * @static - * @memberOf _ - * @since 4.14.0 - * @category Lang - * @param {Object} object The object to inspect. - * @param {Object} source The object of property predicates to conform to. - * @returns {boolean} Returns `true` if `object` conforms, else `false`. - * @example - * - * var object = { 'a': 1, 'b': 2 }; - * - * _.conformsTo(object, { 'b': function(n) { return n > 1; } }); - * // => true - * - * _.conformsTo(object, { 'b': function(n) { return n > 2; } }); - * // => false - */ - function conformsTo(object, source) { - return source == null || baseConformsTo(object, source, keys(source)); - } - - /** - * Performs a - * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) - * comparison between two values to determine if they are equivalent. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to compare. - * @param {*} other The other value to compare. - * @returns {boolean} Returns `true` if the values are equivalent, else `false`. - * @example - * - * var object = { 'a': 1 }; - * var other = { 'a': 1 }; - * - * _.eq(object, object); - * // => true - * - * _.eq(object, other); - * // => false - * - * _.eq('a', 'a'); - * // => true - * - * _.eq('a', Object('a')); - * // => false - * - * _.eq(NaN, NaN); - * // => true - */ - function eq(value, other) { - return value === other || (value !== value && other !== other); - } - - /** - * Checks if `value` is greater than `other`. - * - * @static - * @memberOf _ - * @since 3.9.0 - * @category Lang - * @param {*} value The value to compare. - * @param {*} other The other value to compare. - * @returns {boolean} Returns `true` if `value` is greater than `other`, - * else `false`. - * @see _.lt - * @example - * - * _.gt(3, 1); - * // => true - * - * _.gt(3, 3); - * // => false - * - * _.gt(1, 3); - * // => false - */ - var gt = createRelationalOperation(baseGt); - - /** - * Checks if `value` is greater than or equal to `other`. - * - * @static - * @memberOf _ - * @since 3.9.0 - * @category Lang - * @param {*} value The value to compare. - * @param {*} other The other value to compare. - * @returns {boolean} Returns `true` if `value` is greater than or equal to - * `other`, else `false`. - * @see _.lte - * @example - * - * _.gte(3, 1); - * // => true - * - * _.gte(3, 3); - * // => true - * - * _.gte(1, 3); - * // => false - */ - var gte = createRelationalOperation(function(value, other) { - return value >= other; - }); - - /** - * Checks if `value` is likely an `arguments` object. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is an `arguments` object, - * else `false`. - * @example - * - * _.isArguments(function() { return arguments; }()); - * // => true - * - * _.isArguments([1, 2, 3]); - * // => false - */ - var isArguments = baseIsArguments(function() { return arguments; }()) ? baseIsArguments : function(value) { - return isObjectLike(value) && hasOwnProperty.call(value, 'callee') && - !propertyIsEnumerable.call(value, 'callee'); - }; - - /** - * Checks if `value` is classified as an `Array` object. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is an array, else `false`. - * @example - * - * _.isArray([1, 2, 3]); - * // => true - * - * _.isArray(document.body.children); - * // => false - * - * _.isArray('abc'); - * // => false - * - * _.isArray(_.noop); - * // => false - */ - var isArray = Array.isArray; - - /** - * Checks if `value` is classified as an `ArrayBuffer` object. - * - * @static - * @memberOf _ - * @since 4.3.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is an array buffer, else `false`. - * @example - * - * _.isArrayBuffer(new ArrayBuffer(2)); - * // => true - * - * _.isArrayBuffer(new Array(2)); - * // => false - */ - var isArrayBuffer = nodeIsArrayBuffer ? baseUnary(nodeIsArrayBuffer) : baseIsArrayBuffer; - - /** - * Checks if `value` is array-like. A value is considered array-like if it's - * not a function and has a `value.length` that's an integer greater than or - * equal to `0` and less than or equal to `Number.MAX_SAFE_INTEGER`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is array-like, else `false`. - * @example - * - * _.isArrayLike([1, 2, 3]); - * // => true - * - * _.isArrayLike(document.body.children); - * // => true - * - * _.isArrayLike('abc'); - * // => true - * - * _.isArrayLike(_.noop); - * // => false - */ - function isArrayLike(value) { - return value != null && isLength(value.length) && !isFunction(value); - } - - /** - * This method is like `_.isArrayLike` except that it also checks if `value` - * is an object. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is an array-like object, - * else `false`. - * @example - * - * _.isArrayLikeObject([1, 2, 3]); - * // => true - * - * _.isArrayLikeObject(document.body.children); - * // => true - * - * _.isArrayLikeObject('abc'); - * // => false - * - * _.isArrayLikeObject(_.noop); - * // => false - */ - function isArrayLikeObject(value) { - return isObjectLike(value) && isArrayLike(value); - } - - /** - * Checks if `value` is classified as a boolean primitive or object. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a boolean, else `false`. - * @example - * - * _.isBoolean(false); - * // => true - * - * _.isBoolean(null); - * // => false - */ - function isBoolean(value) { - return value === true || value === false || - (isObjectLike(value) && baseGetTag(value) == boolTag); - } - - /** - * Checks if `value` is a buffer. - * - * @static - * @memberOf _ - * @since 4.3.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a buffer, else `false`. - * @example - * - * _.isBuffer(new Buffer(2)); - * // => true - * - * _.isBuffer(new Uint8Array(2)); - * // => false - */ - var isBuffer = nativeIsBuffer || stubFalse; - - /** - * Checks if `value` is classified as a `Date` object. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a date object, else `false`. - * @example - * - * _.isDate(new Date); - * // => true - * - * _.isDate('Mon April 23 2012'); - * // => false - */ - var isDate = nodeIsDate ? baseUnary(nodeIsDate) : baseIsDate; - - /** - * Checks if `value` is likely a DOM element. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a DOM element, else `false`. - * @example - * - * _.isElement(document.body); - * // => true - * - * _.isElement(''); - * // => false - */ - function isElement(value) { - return isObjectLike(value) && value.nodeType === 1 && !isPlainObject(value); - } - - /** - * Checks if `value` is an empty object, collection, map, or set. - * - * Objects are considered empty if they have no own enumerable string keyed - * properties. - * - * Array-like values such as `arguments` objects, arrays, buffers, strings, or - * jQuery-like collections are considered empty if they have a `length` of `0`. - * Similarly, maps and sets are considered empty if they have a `size` of `0`. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is empty, else `false`. - * @example - * - * _.isEmpty(null); - * // => true - * - * _.isEmpty(true); - * // => true - * - * _.isEmpty(1); - * // => true - * - * _.isEmpty([1, 2, 3]); - * // => false - * - * _.isEmpty({ 'a': 1 }); - * // => false - */ - function isEmpty(value) { - if (value == null) { - return true; - } - if (isArrayLike(value) && - (isArray(value) || typeof value == 'string' || typeof value.splice == 'function' || - isBuffer(value) || isTypedArray(value) || isArguments(value))) { - return !value.length; - } - var tag = getTag(value); - if (tag == mapTag || tag == setTag) { - return !value.size; - } - if (isPrototype(value)) { - return !baseKeys(value).length; - } - for (var key in value) { - if (hasOwnProperty.call(value, key)) { - return false; - } - } - return true; - } - - /** - * Performs a deep comparison between two values to determine if they are - * equivalent. - * - * **Note:** This method supports comparing arrays, array buffers, booleans, - * date objects, error objects, maps, numbers, `Object` objects, regexes, - * sets, strings, symbols, and typed arrays. `Object` objects are compared - * by their own, not inherited, enumerable properties. Functions and DOM - * nodes are compared by strict equality, i.e. `===`. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Lang - * @param {*} value The value to compare. - * @param {*} other The other value to compare. - * @returns {boolean} Returns `true` if the values are equivalent, else `false`. - * @example - * - * var object = { 'a': 1 }; - * var other = { 'a': 1 }; - * - * _.isEqual(object, other); - * // => true - * - * object === other; - * // => false - */ - function isEqual(value, other) { - return baseIsEqual(value, other); - } - - /** - * This method is like `_.isEqual` except that it accepts `customizer` which - * is invoked to compare values. If `customizer` returns `undefined`, comparisons - * are handled by the method instead. The `customizer` is invoked with up to - * six arguments: (objValue, othValue [, index|key, object, other, stack]). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to compare. - * @param {*} other The other value to compare. - * @param {Function} [customizer] The function to customize comparisons. - * @returns {boolean} Returns `true` if the values are equivalent, else `false`. - * @example - * - * function isGreeting(value) { - * return /^h(?:i|ello)$/.test(value); - * } - * - * function customizer(objValue, othValue) { - * if (isGreeting(objValue) && isGreeting(othValue)) { - * return true; - * } - * } - * - * var array = ['hello', 'goodbye']; - * var other = ['hi', 'goodbye']; - * - * _.isEqualWith(array, other, customizer); - * // => true - */ - function isEqualWith(value, other, customizer) { - customizer = typeof customizer == 'function' ? customizer : undefined; - var result = customizer ? customizer(value, other) : undefined; - return result === undefined ? baseIsEqual(value, other, undefined, customizer) : !!result; - } - - /** - * Checks if `value` is an `Error`, `EvalError`, `RangeError`, `ReferenceError`, - * `SyntaxError`, `TypeError`, or `URIError` object. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is an error object, else `false`. - * @example - * - * _.isError(new Error); - * // => true - * - * _.isError(Error); - * // => false - */ - function isError(value) { - if (!isObjectLike(value)) { - return false; - } - var tag = baseGetTag(value); - return tag == errorTag || tag == domExcTag || - (typeof value.message == 'string' && typeof value.name == 'string' && !isPlainObject(value)); - } - - /** - * Checks if `value` is a finite primitive number. - * - * **Note:** This method is based on - * [`Number.isFinite`](https://mdn.io/Number/isFinite). - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a finite number, else `false`. - * @example - * - * _.isFinite(3); - * // => true - * - * _.isFinite(Number.MIN_VALUE); - * // => true - * - * _.isFinite(Infinity); - * // => false - * - * _.isFinite('3'); - * // => false - */ - function isFinite(value) { - return typeof value == 'number' && nativeIsFinite(value); - } - - /** - * Checks if `value` is classified as a `Function` object. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a function, else `false`. - * @example - * - * _.isFunction(_); - * // => true - * - * _.isFunction(/abc/); - * // => false - */ - function isFunction(value) { - if (!isObject(value)) { - return false; - } - // The use of `Object#toString` avoids issues with the `typeof` operator - // in Safari 9 which returns 'object' for typed arrays and other constructors. - var tag = baseGetTag(value); - return tag == funcTag || tag == genTag || tag == asyncTag || tag == proxyTag; - } - - /** - * Checks if `value` is an integer. - * - * **Note:** This method is based on - * [`Number.isInteger`](https://mdn.io/Number/isInteger). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is an integer, else `false`. - * @example - * - * _.isInteger(3); - * // => true - * - * _.isInteger(Number.MIN_VALUE); - * // => false - * - * _.isInteger(Infinity); - * // => false - * - * _.isInteger('3'); - * // => false - */ - function isInteger(value) { - return typeof value == 'number' && value == toInteger(value); - } - - /** - * Checks if `value` is a valid array-like length. - * - * **Note:** This method is loosely based on - * [`ToLength`](http://ecma-international.org/ecma-262/7.0/#sec-tolength). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a valid length, else `false`. - * @example - * - * _.isLength(3); - * // => true - * - * _.isLength(Number.MIN_VALUE); - * // => false - * - * _.isLength(Infinity); - * // => false - * - * _.isLength('3'); - * // => false - */ - function isLength(value) { - return typeof value == 'number' && - value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER; - } - - /** - * Checks if `value` is the - * [language type](http://www.ecma-international.org/ecma-262/7.0/#sec-ecmascript-language-types) - * of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`) - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is an object, else `false`. - * @example - * - * _.isObject({}); - * // => true - * - * _.isObject([1, 2, 3]); - * // => true - * - * _.isObject(_.noop); - * // => true - * - * _.isObject(null); - * // => false - */ - function isObject(value) { - var type = typeof value; - return value != null && (type == 'object' || type == 'function'); - } - - /** - * Checks if `value` is object-like. A value is object-like if it's not `null` - * and has a `typeof` result of "object". - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is object-like, else `false`. - * @example - * - * _.isObjectLike({}); - * // => true - * - * _.isObjectLike([1, 2, 3]); - * // => true - * - * _.isObjectLike(_.noop); - * // => false - * - * _.isObjectLike(null); - * // => false - */ - function isObjectLike(value) { - return value != null && typeof value == 'object'; - } - - /** - * Checks if `value` is classified as a `Map` object. - * - * @static - * @memberOf _ - * @since 4.3.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a map, else `false`. - * @example - * - * _.isMap(new Map); - * // => true - * - * _.isMap(new WeakMap); - * // => false - */ - var isMap = nodeIsMap ? baseUnary(nodeIsMap) : baseIsMap; - - /** - * Performs a partial deep comparison between `object` and `source` to - * determine if `object` contains equivalent property values. - * - * **Note:** This method is equivalent to `_.matches` when `source` is - * partially applied. - * - * Partial comparisons will match empty array and empty object `source` - * values against any array or object value, respectively. See `_.isEqual` - * for a list of supported value comparisons. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category Lang - * @param {Object} object The object to inspect. - * @param {Object} source The object of property values to match. - * @returns {boolean} Returns `true` if `object` is a match, else `false`. - * @example - * - * var object = { 'a': 1, 'b': 2 }; - * - * _.isMatch(object, { 'b': 2 }); - * // => true - * - * _.isMatch(object, { 'b': 1 }); - * // => false - */ - function isMatch(object, source) { - return object === source || baseIsMatch(object, source, getMatchData(source)); - } - - /** - * This method is like `_.isMatch` except that it accepts `customizer` which - * is invoked to compare values. If `customizer` returns `undefined`, comparisons - * are handled by the method instead. The `customizer` is invoked with five - * arguments: (objValue, srcValue, index|key, object, source). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {Object} object The object to inspect. - * @param {Object} source The object of property values to match. - * @param {Function} [customizer] The function to customize comparisons. - * @returns {boolean} Returns `true` if `object` is a match, else `false`. - * @example - * - * function isGreeting(value) { - * return /^h(?:i|ello)$/.test(value); - * } - * - * function customizer(objValue, srcValue) { - * if (isGreeting(objValue) && isGreeting(srcValue)) { - * return true; - * } - * } - * - * var object = { 'greeting': 'hello' }; - * var source = { 'greeting': 'hi' }; - * - * _.isMatchWith(object, source, customizer); - * // => true - */ - function isMatchWith(object, source, customizer) { - customizer = typeof customizer == 'function' ? customizer : undefined; - return baseIsMatch(object, source, getMatchData(source), customizer); - } - - /** - * Checks if `value` is `NaN`. - * - * **Note:** This method is based on - * [`Number.isNaN`](https://mdn.io/Number/isNaN) and is not the same as - * global [`isNaN`](https://mdn.io/isNaN) which returns `true` for - * `undefined` and other non-number values. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is `NaN`, else `false`. - * @example - * - * _.isNaN(NaN); - * // => true - * - * _.isNaN(new Number(NaN)); - * // => true - * - * isNaN(undefined); - * // => true - * - * _.isNaN(undefined); - * // => false - */ - function isNaN(value) { - // An `NaN` primitive is the only value that is not equal to itself. - // Perform the `toStringTag` check first to avoid errors with some - // ActiveX objects in IE. - return isNumber(value) && value != +value; - } - - /** - * Checks if `value` is a pristine native function. - * - * **Note:** This method can't reliably detect native functions in the presence - * of the core-js package because core-js circumvents this kind of detection. - * Despite multiple requests, the core-js maintainer has made it clear: any - * attempt to fix the detection will be obstructed. As a result, we're left - * with little choice but to throw an error. Unfortunately, this also affects - * packages, like [babel-polyfill](https://www.npmjs.com/package/babel-polyfill), - * which rely on core-js. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a native function, - * else `false`. - * @example - * - * _.isNative(Array.prototype.push); - * // => true - * - * _.isNative(_); - * // => false - */ - function isNative(value) { - if (isMaskable(value)) { - throw new Error(CORE_ERROR_TEXT); - } - return baseIsNative(value); - } - - /** - * Checks if `value` is `null`. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is `null`, else `false`. - * @example - * - * _.isNull(null); - * // => true - * - * _.isNull(void 0); - * // => false - */ - function isNull(value) { - return value === null; - } - - /** - * Checks if `value` is `null` or `undefined`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is nullish, else `false`. - * @example - * - * _.isNil(null); - * // => true - * - * _.isNil(void 0); - * // => true - * - * _.isNil(NaN); - * // => false - */ - function isNil(value) { - return value == null; - } - - /** - * Checks if `value` is classified as a `Number` primitive or object. - * - * **Note:** To exclude `Infinity`, `-Infinity`, and `NaN`, which are - * classified as numbers, use the `_.isFinite` method. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a number, else `false`. - * @example - * - * _.isNumber(3); - * // => true - * - * _.isNumber(Number.MIN_VALUE); - * // => true - * - * _.isNumber(Infinity); - * // => true - * - * _.isNumber('3'); - * // => false - */ - function isNumber(value) { - return typeof value == 'number' || - (isObjectLike(value) && baseGetTag(value) == numberTag); - } - - /** - * Checks if `value` is a plain object, that is, an object created by the - * `Object` constructor or one with a `[[Prototype]]` of `null`. - * - * @static - * @memberOf _ - * @since 0.8.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a plain object, else `false`. - * @example - * - * function Foo() { - * this.a = 1; - * } - * - * _.isPlainObject(new Foo); - * // => false - * - * _.isPlainObject([1, 2, 3]); - * // => false - * - * _.isPlainObject({ 'x': 0, 'y': 0 }); - * // => true - * - * _.isPlainObject(Object.create(null)); - * // => true - */ - function isPlainObject(value) { - if (!isObjectLike(value) || baseGetTag(value) != objectTag) { - return false; - } - var proto = getPrototype(value); - if (proto === null) { - return true; - } - var Ctor = hasOwnProperty.call(proto, 'constructor') && proto.constructor; - return typeof Ctor == 'function' && Ctor instanceof Ctor && - funcToString.call(Ctor) == objectCtorString; - } - - /** - * Checks if `value` is classified as a `RegExp` object. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a regexp, else `false`. - * @example - * - * _.isRegExp(/abc/); - * // => true - * - * _.isRegExp('/abc/'); - * // => false - */ - var isRegExp = nodeIsRegExp ? baseUnary(nodeIsRegExp) : baseIsRegExp; - - /** - * Checks if `value` is a safe integer. An integer is safe if it's an IEEE-754 - * double precision number which isn't the result of a rounded unsafe integer. - * - * **Note:** This method is based on - * [`Number.isSafeInteger`](https://mdn.io/Number/isSafeInteger). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a safe integer, else `false`. - * @example - * - * _.isSafeInteger(3); - * // => true - * - * _.isSafeInteger(Number.MIN_VALUE); - * // => false - * - * _.isSafeInteger(Infinity); - * // => false - * - * _.isSafeInteger('3'); - * // => false - */ - function isSafeInteger(value) { - return isInteger(value) && value >= -MAX_SAFE_INTEGER && value <= MAX_SAFE_INTEGER; - } - - /** - * Checks if `value` is classified as a `Set` object. - * - * @static - * @memberOf _ - * @since 4.3.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a set, else `false`. - * @example - * - * _.isSet(new Set); - * // => true - * - * _.isSet(new WeakSet); - * // => false - */ - var isSet = nodeIsSet ? baseUnary(nodeIsSet) : baseIsSet; - - /** - * Checks if `value` is classified as a `String` primitive or object. - * - * @static - * @since 0.1.0 - * @memberOf _ - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a string, else `false`. - * @example - * - * _.isString('abc'); - * // => true - * - * _.isString(1); - * // => false - */ - function isString(value) { - return typeof value == 'string' || - (!isArray(value) && isObjectLike(value) && baseGetTag(value) == stringTag); - } - - /** - * Checks if `value` is classified as a `Symbol` primitive or object. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a symbol, else `false`. - * @example - * - * _.isSymbol(Symbol.iterator); - * // => true - * - * _.isSymbol('abc'); - * // => false - */ - function isSymbol(value) { - return typeof value == 'symbol' || - (isObjectLike(value) && baseGetTag(value) == symbolTag); - } - - /** - * Checks if `value` is classified as a typed array. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a typed array, else `false`. - * @example - * - * _.isTypedArray(new Uint8Array); - * // => true - * - * _.isTypedArray([]); - * // => false - */ - var isTypedArray = nodeIsTypedArray ? baseUnary(nodeIsTypedArray) : baseIsTypedArray; - - /** - * Checks if `value` is `undefined`. - * - * @static - * @since 0.1.0 - * @memberOf _ - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is `undefined`, else `false`. - * @example - * - * _.isUndefined(void 0); - * // => true - * - * _.isUndefined(null); - * // => false - */ - function isUndefined(value) { - return value === undefined; - } - - /** - * Checks if `value` is classified as a `WeakMap` object. - * - * @static - * @memberOf _ - * @since 4.3.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a weak map, else `false`. - * @example - * - * _.isWeakMap(new WeakMap); - * // => true - * - * _.isWeakMap(new Map); - * // => false - */ - function isWeakMap(value) { - return isObjectLike(value) && getTag(value) == weakMapTag; - } - - /** - * Checks if `value` is classified as a `WeakSet` object. - * - * @static - * @memberOf _ - * @since 4.3.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a weak set, else `false`. - * @example - * - * _.isWeakSet(new WeakSet); - * // => true - * - * _.isWeakSet(new Set); - * // => false - */ - function isWeakSet(value) { - return isObjectLike(value) && baseGetTag(value) == weakSetTag; - } - - /** - * Checks if `value` is less than `other`. - * - * @static - * @memberOf _ - * @since 3.9.0 - * @category Lang - * @param {*} value The value to compare. - * @param {*} other The other value to compare. - * @returns {boolean} Returns `true` if `value` is less than `other`, - * else `false`. - * @see _.gt - * @example - * - * _.lt(1, 3); - * // => true - * - * _.lt(3, 3); - * // => false - * - * _.lt(3, 1); - * // => false - */ - var lt = createRelationalOperation(baseLt); - - /** - * Checks if `value` is less than or equal to `other`. - * - * @static - * @memberOf _ - * @since 3.9.0 - * @category Lang - * @param {*} value The value to compare. - * @param {*} other The other value to compare. - * @returns {boolean} Returns `true` if `value` is less than or equal to - * `other`, else `false`. - * @see _.gte - * @example - * - * _.lte(1, 3); - * // => true - * - * _.lte(3, 3); - * // => true - * - * _.lte(3, 1); - * // => false - */ - var lte = createRelationalOperation(function(value, other) { - return value <= other; - }); - - /** - * Converts `value` to an array. - * - * @static - * @since 0.1.0 - * @memberOf _ - * @category Lang - * @param {*} value The value to convert. - * @returns {Array} Returns the converted array. - * @example - * - * _.toArray({ 'a': 1, 'b': 2 }); - * // => [1, 2] - * - * _.toArray('abc'); - * // => ['a', 'b', 'c'] - * - * _.toArray(1); - * // => [] - * - * _.toArray(null); - * // => [] - */ - function toArray(value) { - if (!value) { - return []; - } - if (isArrayLike(value)) { - return isString(value) ? stringToArray(value) : copyArray(value); - } - if (symIterator && value[symIterator]) { - return iteratorToArray(value[symIterator]()); - } - var tag = getTag(value), - func = tag == mapTag ? mapToArray : (tag == setTag ? setToArray : values); - - return func(value); - } - - /** - * Converts `value` to a finite number. - * - * @static - * @memberOf _ - * @since 4.12.0 - * @category Lang - * @param {*} value The value to convert. - * @returns {number} Returns the converted number. - * @example - * - * _.toFinite(3.2); - * // => 3.2 - * - * _.toFinite(Number.MIN_VALUE); - * // => 5e-324 - * - * _.toFinite(Infinity); - * // => 1.7976931348623157e+308 - * - * _.toFinite('3.2'); - * // => 3.2 - */ - function toFinite(value) { - if (!value) { - return value === 0 ? value : 0; - } - value = toNumber(value); - if (value === INFINITY || value === -INFINITY) { - var sign = (value < 0 ? -1 : 1); - return sign * MAX_INTEGER; - } - return value === value ? value : 0; - } - - /** - * Converts `value` to an integer. - * - * **Note:** This method is loosely based on - * [`ToInteger`](http://www.ecma-international.org/ecma-262/7.0/#sec-tointeger). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to convert. - * @returns {number} Returns the converted integer. - * @example - * - * _.toInteger(3.2); - * // => 3 - * - * _.toInteger(Number.MIN_VALUE); - * // => 0 - * - * _.toInteger(Infinity); - * // => 1.7976931348623157e+308 - * - * _.toInteger('3.2'); - * // => 3 - */ - function toInteger(value) { - var result = toFinite(value), - remainder = result % 1; - - return result === result ? (remainder ? result - remainder : result) : 0; - } - - /** - * Converts `value` to an integer suitable for use as the length of an - * array-like object. - * - * **Note:** This method is based on - * [`ToLength`](http://ecma-international.org/ecma-262/7.0/#sec-tolength). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to convert. - * @returns {number} Returns the converted integer. - * @example - * - * _.toLength(3.2); - * // => 3 - * - * _.toLength(Number.MIN_VALUE); - * // => 0 - * - * _.toLength(Infinity); - * // => 4294967295 - * - * _.toLength('3.2'); - * // => 3 - */ - function toLength(value) { - return value ? baseClamp(toInteger(value), 0, MAX_ARRAY_LENGTH) : 0; - } - - /** - * Converts `value` to a number. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to process. - * @returns {number} Returns the number. - * @example - * - * _.toNumber(3.2); - * // => 3.2 - * - * _.toNumber(Number.MIN_VALUE); - * // => 5e-324 - * - * _.toNumber(Infinity); - * // => Infinity - * - * _.toNumber('3.2'); - * // => 3.2 - */ - function toNumber(value) { - if (typeof value == 'number') { - return value; - } - if (isSymbol(value)) { - return NAN; - } - if (isObject(value)) { - var other = typeof value.valueOf == 'function' ? value.valueOf() : value; - value = isObject(other) ? (other + '') : other; - } - if (typeof value != 'string') { - return value === 0 ? value : +value; - } - value = value.replace(reTrim, ''); - var isBinary = reIsBinary.test(value); - return (isBinary || reIsOctal.test(value)) - ? freeParseInt(value.slice(2), isBinary ? 2 : 8) - : (reIsBadHex.test(value) ? NAN : +value); - } - - /** - * Converts `value` to a plain object flattening inherited enumerable string - * keyed properties of `value` to own properties of the plain object. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category Lang - * @param {*} value The value to convert. - * @returns {Object} Returns the converted plain object. - * @example - * - * function Foo() { - * this.b = 2; - * } - * - * Foo.prototype.c = 3; - * - * _.assign({ 'a': 1 }, new Foo); - * // => { 'a': 1, 'b': 2 } - * - * _.assign({ 'a': 1 }, _.toPlainObject(new Foo)); - * // => { 'a': 1, 'b': 2, 'c': 3 } - */ - function toPlainObject(value) { - return copyObject(value, keysIn(value)); - } - - /** - * Converts `value` to a safe integer. A safe integer can be compared and - * represented correctly. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to convert. - * @returns {number} Returns the converted integer. - * @example - * - * _.toSafeInteger(3.2); - * // => 3 - * - * _.toSafeInteger(Number.MIN_VALUE); - * // => 0 - * - * _.toSafeInteger(Infinity); - * // => 9007199254740991 - * - * _.toSafeInteger('3.2'); - * // => 3 - */ - function toSafeInteger(value) { - return value - ? baseClamp(toInteger(value), -MAX_SAFE_INTEGER, MAX_SAFE_INTEGER) - : (value === 0 ? value : 0); - } - - /** - * Converts `value` to a string. An empty string is returned for `null` - * and `undefined` values. The sign of `-0` is preserved. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to convert. - * @returns {string} Returns the converted string. - * @example - * - * _.toString(null); - * // => '' - * - * _.toString(-0); - * // => '-0' - * - * _.toString([1, 2, 3]); - * // => '1,2,3' - */ - function toString(value) { - return value == null ? '' : baseToString(value); - } - - /*------------------------------------------------------------------------*/ - - /** - * Assigns own enumerable string keyed properties of source objects to the - * destination object. Source objects are applied from left to right. - * Subsequent sources overwrite property assignments of previous sources. - * - * **Note:** This method mutates `object` and is loosely based on - * [`Object.assign`](https://mdn.io/Object/assign). - * - * @static - * @memberOf _ - * @since 0.10.0 - * @category Object - * @param {Object} object The destination object. - * @param {...Object} [sources] The source objects. - * @returns {Object} Returns `object`. - * @see _.assignIn - * @example - * - * function Foo() { - * this.a = 1; - * } - * - * function Bar() { - * this.c = 3; - * } - * - * Foo.prototype.b = 2; - * Bar.prototype.d = 4; - * - * _.assign({ 'a': 0 }, new Foo, new Bar); - * // => { 'a': 1, 'c': 3 } - */ - var assign = createAssigner(function(object, source) { - if (isPrototype(source) || isArrayLike(source)) { - copyObject(source, keys(source), object); - return; - } - for (var key in source) { - if (hasOwnProperty.call(source, key)) { - assignValue(object, key, source[key]); - } - } - }); - - /** - * This method is like `_.assign` except that it iterates over own and - * inherited source properties. - * - * **Note:** This method mutates `object`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @alias extend - * @category Object - * @param {Object} object The destination object. - * @param {...Object} [sources] The source objects. - * @returns {Object} Returns `object`. - * @see _.assign - * @example - * - * function Foo() { - * this.a = 1; - * } - * - * function Bar() { - * this.c = 3; - * } - * - * Foo.prototype.b = 2; - * Bar.prototype.d = 4; - * - * _.assignIn({ 'a': 0 }, new Foo, new Bar); - * // => { 'a': 1, 'b': 2, 'c': 3, 'd': 4 } - */ - var assignIn = createAssigner(function(object, source) { - copyObject(source, keysIn(source), object); - }); - - /** - * This method is like `_.assignIn` except that it accepts `customizer` - * which is invoked to produce the assigned values. If `customizer` returns - * `undefined`, assignment is handled by the method instead. The `customizer` - * is invoked with five arguments: (objValue, srcValue, key, object, source). - * - * **Note:** This method mutates `object`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @alias extendWith - * @category Object - * @param {Object} object The destination object. - * @param {...Object} sources The source objects. - * @param {Function} [customizer] The function to customize assigned values. - * @returns {Object} Returns `object`. - * @see _.assignWith - * @example - * - * function customizer(objValue, srcValue) { - * return _.isUndefined(objValue) ? srcValue : objValue; - * } - * - * var defaults = _.partialRight(_.assignInWith, customizer); - * - * defaults({ 'a': 1 }, { 'b': 2 }, { 'a': 3 }); - * // => { 'a': 1, 'b': 2 } - */ - var assignInWith = createAssigner(function(object, source, srcIndex, customizer) { - copyObject(source, keysIn(source), object, customizer); - }); - - /** - * This method is like `_.assign` except that it accepts `customizer` - * which is invoked to produce the assigned values. If `customizer` returns - * `undefined`, assignment is handled by the method instead. The `customizer` - * is invoked with five arguments: (objValue, srcValue, key, object, source). - * - * **Note:** This method mutates `object`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Object - * @param {Object} object The destination object. - * @param {...Object} sources The source objects. - * @param {Function} [customizer] The function to customize assigned values. - * @returns {Object} Returns `object`. - * @see _.assignInWith - * @example - * - * function customizer(objValue, srcValue) { - * return _.isUndefined(objValue) ? srcValue : objValue; - * } - * - * var defaults = _.partialRight(_.assignWith, customizer); - * - * defaults({ 'a': 1 }, { 'b': 2 }, { 'a': 3 }); - * // => { 'a': 1, 'b': 2 } - */ - var assignWith = createAssigner(function(object, source, srcIndex, customizer) { - copyObject(source, keys(source), object, customizer); - }); - - /** - * Creates an array of values corresponding to `paths` of `object`. - * - * @static - * @memberOf _ - * @since 1.0.0 - * @category Object - * @param {Object} object The object to iterate over. - * @param {...(string|string[])} [paths] The property paths to pick. - * @returns {Array} Returns the picked values. - * @example - * - * var object = { 'a': [{ 'b': { 'c': 3 } }, 4] }; - * - * _.at(object, ['a[0].b.c', 'a[1]']); - * // => [3, 4] - */ - var at = flatRest(baseAt); - - /** - * Creates an object that inherits from the `prototype` object. If a - * `properties` object is given, its own enumerable string keyed properties - * are assigned to the created object. - * - * @static - * @memberOf _ - * @since 2.3.0 - * @category Object - * @param {Object} prototype The object to inherit from. - * @param {Object} [properties] The properties to assign to the object. - * @returns {Object} Returns the new object. - * @example - * - * function Shape() { - * this.x = 0; - * this.y = 0; - * } - * - * function Circle() { - * Shape.call(this); - * } - * - * Circle.prototype = _.create(Shape.prototype, { - * 'constructor': Circle - * }); - * - * var circle = new Circle; - * circle instanceof Circle; - * // => true - * - * circle instanceof Shape; - * // => true - */ - function create(prototype, properties) { - var result = baseCreate(prototype); - return properties == null ? result : baseAssign(result, properties); - } - - /** - * Assigns own and inherited enumerable string keyed properties of source - * objects to the destination object for all destination properties that - * resolve to `undefined`. Source objects are applied from left to right. - * Once a property is set, additional values of the same property are ignored. - * - * **Note:** This method mutates `object`. - * - * @static - * @since 0.1.0 - * @memberOf _ - * @category Object - * @param {Object} object The destination object. - * @param {...Object} [sources] The source objects. - * @returns {Object} Returns `object`. - * @see _.defaultsDeep - * @example - * - * _.defaults({ 'a': 1 }, { 'b': 2 }, { 'a': 3 }); - * // => { 'a': 1, 'b': 2 } - */ - var defaults = baseRest(function(object, sources) { - object = Object(object); - - var index = -1; - var length = sources.length; - var guard = length > 2 ? sources[2] : undefined; - - if (guard && isIterateeCall(sources[0], sources[1], guard)) { - length = 1; - } - - while (++index < length) { - var source = sources[index]; - var props = keysIn(source); - var propsIndex = -1; - var propsLength = props.length; - - while (++propsIndex < propsLength) { - var key = props[propsIndex]; - var value = object[key]; - - if (value === undefined || - (eq(value, objectProto[key]) && !hasOwnProperty.call(object, key))) { - object[key] = source[key]; - } - } - } - - return object; - }); - - /** - * This method is like `_.defaults` except that it recursively assigns - * default properties. - * - * **Note:** This method mutates `object`. - * - * @static - * @memberOf _ - * @since 3.10.0 - * @category Object - * @param {Object} object The destination object. - * @param {...Object} [sources] The source objects. - * @returns {Object} Returns `object`. - * @see _.defaults - * @example - * - * _.defaultsDeep({ 'a': { 'b': 2 } }, { 'a': { 'b': 1, 'c': 3 } }); - * // => { 'a': { 'b': 2, 'c': 3 } } - */ - var defaultsDeep = baseRest(function(args) { - args.push(undefined, customDefaultsMerge); - return apply(mergeWith, undefined, args); - }); - - /** - * This method is like `_.find` except that it returns the key of the first - * element `predicate` returns truthy for instead of the element itself. - * - * @static - * @memberOf _ - * @since 1.1.0 - * @category Object - * @param {Object} object The object to inspect. - * @param {Function} [predicate=_.identity] The function invoked per iteration. - * @returns {string|undefined} Returns the key of the matched element, - * else `undefined`. - * @example - * - * var users = { - * 'barney': { 'age': 36, 'active': true }, - * 'fred': { 'age': 40, 'active': false }, - * 'pebbles': { 'age': 1, 'active': true } - * }; - * - * _.findKey(users, function(o) { return o.age < 40; }); - * // => 'barney' (iteration order is not guaranteed) - * - * // The `_.matches` iteratee shorthand. - * _.findKey(users, { 'age': 1, 'active': true }); - * // => 'pebbles' - * - * // The `_.matchesProperty` iteratee shorthand. - * _.findKey(users, ['active', false]); - * // => 'fred' - * - * // The `_.property` iteratee shorthand. - * _.findKey(users, 'active'); - * // => 'barney' - */ - function findKey(object, predicate) { - return baseFindKey(object, getIteratee(predicate, 3), baseForOwn); - } - - /** - * This method is like `_.findKey` except that it iterates over elements of - * a collection in the opposite order. - * - * @static - * @memberOf _ - * @since 2.0.0 - * @category Object - * @param {Object} object The object to inspect. - * @param {Function} [predicate=_.identity] The function invoked per iteration. - * @returns {string|undefined} Returns the key of the matched element, - * else `undefined`. - * @example - * - * var users = { - * 'barney': { 'age': 36, 'active': true }, - * 'fred': { 'age': 40, 'active': false }, - * 'pebbles': { 'age': 1, 'active': true } - * }; - * - * _.findLastKey(users, function(o) { return o.age < 40; }); - * // => returns 'pebbles' assuming `_.findKey` returns 'barney' - * - * // The `_.matches` iteratee shorthand. - * _.findLastKey(users, { 'age': 36, 'active': true }); - * // => 'barney' - * - * // The `_.matchesProperty` iteratee shorthand. - * _.findLastKey(users, ['active', false]); - * // => 'fred' - * - * // The `_.property` iteratee shorthand. - * _.findLastKey(users, 'active'); - * // => 'pebbles' - */ - function findLastKey(object, predicate) { - return baseFindKey(object, getIteratee(predicate, 3), baseForOwnRight); - } - - /** - * Iterates over own and inherited enumerable string keyed properties of an - * object and invokes `iteratee` for each property. The iteratee is invoked - * with three arguments: (value, key, object). Iteratee functions may exit - * iteration early by explicitly returning `false`. - * - * @static - * @memberOf _ - * @since 0.3.0 - * @category Object - * @param {Object} object The object to iterate over. - * @param {Function} [iteratee=_.identity] The function invoked per iteration. - * @returns {Object} Returns `object`. - * @see _.forInRight - * @example - * - * function Foo() { - * this.a = 1; - * this.b = 2; - * } - * - * Foo.prototype.c = 3; - * - * _.forIn(new Foo, function(value, key) { - * console.log(key); - * }); - * // => Logs 'a', 'b', then 'c' (iteration order is not guaranteed). - */ - function forIn(object, iteratee) { - return object == null - ? object - : baseFor(object, getIteratee(iteratee, 3), keysIn); - } - - /** - * This method is like `_.forIn` except that it iterates over properties of - * `object` in the opposite order. - * - * @static - * @memberOf _ - * @since 2.0.0 - * @category Object - * @param {Object} object The object to iterate over. - * @param {Function} [iteratee=_.identity] The function invoked per iteration. - * @returns {Object} Returns `object`. - * @see _.forIn - * @example - * - * function Foo() { - * this.a = 1; - * this.b = 2; - * } - * - * Foo.prototype.c = 3; - * - * _.forInRight(new Foo, function(value, key) { - * console.log(key); - * }); - * // => Logs 'c', 'b', then 'a' assuming `_.forIn` logs 'a', 'b', then 'c'. - */ - function forInRight(object, iteratee) { - return object == null - ? object - : baseForRight(object, getIteratee(iteratee, 3), keysIn); - } - - /** - * Iterates over own enumerable string keyed properties of an object and - * invokes `iteratee` for each property. The iteratee is invoked with three - * arguments: (value, key, object). Iteratee functions may exit iteration - * early by explicitly returning `false`. - * - * @static - * @memberOf _ - * @since 0.3.0 - * @category Object - * @param {Object} object The object to iterate over. - * @param {Function} [iteratee=_.identity] The function invoked per iteration. - * @returns {Object} Returns `object`. - * @see _.forOwnRight - * @example - * - * function Foo() { - * this.a = 1; - * this.b = 2; - * } - * - * Foo.prototype.c = 3; - * - * _.forOwn(new Foo, function(value, key) { - * console.log(key); - * }); - * // => Logs 'a' then 'b' (iteration order is not guaranteed). - */ - function forOwn(object, iteratee) { - return object && baseForOwn(object, getIteratee(iteratee, 3)); - } - - /** - * This method is like `_.forOwn` except that it iterates over properties of - * `object` in the opposite order. - * - * @static - * @memberOf _ - * @since 2.0.0 - * @category Object - * @param {Object} object The object to iterate over. - * @param {Function} [iteratee=_.identity] The function invoked per iteration. - * @returns {Object} Returns `object`. - * @see _.forOwn - * @example - * - * function Foo() { - * this.a = 1; - * this.b = 2; - * } - * - * Foo.prototype.c = 3; - * - * _.forOwnRight(new Foo, function(value, key) { - * console.log(key); - * }); - * // => Logs 'b' then 'a' assuming `_.forOwn` logs 'a' then 'b'. - */ - function forOwnRight(object, iteratee) { - return object && baseForOwnRight(object, getIteratee(iteratee, 3)); - } - - /** - * Creates an array of function property names from own enumerable properties - * of `object`. - * - * @static - * @since 0.1.0 - * @memberOf _ - * @category Object - * @param {Object} object The object to inspect. - * @returns {Array} Returns the function names. - * @see _.functionsIn - * @example - * - * function Foo() { - * this.a = _.constant('a'); - * this.b = _.constant('b'); - * } - * - * Foo.prototype.c = _.constant('c'); - * - * _.functions(new Foo); - * // => ['a', 'b'] - */ - function functions(object) { - return object == null ? [] : baseFunctions(object, keys(object)); - } - - /** - * Creates an array of function property names from own and inherited - * enumerable properties of `object`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Object - * @param {Object} object The object to inspect. - * @returns {Array} Returns the function names. - * @see _.functions - * @example - * - * function Foo() { - * this.a = _.constant('a'); - * this.b = _.constant('b'); - * } - * - * Foo.prototype.c = _.constant('c'); - * - * _.functionsIn(new Foo); - * // => ['a', 'b', 'c'] - */ - function functionsIn(object) { - return object == null ? [] : baseFunctions(object, keysIn(object)); - } - - /** - * Gets the value at `path` of `object`. If the resolved value is - * `undefined`, the `defaultValue` is returned in its place. - * - * @static - * @memberOf _ - * @since 3.7.0 - * @category Object - * @param {Object} object The object to query. - * @param {Array|string} path The path of the property to get. - * @param {*} [defaultValue] The value returned for `undefined` resolved values. - * @returns {*} Returns the resolved value. - * @example - * - * var object = { 'a': [{ 'b': { 'c': 3 } }] }; - * - * _.get(object, 'a[0].b.c'); - * // => 3 - * - * _.get(object, ['a', '0', 'b', 'c']); - * // => 3 - * - * _.get(object, 'a.b.c', 'default'); - * // => 'default' - */ - function get(object, path, defaultValue) { - var result = object == null ? undefined : baseGet(object, path); - return result === undefined ? defaultValue : result; - } - - /** - * Checks if `path` is a direct property of `object`. - * - * @static - * @since 0.1.0 - * @memberOf _ - * @category Object - * @param {Object} object The object to query. - * @param {Array|string} path The path to check. - * @returns {boolean} Returns `true` if `path` exists, else `false`. - * @example - * - * var object = { 'a': { 'b': 2 } }; - * var other = _.create({ 'a': _.create({ 'b': 2 }) }); - * - * _.has(object, 'a'); - * // => true - * - * _.has(object, 'a.b'); - * // => true - * - * _.has(object, ['a', 'b']); - * // => true - * - * _.has(other, 'a'); - * // => false - */ - function has(object, path) { - return object != null && hasPath(object, path, baseHas); - } - - /** - * Checks if `path` is a direct or inherited property of `object`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Object - * @param {Object} object The object to query. - * @param {Array|string} path The path to check. - * @returns {boolean} Returns `true` if `path` exists, else `false`. - * @example - * - * var object = _.create({ 'a': _.create({ 'b': 2 }) }); - * - * _.hasIn(object, 'a'); - * // => true - * - * _.hasIn(object, 'a.b'); - * // => true - * - * _.hasIn(object, ['a', 'b']); - * // => true - * - * _.hasIn(object, 'b'); - * // => false - */ - function hasIn(object, path) { - return object != null && hasPath(object, path, baseHasIn); - } - - /** - * Creates an object composed of the inverted keys and values of `object`. - * If `object` contains duplicate values, subsequent values overwrite - * property assignments of previous values. - * - * @static - * @memberOf _ - * @since 0.7.0 - * @category Object - * @param {Object} object The object to invert. - * @returns {Object} Returns the new inverted object. - * @example - * - * var object = { 'a': 1, 'b': 2, 'c': 1 }; - * - * _.invert(object); - * // => { '1': 'c', '2': 'b' } - */ - var invert = createInverter(function(result, value, key) { - if (value != null && - typeof value.toString != 'function') { - value = nativeObjectToString.call(value); - } - - result[value] = key; - }, constant(identity)); - - /** - * This method is like `_.invert` except that the inverted object is generated - * from the results of running each element of `object` thru `iteratee`. The - * corresponding inverted value of each inverted key is an array of keys - * responsible for generating the inverted value. The iteratee is invoked - * with one argument: (value). - * - * @static - * @memberOf _ - * @since 4.1.0 - * @category Object - * @param {Object} object The object to invert. - * @param {Function} [iteratee=_.identity] The iteratee invoked per element. - * @returns {Object} Returns the new inverted object. - * @example - * - * var object = { 'a': 1, 'b': 2, 'c': 1 }; - * - * _.invertBy(object); - * // => { '1': ['a', 'c'], '2': ['b'] } - * - * _.invertBy(object, function(value) { - * return 'group' + value; - * }); - * // => { 'group1': ['a', 'c'], 'group2': ['b'] } - */ - var invertBy = createInverter(function(result, value, key) { - if (value != null && - typeof value.toString != 'function') { - value = nativeObjectToString.call(value); - } - - if (hasOwnProperty.call(result, value)) { - result[value].push(key); - } else { - result[value] = [key]; - } - }, getIteratee); - - /** - * Invokes the method at `path` of `object`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Object - * @param {Object} object The object to query. - * @param {Array|string} path The path of the method to invoke. - * @param {...*} [args] The arguments to invoke the method with. - * @returns {*} Returns the result of the invoked method. - * @example - * - * var object = { 'a': [{ 'b': { 'c': [1, 2, 3, 4] } }] }; - * - * _.invoke(object, 'a[0].b.c.slice', 1, 3); - * // => [2, 3] - */ - var invoke = baseRest(baseInvoke); - - /** - * Creates an array of the own enumerable property names of `object`. - * - * **Note:** Non-object values are coerced to objects. See the - * [ES spec](http://ecma-international.org/ecma-262/7.0/#sec-object.keys) - * for more details. - * - * @static - * @since 0.1.0 - * @memberOf _ - * @category Object - * @param {Object} object The object to query. - * @returns {Array} Returns the array of property names. - * @example - * - * function Foo() { - * this.a = 1; - * this.b = 2; - * } - * - * Foo.prototype.c = 3; - * - * _.keys(new Foo); - * // => ['a', 'b'] (iteration order is not guaranteed) - * - * _.keys('hi'); - * // => ['0', '1'] - */ - function keys(object) { - return isArrayLike(object) ? arrayLikeKeys(object) : baseKeys(object); - } - - /** - * Creates an array of the own and inherited enumerable property names of `object`. - * - * **Note:** Non-object values are coerced to objects. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category Object - * @param {Object} object The object to query. - * @returns {Array} Returns the array of property names. - * @example - * - * function Foo() { - * this.a = 1; - * this.b = 2; - * } - * - * Foo.prototype.c = 3; - * - * _.keysIn(new Foo); - * // => ['a', 'b', 'c'] (iteration order is not guaranteed) - */ - function keysIn(object) { - return isArrayLike(object) ? arrayLikeKeys(object, true) : baseKeysIn(object); - } - - /** - * The opposite of `_.mapValues`; this method creates an object with the - * same values as `object` and keys generated by running each own enumerable - * string keyed property of `object` thru `iteratee`. The iteratee is invoked - * with three arguments: (value, key, object). - * - * @static - * @memberOf _ - * @since 3.8.0 - * @category Object - * @param {Object} object The object to iterate over. - * @param {Function} [iteratee=_.identity] The function invoked per iteration. - * @returns {Object} Returns the new mapped object. - * @see _.mapValues - * @example - * - * _.mapKeys({ 'a': 1, 'b': 2 }, function(value, key) { - * return key + value; - * }); - * // => { 'a1': 1, 'b2': 2 } - */ - function mapKeys(object, iteratee) { - var result = {}; - iteratee = getIteratee(iteratee, 3); - - baseForOwn(object, function(value, key, object) { - baseAssignValue(result, iteratee(value, key, object), value); - }); - return result; - } - - /** - * Creates an object with the same keys as `object` and values generated - * by running each own enumerable string keyed property of `object` thru - * `iteratee`. The iteratee is invoked with three arguments: - * (value, key, object). - * - * @static - * @memberOf _ - * @since 2.4.0 - * @category Object - * @param {Object} object The object to iterate over. - * @param {Function} [iteratee=_.identity] The function invoked per iteration. - * @returns {Object} Returns the new mapped object. - * @see _.mapKeys - * @example - * - * var users = { - * 'fred': { 'user': 'fred', 'age': 40 }, - * 'pebbles': { 'user': 'pebbles', 'age': 1 } - * }; - * - * _.mapValues(users, function(o) { return o.age; }); - * // => { 'fred': 40, 'pebbles': 1 } (iteration order is not guaranteed) - * - * // The `_.property` iteratee shorthand. - * _.mapValues(users, 'age'); - * // => { 'fred': 40, 'pebbles': 1 } (iteration order is not guaranteed) - */ - function mapValues(object, iteratee) { - var result = {}; - iteratee = getIteratee(iteratee, 3); - - baseForOwn(object, function(value, key, object) { - baseAssignValue(result, key, iteratee(value, key, object)); - }); - return result; - } - - /** - * This method is like `_.assign` except that it recursively merges own and - * inherited enumerable string keyed properties of source objects into the - * destination object. Source properties that resolve to `undefined` are - * skipped if a destination value exists. Array and plain object properties - * are merged recursively. Other objects and value types are overridden by - * assignment. Source objects are applied from left to right. Subsequent - * sources overwrite property assignments of previous sources. - * - * **Note:** This method mutates `object`. - * - * @static - * @memberOf _ - * @since 0.5.0 - * @category Object - * @param {Object} object The destination object. - * @param {...Object} [sources] The source objects. - * @returns {Object} Returns `object`. - * @example - * - * var object = { - * 'a': [{ 'b': 2 }, { 'd': 4 }] - * }; - * - * var other = { - * 'a': [{ 'c': 3 }, { 'e': 5 }] - * }; - * - * _.merge(object, other); - * // => { 'a': [{ 'b': 2, 'c': 3 }, { 'd': 4, 'e': 5 }] } - */ - var merge = createAssigner(function(object, source, srcIndex) { - baseMerge(object, source, srcIndex); - }); - - /** - * This method is like `_.merge` except that it accepts `customizer` which - * is invoked to produce the merged values of the destination and source - * properties. If `customizer` returns `undefined`, merging is handled by the - * method instead. The `customizer` is invoked with six arguments: - * (objValue, srcValue, key, object, source, stack). - * - * **Note:** This method mutates `object`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Object - * @param {Object} object The destination object. - * @param {...Object} sources The source objects. - * @param {Function} customizer The function to customize assigned values. - * @returns {Object} Returns `object`. - * @example - * - * function customizer(objValue, srcValue) { - * if (_.isArray(objValue)) { - * return objValue.concat(srcValue); - * } - * } - * - * var object = { 'a': [1], 'b': [2] }; - * var other = { 'a': [3], 'b': [4] }; - * - * _.mergeWith(object, other, customizer); - * // => { 'a': [1, 3], 'b': [2, 4] } - */ - var mergeWith = createAssigner(function(object, source, srcIndex, customizer) { - baseMerge(object, source, srcIndex, customizer); - }); - - /** - * The opposite of `_.pick`; this method creates an object composed of the - * own and inherited enumerable property paths of `object` that are not omitted. - * - * **Note:** This method is considerably slower than `_.pick`. - * - * @static - * @since 0.1.0 - * @memberOf _ - * @category Object - * @param {Object} object The source object. - * @param {...(string|string[])} [paths] The property paths to omit. - * @returns {Object} Returns the new object. - * @example - * - * var object = { 'a': 1, 'b': '2', 'c': 3 }; - * - * _.omit(object, ['a', 'c']); - * // => { 'b': '2' } - */ - var omit = flatRest(function(object, paths) { - var result = {}; - if (object == null) { - return result; - } - var isDeep = false; - paths = arrayMap(paths, function(path) { - path = castPath(path, object); - isDeep || (isDeep = path.length > 1); - return path; - }); - copyObject(object, getAllKeysIn(object), result); - if (isDeep) { - result = baseClone(result, CLONE_DEEP_FLAG | CLONE_FLAT_FLAG | CLONE_SYMBOLS_FLAG, customOmitClone); - } - var length = paths.length; - while (length--) { - baseUnset(result, paths[length]); - } - return result; - }); - - /** - * The opposite of `_.pickBy`; this method creates an object composed of - * the own and inherited enumerable string keyed properties of `object` that - * `predicate` doesn't return truthy for. The predicate is invoked with two - * arguments: (value, key). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Object - * @param {Object} object The source object. - * @param {Function} [predicate=_.identity] The function invoked per property. - * @returns {Object} Returns the new object. - * @example - * - * var object = { 'a': 1, 'b': '2', 'c': 3 }; - * - * _.omitBy(object, _.isNumber); - * // => { 'b': '2' } - */ - function omitBy(object, predicate) { - return pickBy(object, negate(getIteratee(predicate))); - } - - /** - * Creates an object composed of the picked `object` properties. - * - * @static - * @since 0.1.0 - * @memberOf _ - * @category Object - * @param {Object} object The source object. - * @param {...(string|string[])} [paths] The property paths to pick. - * @returns {Object} Returns the new object. - * @example - * - * var object = { 'a': 1, 'b': '2', 'c': 3 }; - * - * _.pick(object, ['a', 'c']); - * // => { 'a': 1, 'c': 3 } - */ - var pick = flatRest(function(object, paths) { - return object == null ? {} : basePick(object, paths); - }); - - /** - * Creates an object composed of the `object` properties `predicate` returns - * truthy for. The predicate is invoked with two arguments: (value, key). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Object - * @param {Object} object The source object. - * @param {Function} [predicate=_.identity] The function invoked per property. - * @returns {Object} Returns the new object. - * @example - * - * var object = { 'a': 1, 'b': '2', 'c': 3 }; - * - * _.pickBy(object, _.isNumber); - * // => { 'a': 1, 'c': 3 } - */ - function pickBy(object, predicate) { - if (object == null) { - return {}; - } - var props = arrayMap(getAllKeysIn(object), function(prop) { - return [prop]; - }); - predicate = getIteratee(predicate); - return basePickBy(object, props, function(value, path) { - return predicate(value, path[0]); - }); - } - - /** - * This method is like `_.get` except that if the resolved value is a - * function it's invoked with the `this` binding of its parent object and - * its result is returned. - * - * @static - * @since 0.1.0 - * @memberOf _ - * @category Object - * @param {Object} object The object to query. - * @param {Array|string} path The path of the property to resolve. - * @param {*} [defaultValue] The value returned for `undefined` resolved values. - * @returns {*} Returns the resolved value. - * @example - * - * var object = { 'a': [{ 'b': { 'c1': 3, 'c2': _.constant(4) } }] }; - * - * _.result(object, 'a[0].b.c1'); - * // => 3 - * - * _.result(object, 'a[0].b.c2'); - * // => 4 - * - * _.result(object, 'a[0].b.c3', 'default'); - * // => 'default' - * - * _.result(object, 'a[0].b.c3', _.constant('default')); - * // => 'default' - */ - function result(object, path, defaultValue) { - path = castPath(path, object); - - var index = -1, - length = path.length; - - // Ensure the loop is entered when path is empty. - if (!length) { - length = 1; - object = undefined; - } - while (++index < length) { - var value = object == null ? undefined : object[toKey(path[index])]; - if (value === undefined) { - index = length; - value = defaultValue; - } - object = isFunction(value) ? value.call(object) : value; - } - return object; - } - - /** - * Sets the value at `path` of `object`. If a portion of `path` doesn't exist, - * it's created. Arrays are created for missing index properties while objects - * are created for all other missing properties. Use `_.setWith` to customize - * `path` creation. - * - * **Note:** This method mutates `object`. - * - * @static - * @memberOf _ - * @since 3.7.0 - * @category Object - * @param {Object} object The object to modify. - * @param {Array|string} path The path of the property to set. - * @param {*} value The value to set. - * @returns {Object} Returns `object`. - * @example - * - * var object = { 'a': [{ 'b': { 'c': 3 } }] }; - * - * _.set(object, 'a[0].b.c', 4); - * console.log(object.a[0].b.c); - * // => 4 - * - * _.set(object, ['x', '0', 'y', 'z'], 5); - * console.log(object.x[0].y.z); - * // => 5 - */ - function set(object, path, value) { - return object == null ? object : baseSet(object, path, value); - } - - /** - * This method is like `_.set` except that it accepts `customizer` which is - * invoked to produce the objects of `path`. If `customizer` returns `undefined` - * path creation is handled by the method instead. The `customizer` is invoked - * with three arguments: (nsValue, key, nsObject). - * - * **Note:** This method mutates `object`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Object - * @param {Object} object The object to modify. - * @param {Array|string} path The path of the property to set. - * @param {*} value The value to set. - * @param {Function} [customizer] The function to customize assigned values. - * @returns {Object} Returns `object`. - * @example - * - * var object = {}; - * - * _.setWith(object, '[0][1]', 'a', Object); - * // => { '0': { '1': 'a' } } - */ - function setWith(object, path, value, customizer) { - customizer = typeof customizer == 'function' ? customizer : undefined; - return object == null ? object : baseSet(object, path, value, customizer); - } - - /** - * Creates an array of own enumerable string keyed-value pairs for `object` - * which can be consumed by `_.fromPairs`. If `object` is a map or set, its - * entries are returned. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @alias entries - * @category Object - * @param {Object} object The object to query. - * @returns {Array} Returns the key-value pairs. - * @example - * - * function Foo() { - * this.a = 1; - * this.b = 2; - * } - * - * Foo.prototype.c = 3; - * - * _.toPairs(new Foo); - * // => [['a', 1], ['b', 2]] (iteration order is not guaranteed) - */ - var toPairs = createToPairs(keys); - - /** - * Creates an array of own and inherited enumerable string keyed-value pairs - * for `object` which can be consumed by `_.fromPairs`. If `object` is a map - * or set, its entries are returned. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @alias entriesIn - * @category Object - * @param {Object} object The object to query. - * @returns {Array} Returns the key-value pairs. - * @example - * - * function Foo() { - * this.a = 1; - * this.b = 2; - * } - * - * Foo.prototype.c = 3; - * - * _.toPairsIn(new Foo); - * // => [['a', 1], ['b', 2], ['c', 3]] (iteration order is not guaranteed) - */ - var toPairsIn = createToPairs(keysIn); - - /** - * An alternative to `_.reduce`; this method transforms `object` to a new - * `accumulator` object which is the result of running each of its own - * enumerable string keyed properties thru `iteratee`, with each invocation - * potentially mutating the `accumulator` object. If `accumulator` is not - * provided, a new object with the same `[[Prototype]]` will be used. The - * iteratee is invoked with four arguments: (accumulator, value, key, object). - * Iteratee functions may exit iteration early by explicitly returning `false`. - * - * @static - * @memberOf _ - * @since 1.3.0 - * @category Object - * @param {Object} object The object to iterate over. - * @param {Function} [iteratee=_.identity] The function invoked per iteration. - * @param {*} [accumulator] The custom accumulator value. - * @returns {*} Returns the accumulated value. - * @example - * - * _.transform([2, 3, 4], function(result, n) { - * result.push(n *= n); - * return n % 2 == 0; - * }, []); - * // => [4, 9] - * - * _.transform({ 'a': 1, 'b': 2, 'c': 1 }, function(result, value, key) { - * (result[value] || (result[value] = [])).push(key); - * }, {}); - * // => { '1': ['a', 'c'], '2': ['b'] } - */ - function transform(object, iteratee, accumulator) { - var isArr = isArray(object), - isArrLike = isArr || isBuffer(object) || isTypedArray(object); - - iteratee = getIteratee(iteratee, 4); - if (accumulator == null) { - var Ctor = object && object.constructor; - if (isArrLike) { - accumulator = isArr ? new Ctor : []; - } - else if (isObject(object)) { - accumulator = isFunction(Ctor) ? baseCreate(getPrototype(object)) : {}; - } - else { - accumulator = {}; - } - } - (isArrLike ? arrayEach : baseForOwn)(object, function(value, index, object) { - return iteratee(accumulator, value, index, object); - }); - return accumulator; - } - - /** - * Removes the property at `path` of `object`. - * - * **Note:** This method mutates `object`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Object - * @param {Object} object The object to modify. - * @param {Array|string} path The path of the property to unset. - * @returns {boolean} Returns `true` if the property is deleted, else `false`. - * @example - * - * var object = { 'a': [{ 'b': { 'c': 7 } }] }; - * _.unset(object, 'a[0].b.c'); - * // => true - * - * console.log(object); - * // => { 'a': [{ 'b': {} }] }; - * - * _.unset(object, ['a', '0', 'b', 'c']); - * // => true - * - * console.log(object); - * // => { 'a': [{ 'b': {} }] }; - */ - function unset(object, path) { - return object == null ? true : baseUnset(object, path); - } - - /** - * This method is like `_.set` except that accepts `updater` to produce the - * value to set. Use `_.updateWith` to customize `path` creation. The `updater` - * is invoked with one argument: (value). - * - * **Note:** This method mutates `object`. - * - * @static - * @memberOf _ - * @since 4.6.0 - * @category Object - * @param {Object} object The object to modify. - * @param {Array|string} path The path of the property to set. - * @param {Function} updater The function to produce the updated value. - * @returns {Object} Returns `object`. - * @example - * - * var object = { 'a': [{ 'b': { 'c': 3 } }] }; - * - * _.update(object, 'a[0].b.c', function(n) { return n * n; }); - * console.log(object.a[0].b.c); - * // => 9 - * - * _.update(object, 'x[0].y.z', function(n) { return n ? n + 1 : 0; }); - * console.log(object.x[0].y.z); - * // => 0 - */ - function update(object, path, updater) { - return object == null ? object : baseUpdate(object, path, castFunction(updater)); - } - - /** - * This method is like `_.update` except that it accepts `customizer` which is - * invoked to produce the objects of `path`. If `customizer` returns `undefined` - * path creation is handled by the method instead. The `customizer` is invoked - * with three arguments: (nsValue, key, nsObject). - * - * **Note:** This method mutates `object`. - * - * @static - * @memberOf _ - * @since 4.6.0 - * @category Object - * @param {Object} object The object to modify. - * @param {Array|string} path The path of the property to set. - * @param {Function} updater The function to produce the updated value. - * @param {Function} [customizer] The function to customize assigned values. - * @returns {Object} Returns `object`. - * @example - * - * var object = {}; - * - * _.updateWith(object, '[0][1]', _.constant('a'), Object); - * // => { '0': { '1': 'a' } } - */ - function updateWith(object, path, updater, customizer) { - customizer = typeof customizer == 'function' ? customizer : undefined; - return object == null ? object : baseUpdate(object, path, castFunction(updater), customizer); - } - - /** - * Creates an array of the own enumerable string keyed property values of `object`. - * - * **Note:** Non-object values are coerced to objects. - * - * @static - * @since 0.1.0 - * @memberOf _ - * @category Object - * @param {Object} object The object to query. - * @returns {Array} Returns the array of property values. - * @example - * - * function Foo() { - * this.a = 1; - * this.b = 2; - * } - * - * Foo.prototype.c = 3; - * - * _.values(new Foo); - * // => [1, 2] (iteration order is not guaranteed) - * - * _.values('hi'); - * // => ['h', 'i'] - */ - function values(object) { - return object == null ? [] : baseValues(object, keys(object)); - } - - /** - * Creates an array of the own and inherited enumerable string keyed property - * values of `object`. - * - * **Note:** Non-object values are coerced to objects. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category Object - * @param {Object} object The object to query. - * @returns {Array} Returns the array of property values. - * @example - * - * function Foo() { - * this.a = 1; - * this.b = 2; - * } - * - * Foo.prototype.c = 3; - * - * _.valuesIn(new Foo); - * // => [1, 2, 3] (iteration order is not guaranteed) - */ - function valuesIn(object) { - return object == null ? [] : baseValues(object, keysIn(object)); - } - - /*------------------------------------------------------------------------*/ - - /** - * Clamps `number` within the inclusive `lower` and `upper` bounds. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Number - * @param {number} number The number to clamp. - * @param {number} [lower] The lower bound. - * @param {number} upper The upper bound. - * @returns {number} Returns the clamped number. - * @example - * - * _.clamp(-10, -5, 5); - * // => -5 - * - * _.clamp(10, -5, 5); - * // => 5 - */ - function clamp(number, lower, upper) { - if (upper === undefined) { - upper = lower; - lower = undefined; - } - if (upper !== undefined) { - upper = toNumber(upper); - upper = upper === upper ? upper : 0; - } - if (lower !== undefined) { - lower = toNumber(lower); - lower = lower === lower ? lower : 0; - } - return baseClamp(toNumber(number), lower, upper); - } - - /** - * Checks if `n` is between `start` and up to, but not including, `end`. If - * `end` is not specified, it's set to `start` with `start` then set to `0`. - * If `start` is greater than `end` the params are swapped to support - * negative ranges. - * - * @static - * @memberOf _ - * @since 3.3.0 - * @category Number - * @param {number} number The number to check. - * @param {number} [start=0] The start of the range. - * @param {number} end The end of the range. - * @returns {boolean} Returns `true` if `number` is in the range, else `false`. - * @see _.range, _.rangeRight - * @example - * - * _.inRange(3, 2, 4); - * // => true - * - * _.inRange(4, 8); - * // => true - * - * _.inRange(4, 2); - * // => false - * - * _.inRange(2, 2); - * // => false - * - * _.inRange(1.2, 2); - * // => true - * - * _.inRange(5.2, 4); - * // => false - * - * _.inRange(-3, -2, -6); - * // => true - */ - function inRange(number, start, end) { - start = toFinite(start); - if (end === undefined) { - end = start; - start = 0; - } else { - end = toFinite(end); - } - number = toNumber(number); - return baseInRange(number, start, end); - } - - /** - * Produces a random number between the inclusive `lower` and `upper` bounds. - * If only one argument is provided a number between `0` and the given number - * is returned. If `floating` is `true`, or either `lower` or `upper` are - * floats, a floating-point number is returned instead of an integer. - * - * **Note:** JavaScript follows the IEEE-754 standard for resolving - * floating-point values which can produce unexpected results. - * - * @static - * @memberOf _ - * @since 0.7.0 - * @category Number - * @param {number} [lower=0] The lower bound. - * @param {number} [upper=1] The upper bound. - * @param {boolean} [floating] Specify returning a floating-point number. - * @returns {number} Returns the random number. - * @example - * - * _.random(0, 5); - * // => an integer between 0 and 5 - * - * _.random(5); - * // => also an integer between 0 and 5 - * - * _.random(5, true); - * // => a floating-point number between 0 and 5 - * - * _.random(1.2, 5.2); - * // => a floating-point number between 1.2 and 5.2 - */ - function random(lower, upper, floating) { - if (floating && typeof floating != 'boolean' && isIterateeCall(lower, upper, floating)) { - upper = floating = undefined; - } - if (floating === undefined) { - if (typeof upper == 'boolean') { - floating = upper; - upper = undefined; - } - else if (typeof lower == 'boolean') { - floating = lower; - lower = undefined; - } - } - if (lower === undefined && upper === undefined) { - lower = 0; - upper = 1; - } - else { - lower = toFinite(lower); - if (upper === undefined) { - upper = lower; - lower = 0; - } else { - upper = toFinite(upper); - } - } - if (lower > upper) { - var temp = lower; - lower = upper; - upper = temp; - } - if (floating || lower % 1 || upper % 1) { - var rand = nativeRandom(); - return nativeMin(lower + (rand * (upper - lower + freeParseFloat('1e-' + ((rand + '').length - 1)))), upper); - } - return baseRandom(lower, upper); - } - - /*------------------------------------------------------------------------*/ - - /** - * Converts `string` to [camel case](https://en.wikipedia.org/wiki/CamelCase). - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category String - * @param {string} [string=''] The string to convert. - * @returns {string} Returns the camel cased string. - * @example - * - * _.camelCase('Foo Bar'); - * // => 'fooBar' - * - * _.camelCase('--foo-bar--'); - * // => 'fooBar' - * - * _.camelCase('__FOO_BAR__'); - * // => 'fooBar' - */ - var camelCase = createCompounder(function(result, word, index) { - word = word.toLowerCase(); - return result + (index ? capitalize(word) : word); - }); - - /** - * Converts the first character of `string` to upper case and the remaining - * to lower case. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category String - * @param {string} [string=''] The string to capitalize. - * @returns {string} Returns the capitalized string. - * @example - * - * _.capitalize('FRED'); - * // => 'Fred' - */ - function capitalize(string) { - return upperFirst(toString(string).toLowerCase()); - } - - /** - * Deburrs `string` by converting - * [Latin-1 Supplement](https://en.wikipedia.org/wiki/Latin-1_Supplement_(Unicode_block)#Character_table) - * and [Latin Extended-A](https://en.wikipedia.org/wiki/Latin_Extended-A) - * letters to basic Latin letters and removing - * [combining diacritical marks](https://en.wikipedia.org/wiki/Combining_Diacritical_Marks). - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category String - * @param {string} [string=''] The string to deburr. - * @returns {string} Returns the deburred string. - * @example - * - * _.deburr('déjà vu'); - * // => 'deja vu' - */ - function deburr(string) { - string = toString(string); - return string && string.replace(reLatin, deburrLetter).replace(reComboMark, ''); - } - - /** - * Checks if `string` ends with the given target string. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category String - * @param {string} [string=''] The string to inspect. - * @param {string} [target] The string to search for. - * @param {number} [position=string.length] The position to search up to. - * @returns {boolean} Returns `true` if `string` ends with `target`, - * else `false`. - * @example - * - * _.endsWith('abc', 'c'); - * // => true - * - * _.endsWith('abc', 'b'); - * // => false - * - * _.endsWith('abc', 'b', 2); - * // => true - */ - function endsWith(string, target, position) { - string = toString(string); - target = baseToString(target); - - var length = string.length; - position = position === undefined - ? length - : baseClamp(toInteger(position), 0, length); - - var end = position; - position -= target.length; - return position >= 0 && string.slice(position, end) == target; - } - - /** - * Converts the characters "&", "<", ">", '"', and "'" in `string` to their - * corresponding HTML entities. - * - * **Note:** No other characters are escaped. To escape additional - * characters use a third-party library like [_he_](https://mths.be/he). - * - * Though the ">" character is escaped for symmetry, characters like - * ">" and "/" don't need escaping in HTML and have no special meaning - * unless they're part of a tag or unquoted attribute value. See - * [Mathias Bynens's article](https://mathiasbynens.be/notes/ambiguous-ampersands) - * (under "semi-related fun fact") for more details. - * - * When working with HTML you should always - * [quote attribute values](http://wonko.com/post/html-escaping) to reduce - * XSS vectors. - * - * @static - * @since 0.1.0 - * @memberOf _ - * @category String - * @param {string} [string=''] The string to escape. - * @returns {string} Returns the escaped string. - * @example - * - * _.escape('fred, barney, & pebbles'); - * // => 'fred, barney, & pebbles' - */ - function escape(string) { - string = toString(string); - return (string && reHasUnescapedHtml.test(string)) - ? string.replace(reUnescapedHtml, escapeHtmlChar) - : string; - } - - /** - * Escapes the `RegExp` special characters "^", "$", "\", ".", "*", "+", - * "?", "(", ")", "[", "]", "{", "}", and "|" in `string`. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category String - * @param {string} [string=''] The string to escape. - * @returns {string} Returns the escaped string. - * @example - * - * _.escapeRegExp('[lodash](https://lodash.com/)'); - * // => '\[lodash\]\(https://lodash\.com/\)' - */ - function escapeRegExp(string) { - string = toString(string); - return (string && reHasRegExpChar.test(string)) - ? string.replace(reRegExpChar, '\\$&') - : string; - } - - /** - * Converts `string` to - * [kebab case](https://en.wikipedia.org/wiki/Letter_case#Special_case_styles). - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category String - * @param {string} [string=''] The string to convert. - * @returns {string} Returns the kebab cased string. - * @example - * - * _.kebabCase('Foo Bar'); - * // => 'foo-bar' - * - * _.kebabCase('fooBar'); - * // => 'foo-bar' - * - * _.kebabCase('__FOO_BAR__'); - * // => 'foo-bar' - */ - var kebabCase = createCompounder(function(result, word, index) { - return result + (index ? '-' : '') + word.toLowerCase(); - }); - - /** - * Converts `string`, as space separated words, to lower case. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category String - * @param {string} [string=''] The string to convert. - * @returns {string} Returns the lower cased string. - * @example - * - * _.lowerCase('--Foo-Bar--'); - * // => 'foo bar' - * - * _.lowerCase('fooBar'); - * // => 'foo bar' - * - * _.lowerCase('__FOO_BAR__'); - * // => 'foo bar' - */ - var lowerCase = createCompounder(function(result, word, index) { - return result + (index ? ' ' : '') + word.toLowerCase(); - }); - - /** - * Converts the first character of `string` to lower case. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category String - * @param {string} [string=''] The string to convert. - * @returns {string} Returns the converted string. - * @example - * - * _.lowerFirst('Fred'); - * // => 'fred' - * - * _.lowerFirst('FRED'); - * // => 'fRED' - */ - var lowerFirst = createCaseFirst('toLowerCase'); - - /** - * Pads `string` on the left and right sides if it's shorter than `length`. - * Padding characters are truncated if they can't be evenly divided by `length`. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category String - * @param {string} [string=''] The string to pad. - * @param {number} [length=0] The padding length. - * @param {string} [chars=' '] The string used as padding. - * @returns {string} Returns the padded string. - * @example - * - * _.pad('abc', 8); - * // => ' abc ' - * - * _.pad('abc', 8, '_-'); - * // => '_-abc_-_' - * - * _.pad('abc', 3); - * // => 'abc' - */ - function pad(string, length, chars) { - string = toString(string); - length = toInteger(length); - - var strLength = length ? stringSize(string) : 0; - if (!length || strLength >= length) { - return string; - } - var mid = (length - strLength) / 2; - return ( - createPadding(nativeFloor(mid), chars) + - string + - createPadding(nativeCeil(mid), chars) - ); - } - - /** - * Pads `string` on the right side if it's shorter than `length`. Padding - * characters are truncated if they exceed `length`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category String - * @param {string} [string=''] The string to pad. - * @param {number} [length=0] The padding length. - * @param {string} [chars=' '] The string used as padding. - * @returns {string} Returns the padded string. - * @example - * - * _.padEnd('abc', 6); - * // => 'abc ' - * - * _.padEnd('abc', 6, '_-'); - * // => 'abc_-_' - * - * _.padEnd('abc', 3); - * // => 'abc' - */ - function padEnd(string, length, chars) { - string = toString(string); - length = toInteger(length); - - var strLength = length ? stringSize(string) : 0; - return (length && strLength < length) - ? (string + createPadding(length - strLength, chars)) - : string; - } - - /** - * Pads `string` on the left side if it's shorter than `length`. Padding - * characters are truncated if they exceed `length`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category String - * @param {string} [string=''] The string to pad. - * @param {number} [length=0] The padding length. - * @param {string} [chars=' '] The string used as padding. - * @returns {string} Returns the padded string. - * @example - * - * _.padStart('abc', 6); - * // => ' abc' - * - * _.padStart('abc', 6, '_-'); - * // => '_-_abc' - * - * _.padStart('abc', 3); - * // => 'abc' - */ - function padStart(string, length, chars) { - string = toString(string); - length = toInteger(length); - - var strLength = length ? stringSize(string) : 0; - return (length && strLength < length) - ? (createPadding(length - strLength, chars) + string) - : string; - } - - /** - * Converts `string` to an integer of the specified radix. If `radix` is - * `undefined` or `0`, a `radix` of `10` is used unless `value` is a - * hexadecimal, in which case a `radix` of `16` is used. - * - * **Note:** This method aligns with the - * [ES5 implementation](https://es5.github.io/#x15.1.2.2) of `parseInt`. - * - * @static - * @memberOf _ - * @since 1.1.0 - * @category String - * @param {string} string The string to convert. - * @param {number} [radix=10] The radix to interpret `value` by. - * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. - * @returns {number} Returns the converted integer. - * @example - * - * _.parseInt('08'); - * // => 8 - * - * _.map(['6', '08', '10'], _.parseInt); - * // => [6, 8, 10] - */ - function parseInt(string, radix, guard) { - if (guard || radix == null) { - radix = 0; - } else if (radix) { - radix = +radix; - } - return nativeParseInt(toString(string).replace(reTrimStart, ''), radix || 0); - } - - /** - * Repeats the given string `n` times. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category String - * @param {string} [string=''] The string to repeat. - * @param {number} [n=1] The number of times to repeat the string. - * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. - * @returns {string} Returns the repeated string. - * @example - * - * _.repeat('*', 3); - * // => '***' - * - * _.repeat('abc', 2); - * // => 'abcabc' - * - * _.repeat('abc', 0); - * // => '' - */ - function repeat(string, n, guard) { - if ((guard ? isIterateeCall(string, n, guard) : n === undefined)) { - n = 1; - } else { - n = toInteger(n); - } - return baseRepeat(toString(string), n); - } - - /** - * Replaces matches for `pattern` in `string` with `replacement`. - * - * **Note:** This method is based on - * [`String#replace`](https://mdn.io/String/replace). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category String - * @param {string} [string=''] The string to modify. - * @param {RegExp|string} pattern The pattern to replace. - * @param {Function|string} replacement The match replacement. - * @returns {string} Returns the modified string. - * @example - * - * _.replace('Hi Fred', 'Fred', 'Barney'); - * // => 'Hi Barney' - */ - function replace() { - var args = arguments, - string = toString(args[0]); - - return args.length < 3 ? string : string.replace(args[1], args[2]); - } - - /** - * Converts `string` to - * [snake case](https://en.wikipedia.org/wiki/Snake_case). - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category String - * @param {string} [string=''] The string to convert. - * @returns {string} Returns the snake cased string. - * @example - * - * _.snakeCase('Foo Bar'); - * // => 'foo_bar' - * - * _.snakeCase('fooBar'); - * // => 'foo_bar' - * - * _.snakeCase('--FOO-BAR--'); - * // => 'foo_bar' - */ - var snakeCase = createCompounder(function(result, word, index) { - return result + (index ? '_' : '') + word.toLowerCase(); - }); - - /** - * Splits `string` by `separator`. - * - * **Note:** This method is based on - * [`String#split`](https://mdn.io/String/split). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category String - * @param {string} [string=''] The string to split. - * @param {RegExp|string} separator The separator pattern to split by. - * @param {number} [limit] The length to truncate results to. - * @returns {Array} Returns the string segments. - * @example - * - * _.split('a-b-c', '-', 2); - * // => ['a', 'b'] - */ - function split(string, separator, limit) { - if (limit && typeof limit != 'number' && isIterateeCall(string, separator, limit)) { - separator = limit = undefined; - } - limit = limit === undefined ? MAX_ARRAY_LENGTH : limit >>> 0; - if (!limit) { - return []; - } - string = toString(string); - if (string && ( - typeof separator == 'string' || - (separator != null && !isRegExp(separator)) - )) { - separator = baseToString(separator); - if (!separator && hasUnicode(string)) { - return castSlice(stringToArray(string), 0, limit); - } - } - return string.split(separator, limit); - } - - /** - * Converts `string` to - * [start case](https://en.wikipedia.org/wiki/Letter_case#Stylistic_or_specialised_usage). - * - * @static - * @memberOf _ - * @since 3.1.0 - * @category String - * @param {string} [string=''] The string to convert. - * @returns {string} Returns the start cased string. - * @example - * - * _.startCase('--foo-bar--'); - * // => 'Foo Bar' - * - * _.startCase('fooBar'); - * // => 'Foo Bar' - * - * _.startCase('__FOO_BAR__'); - * // => 'FOO BAR' - */ - var startCase = createCompounder(function(result, word, index) { - return result + (index ? ' ' : '') + upperFirst(word); - }); - - /** - * Checks if `string` starts with the given target string. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category String - * @param {string} [string=''] The string to inspect. - * @param {string} [target] The string to search for. - * @param {number} [position=0] The position to search from. - * @returns {boolean} Returns `true` if `string` starts with `target`, - * else `false`. - * @example - * - * _.startsWith('abc', 'a'); - * // => true - * - * _.startsWith('abc', 'b'); - * // => false - * - * _.startsWith('abc', 'b', 1); - * // => true - */ - function startsWith(string, target, position) { - string = toString(string); - position = position == null - ? 0 - : baseClamp(toInteger(position), 0, string.length); - - target = baseToString(target); - return string.slice(position, position + target.length) == target; - } - - /** - * Creates a compiled template function that can interpolate data properties - * in "interpolate" delimiters, HTML-escape interpolated data properties in - * "escape" delimiters, and execute JavaScript in "evaluate" delimiters. Data - * properties may be accessed as free variables in the template. If a setting - * object is given, it takes precedence over `_.templateSettings` values. - * - * **Note:** In the development build `_.template` utilizes - * [sourceURLs](http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl) - * for easier debugging. - * - * For more information on precompiling templates see - * [lodash's custom builds documentation](https://lodash.com/custom-builds). - * - * For more information on Chrome extension sandboxes see - * [Chrome's extensions documentation](https://developer.chrome.com/extensions/sandboxingEval). - * - * @static - * @since 0.1.0 - * @memberOf _ - * @category String - * @param {string} [string=''] The template string. - * @param {Object} [options={}] The options object. - * @param {RegExp} [options.escape=_.templateSettings.escape] - * The HTML "escape" delimiter. - * @param {RegExp} [options.evaluate=_.templateSettings.evaluate] - * The "evaluate" delimiter. - * @param {Object} [options.imports=_.templateSettings.imports] - * An object to import into the template as free variables. - * @param {RegExp} [options.interpolate=_.templateSettings.interpolate] - * The "interpolate" delimiter. - * @param {string} [options.sourceURL='lodash.templateSources[n]'] - * The sourceURL of the compiled template. - * @param {string} [options.variable='obj'] - * The data object variable name. - * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. - * @returns {Function} Returns the compiled template function. - * @example - * - * // Use the "interpolate" delimiter to create a compiled template. - * var compiled = _.template('hello <%= user %>!'); - * compiled({ 'user': 'fred' }); - * // => 'hello fred!' - * - * // Use the HTML "escape" delimiter to escape data property values. - * var compiled = _.template('<%- value %>'); - * compiled({ 'value': ' - - - diff --git a/src/main/resources/templates/views/apps/apis/mainApiKeyProd.html b/src/main/resources/templates/views/apps/apis/mainApiKeyProd.html deleted file mode 100644 index d8b4f5f..0000000 --- a/src/main/resources/templates/views/apps/apis/mainApiKeyProd.html +++ /dev/null @@ -1,162 +0,0 @@ - - - -
    -
    -
    -

    인증키 운영 전환 신청

    -
    -
    -
    -
    -

    - API키 이름 -

    -
    -
    - -
    -
    -
    -
    -
      -
    • -

      인증키가 없을 경우, 승인 시, 인증키는 신규로 발급 됩니다.

      -
    • -
    -
    -
    -
    -
    -

    - API키 선택 -

    -
    -
    -
    - -
    API키 선택
    -
      -
    -
    -
    -
    -
    -
    -

    API

    - -
    -
    - - - - - - - - - - - - - - - -
    -
    - - - - -
    -
    서비스API
    -
    -
    - 올리기 - 내리기 -
    -
    -

    - 넣기 -

    -

    - 빼기 -

    -
    -

    운영 이관 신청 API

    -
    - - - - - - - - - - - - - - - - -
    -
    - - - - -
    -
    서비스API
    -
    -
    - - -

    신청사유

    -
    - -
    - -
    -
    - -
    - -
    -
    -
    - - -
    -
    - -
    - -
    - - - - - - - diff --git a/src/main/resources/templates/views/apps/apis/mainApiList.html b/src/main/resources/templates/views/apps/apis/mainApiList.html index 4223b1e..d984ff8 100644 --- a/src/main/resources/templates/views/apps/apis/mainApiList.html +++ b/src/main/resources/templates/views/apps/apis/mainApiList.html @@ -2,21 +2,31 @@ + layout:decorate="~{layout/kjbank_title_layout}"> + +
    + +

    API 마켓

    +
    +
    @@ -43,11 +52,10 @@
    - +
    -
    -

    오픈API

    -

    API 마켓

    +
    + 총 0
    - -
    - 총 0건 -
    -
    -
    - -
    - - 카테고리 - + +
    +
    + +
    - - -

    - API 이름 -

    - - -

    - API 설명 -

    + + 그룹 이름 +
    - -
    - - 📄 -
    + +

    + API 이름 +

    + + +

    + API 설명 +

    diff --git a/src/main/resources/templates/views/apps/apis/static/tokenApiSpec.html b/src/main/resources/templates/views/apps/apis/static/tokenApiSpec.html index d007700..d93eee7 100644 --- a/src/main/resources/templates/views/apps/apis/static/tokenApiSpec.html +++ b/src/main/resources/templates/views/apps/apis/static/tokenApiSpec.html @@ -1,6 +1,6 @@ + xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kjbank_base_layout}">
    @@ -379,4 +379,4 @@ grant_type=client_credentials&client_id=your_client_id&client_secret=your_client - \ No newline at end of file + diff --git a/src/main/resources/templates/views/apps/commission/commissionManage.html b/src/main/resources/templates/views/apps/commission/commissionManage.html new file mode 100644 index 0000000..473eb22 --- /dev/null +++ b/src/main/resources/templates/views/apps/commission/commissionManage.html @@ -0,0 +1,496 @@ + + + + 수수료 관리 + + +
    +
    + +

    기관 수수료 관리

    +
    +
    + +
    + + +
    +
    +

    + 이용기관은 매월 1~5일 전월 수수료 조회 가능하며, 10일 확정금액으로 정산됩니다.
    + 금액이상시 담당자 연락 및 조정요청 하시기 바랍니다. (매월 1~5일 수수료 조회, 6~9일 수수료 조정 및 확정, 10일 정산) +

    + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    구분API 명조정 전조정 후조정사유
    처리
    건수
    적용
    수수료(원)
    최저
    수수료
    출금
    예상금액
    조정
    건수
    조정
    수수료(원)
    최저
    수수료
    출금
    예상금액
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    구분과금대상
    업무
    수수료
    유형
    과금
    기준
    값구분판매액조정 전조정 후조정사유
    적용
    수수료
    처리
    건수
    최저
    수수료
    출금
    예상수수료
    적용
    수수료
    처리
    건수
    최저
    수수료
    출금
    예상수수료
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    구분조정 전조정 후
    판매액처리건수(건)출금예상금액판매액처리건수(건)출금예상금액
    합계------
    +
    +
    +
    +
    + +
    + + + + + + diff --git a/src/main/resources/templates/views/apps/commission/commissionPrint.html b/src/main/resources/templates/views/apps/commission/commissionPrint.html new file mode 100644 index 0000000..bd3bbe3 --- /dev/null +++ b/src/main/resources/templates/views/apps/commission/commissionPrint.html @@ -0,0 +1,197 @@ + + + + + 수수료 청구서 + + + + + +
    + +
    +
    +

    + 광주은행 오픈뱅킹 +

    +

    우61470 광주광역시 동구 제봉로 225 전화 (062)239-6722 FAX (062)239-6719 오픈뱅크 플랫폼 담당자

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    문서번호전자금융(OBP) 2024-03-001호
    발 급 일2024년 3월 15일
    수 신ABC 핀테크 주식회사
    참 조
    제 목2024년 3월 오픈뱅크플랫폼 API 서비스 이용수수료 청구
    + +
    +

    1. 귀 사의 무궁한 발전을 기원합니다.

    +

    2. 귀 사와 체결된 서비스 이용 계약에 따라 동 업무처리와 관련하여 발생된 수수료를 다음과 같이 청구하오니
    협조하여 주시기 바랍니다.

    +
    + +

    1. 청구내역

    +

    (단위:건수/원)

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    청구내용대출금액처리건수수수료수수료합계
    계좌잔액조회20,543₩50₩1,027,150
    소계₩3,306,780
    개인대출 상품 A₩1,050,000,00055₩5,000₩275,000
    소계₩533,000
    합계₩3,839,780
    + +

    * 수수료는 Max(처리건수*금액, 월최저수수료) 금액으로 출금됩니다

    +
    +

    2. 수수료정산

    +

    ① 정산방법 : 수수료 결제계좌에서 자동출금

    +

    ② 처리일자 : 매월10일 (휴,공휴일인 경우 익영업일). 끝.

    + +
    +

    + 주식회사 광주은행
    + 디지털 사업부장 + (직인생략) +

    +
    +
    + + + + diff --git a/src/main/resources/templates/views/apps/community/mainFaqList.html b/src/main/resources/templates/views/apps/community/mainFaqList.html index d8facf1..df8c898 100644 --- a/src/main/resources/templates/views/apps/community/mainFaqList.html +++ b/src/main/resources/templates/views/apps/community/mainFaqList.html @@ -1,123 +1,132 @@ - + xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kjbank_title_layout}"> - -
    -
    -
    -

    FAQ

    -
    -
    - -
    - -
    -
    - - 총 건 - -
    - - -
    -
    -
      -
    • -
      - -
      -
      -
      -
      -
    • -
    • -
      -
      - -

      조회된 검색 결과가 없습니다.

      -
      -
      - -

      조회된 검색 결과가 없습니다.

      -
      -
      -
    • -
    -
    -
    - - - - -
    +
    +
    + 타이틀 배경 +

    FAQ

    - + +
    + +
    + + + +
    +

    + 총 0건 +

    + +
    + + +
    +
    +
    + + +
    +
    +
    + 질문 내용이 여기에 표시됩니다. + + + + + +
    +
    +
    + 답변 내용이 여기에 표시됩니다. +
    +
    +
    +
    + + +
    +
    + + + + + + + + + + +
    +

    조회된 FAQ가 없습니다

    +

    검색 조건을 변경하여 다시 시도해주세요.

    +
    + + + + +
    +
    + + - diff --git a/src/main/resources/templates/views/apps/community/mainInquiryDetail.html b/src/main/resources/templates/views/apps/community/mainInquiryDetail.html index dc9cb90..80554e8 100644 --- a/src/main/resources/templates/views/apps/community/mainInquiryDetail.html +++ b/src/main/resources/templates/views/apps/community/mainInquiryDetail.html @@ -1,73 +1,81 @@ - + + xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kjbank_title_layout}"> +
    +
    + 타이틀 배경 +

    Q&A

    +
    +
    -
    +
    +
    -
    -
    -

    Q&A

    + +
    +
    + + 답변대기 + + 2025.11.01 +
    +

    질문 제목

    -
    -
    -
    -
    - -
    -
    - -
    -
    -

    - 등록일 : [[${#temporals.format(inquiry.createdDate, 'yyyy-MM-dd HH:mm')}]] - 작성자 : [[${inquiry.maskedInquirerName}]] -

    -
    -
    - -
    - + + -
    -
    - - [[${inquiry.responderName}]] (등록일 : [[${#temporals.format(inquiry.responseDate, 'yyyy-MM-dd HH:mm')}]]) - -
    - -
    -
    - -
    -
    + +
    +
    + 문의 내용이 여기에 표시됩니다.
    -
    - -
    - 수정 - 삭제 -
    +
    + + +
    +
    + 관리자 답변 + 2025.11.02
    -
    - 목록 +
    + 답변 내용이 여기에 표시됩니다. +
    +
    + + +
    +
    + +
    +
    + + +
    -
    + + diff --git a/src/main/resources/templates/views/apps/community/mainInquiryForm.html b/src/main/resources/templates/views/apps/community/mainInquiryForm.html index eceb301..93d1aa9 100644 --- a/src/main/resources/templates/views/apps/community/mainInquiryForm.html +++ b/src/main/resources/templates/views/apps/community/mainInquiryForm.html @@ -1,156 +1,244 @@ - + + xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kjbank_title_layout}"> - -
    - -
    -
    -

    -
    -
    -
    - -
    -
    -

    - 제목 -

    -
    -
    - -
    -
    -
    - -
    -

    - 내용 -

    -
    -
    - - -
    -
    -
    - -
    -

    첨부파일

    -
    -
    - -
    - - -
    -
    -
    - - -
    -
    -
    - -
    -

    첨부파일은 10MB 이내로 등록해 주세요.

    -

    문서파일과 이미지 파일만 등록 가능합니다. (pdf, doc, docx, xls, xlsx, ppt, pptx, hwp, gif, jpg, - png)

    -
    -
    -
    - -
    - 취소 -
    - 확인 -
    -
    -
    -
    -
    +
    +
    + 타이틀 배경 +

    Q&A

    +
    + + +
    + + +
    +

    질문하기

    + 궁금하신 사항을 작성해 주세요. +
    + + +
    +
    + + + +
    +
    + 제목 + 필수 +
    +
    + +
    +
    + + +
    +
    + 내용 + 필수 +
    +
    + + + 0 / 2000 + +
    +
    + + +
    +
    + 첨부파일 +
    +
    +
    +
    + 선택된 파일이 없습니다 + +
    + + + +
    +
    +

    + 첨부파일은 8MB 이내로 등록해 주세요. +

    +

    + + 문서파일과 이미지 파일만 등록 가능합니다. (pdf, doc, docx, xls, xlsx, ppt, pptx, hwp, gif, jpg, png) +

    +
    +
    +
    + +
    +
    + + +
    + + +
    + +
    +
    - + - + if (attachFileInput) { + attachFileInput.value = ''; + } + } + }); + + // File remove handler + btnRemoveFile.addEventListener('click', function (e) { + e.stopPropagation(); + fileInput.value = ''; + fileDisplayText.textContent = '선택된 파일이 없습니다'; + fileDisplayText.classList.remove('has-file'); + fileInputDisplay.classList.remove('has-file'); + btnRemoveFile.style.display = 'none'; + + // Clear attachFile hidden input + if (attachFileInput) { + attachFileInput.value = ''; + } + }); + + // Focus on subject field + document.getElementById('inquirySubject').focus(); + }); + diff --git a/src/main/resources/templates/views/apps/community/mainInquiryList.html b/src/main/resources/templates/views/apps/community/mainInquiryList.html index 6dd6850..e02a3d0 100644 --- a/src/main/resources/templates/views/apps/community/mainInquiryList.html +++ b/src/main/resources/templates/views/apps/community/mainInquiryList.html @@ -1,134 +1,138 @@ + xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kjbank_title_layout}"> - -
    -
    -
    -

    Q&A

    -
    -
    - - -
    - -
    -
    - - 총 건 - -
    - - -
    -
      -
    • -
      - No - 제목 - 작성자 - 처리상태 - 등록일 -
      -
    • -
    • -
      - - - - [[${inquiry.inquirySubject}]] - - - - - - - - -
      -
    • -
    • -
      -
      - -

      조회된 검색 결과가 없습니다.

      -
      -
      - -

      조회된 검색 결과가 없습니다.

      -
      -
      -
    • -
    -
    - -
    - - - - -
    -
    -
    +
    +
    + 타이틀 배경 +

    Q&A

    +
    + +
    + + +
    + + + +
    +

    + 총 0건 +

    + +
    + + +
    +
    +
    + + +
    + +
    +
    NO
    +
    제목
    +
    작성자
    +
    처리상태
    +
    등록일
    +
    + + +
    +
    +
    1
    + +
    홍**
    +
    + + 답변대기 + +
    +
    2025.01.01
    +
    +
    +
    + + +
    +

    조회된 Q&A가 없습니다

    +

    검색 조건을 변경하거나 새로운 질문을 등록해주세요.

    +
    + + + + + + + +
    +
    + - - + + $(function () { + fn_init(); + }); + diff --git a/src/main/resources/templates/views/apps/community/mainNoticeDetail.html b/src/main/resources/templates/views/apps/community/mainNoticeDetail.html index e0eaa29..5276a94 100644 --- a/src/main/resources/templates/views/apps/community/mainNoticeDetail.html +++ b/src/main/resources/templates/views/apps/community/mainNoticeDetail.html @@ -1,70 +1,75 @@ - + + xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kjbank_title_layout}"> +
    +
    + 타이틀 배경 +

    공지사항

    +
    +
    -
    -
    -
    -

    공지사항

    -
    -
    - +
    +
    -
    - 목록 -
    -
    + +
    +

    개인정보처리 방침 정정 공지

    + 2025.11.01
    + + + + + +
    +
    + 공지사항 내용이 여기에 표시됩니다. +
    +
    + + +
    + +
    + +
    - + document.addEventListener('DOMContentLoaded', function() { + var element = document.getElementById('noticeDetail'); + if (element) { + element.innerHTML = decodeHTMLEntities(element.innerHTML); + } + }); + diff --git a/src/main/resources/templates/views/apps/community/mainNoticeList.html b/src/main/resources/templates/views/apps/community/mainNoticeList.html index ee49e5d..c9f66d1 100644 --- a/src/main/resources/templates/views/apps/community/mainNoticeList.html +++ b/src/main/resources/templates/views/apps/community/mainNoticeList.html @@ -1,91 +1,89 @@ + xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kjbank_title_layout}"> -
    -
    -
    -

    공지사항

    -
    -
    - - -
    - -
    -
    - - 총 건 - -
    - - -
    -
      -
    • -
      - NO - 제목 - 등록일 -
      -
    • -
    • -
      - - - - - - - 첨부파일 - - - - - -
      -
    • -
    • -
      -
      -
      - -

      조회된 데이터가 없습니다.

      -
      -
      - -

      조회된 데이터가 없습니다.

      -
      -
      -
      -
    • -
    -
    - - - - -
    +
    +
    + 타이틀 배경 +

    공지사항

    + +
    + + +
    + + +
    +

    + 총 0건 +

    + +
    + + +
    +
    +
    + + +
    + +
    +
    NO
    +
    제목
    +
    등록일
    +
    + + +
    + +
    +
    + + +
    +

    조회된 공지사항이 없습니다

    +
    + + + + +
    +
    + @@ -109,3 +107,4 @@ }); + diff --git a/src/main/resources/templates/views/apps/community/mainPartnershipForm.html b/src/main/resources/templates/views/apps/community/mainPartnershipForm.html index 96c0272..4b47bd4 100644 --- a/src/main/resources/templates/views/apps/community/mainPartnershipForm.html +++ b/src/main/resources/templates/views/apps/community/mainPartnershipForm.html @@ -1,176 +1,222 @@ - + + xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kjbank_title_layout}"> - -
    -
    -
    -

    사업 제휴 신청

    -
    -
    -
    -

    Kbank는 온라인 비즈니스 혁신을 위한 사업 제안을 환영합니다.

    -
    - -
    -
    -
    -

    - 사업 제목 -

    -
    -
    - -
    -
    -
    -
    - -
    -

    - 사업 내용 -

    -
    -
    - -
    -
    -
    -
    - -
    -

    첨부파일

    -
    -
    -
    - -
    -
    -
    - - -
    -
    -
    - -
    -

    첨부파일은 10MB 이내로 등록해 주세요.

    -

    문서파일과 이미지 파일만 등록 가능합니다. (pdf, doc, docx, xls, xlsx, ppt, pptx, hwp, gif, jpg, - png)

    -
    -
    -
    - -
    - 취소 -
    - -
    -
    -
    -
    -
    +
    +
    + 타이틀 배경 +

    사업 제휴

    +
    + +
    + + +
    +

    사업 제휴 신청

    + 광주은행은 온라인 비즈니스 혁신을 위한 사업 제안을 환영합니다. +
    + + +
    +
    + + +
    +
    + 사업 제목 + 필수 +
    +
    + +
    +
    + + +
    +
    + 사업 내용 + 필수 +
    +
    + + + 0 / 4000 + +
    +
    + + +
    +
    + 첨부파일 +
    +
    +
    +
    + 선택된 파일이 없습니다 + +
    + + +
    +
    +

    + 첨부파일은 8MB 이내로 등록해 주세요. +

    +

    + + 문서파일과 이미지 파일만 등록 가능합니다. (pdf, doc, docx, xls, xlsx, ppt, pptx, hwp, gif, jpg, png) +

    +
    +
    +
    + +
    +
    + + +
    + + +
    + +
    +
    - - - + + - + } + // Form submission handler + btnSubmit.addEventListener('click', function (event) { + event.preventDefault(); + + const form = document.getElementById('partnershipForm'); + const bizSubject = document.getElementById('bizSubject').value.trim(); + const bizDetail = document.getElementById('bizDetail').value; + + // Validation + if (!bizSubject) { + customPopups.showAlert('사업 제목을 작성해 주세요.'); + document.getElementById('bizSubject').focus(); + return; + } + if (!bizDetail.trim()) { + customPopups.showAlert('사업 내용을 작성해 주세요.'); + document.getElementById('bizDetail').focus(); + return false; + } + + form.submit(); + }); + + fileInput.addEventListener('change', function () { + const file = fileInput.files[0]; + + if (file) { + /*[# th:if="${!isInternalUser}"]*/ + const allowedExtensions = ['.pdf', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx', '.hwp', '.gif', '.jpg', '.jpeg', '.png']; + const fileExt = '.' + file.name.split('.').pop().toLowerCase(); + + if (!allowedExtensions.includes(fileExt)) { + customPopups.showAlert("허용된 파일 형식이 아닙니다.\n문서파일(pdf, doc, docx, xls, xlsx, ppt, pptx, hwp)과 이미지 파일(gif, jpg, png)만 등록 가능합니다."); + fileInput.value = ''; + return; + } + /*[/]*/ + + fileDisplayText.textContent = file.name; + fileDisplayText.classList.add('has-file'); + fileInputDisplay.classList.add('has-file'); + btnRemoveFile.style.display = 'flex'; + } + }); + + // File remove handler + btnRemoveFile.addEventListener('click', function (e) { + e.stopPropagation(); + fileInput.value = ''; + fileDisplayText.textContent = '선택된 파일이 없습니다'; + fileDisplayText.classList.remove('has-file'); + fileInputDisplay.classList.remove('has-file'); + btnRemoveFile.style.display = 'none'; + }); + + // Focus on subject field + document.getElementById('bizSubject').focus(); + }); + + diff --git a/src/main/resources/templates/views/apps/login/account_recovery.html b/src/main/resources/templates/views/apps/login/account_recovery.html index 2ae974d..48ce90c 100644 --- a/src/main/resources/templates/views/apps/login/account_recovery.html +++ b/src/main/resources/templates/views/apps/login/account_recovery.html @@ -4,210 +4,133 @@
    - -
    -

    아이디 찾기

    -
    +
    -
    -
    -

    우리곁의 수많은 서비스들이
    케이뱅크API
    함께하고 있습니다

    -

    우리곁의 수많은 서비스들이
    케이뱅크API와 함께하고 있습니다

    -
    -
      -
    • - 서비스 이용 수 -
      -

      - 서비스 이용 -
      -
    • -
    • - API 이용건수 -
      -

      - API 이용 -
      -
    • -
    • - API 활용기업 -
      -

      - API 활용 기업 -
      -
    • -
    + +
    +
    + -
    +
    -
    -
    + +
    +
    +
    +
    +

    + 우리곁의 수많은 서비스들이 + 광주은행 오픈 API와 함께하고 있습니다. +

    +
    +
    +
    +

    서비스 이용 수

    +
    + + + + + + + + + +
    +

    9,999

    +
    + +
    + +
    +

    API 이용 건수

    +
    + + + + + + + + + +
    +

    9,999

    +
    + +
    + +
    +

    API 활용 기업

    +
    + + + + + + + + + +
    +

    9,999

    +
    +
    +
    +
    + + + + + + + diff --git a/src/main/resources/templates/views/apps/main/intro.html b/src/main/resources/templates/views/apps/main/intro.html deleted file mode 100644 index 3c9c05a..0000000 --- a/src/main/resources/templates/views/apps/main/intro.html +++ /dev/null @@ -1,78 +0,0 @@ - - - - - - -
    -
    -
    -

    - 경계를 허물다, - 혁신을 앞당기다 -

    -

    - 비즈니스의 성공은 창의적이고
    - 혁신적인 서비스에 달려있습니다. -

    -

    - 비즈니스의 성공은 창의적이고 혁신적인 서비스에 달려있습니다. -

    -
    - -
    -
    - 아이디어 -

    - Kbank API Portal은
    - 아이디어 실현과 혁신의
    - 기회를 제공 합니다. -

    -

    - Kbank API Portal은
    - 아이디어 실현과 혁신의 기회를 제공 합니다. -

    -
    -
    - -
    -

    경쟁력 강화

    -
    - -
    - 첫번째 이미지 - API 활용한 혁신적인
    - 비즈니스 모델과 서비스 제공 - 두번째 이미지 -
    -
    - 세번째 이미지 - 데이터 확보의 용이성으로
    - 비즈니스 성장의 기회 마련 -
    -
    -
    - -
    -
    - 카드 이미지 -
    -

    차별화된 디지털 금융 서비스

    -

    - 금융서비스를 손쉽게 개발할 수 있도록
    - 다양한 API와 테스트 환경을 제공합니다. -

    -
    -
    -
    - -
    -
    - - - - - - - diff --git a/src/main/resources/templates/views/apps/main/newPolicyAgreement.html b/src/main/resources/templates/views/apps/main/newPolicyAgreement.html index d95fde0..06c2720 100644 --- a/src/main/resources/templates/views/apps/main/newPolicyAgreement.html +++ b/src/main/resources/templates/views/apps/main/newPolicyAgreement.html @@ -2,7 +2,7 @@ + layout:decorate="~{layout/kjbank_base_layout}">
    @@ -22,7 +22,7 @@
    -
    +

    개정일: diff --git a/src/main/resources/templates/views/apps/main/userAgreement.html b/src/main/resources/templates/views/apps/main/userAgreement.html index f8f3236..dbd2877 100644 --- a/src/main/resources/templates/views/apps/main/userAgreement.html +++ b/src/main/resources/templates/views/apps/main/userAgreement.html @@ -1,6 +1,6 @@ + xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kjbank_base_layout}">

    diff --git a/src/main/resources/templates/views/apps/mypage/UserConversionCompleted.html b/src/main/resources/templates/views/apps/mypage/UserConversionCompleted.html index 9102bfe..802035e 100644 --- a/src/main/resources/templates/views/apps/mypage/UserConversionCompleted.html +++ b/src/main/resources/templates/views/apps/mypage/UserConversionCompleted.html @@ -2,7 +2,7 @@ + layout:decorate="~{layout/kjbank_nohead_layout}">
    diff --git a/src/main/resources/templates/views/apps/mypage/apiKeyDetail.html b/src/main/resources/templates/views/apps/mypage/apiKeyDetail.html deleted file mode 100644 index 3bedb85..0000000 --- a/src/main/resources/templates/views/apps/mypage/apiKeyDetail.html +++ /dev/null @@ -1,272 +0,0 @@ - - - - API 키 상세 - - -
    -
    -
    -

    인증키 정보

    -
    - -
    - -
    - -
    -

    - 인증키 이름 -

    -
    -
    - -
    -
    -
    -
    -

    - Client Id -

    -
    -
    - -
    -
    -
    - -
    -

    - Client Secret -

    -
    -
    -
    -

    - client_key -

    -
    -
    -
    -
    - -
    -

    -
    -
    -
    - -
    -
    -
    -
    - -
    -

    - 변경일 -

    -
    -
    - -
    -
    -
    - -
    -

    - 인증키 상태 -

    -
    -
    - -
    -
    -
    - -
    - -
    -

    API 정보

    -
    -
    - -
    - - - - - - - - - - - - - - - - - - - - - - - -
    No서비스API명비고
    14공통회원여부조회 -
    -
    - -
    - - - - - - - - - - - - - - - - - - - - - - - -
    No서비스API명비고
    14공통회원여부조회 -
    -
    -
    - - -
    -

    운영 전환 신청 내역

    -
    -
    -
    - - - - - - - - - - - - - - - - - - - - -
    상태일시상세내용
    요청2024.06.01 - 12:12:12 - 운영 전환 요청
    -
    - -
    - - - - - - - - - - - - - - - - - - - - -
    상태일시상세내용
    요청2024.06.01 - 12:12:12 - 운영 전환 요청
    -
    -
    -
    - - -
    -
    - 목록 -
    - -
    -
    -
    - -
    - - - - -
    - - - - - - diff --git a/src/main/resources/templates/views/apps/mypage/apiKeyList.html b/src/main/resources/templates/views/apps/mypage/apiKeyList.html index aae847c..a46426b 100644 --- a/src/main/resources/templates/views/apps/mypage/apiKeyList.html +++ b/src/main/resources/templates/views/apps/mypage/apiKeyList.html @@ -1,92 +1,152 @@ + xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kjbank_title_layout}"> -
    -
    -
    -

    인증키 목록

    -
    -
    -
    -
    - -
    -
    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    No이름인증키인증키상태사용 API 수마지막수정일
    1 - 수신 - - - 활성화1
    -
    -
    -
    - -
    - -
    - -
    -
    -
    -
    -
    +
    +
    + +

    앱관리

    -
    - - -
    - - diff --git a/src/main/resources/templates/views/apps/mypage/apiKeyModifyStep1.html b/src/main/resources/templates/views/apps/mypage/apiKeyModifyStep1.html new file mode 100644 index 0000000..78a72ce --- /dev/null +++ b/src/main/resources/templates/views/apps/mypage/apiKeyModifyStep1.html @@ -0,0 +1,470 @@ + + + + +
    +
    + +

    앱관리

    +
    +
    + +
    + + +
    +

    앱 수정

    + 서비스 중인 앱 정보를 수정합니다. +
    + + +
    +
    + +
    +
    +
    + 앱 정보수정 +
    +
    +
    앱 정보수정
    +
    + + +
    + +
    + + +
    +
    +
    + API 선택 +
    +
    +
    API 선택
    +
    + + +
    + +
    + + +
    +
    +
    + 완료 +
    +
    +
    완료
    +
    +
    +
    + + +
    + + + + + + +
    + + +
    +
    + + + + + +
    +
    + 앱 아이콘 +
    +
    +
    +
    +
    + + + + + +
    + + Icon Preview + + Icon Preview + + + + +
    +
    +

    앱 아이콘 이미지 파일을 업로드하세요

    +

    권장사이즈는 128X128픽셀이며 JPG, PNG, GIF파일만 등록할 수 있습니다.

    +
    + + +
    +
    +
    + + +
    +
    + 앱 이름 + 필수 +
    +
    + +
    +
    + + +
    +
    + 앱 설명 + 필수 +
    +
    + + + 0 / 500 + +
    +
    + + +
    +
    + Call Back URL +
    +
    + +
    +
    + + +
    +
    + 화이트리스트 +
    +
    +
    + + +
    + + + + + + +
    +
    + +
    +
    + + +
    + + 취소 + + +
    + +
    +
    + + + + + + + diff --git a/src/main/resources/templates/views/apps/mypage/apiKeyModifyStep2.html b/src/main/resources/templates/views/apps/mypage/apiKeyModifyStep2.html new file mode 100644 index 0000000..1b78ea1 --- /dev/null +++ b/src/main/resources/templates/views/apps/mypage/apiKeyModifyStep2.html @@ -0,0 +1,739 @@ + + + + +
    +
    + +

    앱관리

    +
    +
    + +
    + + +
    +

    앱 수정

    + 서비스 중인 앱 정보를 수정합니다. +
    + + +
    +
    + +
    +
    +
    + 앱 정보입력 +
    +
    +
    앱 정보입력
    +
    + + +
    + +
    + + +
    +
    +
    + API 선택 +
    +
    +
    API 선택
    +
    + + +
    + +
    + + +
    +
    +
    + 앱 수정 완료 +
    +
    +
    앱 수정 완료
    +
    +
    +
    + + +
    + + + + + + +
    + + +
    + + + + + + + + +
    + + +
    +
    + + + + + +
    + + +
    + + +
    + +
    +
    +

    API 목록을 불러오는 중...

    +
    + + + + + + +
    + +
    +
    + +
    + + +
    + + +
    + +
    + + + + + +
    + + + + + + diff --git a/src/main/resources/templates/views/apps/mypage/apiKeyModifyStep3.html b/src/main/resources/templates/views/apps/mypage/apiKeyModifyStep3.html new file mode 100644 index 0000000..9cfd117 --- /dev/null +++ b/src/main/resources/templates/views/apps/mypage/apiKeyModifyStep3.html @@ -0,0 +1,125 @@ + + + + +
    +
    + +

    앱관리

    +
    +
    + +
    + + +
    +

    앱 수정

    +
    + +
    +
    + +
    +
    +
    + 앱 정보입력 +
    +
    +
    앱정보입력
    +
    + + +
    + +
    + + +
    +
    +
    + API 선택 +
    +
    +
    API 선택
    +
    + + +
    + +
    + + +
    +
    +
    + 앱 수정 완료 +
    +
    +
    앱 수정 완료
    +
    +
    +
    + + +
    + +
    + +
    +
    + 완료 +
    +
    + + +
    +

    앱 수정이 완료되었습니다.

    +

    + 담당자 승인 후 변경 사항이 적용됩니다. +
    + [앱 정보] + 화면에서 수정 내역을 확인할 수 있습니다. +

    +
    +
    + + + +
    + + + + +
    +
    + + + + + diff --git a/src/main/resources/templates/views/apps/mypage/apiKeyProdDetail.html b/src/main/resources/templates/views/apps/mypage/apiKeyProdDetail.html deleted file mode 100644 index fc38639..0000000 --- a/src/main/resources/templates/views/apps/mypage/apiKeyProdDetail.html +++ /dev/null @@ -1,165 +0,0 @@ - - - - API 키 상세 - - -
    -
    -
    -

    인증키 정보

    -
    - -
    - -
    - -
    -

    - 인증키 이름 -

    -
    -
    - - -
    -
    -
    - -
    -

    - 변경일 -

    -
    -
    - -
    -
    -
    - -
    -

    - 인증키 상태 -

    -
    -
    - -
    -
    -
    - -
    - -
    -

    API 정보

    -
    -
    - -
    - - - - - - - - - - - - - - - - - - - - - - - -
    No서비스API명비고
    14공통회원여부조회 -
    -
    - -
    - - - - - - - - - - - - - - - - - - - - - - - -
    No서비스API명비고
    14공통회원여부조회 -
    -
    -
    -
    - -
    -
    - 목록 -
    - -
    -
    -
    - -
    - - - - -
    - - - - - - diff --git a/src/main/resources/templates/views/apps/mypage/apiKeyProdList.html b/src/main/resources/templates/views/apps/mypage/apiKeyProdList.html deleted file mode 100644 index 8eabc6b..0000000 --- a/src/main/resources/templates/views/apps/mypage/apiKeyProdList.html +++ /dev/null @@ -1,89 +0,0 @@ - - - - -
    -
    -
    -

    인증키 목록

    -
    -
    -
    -
    - -
    -
    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    No이름인증키상태사용 API 수마지막수정일
    1 - 수신 - 활성화1
    -
    -
    -
    - -
    - -
    - -
    -
    -
    -
    - -
    -
    -
    -
    - - -
    - - - - - diff --git a/src/main/resources/templates/views/apps/mypage/apiKeyRegisterStep1.html b/src/main/resources/templates/views/apps/mypage/apiKeyRegisterStep1.html new file mode 100644 index 0000000..f33b4ea --- /dev/null +++ b/src/main/resources/templates/views/apps/mypage/apiKeyRegisterStep1.html @@ -0,0 +1,499 @@ + + + + +
    +
    + +

    앱관리

    +
    +
    + +
    + + +
    +

    앱 생성

    + API를 이용하여 서비스할 앱 정보를 등록해주세요. +
    + + +
    +
    + +
    +
    +
    + 앱 정보입력 +
    +
    +
    앱 정보입력
    +
    + + +
    + +
    + + +
    +
    +
    + API 선택 +
    +
    +
    API 선택
    +
    + + +
    + +
    + + +
    +
    +
    + 앱 생성 완료 +
    +
    +
    앱 생성 완료
    +
    +
    +
    + + +
    + + + + + + +
    + + +
    +
    + + +
    +
    + 앱 아이콘 +
    +
    +
    +
    +
    + + + + + + + + + +
    + + +
    +
    +

    앱 아이콘 이미지 파일을 업로드하세요

    +

    권장사이즈는 128X128픽셀이며 JPG, PNG, GIF파일만 등록할 수 있습니다.

    +
    + + +
    +
    +
    + + +
    +
    + 앱 이름 + 필수 +
    +
    + +
    +
    + + +
    +
    + 앱 설명 + 필수 +
    +
    + + + 0 / 500 + +
    +
    + + +
    +
    + Call Back URL +
    +
    + +
    +
    + + +
    +
    + 화이트리스트 +
    +
    +
    + + +
    + + + + + + +
    +
    + +
    +
    + + +
    + +
    + +
    +
    + + + + + + diff --git a/src/main/resources/templates/views/apps/mypage/apiKeyRegisterStep2.html b/src/main/resources/templates/views/apps/mypage/apiKeyRegisterStep2.html new file mode 100644 index 0000000..5c27b57 --- /dev/null +++ b/src/main/resources/templates/views/apps/mypage/apiKeyRegisterStep2.html @@ -0,0 +1,688 @@ + + + + +
    +
    + +

    앱관리

    +
    +
    + +
    + + +
    +

    앱 생성

    + API를 이용하여 서비스할 앱 정보를 등록해주세요. +
    + + +
    +
    + +
    +
    +
    + 앱 정보입력 +
    +
    +
    앱 정보입력
    +
    + + +
    + +
    + + +
    +
    +
    + API 선택 +
    +
    +
    API 선택
    +
    + + +
    + +
    + + +
    +
    +
    + 앱 생성 완료 +
    +
    +
    앱 생성 완료
    +
    +
    +
    + + +
    + + + + + + + + +
    + + +
    +
    + + +
    + + +
    + + +
    + +
    +
    +

    API 목록을 불러오는 중...

    +
    + + + + + + +
    + +
    +
    + +
    + + +
    + + +
    + +
    + + + + + +
    + + + + + + diff --git a/src/main/resources/templates/views/apps/mypage/apiKeyRegisterStep3.html b/src/main/resources/templates/views/apps/mypage/apiKeyRegisterStep3.html new file mode 100644 index 0000000..7e00c34 --- /dev/null +++ b/src/main/resources/templates/views/apps/mypage/apiKeyRegisterStep3.html @@ -0,0 +1,140 @@ + + + + +
    +
    + +

    앱관리

    +
    +
    + +
    + + +
    +

    앱 생성

    +
    + +
    +
    + +
    +
    +
    + 앱 정보입력 +
    +
    +
    앱정보입력
    +
    + + +
    + +
    + + +
    +
    +
    + API 선택 +
    +
    +
    API 선택
    +
    + + +
    + +
    + + +
    +
    +
    + 앱 생성 완료 +
    +
    +
    앱 생성 완료
    +
    +
    +
    + + +
    + +
    + +
    +
    + 완료 +
    +
    + + +
    +

    앱 생성이 완료되었습니다.

    +

    + 담당자 앱 승인 후 + [앱 정보] + 화면에서 +
    + Client ID와 Client Secret을 확인 + 할 수 있습니다. +

    +
    +
    + + + +
    + + + + +
    +
    + + + + + + diff --git a/src/main/resources/templates/views/apps/mypage/apiRequestProdDetail.html b/src/main/resources/templates/views/apps/mypage/apiRequestProdDetail.html deleted file mode 100644 index d3555c5..0000000 --- a/src/main/resources/templates/views/apps/mypage/apiRequestProdDetail.html +++ /dev/null @@ -1,191 +0,0 @@ - - - - API 요청 상세 - - -
    -
    -
    -

    운영 인증키 요청 정보

    -
    - -
    -
    -
    -

    - 이름 -

    -
    -
    - -
    -
    -
    -
    -

    - 유형 -

    -
    -
    - -
    -
    -
    -
    -

    - 상태 -

    -
    -
    - -
    -
    -
    -
    -

    - 사유 -

    -
    -
    - -
    -
    -
    -
    -

    - 요청일시 -

    -
    -
    - -
    -
    -
    -
    -
    -

    API 정보

    -
    - -
    - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    No서비스API명
    삭제
    -
    - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    No서비스API명
    삭제
    -
    -
    - -
    -
    - - -
    -
    -
    - 목록 -
    -
    -
    - - -
    - - - - diff --git a/src/main/resources/templates/views/apps/mypage/apiRequestProdHistory.html b/src/main/resources/templates/views/apps/mypage/apiRequestProdHistory.html deleted file mode 100644 index 47860d7..0000000 --- a/src/main/resources/templates/views/apps/mypage/apiRequestProdHistory.html +++ /dev/null @@ -1,114 +0,0 @@ - - - -
    -
    -
    -

    인증키 신청 목록

    -
    -
    -
    -
    - -
    -
    -
    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    No신청명신청상태신청일처리일자신청사유사용 API 수
    110
    -
    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    No신청명신청상태신청일신청사유사용 API 수
    1110
    -
    -
    -
    - -
    - -
    -
    -
    -
    - -
    -
    -
    - - - - - - diff --git a/src/main/resources/templates/views/apps/mypage/appRequestDetail.html b/src/main/resources/templates/views/apps/mypage/appRequestDetail.html new file mode 100644 index 0000000..bb8d81d --- /dev/null +++ b/src/main/resources/templates/views/apps/mypage/appRequestDetail.html @@ -0,0 +1,239 @@ + + + + API 키 신청 상세 + + +
    +
    + +

    앱관리

    +
    +
    + +
    + + +
    +

    신청 상세

    + 신청하신 API Key의 상세 정보를 확인할 수 있습니다. +
    + + +
    + +
    + 앱 아이콘 +
    + + + + + +
    +
    + +
    + + + 대기중 + + +

    앱 이름

    + +

    + 여기에 입력한 앱 설명이 표시됩니다. +

    +
    +
    + + +
    + + +
    + +
    +
    K00000001
    + +
    +
    + + +
    + +
    +
    + http://www.abcd.co.kr +
    +
    +
    + + +
    + +
    +
    + 10.0.0.1, 10.0.0.2, 192.168.123.1 +
    +
    +
    + + +
    + +
    +
    +
    + 추가된 API 1 + 승인대기 +
    + + +
    +

    선택된 API가 없습니다.

    +
    +
    +
    +
    + + +
    + +
    +
    + 2025.11.22 12:00 +
    +
    +
    + + +
    + +
    +
    + 2025.11.22 12:00 +
    +
    +
    + + +
    + +
    +
    + Approval reason +
    +
    +
    + +
    + + +
    + + + 목록 + +
    + +
    + +
    + + + + + + + diff --git a/src/main/resources/templates/views/apps/mypage/components/commonUserInfo.html b/src/main/resources/templates/views/apps/mypage/components/commonUserInfo.html index 751dcd4..ed43e21 100644 --- a/src/main/resources/templates/views/apps/mypage/components/commonUserInfo.html +++ b/src/main/resources/templates/views/apps/mypage/components/commonUserInfo.html @@ -2,133 +2,105 @@ -
    -

    - 이메일 아이디 -

    -
    -
    - - -
    +
    +
    + 이메일 아이디 +
    +
    + +
    -
    -

    - 성명 -

    -
    -
    - -
    +
    +
    + 성명 필수 +
    +
    +
    -
    -

    - 휴대폰 번호 -

    -
    -
    - +
    +
    + 휴대폰 번호 필수 +
    +
    +
    - - - - + - - - - - + - - -
    -
    - 변경
    +
    -