From 653408c9a367f073d678dd7aebeacba7a69377d3 Mon Sep 17 00:00:00 2001 From: yosuke Date: Tue, 19 Aug 2025 20:06:40 +0900 Subject: [PATCH 01/31] add components --- src/components/organisms/AwardsMedia.tsx | 373 +++++++++++++ src/components/organisms/CaseStudies.tsx | 394 ++++++++++++++ src/components/organisms/Faq.tsx | 375 +++++++++++++ src/components/organisms/Features.tsx | 304 +++++++++++ src/components/organisms/Footer.tsx | 415 ++++++++++++++ src/components/organisms/GettingStarted.tsx | 447 +++++++++++++++ src/components/organisms/HeroSection.tsx | 13 + src/components/organisms/HowItWorks.tsx | 538 +++++++++++++++++++ src/components/organisms/InfiniteLoop.tsx | 316 +++++++++++ src/components/organisms/Pricing.tsx | 33 ++ src/components/organisms/ProblemSolution.tsx | 320 +++++++++++ src/components/organisms/SecurityStack.tsx | 390 ++++++++++++++ src/components/organisms/TrackRecord.tsx | 339 ++++++++++++ src/components/organisms/UseCaseLogos.tsx | 40 ++ src/components/organisms/UseCases.tsx | 501 +++++++++++++++++ 15 files changed, 4798 insertions(+) create mode 100644 src/components/organisms/AwardsMedia.tsx create mode 100644 src/components/organisms/CaseStudies.tsx create mode 100644 src/components/organisms/Faq.tsx create mode 100644 src/components/organisms/Features.tsx create mode 100644 src/components/organisms/Footer.tsx create mode 100644 src/components/organisms/GettingStarted.tsx create mode 100644 src/components/organisms/HeroSection.tsx create mode 100644 src/components/organisms/HowItWorks.tsx create mode 100644 src/components/organisms/InfiniteLoop.tsx create mode 100644 src/components/organisms/Pricing.tsx create mode 100644 src/components/organisms/ProblemSolution.tsx create mode 100644 src/components/organisms/SecurityStack.tsx create mode 100644 src/components/organisms/TrackRecord.tsx create mode 100644 src/components/organisms/UseCaseLogos.tsx create mode 100644 src/components/organisms/UseCases.tsx diff --git a/src/components/organisms/AwardsMedia.tsx b/src/components/organisms/AwardsMedia.tsx new file mode 100644 index 0000000..6314341 --- /dev/null +++ b/src/components/organisms/AwardsMedia.tsx @@ -0,0 +1,373 @@ +'use client'; + +import React, { useState, useEffect } from 'react'; + +export default function AwardsMedia() { + const [isVisible, setIsVisible] = useState(false); + const [activeItem, setActiveItem] = useState(null); + + const awards = [ + { + title: 'ETH Tokyo 2024 Finalist', + subtitle: '(Toban プロトタイプ)', + date: '2024年4月', + type: 'award', + icon: '🏆', + color: '#ff6b6b', + links: [ + { url: 'https://www.linkedin.com/posts/haruki-kondo-517073204_selected-as-a-finalist-for-eth-tokyo-2024-activity-7233684980688642048-GJIh?utm_source=chatgpt.com', text: 'linkedin.com' } + ] + }, + { + title: 'Agentic Ethereum 2025', + subtitle: 'ハッカソン参加', + date: '2025年1月', + type: 'event', + icon: '🚀', + color: '#4ecdc4', + links: [ + { url: 'https://ethglobal.com/events/agents?utm_source=chatgpt.com', text: 'ethglobal.com' } + ] + }, + { + title: 'Fracton Incubation 2024', + subtitle: '採択', + date: '2024年6月', + type: 'program', + icon: '🌱', + color: '#45b7d1', + links: [ + { url: 'https://technode.global/2023/03/22/fracton-ventures-shaping-the-web3-landscape-through-strategic-incubation-and-global-collaboration-qa/?utm_source=chatgpt.com', text: 'technode.global' } + ] + }, + { + title: 'ETHTaipei 2025', + subtitle: 'スピーカー登壇', + date: '2025年1月', + type: 'speaking', + icon: '🎤', + color: '#8b5cf6', + links: [ + { url: 'https://www.dlnews.com/research/internal/ethtaipei-2025-partners-with-ethglobal-vitalik-buterin/?utm_source=chatgpt.com', text: 'ethtaipei.org' } + ] + }, + { + title: '週刊番組 "weekly gm" 出演', + subtitle: '(Joichi Ito YouTube)', + date: '2024年12月', + type: 'media', + icon: '📺', + color: '#f59e0b', + links: [ + { url: 'https://dalab.xyz/en/project/weekly-gm/?utm_source=chatgpt.com', text: 'dalab.xyz' } + ] + } + ]; + + useEffect(() => { + const observer = new IntersectionObserver( + ([entry]) => { + if (entry.isIntersecting) { + setIsVisible(true); + } + }, + { threshold: 0.3 } + ); + + const element = document.getElementById('awards-media'); + if (element) { + observer.observe(element); + } + + return () => { + if (element) { + observer.unobserve(element); + } + }; + }, []); + + const getTypeLabel = (type: string) => { + const typeMap: { [key: string]: string } = { + award: '受賞', + event: 'イベント', + program: 'プログラム', + speaking: '講演', + media: 'メディア' + }; + return typeMap[type] || type; + }; + + return ( +
+
+
+

+ 受賞 & メディア掲載 +

+

+ Tobanプロジェクトの主要な実績とマイルストーン +

+
+ +
+ {/* タイムライン軸 */} +
+ + {awards.map((award, index) => ( +
setActiveItem(index)} + onMouseLeave={() => setActiveItem(null)} + onTouchStart={() => setActiveItem(index)} + onTouchEnd={() => setTimeout(() => setActiveItem(null), 2000)} + > + {/* タイムライン ドット */} +
+ {award.icon} +
+ + {/* コンテンツカード */} +
+ {/* ヘッダー */} +
+
+ {getTypeLabel(award.type)} +
+
{award.date}
+
+ + {/* タイトル */} +

+ {award.title} +

+

{award.subtitle}

+ + {/* リンク */} + +
+
+ ))} +
+
+
+ ); +} + +const sectionStyle: React.CSSProperties = { + padding: '80px 20px', + background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)', + color: 'white', + position: 'relative', + overflow: 'hidden' +}; + +const containerStyle: React.CSSProperties = { + maxWidth: '1000px', + margin: '0 auto', + width: '100%' +}; + +const headerStyle: React.CSSProperties = { + textAlign: 'center', + marginBottom: '60px' +}; + +const titleStyle: React.CSSProperties = { + fontSize: 'clamp(2rem, 4vw, 2.8rem)', + fontWeight: '700', + margin: '0 0 15px 0', + lineHeight: 1.2 +}; + +const highlightStyle: React.CSSProperties = { + background: 'linear-gradient(120deg, #ff6b6b, #4ecdc4)', + WebkitBackgroundClip: 'text', + WebkitTextFillColor: 'transparent', + backgroundClip: 'text' +}; + +const subtitleStyle: React.CSSProperties = { + fontSize: '1.1rem', + color: 'rgba(255, 255, 255, 0.8)', + margin: 0, + fontWeight: '400' +}; + +const timelineStyle: React.CSSProperties = { + position: 'relative', + paddingLeft: '60px' +}; + +const timelineLineStyle: React.CSSProperties = { + position: 'absolute', + left: '30px', + top: '40px', + bottom: '40px', + width: '4px', + background: 'linear-gradient(180deg, rgba(255, 255, 255, 0.3), rgba(255, 255, 255, 0.1))', + borderRadius: '2px' +}; + +const timelineItemStyle: React.CSSProperties = { + position: 'relative', + marginBottom: '40px', + display: 'flex', + alignItems: 'flex-start', + opacity: 0, + transform: 'translateX(-50px)', + transition: 'all 0.6s cubic-bezier(0.4, 0, 0.2, 1)' +}; + +const timelineDotStyle: React.CSSProperties = { + position: 'absolute', + left: '-45px', + top: '20px', + width: '30px', + height: '30px', + borderRadius: '50%', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + transition: 'all 0.3s ease', + zIndex: 2 +}; + +const dotIconStyle: React.CSSProperties = { + fontSize: '0.9rem', + filter: 'drop-shadow(0 2px 4px rgba(0, 0, 0, 0.3))' +}; + +const cardStyle: React.CSSProperties = { + backgroundColor: 'white', + borderRadius: '16px', + padding: '30px', + borderLeft: '4px solid', + boxShadow: '0 10px 40px rgba(0, 0, 0, 0.1)', + transition: 'all 0.4s cubic-bezier(0.4, 0, 0.2, 1)', + cursor: 'pointer', + width: '100%', + marginLeft: '20px' +}; + +const cardHeaderStyle: React.CSSProperties = { + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center', + marginBottom: '20px' +}; + +const typeBadgeStyle: React.CSSProperties = { + padding: '6px 12px', + borderRadius: '12px', + fontSize: '0.8rem', + fontWeight: '600', + textTransform: 'uppercase', + letterSpacing: '0.05em' +}; + +const dateStyle: React.CSSProperties = { + fontSize: '0.9rem', + color: '#64748b', + fontWeight: '500' +}; + +const awardTitleStyle: React.CSSProperties = { + fontSize: '1.3rem', + fontWeight: '600', + margin: '0 0 8px 0', + lineHeight: 1.3, + transition: 'color 0.3s ease' +}; + +const awardSubtitleStyle: React.CSSProperties = { + fontSize: '1rem', + color: '#64748b', + margin: '0 0 20px 0', + lineHeight: 1.4 +}; + +const linksContainerStyle: React.CSSProperties = { + display: 'flex', + gap: '12px', + flexWrap: 'wrap' +}; + +const linkButtonStyle: React.CSSProperties = { + display: 'inline-flex', + alignItems: 'center', + gap: '8px', + padding: '10px 16px', + borderRadius: '8px', + border: '2px solid', + fontSize: '0.9rem', + fontWeight: '600', + textDecoration: 'none', + transition: 'all 0.3s ease', + backgroundColor: 'transparent' +}; + +const linkIconStyle: React.CSSProperties = { + fontSize: '0.8rem' +}; \ No newline at end of file diff --git a/src/components/organisms/CaseStudies.tsx b/src/components/organisms/CaseStudies.tsx new file mode 100644 index 0000000..8d3c3e5 --- /dev/null +++ b/src/components/organisms/CaseStudies.tsx @@ -0,0 +1,394 @@ +'use client'; + +import React, { useState, useEffect } from 'react'; +import Image from 'next/image'; + +export default function CaseStudies() { + const [isVisible, setIsVisible] = useState(false); + const [hoveredCard, setHoveredCard] = useState(null); + + const cases = [ + { + name: 'Comoris', + logoSrc: '/logo/comoris-logo.png', + description: '沖縄・コミュニティ DAO。子育て支援トークン導入', + link: 'Case Study →', + linkUrl: '#', + color: '#ff6b6b', + category: 'コミュニティDAO', + status: 'active' + }, + { + name: '塩尻DAO', + logoSrc: '/logo/shiojiridao-kogo.webp', + description: '長野県・地域活性 DAO。農業バウンティを自動分配', + link: 'Interview →', + linkUrl: '#', + color: '#4ecdc4', + category: '地域DAO', + status: 'active' + }, + { + name: 'ento', + logoSrc: null, + description: 'オープンソース気象データ連携', + link: 'Coming soon', + linkUrl: null, + color: '#45b7d1', + category: 'オープンソース', + status: 'coming' + }, + { + name: 'ETH Tokyo', + logoSrc: '/logo/ETHTokyoLogoBlack.png', + description: 'ハッカソン採択プロジェクト', + link: 'Highlights →', + linkUrl: '#', + color: '#8b5cf6', + category: 'ハッカソン', + status: 'active' + }, + { + name: 'Fracton', + logoSrc: '/logo/fracton-logo.png', + description: 'インキュベーション支援', + link: 'Article →', + linkUrl: '#', + color: '#f59e0b', + category: 'インキュベーター', + status: 'active' + }, + { + name: 'Localcoop', + logoSrc: '/logo/localcoop-logo.png', + description: '助け合いプラットフォーム', + link: 'Coming soon', + linkUrl: null, + color: '#10b981', + category: 'プラットフォーム', + status: 'coming' + } + ]; + + useEffect(() => { + const observer = new IntersectionObserver( + ([entry]) => { + if (entry.isIntersecting) { + setIsVisible(true); + } + }, + { threshold: 0.1, rootMargin: '50px' } + ); + + const element = document.getElementById('case-studies'); + if (element) { + observer.observe(element); + } + + return () => { + if (element) { + observer.unobserve(element); + } + }; + }, []); + + return ( +
+
+
+

+ 導入事例 +

+

+ 実際にTobanを活用している組織とプロジェクト +

+
+ +
+ {cases.map((caseItem, index) => ( +
setHoveredCard(index)} + onMouseLeave={() => setHoveredCard(null)} + > + {/* ステータスバッジ */} +
+ {caseItem.status === 'active' ? '運用中' : '近日公開'} +
+ + {/* カテゴリー */} +
+ {caseItem.category} +
+ + {/* ロゴセクション */} +
+ {caseItem.logoSrc ? ( + {`${caseItem.name} + ) : ( +
+ {caseItem.name[0]} +
+ )} +
+ + {/* コンテンツ */} +
+

+ {caseItem.name} +

+

+ {caseItem.description} +

+
+ + {/* フッター */} +
+ {caseItem.linkUrl ? ( + + {caseItem.link} + + ) : ( + + {caseItem.link} + + )} +
+ + {/* ホバー時のアクセント */} + {hoveredCard === index && ( +
+ )} +
+ ))} +
+
+
+ ); +} + +const sectionStyle: React.CSSProperties = { + padding: '80px 20px', + background: 'linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%)', + minHeight: '100vh', + display: 'flex', + alignItems: 'center' +}; + +const containerStyle: React.CSSProperties = { + maxWidth: '1200px', + margin: '0 auto', + width: '100%' +}; + +const headerStyle: React.CSSProperties = { + textAlign: 'center', + marginBottom: '60px' +}; + +const titleStyle: React.CSSProperties = { + fontSize: 'clamp(2rem, 4vw, 2.5rem)', + fontWeight: '700', + color: '#1e293b', + margin: '0 0 15px 0', + lineHeight: 1.2 +}; + +const highlightStyle: React.CSSProperties = { + background: 'linear-gradient(120deg, #ff6b6b, #4ecdc4)', + WebkitBackgroundClip: 'text', + WebkitTextFillColor: 'transparent', + backgroundClip: 'text' +}; + +const subtitleStyle: React.CSSProperties = { + fontSize: '1.1rem', + color: '#64748b', + margin: 0, + fontWeight: '400' +}; + +const gridStyle: React.CSSProperties = { + display: 'grid', + gridTemplateColumns: 'repeat(auto-fit, minmax(280px, 1fr))', + gap: 'clamp(20px, 4vw, 30px)', + maxWidth: '1100px', + margin: '0 auto' +}; + +const cardStyle: React.CSSProperties = { + padding: 'clamp(20px, 4vw, 30px)', + borderRadius: '20px', + backgroundColor: 'white', + border: '1px solid #e2e8f0', + boxShadow: '0 10px 40px rgba(0, 0, 0, 0.1)', + transition: 'all 0.4s cubic-bezier(0.4, 0, 0.2, 1)', + cursor: 'pointer', + position: 'relative', + overflow: 'hidden', + opacity: 0, + transform: 'translateY(20px)' +}; + +const statusBadgeStyle: React.CSSProperties = { + position: 'absolute', + top: '20px', + right: '20px', + padding: '4px 12px', + borderRadius: '12px', + fontSize: '0.75rem', + fontWeight: '600', + textTransform: 'uppercase', + letterSpacing: '0.05em' +}; + +const categoryStyle: React.CSSProperties = { + display: 'inline-block', + padding: '6px 12px', + borderRadius: '8px', + fontSize: '0.8rem', + fontWeight: '600', + marginBottom: '20px', + letterSpacing: '0.05em' +}; + +const logoSectionStyle: React.CSSProperties = { + width: '100px', + height: '100px', + borderRadius: '16px', + border: '2px solid', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + marginBottom: '25px', + marginLeft: 'auto', + marginRight: 'auto', + transition: 'border-color 0.3s ease' +}; + +const logoPlaceholderStyle: React.CSSProperties = { + width: '60px', + height: '60px', + borderRadius: '12px', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + fontWeight: 'bold' +}; + +const logoTextStyle: React.CSSProperties = { + fontSize: '1.5rem', + fontWeight: '700' +}; + +const contentSectionStyle: React.CSSProperties = { + marginBottom: '25px' +}; + +const nameStyle: React.CSSProperties = { + fontSize: '1.4rem', + fontWeight: '600', + margin: '0 0 15px 0', + lineHeight: 1.3, + transition: 'color 0.3s ease' +}; + +const descriptionStyle: React.CSSProperties = { + fontSize: '1rem', + lineHeight: 1.6, + margin: 0, + transition: 'color 0.3s ease' +}; + +const footerStyle: React.CSSProperties = { + paddingTop: '20px', + borderTop: '1px solid', + transition: 'border-color 0.3s ease' +}; + +const linkStyle: React.CSSProperties = { + display: 'inline-block', + padding: '10px 20px', + borderRadius: '8px', + border: '2px solid', + fontSize: '0.9rem', + fontWeight: '600', + textDecoration: 'none', + transition: 'all 0.3s ease', + backgroundColor: 'transparent' +}; + +const comingSoonStyle: React.CSSProperties = { + display: 'inline-block', + padding: '10px 20px', + borderRadius: '8px', + fontSize: '0.9rem', + fontWeight: '600', + fontStyle: 'italic' +}; + +const accentLineStyle: React.CSSProperties = { + position: 'absolute', + top: 0, + left: 0, + width: '4px', + height: '100%', + transition: 'all 0.3s ease' +}; \ No newline at end of file diff --git a/src/components/organisms/Faq.tsx b/src/components/organisms/Faq.tsx new file mode 100644 index 0000000..a69c379 --- /dev/null +++ b/src/components/organisms/Faq.tsx @@ -0,0 +1,375 @@ +'use client'; + +import React, { useState, useEffect } from 'react'; + +export default function Faq() { + const [isVisible, setIsVisible] = useState(false); + const [openIndex, setOpenIndex] = useState(0); // 最初の質問を開いておく + + const faqs = [ + { + question: 'Web3の知識がなくても使えますか?', + answer: 'はい、ウォレットの作成手順も含めてサポートしています。', + detailedAnswer: 'Tobanは初心者でも簡単に使えるよう設計されています。ウォレットの作成から基本的な使い方まで、ステップバイステップのガイドを提供しており、Web3の専門知識がなくても安心してご利用いただけます。', + icon: '🔰', + color: '#3b82f6' + }, + { + question: '現在のツールと併用できますか?', + answer: 'Discord や GitHub との連携も可能です。', + detailedAnswer: 'Tobanは既存のワークフローを妨げることなく、DiscordボットやGitHub連携、Slack統合など、多様なツールとの連携機能を提供しています。APIも公開しているため、カスタム統合も可能です。', + icon: '🔗', + color: '#10b981' + }, + { + question: '税務処理用の出力は?', + answer: 'ダッシュボードからCSV出力が可能です。', + detailedAnswer: 'すべての取引履歴と分配記録は、税務申告に必要な形式でCSV出力できます。日本の税制に対応した項目分けも行っており、税理士さんとの連携もスムーズです。', + icon: '📊', + color: '#f59e0b' + }, + { + question: 'セキュリティはどのように確保されていますか?', + answer: 'すべてオープンソース、監査済みスマートコントラクトを使用。', + detailedAnswer: 'Tobanのコードはすべてオープンソースで公開されており、使用するスマートコントラクトは第三者機関による監査を受けています。また、秘密鍵の管理はユーザー自身が行うため、中央集権的なリスクがありません。', + icon: '🔒', + color: '#8b5cf6' + }, + { + question: '利用料金はかかりますか?', + answer: 'プラットフォーム利用は無料、ブロックチェーン手数料のみ。', + detailedAnswer: 'Toban自体の利用料金は一切かかりません。ただし、ブロックチェーン上での取引には少額のガス代(手数料)が発生します。これは一般的に数十円から数百円程度です。', + icon: '💰', + color: '#ef4444' + }, + { + question: 'コミュニティサポートはありますか?', + answer: 'Discordコミュニティで24/7サポートを提供。', + detailedAnswer: '活発なDiscordコミュニティがあり、ユーザー同士の助け合いや開発チームからの直接サポートを受けられます。また、定期的なオンラインワークショップや勉強会も開催しています。', + icon: '💬', + color: '#06b6d4' + } + ]; + + useEffect(() => { + const observer = new IntersectionObserver( + ([entry]) => { + if (entry.isIntersecting) { + setIsVisible(true); + } + }, + { threshold: 0.3 } + ); + + const element = document.getElementById('faq'); + if (element) { + observer.observe(element); + } + + return () => { + if (element) { + observer.unobserve(element); + } + }; + }, []); + + const toggleFaq = (index: number) => { + setOpenIndex(openIndex === index ? null : index); + }; + + return ( +
+
+
+

+ よくある質問 +

+

+ Tobanについて寄せられる質問とその回答をまとめました +

+
+ +
+ {faqs.map((faq, index) => ( +
+ + +
+
+

+ A. + {faq.answer} +

+

+ {faq.detailedAnswer} +

+
+
+
+ ))} +
+ + {/* 追加サポート */} +
+

他にご質問がありますか?

+

+ お気軽にお問い合わせください。コミュニティでお待ちしています。 +

+
+ + +
+
+
+
+ ); +} + +const sectionStyle: React.CSSProperties = { + padding: '80px 20px', + background: 'linear-gradient(135deg, #1e293b 0%, #334155 100%)', + color: 'white', + minHeight: '100vh', + display: 'flex', + alignItems: 'center' +}; + +const containerStyle: React.CSSProperties = { + maxWidth: '800px', + margin: '0 auto', + width: '100%' +}; + +const headerStyle: React.CSSProperties = { + textAlign: 'center', + marginBottom: '60px' +}; + +const titleStyle: React.CSSProperties = { + fontSize: 'clamp(2rem, 4vw, 2.8rem)', + fontWeight: '700', + margin: '0 0 15px 0', + lineHeight: 1.2 +}; + +const highlightStyle: React.CSSProperties = { + background: 'linear-gradient(120deg, #3b82f6, #10b981)', + WebkitBackgroundClip: 'text', + WebkitTextFillColor: 'transparent', + backgroundClip: 'text' +}; + +const subtitleStyle: React.CSSProperties = { + fontSize: '1.1rem', + color: 'rgba(255, 255, 255, 0.8)', + margin: 0, + fontWeight: '400' +}; + +const faqContainerStyle: React.CSSProperties = { + marginBottom: '60px' +}; + +const faqItemStyle: React.CSSProperties = { + marginBottom: '20px', + borderRadius: '16px', + overflow: 'hidden', + backgroundColor: 'white', + boxShadow: '0 10px 40px rgba(0, 0, 0, 0.1)', + opacity: 0, + transform: 'translateY(30px)', + transition: 'all 0.4s cubic-bezier(0.4, 0, 0.2, 1)' +}; + +const questionButtonStyle: React.CSSProperties = { + width: '100%', + padding: '25px 30px', + border: '2px solid', + background: 'white', + cursor: 'pointer', + transition: 'all 0.3s ease', + borderRadius: 0 +}; + +const questionHeaderStyle: React.CSSProperties = { + display: 'flex', + alignItems: 'center', + gap: '20px' +}; + +const iconWrapperStyle: React.CSSProperties = { + width: '50px', + height: '50px', + borderRadius: '12px', + border: '2px solid', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + flexShrink: 0 +}; + +const iconStyle: React.CSSProperties = { + fontSize: '1.5rem' +}; + +const questionTextStyle: React.CSSProperties = { + fontSize: '1.1rem', + fontWeight: '600', + textAlign: 'left', + flex: 1, + transition: 'color 0.3s ease' +}; + +const chevronStyle: React.CSSProperties = { + fontSize: '0.8rem', + transition: 'transform 0.3s ease, color 0.3s ease', + flexShrink: 0 +}; + +const answerContainerStyle: React.CSSProperties = { + overflow: 'hidden', + transition: 'all 0.4s cubic-bezier(0.4, 0, 0.2, 1)', + backgroundColor: '#f8fafc' +}; + +const answerContentStyle: React.CSSProperties = { + borderTop: '1px solid #e2e8f0' +}; + +const shortAnswerStyle: React.CSSProperties = { + fontSize: '1rem', + color: '#1e293b', + margin: '0 0 15px 0', + fontWeight: '600', + display: 'flex', + alignItems: 'flex-start', + gap: '10px' +}; + +const answerIconStyle: React.CSSProperties = { + fontSize: '1rem', + fontWeight: 'bold', + flexShrink: 0 +}; + +const detailedAnswerStyle: React.CSSProperties = { + fontSize: '0.95rem', + color: '#64748b', + margin: 0, + lineHeight: 1.6 +}; + +const supportSectionStyle: React.CSSProperties = { + textAlign: 'center', + padding: '50px 30px', + borderRadius: '20px', + background: 'rgba(255, 255, 255, 0.05)', + border: '1px solid rgba(255, 255, 255, 0.1)', + backdropFilter: 'blur(10px)' +}; + +const supportTitleStyle: React.CSSProperties = { + fontSize: '1.5rem', + fontWeight: '600', + margin: '0 0 15px 0', + color: 'white' +}; + +const supportDescriptionStyle: React.CSSProperties = { + fontSize: '1rem', + color: 'rgba(255, 255, 255, 0.8)', + margin: '0 0 30px 0', + lineHeight: 1.5 +}; + +const supportButtonsStyle: React.CSSProperties = { + display: 'flex', + gap: '20px', + justifyContent: 'center', + flexWrap: 'wrap' +}; + +const primarySupportButtonStyle: React.CSSProperties = { + display: 'inline-flex', + alignItems: 'center', + gap: '10px', + padding: '15px 30px', + borderRadius: '12px', + border: 'none', + fontSize: '1rem', + fontWeight: '600', + backgroundColor: '#3b82f6', + color: 'white', + cursor: 'pointer', + transition: 'all 0.3s ease', + boxShadow: '0 4px 20px rgba(59, 130, 246, 0.3)' +}; + +const secondarySupportButtonStyle: React.CSSProperties = { + padding: '15px 30px', + borderRadius: '12px', + border: '2px solid rgba(255, 255, 255, 0.3)', + fontSize: '1rem', + fontWeight: '600', + backgroundColor: 'transparent', + color: 'white', + cursor: 'pointer', + transition: 'all 0.3s ease' +}; + +const supportButtonIconStyle: React.CSSProperties = { + fontSize: '1.1rem' +}; \ No newline at end of file diff --git a/src/components/organisms/Features.tsx b/src/components/organisms/Features.tsx new file mode 100644 index 0000000..3f0cc4d --- /dev/null +++ b/src/components/organisms/Features.tsx @@ -0,0 +1,304 @@ +'use client'; + +import React, { useState, useEffect } from 'react'; + +export default function Features() { + const [isVisible, setIsVisible] = useState(false); + const [activeFeature, setActiveFeature] = useState(null); + + const features = [ + { + title: '即時貢献記録', + text: '役割NFTとアシストクレジットで瞬時にログ。', + icon: '⚡', + color: '#ff6b6b', + detailColor: '#ff5252' + }, + { + title: '自動報酬分配', + text: '複雑な計算もスマートコントラクトが実行。', + icon: '🤖', + color: '#4ecdc4', + detailColor: '#26c6da' + }, + { + title: '履歴ダッシュボード', + text: '過去の活動と分配履歴を一元管理。', + icon: '📊', + color: '#45b7d1', + detailColor: '#42a5f5' + }, + { + title: 'API連携', + text: '既存ツールとの連携も簡単。', + icon: '🔗', + color: '#96c93d', + detailColor: '#8bc34a' + } + ]; + + useEffect(() => { + const observer = new IntersectionObserver( + ([entry]) => { + if (entry.isIntersecting) { + setIsVisible(true); + } + }, + { threshold: 0.3 } + ); + + const element = document.getElementById('features'); + if (element) { + observer.observe(element); + } + + return () => { + if (element) { + observer.unobserve(element); + } + }; + }, []); + + return ( +
+
+
+

+ 主な機能ハイライト +

+

+ プロジェクト運営を革新する4つの核心機能 +

+
+ +
+ {features.map((feature, index) => ( +
setActiveFeature(index)} + onMouseLeave={() => setActiveFeature(null)} + > +
+
+ {feature.icon} +
+
+ +
+

+ {feature.title} +

+

+ {feature.text} +

+
+ +
+
+
+ + {/* 背景装飾 */} +
+
+
+
+
+ ))} +
+
+
+ ); +} + +const sectionStyle: React.CSSProperties = { + padding: '80px 20px', + background: 'linear-gradient(135deg, #1e293b 0%, #334155 50%, #475569 100%)', + color: 'white', + position: 'relative', + overflow: 'hidden' +}; + +const containerStyle: React.CSSProperties = { + maxWidth: '1200px', + margin: '0 auto', + width: '100%', + position: 'relative', + zIndex: 1 +}; + +const headerStyle: React.CSSProperties = { + textAlign: 'center', + marginBottom: '60px' +}; + +const titleStyle: React.CSSProperties = { + fontSize: 'clamp(2rem, 4vw, 2.8rem)', + fontWeight: '700', + margin: '0 0 15px 0', + lineHeight: 1.2 +}; + +const highlightStyle: React.CSSProperties = { + background: 'linear-gradient(120deg, #ff6b6b, #4ecdc4)', + WebkitBackgroundClip: 'text', + WebkitTextFillColor: 'transparent', + backgroundClip: 'text' +}; + +const subtitleStyle: React.CSSProperties = { + fontSize: '1.1rem', + color: 'rgba(255, 255, 255, 0.8)', + margin: 0, + fontWeight: '400' +}; + +const gridStyle: React.CSSProperties = { + display: 'grid', + gridTemplateColumns: 'repeat(auto-fit, minmax(250px, 1fr))', + gap: '30px', + maxWidth: '1000px', + margin: '0 auto' +}; + +const featureCardStyle: React.CSSProperties = { + padding: '40px 30px', + borderRadius: '24px', + backgroundColor: 'white', + border: '1px solid #e2e8f0', + boxShadow: '0 20px 60px rgba(0, 0, 0, 0.15)', + transition: 'all 0.4s cubic-bezier(0.4, 0, 0.2, 1)', + cursor: 'pointer', + position: 'relative', + overflow: 'hidden', + opacity: 0, + transform: 'translateY(50px)' +}; + +const iconWrapperStyle: React.CSSProperties = { + width: '80px', + height: '80px', + borderRadius: '20px', + border: '2px solid', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + marginBottom: '25px', + marginLeft: 'auto', + marginRight: 'auto', + position: 'relative' +}; + +const iconContainerStyle: React.CSSProperties = { + width: '60px', + height: '60px', + borderRadius: '16px', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)' +}; + +const iconStyle: React.CSSProperties = { + fontSize: '1.8rem', + filter: 'drop-shadow(0 2px 4px rgba(255, 255, 255, 0.3))' +}; + +const contentWrapperStyle: React.CSSProperties = { + marginBottom: '25px' +}; + +const featureTitleStyle: React.CSSProperties = { + fontSize: '1.4rem', + fontWeight: '600', + margin: '0 0 15px 0', + lineHeight: 1.3, + transition: 'color 0.3s ease' +}; + +const featureTextStyle: React.CSSProperties = { + fontSize: '1rem', + lineHeight: 1.6, + margin: 0, + transition: 'color 0.3s ease' +}; + +const progressBarStyle: React.CSSProperties = { + height: '4px', + borderRadius: '2px', + overflow: 'hidden', + transition: 'background-color 0.3s ease' +}; + +const progressFillStyle: React.CSSProperties = { + height: '100%', + borderRadius: '2px', + transition: 'width 0.6s cubic-bezier(0.4, 0, 0.2, 1)' +}; + +const backgroundDecorStyle: React.CSSProperties = { + position: 'absolute', + top: 0, + left: 0, + right: 0, + bottom: 0, + pointerEvents: 'none', + overflow: 'hidden' +}; + +const decorCircle1Style: React.CSSProperties = { + position: 'absolute', + top: '-40px', + right: '-40px', + width: '120px', + height: '120px', + borderRadius: '50%', + transition: 'transform 0.4s ease' +}; + +const decorCircle2Style: React.CSSProperties = { + position: 'absolute', + bottom: '-60px', + left: '-60px', + width: '140px', + height: '140px', + borderRadius: '50%', + transition: 'transform 0.4s ease' +}; \ No newline at end of file diff --git a/src/components/organisms/Footer.tsx b/src/components/organisms/Footer.tsx new file mode 100644 index 0000000..b0e34d6 --- /dev/null +++ b/src/components/organisms/Footer.tsx @@ -0,0 +1,415 @@ +'use client'; + +import React, { useState } from 'react'; + +export default function Footer() { + const [hoveredLink, setHoveredLink] = useState(null); + + const socialLinks = [ + { name: 'GitHub', icon: '🐙', url: '#', color: '#333' }, + { name: 'Discord', icon: '💬', url: '#', color: '#5865f2' }, + { name: 'X (Twitter)', icon: '🐦', url: '#', color: '#1da1f2' }, + { name: 'YouTube', icon: '📺', url: '#', color: '#ff0000' } + ]; + + const quickLinks = [ + { name: 'ドキュメント', url: '#' }, + { name: 'API リファレンス', url: '#' }, + { name: 'チュートリアル', url: '#' }, + { name: 'コミュニティ', url: '#' } + ]; + + const legalLinks = [ + { name: '会社情報', url: '#' }, + { name: '利用規約', url: '#' }, + { name: 'プライバシーポリシー', url: '#' }, + { name: 'セキュリティ', url: '#' } + ]; + + const contactInfo = [ + { label: 'サポート', value: 'support@toban.community', icon: '📧' }, + { label: 'パートナーシップ', value: 'partnership@toban.community', icon: '🤝' } + ]; + + return ( + + ); +} + +const footerStyle: React.CSSProperties = { + background: 'linear-gradient(135deg, #0f172a 0%, #1e293b 50%, #334155 100%)', + color: 'white', + marginTop: 'auto' +}; + +const containerStyle: React.CSSProperties = { + maxWidth: '1200px', + margin: '0 auto', + padding: '0 20px' +}; + +const mainContentStyle: React.CSSProperties = { + display: 'grid', + gridTemplateColumns: 'repeat(auto-fit, minmax(280px, 1fr))', + gap: '50px', + padding: '80px 0 60px' +}; + +const brandSectionStyle: React.CSSProperties = { + maxWidth: '400px' +}; + +const logoSectionStyle: React.CSSProperties = { + marginBottom: '25px' +}; + +const logoStyle: React.CSSProperties = { + fontSize: '2rem', + fontWeight: '700', + margin: '0 0 10px 0', + background: 'linear-gradient(120deg, #60a5fa, #34d399)', + WebkitBackgroundClip: 'text', + WebkitTextFillColor: 'transparent', + backgroundClip: 'text' +}; + +const taglineStyle: React.CSSProperties = { + fontSize: '1.1rem', + color: 'rgba(255, 255, 255, 0.9)', + margin: 0, + fontWeight: '500', + lineHeight: 1.4 +}; + +const descriptionStyle: React.CSSProperties = { + fontSize: '1rem', + color: 'rgba(255, 255, 255, 0.7)', + lineHeight: 1.6, + margin: '0 0 30px 0' +}; + +const socialSectionStyle: React.CSSProperties = { + marginTop: '30px' +}; + +const sectionTitleStyle: React.CSSProperties = { + fontSize: '1.1rem', + fontWeight: '600', + color: 'rgba(255, 255, 255, 0.9)', + margin: '0 0 20px 0', + letterSpacing: '0.05em' +}; + +const socialLinksStyle: React.CSSProperties = { + display: 'flex', + gap: '15px', + flexWrap: 'wrap' +}; + +const socialLinkStyle: React.CSSProperties = { + display: 'flex', + alignItems: 'center', + gap: '8px', + padding: '10px 16px', + borderRadius: '12px', + textDecoration: 'none', + transition: 'all 0.3s ease', + border: '1px solid rgba(255, 255, 255, 0.1)' +}; + +const socialIconStyle: React.CSSProperties = { + fontSize: '1.2rem' +}; + +const socialNameStyle: React.CSSProperties = { + fontSize: '0.9rem', + fontWeight: '500' +}; + +const linkSectionStyle: React.CSSProperties = { + minWidth: '200px' +}; + +const linkListStyle: React.CSSProperties = { + listStyle: 'none', + padding: 0, + margin: 0 +}; + +const linkStyle: React.CSSProperties = { + display: 'block', + padding: '8px 0', + textDecoration: 'none', + fontSize: '1rem', + transition: 'color 0.3s ease', + borderBottom: '1px solid transparent' +}; + +const contactSectionStyle: React.CSSProperties = { + minWidth: '280px' +}; + +const contactListStyle: React.CSSProperties = { + marginBottom: '30px' +}; + +const contactItemStyle: React.CSSProperties = { + display: 'flex', + alignItems: 'flex-start', + gap: '12px', + marginBottom: '20px' +}; + +const contactIconStyle: React.CSSProperties = { + fontSize: '1.2rem', + marginTop: '2px' +}; + +const contactLabelStyle: React.CSSProperties = { + fontSize: '0.9rem', + color: 'rgba(255, 255, 255, 0.6)', + marginBottom: '4px', + fontWeight: '500' +}; + +const contactValueStyle: React.CSSProperties = { + fontSize: '1rem', + textDecoration: 'none', + transition: 'color 0.3s ease' +}; + +const newsletterStyle: React.CSSProperties = { + padding: '25px', + borderRadius: '16px', + background: 'rgba(255, 255, 255, 0.05)', + border: '1px solid rgba(255, 255, 255, 0.1)' +}; + +const newsletterTitleStyle: React.CSSProperties = { + fontSize: '1rem', + fontWeight: '600', + color: 'rgba(255, 255, 255, 0.9)', + margin: '0 0 15px 0' +}; + +const newsletterFormStyle: React.CSSProperties = { + display: 'flex', + gap: '10px' +}; + +const emailInputStyle: React.CSSProperties = { + flex: 1, + padding: '12px 16px', + borderRadius: '8px', + border: '1px solid rgba(255, 255, 255, 0.2)', + background: 'rgba(255, 255, 255, 0.1)', + color: 'white', + fontSize: '0.95rem' +}; + +const subscribeButtonStyle: React.CSSProperties = { + padding: '12px 20px', + borderRadius: '8px', + border: 'none', + background: '#60a5fa', + color: 'white', + fontSize: '0.95rem', + fontWeight: '600', + cursor: 'pointer', + transition: 'all 0.3s ease' +}; + +const bottomBarStyle: React.CSSProperties = { + borderTop: '1px solid rgba(255, 255, 255, 0.1)', + padding: '25px 0' +}; + +const bottomContentStyle: React.CSSProperties = { + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center', + flexWrap: 'wrap', + gap: '20px' +}; + +const copyrightStyle: React.CSSProperties = { + flex: 1 +}; + +const copyrightTextStyle: React.CSSProperties = { + fontSize: '0.95rem', + color: 'rgba(255, 255, 255, 0.6)', + margin: 0 +}; + +const statusStyle: React.CSSProperties = { + display: 'flex', + alignItems: 'center' +}; + +const statusItemStyle: React.CSSProperties = { + display: 'flex', + alignItems: 'center', + gap: '8px' +}; + +const statusIndicatorStyle: React.CSSProperties = { + width: '8px', + height: '8px', + borderRadius: '50%', + backgroundColor: '#34d399', + animation: 'pulse 2s ease-in-out infinite' +}; + +const statusTextStyle: React.CSSProperties = { + fontSize: '0.9rem', + color: 'rgba(255, 255, 255, 0.7)', + fontWeight: '500' +}; \ No newline at end of file diff --git a/src/components/organisms/GettingStarted.tsx b/src/components/organisms/GettingStarted.tsx new file mode 100644 index 0000000..030343d --- /dev/null +++ b/src/components/organisms/GettingStarted.tsx @@ -0,0 +1,447 @@ +'use client'; + +import React, { useState, useEffect } from 'react'; + +export default function GettingStarted() { + const [isVisible, setIsVisible] = useState(false); + const [selectedRole, setSelectedRole] = useState(null); + + const roles = [ + { + role: 'コントリビューター', + capabilities: 'タスクに応募・履歴を積む', + initialCost: '0 ETH', + icon: '👥', + color: '#3b82f6', + description: 'プロジェクトに参加してタスクを完了し、貢献の履歴を積み重ねていきます。', + benefits: ['履歴の蓄積', 'スキルアップ', 'コミュニティ参加'] + }, + { + role: '寄付者', + capabilities: 'トークン or ETH をプールへ寄付', + initialCost: '任意', + icon: '💝', + color: '#10b981', + description: 'プロジェクトを資金面で支援し、コミュニティの発展に貢献します。', + benefits: ['社会貢献', '透明性確保', 'インパクト追跡'] + }, + { + role: 'フィールドパートナー', + capabilities: '地域課題を登録し共同運営', + initialCost: '0 ETH', + icon: '🤝', + color: '#f59e0b', + description: '地域や分野の課題を登録し、プロジェクトを共同で運営します。', + benefits: ['課題解決', 'ネットワーク構築', '地域貢献'] + } + ]; + + useEffect(() => { + const observer = new IntersectionObserver( + ([entry]) => { + if (entry.isIntersecting) { + setIsVisible(true); + } + }, + { threshold: 0.1, rootMargin: '50px' } + ); + + const element = document.getElementById('getting-started'); + if (element) { + observer.observe(element); + } + + return () => { + if (element) { + observer.unobserve(element); + } + }; + }, []); + + return ( +
+
+
+

+ 参加方法
+ Getting Started +

+

+ あなたに最適な参加方法を選んで、今すぐTobanコミュニティに参加しましょう +

+
+ +
+ {roles.map((item, index) => ( +
setSelectedRole(index)} + onMouseLeave={() => setSelectedRole(null)} + > + {/* コストバッジ(右上) */} +
+ {item.initialCost} +
+ + {/* アイコン(中央) */} +
+
+ {item.icon} +
+
+ + {/* メインコンテンツ */} +
+

+ {item.role} +

+ +

+ {item.capabilities} +

+ +

+ {item.description} +

+ + {/* メリット */} +
+

主なメリット

+
    + {item.benefits.map((benefit, benefitIndex) => ( +
  • + + {benefit} +
  • + ))} +
+
+
+ + {/* CTA ボタン */} +
+ +
+
+ ))} +
+ + {/* 全体的なCTA */} +
+
+

始める準備はできましたか?

+

+ 数分で参加できます。まずはコントリビューターとして始めて、 + 徐々に他の役割も体験してみましょう。 +

+
+ + +
+
+
+
+
+ ); +} + +const sectionStyle: React.CSSProperties = { + padding: '80px 20px', + background: 'linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%)', + minHeight: '100vh', + display: 'flex', + alignItems: 'center' +}; + +const containerStyle: React.CSSProperties = { + maxWidth: '1200px', + margin: '0 auto', + width: '100%' +}; + +const headerStyle: React.CSSProperties = { + textAlign: 'center', + marginBottom: '60px' +}; + +const titleStyle: React.CSSProperties = { + fontSize: 'clamp(2rem, 4vw, 2.8rem)', + fontWeight: '700', + color: '#1e293b', + margin: '0 0 15px 0', + lineHeight: 1.2 +}; + +const highlightStyle: React.CSSProperties = { + background: 'linear-gradient(120deg, #3b82f6, #10b981)', + WebkitBackgroundClip: 'text', + WebkitTextFillColor: 'transparent', + backgroundClip: 'text' +}; + +const subtitleStyle: React.CSSProperties = { + fontSize: '1.1rem', + color: '#64748b', + margin: 0, + fontWeight: '400', + maxWidth: '600px', + marginLeft: 'auto', + marginRight: 'auto' +}; + +const rolesGridStyle: React.CSSProperties = { + display: 'grid', + gridTemplateColumns: 'repeat(auto-fit, minmax(280px, 1fr))', + gap: 'clamp(20px, 4vw, 30px)', + marginBottom: '60px' +}; + +const roleCardStyle: React.CSSProperties = { + padding: 'clamp(20px, 4vw, 30px)', + borderRadius: '20px', + backgroundColor: 'white', + border: '2px solid', + boxShadow: '0 10px 40px rgba(0, 0, 0, 0.1)', + transition: 'all 0.4s cubic-bezier(0.4, 0, 0.2, 1)', + cursor: 'pointer', + opacity: 0, + transform: 'translateY(20px)', + position: 'relative', + overflow: 'hidden' +}; + +const cardHeaderStyle: React.CSSProperties = { + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center', + marginBottom: '25px' +}; + +const iconContainerStyle: React.CSSProperties = { + width: '60px', + height: '60px', + borderRadius: '16px', + border: '2px solid', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + marginLeft: 'auto', + marginRight: 'auto' +}; + +const iconStyle: React.CSSProperties = { + fontSize: '1.8rem' +}; + +const costBadgeStyle: React.CSSProperties = { + padding: '8px 16px', + borderRadius: '20px', + fontSize: '0.9rem', + fontWeight: '700', + textTransform: 'uppercase', + letterSpacing: '0.05em' +}; + +const cardContentStyle: React.CSSProperties = { + marginBottom: '30px' +}; + +const roleTitleStyle: React.CSSProperties = { + fontSize: '1.4rem', + fontWeight: '600', + margin: '0 0 15px 0', + lineHeight: 1.3, + transition: 'color 0.3s ease' +}; + +const roleCapabilitiesStyle: React.CSSProperties = { + fontSize: '1rem', + color: '#64748b', + margin: '0 0 15px 0', + fontWeight: '500' +}; + +const roleDescriptionStyle: React.CSSProperties = { + fontSize: '0.95rem', + color: '#64748b', + margin: '0 0 20px 0', + lineHeight: 1.5 +}; + +const benefitsStyle: React.CSSProperties = { + marginTop: '20px' +}; + +const benefitsTitleStyle: React.CSSProperties = { + fontSize: '1rem', + fontWeight: '600', + color: '#374151', + margin: '0 0 10px 0' +}; + +const benefitsListStyle: React.CSSProperties = { + listStyle: 'none', + padding: 0, + margin: 0 +}; + +const benefitItemStyle: React.CSSProperties = { + display: 'flex', + alignItems: 'center', + gap: '10px', + fontSize: '0.9rem', + color: '#64748b', + marginBottom: '8px' +}; + +const benefitIconStyle: React.CSSProperties = { + fontSize: '0.8rem', + fontWeight: 'bold' +}; + +const cardFooterStyle: React.CSSProperties = { + paddingTop: '20px', + borderTop: '1px solid #f1f5f9' +}; + +const ctaButtonStyle: React.CSSProperties = { + width: '100%', + padding: '15px 25px', + borderRadius: '12px', + border: '2px solid', + fontSize: '1rem', + fontWeight: '600', + cursor: 'pointer', + transition: 'all 0.3s ease', + backgroundColor: 'transparent' +}; + +const mainCtaStyle: React.CSSProperties = { + textAlign: 'center', + padding: 'clamp(40px, 8vw, 60px) clamp(20px, 6vw, 40px)', + borderRadius: '24px', + background: 'linear-gradient(135deg, #3b82f6, #1d4ed8)', + color: 'white', + position: 'relative', + overflow: 'hidden' +}; + +const ctaContentStyle: React.CSSProperties = { + position: 'relative', + zIndex: 1 +}; + +const ctaTitleStyle: React.CSSProperties = { + fontSize: 'clamp(1.8rem, 3vw, 2.2rem)', + fontWeight: '700', + margin: '0 0 15px 0', + lineHeight: 1.2 +}; + +const ctaDescriptionStyle: React.CSSProperties = { + fontSize: '1.1rem', + color: 'rgba(255, 255, 255, 0.9)', + margin: '0 0 30px 0', + maxWidth: '600px', + marginLeft: 'auto', + marginRight: 'auto', + lineHeight: 1.5 +}; + +const ctaButtonsStyle: React.CSSProperties = { + display: 'flex', + gap: 'clamp(15px, 3vw, 20px)', + justifyContent: 'center', + flexWrap: 'wrap' +}; + +const primaryCtaButtonStyle: React.CSSProperties = { + display: 'inline-flex', + alignItems: 'center', + gap: '10px', + padding: '18px 36px', + borderRadius: '12px', + border: 'none', + fontSize: '1.1rem', + fontWeight: '700', + backgroundColor: 'white', + color: '#3b82f6', + cursor: 'pointer', + transition: 'all 0.3s ease', + boxShadow: '0 4px 20px rgba(0, 0, 0, 0.1)' +}; + +const secondaryCtaButtonStyle: React.CSSProperties = { + padding: '18px 36px', + borderRadius: '12px', + border: '2px solid rgba(255, 255, 255, 0.3)', + fontSize: '1.1rem', + fontWeight: '600', + backgroundColor: 'transparent', + color: 'white', + cursor: 'pointer', + transition: 'all 0.3s ease' +}; + +const ctaButtonIconStyle: React.CSSProperties = { + fontSize: '1.2rem' +}; \ No newline at end of file diff --git a/src/components/organisms/HeroSection.tsx b/src/components/organisms/HeroSection.tsx new file mode 100644 index 0000000..2436c27 --- /dev/null +++ b/src/components/organisms/HeroSection.tsx @@ -0,0 +1,13 @@ +export default function HeroSection() { + return ( +
+
+ hero-image +
+ + +
+
+
+ ); + } \ No newline at end of file diff --git a/src/components/organisms/HowItWorks.tsx b/src/components/organisms/HowItWorks.tsx new file mode 100644 index 0000000..3a7c4a8 --- /dev/null +++ b/src/components/organisms/HowItWorks.tsx @@ -0,0 +1,538 @@ +'use client'; + +import React, { useState, useEffect } from 'react'; + +export default function HowItWorks() { + const [isVisible, setIsVisible] = useState(false); + const [currentStep, setCurrentStep] = useState(0); + const [isMobile, setIsMobile] = useState(false); + const [modalImage, setModalImage] = useState(null); + + const steps = [ + { + number: '01', + title: 'サインイン、ワークスペース参加', + description: 'ウォレット、ガス代なしで利用開始\n誰かから役割かアシストクレジットをもらうと自動的に参加', + image: '/HowItWorksImage/HowItWorks1.png', + color: '#ff6b6b' + }, + { + number: '02', + title: '役割の確認', + description: 'やるべきことや権限・権利を確認\n他にだれがどんな役割をもっているか見える', + image: '/HowItWorksImage/HowItWorks2.png', + color: '#4ecdc4' + }, + { + number: '03', + title: 'アシストクレジットのやりとり', + description: '助けてもらったとき\n気づきをもらったとき\nただ渡したいとき', + images: ['/HowItWorksImage/HowItWorks3-1.png', '/HowItWorksImage/HowItWorks3-2.png'], + color: '#45b7d1' + }, + { + number: '04', + title: '分配コントラクトの作成', + description: '報酬分配をしたい役割を選択\n報酬分配率はアルゴリズムで自動計算', + image: '/HowItWorksImage/HowItWorks4.png', + color: '#96c93d' + }, + { + number: '05', + title: '報酬の分配', + description: 'ERC20、ネイティブトークンを分配コントラクトに送金\n1回のトランザクションで一気に分配', + image: '/HowItWorksImage/HowItWorks5.png', + color: '#f39c12' + } + ]; + + useEffect(() => { + const checkMobile = () => { + setIsMobile(window.innerWidth <= 768); + }; + + checkMobile(); + window.addEventListener('resize', checkMobile); + + return () => window.removeEventListener('resize', checkMobile); + }, []); + + useEffect(() => { + const observer = new IntersectionObserver( + ([entry]) => { + if (entry.isIntersecting) { + setIsVisible(true); + } + }, + { threshold: 0.3 } + ); + + const element = document.getElementById('how-it-works'); + if (element) { + observer.observe(element); + } + + return () => { + if (element) { + observer.unobserve(element); + } + }; + }, []); + + useEffect(() => { + if (isVisible && !isMobile) { + const interval = setInterval(() => { + setCurrentStep((prev) => (prev + 1) % steps.length); + }, 3000); + return () => clearInterval(interval); + } + }, [isVisible, steps.length, isMobile]); + + return ( +
+
+
+

+ 導入も、運用も、
+ シンプルです +

+
+ + {/* ステップ表示 */} +
+ {steps.map((step, index) => ( + + {/* カード */} +
+
+ {/* 画像セクション */} +
+ {step.image ? ( + {step.title} setModalImage(step.image)} + style={{ + maxWidth: '100%', + maxHeight: isMobile ? '200px' : '250px', + objectFit: 'contain', + borderRadius: '10px', + boxShadow: '0 4px 15px rgba(0, 0, 0, 0.15)', + cursor: 'pointer', + transition: 'transform 0.2s ease, box-shadow 0.2s ease' + }} + onMouseEnter={(e) => { + e.currentTarget.style.transform = 'scale(1.02)'; + e.currentTarget.style.boxShadow = '0 6px 20px rgba(0, 0, 0, 0.2)'; + }} + onMouseLeave={(e) => { + e.currentTarget.style.transform = 'scale(1)'; + e.currentTarget.style.boxShadow = '0 4px 15px rgba(0, 0, 0, 0.15)'; + }} + /> + ) : step.images ? ( +
+ {step.images.map((img: string, imgIndex: number) => ( + {`${step.title} setModalImage(img)} + style={{ + maxWidth: isMobile ? '45%' : '180px', + maxHeight: isMobile ? '150px' : '200px', + objectFit: 'contain', + borderRadius: '8px', + boxShadow: '0 4px 15px rgba(0, 0, 0, 0.15)', + cursor: 'pointer', + transition: 'transform 0.2s ease, box-shadow 0.2s ease' + }} + onMouseEnter={(e) => { + e.currentTarget.style.transform = 'scale(1.02)'; + e.currentTarget.style.boxShadow = '0 6px 20px rgba(0, 0, 0, 0.2)'; + }} + onMouseLeave={(e) => { + e.currentTarget.style.transform = 'scale(1)'; + e.currentTarget.style.boxShadow = '0 4px 15px rgba(0, 0, 0, 0.15)'; + }} + /> + ))} +
+ ) : null} +
+ + {/* ステップ番号 */} +
{step.number}
+ + {/* コンテンツセクション */} +
+

+ {step.title} +

+ +

+ {step.description} +

+
+
+ + {/* デスクトップ用矢印(最後のステップ以外) */} + {index < steps.length - 1 && !isMobile && ( +
+
+ → +
+
+ )} +
+ + {/* モバイル用矢印(カードの外側、独立したブロック) */} + {index < steps.length - 1 && isMobile && ( +
+
+ ↓ +
+
+ )} +
+ ))} +
+ + {/* プログレスバー(デスクトップのみ) */} + {!isMobile && ( +
+
+
+
+
+ Step {currentStep + 1} of {steps.length} +
+
+ )} + + {/* 画像拡大モーダル */} + {modalImage && ( +
setModalImage(null)} + > +
e.stopPropagation()} + > + + 拡大表示 +
+
+ )} +
+
+ ); +} + +const sectionStyle: React.CSSProperties = { + padding: '80px 20px', + background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)', + color: 'white', + overflow: 'hidden' +}; + +const containerStyle: React.CSSProperties = { + maxWidth: '1200px', + margin: '0 auto', + width: '100%' +}; + +const headerStyle: React.CSSProperties = { + textAlign: 'center', + marginBottom: '60px' +}; + +const titleStyle: React.CSSProperties = { + fontSize: 'clamp(2rem, 4vw, 2.8rem)', + fontWeight: '700', + margin: 0, + lineHeight: 1.2 +}; + +const highlightStyle: React.CSSProperties = { + background: 'linear-gradient(120deg, #ff6b6b, #4ecdc4)', + WebkitBackgroundClip: 'text', + WebkitTextFillColor: 'transparent', + backgroundClip: 'text' +}; + +const stepsStyle: React.CSSProperties = { + alignItems: 'flex-start' +}; + +const stepCardStyle: React.CSSProperties = { + padding: '30px 20px', + borderRadius: '20px', + backgroundColor: 'white', + border: '2px solid #e2e8f0', + textAlign: 'center', + position: 'relative', + minHeight: '500px', + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'flex-start', + boxShadow: '0 10px 40px rgba(0, 0, 0, 0.1)', + transition: 'all 0.3s ease' +}; + +const stepIconContainerStyle: React.CSSProperties = { + display: 'flex', + flexDirection: 'column', + alignItems: 'center' +}; + +const stepIconStyle: React.CSSProperties = { + width: '60px', + height: '60px', + borderRadius: '50%', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + transition: 'transform 0.3s ease' +}; + +const iconTextStyle: React.CSSProperties = { + fontSize: '1.8rem', + filter: 'drop-shadow(0 2px 4px rgba(255, 255, 255, 0.3))' +}; + +const stepNumberStyle: React.CSSProperties = { + fontSize: '0.9rem', + fontWeight: '700', + color: '#64748b', + letterSpacing: '0.1em' +}; + +const stepTitleStyle: React.CSSProperties = { + fontSize: '1.1rem', + fontWeight: '600', + margin: '0 0 10px 0', + lineHeight: 1.3 +}; + +const stepDescriptionStyle: React.CSSProperties = { + fontSize: '0.9rem', + color: '#64748b', + margin: 0, + lineHeight: 1.4 +}; + +const arrowStyle: React.CSSProperties = { + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + minWidth: '40px', + position: 'absolute', + right: '-30px', + top: '50%', + transform: 'translateY(-50%)', + zIndex: 1 +}; + +const arrowIconStyle: React.CSSProperties = { + fontSize: '1.5rem', + color: 'rgba(255, 255, 255, 0.8)', + fontWeight: 'bold' +}; + +const progressContainerStyle: React.CSSProperties = { + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + gap: '15px' +}; + +const progressBarStyle: React.CSSProperties = { + width: '300px', + height: '6px', + backgroundColor: 'rgba(255, 255, 255, 0.2)', + borderRadius: '3px', + overflow: 'hidden' +}; + +const progressFillStyle: React.CSSProperties = { + height: '100%', + background: 'linear-gradient(90deg, #ff6b6b, #4ecdc4)', + borderRadius: '3px', + transition: 'width 0.5s ease' +}; + +const progressTextStyle: React.CSSProperties = { + fontSize: '0.9rem', + color: 'rgba(255, 255, 255, 0.8)', + fontWeight: '500' +}; + +const modalOverlayStyle: React.CSSProperties = { + position: 'fixed', + top: 0, + left: 0, + right: 0, + bottom: 0, + backgroundColor: 'rgba(0, 0, 0, 0.8)', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + zIndex: 1000, + padding: '20px' +}; + +const modalContentStyle: React.CSSProperties = { + position: 'relative', + maxWidth: '90vw', + maxHeight: '90vh', + backgroundColor: 'white', + borderRadius: '15px', + padding: '20px', + boxShadow: '0 20px 60px rgba(0, 0, 0, 0.3)', + display: 'flex', + alignItems: 'center', + justifyContent: 'center' +}; + +const modalCloseButtonStyle: React.CSSProperties = { + position: 'absolute', + top: '10px', + right: '15px', + background: 'none', + border: 'none', + fontSize: '2rem', + cursor: 'pointer', + color: '#666', + zIndex: 1001, + width: '40px', + height: '40px', + borderRadius: '50%', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + transition: 'all 0.2s ease' +}; + +const modalImageStyle: React.CSSProperties = { + maxWidth: '100%', + maxHeight: '80vh', + objectFit: 'contain', + borderRadius: '10px' +}; \ No newline at end of file diff --git a/src/components/organisms/InfiniteLoop.tsx b/src/components/organisms/InfiniteLoop.tsx new file mode 100644 index 0000000..91d5e09 --- /dev/null +++ b/src/components/organisms/InfiniteLoop.tsx @@ -0,0 +1,316 @@ +'use client'; + +import React, { useState, useEffect } from 'react'; + +const InfiniteLoop: React.FC = () => { + const [currentSpeed, setCurrentSpeed] = useState(12); + const [isPaused, setIsPaused] = useState(false); + const [isHovered, setIsHovered] = useState(false); + const [isMobile, setIsMobile] = useState(false); + + // レスポンシブ対応のためのウィンドウサイズ検出 + useEffect(() => { + const checkIsMobile = () => { + setIsMobile(window.innerWidth <= 768); + }; + + checkIsMobile(); + window.addEventListener('resize', checkIsMobile); + + return () => window.removeEventListener('resize', checkIsMobile); + }, []); + + const logos = [ + 'Comoris', + '塩尻DAO', + 'ento', + 'ETH Tokyo', + 'Fracton', + 'Localcoop' + ]; + + const changeSpeed = (speed: number) => { + setCurrentSpeed(speed); + }; + + const togglePause = () => { + setIsPaused(!isPaused); + }; + + const handleMouseEnter = () => { + setIsHovered(true); + }; + + const handleMouseLeave = () => { + setIsHovered(false); + }; + + return ( +
+ +
+
+
+ {/* 第1セット */} + {logos.map((logo, index) => ( +
{ + e.currentTarget.style.transform = 'scale(1.05)'; + e.currentTarget.style.boxShadow = '0 8px 25px rgba(0, 0, 0, 0.15)'; + }} + onMouseLeave={(e) => { + e.currentTarget.style.transform = 'scale(1)'; + e.currentTarget.style.boxShadow = '0 4px 15px rgba(0, 0, 0, 0.1)'; + }} + > + {logo} +
+ ))} + + {/* 第2セット(シームレスなループ用) */} + {logos.map((logo, index) => ( +
{ + e.currentTarget.style.transform = 'scale(1.05)'; + e.currentTarget.style.boxShadow = '0 8px 25px rgba(0, 0, 0, 0.15)'; + }} + onMouseLeave={(e) => { + e.currentTarget.style.transform = 'scale(1)'; + e.currentTarget.style.boxShadow = '0 4px 15px rgba(0, 0, 0, 0.1)'; + }} + > + {logo} +
+ ))} +
+
+
+ + {/*
+ + + + +
*/} +
+ ); +}; + +// カラーパターンを返す関数 +const getLogoItemColor = (index: number) => { + const colorIndex = index % 6; + const colorPatterns = [ + { background: 'linear-gradient(45deg, #ff6b6b, #ee5a6f)', color: 'white' }, + { background: 'linear-gradient(45deg, #4ecdc4, #44a08d)', color: 'white' }, + { background: 'linear-gradient(45deg, #45b7d1, #96c93d)', color: 'white' }, + { background: 'linear-gradient(45deg, #f093fb, #f5576c)', color: 'white' }, + { background: 'linear-gradient(45deg, #ffecd2, #fcb69f)', color: '#333' }, + { background: 'linear-gradient(45deg, #a8edea, #fed6e3)', color: '#333' } + ]; + + return colorPatterns[colorIndex]; +}; + +// スタイルオブジェクト +const styles: { [key: string]: React.CSSProperties } = { + body: { + margin: 0, + padding: '40px', + background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)', + fontFamily: 'Arial, sans-serif', + minHeight: '100vh', + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + alignItems: 'center', + }, + container: { + width: '100%', + maxWidth: '1200px', + overflow: 'hidden', + background: 'rgba(255, 255, 255, 0.1)', + backdropFilter: 'blur(10px)', + borderRadius: '20px', + padding: '40px 0', + boxShadow: '0 8px 32px rgba(0, 0, 0, 0.1)', + position: 'relative', + }, + containerScrollWrapper: { + width: '100%', + maxWidth: '1200px', + overflow: 'hidden', + background: 'rgba(255, 255, 255, 0.1)', + backdropFilter: 'blur(10px)', + // borderRadius: '20px', + padding: '20px 0', + marginTop: '20px', + boxShadow: '0 8px 32px rgba(0, 0, 0, 0.1)', + position: 'relative', + }, + scrollWrapper: { + overflow: 'hidden', + position: 'relative', + }, + logoTrack: { + display: 'flex', + width: 'calc(200% + 60px)', + animationName: 'scrollStep', + animationTimingFunction: 'linear', + animationIterationCount: 'infinite', + gap: '60px', + alignItems: 'center', + }, + logoItem: { + flexShrink: 0, + height: '80px', + width: '160px', + background: 'rgba(255, 255, 255, 0.9)', + borderRadius: '15px', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + fontWeight: 'bold', + fontSize: '18px', + color: '#333', + boxShadow: '0 4px 15px rgba(0, 0, 0, 0.1)', + transition: 'transform 0.3s ease, box-shadow 0.3s ease', + cursor: 'pointer', + position: 'relative', + }, + logoItemMobile: { + width: '120px', + height: '60px', + fontSize: '14px', + }, + title: { + textAlign: 'center', + color: 'white', + marginBottom: '30px', + fontSize: '28px', + fontWeight: 300, + }, + controls: { + textAlign: 'center', + marginTop: '30px', + }, + speedControl: { + background: 'rgba(255, 255, 255, 0.2)', + border: 'none', + color: 'white', + padding: '10px 20px', + margin: '0 10px', + borderRadius: '25px', + cursor: 'pointer', + transition: 'all 0.3s ease', + backdropFilter: 'blur(10px)', + }, + speedControlActive: { + background: 'rgba(255, 255, 255, 0.4)', + boxShadow: '0 4px 15px rgba(255, 255, 255, 0.2)', + }, +}; + +export default InfiniteLoop; diff --git a/src/components/organisms/Pricing.tsx b/src/components/organisms/Pricing.tsx new file mode 100644 index 0000000..237ce14 --- /dev/null +++ b/src/components/organisms/Pricing.tsx @@ -0,0 +1,33 @@ +export default function Pricing() { + return ( +
+

はじめやすく、続けやすい。

+ + + + + + + + + + + + + + + + + + + + + + + + + +
プラン内容料金
フリープラン1プロジェクト / 5ロールまで無料
スタンダードAPI連携、人数制限解除月額 ¥4,980〜
カスタム法人・自治体向け支援お問合せ
+
+ ); + } \ No newline at end of file diff --git a/src/components/organisms/ProblemSolution.tsx b/src/components/organisms/ProblemSolution.tsx new file mode 100644 index 0000000..42219da --- /dev/null +++ b/src/components/organisms/ProblemSolution.tsx @@ -0,0 +1,320 @@ +'use client'; + +import React, { useState, useEffect } from 'react'; + +export default function ProblemSolution() { + const [isVisible, setIsVisible] = useState(false); + const [isMobile, setIsMobile] = useState(false); + + useEffect(() => { + const checkMobile = () => { + setIsMobile(window.innerWidth <= 768); + }; + + checkMobile(); + window.addEventListener('resize', checkMobile); + + return () => window.removeEventListener('resize', checkMobile); + }, []); + + useEffect(() => { + const observer = new IntersectionObserver( + ([entry]) => { + if (entry.isIntersecting) { + setIsVisible(true); + } + }, + { threshold: 0.3 } + ); + + const element = document.getElementById('problem-solution'); + if (element) { + observer.observe(element); + } + + return () => { + if (element) { + observer.unobserve(element); + } + }; + }, []); + + const problems = [ + { icon: '📊', text: '貢献の見える化が難しい' }, + { icon: '🧮', text: '報酬配分の計算が煩雑' }, + { icon: '🔄', text: 'ボランティアが続かない' }, + { icon: '💬', text: '活動説明が難しい' } + ]; + + const solutions = [ + { icon: '⚡', text: 'アシストクレジットで即時記録' }, + { icon: '🤖', text: '一度設定すれば自動で分配' }, + { icon: '🎯', text: '貢献がトークンに' }, + { icon: '📈', text: '活動履歴として可視化' } + ]; + + return ( +
+
+
+

+ 協働プロジェクトの現場の
+ 「よくある困りごと」
+ Tobanが全て解決します +

+
+ +
+ {/* 課題セクション */} +
+
+
⚠️
+

課題

+
+
+ {problems.map((item, index) => ( +
+ {item.icon} + {item.text} +
+ ))} +
+
+ + {/* 矢印 */} +
+
+ → +
+
+ + {/* モバイル用矢印 */} + {isMobile && ( +
+
+ ↓ +
+
+ )} + + {/* 解決策セクション */} +
+
+
+

解決策

+
+
+ {solutions.map((item, index) => ( +
+ {item.icon} + {item.text} +
+ ))} +
+
+
+
+
+ ); +} + +const sectionStyle: React.CSSProperties = { + padding: '80px 20px', + background: 'linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%)', + minHeight: '100vh', + display: 'flex', + alignItems: 'center' +}; + +const containerStyle: React.CSSProperties = { + maxWidth: '1200px', + margin: '0 auto', + width: '100%' +}; + +const headerStyle: React.CSSProperties = { + textAlign: 'center', + marginBottom: '60px' +}; + +const titleStyle: React.CSSProperties = { + fontSize: 'clamp(1.8rem, 4vw, 2.5rem)', + fontWeight: '700', + color: '#1e293b', + lineHeight: 1.3, + margin: 0 +}; + +const highlightStyle: React.CSSProperties = { + background: 'linear-gradient(120deg, #ff6b6b, #4ecdc4)', + WebkitBackgroundClip: 'text', + WebkitTextFillColor: 'transparent', + backgroundClip: 'text' +}; + +const contentGridStyle: React.CSSProperties = { + alignItems: 'stretch' +}; + +const cardStyle: React.CSSProperties = { + borderRadius: '20px', + backgroundColor: 'white', + boxShadow: '0 20px 60px rgba(0, 0, 0, 0.1)', + border: '1px solid rgba(255, 255, 255, 0.2)', + backdropFilter: 'blur(10px)', + opacity: 0, + transform: 'translateY(30px)' +}; + +const animationStyle: React.CSSProperties = { + animation: 'fadeInUp 0.8s ease-out forwards' +}; + +const cardHeaderStyle: React.CSSProperties = { + display: 'flex', + alignItems: 'center', + marginBottom: '30px', + paddingBottom: '20px', + borderBottom: '2px solid #f1f5f9' +}; + +const problemIconStyle: React.CSSProperties = { + fontSize: '2rem', + marginRight: '15px', + filter: 'drop-shadow(0 2px 4px rgba(255, 107, 107, 0.3))' +}; + +const solutionIconStyle: React.CSSProperties = { + fontSize: '2rem', + marginRight: '15px', + filter: 'drop-shadow(0 2px 4px rgba(78, 205, 196, 0.3))' +}; + +const cardTitleStyle: React.CSSProperties = { + fontSize: '1.5rem', + fontWeight: '600', + margin: 0, + color: '#1e293b' +}; + +const listContainerStyle: React.CSSProperties = { + display: 'flex', + flexDirection: 'column', + gap: '20px' +}; + +const listItemStyle: React.CSSProperties = { + display: 'flex', + alignItems: 'center', + padding: '15px 20px', + backgroundColor: 'rgba(248, 250, 252, 0.8)', + borderRadius: '12px', + border: '1px solid #e2e8f0', + transition: 'all 0.3s ease', + cursor: 'pointer', + opacity: 0, + transform: 'translateX(-20px)' +}; + +const listItemAnimationStyle: React.CSSProperties = { + animation: 'slideInLeft 0.6s ease-out forwards' +}; + +const iconStyle: React.CSSProperties = { + fontSize: '1.5rem', + marginRight: '15px', + minWidth: '30px' +}; + +const arrowContainerStyle: React.CSSProperties = { + display: 'flex', + alignItems: 'center', + justifyContent: 'center' +}; + +const arrowStyle: React.CSSProperties = { + fontSize: '3rem', + color: '#64748b', + fontWeight: 'bold', + opacity: 0, + transform: 'scale(0.5)' +}; + +const arrowAnimationStyle: React.CSSProperties = { + animation: 'bounceIn 1s ease-out 0.5s forwards' +}; + \ No newline at end of file diff --git a/src/components/organisms/SecurityStack.tsx b/src/components/organisms/SecurityStack.tsx new file mode 100644 index 0000000..9eca843 --- /dev/null +++ b/src/components/organisms/SecurityStack.tsx @@ -0,0 +1,390 @@ +'use client'; + +import React, { useState, useEffect } from 'react'; + +export default function SecurityStack() { + const [isVisible, setIsVisible] = useState(false); + const [isMobile, setIsMobile] = useState(false); + const [hoveredCard, setHoveredCard] = useState(null); + + const protocols = [ + { + name: 'Hats Protocol', + logo: '/logo/hats-logo.png', + role: '権限付与・剥奪を NFT 化', + trust: '非転送型 ERC-1155。ロールは DAO が管理', + link: 'https://docs.hatsprotocol.xyz', + linkText: 'docs.hatsprotocol.xyz', + color: '#ff6b6b', + icon: '🎩' + }, + { + name: '0xSplits', + logo: '/logo/splits-logo.png', + role: '報酬の自動分配', + trust: '監査済み & ハイパーストラクチャ', + link: 'https://review.mirror.xyz', + linkText: 'review.mirror.xyz', + color: '#4ecdc4', + icon: '💰' + }, + { + name: 'ENS', + logo: '/logo/ens-mark-Blue.svg', + role: '認識しやすい受取アドレス', + trust: 'オープンソース/分散管理', + link: 'https://ens.domains', + linkText: 'ens.domains', + color: '#45b7d1', + icon: '🌐' + } + ]; + + const securityFeatures = [ + { icon: '🔓', text: 'すべてオープンソース', color: '#ff6b6b' }, + { icon: '🛡️', text: 'スマートコントラクトはすべて監査済み', color: '#4ecdc4' }, + { icon: '⚔️', text: '複数コミュニティで実戦投入', color: '#45b7d1' } + ]; + + useEffect(() => { + const checkMobile = () => { + setIsMobile(window.innerWidth <= 768); + }; + + checkMobile(); + window.addEventListener('resize', checkMobile); + + return () => window.removeEventListener('resize', checkMobile); + }, []); + + useEffect(() => { + const observer = new IntersectionObserver( + ([entry]) => { + if (entry.isIntersecting) { + setIsVisible(true); + } + }, + { threshold: 0.3 } + ); + + const element = document.getElementById('security-stack'); + if (element) { + observer.observe(element); + } + + return () => { + if (element) { + observer.unobserve(element); + } + }; + }, []); + + return ( +
+
+ {/* ヘッダー */} +
+

+ 🔒 + セキュリティ &
+ プロトコルスタック +

+
+ + {/* セキュリティ機能 */} +
+ {securityFeatures.map((feature, index) => ( +
+ {feature.icon} + {feature.text} +
+ ))} +
+ + {/* プロトコルカード */} +
+ {protocols.map((protocol, index) => ( +
!isMobile && setHoveredCard(index)} + onMouseLeave={() => !isMobile && setHoveredCard(null)} + > + {/* ロゴとタイトル */} +
+
+ {protocol.name} +
+

{protocol.name}

+
+ + {/* 役割 */} +
+
+ Toban での役割 +
+

{protocol.role}

+
+ + {/* 信頼ポイント */} +
+
+ 信頼ポイント +
+

+ {protocol.trust}{' '} + + {protocol.linkText} + +

+
+
+ ))} +
+
+
+ ); +} + +const sectionStyle: React.CSSProperties = { + padding: '80px 20px', + background: 'linear-gradient(135deg, #1e293b 0%, #334155 100%)', + color: 'white', + position: 'relative', + overflow: 'hidden' +}; + +const containerStyle: React.CSSProperties = { + maxWidth: '1200px', + margin: '0 auto', + width: '100%', + position: 'relative' +}; + +const headerStyle: React.CSSProperties = { + textAlign: 'center', + marginBottom: '60px' +}; + +const titleStyle: React.CSSProperties = { + fontSize: 'clamp(2rem, 4vw, 2.8rem)', + fontWeight: '700', + margin: 0, + lineHeight: 1.2 +}; + +const securityIconStyle: React.CSSProperties = { + display: 'inline-block', + marginRight: '15px', + fontSize: '2.5rem', + filter: 'drop-shadow(0 4px 8px rgba(255, 215, 0, 0.4))' +}; + +const highlightStyle: React.CSSProperties = { + background: 'linear-gradient(120deg, #ffd700, #ff6b6b)', + WebkitBackgroundClip: 'text', + WebkitTextFillColor: 'transparent', + backgroundClip: 'text' +}; + +const featuresContainerStyle: React.CSSProperties = { + display: 'flex', + flexDirection: 'column', + gap: '15px', + alignItems: 'center' +}; + +const featureItemStyle: React.CSSProperties = { + display: 'flex', + alignItems: 'center', + gap: '15px', + padding: '20px 25px', + backgroundColor: 'rgba(255, 255, 255, 0.1)', + borderRadius: '50px', + backdropFilter: 'blur(10px)', + border: '1px solid rgba(255, 255, 255, 0.2)', + opacity: 0, + transform: 'translateY(20px)', + maxWidth: '600px', + width: '100%' +}; + +const featureAnimationStyle: React.CSSProperties = { + animation: 'slideInRight 0.8s ease-out forwards' +}; + +const featureIconStyle: React.CSSProperties = { + fontSize: '2rem' +}; + +const protocolGridStyle: React.CSSProperties = { + alignItems: 'stretch' +}; + +const protocolCardStyle: React.CSSProperties = { + backgroundColor: 'white', + borderRadius: '20px', + padding: '35px 30px', + border: '2px solid #e2e8f0', + color: '#1e293b', + boxShadow: '0 20px 60px rgba(0, 0, 0, 0.1)', + cursor: 'pointer', + position: 'relative' +}; + +const protocolHeaderStyle: React.CSSProperties = { + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + marginBottom: '30px' +}; + +const logoContainerStyle: React.CSSProperties = { + width: '120px', + height: '120px', + borderRadius: '20px', + backgroundColor: '#f8f9fa', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + marginBottom: '20px', + transition: 'all 0.3s ease' +}; + +const logoStyle: React.CSSProperties = { + width: '80px', + height: 'auto', + maxHeight: '80px', + objectFit: 'contain' +}; + +const protocolTitleStyle: React.CSSProperties = { + fontSize: '1.5rem', + fontWeight: '700', + margin: 0, + transition: 'color 0.3s ease' +}; + +const roleContainerStyle: React.CSSProperties = { + marginBottom: '20px' +}; + +const trustContainerStyle: React.CSSProperties = { + marginBottom: 0 +}; + +const labelStyle: React.CSSProperties = { + display: 'inline-block', + padding: '6px 12px', + borderRadius: '20px', + fontSize: '0.9rem', + fontWeight: '600', + color: 'white', + marginBottom: '10px' +}; + +const roleTextStyle: React.CSSProperties = { + fontSize: '1rem', + lineHeight: 1.6, + margin: 0, + color: '#475569' +}; + +const trustTextStyle: React.CSSProperties = { + fontSize: '1rem', + lineHeight: 1.6, + margin: 0, + color: '#475569' +}; + +const linkStyle: React.CSSProperties = { + textDecoration: 'none', + fontWeight: '600', + transition: 'opacity 0.3s ease' +}; \ No newline at end of file diff --git a/src/components/organisms/TrackRecord.tsx b/src/components/organisms/TrackRecord.tsx new file mode 100644 index 0000000..fccabb6 --- /dev/null +++ b/src/components/organisms/TrackRecord.tsx @@ -0,0 +1,339 @@ +'use client'; + +import React, { useState, useEffect, useRef } from 'react'; + +export default function TrackRecord() { + const [isVisible, setIsVisible] = useState(false); + const [animatedNumbers, setAnimatedNumbers] = useState([0, 0, 0]); + const [isMobile, setIsMobile] = useState(true); // 初期値をtrueに設定してSSR時にパルスを無効化 + const intervalRefs = useRef<(NodeJS.Timeout | null)[]>([null, null, null]); + + const records = [ + { + metric: '累計流通額', + value: '¥125,000,000 相当', + numericValue: 125000000, + suffix: ' 相当', + prefix: '¥', + icon: '💰', + color: '#10b981' + }, + { + metric: '導入組織数', + value: '34 DAO / NPO / 地域団体', + numericValue: 34, + suffix: ' DAO / NPO / 地域団体', + prefix: '', + icon: '🏢', + color: '#3b82f6' + }, + { + metric: '処理 Tx', + value: '18,420 Tx', + numericValue: 18420, + suffix: ' Tx', + prefix: '', + icon: '⚡', + color: '#f59e0b' + } + ]; + + const animateNumber = (finalValue: number, index: number, duration: number = 2000) => { + let startValue = 0; + const increment = finalValue / (duration / 50); + + if (intervalRefs.current[index]) { + clearInterval(intervalRefs.current[index]!); + } + + intervalRefs.current[index] = setInterval(() => { + startValue += increment; + if (startValue >= finalValue) { + startValue = finalValue; + clearInterval(intervalRefs.current[index]!); + intervalRefs.current[index] = null; + } + setAnimatedNumbers(prev => { + const newNumbers = [...prev]; + newNumbers[index] = Math.floor(startValue); + return newNumbers; + }); + }, 50); + }; + + const formatNumber = (num: number, index: number) => { + const record = records[index]; + if (index === 0) { // 累計流通額 + return `${record.prefix}${num.toLocaleString()}`; + } else if (index === 2) { // 処理 Tx + return num.toLocaleString(); + } + return num.toString(); + }; + + useEffect(() => { + // より確実なモバイル判定 + const checkMobile = () => { + const isMobileWidth = window.innerWidth <= 768; + const isMobileUserAgent = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent); + const isTouchDevice = 'ontouchstart' in window || navigator.maxTouchPoints > 0; + + // いずれかの条件が満たされればモバイルとして判定 + setIsMobile(isMobileWidth || isMobileUserAgent || isTouchDevice); + }; + + checkMobile(); + window.addEventListener('resize', checkMobile); + + const observer = new IntersectionObserver( + ([entry]) => { + if (entry.isIntersecting && !isVisible) { + setIsVisible(true); + // アニメーション開始(少し遅延を付けて順次実行) + records.forEach((record, index) => { + setTimeout(() => { + animateNumber(record.numericValue, index); + }, index * 300); + }); + } + }, + { threshold: 0.3 } + ); + + const element = document.getElementById('track-record'); + if (element) { + observer.observe(element); + } + + return () => { + window.removeEventListener('resize', checkMobile); + if (element) { + observer.unobserve(element); + } + // クリーンアップ + intervalRefs.current.forEach(interval => { + if (interval) clearInterval(interval); + }); + }; + }, []); // 依存配列から isVisible を削除 + + return ( +
+
+
+

+ トラックレコード +
+ (2024-06→現在) +

+
+ +
+ {records.map((record, index) => ( +
+
+ {record.icon} +
+ +
+

{record.metric}

+

+ {formatNumber(animatedNumbers[index], index)} + {record.suffix} +

+
+ +
+
+
+ + {/* パルスエフェクト(モバイルでは非表示) */} + {isVisible && !isMobile && ( +
+ )} +
+ ))} +
+ +
+

+ 💡 注:実データは on-chain から自動取得し動的更新に。 +

+
+
+
+ ); +} + +const sectionStyle: React.CSSProperties = { + padding: '80px 20px', + background: 'linear-gradient(135deg, #0f172a 0%, #1e293b 50%, #334155 100%)', + color: 'white', + position: 'relative', + overflow: 'hidden' +}; + +const containerStyle: React.CSSProperties = { + maxWidth: '1200px', + margin: '0 auto', + width: '100%' +}; + +const headerStyle: React.CSSProperties = { + textAlign: 'center', + marginBottom: '60px' +}; + +const titleStyle: React.CSSProperties = { + fontSize: 'clamp(2rem, 4vw, 2.8rem)', + fontWeight: '700', + margin: 0, + lineHeight: 1.2 +}; + +const highlightStyle: React.CSSProperties = { + background: 'linear-gradient(120deg, #10b981, #3b82f6)', + WebkitBackgroundClip: 'text', + WebkitTextFillColor: 'transparent', + backgroundClip: 'text' +}; + +const periodStyle: React.CSSProperties = { + fontSize: '1.2rem', + fontWeight: '400', + color: 'rgba(255, 255, 255, 0.8)' +}; + +const gridStyle: React.CSSProperties = { + display: 'grid', + gridTemplateColumns: 'repeat(auto-fit, minmax(280px, 1fr))', + gap: '40px', + maxWidth: '1000px', + margin: '0 auto', + marginBottom: '60px' +}; + +const cardStyle: React.CSSProperties = { + padding: '40px', + borderRadius: '24px', + background: 'rgba(255, 255, 255, 0.05)', + border: '2px solid', + backdropFilter: 'blur(10px)', + boxShadow: '0 20px 60px rgba(0, 0, 0, 0.3)', + transition: 'all 0.6s cubic-bezier(0.4, 0, 0.2, 1)', + position: 'relative', + overflow: 'hidden', + opacity: 0, + transform: 'translateY(50px) scale(0.9)' +}; + +const iconContainerStyle: React.CSSProperties = { + width: '70px', + height: '70px', + borderRadius: '18px', + border: '2px solid', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + marginBottom: '25px', + marginLeft: 'auto', + marginRight: 'auto' +}; + +const iconStyle: React.CSSProperties = { + fontSize: '2rem', + filter: 'drop-shadow(0 2px 4px rgba(0, 0, 0, 0.3))' +}; + +const contentStyle: React.CSSProperties = { + marginBottom: '25px' +}; + +const metricStyle: React.CSSProperties = { + fontSize: '1rem', + color: 'rgba(255, 255, 255, 0.8)', + margin: '0 0 15px 0', + fontWeight: '500', + letterSpacing: '0.05em' +}; + +const valueStyle: React.CSSProperties = { + fontSize: 'clamp(1.8rem, 4vw, 2.2rem)', + fontWeight: '700', + margin: 0, + lineHeight: 1.2 +}; + +const suffixStyle: React.CSSProperties = { + fontSize: '0.7em', + fontWeight: '400', + opacity: 0.8 +}; + +const progressBarStyle: React.CSSProperties = { + height: '6px', + borderRadius: '3px', + overflow: 'hidden' +}; + +const progressFillStyle: React.CSSProperties = { + height: '100%', + borderRadius: '3px', + transition: 'width 1s cubic-bezier(0.4, 0, 0.2, 1)' +}; + +const pulseStyle: React.CSSProperties = { + position: 'absolute', + top: '50%', + left: '50%', + width: '300px', + height: '300px', + borderRadius: '50%', + transform: 'translate(-50%, -50%)', + pointerEvents: 'none', + zIndex: -1 +}; + +const noteContainerStyle: React.CSSProperties = { + textAlign: 'center', + padding: '30px', + borderRadius: '16px', + background: 'rgba(255, 255, 255, 0.05)', + border: '1px solid rgba(255, 255, 255, 0.1)', + backdropFilter: 'blur(10px)' +}; + +const noteStyle: React.CSSProperties = { + fontSize: '1rem', + color: 'rgba(255, 255, 255, 0.7)', + margin: 0, + fontStyle: 'italic' +}; \ No newline at end of file diff --git a/src/components/organisms/UseCaseLogos.tsx b/src/components/organisms/UseCaseLogos.tsx new file mode 100644 index 0000000..b5a285b --- /dev/null +++ b/src/components/organisms/UseCaseLogos.tsx @@ -0,0 +1,40 @@ +'use client'; + +import React from 'react'; + +export default function UseCaseLogos() { + const useCases = [ + { name: "Comoris", logoPath: "/logo/comoris-logo.png" }, + { name: "塩尻DAO", logoPath: "/logo/shiojiridao-kogo.webp" }, + { name: "ento", logoPath: "/logo/ETHTokyoLogoBlack.png" }, + { name: "ETH Tokyo", logoPath: "/logo/ETHTokyoLogoBlack.png" }, + { name: "Fracton", logoPath: "/logo/fracton-logo.png" }, + { name: "Localcoop", logoPath: "/logo/localcoop-logo.png" }, + ]; + + // 2セット作成でシームレスループ + const marqueeItems = [...useCases, ...useCases]; + + return ( +
+
+ {marqueeItems.map((useCase, index) => ( +
+ + {useCase.name} + + {useCase.logoPath && ( + {`${useCase.name} + )} +
+ ))} +
+
+ ); +} \ No newline at end of file diff --git a/src/components/organisms/UseCases.tsx b/src/components/organisms/UseCases.tsx new file mode 100644 index 0000000..09732fb --- /dev/null +++ b/src/components/organisms/UseCases.tsx @@ -0,0 +1,501 @@ +'use client'; + +import React, { useState, useEffect } from 'react'; + +export default function UseCases() { + const [isVisible, setIsVisible] = useState(false); + const [hoveredCard, setHoveredCard] = useState(null); + const [selectedCard, setSelectedCard] = useState(null); + + const cases = [ + { + title: '自治体の課長さんへ', + subtitle: '(地域振興 / 住民参加施策)', + icon: '🏛️', + color: '#3b82f6', + gradient: 'linear-gradient(135deg,rgb(95, 140, 212), #1d4ed8)', + challenges: [ + 'ボランティア活動の参加率が伸びない', + '「誰がどれくらい活動しているか」を把握できない', + '助成金や成果報告に必要なデータ収集が手間' + ], + solutions: [ + '活動参加をアシストクレジットで即時記録', + '住民の貢献を見える化して、モチベーション向上', + 'データが蓄積されるので、助成金申請・行政報告に活用可能' + ], + value: '透明で持続可能な「住民参加型まちづくり」を実現できる' + }, + { + title: '企業の担当者さんへ', + subtitle: '(新規事業 / コミュニティマーケティング)', + icon: '🏢', + color: '#10b981', + gradient: 'linear-gradient(135deg, #10b981, #047857)', + challenges: [ + '社内外コミュニティでの貢献が定量化できない', + 'コミュニティ施策のROIを上司や経営陣に説明しにくい', + 'インセンティブやリワードが不公平になりがち' + ], + solutions: [ + '定量的な貢献ログをダッシュボードで可視化', + 'スマートコントラクトで公平な報酬分配', + 'API連携で既存マーケティングシステムにデータ統合' + ], + value: '「成果が見える」コミュニティ施策として社内説明・承認が取りやすくなる' + }, + { + title: '地域コミュニティマネージャーさんへ', + subtitle: '(NPO・団体運営者)', + icon: '🤝', + color: '#f59e0b', + gradient: 'linear-gradient(135deg, #f59e0b, #d97706)', + challenges: [ + '会員やボランティアの参加状況が曖昧', + '貢献に対して感謝やリワードをどう設計するか悩む', + '活動の継続性が不安定' + ], + solutions: [ + '役割NFTでメンバーの役割を明確化', + '貢献量に応じた自動報酬で不公平感を減らす', + '活動ログを継続的に蓄積し、信頼の見える化' + ], + value: '貢献が「忘れられない・不公平にならない」仕組みで、持続的なコミュニティ運営が可能' + }, + { + title: 'コミュニティ創設者さん・\n運営者さんへ', + subtitle: '', + icon: '💻', + color: '#8b5cf6', + gradient: 'linear-gradient(135deg,rgb(149, 128, 196), #7c3aed)', + challenges: [ + '仲間の貢献が可視化されにくい', + '金銭的な報酬やインセンティブ設計が難しい', + '運営の負担が集中しやすい' + ], + solutions: [ + '役割NFTとアシストクレジットで「誰がどのくらい貢献したか」を明確化', + '貢献に応じて報酬を自動分配、透明性を確保', + '活動記録を残せるため、次の協力者を巻き込みやすい' + ], + value: '信頼できる仕組みで、コミュニティをゼロからでも安心して立ち上げられる' + } + ]; + + useEffect(() => { + const observer = new IntersectionObserver( + ([entry]) => { + if (entry.isIntersecting) { + setIsVisible(true); + } + }, + { threshold: 0.3 } + ); + + const element = document.getElementById('use-cases'); + if (element) { + observer.observe(element); + } + + return () => { + if (element) { + observer.unobserve(element); + } + }; + }, []); + + return ( +
+
+
+

+ あなたの現場でも、
+ すぐに役立ちます +

+

+ 4つのターゲット別に、Tobanがどのように課題を解決するかをご紹介します +

+
+ +
+ {cases.map((item, index) => ( +
setHoveredCard(index)} + onMouseLeave={() => setHoveredCard(null)} + onClick={() => setSelectedCard(selectedCard === index ? null : index)} + > +
+ {item.icon} +
+ +
+

+ {item.title.split('\n').map((line, i) => ( + + {i > 0 &&
} + {line} +
+ ))} +

+ {item.subtitle && ( +

+ {item.subtitle} +

+ )} + + {/* 常に表示されるプレビュー */} + {selectedCard !== index && ( +
+
+

+ 課題 +

+

+ {item.challenges[0]}など +

+
+
+ )} + + {/* 詳細表示 */} + {selectedCard === index && ( +
+
+

+ 課題 +

+
    + {item.challenges.map((challenge, i) => ( +
  • + + {challenge} +
  • + ))} +
+
+ +
+

+ Tobanが提供する解決策 +

+
    + {item.solutions.map((solution, i) => ( +
  • + + {solution} +
  • + ))} +
+
+ +
+

+ 価値 +

+

+ → {item.value} +

+
+
+ )} +
+ +
+ + {selectedCard === index ? '閉じる ↑' : '詳しく見る ↓'} + +
+ + {/* ホバー時の背景装飾 */} + {hoveredCard === index && ( +
+
+
+
+ )} +
+ ))} +
+
+
+ ); +} + +const sectionStyle: React.CSSProperties = { + padding: '80px 20px', + background: 'linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%)', + minHeight: '100vh', + display: 'flex', + alignItems: 'center' +}; + +const containerStyle: React.CSSProperties = { + maxWidth: '1400px', + margin: '0 auto', + width: '100%' +}; + +const headerStyle: React.CSSProperties = { + textAlign: 'center', + marginBottom: '60px' +}; + +const titleStyle: React.CSSProperties = { + fontSize: 'clamp(2rem, 4vw, 2.5rem)', + fontWeight: '700', + color: '#1e293b', + lineHeight: 1.3, + margin: 0 +}; + +const subtitleStyle: React.CSSProperties = { + fontSize: '1.1rem', + color: '#64748b', + marginTop: '20px', + lineHeight: 1.6, + maxWidth: '600px', + margin: '20px auto 0 auto' +}; + +const highlightStyle: React.CSSProperties = { + background: 'linear-gradient(120deg, #3b82f6, #8b5cf6)', + WebkitBackgroundClip: 'text', + WebkitTextFillColor: 'transparent', + backgroundClip: 'text' +}; + +const gridStyle: React.CSSProperties = { + display: 'grid', + gridTemplateColumns: 'repeat(auto-fit, minmax(350px, 1fr))', + gap: '30px', + maxWidth: '1200px', + margin: '0 auto' +}; + +const cardStyle: React.CSSProperties = { + padding: '30px', + borderRadius: '20px', + backgroundColor: 'white', + border: '1px solid #e2e8f0', + boxShadow: '0 10px 40px rgba(0, 0, 0, 0.1)', + transition: 'all 0.4s cubic-bezier(0.4, 0, 0.2, 1)', + cursor: 'pointer', + position: 'relative', + overflow: 'hidden', + opacity: 0, + transform: 'translateY(30px)', + display: 'flex', + flexDirection: 'column' +}; + +const iconContainerStyle: React.CSSProperties = { + width: '70px', + height: '70px', + borderRadius: '16px', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + marginBottom: '20px', + marginLeft: 'auto', + marginRight: 'auto', + border: '2px solid', + transition: 'all 0.3s ease' +}; + +const iconStyle: React.CSSProperties = { + fontSize: '2rem', + filter: 'drop-shadow(0 2px 4px rgba(0, 0, 0, 0.1))' +}; + +const contentStyle: React.CSSProperties = { + flex: 1, + marginBottom: '20px' +}; + +const cardTitleStyle: React.CSSProperties = { + fontSize: '1.4rem', + fontWeight: '700', + margin: '0 0 8px 0', + lineHeight: 1.2, + transition: 'color 0.3s ease' +}; + +const subtitleCardStyle: React.CSSProperties = { + fontSize: '0.9rem', + margin: '0 0 20px 0', + lineHeight: 1.4, + fontWeight: '500', + transition: 'color 0.3s ease' +}; + +const previewStyle: React.CSSProperties = { + marginTop: '15px' +}; + +const previewSectionStyle: React.CSSProperties = { + marginBottom: '15px' +}; + +const sectionTitleStyle: React.CSSProperties = { + fontSize: '1rem', + fontWeight: '600', + margin: '0 0 10px 0', + textTransform: 'uppercase', + letterSpacing: '0.5px', + transition: 'color 0.3s ease' +}; + +const previewTextStyle: React.CSSProperties = { + fontSize: '0.95rem', + lineHeight: 1.5, + margin: 0, + transition: 'color 0.3s ease' +}; + +const detailStyle: React.CSSProperties = { + marginTop: '15px' +}; + +const detailSectionStyle: React.CSSProperties = { + marginBottom: '25px' +}; + +const listStyle: React.CSSProperties = { + margin: '0', + paddingLeft: '0', + listStyle: 'none' +}; + +const listItemStyle: React.CSSProperties = { + fontSize: '0.95rem', + lineHeight: 1.6, + marginBottom: '8px', + paddingLeft: '20px', + position: 'relative', + transition: 'color 0.3s ease' +}; + +const valueContainerStyle: React.CSSProperties = { + padding: '20px', + borderRadius: '12px', + backgroundColor: 'rgba(59, 130, 246, 0.05)', + border: '1px solid rgba(59, 130, 246, 0.1)', + marginTop: '20px' +}; + +const valueTextStyle: React.CSSProperties = { + fontSize: '1rem', + fontWeight: '600', + lineHeight: 1.5, + margin: 0, + transition: 'color 0.3s ease' +}; + +const cardFooterStyle: React.CSSProperties = { + paddingTop: '20px', + borderTop: '1px solid', + transition: 'border-color 0.3s ease', + textAlign: 'center' +}; + +const learnMoreStyle: React.CSSProperties = { + fontSize: '0.9rem', + fontWeight: '600', + transition: 'color 0.3s ease' +}; + +const decorationStyle: React.CSSProperties = { + position: 'absolute', + top: 0, + left: 0, + right: 0, + bottom: 0, + pointerEvents: 'none', + overflow: 'hidden' +}; + +const decoration1Style: React.CSSProperties = { + position: 'absolute', + top: '-50%', + right: '-20%', + width: '100px', + height: '100px', + borderRadius: '50%', + backgroundColor: 'rgba(255, 255, 255, 0.1)', + animation: 'float 3s ease-in-out infinite' +}; + +const decoration2Style: React.CSSProperties = { + position: 'absolute', + bottom: '-30%', + left: '-10%', + width: '60px', + height: '60px', + borderRadius: '50%', + backgroundColor: 'rgba(255, 255, 255, 0.1)', + animation: 'float 3s ease-in-out infinite reverse' +}; \ No newline at end of file From 8f4b84cfa5469e7a1cb1fde128ecfdc14bb5a74d Mon Sep 17 00:00:00 2001 From: yosuke Date: Tue, 19 Aug 2025 20:31:51 +0900 Subject: [PATCH 02/31] add global.css and excute lint --- src/components/organisms/AwardsMedia.tsx | 426 ++++++------ src/components/organisms/CaseStudies.tsx | 444 ++++++------ src/components/organisms/Faq.tsx | 398 ++++++----- src/components/organisms/Features.tsx | 356 +++++----- src/components/organisms/Footer.tsx | 351 +++++----- src/components/organisms/GettingStarted.tsx | 478 ++++++------- src/components/organisms/HeroSection.tsx | 24 +- src/components/organisms/HowItWorks.tsx | 678 +++++++++++-------- src/components/organisms/InfiniteLoop.tsx | 222 +++--- src/components/organisms/Pricing.tsx | 64 +- src/components/organisms/ProblemSolution.tsx | 377 ++++++----- src/components/organisms/SecurityStack.tsx | 460 +++++++------ src/components/organisms/TrackRecord.tsx | 370 +++++----- src/components/organisms/UseCaseLogos.tsx | 19 +- src/components/organisms/UseCases.tsx | 590 +++++++++------- src/pages/_app.tsx | 1 + src/themes/styles/globals.css | 623 +++++++++++++++++ 17 files changed, 3488 insertions(+), 2393 deletions(-) create mode 100644 src/themes/styles/globals.css diff --git a/src/components/organisms/AwardsMedia.tsx b/src/components/organisms/AwardsMedia.tsx index 6314341..c8360c1 100644 --- a/src/components/organisms/AwardsMedia.tsx +++ b/src/components/organisms/AwardsMedia.tsx @@ -1,6 +1,7 @@ -'use client'; +"use client"; -import React, { useState, useEffect } from 'react'; +import type React from "react"; +import { useState, useEffect } from "react"; export default function AwardsMedia() { const [isVisible, setIsVisible] = useState(false); @@ -8,60 +9,80 @@ export default function AwardsMedia() { const awards = [ { - title: 'ETH Tokyo 2024 Finalist', - subtitle: '(Toban プロトタイプ)', - date: '2024年4月', - type: 'award', - icon: '🏆', - color: '#ff6b6b', + id: "eth-tokyo-2024", + title: "ETH Tokyo 2024 Finalist", + subtitle: "(Toban プロトタイプ)", + date: "2024年4月", + type: "award", + icon: "🏆", + color: "#ff6b6b", links: [ - { url: 'https://www.linkedin.com/posts/haruki-kondo-517073204_selected-as-a-finalist-for-eth-tokyo-2024-activity-7233684980688642048-GJIh?utm_source=chatgpt.com', text: 'linkedin.com' } - ] + { + url: "https://www.linkedin.com/posts/haruki-kondo-517073204_selected-as-a-finalist-for-eth-tokyo-2024-activity-7233684980688642048-GJIh?utm_source=chatgpt.com", + text: "linkedin.com", + }, + ], }, { - title: 'Agentic Ethereum 2025', - subtitle: 'ハッカソン参加', - date: '2025年1月', - type: 'event', - icon: '🚀', - color: '#4ecdc4', + id: "agentic-ethereum-2025", + title: "Agentic Ethereum 2025", + subtitle: "ハッカソン参加", + date: "2025年1月", + type: "event", + icon: "🚀", + color: "#4ecdc4", links: [ - { url: 'https://ethglobal.com/events/agents?utm_source=chatgpt.com', text: 'ethglobal.com' } - ] + { + url: "https://ethglobal.com/events/agents?utm_source=chatgpt.com", + text: "ethglobal.com", + }, + ], }, { - title: 'Fracton Incubation 2024', - subtitle: '採択', - date: '2024年6月', - type: 'program', - icon: '🌱', - color: '#45b7d1', + id: "fracton-incubation-2024", + title: "Fracton Incubation 2024", + subtitle: "採択", + date: "2024年6月", + type: "program", + icon: "🌱", + color: "#45b7d1", links: [ - { url: 'https://technode.global/2023/03/22/fracton-ventures-shaping-the-web3-landscape-through-strategic-incubation-and-global-collaboration-qa/?utm_source=chatgpt.com', text: 'technode.global' } - ] + { + url: "https://technode.global/2023/03/22/fracton-ventures-shaping-the-web3-landscape-through-strategic-incubation-and-global-collaboration-qa/?utm_source=chatgpt.com", + text: "technode.global", + }, + ], }, { - title: 'ETHTaipei 2025', - subtitle: 'スピーカー登壇', - date: '2025年1月', - type: 'speaking', - icon: '🎤', - color: '#8b5cf6', + id: "ethtaipei-2025", + title: "ETHTaipei 2025", + subtitle: "スピーカー登壇", + date: "2025年1月", + type: "speaking", + icon: "🎤", + color: "#8b5cf6", links: [ - { url: 'https://www.dlnews.com/research/internal/ethtaipei-2025-partners-with-ethglobal-vitalik-buterin/?utm_source=chatgpt.com', text: 'ethtaipei.org' } - ] + { + url: "https://www.dlnews.com/research/internal/ethtaipei-2025-partners-with-ethglobal-vitalik-buterin/?utm_source=chatgpt.com", + text: "ethtaipei.org", + }, + ], }, { + id: "weekly-gm-joichi-ito", title: '週刊番組 "weekly gm" 出演', - subtitle: '(Joichi Ito YouTube)', - date: '2024年12月', - type: 'media', - icon: '📺', - color: '#f59e0b', + subtitle: "(Joichi Ito YouTube)", + date: "2024年12月", + type: "media", + icon: "📺", + color: "#f59e0b", links: [ - { url: 'https://dalab.xyz/en/project/weekly-gm/?utm_source=chatgpt.com', text: 'dalab.xyz' } - ] - } + { + url: "https://dalab.xyz/en/project/weekly-gm/?utm_source=chatgpt.com", + text: "dalab.xyz", + }, + ], + }, ]; useEffect(() => { @@ -71,10 +92,10 @@ export default function AwardsMedia() { setIsVisible(true); } }, - { threshold: 0.3 } + { threshold: 0.3 }, ); - const element = document.getElementById('awards-media'); + const element = document.getElementById("awards-media"); if (element) { observer.observe(element); } @@ -88,11 +109,11 @@ export default function AwardsMedia() { const getTypeLabel = (type: string) => { const typeMap: { [key: string]: string } = { - award: '受賞', - event: 'イベント', - program: 'プログラム', - speaking: '講演', - media: 'メディア' + award: "受賞", + event: "イベント", + program: "プログラム", + speaking: "講演", + media: "メディア", }; return typeMap[type] || type; }; @@ -112,15 +133,15 @@ export default function AwardsMedia() {
{/* タイムライン軸 */}
- + {awards.map((award, index) => ( -
setActiveItem(index)} onMouseLeave={() => setActiveItem(null)} @@ -128,76 +149,94 @@ export default function AwardsMedia() { onTouchEnd={() => setTimeout(() => setActiveItem(null), 2000)} > {/* タイムライン ドット */} -
+
{award.icon}
{/* コンテンツカード */} -
+
{/* ヘッダー */}
-
+
{getTypeLabel(award.type)}
-
{award.date}
+
+ {award.date} +
{/* タイトル */} -

+

{award.title}

-

{award.subtitle}

+

+ {award.subtitle} +

{/* リンク */}
{award.links.map((link, linkIndex) => ( - { e.currentTarget.style.backgroundColor = award.color; - e.currentTarget.style.color = 'white'; + e.currentTarget.style.color = "white"; }} onMouseLeave={(e) => { - e.currentTarget.style.backgroundColor = 'transparent'; + e.currentTarget.style.backgroundColor = "transparent"; e.currentTarget.style.color = award.color; }} > @@ -216,158 +255,159 @@ export default function AwardsMedia() { } const sectionStyle: React.CSSProperties = { - padding: '80px 20px', - background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)', - color: 'white', - position: 'relative', - overflow: 'hidden' + padding: "80px 20px", + background: "linear-gradient(135deg, #667eea 0%, #764ba2 100%)", + color: "white", + position: "relative", + overflow: "hidden", }; const containerStyle: React.CSSProperties = { - maxWidth: '1000px', - margin: '0 auto', - width: '100%' + maxWidth: "1000px", + margin: "0 auto", + width: "100%", }; const headerStyle: React.CSSProperties = { - textAlign: 'center', - marginBottom: '60px' + textAlign: "center", + marginBottom: "60px", }; const titleStyle: React.CSSProperties = { - fontSize: 'clamp(2rem, 4vw, 2.8rem)', - fontWeight: '700', - margin: '0 0 15px 0', - lineHeight: 1.2 + fontSize: "clamp(2rem, 4vw, 2.8rem)", + fontWeight: "700", + margin: "0 0 15px 0", + lineHeight: 1.2, }; const highlightStyle: React.CSSProperties = { - background: 'linear-gradient(120deg, #ff6b6b, #4ecdc4)', - WebkitBackgroundClip: 'text', - WebkitTextFillColor: 'transparent', - backgroundClip: 'text' + background: "linear-gradient(120deg, #ff6b6b, #4ecdc4)", + WebkitBackgroundClip: "text", + WebkitTextFillColor: "transparent", + backgroundClip: "text", }; const subtitleStyle: React.CSSProperties = { - fontSize: '1.1rem', - color: 'rgba(255, 255, 255, 0.8)', + fontSize: "1.1rem", + color: "rgba(255, 255, 255, 0.8)", margin: 0, - fontWeight: '400' + fontWeight: "400", }; const timelineStyle: React.CSSProperties = { - position: 'relative', - paddingLeft: '60px' + position: "relative", + paddingLeft: "60px", }; const timelineLineStyle: React.CSSProperties = { - position: 'absolute', - left: '30px', - top: '40px', - bottom: '40px', - width: '4px', - background: 'linear-gradient(180deg, rgba(255, 255, 255, 0.3), rgba(255, 255, 255, 0.1))', - borderRadius: '2px' + position: "absolute", + left: "30px", + top: "40px", + bottom: "40px", + width: "4px", + background: + "linear-gradient(180deg, rgba(255, 255, 255, 0.3), rgba(255, 255, 255, 0.1))", + borderRadius: "2px", }; const timelineItemStyle: React.CSSProperties = { - position: 'relative', - marginBottom: '40px', - display: 'flex', - alignItems: 'flex-start', + position: "relative", + marginBottom: "40px", + display: "flex", + alignItems: "flex-start", opacity: 0, - transform: 'translateX(-50px)', - transition: 'all 0.6s cubic-bezier(0.4, 0, 0.2, 1)' + transform: "translateX(-50px)", + transition: "all 0.6s cubic-bezier(0.4, 0, 0.2, 1)", }; const timelineDotStyle: React.CSSProperties = { - position: 'absolute', - left: '-45px', - top: '20px', - width: '30px', - height: '30px', - borderRadius: '50%', - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - transition: 'all 0.3s ease', - zIndex: 2 + position: "absolute", + left: "-45px", + top: "20px", + width: "30px", + height: "30px", + borderRadius: "50%", + display: "flex", + alignItems: "center", + justifyContent: "center", + transition: "all 0.3s ease", + zIndex: 2, }; const dotIconStyle: React.CSSProperties = { - fontSize: '0.9rem', - filter: 'drop-shadow(0 2px 4px rgba(0, 0, 0, 0.3))' + fontSize: "0.9rem", + filter: "drop-shadow(0 2px 4px rgba(0, 0, 0, 0.3))", }; const cardStyle: React.CSSProperties = { - backgroundColor: 'white', - borderRadius: '16px', - padding: '30px', - borderLeft: '4px solid', - boxShadow: '0 10px 40px rgba(0, 0, 0, 0.1)', - transition: 'all 0.4s cubic-bezier(0.4, 0, 0.2, 1)', - cursor: 'pointer', - width: '100%', - marginLeft: '20px' + backgroundColor: "white", + borderRadius: "16px", + padding: "30px", + borderLeft: "4px solid", + boxShadow: "0 10px 40px rgba(0, 0, 0, 0.1)", + transition: "all 0.4s cubic-bezier(0.4, 0, 0.2, 1)", + cursor: "pointer", + width: "100%", + marginLeft: "20px", }; const cardHeaderStyle: React.CSSProperties = { - display: 'flex', - justifyContent: 'space-between', - alignItems: 'center', - marginBottom: '20px' + display: "flex", + justifyContent: "space-between", + alignItems: "center", + marginBottom: "20px", }; const typeBadgeStyle: React.CSSProperties = { - padding: '6px 12px', - borderRadius: '12px', - fontSize: '0.8rem', - fontWeight: '600', - textTransform: 'uppercase', - letterSpacing: '0.05em' + padding: "6px 12px", + borderRadius: "12px", + fontSize: "0.8rem", + fontWeight: "600", + textTransform: "uppercase", + letterSpacing: "0.05em", }; const dateStyle: React.CSSProperties = { - fontSize: '0.9rem', - color: '#64748b', - fontWeight: '500' + fontSize: "0.9rem", + color: "#64748b", + fontWeight: "500", }; const awardTitleStyle: React.CSSProperties = { - fontSize: '1.3rem', - fontWeight: '600', - margin: '0 0 8px 0', + fontSize: "1.3rem", + fontWeight: "600", + margin: "0 0 8px 0", lineHeight: 1.3, - transition: 'color 0.3s ease' + transition: "color 0.3s ease", }; const awardSubtitleStyle: React.CSSProperties = { - fontSize: '1rem', - color: '#64748b', - margin: '0 0 20px 0', - lineHeight: 1.4 + fontSize: "1rem", + color: "#64748b", + margin: "0 0 20px 0", + lineHeight: 1.4, }; const linksContainerStyle: React.CSSProperties = { - display: 'flex', - gap: '12px', - flexWrap: 'wrap' + display: "flex", + gap: "12px", + flexWrap: "wrap", }; const linkButtonStyle: React.CSSProperties = { - display: 'inline-flex', - alignItems: 'center', - gap: '8px', - padding: '10px 16px', - borderRadius: '8px', - border: '2px solid', - fontSize: '0.9rem', - fontWeight: '600', - textDecoration: 'none', - transition: 'all 0.3s ease', - backgroundColor: 'transparent' + display: "inline-flex", + alignItems: "center", + gap: "8px", + padding: "10px 16px", + borderRadius: "8px", + border: "2px solid", + fontSize: "0.9rem", + fontWeight: "600", + textDecoration: "none", + transition: "all 0.3s ease", + backgroundColor: "transparent", }; const linkIconStyle: React.CSSProperties = { - fontSize: '0.8rem' -}; \ No newline at end of file + fontSize: "0.8rem", +}; diff --git a/src/components/organisms/CaseStudies.tsx b/src/components/organisms/CaseStudies.tsx index 8d3c3e5..a73400f 100644 --- a/src/components/organisms/CaseStudies.tsx +++ b/src/components/organisms/CaseStudies.tsx @@ -1,7 +1,8 @@ -'use client'; +"use client"; -import React, { useState, useEffect } from 'react'; -import Image from 'next/image'; +import type React from "react"; +import { useState, useEffect } from "react"; +import Image from "next/image"; export default function CaseStudies() { const [isVisible, setIsVisible] = useState(false); @@ -9,65 +10,67 @@ export default function CaseStudies() { const cases = [ { - name: 'Comoris', - logoSrc: '/logo/comoris-logo.png', - description: '沖縄・コミュニティ DAO。子育て支援トークン導入', - link: 'Case Study →', - linkUrl: '#', - color: '#ff6b6b', - category: 'コミュニティDAO', - status: 'active' + name: "Comoris", + logoSrc: "/logo/comoris-logo.png", + description: "沖縄・コミュニティ DAO。子育て支援トークン導入", + link: "Case Study →", + linkUrl: "#", + color: "#ff6b6b", + category: "コミュニティDAO", + status: "active", }, { - name: '塩尻DAO', - logoSrc: '/logo/shiojiridao-kogo.webp', - description: '長野県・地域活性 DAO。農業バウンティを自動分配', - link: 'Interview →', - linkUrl: '#', - color: '#4ecdc4', - category: '地域DAO', - status: 'active' + name: "塩尻DAO", + logoSrc: "/logo/shiojiridao-kogo.webp", + description: "長野県・地域活性 DAO。農業バウンティを自動分配", + link: "Interview →", + linkUrl: "#", + color: "#4ecdc4", + category: "地域DAO", + status: "active", }, { - name: 'ento', + id: "ento", + name: "ento", logoSrc: null, - description: 'オープンソース気象データ連携', - link: 'Coming soon', + description: "オープンソース気象データ連携", + link: "Coming soon", linkUrl: null, - color: '#45b7d1', - category: 'オープンソース', - status: 'coming' + color: "#45b7d1", + category: "オープンソース", + status: "coming", }, { - name: 'ETH Tokyo', - logoSrc: '/logo/ETHTokyoLogoBlack.png', - description: 'ハッカソン採択プロジェクト', - link: 'Highlights →', - linkUrl: '#', - color: '#8b5cf6', - category: 'ハッカソン', - status: 'active' + id: "eth-tokyo", + name: "ETH Tokyo", + logoSrc: "/logo/ETHTokyoLogoBlack.png", + description: "ハッカソン採択プロジェクト", + link: "Highlights →", + linkUrl: "#", + color: "#8b5cf6", + category: "ハッカソン", + status: "active", }, { - name: 'Fracton', - logoSrc: '/logo/fracton-logo.png', - description: 'インキュベーション支援', - link: 'Article →', - linkUrl: '#', - color: '#f59e0b', - category: 'インキュベーター', - status: 'active' + name: "Fracton", + logoSrc: "/logo/fracton-logo.png", + description: "インキュベーション支援", + link: "Article →", + linkUrl: "#", + color: "#f59e0b", + category: "インキュベーター", + status: "active", }, { - name: 'Localcoop', - logoSrc: '/logo/localcoop-logo.png', - description: '助け合いプラットフォーム', - link: 'Coming soon', + name: "Localcoop", + logoSrc: "/logo/localcoop-logo.png", + description: "助け合いプラットフォーム", + link: "Coming soon", linkUrl: null, - color: '#10b981', - category: 'プラットフォーム', - status: 'coming' - } + color: "#10b981", + category: "プラットフォーム", + status: "coming", + }, ]; useEffect(() => { @@ -77,10 +80,10 @@ export default function CaseStudies() { setIsVisible(true); } }, - { threshold: 0.1, rootMargin: '50px' } + { threshold: 0.1, rootMargin: "50px" }, ); - const element = document.getElementById('case-studies'); + const element = document.getElementById("case-studies"); if (element) { observer.observe(element); } @@ -106,62 +109,74 @@ export default function CaseStudies() {
{cases.map((caseItem, index) => ( -
setHoveredCard(index)} onMouseLeave={() => setHoveredCard(null)} > {/* ステータスバッジ */} -
- {caseItem.status === 'active' ? '運用中' : '近日公開'} +
+ {caseItem.status === "active" ? "運用中" : "近日公開"}
{/* カテゴリー */} -
+
{caseItem.category}
{/* ロゴセクション */} -
+
{caseItem.logoSrc ? ( - {`${caseItem.name} ) : ( -
+
{caseItem.name[0]}
)} @@ -169,41 +184,50 @@ export default function CaseStudies() { {/* コンテンツ */}
-

+

{caseItem.name}

-

+

{caseItem.description}

{/* フッター */} -
+
{caseItem.linkUrl ? ( - {caseItem.link} ) : ( - + {caseItem.link} )} @@ -211,10 +235,12 @@ export default function CaseStudies() { {/* ホバー時のアクセント */} {hoveredCard === index && ( -
+
)}
))} @@ -225,170 +251,170 @@ export default function CaseStudies() { } const sectionStyle: React.CSSProperties = { - padding: '80px 20px', - background: 'linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%)', - minHeight: '100vh', - display: 'flex', - alignItems: 'center' + padding: "80px 20px", + background: "linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%)", + minHeight: "100vh", + display: "flex", + alignItems: "center", }; const containerStyle: React.CSSProperties = { - maxWidth: '1200px', - margin: '0 auto', - width: '100%' + maxWidth: "1200px", + margin: "0 auto", + width: "100%", }; const headerStyle: React.CSSProperties = { - textAlign: 'center', - marginBottom: '60px' + textAlign: "center", + marginBottom: "60px", }; const titleStyle: React.CSSProperties = { - fontSize: 'clamp(2rem, 4vw, 2.5rem)', - fontWeight: '700', - color: '#1e293b', - margin: '0 0 15px 0', - lineHeight: 1.2 + fontSize: "clamp(2rem, 4vw, 2.5rem)", + fontWeight: "700", + color: "#1e293b", + margin: "0 0 15px 0", + lineHeight: 1.2, }; const highlightStyle: React.CSSProperties = { - background: 'linear-gradient(120deg, #ff6b6b, #4ecdc4)', - WebkitBackgroundClip: 'text', - WebkitTextFillColor: 'transparent', - backgroundClip: 'text' + background: "linear-gradient(120deg, #ff6b6b, #4ecdc4)", + WebkitBackgroundClip: "text", + WebkitTextFillColor: "transparent", + backgroundClip: "text", }; const subtitleStyle: React.CSSProperties = { - fontSize: '1.1rem', - color: '#64748b', + fontSize: "1.1rem", + color: "#64748b", margin: 0, - fontWeight: '400' + fontWeight: "400", }; const gridStyle: React.CSSProperties = { - display: 'grid', - gridTemplateColumns: 'repeat(auto-fit, minmax(280px, 1fr))', - gap: 'clamp(20px, 4vw, 30px)', - maxWidth: '1100px', - margin: '0 auto' + display: "grid", + gridTemplateColumns: "repeat(auto-fit, minmax(280px, 1fr))", + gap: "clamp(20px, 4vw, 30px)", + maxWidth: "1100px", + margin: "0 auto", }; const cardStyle: React.CSSProperties = { - padding: 'clamp(20px, 4vw, 30px)', - borderRadius: '20px', - backgroundColor: 'white', - border: '1px solid #e2e8f0', - boxShadow: '0 10px 40px rgba(0, 0, 0, 0.1)', - transition: 'all 0.4s cubic-bezier(0.4, 0, 0.2, 1)', - cursor: 'pointer', - position: 'relative', - overflow: 'hidden', + padding: "clamp(20px, 4vw, 30px)", + borderRadius: "20px", + backgroundColor: "white", + border: "1px solid #e2e8f0", + boxShadow: "0 10px 40px rgba(0, 0, 0, 0.1)", + transition: "all 0.4s cubic-bezier(0.4, 0, 0.2, 1)", + cursor: "pointer", + position: "relative", + overflow: "hidden", opacity: 0, - transform: 'translateY(20px)' + transform: "translateY(20px)", }; const statusBadgeStyle: React.CSSProperties = { - position: 'absolute', - top: '20px', - right: '20px', - padding: '4px 12px', - borderRadius: '12px', - fontSize: '0.75rem', - fontWeight: '600', - textTransform: 'uppercase', - letterSpacing: '0.05em' + position: "absolute", + top: "20px", + right: "20px", + padding: "4px 12px", + borderRadius: "12px", + fontSize: "0.75rem", + fontWeight: "600", + textTransform: "uppercase", + letterSpacing: "0.05em", }; const categoryStyle: React.CSSProperties = { - display: 'inline-block', - padding: '6px 12px', - borderRadius: '8px', - fontSize: '0.8rem', - fontWeight: '600', - marginBottom: '20px', - letterSpacing: '0.05em' + display: "inline-block", + padding: "6px 12px", + borderRadius: "8px", + fontSize: "0.8rem", + fontWeight: "600", + marginBottom: "20px", + letterSpacing: "0.05em", }; const logoSectionStyle: React.CSSProperties = { - width: '100px', - height: '100px', - borderRadius: '16px', - border: '2px solid', - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - marginBottom: '25px', - marginLeft: 'auto', - marginRight: 'auto', - transition: 'border-color 0.3s ease' + width: "100px", + height: "100px", + borderRadius: "16px", + border: "2px solid", + display: "flex", + alignItems: "center", + justifyContent: "center", + marginBottom: "25px", + marginLeft: "auto", + marginRight: "auto", + transition: "border-color 0.3s ease", }; const logoPlaceholderStyle: React.CSSProperties = { - width: '60px', - height: '60px', - borderRadius: '12px', - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - fontWeight: 'bold' + width: "60px", + height: "60px", + borderRadius: "12px", + display: "flex", + alignItems: "center", + justifyContent: "center", + fontWeight: "bold", }; const logoTextStyle: React.CSSProperties = { - fontSize: '1.5rem', - fontWeight: '700' + fontSize: "1.5rem", + fontWeight: "700", }; const contentSectionStyle: React.CSSProperties = { - marginBottom: '25px' + marginBottom: "25px", }; const nameStyle: React.CSSProperties = { - fontSize: '1.4rem', - fontWeight: '600', - margin: '0 0 15px 0', + fontSize: "1.4rem", + fontWeight: "600", + margin: "0 0 15px 0", lineHeight: 1.3, - transition: 'color 0.3s ease' + transition: "color 0.3s ease", }; const descriptionStyle: React.CSSProperties = { - fontSize: '1rem', + fontSize: "1rem", lineHeight: 1.6, margin: 0, - transition: 'color 0.3s ease' + transition: "color 0.3s ease", }; const footerStyle: React.CSSProperties = { - paddingTop: '20px', - borderTop: '1px solid', - transition: 'border-color 0.3s ease' + paddingTop: "20px", + borderTop: "1px solid", + transition: "border-color 0.3s ease", }; const linkStyle: React.CSSProperties = { - display: 'inline-block', - padding: '10px 20px', - borderRadius: '8px', - border: '2px solid', - fontSize: '0.9rem', - fontWeight: '600', - textDecoration: 'none', - transition: 'all 0.3s ease', - backgroundColor: 'transparent' + display: "inline-block", + padding: "10px 20px", + borderRadius: "8px", + border: "2px solid", + fontSize: "0.9rem", + fontWeight: "600", + textDecoration: "none", + transition: "all 0.3s ease", + backgroundColor: "transparent", }; const comingSoonStyle: React.CSSProperties = { - display: 'inline-block', - padding: '10px 20px', - borderRadius: '8px', - fontSize: '0.9rem', - fontWeight: '600', - fontStyle: 'italic' + display: "inline-block", + padding: "10px 20px", + borderRadius: "8px", + fontSize: "0.9rem", + fontWeight: "600", + fontStyle: "italic", }; const accentLineStyle: React.CSSProperties = { - position: 'absolute', + position: "absolute", top: 0, left: 0, - width: '4px', - height: '100%', - transition: 'all 0.3s ease' -}; \ No newline at end of file + width: "4px", + height: "100%", + transition: "all 0.3s ease", +}; diff --git a/src/components/organisms/Faq.tsx b/src/components/organisms/Faq.tsx index a69c379..91a55b4 100644 --- a/src/components/organisms/Faq.tsx +++ b/src/components/organisms/Faq.tsx @@ -1,6 +1,7 @@ -'use client'; +"use client"; -import React, { useState, useEffect } from 'react'; +import type React from "react"; +import { useState, useEffect } from "react"; export default function Faq() { const [isVisible, setIsVisible] = useState(false); @@ -8,47 +9,57 @@ export default function Faq() { const faqs = [ { - question: 'Web3の知識がなくても使えますか?', - answer: 'はい、ウォレットの作成手順も含めてサポートしています。', - detailedAnswer: 'Tobanは初心者でも簡単に使えるよう設計されています。ウォレットの作成から基本的な使い方まで、ステップバイステップのガイドを提供しており、Web3の専門知識がなくても安心してご利用いただけます。', - icon: '🔰', - color: '#3b82f6' + question: "Web3の知識がなくても使えますか?", + answer: "はい、ウォレットの作成手順も含めてサポートしています。", + detailedAnswer: + "Tobanは初心者でも簡単に使えるよう設計されています。ウォレットの作成から基本的な使い方まで、ステップバイステップのガイドを提供しており、Web3の専門知識がなくても安心してご利用いただけます。", + icon: "🔰", + color: "#3b82f6", }, { - question: '現在のツールと併用できますか?', - answer: 'Discord や GitHub との連携も可能です。', - detailedAnswer: 'Tobanは既存のワークフローを妨げることなく、DiscordボットやGitHub連携、Slack統合など、多様なツールとの連携機能を提供しています。APIも公開しているため、カスタム統合も可能です。', - icon: '🔗', - color: '#10b981' + question: "現在のツールと併用できますか?", + answer: "Discord や GitHub との連携も可能です。", + detailedAnswer: + "Tobanは既存のワークフローを妨げることなく、DiscordボットやGitHub連携、Slack統合など、多様なツールとの連携機能を提供しています。APIも公開しているため、カスタム統合も可能です。", + icon: "🔗", + color: "#10b981", }, { - question: '税務処理用の出力は?', - answer: 'ダッシュボードからCSV出力が可能です。', - detailedAnswer: 'すべての取引履歴と分配記録は、税務申告に必要な形式でCSV出力できます。日本の税制に対応した項目分けも行っており、税理士さんとの連携もスムーズです。', - icon: '📊', - color: '#f59e0b' + id: "tax-export", + question: "税務処理用の出力は?", + answer: "ダッシュボードからCSV出力が可能です。", + detailedAnswer: + "すべての取引履歴と分配記録は、税務申告に必要な形式でCSV出力できます。日本の税制に対応した項目分けも行っており、税理士さんとの連携もスムーズです。", + icon: "📊", + color: "#f59e0b", }, { - question: 'セキュリティはどのように確保されていますか?', - answer: 'すべてオープンソース、監査済みスマートコントラクトを使用。', - detailedAnswer: 'Tobanのコードはすべてオープンソースで公開されており、使用するスマートコントラクトは第三者機関による監査を受けています。また、秘密鍵の管理はユーザー自身が行うため、中央集権的なリスクがありません。', - icon: '🔒', - color: '#8b5cf6' + id: "security", + question: "セキュリティはどのように確保されていますか?", + answer: "すべてオープンソース、監査済みスマートコントラクトを使用。", + detailedAnswer: + "Tobanのコードはすべてオープンソースで公開されており、使用するスマートコントラクトは第三者機関による監査を受けています。また、秘密鍵の管理はユーザー自身が行うため、中央集権的なリスクがありません。", + icon: "🔒", + color: "#8b5cf6", }, { - question: '利用料金はかかりますか?', - answer: 'プラットフォーム利用は無料、ブロックチェーン手数料のみ。', - detailedAnswer: 'Toban自体の利用料金は一切かかりません。ただし、ブロックチェーン上での取引には少額のガス代(手数料)が発生します。これは一般的に数十円から数百円程度です。', - icon: '💰', - color: '#ef4444' + id: "pricing", + question: "利用料金はかかりますか?", + answer: "プラットフォーム利用は無料、ブロックチェーン手数料のみ。", + detailedAnswer: + "Toban自体の利用料金は一切かかりません。ただし、ブロックチェーン上での取引には少額のガス代(手数料)が発生します。これは一般的に数十円から数百円程度です。", + icon: "💰", + color: "#ef4444", }, { - question: 'コミュニティサポートはありますか?', - answer: 'Discordコミュニティで24/7サポートを提供。', - detailedAnswer: '活発なDiscordコミュニティがあり、ユーザー同士の助け合いや開発チームからの直接サポートを受けられます。また、定期的なオンラインワークショップや勉強会も開催しています。', - icon: '💬', - color: '#06b6d4' - } + id: "community-support", + question: "コミュニティサポートはありますか?", + answer: "Discordコミュニティで24/7サポートを提供。", + detailedAnswer: + "活発なDiscordコミュニティがあり、ユーザー同士の助け合いや開発チームからの直接サポートを受けられます。また、定期的なオンラインワークショップや勉強会も開催しています。", + icon: "💬", + color: "#06b6d4", + }, ]; useEffect(() => { @@ -58,10 +69,10 @@ export default function Faq() { setIsVisible(true); } }, - { threshold: 0.3 } + { threshold: 0.3 }, ); - const element = document.getElementById('faq'); + const element = document.getElementById("faq"); if (element) { observer.observe(element); } @@ -91,66 +102,79 @@ export default function Faq() {
{faqs.map((faq, index) => ( -
-
+

- A. + + A. + {faq.answer}

-

- {faq.detailedAnswer} -

+

{faq.detailedAnswer}

@@ -164,11 +188,11 @@ export default function Faq() { お気軽にお問い合わせください。コミュニティでお待ちしています。

- -
@@ -179,197 +203,197 @@ export default function Faq() { } const sectionStyle: React.CSSProperties = { - padding: '80px 20px', - background: 'linear-gradient(135deg, #1e293b 0%, #334155 100%)', - color: 'white', - minHeight: '100vh', - display: 'flex', - alignItems: 'center' + padding: "80px 20px", + background: "linear-gradient(135deg, #1e293b 0%, #334155 100%)", + color: "white", + minHeight: "100vh", + display: "flex", + alignItems: "center", }; const containerStyle: React.CSSProperties = { - maxWidth: '800px', - margin: '0 auto', - width: '100%' + maxWidth: "800px", + margin: "0 auto", + width: "100%", }; const headerStyle: React.CSSProperties = { - textAlign: 'center', - marginBottom: '60px' + textAlign: "center", + marginBottom: "60px", }; const titleStyle: React.CSSProperties = { - fontSize: 'clamp(2rem, 4vw, 2.8rem)', - fontWeight: '700', - margin: '0 0 15px 0', - lineHeight: 1.2 + fontSize: "clamp(2rem, 4vw, 2.8rem)", + fontWeight: "700", + margin: "0 0 15px 0", + lineHeight: 1.2, }; const highlightStyle: React.CSSProperties = { - background: 'linear-gradient(120deg, #3b82f6, #10b981)', - WebkitBackgroundClip: 'text', - WebkitTextFillColor: 'transparent', - backgroundClip: 'text' + background: "linear-gradient(120deg, #3b82f6, #10b981)", + WebkitBackgroundClip: "text", + WebkitTextFillColor: "transparent", + backgroundClip: "text", }; const subtitleStyle: React.CSSProperties = { - fontSize: '1.1rem', - color: 'rgba(255, 255, 255, 0.8)', + fontSize: "1.1rem", + color: "rgba(255, 255, 255, 0.8)", margin: 0, - fontWeight: '400' + fontWeight: "400", }; const faqContainerStyle: React.CSSProperties = { - marginBottom: '60px' + marginBottom: "60px", }; const faqItemStyle: React.CSSProperties = { - marginBottom: '20px', - borderRadius: '16px', - overflow: 'hidden', - backgroundColor: 'white', - boxShadow: '0 10px 40px rgba(0, 0, 0, 0.1)', + marginBottom: "20px", + borderRadius: "16px", + overflow: "hidden", + backgroundColor: "white", + boxShadow: "0 10px 40px rgba(0, 0, 0, 0.1)", opacity: 0, - transform: 'translateY(30px)', - transition: 'all 0.4s cubic-bezier(0.4, 0, 0.2, 1)' + transform: "translateY(30px)", + transition: "all 0.4s cubic-bezier(0.4, 0, 0.2, 1)", }; const questionButtonStyle: React.CSSProperties = { - width: '100%', - padding: '25px 30px', - border: '2px solid', - background: 'white', - cursor: 'pointer', - transition: 'all 0.3s ease', - borderRadius: 0 + width: "100%", + padding: "25px 30px", + border: "2px solid", + background: "white", + cursor: "pointer", + transition: "all 0.3s ease", + borderRadius: 0, }; const questionHeaderStyle: React.CSSProperties = { - display: 'flex', - alignItems: 'center', - gap: '20px' + display: "flex", + alignItems: "center", + gap: "20px", }; const iconWrapperStyle: React.CSSProperties = { - width: '50px', - height: '50px', - borderRadius: '12px', - border: '2px solid', - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - flexShrink: 0 + width: "50px", + height: "50px", + borderRadius: "12px", + border: "2px solid", + display: "flex", + alignItems: "center", + justifyContent: "center", + flexShrink: 0, }; const iconStyle: React.CSSProperties = { - fontSize: '1.5rem' + fontSize: "1.5rem", }; const questionTextStyle: React.CSSProperties = { - fontSize: '1.1rem', - fontWeight: '600', - textAlign: 'left', + fontSize: "1.1rem", + fontWeight: "600", + textAlign: "left", flex: 1, - transition: 'color 0.3s ease' + transition: "color 0.3s ease", }; const chevronStyle: React.CSSProperties = { - fontSize: '0.8rem', - transition: 'transform 0.3s ease, color 0.3s ease', - flexShrink: 0 + fontSize: "0.8rem", + transition: "transform 0.3s ease, color 0.3s ease", + flexShrink: 0, }; const answerContainerStyle: React.CSSProperties = { - overflow: 'hidden', - transition: 'all 0.4s cubic-bezier(0.4, 0, 0.2, 1)', - backgroundColor: '#f8fafc' + overflow: "hidden", + transition: "all 0.4s cubic-bezier(0.4, 0, 0.2, 1)", + backgroundColor: "#f8fafc", }; const answerContentStyle: React.CSSProperties = { - borderTop: '1px solid #e2e8f0' + borderTop: "1px solid #e2e8f0", }; const shortAnswerStyle: React.CSSProperties = { - fontSize: '1rem', - color: '#1e293b', - margin: '0 0 15px 0', - fontWeight: '600', - display: 'flex', - alignItems: 'flex-start', - gap: '10px' + fontSize: "1rem", + color: "#1e293b", + margin: "0 0 15px 0", + fontWeight: "600", + display: "flex", + alignItems: "flex-start", + gap: "10px", }; const answerIconStyle: React.CSSProperties = { - fontSize: '1rem', - fontWeight: 'bold', - flexShrink: 0 + fontSize: "1rem", + fontWeight: "bold", + flexShrink: 0, }; const detailedAnswerStyle: React.CSSProperties = { - fontSize: '0.95rem', - color: '#64748b', + fontSize: "0.95rem", + color: "#64748b", margin: 0, - lineHeight: 1.6 + lineHeight: 1.6, }; const supportSectionStyle: React.CSSProperties = { - textAlign: 'center', - padding: '50px 30px', - borderRadius: '20px', - background: 'rgba(255, 255, 255, 0.05)', - border: '1px solid rgba(255, 255, 255, 0.1)', - backdropFilter: 'blur(10px)' + textAlign: "center", + padding: "50px 30px", + borderRadius: "20px", + background: "rgba(255, 255, 255, 0.05)", + border: "1px solid rgba(255, 255, 255, 0.1)", + backdropFilter: "blur(10px)", }; const supportTitleStyle: React.CSSProperties = { - fontSize: '1.5rem', - fontWeight: '600', - margin: '0 0 15px 0', - color: 'white' + fontSize: "1.5rem", + fontWeight: "600", + margin: "0 0 15px 0", + color: "white", }; const supportDescriptionStyle: React.CSSProperties = { - fontSize: '1rem', - color: 'rgba(255, 255, 255, 0.8)', - margin: '0 0 30px 0', - lineHeight: 1.5 + fontSize: "1rem", + color: "rgba(255, 255, 255, 0.8)", + margin: "0 0 30px 0", + lineHeight: 1.5, }; const supportButtonsStyle: React.CSSProperties = { - display: 'flex', - gap: '20px', - justifyContent: 'center', - flexWrap: 'wrap' + display: "flex", + gap: "20px", + justifyContent: "center", + flexWrap: "wrap", }; const primarySupportButtonStyle: React.CSSProperties = { - display: 'inline-flex', - alignItems: 'center', - gap: '10px', - padding: '15px 30px', - borderRadius: '12px', - border: 'none', - fontSize: '1rem', - fontWeight: '600', - backgroundColor: '#3b82f6', - color: 'white', - cursor: 'pointer', - transition: 'all 0.3s ease', - boxShadow: '0 4px 20px rgba(59, 130, 246, 0.3)' + display: "inline-flex", + alignItems: "center", + gap: "10px", + padding: "15px 30px", + borderRadius: "12px", + border: "none", + fontSize: "1rem", + fontWeight: "600", + backgroundColor: "#3b82f6", + color: "white", + cursor: "pointer", + transition: "all 0.3s ease", + boxShadow: "0 4px 20px rgba(59, 130, 246, 0.3)", }; const secondarySupportButtonStyle: React.CSSProperties = { - padding: '15px 30px', - borderRadius: '12px', - border: '2px solid rgba(255, 255, 255, 0.3)', - fontSize: '1rem', - fontWeight: '600', - backgroundColor: 'transparent', - color: 'white', - cursor: 'pointer', - transition: 'all 0.3s ease' + padding: "15px 30px", + borderRadius: "12px", + border: "2px solid rgba(255, 255, 255, 0.3)", + fontSize: "1rem", + fontWeight: "600", + backgroundColor: "transparent", + color: "white", + cursor: "pointer", + transition: "all 0.3s ease", }; const supportButtonIconStyle: React.CSSProperties = { - fontSize: '1.1rem' -}; \ No newline at end of file + fontSize: "1.1rem", +}; diff --git a/src/components/organisms/Features.tsx b/src/components/organisms/Features.tsx index 3f0cc4d..28ff5fb 100644 --- a/src/components/organisms/Features.tsx +++ b/src/components/organisms/Features.tsx @@ -1,40 +1,45 @@ -'use client'; +"use client"; -import React, { useState, useEffect } from 'react'; +import type React from "react"; +import { useState, useEffect } from "react"; export default function Features() { const [isVisible, setIsVisible] = useState(false); const [activeFeature, setActiveFeature] = useState(null); const features = [ - { - title: '即時貢献記録', - text: '役割NFTとアシストクレジットで瞬時にログ。', - icon: '⚡', - color: '#ff6b6b', - detailColor: '#ff5252' + { + id: "instant-record", + title: "即時貢献記録", + text: "役割NFTとアシストクレジットで瞬時にログ。", + icon: "⚡", + color: "#ff6b6b", + detailColor: "#ff5252", }, - { - title: '自動報酬分配', - text: '複雑な計算もスマートコントラクトが実行。', - icon: '🤖', - color: '#4ecdc4', - detailColor: '#26c6da' + { + id: "auto-distribution", + title: "自動報酬分配", + text: "複雑な計算もスマートコントラクトが実行。", + icon: "🤖", + color: "#4ecdc4", + detailColor: "#26c6da", }, - { - title: '履歴ダッシュボード', - text: '過去の活動と分配履歴を一元管理。', - icon: '📊', - color: '#45b7d1', - detailColor: '#42a5f5' + { + id: "history-dashboard", + title: "履歴ダッシュボード", + text: "過去の活動と分配履歴を一元管理。", + icon: "📊", + color: "#45b7d1", + detailColor: "#42a5f5", + }, + { + id: "api-integration", + title: "API連携", + text: "既存ツールとの連携も簡単。", + icon: "🔗", + color: "#96c93d", + detailColor: "#8bc34a", }, - { - title: 'API連携', - text: '既存ツールとの連携も簡単。', - icon: '🔗', - color: '#96c93d', - detailColor: '#8bc34a' - } ]; useEffect(() => { @@ -44,10 +49,10 @@ export default function Features() { setIsVisible(true); } }, - { threshold: 0.3 } + { threshold: 0.3 }, ); - const element = document.getElementById('features'); + const element = document.getElementById("features"); if (element) { observer.observe(element); } @@ -66,79 +71,100 @@ export default function Features() {

主な機能ハイライト

-

- プロジェクト運営を革新する4つの核心機能 -

+

プロジェクト運営を革新する4つの核心機能

{features.map((feature, index) => ( -
setActiveFeature(index)} onMouseLeave={() => setActiveFeature(null)} > -
-
+
+
{feature.icon}
-

+

{feature.title}

-

+

{feature.text}

-
-
+
+
{/* 背景装飾 */}
-
-
+
+
))} @@ -149,156 +175,156 @@ export default function Features() { } const sectionStyle: React.CSSProperties = { - padding: '80px 20px', - background: 'linear-gradient(135deg, #1e293b 0%, #334155 50%, #475569 100%)', - color: 'white', - position: 'relative', - overflow: 'hidden' + padding: "80px 20px", + background: "linear-gradient(135deg, #1e293b 0%, #334155 50%, #475569 100%)", + color: "white", + position: "relative", + overflow: "hidden", }; const containerStyle: React.CSSProperties = { - maxWidth: '1200px', - margin: '0 auto', - width: '100%', - position: 'relative', - zIndex: 1 + maxWidth: "1200px", + margin: "0 auto", + width: "100%", + position: "relative", + zIndex: 1, }; const headerStyle: React.CSSProperties = { - textAlign: 'center', - marginBottom: '60px' + textAlign: "center", + marginBottom: "60px", }; const titleStyle: React.CSSProperties = { - fontSize: 'clamp(2rem, 4vw, 2.8rem)', - fontWeight: '700', - margin: '0 0 15px 0', - lineHeight: 1.2 + fontSize: "clamp(2rem, 4vw, 2.8rem)", + fontWeight: "700", + margin: "0 0 15px 0", + lineHeight: 1.2, }; const highlightStyle: React.CSSProperties = { - background: 'linear-gradient(120deg, #ff6b6b, #4ecdc4)', - WebkitBackgroundClip: 'text', - WebkitTextFillColor: 'transparent', - backgroundClip: 'text' + background: "linear-gradient(120deg, #ff6b6b, #4ecdc4)", + WebkitBackgroundClip: "text", + WebkitTextFillColor: "transparent", + backgroundClip: "text", }; const subtitleStyle: React.CSSProperties = { - fontSize: '1.1rem', - color: 'rgba(255, 255, 255, 0.8)', + fontSize: "1.1rem", + color: "rgba(255, 255, 255, 0.8)", margin: 0, - fontWeight: '400' + fontWeight: "400", }; const gridStyle: React.CSSProperties = { - display: 'grid', - gridTemplateColumns: 'repeat(auto-fit, minmax(250px, 1fr))', - gap: '30px', - maxWidth: '1000px', - margin: '0 auto' + display: "grid", + gridTemplateColumns: "repeat(auto-fit, minmax(250px, 1fr))", + gap: "30px", + maxWidth: "1000px", + margin: "0 auto", }; const featureCardStyle: React.CSSProperties = { - padding: '40px 30px', - borderRadius: '24px', - backgroundColor: 'white', - border: '1px solid #e2e8f0', - boxShadow: '0 20px 60px rgba(0, 0, 0, 0.15)', - transition: 'all 0.4s cubic-bezier(0.4, 0, 0.2, 1)', - cursor: 'pointer', - position: 'relative', - overflow: 'hidden', + padding: "40px 30px", + borderRadius: "24px", + backgroundColor: "white", + border: "1px solid #e2e8f0", + boxShadow: "0 20px 60px rgba(0, 0, 0, 0.15)", + transition: "all 0.4s cubic-bezier(0.4, 0, 0.2, 1)", + cursor: "pointer", + position: "relative", + overflow: "hidden", opacity: 0, - transform: 'translateY(50px)' + transform: "translateY(50px)", }; const iconWrapperStyle: React.CSSProperties = { - width: '80px', - height: '80px', - borderRadius: '20px', - border: '2px solid', - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - marginBottom: '25px', - marginLeft: 'auto', - marginRight: 'auto', - position: 'relative' + width: "80px", + height: "80px", + borderRadius: "20px", + border: "2px solid", + display: "flex", + alignItems: "center", + justifyContent: "center", + marginBottom: "25px", + marginLeft: "auto", + marginRight: "auto", + position: "relative", }; const iconContainerStyle: React.CSSProperties = { - width: '60px', - height: '60px', - borderRadius: '16px', - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)' + width: "60px", + height: "60px", + borderRadius: "16px", + display: "flex", + alignItems: "center", + justifyContent: "center", + transition: "all 0.3s cubic-bezier(0.4, 0, 0.2, 1)", }; const iconStyle: React.CSSProperties = { - fontSize: '1.8rem', - filter: 'drop-shadow(0 2px 4px rgba(255, 255, 255, 0.3))' + fontSize: "1.8rem", + filter: "drop-shadow(0 2px 4px rgba(255, 255, 255, 0.3))", }; const contentWrapperStyle: React.CSSProperties = { - marginBottom: '25px' + marginBottom: "25px", }; const featureTitleStyle: React.CSSProperties = { - fontSize: '1.4rem', - fontWeight: '600', - margin: '0 0 15px 0', + fontSize: "1.4rem", + fontWeight: "600", + margin: "0 0 15px 0", lineHeight: 1.3, - transition: 'color 0.3s ease' + transition: "color 0.3s ease", }; const featureTextStyle: React.CSSProperties = { - fontSize: '1rem', + fontSize: "1rem", lineHeight: 1.6, margin: 0, - transition: 'color 0.3s ease' + transition: "color 0.3s ease", }; const progressBarStyle: React.CSSProperties = { - height: '4px', - borderRadius: '2px', - overflow: 'hidden', - transition: 'background-color 0.3s ease' + height: "4px", + borderRadius: "2px", + overflow: "hidden", + transition: "background-color 0.3s ease", }; const progressFillStyle: React.CSSProperties = { - height: '100%', - borderRadius: '2px', - transition: 'width 0.6s cubic-bezier(0.4, 0, 0.2, 1)' + height: "100%", + borderRadius: "2px", + transition: "width 0.6s cubic-bezier(0.4, 0, 0.2, 1)", }; const backgroundDecorStyle: React.CSSProperties = { - position: 'absolute', + position: "absolute", top: 0, left: 0, right: 0, bottom: 0, - pointerEvents: 'none', - overflow: 'hidden' + pointerEvents: "none", + overflow: "hidden", }; const decorCircle1Style: React.CSSProperties = { - position: 'absolute', - top: '-40px', - right: '-40px', - width: '120px', - height: '120px', - borderRadius: '50%', - transition: 'transform 0.4s ease' + position: "absolute", + top: "-40px", + right: "-40px", + width: "120px", + height: "120px", + borderRadius: "50%", + transition: "transform 0.4s ease", }; const decorCircle2Style: React.CSSProperties = { - position: 'absolute', - bottom: '-60px', - left: '-60px', - width: '140px', - height: '140px', - borderRadius: '50%', - transition: 'transform 0.4s ease' -}; \ No newline at end of file + position: "absolute", + bottom: "-60px", + left: "-60px", + width: "140px", + height: "140px", + borderRadius: "50%", + transition: "transform 0.4s ease", +}; diff --git a/src/components/organisms/Footer.tsx b/src/components/organisms/Footer.tsx index b0e34d6..d4acce0 100644 --- a/src/components/organisms/Footer.tsx +++ b/src/components/organisms/Footer.tsx @@ -1,34 +1,51 @@ -'use client'; +"use client"; -import React, { useState } from 'react'; +import type React from "react"; +import { useState } from "react"; export default function Footer() { const [hoveredLink, setHoveredLink] = useState(null); const socialLinks = [ - { name: 'GitHub', icon: '🐙', url: '#', color: '#333' }, - { name: 'Discord', icon: '💬', url: '#', color: '#5865f2' }, - { name: 'X (Twitter)', icon: '🐦', url: '#', color: '#1da1f2' }, - { name: 'YouTube', icon: '📺', url: '#', color: '#ff0000' } + { id: "github", name: "GitHub", icon: "🐙", url: "#", color: "#333" }, + { id: "discord", name: "Discord", icon: "💬", url: "#", color: "#5865f2" }, + { + id: "twitter", + name: "X (Twitter)", + icon: "🐦", + url: "#", + color: "#1da1f2", + }, + { id: "youtube", name: "YouTube", icon: "📺", url: "#", color: "#ff0000" }, ]; const quickLinks = [ - { name: 'ドキュメント', url: '#' }, - { name: 'API リファレンス', url: '#' }, - { name: 'チュートリアル', url: '#' }, - { name: 'コミュニティ', url: '#' } + { id: "docs", name: "ドキュメント", url: "#" }, + { id: "api", name: "API リファレンス", url: "#" }, + { id: "tutorial", name: "チュートリアル", url: "#" }, + { id: "community", name: "コミュニティ", url: "#" }, ]; const legalLinks = [ - { name: '会社情報', url: '#' }, - { name: '利用規約', url: '#' }, - { name: 'プライバシーポリシー', url: '#' }, - { name: 'セキュリティ', url: '#' } + { id: "company", name: "会社情報", url: "#" }, + { id: "terms", name: "利用規約", url: "#" }, + { id: "privacy", name: "プライバシーポリシー", url: "#" }, + { id: "security", name: "セキュリティ", url: "#" }, ]; const contactInfo = [ - { label: 'サポート', value: 'support@toban.community', icon: '📧' }, - { label: 'パートナーシップ', value: 'partnership@toban.community', icon: '🤝' } + { + id: "support", + label: "サポート", + value: "support@toban.community", + icon: "📧", + }, + { + id: "partnership", + label: "パートナーシップ", + value: "partnership@toban.community", + icon: "🤝", + }, ]; return ( @@ -41,27 +58,37 @@ export default function Footer() {

Toban

- いちばん簡単な
+ いちばん簡単な +
貢献の記録と報酬の分配

Web3テクノロジーを活用して、コミュニティやプロジェクトの貢献を透明かつ公正に記録・分配するプラットフォームです。

- + {/* ソーシャルリンク */}

フォローする

{socialLinks.map((link, index) => ( setHoveredLink(link.name)} onMouseLeave={() => setHoveredLink(null)} @@ -79,12 +106,15 @@ export default function Footer() {

リソース