dev

🎾 누구나 따라 할 수 있는 테니스 NTRP 레벨 테스트 웹 앱 만들기 가이드

quinton84 2025. 7. 6. 22:24
반응형

안녕하세요,

오늘은 여러분도 직접 만들어 볼 수 있는 '테니스 NTRP 레벨 테스트 웹 앱' 개발 과정을 상세히 안내해 드리려고 합니다.

내 테니스 실력이 객관적으로 어느 정도인지 궁금하셨나요? NTRP(National Tennis Rating Program) 레벨은 테니스 실력을 가늠하는 좋은 기준입니다. 이 가이드를 통해 NTRP 레벨을 쉽고 재미있게 진단해주는 나만의 웹 앱을 직접 만들어보고, 웹 개발의 즐거움을 경험해 보세요! 코딩 초보자도 충분히 따라 할 수 있도록 자세히 설명해 드립니다.

✨ 우리가 만들 웹 앱은 이런 모습이에요!

이 프로젝트는 사용자가 몇 가지 질문에 답하면, 자신의 테니스 NTRP 레벨을 예측해주고 상세한 분석 결과를 제공하는 웹 기반 애플리케이션입니다. 최종적으로는 이런 특징들을 가진 웹 앱을 만들게 될 거예요.

  • 친근하고 위트 있는 NTRP 레벨 설명: 딱딱한 설명 대신, 각 레벨을 '테니스 신생아', '테니스 걸음마', '테니스 전략가' 등 재미있는 별명으로 표현합니다.
  • 8가지 상세 영역 평가: 포핸드, 백핸드, 서브, 발리, 풋워크, 전략, 일관성, 멘탈까지 테니스의 주요 기술 및 심리적 요소를 종합적으로 평가합니다.
  • 직관적이고 깔끔한 디자인: 모바일과 데스크톱 모두에서 쾌적하게 사용할 수 있는 반응형 디자인이 적용됩니다.
  • 상세 분석 리포트: NTRP 레벨뿐만 아니라, 각 영역별 점수(시각적인 바 차트 포함), 강점, 개선점, 그리고 개인 맞춤형 개선 제안까지 제공하여 실력 향상에 실질적인 도움을 줍니다.
  • 광고 연동 가능: 쿠팡 파트너스 같은 제휴 광고를 붙여 수익 창출 가능성도 열어둘 수 있습니다.

 

우선 최종 결과물 부터 보시죠!

https://tangerine-sunflower-94358a.netlify.app/

반응형

 

🚀 단계별 웹 앱 개발 가이드: 지금 바로 시작해 보세요!

이 웹 앱은 HTML, CSS, JavaScript만을 사용하여 만들어지는 '정적 웹사이트' 형태입니다. 복잡한 서버나 데이터베이스 없이도 모든 기능이 웹 브라우저에서 직접 실행되도록 설계됩니다.

1단계: 프로젝트 기획 및 자료 수집 (무엇을 만들까?)

웹 앱을 만들기 전, 어떤 내용을 담을지 구체적으로 계획하고 필요한 정보를 모으는 단계입니다.

  1. NTRP 레벨별 특징 이해:
  • USTA(미국 테니스 협회)의 NTRP 공식 가이드라인 문서를 찾아 읽어보세요. 각 레벨의 일반적인 특성, 스트로크, 전술, 멘탈 등의 설명을 파악합니다.

  • 국내외 테니스 관련 블로그, 커뮤니티, 웹사이트에서 NTRP 레벨에 대한 설명이나 기존 테스트들을 참고하여 실제 테니스 플레이어들이 느끼는 체감 레벨과 특징을 이해합니다. (예: NAMOO_BINS 나무로 만든 집 블로그 같은 위트 있는 NTRP 정의를 참고하면 좋습니다.)
  1. 질문 및 답변 구체화:
  • 수집된 자료를 바탕으로 포핸드, 백핸드, 서브, 발리, 풋워크, 전략, 일관성, 멘탈 등 8가지 핵심 테니스 영역을 선정합니다.
  • 각 영역별로 1점부터 5점까지의 구체적인 답변 문구를 작성합니다. 이때, 사용자가 자신의 실력을 직관적이고 재미있게 선택할 수 있도록 친근하고 위트 있는 표현을 사용하는 것이 중요합니다. (예: "공 때리기도 급급!", "백핸드만 오면 몸부터 돌려 포핸드로 냅다 쳐요." 등)

2단계: 웹 페이지 구조 만들기 (HTML: 앱의 뼈대)

이제 웹 앱의 기본 구조를 HTML 파일 하나로 만들어 봅시다.

  1. index.html 파일 생성: 컴퓨터에 index.html이라는 이름의 빈 텍스트 파일을 만듭니다.
  2. 기본 HTML 코드 준비: 아래와 같은 기본적인 HTML 구조를 index.html 파일에 작성합니다. 이 코드는 웹 앱의 전체적인 틀과 각 화면(환영, 질문, 결과)을 동적으로 표시할 div 요소를 포함합니다.
  • : <head> 태그 안에 <meta name="description" ...>을 추가하여 검색 엔진 최적화(SEO)를 위한 페이지 요약을 미리 넣어두세요.
  • : Google Fonts의 'Poppins' 폰트를 링크하여 현대적인 디자인을 위한 준비를 합니다.
<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>🎾 테니스 NTRP 레벨 테스트</title>
    <!-- SEO를 위한 페이지 요약 추가 -->
    <meta name="description" content="나만의 테니스 NTRP 레벨을 쉽고 재미있게 진단하고, 포핸드, 백핸드, 멘탈까지 상세한 강점과 개선점, 맞춤형 훈련 제안을 받아보세요!">
    <!-- Google Fonts: Poppins -->
   
    <style>
        /* 여기에 CSS 코드가 들어갈 예정입니다 */
    </style>
</head>
<body>
    <div class="container" id="app">
        <!-- 앱 내용이 여기에 JavaScript로 동적으로 로드됩니다 -->
    </div>

    <script>
        /* 여기에 JavaScript 코드가 들어갈 예정입니다 */
    </script>
</body>
</html>

 

3단계: 디자인 및 스타일링 (CSS: 앱에 옷 입히기)

만든 뼈대에 예쁜 옷을 입혀 사용자 인터페이스를 만듭니다.

  1. CSS 코드 작성: 2단계에서 만든 index.html 파일의 <style> 태그 안에 아래 CSS 코드를 작성합니다.
  • 핵심: align-items: center;와 padding, min-height, gap 등의 속성들을 통해 모든 답변 옵션이 스크롤 없이 한 화면에 보이고, 텍스트가 상자 내에서 세로 중앙에 완벽하게 정렬되도록 세심하게 조정되었습니다.
  • : rem 단위를 사용하여 폰트 크기를 조절하면 반응형 디자인에 유리합니다. box-shadow와 border-radius로 부드러운 입체감을 줍니다.

4단계: 테스트 로직 구현 (JavaScript: 앱에 생명 불어넣기)

웹 앱이 실제로 작동하도록 생명을 불어넣는 단계입니다.

  1. JavaScript 코드 작성: 2단계에서 만든 index.html 파일의 <script> 태그 안에 아래 JavaScript 코드를 작성합니다.
  • 핵심: 질문 데이터 관리, 답변 선택 처리, 진행 상황 표시, NTRP 레벨 계산, 상세 분석 결과 생성 등 모든 핵심 기능을 구현합니다.
  • : 사용자의 답변에 따라 웹 페이지의 내용이 동적으로 변경되도록 로직을 작성하여 부드러운 사용자 경험을 제공합니다.

5단계: 피드백 반영 및 개선 (반복 과정)

더 좋은 웹 앱을 만들기 위한 필수적인 단계입니다.

  • 개발 중 또는 배포 후 사용자 피드백을 적극적으로 수렴합니다. 예를 들어, "첫 화면 문구가 너무 길다", "답변 옵션이 스크롤된다", "정렬이 맞지 않는다", "이모지가 깨진다" 등 다양한 의견을 반영합니다.
  • 이러한 피드백을 바탕으로 문구를 간결하게 다듬고, CSS를 정밀하게 조정하며, 이모지 호환성을 개선하는 과정을 여러 차례 반복합니다. 특히, 답변 옵션의 세로 중앙 정렬과 결과 페이지의 영역별 점수 바 시각화는 시각적 완성도를 높이는 데 중요한 부분입니다.

6단계: 광고 연동 (선택 사항 - 쿠팡 파트너스)

만든 웹 앱으로 수익을 창출하고 싶다면 시도해 볼 수 있는 단계입니다.

  • 웹 앱에 쿠팡 파트너스 광고를 연동하여 수익 창출 가능성을 열 수 있습니다.
  • 쿠팡 파트너스에서 제공하는 광고 코드를 HTML 파일의 적절한 위치에 삽입합니다. (예: 환영 화면과 결과 화면 하단)
  • 광고 삽입 유형 예시:
  • 검색 바 위젯: 사용자가 직접 검색어를 입력하여 쿠팡 상품을 찾을 수 있는 형태입니다.

  • 배너 위젯 (다이내믹 위젯): 특정 상품 캐러셀이나 추천 상품 배너 형태로 표시됩니다
  • 필수 고지 문구 추가: "이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다." 와 같은 문구를 웹 앱 내에 명시하여 투명성을 확보해야 합니다. (광고 활성화는 쿠팡 파트너스 심사 완료 후 가능합니다.)
  • 활동 시 주의 사항:
  1. 게시글 작성 시 필수 문구 기재: 쿠팡 파트너스 활동은 공정거래위원회의 심사지침에 따라 추천, 보증인인 파트너스 회원과 당사의 경제적 이해관계를 공개해야 합니다. 따라서 광고가 삽입된 모든 페이지나 게시글에는 위 고지 문구를 반드시 기재해야 합니다.
  2. 불법 스팸 전송 행위 유의: 바로가기 아이콘 등을 이용 시, 수신자의 사전 동의를 얻지 않은 메신저, SNS 등으로 메시지를 발송하는 행위는 불법 스팸 전송 행위로 간주되어 규제기관의 행정제재(3천만원 이하의 과태료) 또는 형사 처벌(1년 이하의 징역 또는 1천만원 이하의 벌금)의 대상이 될 수 있으니 이 점 유의하시기 바랍니다.

7단계: 웹 배포 및 SEO 최적화 (전 세계에 공개하고 잘 보이게 하기!)

만든 웹 앱을 인터넷에 공개하여 누구나 접속할 수 있도록 하고, 검색 엔진에서 잘 발견되도록 돕는 단계입니다.

  • 완성된 index.html 파일을 GitHub Pages 또는 Netlify와 같은 정적 웹 호스팅 서비스를 통해 웹에 배포합니다.
  • GitHub Pages를 통한 배포 방법:
  1. GitHub 저장소 생성: GitHub.com에 로그인 후, 새로운 'Public' 저장소(Repository)를 생성합니다. 저장소 이름은 나중에 웹사이트 URL의 일부가 됩니다.
  2. 파일 업로드: 생성된 저장소에 웹 앱을 구성하는 모든 파일(index.html, CSS, JS 등)을 업로드합니다. 이때, index.html 파일은 저장소의 최상위(root) 폴더에 위치해야 합니다.
  3. GitHub Pages 설정: 저장소 페이지에서 'Settings' 탭으로 이동한 후, 왼쪽 메뉴에서 'Pages'를 클릭합니다. 'Branch' 섹션에서 웹사이트를 배포할 브랜치(일반적으로 main 또는 master)를 선택하고, 'Folder'는 / (root)를 선택한 후 'Save' 버튼을 클릭합니다.
  4. URL 확인: 몇 분 후, 'Your site is published at...' 메시지와 함께 https://[당신의GitHub아이디].github.io/[저장소이름]/ 형식의 웹사이트 URL이 생성됩니다. 이 URL을 통해 접속할 수 있습니다.
  • Netlify를 통한 배포 방법:
  1. Netlify 계정 생성/로그인: Netlify.com에 접속하여 계정을 만들거나 로그인합니다.
  2. 새 사이트 배포 시작: Netlify 대시보드에서 'Add new site' 또는 'New site from Git' 버튼을 클릭합니다.
  3. GitHub 연결 (권장): 'Deploy with GitHub'를 선택하여 GitHub 계정을 Netlify에 연결하고, 배포할 저장소를 선택합니다. (이 방법은 GitHub 저장소에 변경사항이 발생할 때마다 Netlify가 자동으로 재배포해 주어 매우 편리합니다.)
  4. 수동 드래그 앤 드롭 (간편): GitHub 연결 없이, 로컬 컴퓨터에 있는 웹 앱 파일들이 담긴 폴더(예: ntrp/static 폴더의 내용물)를 Netlify 대시보드의 드래그 앤 드롭 영역으로 직접 끌어다 놓아도 배포가 가능합니다.
  5. 배포 설정 확인: 빌드 설정(Build settings)에서 'Build command'는 비워두고, 'Publish directory'는 . (현재 폴더)로 설정합니다. (정적 HTML 파일의 경우 별다른 빌드 과정이 필요 없기 때문입니다.)
  6. URL 확인: 배포가 완료되면 Netlify가 자동으로 생성해주는 URL을 통해 웹 앱에 접속할 수 있습니다.
  • SEO (검색 엔진 최적화)를 위한 추가 노력:
  • <meta name="description"> 추가: index.html 파일의 <head> 태그 안에 <meta name="description" content="나만의 테니스 NTRP 레벨을 쉽고 재미있게 진단하고, 포핸드, 백핸드, 멘탈까지 상세한 강점과 개선점, 맞춤형 훈련 제안을 받아보세요!"> 와 같이 페이지를 요약하는 설명을 추가합니다. 이 내용은 검색 결과에 표시될 수 있습니다.
  • Google Search Console 활용:
  1. Google Search Console에 접속하여 당신의 구글 계정으로 로그인합니다.
  2. 당신의 웹 앱 URL을 '속성'으로 추가하고 소유권을 확인합니다.
  3. URL 검사 도구 사용: 'URL 검사' 기능을 사용하여 당신의 웹 앱 URL을 입력하고 **'색인 생성 요청'**을 하면 구글이 당신의 페이지를 더 빨리 발견하고 검색 결과에 포함하도록 요청할 수 있습니다. 이 과정은 검색 노출 속도를 높이는 데 매우 효과적입니다.
  4. 성능 보고서 모니터링: 서치 콘솔에서 어떤 키워드로 사용자들이 웹 앱에 접속하는지, 클릭률은 어떤지 등을 지속적으로 확인하여 SEO 전략을 개선할 수 있습니다.
  • 블로그/SNS 공유: 지금 작성하는 이 블로그 글처럼, 웹 앱에 대한 설명을 자세히 쓰고 링크를 걸어두면 검색 엔진이 웹 앱을 더 쉽게 발견하고 중요하게 인식할 수 있습니다. 다양한 테니스 커뮤니티나 SNS에도 웹 앱 링크를 자연스럽게 공유하여 트래픽을 유도합니다.

🌟 주요 특징 및 장점

  • 친근하고 위트 있는 NTRP 레벨 설명: 딱딱한 설명 대신, 각 레벨을 '테니스 신생아', '테니스 걸음마', '테니스 전략가' 등 재미있는 별명으로 표현합니다.
  • 8가지 상세 영역 평가: 포핸드, 백핸드, 서브, 발리, 풋워크, 전략, 일관성, 멘탈까지 테니스의 주요 기술 및 심리적 요소를 종합적으로 평가합니다.
  • 직관적인 사용자 인터페이스: 깔끔하고 현대적인 디자인으로 누구나 쉽게 테스트를 진행할 수 있습니다.
  • 상세 분석 리포트: NTRP 레벨뿐만 아니라, 각 영역별 점수(시각적인 바 차트 포함), 강점, 개선점, 그리고 개인 맞춤형 개선 제안까지 제공하여 실력 향상에 실질적인 도움을 줍니다.
  • 완벽한 반응형 디자인: 스마트폰부터 24인치 모니터까지, 어떤 기기에서도 최적화된 화면으로 쾌적하게 이용할 수 있습니다.
  • 쉬운 배포 및 접근성: GitHub Pages나 Netlify를 통해 간단하게 웹에 배포되어 누구나 쉽게 접근하고 공유할 수 있습니다.

💡 웹 앱 사용 방법

  1. 배포된 웹 앱 URL에 접속합니다.
  2. 환영 화면에서 '테스트 시작하기' 버튼을 클릭합니다.
  3. 각 질문에 대해 자신의 실력에 가장 잘 맞는 답변을 선택합니다.
  4. 모든 질문에 답한 후 '결과 보기' 버튼을 클릭하면, 당신의 NTRP 레벨과 상세 분석 리포트를 확인할 수 있습니다.
  5. '다시 테스트하기' 버튼을 눌러 언제든지 재시도할 수 있습니다.

맺음말

이 가이드가 여러분이 직접 테니스 NTRP 레벨 테스트 웹 앱을 만들고 배포하는 데 도움이 되기를 바랍니다. 웹 개발은 생각보다 어렵지 않으며, 작은 아이디어로도 멋진 결과물을 만들 수 있습니다. 궁금한 점이나 추가적인 피드백은 언제든지 환영합니다. 여러분의 즐거운 코딩과 테니스 생활을 응원합니다! 감사합니다!

 

 

최종 코드

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>🎾 테니스 NTRP 레벨 테스트</title>
    <!-- Google Fonts: Poppins -->
    <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap" rel="stylesheet">
    <style>
        :root {
            --primary-gradient: linear-gradient(135deg, #6A11CB 0%, #2575FC 100%);
            --primary-color-start: #6A11CB;
            --primary-color-end: #2575FC;
            --text-dark: #222;
            --text-medium: #555;
            --text-light: #777;
            --bg-light: #f8f9fa;
            --border-light: #e0e0e0;
            --shadow-light: 0 10px 30px rgba(0, 0, 0, 0.08);
            --shadow-medium: 0 15px 45px rgba(0, 0, 0, 0.15);
            --shadow-button-hover: 0 10px 25px rgba(106, 17, 203, 0.4);
        }

        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family: 'Poppins', sans-serif;
            background: var(--primary-gradient);
            min-height: 100vh;
            display: flex;
            justify-content: center;
            align-items: center;
            padding: 20px;
        }

        .container {
            background: white;
            border-radius: 25px;
            padding: 40px;
            box-shadow: var(--shadow-medium);
            max-width: 650px;
            width: 100%;
            text-align: center;
            margin: 20px;
            min-height: 85vh;
            display: flex;
            flex-direction: column;
            justify-content: space-between;
        }

        h1 {
            color: var(--text-dark);
            margin-bottom: 30px;
            font-size: 2.8rem;
            font-weight: 700;
            line-height: 1.2;
            letter-spacing: -0.03em;
        }
        
        h1 .emoji {
            display: inline-block;
            margin-right: 5px;
        }

        .progress-bar {
            width: 100%;
            height: 10px;
            background: var(--border-light);
            border-radius: 5px;
            margin-bottom: 35px;
            overflow: hidden;
        }

        .progress-fill {
            height: 100%;
            background: var(--primary-gradient);
            transition: width 0.4s cubic-bezier(0.25, 0.8, 0.25, 1);
            border-radius: 5px;
        }

        /* Question card and option spacing/sizing adjustments */
        .question-card {
            margin-bottom: 20px;
            flex-grow: 1; 
            display: flex;
            flex-direction: column;
            justify-content: flex-start;
        }

        .question-card h2 {
            color: var(--text-medium);
            margin-bottom: 10px;
            font-size: 1.2rem;
        }

        .question {
            font-size: 1.5rem;
            color: var(--text-dark);
            margin-bottom: 10px;
            line-height: 1.4;
            font-weight: 600;
        }

        .question-description {
            font-size: 1rem;
            color: var(--text-light);
            margin-bottom: 25px;
            line-height: 1.5;
            font-style: normal;
        }

        .options {
            display: flex;
            flex-direction: column;
            gap: 8px; /* Reduced gap */
            margin-bottom: 15px;
        }

        .option {
            display: flex;
            align-items: center; /* 핵심: 세로 중앙 정렬 */
            padding: 12px 20px; /* Reduced padding */
            border: 2px solid var(--border-light);
            border-radius: 12px;
            background: white;
            cursor: pointer;
            transition: all 0.3s ease;
            text-align: left;
            min-height: 50px; /* Reduced min-height */
            box-shadow: 0 3px 10px rgba(0, 0, 0, 0.04);
        }

        .option:hover {
            border-color: var(--primary-color-start);
            transform: translateY(-2px);
            box-shadow: 0 6px 15px rgba(0, 0, 0, 0.08);
        }

        .option.selected {
            border-color: var(--primary-color-end);
            background: var(--primary-gradient);
            color: white;
            transform: translateY(-1px);
            box-shadow: 0 4px 10px rgba(106, 17, 203, 0.3);
        }

        /* Score display removed, so .option .score style is now unused */
        /* .option .score { display: none; } */ 

        .option .description {
            flex: 1; /* Takes up remaining space */
            font-size: 0.95rem; /* Slightly reduced font size */
            line-height: 1.4;
            display: flex;
            align-items: center; /* Ensures text remains vertically centered */
        }

        .navigation {
            display: flex;
            justify-content: space-between;
            gap: 15px;
            margin-top: 15px;
        }

        .nav-btn {
            padding: 12px 25px;
            border: none;
            border-radius: 10px;
            font-size: 1rem;
            font-weight: 600;
            cursor: pointer;
            transition: all 0.3s ease;
            flex: 1;
            text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
        }

        .nav-btn.prev {
            background: var(--bg-light);
            color: var(--text-medium);
            box-shadow: 0 3px 8px rgba(0, 0, 0, 0.06);
        }

        .nav-btn.prev:hover {
            background: #e0e0e0;
            box-shadow: 0 4px 10px rgba(0, 0, 0, 0.09);
        }

        .nav-btn.next,
        .nav-btn.submit {
            background: var(--primary-gradient);
            color: white;
            box-shadow: 0 5px 15px rgba(106, 17, 203, 0.3);
        }

        .nav-btn.next:hover,
        .nav-btn.submit:hover {
            transform: translateY(-2px);
            box-shadow: 0 8px 20px rgba(106, 17, 203, 0.4);
        }

        .nav-btn:disabled {
            opacity: 0.5;
            cursor: not-allowed;
            transform: none;
            box-shadow: none;
        }

        .result-card {
            background: var(--primary-gradient);
            color: white;
            border-radius: 20px;
            padding: 30px;
            margin-bottom: 20px;
            text-align: center;
            box-shadow: var(--shadow-medium);
            flex-grow: 1;
            display: flex;
            flex-direction: column;
            justify-content: center;
        }

        .result-card h2 {
            margin-bottom: 20px;
            font-size: 1.6rem;
        }

        .ntrp-level {
            font-size: 4.5rem;
            font-weight: 800;
            margin: 20px 0;
            text-shadow: 2px 2px 5px rgba(0, 0, 0, 0.3);
            line-height: 1;
        }

        .description {
            font-size: 1.2rem;
            margin-bottom: 20px;
            line-height: 1.5;
        }

        .stats {
            background: rgba(255, 255, 255, 0.1);
            border-radius: 10px;
            padding: 15px;
            margin-top: 15px;
        }

        .stats p {
            margin: 5px 0;
            font-size: 1rem;
        }

        .detailed-analysis {
            background: var(--bg-light);
            border-radius: 20px;
            padding: 30px;
            margin: 20px 0;
            text-align: left;
            box-shadow: var(--shadow-light);
            flex-grow: 1;
            display: flex;
            flex-direction: column;
            justify-content: center;
        }

        .detailed-analysis h3 {
            color: var(--text-dark);
            margin-bottom: 25px;
            font-size: 1.6rem;
            font-weight: 600;
            text-align: center;
        }

        .detailed-analysis h4 {
            color: var(--text-medium);
            margin-bottom: 15px;
            font-size: 1.2rem;
            font-weight: 600;
            text-align: center;
        }

        /* Score Grid (2 columns for desktop) */
        .score-grid {
            display: grid;
            grid-template-columns: repeat(2, 1fr); /* 2 columns for desktop */
            gap: 12px;
            margin-bottom: 25px;
        }

        .score-item {
            display: flex;
            align-items: center; /* Vertically centers content within item */
            gap: 10px;
            padding: 14px;
            background: white;
            border-radius: 10px;
            box-shadow: 0 3px 8px rgba(0, 0, 0, 0.04);
        }

        .category-name {
            min-width: 70px;
            font-weight: 600;
            color: var(--text-dark);
            text-align: left;
            flex-shrink: 0;
            font-size: 0.95rem;
        }

        .score-bar {
            flex-grow: 1;
            height: 12px;
            background: var(--border-light);
            border-radius: 6px;
            overflow: hidden;
        }

        .score-fill {
            height: 100%;
            background: var(--primary-gradient);
            transition: width 0.5s cubic-bezier(0.25, 0.8, 0.25, 1);
            border-radius: 6px;
        }

        .score-value {
            min-width: 35px;
            font-weight: 600;
            color: var(--text-dark);
            text-align: right;
            flex-shrink: 0;
            font-size: 0.95rem;
        }

        .strengths-weaknesses {
            display: grid;
            grid-template-columns: 1fr 1fr;
            gap: 20px;
            margin-bottom: 25px;
        }

        .strengths, .weaknesses {
            background: white;
            padding: 20px;
            border-radius: 12px;
            box-shadow: 0 3px 10px rgba(0, 0, 0, 0.06);
            text-align: center;
        }

        .strengths h4 {
            color: #28a745;
            font-weight: 700;
            margin-bottom: 12px;
        }

        .weaknesses h4 {
            color: #dc3545;
            font-weight: 700;
            margin-bottom: 12px;
        }

        .strengths ul, .weaknesses ul {
            list-style: none;
            padding: 0;
        }

        .strengths li, .weaknesses li {
            padding: 8px 0;
            border-bottom: 1px solid #eee;
            font-size: 1rem;
            color: var(--text-medium);
            line-height: 1.4;
        }

        .strengths li:last-child, .weaknesses li:last-child {
            border-bottom: none;
        }

        .improvement-suggestions {
            background: white;
            padding: 20px;
            border-radius: 12px;
            box-shadow: 0 3px 10px rgba(0, 0, 0, 0.06);
            text-align: center;
            margin-top: 20px;
        }

        .improvement-suggestions h4 {
            color: #007bff;
            font-weight: 700;
            margin-bottom: 12px;
        }

        .improvement-suggestions ul {
            list-style: none;
            padding: 0;
        }

        .improvement-suggestions li {
            padding: 10px 0;
            border-bottom: 1px solid #eee;
            line-height: 1.6;
            font-size: 1rem;
            color: var(--text-medium);
        }

        .improvement-suggestions li:last-child {
            border-bottom: none;
        }

        .restart-btn {
            background: var(--primary-gradient);
            color: white;
            border: none;
            padding: 15px 40px;
            border-radius: 12px;
            font-size: 1rem;
            font-weight: 600;
            cursor: pointer;
            transition: all 0.3s ease;
            margin-top: 30px;
            box-shadow: 0 5px 15px rgba(106, 17, 203, 0.3);
            text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
        }

        .restart-btn:hover {
            transform: translateY(-2px);
            box-shadow: 0 8px 20px rgba(106, 17, 203, 0.4);
        }

        /* Welcome Screen Styles */
        .welcome-screen {
            text-align: center;
            max-width: 800px;
            margin: 0 auto;
            flex-grow: 1;
            display: flex;
            flex-direction: column;
            justify-content: center;
            align-items: center;
        }

        .welcome-title {
            font-size: 3rem;
            font-weight: 800;
            color: var(--primary-color-start);
            margin-bottom: 15px;
            line-height: 1.2;
            letter-spacing: -0.04em;
            text-shadow: 0 3px 8px rgba(0, 0, 0, 0.05);
        }

        .welcome-subtitle {
            font-size: 1.3rem;
            color: var(--text-medium);
            margin-bottom: 20px;
            line-height: 1.6;
            font-weight: 400;
        }

        .welcome-description {
            font-size: 1rem;
            color: var(--text-light);
            line-height: 1.6;
            margin-bottom: 30px;
            text-align: center;
            max-width: 500px;
        }

        .start-btn {
            background: var(--primary-gradient);
            color: white;
            border: none;
            padding: 16px 45px;
            border-radius: 15px;
            font-size: 1.2rem;
            font-weight: 700;
            cursor: pointer;
            transition: all 0.3s ease;
            box-shadow: 0 8px 25px rgba(106, 17, 203, 0.35);
            text-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
            letter-spacing: 0.02em;
        }

        .start-btn:hover {
            transform: translateY(-2px);
            box-shadow: 0 10px 30px rgba(106, 17, 203, 0.45);
        }

        /* Coupang Ad Style */
        .coupang-ad-container {
            margin-top: 25px;
            margin-bottom: 5px;
            text-align: center;
            width: 100%;
        }
        .coupang-ad-container iframe {
            max-width: 100%;
            height: 44px; /* Default height for search bar */
            border-radius: 8px;
            overflow: hidden;
        }
        /* Style for banner ad iframe */
        .coupang-banner-ad-container {
            margin-top: 20px; /* Spacing from previous content */
            margin-bottom: 10px; /* Spacing to next content */
            text-align: center;
            width: 100%;
        }
        .coupang-banner-ad-container iframe {
            max-width: 100%; /* Ensure responsiveness */
            height: 140px; /* Fixed height for banner */
            border-radius: 8px;
            overflow: hidden;
            border: none; /* Remove default iframe border */
        }


        /* Coupang Disclosure Style */
        .coupang-disclosure {
            font-size: 0.75rem;
            color: var(--text-light);
            margin-top: 10px;
            text-align: center;
            line-height: 1.3;
        }


        /* Responsive Design */
        @media (max-width: 768px) {
            .container {
                padding: 20px;
                margin: 10px;
                min-height: 95vh;
            }
            
            h1 {
                font-size: 2rem;
                margin-bottom: 20px;
            }
            
            .question-card h2 {
                font-size: 1rem;
            }
            
            .question {
                font-size: 1.3rem;
                margin-bottom: 8px;
            }
            
            .question-description {
                font-size: 0.85rem;
                margin-bottom: 20px;
            }
            
            .options {
                gap: 8px;
                margin-bottom: 15px;
            }
            
            .option {
                padding: 12px 15px;
                min-height: 50px;
                border-radius: 10px;
            }
            
            .option .description { /* score가 display: none이므로 직접 접근 */
                font-size: 0.85rem;
            }
            
            .nav-btn {
                padding: 10px 20px;
                font-size: 0.9rem;
            }
            
            .result-card {
                padding: 25px;
            }
            
            .result-card h2 {
                font-size: 1.3rem;
            }
            
            .ntrp-level {
                font-size: 3rem;
            }
            
            .description {
                font-size: 1rem;
            }
            
            .stats p {
                font-size: 0.85rem;
            }
            
            .detailed-analysis {
                padding: 20px;
            }
            
            .detailed-analysis h3 {
                font-size: 1.4rem;
                margin-bottom: 15px;
            }
            
            .detailed-analysis h4 {
                font-size: 1rem;
                margin-bottom: 10px;
            }
            
            /* 모바일에서 score-grid는 다시 1열로 */
            .score-grid {
                grid-template-columns: 1fr;
                gap: 8px;
            }
            
            /* 모바일에서 score-item 내부 정렬 다시 설정 */
            .score-item {
                flex-direction: row;
                align-items: center;
                justify-content: space-between;
                gap: 8px;
                padding: 10px 15px;
            }
            
            .category-name {
                min-width: auto;
                text-align: left;
                margin-bottom: 0;
                font-size: 0.9rem;
            }
            
            .score-bar {
                flex-grow: 1;
                height: 10px;
            }
            
            .score-value {
                min-width: auto;
                text-align: right;
                margin-top: 0;
                font-size: 0.9rem;
            }

            .strengths-weaknesses {
                grid-template-columns: 1fr;
                gap: 15px;
            }
            
            .strengths, .weaknesses, .improvement-suggestions {
                padding: 15px;
            }
            
            .strengths h4, .weaknesses h4, .improvement-suggestions h4 {
                font-size: 1rem;
            }
            
            .strengths ul, .weaknesses ul, .improvement-suggestions ul {
                font-size: 0.85rem;
                line-height: 1.4;
            }
            
            .restart-btn {
                padding: 12px 25px;
                font-size: 1rem;
                margin-top: 20px;
            }

            /* Welcome Screen Responsive */
            .welcome-title {
                font-size: 1.8rem;
                margin-bottom: 10px;
            }
            
            .welcome-subtitle {
                font-size: 1rem;
                margin-bottom: 15px;
            }
            
            .welcome-description {
                font-size: 0.85rem;
                margin-bottom: 25px;
            }
            
            .start-btn {
                padding: 12px 30px;
                font-size: 1rem;
            }
            .coupang-disclosure {
                font-size: 0.7rem;
            }
        }
    </style>
</head>
<body>
    <div class="container" id="app">
        <!-- 앱 내용이 여기에 동적으로 로드됩니다 -->
    </div>

    <script>
        // 앱 상태 관리
        let currentState = 'welcome'; // 'welcome', 'quiz', 'result'
        let currentQuestionIndex = 0;
        let answers = {};

        // NTRP 질문 데이터
        const questions = {
            "forehand": {
                "question": "포핸드를 원하는 곳으로 보낼 수 있나요?",
                "description": "포핸드의 방향 제어와 깊이 조절 능력을 평가합니다.",
                "levels": {
                    1: "공 때리기도 급급! 라켓에 맞으면 다행이죠. 😅",
                    2: "간단한 방향(대충 저~쪽)은 어쩌다 가능해요. 가끔 예상치 못한 곳으로! 😓", 
                    3: "중간 속도 공은 원하는 곳으로 척척! 나름 컨트롤 가능! 😉",
                    4: "깊이와 방향을 자유자재로 조절! 상대 코트 구석구석을 노려요. 🌟", 
                    5: "어떤 상황에서도 정확한 포핸드로 게임 지배! 역시 필승! 💪"
                }
            },
            "backhand": {
                "question": "백핸드를 원하는 곳으로 보낼 수 있나요?",
                "description": "백핸드의 일관성과 방향 제어 능력을 평가합니다.",
                "levels": {
                    1: "백핸드만 오면 몸부터 돌려 포핸드로 냅다 쳐요. 나의 숙제! 😭",
                    2: "백핸드를 치긴 하는데 네트행이거나 엉뚱한 방향으로 가요. 삐끗삐끗! 😥",
                    3: "중간 속도 공은 백핸드로도 꽤 안정적! 이제 좀 폼이 나는 듯? 😊",
                    4: "백핸드로도 방향/깊이 자유자재! 상대방이 약점인 줄 알았대요. 😎",
                    5: "포핸드만큼 강력하고 정확한 백핸드로 상대를 압도! 백핸드마스터! 🚀"
                }
            },
            "serve": {
                "question": "서브의 정확성과 파워는 어느 정도인가요?",
                "description": "첫 서브와 세컨드 서브의 성공률과 효과를 평가합니다.",
                "levels": {
                    1: "일단 서브 넣기만 해도 감지덕지! 더블 폴트는 기본이죠. 😥",
                    2: "세컨드 서브는 어떻게든 코트 안에 넣어요. 첫 서브는 모험! 🤞",
                    3: "첫 서브도 가끔 번개처럼 꽂히고, 세컨드 서브도 안정적! ⚡",
                    4: "강력한 파워와 날카로운 정확성! 서브 에이스 제조기! 💥",
                    5: "압박감 속에서도 에이스 꽂는 믿음직한 서브! 서브는 곧 득점! 💯"
                }
            },
            "volley": {
                "question": "발리와 네트 플레이를 할 수 있나요?",
                "description": "네트에서의 편안함과 발리 득점 능력을 평가합니다.",
                "levels": {
                    1: "네트 근처 가는 건 좀... 공이 무서워요. �",
                    2: "간단하게 네트 앞에서 공 받아 넘기는 정도는 해요. 툭! 😅",
                    3: "포핸드 발리는 꽤 자신 있어요! 백핸드는 아직 연습 중... 🤔",
                    4: "발리로 포인트를 직접 따낼 수 있어요! 네트는 나의 구역! 🏰",
                    5: "네트 앞에서 상대를 압박하고 발리로 게임을 끝냅니다! 발리의 마법사! 💫"
                }
            },
            "footwork": {
                "question": "풋워크와 코트 커버리지는 어느 정도인가요?",
                "description": "코트 이동과 포지셔닝 능력을 평가합니다.",
                "levels": {
                    1: "공이 오면 멍... 공 따라가는 것도 버거워요. 😥",
                    2: "기본적인 좌우 이동은 하지만, 빠르진 않아요. 영차영차! 🚶",
                    3: "코트 넓이를 어느 정도 커버해요. 이제 좀 테니스 선수 같아요! 🏃",
                    4: "날렵한 풋워크로 코트 구석구석을 누벼요! 휙휙! 💨",
                    5: "프로 선수처럼 코트 전체를 지배하는 풋워크와 포지셔닝! 코트의 지배자! 👑"
                }
            },
            "strategy": {
                "question": "게임 전략을 세우고 실행할 수 있나요?",
                "description": "상대방 분석과 전략적 사고 능력을 평가합니다.",
                "levels": {
                    1: "그냥 공만 치면 되는 거 아닌가요? 전략은 뭔가요? 🤷",
                    2: "상대방 약점을 대충 노려볼까...? 정도는 생각해요. 🧐",
                    3: "상대 약점 파악하고 거기에 맞춰 공격 시도! 제법 전략적! 🎯",
                    4: "게임을 예측하고 상황에 따라 전략을 유연하게 바꿔요! 이기는 게임은 전략 싸움! 🧠",
                    5: "어떤 상대방도 완벽 분석, 맞춤 전략으로 승리 쟁취! 테니스 브레인! 💡"
                }
            },
            "consistency": {
                "question": "일관성 있게 랠리를 이어갈 수 있나요?",
                "description": "안정적인 샷과 에러 관리 능력을 평가합니다.",
                "levels": {
                    1: "3번 이상 랠리 이어가면 기적! 내 공은 어디로... 😵‍💫",
                    2: "공은 넘기는데, 속도는 좀 느려요 🐢", // 문구 수정 및 이모지 변경
                    3: "중간 속도 랠리는 꽤 안정적! 이제 좀 랠리 맛이 나네요! 👍",
                    4: "빠른 페이스에서도 안정적! 좀처럼 실수하지 않죠. 🛡️",
                    5: "어떤 상황에서도 흔들림 없는 완벽한 일관성! 벽치기 장인! 🧱"
                }
            },
            "mental": {
                "question": "게임 중 멘탈 관리가 잘 되나요?",
                "description": "압박감과 스트레스 상황에서의 대처 능력을 평가합니다.",
                "levels": {
                    1: "실수 한 번 하면 라켓 던지고 싶어져요. 바로 멘탈 붕괴! 🤯",
                    2: "간단한 게임은 괜찮은데, 지고 있으면 표정 관리가 안 돼요. 😠",
                    3: "어느 정도 압박감에 대처 가능! 그래도 속은 부글부글... 😤",
                    4: "중요한 순간에도 침착하고 냉정함 유지! 강철 멘탈! 💎",
                    5: "어떤 상황에서도 흔들리지 않는 완벽 멘탈로 상대를 압도! 멘탈 갑! 👑"
                }
            }
        };

        // NTRP 레벨별 위트 있는 설명
        const ntrpDescriptions = {
            1.0: "테니스 신생아: 라켓을 잡고 코트에 첫발을 내디딘 당신! 테니스 세계에 오신 것을 환영합니다!",
            1.5: "테니스 걸음마: 공을 때리고는 있는데, 어디로 갈지는 공만 아는 단계! 일단 공을 넘기는 게 목표죠.",
            2.0: "테니스 초급 모험가: 공을 넘기기 시작했지만, 아직 기본기가 시급한 테린이! 랠리는 짧지만, 열정은 만렙!",
            2.5: "테니스 길찾기 중: 어설프게 공은 치지만, 어디로 갈지, 왜 이렇게 어려운지 고민이 많은 단계. 그래도 랠리의 맛을 조금씩 알아가죠!",
            3.0: "테니스 기본 다지기: 이제 공은 웬만큼 원하는 곳으로 보내지만, 깊이나 파워는 아직 미완성! 복식에서 '원업 원백'이 익숙한 알뜰 플레이어.",
            3.5: "테니스 실력 점프 중: 일관성과 컨트롤을 갖추고 슬슬 네트 플레이도 시도하는 단계! '저 사람 꽤 치는데?' 소리 듣기 시작할 때죠.",
            4.0: "테니스 숙련자 입문: 포핸드, 백핸드 모두 깊이와 컨트롤을 갖추고, 서브도 제법 날카로운 아마추어 고수! 게임의 흐름을 읽기 시작합니다.",
            4.5: "테니스 전략가 초급: 파워, 스핀, 전략까지 겸비한 테니스 '꾼'! 게임을 이끌어가는 능력이 출중하며, 코트에서 상대방을 괴롭히기 시작합니다.",
            5.0: "테니스 아마추어 최강자: 강력한 공격과 정교한 기술을 겸비한 아마추어 레벨의 정점! 이제 동네 코트에서는 적수를 찾기 어렵죠.",
            5.5: "테니스 준프로의 벽: 프로급 기술과 전략을 갖추고 어떤 상황에서도 흔들리지 않는 멘탈의 소유자! 아마추어와 프로의 경계에 서 있습니다.",
            6.0: "테니스 프로페셔널: 국가대표, 대학 선수, 또는 세계를 무대로 뛰는 진정한 테니스 스타! 이들은 그냥 '테니스 그 자체'입니다.",
            7.0: "테니스 월드클래스: 세계적 수준의 테니스 스타! 이제 당신은 테니스의 신이 되었습니다."
        };

        // 앱 상태 관리
        let result = null;

        // 질문 키 배열
        const questionKeys = Object.keys(questions);

        // 앱 렌더링 함수
        function renderApp() {
            const app = document.getElementById('app');
            
            if (currentState === 'welcome') {
                renderWelcome(app);
            } else if (result) {
                renderResult(app);
            } else {
                renderQuestion(app);
            }
        }

        // 환영 화면 렌더링
        function renderWelcome(app) {
            app.innerHTML = `
                <div class="welcome-screen">
                    <h1 class="welcome-title"><span class="emoji">🌠</span> 당신의 테니스 레벨은?</h1>
                    <p class="welcome-subtitle">간단한 테스트로 지금 바로 확인하세요! <span class="emoji">🚀</span></p>
                    <div class="welcome-description">
                        <p>내 테니스 실력이 궁금하신가요?<br>
                        이 테스트는 당신의 NTRP 레벨을 쉽고 재미있게 진단해 드립니다.<br>
                        몇 가지 질문에 답하고,<br>
                        포핸드부터 멘탈까지 당신의 강점과 개선점을 한눈에 확인해보세요! <span class="emoji">💫</span></p>
                    </div>
                    <button class="start-btn" onclick="startTest()">테스트 시작하기</button>
                    <!-- Coupang Partners Search Bar Ad -->
                    <div class="coupang-ad-container">
                        <iframe src="https://coupa.ng/ciT9UH" width="100%" height="44" frameborder="0" scrolling="no" referrerpolicy="unsafe-url" browsingtopics></iframe>
                    </div>
                    <!-- Coupang Partners Banner Ad -->
                    <div class="coupang-banner-ad-container">
                        <iframe src="https://ads-partners.coupang.com/widgets.html?id=885878&template=carousel&trackingCode=AF6812978&subId=&width=680&height=140&tsource=" width="100%" height="140" frameborder="0" scrolling="no" referrerpolicy="unsafe-url" browsingtopics></iframe>
                    </div>
                    <!-- Coupang Partners Disclosure -->
                    <p class="coupang-disclosure">이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.</p>
                </div>
            `;
        }

        // 질문 렌더링
        function renderQuestion(app) {
            const currentQuestionKey = questionKeys[currentQuestionIndex];
            const currentQuestion = questions[currentQuestionKey];
            
            app.innerHTML = `
                <h1>🎾 테니스 NTRP 레벨 테스트</h1>
                
                <div class="progress-bar">
                    <div class="progress-fill" style="width: ${((currentQuestionIndex + 1) / questionKeys.length) * 100}%"></div>
                </div>
                
                <div class="question-card">
                    <h2>질문 ${currentQuestionIndex + 1} / ${questionKeys.length}</h2>
                    <p class="question">${currentQuestion.question}</p>
                    <p class="question-description">${currentQuestion.description}</p>
                    
                    <div class="options">
                        ${Object.entries(currentQuestion.levels).map(([score, description]) => `
                            <button class="option ${answers[currentQuestionKey] === parseInt(score) ? 'selected' : ''}" 
                                    onclick="selectAnswer('${currentQuestionKey}', ${score})">
                                <!-- Score display removed -->
                                <span class="description">${description}</span>
                            </button>
                        `).join('')}
                    </div>
                </div>

                <div class="navigation">
                    ${currentQuestionIndex > 0 ? `
                        <button class="nav-btn prev" onclick="previousQuestion()">이전</button>
                    ` : ''}
                    
                    ${currentQuestionIndex < questionKeys.length - 1 ? `
                        <button class="nav-btn next" onclick="nextQuestion()" 
                                ${!answers[currentQuestionKey] ? 'disabled' : ''}>다음</button>
                    ` : `
                        <button class="nav-btn submit" onclick="calculateResult()" 
                                ${Object.keys(answers).length < questionKeys.length ? 'disabled' : ''}>결과 보기</button>
                    `}
                </div>
            `;
        }

        // 결과 렌더링
        function renderResult(app) {
            app.innerHTML = `
                <h1>🎾 NTRP 테스트 결과</h1>
                <div class="result-card">
                    <h2>당신의 NTRP 레벨은</h2>
                    <div class="ntrp-level">${result.ntrp_level}</div>
                    <p class="description">${result.description}</p>
                    <div class="stats">
                        <p>평균 점수: ${result.average_score}/5</p>
                        <p>응답한 질문: ${result.total_questions}개</p>
                    </div>
                </div>
                
                <div class="detailed-analysis">
                    <h3>📊 상세 분석</h3>
                    
                    <div class="category-scores">
                        <h4>영역별 점수</h4>
                        <div class="score-grid">
                            ${Object.entries(result.category_scores).map(([category, score]) => `
                                <div class="score-item">
                                    <span class="category-name">${category}</span>
                                    <div class="score-bar">
                                        <div class="score-fill" style="width: ${(score / 5) * 100}%"></div>
                                    </div>
                                    <span class="score-value">${score}/5</span>
                                </div>
                            `).join('')}
                        </div>
                    </div>
                    
                    <div class="strengths-weaknesses">
                        ${result.strengths && result.strengths.length > 0 ? `
                            <div class="strengths">
                                <h4>💪 강점</h4>
                                <ul>
                                    ${result.strengths.map(strength => `<li>${strength}</li>`).join('')}
                                </ul>
                            </div>
                        ` : ''}
                        
                        ${result.weaknesses && result.weaknesses.length > 0 ? `
                            <div class="weaknesses">
                                <h4>🎯 개선점</h4>
                                <ul>
                                    ${result.weaknesses.map(weakness => `<li>${weakness}</li>`).join('')}
                                </ul>
                            </div>
                        ` : ''}
                    </div>
                    
                    ${result.improvement_suggestions ? `
                        <div class="improvement-suggestions">
                            <h4>💡 개선 제안</h4>
                            <ul>
                                ${result.improvement_suggestions.map(suggestion => `<li>${suggestion}</li>`).join('')}
                            </ul>
                        </div>
                    ` : ''}
                </div>
                
                <button class="restart-btn" onclick="restartTest()">다시 테스트하기</button>
                <!-- Coupang Partners Search Bar Ad -->
                <div class="coupang-ad-container">
                    <iframe src="https://coupa.ng/ciT9UH" width="100%" height="44" frameborder="0" scrolling="no" referrerpolicy="unsafe-url" browsingtopics></iframe>
                </div>
                <!-- Coupang Partners Banner Ad -->
                <div class="coupang-banner-ad-container">
                    <iframe src="https://ads-partners.coupang.com/widgets.html?id=885878&template=carousel&trackingCode=AF6812978&subId=&width=680&height=140&tsource=" width="100%" height="140" frameborder="0" scrolling="no" referrerpolicy="unsafe-url" browsingtopics></iframe>
                </div>
                <!-- Coupang Partners Disclosure -->
                <p class="coupang-disclosure">이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.</p>
            `;
        }

        // Test start
        function startTest() {
            currentState = 'quiz';
            currentQuestionIndex = 0;
            answers = {};
            result = null;
            renderApp();
        }

        // Select answer
        function selectAnswer(questionKey, score) {
            answers[questionKey] = score;
            renderApp();
        }

        // Next question
        function nextQuestion() {
            if (currentQuestionIndex < questionKeys.length - 1) {
                currentQuestionIndex++;
                renderApp();
            }
        }

        // Previous question
        function previousQuestion() {
            if (currentQuestionIndex > 0) {
                currentQuestionIndex--;
                renderApp();
                // Restore selected state after rendering
                const currentQuestionKey = questionKeys[currentQuestionIndex];
                const selectedScore = answers[currentQuestionKey];
                if (selectedScore) {
                    const options = document.querySelectorAll('.option');
                    options.forEach(option => {
                        if (questions[currentQuestionKey].levels[selectedScore] === option.querySelector('.description').innerText) {
                            option.classList.add('selected');
                        } else {
                            option.classList.remove('selected');
                        }
                    });
                }
            }
        }

        // Calculate result
        function calculateResult() {
            const totalScore = Object.values(answers).reduce((sum, score) => sum + score, 0);
            const averageScore = totalScore / Object.keys(answers).length;
            
            // Determine NTRP level
            let ntrpLevel;
            if (averageScore <= 1.25) ntrpLevel = 1.0;
            else if (averageScore <= 1.75) ntrpLevel = 1.5;
            else if (averageScore <= 2.25) ntrpLevel = 2.0;
            else if (averageScore <= 2.75) ntrpLevel = 2.5;
            else if (averageScore <= 3.25) ntrpLevel = 3.0;
            else if (averageScore <= 3.75) ntrpLevel = 3.5;
            else if (averageScore <= 4.25) ntrpLevel = 4.0;
            else if (averageScore <= 4.75) ntrpLevel = 4.5;
            else ntrpLevel = 5.0; // If average score is above 4.75, it's 5.0

            // Detailed analysis
            const categoryNames = {
                "forehand": "포핸드",
                "backhand": "백핸드",
                "serve": "서브",
                "volley": "발리",
                "footwork": "풋워크",
                "strategy": "전략",
                "consistency": "일관성",
                "mental": "멘탈"
            };

            const categoryScores = {};
            Object.keys(categoryNames).forEach(category => {
                if (answers[category]) {
                    categoryScores[categoryNames[category]] = answers[category];
                }
            });

            // Analyze strengths and weaknesses
            const strengths = [];
            const weaknesses = [];
            Object.entries(categoryScores).forEach(([category, score]) => {
                if (score >= 4) strengths.push(category);
                else if (score <= 2) weaknesses.push(category);
            });

            // Improvement suggestions
            const improvementSuggestions = [];
            if (weaknesses.includes("포핸드")) {
                improvementSuggestions.push("포핸드 기본기 연습이 필요합니다. 스윙 폼과 타점을 중점적으로 연습해보세요.");
            }
            if (weaknesses.includes("백핸드")) {
                improvementSuggestions.push("백핸드 그립과 준비 동작을 연습해보세요. 백핸드를 피하지 말고 적극적으로 연습하세요.");
            }
            if (weaknesses.includes("서브")) {
                improvementSuggestions.push("서브 연습을 통해 정확성과 파워를 향상시켜보세요. 세컨드 서브부터 안정화하세요.");
            }
            if (weaknesses.includes("발리")) {
                improvementSuggestions.push("네트 플레이에 익숙해지세요. 간단한 발리부터 시작해서 점진적으로 발전시키세요.");
            }
            if (weaknesses.includes("풋워크")) {
                improvementSuggestions.push("기본적인 이동 연습이 필요합니다. 사이드 스텝과 크로스오버 스텝을 연습하세요.");
            }
            if (weaknesses.includes("전략")) {
                improvementSuggestions.push("게임 중 상대방을 관찰하고 약점을 찾아보세요. 다양한 샷 조합을 연습하세요.");
            }
            if (weaknesses.includes("일관성")) {
                improvementSuggestions.push("안정적인 샷을 위해 기본기를 탄탄히 하세요. 에러를 줄이는 연습이 중요합니다.");
            }
            if (weaknesses.includes("멘탈")) {
                improvementSuggestions.push("압박감에 대처하는 연습이 필요합니다. 실수를 해도 포기하지 않는 마음가짐이 중요합니다.");
            }
            if (improvementSuggestions.length === 0) {
                improvementSuggestions.push("전반적으로 좋은 실력을 보유하고 있습니다. 더 높은 레벨을 위해 고급 기술을 연습해보세요.");
            }

            result = {
                ntrp_level: ntrpLevel,
                description: ntrpDescriptions[ntrpLevel] || "정의되지 않은 레벨",
                average_score: Math.round(averageScore * 10) / 10,
                total_questions: Object.keys(answers).length,
                category_scores: categoryScores,
                strengths: strengths,
                weaknesses: weaknesses,
                improvement_suggestions: improvementSuggestions
            };

            renderApp();
        }

        // Restart test
        function restartTest() {
            currentState = 'welcome';
            currentQuestionIndex = 0;
            answers = {};
            result = null;
            renderApp();
        }

        // Initialize app
        renderApp();
    </script>
</body>
</html>
반응형