Site design updates
This commit is contained in:
@@ -1,73 +0,0 @@
|
|||||||
# Dashboard Redesign Plan
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
Transform the main Dashboard into a comprehensive, marketing-focused page that serves as the "face" of the application.
|
|
||||||
|
|
||||||
## Structure
|
|
||||||
|
|
||||||
### 1. Hero Section (Marketing-Focused)
|
|
||||||
- **Purpose**: Explain what IGNY8 is and how it works
|
|
||||||
- **Content**:
|
|
||||||
- Compelling headline: "AI-Powered Content Creation Workflow"
|
|
||||||
- Brief description of the system
|
|
||||||
- Visual workflow diagram (simplified)
|
|
||||||
- Key value propositions
|
|
||||||
|
|
||||||
### 2. App-Wide Insights
|
|
||||||
- **Purpose**: Show aggregated metrics across the entire application
|
|
||||||
- **Metrics** (NOT duplicating planner/writer dashboards):
|
|
||||||
- Total Keywords across all sites
|
|
||||||
- Total Content Pieces created
|
|
||||||
- Total Images generated
|
|
||||||
- Overall workflow completion rate
|
|
||||||
- Recent activity feed
|
|
||||||
- System health indicators
|
|
||||||
|
|
||||||
### 3. Workflow Explainer (5-7 Steps)
|
|
||||||
- **Visual Steps**:
|
|
||||||
1. **Discover Keywords** → Find high-volume keywords from global database
|
|
||||||
2. **Cluster Keywords** → Group related keywords into clusters
|
|
||||||
3. **Generate Ideas** → AI creates content ideas from clusters
|
|
||||||
4. **Create Tasks** → Convert ideas into actionable writing tasks
|
|
||||||
5. **Write Content** → AI generates full content pieces
|
|
||||||
6. **Generate Images** → Create featured and in-article images
|
|
||||||
7. **Publish** → Content ready for publication
|
|
||||||
|
|
||||||
- **Design**: Interactive step-by-step visual with icons and brief descriptions
|
|
||||||
- **Action**: Each step can link to relevant page
|
|
||||||
|
|
||||||
### 4. Automation Setup
|
|
||||||
- **Purpose**: Configure automation settings
|
|
||||||
- **Sections**:
|
|
||||||
- **Keywords Automation**:
|
|
||||||
- How many keywords to take per cycle
|
|
||||||
- Auto-cluster enabled/disabled
|
|
||||||
- Cluster settings (max keywords per cluster)
|
|
||||||
|
|
||||||
- **Ideas Automation**:
|
|
||||||
- Auto-generate ideas from clusters
|
|
||||||
- Ideas per cluster limit
|
|
||||||
|
|
||||||
- **Content Automation**:
|
|
||||||
- Auto-create tasks from ideas
|
|
||||||
- Auto-generate content from tasks
|
|
||||||
|
|
||||||
- **Image Automation**:
|
|
||||||
- Auto-generate images for content
|
|
||||||
- Image generation settings
|
|
||||||
|
|
||||||
- **Note**: These are placeholders/settings that will link to Schedules page or have inline configuration
|
|
||||||
|
|
||||||
## Design Principles
|
|
||||||
- **Marketing-Focused**: Should be impressive enough for marketing screenshots
|
|
||||||
- **Clear & Simple**: Easy to understand the system at a glance
|
|
||||||
- **Actionable**: Quick access to key actions
|
|
||||||
- **Visual**: Heavy use of icons, colors, and visual elements
|
|
||||||
- **Responsive**: Works on all screen sizes
|
|
||||||
|
|
||||||
## Implementation Notes
|
|
||||||
- Use existing components where possible (EnhancedMetricCard, WorkflowPipeline, etc.)
|
|
||||||
- Create new components for workflow explainer
|
|
||||||
- Automation section can be expandable/collapsible cards
|
|
||||||
- Consider adding "Quick Actions" section for one-click workflows
|
|
||||||
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
# Help & Documentation Page Recommendation
|
|
||||||
|
|
||||||
## Decision: **ONE COMBINED PAGE**
|
|
||||||
|
|
||||||
### Analysis
|
|
||||||
|
|
||||||
**Current Structure:**
|
|
||||||
- `/help` - Help & Support (placeholder)
|
|
||||||
- `/help/docs` - Documentation (placeholder)
|
|
||||||
- Both shown as separate menu items in sidebar
|
|
||||||
|
|
||||||
**Documentation Available:**
|
|
||||||
- `/docs` folder with comprehensive technical documentation
|
|
||||||
- 6 main documentation files covering architecture, frontend, backend, AI functions
|
|
||||||
- Well-organized markdown structure
|
|
||||||
|
|
||||||
**Recommendation: Single Combined Page**
|
|
||||||
|
|
||||||
### Reasons:
|
|
||||||
|
|
||||||
1. **User Experience**
|
|
||||||
- Users don't need to decide between "Help" and "Documentation"
|
|
||||||
- All information in one place
|
|
||||||
- Better discoverability
|
|
||||||
|
|
||||||
2. **Content Overlap**
|
|
||||||
- Help content often references documentation
|
|
||||||
- Documentation includes help content
|
|
||||||
- No clear boundary between the two
|
|
||||||
|
|
||||||
3. **Modern Pattern**
|
|
||||||
- Most modern apps combine them (GitHub, Stripe, Vercel, etc.)
|
|
||||||
- Single entry point is cleaner
|
|
||||||
- Better for SEO and navigation
|
|
||||||
|
|
||||||
4. **WordPress Plugin Pattern**
|
|
||||||
- Uses subpages (`?sp=help`, `?sp=docs`)
|
|
||||||
- Suggests they're meant to be together
|
|
||||||
- Can maintain consistency
|
|
||||||
|
|
||||||
5. **Content Size**
|
|
||||||
- Documentation isn't so large it needs separation
|
|
||||||
- Can be organized with tabs/sections
|
|
||||||
|
|
||||||
### Proposed Structure:
|
|
||||||
|
|
||||||
**Single `/help` page with sections:**
|
|
||||||
|
|
||||||
1. **Getting Started** (Tab/Section)
|
|
||||||
- Quick start guide
|
|
||||||
- Common workflows
|
|
||||||
- Video tutorials
|
|
||||||
- Setup instructions
|
|
||||||
|
|
||||||
2. **Documentation** (Tab/Section)
|
|
||||||
- Architecture & Tech Stack
|
|
||||||
- Frontend Documentation
|
|
||||||
- Backend Documentation
|
|
||||||
- AI Functions
|
|
||||||
- API Reference
|
|
||||||
|
|
||||||
3. **FAQ & Troubleshooting** (Tab/Section)
|
|
||||||
- Common questions
|
|
||||||
- Troubleshooting guides
|
|
||||||
- Known issues
|
|
||||||
|
|
||||||
4. **Support** (Tab/Section)
|
|
||||||
- Contact support
|
|
||||||
- Community resources
|
|
||||||
- Feature requests
|
|
||||||
|
|
||||||
### Implementation:
|
|
||||||
|
|
||||||
- Use tabs or sidebar navigation within the page
|
|
||||||
- Smooth transitions between sections
|
|
||||||
- Search functionality across all content
|
|
||||||
- Mobile-responsive design
|
|
||||||
|
|
||||||
@@ -1,194 +0,0 @@
|
|||||||
# Deployment Architecture Analysis
|
|
||||||
|
|
||||||
## Current Setup
|
|
||||||
|
|
||||||
### Domain Routing
|
|
||||||
- **`app.igny8.com`** → Vite dev server container (`igny8_frontend:5173`)
|
|
||||||
- Live reload enabled
|
|
||||||
- Development mode
|
|
||||||
- Changes reflect immediately
|
|
||||||
|
|
||||||
- **`igny8.com`** → Static files from `/var/www/igny8-marketing`
|
|
||||||
- Production marketing site
|
|
||||||
- Requires manual build + copy to update
|
|
||||||
- No containerization
|
|
||||||
|
|
||||||
### Current Issues
|
|
||||||
1. ❌ Marketing site deployment is manual (not containerized)
|
|
||||||
2. ❌ No automated deployment process
|
|
||||||
3. ❌ Dev changes affect `app.igny8.com` but not `igny8.com` (confusing)
|
|
||||||
4. ⚠️ Marketing site not versioned with codebase
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Option Comparison
|
|
||||||
|
|
||||||
### Option A: Separate Containers (Recommended ✅)
|
|
||||||
|
|
||||||
**Structure:**
|
|
||||||
```
|
|
||||||
igny8_frontend_dev → app.igny8.com (Vite dev server)
|
|
||||||
igny8_frontend_prod → app.igny8.com (Production build, optional)
|
|
||||||
igny8_marketing → igny8.com (Marketing static site)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Pros:**
|
|
||||||
- ✅ Clear separation of concerns
|
|
||||||
- ✅ Independent scaling and updates
|
|
||||||
- ✅ Marketing site can be updated without affecting app
|
|
||||||
- ✅ Production app can be containerized separately
|
|
||||||
- ✅ Better security isolation
|
|
||||||
- ✅ Easier CI/CD automation
|
|
||||||
- ✅ Version control for marketing deployments
|
|
||||||
|
|
||||||
**Cons:**
|
|
||||||
- ⚠️ Slightly more complex docker-compose setup
|
|
||||||
- ⚠️ Need to manage 2-3 containers instead of 1
|
|
||||||
|
|
||||||
**Implementation:**
|
|
||||||
```yaml
|
|
||||||
services:
|
|
||||||
igny8_frontend_dev:
|
|
||||||
# Current dev server for app.igny8.com
|
|
||||||
image: igny8-frontend-dev:latest
|
|
||||||
ports: ["8021:5173"]
|
|
||||||
|
|
||||||
igny8_marketing:
|
|
||||||
# Production marketing site for igny8.com
|
|
||||||
image: igny8-marketing:latest
|
|
||||||
build:
|
|
||||||
context: ./frontend
|
|
||||||
dockerfile: Dockerfile.marketing
|
|
||||||
volumes:
|
|
||||||
- marketing_static:/usr/share/caddy:ro
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Option B: Current Approach (Keep Manual)
|
|
||||||
|
|
||||||
**Structure:**
|
|
||||||
```
|
|
||||||
igny8_frontend_dev → app.igny8.com (Vite dev server)
|
|
||||||
/var/www/igny8-marketing → igny8.com (Manual static files)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Pros:**
|
|
||||||
- ✅ Simple (already working)
|
|
||||||
- ✅ No additional containers
|
|
||||||
- ✅ Fast static file serving
|
|
||||||
|
|
||||||
**Cons:**
|
|
||||||
- ❌ Manual deployment process
|
|
||||||
- ❌ No version control for marketing site
|
|
||||||
- ❌ Hard to rollback
|
|
||||||
- ❌ Not containerized (harder to manage)
|
|
||||||
- ❌ Deployment not reproducible
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Option C: Unified Production Build
|
|
||||||
|
|
||||||
**Structure:**
|
|
||||||
```
|
|
||||||
Single container serves both app and marketing from same build
|
|
||||||
```
|
|
||||||
|
|
||||||
**Pros:**
|
|
||||||
- ✅ Single container to manage
|
|
||||||
- ✅ Both sites from same codebase
|
|
||||||
|
|
||||||
**Cons:**
|
|
||||||
- ❌ Can't update marketing without rebuilding app
|
|
||||||
- ❌ Larger container size
|
|
||||||
- ❌ Less flexible deployment
|
|
||||||
- ❌ Dev server still separate anyway
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Recommendation: **Option A - Separate Containers**
|
|
||||||
|
|
||||||
### Why This Is Better:
|
|
||||||
|
|
||||||
1. **Production-Ready App Container**
|
|
||||||
- Can deploy production build of app to `app.igny8.com` when needed
|
|
||||||
- Dev container for development, prod container for production
|
|
||||||
|
|
||||||
2. **Containerized Marketing Site**
|
|
||||||
- Marketing site becomes a proper container
|
|
||||||
- Easy to update: rebuild image, restart container
|
|
||||||
- Version controlled deployments
|
|
||||||
- Rollback capability
|
|
||||||
|
|
||||||
3. **Clear Separation**
|
|
||||||
- Dev environment: `igny8_frontend_dev` → `app.igny8.com`
|
|
||||||
- Production app: `igny8_frontend_prod` → `app.igny8.com` (when ready)
|
|
||||||
- Marketing site: `igny8_marketing` → `igny8.com`
|
|
||||||
|
|
||||||
4. **Better CI/CD**
|
|
||||||
- Can deploy marketing site independently
|
|
||||||
- Can deploy app independently
|
|
||||||
- Automated builds and deployments
|
|
||||||
|
|
||||||
5. **Scalability**
|
|
||||||
- Each service can scale independently
|
|
||||||
- Better resource management
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Implementation Plan
|
|
||||||
|
|
||||||
### Step 1: Create Marketing Dockerfile
|
|
||||||
```dockerfile
|
|
||||||
# frontend/Dockerfile.marketing
|
|
||||||
FROM node:18-alpine AS builder
|
|
||||||
WORKDIR /app
|
|
||||||
COPY . .
|
|
||||||
RUN npm install
|
|
||||||
RUN npm run build:marketing
|
|
||||||
|
|
||||||
FROM caddy:latest
|
|
||||||
COPY --from=builder /app/dist /usr/share/caddy
|
|
||||||
COPY Caddyfile.marketing /etc/caddy/Caddyfile
|
|
||||||
EXPOSE 8020
|
|
||||||
CMD ["caddy", "run", "--config", "/etc/caddy/Caddyfile"]
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 2: Create Marketing Caddyfile
|
|
||||||
```caddyfile
|
|
||||||
# frontend/Caddyfile.marketing
|
|
||||||
:8020 {
|
|
||||||
root * /usr/share/caddy
|
|
||||||
try_files {path} /marketing.html
|
|
||||||
file_server
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 3: Update docker-compose.app.yml
|
|
||||||
Add marketing service alongside frontend dev service.
|
|
||||||
|
|
||||||
### Step 4: Update Main Caddyfile
|
|
||||||
Point `igny8.com` to `igny8_marketing:8020` instead of static files.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Migration Path
|
|
||||||
|
|
||||||
1. **Phase 1**: Add marketing container (keep current setup working)
|
|
||||||
2. **Phase 2**: Test marketing container on staging domain
|
|
||||||
3. **Phase 3**: Switch `igny8.com` to use container
|
|
||||||
4. **Phase 4**: Remove manual `/var/www/igny8-marketing` setup
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Conclusion
|
|
||||||
|
|
||||||
**Separate containers (Option A) is the best long-term solution** because:
|
|
||||||
- ✅ Production-ready architecture
|
|
||||||
- ✅ Better DevOps practices
|
|
||||||
- ✅ Easier maintenance
|
|
||||||
- ✅ Scalable and flexible
|
|
||||||
- ✅ Industry standard approach
|
|
||||||
|
|
||||||
The current setup works but is not ideal for production. Separate containers provide better separation, versioning, and deployment automation.
|
|
||||||
|
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
# Deployment Status - Marketing Container
|
|
||||||
|
|
||||||
**Last Updated:** 2025-11-13
|
|
||||||
**Status:** ✅ **OPERATIONAL**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Current Status
|
|
||||||
|
|
||||||
### Containers
|
|
||||||
- ✅ `igny8_marketing` - Running (Port 8020 internal, 8022 external)
|
|
||||||
- ✅ `igny8_caddy` - Running (Routes `igny8.com` → `igny8_marketing:8020`)
|
|
||||||
- ✅ `igny8_frontend` - Running (Vite dev server for `app.igny8.com`)
|
|
||||||
- ✅ `igny8_backend` - Running (Django API for `api.igny8.com`)
|
|
||||||
|
|
||||||
### Network
|
|
||||||
- ✅ All containers on `igny8_net` network
|
|
||||||
- ✅ Caddy can reach marketing container
|
|
||||||
- ✅ Marketing container serving on port 8020
|
|
||||||
|
|
||||||
### HTTP Status
|
|
||||||
- ✅ Marketing container: HTTP 200 (direct access)
|
|
||||||
- ✅ Through Caddy: HTTP 200 (production routing)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Deployment Process Verified
|
|
||||||
|
|
||||||
The automated deployment process has been tested and is working:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 1. Build marketing image
|
|
||||||
cd /data/app/igny8/frontend
|
|
||||||
docker build -t igny8-marketing:latest -f Dockerfile.marketing .
|
|
||||||
|
|
||||||
# 2. Restart container
|
|
||||||
cd /data/app/igny8
|
|
||||||
docker compose -f docker-compose.app.yml -p igny8-app restart igny8_marketing
|
|
||||||
```
|
|
||||||
|
|
||||||
**Result:** ✅ Container restarts with new build, site updates immediately.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Architecture
|
|
||||||
|
|
||||||
```
|
|
||||||
Internet
|
|
||||||
↓
|
|
||||||
Caddy (HTTPS:443)
|
|
||||||
↓
|
|
||||||
igny8.com → igny8_marketing:8020 (Container)
|
|
||||||
app.igny8.com → igny8_frontend:5173 (Vite Dev)
|
|
||||||
api.igny8.com → igny8_backend:8010 (Django)
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Quick Commands
|
|
||||||
|
|
||||||
### Check Status
|
|
||||||
```bash
|
|
||||||
docker ps --filter "name=igny8_marketing"
|
|
||||||
docker logs igny8_marketing --tail 20
|
|
||||||
```
|
|
||||||
|
|
||||||
### Update Marketing Site
|
|
||||||
```bash
|
|
||||||
cd /data/app/igny8/frontend
|
|
||||||
docker build -t igny8-marketing:latest -f Dockerfile.marketing .
|
|
||||||
cd /data/app/igny8
|
|
||||||
docker compose -f docker-compose.app.yml -p igny8-app restart igny8_marketing
|
|
||||||
```
|
|
||||||
|
|
||||||
### Test Connectivity
|
|
||||||
```bash
|
|
||||||
# Direct container access
|
|
||||||
curl http://localhost:8022/marketing.html
|
|
||||||
|
|
||||||
# Through Caddy (production)
|
|
||||||
curl https://igny8.com/marketing.html
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Migration Complete
|
|
||||||
|
|
||||||
✅ **Old manual process is deprecated**
|
|
||||||
✅ **New containerized process is active**
|
|
||||||
✅ **Site is fully operational**
|
|
||||||
|
|
||||||
The marketing site is now:
|
|
||||||
- Containerized
|
|
||||||
- Version controlled (Docker images)
|
|
||||||
- Automatically deployed
|
|
||||||
- Easy to rollback
|
|
||||||
- Production-ready
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Next Steps (Optional)
|
|
||||||
|
|
||||||
1. **Set up CI/CD** - Automate builds on git push
|
|
||||||
2. **Add health checks** - Monitor container health
|
|
||||||
3. **Set up monitoring** - Track container metrics
|
|
||||||
4. **Create backup strategy** - Tag images before updates
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**See Also:**
|
|
||||||
- [Marketing Deployment Guide](./MARKETING_DEPLOYMENT.md)
|
|
||||||
- [Deployment Architecture](./DEPLOYMENT_ARCHITECTURE.md)
|
|
||||||
|
|
||||||
@@ -1,236 +0,0 @@
|
|||||||
# Marketing Dev Frontend Configuration Analysis
|
|
||||||
|
|
||||||
**Date:** 2025-01-XX
|
|
||||||
**Status:** ✅ **FIXED** - All issues resolved
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Executive Summary
|
|
||||||
|
|
||||||
Analysis of the marketing dev frontend container and configuration reveals:
|
|
||||||
- ✅ **Architecture Consistency**: Follows existing architecture (separate containers)
|
|
||||||
- ✅ **No Parallel Builds**: Uses `image:` not `build:` to avoid conflicts
|
|
||||||
- ✅ **Caddy Routing**: Correctly configured for dev mode
|
|
||||||
- ✅ **Network Configuration**: All containers on `igny8_net` network
|
|
||||||
- ⚠️ **FIXED**: Port mismatch in production marketing container
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Configuration Analysis
|
|
||||||
|
|
||||||
### 1. Container Configuration ✅
|
|
||||||
|
|
||||||
#### `igny8_marketing_dev` (Development)
|
|
||||||
- **Image**: `igny8-marketing-dev:latest`
|
|
||||||
- **Ports**: `8023:5174` (external:internal)
|
|
||||||
- **Volume Mount**: `/data/app/igny8/frontend:/app:rw` (live reload)
|
|
||||||
- **Network**: `igny8_net`
|
|
||||||
- **Status**: ✅ Correctly configured
|
|
||||||
|
|
||||||
#### `igny8_marketing` (Production)
|
|
||||||
- **Image**: `igny8-marketing:latest`
|
|
||||||
- **Ports**: `8022:8020` (external:internal) - **FIXED**
|
|
||||||
- **Network**: `igny8_net`
|
|
||||||
- **Status**: ✅ Fixed - now matches Dockerfile.marketing (port 8020)
|
|
||||||
|
|
||||||
### 2. Dockerfile Configuration ✅
|
|
||||||
|
|
||||||
#### Dockerfile.marketing.dev
|
|
||||||
- **Base**: `node:18-alpine`
|
|
||||||
- **Port**: `5174` (Vite dev server)
|
|
||||||
- **Command**: `npm run dev:marketing`
|
|
||||||
- **Status**: ✅ Correct
|
|
||||||
|
|
||||||
#### Dockerfile.marketing
|
|
||||||
- **Base**: `caddy:latest` (multi-stage build)
|
|
||||||
- **Port**: `8020` (Caddy server)
|
|
||||||
- **Command**: `caddy run --config /etc/caddy/Caddyfile`
|
|
||||||
- **Status**: ✅ Correct
|
|
||||||
|
|
||||||
### 3. Caddy Routing Configuration ✅
|
|
||||||
|
|
||||||
**Main Caddyfile Location**: `/var/lib/docker/volumes/portainer_data/_data/caddy/Caddyfile`
|
|
||||||
|
|
||||||
**Current Configuration (Dev Mode)**:
|
|
||||||
```caddyfile
|
|
||||||
igny8.com {
|
|
||||||
reverse_proxy igny8_marketing_dev:5174 {
|
|
||||||
# WebSocket support for HMR
|
|
||||||
header_up Connection {>Connection}
|
|
||||||
header_up Upgrade {>Upgrade}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Status**: ✅ Correctly routing to dev container with HMR support
|
|
||||||
|
|
||||||
**Production Mode** (when switching):
|
|
||||||
```caddyfile
|
|
||||||
igny8.com {
|
|
||||||
reverse_proxy igny8_marketing:8020 {
|
|
||||||
# Static production build
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. Package.json Scripts ✅
|
|
||||||
|
|
||||||
- `dev:marketing`: `vite --host 0.0.0.0 --port 5174 --force marketing.html` ✅
|
|
||||||
- `build:marketing`: `vite build --mode marketing` ✅
|
|
||||||
|
|
||||||
**Status**: ✅ All scripts correctly configured
|
|
||||||
|
|
||||||
### 5. Network Architecture ✅
|
|
||||||
|
|
||||||
All containers are on the `igny8_net` external network:
|
|
||||||
- ✅ `igny8_marketing_dev` → `igny8_net`
|
|
||||||
- ✅ `igny8_marketing` → `igny8_net`
|
|
||||||
- ✅ `igny8_frontend` → `igny8_net`
|
|
||||||
- ✅ `igny8_backend` → `igny8_net`
|
|
||||||
- ✅ `igny8_caddy` → `igny8_net`
|
|
||||||
|
|
||||||
**Status**: ✅ All containers can communicate via container names
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Issues Found & Fixed
|
|
||||||
|
|
||||||
### Issue 1: Port Mismatch in Production Container ⚠️ → ✅ FIXED
|
|
||||||
|
|
||||||
**Problem**:
|
|
||||||
- `docker-compose.app.yml` mapped `8022:5174` for `igny8_marketing`
|
|
||||||
- But `Dockerfile.marketing` exposes port `8020` (Caddy)
|
|
||||||
- This would cause connection failures when Caddy routes to production
|
|
||||||
|
|
||||||
**Fix Applied**:
|
|
||||||
```yaml
|
|
||||||
# Before
|
|
||||||
ports:
|
|
||||||
- "0.0.0.0:8022:5174" # WRONG
|
|
||||||
|
|
||||||
# After
|
|
||||||
ports:
|
|
||||||
- "0.0.0.0:8022:8020" # CORRECT - matches Caddy port
|
|
||||||
```
|
|
||||||
|
|
||||||
**Status**: ✅ Fixed in `docker-compose.app.yml`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Architecture Consistency Check
|
|
||||||
|
|
||||||
### ✅ Follows Existing Architecture
|
|
||||||
|
|
||||||
1. **Separate Containers**: ✅
|
|
||||||
- Dev and production containers are separate
|
|
||||||
- Matches architecture principle (Option A from DEPLOYMENT_ARCHITECTURE.md)
|
|
||||||
|
|
||||||
2. **No Parallel Builds**: ✅
|
|
||||||
- Uses `image:` not `build:` in docker-compose
|
|
||||||
- Prevents Portainer/CLI conflicts
|
|
||||||
- Images built separately as documented
|
|
||||||
|
|
||||||
3. **Network Isolation**: ✅
|
|
||||||
- All containers on `igny8_net`
|
|
||||||
- External network (shared with infra stack)
|
|
||||||
- Container name resolution works
|
|
||||||
|
|
||||||
4. **Port Allocation**: ✅
|
|
||||||
- `8021`: Frontend dev (app)
|
|
||||||
- `8022`: Marketing production
|
|
||||||
- `8023`: Marketing dev
|
|
||||||
- No conflicts
|
|
||||||
|
|
||||||
5. **Volume Mounts**: ✅
|
|
||||||
- Dev container has volume mount for HMR
|
|
||||||
- Production container is stateless (built image)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Accessibility Verification
|
|
||||||
|
|
||||||
### ✅ All Services Accessible
|
|
||||||
|
|
||||||
1. **Direct Access**:
|
|
||||||
- Marketing Dev: `http://localhost:8023` ✅
|
|
||||||
- Marketing Prod: `http://localhost:8022` ✅
|
|
||||||
- Frontend Dev: `http://localhost:8021` ✅
|
|
||||||
|
|
||||||
2. **Through Caddy (HTTPS)**:
|
|
||||||
- `https://igny8.com` → `igny8_marketing_dev:5174` (dev mode) ✅
|
|
||||||
- `https://app.igny8.com` → `igny8_frontend:5173` ✅
|
|
||||||
- `https://api.igny8.com` → `igny8_backend:8010` ✅
|
|
||||||
|
|
||||||
3. **WebSocket Support**:
|
|
||||||
- Caddy configured with WebSocket headers for HMR ✅
|
|
||||||
- Dev container supports HMR ✅
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Gaps & Parallel Builds Check
|
|
||||||
|
|
||||||
### ✅ No Gaps Found
|
|
||||||
|
|
||||||
1. **Container Definitions**: All containers defined in `docker-compose.app.yml`
|
|
||||||
2. **Dockerfiles**: All Dockerfiles exist and are correct
|
|
||||||
3. **Caddyfile**: Routing configured correctly
|
|
||||||
4. **Scripts**: All npm scripts exist in package.json
|
|
||||||
5. **Network**: All containers on same network
|
|
||||||
|
|
||||||
### ✅ No Parallel Builds
|
|
||||||
|
|
||||||
1. **docker-compose.app.yml**: Uses `image:` not `build:` ✅
|
|
||||||
2. **Build Instructions**: Clear documentation to build separately ✅
|
|
||||||
3. **No Conflicts**: Portainer and CLI can use same compose file ✅
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
### ✅ Configuration Status
|
|
||||||
|
|
||||||
| Component | Status | Notes |
|
|
||||||
|-----------|--------|-------|
|
|
||||||
| **Container Config** | ✅ Fixed | Port mismatch corrected |
|
|
||||||
| **Dockerfiles** | ✅ Correct | All ports match |
|
|
||||||
| **Caddy Routing** | ✅ Correct | Dev mode active |
|
|
||||||
| **Network** | ✅ Correct | All on `igny8_net` |
|
|
||||||
| **Scripts** | ✅ Correct | All npm scripts exist |
|
|
||||||
| **Architecture** | ✅ Consistent | Follows existing patterns |
|
|
||||||
| **Accessibility** | ✅ Accessible | All services reachable |
|
|
||||||
| **No Gaps** | ✅ Complete | All components present |
|
|
||||||
| **No Parallel Builds** | ✅ Clean | Uses `image:` not `build:` |
|
|
||||||
|
|
||||||
### ✅ All Issues Resolved
|
|
||||||
|
|
||||||
1. ✅ Port mismatch fixed in `docker-compose.app.yml`
|
|
||||||
2. ✅ Configuration consistent with architecture
|
|
||||||
3. ✅ All services accessible
|
|
||||||
4. ✅ No gaps or parallel builds
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Recommendations
|
|
||||||
|
|
||||||
### Current Setup (Dev Mode)
|
|
||||||
- ✅ Marketing dev container is active and accessible
|
|
||||||
- ✅ HMR working through Caddy
|
|
||||||
- ✅ All routing correct
|
|
||||||
|
|
||||||
### For Production Deployment
|
|
||||||
1. Build production image: `docker build -t igny8-marketing:latest -f Dockerfile.marketing .`
|
|
||||||
2. Update Caddyfile to route to `igny8_marketing:8020`
|
|
||||||
3. Restart Caddy: `docker compose restart caddy`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Conclusion
|
|
||||||
|
|
||||||
The marketing dev frontend container and configuration are:
|
|
||||||
- ✅ **Consistent** with existing architecture
|
|
||||||
- ✅ **Fully configured** and accessible
|
|
||||||
- ✅ **No gaps** or missing components
|
|
||||||
- ✅ **No parallel builds** - clean configuration
|
|
||||||
|
|
||||||
**All issues have been identified and fixed. The system is ready for use.**
|
|
||||||
|
|
||||||
@@ -1,210 +0,0 @@
|
|||||||
# Marketing Site Container Deployment Guide
|
|
||||||
|
|
||||||
## ✅ Implementation Complete
|
|
||||||
|
|
||||||
The marketing site is now containerized and running! This document explains the new setup and how to use it.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🏗️ Architecture
|
|
||||||
|
|
||||||
### Before (Manual)
|
|
||||||
- Marketing files in `/var/www/igny8-marketing/`
|
|
||||||
- Manual build → copy → restart process
|
|
||||||
- No version control for deployments
|
|
||||||
|
|
||||||
### After (Containerized) ✅
|
|
||||||
- Marketing site runs in `igny8_marketing` container
|
|
||||||
- Automated builds and deployments
|
|
||||||
- Version controlled with Docker images
|
|
||||||
- Easy rollback capability
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📦 New Components
|
|
||||||
|
|
||||||
### 1. Dockerfile.marketing
|
|
||||||
**Location:** `/data/app/igny8/frontend/Dockerfile.marketing`
|
|
||||||
|
|
||||||
Builds the marketing site and serves it with Caddy.
|
|
||||||
|
|
||||||
### 2. Caddyfile.marketing
|
|
||||||
**Location:** `/data/app/igny8/frontend/Caddyfile.marketing`
|
|
||||||
|
|
||||||
Caddy configuration for the marketing container (port 8020).
|
|
||||||
|
|
||||||
### 3. igny8_marketing Service
|
|
||||||
**Location:** `docker-compose.app.yml`
|
|
||||||
|
|
||||||
New container service for the marketing site.
|
|
||||||
|
|
||||||
### 4. Updated Main Caddyfile
|
|
||||||
**Location:** `/var/lib/docker/volumes/portainer_data/_data/caddy/Caddyfile`
|
|
||||||
|
|
||||||
Now routes `igny8.com` to `igny8_marketing:8020` container instead of static files.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🚀 Deployment Process
|
|
||||||
|
|
||||||
### Initial Setup (One-time)
|
|
||||||
|
|
||||||
1. **Build the marketing image:**
|
|
||||||
```bash
|
|
||||||
cd /data/app/igny8/frontend
|
|
||||||
docker build -t igny8-marketing:latest -f Dockerfile.marketing .
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Start the marketing container:**
|
|
||||||
```bash
|
|
||||||
cd /data/app/igny8
|
|
||||||
docker compose -f docker-compose.app.yml -p igny8-app up -d igny8_marketing
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **Reload Caddy:**
|
|
||||||
```bash
|
|
||||||
cd /data/app
|
|
||||||
docker compose restart caddy
|
|
||||||
```
|
|
||||||
|
|
||||||
### Updating Marketing Site
|
|
||||||
|
|
||||||
**New Process (Automated):**
|
|
||||||
```bash
|
|
||||||
# 1. Rebuild the marketing image
|
|
||||||
cd /data/app/igny8/frontend
|
|
||||||
docker build -t igny8-marketing:latest -f Dockerfile.marketing .
|
|
||||||
|
|
||||||
# 2. Restart the container (picks up new image)
|
|
||||||
cd /data/app/igny8
|
|
||||||
docker compose -f docker-compose.app.yml -p igny8-app restart igny8_marketing
|
|
||||||
```
|
|
||||||
|
|
||||||
**Old Process (Manual - No Longer Needed):**
|
|
||||||
```bash
|
|
||||||
# ❌ OLD WAY - Don't use anymore
|
|
||||||
npm run build:marketing
|
|
||||||
sudo cp -r dist/* /var/www/igny8-marketing/
|
|
||||||
docker compose restart caddy
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔄 Rollback Process
|
|
||||||
|
|
||||||
If you need to rollback to a previous version:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 1. Tag the current image as backup
|
|
||||||
docker tag igny8-marketing:latest igny8-marketing:backup-$(date +%Y%m%d)
|
|
||||||
|
|
||||||
# 2. Tag a previous image as latest (if you have it)
|
|
||||||
docker tag igny8-marketing:previous-version igny8-marketing:latest
|
|
||||||
|
|
||||||
# 3. Restart container
|
|
||||||
docker compose -f docker-compose.app.yml -p igny8-app restart igny8_marketing
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📊 Container Status
|
|
||||||
|
|
||||||
### Check Marketing Container
|
|
||||||
```bash
|
|
||||||
docker ps --filter "name=igny8_marketing"
|
|
||||||
```
|
|
||||||
|
|
||||||
### View Marketing Logs
|
|
||||||
```bash
|
|
||||||
docker logs igny8_marketing
|
|
||||||
docker logs igny8_marketing --tail 50 -f # Follow logs
|
|
||||||
```
|
|
||||||
|
|
||||||
### Test Marketing Site
|
|
||||||
```bash
|
|
||||||
# Test direct container access
|
|
||||||
curl http://localhost:8022/marketing.html
|
|
||||||
|
|
||||||
# Test through Caddy (production)
|
|
||||||
curl https://igny8.com/marketing.html
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔍 Troubleshooting
|
|
||||||
|
|
||||||
### Container Not Starting
|
|
||||||
```bash
|
|
||||||
# Check logs
|
|
||||||
docker logs igny8_marketing
|
|
||||||
|
|
||||||
# Check if image exists
|
|
||||||
docker images | grep igny8-marketing
|
|
||||||
|
|
||||||
# Rebuild if needed
|
|
||||||
cd /data/app/igny8/frontend
|
|
||||||
docker build -t igny8-marketing:latest -f Dockerfile.marketing .
|
|
||||||
```
|
|
||||||
|
|
||||||
### Caddy Not Routing Correctly
|
|
||||||
```bash
|
|
||||||
# Check Caddy logs
|
|
||||||
docker logs igny8_caddy
|
|
||||||
|
|
||||||
# Verify Caddyfile
|
|
||||||
cat /var/lib/docker/volumes/portainer_data/_data/caddy/Caddyfile
|
|
||||||
|
|
||||||
# Reload Caddy
|
|
||||||
cd /data/app
|
|
||||||
docker compose restart caddy
|
|
||||||
```
|
|
||||||
|
|
||||||
### Network Issues
|
|
||||||
```bash
|
|
||||||
# Verify containers are on same network
|
|
||||||
docker network inspect igny8_net | grep -A 5 igny8_marketing
|
|
||||||
docker network inspect igny8_net | grep -A 5 igny8_caddy
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📝 File Locations
|
|
||||||
|
|
||||||
| Component | Location |
|
|
||||||
|-----------|----------|
|
|
||||||
| Dockerfile.marketing | `/data/app/igny8/frontend/Dockerfile.marketing` |
|
|
||||||
| Caddyfile.marketing | `/data/app/igny8/frontend/Caddyfile.marketing` |
|
|
||||||
| docker-compose.app.yml | `/data/app/igny8/docker-compose.app.yml` |
|
|
||||||
| Main Caddyfile | `/var/lib/docker/volumes/portainer_data/_data/caddy/Caddyfile` |
|
|
||||||
| Marketing Image | Docker: `igny8-marketing:latest` |
|
|
||||||
| Container Name | `igny8_marketing` |
|
|
||||||
| Container Port | `8020` (internal), `8022` (external) |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ✅ Benefits
|
|
||||||
|
|
||||||
1. **Automated Deployments** - No more manual file copying
|
|
||||||
2. **Version Control** - Each deployment is a Docker image
|
|
||||||
3. **Easy Rollback** - Quick container image rollback
|
|
||||||
4. **Isolation** - Marketing site isolated in its own container
|
|
||||||
5. **Reproducible** - Same build process every time
|
|
||||||
6. **CI/CD Ready** - Can be fully automated
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎯 Current Status
|
|
||||||
|
|
||||||
✅ Marketing container is **running**
|
|
||||||
✅ Caddy routing is **configured**
|
|
||||||
✅ Site is **accessible** at `https://igny8.com`
|
|
||||||
✅ Direct container access at `http://localhost:8022`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📚 Related Documentation
|
|
||||||
|
|
||||||
- [Deployment Architecture Analysis](./DEPLOYMENT_ARCHITECTURE.md)
|
|
||||||
- Docker Compose: `/data/app/igny8/docker-compose.app.yml`
|
|
||||||
- Main Caddyfile: `/var/lib/docker/volumes/portainer_data/_data/caddy/Caddyfile`
|
|
||||||
|
|
||||||
@@ -1,160 +0,0 @@
|
|||||||
# Marketing Development Environment with HMR
|
|
||||||
|
|
||||||
**Status:** ✅ **ACTIVE**
|
|
||||||
|
|
||||||
The marketing site now has a development environment with Hot Module Replacement (HMR) - just like the app dev environment!
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🚀 Quick Start
|
|
||||||
|
|
||||||
### Current Setup
|
|
||||||
- **Dev Server:** `igny8_marketing_dev` (Vite with HMR)
|
|
||||||
- **Access:** `https://igny8.com` (routed through Caddy)
|
|
||||||
- **Direct Access:** `http://localhost:8023`
|
|
||||||
- **Port:** 5174 (internal), 8023 (external)
|
|
||||||
|
|
||||||
### How It Works
|
|
||||||
1. **Volume Mount:** `/data/app/igny8/frontend` → `/app` (live file watching)
|
|
||||||
2. **HMR Enabled:** Changes to files/images update in real-time
|
|
||||||
3. **No Rebuild Needed:** Just edit files and see changes instantly!
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📝 Development Workflow
|
|
||||||
|
|
||||||
### Making Changes
|
|
||||||
1. **Edit files** in `/data/app/igny8/frontend/src/marketing/`
|
|
||||||
2. **Edit images** in `/data/app/igny8/frontend/public/marketing/images/`
|
|
||||||
3. **See changes instantly** - HMR updates the browser automatically!
|
|
||||||
|
|
||||||
### No Need To:
|
|
||||||
- ❌ Run `npm run build:marketing`
|
|
||||||
- ❌ Rebuild Docker image
|
|
||||||
- ❌ Restart container
|
|
||||||
- ❌ Copy files manually
|
|
||||||
|
|
||||||
### Just:
|
|
||||||
- ✅ Edit files
|
|
||||||
- ✅ Save
|
|
||||||
- ✅ See changes in browser (HMR handles the rest!)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔄 Switching Between Dev and Production
|
|
||||||
|
|
||||||
### Development Mode (Current)
|
|
||||||
**Caddyfile routes to:** `igny8_marketing_dev:5174`
|
|
||||||
|
|
||||||
```caddyfile
|
|
||||||
igny8.com {
|
|
||||||
reverse_proxy igny8_marketing_dev:5174 {
|
|
||||||
# WebSocket support for HMR
|
|
||||||
header_up Connection {>Connection}
|
|
||||||
header_up Upgrade {>Upgrade}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Production Mode
|
|
||||||
**Caddyfile routes to:** `igny8_marketing:8020`
|
|
||||||
|
|
||||||
```caddyfile
|
|
||||||
igny8.com {
|
|
||||||
reverse_proxy igny8_marketing:8020 {
|
|
||||||
# Static production build
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**To switch:** Edit `/var/lib/docker/volumes/portainer_data/_data/caddy/Caddyfile` and restart Caddy.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🛠️ Container Management
|
|
||||||
|
|
||||||
### Start Dev Server
|
|
||||||
```bash
|
|
||||||
cd /data/app/igny8
|
|
||||||
docker compose -f docker-compose.app.yml -p igny8-app up -d igny8_marketing_dev
|
|
||||||
```
|
|
||||||
|
|
||||||
### View Logs
|
|
||||||
```bash
|
|
||||||
docker logs igny8_marketing_dev -f
|
|
||||||
```
|
|
||||||
|
|
||||||
### Restart Dev Server
|
|
||||||
```bash
|
|
||||||
docker compose -f docker-compose.app.yml -p igny8-app restart igny8_marketing_dev
|
|
||||||
```
|
|
||||||
|
|
||||||
### Stop Dev Server
|
|
||||||
```bash
|
|
||||||
docker compose -f docker-compose.app.yml -p igny8-app stop igny8_marketing_dev
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📂 File Locations
|
|
||||||
|
|
||||||
| Type | Location |
|
|
||||||
|------|----------|
|
|
||||||
| **Marketing Components** | `/data/app/igny8/frontend/src/marketing/` |
|
|
||||||
| **Marketing Pages** | `/data/app/igny8/frontend/src/marketing/pages/` |
|
|
||||||
| **Marketing Images** | `/data/app/igny8/frontend/public/marketing/images/` |
|
|
||||||
| **Marketing Styles** | `/data/app/igny8/frontend/src/marketing/styles/` |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ✅ Benefits
|
|
||||||
|
|
||||||
1. **Real-time Updates** - Changes reflect immediately
|
|
||||||
2. **No Rebuilds** - Edit and save, that's it!
|
|
||||||
3. **Fast Development** - Same experience as app dev environment
|
|
||||||
4. **Image Updates** - Images in `public/marketing/images/` update instantly
|
|
||||||
5. **Component Updates** - React components hot-reload automatically
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔍 Troubleshooting
|
|
||||||
|
|
||||||
### Changes Not Appearing
|
|
||||||
1. Check container is running: `docker ps | grep igny8_marketing_dev`
|
|
||||||
2. Check logs: `docker logs igny8_marketing_dev`
|
|
||||||
3. Verify volume mount: Files should be in `/data/app/igny8/frontend/`
|
|
||||||
|
|
||||||
### HMR Not Working
|
|
||||||
1. Check browser console for WebSocket errors
|
|
||||||
2. Verify Caddyfile has WebSocket headers
|
|
||||||
3. Restart Caddy: `docker compose restart caddy`
|
|
||||||
|
|
||||||
### Port Conflicts
|
|
||||||
- Dev server uses port 5174 (internal), 8023 (external)
|
|
||||||
- If conflicts occur, change port in `docker-compose.app.yml`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📊 Current Status
|
|
||||||
|
|
||||||
✅ **Dev Server:** Running
|
|
||||||
✅ **HMR:** Enabled
|
|
||||||
✅ **Volume Mount:** Active
|
|
||||||
✅ **Caddy Routing:** Configured
|
|
||||||
✅ **WebSocket Support:** Enabled
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎯 Next Steps
|
|
||||||
|
|
||||||
When ready for production:
|
|
||||||
1. Build production image: `docker build -t igny8-marketing:latest -f Dockerfile.marketing .`
|
|
||||||
2. Update Caddyfile to route to `igny8_marketing:8020`
|
|
||||||
3. Restart Caddy: `docker compose restart caddy`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**See Also:**
|
|
||||||
- [Marketing Deployment Guide](./MARKETING_DEPLOYMENT.md)
|
|
||||||
- [Deployment Architecture](./DEPLOYMENT_ARCHITECTURE.md)
|
|
||||||
|
|
||||||
@@ -43,7 +43,7 @@ const caseStudies = [
|
|||||||
|
|
||||||
const CaseStudies: React.FC = () => {
|
const CaseStudies: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<div className="bg-white text-slate-900">
|
<div className="bg-gradient-to-b from-white via-slate-50/30 to-white text-slate-900">
|
||||||
<section className="max-w-6xl mx-auto px-6 pt-24 pb-16">
|
<section className="max-w-6xl mx-auto px-6 pt-24 pb-16">
|
||||||
<SectionHeading
|
<SectionHeading
|
||||||
eyebrow="Proof"
|
eyebrow="Proof"
|
||||||
@@ -53,34 +53,49 @@ const CaseStudies: React.FC = () => {
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section className="max-w-6xl mx-auto px-6 pb-24 space-y-12">
|
<section className="max-w-6xl mx-auto px-6 pb-24 space-y-12">
|
||||||
{caseStudies.map((cs) => (
|
{caseStudies.map((cs, idx) => {
|
||||||
|
const gradients = [
|
||||||
|
{ border: "border-[#0693e3]/40", bg: "from-[#0693e3]/10 via-white to-[#0bbf87]/5" },
|
||||||
|
{ border: "border-[#0bbf87]/40", bg: "from-[#0bbf87]/10 via-white to-[#ff7a00]/5" },
|
||||||
|
{ border: "border-[#5d4ae3]/40", bg: "from-[#5d4ae3]/10 via-white to-[#0693e3]/5" },
|
||||||
|
];
|
||||||
|
const gradient = gradients[idx % gradients.length];
|
||||||
|
const metricColors = [
|
||||||
|
{ border: "border-[#0693e3]/30", bg: "from-[#0693e3]/10 to-white", text: "text-[#0693e3]" },
|
||||||
|
{ border: "border-[#0bbf87]/30", bg: "from-[#0bbf87]/10 to-white", text: "text-[#0bbf87]" },
|
||||||
|
{ border: "border-[#ff7a00]/30", bg: "from-[#ff7a00]/10 to-white", text: "text-[#ff7a00]" },
|
||||||
|
];
|
||||||
|
return (
|
||||||
<div
|
<div
|
||||||
key={cs.company}
|
key={cs.company}
|
||||||
className="rounded-3xl border border-slate-200 bg-white p-12 grid grid-cols-1 lg:grid-cols-2 gap-12"
|
className={`rounded-3xl border-2 ${gradient.border} bg-gradient-to-br ${gradient.bg} p-12 grid grid-cols-1 lg:grid-cols-2 gap-12 hover:shadow-xl transition-all`}
|
||||||
>
|
>
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<span className="text-xs uppercase tracking-[0.3em] text-slate-500">
|
<span className="text-xs uppercase tracking-[0.3em] text-slate-500 font-semibold">
|
||||||
{cs.company}
|
{cs.company}
|
||||||
</span>
|
</span>
|
||||||
<h3 className="text-2xl font-semibold text-slate-900">{cs.headline}</h3>
|
<h3 className="text-2xl font-semibold text-slate-900">{cs.headline}</h3>
|
||||||
<p className="text-sm text-slate-600 leading-relaxed">{cs.summary}</p>
|
<p className="text-sm text-slate-600 leading-relaxed">{cs.summary}</p>
|
||||||
<div className="grid grid-cols-3 gap-4">
|
<div className="grid grid-cols-3 gap-4">
|
||||||
{cs.metrics.map((metric) => (
|
{cs.metrics.map((metric, metricIdx) => {
|
||||||
|
const metricColor = metricColors[metricIdx % metricColors.length];
|
||||||
|
return (
|
||||||
<div
|
<div
|
||||||
key={metric.label}
|
key={metric.label}
|
||||||
className="rounded-2xl border border-slate-200 bg-white p-4 text-center space-y-2"
|
className={`rounded-2xl border-2 ${metricColor.border} bg-gradient-to-br ${metricColor.bg} p-4 text-center space-y-2 hover:shadow-lg transition-all`}
|
||||||
>
|
>
|
||||||
<div className="text-xl font-semibold text-slate-900">
|
<div className={`text-xl font-semibold ${metricColor.text}`}>
|
||||||
{metric.value}
|
{metric.value}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-xs uppercase tracking-[0.2em] text-slate-500">
|
<div className="text-xs uppercase tracking-[0.2em] text-slate-500">
|
||||||
{metric.label}
|
{metric.label}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="rounded-3xl border border-slate-200 bg-slate-100 overflow-hidden">
|
<div className="rounded-3xl border-2 border-slate-200 bg-gradient-to-br from-slate-50 to-white overflow-hidden shadow-inner">
|
||||||
<img
|
<img
|
||||||
src={`/marketing/images/${cs.image}`}
|
src={`/marketing/images/${cs.image}`}
|
||||||
alt={`${cs.company} case study`}
|
alt={`${cs.company} case study`}
|
||||||
@@ -88,34 +103,39 @@ const CaseStudies: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
);
|
||||||
|
})}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section className="bg-slate-50/70 border-y border-slate-200">
|
<section className="bg-gradient-to-br from-[#0693e3]/10 via-slate-50/70 to-[#0bbf87]/10 border-y border-[#0693e3]/20">
|
||||||
<div className="max-w-6xl mx-auto px-6 py-24 grid grid-cols-1 md:grid-cols-2 gap-12">
|
<div className="max-w-6xl mx-auto px-6 py-24 grid grid-cols-1 md:grid-cols-2 gap-12">
|
||||||
<div className="space-y-4">
|
<div className="rounded-3xl border-2 border-[#0693e3]/30 bg-gradient-to-br from-[#0693e3]/10 to-white p-8 space-y-4">
|
||||||
<h4 className="text-lg font-semibold text-slate-900">Results you can expect</h4>
|
<h4 className="text-lg font-semibold text-slate-900 flex items-center gap-2">
|
||||||
|
<span className="size-2 rounded-full bg-[#0693e3]"></span>
|
||||||
|
Results you can expect
|
||||||
|
</h4>
|
||||||
<ul className="space-y-3 text-sm text-slate-600">
|
<ul className="space-y-3 text-sm text-slate-600">
|
||||||
<li className="flex gap-3">
|
{[
|
||||||
<span className="mt-1 size-1.5 rounded-full bg-brand-500" />
|
{ text: "30-60 day onboarding to deploy automation and Thinker governance.", color: "bg-[#0693e3]" },
|
||||||
30-60 day onboarding to deploy automation and Thinker governance.
|
{ text: "3-5× increase in content throughput without sacrificing editorial quality.", color: "bg-[#0bbf87]" },
|
||||||
</li>
|
{ text: "Clear ROI dashboards tying automation to revenue outcomes.", color: "bg-[#ff7a00]" },
|
||||||
<li className="flex gap-3">
|
].map((item) => (
|
||||||
<span className="mt-1 size-1.5 rounded-full bg-brand-500" />
|
<li key={item.text} className="flex gap-3">
|
||||||
3-5× increase in content throughput without sacrificing editorial quality.
|
<span className={`mt-1 size-1.5 rounded-full ${item.color} shadow-sm`} />
|
||||||
</li>
|
{item.text}
|
||||||
<li className="flex gap-3">
|
|
||||||
<span className="mt-1 size-1.5 rounded-full bg-brand-500" />
|
|
||||||
Clear ROI dashboards tying automation to revenue outcomes.
|
|
||||||
</li>
|
</li>
|
||||||
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div className="rounded-3xl border border-slate-200 bg-white p-10 space-y-4 text-sm text-slate-600">
|
<div className="rounded-3xl border-2 border-[#0bbf87]/30 bg-gradient-to-br from-[#0bbf87]/10 to-white p-10 space-y-4 text-sm text-slate-600">
|
||||||
<h4 className="text-lg font-semibold text-slate-900">Customer advisory board</h4>
|
<h4 className="text-lg font-semibold text-slate-900 flex items-center gap-2">
|
||||||
|
<span className="size-2 rounded-full bg-[#0bbf87]"></span>
|
||||||
|
Customer advisory board
|
||||||
|
</h4>
|
||||||
<p>
|
<p>
|
||||||
Igny8’s roadmap is shaped by an active community of customer strategists, agency partners, and product marketers. Join and get early access to features, template libraries, and industry benchmarks.
|
Igny8's roadmap is shaped by an active community of customer strategists, agency partners, and product marketers. Join and get early access to features, template libraries, and industry benchmarks.
|
||||||
</p>
|
</p>
|
||||||
<button className="inline-flex items-center justify-center rounded-full bg-brand-500 hover:bg-brand-400 px-5 py-2 text-sm font-semibold">
|
<button className="inline-flex items-center justify-center rounded-full bg-gradient-to-r from-[#0693e3] to-[#0472b8] hover:from-[#0472b8] hover:to-[#0693e3] text-white px-5 py-2 text-sm font-semibold shadow-lg shadow-[#0693e3]/30 transition-all">
|
||||||
Join the CAB waitlist
|
Join the CAB waitlist
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import CTASection from "../components/CTASection";
|
|||||||
|
|
||||||
const Contact: React.FC = () => {
|
const Contact: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<div className="bg-white text-slate-900">
|
<div className="bg-gradient-to-b from-white via-slate-50/30 to-white text-slate-900">
|
||||||
<section className="max-w-4xl mx-auto px-6 pt-24 pb-12">
|
<section className="max-w-4xl mx-auto px-6 pt-24 pb-12">
|
||||||
<SectionHeading
|
<SectionHeading
|
||||||
eyebrow="Contact"
|
eyebrow="Contact"
|
||||||
@@ -14,14 +14,14 @@ const Contact: React.FC = () => {
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section className="max-w-5xl mx-auto px-6 pb-24 grid grid-cols-1 lg:grid-cols-2 gap-12">
|
<section className="max-w-5xl mx-auto px-6 pb-24 grid grid-cols-1 lg:grid-cols-2 gap-12">
|
||||||
<form className="rounded-3xl border border-slate-200 bg-white p-10 space-y-6">
|
<form className="rounded-3xl border-2 border-[#0693e3]/30 bg-gradient-to-br from-[#0693e3]/10 via-white to-[#0bbf87]/5 p-10 space-y-6 shadow-lg">
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||||
<label className="flex flex-col gap-2 text-sm text-slate-600">
|
<label className="flex flex-col gap-2 text-sm text-slate-600">
|
||||||
First name
|
First name
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Alex"
|
placeholder="Alex"
|
||||||
className="rounded-xl border border-slate-200 bg-slate-50/60 px-4 py-3 text-sm text-slate-900 placeholder:text-slate-500 focus:outline-none focus:border-brand-400"
|
className="rounded-xl border-2 border-slate-200 bg-white px-4 py-3 text-sm text-slate-900 placeholder:text-slate-500 focus:outline-none focus:border-[#0693e3] focus:ring-2 focus:ring-[#0693e3]/20"
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
<label className="flex flex-col gap-2 text-sm text-slate-600">
|
<label className="flex flex-col gap-2 text-sm text-slate-600">
|
||||||
@@ -29,7 +29,7 @@ const Contact: React.FC = () => {
|
|||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Rivera"
|
placeholder="Rivera"
|
||||||
className="rounded-xl border border-slate-200 bg-slate-50/60 px-4 py-3 text-sm text-slate-900 placeholder:text-slate-500 focus:outline-none focus:border-brand-400"
|
className="rounded-xl border-2 border-slate-200 bg-white px-4 py-3 text-sm text-slate-900 placeholder:text-slate-500 focus:outline-none focus:border-[#0693e3] focus:ring-2 focus:ring-[#0693e3]/20"
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@@ -39,7 +39,7 @@ const Contact: React.FC = () => {
|
|||||||
<input
|
<input
|
||||||
type="email"
|
type="email"
|
||||||
placeholder="you@company.com"
|
placeholder="you@company.com"
|
||||||
className="rounded-xl border border-slate-200 bg-slate-50/60 px-4 py-3 text-sm text-slate-900 placeholder:text-slate-500 focus:outline-none focus:border-brand-400"
|
className="rounded-xl border-2 border-slate-200 bg-white px-4 py-3 text-sm text-slate-900 placeholder:text-slate-500 focus:outline-none focus:border-[#0693e3] focus:ring-2 focus:ring-[#0693e3]/20"
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
@@ -48,7 +48,7 @@ const Contact: React.FC = () => {
|
|||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Company name"
|
placeholder="Company name"
|
||||||
className="rounded-xl border border-slate-200 bg-slate-50/60 px-4 py-3 text-sm text-slate-900 placeholder:text-slate-500 focus:outline-none focus:border-brand-400"
|
className="rounded-xl border-2 border-slate-200 bg-white px-4 py-3 text-sm text-slate-900 placeholder:text-slate-500 focus:outline-none focus:border-[#0693e3] focus:ring-2 focus:ring-[#0693e3]/20"
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
@@ -57,48 +57,52 @@ const Contact: React.FC = () => {
|
|||||||
<textarea
|
<textarea
|
||||||
rows={4}
|
rows={4}
|
||||||
placeholder="Tell us about your current workflow, challenges, and goals."
|
placeholder="Tell us about your current workflow, challenges, and goals."
|
||||||
className="rounded-xl border border-slate-200 bg-slate-50/60 px-4 py-3 text-sm text-slate-900 placeholder:text-slate-500 focus:outline-none focus:border-brand-400 resize-none"
|
className="rounded-xl border-2 border-slate-200 bg-white px-4 py-3 text-sm text-slate-900 placeholder:text-slate-500 focus:outline-none focus:border-[#0693e3] focus:ring-2 focus:ring-[#0693e3]/20 resize-none"
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
className="inline-flex items-center justify-center rounded-full bg-brand-500 hover:bg-brand-400 px-6 py-3 text-sm font-semibold"
|
className="inline-flex items-center justify-center rounded-full bg-gradient-to-r from-[#0693e3] to-[#0472b8] hover:from-[#0472b8] hover:to-[#0693e3] text-white px-6 py-3 text-sm font-semibold shadow-lg shadow-[#0693e3]/30 transition-all w-full"
|
||||||
>
|
>
|
||||||
Book strategy call
|
Book strategy call
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div className="space-y-8">
|
<div className="space-y-8">
|
||||||
<div className="rounded-3xl border border-slate-200 bg-white p-8 space-y-4 text-sm text-slate-600">
|
<div className="rounded-3xl border-2 border-[#0bbf87]/30 bg-gradient-to-br from-[#0bbf87]/10 to-white p-8 space-y-4 text-sm text-slate-600">
|
||||||
<h3 className="text-lg font-semibold text-slate-900">Calendly placeholder</h3>
|
<h3 className="text-lg font-semibold text-slate-900 flex items-center gap-2">
|
||||||
<div className="aspect-[4/3] rounded-2xl border border-slate-200 bg-slate-100 flex items-center justify-center text-xs text-slate-500">
|
<span className="size-2 rounded-full bg-[#0bbf87]"></span>
|
||||||
|
Calendly placeholder
|
||||||
|
</h3>
|
||||||
|
<div className="aspect-[4/3] rounded-2xl border-2 border-slate-200 bg-gradient-to-br from-slate-50 to-white flex items-center justify-center text-xs text-slate-500 shadow-inner">
|
||||||
Embed Calendly iframe here
|
Embed Calendly iframe here
|
||||||
</div>
|
</div>
|
||||||
<p>
|
<p>
|
||||||
Prefer async? Email us at{" "}
|
Prefer async? Email us at{" "}
|
||||||
<a href="mailto:hello@igny8.com" className="text-brand-200 hover:text-brand-100">
|
<a href="mailto:hello@igny8.com" className="text-[#0693e3] hover:text-[#0472b8] font-semibold">
|
||||||
hello@igny8.com
|
hello@igny8.com
|
||||||
</a>{" "}
|
</a>{" "}
|
||||||
or join our community Slack.
|
or join our community Slack.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="rounded-3xl border border-slate-200 bg-white p-8 space-y-4">
|
<div className="rounded-3xl border-2 border-[#ff7a00]/30 bg-gradient-to-br from-[#ff7a00]/10 to-white p-8 space-y-4">
|
||||||
<h3 className="text-lg font-semibold text-slate-900">Support perks</h3>
|
<h3 className="text-lg font-semibold text-slate-900 flex items-center gap-2">
|
||||||
|
<span className="size-2 rounded-full bg-[#ff7a00]"></span>
|
||||||
|
Support perks
|
||||||
|
</h3>
|
||||||
<ul className="space-y-3 text-sm text-slate-600">
|
<ul className="space-y-3 text-sm text-slate-600">
|
||||||
<li className="flex gap-3">
|
{[
|
||||||
<span className="mt-1 size-1.5 rounded-full bg-brand-500" />
|
{ text: "24-hour response time on all Launch+ plans.", color: "bg-[#0693e3]" },
|
||||||
24-hour response time on all Launch+ plans.
|
{ text: "Dedicated success architect for Scale and Enterprise.", color: "bg-[#0bbf87]" },
|
||||||
</li>
|
{ text: "Migration services when replacing legacy content stacks.", color: "bg-[#ff7a00]" },
|
||||||
<li className="flex gap-3">
|
].map((item) => (
|
||||||
<span className="mt-1 size-1.5 rounded-full bg-brand-500" />
|
<li key={item.text} className="flex gap-3">
|
||||||
Dedicated success architect for Scale and Enterprise.
|
<span className={`mt-1 size-1.5 rounded-full ${item.color} shadow-sm`} />
|
||||||
</li>
|
{item.text}
|
||||||
<li className="flex gap-3">
|
|
||||||
<span className="mt-1 size-1.5 rounded-full bg-brand-500" />
|
|
||||||
Migration services when replacing legacy content stacks.
|
|
||||||
</li>
|
</li>
|
||||||
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ const tiers = [
|
|||||||
|
|
||||||
const Partners: React.FC = () => {
|
const Partners: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<div className="bg-white text-slate-900">
|
<div className="bg-gradient-to-b from-white via-slate-50/30 to-white text-slate-900">
|
||||||
<section className="max-w-6xl mx-auto px-6 pt-24 pb-16">
|
<section className="max-w-6xl mx-auto px-6 pt-24 pb-16">
|
||||||
<SectionHeading
|
<SectionHeading
|
||||||
eyebrow="Partners"
|
eyebrow="Partners"
|
||||||
@@ -41,28 +41,39 @@ const Partners: React.FC = () => {
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section className="max-w-6xl mx-auto px-6 pb-24 grid grid-cols-1 md:grid-cols-3 gap-8">
|
<section className="max-w-6xl mx-auto px-6 pb-24 grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||||
{tiers.map((tier) => (
|
{tiers.map((tier, idx) => {
|
||||||
|
const colors = [
|
||||||
|
{ border: "border-[#0693e3]/40", gradient: "from-[#0693e3]/10 to-white", dot: "bg-[#0693e3]" },
|
||||||
|
{ border: "border-[#0bbf87]/40", gradient: "from-[#0bbf87]/10 to-white", dot: "bg-[#0bbf87]" },
|
||||||
|
{ border: "border-[#ff7a00]/40", gradient: "from-[#ff7a00]/10 to-white", dot: "bg-[#ff7a00]" },
|
||||||
|
];
|
||||||
|
const colorScheme = colors[idx % colors.length];
|
||||||
|
return (
|
||||||
<div
|
<div
|
||||||
key={tier.title}
|
key={tier.title}
|
||||||
className="rounded-3xl border border-slate-200 bg-white p-8 flex flex-col gap-5"
|
className={`rounded-3xl border-2 ${colorScheme.border} bg-gradient-to-br ${colorScheme.gradient} p-8 flex flex-col gap-5 hover:shadow-xl transition-all hover:-translate-y-1`}
|
||||||
>
|
>
|
||||||
<span className="text-xs uppercase tracking-[0.3em] text-slate-500">
|
<span className="text-xs uppercase tracking-[0.3em] text-slate-500 font-semibold">
|
||||||
Program
|
Program
|
||||||
</span>
|
</span>
|
||||||
<h3 className="text-xl font-semibold text-slate-900">{tier.title}</h3>
|
<h3 className="text-xl font-semibold text-slate-900">{tier.title}</h3>
|
||||||
<ul className="space-y-3 text-sm text-slate-600">
|
<ul className="space-y-3 text-sm text-slate-600">
|
||||||
{tier.benefits.map((benefit) => (
|
{tier.benefits.map((benefit, benefitIdx) => {
|
||||||
|
const benefitColors = ["bg-[#0693e3]", "bg-[#0bbf87]", "bg-[#ff7a00]", "bg-[#5d4ae3]"];
|
||||||
|
return (
|
||||||
<li key={benefit} className="flex gap-3">
|
<li key={benefit} className="flex gap-3">
|
||||||
<span className="mt-1 size-1.5 rounded-full bg-brand-500" />
|
<span className={`mt-1 size-1.5 rounded-full ${benefitColors[benefitIdx % benefitColors.length]} shadow-sm`} />
|
||||||
{benefit}
|
{benefit}
|
||||||
</li>
|
</li>
|
||||||
))}
|
);
|
||||||
|
})}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
))}
|
);
|
||||||
|
})}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section className="bg-slate-50/70 border-y border-slate-200">
|
<section className="bg-gradient-to-br from-[#0693e3]/10 via-slate-50/70 to-[#0bbf87]/10 border-y border-[#0693e3]/20">
|
||||||
<div className="max-w-6xl mx-auto px-6 py-24 grid grid-cols-1 lg:grid-cols-2 gap-12">
|
<div className="max-w-6xl mx-auto px-6 py-24 grid grid-cols-1 lg:grid-cols-2 gap-12">
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<SectionHeading
|
<SectionHeading
|
||||||
@@ -71,25 +82,26 @@ const Partners: React.FC = () => {
|
|||||||
description="Use Igny8 APIs and webhooks to power your own products, analytics, or client portals. Automate keyword ingestion, content creation, asset delivery, and reporting."
|
description="Use Igny8 APIs and webhooks to power your own products, analytics, or client portals. Automate keyword ingestion, content creation, asset delivery, and reporting."
|
||||||
align="left"
|
align="left"
|
||||||
/>
|
/>
|
||||||
<div className="rounded-3xl border border-slate-200 bg-white p-6 text-sm text-slate-600">
|
<div className="rounded-3xl border-2 border-[#0693e3]/30 bg-gradient-to-br from-[#0693e3]/10 to-white p-6 text-sm text-slate-600 shadow-lg">
|
||||||
API docs placeholder (download at `/marketing/images/api-docs.png`, 1100×720).
|
API docs placeholder (download at `/marketing/images/api-docs.png`, 1100×720).
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="rounded-3xl border border-slate-200 bg-white p-10 space-y-6">
|
<div className="rounded-3xl border-2 border-[#0bbf87]/30 bg-gradient-to-br from-[#0bbf87]/10 to-white p-10 space-y-6">
|
||||||
<h4 className="text-lg font-semibold text-slate-900">Partner resources</h4>
|
<h4 className="text-lg font-semibold text-slate-900 flex items-center gap-2">
|
||||||
|
<span className="size-2 rounded-full bg-[#0bbf87]"></span>
|
||||||
|
Partner resources
|
||||||
|
</h4>
|
||||||
<ul className="space-y-4 text-sm text-slate-600">
|
<ul className="space-y-4 text-sm text-slate-600">
|
||||||
<li className="flex gap-3">
|
{[
|
||||||
<span className="mt-1 size-1.5 rounded-full bg-brand-500" />
|
{ text: "Sales playbooks, ROI calculators, and demo environments.", color: "bg-[#0693e3]" },
|
||||||
Sales playbooks, ROI calculators, and demo environments.
|
{ text: "Shared Slack channels with Igny8 product and marketing teams.", color: "bg-[#0bbf87]" },
|
||||||
</li>
|
{ text: "Quarterly partner labs to showcase launches and integrations.", color: "bg-[#ff7a00]" },
|
||||||
<li className="flex gap-3">
|
].map((item) => (
|
||||||
<span className="mt-1 size-1.5 rounded-full bg-brand-500" />
|
<li key={item.text} className="flex gap-3">
|
||||||
Shared Slack channels with Igny8 product and marketing teams.
|
<span className={`mt-1 size-1.5 rounded-full ${item.color} shadow-sm`} />
|
||||||
</li>
|
{item.text}
|
||||||
<li className="flex gap-3">
|
|
||||||
<span className="mt-1 size-1.5 rounded-full bg-brand-500" />
|
|
||||||
Quarterly partner labs to showcase launches and integrations.
|
|
||||||
</li>
|
</li>
|
||||||
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -149,28 +149,28 @@ const Pricing: React.FC = () => {
|
|||||||
<h3 className="text-xl font-semibold text-slate-900">
|
<h3 className="text-xl font-semibold text-slate-900">
|
||||||
Compare plan capabilities
|
Compare plan capabilities
|
||||||
</h3>
|
</h3>
|
||||||
<div className="overflow-hidden rounded-3xl border border-slate-200 bg-white">
|
<div className="overflow-hidden rounded-3xl border-2 border-[#0693e3]/20 bg-gradient-to-br from-white via-[#0693e3]/5 to-[#0bbf87]/5 shadow-lg">
|
||||||
<table className="min-w-full text-sm text-slate-600">
|
<table className="min-w-full text-sm text-slate-600">
|
||||||
<thead className="bg-white text-slate-600 uppercase text-xs tracking-[0.3em]">
|
<thead className="bg-gradient-to-r from-[#0693e3]/10 via-[#5d4ae3]/10 to-[#0bbf87]/10 text-slate-700 uppercase text-xs tracking-[0.3em]">
|
||||||
<tr>
|
<tr>
|
||||||
<th className="px-6 py-4 text-left">Capability</th>
|
<th className="px-6 py-4 text-left font-semibold">Capability</th>
|
||||||
<th className="px-6 py-4 text-center">Launch</th>
|
<th className="px-6 py-4 text-center font-semibold">Launch</th>
|
||||||
<th className="px-6 py-4 text-center">Scale</th>
|
<th className="px-6 py-4 text-center font-semibold text-[#0693e3]">Scale</th>
|
||||||
<th className="px-6 py-4 text-center">Enterprise</th>
|
<th className="px-6 py-4 text-center font-semibold">Enterprise</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{featureMatrix.map((row, index) => (
|
{featureMatrix.map((row, index) => (
|
||||||
<tr key={row.feature} className={index % 2 === 0 ? "bg-white/3" : ""}>
|
<tr key={row.feature} className={index % 2 === 0 ? "bg-white/50" : "bg-gradient-to-r from-white/30 to-transparent"}>
|
||||||
<td className="px-6 py-5 text-slate-900 font-medium">{row.feature}</td>
|
<td className="px-6 py-5 text-slate-900 font-medium">{row.feature}</td>
|
||||||
<td className="px-6 py-5 text-center">
|
<td className="px-6 py-5 text-center">
|
||||||
{row.launch === true ? "Included" : row.launch}
|
{row.launch === true ? <span className="inline-flex items-center gap-1"><span className="size-1.5 rounded-full bg-[#0bbf87]"></span>Included</span> : row.launch}
|
||||||
|
</td>
|
||||||
|
<td className="px-6 py-5 text-center font-medium text-[#0693e3]">
|
||||||
|
{row.scale === true ? <span className="inline-flex items-center gap-1"><span className="size-1.5 rounded-full bg-[#0693e3]"></span>Included</span> : row.scale}
|
||||||
</td>
|
</td>
|
||||||
<td className="px-6 py-5 text-center">
|
<td className="px-6 py-5 text-center">
|
||||||
{row.scale === true ? "Included" : row.scale}
|
{row.enterprise === true ? <span className="inline-flex items-center gap-1"><span className="size-1.5 rounded-full bg-[#5d4ae3]"></span>Included</span> : row.enterprise}
|
||||||
</td>
|
|
||||||
<td className="px-6 py-5 text-center">
|
|
||||||
{row.enterprise === true ? "Included" : row.enterprise}
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
@@ -179,10 +179,11 @@ const Pricing: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section className="bg-gradient-to-br from-[#0693e3]/5 via-slate-50/70 to-[#0bbf87]/5 border-y border-[#0693e3]/10">
|
<section className="bg-gradient-to-br from-[#0693e3]/10 via-slate-50/70 to-[#0bbf87]/10 border-y border-[#0693e3]/20">
|
||||||
<div className="max-w-6xl mx-auto px-6 py-24 grid grid-cols-1 md:grid-cols-2 gap-12 text-sm text-slate-600">
|
<div className="max-w-6xl mx-auto px-6 py-24 grid grid-cols-1 md:grid-cols-2 gap-12">
|
||||||
<div className="space-y-4">
|
<div className="rounded-3xl border-2 border-[#0693e3]/30 bg-gradient-to-br from-[#0693e3]/5 to-white p-8 space-y-4 text-sm text-slate-600">
|
||||||
<h4 className="text-lg font-semibold text-slate-900">
|
<h4 className="text-lg font-semibold text-slate-900 flex items-center gap-2">
|
||||||
|
<span className="size-2 rounded-full bg-[#0693e3]"></span>
|
||||||
Usage-based credits explained
|
Usage-based credits explained
|
||||||
</h4>
|
</h4>
|
||||||
<p>
|
<p>
|
||||||
@@ -192,8 +193,9 @@ const Pricing: React.FC = () => {
|
|||||||
Need more? Add packs instantly or set automation rules to pause when thresholds are hit.
|
Need more? Add packs instantly or set automation rules to pause when thresholds are hit.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-4">
|
<div className="rounded-3xl border-2 border-[#0bbf87]/30 bg-gradient-to-br from-[#0bbf87]/5 to-white p-8 space-y-4 text-sm text-slate-600">
|
||||||
<h4 className="text-lg font-semibold text-slate-900">
|
<h4 className="text-lg font-semibold text-slate-900 flex items-center gap-2">
|
||||||
|
<span className="size-2 rounded-full bg-[#0bbf87]"></span>
|
||||||
Security & compliance
|
Security & compliance
|
||||||
</h4>
|
</h4>
|
||||||
<p>
|
<p>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import CTASection from "../components/CTASection";
|
|||||||
|
|
||||||
const Product: React.FC = () => {
|
const Product: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<div className="bg-white text-slate-900">
|
<div className="bg-gradient-to-b from-white via-slate-50/30 to-white text-slate-900">
|
||||||
<section className="max-w-6xl mx-auto px-6 pt-24 pb-16">
|
<section className="max-w-6xl mx-auto px-6 pt-24 pb-16">
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-16 items-center">
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-16 items-center">
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
@@ -14,21 +14,21 @@ const Product: React.FC = () => {
|
|||||||
description="Planner, Writer, Thinker, and Automation act as one cohesive system. Each module is powerful on its own—together they deliver a compounding growth engine."
|
description="Planner, Writer, Thinker, and Automation act as one cohesive system. Each module is powerful on its own—together they deliver a compounding growth engine."
|
||||||
align="left"
|
align="left"
|
||||||
/>
|
/>
|
||||||
<div className="rounded-3xl border border-slate-200 bg-white p-6 space-y-4 text-sm text-slate-600">
|
<div className="rounded-3xl border-2 border-[#0693e3]/30 bg-gradient-to-br from-[#0693e3]/10 via-white to-[#0bbf87]/5 p-6 space-y-4 text-sm text-slate-600 shadow-lg">
|
||||||
<div className="flex items-center justify-between text-slate-900">
|
<div className="flex items-center justify-between text-slate-900">
|
||||||
<span className="font-semibold">Modules included</span>
|
<span className="font-semibold">Modules included</span>
|
||||||
<span>4 products · 12 automation recipes</span>
|
<span className="text-xs uppercase tracking-wider text-[#0693e3] font-semibold">4 products · 12 automation recipes</span>
|
||||||
</div>
|
</div>
|
||||||
<ul className="space-y-3">
|
<ul className="space-y-3">
|
||||||
{[
|
{[
|
||||||
"Planner → Find, cluster, and prioritize keywords with AI scoring and SERP insights.",
|
{ text: "Planner → Find, cluster, and prioritize keywords with AI scoring and SERP insights.", color: "bg-[#0693e3]" },
|
||||||
"Writer → Generate on-brand long-form content from briefs with tone, audience, and compliance controls.",
|
{ text: "Writer → Generate on-brand long-form content from briefs with tone, audience, and compliance controls.", color: "bg-[#0bbf87]" },
|
||||||
"Thinker → Manage prompts, author profiles, and brand playbooks that feed every generation.",
|
{ text: "Thinker → Manage prompts, author profiles, and brand playbooks that feed every generation.", color: "bg-[#ff7a00]" },
|
||||||
"Automation → Run scheduled workflows that move keywords to ideas, tasks, content, and images automatically.",
|
{ text: "Automation → Run scheduled workflows that move keywords to ideas, tasks, content, and images automatically.", color: "bg-[#5d4ae3]" },
|
||||||
].map((point) => (
|
].map((point) => (
|
||||||
<li key={point} className="flex gap-3">
|
<li key={point.text} className="flex gap-3">
|
||||||
<span className="mt-1 size-2 rounded-full bg-brand-500" />
|
<span className={`mt-1 size-2 rounded-full ${point.color} shadow-sm`} />
|
||||||
{point}
|
{point.text}
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
@@ -90,25 +90,36 @@ const Product: React.FC = () => {
|
|||||||
],
|
],
|
||||||
image: "automation-timeline.png",
|
image: "automation-timeline.png",
|
||||||
},
|
},
|
||||||
].map((module) => (
|
].map((module, idx) => {
|
||||||
|
const colors = [
|
||||||
|
{ border: "border-[#0693e3]/40", gradient: "from-[#0693e3]/10 to-white", dot: "bg-[#0693e3]" },
|
||||||
|
{ border: "border-[#0bbf87]/40", gradient: "from-[#0bbf87]/10 to-white", dot: "bg-[#0bbf87]" },
|
||||||
|
{ border: "border-[#ff7a00]/40", gradient: "from-[#ff7a00]/10 to-white", dot: "bg-[#ff7a00]" },
|
||||||
|
{ border: "border-[#5d4ae3]/40", gradient: "from-[#5d4ae3]/10 to-white", dot: "bg-[#5d4ae3]" },
|
||||||
|
];
|
||||||
|
const colorScheme = colors[idx % colors.length];
|
||||||
|
return (
|
||||||
<div
|
<div
|
||||||
key={module.title}
|
key={module.title}
|
||||||
className="rounded-3xl border border-slate-200 bg-white p-10 flex flex-col gap-6"
|
className={`rounded-3xl border-2 ${colorScheme.border} bg-gradient-to-br ${colorScheme.gradient} p-10 flex flex-col gap-6 hover:shadow-xl transition-all hover:-translate-y-1`}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-3 text-sm uppercase tracking-[0.3em] text-slate-900/40">
|
<div className="flex items-center gap-3 text-sm uppercase tracking-[0.3em] text-slate-700 font-semibold">
|
||||||
<span className="size-2 rounded-full bg-brand-500" />
|
<span className={`size-2 rounded-full ${colorScheme.dot} shadow-sm`} />
|
||||||
{module.title}
|
{module.title}
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-2xl font-semibold text-slate-900">{module.title} platform</h3>
|
<h3 className="text-2xl font-semibold text-slate-900">{module.title} platform</h3>
|
||||||
<ul className="space-y-3 text-sm text-slate-900/65">
|
<ul className="space-y-3 text-sm text-slate-900/65">
|
||||||
{module.copy.map((line) => (
|
{module.copy.map((line, lineIdx) => {
|
||||||
|
const dotColors = ["bg-[#0693e3]", "bg-[#0bbf87]", "bg-[#ff7a00]", "bg-[#5d4ae3]"];
|
||||||
|
return (
|
||||||
<li key={line} className="flex gap-3">
|
<li key={line} className="flex gap-3">
|
||||||
<span className="mt-1 size-1.5 rounded-full bg-brand-500" />
|
<span className={`mt-1 size-1.5 rounded-full ${dotColors[lineIdx % dotColors.length]} shadow-sm`} />
|
||||||
{line}
|
{line}
|
||||||
</li>
|
</li>
|
||||||
))}
|
);
|
||||||
|
})}
|
||||||
</ul>
|
</ul>
|
||||||
<div className="rounded-2xl border border-slate-200 bg-slate-100 overflow-hidden">
|
<div className="rounded-2xl border-2 border-slate-200 bg-gradient-to-br from-slate-50 to-white overflow-hidden shadow-inner">
|
||||||
<img
|
<img
|
||||||
src={`/marketing/images/${module.image}`}
|
src={`/marketing/images/${module.image}`}
|
||||||
alt={`${module.title} module`}
|
alt={`${module.title} module`}
|
||||||
@@ -116,12 +127,13 @@ const Product: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section className="max-w-6xl mx-auto px-6 py-24">
|
<section className="max-w-6xl mx-auto px-6 py-24">
|
||||||
<div className="rounded-3xl border border-slate-200 bg-gradient-to-br from-brand-50 via-white to-slate-50 p-10 md:p-16 flex flex-col lg:flex-row gap-16">
|
<div className="rounded-3xl border-2 border-[#0693e3]/30 bg-gradient-to-br from-[#0693e3]/10 via-white to-[#0bbf87]/5 p-10 md:p-16 flex flex-col lg:flex-row gap-16 shadow-xl">
|
||||||
<div className="flex-1 space-y-6">
|
<div className="flex-1 space-y-6">
|
||||||
<SectionHeading
|
<SectionHeading
|
||||||
eyebrow="Automation timeline"
|
eyebrow="Automation timeline"
|
||||||
@@ -131,20 +143,20 @@ const Product: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 text-sm text-slate-600">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 text-sm text-slate-600">
|
||||||
{[
|
{[
|
||||||
"Real-time status for every automation recipe with success rates and manual interventions logged.",
|
{ text: "Real-time status for every automation recipe with success rates and manual interventions logged.", color: "bg-[#0693e3]" },
|
||||||
"Smart recommendations to rebalance workloads, add credits, or adjust prompts when performance shifts.",
|
{ text: "Smart recommendations to rebalance workloads, add credits, or adjust prompts when performance shifts.", color: "bg-[#0bbf87]" },
|
||||||
"Exportable reports to share results with leadership or clients in one click.",
|
{ text: "Exportable reports to share results with leadership or clients in one click.", color: "bg-[#ff7a00]" },
|
||||||
"Granular permissions so teams can automate while leadership maintains oversight.",
|
{ text: "Granular permissions so teams can automate while leadership maintains oversight.", color: "bg-[#5d4ae3]" },
|
||||||
].map((item) => (
|
].map((item) => (
|
||||||
<div key={item} className="flex gap-3">
|
<div key={item.text} className="flex gap-3">
|
||||||
<span className="mt-1 size-2 rounded-full bg-brand-500" />
|
<span className={`mt-1 size-2 rounded-full ${item.color} shadow-sm`} />
|
||||||
{item}
|
{item.text}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<div className="rounded-3xl border border-slate-200 bg-white overflow-hidden">
|
<div className="rounded-3xl border-2 border-slate-200 bg-white overflow-hidden shadow-lg">
|
||||||
<img
|
<img
|
||||||
src="/marketing/images/automation-timeline.png"
|
src="/marketing/images/automation-timeline.png"
|
||||||
alt="Automation timeline"
|
alt="Automation timeline"
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ const webinars = [
|
|||||||
|
|
||||||
const Resources: React.FC = () => {
|
const Resources: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<div className="bg-white text-slate-900">
|
<div className="bg-gradient-to-b from-white via-slate-50/30 to-white text-slate-900">
|
||||||
<section className="max-w-6xl mx-auto px-6 pt-24 pb-16 space-y-6">
|
<section className="max-w-6xl mx-auto px-6 pt-24 pb-16 space-y-6">
|
||||||
<SectionHeading
|
<SectionHeading
|
||||||
eyebrow="Resources"
|
eyebrow="Resources"
|
||||||
@@ -48,40 +48,55 @@ const Resources: React.FC = () => {
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section className="max-w-6xl mx-auto px-6 pb-24 grid grid-cols-1 md:grid-cols-3 gap-6">
|
<section className="max-w-6xl mx-auto px-6 pb-24 grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||||
{articles.map((article) => (
|
{articles.map((article, idx) => {
|
||||||
|
const colors = [
|
||||||
|
{ border: "border-[#0693e3]/40", gradient: "from-[#0693e3]/10 to-white" },
|
||||||
|
{ border: "border-[#0bbf87]/40", gradient: "from-[#0bbf87]/10 to-white" },
|
||||||
|
{ border: "border-[#ff7a00]/40", gradient: "from-[#ff7a00]/10 to-white" },
|
||||||
|
];
|
||||||
|
const colorScheme = colors[idx % colors.length];
|
||||||
|
return (
|
||||||
<article
|
<article
|
||||||
key={article.title}
|
key={article.title}
|
||||||
className="rounded-3xl border border-slate-200 bg-white p-8 flex flex-col gap-6"
|
className={`rounded-3xl border-2 ${colorScheme.border} bg-gradient-to-br ${colorScheme.gradient} p-8 flex flex-col gap-6 hover:shadow-xl transition-all hover:-translate-y-1`}
|
||||||
>
|
>
|
||||||
<span className="text-xs uppercase tracking-[0.3em] text-slate-500">
|
<span className="text-xs uppercase tracking-[0.3em] text-slate-500 font-semibold">
|
||||||
{article.date}
|
{article.date}
|
||||||
</span>
|
</span>
|
||||||
<h3 className="text-xl font-semibold text-slate-900">{article.title}</h3>
|
<h3 className="text-xl font-semibold text-slate-900">{article.title}</h3>
|
||||||
<p className="text-sm text-slate-600 leading-relaxed">{article.description}</p>
|
<p className="text-sm text-slate-600 leading-relaxed">{article.description}</p>
|
||||||
<div className="rounded-2xl border border-slate-200 bg-slate-100 h-40 flex items-center justify-center text-xs text-slate-500">
|
<div className="rounded-2xl border-2 border-slate-200 bg-gradient-to-br from-slate-50 to-white h-40 flex items-center justify-center text-xs text-slate-500 shadow-inner">
|
||||||
Article cover placeholder (800×600) → /marketing/images/resource-hero.png
|
Article cover placeholder (800×600) → /marketing/images/resource-hero.png
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
))}
|
);
|
||||||
|
})}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section className="bg-slate-50/70 border-y border-slate-200">
|
<section className="bg-gradient-to-br from-[#0693e3]/10 via-slate-50/70 to-[#0bbf87]/10 border-y border-[#0693e3]/20">
|
||||||
<div className="max-w-6xl mx-auto px-6 py-24 grid grid-cols-1 md:grid-cols-2 gap-8">
|
<div className="max-w-6xl mx-auto px-6 py-24 grid grid-cols-1 md:grid-cols-2 gap-8">
|
||||||
{webinars.map((webinar) => (
|
{webinars.map((webinar, idx) => {
|
||||||
|
const colors = [
|
||||||
|
{ border: "border-[#0693e3]/40", gradient: "from-[#0693e3]/10 to-white" },
|
||||||
|
{ border: "border-[#0bbf87]/40", gradient: "from-[#0bbf87]/10 to-white" },
|
||||||
|
];
|
||||||
|
const colorScheme = colors[idx % colors.length];
|
||||||
|
return (
|
||||||
<div
|
<div
|
||||||
key={webinar.title}
|
key={webinar.title}
|
||||||
className="rounded-3xl border border-slate-200 bg-white p-8 flex flex-col gap-4"
|
className={`rounded-3xl border-2 ${colorScheme.border} bg-gradient-to-br ${colorScheme.gradient} p-8 flex flex-col gap-4 hover:shadow-xl transition-all hover:-translate-y-1`}
|
||||||
>
|
>
|
||||||
<span className="text-xs uppercase tracking-[0.3em] text-slate-500">
|
<span className="text-xs uppercase tracking-[0.3em] text-slate-500 font-semibold">
|
||||||
{webinar.date}
|
{webinar.date}
|
||||||
</span>
|
</span>
|
||||||
<h3 className="text-lg font-semibold text-slate-900">{webinar.title}</h3>
|
<h3 className="text-lg font-semibold text-slate-900">{webinar.title}</h3>
|
||||||
<p className="text-sm text-slate-600">{webinar.description}</p>
|
<p className="text-sm text-slate-600">{webinar.description}</p>
|
||||||
<button className="inline-flex items-center justify-center rounded-full bg-brand-500 hover:bg-brand-400 px-5 py-2 text-sm font-semibold">
|
<button className="inline-flex items-center justify-center rounded-full bg-gradient-to-r from-[#0693e3] to-[#0472b8] hover:from-[#0472b8] hover:to-[#0693e3] text-white px-5 py-2 text-sm font-semibold shadow-lg shadow-[#0693e3]/30 transition-all">
|
||||||
Save my seat
|
Save my seat
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
))}
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@@ -108,8 +123,11 @@ const Resources: React.FC = () => {
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div className="rounded-3xl border border-slate-200 bg-white p-10 space-y-6">
|
<div className="rounded-3xl border-2 border-[#0693e3]/30 bg-gradient-to-br from-[#0693e3]/10 via-white to-[#0bbf87]/5 p-10 space-y-6 shadow-lg">
|
||||||
<h3 className="text-2xl font-semibold text-slate-900">Join the Igny8 newsletter</h3>
|
<h3 className="text-2xl font-semibold text-slate-900 flex items-center gap-2">
|
||||||
|
<span className="size-2 rounded-full bg-[#0693e3]"></span>
|
||||||
|
Join the Igny8 newsletter
|
||||||
|
</h3>
|
||||||
<p className="text-sm text-slate-600">
|
<p className="text-sm text-slate-600">
|
||||||
Monthly insights on AI, SEO, and automation. No fluff—just tactical guidance and event invites.
|
Monthly insights on AI, SEO, and automation. No fluff—just tactical guidance and event invites.
|
||||||
</p>
|
</p>
|
||||||
@@ -117,11 +135,11 @@ const Resources: React.FC = () => {
|
|||||||
<input
|
<input
|
||||||
type="email"
|
type="email"
|
||||||
placeholder="you@company.com"
|
placeholder="you@company.com"
|
||||||
className="flex-1 rounded-full border border-slate-200 bg-slate-50/60 px-4 py-3 text-sm text-slate-900 placeholder:text-slate-500 focus:outline-none focus:border-brand-400"
|
className="flex-1 rounded-full border-2 border-slate-200 bg-white px-4 py-3 text-sm text-slate-900 placeholder:text-slate-500 focus:outline-none focus:border-[#0693e3] focus:ring-2 focus:ring-[#0693e3]/20"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
className="inline-flex items-center justify-center rounded-full bg-brand-500 hover:bg-brand-400 px-6 py-3 text-sm font-semibold"
|
className="inline-flex items-center justify-center rounded-full bg-gradient-to-r from-[#0693e3] to-[#0472b8] hover:from-[#0472b8] hover:to-[#0693e3] text-white px-6 py-3 text-sm font-semibold shadow-lg shadow-[#0693e3]/30 transition-all"
|
||||||
>
|
>
|
||||||
Subscribe
|
Subscribe
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ const personas = [
|
|||||||
|
|
||||||
const Solutions: React.FC = () => {
|
const Solutions: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<div className="bg-white text-slate-900">
|
<div className="bg-gradient-to-b from-white via-slate-50/30 to-white text-slate-900">
|
||||||
<section className="max-w-6xl mx-auto px-6 pt-24 pb-16">
|
<section className="max-w-6xl mx-auto px-6 pt-24 pb-16">
|
||||||
<SectionHeading
|
<SectionHeading
|
||||||
eyebrow="Solutions"
|
eyebrow="Solutions"
|
||||||
@@ -59,17 +59,24 @@ const Solutions: React.FC = () => {
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section className="max-w-6xl mx-auto px-6 pb-24 space-y-12">
|
<section className="max-w-6xl mx-auto px-6 pb-24 space-y-12">
|
||||||
{personas.map((persona) => (
|
{personas.map((persona, idx) => {
|
||||||
|
const gradients = [
|
||||||
|
{ border: "border-[#0693e3]/40", bg: "from-[#0693e3]/10 via-white to-[#0bbf87]/5" },
|
||||||
|
{ border: "border-[#0bbf87]/40", bg: "from-[#0bbf87]/10 via-white to-[#ff7a00]/5" },
|
||||||
|
{ border: "border-[#5d4ae3]/40", bg: "from-[#5d4ae3]/10 via-white to-[#0693e3]/5" },
|
||||||
|
];
|
||||||
|
const gradient = gradients[idx % gradients.length];
|
||||||
|
return (
|
||||||
<div
|
<div
|
||||||
key={persona.name}
|
key={persona.name}
|
||||||
className="rounded-3xl border border-slate-200 bg-white p-10 md:p-16 grid grid-cols-1 lg:grid-cols-3 gap-12"
|
className={`rounded-3xl border-2 ${gradient.border} bg-gradient-to-br ${gradient.bg} p-10 md:p-16 grid grid-cols-1 lg:grid-cols-3 gap-12 hover:shadow-xl transition-all`}
|
||||||
>
|
>
|
||||||
<div className="lg:col-span-1 space-y-4">
|
<div className="lg:col-span-1 space-y-4">
|
||||||
<span className="text-xs uppercase tracking-[0.3em] text-slate-500">
|
<span className="text-xs uppercase tracking-[0.3em] text-slate-500">
|
||||||
Persona
|
Persona
|
||||||
</span>
|
</span>
|
||||||
<h3 className="text-2xl font-semibold">{persona.name}</h3>
|
<h3 className="text-2xl font-semibold">{persona.name}</h3>
|
||||||
<div className="rounded-2xl border border-slate-200 bg-slate-100 overflow-hidden">
|
<div className="rounded-2xl border-2 border-slate-200 bg-gradient-to-br from-slate-50 to-white overflow-hidden shadow-inner">
|
||||||
<img
|
<img
|
||||||
src={`/marketing/images/${persona.image}`}
|
src={`/marketing/images/${persona.image}`}
|
||||||
alt={`${persona.name} workflow`}
|
alt={`${persona.name} workflow`}
|
||||||
@@ -78,56 +85,71 @@ const Solutions: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<h4 className="text-sm uppercase tracking-[0.3em] text-slate-500">
|
<h4 className="text-sm uppercase tracking-[0.3em] text-rose-600 font-semibold flex items-center gap-2">
|
||||||
|
<span className="size-1.5 rounded-full bg-rose-400"></span>
|
||||||
Pain points
|
Pain points
|
||||||
</h4>
|
</h4>
|
||||||
<ul className="space-y-4 text-sm text-slate-600">
|
<ul className="space-y-4 text-sm text-slate-600">
|
||||||
{persona.pains.map((pain) => (
|
{persona.pains.map((pain) => (
|
||||||
<li key={pain} className="flex gap-3">
|
<li key={pain} className="flex gap-3">
|
||||||
<span className="mt-1 size-1.5 rounded-full bg-rose-300" />
|
<span className="mt-1 size-1.5 rounded-full bg-rose-400 shadow-sm" />
|
||||||
{pain}
|
{pain}
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<h4 className="text-sm uppercase tracking-[0.3em] text-slate-500">
|
<h4 className="text-sm uppercase tracking-[0.3em] text-[#0693e3] font-semibold flex items-center gap-2">
|
||||||
|
<span className="size-1.5 rounded-full bg-[#0693e3]"></span>
|
||||||
Outcomes with Igny8
|
Outcomes with Igny8
|
||||||
</h4>
|
</h4>
|
||||||
<ul className="space-y-4 text-sm text-slate-600">
|
<ul className="space-y-4 text-sm text-slate-600">
|
||||||
{persona.outcomes.map((outcome) => (
|
{persona.outcomes.map((outcome, outcomeIdx) => {
|
||||||
|
const outcomeColors = ["bg-[#0693e3]", "bg-[#0bbf87]", "bg-[#ff7a00]"];
|
||||||
|
return (
|
||||||
<li key={outcome} className="flex gap-3">
|
<li key={outcome} className="flex gap-3">
|
||||||
<span className="mt-1 size-1.5 rounded-full bg-brand-500" />
|
<span className={`mt-1 size-1.5 rounded-full ${outcomeColors[outcomeIdx % outcomeColors.length]} shadow-sm`} />
|
||||||
{outcome}
|
{outcome}
|
||||||
</li>
|
</li>
|
||||||
))}
|
);
|
||||||
|
})}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
);
|
||||||
|
})}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section className="bg-slate-50/70 border-y border-slate-200">
|
<section className="bg-gradient-to-br from-[#0693e3]/10 via-slate-50/70 to-[#0bbf87]/10 border-y border-[#0693e3]/20">
|
||||||
<div className="max-w-6xl mx-auto px-6 py-24 grid grid-cols-1 md:grid-cols-3 gap-8">
|
<div className="max-w-6xl mx-auto px-6 py-24 grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||||
{[
|
{[
|
||||||
{
|
{
|
||||||
metric: "3.2×",
|
metric: "3.2×",
|
||||||
label: "Average lift in organic traffic within 90 days.",
|
label: "Average lift in organic traffic within 90 days.",
|
||||||
|
color: "border-[#0693e3]/40",
|
||||||
|
gradient: "from-[#0693e3]/10 to-white",
|
||||||
|
textColor: "text-[#0693e3]",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
metric: "48%",
|
metric: "48%",
|
||||||
label: "Reduction in time-to-publish from keyword discovery.",
|
label: "Reduction in time-to-publish from keyword discovery.",
|
||||||
|
color: "border-[#0bbf87]/40",
|
||||||
|
gradient: "from-[#0bbf87]/10 to-white",
|
||||||
|
textColor: "text-[#0bbf87]",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
metric: "4 tools",
|
metric: "4 tools",
|
||||||
label: "Average number of point solutions replaced by Igny8.",
|
label: "Average number of point solutions replaced by Igny8.",
|
||||||
|
color: "border-[#ff7a00]/40",
|
||||||
|
gradient: "from-[#ff7a00]/10 to-white",
|
||||||
|
textColor: "text-[#ff7a00]",
|
||||||
},
|
},
|
||||||
].map((item) => (
|
].map((item) => (
|
||||||
<div
|
<div
|
||||||
key={item.metric}
|
key={item.metric}
|
||||||
className="rounded-3xl border border-slate-200 bg-white p-8 text-center space-y-4"
|
className={`rounded-3xl border-2 ${item.color} bg-gradient-to-br ${item.gradient} p-8 text-center space-y-4 hover:shadow-xl transition-all hover:-translate-y-1`}
|
||||||
>
|
>
|
||||||
<div className="text-4xl font-semibold">{item.metric}</div>
|
<div className={`text-4xl font-semibold ${item.textColor}`}>{item.metric}</div>
|
||||||
<p className="text-sm text-slate-600">{item.label}</p>
|
<p className="text-sm text-slate-600">{item.label}</p>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -31,34 +31,43 @@ const tourSteps = [
|
|||||||
|
|
||||||
const Tour: React.FC = () => {
|
const Tour: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<div className="bg-white text-slate-900">
|
<div className="bg-gradient-to-b from-white via-slate-50/30 to-white text-slate-900">
|
||||||
<section className="max-w-6xl mx-auto px-6 pt-24 pb-16 space-y-6">
|
<section className="max-w-6xl mx-auto px-6 pt-24 pb-16 space-y-6">
|
||||||
<SectionHeading
|
<SectionHeading
|
||||||
eyebrow="Guided Tour"
|
eyebrow="Guided Tour"
|
||||||
title="Experience the entire Igny8 journey in minutes."
|
title="Experience the entire Igny8 journey in minutes."
|
||||||
description="Walk through the workflow that moves market intelligence into production-ready content. Each step builds toward an automated growth flywheel."
|
description="Walk through the workflow that moves market intelligence into production-ready content. Each step builds toward an automated growth flywheel."
|
||||||
/>
|
/>
|
||||||
<div className="rounded-3xl border border-slate-200 bg-white p-8">
|
<div className="rounded-3xl border-2 border-[#0693e3]/30 bg-gradient-to-br from-[#0693e3]/10 via-white to-[#0bbf87]/5 p-8 shadow-lg">
|
||||||
<div className="aspect-video rounded-2xl border border-slate-200 bg-slate-100 flex items-center justify-center text-slate-500 text-sm">
|
<div className="aspect-video rounded-2xl border-2 border-slate-200 bg-gradient-to-br from-slate-50 to-white flex items-center justify-center text-slate-500 text-sm shadow-inner">
|
||||||
Video walkthrough placeholder (embed demo or Loom)
|
Video walkthrough placeholder (embed demo or Loom)
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section className="max-w-6xl mx-auto px-6 pb-24 space-y-12">
|
<section className="max-w-6xl mx-auto px-6 pb-24 space-y-12">
|
||||||
{tourSteps.map((step, index) => (
|
{tourSteps.map((step, index) => {
|
||||||
|
const colors = [
|
||||||
|
{ border: "border-[#0693e3]/40", gradient: "from-[#0693e3]/10 to-white", dot: "bg-[#0693e3]" },
|
||||||
|
{ border: "border-[#0bbf87]/40", gradient: "from-[#0bbf87]/10 to-white", dot: "bg-[#0bbf87]" },
|
||||||
|
{ border: "border-[#ff7a00]/40", gradient: "from-[#ff7a00]/10 to-white", dot: "bg-[#ff7a00]" },
|
||||||
|
{ border: "border-[#5d4ae3]/40", gradient: "from-[#5d4ae3]/10 to-white", dot: "bg-[#5d4ae3]" },
|
||||||
|
];
|
||||||
|
const colorScheme = colors[index % colors.length];
|
||||||
|
return (
|
||||||
<div
|
<div
|
||||||
key={step.title}
|
key={step.title}
|
||||||
className="grid grid-cols-1 lg:grid-cols-2 gap-12 items-center"
|
className={`grid grid-cols-1 lg:grid-cols-2 gap-12 items-center rounded-3xl border-2 ${colorScheme.border} bg-gradient-to-br ${colorScheme.gradient} p-8 hover:shadow-xl transition-all`}
|
||||||
>
|
>
|
||||||
<div className={`space-y-4 ${index % 2 === 1 ? "lg:order-2" : ""}`}>
|
<div className={`space-y-4 ${index % 2 === 1 ? "lg:order-2" : ""}`}>
|
||||||
<span className="text-xs uppercase tracking-[0.3em] text-slate-500">
|
<span className="text-xs uppercase tracking-[0.3em] text-slate-500 font-semibold flex items-center gap-2">
|
||||||
|
<span className={`size-1.5 rounded-full ${colorScheme.dot}`}></span>
|
||||||
Step {index + 1}
|
Step {index + 1}
|
||||||
</span>
|
</span>
|
||||||
<h3 className="text-2xl font-semibold">{step.title}</h3>
|
<h3 className="text-2xl font-semibold">{step.title}</h3>
|
||||||
<p className="text-sm text-slate-600 leading-relaxed">{step.description}</p>
|
<p className="text-sm text-slate-600 leading-relaxed">{step.description}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className={`rounded-3xl border border-slate-200 bg-white overflow-hidden ${index % 2 === 1 ? "lg:order-1" : ""}`}>
|
<div className={`rounded-3xl border-2 border-slate-200 bg-gradient-to-br from-slate-50 to-white overflow-hidden shadow-inner ${index % 2 === 1 ? "lg:order-1" : ""}`}>
|
||||||
<img
|
<img
|
||||||
src={`/marketing/images/${step.image}`}
|
src={`/marketing/images/${step.image}`}
|
||||||
alt={step.title}
|
alt={step.title}
|
||||||
@@ -66,10 +75,11 @@ const Tour: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
);
|
||||||
|
})}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section className="bg-slate-50/70 border-y border-slate-200">
|
<section className="bg-gradient-to-br from-[#0693e3]/10 via-slate-50/70 to-[#0bbf87]/10 border-y border-[#0693e3]/20">
|
||||||
<div className="max-w-6xl mx-auto px-6 py-24 space-y-10">
|
<div className="max-w-6xl mx-auto px-6 py-24 space-y-10">
|
||||||
<SectionHeading
|
<SectionHeading
|
||||||
eyebrow="Automation recipes"
|
eyebrow="Automation recipes"
|
||||||
@@ -78,19 +88,19 @@ const Tour: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-6 text-sm text-slate-600">
|
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-6 text-sm text-slate-600">
|
||||||
{[
|
{[
|
||||||
{ name: "Keywords → Ideas", time: "Nightly", highlight: "Targets new opportunities" },
|
{ name: "Keywords → Ideas", time: "Nightly", highlight: "Targets new opportunities", color: "border-[#0693e3]/40", gradient: "from-[#0693e3]/10 to-white" },
|
||||||
{ name: "Ideas → Tasks", time: "Daily", highlight: "Staff writers automatically" },
|
{ name: "Ideas → Tasks", time: "Daily", highlight: "Staff writers automatically", color: "border-[#0bbf87]/40", gradient: "from-[#0bbf87]/10 to-white" },
|
||||||
{ name: "Tasks → Content", time: "Hourly", highlight: "Generate & queue drafts" },
|
{ name: "Tasks → Content", time: "Hourly", highlight: "Generate & queue drafts", color: "border-[#ff7a00]/40", gradient: "from-[#ff7a00]/10 to-white" },
|
||||||
{ name: "Content → Images", time: "On approval", highlight: "Produce branded visuals" },
|
{ name: "Content → Images", time: "On approval", highlight: "Produce branded visuals", color: "border-[#5d4ae3]/40", gradient: "from-[#5d4ae3]/10 to-white" },
|
||||||
{ name: "Content → WordPress", time: "Manual launch", highlight: "One-click publish" },
|
{ name: "Content → WordPress", time: "Manual launch", highlight: "One-click publish", color: "border-[#0693e3]/40", gradient: "from-[#0693e3]/10 to-white" },
|
||||||
{ name: "SERP Win/Loss Alerts", time: "Real-time", highlight: "Trigger optimizations" },
|
{ name: "SERP Win/Loss Alerts", time: "Real-time", highlight: "Trigger optimizations", color: "border-[#0bbf87]/40", gradient: "from-[#0bbf87]/10 to-white" },
|
||||||
].map((recipe) => (
|
].map((recipe) => (
|
||||||
<div
|
<div
|
||||||
key={recipe.name}
|
key={recipe.name}
|
||||||
className="rounded-3xl border border-slate-200 bg-white p-6 space-y-3"
|
className={`rounded-3xl border-2 ${recipe.color} bg-gradient-to-br ${recipe.gradient} p-6 space-y-3 hover:shadow-xl transition-all hover:-translate-y-1`}
|
||||||
>
|
>
|
||||||
<h4 className="text-base font-semibold text-slate-900">{recipe.name}</h4>
|
<h4 className="text-base font-semibold text-slate-900">{recipe.name}</h4>
|
||||||
<div className="text-xs uppercase tracking-[0.3em] text-slate-500">
|
<div className="text-xs uppercase tracking-[0.3em] text-slate-500 font-semibold">
|
||||||
{recipe.time}
|
{recipe.time}
|
||||||
</div>
|
</div>
|
||||||
<p>{recipe.highlight}</p>
|
<p>{recipe.highlight}</p>
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ const roadmapItems = [
|
|||||||
|
|
||||||
const Waitlist: React.FC = () => {
|
const Waitlist: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<div className="bg-white text-slate-900">
|
<div className="bg-gradient-to-b from-white via-slate-50/30 to-white text-slate-900">
|
||||||
<section className="max-w-4xl mx-auto px-6 pt-24 pb-12">
|
<section className="max-w-4xl mx-auto px-6 pt-24 pb-12">
|
||||||
<SectionHeading
|
<SectionHeading
|
||||||
eyebrow="Roadmap preview"
|
eyebrow="Roadmap preview"
|
||||||
@@ -29,30 +29,33 @@ const Waitlist: React.FC = () => {
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section className="max-w-5xl mx-auto px-6 pb-24 grid grid-cols-1 lg:grid-cols-2 gap-12">
|
<section className="max-w-5xl mx-auto px-6 pb-24 grid grid-cols-1 lg:grid-cols-2 gap-12">
|
||||||
<div className="rounded-3xl border border-slate-200 bg-white p-10 space-y-6">
|
<div className="rounded-3xl border-2 border-[#0693e3]/30 bg-gradient-to-br from-[#0693e3]/10 via-white to-[#0bbf87]/5 p-10 space-y-6 shadow-lg">
|
||||||
<h3 className="text-lg font-semibold text-slate-900">Join the waitlist</h3>
|
<h3 className="text-lg font-semibold text-slate-900 flex items-center gap-2">
|
||||||
|
<span className="size-2 rounded-full bg-[#0693e3]"></span>
|
||||||
|
Join the waitlist
|
||||||
|
</h3>
|
||||||
<p className="text-sm text-slate-600">
|
<p className="text-sm text-slate-600">
|
||||||
Share your details and we’ll invite you to beta cohorts with onboarding resources and direct feedback loops to our product team.
|
Share your details and we'll invite you to beta cohorts with onboarding resources and direct feedback loops to our product team.
|
||||||
</p>
|
</p>
|
||||||
<form className="space-y-4">
|
<form className="space-y-4">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Name"
|
placeholder="Name"
|
||||||
className="w-full rounded-xl border border-slate-200 bg-slate-50/60 px-4 py-3 text-sm text-slate-900 placeholder:text-slate-500 focus:outline-none focus:border-brand-400"
|
className="w-full rounded-xl border-2 border-slate-200 bg-white px-4 py-3 text-sm text-slate-900 placeholder:text-slate-500 focus:outline-none focus:border-[#0693e3] focus:ring-2 focus:ring-[#0693e3]/20"
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
type="email"
|
type="email"
|
||||||
placeholder="Work email"
|
placeholder="Work email"
|
||||||
className="w-full rounded-xl border border-slate-200 bg-slate-50/60 px-4 py-3 text-sm text-slate-900 placeholder:text-slate-500 focus:outline-none focus:border-brand-400"
|
className="w-full rounded-xl border-2 border-slate-200 bg-white px-4 py-3 text-sm text-slate-900 placeholder:text-slate-500 focus:outline-none focus:border-[#0693e3] focus:ring-2 focus:ring-[#0693e3]/20"
|
||||||
/>
|
/>
|
||||||
<textarea
|
<textarea
|
||||||
rows={4}
|
rows={4}
|
||||||
placeholder="Tell us about your current workflow and why you're excited."
|
placeholder="Tell us about your current workflow and why you're excited."
|
||||||
className="w-full rounded-xl border border-slate-200 bg-slate-50/60 px-4 py-3 text-sm text-slate-900 placeholder:text-slate-500 focus:outline-none focus:border-brand-400 resize-none"
|
className="w-full rounded-xl border-2 border-slate-200 bg-white px-4 py-3 text-sm text-slate-900 placeholder:text-slate-500 focus:outline-none focus:border-[#0693e3] focus:ring-2 focus:ring-[#0693e3]/20 resize-none"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
className="inline-flex items-center justify-center rounded-full bg-brand-500 hover:bg-brand-400 px-6 py-3 text-sm font-semibold"
|
className="inline-flex items-center justify-center rounded-full bg-gradient-to-r from-[#0693e3] to-[#0472b8] hover:from-[#0472b8] hover:to-[#0693e3] text-white px-6 py-3 text-sm font-semibold shadow-lg shadow-[#0693e3]/30 transition-all w-full"
|
||||||
>
|
>
|
||||||
Join waitlist
|
Join waitlist
|
||||||
</button>
|
</button>
|
||||||
@@ -60,23 +63,32 @@ const Waitlist: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="rounded-3xl border border-slate-200 bg-white p-8 space-y-4">
|
<div className="rounded-3xl border-2 border-[#0bbf87]/30 bg-gradient-to-br from-[#0bbf87]/10 to-white p-8 space-y-4">
|
||||||
<h4 className="text-lg font-semibold text-slate-900">What's coming</h4>
|
<h4 className="text-lg font-semibold text-slate-900 flex items-center gap-2">
|
||||||
|
<span className="size-2 rounded-full bg-[#0bbf87]"></span>
|
||||||
|
What's coming
|
||||||
|
</h4>
|
||||||
<ul className="space-y-3 text-sm text-slate-600">
|
<ul className="space-y-3 text-sm text-slate-600">
|
||||||
{roadmapItems.map((item) => (
|
{roadmapItems.map((item, idx) => {
|
||||||
|
const colors = ["bg-[#0693e3]", "bg-[#0bbf87]", "bg-[#ff7a00]"];
|
||||||
|
return (
|
||||||
<li key={item.title} className="flex gap-3">
|
<li key={item.title} className="flex gap-3">
|
||||||
<span className="mt-1 size-1.5 rounded-full bg-brand-500" />
|
<span className={`mt-1 size-1.5 rounded-full ${colors[idx % colors.length]} shadow-sm`} />
|
||||||
<div>
|
<div>
|
||||||
<div className="font-semibold text-slate-900">{item.title}</div>
|
<div className="font-semibold text-slate-900">{item.title}</div>
|
||||||
<div>{item.description}</div>
|
<div>{item.description}</div>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
))}
|
);
|
||||||
|
})}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div className="rounded-3xl border border-slate-200 bg-white p-8 text-sm text-slate-600 space-y-3">
|
<div className="rounded-3xl border-2 border-[#ff7a00]/30 bg-gradient-to-br from-[#ff7a00]/10 to-white p-8 text-sm text-slate-600 space-y-3">
|
||||||
<h4 className="text-lg font-semibold text-slate-900">How the beta works</h4>
|
<h4 className="text-lg font-semibold text-slate-900 flex items-center gap-2">
|
||||||
<p>We onboard new features to the waitlist in weekly waves. You’ll receive playbooks, sample workflows, and a feedback channel with our product team.</p>
|
<span className="size-2 rounded-full bg-[#ff7a00]"></span>
|
||||||
|
How the beta works
|
||||||
|
</h4>
|
||||||
|
<p>We onboard new features to the waitlist in weekly waves. You'll receive playbooks, sample workflows, and a feedback channel with our product team.</p>
|
||||||
<p>Participants also get extended credits to experiment with automation scenarios.</p>
|
<p>Participants also get extended credits to experiment with automation scenarios.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
237
prompts.txt
237
prompts.txt
@@ -1,237 +0,0 @@
|
|||||||
|
|
||||||
1. Clusteirng Prompt
|
|
||||||
|
|
||||||
You are a semantic strategist and SEO architecture engine. Your task is to analyze the provided keyword list and group them into meaningful, intent-driven topic clusters that reflect how real users search, think, and act online.
|
|
||||||
|
|
||||||
Return a single JSON object with a "clusters" array. Each cluster must follow this structure:
|
|
||||||
|
|
||||||
{
|
|
||||||
"name": "[Descriptive cluster name — natural, SEO-relevant, clearly expressing the topic]",
|
|
||||||
"description": "[1–2 concise sentences explaining what this cluster covers and why these keywords belong together]",
|
|
||||||
"keywords": ["keyword 1", "keyword 2", "keyword 3", "..."]
|
|
||||||
}
|
|
||||||
|
|
||||||
CLUSTERING STRATEGY:
|
|
||||||
|
|
||||||
1. Keyword-first, structure-follows:
|
|
||||||
- Do NOT rely on assumed categories or existing content structures.
|
|
||||||
- Begin purely from the meaning, intent, and behavioral connection between keywords.
|
|
||||||
|
|
||||||
2. Use multi-dimensional grouping logic:
|
|
||||||
- Group keywords by these behavioral dimensions:
|
|
||||||
• Search Intent → informational, commercial, transactional, navigational
|
|
||||||
• Use-Case or Problem → what the user is trying to achieve or solve
|
|
||||||
• Function or Feature → how something works or what it does
|
|
||||||
• Persona or Audience → who the content or product serves
|
|
||||||
• Context → location, time, season, platform, or device
|
|
||||||
- Combine 2–3 dimensions naturally where they make sense.
|
|
||||||
|
|
||||||
3. Model real search behavior:
|
|
||||||
- Favor clusters that form natural user journeys such as:
|
|
||||||
• Problem ➝ Solution
|
|
||||||
• General ➝ Specific
|
|
||||||
• Product ➝ Use-case
|
|
||||||
• Buyer ➝ Benefit
|
|
||||||
• Tool ➝ Function
|
|
||||||
• Task ➝ Method
|
|
||||||
- Each cluster should feel like a real topic hub users would explore in depth.
|
|
||||||
|
|
||||||
4. Avoid superficial groupings:
|
|
||||||
- Do not cluster keywords just because they share words.
|
|
||||||
- Do not force-fit outliers or unrelated keywords.
|
|
||||||
- Exclude keywords that don’t logically connect to any cluster.
|
|
||||||
|
|
||||||
5. Quality rules:
|
|
||||||
- Each cluster should include between 3–10 strongly related keywords.
|
|
||||||
- Never duplicate a keyword across multiple clusters.
|
|
||||||
- Prioritize semantic strength, search intent, and usefulness for SEO-driven content structure.
|
|
||||||
- It’s better to output fewer, high-quality clusters than many weak or shallow ones.
|
|
||||||
|
|
||||||
INPUT FORMAT:
|
|
||||||
{
|
|
||||||
"keywords": [IGNY8_KEYWORDS]
|
|
||||||
}
|
|
||||||
|
|
||||||
OUTPUT FORMAT:
|
|
||||||
Return ONLY the final JSON object in this format:
|
|
||||||
{
|
|
||||||
"clusters": [
|
|
||||||
{
|
|
||||||
"name": "...",
|
|
||||||
"description": "...",
|
|
||||||
"keywords": ["...", "...", "..."]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
Do not include any explanations, text, or commentary outside the JSON output.
|
|
||||||
|
|
||||||
|
|
||||||
++++++++++++++++++++++++++++++++++++++
|
|
||||||
|
|
||||||
2. Ideas Generation Prompt
|
|
||||||
|
|
||||||
Generate SEO-optimized, high-quality content ideas and outlines for each keyword cluster.
|
|
||||||
Input:
|
|
||||||
Clusters: [IGNY8_CLUSTERS]
|
|
||||||
Keywords: [IGNY8_CLUSTER_KEYWORDS]
|
|
||||||
|
|
||||||
Output: JSON with "ideas" array.
|
|
||||||
Each cluster → 1 cluster_hub + 2–4 supporting ideas.
|
|
||||||
Each idea must include:
|
|
||||||
title, description, content_type, content_structure, cluster_id, estimated_word_count (1500–2200), and covered_keywords.
|
|
||||||
|
|
||||||
Outline Rules:
|
|
||||||
|
|
||||||
Intro: 1 hook (30–40 words) + 2 intro paragraphs (50–60 words each).
|
|
||||||
|
|
||||||
5–8 H2 sections, each with 2–3 H3s.
|
|
||||||
|
|
||||||
Each H2 ≈ 250–300 words, mixed content (paragraphs, lists, tables, blockquotes).
|
|
||||||
|
|
||||||
Vary section format and tone; no bullets or lists at start.
|
|
||||||
|
|
||||||
Tables have columns; blockquotes = expert POV or data insight.
|
|
||||||
|
|
||||||
Use depth, examples, and real context.
|
|
||||||
|
|
||||||
Avoid repetitive structure.
|
|
||||||
|
|
||||||
Tone: Professional editorial flow. No generic phrasing. Use varied sentence openings and realistic examples.
|
|
||||||
|
|
||||||
Output JSON Example:
|
|
||||||
|
|
||||||
{
|
|
||||||
"ideas": [
|
|
||||||
{
|
|
||||||
"title": "Best Organic Cotton Duvet Covers for All Seasons",
|
|
||||||
"description": {
|
|
||||||
"introduction": {
|
|
||||||
"hook": "Transform your sleep with organic cotton that blends comfort and sustainability.",
|
|
||||||
"paragraphs": [
|
|
||||||
{"content_type": "paragraph", "details": "Overview of organic cotton's rise in bedding industry."},
|
|
||||||
{"content_type": "paragraph", "details": "Why consumers prefer organic bedding over synthetic alternatives."}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"H2": [
|
|
||||||
{
|
|
||||||
"heading": "Why Choose Organic Cotton for Bedding?",
|
|
||||||
"subsections": [
|
|
||||||
{"subheading": "Health and Skin Benefits", "content_type": "paragraph", "details": "Discuss hypoallergenic and chemical-free aspects."},
|
|
||||||
{"subheading": "Environmental Sustainability", "content_type": "list", "details": "Eco benefits like low water use, no pesticides."},
|
|
||||||
{"subheading": "Long-Term Cost Savings", "content_type": "table", "details": "Compare durability and pricing over time."}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"content_type": "post",
|
|
||||||
"content_structure": "review",
|
|
||||||
"cluster_id": 12,
|
|
||||||
"estimated_word_count": 1800,
|
|
||||||
"covered_keywords": "organic duvet covers, eco-friendly bedding, sustainable sheets"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
==================================================
|
|
||||||
|
|
||||||
3. Content Generation Prompt
|
|
||||||
|
|
||||||
You are an editorial content strategist. Your task is to generate a complete JSON response object that includes all the fields listed below, based on the provided content idea, keyword cluster, and keyword list.
|
|
||||||
|
|
||||||
Only the `content` field should contain HTML inside JSON onject.
|
|
||||||
|
|
||||||
==================
|
|
||||||
Generate a complete JSON response object matching this structure:
|
|
||||||
==================
|
|
||||||
|
|
||||||
{
|
|
||||||
"title": "[Blog title using the primary keyword — full sentence case]",
|
|
||||||
"meta_title": "[Meta title under 60 characters — natural, optimized, and compelling]",
|
|
||||||
"meta_description": "[Meta description under 160 characters — clear and enticing summary]",
|
|
||||||
"content": "[HTML content — full editorial structure with <p>, <h2>, <h3>, <ul>, <ol>, <table>]",
|
|
||||||
"word_count": [Exact integer — word count of HTML body only],
|
|
||||||
"primary_keyword": "[Single primary keyword used in title and first paragraph]",
|
|
||||||
"secondary_keywords": [
|
|
||||||
"[Keyword 1]",
|
|
||||||
"[Keyword 2]",
|
|
||||||
"[Keyword 3]"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"[2–4 word lowercase tag 1]",
|
|
||||||
"[2–4 word lowercase tag 2]",
|
|
||||||
"[2–4 word lowercase tag 3]",
|
|
||||||
"[2–4 word lowercase tag 4]",
|
|
||||||
"[2–4 word lowercase tag 5]"
|
|
||||||
],
|
|
||||||
"categories": [
|
|
||||||
"[Parent Category > Child Category]",
|
|
||||||
"[Optional Second Category > Optional Subcategory]"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
===========================
|
|
||||||
CONTENT FLOW RULES
|
|
||||||
===========================
|
|
||||||
|
|
||||||
**INTRODUCTION:**
|
|
||||||
- Start with 1 italicized hook (30–40 words)
|
|
||||||
- Follow with 2 narrative paragraphs (each 50–60 words; 2–3 sentences max)
|
|
||||||
- No headings allowed in intro
|
|
||||||
|
|
||||||
**H2 SECTIONS (5–8 total):**
|
|
||||||
Each section should be 250–300 words and follow this format:
|
|
||||||
1. Two narrative paragraphs (80–120 words each, 2–3 sentences)
|
|
||||||
2. One list or table (must come *after* a paragraph)
|
|
||||||
3. Optional closing paragraph (40–60 words)
|
|
||||||
4. Insert 2–3 subsections naturally after main paragraphs
|
|
||||||
|
|
||||||
**Formatting Rules:**
|
|
||||||
- Vary use of unordered lists, ordered lists, and tables across sections
|
|
||||||
- Never begin any section or sub-section with a list or table
|
|
||||||
|
|
||||||
===========================
|
|
||||||
KEYWORD & SEO RULES
|
|
||||||
===========================
|
|
||||||
|
|
||||||
- **Primary keyword** must appear in:
|
|
||||||
- The title
|
|
||||||
- First paragraph of the introduction
|
|
||||||
- At least 2 H2 headings
|
|
||||||
|
|
||||||
- **Secondary keywords** must be used naturally, not forced
|
|
||||||
|
|
||||||
- **Tone & style guidelines:**
|
|
||||||
- No robotic or passive voice
|
|
||||||
- Avoid generic intros like "In today's world…"
|
|
||||||
- Don't repeat heading in opening sentence
|
|
||||||
- Vary sentence structure and length
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
===========================
|
|
||||||
INPUT VARIABLES
|
|
||||||
===========================
|
|
||||||
|
|
||||||
CONTENT IDEA DETAILS:
|
|
||||||
[IGNY8_IDEA]
|
|
||||||
|
|
||||||
KEYWORD CLUSTER:
|
|
||||||
[IGNY8_CLUSTER]
|
|
||||||
|
|
||||||
ASSOCIATED KEYWORDS:
|
|
||||||
[IGNY8_KEYWORDS]
|
|
||||||
|
|
||||||
===========================
|
|
||||||
OUTPUT FORMAT
|
|
||||||
===========================
|
|
||||||
|
|
||||||
Return ONLY the final JSON object.
|
|
||||||
Do NOT include any comments, formatting, or explanations.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
First after rebuild 3
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user