Site design updates

This commit is contained in:
Desktop
2025-11-14 01:16:08 +05:00
parent f8bab8d432
commit e74c048f46
17 changed files with 429 additions and 1618 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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.

View File

@@ -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)

View File

@@ -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.**

View File

@@ -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`

View File

@@ -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)

View File

@@ -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>
Igny8s 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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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"

View File

@@ -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 fluffjust tactical guidance and event invites. Monthly insights on AI, SEO, and automation. No fluffjust 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>

View File

@@ -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>
))} ))}

View File

@@ -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>

View File

@@ -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 well 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. Youll 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>

View File

@@ -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": "[12 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 23 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 dont logically connect to any cluster.
5. Quality rules:
- Each cluster should include between 310 strongly related keywords.
- Never duplicate a keyword across multiple clusters.
- Prioritize semantic strength, search intent, and usefulness for SEO-driven content structure.
- Its 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 + 24 supporting ideas.
Each idea must include:
title, description, content_type, content_structure, cluster_id, estimated_word_count (15002200), and covered_keywords.
Outline Rules:
Intro: 1 hook (3040 words) + 2 intro paragraphs (5060 words each).
58 H2 sections, each with 23 H3s.
Each H2 ≈ 250300 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": [
"[24 word lowercase tag 1]",
"[24 word lowercase tag 2]",
"[24 word lowercase tag 3]",
"[24 word lowercase tag 4]",
"[24 word lowercase tag 5]"
],
"categories": [
"[Parent Category > Child Category]",
"[Optional Second Category > Optional Subcategory]"
]
}
===========================
CONTENT FLOW RULES
===========================
**INTRODUCTION:**
- Start with 1 italicized hook (3040 words)
- Follow with 2 narrative paragraphs (each 5060 words; 23 sentences max)
- No headings allowed in intro
**H2 SECTIONS (58 total):**
Each section should be 250300 words and follow this format:
1. Two narrative paragraphs (80120 words each, 23 sentences)
2. One list or table (must come *after* a paragraph)
3. Optional closing paragraph (4060 words)
4. Insert 23 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