<!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">☰</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">
© 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>