From 108f95dbfa9ac1099329aeac94d03b84a2da9e52 Mon Sep 17 00:00:00 2001 From: Mark Gregory Date: Mon, 27 Jul 2026 07:37:11 +0100 Subject: [PATCH] front end fixes --- .../Resources/views/public/index.blade.php | 14 +- .../Resources/views/public/show.blade.php | 14 +- .../Resources/views/public/index.blade.php | 14 +- .../Resources/views/public/show.blade.php | 14 +- .../Http/Controllers/NewsletterController.php | 11 + Modules/Newsletter/Routes/web.php | 2 + .../Resources/views/public/show.blade.php | 14 +- ...0_add_pricing_fields_to_products_table.php | 34 ++ .../Http/Requests/StoreProductRequest.php | 29 +- Modules/Products/Models/Product.php | 8 +- .../views/admin/products/form.blade.php | 26 +- .../views/admin/products/index.blade.php | 27 +- .../Providers/StylistServiceProvider.php | 4 + Modules/Stylist/Services/FrontendContent.php | 99 ++++ database/seeders/SaaslandDemoSeeder.php | 56 +++ package-lock.json | 206 +++++++++ package.json | 1 + resources/themes/saasland/css/theme.css | 1 + resources/themes/saasland/theme.json | 89 +++- .../themes/saasland/views/home.blade.php | 430 +++--------------- .../saasland/views/layouts/app.blade.php | 17 +- 21 files changed, 687 insertions(+), 423 deletions(-) create mode 100644 Modules/Products/Database/Migrations/2026_07_24_150000_add_pricing_fields_to_products_table.php create mode 100644 Modules/Stylist/Services/FrontendContent.php create mode 100644 database/seeders/SaaslandDemoSeeder.php diff --git a/Modules/Blog/Resources/views/public/index.blade.php b/Modules/Blog/Resources/views/public/index.blade.php index 21fb740..f5c81f0 100644 --- a/Modules/Blog/Resources/views/public/index.blade.php +++ b/Modules/Blog/Resources/views/public/index.blade.php @@ -1,9 +1,9 @@ -@extends('stylist-theme-saasland::layouts.app') -@php($title = 'Blog') -@php($description = 'Ideas, guides and updates from our team.') -@php($eyebrow = 'Insights') -@php($breadcrumbs = ['Blog' => null]) -@section('content') +
@@ -24,4 +24,4 @@
{{ $records->links() }}
-@endsection +
diff --git a/Modules/Blog/Resources/views/public/show.blade.php b/Modules/Blog/Resources/views/public/show.blade.php index 530398c..878edb7 100644 --- a/Modules/Blog/Resources/views/public/show.blade.php +++ b/Modules/Blog/Resources/views/public/show.blade.php @@ -1,9 +1,9 @@ -@extends('stylist-theme-saasland::layouts.app') -@php($title = $record->title) -@php($description = $record->excerpt) -@php($eyebrow = 'Blog') -@php($breadcrumbs = ['Blog' => route('blog.public.index'), $record->title => null]) -@section('content') +
@@ -16,4 +16,4 @@
-@endsection +
diff --git a/Modules/News/Resources/views/public/index.blade.php b/Modules/News/Resources/views/public/index.blade.php index 32294de..f78bc96 100644 --- a/Modules/News/Resources/views/public/index.blade.php +++ b/Modules/News/Resources/views/public/index.blade.php @@ -1,9 +1,9 @@ -@extends('stylist-theme-saasland::layouts.app') -@php($title = 'News') -@php($description = 'The latest announcements, releases and company updates.') -@php($eyebrow = 'Updates') -@php($breadcrumbs = ['News' => null]) -@section('content') +
@@ -24,4 +24,4 @@
{{ $records->links() }}
-@endsection +
diff --git a/Modules/News/Resources/views/public/show.blade.php b/Modules/News/Resources/views/public/show.blade.php index 37d696b..ebea25f 100644 --- a/Modules/News/Resources/views/public/show.blade.php +++ b/Modules/News/Resources/views/public/show.blade.php @@ -1,9 +1,9 @@ -@extends('stylist-theme-saasland::layouts.app') -@php($title = $record->title) -@php($description = $record->excerpt) -@php($eyebrow = 'News') -@php($breadcrumbs = ['News' => route('news.public.index'), $record->title => null]) -@section('content') +
@@ -16,4 +16,4 @@
-@endsection +
diff --git a/Modules/Newsletter/Http/Controllers/NewsletterController.php b/Modules/Newsletter/Http/Controllers/NewsletterController.php index 0522ce1..8e37e2c 100644 --- a/Modules/Newsletter/Http/Controllers/NewsletterController.php +++ b/Modules/Newsletter/Http/Controllers/NewsletterController.php @@ -32,6 +32,17 @@ final class NewsletterController return back()->with('status', 'Campaign created.'); } + public function publicSubscribe(Request $r): RedirectResponse + { + $data = $r->validate(['email' => ['required', 'email', 'max:255'], 'name' => ['nullable', 'string', 'max:255']]); + Subscriber::query()->updateOrCreate( + ['email' => strtolower($data['email'])], + ['name' => $data['name'] ?? null, 'status' => 'confirmed', 'confirmation_token' => Str::random(48), 'confirmed_at' => now()] + ); + + return back()->with('status', 'Thank you for subscribing.'); + } + public function destroySubscriber(Subscriber $subscriber): RedirectResponse { $subscriber->delete(); diff --git a/Modules/Newsletter/Routes/web.php b/Modules/Newsletter/Routes/web.php index 0b7a4e5..045e8f9 100644 --- a/Modules/Newsletter/Routes/web.php +++ b/Modules/Newsletter/Routes/web.php @@ -9,3 +9,5 @@ Route::middleware(['auth', 'verified'])->prefix('administration/newsletter')->na Route::delete('/subscribers/{subscriber}', [NewsletterController::class, 'destroySubscriber'])->name('subscribers.destroy'); Route::post('/campaigns', [NewsletterController::class, 'storeCampaign'])->name('campaigns.store'); }); + +Route::post('/newsletter/subscribe', [NewsletterController::class, 'publicSubscribe'])->middleware('throttle:10,1')->name('newsletter.public.subscribe'); diff --git a/Modules/Pages/Resources/views/public/show.blade.php b/Modules/Pages/Resources/views/public/show.blade.php index dc7df80..9f3f52f 100644 --- a/Modules/Pages/Resources/views/public/show.blade.php +++ b/Modules/Pages/Resources/views/public/show.blade.php @@ -1,9 +1,9 @@ -@extends('stylist-theme-saasland::layouts.app') -@php($title = $page->title) -@php($description = $page->summary) -@php($eyebrow = 'Page') -@php($breadcrumbs = [$page->title => null]) -@section('content') +
@@ -15,4 +15,4 @@
-@endsection +
diff --git a/Modules/Products/Database/Migrations/2026_07_24_150000_add_pricing_fields_to_products_table.php b/Modules/Products/Database/Migrations/2026_07_24_150000_add_pricing_fields_to_products_table.php new file mode 100644 index 0000000..f6b9a80 --- /dev/null +++ b/Modules/Products/Database/Migrations/2026_07_24_150000_add_pricing_fields_to_products_table.php @@ -0,0 +1,34 @@ +string('product_type')->default('product')->after('name'); + $table->text('description')->nullable()->after('product_type'); + $table->decimal('price', 12, 2)->nullable()->after('description'); + $table->string('currency', 3)->default('GBP')->after('price'); + $table->string('billing_period')->nullable()->after('currency'); + $table->json('features')->nullable()->after('billing_period'); + $table->string('cta_label')->nullable()->after('features'); + $table->string('cta_url')->nullable()->after('cta_label'); + $table->boolean('is_featured')->default(false)->after('cta_url'); + $table->unsignedInteger('sort_order')->default(0)->after('is_featured'); + }); + } + + public function down(): void + { + Schema::table('products', function (Blueprint $table): void { + $table->dropColumn([ + 'product_type', 'description', 'price', 'currency', 'billing_period', + 'features', 'cta_label', 'cta_url', 'is_featured', 'sort_order', + ]); + }); + } +}; diff --git a/Modules/Products/Http/Requests/StoreProductRequest.php b/Modules/Products/Http/Requests/StoreProductRequest.php index e41dc6c..ae953bc 100644 --- a/Modules/Products/Http/Requests/StoreProductRequest.php +++ b/Modules/Products/Http/Requests/StoreProductRequest.php @@ -11,8 +11,35 @@ final class StoreProductRequest extends FormRequest return $this->user() !== null; } + protected function prepareForValidation(): void + { + $this->merge([ + 'is_active' => $this->boolean('is_active'), + 'is_featured' => $this->boolean('is_featured'), + 'features' => collect(preg_split('/\r\n|\r|\n/', (string) $this->input('features'))) + ->map(fn (string $feature) => trim($feature)) + ->filter() + ->values() + ->all(), + ]); + } + public function rules(): array { - return ['name' => ['required', 'string', 'max:255'], 'is_active' => ['sometimes', 'boolean']]; + return [ + 'name' => ['required', 'string', 'max:255'], + 'product_type' => ['required', 'in:product,plan'], + 'description' => ['nullable', 'string'], + 'price' => ['nullable', 'numeric', 'min:0'], + 'currency' => ['required', 'string', 'size:3'], + 'billing_period' => ['nullable', 'string', 'max:40'], + 'features' => ['array'], + 'features.*' => ['string', 'max:255'], + 'cta_label' => ['nullable', 'string', 'max:80'], + 'cta_url' => ['nullable', 'string', 'max:2048'], + 'is_featured' => ['boolean'], + 'sort_order' => ['integer', 'min:0'], + 'is_active' => ['boolean'], + ]; } } diff --git a/Modules/Products/Models/Product.php b/Modules/Products/Models/Product.php index 13d567e..30dbf31 100644 --- a/Modules/Products/Models/Product.php +++ b/Modules/Products/Models/Product.php @@ -10,6 +10,12 @@ final class Product extends Model protected function casts(): array { - return ['is_active' => 'boolean']; + return [ + 'price' => 'decimal:2', + 'features' => 'array', + 'is_active' => 'boolean', + 'is_featured' => 'boolean', + 'sort_order' => 'integer', + ]; } } diff --git a/Modules/Products/Resources/views/admin/products/form.blade.php b/Modules/Products/Resources/views/admin/products/form.blade.php index 2e0b179..a98563c 100644 --- a/Modules/Products/Resources/views/admin/products/form.blade.php +++ b/Modules/Products/Resources/views/admin/products/form.blade.php @@ -1 +1,25 @@ -
@csrf{{ __('Save Product') }} +
+ @csrf +
+ + + + + + + + + +
+ + +
+ + +
+
+ + +
+ {{ __('Save Product') }} + diff --git a/Modules/Products/Resources/views/admin/products/index.blade.php b/Modules/Products/Resources/views/admin/products/index.blade.php index b57a1db..757785c 100644 --- a/Modules/Products/Resources/views/admin/products/index.blade.php +++ b/Modules/Products/Resources/views/admin/products/index.blade.php @@ -1,3 +1,26 @@ - -

Product

@foreach ($records as $record)
{{ $record->name }}
@endforeach{{ $records->links() }}
+ +
+
+

Products & Pricing

+

Manage catalogue products and public-facing pricing plans from one place.

+
+
+ @include('products::admin.products.form') +
+
+ @forelse ($records as $record) +
+
+

{{ $record->name }}

{{ ucfirst($record->product_type ?? 'product') }}

+ @if($record->is_featured)Featured@endif +
+ @if($record->price !== null)

{{ $record->currency }} {{ $record->price }} /{{ $record->billing_period }}

@else

Custom

@endif +

{{ $record->description }}

+
+ @empty +

No products or plans have been created.

+ @endforelse +
+ {{ $records->links() }} +
diff --git a/Modules/Stylist/Providers/StylistServiceProvider.php b/Modules/Stylist/Providers/StylistServiceProvider.php index b0bc209..f971f31 100644 --- a/Modules/Stylist/Providers/StylistServiceProvider.php +++ b/Modules/Stylist/Providers/StylistServiceProvider.php @@ -15,6 +15,7 @@ use Modules\Stylist\Html\ThemeHtmlBuilder; use Modules\Stylist\Html\ThemeSeoBuilder; use Modules\Stylist\Support\FrontendNavigation; use Modules\Stylist\Support\ModuleInfo; +use Modules\Stylist\Services\FrontendContent; use Modules\Stylist\Services\ThemeSettings; use Modules\Stylist\Theme\Loader; use Modules\Stylist\Theme\Stylist; @@ -71,6 +72,7 @@ final class StylistServiceProvider extends ServiceProvider $theme = $this->app->make(Loader::class)->find('saasland'); $settings = $this->app->make(ThemeSettings::class)->get($theme); $navigation = $this->app->make(FrontendNavigation::class); + $homepage = $this->app->make(FrontendContent::class)->homepage($settings); $view->with([ 'siteTitle' => filled($settings['site_title'] ?? null) ? $settings['site_title'] : config('app.name', 'Saasland'), @@ -81,6 +83,8 @@ final class StylistServiceProvider extends ServiceProvider 'linkedinUrl' => $settings['linkedin_url'] ?? null, 'primaryNavigation' => $navigation->primary(), 'footerNavigation' => $navigation->footer(), + 'themeSettings' => $settings, + ...$homepage, ]); }); } diff --git a/Modules/Stylist/Services/FrontendContent.php b/Modules/Stylist/Services/FrontendContent.php new file mode 100644 index 0000000..e590d0c --- /dev/null +++ b/Modules/Stylist/Services/FrontendContent.php @@ -0,0 +1,99 @@ + */ + public function homepage(array $settings): array + { + return [ + 'pricingPlans' => $this->enabled($settings, 'show_pricing', true) ? $this->pricingPlans() : collect(), + 'testimonials' => $this->enabled($settings, 'show_testimonials', true) ? $this->testimonials() : collect(), + 'faqs' => $this->enabled($settings, 'show_faq', true) ? $this->faqs() : collect(), + 'teamMembers' => $this->enabled($settings, 'show_team', false) ? $this->team() : collect(), + 'latestBlogPosts' => $this->enabled($settings, 'show_blog', true) ? $this->blogPosts() : collect(), + 'latestNewsArticles' => $this->enabled($settings, 'show_news', true) ? $this->newsArticles() : collect(), + ]; + } + + private function pricingPlans(): Collection + { + return $this->query('products', fn () => Product::query() + ->where('is_active', true) + ->where('product_type', 'plan') + ->orderBy('sort_order') + ->orderBy('id') + ->limit(6) + ->get()); + } + + private function testimonials(): Collection + { + return $this->query('testimonials', fn () => Testimonial::query() + ->where('enabled', true) + ->latest('id') + ->limit(6) + ->get()); + } + + private function faqs(): Collection + { + return $this->query('faqs', fn () => Faq::query() + ->where('enabled', true) + ->orderBy('sort_order') + ->limit(12) + ->get()); + } + + private function team(): Collection + { + return $this->query('team_members', fn () => TeamMember::query() + ->where('enabled', true) + ->orderBy('sort_order') + ->limit(8) + ->get()); + } + + private function blogPosts(): Collection + { + return $this->query('blog_posts', fn () => Blog::query() + ->published() + ->latest('published_at') + ->limit(3) + ->get()); + } + + private function newsArticles(): Collection + { + return $this->query('news_articles', fn () => News::query() + ->published() + ->latest('published_at') + ->limit(3) + ->get()); + } + + private function query(string $table, callable $callback): Collection + { + try { + return Schema::hasTable($table) ? $callback() : collect(); + } catch (Throwable) { + return collect(); + } + } + + private function enabled(array $settings, string $key, bool $default): bool + { + return filter_var($settings[$key] ?? $default, FILTER_VALIDATE_BOOL); + } +} diff --git a/database/seeders/SaaslandDemoSeeder.php b/database/seeders/SaaslandDemoSeeder.php new file mode 100644 index 0000000..95efa57 --- /dev/null +++ b/database/seeders/SaaslandDemoSeeder.php @@ -0,0 +1,56 @@ + 'Starter', 'price' => 19, 'description' => 'For individuals and small teams getting started.', 'features' => ['5 team members', 'Core analytics', 'Email support'], 'cta_label' => 'Start free', 'cta_url' => '/register', 'sort_order' => 10], + ['name' => 'Growth', 'price' => 49, 'description' => 'For growing businesses that need more control.', 'features' => ['25 team members', 'Advanced analytics', 'Automations', 'Priority support'], 'cta_label' => 'Choose Growth', 'cta_url' => '/register', 'is_featured' => true, 'sort_order' => 20], + ['name' => 'Scale', 'price' => null, 'description' => 'For established organisations with complex needs.', 'features' => ['Unlimited members', 'Custom integrations', 'Dedicated success manager'], 'cta_label' => 'Contact sales', 'cta_url' => '#contact', 'sort_order' => 30], + ] as $plan) { + Product::query()->updateOrCreate( + ['name' => $plan['name'], 'product_type' => 'plan'], + $plan + ['product_type' => 'plan', 'currency' => 'GBP', 'billing_period' => 'month', 'is_active' => true] + ); + } + } + + if (Schema::hasTable('testimonials')) { + foreach ([ + ['name' => 'Alex Morgan', 'company' => 'Northstar', 'content' => 'The platform gave us one clear view of the business and cut reporting time dramatically.', 'rating' => 5], + ['name' => 'Priya Shah', 'company' => 'Lumina', 'content' => 'Our onboarding is faster, our handoffs are cleaner, and the team finally works from the same data.', 'rating' => 5], + ['name' => 'Daniel Reed', 'company' => 'Orbit', 'content' => 'We launched in days and had enough insight to improve conversion in the first month.', 'rating' => 5], + ] as $item) { + Testimonial::query()->updateOrCreate(['name' => $item['name'], 'company' => $item['company']], $item + ['enabled' => true]); + } + } + + if (Schema::hasTable('faqs')) { + foreach ([ + ['question' => 'Can I change plan later?', 'answer' => 'Yes. Plans can be upgraded or changed whenever your requirements evolve.', 'sort_order' => 10], + ['question' => 'Is my data secure?', 'answer' => 'The platform uses role-based access controls, secure sessions, audit logging, and production-ready deployment guidance.', 'sort_order' => 20], + ['question' => 'Can the frontend be customised?', 'answer' => 'Yes. Theme settings control branding, content sections, navigation, colours, and public-site presentation.', 'sort_order' => 30], + ] as $item) { + Faq::query()->updateOrCreate(['question' => $item['question']], $item + ['category' => 'General', 'enabled' => true]); + } + } + + if (Schema::hasTable('team_members')) { + TeamMember::query()->updateOrCreate( + ['name' => 'Product Team'], + ['job_title' => 'Platform specialists', 'biography' => 'A multidisciplinary team focused on reliable, secure, and elegant digital experiences.', 'email' => null, 'sort_order' => 10, 'enabled' => true] + ); + } + } +} diff --git a/package-lock.json b/package-lock.json index e4f244c..a0a27f5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "vite": "^8.0.0" }, "devDependencies": { + "fontaine": "^0.8.0", "glob": "^11.0.0", "sass": "^1.89.0" }, @@ -22,6 +23,19 @@ "lightningcss-linux-x64-gnu": "^1.29.1" } }, + "node_modules/@capsizecss/unpack": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@capsizecss/unpack/-/unpack-4.0.1.tgz", + "integrity": "sha512-CuNiSqg7+e1cO/GjffyMOm5Tt2jUF9CWHHnvQ/UkqvtkGfHdgwEC0wpmq7fkN3gxwpRnrAN0WzO3vREKmNolMQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "fontkitten": "^1.0.3" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@emnapi/core": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", @@ -1023,6 +1037,26 @@ "tslib": "^2.4.0" } }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/acorn": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", + "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", + "devOptional": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -1170,6 +1204,13 @@ "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" } }, + "node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "devOptional": true, + "license": "MIT" + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -1185,6 +1226,20 @@ "node": ">= 8" } }, + "node_modules/css-tree": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, "node_modules/detect-libc": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", @@ -1222,6 +1277,16 @@ "node": ">=6" } }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, "node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", @@ -1239,6 +1304,38 @@ } } }, + "node_modules/fontaine": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/fontaine/-/fontaine-0.8.0.tgz", + "integrity": "sha512-eek1GbzOdWIj9FyQH/emqW1aEdfC3lYRCHepzwlFCm5T77fBSRSyNRKE6/antF1/B1M+SfJXVRQTY9GAr7lnDg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@capsizecss/unpack": "^4.0.0", + "css-tree": "^3.1.0", + "magic-regexp": "^0.10.0", + "magic-string": "^0.30.21", + "pathe": "^2.0.3", + "ufo": "^1.6.1", + "unplugin": "^2.3.10" + }, + "engines": { + "node": ">=18.12.0" + } + }, + "node_modules/fontkitten": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/fontkitten/-/fontkitten-1.0.3.tgz", + "integrity": "sha512-Wp1zXWPVUPBmfoa3Cqc9ctaKuzKAV6uLstRqlR56kSjplf5uAce+qeyYym7F+PHbGTk+tCEdkCW6RD7DX/gBZw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "tiny-inflate": "^1.0.3" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/foreground-child": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", @@ -1710,6 +1807,22 @@ "node": "20 || >=22" } }, + "node_modules/magic-regexp": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/magic-regexp/-/magic-regexp-0.10.0.tgz", + "integrity": "sha512-Uly1Bu4lO1hwHUW0CQeSWuRtzCMNO00CmXtS8N6fyvB3B979GOEEeAkiTUDsmbYLAbvpUS/Kt5c4ibosAzVyVg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "estree-walker": "^3.0.3", + "magic-string": "^0.30.12", + "mlly": "^1.7.2", + "regexp-tree": "^0.1.27", + "type-level-regexp": "~0.1.17", + "ufo": "^1.5.4", + "unplugin": "^2.0.0" + } + }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", @@ -1719,6 +1832,13 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, + "node_modules/mdn-data": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", + "devOptional": true, + "license": "CC0-1.0" + }, "node_modules/minimatch": { "version": "10.2.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", @@ -1745,6 +1865,19 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/mlly": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.2.tgz", + "integrity": "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.16.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.3" + } + }, "node_modules/nanoid": { "version": "3.3.16", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", @@ -1804,6 +1937,13 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "devOptional": true, + "license": "MIT" + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -1822,6 +1962,18 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, "node_modules/postcss": { "version": "8.5.22", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.22.tgz", @@ -1864,6 +2016,16 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/regexp-tree": { + "version": "0.1.27", + "resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.27.tgz", + "integrity": "sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==", + "devOptional": true, + "license": "MIT", + "bin": { + "regexp-tree": "bin/regexp-tree" + } + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -2053,6 +2215,13 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/tiny-inflate": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", + "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==", + "devOptional": true, + "license": "MIT" + }, "node_modules/tinyglobby": { "version": "0.2.17", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", @@ -2084,6 +2253,36 @@ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, + "node_modules/type-level-regexp": { + "version": "0.1.17", + "resolved": "https://registry.npmjs.org/type-level-regexp/-/type-level-regexp-0.1.17.tgz", + "integrity": "sha512-wTk4DH3cxwk196uGLK/E9pE45aLfeKJacKmcEgEOA/q5dnPGNxXt0cfYdFxb57L+sEpf1oJH4Dnx/pnRcku9jg==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/ufo": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.4.tgz", + "integrity": "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/unplugin": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.3.11.tgz", + "integrity": "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "acorn": "^8.15.0", + "picomatch": "^4.0.3", + "webpack-virtual-modules": "^0.6.2" + }, + "engines": { + "node": ">=18.12.0" + } + }, "node_modules/vite": { "version": "8.1.5", "resolved": "https://registry.npmjs.org/vite/-/vite-8.1.5.tgz", @@ -2183,6 +2382,13 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/webpack-virtual-modules": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", + "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", + "devOptional": true, + "license": "MIT" + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index 56a1f46..0d7727c 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "vite": "^8.0.0" }, "devDependencies": { + "fontaine": "^0.8.0", "glob": "^11.0.0", "sass": "^1.89.0" }, diff --git a/resources/themes/saasland/css/theme.css b/resources/themes/saasland/css/theme.css index b7b9666..d432c22 100644 --- a/resources/themes/saasland/css/theme.css +++ b/resources/themes/saasland/css/theme.css @@ -6,3 +6,4 @@ /* Reusable public frontend shell */ .logo img{display:block;max-height:38px;max-width:190px}.desktop-nav{align-items:center}.nav-actions{display:flex;align-items:center;gap:12px}.nav-login,.nav-account{color:#d8d1ca;font-weight:700;font-size:14px}.nav-login:hover,.nav-account:hover{color:var(--orange)}.nav-register{padding:9px 16px;font-size:14px}.menu{width:42px;height:42px;border:1px solid var(--line);border-radius:10px;align-items:center;justify-content:center;flex-direction:column;gap:5px}.menu span{display:block;width:19px;height:2px;background:#fff;border-radius:99px}.nav-dropdown{position:relative}.nav-dropdown-toggle{display:flex;align-items:center;gap:5px;padding:0;border:0;background:none;color:#c8c2bb;font:inherit;cursor:pointer}.nav-dropdown-toggle:hover{color:var(--orange)}.nav-dropdown-menu,.account-dropdown{position:absolute;top:calc(100% + 14px);min-width:190px;padding:9px;border:1px solid var(--line);border-radius:12px;background:#151310;box-shadow:0 20px 55px #0009}.nav-dropdown-menu a,.account-dropdown a,.account-dropdown button{display:block;width:100%;padding:10px 12px;border-radius:8px;color:#d8d1ca;text-align:left;font:inherit}.nav-dropdown-menu a:hover,.account-dropdown a:hover,.account-dropdown button:hover{background:#f9731615;color:var(--orange)}.account-menu{position:relative}.account-trigger{display:flex;align-items:center;gap:8px;border:1px solid var(--line);border-radius:999px;padding:5px 11px 5px 5px;background:#151310;color:#fff;cursor:pointer}.account-trigger>span:first-child{display:grid;place-items:center;width:30px;height:30px;border-radius:50%;background:linear-gradient(135deg,var(--orange),#c2410c);font-weight:850}.account-name{max-width:130px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:13px;font-weight:700}.account-dropdown{right:0}.account-dropdown form,.account-dropdown button{margin:0;border:0;background:none}.mobile-auth{display:flex;flex-direction:column;gap:4px;margin-top:12px;padding-top:12px;border-top:1px solid var(--line)}.mobile-auth button{width:100%;border:0;background:none;color:#d8d1ca;text-align:left;font:inherit;padding:10px}.mobile-primary{color:var(--orange)!important;font-weight:800}.page-main{min-height:45vh;background:linear-gradient(180deg,#0c0b0a,#12100e 45%,#0c0b0a)}.page-hero{position:relative;overflow:hidden;padding:92px 0 78px;border-bottom:1px solid var(--line);background:radial-gradient(circle at 30% 0,#3a210f 0,transparent 40%),linear-gradient(145deg,#0c0b0a,#171108)}.page-orb{position:absolute;border-radius:50%;filter:blur(80px);opacity:.13;background:var(--orange)}.page-orb-one{width:360px;height:360px;right:10%;top:-50%}.page-orb-two{width:260px;height:260px;left:5%;bottom:-70%}.page-hero-inner{position:relative}.page-hero h1{max-width:900px;margin:16px 0 12px;font-size:clamp(42px,6vw,70px);line-height:1.02;letter-spacing:-.05em}.page-hero p{max-width:720px;margin:0;color:#bbb3ab;font-size:18px}.breadcrumbs{display:flex;align-items:center;gap:9px;margin-bottom:20px;color:#8f877f;font-size:13px}.breadcrumbs a:hover{color:var(--orange)}.content-section{padding:76px 0 96px}.content-layout{max-width:940px}.content-card{position:relative;padding:clamp(28px,5vw,64px);overflow:hidden;border:1px solid var(--line);border-radius:20px;background:linear-gradient(145deg,#191714,#11100e);box-shadow:0 30px 80px #0004}.content-card:before{content:"";position:absolute;inset:0 auto auto 0;width:100%;height:4px;background:linear-gradient(90deg,var(--orange),#fbbf24,transparent)}.content-lead{margin:0 0 32px;padding-bottom:28px;border-bottom:1px solid var(--line);color:#ddd5cd;font-size:clamp(19px,2.5vw,24px);line-height:1.55}.cms-content{color:#c4bdb5;font-size:17px}.cms-content h2,.cms-content h3,.cms-content h4{color:#fff;line-height:1.2;letter-spacing:-.025em}.cms-content h2{margin:2em 0 .65em;font-size:32px}.cms-content h3{margin:1.7em 0 .55em;font-size:24px}.cms-content p,.cms-content ul,.cms-content ol,.cms-content blockquote{margin:0 0 1.35em}.cms-content a{color:var(--orange2);text-decoration:underline;text-underline-offset:3px}.cms-content img{max-width:100%;height:auto;border-radius:14px}.cms-content blockquote{padding:18px 22px;border-left:3px solid var(--orange);border-radius:0 10px 10px 0;background:#f973160b;color:#e9e2da}.cms-content table{width:100%;border-collapse:collapse;margin:28px 0}.cms-content th,.cms-content td{padding:12px;border:1px solid var(--line);text-align:left}.cms-content th{background:#ffffff08;color:#fff}.article-meta{display:flex;flex-wrap:wrap;gap:10px 20px;margin-bottom:24px;color:#928a82;font-size:14px}.article-meta span+time:before{content:"•";margin-right:20px;color:var(--orange)}.content-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:24px}.post-card{position:relative;overflow:hidden;border:1px solid var(--line);border-radius:18px;background:linear-gradient(145deg,#191714,#11100e);transition:transform .25s,border-color .25s,box-shadow .25s}.post-card:hover{transform:translateY(-5px);border-color:#7c3d18;box-shadow:0 24px 60px #0005}.post-card-accent{height:5px;background:linear-gradient(90deg,var(--orange),#fbbf24)}.post-card:nth-child(3n+2) .post-card-accent{background:linear-gradient(90deg,#8b5cf6,#ec4899)}.post-card:nth-child(3n+3) .post-card-accent{background:linear-gradient(90deg,#14b8a6,#22c55e)}.post-card-body{padding:28px}.post-meta{color:var(--orange2);font-size:12px;font-weight:800;text-transform:uppercase;letter-spacing:.08em}.post-card h2{margin:12px 0;font-size:25px;line-height:1.18}.post-card h2 a:hover{color:var(--orange)}.post-card p{color:var(--muted)}.read-more{display:inline-block;margin-top:8px;color:var(--orange2);font-weight:800}.empty-state{grid-column:1/-1;padding:54px;border:1px dashed #4a423a;border-radius:16px;color:var(--muted);text-align:center}.theme-pagination{margin-top:38px}.theme-pagination nav{color:#c8c2bb}.theme-pagination a,.theme-pagination span{border-color:var(--line)!important;background:#151310!important;color:#c8c2bb!important}.theme-pagination span[aria-current="page"] span{background:var(--orange)!important;color:#fff!important}.copyright{display:flex;justify-content:space-between;gap:20px}.footer-brand{max-width:340px} @media(max-width:980px){.desktop-nav,.nav-login,.nav-account,.nav-register,.account-menu{display:none}.menu{display:flex}.mobile-nav{top:72px;max-height:calc(100vh - 72px);overflow:auto;border-bottom:1px solid var(--line)}.mobile-nav>a,.mobile-nav>.nav-dropdown>a,.mobile-nav .nav-dropdown-toggle{padding:10px;color:#d8d1ca}.mobile-nav .nav-dropdown{display:flex;flex-direction:column}.mobile-nav .nav-dropdown-menu{position:static;margin:0 0 8px 12px;box-shadow:none}.content-grid{grid-template-columns:1fr}}@media(max-width:640px){.page-hero{padding:68px 0 56px}.content-section{padding:48px 0 70px}.content-card{border-radius:15px}.article-meta span+time:before{display:none}.copyright{flex-direction:column}.account-name{display:none}} +.newsletter-panel{display:flex;align-items:center;justify-content:space-between;gap:40px}.newsletter-panel h2{margin:12px 0 8px}.newsletter-panel p{margin:0;color:var(--muted)}.newsletter-panel form{display:flex;gap:10px;min-width:min(100%,430px)}.newsletter-panel input{flex:1;min-width:0;border:1px solid var(--line);border-radius:10px;background:#0c0b0a;color:#fff;padding:13px 15px;font:inherit}.newsletter-panel input:focus{outline:2px solid #f9731666;border-color:var(--orange)}@media(max-width:800px){.newsletter-panel{align-items:stretch;flex-direction:column}.newsletter-panel form{min-width:0;flex-direction:column}} diff --git a/resources/themes/saasland/theme.json b/resources/themes/saasland/theme.json index f011506..6a91c27 100644 --- a/resources/themes/saasland/theme.json +++ b/resources/themes/saasland/theme.json @@ -1,9 +1,9 @@ { "name": "Saasland", "slug": "saasland", - "version": "1.1.0", + "version": "1.2.0", "author": "ABSDEV", - "description": "Dark SaaS marketing theme with integrated feature, pricing, privacy and terms sections.", + "description": "Configurable premium SaaS frontend with CMS-driven content, pricing, blog, news, team, testimonials, FAQ and newsletter sections.", "entry_view": "home", "stylesheet": "css/theme.css", "script": "js/app.js", @@ -86,6 +86,91 @@ "type": "url", "label": "LinkedIn URL", "default": "" + }, + "hero_eyebrow": { + "type": "text", + "label": "Hero eyebrow", + "default": "A better way to run your business" + }, + "hero_description": { + "type": "textarea", + "label": "Hero description", + "default": "Streamline operations, boost productivity, and improve customer satisfaction with one connected platform built for modern teams." + }, + "primary_cta_label": { + "type": "text", + "label": "Primary button label", + "default": "Get started free" + }, + "primary_cta_url": { + "type": "text", + "label": "Primary button URL", + "default": "/register" + }, + "secondary_cta_label": { + "type": "text", + "label": "Secondary button label", + "default": "Book demo" + }, + "secondary_cta_url": { + "type": "text", + "label": "Secondary button URL", + "default": "#contact" + }, + "show_metrics": { + "type": "checkbox", + "label": "Show performance preview", + "default": true + }, + "show_features": { + "type": "checkbox", + "label": "Show features", + "default": true + }, + "show_pricing": { + "type": "checkbox", + "label": "Show pricing", + "default": true + }, + "show_testimonials": { + "type": "checkbox", + "label": "Show testimonials", + "default": true + }, + "show_team": { + "type": "checkbox", + "label": "Show team", + "default": false + }, + "show_blog": { + "type": "checkbox", + "label": "Show blog", + "default": true + }, + "show_news": { + "type": "checkbox", + "label": "Show news", + "default": true + }, + "show_faq": { + "type": "checkbox", + "label": "Show FAQ", + "default": true + }, + "show_newsletter": { + "type": "checkbox", + "label": "Show newsletter signup", + "default": true + }, + "cta_title": { + "type": "text", + "label": "Final CTA title", + "default": "Ready to transform your business?" + }, + "cta_description": { + "type": "text", + "label": "Final CTA description", + "default": "Start building a faster, more connected operation today." } }, "seo": { diff --git a/resources/themes/saasland/views/home.blade.php b/resources/themes/saasland/views/home.blade.php index f86033f..01aa871 100644 --- a/resources/themes/saasland/views/home.blade.php +++ b/resources/themes/saasland/views/home.blade.php @@ -3,386 +3,70 @@ @php($description = $siteDescription) @php($hero = true) @section('content') -
-
-
+@php + $enabled = fn (string $key, bool $default = true) => filter_var($themeSettings[$key] ?? $default, FILTER_VALIDATE_BOOL); + $heroTitle = filled($themeSettings['hero_title'] ?? null) ? $themeSettings['hero_title'] : 'Transform Your Business With Our SaaS Solution'; + $heroText = filled($themeSettings['hero_description'] ?? null) ? $themeSettings['hero_description'] : 'Streamline operations, boost productivity, and improve customer satisfaction with one connected platform built for modern teams.'; +@endphp +
+
+
+ {{ $themeSettings['hero_eyebrow'] ?? 'A better way to run your business' }} +

{{ $heroTitle }}

+

{{ $heroText }}

+ + @if($enabled('show_metrics', true)) +
+
Performance overviewLive
+
Monthly revenue£48,240+18.4%
Active users12,842+9.2%
Conversion7.8%+2.1%
+
+
+ @endif +
+
-
- Introducing Saasland 2.0 +@if($enabled('show_features', true)) +
Powerful features

Everything your team needs to grow

Planning, collaboration, reporting, publishing, commerce and customer insight in one extensible platform.

+@foreach([['Smart automation','Remove repetitive work and delays.'],['Real-time analytics','Turn activity into useful business insight.'],['Content operations','Create, review and publish confidently.'],['Secure by design','Protect users and data with dependable controls.'],['Flexible integrations','Connect the tools your organisation already uses.'],['Modular architecture','Activate only the capabilities each site needs.']] as $i => $feature) +
{{ str_pad((string)($i+1),2,'0',STR_PAD_LEFT) }}

{{ $feature[0] }}

{{ $feature[1] }}

+@endforeach +
+@endif -

- Transform Your Business With Our - SaaS Solution -

+@if($enabled('show_pricing', true) && $pricingPlans->isNotEmpty()) +
Simple pricing

Choose a plan that fits

Plans are managed from Products & Pricing in the CMS.

+@foreach($pricingPlans as $plan) + +@endforeach +
+@endif -

- Streamline operations, boost productivity, and improve customer satisfaction - with one connected platform built for modern teams. -

+@if($enabled('show_testimonials', true) && $testimonials->isNotEmpty()) +
Customer stories

Trusted by ambitious teams

@foreach($testimonials as $testimonial)
“{{ $testimonial->content }}”
{{ $testimonial->name }}@if($testimonial->company) · {{ $testimonial->company }}@endif
@endforeach
+@endif - +@if($enabled('show_team', false) && $teamMembers->isNotEmpty()) +
Our team

People behind the platform

@foreach($teamMembers as $member)

{{ $member->name }}

{{ $member->job_title }}

{{ $member->biography }}

@endforeach
+@endif -
- - 500+ businesses already use our platform -
+@if($enabled('show_blog', true) && $latestBlogPosts->isNotEmpty()) +
Insights

Latest from the blog

@foreach($latestBlogPosts as $post)
{{ optional($post->published_at)->format('d M Y') }}

{{ $post->title }}

{{ $post->excerpt }}

Read article →
@endforeach
+@endif -
-
- Performance overview - Live -
+@if($enabled('show_news', true) && $latestNewsArticles->isNotEmpty()) +
Newsroom

Latest news

@foreach($latestNewsArticles as $article)
{{ optional($article->published_at)->format('d M Y') }}

{{ $article->title }}

{{ $article->excerpt }}

Read update →
@endforeach
+@endif -
-
- Monthly revenue - £48,240 - +18.4% -
-
- Active users - 12,842 - +9.2% -
-
- Conversion - 7.8% - +2.1% -
-
+@if($enabled('show_faq', true) && $faqs->isNotEmpty()) +

Answers before you begin

@foreach($faqs as $faq)

{{ $faq->answer }}

@endforeach
+@endif -
- - - - - - - -
-
-
-
+@if($enabled('show_newsletter', true)) +
+@endif -
-
-
- Powerful features -

Everything your team needs to grow

-

- Move faster with flexible tools that bring planning, collaboration, - reporting, and customer insight into one workspace. -

-
- -
-
- 01 -

Smart automation

-

Automate routine processes and let your team focus on work that creates value.

-
-
- 02 -

Real-time analytics

-

Understand performance instantly with useful dashboards and actionable reporting.

-
-
- 03 -

Team collaboration

-

Keep discussions, decisions, documents, and delivery aligned in one place.

-
-
- 04 -

Secure by design

-

Protect business data with robust access controls and dependable infrastructure.

-
-
- 05 -

Easy integrations

-

Connect the services you already use without rebuilding your existing workflow.

-
-
- 06 -

Priority support

-

Get expert assistance when you need it from a team that understands your goals.

-
-
-
-
- -
-
-
- -

From first idea to measurable growth

-

- Launch quickly, learn from every interaction, and scale with confidence - using a platform designed around clear outcomes. -

-
    -
  • Fast, guided onboarding
  • -
  • Custom workflows for every team
  • -
  • Live performance and usage insight
  • -
  • Enterprise-ready controls
  • -
-
- -
-
- 01 -
-

Connect

-

Bring your data and tools together.

-
-
-
- 02 -
-

Automate

-

Remove repetitive work and delays.

-
-
-
- 03 -
-

Optimise

-

Use evidence to improve every cycle.

-
-
-
-
-
- -
-
-
- Simple pricing -

Choose a plan that fits

-
- -
-
-

Starter

- £19/month -

For individuals and small teams getting started.

-
    -
  • 5 team members
  • -
  • Core analytics
  • -
  • Email support
  • -
- Start free -
- - - -
-

Scale

- Custom -

For established organisations with complex needs.

-
    -
  • Unlimited members
  • -
  • Custom integrations
  • -
  • Dedicated success manager
  • -
- Contact sales -
-
-
-
- -
-
-
- Customer stories -

Trusted by ambitious teams

-
- -
-
- “Saasland gave us one clear view of the business and cut reporting time dramatically.” -
Alex Morgan · Northstar
-
-
- “Our onboarding is faster, our handoffs are cleaner, and the team finally works from the same data.” -
Priya Shah · Lumina
-
-
- “We launched in days and had enough insight to improve conversion in the first month.” -
Daniel Reed · Orbit
-
-
-
-
- -
-
-
- -

Answers before you begin

-
- -
-
- -

- Yes. Start with a free trial and move to a paid plan when your team is ready. -

-
-
- -

- You can upgrade or change your plan as your requirements evolve. -

-
-
- -

- Yes. Saasland is designed to connect with common business platforms and custom services. -

-
-
-
-
- - - - - -
-
-

Ready to transform your business?

-

Start building a faster, more connected operation today.

- @auth - Open dashboard → - @else - @if (Route::has('register')) - Get started free → - @endif - @endauth -
-
+

{{ $themeSettings['cta_title'] ?? 'Ready to transform your business?' }}

{{ $themeSettings['cta_description'] ?? 'Start building a faster, more connected operation today.' }}

@authOpen dashboard →@else @if(Route::has('register'))Get started free →@endif @endauth
@endsection diff --git a/resources/themes/saasland/views/layouts/app.blade.php b/resources/themes/saasland/views/layouts/app.blade.php index 9503a37..c58a572 100644 --- a/resources/themes/saasland/views/layouts/app.blade.php +++ b/resources/themes/saasland/views/layouts/app.blade.php @@ -1,10 +1,11 @@ -@php - $title = $title ?? null; - $description = $description ?? null; - $eyebrow = $eyebrow ?? null; - $hero = $hero ?? false; - $breadcrumbs = $breadcrumbs ?? []; -@endphp +@props([ + 'title' => null, + 'description' => null, + 'eyebrow' => null, + 'hero' => false, + 'breadcrumbs' => [], +]) + @@ -49,7 +50,7 @@ @endunless
- @yield('content') + {{ $slot }}
@include('stylist-theme-saasland::partials.footer')