commit dd2a768e727b2f90dc462ce0269e958cb5625fb9 Author: baldeau Date: Sun Oct 13 22:27:33 2024 +0200 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3ddf8a8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,34 @@ +# Files and directories created by pub. +**/doc/api/ +.dart_tool/ +.packages + +# Conventional directory for build output. +/build/ + +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter related +.flutter-plugins +.flutter-plugins-dependencies diff --git a/README.md b/README.md new file mode 100644 index 0000000..0bf8b36 --- /dev/null +++ b/README.md @@ -0,0 +1,51 @@ +# hsrw_campus_website + +The app landing page for the HSRW Campus App. + +![App Screenshot 1](./screenshot/one.png) +![App Screenshot 2](./screenshot/two.png) + +## Requirements + +Install jaspr: + +```bash +dart pub global activate jaspr_cli +``` + +Install `tailwindcss`: + +- macOS + +```bash +brew install tailwindcss +``` + +- Windows + +```powershell +winget install --id=TailwindLabs.TailwindCSS -e +``` + +- Linux (manual installation) + +```bash +curl -sLO https://github.com/tailwindlabs/tailwindcss/releases/latest/download/tailwindcss- +chmod +x tailwindcss- +# e.g. /usr/local/bin on unix based systems (linux, macos) +mv tailwindcss- /usr/local/bin/tailwindcss +``` + +## Running the project + +Run your project using `jaspr serve`. + +The development server will be available on `http://localhost:8080`. + +## Building the project + +Currently there is a build error and the `style.css` file is not generated correctly. This will be fixed in the future, please use the `jaspr serve` command to run the project. + +Build your project using `jaspr build`. + +The output will be located inside the `build/jaspr/` directory. \ No newline at end of file diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 0000000..5e93025 --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,42 @@ +# This file configures the static analysis results for your project (errors, +# warnings, and lints). +# +# This enables the 'recommended' set of lints from `package:lints`. +# This set helps identify many issues that may lead to problems when running +# or consuming Dart code, and enforces writing Dart using a single, idiomatic +# style and format. +# +# If you want a smaller set of lints you can change this to specify +# 'package:lints/core.yaml'. These are just the most critical lints +# (the recommended set includes the core lints). +# The core lints are also what is used by pub.dev for scoring packages. + +include: package:lints/recommended.yaml + +analyzer: + # Jaspr has a custom lint package 'jaspr_lints', which needs the 'custom_lint' analyzer plugin. + # + # Unfortunately, running 'dart analyze' does not pick up the custom lints. Instead, you need to + # run a separate command for this: `jaspr analyze` + plugins: + - custom_lint +# exclude: +# - path/to/excluded/files/** + +# Uncomment the following section to enable or disable additional rules. + +# linter: +# rules: +# camel_case_types: true + +# For controlling Jaspr specific lint rules, we need a slightly different config. + +# custom_lint: +# rules: +# prefer_html_methods: false + +# For more information about the core and recommended set of lints, see +# https://dart.dev/go/core-lints + +# For additional information about configuring this file, see +# https://dart.dev/guides/language/analysis-options diff --git a/lib/app.dart b/lib/app.dart new file mode 100644 index 0000000..eaf714c --- /dev/null +++ b/lib/app.dart @@ -0,0 +1,67 @@ +import 'package:hsrw_campus_website/pages/landing.dart'; +import 'package:hsrw_campus_website/pages/tos.dart'; +import 'package:jaspr/jaspr.dart'; +import 'package:jaspr_router/jaspr_router.dart'; + +import 'components/header.dart'; +import 'components/footer.dart'; +import 'pages/privacy.dart'; +import 'state/theme_state.dart'; + +class App extends StatefulComponent { + const App({super.key}); + + @override + State createState() => _AppState(); +} + +class _AppState extends State { + bool _isDarkMode = true; + + void _toggleTheme(bool value) { + setState(() { + _isDarkMode = value; + }); + } + + @override + Iterable build(BuildContext context) sync* { + yield ThemeState( + isDarkMode: _isDarkMode, + toggleTheme: _toggleTheme, + child: div( + classes: + 'min-h-screen ${_isDarkMode ? 'bg-gray-900 text-white' : 'bg-gray-100 text-gray-900'} flex flex-col', + [ + Router(routes: [ + ShellRoute( + builder: (context, state, child) => Fragment(children: [ + div(classes: 'fixed top-0 left-0 right-0 z-10', [ + const Header(), + ]), + div(classes: 'flex-grow pt-24', [ + child, + ]), + const Footer(), + ]), + routes: [ + Route( + path: '/', + title: 'Home', + builder: (context, state) => const LandingPage()), + Route( + path: '/privacy', + title: 'Privacy Policy', + builder: (context, state) => const PrivacyPolicyPage()), + Route( + path: '/tos', + title: 'Terms of Service', + builder: (context, state) => const TermsOfServicePage()), + ], + ), + ]), + ], + ), + ); + } +} diff --git a/lib/components/counter.dart b/lib/components/counter.dart new file mode 100644 index 0000000..07ccdc5 --- /dev/null +++ b/lib/components/counter.dart @@ -0,0 +1,55 @@ +import 'package:jaspr/jaspr.dart'; + +import '../constants/theme.dart'; + +class Counter extends StatefulComponent { + const Counter({super.key}); + + @override + State createState() => CounterState(); +} + +class CounterState extends State { + int count = 0; + + @override + Iterable build(BuildContext context) sync* { + yield div(classes: 'counter', [ + button( + onClick: () { + setState(() => count--); + }, + [text('-')], + ), + span([text('$count')]), + button( + onClick: () { + setState(() => count++); + }, + [text('+')], + ), + ]); + } + + @css + static final styles = [ + css('.counter', [ + css('&').flexbox(alignItems: AlignItems.center).box( + padding: EdgeInsets.symmetric(vertical: 10.px), + border: Border.symmetric(vertical: BorderSide.solid(color: primaryColor, width: 1.px)), + ), + css('button', [ + css('&') + .text(fontSize: 2.rem) + .box(width: 2.em, height: 2.em, border: Border.unset, cursor: Cursor.pointer) + .box(radius: BorderRadius.all(Radius.circular(2.em))) + .flexbox(justifyContent: JustifyContent.center, alignItems: AlignItems.center) + .background(color: Colors.transparent), + css('&:hover').background(color: const Color.hex('#0001')), + ]), + css('span') // + .box(padding: EdgeInsets.symmetric(horizontal: 2.rem), boxSizing: BoxSizing.borderBox, minWidth: 2.5.em) + .text(color: primaryColor, fontSize: 4.rem, align: TextAlign.center), + ]), + ]; +} diff --git a/lib/components/footer.dart b/lib/components/footer.dart new file mode 100644 index 0000000..e72aa45 --- /dev/null +++ b/lib/components/footer.dart @@ -0,0 +1,67 @@ +import 'package:jaspr/jaspr.dart'; +import 'package:jaspr_router/jaspr_router.dart'; +import '../state/theme_state.dart'; +import '../config/app_config.dart'; + +class Footer extends StatelessComponent { + const Footer({super.key}); + + @override + Iterable build(BuildContext context) sync* { + var themeState = ThemeState.of(context); + + yield footer( + classes: themeState.isDarkMode + ? 'bg-gray-800 text-gray-300' + : 'bg-gray-200 text-gray-700', + [ + div( + classes: 'max-w-7xl mx-auto py-8 px-4 sm:px-6 lg:px-8', + [ + div( + classes: 'flex flex-col md:flex-row justify-between items-center', + [ + // Logo and company name + div( + classes: 'flex items-center mb-4 md:mb-0', + [ + img( + src: AppConfig.appLogo, + alt: 'Company logo', + classes: 'h-8 w-8 mr-2', + ), + span( + classes: + 'font-semibold text-lg ${themeState.isDarkMode ? 'text-white' : 'text-gray-900'}', + [text(AppConfig.appName)], + ), + ], + ), + // Navigation links + nav( + classes: 'flex flex-wrap justify-center space-x-4', + [ + for (var entry in AppConfig.navLinks.entries) + Link(to: entry.value, child: text(entry.key)), + ], + ), + ], + ), + div( + classes: + 'mt-8 border-t ${themeState.isDarkMode ? 'border-gray-700' : 'border-gray-300'} pt-8 text-center', + [ + p( + classes: themeState.isDarkMode + ? 'text-sm text-gray-400' + : 'text-sm text-gray-500', + [text(AppConfig.footerText)], + ), + ], + ), + ], + ), + ], + ); + } +} diff --git a/lib/components/header.dart b/lib/components/header.dart new file mode 100644 index 0000000..079acce --- /dev/null +++ b/lib/components/header.dart @@ -0,0 +1,146 @@ +import 'package:jaspr/jaspr.dart'; +import 'package:jaspr_router/jaspr_router.dart'; +import '../state/theme_state.dart'; +import '../config/app_config.dart'; + +class Header extends StatefulComponent { + const Header({super.key}); + + @override + State
createState() => _HeaderState(); +} + +class _HeaderState extends State
{ + bool _isMenuOpen = false; + + void _toggleMenu() { + setState(() { + _isMenuOpen = !_isMenuOpen; + }); + } + + @override + Iterable build(BuildContext context) sync* { + var activePath = RouteState.of(context).location; + var themeState = ThemeState.of(context); + + yield header( + classes: themeState.isDarkMode + ? 'bg-gray-800 shadow-md' + : 'bg-white shadow-md', + [ + div( + classes: 'max-w-7xl mx-auto px-4 sm:px-6 lg:px-8', + [ + div( + classes: 'flex justify-between items-center h-16', + [ + // Logo + Link( + to: '/', + child: div( + classes: 'flex items-center', + [ + img( + src: AppConfig.appLogo, + alt: 'Logo', + classes: 'h-8 w-8 mr-2', + ), + span( + classes: + 'font-bold text-xl ${themeState.isDarkMode ? 'text-white' : 'text-gray-900'}', + [text(AppConfig.appName)], + ), + ], + ), + ), + // Mobile menu button + div( + classes: 'md:hidden', + [ + button( + classes: + '${themeState.isDarkMode ? 'text-gray-300 hover:text-white' : 'text-gray-600 hover:text-gray-900'} focus:outline-none focus:ring-2 focus:ring-inset focus:ring-indigo-500', + events: {'click': (e) => _toggleMenu()}, + [ + span(classes: 'sr-only', [text('Open main menu')]), + // Hamburger icon + svg( + classes: 'h-6 w-6', + attributes: { + 'fill': 'none', + 'viewBox': '0 0 24 24', + 'stroke': 'currentColor', + 'aria-hidden': 'true', + }, + [ + DomComponent( + tag: 'path', + attributes: { + 'stroke-linecap': 'round', + 'stroke-linejoin': 'round', + 'stroke-width': '2', + 'd': 'M4 6h16M4 12h16M4 18h16', + }, + ), + ], + ), + ], + ), + ], + ), + // Desktop navigation + nav( + classes: 'hidden md:flex space-x-4', + [ + for (var entry in AppConfig.navLinks.entries) + Link( + to: entry.value, + child: div( + classes: + 'px-3 py-2 rounded-md text-sm font-medium ${activePath == entry.value ? (themeState.isDarkMode ? 'bg-gray-900 text-white' : 'bg-gray-200 text-gray-900') : (themeState.isDarkMode ? 'text-gray-300 hover:bg-gray-700 hover:text-white' : 'text-gray-700 hover:bg-gray-200 hover:text-gray-900')}', + [text(entry.key)], + ), + ), + ], + ), + // Theme toggle button + button( + classes: + 'ml-4 p-2 rounded-md ${themeState.isDarkMode ? 'bg-gray-700 text-yellow-400' : 'bg-gray-200 text-gray-800'}', + events: { + 'click': (e) => + themeState.toggleTheme(!themeState.isDarkMode), + }, + [ + text(themeState.isDarkMode ? '☀️' : '🌙'), + ], + ), + ], + ), + ], + ), + // Mobile navigation + div( + classes: 'md:hidden ${_isMenuOpen ? '' : 'hidden'}', + [ + div( + classes: 'px-2 pt-2 pb-3 space-y-1 sm:px-3', + [ + for (var entry in AppConfig.navLinks.entries) + Link( + to: entry.value, + child: div( + classes: + 'block px-3 py-2 rounded-md text-base font-medium ${activePath == entry.value ? (themeState.isDarkMode ? 'bg-gray-900 text-white' : 'bg-gray-200 text-gray-900') : (themeState.isDarkMode ? 'text-gray-300 hover:bg-gray-700 hover:text-white' : 'text-gray-700 hover:bg-gray-200 hover:text-gray-900')}', + [text(entry.key)], + ), + ), + ], + ), + ], + ), + ], + ); + } +} diff --git a/lib/config/app_config.dart b/lib/config/app_config.dart new file mode 100644 index 0000000..716212e --- /dev/null +++ b/lib/config/app_config.dart @@ -0,0 +1,131 @@ +class AppConfig { + static const String appName = 'HSRW Campus App'; + static const String appLogo = 'https://placehold.co/40x40?text=Logo'; + + static const Map navLinks = { + 'Home': '/', + 'Privacy': '/privacy', + 'Terms': '/tos', + }; + + static const String heroTitle = 'HSRW Campus App'; + static const String heroSubtitle = + 'Lorem ipsum, dolor sit amet consectetur adipisicing elit.'; + static const String heroImage = + 'https://placehold.co/640x1386?text=App+Screenshot'; + + static const String featuresTitle = 'Features'; + static const String featuresSubtitle = 'Everything you need'; + static const String featuresDescription = + 'Lorem ipsum dolor sit amet consect adipisicing elit. Possimus magnam voluptatum cupiditate veritatis in accusamus quisquam.'; + + static const List features = [ + Feature( + title: 'Feature 1', + description: + 'Lorem ipsum, dolor sit amet consectetur adipisicing elit. Maiores impedit perferendis suscipit eaque, iste dolor cupiditate blanditiis ratione.', + icon: '🚀', + ), + Feature( + title: 'Feature 2', + description: + 'Lorem ipsum, dolor sit amet consectetur adipisicing elit. Maiores impedit perferendis suscipit eaque, iste dolor cupiditate blanditiis ratione.', + icon: '💡', + ), + Feature( + title: 'Feature 3', + description: + 'Lorem ipsum, dolor sit amet consectetur adipisicing elit. Maiores impedit perferendis suscipit eaque, iste dolor cupiditate blanditiis ratione.', + icon: '🔧', + ), + Feature( + title: 'Feature 4', + description: + 'Lorem ipsum, dolor sit amet consectetur adipisicing elit. Maiores impedit perferendis suscipit eaque, iste dolor cupiditate blanditiis ratione.', + icon: '📊', + ), + ]; + + static const String ctaTitle = 'Ready to dive in?'; + static const String ctaSubtitle = 'Download now and start your journey.'; + static const String ctaDescription = + 'Available on iOS and Android. Download now and experience the future.'; + static const String appStoreLink = '#'; + static const String appStoreImage = + 'https://placehold.co/120x40?text=App+Store'; + static const String playStoreLink = '#'; + static const String playStoreImage = + 'https://placehold.co/135x40?text=Google+Play'; + + static const String footerText = + '© 2023 HSRW Campus App. All rights reserved.'; + + // Privacy Policy + static const String privacyTitle = 'Privacy Policy'; + static const String privacyLastUpdated = 'Last updated: May 1, 2023'; + static const List privacySections = [ + PrivacySection( + title: 'Introduction', + content: + 'This Privacy Policy describes how Your Indie App ("we", "our", or "us") collects, uses, and shares your personal information when you use our mobile application.', + ), + PrivacySection( + title: 'Information We Collect', + content: + 'We collect information that you provide directly to us, such as when you create an account, use our services, or contact us for support. This may include your name, email address, and usage data.', + ), + PrivacySection( + title: 'How We Use Your Information', + content: + 'We use the information we collect to provide, maintain, and improve our services, to communicate with you, and to comply with legal obligations.', + ), + ]; + + // Terms of Service + static const String tosTitle = 'Terms of Service'; + static const String tosLastUpdated = 'Last updated: May 1, 2023'; + static const List tosSections = [ + TOSSection( + title: 'Acceptance of Terms', + content: + 'By accessing or using Your Indie App, you agree to be bound by these Terms of Service and all applicable laws and regulations.', + ), + TOSSection( + title: 'Use of the Service', + content: + 'You may use our service for your personal, non-commercial use only. You must not use the service for any illegal or unauthorized purpose.', + ), + TOSSection( + title: 'User Accounts', + content: + 'You are responsible for maintaining the confidentiality of your account and password. You agree to accept responsibility for all activities that occur under your account.', + ), + ]; + + // Landing page layout configuration + static const bool useAlternativeFeatureLayout = + false; // Set to true to use the new layout +} + +class Feature { + final String title; + final String description; + final String icon; + + const Feature( + {required this.title, required this.description, required this.icon}); +} + +class PrivacySection { + final String title; + final String content; + + const PrivacySection({required this.title, required this.content}); +} + +class TOSSection { + final String title; + final String content; + + const TOSSection({required this.title, required this.content}); +} diff --git a/lib/constants/theme.dart b/lib/constants/theme.dart new file mode 100644 index 0000000..f66c931 --- /dev/null +++ b/lib/constants/theme.dart @@ -0,0 +1,5 @@ +import 'package:jaspr/jaspr.dart'; + +// As your css styles are defined using just Dart, you can simply +// use global variables or methods for common things like colors. +const primaryColor = Color.hex('#01589B'); diff --git a/lib/jaspr_options.dart b/lib/jaspr_options.dart new file mode 100644 index 0000000..802dc79 --- /dev/null +++ b/lib/jaspr_options.dart @@ -0,0 +1,28 @@ +// GENERATED FILE, DO NOT MODIFY +// Generated with jaspr_builder + +import 'package:jaspr/jaspr.dart'; +import 'package:hsrw_campus_website/pages/home.dart' as prefix0; + +/// Default [JasprOptions] for use with your jaspr project. +/// +/// Use this to initialize jaspr **before** calling [runApp]. +/// +/// Example: +/// ```dart +/// import 'jaspr_options.dart'; +/// +/// void main() { +/// Jaspr.initializeApp( +/// options: defaultJasprOptions, +/// ); +/// +/// runApp(...); +/// } +/// ``` +final defaultJasprOptions = JasprOptions( + clients: { + prefix0.Home: ClientTarget('pages/home'), + }, + styles: () => [], +); diff --git a/lib/main.dart b/lib/main.dart new file mode 100644 index 0000000..042cd7b --- /dev/null +++ b/lib/main.dart @@ -0,0 +1,33 @@ +// The entrypoint for the **server** environment. +// +// The [main] method will only be executed on the server during pre-rendering. +// To run code on the client, use the @client annotation. + +// Server-specific jaspr import. +import 'package:jaspr/server.dart'; + +// Imports the [App] component. +import 'app.dart'; + +// This file is generated automatically by Jaspr, do not remove or edit. +import 'jaspr_options.dart'; + +void main() { + // Initializes the server environment with the generated default options. + Jaspr.initializeApp( + options: defaultJasprOptions, + ); + + // Starts the app. + // + // [Document] renders the root document structure (, and ) + // with the provided parameters and components. + runApp(Document( + title: 'hsrw_campus_website', + head: [ + // Link the styles.css file, this will be generated by the tailwind integration. + link(href: 'styles.css', rel: 'stylesheet'), + ], + body: App(), + )); +} diff --git a/lib/pages/about.dart b/lib/pages/about.dart new file mode 100644 index 0000000..7e9ff7b --- /dev/null +++ b/lib/pages/about.dart @@ -0,0 +1,40 @@ +import 'package:jaspr/jaspr.dart'; + +class About extends StatelessComponent { + const About({super.key}); + + @override + Iterable build(BuildContext context) sync* { + yield section([ + ol([ + li([ + h3([text('📖 Documentation')]), + text('Jaspr\'s '), + a( + href: 'https://docs.page/schultek/jaspr', + [text('official documentation')]), + text(' provides you with all information you need to get started.'), + ]), + li([ + h3([text('💬 Community')]), + text('Got stuck? Ask your question on the official '), + a(href: 'https://docs.page/schultek/jaspr', [text('Discord server')]), + text(' for the Jaspr community.'), + ]), + li([ + h3([text('📦 Ecosystem')]), + text( + 'Get official packages and integrations for your project like jaspr_router, jaspr_tailwind or jaspr_riverpod. Find packages built for Jaspr on pub.dev using the '), + a(href: 'https://pub.dev/packages?q=topic%3Ajaspr', [text('#jaspr')]), + text(' topic, or publish your own.'), + ]), + li([ + h3([text('💙 Support Jaspr')]), + text('If you like Jaspr, consider starring us on '), + a(href: 'https://github.com/schultek/jaspr', [text('Github')]), + text(' and tell your friends.'), + ]), + ]), + ]); + } +} diff --git a/lib/pages/home.dart b/lib/pages/home.dart new file mode 100644 index 0000000..61397ef --- /dev/null +++ b/lib/pages/home.dart @@ -0,0 +1,44 @@ +import 'package:jaspr/jaspr.dart'; + +import '../components/counter.dart'; + +// By using the @client annotation this component will be automatically compiled to javascript and mounted +// on the client. Therefore: +// - this file and any imported file must be compilable for both server and client environments. +// - this component and any child components will be built once on the server during pre-rendering and then +// again on the client during normal rendering. +@client +class Home extends StatefulComponent { + const Home({super.key}); + + @override + State createState() => HomeState(); +} + +class HomeState extends State { + + @override + void initState() { + super.initState(); + // Run code depending on the rendering environment. + if (kIsWeb) { + print("Hello client"); + // When using @client components there is no default `main()` function on the client where you would normally + // run any client-side initialization logic. Instead you can put it here, considering this component is only + // mounted once at the root of your client-side component tree. + } else { + print("Hello server"); + } + } + + @override + Iterable build(BuildContext context) sync* { + yield section([ + img(src: 'images/logo.png', width: 80), + h1([text('Welcome')]), + p([text('You successfully create a new Jaspr site.')]), + div(styles: Styles.box(height: 100.px), []), + const Counter(), + ]); + } +} diff --git a/lib/pages/landing.dart b/lib/pages/landing.dart new file mode 100644 index 0000000..80673e9 --- /dev/null +++ b/lib/pages/landing.dart @@ -0,0 +1,201 @@ +import 'package:jaspr/jaspr.dart'; +import '../state/theme_state.dart'; +import '../config/app_config.dart'; + +class LandingPage extends StatelessComponent { + const LandingPage({super.key}); + + @override + Iterable build(BuildContext context) sync* { + var themeState = ThemeState.of(context); + + yield div(classes: 'min-h-screen flex flex-col', [ + // Hero section + section( + classes: + 'flex-1 flex flex-col justify-center items-center text-center px-4 sm:px-6 lg:px-8 ${AppConfig.useAlternativeFeatureLayout ? 'pb-16' : ''}', + [ + h1( + classes: + 'text-4xl sm:text-5xl md:text-6xl font-extrabold ${themeState.isDarkMode ? 'text-white' : 'text-gray-900'}', + [text(AppConfig.heroTitle)], + ), + p( + classes: + 'mt-3 max-w-md mx-auto text-xl ${themeState.isDarkMode ? 'text-gray-300' : 'text-gray-600'} sm:text-2xl md:mt-5 md:max-w-3xl', + [text(AppConfig.heroSubtitle)], + ), + // App showcase (iPhone frame) for original layout + if (!AppConfig.useAlternativeFeatureLayout) + div(classes: 'mt-16 max-w-lg mx-auto', [ + div(classes: 'iphone-mask', [ + img( + src: AppConfig.heroImage, + alt: 'App Screenshot', + classes: 'w-full h-auto'), + ]), + ]), + ], + ), + + // Features section with conditional layout + AppConfig.useAlternativeFeatureLayout + ? _buildAlternativeFeatureSection(themeState) + : _buildOriginalFeatureSection(themeState), + + // CTA section + section( + classes: + 'bg-indigo-700 ${AppConfig.useAlternativeFeatureLayout ? 'mt-16' : ''}', + [ + div( + classes: + 'max-w-2xl mx-auto text-center py-16 px-4 sm:py-20 sm:px-6 lg:px-8', + [ + h2( + classes: 'text-3xl font-extrabold text-white sm:text-4xl', + [ + span(classes: 'block', [text(AppConfig.ctaTitle)]), + span(classes: 'block', [text(AppConfig.ctaSubtitle)]), + ]), + p( + classes: 'mt-4 text-lg leading-6 text-indigo-200', + [text(AppConfig.ctaDescription)]), + div(classes: 'mt-8 flex justify-center space-x-4', [ + a( + classes: + 'inline-flex items-center px-5 py-3 border border-transparent text-base font-medium rounded-md text-indigo-600 bg-white hover:bg-indigo-50', + href: AppConfig.appStoreLink, + [ + img( + src: AppConfig.appStoreImage, + alt: 'Download on the App Store', + classes: 'h-10'), + ]), + a( + classes: + 'inline-flex items-center px-5 py-3 border border-transparent text-base font-medium rounded-md text-indigo-600 bg-white hover:bg-indigo-50', + href: AppConfig.playStoreLink, + [ + img( + src: AppConfig.playStoreImage, + alt: 'Get it on Google Play', + classes: 'h-10'), + ]), + ]), + ]), + ]), + ]); + } + + Component _buildOriginalFeatureSection(ThemeState themeState) { + return section( + classes: 'py-12 ${themeState.isDarkMode ? 'bg-gray-800' : 'bg-gray-100'}', + [ + div(classes: 'max-w-7xl mx-auto px-4 sm:px-6 lg:px-8', [ + div(classes: 'lg:text-center', [ + h2( + classes: + 'text-base text-indigo-400 font-semibold tracking-wide uppercase', + [text(AppConfig.featuresTitle)], + ), + p( + classes: + 'mt-2 text-3xl leading-8 font-extrabold tracking-tight ${themeState.isDarkMode ? 'text-white' : 'text-gray-900'} sm:text-4xl', + [text(AppConfig.featuresSubtitle)], + ), + p( + classes: + 'mt-4 max-w-2xl text-xl ${themeState.isDarkMode ? 'text-gray-300' : 'text-gray-500'} lg:mx-auto', + [text(AppConfig.featuresDescription)], + ), + ]), + div(classes: 'mt-10', [ + div( + classes: + 'space-y-10 md:space-y-0 md:grid md:grid-cols-2 md:gap-x-8 md:gap-y-10', + [ + for (var feature in AppConfig.features) + _buildFeatureItem(feature, themeState), + ], + ), + ]), + ]), + ], + ); + } + + Component _buildAlternativeFeatureSection(ThemeState themeState) { + return section( + classes: 'py-16 ${themeState.isDarkMode ? 'bg-gray-800' : 'bg-gray-100'}', + [ + div(classes: 'max-w-7xl mx-auto px-4 sm:px-6 lg:px-8', [ + div(classes: 'lg:text-center mb-16', [ + h2( + classes: + 'text-base text-indigo-400 font-semibold tracking-wide uppercase', + [text(AppConfig.featuresTitle)], + ), + p( + classes: + 'mt-2 text-3xl leading-8 font-extrabold tracking-tight ${themeState.isDarkMode ? 'text-white' : 'text-gray-900'} sm:text-4xl', + [text(AppConfig.featuresSubtitle)], + ), + p( + classes: + 'mt-4 max-w-2xl text-xl ${themeState.isDarkMode ? 'text-gray-300' : 'text-gray-500'} lg:mx-auto', + [text(AppConfig.featuresDescription)], + ), + ]), + div(classes: 'flex flex-col lg:flex-row items-center', [ + // App screenshot with iPhone frame + div(classes: 'lg:w-1/2 mb-10 lg:mb-0', [ + div(classes: 'max-w-md mx-auto', [ + div(classes: 'iphone-mask', [ + img( + src: AppConfig.heroImage, + alt: 'App Screenshot', + classes: 'w-full h-auto', + ), + ]), + ]), + ]), + // Features list + div(classes: 'lg:w-1/2 lg:pl-12', [ + div(classes: 'space-y-10', [ + for (var feature in AppConfig.features) + _buildFeatureItem(feature, themeState), + ]), + ]), + ]), + ]), + ], + ); + } + + Component _buildFeatureItem(Feature feature, ThemeState themeState) { + return div(classes: 'relative', [ + div([ + div( + classes: + 'absolute flex items-center justify-center h-12 w-12 rounded-md bg-indigo-500 text-white', + [ + span(classes: 'text-2xl', [text(feature.icon)]), + ], + ), + div(classes: 'ml-16', [ + h3( + classes: + 'text-lg leading-6 font-medium ${themeState.isDarkMode ? 'text-white' : 'text-gray-900'}', + [text(feature.title)], + ), + p( + classes: + 'mt-2 text-base ${themeState.isDarkMode ? 'text-gray-300' : 'text-gray-500'}', + [text(feature.description)], + ), + ]), + ]), + ]); + } +} diff --git a/lib/pages/privacy.dart b/lib/pages/privacy.dart new file mode 100644 index 0000000..74351bb --- /dev/null +++ b/lib/pages/privacy.dart @@ -0,0 +1,46 @@ +import 'package:jaspr/jaspr.dart'; +import '../state/theme_state.dart'; +import '../config/app_config.dart'; + +class PrivacyPolicyPage extends StatelessComponent { + const PrivacyPolicyPage({super.key}); + + @override + Iterable build(BuildContext context) sync* { + var themeState = ThemeState.of(context); + + yield div( + classes: 'max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-12', + [ + h1( + classes: + 'text-3xl font-bold ${themeState.isDarkMode ? 'text-white' : 'text-gray-900'}', + [text(AppConfig.privacyTitle)], + ), + p( + classes: + 'mt-2 text-sm ${themeState.isDarkMode ? 'text-gray-400' : 'text-gray-600'}', + [text(AppConfig.privacyLastUpdated)], + ), + div( + classes: 'mt-8 space-y-8', + [ + for (var section in AppConfig.privacySections) + div([ + h2( + classes: + 'text-xl font-semibold ${themeState.isDarkMode ? 'text-white' : 'text-gray-900'}', + [text(section.title)], + ), + p( + classes: + 'mt-2 ${themeState.isDarkMode ? 'text-gray-300' : 'text-gray-600'}', + [text(section.content)], + ), + ]), + ], + ), + ], + ); + } +} diff --git a/lib/pages/tos.dart b/lib/pages/tos.dart new file mode 100644 index 0000000..7266148 --- /dev/null +++ b/lib/pages/tos.dart @@ -0,0 +1,46 @@ +import 'package:jaspr/jaspr.dart'; +import '../state/theme_state.dart'; +import '../config/app_config.dart'; + +class TermsOfServicePage extends StatelessComponent { + const TermsOfServicePage({super.key}); + + @override + Iterable build(BuildContext context) sync* { + var themeState = ThemeState.of(context); + + yield div( + classes: 'max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-12', + [ + h1( + classes: + 'text-3xl font-bold ${themeState.isDarkMode ? 'text-white' : 'text-gray-900'}', + [text(AppConfig.tosTitle)], + ), + p( + classes: + 'mt-2 text-sm ${themeState.isDarkMode ? 'text-gray-400' : 'text-gray-600'}', + [text(AppConfig.tosLastUpdated)], + ), + div( + classes: 'mt-8 space-y-8', + [ + for (var section in AppConfig.tosSections) + div([ + h2( + classes: + 'text-xl font-semibold ${themeState.isDarkMode ? 'text-white' : 'text-gray-900'}', + [text(section.title)], + ), + p( + classes: + 'mt-2 ${themeState.isDarkMode ? 'text-gray-300' : 'text-gray-600'}', + [text(section.content)], + ), + ]), + ], + ), + ], + ); + } +} diff --git a/lib/state/theme_state.dart b/lib/state/theme_state.dart new file mode 100644 index 0000000..98b5121 --- /dev/null +++ b/lib/state/theme_state.dart @@ -0,0 +1,22 @@ +import 'package:jaspr/jaspr.dart'; + +class ThemeState extends InheritedComponent { + final bool isDarkMode; + final Function(bool) toggleTheme; + + const ThemeState({ + required this.isDarkMode, + required this.toggleTheme, + required super.child, + super.key, + }); + + static ThemeState of(BuildContext context) { + return context.dependOnInheritedComponentOfExactType()!; + } + + @override + bool updateShouldNotify(ThemeState oldComponent) { + return isDarkMode != oldComponent.isDarkMode; + } +} diff --git a/pubspec.lock b/pubspec.lock new file mode 100644 index 0000000..15f5db2 --- /dev/null +++ b/pubspec.lock @@ -0,0 +1,714 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: "45cfa8471b89fb6643fe9bf51bd7931a76b8f5ec2d65de4fb176dba8d4f22c77" + url: "https://pub.dev" + source: hosted + version: "73.0.0" + _macros: + dependency: transitive + description: dart + source: sdk + version: "0.3.2" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: "4959fec185fe70cce007c57e9ab6983101dbe593d2bf8bbfb4453aaec0cf470a" + url: "https://pub.dev" + source: hosted + version: "6.8.0" + analyzer_plugin: + dependency: transitive + description: + name: analyzer_plugin + sha256: "9661b30b13a685efaee9f02e5d01ed9f2b423bd889d28a304d02d704aee69161" + url: "https://pub.dev" + source: hosted + version: "0.11.3" + archive: + dependency: transitive + description: + name: archive + sha256: cb6a278ef2dbb298455e1a713bda08524a175630ec643a242c399c932a0a1f7d + url: "https://pub.dev" + source: hosted + version: "3.6.1" + args: + dependency: transitive + description: + name: args + sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" + url: "https://pub.dev" + source: hosted + version: "2.5.0" + async: + dependency: transitive + description: + name: async + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + url: "https://pub.dev" + source: hosted + version: "2.11.0" + bazel_worker: + dependency: transitive + description: + name: bazel_worker + sha256: "4eef19cc486c289e4b06c69d0f6f3192e85cc93c25d4d15d02afb205e388d2f0" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + binary_codec: + dependency: transitive + description: + name: binary_codec + sha256: "368144225d749e1e33f2f4628d0c70bffff99b99b1d6c0777b039f8967365b07" + url: "https://pub.dev" + source: hosted + version: "2.0.3" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + build: + dependency: transitive + description: + name: build + sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + build_config: + dependency: transitive + description: + name: build_config + sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1 + url: "https://pub.dev" + source: hosted + version: "1.1.1" + build_daemon: + dependency: transitive + description: + name: build_daemon + sha256: "79b2aef6ac2ed00046867ed354c88778c9c0f029df8a20fe10b5436826721ef9" + url: "https://pub.dev" + source: hosted + version: "4.0.2" + build_modules: + dependency: transitive + description: + name: build_modules + sha256: "403ba034d94f1a0f26362fe14fd83e9ff33644f5cbe879982920e3d209650b43" + url: "https://pub.dev" + source: hosted + version: "5.0.9" + build_resolvers: + dependency: transitive + description: + name: build_resolvers + sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a" + url: "https://pub.dev" + source: hosted + version: "2.4.2" + build_runner: + dependency: "direct dev" + description: + name: build_runner + sha256: "028819cfb90051c6b5440c7e574d1896f8037e3c96cf17aaeb054c9311cfbf4d" + url: "https://pub.dev" + source: hosted + version: "2.4.13" + build_runner_core: + dependency: transitive + description: + name: build_runner_core + sha256: f8126682b87a7282a339b871298cc12009cb67109cfa1614d6436fb0289193e0 + url: "https://pub.dev" + source: hosted + version: "7.3.2" + built_collection: + dependency: transitive + description: + name: built_collection + sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" + url: "https://pub.dev" + source: hosted + version: "5.1.1" + built_value: + dependency: transitive + description: + name: built_value + sha256: c7913a9737ee4007efedaffc968c049fd0f3d0e49109e778edc10de9426005cb + url: "https://pub.dev" + source: hosted + version: "8.9.2" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff + url: "https://pub.dev" + source: hosted + version: "2.0.3" + ci: + dependency: transitive + description: + name: ci + sha256: "145d095ce05cddac4d797a158bc4cf3b6016d1fe63d8c3d2fbd7212590adca13" + url: "https://pub.dev" + source: hosted + version: "0.1.0" + cli_util: + dependency: transitive + description: + name: cli_util + sha256: c05b7406fdabc7a49a3929d4af76bcaccbbffcbcdcf185b082e1ae07da323d19 + url: "https://pub.dev" + source: hosted + version: "0.4.1" + code_builder: + dependency: transitive + description: + name: code_builder + sha256: f692079e25e7869c14132d39f223f8eec9830eb76131925143b2129c4bb01b37 + url: "https://pub.dev" + source: hosted + version: "4.10.0" + collection: + dependency: transitive + description: + name: collection + sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf + url: "https://pub.dev" + source: hosted + version: "1.19.0" + convert: + dependency: transitive + description: + name: convert + sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" + url: "https://pub.dev" + source: hosted + version: "3.1.1" + crypto: + dependency: transitive + description: + name: crypto + sha256: ec30d999af904f33454ba22ed9a86162b35e52b44ac4807d1d93c288041d7d27 + url: "https://pub.dev" + source: hosted + version: "3.0.5" + csslib: + dependency: transitive + description: + name: csslib + sha256: "706b5707578e0c1b4b7550f64078f0a0f19dec3f50a178ffae7006b0a9ca58fb" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + custom_lint: + dependency: transitive + description: + name: custom_lint + sha256: "4939d89e580c36215e48a7de8fd92f22c79dcc3eb11fda84f3402b3b45aec663" + url: "https://pub.dev" + source: hosted + version: "0.6.5" + custom_lint_builder: + dependency: transitive + description: + name: custom_lint_builder + sha256: d9e5bb63ed52c1d006f5a1828992ba6de124c27a531e8fba0a31afffa81621b3 + url: "https://pub.dev" + source: hosted + version: "0.6.5" + custom_lint_core: + dependency: transitive + description: + name: custom_lint_core + sha256: "4ddbbdaa774265de44c97054dcec058a83d9081d071785ece601e348c18c267d" + url: "https://pub.dev" + source: hosted + version: "0.6.5" + dart_style: + dependency: transitive + description: + name: dart_style + sha256: "7856d364b589d1f08986e140938578ed36ed948581fbc3bc9aef1805039ac5ab" + url: "https://pub.dev" + source: hosted + version: "2.3.7" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + freezed_annotation: + dependency: transitive + description: + name: freezed_annotation + sha256: c2e2d632dd9b8a2b7751117abcfc2b4888ecfe181bd9fca7170d9ef02e595fe2 + url: "https://pub.dev" + source: hosted + version: "2.4.4" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 + url: "https://pub.dev" + source: hosted + version: "4.0.0" + glob: + dependency: transitive + description: + name: glob + sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + graphs: + dependency: transitive + description: + name: graphs + sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + hotreloader: + dependency: transitive + description: + name: hotreloader + sha256: ed56fdc1f3a8ac924e717257621d09e9ec20e308ab6352a73a50a1d7a4d9158e + url: "https://pub.dev" + source: hosted + version: "4.2.0" + html: + dependency: transitive + description: + name: html + sha256: "3a7812d5bcd2894edf53dfaf8cd640876cf6cef50a8f238745c8b8120ea74d3a" + url: "https://pub.dev" + source: hosted + version: "0.15.4" + http: + dependency: transitive + description: + name: http + sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010 + url: "https://pub.dev" + source: hosted + version: "1.2.2" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b" + url: "https://pub.dev" + source: hosted + version: "3.2.1" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "40f592dd352890c3b60fec1b68e786cefb9603e05ff303dbc4dda49b304ecdf4" + url: "https://pub.dev" + source: hosted + version: "4.1.0" + io: + dependency: transitive + description: + name: io + sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e" + url: "https://pub.dev" + source: hosted + version: "1.0.4" + jaspr: + dependency: "direct main" + description: + name: jaspr + sha256: "0e43fa3bfa364ef0f51cf849b20dbad0bf6c4c3c0f1b1adab8a09e2c7730d55c" + url: "https://pub.dev" + source: hosted + version: "0.16.0" + jaspr_builder: + dependency: "direct dev" + description: + name: jaspr_builder + sha256: "383caac3d4ecf025519a4e582660869150453020ccdb6fec763c558046469b4f" + url: "https://pub.dev" + source: hosted + version: "0.16.0" + jaspr_lints: + dependency: "direct dev" + description: + name: jaspr_lints + sha256: "8622080c80d16349268c05ed4cced8ac827002f71c4ea08b6e4fe04d585d85a3" + url: "https://pub.dev" + source: hosted + version: "0.1.2" + jaspr_router: + dependency: "direct main" + description: + name: jaspr_router + sha256: "192d1baf7d722e7c04d4d78676ff97ad2eb920839b1ffee1838d8650d5e1be7a" + url: "https://pub.dev" + source: hosted + version: "0.6.0" + jaspr_tailwind: + dependency: "direct dev" + description: + name: jaspr_tailwind + sha256: "6bc8e5723d0b58816c71044e50b0f580e6518d9b6e6664e52fbbd81efc416705" + url: "https://pub.dev" + source: hosted + version: "0.3.0" + jaspr_web_compilers: + dependency: "direct dev" + description: + name: jaspr_web_compilers + sha256: "068e42fbb89e5a7b7d47849886669b80a0d9e011f76bbfd85f6444d0b80cc4f4" + url: "https://pub.dev" + source: hosted + version: "4.0.10" + js: + dependency: transitive + description: + name: js + sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf + url: "https://pub.dev" + source: hosted + version: "0.7.1" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" + url: "https://pub.dev" + source: hosted + version: "4.9.0" + lints: + dependency: "direct dev" + description: + name: lints + sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290 + url: "https://pub.dev" + source: hosted + version: "3.0.0" + logging: + dependency: transitive + description: + name: logging + sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + macros: + dependency: transitive + description: + name: macros + sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536" + url: "https://pub.dev" + source: hosted + version: "0.1.2-main.4" + matcher: + dependency: transitive + description: + name: matcher + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + url: "https://pub.dev" + source: hosted + version: "0.12.16+1" + meta: + dependency: transitive + description: + name: meta + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + url: "https://pub.dev" + source: hosted + version: "1.16.0" + mime: + dependency: transitive + description: + name: mime + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + package_config: + dependency: transitive + description: + name: package_config + sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + path: + dependency: transitive + description: + name: path + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + url: "https://pub.dev" + source: hosted + version: "1.9.0" + pool: + dependency: transitive + description: + name: pool + sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" + url: "https://pub.dev" + source: hosted + version: "1.5.1" + protobuf: + dependency: transitive + description: + name: protobuf + sha256: "68645b24e0716782e58948f8467fd42a880f255096a821f9e7d0ec625b00c84d" + url: "https://pub.dev" + source: hosted + version: "3.1.0" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + sha256: c799b721d79eb6ee6fa56f00c04b472dcd44a30d258fac2174a6ec57302678f8 + url: "https://pub.dev" + source: hosted + version: "1.3.0" + rxdart: + dependency: transitive + description: + name: rxdart + sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962" + url: "https://pub.dev" + source: hosted + version: "0.28.0" + scratch_space: + dependency: transitive + description: + name: scratch_space + sha256: "8510fbff458d733a58fc427057d1ac86303b376d609d6e1bc43f240aad9aa445" + url: "https://pub.dev" + source: hosted + version: "1.0.2" + shelf: + dependency: transitive + description: + name: shelf + sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 + url: "https://pub.dev" + source: hosted + version: "1.4.2" + shelf_gzip: + dependency: transitive + description: + name: shelf_gzip + sha256: "4f4b793c0f969f348aece1ab4cc05fceba9fea431c1ce76b1bc0fa369cecfc15" + url: "https://pub.dev" + source: hosted + version: "4.1.0" + shelf_proxy: + dependency: transitive + description: + name: shelf_proxy + sha256: a71d2307f4393211930c590c3d2c00630f6c5a7a77edc1ef6436dfd85a6a7ee3 + url: "https://pub.dev" + source: hosted + version: "1.0.4" + shelf_static: + dependency: transitive + description: + name: shelf_static + sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3 + url: "https://pub.dev" + source: hosted + version: "1.1.3" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + sha256: "073c147238594ecd0d193f3456a5fe91c4b0abbcc68bf5cd95b36c4e194ac611" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + source_gen: + dependency: transitive + description: + name: source_gen + sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832" + url: "https://pub.dev" + source: hosted + version: "1.5.0" + source_maps: + dependency: transitive + description: + name: source_maps + sha256: "708b3f6b97248e5781f493b765c3337db11c5d2c81c3094f10904bfa8004c703" + url: "https://pub.dev" + source: hosted + version: "0.10.12" + source_span: + dependency: transitive + description: + name: source_span + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + url: "https://pub.dev" + source: hosted + version: "1.10.0" + sprintf: + dependency: transitive + description: + name: sprintf + sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" + url: "https://pub.dev" + source: hosted + version: "7.0.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377" + url: "https://pub.dev" + source: hosted + version: "1.12.0" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + url: "https://pub.dev" + source: hosted + version: "2.1.2" + stream_transform: + dependency: transitive + description: + name: stream_transform + sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3" + url: "https://pub.dev" + source: hosted + version: "1.3.0" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + test_api: + dependency: transitive + description: + name: test_api + sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" + url: "https://pub.dev" + source: hosted + version: "0.7.3" + timing: + dependency: transitive + description: + name: timing + sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + url: "https://pub.dev" + source: hosted + version: "1.3.2" + uuid: + dependency: transitive + description: + name: uuid + sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff + url: "https://pub.dev" + source: hosted + version: "4.5.1" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b + url: "https://pub.dev" + source: hosted + version: "14.3.0" + watcher: + dependency: transitive + description: + name: watcher + sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + web: + dependency: transitive + description: + name: web + sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb + url: "https://pub.dev" + source: hosted + version: "1.1.0" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83" + url: "https://pub.dev" + source: hosted + version: "0.1.6" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: "9f187088ed104edd8662ca07af4b124465893caf063ba29758f97af57e61da8f" + url: "https://pub.dev" + source: hosted + version: "3.0.1" + yaml: + dependency: transitive + description: + name: yaml + sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" + url: "https://pub.dev" + source: hosted + version: "3.1.2" +sdks: + dart: ">=3.5.0 <3.6.0" diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 0000000..7157643 --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,22 @@ +name: hsrw_campus_website +description: A new jaspr project. +version: 0.0.1 + +environment: + sdk: '>=3.0.0 <4.0.0' + +dependencies: + jaspr: ^0.16.0 + jaspr_router: ^0.6.0 + +dev_dependencies: + build_runner: ^2.4.0 + jaspr_web_compilers: ^4.0.10 + jaspr_builder: ^0.16.0 + jaspr_lints: ^0.1.2 + lints: ^3.0.0 + jaspr_tailwind: ^0.3.0 + +jaspr: + mode: static + diff --git a/screenshot/one.png b/screenshot/one.png new file mode 100644 index 0000000..30b55fd Binary files /dev/null and b/screenshot/one.png differ diff --git a/screenshot/two.png b/screenshot/two.png new file mode 100644 index 0000000..c773bb1 Binary files /dev/null and b/screenshot/two.png differ diff --git a/tailwind.config.js b/tailwind.config.js new file mode 100644 index 0000000..121ad9f --- /dev/null +++ b/tailwind.config.js @@ -0,0 +1,8 @@ +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: ['./{lib,web}/**/*.dart'], + theme: { + extend: {}, + }, + plugins: [], +} \ No newline at end of file diff --git a/web/favicon.ico b/web/favicon.ico new file mode 100644 index 0000000..bd829b4 Binary files /dev/null and b/web/favicon.ico differ diff --git a/web/images/logo.png b/web/images/logo.png new file mode 100644 index 0000000..3cf9187 Binary files /dev/null and b/web/images/logo.png differ diff --git a/web/styles.css b/web/styles.css new file mode 100644 index 0000000..99f18ae --- /dev/null +++ b/web/styles.css @@ -0,0 +1,122 @@ +@import url(https://fonts.googleapis.com/css?family=Roboto); + +html, +body { + font-family: 'Roboto', sans-serif; + width: 100%; + min-height: 100vh; + padding: 0; + margin: 0; + + --primary-color: #01589B; +} + +h1 { + font-size: 4rem; + margin: unset; +} + +.main { + height: 100vh; + display: flex; + flex-direction: column; + flex-wrap: wrap; +} + +.main section { + flex: 1; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +} + +header { + display: flex; + justify-content: center; + padding: 1em; +} + +header nav { + background-color: var(--primary-color); + height: 3em; + border-radius: 10px; + overflow: clip; + display: flex; + justify-content: space-between; +} + +header nav a { + color: white; + font-weight: 700; + text-decoration: none; + padding-left: 2em; + padding-right: 2em; + height: 100%; + display: flex; + align-items: center; +} + +header nav a:hover { + background-color: #0005; +} + +header nav div.active { + position: relative; +} + +header nav div.active::before { + content: ""; + display: block; + height: 2px; + border-radius: 1px; + position: absolute; + left: 20px; + bottom: 0.5em; + right: 20px; + background-color: white; +} + +.counter { + display: flex; + align-items: center; + padding-top: 10px; + padding-bottom: 10px; + border-top-style: solid; + border-bottom-style: solid; + border-top-color: var(--primary-color); + border-bottom-color: var(--primary-color); + border-top-width: 1px; + border-bottom-width: 1px; +} + +.counter button { + font-size: 2rem; + width: 2em; + height: 2em; + border: unset; + cursor: pointer; + border-radius: 2em; + display: flex; + justify-content: center; + align-items: center; + background-color: transparent; +} + +.counter button:hover { + background-color: #0001; +} + +.counter span { + padding-left: 2rem; + padding-right: 2rem; + box-sizing: border-box; + min-width: 2.5em; + color: var(--primary-color); + font-size: 4rem; + text-align: center; +} + +ol { + max-width: 500px; +} \ No newline at end of file diff --git a/web/styles.tw.css b/web/styles.tw.css new file mode 100644 index 0000000..de51aff --- /dev/null +++ b/web/styles.tw.css @@ -0,0 +1,92 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer base { + body { + @apply bg-gray-900 text-white; + } +} + +@layer components { + .btn { + @apply px-4 py-2 rounded-lg font-semibold transition-colors duration-200; + } + + .btn-primary { + @apply bg-indigo-600 hover:bg-indigo-700 text-white; + } + + .btn-secondary { + @apply bg-gray-700 hover:bg-gray-600 text-white; + } + + .pixel-mask { + position: relative; + width: 310px; + height: 680px; + margin: 0 auto; + overflow: hidden; + border-radius: 30px; + box-shadow: 0 0 0 12px #2b2b2b, 0 0 0 14px #1e1e1e, 0 0 0 22px #101010; + background-color: #000; + } + + .iphone-mask { + position: relative; + width: 300px; + height: 650px; + margin: 0 auto; + overflow: hidden; + border-radius: 40px; + box-shadow: 0 0 0 11px #1f1f1f, 0 0 0 13px #191919, 0 0 0 20px #111; + } + + .iphone-mask::before { + content: ""; + position: absolute; + top: 0; + left: 50%; + transform: translateX(-50%); + height: 30px; + width: 60%; + background-color: #1f1f1f; + border-bottom-left-radius: 20px; + border-bottom-right-radius: 20px; + z-index: 10; + } + + .iphone-mask img { + width: 100%; + height: 100%; + object-fit: cover; + } +} + +@layer utilities { + .transition-all { + transition-property: all; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; + } + + .duration-300 { + transition-duration: 300ms; + } + + @variants responsive { + + /* Hide scrollbar for Chrome, Safari and Opera */ + .no-scrollbar::-webkit-scrollbar { + display: none; + } + + /* Hide scrollbar for IE, Edge and Firefox */ + .no-scrollbar { + -ms-overflow-style: none; + /* IE and Edge */ + scrollbar-width: none; + /* Firefox */ + } + } +} \ No newline at end of file