feat: add platform homepage and content management system with improved UI

Implemented a comprehensive CMS for managing platform homepage and content pages:

- Platform homepage manager with template selection (default, minimal, modern)
- Content pages CRUD with platform defaults and vendor overrides
- Sidebar navigation for Content Management section
- Dedicated API endpoints for creating, updating, deleting pages
- Template support for customizable homepage layouts
- Header/footer navigation integration for content pages
- Comprehensive documentation for platform homepage setup
- Migration script for creating initial platform pages

UI improvements:
- Fixed action buttons styling in content pages table to match design system
- Added proper hover states, rounded corners, and better contrast
- Increased button size and padding for better usability

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-28 07:17:30 +01:00
parent 9f8ad71d85
commit 83a6831b2e
20 changed files with 4177 additions and 0 deletions

View File

@@ -32,6 +32,7 @@ class ContentPageCreate(BaseModel):
title: str = Field(..., max_length=200, description="Page title")
content: str = Field(..., description="HTML or Markdown content")
content_format: str = Field(default="html", description="Content format: html or markdown")
template: str = Field(default="default", max_length=50, description="Template name (default, minimal, modern)")
meta_description: Optional[str] = Field(None, max_length=300, description="SEO meta description")
meta_keywords: Optional[str] = Field(None, max_length=300, description="SEO keywords")
is_published: bool = Field(default=False, description="Publish immediately")
@@ -46,6 +47,7 @@ class ContentPageUpdate(BaseModel):
title: Optional[str] = Field(None, max_length=200)
content: Optional[str] = None
content_format: Optional[str] = None
template: Optional[str] = Field(None, max_length=50)
meta_description: Optional[str] = Field(None, max_length=300)
meta_keywords: Optional[str] = Field(None, max_length=300)
is_published: Optional[bool] = None
@@ -120,6 +122,7 @@ def create_platform_page(
content=page_data.content,
vendor_id=None, # Platform default
content_format=page_data.content_format,
template=page_data.template,
meta_description=page_data.meta_description,
meta_keywords=page_data.meta_keywords,
is_published=page_data.is_published,
@@ -202,6 +205,7 @@ def update_page(
title=page_data.title,
content=page_data.content,
content_format=page_data.content_format,
template=page_data.template,
meta_description=page_data.meta_description,
meta_keywords=page_data.meta_keywords,
is_published=page_data.is_published,

View File

@@ -21,6 +21,10 @@ Routes:
- GET /users → User management page (auth required)
- GET /imports → Import history page (auth required)
- GET /settings → Settings page (auth required)
- GET /platform-homepage → Platform homepage manager (auth required)
- GET /content-pages → Content pages list (auth required)
- GET /content-pages/create → Create content page (auth required)
- GET /content-pages/{page_id}/edit → Edit content page (auth required)
"""
from fastapi import APIRouter, Request, Depends, Path
@@ -306,6 +310,89 @@ async def admin_settings_page(
)
# ============================================================================
# CONTENT MANAGEMENT SYSTEM (CMS) ROUTES
# ============================================================================
@router.get("/platform-homepage", response_class=HTMLResponse, include_in_schema=False)
async def admin_platform_homepage_manager(
request: Request,
current_user: User = Depends(get_current_admin_from_cookie_or_header),
db: Session = Depends(get_db)
):
"""
Render platform homepage manager.
Allows editing the main platform homepage with template selection.
"""
return templates.TemplateResponse(
"admin/platform-homepage.html",
{
"request": request,
"user": current_user,
}
)
@router.get("/content-pages", response_class=HTMLResponse, include_in_schema=False)
async def admin_content_pages_list(
request: Request,
current_user: User = Depends(get_current_admin_from_cookie_or_header),
db: Session = Depends(get_db)
):
"""
Render content pages list.
Shows all platform defaults and vendor overrides with filtering.
"""
return templates.TemplateResponse(
"admin/content-pages.html",
{
"request": request,
"user": current_user,
}
)
@router.get("/content-pages/create", response_class=HTMLResponse, include_in_schema=False)
async def admin_content_page_create(
request: Request,
current_user: User = Depends(get_current_admin_from_cookie_or_header),
db: Session = Depends(get_db)
):
"""
Render create content page form.
Allows creating new platform defaults or vendor-specific pages.
"""
return templates.TemplateResponse(
"admin/content-page-edit.html",
{
"request": request,
"user": current_user,
"page_id": None, # Indicates this is a create operation
}
)
@router.get("/content-pages/{page_id}/edit", response_class=HTMLResponse, include_in_schema=False)
async def admin_content_page_edit(
request: Request,
page_id: int = Path(..., description="Content page ID"),
current_user: User = Depends(get_current_admin_from_cookie_or_header),
db: Session = Depends(get_db)
):
"""
Render edit content page form.
Allows editing existing platform or vendor content pages.
"""
return templates.TemplateResponse(
"admin/content-page-edit.html",
{
"request": request,
"user": current_user,
"page_id": page_id,
}
)
# ============================================================================
# DEVELOPER TOOLS - COMPONENTS & TESTING
# ============================================================================

View File

@@ -176,6 +176,7 @@ class ContentPageService:
content: str,
vendor_id: Optional[int] = None,
content_format: str = "html",
template: str = "default",
meta_description: Optional[str] = None,
meta_keywords: Optional[str] = None,
is_published: bool = False,
@@ -194,6 +195,7 @@ class ContentPageService:
content: HTML or Markdown content
vendor_id: Vendor ID (None for platform default)
content_format: "html" or "markdown"
template: Template name for homepage/landing pages (default, minimal, modern, etc.)
meta_description: SEO description
meta_keywords: SEO keywords
is_published: Publish immediately
@@ -211,6 +213,7 @@ class ContentPageService:
title=title,
content=content,
content_format=content_format,
template=template,
meta_description=meta_description,
meta_keywords=meta_keywords,
is_published=is_published,
@@ -236,6 +239,7 @@ class ContentPageService:
title: Optional[str] = None,
content: Optional[str] = None,
content_format: Optional[str] = None,
template: Optional[str] = None,
meta_description: Optional[str] = None,
meta_keywords: Optional[str] = None,
is_published: Optional[bool] = None,
@@ -253,6 +257,7 @@ class ContentPageService:
title: New title
content: New content
content_format: New format
template: New template name
meta_description: New SEO description
meta_keywords: New SEO keywords
is_published: New publish status
@@ -277,6 +282,8 @@ class ContentPageService:
page.content = content
if content_format is not None:
page.content_format = content_format
if template is not None:
page.template = template
if meta_description is not None:
page.meta_description = meta_description
if meta_keywords is not None:

View File

@@ -0,0 +1,301 @@
{# app/templates/admin/content-page-edit.html #}
{% extends "admin/base.html" %}
{% block title %}{% if page_id %}Edit{% else %}Create{% endif %} Content Page{% endblock %}
{% block alpine_data %}contentPageEditor({{ page_id if page_id else 'null' }}){% endblock %}
{% block content %}
<!-- Page Header -->
<div class="flex items-center justify-between my-6">
<div>
<h2 class="text-2xl font-semibold text-gray-700 dark:text-gray-200">
<span x-text="pageId ? 'Edit Content Page' : 'Create Content Page'"></span>
</h2>
<p class="text-sm text-gray-600 dark:text-gray-400 mt-1">
<span x-show="!pageId">Create a new platform default or vendor-specific page</span>
<span x-show="pageId">Modify an existing content page</span>
</p>
</div>
<div class="flex gap-2">
<a
href="/admin/content-pages"
class="flex items-center px-4 py-2 text-sm font-medium leading-5 text-gray-700 dark:text-gray-200 transition-colors duration-150 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-lg hover:border-gray-400 dark:hover:border-gray-500 focus:outline-none focus:shadow-outline-purple"
>
<span x-html="$icon('arrow-left', 'w-4 h-4 mr-2')"></span>
Back to List
</a>
<button
@click="savePage()"
:disabled="saving"
class="flex items-center px-4 py-2 text-sm font-medium leading-5 text-white transition-colors duration-150 bg-purple-600 border border-transparent rounded-lg hover:bg-purple-700 focus:outline-none focus:shadow-outline-purple disabled:opacity-50 disabled:cursor-not-allowed"
>
<span x-show="!saving" x-html="$icon('check', 'w-4 h-4 mr-2')"></span>
<span x-show="saving" x-html="$icon('spinner', 'w-4 h-4 mr-2')"></span>
<span x-text="saving ? 'Saving...' : (pageId ? 'Update Page' : 'Create Page')"></span>
</button>
</div>
</div>
<!-- Loading State -->
<div x-show="loading" class="text-center py-12">
<span x-html="$icon('spinner', 'inline w-8 h-8 text-purple-600')"></span>
<p class="mt-2 text-gray-600 dark:text-gray-400">Loading page...</p>
</div>
<!-- Error State -->
<div x-show="error && !loading" class="mb-6 p-4 bg-red-100 border border-red-400 text-red-700 rounded-lg flex items-start">
<span x-html="$icon('exclamation', 'w-5 h-5 mr-3 mt-0.5 flex-shrink-0')"></span>
<div>
<p class="font-semibold">Error</p>
<p class="text-sm" x-text="error"></p>
</div>
</div>
<!-- Success Message -->
<div x-show="successMessage" x-transition class="mb-6 p-4 bg-green-100 border border-green-400 text-green-700 rounded-lg flex items-start">
<span x-html="$icon('check-circle', 'w-5 h-5 mr-3 mt-0.5 flex-shrink-0')"></span>
<div>
<p class="font-semibold" x-text="successMessage"></p>
</div>
</div>
<!-- Main Form -->
<div x-show="!loading" class="bg-white dark:bg-gray-800 rounded-lg shadow-md overflow-hidden">
<form @submit.prevent="savePage()">
<!-- Basic Information -->
<div class="p-6 border-b border-gray-200 dark:border-gray-700">
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">
Basic Information
</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<!-- Page Title -->
<div class="md:col-span-2">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Page Title <span class="text-red-500">*</span>
</label>
<input
type="text"
x-model="form.title"
required
maxlength="200"
class="w-full px-3 py-2 text-gray-700 dark:text-gray-300 border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:border-purple-500 dark:bg-gray-700"
placeholder="About Us"
>
</div>
<!-- Slug -->
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Slug <span class="text-red-500">*</span>
</label>
<input
type="text"
x-model="form.slug"
required
maxlength="100"
pattern="[a-z0-9_-]+"
class="w-full px-3 py-2 text-gray-700 dark:text-gray-300 border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:border-purple-500 dark:bg-gray-700"
placeholder="about"
>
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
URL-safe identifier (lowercase, numbers, hyphens, underscores only)
</p>
</div>
<!-- Vendor ID (Platform vs Vendor-specific) -->
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Page Type
</label>
<select
x-model="form.vendor_id"
class="w-full px-3 py-2 text-gray-700 dark:text-gray-300 border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:border-purple-500 dark:bg-gray-700"
>
<option :value="null">Platform Default</option>
<!-- TODO: Load vendors dynamically if needed -->
</select>
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
Platform defaults are shown to all vendors
</p>
</div>
</div>
</div>
<!-- Content -->
<div class="p-6 border-b border-gray-200 dark:border-gray-700">
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">
Page Content
</h3>
<!-- Content Format -->
<div class="mb-4">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Content Format
</label>
<div class="flex gap-4">
<label class="flex items-center cursor-pointer">
<input type="radio" x-model="form.content_format" value="html" class="mr-2">
<span class="text-sm">HTML</span>
</label>
<label class="flex items-center cursor-pointer">
<input type="radio" x-model="form.content_format" value="markdown" class="mr-2">
<span class="text-sm">Markdown</span>
</label>
</div>
</div>
<!-- Content Editor -->
<div class="mb-4">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Content <span class="text-red-500">*</span>
</label>
<textarea
x-model="form.content"
required
rows="12"
class="w-full px-3 py-2 text-gray-700 dark:text-gray-300 border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:border-purple-500 dark:bg-gray-700 font-mono text-sm"
placeholder="<h2>Your content here...</h2>"
></textarea>
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
<span x-show="form.content_format === 'html'">Enter HTML content. Basic HTML tags are supported.</span>
<span x-show="form.content_format === 'markdown'">Enter Markdown content. Will be converted to HTML.</span>
</p>
</div>
</div>
<!-- SEO Settings -->
<div class="p-6 border-b border-gray-200 dark:border-gray-700">
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">
SEO & Metadata
</h3>
<div class="space-y-4">
<!-- Meta Description -->
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Meta Description
</label>
<textarea
x-model="form.meta_description"
rows="2"
maxlength="300"
class="w-full px-3 py-2 text-gray-700 dark:text-gray-300 border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:border-purple-500 dark:bg-gray-700"
placeholder="A brief description for search engines"
></textarea>
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
<span x-text="(form.meta_description || '').length"></span>/300 characters (150-160 recommended)
</p>
</div>
<!-- Meta Keywords -->
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Meta Keywords
</label>
<input
type="text"
x-model="form.meta_keywords"
maxlength="300"
class="w-full px-3 py-2 text-gray-700 dark:text-gray-300 border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:border-purple-500 dark:bg-gray-700"
placeholder="keyword1, keyword2, keyword3"
>
</div>
</div>
</div>
<!-- Navigation & Display Settings -->
<div class="p-6 border-b border-gray-200 dark:border-gray-700">
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">
Navigation & Display
</h3>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<!-- Display Order -->
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Display Order
</label>
<input
type="number"
x-model.number="form.display_order"
min="0"
class="w-full px-3 py-2 text-gray-700 dark:text-gray-300 border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:border-purple-500 dark:bg-gray-700"
>
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">Lower = first</p>
</div>
<!-- Show in Header -->
<div class="flex items-center">
<label class="flex items-center cursor-pointer">
<input
type="checkbox"
x-model="form.show_in_header"
class="w-5 h-5 text-purple-600 border-gray-300 rounded focus:ring-purple-500"
>
<span class="ml-3 text-sm font-medium text-gray-900 dark:text-white">
Show in Header
</span>
</label>
</div>
<!-- Show in Footer -->
<div class="flex items-center">
<label class="flex items-center cursor-pointer">
<input
type="checkbox"
x-model="form.show_in_footer"
class="w-5 h-5 text-purple-600 border-gray-300 rounded focus:ring-purple-500"
>
<span class="ml-3 text-sm font-medium text-gray-900 dark:text-white">
Show in Footer
</span>
</label>
</div>
</div>
</div>
<!-- Publishing Settings -->
<div class="p-6 bg-gray-50 dark:bg-gray-700/50">
<div class="flex items-center justify-between">
<div>
<label class="flex items-center cursor-pointer">
<input
type="checkbox"
x-model="form.is_published"
class="w-5 h-5 text-purple-600 border-gray-300 rounded focus:ring-purple-500"
>
<span class="ml-3 text-sm font-medium text-gray-900 dark:text-white">
Published
</span>
</label>
<p class="ml-8 text-xs text-gray-500 dark:text-gray-400">
Make this page visible to the public
</p>
</div>
<div class="flex gap-2">
<a
href="/admin/content-pages"
class="px-6 py-2 text-sm font-medium leading-5 text-gray-700 dark:text-gray-200 transition-colors duration-150 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-lg hover:border-gray-400 dark:hover:border-gray-500"
>
Cancel
</a>
<button
type="submit"
:disabled="saving"
class="px-6 py-2 text-sm font-medium leading-5 text-white transition-colors duration-150 bg-purple-600 border border-transparent rounded-lg hover:bg-purple-700 focus:outline-none focus:shadow-outline-purple disabled:opacity-50 disabled:cursor-not-allowed"
>
<span x-text="saving ? 'Saving...' : (pageId ? 'Update Page' : 'Create Page')"></span>
</button>
</div>
</div>
</div>
</form>
</div>
{% endblock %}
{% block extra_scripts %}
<script src="{{ url_for('static', path='admin/js/content-page-edit.js') }}"></script>
{% endblock %}

View File

@@ -0,0 +1,200 @@
{# app/templates/admin/content-pages.html #}
{% extends "admin/base.html" %}
{% block title %}Content Pages{% endblock %}
{% block alpine_data %}contentPagesManager(){% endblock %}
{% block content %}
<!-- Page Header -->
<div class="flex items-center justify-between my-6">
<div>
<h2 class="text-2xl font-semibold text-gray-700 dark:text-gray-200">
Content Pages
</h2>
<p class="text-sm text-gray-600 dark:text-gray-400 mt-1">
Manage platform defaults and vendor-specific content pages
</p>
</div>
<a
href="/admin/content-pages/create"
class="flex items-center px-4 py-2 text-sm font-medium leading-5 text-white transition-colors duration-150 bg-purple-600 border border-transparent rounded-lg hover:bg-purple-700 focus:outline-none focus:shadow-outline-purple"
>
<span x-html="$icon('plus', 'w-4 h-4 mr-2')"></span>
Create Page
</a>
</div>
<!-- Loading State -->
<div x-show="loading" class="text-center py-12">
<span x-html="$icon('spinner', 'inline w-8 h-8 text-purple-600')"></span>
<p class="mt-2 text-gray-600 dark:text-gray-400">Loading pages...</p>
</div>
<!-- Error State -->
<div x-show="error && !loading" class="mb-6 p-4 bg-red-100 border border-red-400 text-red-700 rounded-lg flex items-start">
<span x-html="$icon('exclamation', 'w-5 h-5 mr-3 mt-0.5 flex-shrink-0')"></span>
<div>
<p class="font-semibold">Error loading pages</p>
<p class="text-sm" x-text="error"></p>
</div>
</div>
<!-- Tabs and Filters -->
<div x-show="!loading" class="mb-6 bg-white dark:bg-gray-800 rounded-lg shadow-md p-4">
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
<!-- Tabs -->
<div class="flex space-x-2 border-b border-gray-200 dark:border-gray-700">
<button
@click="activeTab = 'all'"
class="px-4 py-2 text-sm font-medium transition-colors"
:class="activeTab === 'all' ? 'text-purple-600 border-b-2 border-purple-600' : 'text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200'"
>
All Pages
<span class="ml-2 px-2 py-0.5 text-xs rounded-full bg-gray-100 dark:bg-gray-700" x-text="allPages.length"></span>
</button>
<button
@click="activeTab = 'platform'"
class="px-4 py-2 text-sm font-medium transition-colors"
:class="activeTab === 'platform' ? 'text-purple-600 border-b-2 border-purple-600' : 'text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200'"
>
Platform Defaults
<span class="ml-2 px-2 py-0.5 text-xs rounded-full bg-gray-100 dark:bg-gray-700" x-text="platformPages.length"></span>
</button>
<button
@click="activeTab = 'vendor'"
class="px-4 py-2 text-sm font-medium transition-colors"
:class="activeTab === 'vendor' ? 'text-purple-600 border-b-2 border-purple-600' : 'text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200'"
>
Vendor Overrides
<span class="ml-2 px-2 py-0.5 text-xs rounded-full bg-gray-100 dark:bg-gray-700" x-text="vendorPages.length"></span>
</button>
</div>
<!-- Search -->
<div class="relative">
<span class="absolute inset-y-0 left-0 flex items-center pl-3">
<span x-html="$icon('search', 'w-5 h-5 text-gray-400')"></span>
</span>
<input
type="text"
x-model="searchQuery"
placeholder="Search pages..."
class="pl-10 pr-4 py-2 text-sm text-gray-700 dark:text-gray-300 bg-gray-50 dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:border-purple-500"
>
</div>
</div>
</div>
<!-- Pages Table -->
<div x-show="!loading && filteredPages.length > 0" class="bg-white dark:bg-gray-800 rounded-lg shadow-md overflow-hidden">
<div class="overflow-x-auto">
<table class="w-full whitespace-nowrap">
<thead>
<tr class="text-xs font-semibold tracking-wide text-left text-gray-500 uppercase border-b dark:border-gray-700 bg-gray-50 dark:bg-gray-700/50 dark:text-gray-400">
<th class="px-4 py-3">Page</th>
<th class="px-4 py-3">Slug</th>
<th class="px-4 py-3">Type</th>
<th class="px-4 py-3">Status</th>
<th class="px-4 py-3">Navigation</th>
<th class="px-4 py-3">Updated</th>
<th class="px-4 py-3">Actions</th>
</tr>
</thead>
<tbody class="bg-white dark:bg-gray-800 divide-y dark:divide-gray-700">
<template x-for="page in filteredPages" :key="page.id">
<tr class="text-gray-700 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-700/50">
<!-- Page Title -->
<td class="px-4 py-3">
<div>
<p class="font-semibold text-gray-900 dark:text-white" x-text="page.title"></p>
<p class="text-xs text-gray-600 dark:text-gray-400" x-show="page.vendor_name">
Vendor: <span x-text="page.vendor_name"></span>
</p>
</div>
</td>
<!-- Slug -->
<td class="px-4 py-3 text-sm">
<code class="px-2 py-1 text-xs bg-gray-100 dark:bg-gray-700 rounded" x-text="'/' + page.slug"></code>
</td>
<!-- Type -->
<td class="px-4 py-3 text-sm">
<span
class="px-2 py-1 text-xs font-semibold rounded-full"
:class="page.is_platform_default ? 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200' : 'bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-200'"
x-text="page.is_platform_default ? 'Platform' : 'Vendor'"
></span>
</td>
<!-- Status -->
<td class="px-4 py-3 text-sm">
<span
class="px-2 py-1 text-xs font-semibold rounded-full"
:class="page.is_published ? 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200' : 'bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-300'"
x-text="page.is_published ? 'Published' : 'Draft'"
></span>
</td>
<!-- Navigation -->
<td class="px-4 py-3 text-xs">
<div class="flex gap-1">
<span x-show="page.show_in_header" class="px-1.5 py-0.5 bg-indigo-100 text-indigo-800 dark:bg-indigo-900 dark:text-indigo-200 rounded">Header</span>
<span x-show="page.show_in_footer" class="px-1.5 py-0.5 bg-teal-100 text-teal-800 dark:bg-teal-900 dark:text-teal-200 rounded">Footer</span>
<span x-show="!page.show_in_header && !page.show_in_footer" class="text-gray-400"></span>
</div>
</td>
<!-- Updated -->
<td class="px-4 py-3 text-xs" x-text="formatDate(page.updated_at)"></td>
<!-- Actions -->
<td class="px-4 py-3">
<div class="flex items-center space-x-2 text-sm">
<a
:href="`/admin/content-pages/${page.id}/edit`"
class="flex items-center justify-center p-2 text-purple-600 rounded-lg hover:bg-purple-50 dark:text-purple-400 dark:hover:bg-gray-700 focus:outline-none transition-colors"
title="Edit"
>
<span x-html="$icon('pencil', 'w-5 h-5')"></span>
</a>
<button
@click="deletePage(page)"
class="flex items-center justify-center p-2 text-red-600 rounded-lg hover:bg-red-50 dark:text-red-400 dark:hover:bg-gray-700 focus:outline-none transition-colors"
title="Delete"
>
<span x-html="$icon('trash', 'w-5 h-5')"></span>
</button>
</div>
</td>
</tr>
</template>
</tbody>
</table>
</div>
</div>
<!-- Empty State -->
<div x-show="!loading && filteredPages.length === 0" class="text-center py-12 bg-white dark:bg-gray-800 rounded-lg shadow-md">
<span x-html="$icon('document-text', 'inline w-16 h-16 text-gray-400 mb-4')"></span>
<h3 class="text-lg font-semibold text-gray-700 dark:text-gray-300 mb-2">No pages found</h3>
<p class="text-gray-500 dark:text-gray-400 mb-4" x-show="searchQuery">
No pages match your search: "<span x-text="searchQuery"></span>"
</p>
<p class="text-gray-500 dark:text-gray-400 mb-4" x-show="!searchQuery && activeTab === 'vendor'">
No vendor-specific pages have been created yet.
</p>
<a
href="/admin/content-pages/create"
class="inline-flex items-center px-4 py-2 text-sm font-medium text-white bg-purple-600 rounded-lg hover:bg-purple-700"
>
<span x-html="$icon('plus', 'w-4 h-4 mr-2')"></span>
Create First Page
</a>
</div>
{% endblock %}
{% block extra_scripts %}
<script src="{{ url_for('static', path='admin/js/content-pages.js') }}"></script>
{% endblock %}

View File

@@ -54,6 +54,37 @@
</li>
</ul>
<!-- Content Management Section -->
<div class="px-6 my-6">
<hr class="border-gray-200 dark:border-gray-700" />
</div>
<p class="px-6 text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase tracking-wider">
Content Management
</p>
<ul class="mt-3">
<!-- Platform Homepage -->
<li class="relative px-6 py-3">
<span x-show="currentPage === 'platform-homepage'" class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg" aria-hidden="true"></span>
<a class="inline-flex items-center w-full text-sm font-semibold transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200"
:class="currentPage === 'platform-homepage' ? 'text-gray-800 dark:text-gray-100' : ''"
href="/admin/platform-homepage">
<span x-html="$icon('home')"></span>
<span class="ml-4">Platform Homepage</span>
</a>
</li>
<!-- Content Pages -->
<li class="relative px-6 py-3">
<span x-show="currentPage === 'content-pages'" class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg" aria-hidden="true"></span>
<a class="inline-flex items-center w-full text-sm font-semibold transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200"
:class="currentPage === 'content-pages' ? 'text-gray-800 dark:text-gray-100' : ''"
href="/admin/content-pages">
<span x-html="$icon('document-text')"></span>
<span class="ml-4">Content Pages</span>
</a>
</li>
</ul>
<!-- Developer Tools Section -->
<div class="px-6 my-6">
<hr class="border-gray-200 dark:border-gray-700" />
@@ -188,6 +219,37 @@
</li>
</ul>
<!-- Content Management Section -->
<div class="px-6 my-6">
<hr class="border-gray-200 dark:border-gray-700" />
</div>
<p class="px-6 text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase tracking-wider">
Content Management
</p>
<ul class="mt-3">
<!-- Platform Homepage -->
<li class="relative px-6 py-3">
<span x-show="currentPage === 'platform-homepage'" class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg" aria-hidden="true"></span>
<a class="inline-flex items-center w-full text-sm font-semibold transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200"
:class="currentPage === 'platform-homepage' ? 'text-gray-800 dark:text-gray-100' : ''"
href="/admin/platform-homepage">
<span x-html="$icon('home')"></span>
<span class="ml-4">Platform Homepage</span>
</a>
</li>
<!-- Content Pages -->
<li class="relative px-6 py-3">
<span x-show="currentPage === 'content-pages'" class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg" aria-hidden="true"></span>
<a class="inline-flex items-center w-full text-sm font-semibold transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200"
:class="currentPage === 'content-pages' ? 'text-gray-800 dark:text-gray-100' : ''"
href="/admin/content-pages">
<span x-html="$icon('document-text')"></span>
<span class="ml-4">Content Pages</span>
</a>
</li>
</ul>
<!-- Developer Tools Section -->
<div class="px-6 my-6">
<hr class="border-gray-200 dark:border-gray-700" />

View File

@@ -0,0 +1,248 @@
{# app/templates/admin/platform-homepage.html #}
{% extends "admin/base.html" %}
{% block title %}Platform Homepage Manager{% endblock %}
{% block alpine_data %}platformHomepageManager(){% endblock %}
{% block content %}
<!-- Page Header -->
<div class="flex items-center justify-between my-6">
<div>
<h2 class="text-2xl font-semibold text-gray-700 dark:text-gray-200">
Platform Homepage
</h2>
<p class="text-sm text-gray-600 dark:text-gray-400 mt-1">
Manage your platform's main landing page at <a href="/" target="_blank" class="text-purple-600 hover:underline">localhost:8000</a>
</p>
</div>
<div class="flex gap-2">
<a
href="/"
target="_blank"
class="flex items-center px-4 py-2 text-sm font-medium leading-5 text-gray-700 dark:text-gray-200 transition-colors duration-150 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-lg hover:border-gray-400 dark:hover:border-gray-500 focus:outline-none focus:shadow-outline-purple"
>
<span x-html="$icon('eye', 'w-4 h-4 mr-2')"></span>
Preview
</a>
<button
@click="savePage()"
:disabled="saving"
class="flex items-center px-4 py-2 text-sm font-medium leading-5 text-white transition-colors duration-150 bg-purple-600 border border-transparent rounded-lg hover:bg-purple-700 focus:outline-none focus:shadow-outline-purple disabled:opacity-50 disabled:cursor-not-allowed"
>
<span x-show="!saving" x-html="$icon('check', 'w-4 h-4 mr-2')"></span>
<span x-show="saving" x-html="$icon('spinner', 'w-4 h-4 mr-2')"></span>
<span x-text="saving ? 'Saving...' : 'Save Changes'"></span>
</button>
</div>
</div>
<!-- Loading State -->
<div x-show="loading" class="text-center py-12">
<span x-html="$icon('spinner', 'inline w-8 h-8 text-purple-600')"></span>
<p class="mt-2 text-gray-600 dark:text-gray-400">Loading homepage...</p>
</div>
<!-- Error State -->
<div x-show="error && !loading" class="mb-6 p-4 bg-red-100 border border-red-400 text-red-700 rounded-lg flex items-start">
<span x-html="$icon('exclamation', 'w-5 h-5 mr-3 mt-0.5 flex-shrink-0')"></span>
<div>
<p class="font-semibold">Error loading homepage</p>
<p class="text-sm" x-text="error"></p>
</div>
</div>
<!-- Success Message -->
<div x-show="successMessage" x-transition class="mb-6 p-4 bg-green-100 border border-green-400 text-green-700 rounded-lg flex items-start">
<span x-html="$icon('check-circle', 'w-5 h-5 mr-3 mt-0.5 flex-shrink-0')"></span>
<div>
<p class="font-semibold" x-text="successMessage"></p>
</div>
</div>
<!-- Main Form -->
<div x-show="!loading && page" class="bg-white dark:bg-gray-800 rounded-lg shadow-md overflow-hidden">
<form @submit.prevent="savePage()">
<!-- Template Selection -->
<div class="p-6 border-b border-gray-200 dark:border-gray-700">
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">
Template Selection
</h3>
<p class="text-sm text-gray-600 dark:text-gray-400 mb-4">
Choose the visual style for your homepage. Each template offers a different layout and design.
</p>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<!-- Default Template -->
<label class="cursor-pointer">
<input type="radio" name="template" value="default" x-model="page.template" class="sr-only">
<div
class="border-2 rounded-lg p-4 transition-all"
:class="page.template === 'default' ? 'border-purple-600 bg-purple-50 dark:bg-purple-900/20' : 'border-gray-300 dark:border-gray-600 hover:border-gray-400'"
>
<div class="flex items-center justify-between mb-2">
<span class="font-semibold text-gray-900 dark:text-white">Default</span>
<span x-show="page.template === 'default'" x-html="$icon('check-circle', 'w-5 h-5 text-purple-600')"></span>
</div>
<p class="text-sm text-gray-600 dark:text-gray-400">
Full-featured with hero section, features grid, vendor cards, and call-to-action.
</p>
<div class="mt-3 h-24 bg-gradient-to-r from-purple-400 to-pink-400 rounded"></div>
</div>
</label>
<!-- Minimal Template -->
<label class="cursor-pointer">
<input type="radio" name="template" value="minimal" x-model="page.template" class="sr-only">
<div
class="border-2 rounded-lg p-4 transition-all"
:class="page.template === 'minimal' ? 'border-purple-600 bg-purple-50 dark:bg-purple-900/20' : 'border-gray-300 dark:border-gray-600 hover:border-gray-400'"
>
<div class="flex items-center justify-between mb-2">
<span class="font-semibold text-gray-900 dark:text-white">Minimal</span>
<span x-show="page.template === 'minimal'" x-html="$icon('check-circle', 'w-5 h-5 text-purple-600')"></span>
</div>
<p class="text-sm text-gray-600 dark:text-gray-400">
Clean and simple design with emoji icons and essential information only.
</p>
<div class="mt-3 h-24 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded"></div>
</div>
</label>
<!-- Modern Template -->
<label class="cursor-pointer">
<input type="radio" name="template" value="modern" x-model="page.template" class="sr-only">
<div
class="border-2 rounded-lg p-4 transition-all"
:class="page.template === 'modern' ? 'border-purple-600 bg-purple-50 dark:bg-purple-900/20' : 'border-gray-300 dark:border-gray-600 hover:border-gray-400'"
>
<div class="flex items-center justify-between mb-2">
<span class="font-semibold text-gray-900 dark:text-white">Modern</span>
<span x-show="page.template === 'modern'" x-html="$icon('check-circle', 'w-5 h-5 text-purple-600')"></span>
</div>
<p class="text-sm text-gray-600 dark:text-gray-400">
Trendy design with animated gradients, stats, and modern UI elements.
</p>
<div class="mt-3 h-24 bg-gradient-to-r from-indigo-500 via-purple-500 to-pink-500 rounded"></div>
</div>
</label>
</div>
</div>
<!-- Page Content -->
<div class="p-6 border-b border-gray-200 dark:border-gray-700">
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">
Page Content
</h3>
<!-- Title -->
<div class="mb-4">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Page Title
</label>
<input
type="text"
x-model="page.title"
required
maxlength="200"
class="w-full px-3 py-2 text-gray-700 dark:text-gray-300 border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:border-purple-500 dark:bg-gray-700"
placeholder="Welcome to Our Multi-Vendor Marketplace"
>
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
Main heading displayed on the homepage
</p>
</div>
<!-- Content (HTML) -->
<div class="mb-4">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Content (HTML)
</label>
<textarea
x-model="page.content"
rows="6"
class="w-full px-3 py-2 text-gray-700 dark:text-gray-300 border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:border-purple-500 dark:bg-gray-700 font-mono text-sm"
placeholder="<p>Your platform description here...</p>"
></textarea>
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
HTML content shown below the title. Basic HTML tags are supported.
</p>
</div>
</div>
<!-- SEO Settings -->
<div class="p-6 border-b border-gray-200 dark:border-gray-700">
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">
SEO Settings
</h3>
<!-- Meta Description -->
<div class="mb-4">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Meta Description
</label>
<textarea
x-model="page.meta_description"
rows="2"
maxlength="300"
class="w-full px-3 py-2 text-gray-700 dark:text-gray-300 border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:border-purple-500 dark:bg-gray-700"
placeholder="A brief description for search engines (150-160 characters recommended)"
></textarea>
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
<span x-text="(page.meta_description || '').length"></span>/300 characters
</p>
</div>
<!-- Meta Keywords -->
<div class="mb-4">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Meta Keywords
</label>
<input
type="text"
x-model="page.meta_keywords"
maxlength="300"
class="w-full px-3 py-2 text-gray-700 dark:text-gray-300 border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:border-purple-500 dark:bg-gray-700"
placeholder="marketplace, multi-vendor, e-commerce"
>
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
Comma-separated keywords (optional)
</p>
</div>
</div>
<!-- Publishing Settings -->
<div class="p-6 bg-gray-50 dark:bg-gray-700/50">
<div class="flex items-center justify-between">
<div>
<label class="flex items-center cursor-pointer">
<input
type="checkbox"
x-model="page.is_published"
class="w-5 h-5 text-purple-600 border-gray-300 rounded focus:ring-purple-500"
>
<span class="ml-3 text-sm font-medium text-gray-900 dark:text-white">
Published
</span>
</label>
<p class="ml-8 text-xs text-gray-500 dark:text-gray-400">
Make this homepage visible to the public
</p>
</div>
<button
type="submit"
:disabled="saving"
class="px-6 py-2 text-sm font-medium leading-5 text-white transition-colors duration-150 bg-purple-600 border border-transparent rounded-lg hover:bg-purple-700 focus:outline-none focus:shadow-outline-purple disabled:opacity-50 disabled:cursor-not-allowed"
>
<span x-text="saving ? 'Saving...' : 'Save Changes'"></span>
</button>
</div>
</div>
</form>
</div>
{% endblock %}
{% block extra_scripts %}
<script src="{{ url_for('static', path='admin/js/platform-homepage.js') }}"></script>
{% endblock %}

View File

@@ -0,0 +1,299 @@
{# app/templates/platform/base.html #}
{# Base template for platform public pages (homepage, about, faq, etc.) #}
<!DOCTYPE html>
<html lang="en" x-data="platformLayoutData()" x-bind:class="{ 'dark': dark }">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
{# Dynamic page title #}
<title>{% block title %}Multi-Vendor Marketplace Platform{% endblock %}</title>
{# SEO Meta Tags #}
<meta name="description" content="{% block meta_description %}Leading multi-vendor marketplace platform connecting vendors with customers worldwide.{% endblock %}">
<meta name="keywords" content="{% block meta_keywords %}marketplace, multi-vendor, e-commerce, online shopping{% endblock %}">
{# Favicon #}
<link rel="icon" type="image/x-icon" href="{{ url_for('static', path='favicon.ico') }}">
{# Platform color scheme #}
<style id="platform-theme-variables">
:root {
/* Platform Colors */
--color-primary: #6366f1; /* Indigo */
--color-secondary: #8b5cf6; /* Purple */
--color-accent: #ec4899; /* Pink */
/* Typography */
--font-heading: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
--font-body: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
}
/* Dark mode colors */
.dark {
--color-primary: #818cf8;
--color-secondary: #a78bfa;
--color-accent: #f472b6;
}
</style>
{# Fonts: Local fallback + Google Fonts #}
<link href="/static/shared/fonts/inter.css" rel="stylesheet" />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet" />
{# Tailwind CSS with local fallback #}
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css"
onerror="this.onerror=null; this.href='{{ url_for('static', path='shared/css/tailwind.min.css') }}';">
{# Platform-specific styles #}
<style>
/* Smooth scrolling */
html {
scroll-behavior: smooth;
}
/* Custom gradients */
.gradient-primary {
background: linear-gradient(135deg, var(--color-primary) 0%, var(--color-secondary) 100%);
}
.gradient-accent {
background: linear-gradient(135deg, var(--color-secondary) 0%, var(--color-accent) 100%);
}
/* Button styles */
.btn-primary {
background-color: var(--color-primary);
color: white;
padding: 0.75rem 1.5rem;
border-radius: 0.5rem;
font-weight: 600;
transition: all 0.2s;
}
.btn-primary:hover {
opacity: 0.9;
transform: translateY(-1px);
}
/* Card hover effect */
.card-hover {
transition: all 0.3s ease;
}
.card-hover:hover {
transform: translateY(-4px);
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
}
</style>
{% block extra_head %}{% endblock %}
</head>
<body class="bg-gray-50 dark:bg-gray-900 text-gray-900 dark:text-gray-100 transition-colors duration-200">
{# Header / Navigation #}
<header class="sticky top-0 z-50 bg-white dark:bg-gray-800 shadow-sm border-b border-gray-200 dark:border-gray-700">
<nav class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex justify-between items-center h-16">
{# Logo / Brand #}
<div class="flex items-center">
<a href="/" class="flex items-center space-x-3">
<div class="w-8 h-8 rounded-lg gradient-primary flex items-center justify-center">
<span class="text-white font-bold text-xl">M</span>
</div>
<span class="text-xl font-bold text-gray-900 dark:text-white">
Marketplace
</span>
</a>
</div>
{# Desktop Navigation #}
<div class="hidden md:flex items-center space-x-8">
{# Dynamic header navigation from CMS #}
{% if header_pages %}
{% for page in header_pages %}
<a href="/{{ page.slug }}"
class="text-gray-700 dark:text-gray-300 hover:text-primary dark:hover:text-primary font-medium transition-colors">
{{ page.title }}
</a>
{% endfor %}
{% endif %}
{# Dark mode toggle #}
<button
@click="toggleDarkMode()"
class="p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors"
aria-label="Toggle dark mode"
>
<svg x-show="!dark" class="w-5 h-5 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z"></path>
</svg>
<svg x-show="dark" class="w-5 h-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z"></path>
</svg>
</button>
</div>
{# Mobile menu button #}
<div class="md:hidden">
<button
@click="mobileMenuOpen = !mobileMenuOpen"
class="p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700"
aria-label="Toggle menu"
>
<svg x-show="!mobileMenuOpen" class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path>
</svg>
<svg x-show="mobileMenuOpen" class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>
</button>
</div>
</div>
{# Mobile menu #}
<div x-show="mobileMenuOpen" x-cloak class="md:hidden py-4 border-t border-gray-200 dark:border-gray-700">
{% if header_pages %}
{% for page in header_pages %}
<a href="/{{ page.slug }}"
class="block px-4 py-2 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg">
{{ page.title }}
</a>
{% endfor %}
{% endif %}
</div>
</nav>
</header>
{# Main Content #}
<main class="min-h-screen">
{% block content %}{% endblock %}
</main>
{# Footer #}
<footer class="bg-white dark:bg-gray-800 border-t border-gray-200 dark:border-gray-700 mt-16">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
<div class="grid grid-cols-1 md:grid-cols-4 gap-8">
{# Brand Column #}
<div class="col-span-1">
<div class="flex items-center space-x-3 mb-4">
<div class="w-8 h-8 rounded-lg gradient-primary flex items-center justify-center">
<span class="text-white font-bold text-xl">M</span>
</div>
<span class="text-xl font-bold text-gray-900 dark:text-white">
Marketplace
</span>
</div>
<p class="text-gray-600 dark:text-gray-400 text-sm">
Connecting vendors with customers worldwide. Build your online store today.
</p>
</div>
{# Quick Links #}
<div>
<h4 class="font-semibold text-gray-900 dark:text-white mb-4">Quick Links</h4>
<ul class="space-y-2">
{% if footer_pages %}
{% for page in footer_pages %}
<li>
<a href="/{{ page.slug }}"
class="text-gray-600 dark:text-gray-400 hover:text-primary dark:hover:text-primary transition-colors">
{{ page.title }}
</a>
</li>
{% endfor %}
{% endif %}
</ul>
</div>
{# Platform Links #}
<div>
<h4 class="font-semibold text-gray-900 dark:text-white mb-4">Platform</h4>
<ul class="space-y-2">
<li>
<a href="/admin/login" class="text-gray-600 dark:text-gray-400 hover:text-primary transition-colors">
Admin Login
</a>
</li>
<li>
<a href="/vendor/wizamart/login" class="text-gray-600 dark:text-gray-400 hover:text-primary transition-colors">
Vendor Login
</a>
</li>
</ul>
</div>
{# Contact Info #}
<div>
<h4 class="font-semibold text-gray-900 dark:text-white mb-4">Contact</h4>
<ul class="space-y-2 text-gray-600 dark:text-gray-400 text-sm">
<li>support@marketplace.com</li>
<li>+1 (555) 123-4567</li>
<li>123 Business St, Suite 100</li>
<li>San Francisco, CA 94102</li>
</ul>
</div>
</div>
{# Bottom Bar #}
<div class="mt-12 pt-8 border-t border-gray-200 dark:border-gray-700">
<div class="flex flex-col md:flex-row justify-between items-center">
<p class="text-gray-600 dark:text-gray-400 text-sm">
© 2024 Marketplace Platform. All rights reserved.
</p>
<div class="flex space-x-6 mt-4 md:mt-0">
<a href="/privacy" class="text-gray-600 dark:text-gray-400 hover:text-primary text-sm transition-colors">
Privacy Policy
</a>
<a href="/terms" class="text-gray-600 dark:text-gray-400 hover:text-primary text-sm transition-colors">
Terms of Service
</a>
</div>
</div>
</div>
</div>
</footer>
{# Scripts #}
<!-- Alpine.js for interactivity -->
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.13.3/dist/cdn.min.js"></script>
<!-- Platform layout data -->
<script>
function platformLayoutData() {
return {
// Dark mode
dark: localStorage.getItem('darkMode') === 'true',
// Mobile menu
mobileMenuOpen: false,
// Initialize
init() {
// Apply dark mode on load
if (this.dark) {
document.documentElement.classList.add('dark');
}
},
// Toggle dark mode
toggleDarkMode() {
this.dark = !this.dark;
localStorage.setItem('darkMode', this.dark);
if (this.dark) {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
}
};
}
</script>
{% block extra_scripts %}{% endblock %}
</body>
</html>

View File

@@ -0,0 +1,246 @@
{# app/templates/platform/content-page.html #}
{# Generic template for platform content pages (About, FAQ, Terms, Contact, etc.) #}
{% extends "platform/base.html" %}
{% block title %}{{ page.title }} - Marketplace{% endblock %}
{% block meta_description %}
{% if page.meta_description %}
{{ page.meta_description }}
{% else %}
{{ page.title }} - Multi-Vendor Marketplace Platform
{% endif %}
{% endblock %}
{% block meta_keywords %}
{% if page.meta_keywords %}
{{ page.meta_keywords }}
{% else %}
{{ page.title }}, marketplace, platform
{% endif %}
{% endblock %}
{% block content %}
<div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
{# Breadcrumbs #}
<nav class="flex mb-8 text-sm" aria-label="Breadcrumb">
<ol class="inline-flex items-center space-x-2">
<li class="inline-flex items-center">
<a href="/" class="text-gray-600 dark:text-gray-400 hover:text-primary dark:hover:text-primary transition-colors">
<svg class="w-4 h-4 mr-2" fill="currentColor" viewBox="0 0 20 20">
<path d="M10.707 2.293a1 1 0 00-1.414 0l-7 7a1 1 0 001.414 1.414L4 10.414V17a1 1 0 001 1h2a1 1 0 001-1v-2a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 001 1h2a1 1 0 001-1v-6.586l.293.293a1 1 0 001.414-1.414l-7-7z"></path>
</svg>
Home
</a>
</li>
<li>
<div class="flex items-center">
<svg class="w-4 h-4 text-gray-400 mx-2" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"></path>
</svg>
<span class="text-gray-700 dark:text-gray-300 font-medium">{{ page.title }}</span>
</div>
</li>
</ol>
</nav>
{# Page Header #}
<div class="mb-12">
<h1 class="text-4xl md:text-5xl font-bold text-gray-900 dark:text-white mb-4">
{{ page.title }}
</h1>
{# Published date (if available) #}
{% if page.published_at %}
<div class="flex items-center text-sm text-gray-600 dark:text-gray-400">
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
</svg>
<span>Published {{ page.published_at.strftime('%B %d, %Y') }}</span>
</div>
{% endif %}
</div>
{# Page Content #}
<div class="bg-white dark:bg-gray-800 rounded-2xl shadow-sm p-8 md:p-12">
<div class="prose prose-lg dark:prose-invert max-w-none">
{% if page.content_format == 'markdown' %}
{# Future enhancement: Render with markdown library #}
<div class="markdown-content">
{{ page.content | safe }}
</div>
{% else %}
{# HTML content (default) #}
{{ page.content | safe }}
{% endif %}
</div>
</div>
{# Last updated timestamp #}
{% if page.updated_at %}
<div class="mt-8 pt-6 border-t border-gray-200 dark:border-gray-700 text-center">
<p class="text-sm text-gray-500 dark:text-gray-400">
Last updated: {{ page.updated_at.strftime('%B %d, %Y') }}
</p>
</div>
{% endif %}
{# Call-to-action section (for specific pages) #}
{% if page.slug in ['about', 'contact'] %}
<div class="mt-12 bg-gradient-to-r from-purple-50 to-pink-50 dark:from-purple-900/20 dark:to-pink-900/20 rounded-2xl p-8 text-center">
<h3 class="text-2xl font-bold text-gray-900 dark:text-white mb-4">
{% if page.slug == 'about' %}
Ready to Get Started?
{% elif page.slug == 'contact' %}
Have Questions?
{% endif %}
</h3>
<p class="text-gray-600 dark:text-gray-400 mb-6">
{% if page.slug == 'about' %}
Join thousands of vendors already selling on our platform
{% elif page.slug == 'contact' %}
Our team is here to help you succeed
{% endif %}
</p>
<a href="/contact" class="inline-block bg-gray-900 dark:bg-white text-white dark:text-gray-900 px-8 py-3 rounded-lg font-semibold hover:opacity-90 transition">
{% if page.slug == 'about' %}
Contact Sales
{% elif page.slug == 'contact' %}
Send Us a Message
{% endif %}
</a>
</div>
{% endif %}
</div>
{# Additional styling for prose content #}
<style>
/* Enhanced prose styling for content pages */
.prose {
color: inherit;
}
.prose h1, .prose h2, .prose h3, .prose h4, .prose h5, .prose h6 {
color: inherit;
font-weight: 700;
margin-top: 2em;
margin-bottom: 1em;
}
.prose h2 {
font-size: 1.875rem;
line-height: 2.25rem;
border-bottom: 2px solid var(--color-primary);
padding-bottom: 0.5rem;
}
.prose h3 {
font-size: 1.5rem;
line-height: 2rem;
}
.prose p {
margin-bottom: 1.5em;
line-height: 1.75;
}
.prose ul, .prose ol {
margin-bottom: 1.5em;
padding-left: 1.5em;
}
.prose li {
margin-bottom: 0.5em;
}
.prose a {
color: var(--color-primary);
text-decoration: underline;
font-weight: 500;
}
.prose a:hover {
opacity: 0.8;
}
.prose strong {
font-weight: 600;
color: inherit;
}
.prose code {
background-color: rgba(0, 0, 0, 0.05);
padding: 0.2em 0.4em;
border-radius: 0.25rem;
font-size: 0.9em;
}
.dark .prose code {
background-color: rgba(255, 255, 255, 0.1);
}
.prose pre {
background-color: rgba(0, 0, 0, 0.05);
padding: 1em;
border-radius: 0.5rem;
overflow-x: auto;
margin-bottom: 1.5em;
}
.dark .prose pre {
background-color: rgba(255, 255, 255, 0.05);
}
.prose blockquote {
border-left: 4px solid var(--color-primary);
padding-left: 1em;
font-style: italic;
opacity: 0.9;
margin: 1.5em 0;
}
.prose table {
width: 100%;
border-collapse: collapse;
margin-bottom: 1.5em;
}
.prose th, .prose td {
border: 1px solid rgba(0, 0, 0, 0.1);
padding: 0.75em;
text-align: left;
}
.dark .prose th, .dark .prose td {
border-color: rgba(255, 255, 255, 0.1);
}
.prose th {
background-color: rgba(0, 0, 0, 0.05);
font-weight: 600;
}
.dark .prose th {
background-color: rgba(255, 255, 255, 0.05);
}
.prose hr {
border: 0;
border-top: 2px solid rgba(0, 0, 0, 0.1);
margin: 3em 0;
}
.dark .prose hr {
border-top-color: rgba(255, 255, 255, 0.1);
}
.prose img {
border-radius: 0.5rem;
margin: 2em auto;
max-width: 100%;
height: auto;
}
</style>
{% endblock %}

View File

@@ -0,0 +1,228 @@
{# app/templates/platform/homepage-default.html #}
{# Default platform homepage template #}
{% extends "platform/base.html" %}
{% block title %}
{% if page %}{{ page.title }}{% else %}Home{% endif %} - Multi-Vendor Marketplace
{% endblock %}
{% block meta_description %}
{% if page and page.meta_description %}
{{ page.meta_description }}
{% else %}
Leading multi-vendor marketplace platform. Connect with thousands of vendors and discover millions of products.
{% endif %}
{% endblock %}
{% block content %}
<!-- ═══════════════════════════════════════════════════════════════ -->
<!-- HERO SECTION -->
<!-- ═══════════════════════════════════════════════════════════════ -->
<section class="gradient-primary text-white py-20">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="text-center">
{% if page %}
{# CMS-driven content #}
<h1 class="text-4xl md:text-6xl font-bold mb-6">
{{ page.title }}
</h1>
<div class="text-xl md:text-2xl mb-8 opacity-90 max-w-3xl mx-auto">
{{ page.content | safe }}
</div>
{% else %}
{# Default fallback content #}
<h1 class="text-4xl md:text-6xl font-bold mb-6">
Welcome to Our Marketplace
</h1>
<p class="text-xl md:text-2xl mb-8 opacity-90 max-w-3xl mx-auto">
Connect vendors with customers worldwide. Build your online store and reach millions of shoppers.
</p>
{% endif %}
<div class="flex flex-col sm:flex-row gap-4 justify-center items-center">
<a href="/vendors"
class="btn-primary inline-flex items-center space-x-2">
<span>Browse Vendors</span>
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 7l5 5m0 0l-5 5m5-5H6"></path>
</svg>
</a>
<a href="/contact"
class="bg-white text-gray-900 px-6 py-3 rounded-lg font-semibold hover:bg-gray-100 transition">
Get Started
</a>
</div>
</div>
</div>
</section>
<!-- ═══════════════════════════════════════════════════════════════ -->
<!-- FEATURES SECTION -->
<!-- ═══════════════════════════════════════════════════════════════ -->
<section class="py-16 bg-white dark:bg-gray-800">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="text-center mb-12">
<h2 class="text-3xl md:text-4xl font-bold text-gray-900 dark:text-white mb-4">
Why Choose Our Platform?
</h2>
<p class="text-lg text-gray-600 dark:text-gray-400 max-w-2xl mx-auto">
Everything you need to launch and grow your online business
</p>
</div>
<div class="grid grid-cols-1 md:grid-cols-3 gap-8">
<!-- Feature 1 -->
<div class="card-hover bg-gray-50 dark:bg-gray-700 rounded-xl p-8 text-center">
<div class="w-16 h-16 mx-auto mb-4 rounded-full gradient-primary flex items-center justify-center">
<svg class="w-8 h-8 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"></path>
</svg>
</div>
<h3 class="text-xl font-semibold text-gray-900 dark:text-white mb-3">
Lightning Fast
</h3>
<p class="text-gray-600 dark:text-gray-400">
Optimized for speed and performance. Your store loads in milliseconds.
</p>
</div>
<!-- Feature 2 -->
<div class="card-hover bg-gray-50 dark:bg-gray-700 rounded-xl p-8 text-center">
<div class="w-16 h-16 mx-auto mb-4 rounded-full gradient-primary flex items-center justify-center">
<svg class="w-8 h-8 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"></path>
</svg>
</div>
<h3 class="text-xl font-semibold text-gray-900 dark:text-white mb-3">
Secure & Reliable
</h3>
<p class="text-gray-600 dark:text-gray-400">
Enterprise-grade security with 99.9% uptime guarantee.
</p>
</div>
<!-- Feature 3 -->
<div class="card-hover bg-gray-50 dark:bg-gray-700 rounded-xl p-8 text-center">
<div class="w-16 h-16 mx-auto mb-4 rounded-full gradient-primary flex items-center justify-center">
<svg class="w-8 h-8 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 21a4 4 0 01-4-4V5a2 2 0 012-2h4a2 2 0 012 2v12a4 4 0 01-4 4zm0 0h12a2 2 0 002-2v-4a2 2 0 00-2-2h-2.343M11 7.343l1.657-1.657a2 2 0 012.828 0l2.829 2.829a2 2 0 010 2.828l-8.486 8.485M7 17h.01"></path>
</svg>
</div>
<h3 class="text-xl font-semibold text-gray-900 dark:text-white mb-3">
Fully Customizable
</h3>
<p class="text-gray-600 dark:text-gray-400">
Brand your store with custom themes, colors, and layouts.
</p>
</div>
</div>
</div>
</section>
<!-- ═══════════════════════════════════════════════════════════════ -->
<!-- FEATURED VENDORS SECTION -->
<!-- ═══════════════════════════════════════════════════════════════ -->
<section class="py-16 bg-gray-50 dark:bg-gray-900">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="text-center mb-12">
<h2 class="text-3xl md:text-4xl font-bold text-gray-900 dark:text-white mb-4">
Featured Vendors
</h2>
<p class="text-lg text-gray-600 dark:text-gray-400">
Discover amazing shops from around the world
</p>
</div>
<div class="grid grid-cols-1 md:grid-cols-3 gap-8">
<!-- Vendor Card 1 - Placeholder -->
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-sm hover:shadow-lg transition-shadow overflow-hidden">
<div class="h-48 bg-gradient-to-r from-purple-400 to-pink-400"></div>
<div class="p-6">
<h3 class="text-xl font-semibold text-gray-900 dark:text-white mb-2">
Sample Vendor 1
</h3>
<p class="text-gray-600 dark:text-gray-400 mb-4">
Premium products and exceptional service
</p>
<a href="/vendors/vendor1/shop"
class="text-primary hover:underline font-medium inline-flex items-center">
Visit Store
<svg class="w-4 h-4 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
</svg>
</a>
</div>
</div>
<!-- Vendor Card 2 - Placeholder -->
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-sm hover:shadow-lg transition-shadow overflow-hidden">
<div class="h-48 bg-gradient-to-r from-blue-400 to-cyan-400"></div>
<div class="p-6">
<h3 class="text-xl font-semibold text-gray-900 dark:text-white mb-2">
Sample Vendor 2
</h3>
<p class="text-gray-600 dark:text-gray-400 mb-4">
Quality craftsmanship meets modern design
</p>
<a href="/vendors/vendor2/shop"
class="text-primary hover:underline font-medium inline-flex items-center">
Visit Store
<svg class="w-4 h-4 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
</svg>
</a>
</div>
</div>
<!-- Vendor Card 3 - Placeholder -->
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-sm hover:shadow-lg transition-shadow overflow-hidden">
<div class="h-48 bg-gradient-to-r from-green-400 to-teal-400"></div>
<div class="p-6">
<h3 class="text-xl font-semibold text-gray-900 dark:text-white mb-2">
Sample Vendor 3
</h3>
<p class="text-gray-600 dark:text-gray-400 mb-4">
Eco-friendly products for sustainable living
</p>
<a href="/vendors/vendor3/shop"
class="text-primary hover:underline font-medium inline-flex items-center">
Visit Store
<svg class="w-4 h-4 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
</svg>
</a>
</div>
</div>
</div>
<div class="text-center mt-12">
<a href="/vendors" class="btn-primary inline-block">
View All Vendors
</a>
</div>
</div>
</section>
<!-- ═══════════════════════════════════════════════════════════════ -->
<!-- CTA SECTION -->
<!-- ═══════════════════════════════════════════════════════════════ -->
<section class="py-16 bg-white dark:bg-gray-800">
<div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
<h2 class="text-3xl md:text-4xl font-bold text-gray-900 dark:text-white mb-4">
Ready to Get Started?
</h2>
<p class="text-lg text-gray-600 dark:text-gray-400 mb-8">
Join thousands of vendors already selling on our platform
</p>
<div class="flex flex-col sm:flex-row gap-4 justify-center">
<a href="/contact" class="btn-primary">
Contact Sales
</a>
<a href="/about"
class="bg-gray-100 dark:bg-gray-700 text-gray-900 dark:text-white px-6 py-3 rounded-lg font-semibold hover:bg-gray-200 dark:hover:bg-gray-600 transition">
Learn More
</a>
</div>
</div>
</section>
{% endblock %}

View File

@@ -0,0 +1,100 @@
{# app/templates/platform/homepage-minimal.html #}
{# Minimal/clean platform homepage template #}
{% extends "platform/base.html" %}
{% block title %}
{% if page %}{{ page.title }}{% else %}Home{% endif %} - Marketplace
{% endblock %}
{% block content %}
<!-- ═══════════════════════════════════════════════════════════════ -->
<!-- MINIMAL HERO -->
<!-- ═══════════════════════════════════════════════════════════════ -->
<section class="py-32 bg-white dark:bg-gray-800">
<div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
{% if page %}
<h1 class="text-5xl md:text-7xl font-bold text-gray-900 dark:text-white mb-8 leading-tight">
{{ page.title }}
</h1>
<div class="text-xl text-gray-600 dark:text-gray-400 mb-12 max-w-2xl mx-auto">
{{ page.content | safe }}
</div>
{% else %}
<h1 class="text-5xl md:text-7xl font-bold text-gray-900 dark:text-white mb-8 leading-tight">
Multi-Vendor<br>Marketplace
</h1>
<p class="text-xl text-gray-600 dark:text-gray-400 mb-12 max-w-2xl mx-auto">
The simplest way to launch your online store and connect with customers worldwide.
</p>
{% endif %}
<a href="/contact"
class="inline-block bg-gray-900 dark:bg-white text-white dark:text-gray-900 px-8 py-4 rounded-lg font-semibold hover:opacity-90 transition text-lg">
Get Started
</a>
</div>
</section>
<!-- ═══════════════════════════════════════════════════════════════ -->
<!-- MINIMAL FEATURES -->
<!-- ═══════════════════════════════════════════════════════════════ -->
<section class="py-24 bg-gray-50 dark:bg-gray-900">
<div class="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="grid grid-cols-1 md:grid-cols-3 gap-12">
<div class="text-center">
<div class="text-4xl mb-4"></div>
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-2">
Fast
</h3>
<p class="text-gray-600 dark:text-gray-400 text-sm">
Lightning-fast performance optimized for conversions
</p>
</div>
<div class="text-center">
<div class="text-4xl mb-4">🔒</div>
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-2">
Secure
</h3>
<p class="text-gray-600 dark:text-gray-400 text-sm">
Enterprise-grade security for your peace of mind
</p>
</div>
<div class="text-center">
<div class="text-4xl mb-4">🎨</div>
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-2">
Custom
</h3>
<p class="text-gray-600 dark:text-gray-400 text-sm">
Fully customizable to match your brand identity
</p>
</div>
</div>
</div>
</section>
<!-- ═══════════════════════════════════════════════════════════════ -->
<!-- MINIMAL CTA -->
<!-- ═══════════════════════════════════════════════════════════════ -->
<section class="py-24 bg-white dark:bg-gray-800">
<div class="max-w-3xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
<h2 class="text-3xl font-bold text-gray-900 dark:text-white mb-6">
Ready to launch?
</h2>
<p class="text-gray-600 dark:text-gray-400 mb-8">
Join our marketplace today
</p>
<div class="flex flex-col sm:flex-row gap-4 justify-center">
<a href="/contact"
class="inline-block bg-gray-900 dark:bg-white text-white dark:text-gray-900 px-6 py-3 rounded-lg font-semibold hover:opacity-90 transition">
Contact Us
</a>
<a href="/about"
class="inline-block border-2 border-gray-900 dark:border-white text-gray-900 dark:text-white px-6 py-3 rounded-lg font-semibold hover:bg-gray-50 dark:hover:bg-gray-700 transition">
Learn More
</a>
</div>
</div>
</section>
{% endblock %}

View File

@@ -0,0 +1,258 @@
{# app/templates/platform/homepage-modern.html #}
{# Modern/trendy platform homepage template with animations #}
{% extends "platform/base.html" %}
{% block title %}
{% if page %}{{ page.title }}{% else %}Home{% endif %} - Marketplace
{% endblock %}
{% block extra_head %}
<style>
@keyframes float {
0%, 100% { transform: translateY(0px); }
50% { transform: translateY(-20px); }
}
.float-animation {
animation: float 6s ease-in-out infinite;
}
@keyframes gradient {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
.animated-gradient {
background: linear-gradient(270deg, #6366f1, #8b5cf6, #ec4899, #f43f5e);
background-size: 400% 400%;
animation: gradient 15s ease infinite;
}
</style>
{% endblock %}
{% block content %}
<!-- ═══════════════════════════════════════════════════════════════ -->
<!-- MODERN HERO WITH GRADIENT -->
<!-- ═══════════════════════════════════════════════════════════════ -->
<section class="relative overflow-hidden animated-gradient text-white py-24 md:py-32">
{# Decorative elements #}
<div class="absolute top-0 right-0 w-1/3 h-full opacity-20">
<div class="absolute top-20 right-20 w-72 h-72 rounded-full bg-white blur-3xl"></div>
<div class="absolute bottom-20 right-40 w-96 h-96 rounded-full bg-white blur-3xl"></div>
</div>
<div class="relative max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="grid grid-cols-1 lg:grid-cols-2 gap-12 items-center">
{# Left column - Content #}
<div>
{% if page %}
<h1 class="text-5xl md:text-7xl font-black mb-6 leading-tight">
{{ page.title }}
</h1>
<div class="text-xl md:text-2xl mb-8 opacity-95">
{{ page.content | safe }}
</div>
{% else %}
<div class="inline-block px-4 py-2 bg-white/20 backdrop-blur-sm rounded-full text-sm font-semibold mb-6">
✨ The Future of E-Commerce
</div>
<h1 class="text-5xl md:text-7xl font-black mb-6 leading-tight">
Build Your<br>
<span class="text-transparent bg-clip-text bg-white">
Dream Store
</span>
</h1>
<p class="text-xl md:text-2xl mb-8 opacity-95">
Launch a stunning online marketplace in minutes. No coding required. Scale effortlessly.
</p>
{% endif %}
<div class="flex flex-col sm:flex-row gap-4">
<a href="/contact"
class="inline-flex items-center justify-center bg-white text-gray-900 px-8 py-4 rounded-xl font-bold hover:shadow-2xl transform hover:scale-105 transition-all duration-200">
<span>Start Free Trial</span>
<svg class="w-5 h-5 ml-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 7l5 5m0 0l-5 5m5-5H6"></path>
</svg>
</a>
<a href="#features"
class="inline-flex items-center justify-center border-2 border-white text-white px-8 py-4 rounded-xl font-bold hover:bg-white/10 backdrop-blur-sm transition-all duration-200">
Learn More
</a>
</div>
{# Stats #}
<div class="grid grid-cols-3 gap-6 mt-12 pt-12 border-t border-white/20">
<div>
<div class="text-3xl font-bold mb-1">10K+</div>
<div class="text-sm opacity-80">Active Vendors</div>
</div>
<div>
<div class="text-3xl font-bold mb-1">50M+</div>
<div class="text-sm opacity-80">Products Sold</div>
</div>
<div>
<div class="text-3xl font-bold mb-1">99.9%</div>
<div class="text-sm opacity-80">Uptime</div>
</div>
</div>
</div>
{# Right column - Visual element #}
<div class="hidden lg:block float-animation">
<div class="relative">
<div class="w-full h-96 bg-white/10 backdrop-blur-xl rounded-3xl shadow-2xl p-8">
<div class="w-full h-full bg-gradient-to-br from-white/20 to-transparent rounded-2xl flex items-center justify-center">
<div class="text-center">
<div class="text-8xl mb-4">🚀</div>
<div class="text-2xl font-bold">Launch Today</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- ═══════════════════════════════════════════════════════════════ -->
<!-- FEATURES WITH CARDS -->
<!-- ═══════════════════════════════════════════════════════════════ -->
<section id="features" class="py-24 bg-gray-50 dark:bg-gray-900">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="text-center mb-16">
<div class="inline-block px-4 py-2 bg-purple-100 dark:bg-purple-900/30 text-purple-600 dark:text-purple-400 rounded-full text-sm font-semibold mb-4">
✨ Features
</div>
<h2 class="text-4xl md:text-5xl font-bold text-gray-900 dark:text-white mb-4">
Everything You Need
</h2>
<p class="text-xl text-gray-600 dark:text-gray-400 max-w-2xl mx-auto">
Powerful features to help you succeed in the digital marketplace
</p>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
{# Feature cards with hover effects #}
<div class="group bg-white dark:bg-gray-800 rounded-2xl p-8 shadow-sm hover:shadow-2xl transition-all duration-300 transform hover:-translate-y-2">
<div class="w-14 h-14 rounded-2xl bg-gradient-to-br from-purple-500 to-pink-500 flex items-center justify-center mb-6 group-hover:scale-110 transition-transform">
<svg class="w-7 h-7 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"></path>
</svg>
</div>
<h3 class="text-xl font-bold text-gray-900 dark:text-white mb-3">
Blazing Fast
</h3>
<p class="text-gray-600 dark:text-gray-400">
Optimized for performance with sub-second page loads and instant search results.
</p>
</div>
<div class="group bg-white dark:bg-gray-800 rounded-2xl p-8 shadow-sm hover:shadow-2xl transition-all duration-300 transform hover:-translate-y-2">
<div class="w-14 h-14 rounded-2xl bg-gradient-to-br from-blue-500 to-cyan-500 flex items-center justify-center mb-6 group-hover:scale-110 transition-transform">
<svg class="w-7 h-7 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"></path>
</svg>
</div>
<h3 class="text-xl font-bold text-gray-900 dark:text-white mb-3">
Bank-Level Security
</h3>
<p class="text-gray-600 dark:text-gray-400">
Enterprise-grade encryption and security measures to protect your business.
</p>
</div>
<div class="group bg-white dark:bg-gray-800 rounded-2xl p-8 shadow-sm hover:shadow-2xl transition-all duration-300 transform hover:-translate-y-2">
<div class="w-14 h-14 rounded-2xl bg-gradient-to-br from-green-500 to-teal-500 flex items-center justify-center mb-6 group-hover:scale-110 transition-transform">
<svg class="w-7 h-7 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 21a4 4 0 01-4-4V5a2 2 0 012-2h4a2 2 0 012 2v12a4 4 0 01-4 4zm0 0h12a2 2 0 002-2v-4a2 2 0 00-2-2h-2.343M11 7.343l1.657-1.657a2 2 0 012.828 0l2.829 2.829a2 2 0 010 2.828l-8.486 8.485M7 17h.01"></path>
</svg>
</div>
<h3 class="text-xl font-bold text-gray-900 dark:text-white mb-3">
Fully Customizable
</h3>
<p class="text-gray-600 dark:text-gray-400">
Brand your store with custom themes, colors, fonts, and layouts.
</p>
</div>
<div class="group bg-white dark:bg-gray-800 rounded-2xl p-8 shadow-sm hover:shadow-2xl transition-all duration-300 transform hover:-translate-y-2">
<div class="w-14 h-14 rounded-2xl bg-gradient-to-br from-orange-500 to-red-500 flex items-center justify-center mb-6 group-hover:scale-110 transition-transform">
<svg class="w-7 h-7 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"></path>
</svg>
</div>
<h3 class="text-xl font-bold text-gray-900 dark:text-white mb-3">
Analytics & Insights
</h3>
<p class="text-gray-600 dark:text-gray-400">
Powerful analytics to track sales, customer behavior, and growth metrics.
</p>
</div>
<div class="group bg-white dark:bg-gray-800 rounded-2xl p-8 shadow-sm hover:shadow-2xl transition-all duration-300 transform hover:-translate-y-2">
<div class="w-14 h-14 rounded-2xl bg-gradient-to-br from-indigo-500 to-purple-500 flex items-center justify-center mb-6 group-hover:scale-110 transition-transform">
<svg class="w-7 h-7 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 18h.01M8 21h8a2 2 0 002-2V5a2 2 0 00-2-2H8a2 2 0 00-2 2v14a2 2 0 002 2z"></path>
</svg>
</div>
<h3 class="text-xl font-bold text-gray-900 dark:text-white mb-3">
Mobile-First Design
</h3>
<p class="text-gray-600 dark:text-gray-400">
Beautiful, responsive design that works perfectly on all devices.
</p>
</div>
<div class="group bg-white dark:bg-gray-800 rounded-2xl p-8 shadow-sm hover:shadow-2xl transition-all duration-300 transform hover:-translate-y-2">
<div class="w-14 h-14 rounded-2xl bg-gradient-to-br from-pink-500 to-rose-500 flex items-center justify-center mb-6 group-hover:scale-110 transition-transform">
<svg class="w-7 h-7 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M18.364 5.636l-3.536 3.536m0 5.656l3.536 3.536M9.172 9.172L5.636 5.636m3.536 9.192l-3.536 3.536M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-5 0a4 4 0 11-8 0 4 4 0 018 0z"></path>
</svg>
</div>
<h3 class="text-xl font-bold text-gray-900 dark:text-white mb-3">
24/7 Support
</h3>
<p class="text-gray-600 dark:text-gray-400">
Round-the-clock customer support to help you succeed at every step.
</p>
</div>
</div>
</div>
</section>
<!-- ═══════════════════════════════════════════════════════════════ -->
<!-- MODERN CTA WITH GRADIENT -->
<!-- ═══════════════════════════════════════════════════════════════ -->
<section class="py-24 relative overflow-hidden">
<div class="absolute inset-0 gradient-accent opacity-90"></div>
<div class="relative max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 text-center text-white">
<h2 class="text-4xl md:text-5xl font-bold mb-6">
Start Your Journey Today
</h2>
<p class="text-xl mb-10 opacity-90">
Join thousands of successful vendors on our platform
</p>
<div class="flex flex-col sm:flex-row gap-4 justify-center">
<a href="/contact"
class="inline-flex items-center justify-center bg-white text-gray-900 px-8 py-4 rounded-xl font-bold hover:shadow-2xl transform hover:scale-105 transition-all duration-200">
<span>Get Started Free</span>
<svg class="w-5 h-5 ml-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 7l5 5m0 0l-5 5m5-5H6"></path>
</svg>
</a>
<a href="/about"
class="inline-flex items-center justify-center border-2 border-white text-white px-8 py-4 rounded-xl font-bold hover:bg-white/10 backdrop-blur-sm transition-all duration-200">
Learn More About Us
</a>
</div>
<p class="mt-8 text-sm opacity-75">
No credit card required · Free 14-day trial · Cancel anytime
</p>
</div>
</section>
{% endblock %}