front end fixes
This commit is contained in:
@@ -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')
|
||||
<x-stylist-theme-saasland::layouts.app
|
||||
title="Blog"
|
||||
description="Ideas, guides and updates from our team."
|
||||
eyebrow="Insights"
|
||||
:breadcrumbs="['Blog' => null]"
|
||||
>
|
||||
<section class="content-section">
|
||||
<div class="wrap">
|
||||
<div class="content-grid">
|
||||
@@ -24,4 +24,4 @@
|
||||
<div class="theme-pagination">{{ $records->links() }}</div>
|
||||
</div>
|
||||
</section>
|
||||
@endsection
|
||||
</x-stylist-theme-saasland::layouts.app>
|
||||
|
||||
@@ -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')
|
||||
<x-stylist-theme-saasland::layouts.app
|
||||
:title="$record->title"
|
||||
:description="$record->excerpt"
|
||||
eyebrow="Blog"
|
||||
:breadcrumbs="['Blog' => route('blog.public.index'), $record->title => null]"
|
||||
>
|
||||
<section class="content-section">
|
||||
<div class="wrap content-layout">
|
||||
<article class="content-card prose-content">
|
||||
@@ -16,4 +16,4 @@
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
@endsection
|
||||
</x-stylist-theme-saasland::layouts.app>
|
||||
|
||||
@@ -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')
|
||||
<x-stylist-theme-saasland::layouts.app
|
||||
title="News"
|
||||
description="The latest announcements, releases and company updates."
|
||||
eyebrow="Updates"
|
||||
:breadcrumbs="['News' => null]"
|
||||
>
|
||||
<section class="content-section">
|
||||
<div class="wrap">
|
||||
<div class="content-grid">
|
||||
@@ -24,4 +24,4 @@
|
||||
<div class="theme-pagination">{{ $records->links() }}</div>
|
||||
</div>
|
||||
</section>
|
||||
@endsection
|
||||
</x-stylist-theme-saasland::layouts.app>
|
||||
|
||||
@@ -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')
|
||||
<x-stylist-theme-saasland::layouts.app
|
||||
:title="$record->title"
|
||||
:description="$record->excerpt"
|
||||
eyebrow="News"
|
||||
:breadcrumbs="['News' => route('news.public.index'), $record->title => null]"
|
||||
>
|
||||
<section class="content-section">
|
||||
<div class="wrap content-layout">
|
||||
<article class="content-card prose-content">
|
||||
@@ -16,4 +16,4 @@
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
@endsection
|
||||
</x-stylist-theme-saasland::layouts.app>
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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')
|
||||
<x-stylist-theme-saasland::layouts.app
|
||||
:title="$page->title"
|
||||
:description="$page->summary"
|
||||
eyebrow="Page"
|
||||
:breadcrumbs="[$page->title => null]"
|
||||
>
|
||||
<section class="content-section">
|
||||
<div class="wrap content-layout">
|
||||
<article class="content-card prose-content">
|
||||
@@ -15,4 +15,4 @@
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
@endsection
|
||||
</x-stylist-theme-saasland::layouts.app>
|
||||
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('products', function (Blueprint $table): void {
|
||||
$table->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',
|
||||
]);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -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'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1,25 @@
|
||||
<form method="POST">@csrf<flux:input name="name" :label="__('Name')" required /><flux:button type="submit" variant="primary">{{ __('Save Product') }}</flux:button></form>
|
||||
<form method="POST" class="space-y-5">
|
||||
@csrf
|
||||
<div class="grid gap-5 md:grid-cols-2">
|
||||
<flux:input name="name" :label="__('Name')" required />
|
||||
<flux:select name="product_type" :label="__('Type')">
|
||||
<option value="product">Product</option>
|
||||
<option value="plan">Pricing plan</option>
|
||||
</flux:select>
|
||||
<flux:input name="price" type="number" step="0.01" min="0" :label="__('Price')" />
|
||||
<flux:input name="currency" value="GBP" maxlength="3" :label="__('Currency')" />
|
||||
<flux:input name="billing_period" value="month" :label="__('Billing period')" />
|
||||
<flux:input name="sort_order" type="number" min="0" value="0" :label="__('Sort order')" />
|
||||
</div>
|
||||
<flux:textarea name="description" :label="__('Description')" rows="3" />
|
||||
<flux:textarea name="features" :label="__('Features — one per line')" rows="6" />
|
||||
<div class="grid gap-5 md:grid-cols-2">
|
||||
<flux:input name="cta_label" :label="__('Button label')" />
|
||||
<flux:input name="cta_url" :label="__('Button URL')" />
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-6">
|
||||
<flux:checkbox name="is_active" value="1" checked :label="__('Active')" />
|
||||
<flux:checkbox name="is_featured" value="1" :label="__('Featured / most popular')" />
|
||||
</div>
|
||||
<flux:button type="submit" variant="primary">{{ __('Save Product') }}</flux:button>
|
||||
</form>
|
||||
|
||||
@@ -1,3 +1,26 @@
|
||||
<x-layouts.app :title="__('Product')">
|
||||
<div class="space-y-6"><h1 class="text-2xl font-semibold">Product</h1>@foreach ($records as $record)<div>{{ $record->name }}</div>@endforeach{{ $records->links() }}</div>
|
||||
<x-layouts.app :title="__('Products & Pricing')">
|
||||
<div class="space-y-6">
|
||||
<div>
|
||||
<h1 class="text-2xl font-semibold">Products & Pricing</h1>
|
||||
<p class="mt-1 text-sm text-zinc-500">Manage catalogue products and public-facing pricing plans from one place.</p>
|
||||
</div>
|
||||
<section class="rounded-2xl border border-zinc-200/70 bg-white/80 p-6 shadow-sm backdrop-blur dark:border-white/10 dark:bg-zinc-950/70">
|
||||
@include('products::admin.products.form')
|
||||
</section>
|
||||
<div class="grid gap-4 lg:grid-cols-3">
|
||||
@forelse ($records as $record)
|
||||
<article class="rounded-2xl border border-zinc-200/70 bg-white/80 p-5 shadow-sm backdrop-blur dark:border-white/10 dark:bg-zinc-950/70">
|
||||
<div class="flex items-start justify-between gap-4">
|
||||
<div><h2 class="font-semibold">{{ $record->name }}</h2><p class="text-sm text-zinc-500">{{ ucfirst($record->product_type ?? 'product') }}</p></div>
|
||||
@if($record->is_featured)<span class="rounded-full bg-orange-500/10 px-2.5 py-1 text-xs font-semibold text-orange-600">Featured</span>@endif
|
||||
</div>
|
||||
@if($record->price !== null)<p class="mt-4 text-2xl font-bold">{{ $record->currency }} {{ $record->price }} <small class="text-sm font-normal">/{{ $record->billing_period }}</small></p>@else<p class="mt-4 text-2xl font-bold">Custom</p>@endif
|
||||
<p class="mt-2 text-sm text-zinc-500">{{ $record->description }}</p>
|
||||
</article>
|
||||
@empty
|
||||
<p class="text-sm text-zinc-500">No products or plans have been created.</p>
|
||||
@endforelse
|
||||
</div>
|
||||
{{ $records->links() }}
|
||||
</div>
|
||||
</x-layouts.app>
|
||||
|
||||
@@ -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,
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Stylist\Services;
|
||||
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Modules\Blog\Models\Blog;
|
||||
use Modules\Faq\Models\Faq;
|
||||
use Modules\News\Models\News;
|
||||
use Modules\Products\Models\Product;
|
||||
use Modules\Team\Models\TeamMember;
|
||||
use Modules\Testimonials\Models\Testimonial;
|
||||
use Throwable;
|
||||
|
||||
final class FrontendContent
|
||||
{
|
||||
/** @return array<string, mixed> */
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use Illuminate\Database\Seeder;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Modules\Faq\Models\Faq;
|
||||
use Modules\Products\Models\Product;
|
||||
use Modules\Team\Models\TeamMember;
|
||||
use Modules\Testimonials\Models\Testimonial;
|
||||
|
||||
final class SaaslandDemoSeeder extends Seeder
|
||||
{
|
||||
public function run(): void
|
||||
{
|
||||
if (Schema::hasTable('products')) {
|
||||
foreach ([
|
||||
['name' => '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]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
+206
@@ -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",
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
"vite": "^8.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"fontaine": "^0.8.0",
|
||||
"glob": "^11.0.0",
|
||||
"sass": "^1.89.0"
|
||||
},
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -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": {
|
||||
|
||||
@@ -3,386 +3,70 @@
|
||||
@php($description = $siteDescription)
|
||||
@php($hero = true)
|
||||
@section('content')
|
||||
<section class="hero">
|
||||
<div class="glow one"></div>
|
||||
<div class="glow two"></div>
|
||||
@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
|
||||
<section class="hero">
|
||||
<div class="glow one"></div><div class="glow two"></div>
|
||||
<div class="wrap hero-inner">
|
||||
<span class="eyebrow">{{ $themeSettings['hero_eyebrow'] ?? 'A better way to run your business' }}</span>
|
||||
<h1>{{ $heroTitle }}</h1>
|
||||
<p>{{ $heroText }}</p>
|
||||
<div class="actions">
|
||||
<a class="btn primary" href="{{ $themeSettings['primary_cta_url'] ?? (Route::has('register') ? route('register') : '#contact') }}">{{ $themeSettings['primary_cta_label'] ?? 'Get started free' }} →</a>
|
||||
<a class="btn secondary" href="{{ $themeSettings['secondary_cta_url'] ?? '#contact' }}">{{ $themeSettings['secondary_cta_label'] ?? 'Book demo' }}</a>
|
||||
</div>
|
||||
@if($enabled('show_metrics', true))
|
||||
<div class="dashboard">
|
||||
<div class="dash-top"><b>Performance overview</b><span>Live</span></div>
|
||||
<div class="metrics"><article><small>Monthly revenue</small><strong>£48,240</strong><em>+18.4%</em></article><article><small>Active users</small><strong>12,842</strong><em>+9.2%</em></article><article><small>Conversion</small><strong>7.8%</strong><em>+2.1%</em></article></div>
|
||||
<div class="chart" aria-label="Performance chart"><i style="height:28%"></i><i style="height:42%"></i><i style="height:35%"></i><i style="height:62%"></i><i style="height:54%"></i><i style="height:78%"></i><i style="height:92%"></i></div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="wrap hero-inner">
|
||||
<span class="eyebrow">Introducing Saasland 2.0</span>
|
||||
@if($enabled('show_features', true))
|
||||
<section class="section" id="features"><div class="wrap"><div class="heading"><span>Powerful features</span><h2>Everything your team needs to grow</h2><p>Planning, collaboration, reporting, publishing, commerce and customer insight in one extensible platform.</p></div><div class="grid three">
|
||||
@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)
|
||||
<article class="card"><b>{{ str_pad((string)($i+1),2,'0',STR_PAD_LEFT) }}</b><h3>{{ $feature[0] }}</h3><p>{{ $feature[1] }}</p></article>
|
||||
@endforeach
|
||||
</div></div></section>
|
||||
@endif
|
||||
|
||||
<h1>
|
||||
Transform Your Business With Our
|
||||
<span>SaaS Solution</span>
|
||||
</h1>
|
||||
@if($enabled('show_pricing', true) && $pricingPlans->isNotEmpty())
|
||||
<section class="section alt" id="pricing"><div class="wrap"><div class="heading"><span>Simple pricing</span><h2>Choose a plan that fits</h2><p>Plans are managed from Products & Pricing in the CMS.</p></div><div class="grid three pricing">
|
||||
@foreach($pricingPlans as $plan)
|
||||
<article class="card plan {{ $plan->is_featured ? 'featured' : '' }}">@if($plan->is_featured)<div class="popular">Most popular</div>@endif<h3>{{ $plan->name }}</h3><strong>@if($plan->price !== null){{ $plan->currency === 'GBP' ? '£' : $plan->currency.' ' }}{{ rtrim(rtrim(number_format((float)$plan->price,2), '0'), '.') }}<small>/{{ $plan->billing_period ?: 'month' }}</small>@else Custom @endif</strong><p>{{ $plan->description }}</p><ul>@foreach((array)$plan->features as $feature)<li>{{ $feature }}</li>@endforeach</ul><a class="btn {{ $plan->is_featured ? 'primary' : 'secondary' }}" href="{{ $plan->cta_url ?: '#contact' }}">{{ $plan->cta_label ?: 'Learn more' }}</a></article>
|
||||
@endforeach
|
||||
</div></div></section>
|
||||
@endif
|
||||
|
||||
<p>
|
||||
Streamline operations, boost productivity, and improve customer satisfaction
|
||||
with one connected platform built for modern teams.
|
||||
</p>
|
||||
@if($enabled('show_testimonials', true) && $testimonials->isNotEmpty())
|
||||
<section class="section"><div class="wrap"><div class="heading"><span>Customer stories</span><h2>Trusted by ambitious teams</h2></div><div class="grid three">@foreach($testimonials as $testimonial)<blockquote class="card">“{{ $testimonial->content }}”<footer>{{ $testimonial->name }}@if($testimonial->company) · {{ $testimonial->company }}@endif</footer></blockquote>@endforeach</div></div></section>
|
||||
@endif
|
||||
|
||||
<div class="actions">
|
||||
<a class="btn primary" href="#pricing">Get Started Free →</a>
|
||||
<a class="btn secondary" href="#contact">Book Demo</a>
|
||||
</div>
|
||||
@if($enabled('show_team', false) && $teamMembers->isNotEmpty())
|
||||
<section class="section alt"><div class="wrap"><div class="heading"><span>Our team</span><h2>People behind the platform</h2></div><div class="grid three">@foreach($teamMembers as $member)<article class="card"><h3>{{ $member->name }}</h3><b>{{ $member->job_title }}</b><p>{{ $member->biography }}</p></article>@endforeach</div></div></section>
|
||||
@endif
|
||||
|
||||
<div class="trust">
|
||||
<div class="avatars" aria-hidden="true">
|
||||
<i>A</i>
|
||||
<i>B</i>
|
||||
<i>C</i>
|
||||
</div>
|
||||
<small><b>500+</b> businesses already use our platform</small>
|
||||
</div>
|
||||
@if($enabled('show_blog', true) && $latestBlogPosts->isNotEmpty())
|
||||
<section class="section"><div class="wrap"><div class="heading"><span>Insights</span><h2>Latest from the blog</h2></div><div class="grid three">@foreach($latestBlogPosts as $post)<article class="card"><b>{{ optional($post->published_at)->format('d M Y') }}</b><h3>{{ $post->title }}</h3><p>{{ $post->excerpt }}</p><a class="read-more" href="{{ route('blog.public.show',$post->slug) }}">Read article →</a></article>@endforeach</div></div></section>
|
||||
@endif
|
||||
|
||||
<div class="dashboard">
|
||||
<div class="dash-top">
|
||||
<b>Performance overview</b>
|
||||
<span>Live</span>
|
||||
</div>
|
||||
@if($enabled('show_news', true) && $latestNewsArticles->isNotEmpty())
|
||||
<section class="section alt"><div class="wrap"><div class="heading"><span>Newsroom</span><h2>Latest news</h2></div><div class="grid three">@foreach($latestNewsArticles as $article)<article class="card"><b>{{ optional($article->published_at)->format('d M Y') }}</b><h3>{{ $article->title }}</h3><p>{{ $article->excerpt }}</p><a class="read-more" href="{{ route('news.public.show',$article->slug) }}">Read update →</a></article>@endforeach</div></div></section>
|
||||
@endif
|
||||
|
||||
<div class="metrics">
|
||||
<article>
|
||||
<small>Monthly revenue</small>
|
||||
<strong>£48,240</strong>
|
||||
<em>+18.4%</em>
|
||||
</article>
|
||||
<article>
|
||||
<small>Active users</small>
|
||||
<strong>12,842</strong>
|
||||
<em>+9.2%</em>
|
||||
</article>
|
||||
<article>
|
||||
<small>Conversion</small>
|
||||
<strong>7.8%</strong>
|
||||
<em>+2.1%</em>
|
||||
</article>
|
||||
</div>
|
||||
@if($enabled('show_faq', true) && $faqs->isNotEmpty())
|
||||
<section class="section"><div class="wrap faq"><div><span class="section-label">FAQ</span><h2>Answers before you begin</h2></div><div>@foreach($faqs as $faq)<article><button type="button" @click="openFaq = openFaq === {{ $faq->id }} ? null : {{ $faq->id }}">{{ $faq->question }}<span>+</span></button><p x-cloak x-show="openFaq === {{ $faq->id }}">{{ $faq->answer }}</p></article>@endforeach</div></div></section>
|
||||
@endif
|
||||
|
||||
<div class="chart" aria-label="Performance chart">
|
||||
<i style="height: 28%"></i>
|
||||
<i style="height: 42%"></i>
|
||||
<i style="height: 35%"></i>
|
||||
<i style="height: 62%"></i>
|
||||
<i style="height: 54%"></i>
|
||||
<i style="height: 78%"></i>
|
||||
<i style="height: 92%"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@if($enabled('show_newsletter', true))
|
||||
<section class="section alt"><div class="wrap"><div class="content-card newsletter-panel"><div><span class="section-label">Newsletter</span><h2>Useful updates, no noise</h2><p>Receive product news, publishing guidance, and practical platform updates.</p></div><form method="POST" action="{{ route('newsletter.public.subscribe') }}">@csrf<input type="email" name="email" required placeholder="you@example.com" aria-label="Email address"><button class="btn primary" type="submit">Subscribe</button></form></div></div></section>
|
||||
@endif
|
||||
|
||||
<section class="section" id="features">
|
||||
<div class="wrap">
|
||||
<div class="heading">
|
||||
<span>Powerful features</span>
|
||||
<h2>Everything your team needs to grow</h2>
|
||||
<p>
|
||||
Move faster with flexible tools that bring planning, collaboration,
|
||||
reporting, and customer insight into one workspace.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="grid three">
|
||||
<article class="card">
|
||||
<b>01</b>
|
||||
<h3>Smart automation</h3>
|
||||
<p>Automate routine processes and let your team focus on work that creates value.</p>
|
||||
</article>
|
||||
<article class="card">
|
||||
<b>02</b>
|
||||
<h3>Real-time analytics</h3>
|
||||
<p>Understand performance instantly with useful dashboards and actionable reporting.</p>
|
||||
</article>
|
||||
<article class="card">
|
||||
<b>03</b>
|
||||
<h3>Team collaboration</h3>
|
||||
<p>Keep discussions, decisions, documents, and delivery aligned in one place.</p>
|
||||
</article>
|
||||
<article class="card">
|
||||
<b>04</b>
|
||||
<h3>Secure by design</h3>
|
||||
<p>Protect business data with robust access controls and dependable infrastructure.</p>
|
||||
</article>
|
||||
<article class="card">
|
||||
<b>05</b>
|
||||
<h3>Easy integrations</h3>
|
||||
<p>Connect the services you already use without rebuilding your existing workflow.</p>
|
||||
</article>
|
||||
<article class="card">
|
||||
<b>06</b>
|
||||
<h3>Priority support</h3>
|
||||
<p>Get expert assistance when you need it from a team that understands your goals.</p>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section alt" id="roadmap">
|
||||
<div class="wrap split">
|
||||
<div>
|
||||
<span class="section-label">Built for momentum</span>
|
||||
<h2>From first idea to measurable growth</h2>
|
||||
<p>
|
||||
Launch quickly, learn from every interaction, and scale with confidence
|
||||
using a platform designed around clear outcomes.
|
||||
</p>
|
||||
<ul class="checks">
|
||||
<li>Fast, guided onboarding</li>
|
||||
<li>Custom workflows for every team</li>
|
||||
<li>Live performance and usage insight</li>
|
||||
<li>Enterprise-ready controls</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="roadmap">
|
||||
<article>
|
||||
<span>01</span>
|
||||
<div>
|
||||
<h3>Connect</h3>
|
||||
<p>Bring your data and tools together.</p>
|
||||
</div>
|
||||
</article>
|
||||
<article>
|
||||
<span>02</span>
|
||||
<div>
|
||||
<h3>Automate</h3>
|
||||
<p>Remove repetitive work and delays.</p>
|
||||
</div>
|
||||
</article>
|
||||
<article>
|
||||
<span>03</span>
|
||||
<div>
|
||||
<h3>Optimise</h3>
|
||||
<p>Use evidence to improve every cycle.</p>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" id="pricing">
|
||||
<div class="wrap">
|
||||
<div class="heading">
|
||||
<span>Simple pricing</span>
|
||||
<h2>Choose a plan that fits</h2>
|
||||
</div>
|
||||
|
||||
<div class="grid three pricing">
|
||||
<article class="card plan">
|
||||
<h3>Starter</h3>
|
||||
<strong>£19<small>/month</small></strong>
|
||||
<p>For individuals and small teams getting started.</p>
|
||||
<ul>
|
||||
<li>5 team members</li>
|
||||
<li>Core analytics</li>
|
||||
<li>Email support</li>
|
||||
</ul>
|
||||
<a class="btn secondary" href="#contact">Start free</a>
|
||||
</article>
|
||||
|
||||
<article class="card plan featured">
|
||||
<div class="popular">Most popular</div>
|
||||
<h3>Growth</h3>
|
||||
<strong>£49<small>/month</small></strong>
|
||||
<p>For growing businesses that need more control.</p>
|
||||
<ul>
|
||||
<li>25 team members</li>
|
||||
<li>Advanced analytics</li>
|
||||
<li>Automations</li>
|
||||
<li>Priority support</li>
|
||||
</ul>
|
||||
<a class="btn primary" href="#contact">Choose Growth</a>
|
||||
</article>
|
||||
|
||||
<article class="card plan">
|
||||
<h3>Scale</h3>
|
||||
<strong>Custom</strong>
|
||||
<p>For established organisations with complex needs.</p>
|
||||
<ul>
|
||||
<li>Unlimited members</li>
|
||||
<li>Custom integrations</li>
|
||||
<li>Dedicated success manager</li>
|
||||
</ul>
|
||||
<a class="btn secondary" href="#contact">Contact sales</a>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section alt">
|
||||
<div class="wrap">
|
||||
<div class="heading">
|
||||
<span>Customer stories</span>
|
||||
<h2>Trusted by ambitious teams</h2>
|
||||
</div>
|
||||
|
||||
<div class="grid three">
|
||||
<blockquote class="card">
|
||||
“Saasland gave us one clear view of the business and cut reporting time dramatically.”
|
||||
<footer>Alex Morgan · Northstar</footer>
|
||||
</blockquote>
|
||||
<blockquote class="card">
|
||||
“Our onboarding is faster, our handoffs are cleaner, and the team finally works from the same data.”
|
||||
<footer>Priya Shah · Lumina</footer>
|
||||
</blockquote>
|
||||
<blockquote class="card">
|
||||
“We launched in days and had enough insight to improve conversion in the first month.”
|
||||
<footer>Daniel Reed · Orbit</footer>
|
||||
</blockquote>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section">
|
||||
<div class="wrap faq">
|
||||
<div>
|
||||
<span class="section-label">FAQ</span>
|
||||
<h2>Answers before you begin</h2>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<article>
|
||||
<button type="button" @click="openFaq = openFaq === 1 ? null : 1">
|
||||
Can I try Saasland free?
|
||||
<span>+</span>
|
||||
</button>
|
||||
<p x-show="openFaq === 1">
|
||||
Yes. Start with a free trial and move to a paid plan when your team is ready.
|
||||
</p>
|
||||
</article>
|
||||
<article>
|
||||
<button type="button" @click="openFaq = openFaq === 2 ? null : 2">
|
||||
Can I change plans later?
|
||||
<span>+</span>
|
||||
</button>
|
||||
<p x-show="openFaq === 2">
|
||||
You can upgrade or change your plan as your requirements evolve.
|
||||
</p>
|
||||
</article>
|
||||
<article>
|
||||
<button type="button" @click="openFaq = openFaq === 3 ? null : 3">
|
||||
Does it integrate with existing tools?
|
||||
<span>+</span>
|
||||
</button>
|
||||
<p x-show="openFaq === 3">
|
||||
Yes. Saasland is designed to connect with common business platforms and custom services.
|
||||
</p>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section alt legal-area" id="privacy">
|
||||
<div class="wrap">
|
||||
<div class="heading">
|
||||
<span>Privacy</span>
|
||||
<h2>Your data, handled responsibly</h2>
|
||||
<p>
|
||||
We collect only the information needed to provide, secure, and improve
|
||||
Saasland. We do not sell your personal information.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="grid three legal-grid">
|
||||
<article class="card legal-card">
|
||||
<b>01</b>
|
||||
<h3>Information we collect</h3>
|
||||
<p>
|
||||
Account details, contact information, billing records, product usage,
|
||||
support messages, and technical data such as browser and device details.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article class="card legal-card">
|
||||
<b>02</b>
|
||||
<h3>How we use it</h3>
|
||||
<p>
|
||||
To operate the service, process payments, protect accounts, provide
|
||||
support, improve features, and meet legal obligations.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article class="card legal-card">
|
||||
<b>03</b>
|
||||
<h3>Your choices</h3>
|
||||
<p>
|
||||
You may request access, correction, export, or deletion of your data,
|
||||
and you can change communication preferences at any time.
|
||||
</p>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<div class="legal-note">
|
||||
<strong>Privacy contact</strong>
|
||||
<p>
|
||||
Questions about privacy can be sent to
|
||||
<a href="mailto:privacy@example.com">privacy@example.com</a>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section legal-area" id="terms">
|
||||
<div class="wrap">
|
||||
<div class="heading">
|
||||
<span>Terms</span>
|
||||
<h2>Clear terms for using Saasland</h2>
|
||||
<p>
|
||||
By creating an account or using the service, you agree to use Saasland
|
||||
lawfully and in accordance with these core terms.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="grid three legal-grid">
|
||||
<article class="card legal-card">
|
||||
<b>01</b>
|
||||
<h3>Account responsibilities</h3>
|
||||
<p>
|
||||
Keep account details accurate, protect login credentials, and ensure
|
||||
activity under your account follows applicable laws and these terms.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article class="card legal-card">
|
||||
<b>02</b>
|
||||
<h3>Subscriptions and billing</h3>
|
||||
<p>
|
||||
Paid plans renew according to the selected billing period. Fees are due
|
||||
when stated and plan changes take effect under the displayed terms.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article class="card legal-card">
|
||||
<b>03</b>
|
||||
<h3>Acceptable use</h3>
|
||||
<p>
|
||||
Do not misuse the platform, interfere with service availability, access
|
||||
data without permission, or use Saasland for unlawful activity.
|
||||
</p>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<div class="legal-note">
|
||||
<strong>Legal contact</strong>
|
||||
<p>
|
||||
Questions about these terms can be sent to
|
||||
<a href="mailto:legal@example.com">legal@example.com</a>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="cta" id="contact">
|
||||
<div class="wrap">
|
||||
<h2>Ready to transform your business?</h2>
|
||||
<p>Start building a faster, more connected operation today.</p>
|
||||
@auth
|
||||
<a class="btn light" href="{{ route('dashboard') }}">Open dashboard →</a>
|
||||
@else
|
||||
@if (Route::has('register'))
|
||||
<a class="btn light" href="{{ route('register') }}">Get started free →</a>
|
||||
@endif
|
||||
@endauth
|
||||
</div>
|
||||
</section>
|
||||
<section class="cta" id="contact"><div class="wrap"><h2>{{ $themeSettings['cta_title'] ?? 'Ready to transform your business?' }}</h2><p>{{ $themeSettings['cta_description'] ?? 'Start building a faster, more connected operation today.' }}</p>@auth<a class="btn light" href="{{ route('dashboard') }}">Open dashboard →</a>@else @if(Route::has('register'))<a class="btn light" href="{{ route('register') }}">Get started free →</a>@endif @endauth</div></section>
|
||||
@endsection
|
||||
|
||||
@@ -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' => [],
|
||||
])
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}" id="top">
|
||||
<head>
|
||||
@@ -49,7 +50,7 @@
|
||||
@endunless
|
||||
|
||||
<main class="{{ $hero ? 'marketing-main' : 'page-main' }}">
|
||||
@yield('content')
|
||||
{{ $slot }}
|
||||
</main>
|
||||
|
||||
@include('stylist-theme-saasland::partials.footer')
|
||||
|
||||
Reference in New Issue
Block a user