diff --git a/.github/ISSUE_TEMPLATE/bug-template.md b/.github/ISSUE_TEMPLATE/bug-template.md new file mode 100644 index 00000000..c8ff8e1d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-template.md @@ -0,0 +1,20 @@ +--- +name: Bug template +about: 버그 픽스 +title: '' +labels: '' +assignees: '' + +--- + +## 어떤 기능인가요? + +> 추가하려는 기능에 대해 간결하게 설명해주세요 + +## 작업 상세 내용 + +- [ ] TODO +- [ ] TODO +- [ ] TODO + +## 참고할만한 자료(선택) diff --git a/.github/ISSUE_TEMPLATE/feature-template.md b/.github/ISSUE_TEMPLATE/feature-template.md new file mode 100644 index 00000000..0bc8d430 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature-template.md @@ -0,0 +1,20 @@ +--- +name: Feature template +about: task 기능 +title: '' +labels: '' +assignees: '' + +--- + +## 어떤 기능인가요? + +> 추가하려는 기능에 대해 간결하게 설명해주세요 + +## 작업 상세 내용 + +- [ ] TODO +- [ ] TODO +- [ ] TODO + +## 참고할만한 자료(선택) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..c74c7aa7 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,15 @@ +## #️⃣연관된 이슈 + +> ex) #이슈번호, #이슈번호 + +## 📝작업 내용 + +> 이번 PR에서 작업한 내용을 간략히 설명해주세요(이미지 첨부 가능) + +### 스크린샷 (선택) + +## 💬리뷰 요구사항(선택) + +> 리뷰어가 특별히 봐주었으면 하는 부분이 있다면 작성해주세요 +> +> ex) 메서드 XXX의 이름을 더 잘 짓고 싶은데 혹시 좋은 명칭이 있을까요? \ No newline at end of file diff --git a/.github/workflows/be-cd.yml b/.github/workflows/be-cd.yml new file mode 100644 index 00000000..6e5c2970 --- /dev/null +++ b/.github/workflows/be-cd.yml @@ -0,0 +1,57 @@ +name: froxy BE Continuous Delivery + +on: + push: + branches: + - deploy + +jobs: + be-cd: + runs-on: ubuntu-20.04 # 빌드가 진행될 환경 설정 + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Setup Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Login to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKER_USERNAME }} # DockerHub 사용자 이름 + password: ${{ secrets.DOCKER_PASSWORD }} # DockerHub 비밀번호 + + #이미지 이름 저장 + - name: Set Docker image tag as a variable + run: echo "DOCKER_TAG=${{ secrets.DOCKER_USERNAME }}/froxy-server:latest" >> $GITHUB_ENV + + - name: Create .env file + run: | + echo "${{ secrets.BE_ENV }}" > ./apps/backend/.env + + - name: Build Docker image + run: | + docker build -t $DOCKER_TAG . + + - name: Push Docker image to Docker Hub + run: | + docker push $DOCKER_TAG + + - name: Setup SSH + uses: webfactory/ssh-agent@v0.5.3 + with: + ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }} + + - name: Deploy + run: | + ssh -o StrictHostKeyChecking=no mun@211.188.48.24 " + if [ \$(docker ps -a -q -f name=froxy-container) ]; then + docker stop froxy-container + docker rm froxy-container + fi + docker pull ${{ secrets.DOCKER_USERNAME }}/froxy-server:latest && \ + docker run --network host -d --name froxy-container -v /var/run/docker.sock:/var/run/docker.sock ${{ secrets.DOCKER_USERNAME }}/froxy-server:latest + docker image prune -f + " + diff --git a/.github/workflows/be-ci.yml b/.github/workflows/be-ci.yml new file mode 100644 index 00000000..297c12cf --- /dev/null +++ b/.github/workflows/be-ci.yml @@ -0,0 +1,49 @@ +name: Froxy-BE Continuous Integration + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + be-ci: + runs-on: ubuntu-20.04 + if: contains(github.event.pull_request.labels.*.name, '💻 Be') + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Setup pnpm + uses: pnpm/action-setup@v2 + with: + version: 9 + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: '20' # 적절한 Node.js 버전으로 설정 + cache: 'pnpm' # pnpm 캐싱 활성화 + + - name: Create .env file + run: | + echo "${{ secrets.BE_ENV }}" > ./apps/backend/.env + + - name: Install dependencies #라이브러리설치 + run: | + pnpm install --no-frozen-lockfile + + - name: Run lint & Turbo build + run: | + pnpm lint --filter=backend + pnpm turbo run build --filter=backend + #FE CI : pnpm turbo run build --filter=frontend + #전역 CI : pnpm turbo run build + + #테스트코드 빌드는 테스트코드를 추가할때 사용 + #- name: Run Turbo tests + # run: | + # pnpm turbo run test --filter=be diff --git a/.github/workflows/fe-cd.yml b/.github/workflows/fe-cd.yml new file mode 100644 index 00000000..6c88dd99 --- /dev/null +++ b/.github/workflows/fe-cd.yml @@ -0,0 +1,50 @@ +name: Froxy-FE Continuous Integration + +on: + push: + branches: + - deploy + +jobs: + fe-cd: + runs-on: ubuntu-20.04 + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Setup pnpm + uses: pnpm/action-setup@v2 + with: + version: 9 + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: '20' # 적절한 Node.js 버전으로 설정 + cache: 'pnpm' # pnpm 캐싱 활성화 + + - name: Create .env file + run: | + echo "${{ secrets.FE_ENV }}" > apps/frontend/.env + + - name: Install dependencies #라이브러리설치 + run: | + pnpm install --no-frozen-lockfile + + - name: Run lint & Turbo build + run: | + pnpm lint --filter=frontend + pnpm turbo run build --filter=frontend + + - name: Upload to Object Storage + env: + AWS_ACCESS_KEY_ID: ${{ secrets.NCP_ACCESS_NAME }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.NCP_ACCESS_SECRET }} + AWS_DEFAULT_REGION: 'kr-standard' # 리전 설정 + run: | + # AWS CLI 설치 확인 및 설정 + sudo apt-get update && sudo apt-get install -y awscli + + # 빌드된 정적 파일을 오브젝트 스토리지로 업로드 + aws s3 sync ./apps/frontend/dist s3://froxy-fe --endpoint-url=https://kr.object.ncloudstorage.com --acl public-read diff --git a/.github/workflows/fe-ci.yml b/.github/workflows/fe-ci.yml new file mode 100644 index 00000000..46c88dbb --- /dev/null +++ b/.github/workflows/fe-ci.yml @@ -0,0 +1,42 @@ +name: Froxy-CI Continuous Integration + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + fe-ci: + runs-on: ubuntu-20.04 + if: contains(github.event.pull_request.labels.*.name, '💻 Fe') + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Setup pnpm + uses: pnpm/action-setup@v2 + with: + version: 9 + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: '20' # 적절한 Node.js 버전으로 설정 + cache: 'pnpm' # pnpm 캐싱 활성화 + + - name: Create .env file + run: | + echo "${{ secrets.FE_ENV }}" > apps/frontend/.env + + - name: Install dependencies #라이브러리설치 + run: | + pnpm install --no-frozen-lockfile + + - name: Run lint & Turbo build + run: | + pnpm lint --filter=frontend + pnpm turbo run build --filter=frontend diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..da035b4d --- /dev/null +++ b/.gitignore @@ -0,0 +1,43 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# Dependencies +node_modules +.pnp +.pnp.js + +# Local env files +.server.env +.env +.env.local +.env.development.local +.env.test.local +.env.production.local + +# Testing +coverage + +# Turbo +.turbo + +# Vercel +.vercel + +# Build Outputs +.next/ +out/ +build +dist + + +# Debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Misc +.DS_Store +*.pem + + +#docker +mysql-data/* \ No newline at end of file diff --git a/.husky/commit-msg b/.husky/commit-msg new file mode 100644 index 00000000..7f460e1c --- /dev/null +++ b/.husky/commit-msg @@ -0,0 +1,27 @@ +#!/bin/sh + +# 브랜치 이름과 커밋 메시지 파일 가져오기 +BRANCH_NAME=$(git rev-parse --abbrev-ref HEAD) +COMMIT_FILE=$1 + +# 브랜치 이름에서 커밋 번호 추출 +if [[ $BRANCH_NAME =~ [Ff]eature-#([0-9]+) ]]; then + COMMIT_NUMBER=${BASH_REMATCH[1]} + + # 기존 커밋 메시지 불러오기 + MESSAGE=$(cat "$COMMIT_FILE") + + # 커밋 메시지에서 타입과 나머지 내용 추출 (예: Chore: 환경설정) + if [[ $MESSAGE =~ ^([A-Za-z]+):\s*(.*)$ ]]; then + COMMIT_TYPE=${BASH_REMATCH[1]} + COMMIT_CONTENT=${BASH_REMATCH[2]} + + # 커밋 번호가 메시지에 없는 경우 추가 + if [[ ! $COMMIT_CONTENT =~ \(#$COMMIT_NUMBER\) ]]; then + echo "$COMMIT_TYPE(#$COMMIT_NUMBER): $COMMIT_CONTENT" > "$COMMIT_FILE" + fi + fi +fi + +# 커밋 메시지 유효성 검사 +npx --no-install commitlint --edit "$1" \ No newline at end of file diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 00000000..d0a77842 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1 @@ +npx lint-staged \ No newline at end of file diff --git a/.husky/pre-push b/.husky/pre-push new file mode 100644 index 00000000..d0a77842 --- /dev/null +++ b/.husky/pre-push @@ -0,0 +1 @@ +npx lint-staged \ No newline at end of file diff --git a/.npmrc b/.npmrc new file mode 100644 index 00000000..e69de29b diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 00000000..2c886e57 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,10 @@ +{ + "singleQuote": true, + "trailingComma": "none", + "semi": true, + "tabWidth": 2, + "printWidth": 120, + "arrowParens": "always", + "bracketSpacing": true, + "endOfLine": "auto" +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..50630c3a --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,10 @@ +{ + "eslint.workingDirectories": [ + { + "mode": "auto" + } + ], + "editor.codeActionsOnSave": { + "source.fixAll.eslint": "always" + } +} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..6a93de80 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,18 @@ +FROM node:20 + +WORKDIR /app + +COPY package*.json ./ +RUN npm install -g pnpm + +COPY . . + +RUN pnpm install + +RUN pnpm lint --filter=backend && pnpm turbo run build --filter=backend + +WORKDIR /app/apps/backend +CMD ["pnpm", "run", "start:prod"] + +# 외부에서 접근할 수 있도록 포트 노출 +EXPOSE 3000 diff --git a/README.md b/README.md new file mode 100644 index 00000000..d76dec85 --- /dev/null +++ b/README.md @@ -0,0 +1,331 @@ +
+ +

Froxy

+ +Portfolio UI Kit Cover + +
빠르고 간편하게 코드를 실행하세요 🐸
+ +
+ +> Froxy는 개구리를 뜻하는 ‘Frog’와 ‘Proxy’의 합성어로, 사용자가 직접 코드를 실행하지 않고도 결과를 빠르게 확인할 수 있는 서비스입니다. 🐸💻
+> +> Gist에서 코드를 복제하고 실행 환경을 설정하는 번거로움 없이, Froxy와 함께라면 폴짝! 뛰어넘어 간편하게 코드를 실행하고 결과를 확인할 수 있습니다. 다양한 기능을 통해 코드를 테스트하고 실행 결과를 즉시 확인해 보세요! + +
+ +
+ +

+지금 코드 실행하러 가기 +

+ +[팀 노션](https://freckle-calliandra-79a.notion.site/Team38-F-Rog-12d9038c617380509fbdf4eb928e4238) +| +[팀 피그마](https://camo.githubusercontent.com/8a61ef97622df78c36d2ac0c400be9d154e0a756137e6752117de9bc1a78660a/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4669676d612d4632344531453f7374796c653d666f722d7468652d6261646765266c6f676f3d6669676d61266c6f676f436f6c6f723d7768697465) +| +[팀 피그잼](https://www.figma.com/board/NYv2EBl18ZcY9sxcYuqn9M/%ED%8C%80-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EB%B3%B4%EB%93%9C!?node-id=0-1&node-type=canvas&t=USILPXIX2R8atRyd-0) +| +[개발 위키](https://freckle-calliandra-79a.notion.site/12d9038c6173807297b4d21e68b642c8) + +
+ +
+

목차

+ +- [주제 선정 동기](https://github.com/boostcampwm-2024/web38-Froxy#%EF%B8%8F-%EC%A3%BC%EC%A0%9C-%EC%84%A0%EC%A0%95-%EB%8F%99%EA%B8%B0) +- [주요 기능](https://github.com/boostcampwm-2024/web38-Froxy#%EC%A3%BC%EC%9A%94-%EA%B8%B0%EB%8A%A5) + - [Gist 코드를 빠르게 게시하기](https://github.com/boostcampwm-2024/web38-Froxy#gist-%EC%BD%94%EB%93%9C%EB%A5%BC-%EB%B9%A0%EB%A5%B4%EA%B2%8C-%EA%B2%8C%EC%8B%9C%ED%95%98%EA%B8%B0) + - [다른 사람의 Gist 확인하기](https://github.com/boostcampwm-2024/web38-Froxy#%EB%8B%A4%EB%A5%B8-%EC%82%AC%EB%9E%8C%EC%9D%98-gist-%ED%99%95%EC%9D%B8%ED%95%98%EA%B8%B0) + - [Gist 코드를 빠르게 실행하기](https://github.com/boostcampwm-2024/web38-Froxy#gist-%EC%BD%94%EB%93%9C%EB%A5%BC-%EB%B9%A0%EB%A5%B4%EA%B2%8C-%EC%8B%A4%ED%96%89%ED%95%98%EA%B8%B0) +- [FE 기술적 도전](https://github.com/boostcampwm-2024/web38-Froxy#fe-%EA%B8%B0%EC%88%A0%EC%A0%81-%EB%8F%84%EC%A0%84) + + - [QueryKey Factory로 QueryKey 구조화 하기](https://github.com/boostcampwm-2024/web38-Froxy#querykey-factory%EB%A1%9C-querykey-%EA%B5%AC%EC%A1%B0%ED%99%94-%ED%95%98%EA%B8%B0) + - [계층화와 도메인 모델](https://github.com/boostcampwm-2024/web38-Froxy#%EA%B3%84%EC%B8%B5%ED%99%94%EC%99%80-%EB%8F%84%EB%A9%94%EC%9D%B8-%EB%AA%A8%EB%8D%B8) + - [MSW로 개발 효율화하기](https://github.com/boostcampwm-2024/web38-Froxy#msw%EB%A1%9C-%EA%B0%9C%EB%B0%9C-%ED%9A%A8%EC%9C%A8%ED%99%94%ED%95%98%EA%B8%B0) + - [Suspense, ErrorBoundary로 Fallback 구현하기](https://github.com/boostcampwm-2024/web38-Froxy#suspense-errorboundary%EB%A1%9C-fallback-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0) + +- [BE 기술적 도전](https://github.com/boostcampwm-2024/web38-Froxy#be-%EA%B8%B0%EC%88%A0%EC%A0%81-%EB%8F%84%EC%A0%84) + + - [docker를 이용한 코드 실행](https://github.com/boostcampwm-2024/web38-Froxy#docker%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EC%BD%94%EB%93%9C-%EC%8B%A4%ED%96%89) + - [queue & pool을 이용한 스케줄링](https://github.com/boostcampwm-2024/web38-Froxy#queue--pool%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EC%8A%A4%EC%BC%80%EC%A4%84%EB%A7%81) + - [Octokit 클라이언트 대신 직접 Gist API 모듈화](https://github.com/boostcampwm-2024/web38-Froxy#octokit-%ED%81%B4%EB%9D%BC%EC%9D%B4%EC%96%B8%ED%8A%B8-%EB%8C%80%EC%8B%A0-%EC%A7%81%EC%A0%91-gist-api-%EB%AA%A8%EB%93%88%ED%99%94) + - [TypeORM을 통한 다대다 테이블 관리](https://github.com/boostcampwm-2024/web38-Froxy#typeorm%EC%9D%84-%ED%86%B5%ED%95%9C-%EB%8B%A4%EB%8C%80%EB%8B%A4-%ED%85%8C%EC%9D%B4%EB%B8%94-%EA%B4%80%EB%A6%AC) + +- [기술 스택](https://github.com/boostcampwm-2024/web38-Froxy#%EA%B8%B0%EC%88%A0-%EC%8A%A4%ED%83%9D) +- [프로젝트 아키텍처](https://github.com/boostcampwm-2024/web38-Froxy#-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98) +- [프로젝트 Flow](https://github.com/boostcampwm-2024/web38-Froxy#%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-flow) +- [팀원](https://github.com/boostcampwm-2024/web38-Froxy#-%ED%8C%80%EC%9B%90) + +
+ +## ⭐️ 주제 선정 동기 + +부스트캠프 챌린지 과정에서는 매일 도전 과제를 수행하며, 동시에 그룹원들의 과제를 Gist를 통해 확인하고 피드백하는 시간을 가집니다. + +이 과정에서 캠퍼들은 Gist 경로를 찾아 로컬 환경에 클론(clone)한 뒤 실행하는 일련의 과정을 반복하게 되며, 특히 익숙하지 않은 도구를 사용하면서 이를 수행할 때 여러 가지 개발 환경 문제를 겪게 됩니다. + +Froxy는 이러한 반복적인 코드 확인과 실행 과정을 보다 간편하고 효율적으로 지원하기 위해 만들었습니다. Froxy는 캠퍼들이 실행 환경에서 발생하는 문제를 줄이고, 코드 리뷰와 피드백에 더 집중할 수 있는 환경을 제공합니다. 이를 통해 캠퍼들이 과제 수행과 코드 리뷰에 있어 생산성을 높이고, 학습 과정에 더 몰입할 수 있도록 돕는 것이 Froxy의 목표입니다. + + + + + + +
+ image +
검색하기 어려운 표
+
+ image +
그룹원을 태그나 제목으로 검색
+
+ +## 🐸 주요 기능 + +### Gist 코드를 빠르게 게시하기 + +> 사용자의 Gist 코드를 쉽고 빠르게 게시할 수 있게 만들었습니다. + +![생성](https://github.com/user-attachments/assets/deee5eb7-d79b-46fd-8f20-f3ab823f55ba) + +### 다른 사람의 Gist 확인하기 + +> 다른 캠퍼가 올린 Gist를 빠르게 찾을 수 있습니다. + +![목록](https://github.com/user-attachments/assets/90b33f0b-4ee5-481e-bfd7-2c0819d9be4d) + +### Gist 코드를 빠르게 실행하기 + +> 코드의 입력값 제공해 빠르게 실행할 수 있습니다. + +![실행](https://github.com/user-attachments/assets/4b09df35-2d04-4976-bf82-d282e4a7a794) + +## 🧑🏻‍💻 FE 기술적 도전 + +Froxy의 프론트엔드에서는 서버 데이터를 효과적으로 UI로 전달하는 것이 핵심 과제였습니다. 이를 위해 아래와 같은 기술적 도전에 집중했습니다. + +### QueryKey Factory로 QueryKey 구조화 하기 + +> [Effective QueryKey](https://github.com/boostcampwm-2024/web38-Froxy/wiki/%5B%EB%AF%BC%EC%9A%B0%5DEffective-Query-Key) + +- Tanstack Query를 사용해 서버상태를 관리하고 있었기 때문에 각 도메인마다 **구조화된 QueryKey가** 필요했습니다. +- 이를 위해, API 계층에서 사용하는 fetch 함수를 인자로 받아 **QueryKey와 QueryFunction을 함께 동적으로 생성하는 QueryKey Factory를 구현**했습니다. +- 이를 통해 프론트 코드 전역에서 **구조화된 QueryOption들을 선언적으로 재사용**할 수 있었습니다. + +### 계층화와 도메인 모델 + +> [나도 써 본 잘알려진 UI 패턴(프론트엔드 계층화, 도메인 모델 객체 활용)]() + +- 프론트엔드 코드의 유지보수성을 높이기 위해 계층화된 아키텍처를 도입했습니다. +- 각각의 계층(API, Query, Hook, UI)를 통해 추가 요구사항이 생겼을 때 필요한 코드를 **계층으로 분리해 독립적으로 개발**할 수 있었습니다. +- 또한 **도메인 모델**을 설계해 각각에 도메인에 필요한 **비즈니스 로직을 캡슐화**해 UI에서 불필요한 비즈니스 로직을 선언하지 않고 UI로직만 관리할 수 있도록 했습니다. + +```mermaid +sequenceDiagram + participant UI + participant Hooks + participant Query + participant API + + UI->>Hooks: useCustomHook 호출 + Hooks->>Query: 데이터 쿼리 요청 (fetch or mutate) + alt Cache HIT + Query->>Query: 캐시 데이터 반환 + Query-->>Hooks: 도메인 Model과 쿼리 옵션 전달 + else Cache MISS + Query->>API: HTTP 요청 전송 (Axios, fetch 등) + API-->>Query: 응답 데이터 반환 후 Model로 래핑 + Query-->>Hooks: 도메인 Model과 쿼리 옵션 전달 + end + Hooks-->>UI: 도메인 Model과 상태 전달 + UI->>UI: UI 업데이트 +``` + +### MSW로 개발 효율화하기 + +> [MSW로 개발 효율화하기](https://github.com/boostcampwm-2024/web38-Froxy/wiki/MSW) + +- **MSW**를 이용해서 백엔드의 API 개발이 완료되지 않은 상태에서 **API 호출과 관련된 시나리오를 테스트**할 수 있었습니다. +- MockRepository를 이용해 요청 조건에 따라 데이터를 **동적으로 응답**하도록 구현했습니다. +- 이를 통해 인증이 필요한 요청에 대한 로직이나 tanstack query를 미리 적용해보고 테스트해볼 수 있어서 나중에 실제 API를 연결할 때 빠르게 진행할 수 있었습니다. + +### Suspense, ErrorBoundary로 Fallback 구현하기 + +> [Suspense, ErrorBoundary로 Fallback 구현하기](https://github.com/boostcampwm-2024/web38-Froxy/wiki/Suspense+ErrorBoundary) + + + + + + + + +
+ 로딩 시 Fallback UI +
+ 로딩 시 Fallback UI +
+ 에러 시 Fallback UI +
+ 에러 시 Fallback UI +
+ +- 리액트를 선언적으로 사용하기 위해서 **로딩 상태는 Suspense**가, **에러 상태는 ErrorBoundary**가 관리하도록 역할을 분리하고자 했습니다. +- Layout Shift 문제 방지와 사용자 경험 개선을 위해서 **Skeleton UI**를 사용해서 로딩 시 fallback UI를 구현했습니다. +- ErrorBoundary와 tanstack query의 **useQueryErrorResetBoundary()** 훅을 이용해서 오류 발생 시 **재시도**가 가능하도록 에러 fallback UI를 구현했습니다. + +## 🧑🏻‍💻 BE 기술적 도전 + +### docker를 이용한 코드 실행 + +> [dockerode를 이용한 컨테이너 관리](https://github.com/boostcampwm-2024/web38-Froxy/wiki/Dockerode%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88-%EC%82%AC%EC%9A%A9) + +- 쉘을 이용한 방법보다는 nest에서 직접 docker를 관리하고자 했습니다. +- git clone, image build보다 빠른 속도를 위해 컨테이너에 직접 파일을 parsing, 삽입하도록 구현했습니다. +- 컨테이너와 소켓을 통해 입출력 결과를 반환할 수 있습니다. + +### queue & pool을 이용한 스케줄링 + +> [Redis-queue를 이용한 컨테이너 스케줄링](https://github.com/boostcampwm-2024/web38-Froxy/wiki/redis%E2%80%90queue%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88-%EC%8A%A4%EC%BC%80%EC%A4%84%EB%A7%81) + +- queue를 이용해 요청이 순차적으로 처리되도록 했습니다. +- container pool을 직접 관리하여 리소스 사용률을 감소시켰습니다. + +```mermaid +graph LR + API[API 요청] -->|요청 추가| RedisQueue[Redis Queue] + RedisQueue -->|작업 요청| DockerService[Docker 서비스] + + DockerService -->|컨테이너 요청| Pool[컨테이너 Pool] + Pool -->|할당된 컨테이너| Container[컨테이너] + Container -->|작업 실행| Task[js 실행] + Task -->|결과 반환| Container + Container -->|컨테이너 반납| Pool + Container -->|작업 결과| Response[API 응답] + DockerService -->|결과 반환| RedisQueue + + +``` + +### Octokit 클라이언트 대신 직접 Gist API 모듈화 + +> [사용자 지정 Gist API 파싱 모듈](https://github.com/boostcampwm-2024/web38-Froxy/wiki/%EC%82%AC%EC%9A%A9%EC%9E%90-%EC%A7%80%EC%A0%95-gist-api-%ED%8C%8C%EC%8B%B1-%EB%AA%A8%EB%93%88) + +- GitHub 공식 client Octokit 모듈 사용이 nest와의 호환성 문제로 인하여 REST API를 통해 모듈을 직접 구현했습니다. +- 이때 type-safe한 환경을 만들기 위해서 응답과 요청에 대한 type 추론이 가능하도록 설계했습니다. +- 이는TypeScript의 장점을 최대한 활용할 수 있도록 dto로 파싱하여 응답을 필터링하고 유효성 검사를 할 수 있게 되었습니다. + +### TypeORM을 통한 다대다 테이블 관리 + +> [typeORM 다대다 테이블 트러블 슈팅](https://github.com/boostcampwm-2024/web38-Froxy/wiki/typeORM-%EB%8B%A4%EB%8C%80%EB%8B%A4-%EC%86%8D%EC%84%B1-%EB%8D%B0%EC%9D%B4%ED%84%B0-%ED%8A%B8%EB%9F%AC%EB%B8%94-%EC%8A%88%ED%8C%85) + +- update 함수를 쓰는 과정에서 **다대다 관계**의 데이터는 관계 테이블을 통해 연결되기 때문에, 직접 필드로 접근해 조건을 걸 수 없는 문제를 발견했습니다. +- 다대다 관계를 사용하는 repository에서 update 사용 시, 쉽게 보이는 오류이며 save로 임시로 오류를 막았으나 find 및 save로 인한 원자성 해침을 막기 위한 방침이 필요했습니다. +- 또한 tag 미사용 시 데이터를 자동으로 삭제하는 기능을 위해서, tag-lotus relation 테이블을 추가하여 one-many 관계로 분리하였습니다. + +## 🔧 기술 스택 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
분류기술 스택
+

공통

+
+ +
+

프론트엔드

+
+ + + + + +
+

백엔드

+
+ + + + + +
+

패키지 매니저

+
+ + +
+

배포

+
+ + + + +
+

협업

+
+ + + +
+ +## 🔧 프로젝트 아키텍처 + +![image](https://github.com/user-attachments/assets/3b99d8b7-84e7-4555-a397-25757a067f2e) + +## 🔧 프로젝트 flow + +![flow](https://github.com/user-attachments/assets/49847a33-5851-4e6c-b109-689156b1a32f) + +## 👥 팀원 + + + + + + + + + + + + + + + + + + + + + +
김민우이나경김현지문준호
김민우이나경김현지문준호
FEFEBEBE
diff --git a/apps/frontend/.gitignore b/apps/frontend/.gitignore new file mode 100644 index 00000000..b88c8135 --- /dev/null +++ b/apps/frontend/.gitignore @@ -0,0 +1,28 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ diff --git a/apps/frontend/README.md b/apps/frontend/README.md new file mode 100644 index 00000000..15ad2cd0 --- /dev/null +++ b/apps/frontend/README.md @@ -0,0 +1 @@ +# 프론트엔드 diff --git a/apps/frontend/eslint.config.js b/apps/frontend/eslint.config.js new file mode 100644 index 00000000..e45b5497 --- /dev/null +++ b/apps/frontend/eslint.config.js @@ -0,0 +1,106 @@ +import js from '@eslint/js'; +import globals from 'globals'; +import reactHooks from 'eslint-plugin-react-hooks'; +import reactRefresh from 'eslint-plugin-react-refresh'; +import tseslint from 'typescript-eslint'; +import eslintImport from 'eslint-plugin-import'; +import prettierPlugin from 'eslint-plugin-prettier'; +import noRelativeImportPathsPlugin from 'eslint-plugin-no-relative-import-paths'; + +export default tseslint.config( + { ignores: ['dist'] }, + { + extends: [js.configs.recommended, ...tseslint.configs.recommended], + files: ['**/*.{ts,tsx}'], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser + }, + plugins: { + 'react-hooks': reactHooks, + 'react-refresh': reactRefresh, + 'no-relative-import-paths': noRelativeImportPathsPlugin, + import: eslintImport, + prettier: prettierPlugin + }, + rules: { + ...reactHooks.configs.recommended.rules, + 'prettier/prettier': 'error', // Prettier 규칙을 ESLint에서 에러로 표시 + '@typescript-eslint/no-unnecessary-type-constraint': 'off', + '@typescript-eslint/no-explicit-any': 'off', + 'import/order': [ + 'error', + { + groups: [ + ['builtin', 'external'], // 내장 모듈과 외부 모듈 그룹 + ['internal', 'parent', 'sibling', 'index'] // 내부 모듈 그룹 + ], + pathGroups: [ + { + pattern: 'react', + group: 'builtin', + position: 'before' // react를 최상위에 오도록 설정 + }, + { + pattern: 'react-dom', + group: 'builtin', + position: 'before' + } + ], + pathGroupsExcludedImportTypes: ['builtin'], + alphabetize: { order: 'asc', caseInsensitive: true } // 알파벳 순 정렬 + } + ], + 'sort-imports': [ + 'error', + { + ignoreDeclarationSort: true, // `import` 자체의 정렬은 무시 + ignoreMemberSort: false // 세부 항목 정렬은 적용 + } + ], + 'no-relative-import-paths/no-relative-import-paths': [ + 'warn', + { allowSameFolder: true, rootDir: 'src', prefix: '@' } + ], + '@typescript-eslint/naming-convention': [ + 'warn', + { + selector: 'variable', + format: ['camelCase', 'PascalCase', 'UPPER_CASE'] + }, // 변수명은 camelCase, PascalCase, UPPER_CASE 형식 중 하나여야 함 + { + selector: 'function', + format: ['camelCase', 'PascalCase'] + }, // 함수명은 camelCase, PascalCase 형식 중 하나여야 함 + { + selector: 'typeLike', + format: ['PascalCase'] + }, // 타입명은 PascalCase 형식이어야 함 + { + selector: 'interface', + format: ['PascalCase'], + custom: { + regex: '^I[A-Z]', + match: false + } + }, // 인터페이스명은 PascalCase이고 I로 시작하면 안됨 + { + selector: 'typeAlias', + format: ['PascalCase'], + custom: { + regex: '^T[A-Z]', + match: false + } + }, // 타입 별칭명은 PascalCase이고 T로 시작하면 안됨 + { + selector: 'typeParameter', + format: ['PascalCase'], + custom: { + regex: '^T[A-Z]', + match: false + } + } // 타입 매개변수명은 PascalCase이고 T로 시작하면 안됨 + ] + } + } +); diff --git a/apps/frontend/index.html b/apps/frontend/index.html new file mode 100644 index 00000000..1076b4b8 --- /dev/null +++ b/apps/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + Froxy + + +
+ + + diff --git a/apps/frontend/package.json b/apps/frontend/package.json new file mode 100644 index 00000000..06de9472 --- /dev/null +++ b/apps/frontend/package.json @@ -0,0 +1,76 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.1", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "eslint .", + "preview": "vite preview", + "test": "vitest", + "test:e2e": "playwright test", + "test:e2e:ui": "playwright test --ui", + "test:e2e:debug": "playwright test --debug", + "test:e2e:report": "playwright show-report" + }, + "lint-staged": { + "*.{ts,tsx}": [ + "eslint --fix", + "prettier --write" + ] + }, + "dependencies": { + "@froxy/design": "workspace:^", + "@froxy/react-markdown": "workspace:^", + "@hookform/resolvers": "^3.9.1", + "@lottiefiles/dotlottie-react": "^0.10.0", + "@tanstack/react-query": "^5.59.19", + "@tanstack/react-router": "^1.78.3", + "axios": "^1.7.7", + "framer-motion": "^11.11.11", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-hook-form": "^7.53.2", + "react-icons": "^5.3.0", + "zod": "^3.23.8" + }, + "devDependencies": { + "@eslint/js": "^9.13.0", + "@playwright/test": "^1.49.0", + "@tanstack/react-query-devtools": "^5.59.19", + "@tanstack/router-devtools": "^1.78.3", + "@tanstack/router-plugin": "^1.78.3", + "@testing-library/jest-dom": "^6.6.3", + "@testing-library/react": "^16.0.1", + "@types/node": "^20.3.1", + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", + "@typescript-eslint/parser": "^5.59.11", + "@vitejs/plugin-react-swc": "^3.5.0", + "autoprefixer": "^10.4.20", + "eslint": "^9.13.0", + "eslint-config-prettier": "^8.8.0", + "eslint-plugin-import": "^2.31.0", + "eslint-plugin-no-relative-import-paths": "^1.5.5", + "eslint-plugin-prettier": "^4.2.1", + "eslint-plugin-react-hooks": "^5.0.0", + "eslint-plugin-react-refresh": "^0.4.14", + "globals": "^15.11.0", + "jsdom": "^25.0.1", + "msw": "^2.6.4", + "postcss": "^8.4.47", + "prettier": "2.x", + "tailwindcss": "^3.4.14", + "typescript": "~5.6.2", + "typescript-eslint": "^8.11.0", + "vite": "^5.4.10", + "vite-tsconfig-paths": "^5.0.1", + "vitest": "^2.1.4" + }, + "msw": { + "workerDirectory": [ + "public" + ] + } +} diff --git a/apps/frontend/playwright.config.ts b/apps/frontend/playwright.config.ts new file mode 100644 index 00000000..9e21df9e --- /dev/null +++ b/apps/frontend/playwright.config.ts @@ -0,0 +1,35 @@ +import { defineConfig, devices } from '@playwright/test'; + +export default defineConfig({ + testDir: './src/app/test/e2e', + fullyParallel: true, + reporter: 'html', + use: { + baseURL: 'http://localhost:5173', + trace: 'on-first-retry' + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] } + }, + + { + name: 'firefox', + use: { ...devices['Desktop Firefox'] } + }, + + { + name: 'webkit', + use: { ...devices['Desktop Safari'] } + } + ], + + /* Run your local dev server before starting the tests */ + webServer: { + command: 'pnpm dev', + url: 'http://localhost:5173' + } +}); diff --git a/apps/frontend/postcss.config.js b/apps/frontend/postcss.config.js new file mode 100644 index 00000000..2e7af2b7 --- /dev/null +++ b/apps/frontend/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/apps/frontend/public/android-chrome-192x192.png b/apps/frontend/public/android-chrome-192x192.png new file mode 100644 index 00000000..a57301a2 Binary files /dev/null and b/apps/frontend/public/android-chrome-192x192.png differ diff --git a/apps/frontend/public/android-chrome-512x512.png b/apps/frontend/public/android-chrome-512x512.png new file mode 100644 index 00000000..e827c926 Binary files /dev/null and b/apps/frontend/public/android-chrome-512x512.png differ diff --git a/apps/frontend/public/apple-touch-icon.png b/apps/frontend/public/apple-touch-icon.png new file mode 100644 index 00000000..42f9e079 Binary files /dev/null and b/apps/frontend/public/apple-touch-icon.png differ diff --git a/apps/frontend/public/favicon-16x16.png b/apps/frontend/public/favicon-16x16.png new file mode 100644 index 00000000..c96c3be4 Binary files /dev/null and b/apps/frontend/public/favicon-16x16.png differ diff --git a/apps/frontend/public/favicon-32x32.png b/apps/frontend/public/favicon-32x32.png new file mode 100644 index 00000000..6401c30e Binary files /dev/null and b/apps/frontend/public/favicon-32x32.png differ diff --git a/apps/frontend/public/favicon.ico b/apps/frontend/public/favicon.ico new file mode 100644 index 00000000..d494de19 Binary files /dev/null and b/apps/frontend/public/favicon.ico differ diff --git a/apps/frontend/public/image/exampleImage.jpeg b/apps/frontend/public/image/exampleImage.jpeg new file mode 100644 index 00000000..3ef371bf Binary files /dev/null and b/apps/frontend/public/image/exampleImage.jpeg differ diff --git a/apps/frontend/public/image/logoIcon.svg b/apps/frontend/public/image/logoIcon.svg new file mode 100644 index 00000000..c96cd06b --- /dev/null +++ b/apps/frontend/public/image/logoIcon.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/apps/frontend/public/image/onboardingImage.svg b/apps/frontend/public/image/onboardingImage.svg new file mode 100644 index 00000000..6d460343 --- /dev/null +++ b/apps/frontend/public/image/onboardingImage.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/apps/frontend/public/json/404Animation.json b/apps/frontend/public/json/404Animation.json new file mode 100644 index 00000000..6125cef5 --- /dev/null +++ b/apps/frontend/public/json/404Animation.json @@ -0,0 +1 @@ +{"v":"5.5.7","meta":{"g":"LottieFiles AE 0.1.20","a":"","k":"","d":"","tc":""},"fr":29.9700012207031,"ip":0,"op":60.0000024438501,"w":320,"h":200,"nm":"404","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"mark Outlines","parent":2,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[19.9,19.295,0],"ix":2},"a":{"a":0,"k":[2.503,12.271,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.244,0],[0,0],[0,1.244],[-1.244,0],[0,-1.244]],"o":[[0,0],[-1.244,0],[0,-1.244],[1.244,0],[0,1.244]],"v":[[0,2.253],[0,2.253],[-2.253,-0.001],[0,-2.253],[2.253,-0.001]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[2.503,22.04],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.244,0],[0,0],[0,1.244],[0,0],[-1.244,0],[0,-1.244],[0,0]],"o":[[0,0],[-1.244,0],[0,0],[0,-1.244],[1.244,0],[0,0],[0,1.244]],"v":[[0,8.26],[0,8.26],[-2.253,6.007],[-2.253,-6.008],[0,-8.26],[2.253,-6.008],[2.253,6.007]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[2.503,8.51],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":60.0000024438501,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Layer 3 Outlines","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":14,"s":[0]},{"t":17.0000006924242,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":12,"s":[198.645,-21.677,0],"to":[0,13.167,0],"ti":[0,-13.167,0]},{"t":17.0000006924242,"s":[198.645,57.323,0]}],"ix":2,"x":"var $bm_rt;\nvar _0x9233 = [\n 'BOUNCr overSHOOT+',\n 'Amplitude',\n 'Frequency',\n 'Decay',\n 'Floor',\n 'index',\n 'time',\n 'frameDuration',\n 'PI',\n 'sin',\n 'exp',\n 'abs'\n ];\ntry {\n var effecto = effect(_0x9233[0]);\n var amp = $bm_div(effecto(_0x9233[1]), 1000);\n var freq = effecto(_0x9233[2]);\n var decay = effecto(_0x9233[3]);\n var floor = effecto(_0x9233[4]);\n var n, numkeys, v, t;\n if (floor != true) {\n $bm_rt = n = 0;\n if (numKeys > 0) {\n $bm_rt = n = nearestKey(time)[_0x9233[5]];\n if (key(n)[_0x9233[6]] > time) {\n n--;\n }\n }\n ;\n if (n == 0) {\n $bm_rt = t = 0;\n } else {\n $bm_rt = t = $bm_sub(time, key(n)[_0x9233[6]]);\n }\n ;\n if (n > 0) {\n v = velocityAtTime($bm_sub(key(n)[_0x9233[6]], $bm_div(thisComp[_0x9233[7]], 10)));\n $bm_rt = $bm_sum(value, $bm_mul($bm_mul(v, amp), $bm_div(Math[_0x9233[9]]($bm_mul($bm_mul($bm_mul(freq, t), 2), Math[_0x9233[8]])), Math[_0x9233[10]]($bm_mul(decay, t)))));\n } else {\n $bm_rt = value;\n }\n } else {\n $bm_rt = n = 0;\n if (numKeys > 0) {\n $bm_rt = n = nearestKey(time)[_0x9233[5]];\n if (key(n)[_0x9233[6]] > time) {\n n--;\n }\n }\n ;\n if (n == 0) {\n $bm_rt = t = 0;\n } else {\n $bm_rt = t = $bm_sub(time, key(n)[_0x9233[6]]);\n }\n ;\n if (n > 0) {\n v = velocityAtTime($bm_sub(key(n)[_0x9233[6]], $bm_div(thisComp[_0x9233[7]], 10)));\n $bm_rt = $bm_sum(value, $bm_mul($bm_mul(v, amp), $bm_neg($bm_div(Math[_0x9233[11]](Math[_0x9233[9]]($bm_mul($bm_mul($bm_mul(freq, t), 2), Math[_0x9233[8]]))), Math[_0x9233[10]]($bm_mul(decay, t))))));\n } else {\n $bm_rt = value;\n }\n }\n} catch (err) {\n $bm_rt = value;\n}"},"a":{"a":0,"k":[19.9,19.899,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"ef":[{"ty":5,"nm":"BOUNCr overSHOOT+","np":8,"mn":"Pseudo/pse bouncR overSHOOT+","ix":1,"en":1,"ef":[{"ty":0,"nm":"Amplitude","mn":"Pseudo/pse bouncR overSHOOT+-0001","ix":1,"v":{"a":0,"k":65,"ix":1}},{"ty":0,"nm":"Frequency","mn":"Pseudo/pse bouncR overSHOOT+-0002","ix":2,"v":{"a":0,"k":3,"ix":2}},{"ty":0,"nm":"Decay","mn":"Pseudo/pse bouncR overSHOOT+-0003","ix":3,"v":{"a":0,"k":6,"ix":3}},{"ty":0,"nm":"Floor","mn":"Pseudo/pse bouncR overSHOOT+-0004","ix":4,"v":{"a":0,"k":0,"ix":4}},{"ty":6,"nm":"ゥ2018 pixelbot - BOUNCr_v1.1","mn":"Pseudo/pse bouncR overSHOOT+-0005","ix":5,"v":0},{"ty":6,"nm":"BOUNCr overSHOOT+","mn":"Pseudo/pse bouncR overSHOOT+-0006","ix":6,"v":0}]}],"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-10.852],[10.852,0],[0,10.852],[-10.852,0]],"o":[[0,10.852],[-10.852,0],[0,-10.852],[10.852,0]],"v":[[19.65,0],[0.001,19.649],[-19.65,0],[0.001,-19.649]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"gf","o":{"a":0,"k":100,"ix":10},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0.949,0.345,0.306,0.5,0.969,0.249,0.269,1,0.99,0.153,0.232],"ix":9}},"s":{"a":0,"k":[0.919,-19.305],"ix":5},"e":{"a":0,"k":[1.461,19.954],"ix":6},"t":1,"nm":"Gradient Fill 1","mn":"ADBE Vector Graphic - G-Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[19.9,19.899],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":60.0000024438501,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"shadow Outlines","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":20,"s":[1]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":22,"s":[59.474]},{"t":34.0000013848484,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[192.874,67.919,0],"ix":2},"a":{"a":0,"k":[10.821,15.161,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.518,0.466],[0.611,2.446],[1.717,3.21],[3,2.924],[0.416,0.36],[0,-5.597],[-9.025,0]],"o":[[-0.422,-2.528],[-1.032,-4.229],[-1.743,-3.26],[-0.387,-0.377],[-4.29,2.946],[0,9.026],[1.671,0]],"v":[[10.572,14.192],[9.033,6.724],[4.888,-4.486],[-2.26,-13.806],[-3.464,-14.911],[-10.572,-1.432],[5.771,14.911]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0.286549407361,0.552295520259,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[10.821,15.161],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":60.0000024438501,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Layer 5 Outlines","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":17,"s":[160.035,100,0],"to":[0,0.917,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":19,"s":[160.035,105.5,0],"to":[0,0,0],"ti":[0,0.917,0]},{"t":22.0000008960784,"s":[160.035,100,0]}],"ix":2},"a":{"a":0,"k":[45.07,56.17,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0.946,-4.134],[1.675,-1.603],[2.103,0],[2.364,4.894],[0,12.516],[-1.917,4.368],[-3.475,0],[-1.795,-3.644],[0,-16.272]],"o":[[-0.862,3.772],[-1.554,1.484],[-2.009,0],[-2.094,-4.339],[0,-12.412],[1.605,-3.657],[3.443,0],[1.436,2.914],[0,8.997]],"v":[[9.154,20.45],[5.328,28.55],[-0.105,30.756],[-7.423,25.295],[-10.579,-0.105],[-7.691,-25.394],[-0.245,-30.756],[7.432,-25.414],[10.579,0.662]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[1.955,7.82],[1.717,3.21],[3,2.923],[4.787,1.84],[7.226,0],[7.565,-8.395],[0,-20.411],[-1.408,-6.709],[-2.646,-4.04],[-5.755,-2.978],[-9.056,0],[-6.851,9.387],[0,19.134]],"o":[[-1.032,-4.229],[-1.743,-3.26],[-3.071,-2.996],[-4.746,-1.823],[-15.154,0],[-7.59,8.422],[0,8.085],[1.443,6.876],[3.669,5.731],[5.711,2.956],[16.3,0],[6.636,-9.094],[0,-8.49]],"v":[[41.871,-25.357],[37.727,-36.567],[30.579,-45.886],[18.738,-53.173],[0.697,-55.92],[-33.54,-43.269],[-44.82,-0.42],[-42.697,21.875],[-36.527,38.34],[-22.324,51.465],[-0.07,55.92],[34.818,41.773],[44.82,-0.768]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0.090000002992,0.528999956916,0.933000033509,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[45.07,56.17],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":60.0000024438501,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Layer 6 Outlines","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[255.368,99.127,0],"ix":2},"a":{"a":0,"k":[47.967,55.297,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[4.087,7.857],[-13.799,7.857],[4.087,-13.19]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[1.657,0],[0,0],[0,0],[1.657,0],[0,0],[0.57,-0.677],[0,0],[0,-0.707],[0,0],[-1.657,0],[0,0],[0,0],[-1.657,0],[0,0],[0,1.658],[0,0],[0,0],[0,1.657],[0,0]],"o":[[0,0],[0,0],[0,-1.657],[0,0],[-0.886,0],[0,0],[-0.455,0.541],[0,0],[0,1.657],[0,0],[0,0],[0,1.658],[0,0],[1.657,0],[0,0],[0,0],[1.657,0],[0,0],[0,-1.657]],"v":[[44.716,7.857],[34.871,7.857],[34.871,-52.047],[31.871,-55.047],[7.087,-55.047],[4.79,-53.978],[-47.012,7.598],[-47.716,9.53],[-47.716,32.919],[-44.716,35.919],[4.087,35.919],[4.087,52.047],[7.087,55.047],[31.871,55.047],[34.871,52.047],[34.871,35.919],[44.716,35.919],[47.716,32.919],[47.716,10.857]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0.090000002992,0.528999956916,0.933000033509,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[47.966,55.297],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":60.0000024438501,"st":0,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"Layer 7 Outlines","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[64.633,99.127,0],"ix":2},"a":{"a":0,"k":[47.966,55.297,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[4.087,7.857],[-13.799,7.857],[4.087,-13.19]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[1.657,0],[0,0],[0,0],[1.657,0],[0,0],[0.57,-0.677],[0,0],[0,-0.707],[0,0],[-1.657,0],[0,0],[0,0],[-1.657,0],[0,0],[0,1.658],[0,0],[0,0],[0,1.657],[0,0]],"o":[[0,0],[0,0],[0,-1.657],[0,0],[-0.886,0],[0,0],[-0.455,0.541],[0,0],[0,1.657],[0,0],[0,0],[0,1.658],[0,0],[1.657,0],[0,0],[0,0],[1.657,0],[0,0],[0,-1.657]],"v":[[44.717,7.857],[34.87,7.857],[34.87,-52.047],[31.87,-55.047],[7.087,-55.047],[4.79,-53.978],[-47.012,7.598],[-47.717,9.53],[-47.717,32.919],[-44.717,35.919],[4.087,35.919],[4.087,52.047],[7.087,55.047],[31.87,55.047],[34.87,52.047],[34.87,35.919],[44.717,35.919],[47.717,32.919],[47.717,10.857]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0.090000002992,0.528999956916,0.933000033509,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[47.966,55.297],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":60.0000024438501,"st":0,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/apps/frontend/public/json/emptyHistoryAnimation.json b/apps/frontend/public/json/emptyHistoryAnimation.json new file mode 100644 index 00000000..ad6aa197 --- /dev/null +++ b/apps/frontend/public/json/emptyHistoryAnimation.json @@ -0,0 +1 @@ +{"v":"5.5.5","fr":60,"ip":0,"op":225,"w":985,"h":910,"nm":"▽ Group 62","ddd":0,"assets":[{"id":"comp_0","layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 16","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[492.5,455,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[],"ip":0,"op":3600,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Shape Layer 14","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[492.5,455,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-40.75,-106.75],[-17.5,-96.5]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.350949754902,0.350949754902,0.350949754902,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.075],"y":[0.344]},"t":44,"s":[0]},{"t":70,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":3600,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Shape Layer 13","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[492.5,455,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-62.25,-138.25],[-14.5,-118.25]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.350949754902,0.350949754902,0.350949754902,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.075],"y":[0.344]},"t":41,"s":[0]},{"t":67,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":3600,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Shape Layer 12","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[492.5,455,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-58.5,-154.5],[-9.75,-133]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.350949754902,0.350949754902,0.350949754902,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.075],"y":[0.344]},"t":38,"s":[0]},{"t":64,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":3600,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Shape Layer 11","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[492.5,455,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-54.25,-171],[-5.75,-150.25]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.350949754902,0.350949754902,0.350949754902,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.075],"y":[0.344]},"t":37,"s":[0]},{"t":63,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":3600,"st":0,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"Shape Layer 10","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[492.5,455,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-46.5,-185.5],[2.75,-165.25]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.350949754902,0.350949754902,0.350949754902,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.075],"y":[0.344]},"t":34,"s":[0]},{"t":60,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":3600,"st":0,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"Shape Layer 9","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[492.5,455,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0.295,3.052],[-2.065,1.835],[-3.494,-0.079],[-1.058,-3.597],[7,1.25]],"o":[[0,0],[-0.25,-2.588],[1.985,-1.764],[4.428,0.1],[2.5,8.5],[-1.984,-0.354]],"v":[[-1.25,-183],[-3.143,-188.688],[-0.5,-196],[8.497,-199.346],[18.5,-193.25],[5.25,-180]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.350949754902,0.350949754902,0.350949754902,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.075],"y":[0.344]},"t":38,"s":[0]},{"t":64,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":3600,"st":0,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"Shape Layer 8","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[492.5,455,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-32.75,-207],[-15.75,-200.5]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.350949754902,0.350949754902,0.350949754902,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.075],"y":[0.344]},"t":32,"s":[0]},{"t":58,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":3600,"st":0,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"Page","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[471.356,299.63,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[1.82,-7.34],[-0.31,0.1],[6.69,2.57],[0.94,0.38],[0.17,0.55],[-0.03,0.35],[-0.04,0.42],[-0.11,0.88],[-0.32,1.7],[-5.49,8.32],[-1.22,1.37],[0.3,-0.08],[-7.46,-2.75],[-0.76,-0.25],[0.1,-0.37],[0.32,-0.7],[1.27,-2.91],[1.3,-3.61],[-0.12,0.33],[-3.33,7.16],[0.06,0.02],[7.84,4.29],[0.18,-0.21],[2.26,-4.92],[1.2,-5.73],[0.26,-3.47],[-0.94,-0.49],[-0.9,-0.36],[-7.11,-3.21],[-0.08,0.31],[-2.56,7.12],[0.11,-0.33]],"o":[[-2.56,7.12],[0.3,-0.11],[-6.54,-2.95],[-0.95,-0.36],[-0.53,-0.22],[-0.1,-0.34],[0.03,-0.42],[0.08,-0.9],[0.21,-1.71],[1.85,-9.78],[1,-1.53],[-0.3,0.08],[7,3.82],[0.75,0.27],[0.37,0.13],[-0.19,0.74],[-1.32,2.89],[-1.54,3.52],[-0.12,0.32],[2.69,-7.43],[0.04,-0.07],[-8.28,-3.26],[-0.26,-0.14],[-3.61,4.09],[-2.44,5.32],[-0.71,3.41],[-0.08,1.02],[0.85,0.44],[7.25,2.86],[0.24,0.11],[1.82,-7.34],[0.11,-0.32],[0,0]],"v":[[10.354,5.075],[3.894,26.805],[4.814,26.495],[-14.936,17.865],[-17.766,16.755],[-19.726,15.665],[-19.636,14.405],[-19.536,13.135],[-19.256,10.465],[-18.456,5.355],[-7.426,-22.275],[-4.156,-26.715],[-5.046,-26.475],[17.204,-17.565],[19.454,-16.725],[19.624,-16.475],[18.614,-14.275],[14.724,-5.585],[10.354,5.075],[11.374,4.935],[20.764,-16.875],[20.664,-17.045],[-4.226,-26.965],[-5.106,-26.725],[-13.706,-12.625],[-19.186,4.025],[-20.646,14.355],[-20.136,16.545],[-17.466,17.685],[3.994,26.975],[4.914,26.665],[11.374,4.935],[10.354,5.075]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960992813,0.501960992813,0.501960992813,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[300,300],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Fill 50","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":0,"op":3600,"st":0,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"Shape Layer 7","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[412.178,346.625,0],"ix":2},"a":{"a":0,"k":[-80.322,-108.375,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[8,6.5],[-2.5,-33.5],[0,0],[-38.5,76]],"o":[[-28.5,25],[22.5,10.5],[0,0],[-37.5,-13.5]],"v":[[-33,-235.5],[-81,-108],[-8.5,-75.5],[40,-206]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.501960784314,0.501960784314,0.501960784314,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":3600,"st":0,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"Page 6","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.026],"y":[0.06]},"t":40,"s":[0]},{"t":63,"s":[100]}],"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.194],"y":[1.028]},"o":{"x":[0.067],"y":[1.268]},"t":41,"s":[-10]},{"t":99,"s":[0]}],"ix":10},"p":{"a":0,"k":[493.663,378.251,0],"ix":2},"a":{"a":0,"k":[-25.808,62.423,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-1.62,-1.21],[-0.23,0.21],[0.33,-0.4],[0.65,-0.91],[1.37,-2.16],[2.62,-7.94],[0.61,-1.97],[-0.29,0.08],[0.37,0.2],[-0.5,-0.27],[-0.38,-0.2],[-0.1,0.33],[-1.81,4.54],[-4.14,5.99],[-0.68,0.93],[-0.36,0.46],[-0.11,0.16],[0.59,0.19],[0.48,0.36],[1.69,1.11],[-0.48,-0.31]],"o":[[1.69,1.11],[0.23,-0.21],[-0.59,-0.2],[-0.73,0.85],[-1.48,2.08],[-4.51,7.05],[-0.65,1.96],[0.29,-0.08],[-0.37,-0.2],[-0.38,-0.21],[0.37,0.2],[0.24,0.14],[1.43,-4.67],[2.69,-6.8],[0.66,-0.96],[0.35,-0.47],[0.12,-0.15],[0.29,-0.41],[0.33,0.11],[-1.62,-1.21],[-0.38,-0.24],[0,0]],"v":[[4.827,-20.462],[9.817,-17.002],[10.497,-17.632],[9.107,-16.842],[7.087,-14.152],[2.777,-7.802],[-7.743,14.778],[-9.583,20.688],[-8.703,20.448],[-9.813,19.848],[-10.593,20.438],[-9.483,21.038],[-8.603,20.808],[-3.843,6.978],[6.687,-12.052],[8.697,-14.902],[9.757,-16.302],[10.137,-16.752],[9.907,-16.952],[10.597,-17.592],[5.617,-21.052],[4.827,-20.462]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960992813,0.501960992813,0.501960992813,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[300,300],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Fill 32","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":3600,"st":0,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":"Shape Layer 15","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.026],"y":[0.06]},"t":40,"s":[0]},{"t":63,"s":[100]}],"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.194],"y":[1.028]},"o":{"x":[0.067],"y":[1.268]},"t":41,"s":[-10]},{"t":99,"s":[0]}],"ix":10},"p":{"a":0,"k":[490.97,360.535,0],"ix":2},"a":{"a":0,"k":[-1.53,-94.465,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[17.418,-67.064],[0,0]],"o":[[0,0],[3.461,-18.041],[0,0]],"v":[[34.284,-193.75],[-1.5,-94.5],[45.75,-187.5]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.87740502451,0.87740502451,0.87740502451,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[101.989,99.963],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":3600,"st":0,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":"Page 7","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.026],"y":[0.06]},"t":46,"s":[0]},{"t":69,"s":[100]}],"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.194],"y":[1.028]},"o":{"x":[0.067],"y":[1.268]},"t":47,"s":[-10]},{"t":105,"s":[0]}],"ix":10},"p":{"a":0,"k":[492.459,373.771,0],"ix":2},"a":{"a":0,"k":[-34.569,50.461,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-1.11,-1.29],[-0.29,0.15],[0.5,-0.44],[1.43,-1.36],[3.2,-5.07],[1.59,-3.68],[-0.14,0.33],[-3.98,5.09],[-3.34,3.09],[-0.79,0.69],[-0.32,0.26],[-0.09,0.1],[0.72,0.38],[0.3,0.35],[1.24,1.17],[-0.3,-0.27]],"o":[[1.23,1.17],[0.29,-0.15],[-0.85,-0.45],[-1.48,1.31],[-4.35,4.12],[-2.14,3.39],[-0.21,0.47],[2.57,-5.93],[2.81,-3.59],[0.77,-0.71],[0.3,-0.27],[0.11,-0.09],[0.28,-0.33],[0.22,0.12],[-1.11,-1.28],[-0.27,-0.25],[0,0]],"v":[[7.548,-16.61],[11.158,-13],[12.038,-13.46],[9.188,-11.73],[4.827,-7.7],[-6.553,6.14],[-12.132,16.78],[-11.182,16.82],[-1.372,0.18],[7.977,-9.69],[10.307,-11.79],[11.238,-12.58],[11.598,-12.86],[11.227,-12.95],[12.107,-13.41],[8.488,-17.02],[7.548,-16.61]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960992813,0.501960992813,0.501960992813,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[300,300],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Fill 28","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":3600,"st":0,"bm":0},{"ddd":0,"ind":15,"ty":4,"nm":"Shape Layer 17","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.026],"y":[0.06]},"t":46,"s":[0]},{"t":69,"s":[100]}],"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.194],"y":[1.028]},"o":{"x":[0.067],"y":[1.268]},"t":47,"s":[-10]},{"t":105,"s":[0]}],"ix":10},"p":{"a":0,"k":[507.867,343.3,0],"ix":2},"a":{"a":0,"k":[15.367,-111.7,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[23,-38]],"o":[[0,0],[0,0],[6,-20.5]],"v":[[49.5,-177.75],[58.75,-170.25],[13,-114.5]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.87740502451,0.87740502451,0.87740502451,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":3600,"st":0,"bm":0}]},{"id":"comp_1","layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Fill 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[157.973,122.959,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0.59,2],[-17.79,0],[-6.7,-1.16],[-6.74,0.1],[-5.95,13.01],[7.48,9.32],[5.81,2.71],[15.74,-5.22],[1.25,-4.87]],"o":[[2.03,0.36],[-0.6,-2.04],[6.8,0],[6.65,1.15],[7.45,-0.11],[5.02,-10.99],[-4.01,-5],[-8.29,-3.86],[-15.73,5.22],[-9.75,38.06]],"v":[[-24.755,40.762],[-23.845,38.382],[-16.395,25.092],[3.765,27.882],[23.905,29.462],[49.315,10.522],[44.985,-19.118],[24.515,-33.998],[-24.755,-38.848],[-50.005,-14.248]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.944113016129,0.944113016129,0.944113016129,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[300,300],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Fill 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":3600,"st":0,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":0,"nm":"Pre-comp 1","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.511],"y":[1]},"o":{"x":[0.012],"y":[0.399]},"t":52,"s":[-80]},{"t":147,"s":[0]}],"ix":10},"p":{"a":0,"k":[488.737,378.563,0],"ix":2},"a":{"a":0,"k":[488.574,378.572,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.509,0.509,0.667],"y":[1,1,1]},"o":{"x":[0.005,0.005,0.333],"y":[0.874,0.874,0]},"t":65,"s":[0,0,100]},{"t":190,"s":[100,100,100]}],"ix":6}},"ao":0,"w":985,"h":910,"ip":56,"op":3656,"st":56,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Shape Layer 6","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[485.213,615.857,0],"ix":2},"a":{"a":0,"k":[-5.5,168.211,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.325,0.325,0.667],"y":[0.995,0.995,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":58,"s":[100,100,100]},{"i":{"x":[0.349,0.349,0.667],"y":[0.741,0.741,1]},"o":{"x":[0.693,0.693,0.333],"y":[0.018,0.018,0]},"t":98,"s":[138.645,138.645,100]},{"i":{"x":[0.568,0.568,0.667],"y":[2.815,2.815,1]},"o":{"x":[0.385,0.385,0.333],"y":[2.381,2.381,0]},"t":137,"s":[96,96,100]},{"i":{"x":[0.624,0.624,0.667],"y":[0.963,0.963,1]},"o":{"x":[0.468,0.468,0.333],"y":[0.158,0.158,0]},"t":180,"s":[95,95,100]},{"i":{"x":[0.541,0.541,0.667],"y":[0.506,0.506,1]},"o":{"x":[0.468,0.468,0.333],"y":[-0.017,-0.017,0]},"t":230,"s":[120,120,100]},{"i":{"x":[0.459,0.459,0.667],"y":[1.04,1.04,1]},"o":{"x":[0.36,0.36,0.333],"y":[0.613,0.613,0]},"t":293,"s":[100,100,100]},{"i":{"x":[0.727,0.727,0.667],"y":[0.551,0.551,1]},"o":{"x":[0.45,0.45,0.333],"y":[0.035,0.035,0]},"t":318,"s":[96,96,100]},{"i":{"x":[0.638,0.638,0.667],"y":[1.012,1.012,1]},"o":{"x":[0.349,0.349,0.333],"y":[0.533,0.533,0]},"t":360,"s":[120,120,100]},{"i":{"x":[0.437,0.437,0.667],"y":[0.5,0.5,1]},"o":{"x":[0.657,0.657,0.333],"y":[0.057,0.057,0]},"t":383,"s":[134,134,100]},{"t":402,"s":[114,114,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[14.774,21.711],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.850980451995,0.61568627451,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[1.887,157.355],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":53,"op":3653,"st":53,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Shape Layer 5","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[372.523,613.982,0],"ix":2},"a":{"a":0,"k":[9.274,168.211,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.325,0.325,0.667],"y":[0.995,0.995,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":58,"s":[100,100,100]},{"i":{"x":[0.349,0.349,0.667],"y":[0.741,0.741,1]},"o":{"x":[0.693,0.693,0.333],"y":[0.018,0.018,0]},"t":98,"s":[138.645,138.645,100]},{"i":{"x":[0.568,0.568,0.667],"y":[2.815,2.815,1]},"o":{"x":[0.385,0.385,0.333],"y":[2.381,2.381,0]},"t":137,"s":[96,96,100]},{"i":{"x":[0.624,0.624,0.667],"y":[0.963,0.963,1]},"o":{"x":[0.468,0.468,0.333],"y":[0.158,0.158,0]},"t":180,"s":[95,95,100]},{"i":{"x":[0.541,0.541,0.667],"y":[0.506,0.506,1]},"o":{"x":[0.468,0.468,0.333],"y":[-0.017,-0.017,0]},"t":230,"s":[120,120,100]},{"i":{"x":[0.459,0.459,0.667],"y":[1.04,1.04,1]},"o":{"x":[0.36,0.36,0.333],"y":[0.613,0.613,0]},"t":293,"s":[100,100,100]},{"i":{"x":[0.727,0.727,0.667],"y":[0.551,0.551,1]},"o":{"x":[0.45,0.45,0.333],"y":[0.035,0.035,0]},"t":318,"s":[96,96,100]},{"i":{"x":[0.638,0.638,0.667],"y":[1.012,1.012,1]},"o":{"x":[0.349,0.349,0.333],"y":[0.533,0.533,0]},"t":360,"s":[120,120,100]},{"i":{"x":[0.437,0.437,0.667],"y":[0.5,0.5,1]},"o":{"x":[0.657,0.657,0.333],"y":[0.057,0.057,0]},"t":383,"s":[134,134,100]},{"t":402,"s":[114,114,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[14.774,21.711],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.850980451995,0.61568627451,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[1.887,157.355],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":53,"op":3653,"st":53,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Shape Layer 4","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[395.437,594.09,0],"ix":2},"a":{"a":0,"k":[-30.188,139.09,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-13.625,11.375]],"o":[[0,0],[13.625,-11.375]],"v":[[-40.375,136.375],[-20,136.875]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.619607843137,0.501960784314,0.321568627451,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":4,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.626],"y":[1]},"o":{"x":[0.695],"y":[0]},"t":38,"s":[50]},{"t":73,"s":[100]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.634],"y":[1]},"o":{"x":[0.882],"y":[0]},"t":38,"s":[50]},{"t":73,"s":[0]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":38,"op":3638,"st":38,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Shape Layer 3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[462.312,594.09,0],"ix":2},"a":{"a":0,"k":[-30.188,139.09,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-13.625,11.375]],"o":[[0,0],[13.625,-11.375]],"v":[[-40.375,136.375],[-20,136.875]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.619607843137,0.501960784314,0.321568627451,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":4,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.626],"y":[1]},"o":{"x":[0.695],"y":[0]},"t":38,"s":[50]},{"t":73,"s":[100]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.634],"y":[1]},"o":{"x":[0.882],"y":[0]},"t":38,"s":[50]},{"t":73,"s":[0]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":38,"op":3638,"st":38,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"Shape Layer 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[427.688,624.517,0],"ix":2},"a":{"a":0,"k":[-64.812,169.517,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-2.25,-5],[-20.75,22.25]],"o":[[2,3.875],[22.08,-23.676]],"v":[[-84.375,163.625],[-45.25,165]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.619607843137,0.501960784314,0.321568627451,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":4,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.626],"y":[1]},"o":{"x":[0.695],"y":[0]},"t":54,"s":[50]},{"i":{"x":[0.526],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":89,"s":[100]},{"i":{"x":[0.334],"y":[1]},"o":{"x":[0.453],"y":[0]},"t":155.562,"s":[80]},{"i":{"x":[0.461],"y":[1]},"o":{"x":[0.63],"y":[0]},"t":239,"s":[100]},{"i":{"x":[0.42],"y":[1]},"o":{"x":[0.485],"y":[0]},"t":325,"s":[80]},{"t":408.4375,"s":[100]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.634],"y":[1]},"o":{"x":[0.882],"y":[0]},"t":54,"s":[50]},{"i":{"x":[0.575],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":89,"s":[0]},{"i":{"x":[0.634],"y":[1]},"o":{"x":[0.568],"y":[0]},"t":155.562,"s":[20]},{"i":{"x":[0.562],"y":[1]},"o":{"x":[0.373],"y":[0]},"t":239,"s":[0]},{"i":{"x":[0.568],"y":[1]},"o":{"x":[0.499],"y":[0]},"t":325,"s":[20]},{"t":408.4375,"s":[0]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":54,"op":3654,"st":54,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"Bubbel 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[385.71,440.982,0],"ix":2},"a":{"a":0,"k":[-29.71,24.982,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0,0,0.667],"y":[1,1,1]},"o":{"x":[0.648,0.648,0.333],"y":[0,0,0]},"t":21,"s":[0,0,100]},{"t":84,"s":[-100,100,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.25,-0.9],[-1.04,0.06],[-4.13,1.83],[-0.16,0.068],[-0.385,0.148],[1.549,1.633],[3.32,-5.46]],"o":[[0.78,0.56],[2.33,-0.12],[0.162,-0.071],[0.396,-0.168],[8.268,-3.182],[-3.02,-3.21],[0.02,1.45]],"v":[[-11.36,3.79],[-8.4,4.49],[-1.23,1.99],[-0.746,1.781],[0.425,1.307],[12.74,-2.26],[-13,-0.15]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.850000023842,0.850000023842,0.850000023842,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[300,300],"ix":3},"r":{"a":0,"k":-210,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Fill 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-38,"op":3562,"st":-38,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"Bubble","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[656.185,350.788,0],"ix":2},"a":{"a":0,"k":[-17.205,11.132,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0,0,0.667],"y":[1,1,1]},"o":{"x":[0.039,0.039,0.333],"y":[0.31,0.31,0]},"t":67,"s":[0,0,100]},{"t":117,"s":[100,100,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-1.21,-0.79],[-0.94,-0.12],[-2.08,1.93],[1.57,1.52],[-0.07,-4.55]],"o":[[0.02,1.26],[0.75,0.48],[3.25,0.41],[1.24,-1.14],[-2.91,-2.79],[0,0]],"v":[[-7,-0.31],[-5.42,3.12],[-2.76,3.9],[4.31,1.55],[6.5,-2.1],[-7,-0.31]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.850000023842,0.850000023842,0.850000023842,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[300,300],"ix":3},"r":{"a":0,"k":-195,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Fill 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":3600,"st":0,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"bubble","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[574.516,424.335,0],"ix":2},"a":{"a":0,"k":[-50.367,24.951,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0,0,0.667],"y":[1,1,1]},"o":{"x":[0.009,0.009,0.333],"y":[0.234,0.234,0]},"t":57,"s":[0,0,100]},{"t":127,"s":[100,100,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.81,-1.47],[-1.51,0.09],[-5.96,2.98],[2.37,2.84],[4.78,-8.91]],"o":[[1.12,0.91],[3.35,-0.19],[13.21,-6.6],[-4.36,-5.23],[0.03,2.37]],"v":[[-16.37,6.182],[-12.1,7.322],[-1.77,3.252],[18.37,-3.688],[-18.74,-0.238]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.949999988079,0.949999988079,0.949999988079,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[300,300],"ix":3},"r":{"a":0,"k":-195,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Fill 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":3600,"st":0,"bm":0},{"ddd":0,"ind":17,"ty":0,"nm":"Big Bubble","refId":"comp_1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[413.5,444.008,0],"ix":2},"a":{"a":0,"k":[81.5,245.508,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0,0,0.667],"y":[1,1,1]},"o":{"x":[0.019,0.019,0.333],"y":[0.528,0.528,0]},"t":29,"s":[0,0,100]},{"t":167,"s":[98,98,100]}],"ix":6}},"ao":0,"w":315,"h":246,"ip":0,"op":3600,"st":0,"bm":0},{"ddd":0,"ind":21,"ty":4,"nm":"Shape Layer 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[492.5,455,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[4.25,-20.25],[3,-10.25],[4.25,-6.25],[10,-2.25],[4.052,5.462],[1.25,14.5],[0.75,-10.25],[0,0],[0,0]],"o":[[0,0],[0,0],[-2,30.25],[-3.458,11.815],[-5,7.75],[-11.649,2.621],[-5.75,-7.75],[-1.801,-20.888],[0.5,-12.5],[0,0],[0,0]],"v":[[-119,6],[152,9.25],[146.75,130.75],[136.75,188.25],[123.5,216.5],[99.25,236],[76.5,225.5],[67,188],[69,127],[76.75,57.5],[81.875,24.375]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.996078431373,0.894117647059,0.741176470588,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":2,"s":[38]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":4,"s":[49]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":7,"s":[60]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":9,"s":[67]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":12,"s":[69]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":20,"s":[69]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":23,"s":[72.333]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":27,"s":[76.111]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":29,"s":[81]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":32,"s":[88]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":35,"s":[95]},{"t":41,"s":[100]}],"ix":2},"o":{"a":0,"k":-360,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":3600,"st":0,"bm":0},{"ddd":0,"ind":22,"ty":4,"nm":"Folder Front 3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[443.987,587.909,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[0,0],[4.237,-0.553],[6.394,8.698],[0,0],[-2.871,-1.286],[-3.656,-0.473],[-2.84,-0.45],[-3.34,-0.28],[-4.16,-0.18],[-4.6,-0.08],[-4.67,0.02],[-4.37,0.13],[-3.68,0.24],[-3.43,1.26]],"o":[[-6.456,1.023],[-6.763,0.03],[0,0],[1.519,2.125],[1.529,0.685],[2.583,0.334],[3.31,0.53],[4.14,0.35],[4.59,0.21],[4.67,0.09],[4.37,-0.01],[3.69,-0.1],[3.59,-0.22],[0,0]],"v":[[49.294,34.924],[45.85,35.334],[-44.306,30.374],[-45.848,28.739],[-40.316,32.971],[-33.09,34.337],[-26.383,35.167],[-17.903,35.734],[-6.203,36.171],[0.494,36.601],[14.494,36.701],[27.927,36.751],[38.654,36.271],[49.294,34.541]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":22,"s":[{"i":[[0,0],[3.987,-0.137],[34.561,10.657],[0,0],[-1.263,-0.523],[-2.739,-0.515],[-2.654,-0.303],[-3.34,-0.28],[-4.16,-0.18],[-4.6,-0.08],[-4.67,0.02],[-4.37,0.13],[-3.68,0.24],[-3.43,1.26]],"o":[[-3.623,0.69],[-29.58,-0.12],[0,0],[2.644,3.833],[1.404,0.643],[2.38,0.448],[3.33,0.381],[4.14,0.35],[4.59,0.21],[4.67,0.09],[4.37,-0.01],[3.69,-0.1],[3.59,-0.22],[0,0]],"v":[[49.793,34.341],[41.184,35.667],[-44.89,29.457],[-46.515,28.031],[-40.233,33.012],[-34.048,34.337],[-25.716,35.334],[-17.736,35.942],[-6.453,36.504],[0.494,36.601],[14.493,36.701],[27.593,36.501],[38.653,36.021],[49.293,34.541]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":23,"s":[{"i":[[0,0],[2.456,2.744],[33.977,11.933],[-0.91,-2.287],[-2.696,-1.758],[-2.504,-0.442],[-3.091,-0.257],[-3.34,-0.28],[-4.16,-0.18],[-4.6,-0.08],[-4.67,0.02],[-4.37,0.13],[-3.68,0.24],[-3.43,1.26]],"o":[[-4.862,0.836],[-29.58,-0.12],[0,0],[0.91,2.287],[2.696,1.758],[2.172,0.383],[3.341,0.277],[4.14,0.35],[4.59,0.21],[4.67,0.09],[4.37,-0.01],[3.69,-0.1],[3.59,-0.22],[0,0]],"v":[[49.793,34.341],[41.298,31.12],[-47.713,23.154],[-46.989,26.202],[-42.359,32.189],[-35.908,34.348],[-28.28,35.12],[-19.096,35.854],[-9.443,36.358],[0.494,36.601],[14.494,36.701],[27.593,36.501],[38.653,36.021],[49.293,34.541]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":25,"s":[{"i":[[0,0],[3.071,5.697],[33.227,13.573],[0,0],[-1.346,-1.857],[-2.406,-0.473],[-2.84,-0.45],[-3.34,-0.28],[-4.16,-0.18],[-4.6,-0.08],[-4.67,0.02],[-4.37,0.13],[-3.68,0.24],[-3.43,1.26]],"o":[[-4.622,0.857],[-29.58,-0.12],[0,0],[0.394,3.333],[0.84,1.159],[2.555,0.503],[3.31,0.53],[4.14,0.35],[4.59,0.21],[4.67,0.09],[4.37,-0.01],[3.69,-0.1],[3.59,-0.22],[0,0]],"v":[[49.793,34.341],[39.684,27.417],[-48.223,17.207],[-47.89,20.197],[-45.066,29.637],[-39.423,33.254],[-31.883,34.584],[-20.736,35.901],[-13.286,36.171],[0.494,36.601],[14.494,36.701],[27.593,36.501],[38.653,36.021],[49.293,34.541]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":27,"s":[{"i":[[0,0],[0.74,7.15],[33.22,13.544],[0,0],[-1.262,-3.352],[-1.185,-0.936],[-2.84,-0.45],[-3.34,-0.28],[-4.16,-0.18],[-4.6,-0.08],[-4.67,0.02],[-4.37,0.13],[-3.68,0.24],[-3.43,1.26]],"o":[[-6.465,1.026],[-29.58,-0.12],[0,0],[0.727,5.833],[0.925,2.458],[1.511,1.194],[3.31,0.53],[4.14,0.35],[4.59,0.21],[4.67,0.09],[4.37,-0.01],[3.69,-0.1],[3.59,-0.22],[0,0]],"v":[[49.793,34.341],[38.931,22.714],[-49.057,12.108],[-48.89,15.774],[-46.817,27.216],[-43.09,31.92],[-35.716,34.251],[-25.736,35.401],[-13.286,36.171],[0.494,36.601],[14.493,36.701],[27.593,36.501],[38.653,36.021],[49.293,34.541]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[0,0],[-0.679,12.947],[39.061,7.24],[0,0],[-0.654,-2.477],[-2.073,-1.89],[-2.84,-0.45],[-3.34,-0.28],[-4.16,-0.18],[-4.6,-0.08],[-4.67,0.02],[-4.37,0.13],[-3.68,0.24],[-3.43,1.26]],"o":[[-10.622,2.19],[-31.846,5.78],[0,0],[0.227,3.833],[0.654,2.477],[2.125,1.938],[3.31,0.53],[4.14,0.35],[4.59,0.21],[4.67,0.09],[4.37,-0.01],[3.69,-0.1],[3.59,-0.22],[0,0]],"v":[[49.794,34.341],[38.35,5.084],[-49.39,4.957],[-48.973,15.031],[-47.733,24.971],[-43.756,31.421],[-35.716,34.251],[-25.736,35.401],[-13.286,36.171],[0.494,36.601],[14.493,36.701],[27.594,36.501],[38.654,36.021],[49.294,34.541]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":32,"s":[{"i":[[0,0],[-3.068,20.113],[32.74,2.487],[0,0],[-1.171,-4.286],[-2.798,-2.377],[-2.84,-0.45],[-3.34,-0.28],[-4.16,-0.18],[-4.6,-0.08],[-4.67,0.02],[-4.37,0.13],[-3.68,0.24],[-3.43,1.26]],"o":[[-12.4,2.857],[-30.335,1.847],[0,0],[0.561,5.167],[0.944,3.462],[2.182,1.873],[3.31,0.53],[4.14,0.35],[4.59,0.21],[4.67,0.09],[4.37,-0.01],[3.69,-0.1],[3.59,-0.22],[0,0]],"v":[[49.794,34.341],[39.406,-5.916],[-49.39,-7.487],[-49.362,11.475],[-47.511,24.471],[-43.756,31.421],[-35.716,34.251],[-25.736,35.401],[-13.286,36.171],[0.494,36.601],[14.493,36.701],[27.594,36.501],[38.654,36.021],[49.294,34.541]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":33,"s":[{"i":[[0,0],[-2.513,15.613],[29.58,0.11],[-0.061,-4.667],[-1.733,-5.097],[-1.678,-1.783],[-2.84,-0.45],[-3.34,-0.28],[-4.16,-0.18],[-4.6,-0.08],[-4.67,0.02],[-4.37,0.13],[-3.68,0.24],[-3.43,1.26]],"o":[[-13.289,3.19],[-29.58,-0.12],[0,0],[0.061,4.667],[1.154,3.393],[1.594,1.693],[3.31,0.53],[4.14,0.35],[4.59,0.21],[4.67,0.09],[4.37,-0.01],[3.69,-0.1],[3.59,-0.22],[0,0]],"v":[[49.794,34.341],[39.684,-6.416],[-49.39,-13.709],[-49.806,9.864],[-47.983,24.471],[-43.756,31.421],[-35.716,34.251],[-25.736,35.401],[-13.286,36.171],[0.494,36.601],[14.493,36.701],[27.594,36.501],[38.654,36.021],[49.294,34.541]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":34,"s":[{"i":[[0,0],[-3.358,18.268],[50.384,5.02],[0,0],[-0.763,-2.607],[-1.822,-1.473],[-2.84,-0.45],[-3.34,-0.28],[-4.16,-0.18],[-4.6,-0.08],[-4.67,0.02],[-4.37,0.13],[-3.68,0.24],[-3.43,1.26]],"o":[[-15.151,4.647],[-29.58,-0.12],[0,0],[0.154,6.328],[0.594,2.032],[2.236,1.808],[3.31,0.53],[4.14,0.35],[4.59,0.21],[4.67,0.09],[4.37,-0.01],[3.69,-0.1],[3.59,-0.22],[0,0]],"v":[[49.794,34.341],[40.889,-17.615],[-49.047,-17.989],[-49.233,5.285],[-47.233,24.387],[-43.84,30.754],[-35.716,34.251],[-25.736,35.401],[-13.286,36.171],[0.494,36.601],[14.493,36.701],[27.594,36.501],[38.654,36.021],[49.294,34.541]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":35,"s":[{"i":[[0,0],[-4.203,20.923],[31.924,0.663],[0,0],[-1.948,-8.876],[-1.671,-1.454],[-2.84,-0.45],[-3.34,-0.28],[-4.16,-0.18],[-4.6,-0.08],[-4.67,0.02],[-4.37,0.13],[-3.68,0.24],[-3.43,1.26]],"o":[[-17.014,6.105],[-29.58,-0.12],[0,0],[0.062,6.887],[0.832,3.789],[1.937,1.685],[3.31,0.53],[4.14,0.35],[4.59,0.21],[4.67,0.09],[4.37,-0.01],[3.69,-0.1],[3.59,-0.22],[0,0]],"v":[[49.794,34.341],[41.594,-23.648],[-49.647,-23.213],[-49.558,-0.023],[-47.381,24.239],[-43.766,31.345],[-35.716,34.251],[-25.736,35.401],[-13.286,36.171],[0.494,36.601],[14.494,36.701],[27.594,36.501],[38.654,36.021],[49.294,34.541]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":38,"s":[{"i":[[0,0],[-4.31,21.26],[29.58,0.11],[0,0],[-0.444,-5.59],[-3.16,-2.62],[-2.84,-0.45],[-3.34,-0.28],[-4.16,-0.18],[-4.6,-0.08],[-4.67,0.02],[-4.37,0.13],[-3.68,0.24],[-3.43,1.26]],"o":[[-17.25,6.29],[-29.58,-0.12],[0,0],[-2.773,16.167],[0.362,4.56],[2.21,1.84],[3.31,0.53],[4.14,0.35],[4.59,0.21],[4.67,0.09],[4.37,-0.01],[3.69,-0.1],[3.59,-0.22],[0,0]],"v":[[49.794,34.341],[42.684,-31.583],[-49.223,-32.376],[-50.223,-14.303],[-49.191,19.054],[-43.756,31.421],[-35.716,34.251],[-25.736,35.401],[-13.286,36.171],[0.494,36.601],[14.494,36.701],[27.594,36.501],[38.654,36.021],[49.294,34.541]],"c":true}]},{"t":41,"s":[{"i":[[0,0],[-4.31,21.26],[29.58,0.11],[0.729,-13.637],[-2.596,-16.19],[-3.16,-2.62],[-2.84,-0.45],[-3.34,-0.28],[-4.16,-0.18],[-4.6,-0.08],[-4.67,0.02],[-4.37,0.13],[-3.68,0.24],[-3.43,1.26]],"o":[[-17.25,6.29],[-29.58,-0.12],[0,0],[-0.606,11.333],[0.649,4.051],[2.21,1.84],[3.31,0.53],[4.14,0.35],[4.59,0.21],[4.67,0.09],[4.37,-0.01],[3.69,-0.1],[3.59,-0.22],[0,0]],"v":[[49.794,34.341],[43.684,-36.249],[-49.556,-36.709],[-51.223,-19.469],[-49.9,18.887],[-43.756,31.421],[-35.716,34.251],[-25.736,35.401],[-13.286,36.171],[0.494,36.601],[14.494,36.701],[27.594,36.501],[38.654,36.021],[49.294,34.541]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.980392156863,0.81568627451,0.564705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[300,300],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Fill 8","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":20,"op":3583,"st":-17,"bm":0},{"ddd":0,"ind":23,"ty":4,"nm":"Folder back 2","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[0]},{"t":11,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[509.689,578.168,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"st","c":{"a":0,"k":[0.278431372549,0.286274509804,0.627450980392,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":177,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.90588241278,0.725490196078,0.454901990704,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[0,0],[-0.478,2.311],[-0.2,5.873],[-0.009,0.31],[-0.039,0.46],[0.318,8.583],[-0.061,0.318],[-0.624,-5.547],[0,0],[3.45,0.69],[0,0],[0,0],[0,0],[0,0],[0,0],[-26.247,0.049]],"o":[[3.58,-0.22],[1.161,-5.62],[0.013,-0.377],[0.184,-6.359],[0.523,-6.099],[-0.01,-0.271],[0.286,-1.49],[0.382,3.394],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[26.246,-0.049]],"v":[[44.257,-46.898],[44.647,-49.211],[45.327,-48.851],[45.36,-49.88],[44.981,-49.348],[44.887,-47.366],[44.857,-48.257],[44.927,-50.475],[45.154,-50.291],[-45.826,-51.271],[-45.456,-50.302],[-45.592,-48.794],[-45.586,-48.25],[-45.581,-47.825],[-45.579,-47.68],[-4.771,-49.365]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":2,"s":[{"i":[[0,0],[-0.011,-1.816],[0.062,1.037],[-0.001,0.033],[0.031,1.047],[-0.061,-0.825],[-0.015,-0.011],[-0.093,-0.419],[0,0],[3.45,0.69],[-0.189,-3.089],[0,0],[0.042,-1.084],[0,0],[0,0],[-25.262,-0.666]],"o":[[0.66,-0.164],[-0.186,1.251],[0.001,-0.029],[0.028,-0.675],[0.078,-5.196],[0.061,0.912],[0.111,0.089],[0.09,0.526],[0,0],[0,0],[0.189,3.089],[0,0],[-0.042,1.084],[0,0],[0,0],[25.238,0.167]],"v":[[44.793,-18.572],[45.483,-21.82],[45.391,-26.952],[45.395,-27.046],[45.467,-30.126],[45.641,-34.875],[45.673,-35.184],[45.541,-37.099],[45.487,-38.291],[-45.493,-39.271],[-44.714,-32.086],[-44.72,-24.326],[-44.816,-22.082],[-44.717,-20.416],[-44.84,-19.228],[-2.429,-16.081]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":3,"s":[{"i":[[0,0],[-0.05,0.011],[0.042,0.862],[-0.002,0.029],[0.005,0.754],[-0.036,0.386],[-0.007,0.156],[-0.107,2.895],[0,0],[3.45,0.69],[-0.248,-3.965],[0.01,-1.116],[0.021,-0.542],[0.034,-0.44],[0,0],[-26.369,-0.954]],"o":[[0.543,-0.319],[0.004,-1.393],[0.002,-0.026],[0.038,-0.595],[0.078,-3.485],[0.031,0.312],[0.052,-1.096],[0.09,-2.04],[0,0],[0,0],[0.248,3.965],[-0.01,1.116],[-0.021,0.542],[-0.023,0.299],[0,0],[26.357,0.704]],"v":[[44.463,-12.235],[45.211,-17.349],[45.255,-23.589],[45.261,-23.671],[45.37,-26.258],[45.501,-31.131],[45.557,-34.679],[45.463,-35.058],[45.487,-38.291],[-45.493,-39.271],[-44.605,-28.796],[-44.404,-19.695],[-44.369,-16.415],[-44.32,-14.476],[-44.423,-12.879],[-6.215,-6.951]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":4,"s":[{"i":[[0,0],[-0.089,1.839],[0.023,0.687],[-0.002,0.025],[-0.02,0.462],[-0.011,1.597],[0.001,0.323],[-0.122,6.209],[0,0],[3.45,0.69],[-0.306,-4.841],[0.021,-2.233],[0,0],[0.069,-0.881],[0,0],[-27.476,-1.241]],"o":[[0.426,-0.475],[0.195,-4.037],[0.002,-0.022],[0.048,-0.516],[0.078,-1.775],[0.002,-0.287],[-0.006,-2.282],[0.091,-4.606],[0,0],[0,0],[0.306,4.841],[-0.021,2.233],[0,0],[-0.047,0.598],[0,0],[27.476,1.241]],"v":[[44.132,-5.898],[44.939,-12.878],[45.119,-20.226],[45.126,-20.297],[45.272,-22.389],[45.361,-27.386],[45.44,-34.173],[45.385,-33.017],[45.487,-38.291],[-45.493,-39.271],[-44.497,-25.505],[-44.087,-15.063],[-43.922,-10.998],[-43.923,-8.536],[-44.007,-6.531],[-10.001,2.179]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":6,"s":[{"i":[[0,0],[-0.175,1.963],[-0.019,1.687],[-0.001,0.049],[-0.007,0.462],[-0.011,1.589],[-0.028,0.322],[-0.163,6.208],[0,0],[3.45,0.69],[-0.286,-6.835],[0.068,-3.354],[0,0],[0,0],[0,0],[-23.174,-0.045]],"o":[[0.593,-2.6],[0.411,-4.62],[0,-0.022],[0.016,-0.996],[0.078,-4.983],[0.002,-0.292],[0.66,-7.622],[0.207,-7.887],[0,0],[0,0],[0.286,6.835],[-0.069,3.414],[0,0],[0,0],[0,0],[23.174,0.045]],"v":[[42.091,12.977],[44.148,0.956],[45.161,-12.851],[45.162,-12.958],[45.314,-20.681],[45.404,-27.336],[45.607,-31.507],[45.552,-32.017],[45.487,-38.291],[-45.493,-39.271],[-44.31,-22.748],[-43.622,0.089],[-43.701,6.659],[-44.114,9.642],[-44.279,12.802],[-11.199,18.207]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":9,"s":[{"i":[[0,0],[-1.232,4.97],[-0.013,0.383],[-0.006,0.199],[-0.154,1.176],[0.08,-0.548],[0.004,-0.044],[-0.049,0.106],[0,0],[3.45,0.69],[-0.473,-10.523],[0,0],[0,0],[0.898,-2.465],[0,0],[-26.941,-1.093]],"o":[[5.385,-2.183],[1.745,-7.036],[0.007,-0.208],[0.123,-4.094],[-0.172,-0.9],[-0.003,0.017],[-0.131,1.426],[0.049,-0.106],[0,0],[0,0],[0.139,19.523],[0,0],[0,0],[-1.188,3.259],[0,0],[26.941,1.093]],"v":[[31.424,34.602],[41.564,17.872],[44.661,-7.268],[44.68,-7.878],[44.897,-15.014],[45.033,-22.181],[45.273,-28.34],[45.427,-32.808],[45.487,-38.291],[-45.493,-39.271],[-41.331,-5.104],[-41.837,15.187],[-42.838,23.245],[-44.09,28.301],[-46.34,31.969],[-17.799,37.993]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":10,"s":[{"i":[[0,0],[-2.311,4.874],[-0.333,2.187],[-0.017,0.253],[-0.132,0.923],[-0.212,2.875],[-0.041,0.752],[-0.087,2.14],[0,0],[3.45,0.69],[-0.196,-15.078],[0.757,-6.678],[0.536,-2.364],[0.648,-2.211],[0,0],[-22.907,-1.026]],"o":[[7.579,-1.739],[2.967,-7.092],[0.008,-0.157],[0.18,-3.631],[0.022,-1.032],[0.007,-0.091],[0.051,-1.519],[0.102,-2.7],[0,0],[0,0],[0.139,10.746],[-0.157,1.382],[-0.214,1.469],[-0.906,3.09],[0,0],[34.903,3.264]],"v":[[26.924,37.352],[39.342,23.22],[44.189,-2.532],[44.226,-3.154],[44.842,-12.403],[45.114,-19.433],[45.343,-25.34],[45.482,-32.086],[45.487,-38.291],[-45.493,-39.271],[-41.997,-3.993],[-42.282,16.097],[-43.227,25.45],[-44.506,30.297],[-46.729,33.469],[-20.094,38.655]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":11,"s":[{"i":[[0,0],[-3.389,4.778],[-0.653,3.991],[-0.028,0.307],[-0.111,0.671],[-0.505,6.297],[-0.086,1.547],[-0.125,4.174],[0,0],[3.45,0.69],[-0.742,-10.556],[0.125,-2.779],[0.265,-3.071],[0.685,-2.905],[0,0],[-18.873,-0.96]],"o":[[9.774,-1.294],[4.189,-7.147],[0.009,-0.106],[0.238,-3.169],[0.215,-1.165],[0.016,-0.199],[0.233,-4.464],[0.154,-5.293],[0,0],[0,0],[0.806,11.468],[-0.125,2.779],[-0.568,3.151],[-0.685,2.905],[0,0],[19.088,0.88]],"v":[[21.924,37.602],[37.12,28.567],[43.716,2.205],[43.773,1.571],[44.786,-9.792],[45.196,-16.685],[45.412,-22.34],[45.538,-31.364],[45.487,-38.291],[-45.493,-39.271],[-42.164,-8.382],[-42.726,16.841],[-43.616,26.322],[-45.173,32.459],[-47.118,34.969],[-22.39,39.317]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":12,"s":[{"i":[[0,0],[-4.457,4.685],[-0.974,5.795],[-0.039,0.361],[-0.089,0.418],[-0.797,9.72],[-0.131,2.343],[-0.163,6.208],[0,0],[3.45,0.69],[-0.126,-10.529],[0.187,-4.168],[0.397,-4.607],[0.852,-2.631],[0,0],[-14.839,-0.893]],"o":[[10.051,-0.516],[7.328,-7.703],[0.009,-0.056],[0.295,-2.706],[0.245,-1.15],[0.025,-0.307],[0.414,-7.409],[0.207,-7.887],[0,0],[0,0],[0.139,11.69],[-0.187,4.168],[-0.853,4.726],[-0.852,2.631],[0,0],[15.161,0.773]],"v":[[17.174,39.102],[33.564,33.206],[43.077,7.149],[43.153,6.504],[44.564,-6.764],[45.11,-13.563],[45.356,-19.09],[45.51,-30.392],[45.487,-38.291],[-45.493,-39.271],[-42.331,-1.104],[-42.337,16.584],[-43.172,26.86],[-45.173,34.121],[-47.507,36.469],[-24.685,39.979]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":15,"s":[{"i":[[0,0],[-4.922,4.13],[-1.685,4.437],[-0.014,0.054],[-0.068,0.457],[-0.777,10.297],[-0.028,0.322],[-0.163,6.208],[0,0],[3.45,0.69],[-0.289,-8.419],[1.021,-8.777],[0,0],[3.352,-3.507],[0,0],[0,0]],"o":[[21.135,-0.766],[1.828,-2.786],[0.008,-0.021],[0.965,-3.613],[0.995,-6.65],[0.025,-0.326],[0.66,-7.622],[0.207,-7.887],[0,0],[0,0],[0.306,8.922],[-1.021,8.777],[0,0],[-3.352,3.507],[0,0],[0,0]],"v":[[10.674,39.686],[34.814,33.289],[39.994,24.316],[41.594,18.449],[42.981,10.069],[44.695,-8.202],[45.19,-19.173],[45.677,-32.558],[45.487,-38.291],[-45.493,-39.271],[-41.831,-9.836],[-41.837,16.309],[-45.088,28.953],[-49.006,34.579],[-56.007,37.969],[-28.372,39.734]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":18,"s":[{"i":[[0,0],[-3.431,1.278],[-2.373,5.272],[-0.013,0.033],[-0.089,0.453],[-0.219,4.759],[-0.018,0.323],[-0.163,6.208],[0,0],[3.45,0.69],[-0.473,-13.483],[0.187,-5.06],[1.646,-4.532],[0.981,-0.539],[0,0],[0,0]],"o":[[27.246,-0.436],[5.707,-2.108],[0.009,-0.02],[0.274,-0.685],[1.243,-6.319],[0.015,-0.323],[0.446,-7.795],[0.207,-7.887],[0,0],[0,0],[0.473,13.483],[-0.187,5.06],[-2.43,5.769],[-3.724,2.045],[0,0],[0,0]],"v":[[-6.34,40.016],[27.784,37.419],[38.587,25.134],[38.637,25.009],[41.742,15.437],[43.696,1.625],[44.363,-6.702],[45.013,-24.46],[45.487,-38.291],[-45.493,-39.271],[-42.497,-11.897],[-41.837,13.526],[-44.011,25.734],[-51.506,35.208],[-58.315,37.304],[-38.901,39.173]],"c":true}]},{"t":20,"s":[{"i":[[0,0],[-11.363,2.599],[-1.337,2.042],[-1.054,2.897],[-0.187,0.423],[-0.205,5.037],[-0.027,0.322],[-0.146,6.209],[0,0],[3.452,0.681],[-0.168,-11.809],[0,0],[2.034,-5.359],[4.824,-2.192],[0,0],[0,0]],"o":[[3.579,-0.23],[6.392,-1.462],[0.012,-0.019],[1.054,-2.897],[1.014,-2.294],[0.013,-0.323],[0.639,-7.624],[0.186,-7.888],[0,0],[0,0],[0.168,11.809],[0,0],[-1.133,3.641],[-4.584,1.977],[0,0],[0,0]],"v":[[-10.47,40.057],[26,38.298],[35.395,31.795],[38.838,25.15],[40.712,19.296],[43.514,3.465],[44.181,-3.501],[45.276,-25.014],[45.384,-38.414],[-45.599,-39.147],[-43.859,-10.723],[-43.435,11.278],[-45.392,23.945],[-52.515,34.778],[-60.654,36.759],[-46.518,38.602]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.90588241278,0.725490196078,0.454901990704,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-0.115,0.074],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[300,300],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Fill 6","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":3600,"st":0,"bm":0},{"ddd":0,"ind":25,"ty":4,"nm":"Shadow","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":17,"s":[0]},{"t":42,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[461.597,695.146,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0,0,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":17,"s":[17,17,100]},{"t":30,"s":[100,100,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[1.48,-0.56],[10.66,-0.02],[20,0],[-12.36,2.88],[-14.22,0.38],[-12.88,-0.85],[0.51,-2.66]],"o":[[-0.1,0.48],[-10.36,3.95],[-20.02,0.04],[-12.49,-0.83],[14.07,-3.28],[12.89,-0.35],[2.18,0.15],[0,0]],"v":[[57.478,0.585],[55.198,2.145],[21.928,4.895],[-38.122,4.085],[-52.782,-1.725],[-9.232,-4.905],[29.438,-4.145],[57.478,0.585]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933000028133,0.933000028133,0.933000028133,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[300,300],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Fill 4","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":3600,"st":0,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/apps/frontend/public/json/errorAnimation.json b/apps/frontend/public/json/errorAnimation.json new file mode 100644 index 00000000..a602c482 --- /dev/null +++ b/apps/frontend/public/json/errorAnimation.json @@ -0,0 +1,463 @@ +{ + "v": "5.5.10", + "fr": 29.9700012207031, + "ip": 0, + "op": 75.0000030548126, + "w": 300, + "h": 335, + "nm": "Comp 1", + "ddd": 0, + "assets": [], + "layers": [ + { + "ddd": 0, + "ind": 1, + "ty": 4, + "nm": "Shape Layer 6", + "sr": 1, + "ks": { + "o": { "a": 0, "k": 100, "ix": 11 }, + "r": { "a": 0, "k": 0, "ix": 10 }, + "p": { + "a": 1, + "k": [ + { + "i": { "x": 0.667, "y": 1 }, + "o": { "x": 0.333, "y": 0 }, + "t": 25, + "s": [148, 190.571, 0], + "to": [0, 1.667, 0], + "ti": [0, -1.667, 0] + }, + { "t": 32.0000013033867, "s": [148, 200.571, 0] } + ], + "ix": 2 + }, + "a": { "a": 0, "k": [0, 31.75, 0], "ix": 1 }, + "s": { + "a": 1, + "k": [ + { + "i": { "x": [0.833, 0.833, 0.833], "y": [1, 1, 1] }, + "o": { "x": [0.333, 0.333, 0.333], "y": [0, 0, 0] }, + "t": 25, + "s": [0, 0, 100] + }, + { + "i": { "x": [0.833, 0.833, 0.833], "y": [1, 1, 1] }, + "o": { "x": [0.167, 0.167, 0.167], "y": [0, 0, 0] }, + "t": 32, + "s": [119.25, 136.286, 100] + }, + { + "i": { "x": [0.833, 0.833, 0.833], "y": [1, 1, 1] }, + "o": { "x": [0.167, 0.167, 0.167], "y": [0, 0, 0] }, + "t": 34, + "s": [86, 98.286, 100] + }, + { "t": 36.0000014663101, "s": [100, 114.286, 100] } + ], + "ix": 6 + } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "d": 1, + "ty": "el", + "s": { "a": 0, "k": [20, 20], "ix": 2 }, + "p": { "a": 0, "k": [0, 0], "ix": 3 }, + "nm": "Ellipse Path 1", + "mn": "ADBE Vector Shape - Ellipse", + "hd": false + }, + { + "ty": "fl", + "c": { "a": 0, "k": [1, 1, 1, 1], "ix": 4 }, + "o": { "a": 0, "k": 100, "ix": 5 }, + "r": 1, + "bm": 0, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill", + "hd": false + }, + { + "ty": "tr", + "p": { "a": 0, "k": [0, 40.5], "ix": 2 }, + "a": { "a": 0, "k": [0, 0], "ix": 1 }, + "s": { "a": 0, "k": [100, 100], "ix": 3 }, + "r": { "a": 0, "k": 0, "ix": 6 }, + "o": { "a": 0, "k": 100, "ix": 7 }, + "sk": { "a": 0, "k": 0, "ix": 4 }, + "sa": { "a": 0, "k": 0, "ix": 5 }, + "nm": "Transform" + } + ], + "nm": "Ellipse 1", + "np": 3, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 25.0000010182709, + "op": 372.000015151871, + "st": -6.00000024438501, + "bm": 0 + }, + { + "ddd": 0, + "ind": 2, + "ty": 4, + "nm": "Shape Layer 5", + "sr": 1, + "ks": { + "o": { "a": 0, "k": 100, "ix": 11 }, + "r": { "a": 0, "k": 0, "ix": 10 }, + "p": { "a": 0, "k": [149, 187.5, 0], "ix": 2 }, + "a": { "a": 0, "k": [0, 78.156, 0], "ix": 1 }, + "s": { + "a": 1, + "k": [ + { + "i": { "x": [0.833, 0.833, 0.833], "y": [1, 1, 1] }, + "o": { "x": [0.333, 0.333, 0.333], "y": [0, 0, 0] }, + "t": 19, + "s": [52.459, 2.459, 100] + }, + { + "i": { "x": [0.833, 0.833, 0.833], "y": [1, 1, 1] }, + "o": { "x": [0.167, 0.167, 0.167], "y": [0, 0, 0] }, + "t": 27, + "s": [52.459, 61.459, 100] + }, + { + "i": { "x": [0.833, 0.833, 0.833], "y": [1, 1, 1] }, + "o": { "x": [0.167, 0.167, 0.167], "y": [0, 0, 0] }, + "t": 30.5, + "s": [52.459, 45.459, 100] + }, + { "t": 34.0000013848484, "s": [52.459, 52.459, 100] } + ], + "ix": 6 + } + }, + "ao": 0, + "hasMask": true, + "masksProperties": [ + { + "inv": false, + "mode": "f", + "pt": { + "a": 0, + "k": { + "i": [ + [-3.805, 13.022], + [-0.092, 37.927], + [0, -49.691], + [-4.587, -15.298] + ], + "o": [ + [3.996, -13.582], + [0, -49.932], + [0, 37.689], + [4.06, 13.26] + ], + "v": [ + [10.277, 74.31], + [31.587, -36.638], + [-30.587, -36.638], + [-9.624, 74.31] + ], + "c": true + }, + "ix": 1 + }, + "o": { "a": 0, "k": 100, "ix": 3 }, + "x": { "a": 0, "k": 0, "ix": 4 }, + "nm": "Mask 1" + } + ], + "shapes": [ + { + "ty": "gr", + "it": [ + { + "ty": "rc", + "d": 1, + "s": { "a": 0, "k": [234, 260], "ix": 2 }, + "p": { "a": 0, "k": [0, 0], "ix": 3 }, + "r": { "a": 0, "k": 0, "ix": 4 }, + "nm": "Rectangle Path 1", + "mn": "ADBE Vector Shape - Rect", + "hd": false + }, + { + "ty": "fl", + "c": { "a": 0, "k": [1, 1, 1, 1], "ix": 4 }, + "o": { "a": 0, "k": 100, "ix": 5 }, + "r": 1, + "bm": 0, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill", + "hd": false + }, + { + "ty": "tr", + "p": { "a": 0, "k": [5, 1.5], "ix": 2 }, + "a": { "a": 0, "k": [0, 0], "ix": 1 }, + "s": { "a": 0, "k": [100, 100], "ix": 3 }, + "r": { "a": 0, "k": 0, "ix": 6 }, + "o": { "a": 0, "k": 100, "ix": 7 }, + "sk": { "a": 0, "k": 0, "ix": 4 }, + "sa": { "a": 0, "k": 0, "ix": 5 }, + "nm": "Transform" + } + ], + "nm": "Rectangle 1", + "np": 3, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 19.0000007738859, + "op": 374.000015233332, + "st": -4.00000016292334, + "bm": 0 + }, + { + "ddd": 0, + "ind": 5, + "ty": 4, + "nm": "Shape Layer 3", + "sr": 1, + "ks": { + "o": { "a": 0, "k": 100, "ix": 11 }, + "r": { "a": 0, "k": 0, "ix": 10 }, + "p": { "a": 0, "k": [150, 167, 0], "ix": 2 }, + "a": { "a": 0, "k": [-4, -8.5, 0], "ix": 1 }, + "s": { + "a": 1, + "k": [ + { + "i": { "x": [0.471, 0.471, 0.667], "y": [1, 1, 1] }, + "o": { "x": [0.48, 0.48, 0.333], "y": [0, 0, 0] }, + "t": 7, + "s": [0, 0, 100] + }, + { "t": 25.0000010182709, "s": [78.151, 78.151, 100] } + ], + "ix": 6 + } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "d": 1, + "ty": "el", + "s": { "a": 0, "k": [238, 238], "ix": 2 }, + "p": { "a": 0, "k": [0, 0], "ix": 3 }, + "nm": "Ellipse Path 1", + "mn": "ADBE Vector Shape - Ellipse", + "hd": false + }, + { + "ty": "fl", + "c": { "a": 0, "k": [1, 0.486274509804, 0.501960784314, 1], "ix": 4 }, + "o": { "a": 0, "k": 100, "ix": 5 }, + "r": 1, + "bm": 0, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill", + "hd": false + }, + { + "ty": "tr", + "p": { "a": 0, "k": [-4, -8.5], "ix": 2 }, + "a": { "a": 0, "k": [0, 0], "ix": 1 }, + "s": { "a": 0, "k": [100, 100], "ix": 3 }, + "r": { "a": 0, "k": 0, "ix": 6 }, + "o": { "a": 0, "k": 100, "ix": 7 }, + "sk": { "a": 0, "k": 0, "ix": 4 }, + "sa": { "a": 0, "k": 0, "ix": 5 }, + "nm": "Transform" + } + ], + "nm": "Ellipse 1", + "np": 3, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 7.00000028511585, + "op": 385.000015681372, + "st": 7.00000028511585, + "bm": 0 + }, + { + "ddd": 0, + "ind": 6, + "ty": 4, + "nm": "Shape Layer 2", + "sr": 1, + "ks": { + "o": { "a": 0, "k": 20, "ix": 11 }, + "r": { "a": 0, "k": 0, "ix": 10 }, + "p": { "a": 0, "k": [150, 167, 0], "ix": 2 }, + "a": { "a": 0, "k": [-4, -8.5, 0], "ix": 1 }, + "s": { + "a": 1, + "k": [ + { + "i": { "x": [0.471, 0.471, 0.667], "y": [1, 1, 1] }, + "o": { "x": [0.48, 0.48, 0.333], "y": [0, 0, 0] }, + "t": 3, + "s": [0, 0, 100] + }, + { "t": 21.0000008553475, "s": [96.639, 96.639, 100] } + ], + "ix": 6 + } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "d": 1, + "ty": "el", + "s": { "a": 0, "k": [238, 238], "ix": 2 }, + "p": { "a": 0, "k": [0, 0], "ix": 3 }, + "nm": "Ellipse Path 1", + "mn": "ADBE Vector Shape - Ellipse", + "hd": false + }, + { + "ty": "fl", + "c": { "a": 0, "k": [1, 0.486274509804, 0.501960784314, 1], "ix": 4 }, + "o": { "a": 0, "k": 100, "ix": 5 }, + "r": 1, + "bm": 0, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill", + "hd": false + }, + { + "ty": "tr", + "p": { "a": 0, "k": [-4, -8.5], "ix": 2 }, + "a": { "a": 0, "k": [0, 0], "ix": 1 }, + "s": { "a": 0, "k": [100, 100], "ix": 3 }, + "r": { "a": 0, "k": 0, "ix": 6 }, + "o": { "a": 0, "k": 100, "ix": 7 }, + "sk": { "a": 0, "k": 0, "ix": 4 }, + "sa": { "a": 0, "k": 0, "ix": 5 }, + "nm": "Transform" + } + ], + "nm": "Ellipse 1", + "np": 3, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 3.00000012219251, + "op": 381.000015518448, + "st": 3.00000012219251, + "bm": 0 + }, + { + "ddd": 0, + "ind": 7, + "ty": 4, + "nm": "Shape Layer 1", + "sr": 1, + "ks": { + "o": { "a": 0, "k": 10, "ix": 11 }, + "r": { "a": 0, "k": 0, "ix": 10 }, + "p": { "a": 0, "k": [150, 167, 0], "ix": 2 }, + "a": { "a": 0, "k": [-4, -8.5, 0], "ix": 1 }, + "s": { + "a": 1, + "k": [ + { + "i": { "x": [0.471, 0.471, 0.667], "y": [1, 1, 1] }, + "o": { "x": [0.48, 0.48, 0.333], "y": [0, 0, 0] }, + "t": 0, + "s": [0, 0, 100] + }, + { "t": 18.000000733155, "s": [110.084, 110.084, 100] } + ], + "ix": 6 + } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "d": 1, + "ty": "el", + "s": { "a": 0, "k": [238, 238], "ix": 2 }, + "p": { "a": 0, "k": [0, 0], "ix": 3 }, + "nm": "Ellipse Path 1", + "mn": "ADBE Vector Shape - Ellipse", + "hd": false + }, + { + "ty": "fl", + "c": { "a": 0, "k": [1, 0.486274509804, 0.501960784314, 1], "ix": 4 }, + "o": { "a": 0, "k": 100, "ix": 5 }, + "r": 1, + "bm": 0, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill", + "hd": false + }, + { + "ty": "tr", + "p": { "a": 0, "k": [-4, -8.5], "ix": 2 }, + "a": { "a": 0, "k": [0, 0], "ix": 1 }, + "s": { "a": 0, "k": [100, 100], "ix": 3 }, + "r": { "a": 0, "k": 0, "ix": 6 }, + "o": { "a": 0, "k": 100, "ix": 7 }, + "sk": { "a": 0, "k": 0, "ix": 4 }, + "sa": { "a": 0, "k": 0, "ix": 5 }, + "nm": "Transform" + } + ], + "nm": "Ellipse 1", + "np": 3, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 0, + "op": 378.000015396256, + "st": 0, + "bm": 0 + } + ], + "markers": [{ "tm": 40.0000016292334, "cm": "", "dr": 0 }] +} diff --git a/apps/frontend/public/json/frogAnimation.json b/apps/frontend/public/json/frogAnimation.json new file mode 100644 index 00000000..333129ee --- /dev/null +++ b/apps/frontend/public/json/frogAnimation.json @@ -0,0 +1 @@ +{"v":"4.8.0","meta":{"g":"LottieFiles AE 2.0.4","a":"","k":"","d":"","tc":""},"fr":29.9700012207031,"ip":0,"op":90.0000036657751,"w":1000,"h":1000,"nm":"Comp 1","ddd":0,"assets":[{"id":"image_0","w":171,"h":20,"u":"","p":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAKsAAAAUCAYAAADydM25AAAACXBIWXMAAAABAAAAAQBPJcTWAAAAJHpUWHRDcmVhdG9yAAAImXNMyU9KVXBMK0ktUnBNS0tNLikGAEF6Bs5qehXFAAABYklEQVRoge3WsW3CQBTG8f85UUIXNog3yDUJdPEGYQxI5Q2SEaiijMEIVyKlMRuYDXAHRXwpDAUQzBkJCZnvV57Od+/5PcsPRERERERERERELpI5umPUS/EkGBymdHz9ZI1uSG2X1V2CjyyUyf6GKMf4DG8mfE/zRmdvvD9byhsL3gIxmPTks9qgpTWrb9ZhLwOedlYLwNW+iGE/AZIqUfMaGj8wByYY7/BR9m/wW0l6e+D8AsyA76lrcHc7tLhmR5q1/wn+IzDoAngI3NvUjP0C1JnTWVnG2eJM8VyuFtfstvbxznLM8t4CbwGXhSQ9B59vLwV9xU2SnoFJr7JRodU1Oz6zbpz2myjATAAHuIMzyehlgDcJMAAeg2OqkszW81N2lb/9Oi2rWXiz7hr2E4y3eNMF36UakjezUE70mzUe7Ktz4+oskvVKDGUMkasi9gs15olUMxERERERERERkXP4A4X208aieaQiAAAAAElFTkSuQmCC","e":1},{"id":"image_1","w":66,"h":33,"u":"","p":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEIAAAAhCAYAAABtNH0cAAAACXBIWXMAAAABAAAAAQBPJcTWAAAAJHpUWHRDcmVhdG9yAAAImXNMyU9KVXBMK0ktUnBNS0tNLikGAEF6Bs5qehXFAAACaUlEQVRoge2aQVbaUBSGv/u0ibOyg7KDOtROmqFCPQdXULuCsgS6A90BrkA81egwToRh3EHcAcxqqrkdgDYqUEDIlWO/EUle3vn5z3335d5ENk8r+8A6b5h2NQxW6Zvw2VqMNc5awGvhvxFwDeAQTYyFWJMAOFFJbHUYMwgEl2UuslViTOZiALd2uxpba7FEVWIAAdg8rSTAB0tBVrSrocDfXaNlqMWS4/sffSNEIysllmguABxAu3LWYrCfviXWUu+xEQACTRM1VogeRrut7v3hgxGZy5omgozQu5Vm/vjBiM72eYLoYeGKbLjo7JxE+ROPag0VbRSpxgrNXOPpuUdGdLbPE+CgID1WPIsGGFJ9+qnXAHoFCLKgpy7bG3bhmRHRbquL6NDBS49KYxD1z5BR92yG201Uvi5MVPFctKthMOriyMaMf+PXFa4WIql4en7q1cYNGBkRAMFRrXTjpQnwfp6qCqYnEFxWw7FV9thWXbTb6goELHHy1MzV/mUCTNCzvKyG8bKaofBt2FY5jLFLI8+n08q6QsRyLJOJlkOeibvYl9Uw9lOv/NoTqMLVtCbAFBGRZ/B27Pss9y6YYz/19vJV5aTMZATAxs8vgfQr1tfQ4ushujfoq8zEzC94OjsnkZ966wI/sE2kB37qlV9iArwgIvIER7VS6qV1hTpFJVPRQxUd+cg89XTzmOSe4KhW+uWlNaAu8HGecw+4RmXf//2uOUseGMdcjcizcbZVlruVGqIB/eeQWSPlApWWiEbT7gTTsDAjnrJxtlXmdrXsXBYAaP9zhFJuSFcgBlDRWFSSRf7xp/wBiGTMDJvgS8YAAAAASUVORK5CYII=","e":1},{"id":"image_2","w":68,"h":35,"u":"","p":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEQAAAAjCAYAAAAt4qxQAAAACXBIWXMAAAABAAAAAQBPJcTWAAAAJHpUWHRDcmVhdG9yAAAImXNMyU9KVXBMK0ktUnBNS0tNLikGAEF6Bs5qehXFAAACP0lEQVRoge2aQW7aQBSGvzepSHY9QjlCl7Aqy8YQiZyg5gbNDTgCuYG5gVECdAkbYOncwNwAtkjx6yI4BQTEgGGcwreyNWb069c/vJlnS7HtNIDvXAAIvvBmxg/bSrKCsS0ga1wMWSYwiIa2VWQFgYkRldC2kKygooFR0cC2kKwgKuHFkAUG5U5gRrd/QmBsW0wG6ENcZUR7NpVkAYEe/Cu7vj0pmcEHkPiu2HYmwFdrcuwyHpY7eVjcmImebUoEvPj63RB9vfLWPXwORCby4ut3Q0Z3zz3m/7RnhWhzXmmBlbOMRqZ+aj22UdH64v2SIWeXkpV0wJrT7hmlZLqaDlhjyDwljycQZBeV+mo6YEM/5HqWq/N/b+f7w0q7sW5grSG9e38iUD2uJmtM1UTupsGNHbNBuRMo1I4iySai7rqlErO1hTgqdzxEm6mLsoRCbeh0t+7IZdtgTLFz66HyKx1ZlhBtDp2u++FjSef71KYkNAN26LoPna77KZePykNSM2DH1xBDp+ui8rCzKDtMFWqbyusmEi+ZRQpPlZKYyCej/ROFFwPuoNzZuV+814uq0d1z73qWywOtfX5/ZB5vZrnSPmbAnglZZJ4WD/h26FwH0hf4va8RMQcbElNoO65AnRMbo/AiovWP9hdJSc2QmLkxLsf/oqClkWnMD6OpkbohMYXuz7y8XlURrZKeOS0F/2aW83v3/iSlOZc4miGrFJ4qJWOikormUcnz9l3Kpio1BkJEQyITqEqQdhIuJOQv1hDJF/vzewUAAAAASUVORK5CYII=","e":1},{"id":"image_3","w":66,"h":33,"u":"","p":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEIAAAAhCAYAAABtNH0cAAAACXBIWXMAAAABAAAAAQBPJcTWAAAAJHpUWHRDcmVhdG9yAAAImXNMyU9KVXBMK0ktUnBNS0tNLikGAEF6Bs5qehXFAAACMUlEQVRoge2aS3LaQBCGvx6nqOzwDdANzFKwQdkR4VTkG5AbcAQdwTcIOQFUGcEWbxLvQm7gI5ilqbI6C4TKYB4CCTDC3wpJM13Nr57ueUg4APagbolKWVTKKmqhYq1p/iQwCuHRwOh3oz86hI+yL8OV/lcP8FBxgFIKU2NgqND9PCl0hzfdp0wcXCBTIaI376PiAcUsbceI/tKXi/bDt94wU7NZGLHvrh0xoQ/UsrCXkHsNjZ+VIKmEsAd1S0JzC3zPwpkduRdopc0lZteOlV6jJaEZcVwRAGoKf6uB66cxsnVEOB3v8rkw6XLYYZAIhX8GmrtEx1ZCVAO3rDBkX4kwG8aINv+4g+42nRIPDTtwmycgAkARlY4duM1tOiWKCDtwmwI/d3LriCj8eGj020nabhTiVEWYkVSMtUKcSE7YjOjNppyxUoioOjxy6iJMGQs466rJymQZlcg8iABQDKHtdLzLVQ2WClHpNVq8w3lCGgSungsTf83zeaJp84j8RMMcGpovy9YnbyIiWjvkUgQATHi79PbrC/vu2uH4a4e9InC1bLI1J0S0lM49Av7ivViIauCWyVmCXEMp2kGLiYVQ0dbh/TkiKnP/17x64L1pnG9q9qBuzS4MxBut+a0UK5CXi/jlzyLi3KJhiuiCENMt93MkLg4mGidpzh1OmmjuhBGV8pF9OSrGhA58CIGKWgBm9uNsic5hzYYD2XPAghQHPDmiBB9CxHwCDvL9wXvnP59cp71qPdWYAAAAAElFTkSuQmCC","e":1},{"id":"image_4","w":68,"h":35,"u":"","p":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEQAAAAjCAYAAAAt4qxQAAAACXBIWXMAAAABAAAAAQBPJcTWAAAAJHpUWHRDcmVhdG9yAAAImXNMyU9KVXBMK0ktUnBNS0tNLikGAEF6Bs5qehXFAAACSElEQVRoge2ZTXKbQBCFX09UKDt0A3MDa4m8mqWEU2V8A3KCcAQdwTcwPoGUSiGyi1aJl+QEsW4Q7WRKobMZqyysH5AQYNC3ZKCr69Xr7mGGUBByZHcWrWVXiFgy0AHQ3fNJCKYnZgo/Llvh9Hb8t4g86ZTBe5OBDSbJgCTg8phYDPwmYEqA9/N6EuaVY5LcBTGDvkGxcAE4APS84ytmBHixiL3HwfenPAPnJoj57ZMkEbsAbvKKmQriByYe5iXM0YIoR9yhaCGSED+0n9vusb3mKEGufGvIgIvTlUZW5gy4j9cT79AABwliBn0DsRgf2yhPyNd2pDmHuCWzIGpyeKiOK7YxI8DOOpFElpdN33LANEL1xQCACwampm85WT5K7ZCeb90B+JI1qyrAwOe0fSWVIO9ZjBfSirJXENO3HALuc8mqbIhvf1nBeOcruxZVAx3lm1WpzAmQuxrtVkHUhivE+2igWZi1I627bSRvnzKxGKN+YgDAxbMWedsWNwpy5VvDCm+68uCmNxnYmxbelEyNSyXJvB1pRrJ03jhE/ajVXQwA0CMtcpMP1xyifuF/FJdT6cxZxN3XRwdrDlHnGU1CV4dZK1YOUb3jT/E5lc5aL1k5JKlUg9AXWrSaOK9Lxik+l8qwMoMA1Ba9GZNlIwRcmkHfAF4cwiRLzKcS0L8PNqAEYUCWmk0VIJYAIOTI7tR8m54WCQBi0Vruu1JsCroZ9A0hRCzLzqQqEFNXqIvnM1CCYP8tfKPIdA1RdxiQZ0ESnAU5s5v/GNG2dQAlMzcAAAAASUVORK5CYII=","e":1},{"id":"image_5","w":189,"h":82,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_6","w":120,"h":148,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_7","w":120,"h":148,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_8","w":209,"h":103,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_9","w":221,"h":151,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_10","w":221,"h":169,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_11","w":114,"h":142,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_12","w":114,"h":142,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_13","w":82,"h":35,"u":"","p":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFIAAAAjCAYAAAAHUl3/AAAACXBIWXMAAAABAAAAAQBPJcTWAAAAJHpUWHRDcmVhdG9yAAAImXNMyU9KVXBMK0ktUnBNS0tNLikGAEF6Bs5qehXFAAACrElEQVRoge2ZMXLaQBSG/ycx2B1cIIOqTDpTCpqoBIVCOUCMdANyA3ICkxNIhiZdyEzCpFSFKaFOY2Z8AOgCsfRSGAgmWEggCcvoq2C1+/Tmm7e7WolwZOROTQG7ksCQWGAJTJKPYRNiDJlowkTD8/vM0Da6k4hT9YTivJliavnf4lwjYoWZigRchBh+DGAIkE0O232jNwwx9k4iFymbFYlEUWOwHrK4XYzB6JILKw6pkYmU26pOgA7gbVT3CMCYGFbWzbaiWgJCFamYWn4m/tEBbgAohBk7NIiv+Z6bA+PnbahhwwpUar9rANwEkAsrZqSELPRgkXKnphC7Fp5rBXozJUarX+81Dw20t0jF1PKzzKwFpvqhSRwbBkaCA/2QTWkvkQmvQg/o483lj9ZeI4MOWKyFV/vcLCF8O3OyetDdPZDIUqdqvYSpvAsGRnBcLchG5FvkqUhcY0oOFL/rpi+RpbZq43k8WMeNb5nCrg6lTtXCaUoEgByLsGWzIu3q6CnyBKfzNnIQha5ianmvTk+KlNuqnkp8gICLmTi3vPqI2xrLplqEgC8AzqNILKG8efX+9fTu66/BtotbK9IVYSEpZ+ZY4auyqRa3XflPZPlabcb83jBRsIitJ59Hjz8Pr8Hmt0ir0RMGjMFlz1pve1SRs8yshVTiTghobratRMpmRUp3ad8U5LaqrzesRAqCoG/2TnmazapciWRCI/Zskk1B7tSU5R8BAEqdqoZ0bQwMwdGXv5cVqR0nlYTDtPImLBqUY+WScHLL6b2syBf2ySA+BNdVAB+v0VK8YUIR+CdyfMRckk4eWIgkhnXUVJIM8S2wEJl1sy2kVbkXDNECFiJtozshBxpSmcFgfB58+G4Da5tN3+gNz5xskRifkAr1hIERA8ZNvZeeBsPmL5S55GGPa+XPAAAAAElFTkSuQmCC","e":1},{"id":"image_14","w":82,"h":35,"u":"","p":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFIAAAAjCAYAAAAHUl3/AAAACXBIWXMAAAABAAAAAQBPJcTWAAAAJHpUWHRDcmVhdG9yAAAImXNMyU9KVXBMK0ktUnBNS0tNLikGAEF6Bs5qehXFAAACu0lEQVRoge2av3LaQBDGv71jHHfwAhmryqQz6QRNVNqKC+UBbIs3cN6ANzBvIBmadMEzCUmpyqbEdRoz4weALiKWNoWBsQnoD0jIsvWr0LG3t/PNak+3EiFj1M6RBvYVwVBYsAImJcK0ETEGTDRiosHufWngNLqjlEMNhLa5mGYZlT9yYhCxxkxVAvYTdD8EMADIIY+dq0ZvkKDvUFIXUrUOFJLSYLCZsHBhDMHokg97G6KmJqTa1k0CTAAf01ojBkNi2Dv+TiutEpCokJplVFz51wT4DMBekr4Tg/iC77nZb/y6TdRtUo5q7U9nADcBlJPymSoJC7qxkGrnSCP2bTzXDAxmTIzW1WmvuamjtYXULKPiltwWmE43DSJrGLgRHsxNNqW1hKxbepUlushnFgZAX65PfrTWmhl3wrQWnq+zWE64fOPtmHF391hC1jqH9ku4lcNg4Aaeb8TZiCIJ+ZLqYQzG5EGLWjdFFCNXTrqvTEQAKLOEU7f0ahTjUCFrnUMbz+N0kgVllnBU60AJMwwU8rXUxBDKkKKrWUYlyGilkGpbNwsRHyBg35UTO8hGLhusW3oVAl8B7KYRWE55//bzu/Hdt9/9ZX8uzUhfwkZezsxbhc9XbT7/CVm/0Jtb7hvmCpZYevJ58hz50Aab3KLIxkAYaPRPevbjsScZ6ZbcFgoRQyGguTg2F1K1DpRil47MntrWzccDcyGFEOaidcFqFrNyLiQTzrYeTb7ZUztH2uxCAECtc2igqI2xIXjm7PcsI41sQsk5THPdxHRAyyqWnFOe3d5i2tl4Ya8MtgcxVwFAUIki9dsKghHkF0JuAnnsAIDwhXCyDSXXDGevIkT/+LuDhy+5CuJCPH/2FgBAHgwA48wCyiEMNK6Pf3Zn1wIArhq9AXnQAFxmFVhOGIP4gjx8WOz+FCTEP90c53905F+iAAAAAElFTkSuQmCC","e":1},{"id":"comp_0","layers":[{"ddd":0,"ind":1,"ty":3,"nm":"MainPosition","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":8,"s":[500,500,0],"to":[0,-7.667,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":14,"s":[500,454,0],"to":[0,0,0],"ti":[0,-7.667,0]},{"t":21.0000008553475,"s":[500,500,0]}],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"ip":0,"op":180.00000733155,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":3,"nm":"BodyPosition","parent":1,"sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[0,0,0],"to":[0,1,0],"ti":[0,16.833,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":4.114,"s":[0,6,0],"to":[0,-16.833,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":13,"s":[0,-101,0],"to":[0,0,0],"ti":[0,-16.833,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":20.571,"s":[0,6,0],"to":[0,16.833,0],"ti":[0,1,0]},{"t":24.00000097754,"s":[0,0,0]}],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"ip":0,"op":180.00000733155,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":2,"nm":"OnEyes","parent":2,"refId":"image_0","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":60.571,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":61.429,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":62.286,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":82.285,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":83.143,"s":[100]},{"t":84.0000034213901,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-15.5,-71.36,0],"ix":2},"a":{"a":0,"k":[85.21,9.914,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"ip":0,"op":180.00000733155,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":2,"nm":"RightEyesBottom","parent":2,"refId":"image_1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[38.862,-38.621,0],"ix":2},"a":{"a":0,"k":[33,33,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":58,"s":[100,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":61.429,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":64,"s":[100,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":80,"s":[100,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":83.429,"s":[100,100,100]},{"t":86.0000035028518,"s":[100,0,100]}],"ix":6}},"ao":0,"ip":0,"op":180.00000733155,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":2,"nm":"LeftEyeBottom","parent":2,"refId":"image_2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-69.25,-34.878,0],"ix":2},"a":{"a":0,"k":[34,35,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":58,"s":[100,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":61.429,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":64,"s":[100,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":80,"s":[100,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":83.429,"s":[100,100,100]},{"t":86.0000035028518,"s":[100,0,100]}],"ix":6}},"ao":0,"ip":0,"op":180.00000733155,"st":0,"bm":0},{"ddd":0,"ind":6,"ty":2,"nm":"RightEye","parent":2,"refId":"image_3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[39.112,-101.85,0],"ix":2},"a":{"a":0,"k":[33,0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":58,"s":[100,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":61.429,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":64,"s":[100,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":80,"s":[100,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":83.429,"s":[100,100,100]},{"t":86.0000035028518,"s":[100,0,100]}],"ix":6}},"ao":0,"ip":0,"op":180.00000733155,"st":0,"bm":0},{"ddd":0,"ind":7,"ty":2,"nm":"LeftEye","parent":2,"refId":"image_4","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-69.25,-103.205,0],"ix":2},"a":{"a":0,"k":[34,0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":58,"s":[100,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":61.429,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":64,"s":[100,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":80,"s":[100,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":83.429,"s":[100,100,100]},{"t":86.0000035028518,"s":[100,0,100]}],"ix":6}},"ao":0,"ip":0,"op":180.00000733155,"st":0,"bm":0},{"ddd":0,"ind":8,"ty":2,"nm":"Eyes","parent":2,"refId":"image_5","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-16.171,-69.611,0],"ix":2},"a":{"a":0,"k":[94.491,40.639,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"ip":0,"op":180.00000733155,"st":0,"bm":0},{"ddd":0,"ind":9,"ty":2,"nm":"RightHand","parent":2,"refId":"image_6","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[39.349,122.629,0],"ix":2},"a":{"a":0,"k":[59.828,73.566,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"ip":0,"op":180.00000733155,"st":0,"bm":0},{"ddd":0,"ind":10,"ty":2,"nm":"LeftHand","parent":2,"refId":"image_7","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-66.773,122.629,0],"ix":2},"a":{"a":0,"k":[59.828,73.566,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"ip":0,"op":180.00000733155,"st":0,"bm":0},{"ddd":0,"ind":11,"ty":2,"nm":"Mouth","parent":2,"refId":"image_8","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-15.049,20.833,0],"ix":2},"a":{"a":0,"k":[104.338,51.169,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"ip":0,"op":180.00000733155,"st":0,"bm":0},{"ddd":0,"ind":12,"ty":2,"nm":"Head","parent":2,"refId":"image_9","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-15.049,-2.296,0],"ix":2},"a":{"a":0,"k":[110.199,75.419,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"ip":0,"op":180.00000733155,"st":0,"bm":0},{"ddd":0,"ind":13,"ty":2,"nm":"Stomach","parent":2,"refId":"image_10","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-13.927,95.324,0],"ix":2},"a":{"a":0,"k":[110.199,84.406,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"ip":0,"op":180.00000733155,"st":0,"bm":0},{"ddd":0,"ind":14,"ty":3,"nm":"LegsPosition","parent":1,"sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":8,"s":[0,0,0],"to":[0,-4.5,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":13,"s":[0,-27,0],"to":[0,0,0],"ti":[0,-4.5,0]},{"t":19.0000007738859,"s":[0,0,0]}],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":8,"s":[100,108,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":13,"s":[91,99,100]},{"t":22.0000008960784,"s":[100,108,100]}],"ix":6}},"ao":0,"ip":0,"op":180.00000733155,"st":0,"bm":0},{"ddd":0,"ind":15,"ty":2,"nm":"RightLeg","parent":14,"refId":"image_11","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":13,"s":[-37]},{"t":21.0000008553475,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[74.115,103.934,0],"to":[-0.833,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":13,"s":[69.115,103.934,0],"to":[0,0,0],"ti":[-0.833,0,0]},{"t":21.0000008553475,"s":[74.115,103.934,0]}],"ix":2},"a":{"a":0,"k":[56.532,70.56,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"ip":0,"op":180.00000733155,"st":0,"bm":0},{"ddd":0,"ind":16,"ty":2,"nm":"LeftLeg","parent":14,"refId":"image_12","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":13,"s":[37]},{"t":21.0000008553475,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[-100.612,103.934,0],"to":[0.833,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":13,"s":[-95.612,103.934,0],"to":[0,0,0],"ti":[0.833,0,0]},{"t":21.0000008553475,"s":[-100.612,103.934,0]}],"ix":2},"a":{"a":0,"k":[56.533,70.56,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"ip":0,"op":180.00000733155,"st":0,"bm":0},{"ddd":0,"ind":17,"ty":2,"nm":"RighFoot","parent":14,"refId":"image_13","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[104.031,155.896,0],"ix":2},"a":{"a":0,"k":[40.666,17.079,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"ip":0,"op":180.00000733155,"st":0,"bm":0},{"ddd":0,"ind":18,"ty":2,"nm":"LeftFoot","parent":14,"refId":"image_14","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-132.85,155.896,0],"ix":2},"a":{"a":0,"k":[40.667,17.079,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"ip":0,"op":180.00000733155,"st":0,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Tong","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[502.555,504.25,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[93.502,101.538,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[23,13],[12,2]],"o":[[0,0],[-23,-13],[-12,-2]],"v":[[3,-6],[-38.931,-13.409],[-138.5,-65]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.764705882353,0.164936559341,0.249609778909,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":12,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":32,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":36,"s":[100]},{"t":42.0000017106951,"s":[0]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":180.00000733155,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Mouth","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[498.43,500.25,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":30,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":33,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":41,"s":[100,100,100]},{"t":43.0000017514259,"s":[0,0,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[18.75,8.5],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.407843167174,0.215686289469,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":6,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[4.125,-0.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":180.00000733155,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":0,"nm":"FrogJump","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[522,500,0],"ix":2},"a":{"a":0,"k":[500,500,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"w":1000,"h":1000,"ip":0,"op":180.00000733155,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":3,"nm":"FlyPosition","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.667,"y":0.322},"o":{"x":0.333,"y":0},"t":1,"s":[279,686,0],"to":[-2.714,2.544,0],"ti":[-59.875,9.112,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0.613},"t":19,"s":[473.908,678.366,0],"to":[142.428,-21.675,0],"ti":[2.853,1.115,0]},{"t":36.0000014663101,"s":[672,632,0]}],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"ip":0,"op":180.00000733155,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Shape Layer 2","parent":4,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":1,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":2.654,"s":[9]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":4.308,"s":[-33]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":5.961,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":7.615,"s":[9]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":9.269,"s":[-33]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":10.924,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":12.576,"s":[9]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":14.231,"s":[-33]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":15.885,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":17.539,"s":[9]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":19.193,"s":[-33]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":20.846,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":22.5,"s":[9]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":24.154,"s":[-33]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":25.808,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":27.461,"s":[9]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":29.115,"s":[-33]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":30.769,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":32.424,"s":[9]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":34.076,"s":[-33]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":35.731,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":37.385,"s":[9]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":39.039,"s":[-33]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":40.693,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":42.346,"s":[9]},{"t":44.0000017921567,"s":[-33]}],"ix":10},"p":{"a":0,"k":[-296.625,-194.312,0],"ix":2},"a":{"a":0,"k":[-295.562,-193.562,0],"ix":1},"s":{"a":0,"k":[105.582,108.337,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[7.125,8.875],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.741176470588,0.741176470588,0.741176470588,1],"ix":4},"o":{"a":0,"k":99,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-292.312,-191.938],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[61.079,79.568],"ix":3},"r":{"a":0,"k":-69.975,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":1.00000004073083,"op":36.0000014663101,"st":13.0000005295009,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"Shape Layer 1","parent":4,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-295.727,-194.125,0],"ix":2},"a":{"a":0,"k":[-293.438,-194.125,0],"ix":1},"s":{"a":0,"k":[102.352,55.053,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[13,14],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":99,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-293.5,-194],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":1.00000004073083,"op":36.0000014663101,"st":-13.0000005295009,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"Shape Layer 3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[486,496,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[119.015,79.183,100],"ix":6}},"ao":0,"shapes":[],"ip":0,"op":180.00000733155,"st":0,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/apps/frontend/public/json/loadingAnimation.json b/apps/frontend/public/json/loadingAnimation.json new file mode 100644 index 00000000..4046a962 --- /dev/null +++ b/apps/frontend/public/json/loadingAnimation.json @@ -0,0 +1 @@ +{"v":"5.7.8","fr":29.9700012207031,"ip":0,"op":30.0000012219251,"w":500,"h":500,"nm":"Loading","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 4","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":13,"s":[546,266,0],"to":[0,-11,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":17,"s":[546,200,0],"to":[0,0,0],"ti":[0,-11,0]},{"t":21.0000008553475,"s":[546,266,0]}],"ix":2,"l":2,"x":"var $bm_rt;\nvar $bm_rt;\nvar n, n, t, t, v, amp, freq, decay;\n$bm_rt = $bm_rt = n = 0;\nif (numKeys > 0) {\n $bm_rt = $bm_rt = n = nearestKey(time).index;\n if (key(n).time > time) {\n n--;\n }\n}\nif (n == 0) {\n $bm_rt = $bm_rt = t = 0;\n} else {\n $bm_rt = $bm_rt = t = sub(time, key(n).time);\n}\nif (n > 0 && t < 1) {\n v = velocityAtTime(sub(key(n).time, div(thisComp.frameDuration, 10)));\n amp = 0.05;\n freq = 4;\n decay = 8;\n $bm_rt = $bm_rt = add(value, div(mul(mul(v, amp), Math.sin(mul(mul(mul(freq, t), 2), Math.PI))), Math.exp(mul(decay, t))));\n} else {\n $bm_rt = $bm_rt = value;\n}"},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[45.137,45.137],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.7490196078431373,0.24705882352941178,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":8,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.7490196078431373,0.24705882352941178,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-167.432,-17.432],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":30.0000012219251,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Shape Layer 3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":9,"s":[454,266,0],"to":[0,-11,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":13,"s":[454,200,0],"to":[0,0,0],"ti":[0,-11,0]},{"t":17.0000006924242,"s":[454,266,0]}],"ix":2,"l":2,"x":"var $bm_rt;\nvar $bm_rt;\nvar n, n, t, t, v, amp, freq, decay;\n$bm_rt = $bm_rt = n = 0;\nif (numKeys > 0) {\n $bm_rt = $bm_rt = n = nearestKey(time).index;\n if (key(n).time > time) {\n n--;\n }\n}\nif (n == 0) {\n $bm_rt = $bm_rt = t = 0;\n} else {\n $bm_rt = $bm_rt = t = sub(time, key(n).time);\n}\nif (n > 0 && t < 1) {\n v = velocityAtTime(sub(key(n).time, div(thisComp.frameDuration, 10)));\n amp = 0.05;\n freq = 4;\n decay = 8;\n $bm_rt = $bm_rt = add(value, div(mul(mul(v, amp), Math.sin(mul(mul(mul(freq, t), 2), Math.PI))), Math.exp(mul(decay, t))));\n} else {\n $bm_rt = $bm_rt = value;\n}"},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[45.137,45.137],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.3568627450980392,0.7607843137254902,0.9058823529411765,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":8,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.3568627450980392,0.7607843137254902,0.9058823529411765,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-167.432,-17.432],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":30.0000012219251,"st":-359.00001462237,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Shape Layer 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":4,"s":[365,266,0],"to":[0,-11,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":9,"s":[365,200,0],"to":[0,0,0],"ti":[0,-11,0]},{"t":13.0000005295009,"s":[365,266,0]}],"ix":2,"l":2,"x":"var $bm_rt;\nvar $bm_rt;\nvar n, n, t, t, v, amp, freq, decay;\n$bm_rt = $bm_rt = n = 0;\nif (numKeys > 0) {\n $bm_rt = $bm_rt = n = nearestKey(time).index;\n if (key(n).time > time) {\n n--;\n }\n}\nif (n == 0) {\n $bm_rt = $bm_rt = t = 0;\n} else {\n $bm_rt = $bm_rt = t = sub(time, key(n).time);\n}\nif (n > 0 && t < 1) {\n v = velocityAtTime(sub(key(n).time, div(thisComp.frameDuration, 10)));\n amp = 0.05;\n freq = 4;\n decay = 8;\n $bm_rt = $bm_rt = add(value, div(mul(mul(v, amp), Math.sin(mul(mul(mul(freq, t), 2), Math.PI))), Math.exp(mul(decay, t))));\n} else {\n $bm_rt = $bm_rt = value;\n}"},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[45.137,45.137],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.7019607843137254,0.5333333333333333,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":8,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0.7019607843137254,0.5333333333333333,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-167.432,-17.432],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":30.0000012219251,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Shape Layer 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":0,"s":[277,266,0],"to":[0,-11,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":4,"s":[277,200,0],"to":[0,0,0],"ti":[0,-11,0]},{"t":9.00000036657752,"s":[277,266,0]}],"ix":2,"l":2,"x":"var $bm_rt;\nvar $bm_rt;\nvar n, n, t, t, v, amp, freq, decay;\n$bm_rt = $bm_rt = n = 0;\nif (numKeys > 0) {\n $bm_rt = $bm_rt = n = nearestKey(time).index;\n if (key(n).time > time) {\n n--;\n }\n}\nif (n == 0) {\n $bm_rt = $bm_rt = t = 0;\n} else {\n $bm_rt = $bm_rt = t = sub(time, key(n).time);\n}\nif (n > 0 && t < 1) {\n v = velocityAtTime(sub(key(n).time, div(thisComp.frameDuration, 10)));\n amp = 0.05;\n freq = 4;\n decay = 8;\n $bm_rt = $bm_rt = add(value, div(mul(mul(v, amp), Math.sin(mul(mul(mul(freq, t), 2), Math.PI))), Math.exp(mul(decay, t))));\n} else {\n $bm_rt = $bm_rt = value;\n}"},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[45.137,45.137],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.06274509803921569,0.023529411764705882,0.6235294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":8,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.06274509803921569,0.023529411764705882,0.6235294117647059,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-167.432,-17.432],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":30.0000012219251,"st":0,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/apps/frontend/public/mockServiceWorker.js b/apps/frontend/public/mockServiceWorker.js new file mode 100644 index 00000000..749090e1 --- /dev/null +++ b/apps/frontend/public/mockServiceWorker.js @@ -0,0 +1,291 @@ +/* tslint:disable */ + +/** + * Mock Service Worker. + * @see https://github.com/mswjs/msw + * - Please do NOT modify this file. + * - Please do NOT serve this file on production. + */ + +const PACKAGE_VERSION = '2.6.4'; +const INTEGRITY_CHECKSUM = 'ca7800994cc8bfb5eb961e037c877074'; +const IS_MOCKED_RESPONSE = Symbol('isMockedResponse'); +const activeClientIds = new Set(); + +self.addEventListener('install', function () { + self.skipWaiting(); +}); + +self.addEventListener('activate', function (event) { + event.waitUntil(self.clients.claim()); +}); + +self.addEventListener('message', async function (event) { + const clientId = event.source.id; + + if (!clientId || !self.clients) { + return; + } + + const client = await self.clients.get(clientId); + + if (!client) { + return; + } + + const allClients = await self.clients.matchAll({ + type: 'window' + }); + + switch (event.data) { + case 'KEEPALIVE_REQUEST': { + sendToClient(client, { + type: 'KEEPALIVE_RESPONSE' + }); + break; + } + + case 'INTEGRITY_CHECK_REQUEST': { + sendToClient(client, { + type: 'INTEGRITY_CHECK_RESPONSE', + payload: { + packageVersion: PACKAGE_VERSION, + checksum: INTEGRITY_CHECKSUM + } + }); + break; + } + + case 'MOCK_ACTIVATE': { + activeClientIds.add(clientId); + + sendToClient(client, { + type: 'MOCKING_ENABLED', + payload: { + client: { + id: client.id, + frameType: client.frameType + } + } + }); + break; + } + + case 'MOCK_DEACTIVATE': { + activeClientIds.delete(clientId); + break; + } + + case 'CLIENT_CLOSED': { + activeClientIds.delete(clientId); + + const remainingClients = allClients.filter((client) => { + return client.id !== clientId; + }); + + // Unregister itself when there are no more clients + if (remainingClients.length === 0) { + self.registration.unregister(); + } + + break; + } + } +}); + +self.addEventListener('fetch', function (event) { + const { request } = event; + + // Bypass navigation requests. + if (request.mode === 'navigate') { + return; + } + + // Opening the DevTools triggers the "only-if-cached" request + // that cannot be handled by the worker. Bypass such requests. + if (request.cache === 'only-if-cached' && request.mode !== 'same-origin') { + return; + } + + // Bypass all requests when there are no active clients. + // Prevents the self-unregistered worked from handling requests + // after it's been deleted (still remains active until the next reload). + if (activeClientIds.size === 0) { + return; + } + + // Generate unique request ID. + const requestId = crypto.randomUUID(); + event.respondWith(handleRequest(event, requestId)); +}); + +async function handleRequest(event, requestId) { + const client = await resolveMainClient(event); + const response = await getResponse(event, client, requestId); + + // Send back the response clone for the "response:*" life-cycle events. + // Ensure MSW is active and ready to handle the message, otherwise + // this message will pend indefinitely. + if (client && activeClientIds.has(client.id)) { + (async function () { + const responseClone = response.clone(); + + sendToClient( + client, + { + type: 'RESPONSE', + payload: { + requestId, + isMockedResponse: IS_MOCKED_RESPONSE in response, + type: responseClone.type, + status: responseClone.status, + statusText: responseClone.statusText, + body: responseClone.body, + headers: Object.fromEntries(responseClone.headers.entries()) + } + }, + [responseClone.body] + ); + })(); + } + + return response; +} + +// Resolve the main client for the given event. +// Client that issues a request doesn't necessarily equal the client +// that registered the worker. It's with the latter the worker should +// communicate with during the response resolving phase. +async function resolveMainClient(event) { + const client = await self.clients.get(event.clientId); + + if (activeClientIds.has(event.clientId)) { + return client; + } + + if (client?.frameType === 'top-level') { + return client; + } + + const allClients = await self.clients.matchAll({ + type: 'window' + }); + + return allClients + .filter((client) => { + // Get only those clients that are currently visible. + return client.visibilityState === 'visible'; + }) + .find((client) => { + // Find the client ID that's recorded in the + // set of clients that have registered the worker. + return activeClientIds.has(client.id); + }); +} + +async function getResponse(event, client, requestId) { + const { request } = event; + + // Clone the request because it might've been already used + // (i.e. its body has been read and sent to the client). + const requestClone = request.clone(); + + function passthrough() { + // Cast the request headers to a new Headers instance + // so the headers can be manipulated with. + const headers = new Headers(requestClone.headers); + + // Remove the "accept" header value that marked this request as passthrough. + // This prevents request alteration and also keeps it compliant with the + // user-defined CORS policies. + headers.delete('accept', 'msw/passthrough'); + + return fetch(requestClone, { headers }); + } + + // Bypass mocking when the client is not active. + if (!client) { + return passthrough(); + } + + // Bypass initial page load requests (i.e. static assets). + // The absence of the immediate/parent client in the map of the active clients + // means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet + // and is not ready to handle requests. + if (!activeClientIds.has(client.id)) { + return passthrough(); + } + + // Notify the client that a request has been intercepted. + const requestBuffer = await request.arrayBuffer(); + const clientMessage = await sendToClient( + client, + { + type: 'REQUEST', + payload: { + id: requestId, + url: request.url, + mode: request.mode, + method: request.method, + headers: Object.fromEntries(request.headers.entries()), + cache: request.cache, + credentials: request.credentials, + destination: request.destination, + integrity: request.integrity, + redirect: request.redirect, + referrer: request.referrer, + referrerPolicy: request.referrerPolicy, + body: requestBuffer, + keepalive: request.keepalive + } + }, + [requestBuffer] + ); + + switch (clientMessage.type) { + case 'MOCK_RESPONSE': { + return respondWithMock(clientMessage.data); + } + + case 'PASSTHROUGH': { + return passthrough(); + } + } + + return passthrough(); +} + +function sendToClient(client, message, transferrables = []) { + return new Promise((resolve, reject) => { + const channel = new MessageChannel(); + + channel.port1.onmessage = (event) => { + if (event.data && event.data.error) { + return reject(event.data.error); + } + + resolve(event.data); + }; + + client.postMessage(message, [channel.port2].concat(transferrables.filter(Boolean))); + }); +} + +async function respondWithMock(response) { + // Setting response status code to 0 is a no-op. + // However, when responding with a "Response.error()", the produced Response + // instance will have status code set to 0. Since it's not possible to create + // a Response instance with status code 0, handle that use-case separately. + if (response.status === 0) { + return Response.error(); + } + + const mockedResponse = new Response(response.body, response); + + Reflect.defineProperty(mockedResponse, IS_MOCKED_RESPONSE, { + value: true, + enumerable: true + }); + + return mockedResponse; +} diff --git a/apps/frontend/public/site.webmanifest b/apps/frontend/public/site.webmanifest new file mode 100644 index 00000000..45dc8a20 --- /dev/null +++ b/apps/frontend/public/site.webmanifest @@ -0,0 +1 @@ +{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} \ No newline at end of file diff --git a/apps/frontend/src/app/main.tsx b/apps/frontend/src/app/main.tsx new file mode 100644 index 00000000..eb53c5e3 --- /dev/null +++ b/apps/frontend/src/app/main.tsx @@ -0,0 +1,35 @@ +import { StrictMode } from 'react'; +import ReactDOM from 'react-dom/client'; +import { QueryProvider } from './query'; +import { RouteProvider } from './router'; + +import './style/index.css'; +import { OverlayProvider } from '@/shared/overlay'; + +async function enableMocking() { + if (process.env.NODE_ENV !== 'development') { + return; + } + + const { worker } = await import('./mock/browser'); + + return worker.start({ + onUnhandledRequest: (request, print) => { + if (!request.url.includes('/api/')) return; + print.warning(); + } + }); +} +enableMocking(); + +const root = ReactDOM.createRoot(document.getElementById('root')!); + +root.render( + + + + + + + +); diff --git a/apps/frontend/src/app/mock/MockRepository/MockRepository.test.ts b/apps/frontend/src/app/mock/MockRepository/MockRepository.test.ts new file mode 100644 index 00000000..df3e87a3 --- /dev/null +++ b/apps/frontend/src/app/mock/MockRepository/MockRepository.test.ts @@ -0,0 +1,183 @@ +import { describe, expect, test } from 'vitest'; +import { MockRepository } from './MockRepository'; + +interface User { + userId: string; + password: string; + name: string; + email: string; + age: number; + isAdult: boolean; +} + +const getRepository = (...users: User[]) => { + const repository = new MockRepository(); + + for (const user of users) { + repository.create(user); + } + + return repository; +}; + +describe('InMemoryRepository', () => { + test('create 메서드는 데이터를 생성하고 생성된 데이터를 반환한다.', async () => { + //Given + const repository = getRepository(); + const user = users[0]; + const expected = { id: '0', ...user }; + + //When + const result = await repository.create(user); + + //Then + expect(result).toEqual(expected); + }); + + test('delete 메서드는 데이터를 삭제하고 삭제된 데이터를 반환한다.', async () => { + //Given + const repository = getRepository(...users); + const user = users[1]; + const expected = { id: '1', ...user }; + + //When + const result = await repository.delete({ userId: user.userId }); + + //Then + expect(result).toEqual(expected); + }); + + test('delete 메서드는 데이터가 없으면 에러를 반환한다.', async () => { + //Given + const repository = getRepository(...users); + const notExistId = 'user6'; + + // When + const run = async () => repository.delete({ userId: notExistId }); + + // Then + expect(run).rejects.toThrow('Not found'); + }); + + test('update 메서드는 데이터를 수정하고 수정된 데이터를 반환한다.', async () => { + //Given + const repository = getRepository(...users); + + const user = users[4]; + const updateData = { name: 'updatedUser' }; + const expected = { id: '4', ...user, ...updateData }; + + //When + const result = await repository.update({ userId: user.userId }, updateData); + + //Then + expect(result).toEqual(expected); + }); + + test('update 메서드는 데이터가 없으면 에러를 반환한다.', async () => { + //Given + const repository = getRepository(...users); + const notExistId = 'user6'; + + //When + const run = async () => repository.update({ userId: notExistId }, { name: 'updatedUser' }); + + //Then + expect(run).rejects.toThrow('Not found'); + }); + + test('findMany 메서드는 조건에 맞는 데이터를 찾아 반환한다.', async () => { + //Given + const repository = getRepository(...users); + + const query = { isAdult: true }; + const expected = users.map((user, id) => ({ id: String(id), ...user })).filter((user) => user.isAdult); + + //When + const result = await repository.findMany({ query }); + + //Then + expect(result).toEqual(expected); + }); + + test('findMany 메서드는 조건에 맞는 데이터가 없으면 빈 배열을 반환한다.', async () => { + //Given + const repository = getRepository(...users); + const query = { age: -1 }; + + //When + const result = await repository.findMany({ query }); + + //Then + expect(result).toEqual([]); + }); + + test('findOne 메서드는 조건에 맞는 데이터를 찾아 반환한다.', async () => { + //Given + const repository = getRepository(...users); + const user = users[2]; + const query = { userId: user.userId }; + const expected = { id: '2', ...user }; + + //When + const result = await repository.findOne(query); + + //Then + expect(result).toEqual(expected); + }); + + test('findOne 메서드는 조건에 맞는 데이터가 없으면 에러를 반환한다.', async () => { + //Given + const repository = getRepository(...users); + const query = { age: -1 }; + + //When + const run = async () => repository.findOne(query); + + //Then + expect(run).rejects.toThrow('Not found'); + }); +}); + +const users: User[] = [ + { + userId: 'user1', + password: 'password1', + name: 'Alice', + email: 'alice@example.com', + age: 25, + isAdult: true + }, + { + userId: 'user2', + password: 'password2', + name: 'Bob', + email: 'bob@example.com', + age: 17, + isAdult: false + }, + { + userId: 'user3', + password: 'password3', + name: 'Charlie', + email: 'charlie@example.com', + age: 30, + isAdult: true + }, + { + userId: 'user4', + password: 'password4', + name: 'David', + email: 'david@example.com', + age: 22, + isAdult: true + }, + { + userId: 'user5', + password: 'password5', + name: 'Eve', + email: 'eve@example.com', + age: 16, + isAdult: false + } +]; diff --git a/apps/frontend/src/app/mock/MockRepository/MockRepository.ts b/apps/frontend/src/app/mock/MockRepository/MockRepository.ts new file mode 100644 index 00000000..20f6646a --- /dev/null +++ b/apps/frontend/src/app/mock/MockRepository/MockRepository.ts @@ -0,0 +1,96 @@ +export type Identifiable = T & { id: string }; + +export class MockRepository { + private _autoId = 0; + private memory: Identifiable[] = []; + + private isMatch(owner: Partial, target: Partial): boolean { + for (const key in target) { + if ( + !Object.prototype.hasOwnProperty.call(owner, key) || + target[key as keyof typeof target] !== owner[key as keyof typeof owner] + ) { + return false; + } + } + return true; + } + + private isPartialMatch(owner: Partial, target: Partial): boolean { + for (const key in target) { + if (!Object.prototype.hasOwnProperty.call(owner, key)) return false; + + const ownerValue = owner[key as keyof T]; + const targetValue = target[key as keyof T]; + + if (typeof ownerValue === 'boolean' && ownerValue !== targetValue) { + return false; + } + + if (typeof targetValue === 'string' && !(ownerValue as string)?.includes(targetValue)) { + return false; + } + } + return true; + } + + private generateId() { + return String(this._autoId++); + } + + private paginate(items: Identifiable[], page: number, size: number) { + const start = (page - 1) * size; + const end = start + size; + const data = items.slice(start, end); + const maxPage = Math.ceil(items.length / size); + return { data, maxPage }; + } + + async create(arg: T) { + const data = { ...arg, id: this.generateId() }; + + this.memory.push(data); + + return data; + } + + async delete(query: Partial>) { + const index = this.memory.findIndex((t) => this.isMatch(t, query)); + + if (index === -1) throw new Error('Not found'); + + const deletedData = this.memory[index]; + + this.memory = this.memory.filter((t) => t.id !== deletedData.id); + + return deletedData; + } + + async update(query: Partial>, data: Partial>) { + const index = this.memory.findIndex((item) => this.isMatch(item, query)); + + if (index === -1) throw new Error('Not found'); + + this.memory[index] = { ...this.memory[index], ...data }; + + return this.memory[index]; + } + + async findMany({ query, page = 1, size = 10 }: { query?: Partial>; page?: number; size?: number }) { + const filtered = query ? this.memory.filter((item) => this.isMatch(item, query)) : this.memory; + return this.paginate(filtered, page, size); + } + + async findOne(query: Partial>) { + const data = this.memory.find((item) => this.isMatch(item, query)); + + if (!data) throw new Error('Not found'); + + return data; + } + + async search({ query, page = 1, size = 10 }: { query?: Partial>; page?: number; size?: number }) { + const filtered = query ? this.memory.filter((item) => this.isPartialMatch(item, query)) : this.memory; + return this.paginate(filtered, page, size); + } +} diff --git a/apps/frontend/src/app/mock/MockRepository/index.ts b/apps/frontend/src/app/mock/MockRepository/index.ts new file mode 100644 index 00000000..218017f7 --- /dev/null +++ b/apps/frontend/src/app/mock/MockRepository/index.ts @@ -0,0 +1 @@ +export * from './MockRepository'; diff --git a/apps/frontend/src/app/mock/browser.ts b/apps/frontend/src/app/mock/browser.ts new file mode 100644 index 00000000..0a564278 --- /dev/null +++ b/apps/frontend/src/app/mock/browser.ts @@ -0,0 +1,4 @@ +import { setupWorker } from 'msw/browser'; +import { handlers } from './handlers'; + +export const worker = setupWorker(...handlers); diff --git a/apps/frontend/src/app/mock/gistResolvers.ts b/apps/frontend/src/app/mock/gistResolvers.ts new file mode 100644 index 00000000..cb6317d9 --- /dev/null +++ b/apps/frontend/src/app/mock/gistResolvers.ts @@ -0,0 +1,61 @@ +import { HttpResponse, PathParams } from 'msw'; + +// 사용자의 Gist 목록 조회 +export const getUserGistList = () => { + return HttpResponse.json({ + gists: [ + { + gistId: 'abc123', + title: 'My First Gist', + nickname: 'tech_guru' + }, + { + gistId: 'def456', + title: 'React Hook Tips', + nickname: 'frontend_master' + }, + { + gistId: 'ghi789', + title: 'Node.js Best Practices', + nickname: 'backend_expert' + } + ], + page: 1, + size: 10, + hasNextPage: true + }); +}; + +// 특정 Gist 파일 조회 api +export const getGistDetail = ({ params }: { params: PathParams }) => { + const { gistId } = params; + + if (!gistId) { + return new HttpResponse('Bad Request', { + status: 400, + headers: { + 'Content-Type': 'text/plain' + } + }); + } + + return HttpResponse.json({ + files: [ + { + filename: 'index.tsx', + language: 'ts', + content: 'const a = 1;\nconsole.log(a);\n' + }, + { + filename: 'main.tsx', + language: 'ts', + content: `const a = 1;` + }, + { + filename: 'README.md', + language: 'md', + content: `# Hello World\n\n This is a markdown file.` + } + ] + }); +}; diff --git a/apps/frontend/src/app/mock/handlers.ts b/apps/frontend/src/app/mock/handlers.ts new file mode 100644 index 00000000..73504cf5 --- /dev/null +++ b/apps/frontend/src/app/mock/handlers.ts @@ -0,0 +1,37 @@ +import { http } from 'msw'; +import { getGistDetail, getUserGistList } from './gistResolvers'; +import { getHistory, getHistoryList, getTagList, postCodeRun, postTag } from './historyResolvers'; +import { + deleteLotus, + getLotusDetail, + getPublicLotusList, + getUserLotusList, + patchLotus, + postCreateLotus +} from './lotusResolvers'; +import { getLogin, getUserInfo, patchUserInfo } from './userResolvers'; + +const apiUrl = (path: string) => `${import.meta.env.VITE_API_URL}${path}`; + +export const handlers = [ + // user + http.get(apiUrl(`/api/user`), getUserInfo), + http.patch(apiUrl(`/api/user`), patchUserInfo), + http.get(apiUrl(`/api/user/login/callback`), getLogin), + http.get(apiUrl(`/api/user/lotus`), getUserLotusList), + http.get(apiUrl(`/api/user/gist`), getUserGistList), + http.get(apiUrl(`/api/user/gist/:gistId`), getGistDetail), + // lotus + http.get(apiUrl(`/api/lotus`), getPublicLotusList), + http.get(apiUrl(`/api/lotus/:lotusId`), getLotusDetail), + http.post(apiUrl(`/api/lotus`), postCreateLotus), + http.patch(apiUrl(`/api/lotus/:id`), patchLotus), + http.delete(apiUrl(`/api/lotus/:id`), deleteLotus), + // history + http.get(apiUrl(`/api/lotus/:lotusId/history`), getHistoryList), + http.post(apiUrl(`/api/lotus/:lotusId/history`), postCodeRun), + http.get(apiUrl(`/api/lotus/:lotusId/history/:historyId`), getHistory), + // tag + http.get(apiUrl(`/api/tag`), getTagList), + http.post(apiUrl(`/api/tag`), postTag) +]; diff --git a/apps/frontend/src/app/mock/historyResolvers.ts b/apps/frontend/src/app/mock/historyResolvers.ts new file mode 100644 index 00000000..453de15c --- /dev/null +++ b/apps/frontend/src/app/mock/historyResolvers.ts @@ -0,0 +1,207 @@ +import { DefaultBodyType, HttpResponse, PathParams, StrictRequest } from 'msw'; +import { MockRepository } from './MockRepository'; +import { HistoryDto } from '@/feature/history'; + +const historyList = new MockRepository>(); + +const insertHistory = () => { + const historyMock: HistoryDto[] = [ + { + id: '2000001', + status: 'SUCCESS', + date: '2024-11-15T14:30:00Z', + filename: 'main.js', + input: '11', + output: '3' + }, + { + id: '2000002', + status: 'SUCCESS', + date: '2024-11-16T12:00:00Z', + filename: 'index.js', + input: '12', + output: 'console.log(7)' + }, + { + id: '2000003', + status: 'ERROR', + date: '2024-11-14T16:45:00Z', + filename: 'main.js', + input: '13', + output: 'Error: Cannot find module' + } + ]; + + for (const item of historyMock) { + historyList.create(item); + } +}; + +insertHistory(); + +// Lotus History 목록 조회 +export const getHistoryList = async ({ + request, + params +}: { + request: StrictRequest; + params: PathParams; +}) => { + const { lotusId } = params; + + const url = new URL(request.url); + const page = Number(url.searchParams.get('page')) || 1; + + if (!lotusId) { + return new HttpResponse('Bad Request', { + status: 400, + headers: { + 'Content-Type': 'text/plain' + } + }); + } + const { data, maxPage: max } = await historyList.findMany({ page }); + const list = data.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()); + + return HttpResponse.json({ + list, + page: { + current: page, + max + } + }); +}; + +// 코드 실행 +interface PostCodeRunBody { + input?: string; + execFileName: string; +} + +export const postCodeRun = async ({ request }: { request: StrictRequest }) => { + const body = (await request.json()) as PostCodeRunBody; + + if (!body?.execFileName) + return new HttpResponse('Bad Request', { + status: 400, + headers: { + 'Content-Type': 'text/plain' + } + }); + + const newHistory = await historyList.create({ + filename: body.execFileName, + date: new Date().toISOString(), + status: 'PENDING', + input: body?.input ?? '', + output: '' + }); + + setTimeout(() => { + historyList.update( + { id: newHistory.id }, + { + status: 'SUCCESS', + output: `입력한 값: ${newHistory.input} ` + } + ); + }, 2000); + + return HttpResponse.json({ + status: newHistory.status + }); +}; + +// 해당 히스토리 정보 +export const getHistory = async ({ params }: { params: PathParams }) => { + const { lotusId, historyId } = params; + + if (!lotusId || !historyId) { + return new HttpResponse('Bad Request', { + status: 400, + headers: { + 'Content-Type': 'text/plain' + } + }); + } + + const history = await historyList.findOne({ id: historyId as string }); + + return HttpResponse.json(history); +}; + +// 태그 조회 +export const getTagList = ({ request }: { request: StrictRequest }) => { + const authorization = request.headers.get('Authorization'); + + if (!authorization || !authorization.startsWith('Bearer ')) { + return new HttpResponse('Unauthorized: Invalid or missing token', { + status: 401, + headers: { + 'Content-Type': 'text/plain' + } + }); + } + + const url = new URL(request.url); + const keyword = url.searchParams.get('keyword'); + + if (!keyword) { + return new HttpResponse('Bad Request', { + status: 400, + headers: { + 'Content-Type': 'text/plain' + } + }); + } + + return HttpResponse.json({ + tags: [ + { + tagName: 'TypeScript' + }, + { + tagName: 'Generics' + }, + { + tagName: 'Programming' + } + ] + }); +}; + +// 태그 생성 +interface PostTagBody { + tag: string; +} + +export const postTag = async ({ request }: { request: StrictRequest }) => { + const authorization = request.headers.get('Authorization'); + + if (!authorization || !authorization.startsWith('Bearer ')) { + return new HttpResponse('Unauthorized: Invalid or missing token', { + status: 401, + headers: { + 'Content-Type': 'text/plain' + } + }); + } + + try { + const body = (await request.json()) as PostTagBody; + + if (!body) throw new Error('요청 형식이 올바르지 않음'); + + return HttpResponse.json({ + message: '성공' + }); + } catch (error) { + console.error(error); + return new HttpResponse('Bad Request', { + status: 400, + headers: { + 'Content-Type': 'text/plain' + } + }); + } +}; diff --git a/apps/frontend/src/app/mock/lotusResolvers.ts b/apps/frontend/src/app/mock/lotusResolvers.ts new file mode 100644 index 00000000..ba95478c --- /dev/null +++ b/apps/frontend/src/app/mock/lotusResolvers.ts @@ -0,0 +1,362 @@ +import { DefaultBodyType, HttpResponse, PathParams, StrictRequest } from 'msw'; +import { MockRepository } from './MockRepository'; +import { LotusDto } from '@/feature/lotus'; +import { UserDto } from '@/feature/user'; + +const lotusList = new MockRepository>(); + +insertLotus(); + +const MOCK_UUID = 'mock-uuid'; + +// 사용자의 Lotus 목록 조회 +export const getUserLotusList = async ({ request }: { request: StrictRequest }) => { + const authorization = request.headers.get('Authorization'); + + const [type, token] = authorization?.split(' ') || []; + + if (token !== MOCK_UUID || type !== 'Bearer') { + return new HttpResponse('Unauthorized: Invalid or missing token', { + status: 401, + headers: { + 'Content-Type': 'text/plain' + } + }); + } + + const url = new URL(request.url); + const page = Number(url.searchParams.get('page')) || 1; + const size = Number(url.searchParams.get('size')) || 5; + + const { data: lotuses, maxPage: max } = await lotusList.findMany({ page, size }); + + return HttpResponse.json({ + lotuses, + page: { + current: page, + max + } + }); +}; + +// public lotus 목록 조회 +export const getPublicLotusList = async ({ request }: { request: StrictRequest }) => { + const url = new URL(request.url); + const page = Number(url.searchParams.get('page')) || 1; + const size = Number(url.searchParams.get('size')) || 5; + const search = url.searchParams.get('search') || ''; + + const { data: lotuses, maxPage: max } = await lotusList.search({ + query: { title: search, isPublic: true }, + page, + size + }); + + return HttpResponse.json({ + lotuses, + page: { + current: page, + max + } + }); +}; + +// public lotus 상세 조회 +export const getLotusDetail = async ({ params }: { params: Record }) => { + const lotusId = params.lotusId; + + const lotus = await lotusList.findOne({ id: lotusId }); + + return HttpResponse.json({ + ...lotus, + language: 'javascript', + files: [ + { + filename: 'index.js', + language: 'javascript', + content: "console.log('Hello, World!');" + }, + { + filename: 'run.js', + language: 'javascript', + content: `function run() {\n console.log('Running...');\n}` + }, + { + filename: 'README.md', + language: 'markdown', + content: + '## #️⃣연관된 이슈\n' + + '\n' + + '#71\n' + + '\n' + + '## 📝작업 내용\n' + + '\n' + + '- MockRepository를 사용해 동적인 Mocking 구현\n' + + '- lotusList api 계층\n' + + '- lotusList query 계층\n' + + '- SuspenseLotusCardList 구현\n' + + '\n' + + '### 스크린샷 (선택)\n' + + '\n' + + '![lotusListPage](https://github.com/user-attachments/assets/01fa0ad7-f556-485f-b442-2b9a51161f0d)\n' + + '\n' + + '## 💬리뷰 요구사항(선택)\n' + + '\n' + + '> 모킹파일을 건드리고, 구조화 했더니 변경사항이 너무 많네요.. 죄송합니다.\n' + + '\n' + + '네이밍이 이상하거나 이해가 가지 않는 부분 모두 코멘트 남겨주세요!\n' + + '\n' + + '모킹은 순차적으로 동적으로 변경해볼 예정입니다.\n' + + '\n' + + '파일 경로나 query-key 구조화 같은 경우에는 추후에 정리가 필요해보입니다.\n' + } + ] + }); +}; + +type CreateLotusDto = { + title: string; + isPublic: false; + tags: string[]; + gistUuid: string; +}; + +//lotus 생성 +export const postCreateLotus = async ({ request }: { request: StrictRequest }) => { + const body = (await request.json()) as CreateLotusDto; + + const lotus = await lotusList.create({ + ...body, + date: new Date().toISOString(), + author: { id: '1', nickname: 'js_master', profile: 'https://devblog.com/authors/js_master', gistUrl: '' }, + logo: "data:image/png;base64,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';", + link: 'https://devblog.com/articles/1000000001', + isPublic: false, + gistUrl: '' + }); + + return HttpResponse.json(lotus); +}; + +// lotus 수정 +export const patchLotus = async ({ + params, + request +}: { + params: PathParams; + request: StrictRequest; +}) => { + const { id } = params; + + const body = (await request.json()) as Partial; + + if (!id || typeof id !== 'string') return HttpResponse.json({ message: 'id is required' }); + + const lotus = await lotusList.findOne({ id }); + + const updatedLotus = await lotusList.update(lotus, body); + + return HttpResponse.json(updatedLotus); +}; + +// lotus 삭제 +export const deleteLotus = async ({ params }: { params: PathParams }) => { + const { id } = params; + + if (!id || typeof id !== 'string') return HttpResponse.json({ message: 'id is required' }); + + const lotus = await lotusList.findOne({ + id + }); + + await lotusList.delete(lotus); + + return HttpResponse.json(lotus); +}; + +function insertLotus() { + const jsImage = + 'data:image/png;base64,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'; + + const mockLotusData: (LotusDto & { author: UserDto })[] = [ + { + id: '1000000001', + link: 'https://devblog.com/articles/1000000001', + title: 'Understanding JavaScript Closures', + logo: jsImage, + date: new Date('2024-11-01').toISOString(), + tags: ['JavaScript', 'Closures', 'Web Development'], + author: { + id: '0', + nickname: 'js_master', + profile: 'https://devblog.com/authors/js_master', + gistUrl: '' + }, + isPublic: true, + gistUrl: '' + }, + { + id: '1000000002', + link: 'https://devblog.com/articles/1000000002', + title: 'A Guide to Responsive Design with CSS', + logo: jsImage, + date: new Date('2024-10-28').toISOString(), + tags: ['CSS', 'Responsive Design', 'Frontend'], + author: { + id: '2', + nickname: 'css_wizard', + profile: 'https://devblog.com/authors/css_wizard', + gistUrl: '' + }, + isPublic: true, + gistUrl: '' + }, + { + id: '1000000003', + link: 'https://devblog.com/articles/1000000003', + title: 'TypeScript vs JavaScript: Key Differences', + logo: jsImage, + date: new Date('2024-10-25').toISOString(), + tags: ['TypeScript', 'JavaScript', 'Programming'], + author: { + id: '3', + nickname: 'ts_guru', + profile: 'https://devblog.com/authors/ts_guru', + gistUrl: '' + }, + isPublic: true, + gistUrl: '' + }, + { + id: '1000000004', + link: 'https://devblog.com/articles/1000000004', + title: 'How to Build RESTful APIs with Node.js', + logo: jsImage, + date: new Date('2024-10-20').toISOString(), + tags: ['Node.js', 'API', 'Backend'], + author: { + id: '4', + nickname: 'node_dev', + profile: 'https://devblog.com/authors/node_dev', + gistUrl: '' + }, + isPublic: false, + gistUrl: '' + }, + { + id: '1000000005', + link: 'https://devblog.com/articles/1000000005', + title: 'Top 10 Python Libraries for Data Science', + logo: jsImage, + date: new Date('2024-10-15').toISOString(), + tags: ['Python', 'Data Science', 'Libraries'], + author: { + id: '5', + nickname: 'data_scientist', + profile: 'https://devblog.com/authors/data_scientist', + gistUrl: '' + }, + isPublic: true, + gistUrl: '' + }, + { + id: '1000000006', + link: 'https://devblog.com/articles/1000000006', + title: 'React State Management: Context vs Redux', + logo: jsImage, + date: new Date('2024-10-10').toISOString(), + tags: ['React', 'Redux', 'Frontend'], + author: { + id: '6', + nickname: 'react_expert', + profile: 'https://devblog.com/authors/react_expert', + gistUrl: '' + }, + isPublic: false, + gistUrl: '' + }, + { + id: '1000000007', + link: 'https://devblog.com/articles/1000000007', + title: 'Mastering Git: Branching and Merging', + logo: jsImage, + date: new Date('2024-10-05').toISOString(), + tags: ['Git', 'Version Control', 'DevOps'], + author: { + id: '7', + nickname: 'git_master', + profile: 'https://devblog.com/authors/git_master', + gistUrl: '' + }, + isPublic: true, + gistUrl: '' + }, + { + id: '1000000008', + link: 'https://devblog.com/articles/1000000008', + title: 'Introduction to Kubernetes for Beginners', + logo: jsImage, + date: new Date('2024-10-01').toISOString(), + tags: ['Kubernetes', 'DevOps', 'Containers'], + author: { + id: '8', + nickname: 'k8s_pro', + profile: 'https://devblog.com/authors/k8s_pro', + gistUrl: '' + }, + isPublic: true, + gistUrl: '' + }, + { + id: '1000000009', + link: 'https://devblog.com/articles/1000000009', + title: 'Building Scalable Microservices with Spring Boot', + logo: jsImage, + date: new Date('2024-09-25').toISOString(), + tags: ['Spring Boot', 'Microservices', 'Backend'], + author: { + id: '9', + nickname: 'spring_dev', + profile: 'https://devblog.com/authors/spring_dev', + gistUrl: '' + }, + isPublic: false, + gistUrl: '' + }, + { + id: '1000000010', + link: 'https://devblog.com/articles/1000000010', + title: 'GraphQL Basics: Query Language for APIs', + logo: jsImage, + date: new Date('2024-09-20').toISOString(), + tags: ['GraphQL', 'API', 'Web Development'], + author: { + id: '10', + nickname: 'graphql_guru', + profile: 'https://devblog.com/authors/graphql_guru', + gistUrl: '' + }, + isPublic: true, + gistUrl: '' + }, + { + id: '1000000011', + link: 'https://devblog.com/articles/1000000011', + title: 'Understanding Docker: A Comprehensive Guide', + logo: jsImage, + date: new Date('2024-09-15').toISOString(), + tags: ['Docker', 'DevOps', 'Containers'], + author: { + id: '11', + nickname: 'docker_wizard', + profile: 'https://devblog.com/authors/docker_wizard', + gistUrl: '' + }, + isPublic: true, + gistUrl: '' + } + ]; + mockLotusData.forEach((lotus) => { + lotusList.create(lotus); + }); +} diff --git a/apps/frontend/src/app/mock/userResolvers.ts b/apps/frontend/src/app/mock/userResolvers.ts new file mode 100644 index 00000000..1dd517f3 --- /dev/null +++ b/apps/frontend/src/app/mock/userResolvers.ts @@ -0,0 +1,92 @@ +import { DefaultBodyType, HttpResponse, StrictRequest } from 'msw'; +import { MockRepository } from './MockRepository'; +import { UserDto } from '@/feature/user'; + +const MOCK_CODE = 'mock-code'; +const MOCK_UUID = 'mock-uuid'; + +const userList = new MockRepository>(); + +const insertUser = () => { + const userMock: UserDto = { + id: '1', + nickname: 'mockUser', + profile: '/image/exampleImage.jpeg', + gistUrl: 'https://github.com' + }; + + userList.create(userMock); +}; + +insertUser(); + +// github 사용자 기본 정보 조회 api +export const getUserInfo = async ({ request }: { request: StrictRequest }) => { + const authorization = request.headers.get('Authorization'); + + const [type, token] = authorization?.split(' ') || []; + + if (token !== MOCK_UUID || type !== 'Bearer') { + return new HttpResponse('Unauthorized: Invalid or missing token', { + status: 401, + headers: { + 'Content-Type': 'text/plain' + } + }); + } + + const user = await userList.findOne({ id: '0' }); + + return HttpResponse.json(user); +}; + +// 사용자 프로필 수정 api - 일단 닉네임만 수정되도록 + +export const patchUserInfo = async ({ request }: { request: StrictRequest }) => { + const authorization = request.headers.get('Authorization'); + + if (!authorization || !authorization.startsWith('Bearer ')) { + return new HttpResponse('Unauthorized: Invalid or missing token', { + status: 401, + headers: { + 'Content-Type': 'text/plain' + } + }); + } + + try { + const body = (await request.json()) as Partial; + + const user = await userList.findOne({ id: '0' }); + + const updatedUser = await userList.update(user, body); + + return HttpResponse.json(updatedUser); + } catch (error) { + console.error(error); + return new HttpResponse('Bad Request', { + status: 400, + headers: { + 'Content-Type': 'text/plain' + } + }); + } +}; + +// 로그인 api +export const getLogin = ({ request }: { request: StrictRequest }) => { + const url = new URL(request.url); + const code = url.searchParams.get('code'); + + if (code !== MOCK_CODE) + return new HttpResponse('Unauthorized: Invalid or missing code', { + status: 401, + headers: { + 'Content-Type': 'text/plain' + } + }); + + return HttpResponse.json({ + token: MOCK_UUID + }); +}; diff --git a/apps/frontend/src/app/query/QueryProvider.tsx b/apps/frontend/src/app/query/QueryProvider.tsx new file mode 100644 index 00000000..4f7a61b5 --- /dev/null +++ b/apps/frontend/src/app/query/QueryProvider.tsx @@ -0,0 +1,16 @@ +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; + +type QueryProviderProps = { + children: React.ReactNode; +}; + +export const queryClient = new QueryClient(); + +export function QueryProvider(props: QueryProviderProps) { + return ( + + {props.children} + + ); +} diff --git a/apps/frontend/src/app/query/index.ts b/apps/frontend/src/app/query/index.ts new file mode 100644 index 00000000..daf15d78 --- /dev/null +++ b/apps/frontend/src/app/query/index.ts @@ -0,0 +1 @@ +export * from './QueryProvider'; diff --git a/apps/frontend/src/app/router/RouteProvider.tsx b/apps/frontend/src/app/router/RouteProvider.tsx new file mode 100644 index 00000000..443d08e5 --- /dev/null +++ b/apps/frontend/src/app/router/RouteProvider.tsx @@ -0,0 +1,21 @@ +import { RouterProvider as Provider, createRouter } from '@tanstack/react-router'; +import { TanStackRouterDevtools } from '@tanstack/router-devtools'; +import { routeTree } from '@/app/router/routeTree.gen'; +import { NotFoundPage } from '@/page/-NotFoundPage'; + +const router = createRouter({ routeTree, defaultNotFoundComponent: () => }); + +declare module '@tanstack/react-router' { + interface Register { + router: typeof router; + } +} + +export function RouteProvider() { + return ( + <> + + {import.meta.env.DEV && } + + ); +} diff --git a/apps/frontend/src/app/router/index.ts b/apps/frontend/src/app/router/index.ts new file mode 100644 index 00000000..604a2896 --- /dev/null +++ b/apps/frontend/src/app/router/index.ts @@ -0,0 +1 @@ +export * from './RouteProvider'; diff --git a/apps/frontend/src/app/router/routeTree.gen.ts b/apps/frontend/src/app/router/routeTree.gen.ts new file mode 100644 index 00000000..4af4aeb9 --- /dev/null +++ b/apps/frontend/src/app/router/routeTree.gen.ts @@ -0,0 +1,259 @@ +/* eslint-disable */ + +// @ts-nocheck + +// noinspection JSUnusedGlobalSymbols + +// This file was automatically generated by TanStack Router. +// You should NOT make any changes in this file as it will be overwritten. +// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. + +import { createFileRoute } from '@tanstack/react-router'; + +// Import Routes + +import { Route as rootRoute } from './../../page/__root'; +import { Route as LoginIndexImport } from './../../page/login/index'; +import { Route as mainUserIndexImport } from './../../page/(main)/user/index'; +import { Route as mainLotusIndexImport } from './../../page/(main)/lotus/index'; +import { Route as mainLotusCreateIndexImport } from './../../page/(main)/lotus/create/index'; +import { Route as mainLotusLotusIdIndexImport } from './../../page/(main)/lotus/$lotusId/index'; + +// Create Virtual Routes + +const mainRouteLazyImport = createFileRoute('/(main)')(); +const IndexLazyImport = createFileRoute('/')(); + +// Create/Update Routes + +const mainRouteLazyRoute = mainRouteLazyImport + .update({ + id: '/(main)', + path: '/', + getParentRoute: () => rootRoute + } as any) + .lazy(() => import('./../../page/(main)/route.lazy').then((d) => d.Route)); + +const IndexLazyRoute = IndexLazyImport.update({ + id: '/', + path: '/', + getParentRoute: () => rootRoute +} as any).lazy(() => import('./../../page/index.lazy').then((d) => d.Route)); + +const LoginIndexRoute = LoginIndexImport.update({ + id: '/login/', + path: '/login/', + getParentRoute: () => rootRoute +} as any); + +const mainUserIndexRoute = mainUserIndexImport + .update({ + id: '/user/', + path: '/user/', + getParentRoute: () => mainRouteLazyRoute + } as any) + .lazy(() => import('./../../page/(main)/user/index.lazy').then((d) => d.Route)); + +const mainLotusIndexRoute = mainLotusIndexImport + .update({ + id: '/lotus/', + path: '/lotus/', + getParentRoute: () => mainRouteLazyRoute + } as any) + .lazy(() => import('./../../page/(main)/lotus/index.lazy').then((d) => d.Route)); + +const mainLotusCreateIndexRoute = mainLotusCreateIndexImport + .update({ + id: '/lotus/create/', + path: '/lotus/create/', + getParentRoute: () => mainRouteLazyRoute + } as any) + .lazy(() => import('./../../page/(main)/lotus/create/index.lazy').then((d) => d.Route)); + +const mainLotusLotusIdIndexRoute = mainLotusLotusIdIndexImport + .update({ + id: '/lotus/$lotusId/', + path: '/lotus/$lotusId/', + getParentRoute: () => mainRouteLazyRoute + } as any) + .lazy(() => import('./../../page/(main)/lotus/$lotusId/index.lazy').then((d) => d.Route)); + +// Populate the FileRoutesByPath interface + +declare module '@tanstack/react-router' { + interface FileRoutesByPath { + '/': { + id: '/'; + path: '/'; + fullPath: '/'; + preLoaderRoute: typeof IndexLazyImport; + parentRoute: typeof rootRoute; + }; + '/(main)': { + id: '/(main)'; + path: '/'; + fullPath: '/'; + preLoaderRoute: typeof mainRouteLazyImport; + parentRoute: typeof rootRoute; + }; + '/login/': { + id: '/login/'; + path: '/login'; + fullPath: '/login'; + preLoaderRoute: typeof LoginIndexImport; + parentRoute: typeof rootRoute; + }; + '/(main)/lotus/': { + id: '/(main)/lotus/'; + path: '/lotus'; + fullPath: '/lotus'; + preLoaderRoute: typeof mainLotusIndexImport; + parentRoute: typeof mainRouteLazyImport; + }; + '/(main)/user/': { + id: '/(main)/user/'; + path: '/user'; + fullPath: '/user'; + preLoaderRoute: typeof mainUserIndexImport; + parentRoute: typeof mainRouteLazyImport; + }; + '/(main)/lotus/$lotusId/': { + id: '/(main)/lotus/$lotusId/'; + path: '/lotus/$lotusId'; + fullPath: '/lotus/$lotusId'; + preLoaderRoute: typeof mainLotusLotusIdIndexImport; + parentRoute: typeof mainRouteLazyImport; + }; + '/(main)/lotus/create/': { + id: '/(main)/lotus/create/'; + path: '/lotus/create'; + fullPath: '/lotus/create'; + preLoaderRoute: typeof mainLotusCreateIndexImport; + parentRoute: typeof mainRouteLazyImport; + }; + } +} + +// Create and export the route tree + +interface mainRouteLazyRouteChildren { + mainLotusIndexRoute: typeof mainLotusIndexRoute; + mainUserIndexRoute: typeof mainUserIndexRoute; + mainLotusLotusIdIndexRoute: typeof mainLotusLotusIdIndexRoute; + mainLotusCreateIndexRoute: typeof mainLotusCreateIndexRoute; +} + +const mainRouteLazyRouteChildren: mainRouteLazyRouteChildren = { + mainLotusIndexRoute: mainLotusIndexRoute, + mainUserIndexRoute: mainUserIndexRoute, + mainLotusLotusIdIndexRoute: mainLotusLotusIdIndexRoute, + mainLotusCreateIndexRoute: mainLotusCreateIndexRoute +}; + +const mainRouteLazyRouteWithChildren = mainRouteLazyRoute._addFileChildren(mainRouteLazyRouteChildren); + +export interface FileRoutesByFullPath { + '/': typeof mainRouteLazyRouteWithChildren; + '/login': typeof LoginIndexRoute; + '/lotus': typeof mainLotusIndexRoute; + '/user': typeof mainUserIndexRoute; + '/lotus/$lotusId': typeof mainLotusLotusIdIndexRoute; + '/lotus/create': typeof mainLotusCreateIndexRoute; +} + +export interface FileRoutesByTo { + '/': typeof mainRouteLazyRouteWithChildren; + '/login': typeof LoginIndexRoute; + '/lotus': typeof mainLotusIndexRoute; + '/user': typeof mainUserIndexRoute; + '/lotus/$lotusId': typeof mainLotusLotusIdIndexRoute; + '/lotus/create': typeof mainLotusCreateIndexRoute; +} + +export interface FileRoutesById { + __root__: typeof rootRoute; + '/': typeof IndexLazyRoute; + '/(main)': typeof mainRouteLazyRouteWithChildren; + '/login/': typeof LoginIndexRoute; + '/(main)/lotus/': typeof mainLotusIndexRoute; + '/(main)/user/': typeof mainUserIndexRoute; + '/(main)/lotus/$lotusId/': typeof mainLotusLotusIdIndexRoute; + '/(main)/lotus/create/': typeof mainLotusCreateIndexRoute; +} + +export interface FileRouteTypes { + fileRoutesByFullPath: FileRoutesByFullPath; + fullPaths: '/' | '/login' | '/lotus' | '/user' | '/lotus/$lotusId' | '/lotus/create'; + fileRoutesByTo: FileRoutesByTo; + to: '/' | '/login' | '/lotus' | '/user' | '/lotus/$lotusId' | '/lotus/create'; + id: + | '__root__' + | '/' + | '/(main)' + | '/login/' + | '/(main)/lotus/' + | '/(main)/user/' + | '/(main)/lotus/$lotusId/' + | '/(main)/lotus/create/'; + fileRoutesById: FileRoutesById; +} + +export interface RootRouteChildren { + IndexLazyRoute: typeof IndexLazyRoute; + mainRouteLazyRoute: typeof mainRouteLazyRouteWithChildren; + LoginIndexRoute: typeof LoginIndexRoute; +} + +const rootRouteChildren: RootRouteChildren = { + IndexLazyRoute: IndexLazyRoute, + mainRouteLazyRoute: mainRouteLazyRouteWithChildren, + LoginIndexRoute: LoginIndexRoute +}; + +export const routeTree = rootRoute._addFileChildren(rootRouteChildren)._addFileTypes(); + +/* ROUTE_MANIFEST_START +{ + "routes": { + "__root__": { + "filePath": "__root.tsx", + "children": [ + "/", + "/(main)", + "/login/" + ] + }, + "/": { + "filePath": "index.lazy.tsx" + }, + "/(main)": { + "filePath": "(main)/route.lazy.tsx", + "children": [ + "/(main)/lotus/", + "/(main)/user/", + "/(main)/lotus/$lotusId/", + "/(main)/lotus/create/" + ] + }, + "/login/": { + "filePath": "login/index.tsx" + }, + "/(main)/lotus/": { + "filePath": "(main)/lotus/index.tsx", + "parent": "/(main)" + }, + "/(main)/user/": { + "filePath": "(main)/user/index.tsx", + "parent": "/(main)" + }, + "/(main)/lotus/$lotusId/": { + "filePath": "(main)/lotus/$lotusId/index.tsx", + "parent": "/(main)" + }, + "/(main)/lotus/create/": { + "filePath": "(main)/lotus/create/index.tsx", + "parent": "/(main)" + } + } +} +ROUTE_MANIFEST_END */ diff --git a/apps/frontend/src/app/style/github.css b/apps/frontend/src/app/style/github.css new file mode 100644 index 00000000..8ad192a9 --- /dev/null +++ b/apps/frontend/src/app/style/github.css @@ -0,0 +1,214 @@ +/* General Styles */ +.github { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', + 'Segoe UI Emoji', 'Segoe UI Symbol'; + font-size: 16px; + line-height: 1.6; + color: #24292e; +} + +/* Headings */ +.github h1, +.github h2, +.github h3, +.github h4, +.github h5, +.github h6 { + font-weight: 600; + line-height: 1.25; + color: #2c3e50; + margin-top: 24px; + margin-bottom: 16px; + scroll-margin-top: 60px; +} + +.github h1 { + font-size: 2.25em; + border-bottom: 1px solid #eaecef; + padding-bottom: 0.3em; +} +.github h2 { + font-size: 1.75em; + border-bottom: 1px solid #eaecef; + padding-bottom: 0.3em; +} +.github h3 { + font-size: 1.5em; +} +.github h4 { + font-size: 1.25em; +} +.github h5 { + font-size: 1em; +} +.github h6 { + font-size: 0.875em; + color: #6a737d; +} + +/* Paragraphs */ +.github p { + margin-top: 0; + margin-bottom: 16px; + color: #333; +} + +/* Lists */ +.github ul, +.github ol { + padding-left: 2em; + margin-top: 0; + margin-bottom: 16px; +} + +.github ul ul, +.github ul ol, +.github ol ol, +.github ol ul { + margin-top: 0; + margin-bottom: 0; +} + +.github li { + word-wrap: break-word; + margin-bottom: 4px; +} + +.github ul { + list-style-type: disc; +} + +.github ol { + list-style-type: decimal; +} + +.github li > p { + margin: 0; +} + +/* Links */ +.github a { + color: #0366d6; + text-decoration: none; + transition: color 0.2s ease; +} + +.github a:hover { + color: #0056b3; + text-decoration: underline; +} + +/* Code Blocks */ +.github pre { + background-color: #f6f8fa; + border-radius: 6px; + padding: 16px; + overflow: auto; + font-size: 0.85em; + line-height: 1.45; + margin: 16px 0; +} + +.github code { + background-color: #f6f8fa; + border-radius: 3px; + padding: 2px 4px; + font-size: 0.9em; + color: #e83e8c; + font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace; +} + +.github pre code { + background-color: transparent; + padding: 0; + color: #24292e; +} + +/* Tables */ +.github table { + border-collapse: collapse; + border-spacing: 0; + display: block; + width: 100%; + overflow: auto; + margin-bottom: 16px; +} + +.github table th { + font-weight: 600; + background-color: #f6f8fa; + padding: 8px; +} + +.github table th, +.github table td { + padding: 6px 13px; + border: 1px solid #dfe2e5; + text-align: left; +} + +.github table tr { + background-color: #fff; + border-top: 1px solid #c6cbd1; +} + +.github table tr:nth-child(2n) { + background-color: #f6f8fa; +} + +/* Images */ +.github img { + max-width: 100%; + height: auto; + box-sizing: content-box; + background-color: #fff; + margin: 16px 0; + border-radius: 4px; +} + +/* Blockquotes */ +.github blockquote { + padding: 0.5em 1em; + color: #6a737d; + border-left: 0.25em solid #dfe2e5; + margin: 16px 0; + background-color: #f9f9f9; +} + +.github blockquote p { + margin: 0; +} + +/* Horizontal Rule */ +.github hr { + height: 0.25em; + padding: 0; + margin: 24px 0; + background-color: #e1e4e8; + border: 0; + opacity: 0.8; +} + +/* Inline Code */ +.github code { + background: rgba(27, 31, 35, 0.05); + border-radius: 3px; + font-size: 85%; + padding: 0.2em 0.4em; + font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace; +} + +/* Inline Math */ +.github .math-inline { + font-family: 'Cambria Math', 'STIX Math', 'Avenir Next', sans-serif; + font-size: 1em; + background: #fafbfc; + padding: 2px 6px; + border-radius: 3px; +} + +/* Code Spacing */ +.github pre code { + font-size: 1em; + line-height: 1.4; +} diff --git a/apps/frontend/src/app/style/index.css b/apps/frontend/src/app/style/index.css new file mode 100644 index 00000000..d2ffc043 --- /dev/null +++ b/apps/frontend/src/app/style/index.css @@ -0,0 +1,9 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer base { + :root { + --radius: 0.5rem; + } +} diff --git a/apps/frontend/src/app/test/e2e/lotusDetail.spec.ts b/apps/frontend/src/app/test/e2e/lotusDetail.spec.ts new file mode 100644 index 00000000..11874551 --- /dev/null +++ b/apps/frontend/src/app/test/e2e/lotusDetail.spec.ts @@ -0,0 +1,109 @@ +import test, { expect } from '@playwright/test'; + +test('Lotus 상세 페이지에서 코드 실행을 하고 결과를 확인할 수 있다.', async ({ page }) => { + // Given + await page.goto('/lotus'); + const lotusLink = page.getByTestId('lotus-link').first(); + await lotusLink.click(); + await page.waitForLoadState('load'); + + // When + await page.getByRole('button', { name: '실행하기' }).click(); + await page.getByRole('combobox').click(); + await page.getByRole('listbox').click(); + await page.getByRole('button', { name: '새로운 항목 추가' }).click(); + await page.getByPlaceholder('Input').fill('12'); + await page.getByRole('button', { name: '새로운 항목 추가' }).click(); + await page.getByPlaceholder('Input 2').click(); + await page.getByPlaceholder('Input 2').fill('23'); + await page.locator('form').getByRole('paragraph').nth(1).click(); + await page.locator('section').getByRole('button', { name: '실행하기' }).click(); + + // Then + await page.waitForSelector('[data-testid="history-status"]'); + await expect(page.getByText('코드가 실행되었습니다.')).toBeVisible(); + const codeRunStatus = page.getByTestId('history-status').first(); + expect(await codeRunStatus.textContent()).toBe('pending'); +}); + +test('Lotus 상세 페이지에서 제목과 태그를 수정할 수 있다.', async ({ page }) => { + // Given + const MOCK_CODE = 'mock-code'; + await page.goto(`/login?code=${MOCK_CODE}`); + const lotusLink = page.getByTestId('lotus-link').first(); + await lotusLink.click(); + await page.waitForLoadState('load'); + + // When + await page.getByRole('button', { name: '수정하기' }).click(); + await page.getByPlaceholder('제목을 입력해주세요').click(); + await page.getByPlaceholder('제목을 입력해주세요').fill('New Title'); + await page + .locator('form div') + .filter({ hasText: /^JavaScript$/ }) + .getByRole('button') + .click(); + await page.getByPlaceholder('태그를 입력해주세요').click(); + await page.getByPlaceholder('태그를 입력해주세요').fill('new Tag'); + await page.getByPlaceholder('태그를 입력해주세요').press('Enter'); + await page.locator('form').getByRole('button', { name: '수정하기' }).click(); + + // Then + await expect(page.getByText('New Title')).toBeVisible(); + await expect(page.getByText('JavaScript')).toBeHidden(); + await expect(page.getByText('new Tag')).toBeVisible(); + await expect(page.getByText('Lotus가 수정되었습니다.')).toBeVisible(); +}); + +test('Lotus 상세 페이지에서 삭제할 수 있다.', async ({ page }) => { + // Given + const MOCK_CODE = 'mock-code'; + await page.goto(`/login?code=${MOCK_CODE}`); + const lotusLink = page.getByTestId('lotus-link').first(); + await lotusLink.click(); + await page.waitForLoadState('load'); + + // When + const deleteButton = page.getByRole('button', { name: '삭제하기' }); + await deleteButton.waitFor({ state: 'visible' }); + const deletedTitle = await page.getByTestId('lotus-title').textContent(); + await deleteButton.click(); + + await expect(page.getByText('정말로 삭제하시겠습니까?')).toBeVisible(); + await page.getByRole('button', { name: '삭제하기' }).nth(1).click(); + + // Then + await expect(page.getByText('Lotus는 gist 저장소들을 의미합니다')).toBeVisible(); + await page.waitForSelector('[data-testid="lotus-title"]'); + + const lotusTitles = await page.getByTestId('lotus-title').allTextContents(); + const isDelete = lotusTitles.every((value) => value !== deletedTitle); + expect(isDelete).toBe(true); + await expect(page.getByText('Lotus가 삭제되었습니다.')).toBeVisible(); +}); + +test('Lotus 상세 페이지에서 public을 Private으로 바꾸면 Lotus 목록 페이지에서 사라진다.', async ({ page }) => { + // Given + const MOCK_CODE = 'mock-code'; + await page.goto(`/login?code=${MOCK_CODE}`); + const lotusLink = page.getByTestId('lotus-link').first(); + await lotusLink.click(); + await page.waitForLoadState('load'); + + // When + const publicSwitch = page.getByRole('switch'); + await publicSwitch.waitFor({ state: 'visible' }); + await publicSwitch.click(); + await page.waitForTimeout(3000); + + const targetTitle = await page.getByTestId('lotus-title').textContent(); + await page.getByRole('button', { name: '로고 Froxy' }).click(); + + // Then + await expect(page.getByText('Lotus는 gist 저장소들을 의미합니다')).toBeVisible(); + await page.waitForSelector('[data-testid="lotus-title"]'); + + const lotusTitles = await page.getByTestId('lotus-title').allTextContents(); + const isHidden = lotusTitles.every((value) => value !== targetTitle); + expect(isHidden).toBe(true); +}); diff --git a/apps/frontend/src/app/test/e2e/lotusList.spec.ts b/apps/frontend/src/app/test/e2e/lotusList.spec.ts new file mode 100644 index 00000000..99b7a026 --- /dev/null +++ b/apps/frontend/src/app/test/e2e/lotusList.spec.ts @@ -0,0 +1,49 @@ +import test, { expect } from '@playwright/test'; + +test('온보딩 페이지에서 "공개 프로젝트 보러가기" 버튼을 누르면 public Lotus 목록을 볼 수 있다.', async ({ page }) => { + // Give + await page.goto('/'); + + // When + await page.getByRole('button', { name: '공개 프로젝트 보러가기' }).click(); + await page.waitForSelector('[data-testid="lotus-title"]'); + const lotusComponents = await page.getByTestId('lotus-title').count(); + + // then + await expect(page.getByText('Lotus는 gist 저장소들을 의미합니다')).toBeVisible(); + expect(lotusComponents).toBeGreaterThan(0); + await expect(page.getByText('1', { exact: true })).toBeVisible(); +}); + +test('Lotus 목록 페이지에서 제목 검색이 가능해야 한다.', async ({ page }) => { + // Give + const searchText = 'test'; + await page.goto('/lotus'); + + // When + await page.getByPlaceholder('제목을 검색해주세요').click(); + await page.getByPlaceholder('제목을 검색해주세요').fill(searchText); + await page.getByRole('button', { name: '검색하기' }).click(); + + await page.waitForSelector('[data-testid="lotus-title"]', { timeout: 3000 }).catch(() => {}); + const lotusTitles = await page.getByTestId('lotus-title').allTextContents(); + + // then + const hasInvalidTitle = lotusTitles?.some((title) => !title.includes(searchText)); + expect(hasInvalidTitle).toBe(false); +}); + +test('Lotus 목록 페이지에서 Lotus 카드를 클릭하면 Lotus 상세 페이지로 이동한다.', async ({ page }) => { + // Given + await page.goto('/lotus'); + + // When + const lotusLink = page.getByTestId('lotus-link').first(); + const expectedUrl = await lotusLink.getAttribute('href'); + await lotusLink.click(); + await page.waitForLoadState('load'); + + // Then + const currentUrl = page.url(); + expect(currentUrl).toBe('http://localhost:5173' + expectedUrl); +}); diff --git a/apps/frontend/src/app/test/e2e/user.spec.ts b/apps/frontend/src/app/test/e2e/user.spec.ts new file mode 100644 index 00000000..87b94f45 --- /dev/null +++ b/apps/frontend/src/app/test/e2e/user.spec.ts @@ -0,0 +1,126 @@ +import test, { expect } from '@playwright/test'; + +test('로그인을 안하면 헤더에서 로그인 버튼을 볼 수 있다.', async ({ page }) => { + // Given + await page.goto(`/lotus`); + + // When + const loginButton = page.getByRole('button', { name: 'Login' }); + await loginButton.waitFor({ state: 'visible' }); + + // Then + expect(await loginButton.isVisible()).toBe(true); +}); + +test('로그인 상태이면 헤더에서 Lotus 작성, 로그인, 프로필을 볼 수 있다.', async ({ page }) => { + // Given + const MOCK_CODE = 'mock-code'; + await page.goto(`/login?code=${MOCK_CODE}`); + + // When + const createLotusButton = page.getByRole('link', { name: 'Create Lotus' }); + const logoutButton = page.getByRole('button', { name: 'Logout' }); + const profile = page.getByTestId('header-profile'); + await profile.waitFor({ state: 'visible' }); + + // Then + expect(await page.getByText('mockUser님 환영합니다!').isVisible()).toBe(true); + expect(await createLotusButton.isVisible()).toBe(true); + expect(await logoutButton.isVisible()).toBe(true); + expect(await profile.isVisible()).toBe(true); +}); + +test('로그인 상태이면 헤더에서 Lotus를 생성하기를 통해 Lotus를 생성할 수 있다.', async ({ page }) => { + // Given + const MOCK_CODE = 'mock-code'; + await page.goto(`/login?code=${MOCK_CODE}`); + + // When + await page.getByRole('link', { name: 'Create Lotus' }).click(); + + await page.getByPlaceholder('제목을 입력해주세요').click(); + await page.getByPlaceholder('제목을 입력해주세요').fill('테스트 제목'); + await page.getByPlaceholder('태그를 입력해주세요').click(); + await page.getByPlaceholder('태그를 입력해주세요').fill('tag1'); + await page.getByPlaceholder('태그를 입력해주세요').press('Enter'); + await page.getByPlaceholder('태그를 입력해주세요').fill('tag2'); + await page.getByPlaceholder('태그를 입력해주세요').press('Enter'); + + await page.getByRole('combobox').click(); + await page.getByLabel('My First Gist').click(); + await page.getByRole('button', { name: '생성하기' }).click(); + + // Then + await expect(page.getByText('테스트 제목')).toBeVisible(); + await expect(page.getByText('tag1')).toBeVisible(); + await expect(page.getByText('tag2')).toBeVisible(); + await expect(page.getByText('Lotus가 생성되었습니다.')).toBeVisible(); +}); + +test('헤더에서 로그아웃 버튼을 누르면 메인 페이지로 이동한다.', async ({ page }) => { + // Given + const MOCK_CODE = 'mock-code'; + await page.goto(`/login?code=${MOCK_CODE}`); + + // When + await page.getByRole('button', { name: 'Logout' }).click(); + await page.waitForLoadState('load'); + + // Then + const currentUrl = page.url(); + expect(currentUrl).toBe('http://localhost:5173/'); +}); + +test('헤더에서 프로필 버튼을 누르면 사용자 정보 페이지로 이동한다.', async ({ page }) => { + // Given + const MOCK_CODE = 'mock-code'; + await page.goto(`/login?code=${MOCK_CODE}`); + + // When + await page.getByTestId('header-profile').click(); + await page.waitForLoadState('load'); + await page.waitForSelector('[data-testid="lotus-title"]'); + + // Then + const lotusComponents = await page.getByTestId('lotus-title').count(); + expect(lotusComponents).toBeGreaterThan(0); + await expect(page.getByTestId('user-profile')).toBeVisible(); + await expect(page.getByTestId('user-nickname')).toBeVisible(); +}); + +test('사용자 정보 페이지에서 자신의 닉네임을 수정할 수 있다.', async ({ page }) => { + // Given + const MOCK_CODE = 'mock-code'; + await page.goto(`/login?code=${MOCK_CODE}`); + + // When + await page.getByTestId('header-profile').click(); + await page.getByTestId('user-profile').waitFor({ state: 'visible' }); + await page.locator('.flex > svg').first().click(); + await page.getByPlaceholder('값을 입력해주세요').click(); + await page.getByPlaceholder('값을 입력해주세요').fill('new Nickname'); + await page.getByRole('button', { name: '수정하기' }).click(); + + // Then + await page.getByText('new Nickname').waitFor({ state: 'visible' }); + expect(await page.getByText('new Nickname').isVisible()).toBe(true); + expect(await page.getByText('닉네임이 수정되었습니다.').isVisible()).toBe(true); +}); + +test('사용자 정보 페이지에서 자신이 작성한 Lotus 카드를 클릭하면 Lotus 상세 페이지로 이동한다.', async ({ page }) => { + // Given + const MOCK_CODE = 'mock-code'; + await page.goto(`/login?code=${MOCK_CODE}`); + + // When + await page.getByTestId('header-profile').click(); + await page.waitForLoadState('load'); + const lotusLink = page.getByTestId('lotus-link').first(); + const expectedUrl = await lotusLink.getAttribute('href'); + await lotusLink.click(); + await page.waitForLoadState('load'); + + // Then + const currentUrl = page.url(); + expect(currentUrl).toBe('http://localhost:5173' + expectedUrl); +}); diff --git a/apps/frontend/src/app/test/setupTests.ts b/apps/frontend/src/app/test/setupTests.ts new file mode 100644 index 00000000..0bcc78cb --- /dev/null +++ b/apps/frontend/src/app/test/setupTests.ts @@ -0,0 +1,7 @@ +import { cleanup } from '@testing-library/react'; +import { afterEach } from 'vitest'; +import '@testing-library/jest-dom/vitest'; + +afterEach(() => { + cleanup(); +}); diff --git a/apps/frontend/src/app/vite-env.d.ts b/apps/frontend/src/app/vite-env.d.ts new file mode 100644 index 00000000..11f02fe2 --- /dev/null +++ b/apps/frontend/src/app/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/apps/frontend/src/feature/codeView/.index.tsx.icloud b/apps/frontend/src/feature/codeView/.index.tsx.icloud new file mode 100644 index 00000000..40fb1f55 Binary files /dev/null and b/apps/frontend/src/feature/codeView/.index.tsx.icloud differ diff --git a/apps/frontend/src/feature/codeView/.model.ts.icloud b/apps/frontend/src/feature/codeView/.model.ts.icloud new file mode 100644 index 00000000..50cf5f50 Binary files /dev/null and b/apps/frontend/src/feature/codeView/.model.ts.icloud differ diff --git a/apps/frontend/src/feature/codeView/.type 2.ts.icloud b/apps/frontend/src/feature/codeView/.type 2.ts.icloud new file mode 100644 index 00000000..e6ff3988 Binary files /dev/null and b/apps/frontend/src/feature/codeView/.type 2.ts.icloud differ diff --git a/apps/frontend/src/feature/codeView/.type 3.ts.icloud b/apps/frontend/src/feature/codeView/.type 3.ts.icloud new file mode 100644 index 00000000..17f887bf Binary files /dev/null and b/apps/frontend/src/feature/codeView/.type 3.ts.icloud differ diff --git a/apps/frontend/src/feature/codeView/component/CodeSideBar.tsx b/apps/frontend/src/feature/codeView/component/CodeSideBar.tsx new file mode 100644 index 00000000..c994bb47 --- /dev/null +++ b/apps/frontend/src/feature/codeView/component/CodeSideBar.tsx @@ -0,0 +1,26 @@ +import { HTMLProps } from 'react'; +import { Button } from '@froxy/design/components'; +import { cn } from '@froxy/design/utils'; +import { useCodeViewActionContext, useCodeViewContext } from '@/feature/codeView/hook'; + +type CodeSideBarProps = HTMLProps; + +export function CodeSideBar({ className, ...props }: CodeSideBarProps) { + const { value, current } = useCodeViewContext(); + const setCurrentCode = useCodeViewActionContext(); + + return ( +
+ {value.map((v, i) => ( + + ))} +
+ ); +} diff --git a/apps/frontend/src/feature/codeView/component/CodeViewProvider.tsx b/apps/frontend/src/feature/codeView/component/CodeViewProvider.tsx new file mode 100644 index 00000000..e148ec34 --- /dev/null +++ b/apps/frontend/src/feature/codeView/component/CodeViewProvider.tsx @@ -0,0 +1,28 @@ +import { useState } from 'react'; +import { CodeViewActionContext, CodeViewContext } from '@/feature/codeView/hook'; +import { CodeFileModel } from '@/feature/codeView/model'; + +type CodeViewProviderProps = { + value: CodeFileModel[]; + children: React.ReactNode; + current?: number; +}; + +export function CodeViewProvider({ value, children, current: currentIndex = 0 }: CodeViewProviderProps) { + const [current, setCurrent] = useState(currentIndex); + + const setCurrentCode = (index: number) => setCurrent(index); + + return ( + + + {children} + + + ); +} diff --git a/apps/frontend/src/feature/codeView/component/CodeViewer.tsx b/apps/frontend/src/feature/codeView/component/CodeViewer.tsx new file mode 100644 index 00000000..54000e56 --- /dev/null +++ b/apps/frontend/src/feature/codeView/component/CodeViewer.tsx @@ -0,0 +1,40 @@ +import { HTMLProps } from 'react'; +import { cn } from '@froxy/design/utils'; +import { Markdown } from '@froxy/react-markdown'; +import { Text } from '@/components'; +import { useCodeViewContext } from '@/feature/codeView/hook'; + +type CodeViewerProps = { + children?: string; + theme?: 'github-light' | 'github-dark'; +} & HTMLProps; + +// TODO : 아주 막연하게 구현된 컴포넌트, 추후에 스타일 리팩터링 필요 +export function CodeViewer({ className, ...props }: CodeViewerProps) { + const { value, current } = useCodeViewContext(); + + const file = value[current]; + + if (!file.canView) + return ( +
+ {file.filename} + 지원하지 않는 확장자입니다. +
+ ); + + return ( + figure]:h-full [&>figure]:w-full [&>figure>pre]:h-full [&>figure>pre]:w-full [&>figure>pre]:m-0' + : 'p-2', + 'overflow-scroll', + className + )} + markdown={file.toMarkdown()} + {...props} + /> + ); +} diff --git a/apps/frontend/src/feature/codeView/component/index.ts b/apps/frontend/src/feature/codeView/component/index.ts new file mode 100644 index 00000000..cb01b0fe --- /dev/null +++ b/apps/frontend/src/feature/codeView/component/index.ts @@ -0,0 +1,5 @@ +import { CodeSideBar } from './CodeSideBar'; +import { CodeViewer } from './CodeViewer'; +import { CodeViewProvider } from './CodeViewProvider'; + +export const CodeView = Object.assign(CodeViewProvider, { SideBar: CodeSideBar, Viewer: CodeViewer }); diff --git a/apps/frontend/src/feature/codeView/constant.ts b/apps/frontend/src/feature/codeView/constant.ts new file mode 100644 index 00000000..7bd8f26f --- /dev/null +++ b/apps/frontend/src/feature/codeView/constant.ts @@ -0,0 +1,45 @@ +export const LANGUAGES_EXT = { + TypeScript: 'ts', + ts: 'ts', + Ts: 'ts', + JavaScript: 'js', + Js: 'js', + js: 'js', + json: 'json', + JSON: 'json', + jsx: 'jsx', + tsx: 'tsx', + css: 'css', + scss: 'scss', + sass: 'sass', + html: 'html', + babelrc: 'json', + prettierrc: 'json', + eslintrc: 'json' +} as const; + +export const CANT_VIEW_EXT = [ + 'png', + 'jpg', + 'jpeg', + 'gif', + 'svg', + 'bmp', + 'webp', + 'ico', + 'tiff', + 'tif', + 'raw', + 'heif', + 'heic', + 'apng', + 'avif', + 'jxl', + 'mp4', + 'mov', + 'avi', + 'wmv', + 'flv', + 'mkv', + 'webm' +]; diff --git a/apps/frontend/src/feature/codeView/hook.ts b/apps/frontend/src/feature/codeView/hook.ts new file mode 100644 index 00000000..2c5b401f --- /dev/null +++ b/apps/frontend/src/feature/codeView/hook.ts @@ -0,0 +1,31 @@ +import { createContext, useContext } from 'react'; +import { CodeFileModel } from '.'; + +export interface CodeViewContext { + value: CodeFileModel[]; + current: number; +} +export type CodeViewActionContext = (arg: number) => void; + +export const CodeViewContext = createContext(null); +export const CodeViewActionContext = createContext(null); + +export const useCodeViewContext = () => { + const value = useContext(CodeViewContext); + + if (!value) { + throw new Error('useCodeViewContext must be used within a CodeViewProvider'); + } + + return value; +}; + +export const useCodeViewActionContext = () => { + const value = useContext(CodeViewActionContext); + + if (!value) { + throw new Error('useCodeViewActionContext must be used within a CodeViewProvider'); + } + + return value; +}; diff --git a/apps/frontend/src/feature/codeView/index.ts b/apps/frontend/src/feature/codeView/index.ts new file mode 100644 index 00000000..cd9ca1c0 --- /dev/null +++ b/apps/frontend/src/feature/codeView/index.ts @@ -0,0 +1,2 @@ +export * from './component'; +export * from './model'; diff --git a/apps/frontend/src/feature/codeView/model/index.ts b/apps/frontend/src/feature/codeView/model/index.ts new file mode 100644 index 00000000..9f8ccadd --- /dev/null +++ b/apps/frontend/src/feature/codeView/model/index.ts @@ -0,0 +1 @@ +export * from './model'; diff --git a/apps/frontend/src/feature/codeView/model/model.test.ts b/apps/frontend/src/feature/codeView/model/model.test.ts new file mode 100644 index 00000000..b95995b9 --- /dev/null +++ b/apps/frontend/src/feature/codeView/model/model.test.ts @@ -0,0 +1,193 @@ +import { describe, expect, it } from 'vitest'; +import { CodeFileModel } from './model'; + +describe('CodeFileModel', () => { + it.each([ + { + description: '올바른 DTO를 받아 CodeFileModel을 생성합니다.', + dto: { + filename: 'example.js', + language: 'JavaScript', + content: 'console.log("Hello, world!");' + }, + expected: { + filename: 'example.js', + language: 'JavaScript', + content: 'console.log("Hello, world!");', + ext: 'js' + } + }, + { + description: '비어있는 값은 빈 값으로 처리합니다.', + dto: { + filename: '', + language: '', + content: '' + }, + expected: { + filename: '', + language: '', + content: '', + ext: '' + } + } + ])('$description', ({ dto, expected }) => { + // Given + const model = new CodeFileModel(dto); + + //When + + // Then: 필드 검증 + expect(model).toMatchObject(expected); + }); + + it.each([ + { + description: 'README 파일일 경우 isREADME가 true입니다.', + dto: { + filename: 'README.md', + language: 'Markdown', + content: '# Hello World' + }, + expected: true + }, + { + description: 'README 파일이 아닐 경우 isREADME가 false입니다.', + dto: { + filename: 'example.js', + language: 'JavaScript', + content: 'console.log("Hello, world!");' + }, + expected: false + } + ])('$description', ({ dto, expected }) => { + // Given + const model = new CodeFileModel(dto); + + //When + + // Then: isREADME getter 확인 + expect(model.isREADME).toBe(expected); + }); + + it.each([ + { + description: '마크다운 파일인 경우 isMarkdown가 true입니다.', + dto: { + filename: 'example.md', + language: 'Markdown', + content: '# Hello World' + }, + expected: true + }, + { + description: '마크다운 파일이 아닌 경우 isMarkdown가 false입니다.', + dto: { + filename: 'example.js', + language: 'JavaScript', + content: 'console.log("Hello, world!");' + }, + expected: false + } + ])('$description', ({ dto, expected }) => { + // Given + const model = new CodeFileModel(dto); + + //When + + // Then: isMarkdown getter 확인 + expect(model.isMarkdown).toBe(expected); + }); + + it.each([ + { + description: '지원하는 확장자 형식인 경우 canView가 true입니다.', + dto: { + filename: 'example.md', + language: 'Markdown', + content: '# Hello World' + }, + expected: true + }, + { + description: '지원하지 않는 확장자의 경우 canView가 false입니다.', + dto: { + filename: 'example.png', + language: 'Binary', + content: '' + }, + expected: false + } + ])('$description', ({ dto, expected }) => { + // Given + const model = new CodeFileModel(dto); + + //When + + // Then: canView getter 확인 + expect(model.canView).toBe(expected); + }); + + it.each([ + { + description: 'README 파일이 존재하는 경우 getDefaultFile은 README 파일을 반환합니다.', + files: [ + new CodeFileModel({ filename: 'README.md', language: 'Markdown', content: '# Hello' }), + new CodeFileModel({ filename: 'example.js', language: 'JavaScript', content: 'console.log("test");' }), + new CodeFileModel({ filename: 'example.md', language: 'Markdown', content: 'hola' }) + ], + expected: 'README.md' + }, + { + description: 'README 파일이 없고 마크다운 파일이 존재하는 경우 getDefaultFile은 마크다운 파일을 반환합니다.', + files: [ + new CodeFileModel({ filename: 'example.md', language: 'Markdown', content: '# Hello' }), + new CodeFileModel({ filename: 'example.js', language: 'JavaScript', content: 'console.log("test");' }) + ], + expected: 'example.md' + }, + { + description: 'README 파일과 마크다운 파일이 없는 경우 getDefaultFile은 undefined를 반환합니다.', + files: [ + new CodeFileModel({ filename: 'example.js', language: 'JavaScript', content: 'console.log("test");' }), + new CodeFileModel({ filename: 'example.py', language: 'Python', content: 'print("Hello")' }) + ], + expected: undefined + } + ])('$description', ({ files, expected }) => { + //Given + + //When + const defaultFile = CodeFileModel.getDefaultFile(files); + + //Then + expect(defaultFile?.filename).toBe(expected); + }); + + it.each([ + { + description: '지원하는 언어파일인 경우 언어에 맞는 content를 마크다운 문자열로 반환합니다', + dto: { + filename: 'example.js', + language: 'JavaScript', + content: 'console.log("Hello, world!");' + }, + expected: '```js\nconsole.log("Hello, world!");\n ```' + }, + { + description: '지원하지 않는 언어 파일이나, 언어 파일이 아닌 경우 그냥 content를 반환합니다', + dto: { + filename: 'example.txt', + language: 'Text', + content: 'Just some text' + }, + expected: 'Just some text' + } + ])('$description', ({ dto, expected }) => { + // Given + const model = new CodeFileModel(dto); + + // Then: toMarkdown 확인 + expect(model.toMarkdown()).toBe(expected); + }); +}); diff --git a/apps/frontend/src/feature/codeView/model/model.ts b/apps/frontend/src/feature/codeView/model/model.ts new file mode 100644 index 00000000..063dbea6 --- /dev/null +++ b/apps/frontend/src/feature/codeView/model/model.ts @@ -0,0 +1,52 @@ +import { CANT_VIEW_EXT, LANGUAGES_EXT } from '@/feature/codeView/constant'; + +export interface CodeFileDto { + filename: string; + language: string; + content: string; +} + +export class CodeFileModel { + public filename: string; + public language: string; + public content: string; + public ext: string; + + constructor(dto: CodeFileDto) { + this.filename = dto?.filename || ''; + this.language = dto?.language || ''; + this.content = dto?.content || ''; + this.ext = dto.filename.split('.').pop() || ''; + } + + static getDefaultFile(list: CodeFileModel[]) { + const readme = CodeFileModel.getREADME(list); + const md = list.find((file) => file.isMarkdown); + + return readme || md; + } + + static getREADME(list: CodeFileModel[]) { + return list.find((file) => file.isREADME); + } + + get isREADME() { + return this.filename === 'README.md'; + } + + get isMarkdown() { + return this.ext === 'md'; + } + + get isCode() { + return this.ext in LANGUAGES_EXT; + } + + get canView() { + return !CANT_VIEW_EXT.includes(this.ext); + } + + toMarkdown() { + return this.isCode ? `\`\`\`${this.ext}\n${this.content}\n \`\`\`` : this.content; + } +} diff --git a/apps/frontend/src/feature/codeView/type.ts b/apps/frontend/src/feature/codeView/type.ts new file mode 100644 index 00000000..0f985b88 --- /dev/null +++ b/apps/frontend/src/feature/codeView/type.ts @@ -0,0 +1,5 @@ +export interface CodeViewValue { + filename: string; + language: string; + content: string; +} diff --git a/apps/frontend/src/feature/comment/component/Comment.tsx b/apps/frontend/src/feature/comment/component/Comment.tsx new file mode 100644 index 00000000..a9dc55f7 --- /dev/null +++ b/apps/frontend/src/feature/comment/component/Comment.tsx @@ -0,0 +1,23 @@ +import { HTMLProps, forwardRef } from 'react'; +import { cn } from '@froxy/design/utils'; +import { Markdown } from '@froxy/react-markdown'; + +type CommentViewerProps = { + children?: string; +} & HTMLProps; + +export function CommentViewer({ className, children, ...props }: CommentViewerProps) { + return ; +} + +type CommentEditorProps = {} & HTMLProps; + +export const CommentEditor = forwardRef(({ className, ...props }, ref) => { + return ( +