diff --git a/.gitignore b/.gitignore index dedf483..af0a69b 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,10 @@ coverage # dotenv environment variables file .env + +# FVM Version Cache +.fvm/ + +# Cursor related +.cursor/ +cursorrules \ No newline at end of file diff --git a/README.md b/README.md index 8b09365..b290f19 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,4 @@ -## Flutter AI Circle Website - -## Flutteristas Website +## Flutter Community AI Circle Website The site is built with Dart utilizing the [Dart Web Platform](https://dart.dev/web) with the [Web](https://pub.dev/packages/web) package. diff --git a/lib/src/components/footer.dart b/lib/src/components/footer.dart index 4eb3d20..f753ce8 100644 --- a/lib/src/components/footer.dart +++ b/lib/src/components/footer.dart @@ -14,10 +14,11 @@ class Footer extends StatelessComponent { tag: 'div', classes: 'footer-grid', children: [ - _footerColumn('Company', ['About', 'Careers', 'Blog', 'Press']), - _footerColumn('Product', ['Features', 'Pricing', 'Integrations', 'FAQ']), - _footerColumn('Resources', ['Documentation', 'Guides', 'Support', 'API']), - _footerColumn('Legal', ['Privacy', 'Terms', 'Security', 'Cookies']), + _footerColumn( + 'Community', ['About', 'Mission', 'Join the Circle', 'Start Contributing']), + _footerColumn('Resources', ['Docs', 'Templates', 'Survey', 'Events']), + _footerColumn('Channels', ['YouTube', 'Twitter', 'Forum', 'GitHub']), + _footerColumn('Legal', ['Privacy', 'Terms', 'Code of Conduct', 'Licensing']), ], ), DomComponent( @@ -27,7 +28,7 @@ class Footer extends StatelessComponent { DomComponent( tag: 'small', classes: 'copyright', - child: Text('© 2025 Flutter Community. All rights reserved.'), + child: Text('© 2025 Flutter Community AI Circle. All rights reserved.'), ), DomComponent( tag: 'div', diff --git a/lib/src/components/home_page.dart b/lib/src/components/home_page.dart index 975e128..bc5b67f 100644 --- a/lib/src/components/home_page.dart +++ b/lib/src/components/home_page.dart @@ -1,5 +1,4 @@ import 'package:jaspr/browser.dart'; -import 'package:web/web.dart' hide Text; class HomePage extends StatelessComponent { @override @@ -11,6 +10,7 @@ class HomePage extends StatelessComponent { _heroSection(), _featuresSection(), _ctaSection(), + _faqSection(), ], ); } @@ -19,80 +19,35 @@ class HomePage extends StatelessComponent { return DomComponent( tag: 'section', classes: 'hero', - styles: Styles(raw: { - 'padding-top': '120px', - 'min-height': '90vh', - 'display': 'flex', - 'flex-direction': 'column', - 'justify-content': 'center', - 'background': 'radial-gradient(circle at 50% 50%, #1f1f1f, #000000)', - 'position': 'relative', - 'overflow': 'hidden', - }), children: [ DomComponent( tag: 'div', classes: 'container', - styles: Styles(raw: { - 'text-align': 'center', - 'max-width': '800px', - 'margin': '0 auto', - }), children: [ DomComponent( tag: 'h1', - styles: Styles(raw: { - 'font-size': '64px', - 'margin-bottom': '20px', - 'background': 'linear-gradient(to right, #ffffff, #ff6700)', - '-webkit-background-clip': 'text', - 'background-clip': 'text', - 'color': 'transparent', - }), - child: Text('Unleash Your Digital Potential'), + child: Text('Build Agentic Flutter Experiences'), ), DomComponent( tag: 'p', - styles: Styles(raw: { - 'font-size': '20px', - 'margin-bottom': '40px', - 'color': 'var(--secondary-text)', - }), - child: Text('Powerful tools for modern creators. Build, launch, ' - 'and scale your next big idea with our intuitive platform.'), + child: Text( + 'A community-powered space for developers building with AI, agentic apps, and next-gen Flutter workflows.'), ), DomComponent( tag: 'div', - styles: Styles(raw: { - 'display': 'flex', - 'gap': '20px', - 'justify-content': 'center', - }), + classes: 'buttons-container', children: [ DomComponent( tag: 'a', classes: 'cta_button', attributes: {'href': '#'}, - styles: Styles(raw: { - 'font-size': '18px', - }), - child: Text('Start Free Trial'), + child: Text('Explore Builders'), ), DomComponent( tag: 'a', + classes: 'secondary-button', attributes: {'href': '#'}, - styles: Styles(raw: { - 'display': 'inline-flex', - 'align-items': 'center', - 'gap': '8px', - 'padding': '12px 24px', - 'border': '1px solid var(--secondary-text)', - 'border-radius': '4px', - 'color': 'var(--text-color)', - 'font-weight': '600', - 'font-size': '18px', - }), - child: Text('Learn More'), + child: Text('What is Agentic Flutter?'), ), ], ), @@ -103,13 +58,10 @@ class HomePage extends StatelessComponent { } Component _featuresSection() { + // TODO: (maybe/extra) Add scroll-based animation to reveal this section when it enters viewport return DomComponent( tag: 'section', id: 'features', - styles: Styles(raw: { - 'padding': '100px 0', - 'background-color': 'var(--primary-color)', - }), children: [ DomComponent( tag: 'div', @@ -117,27 +69,19 @@ class HomePage extends StatelessComponent { children: [ DomComponent( tag: 'h2', - styles: Styles(raw: { - 'text-align': 'center', - 'font-size': '42px', - 'margin-bottom': '60px', - }), - child: Text('Why Choose Our Product'), + classes: 'section-title', + child: Text('Highlights from the Flutter Community AI Circle'), ), DomComponent( tag: 'div', - styles: Styles(raw: { - 'display': 'grid', - 'grid-template-columns': 'repeat(auto-fit, minmax(300px, 1fr))', - 'gap': '40px', - }), + classes: 'features-grid', children: [ - _featureCard('Lightning Fast', - 'Experience performance like never before with our optimized platform.'), - _featureCard('Highly Secure', - 'Your data is protected with enterprise-grade security measures.'), - _featureCard('Always Available', - '99.9% uptime guarantee so you\'re always online when it counts.'), + // TODO: Add YouTube video embed for past livestream + _featureCard('Past Livestream', 'Vibe Coding a Card Game with Norbert & Friends'), + // TODO: Add calendar integration or dynamic content for upcoming events - can they add them to their calendar or directly go set a reminder? + _featureCard('Upcoming', 'Humpday Q&A: Agentic Apps Spotlight'), + // TODO: Add link to survey form/ or forms - maybe regular community one idk. + _featureCard('Survey', 'Help shape open-source tooling for AI in Flutter'), ], ), ], @@ -149,36 +93,14 @@ class HomePage extends StatelessComponent { Component _featureCard(String title, String description) { return DomComponent( tag: 'div', - styles: Styles(raw: { - 'background-color': 'rgba(255, 255, 255, 0.05)', - 'border-radius': '8px', - 'padding': '30px', - 'transition': 'transform 0.3s', - }), - events: { - 'mouseover': (event) => (event.currentTarget as HTMLDivElement) // - .style - .setProperty('transform', 'translateY(-10px)'), - 'mouseout': (event) => (event.currentTarget as HTMLDivElement) // - .style - .setProperty('transform', 'translateY(0)'), - }, + classes: 'feature-card', children: [ DomComponent( tag: 'h3', - styles: Styles(raw: { - 'font-size': '24px', - 'margin-bottom': '15px', - 'color': 'var(--accent-color)', - }), child: Text(title), ), DomComponent( tag: 'p', - styles: Styles(raw: { - 'color': 'var(--secondary-text)', - 'line-height': '1.6', - }), child: Text(description), ), ], @@ -186,54 +108,83 @@ class HomePage extends StatelessComponent { } Component _ctaSection() { + // TODO:(maybe/extra) Add scroll-based fade-in animation for this section return DomComponent( tag: 'section', - styles: Styles(raw: { - 'padding': '80px 0', - 'background': 'linear-gradient(135deg, var(--primary-color), #330000)', - }), + classes: 'cta-section', children: [ DomComponent( tag: 'div', classes: 'container', - styles: Styles(raw: { - 'text-align': 'center', - }), children: [ DomComponent( tag: 'h2', - styles: Styles(raw: { - 'font-size': '38px', - 'margin-bottom': '20px', - }), - child: Text('Ready to Take the Next Step?'), + classes: 'section-title', + child: Text('Your voice shapes the future of AI in Flutter.'), ), DomComponent( tag: 'p', - styles: Styles(raw: { - 'font-size': '18px', - 'max-width': '600px', - 'margin': '0 auto 40px', - 'color': 'var(--secondary-text)', - }), child: Text( - 'Join thousands of satisfied users who have transformed ' - 'their digital presence with us.', + 'Take our community survey and help us understand how AI is changing the way we code, collaborate, and create.', ), ), DomComponent( tag: 'a', classes: 'cta_button', attributes: {'href': '#'}, + child: Text('Take the Survey'), + ), + ], + ), + ], + ); + } + + Component _faqSection() { + // TODO:(maybe/extra) Implement intersection observer to animate FAQ items sequentially as they come into view + return DomComponent( + tag: 'section', + id: 'faq', + children: [ + DomComponent( + tag: 'div', + classes: 'container', + children: [ + DomComponent( + tag: 'h2', + classes: 'section-title', styles: Styles(raw: { - 'font-size': '18px', - 'padding': '15px 30px', + 'text-align': 'center', + 'margin-bottom': 'var(--spacing-lg)', }), - child: Text('Get Started Now'), + child: Text('Frequently Asked Questions'), ), + _faqItem('What is the Flutter Community AI Circle?', + 'The Flutter Community AI Circle is a community initiative focused on exploring and developing AI-powered capabilities within Flutter apps. We bring together developers interested in building agentic experiences, leveraging AI models, and advancing Flutter\'s potential in this space.'), + _faqItem('Who can join this community?', + 'Anyone interested in the intersection of Flutter and AI is welcome! Whether you\'re a beginner curious about AI capabilities or an experienced developer working on complex agentic apps, this community is for you. We value diverse perspectives and experience levels.'), + _faqItem('How can I contribute to the Flutter Community AI Circle?', + 'There are many ways to contribute: participate in our surveys, join live coding sessions, share your projects in our forums, contribute to open-source repositories, or help document best practices. Reach out through any of our channels to get involved!'), ], ), ], ); } + + Component _faqItem(String question, String answer) { + return DomComponent( + tag: 'div', + classes: 'faq-item', + children: [ + DomComponent( + tag: 'h3', + child: Text(question), + ), + DomComponent( + tag: 'p', + child: Text(answer), + ), + ], + ); + } } diff --git a/lib/src/components/navbar.dart b/lib/src/components/navbar.dart index f5df5a6..aa64c59 100644 --- a/lib/src/components/navbar.dart +++ b/lib/src/components/navbar.dart @@ -1,61 +1,38 @@ import 'package:jaspr/browser.dart'; -import 'package:web/web.dart' hide Text; class Navbar extends StatelessComponent { @override Iterable build(BuildContext context) sync* { yield DomComponent( tag: 'nav', - styles: Styles(raw: { - 'position': 'fixed', - 'top': '0', - 'left': '0', - 'right': '0', - 'z-index': '100', - 'padding': '20px 0', - 'background-color': 'rgba(0, 0, 0, 0.8)', - 'backdrop-filter': 'blur(10px)', - }), + classes: 'navbar', children: [ DomComponent( tag: 'div', classes: 'container', - styles: Styles(raw: { - 'display': 'flex', - 'justify-content': 'space-between', - 'align-items': 'center', - }), children: [ DomComponent( tag: 'a', attributes: {'href': '#'}, - styles: Styles(raw: { - 'font-size': '24px', - 'font-weight': 'bold', - 'color': 'var(--accent-color)', - }), + classes: 'navbar-brand', child: RawText('Flutter Community
AI Circle'), ), DomComponent( tag: 'div', classes: 'nav-links', - styles: Styles(raw: { - 'display': 'flex', - 'gap': '30px', - }), children: [ _navLink('Home', '#'), - _navLink('Features', '#features'), - _navLink('Pricing', '#pricing'), - _navLink('About', '#about'), - _navLink('Contact', '#contact'), + _navLink('Forum', '#forum'), + _navLink('YouTube', '#youtube'), + _navLink('GitHub', '#github'), + _navLink('Docs', '#docs'), ], ), DomComponent( tag: 'a', classes: 'cta_button', attributes: {'href': '#'}, - child: Text('Join the Circle'), + child: Text('Take the Survey'), ), ], ), @@ -67,9 +44,7 @@ class Navbar extends StatelessComponent { return DomComponent( tag: 'a', attributes: {'href': href}, - styles: Styles(raw: { - 'font-weight': '500', - }), + classes: 'nav-link', child: Text(text), ); } diff --git a/web/styles.css b/web/styles.css index a6004c7..07de524 100644 --- a/web/styles.css +++ b/web/styles.css @@ -18,11 +18,20 @@ body > * { } :root { - --primary-color: #0f0f0f; - --accent-color: #ff6700; - --text-color: #ffffff; - --secondary-text: #b3b3b3; - --background: #000000; + --primary-color: #181822; + --accent-color: #ab57ff; + --accent-gradient: linear-gradient(90deg, #ab57ff, #4e9fff); + --text-color: #e6e6f2; + --secondary-text: #8888aa; + --background: #0a0a0f; + --card-bg: rgba(255, 255, 255, 0.05); + --shadow-color: rgba(171, 87, 255, 0.3); + --border-radius: 4px; + --spacing-sm: 8px; + --spacing-md: 20px; + --spacing-lg: 40px; + --spacing-xl: 60px; + --transition-speed: 0.3s; } body { @@ -35,37 +44,68 @@ body { .container { max-width: 1200px; margin: 0 auto; - padding: 0 20px; + padding: 0 var(--spacing-md); } a { color: var(--text-color); text-decoration: none; - transition: color 0.3s; + transition: color var(--transition-speed); } a:hover { color: var(--accent-color); } +/* Button styles */ .cta_button { display: inline-block; - background-color: var(--accent-color); - color: var(--text-color); + background: var(--accent-gradient); + color: #fff; /* High readability */ padding: 12px 24px; - border-radius: 4px; + border-radius: var(--border-radius); font-weight: 600; - transition: background-color 0.3s; + transition: all var(--transition-speed); + position: relative; + border: none; + box-shadow: 0 0 8px var(--shadow-color); + text-align: center; } .cta_button:hover { - background-color: var(--text-color); - color: var(--accent-color) + background: var(--accent-gradient); + color: #fff; + box-shadow: 0 0 12px var(--shadow-color), 0 0 2px var(--accent-color); + transform: translateY(-2px) scale(1.03); + filter: brightness(1.1); +} + + + + +.secondary-button { + display: inline-flex; + align-items: center; + gap: var(--spacing-sm); + padding: 12px 24px; + border: 1px solid var(--secondary-text); + border-radius: var(--border-radius); + color: var(--text-color); + font-weight: 600; + font-size: 18px; + transition: all var(--transition-speed); +} + +.secondary-button:hover { + border-color: var(--accent-color); + color: var(--accent-color); + box-shadow: 0 0 10px rgba(171, 87, 255, 0.2); + transform: translateY(-2px); } /* Home page styles */ .home-page { - /* No specific styles in the snippet */ + width: 100%; } /* Hero section */ @@ -75,7 +115,7 @@ a:hover { display: flex; flex-direction: column; justify-content: center; - background: radial-gradient(circle at 50% 50%, #1f1f1f, #000000); + background: radial-gradient(circle at 50% 50%, var(--primary-color), var(--background)); position: relative; overflow: hidden; } @@ -88,8 +128,8 @@ a:hover { .hero h1 { font-size: 64px; - margin-bottom: 20px; - background: linear-gradient(to right, #ffffff, #ff6700); + margin-bottom: var(--spacing-md); + background: var(--accent-gradient); -webkit-background-clip: text; background-clip: text; color: transparent; @@ -97,13 +137,13 @@ a:hover { .hero p { font-size: 20px; - margin-bottom: 40px; + margin-bottom: var(--spacing-lg); color: var(--secondary-text); } .hero .buttons-container { display: flex; - gap: 20px; + gap: var(--spacing-md); justify-content: center; } @@ -111,48 +151,85 @@ a:hover { font-size: 18px; } -.hero .secondary-button { - display: inline-flex; - align-items: center; - gap: 8px; - padding: 12px 24px; - border: 1px solid var(--secondary-text); - border-radius: 4px; - color: var(--text-color); - font-weight: 600; - font-size: 18px; -} - /* Features section */ #features { - padding: 100px 0; + padding: var(--spacing-xl) 0; background-color: var(--primary-color); } #features h2 { text-align: center; font-size: 42px; - margin-bottom: 60px; + margin-bottom: var(--spacing-xl); } -#features .features-grid { +.features-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); - gap: 40px; + gap: var(--spacing-lg); } -/* Footer styles */ +.feature-card { + background-color: var(--card-bg); + border-radius: 8px; + padding: 30px; + transition: all var(--transition-speed); + border: 1px solid transparent; +} + +.feature-card:hover { + transform: translateY(-10px); + box-shadow: 0 5px 20px rgba(0, 0, 0, 0.2); + border-color: var(--accent-color); + box-shadow: 0 0 20px var(--shadow-color); +} + +.feature-card h3 { + font-size: 24px; + margin-bottom: 15px; + color: var(--accent-color); +} + +.feature-card p { + color: var(--secondary-text); + line-height: 1.6; +} + +/* CTA section */ +.cta-section { + padding: 80px 0; + background: linear-gradient(135deg, var(--primary-color), #32164f); + text-align: center; +} + +.cta-section h2 { + font-size: 38px; + margin-bottom: var(--spacing-md); +} + +.cta-section p { + font-size: 18px; + max-width: 600px; + margin: 0 auto var(--spacing-lg); + color: var(--secondary-text); +} + +.cta-section .cta_button { + font-size: 18px; + padding: 15px 30px; +} +/* Footer styles */ footer { - background-color: #0a0a0a; + background-color: #11111a; padding: 60px 0 30px; } footer .footer-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); - gap: 40px; - margin-bottom: 40px; + gap: var(--spacing-lg); + margin-bottom: var(--spacing-lg); } footer .footer-bottom { @@ -162,7 +239,7 @@ footer .footer-bottom { justify-content: space-between; align-items: center; flex-wrap: wrap; - gap: 20px; + gap: var(--spacing-md); } footer .copyright { @@ -172,12 +249,12 @@ footer .copyright { footer .social-links { display: flex; - gap: 20px; + gap: var(--spacing-md); } footer h3 { font-size: 18px; - margin-bottom: 20px; + margin-bottom: var(--spacing-md); color: var(--text-color); } @@ -193,9 +270,108 @@ footer li { footer a { color: var(--secondary-text); - transition: color 0.3s; + transition: color var(--transition-speed); } footer a:hover { color: var(--accent-color); } + +/* Navbar styles */ +.navbar { + position: fixed; + top: 0; + left: 0; + right: 0; + z-index: 100; + padding: 20px 0; + background-color: rgba(0, 0, 0, 0.8); + backdrop-filter: blur(10px); +} + +.navbar .container { + display: flex; + justify-content: space-between; + align-items: center; +} + +.navbar-brand { + font-size: 24px; + font-weight: bold; + color: var(--accent-color); +} + +.nav-links { + display: flex; + gap: 30px; +} + +.nav-link { + font-weight: 500; + position: relative; +} + +.nav-link::after { + content: ''; + position: absolute; + width: 0; + height: 2px; + bottom: -4px; + left: 0; + background: var(--accent-gradient); + transition: width var(--transition-speed); +} + +.nav-link:hover::after { + width: 100%; +} + +/* FAQ Section */ +#faq { + padding: var(--spacing-xl) 0; + background-color: var(--background); +} + +.faq-item { + margin-bottom: var(--spacing-lg); + padding: var(--spacing-md); + background-color: rgba(255, 255, 255, 0.03); + border-radius: var(--border-radius); + border-left: 2px solid var(--accent-color); + transition: all var(--transition-speed); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); +} + +.faq-item:hover { + transform: translateX(5px); + box-shadow: 0 0 15px rgba(171, 87, 255, 0.15); + border-left: 4px solid var(--accent-color); +} + +.faq-item:last-child { + margin-bottom: 0; +} + +.faq-item h3 { + margin-bottom: var(--spacing-sm); + color: var(--accent-color); + font-size: 20px; +} + +.faq-item p { + color: var(--secondary-text); + line-height: 1.6; + margin: 0; +} + +/* Animation Hints - for future implementation */ +.section-title { + /* + TODO: Implement scroll-based animations + Add fade-in and slide-up animation when section comes into viewport + Example: + .section-title.visible { + animation: fadeSlideUp 0.8s ease forwards; + } + */ +}