サブスク自動整理・管理サービス

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>SUBS Management - サブスク自動整理・管理サービス</title>
  <meta name="description" content="複数の定額サービスを一括管理・整理し、解約し忘れを防止して家計を賢くするプラットフォーム">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">

  <!-- ==========================
       外部ライブラリ (CDN)
  ========================== -->
  <!-- GSAP + ScrollTrigger -->
  <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"
    integrity="sha512-xxxx" 
    crossorigin="anonymous" 
    referrerpolicy="no-referrer">
  </script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/ScrollTrigger.min.js"
    integrity="sha512-xxxx" 
    crossorigin="anonymous" 
    referrerpolicy="no-referrer">
  </script>
  <!-- Three.js -->
  <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r152/three.min.js"
    integrity="sha512-xxxx" 
    crossorigin="anonymous" 
    referrerpolicy="no-referrer">
  </script>

  <style>
    /* ==========================
       ベーススタイル
    ========================== */
    * {
      box-sizing: border-box;
    }
    html, body {
      margin: 0; 
      padding: 0;
      font-family: sans-serif;
      color: #333;
      background-color: #f9f9f9;
      line-height: 1.6;
      scroll-behavior: smooth; /* スムーズスクロール */
    }
    a {
      text-decoration: none;
      color: #0078d7;
      transition: color 0.2s ease;
    }
    a:hover {
      color: #005bb5;
    }
    h1, h2, h3, h4 {
      margin: 0;
      font-weight: normal;
    }
    p {
      margin: 0 0 1em;
    }
    ul {
      margin: 0; 
      padding: 0; 
      list-style: none;
    }
    img {
      max-width: 100%;
      display: block;
    }

    /* ==========================
       カラー変数
    ========================== */
    :root {
      --color-primary: #0078d7;
      --color-accent: #ffcc00;
      --color-bg: #f9f9f9;
      --color-white: #ffffff;
      --color-border: #eee;
      --color-text: #333;
    }

    /* ==========================
       全体のレイアウト用コンテナ
    ========================== */
    .container {
      width: 90%;
      max-width: 1200px;
      margin: 0 auto;
      padding: 60px 0;
    }

    /* ==========================
       ヘッダー
    ========================== */
    header {
      position: sticky;
      top: 0;
      z-index: 998;
      background: var(--color-white);
      box-shadow: 0 2px 4px rgba(0,0,0,0.1);
    }
    .header-inner {
      display: flex;
      align-items: center;
      justify-content: space-between;
      height: 60px;
      width: 90%;
      max-width: 1200px;
      margin: 0 auto;
    }
    .header-logo {
      font-size: 1.5rem;
      color: var(--color-primary);
      font-weight: bold;
    }
    nav ul {
      display: flex;
      gap: 30px;
    }
    nav a {
      color: var(--color-text);
      font-size: 0.95rem;
      font-weight: bold;
    }
    nav a:hover {
      color: var(--color-primary);
    }
    .hamburger {
      display: none;
      font-size: 1.5rem;
      cursor: pointer;
    }
    .mobile-nav {
      display: none;
      position: absolute;
      top: 60px; right: 0;
      width: 100%;
      max-width: 300px;
      background: var(--color-white);
      box-shadow: 0 2px 8px rgba(0,0,0,0.2);
      z-index: 999;
    }
    .mobile-nav ul {
      display: flex; 
      flex-direction: column;
      gap: 20px; 
      padding: 20px;
    }
    .mobile-nav a {
      font-size: 1rem; 
      color: var(--color-text);
    }

    /* ==========================
       Three.js 背景キャンバス
    ========================== */
    #three-bg {
      position: fixed;
      top: 0; left: 0;
      width: 100vw; height: 100vh;
      z-index: -1;
      background: #000; /* 読み込み時の仮色 */
    }

    /* ==========================
       ヒーローセクション
    ========================== */
    .hero {
      position: relative;
      min-height: 80vh;
      display: flex;
      align-items: center;
      justify-content: center;
      text-align: center;
      color: #fff;
      padding: 100px 20px;
      overflow: hidden;
    }
    .hero h1 {
      font-size: 2.5rem;
      margin-bottom: 20px;
      line-height: 1.2;
      transform: translateY(50px);
      opacity: 0;
    }
    .hero p {
      font-size: 1.1rem;
      margin-bottom: 30px;
      transform: translateY(50px);
      opacity: 0;
    }
    .hero .cta-btn {
      background: var(--color-accent);
      color: #333;
      padding: 15px 30px;
      border-radius: 6px;
      font-size: 1rem;
      font-weight: bold;
      transition: background-color 0.2s ease;
      transform: translateY(50px);
      opacity: 0;
    }
    .hero .cta-btn:hover {
      background: #e6b800;
    }

    /* ==========================
       Statsセクション (数値カウンター)
    ========================== */
    .stats-section {
      background: var(--color-bg);
      text-align: center;
      padding: 60px 20px;
    }
    .stats-section h2 {
      color: var(--color-primary);
      margin-bottom: 40px;
    }
    .stats-grid {
      display: flex; 
      flex-wrap: wrap; 
      gap: 40px; 
      justify-content: center;
    }
    .stat-item {
      flex: 1 1 200px;
      max-width: 250px;
      background: var(--color-white);
      border-radius: 8px;
      box-shadow: 0 2px 5px rgba(0,0,0,0.1);
      padding: 20px;
      transition: transform 0.3s ease;
    }
    .stat-item:hover { transform: translateY(-5px); }
    .stat-number {
      font-size: 2rem;
      color: var(--color-primary);
      font-weight: bold;
      margin-bottom: 10px;
    }
    .stat-label {
      font-size: 0.95rem;
      color: #666;
    }

    /* ==========================
       Testimonials (カルーセル)
    ========================== */
    .testimonials-section {
      background: var(--color-white);
      padding: 60px 20px;
    }
    .testimonials-section h2 {
      text-align: center;
      color: var(--color-primary);
      margin-bottom: 40px;
    }
    .carousel-container {
      max-width: 800px; 
      margin: 0 auto; 
      position: relative;
      overflow: hidden;
    }
    .carousel-track {
      display: flex; 
      transition: transform 0.5s ease;
    }
    .carousel-slide {
      min-width: 100%;
      background: var(--color-bg);
      border-radius: 8px;
      box-shadow: 0 2px 5px rgba(0,0,0,0.1);
      margin: 0 10px;
      padding: 30px;
      text-align: center;
    }
    .carousel-text {
      font-size: 1rem;
      font-style: italic;
      color: #555;
      margin-bottom: 10px;
    }
    .carousel-author {
      font-weight: bold;
      color: #333;
      font-size: 0.9rem;
    }
    .carousel-buttons {
      text-align: center;
      margin-top: 20px;
    }
    .carousel-btn {
      background: var(--color-primary);
      color: #fff;
      border: none;
      padding: 8px 12px;
      border-radius: 4px;
      margin: 0 5px;
      cursor: pointer;
      font-size: 1rem;
    }
    .carousel-btn:hover {
      background: #005bb5;
    }

    /* ==========================
       フォームセクション (リアルタイムバリデーション)
    ========================== */
    .form-section {
      background: var(--color-accent);
      padding: 60px 20px;
    }
    .form-section h2 {
      text-align: center;
      color: var(--color-primary);
      margin-bottom: 40px;
    }
    .signup-form {
      max-width: 600px;
      margin: 0 auto;
      background: var(--color-white);
      border-radius: 8px;
      box-shadow: 0 2px 8px rgba(0,0,0,0.1);
      padding: 30px;
    }
    .form-group {
      margin-bottom: 20px;
    }
    .form-group label {
      display: block;
      margin-bottom: 5px;
      font-weight: bold;
    }
    .form-group input {
      width: 100%;
      padding: 10px;
      border-radius: 4px;
      border: 1px solid #ccc;
      font-size: 1rem;
    }
    .error-message {
      font-size: 0.85rem;
      color: red;
      display: none;
    }
    .signup-submit {
      background: var(--color-primary);
      color: #fff;
      border: none;
      padding: 12px 24px;
      border-radius: 4px;
      font-size: 1rem;
      cursor: pointer;
      transition: background 0.2s ease;
    }
    .signup-submit:hover {
      background: #005bb5;
    }

    /* ==========================
       フッター
    ========================== */
    footer {
      background: #f0f0f0; 
      padding: 20px; 
      text-align: center;
      margin-top: 60px;
    }
    footer .footer-note {
      font-size: 0.9rem;
      color: #666;
      margin-top: 10px;
    }

    /* ==========================
       レスポンシブ
    ========================== */
    @media (max-width: 768px) {
      .header-inner {
        justify-content: space-between;
      }
      nav ul { display: none; }
      .hamburger { display: block; }

      .stats-grid { display: block; }
      .stat-item { margin: 0 auto 20px; }

      .carousel-slide { margin: 0 5px; }
    }
  </style>
</head>
<body>

  <!-- Three.js 背景キャンバス -->
  <canvas id="three-bg" aria-hidden="true"></canvas>

  <!-- ヘッダー -->
  <header>
    <div class="header-inner">
      <div class="header-logo">SUBS Management</div>
      <nav>
        <ul id="navLinks">
          <li><a href="#hero">Home</a></li>
          <li><a href="#stats">Stats</a></li>
          <li><a href="#testimonials">Reviews</a></li>
          <li><a href="#signup">SignUp</a></li>
        </ul>
      </nav>
      <!-- ハンバーガーメニュー -->
      <div class="hamburger" id="hamburger">&#9776;</div>
    </div>
    <!-- モバイル用ナビ -->
    <div class="mobile-nav" id="mobileNav">
      <ul>
        <li><a href="#hero">Home</a></li>
        <li><a href="#stats">Stats</a></li>
        <li><a href="#testimonials">Reviews</a></li>
        <li><a href="#signup">SignUp</a></li>
      </ul>
    </div>
  </header>

  <!-- ヒーローセクション -->
  <section class="hero" id="hero">
    <h1>サブスクを一括管理して、賢く節約</h1>
    <p>
      使っていないサブスクを一元化・解約サポート。<br>
      無駄な出費を削減し、あなたの家計をスリムにしませんか?
    </p>
    <a href="#signup" class="cta-btn">今すぐ始める</a>
  </section>

  <!-- Statsセクション (数値カウンター) -->
  <section class="stats-section" id="stats">
    <div class="container">
      <h2>導入実績・成果</h2>
      <div class="stats-grid">
        <div class="stat-item">
          <div class="stat-number" data-target="12000">0</div>
          <div class="stat-label">累計ユーザー</div>
        </div>
        <div class="stat-item">
          <div class="stat-number" data-target="9800">0</div>
          <div class="stat-label">解約サポート実行</div>
        </div>
        <div class="stat-item">
          <div class="stat-number" data-target="95">0</div>
          <div class="stat-label">満足度(%)</div>
        </div>
        <div class="stat-item">
          <div class="stat-number" data-target="1500000">0</div>
          <div class="stat-label">総節約額(円)</div>
        </div>
      </div>
    </div>
  </section>

  <!-- Testimonials (カルーセル) -->
  <section class="testimonials-section" id="testimonials">
    <h2>利用者の声</h2>
    <div class="carousel-container">
      <div class="carousel-track" id="carouselTrack">
        <!-- Slide1 -->
        <div class="carousel-slide">
          <p class="carousel-text">
            「いくつもの動画配信サービスを契約していたのですが、まとめて管理できるから凄く楽になりました!」
          </p>
          <p class="carousel-author">- 田中さん (30代 / 会社員)</p>
        </div>
        <!-- Slide2 -->
        <div class="carousel-slide">
          <p class="carousel-text">
            「解約リマインドが来るので本当に助かります。<br>
             先月だけで2,000円以上も節約できました!」
          </p>
          <p class="carousel-author">- 佐藤さん (40代 / 主婦)</p>
        </div>
        <!-- Slide3 -->
        <div class="carousel-slide">
          <p class="carousel-text">
            「支払い情報が一元化されるのは最高。<br>
             何よりUIがシンプルで使いやすいです!」
          </p>
          <p class="carousel-author">- 鈴木さん (20代 / フリーランス)</p>
        </div>
      </div>
      <div class="carousel-buttons">
        <button class="carousel-btn" id="prevSlide"><</button>
        <button class="carousel-btn" id="nextSlide">></button>
      </div>
    </div>
  </section>

  <!-- フォームセクション (リアルタイムバリデーション) -->
  <section class="form-section" id="signup">
    <div class="container">
      <h2>新規登録 (無料)</h2>
      <div class="signup-form" id="signupForm">
        <div class="form-group">
          <label for="signupEmail">メールアドレス</label>
          <input type="email" id="signupEmail" aria-required="true" placeholder="example@example.com">
          <p class="error-message" id="emailError">正しいメールアドレスを入力してください</p>
        </div>
        <div class="form-group">
          <label for="signupPassword">パスワード (8文字以上)</label>
          <input type="password" id="signupPassword" aria-required="true" placeholder="8文字以上で入力">
          <p class="error-message" id="passwordError">パスワードは8文字以上で入力してください</p>
        </div>
        <button class="signup-submit" id="signupSubmitBtn">登録</button>
      </div>
    </div>
  </section>

  <!-- フッター -->
  <footer>
    <p class="footer-note">
      &copy; 2025 SUBS Management. All rights reserved.
    </p>
  </footer>

  <script>
    /*************************************************************
     * 1. Three.js 背景 
     *************************************************************/
    let scene, camera, renderer;
    const canvas = document.getElementById('three-bg');
    let objects = [];

    function initThreeBG() {
      scene = new THREE.Scene();
      camera = new THREE.PerspectiveCamera(
        75,
        window.innerWidth / window.innerHeight,
        0.1,
        1000
      );
      camera.position.z = 250;

      renderer = new THREE.WebGLRenderer({ canvas: canvas, alpha: true });
      renderer.setSize(window.innerWidth, window.innerHeight);

      // ライト
      const ambient = new THREE.AmbientLight(0xffffff, 0.7);
      scene.add(ambient);
      const pointLight = new THREE.PointLight(0xffffff, 0.8);
      pointLight.position.set(50, 50, 50);
      scene.add(pointLight);

      // いくつかのオブジェクトをランダム配置
      const geometryTypes = [
        new THREE.TorusGeometry(10, 3, 16, 100),
        new THREE.SphereGeometry(8, 32, 32),
        new THREE.BoxGeometry(12, 12, 12)
      ];

      for(let i=0; i<30; i++){
        const geo = geometryTypes[Math.floor(Math.random() * geometryTypes.length)];
        const material = new THREE.MeshStandardMaterial({
          color: 0xffffff
        });
        // 色相をランダム化
        material.color.setHSL(Math.random(), 0.5, 0.5);

        const mesh = new THREE.Mesh(geo, material);
        mesh.position.x = (Math.random() - 0.5) * 400;
        mesh.position.y = (Math.random() - 0.5) * 400;
        mesh.position.z = (Math.random() - 0.5) * 400;
        mesh.rotation.x = Math.random() * Math.PI;
        mesh.rotation.y = Math.random() * Math.PI;
        scene.add(mesh);
        objects.push(mesh);
      }
    }

    function animateThreeBG() {
      requestAnimationFrame(animateThreeBG);
      objects.forEach(obj => {
        obj.rotation.x += 0.001;
        obj.rotation.y += 0.001;
      });
      camera.position.z += Math.sin(Date.now() * 0.0005) * 0.05; // カメラ揺らし
      renderer.render(scene, camera);
    }

    /*************************************************************
     * 2. ハンバーガーメニュー
     *************************************************************/
    const hamburger = document.getElementById('hamburger');
    const mobileNav = document.getElementById('mobileNav');
    let navOpen = false;
    hamburger.addEventListener('click', () => {
      navOpen = !navOpen;
      mobileNav.style.display = navOpen ? 'block' : 'none';
    });

    /*************************************************************
     * 3. GSAP + ScrollTrigger
     *    - Heroセクションのタイトルやボタンをアニメ
     *    - 他のセクションもスクロールでフェードイン
     *************************************************************/
    window.addEventListener('DOMContentLoaded', () => {
      gsap.registerPlugin(ScrollTrigger);

      // Heroアニメ
      gsap.to('.hero h1', {
        duration: 1, y: 0, opacity: 1, ease: 'power2.out'
      });
      gsap.to('.hero p', {
        duration: 1, y: 0, opacity: 1, delay: 0.2, ease: 'power2.out'
      });
      gsap.to('.hero .cta-btn', {
        duration: 1, y: 0, opacity: 1, delay: 0.4, ease: 'power2.out'
      });

      // Statsセクション
      gsap.from('#stats .stats-grid', {
        scrollTrigger: {
          trigger: '#stats',
          start: 'top 80%',
        },
        y: 50, opacity: 0, duration: 1, ease: 'power2.out'
      });

      // Testimonials
      gsap.from('#testimonials .carousel-container', {
        scrollTrigger: {
          trigger: '#testimonials',
          start: 'top 80%',
        },
        y: 50, opacity: 0, duration: 1, ease: 'power2.out'
      });

      // SignUpフォーム
      gsap.from('#signupForm', {
        scrollTrigger: {
          trigger: '#signupForm',
          start: 'top 80%',
        },
        y: 50, opacity: 0, duration: 1, ease: 'power2.out'
      });
    });

    /*************************************************************
     * 4. Stats カウンター (Intersection Observer)
     *************************************************************/
    const statNumbers = document.querySelectorAll('.stat-number');
    const statsObserver = new IntersectionObserver((entries, obs) => {
      entries.forEach(entry => {
        if(entry.isIntersecting) {
          const el = entry.target;
          const targetVal = parseInt(el.getAttribute('data-target'), 10);
          let currentVal = 0;
          const increment = Math.ceil(targetVal / 100);

          const timer = setInterval(() => {
            currentVal += increment;
            if(currentVal >= targetVal) {
              currentVal = targetVal;
              clearInterval(timer);
            }
            el.textContent = currentVal.toLocaleString();
          }, 20);

          obs.unobserve(el);
        }
      });
    }, { threshold: 0.5 });

    statNumbers.forEach(num => statsObserver.observe(num));

    /*************************************************************
     * 5. Testimonials カルーセル
     *************************************************************/
    const carouselTrack = document.getElementById('carouselTrack');
    const prevSlideBtn = document.getElementById('prevSlide');
    const nextSlideBtn = document.getElementById('nextSlide');
    let currentSlideIndex = 0;
    const slides = Array.from(document.querySelectorAll('.carousel-slide'));

    function updateCarousel() {
      carouselTrack.style.transform = `translateX(${-100 * currentSlideIndex}%)`;
    }

    prevSlideBtn.addEventListener('click', () => {
      currentSlideIndex = (currentSlideIndex === 0) ? slides.length - 1 : currentSlideIndex - 1;
      updateCarousel();
    });
    nextSlideBtn.addEventListener('click', () => {
      currentSlideIndex = (currentSlideIndex === slides.length - 1) ? 0 : currentSlideIndex + 1;
      updateCarousel();
    });

    /*************************************************************
     * 6. リアルタイム・フォームバリデーション
     *************************************************************/
    const signupEmail = document.getElementById('signupEmail');
    const signupPassword = document.getElementById('signupPassword');
    const emailError = document.getElementById('emailError');
    const passwordError = document.getElementById('passwordError');
    const signupSubmitBtn = document.getElementById('signupSubmitBtn');

    function validateEmail(email) {
      return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
    }
    function validatePassword(pass) {
      return pass.length >= 8;
    }

    signupEmail.addEventListener('input', () => {
      if(!validateEmail(signupEmail.value.trim())) {
        emailError.style.display = 'block';
      } else {
        emailError.style.display = 'none';
      }
    });
    signupPassword.addEventListener('input', () => {
      if(!validatePassword(signupPassword.value.trim())) {
        passwordError.style.display = 'block';
      } else {
        passwordError.style.display = 'none';
      }
    });

    signupSubmitBtn.addEventListener('click', () => {
      const emailVal = signupEmail.value.trim();
      const passVal = signupPassword.value.trim();

      let hasError = false;
      if(!validateEmail(emailVal)) {
        emailError.style.display = 'block';
        hasError = true;
      }
      if(!validatePassword(passVal)) {
        passwordError.style.display = 'block';
        hasError = true;
      }
      if(hasError) return;

      alert('登録ありがとうございました!');
      // フォームリセット例
      signupEmail.value = '';
      signupPassword.value = '';
      emailError.style.display = 'none';
      passwordError.style.display = 'none';
    });

    /*************************************************************
     * 7. 画面リサイズ時のThree.js再設定
     *************************************************************/
    window.addEventListener('resize', () => {
      renderer.setSize(window.innerWidth, window.innerHeight);
      camera.aspect = window.innerWidth / window.innerHeight;
      camera.updateProjectionMatrix();
    });

    /*************************************************************
     * 8. ページ読み込み完了時に Three.js 初期化 & アニメ開始
     *************************************************************/
    window.addEventListener('load', () => {
      initThreeBG();
      animateThreeBG();
    });
  </script>
</body>
</html>

投稿者: chosuke

趣味はゲームやアニメや漫画などです

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です