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 = () => {
|
||||
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">
|
||||
<SectionHeading
|
||||
eyebrow="Proof"
|
||||
@@ -53,69 +53,89 @@ const CaseStudies: React.FC = () => {
|
||||
</section>
|
||||
|
||||
<section className="max-w-6xl mx-auto px-6 pb-24 space-y-12">
|
||||
{caseStudies.map((cs) => (
|
||||
<div
|
||||
key={cs.company}
|
||||
className="rounded-3xl border border-slate-200 bg-white p-12 grid grid-cols-1 lg:grid-cols-2 gap-12"
|
||||
>
|
||||
<div className="space-y-6">
|
||||
<span className="text-xs uppercase tracking-[0.3em] text-slate-500">
|
||||
{cs.company}
|
||||
</span>
|
||||
<h3 className="text-2xl font-semibold text-slate-900">{cs.headline}</h3>
|
||||
<p className="text-sm text-slate-600 leading-relaxed">{cs.summary}</p>
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
{cs.metrics.map((metric) => (
|
||||
<div
|
||||
key={metric.label}
|
||||
className="rounded-2xl border border-slate-200 bg-white p-4 text-center space-y-2"
|
||||
>
|
||||
<div className="text-xl font-semibold text-slate-900">
|
||||
{metric.value}
|
||||
</div>
|
||||
<div className="text-xs uppercase tracking-[0.2em] text-slate-500">
|
||||
{metric.label}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{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
|
||||
key={cs.company}
|
||||
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">
|
||||
<span className="text-xs uppercase tracking-[0.3em] text-slate-500 font-semibold">
|
||||
{cs.company}
|
||||
</span>
|
||||
<h3 className="text-2xl font-semibold text-slate-900">{cs.headline}</h3>
|
||||
<p className="text-sm text-slate-600 leading-relaxed">{cs.summary}</p>
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
{cs.metrics.map((metric, metricIdx) => {
|
||||
const metricColor = metricColors[metricIdx % metricColors.length];
|
||||
return (
|
||||
<div
|
||||
key={metric.label}
|
||||
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 ${metricColor.text}`}>
|
||||
{metric.value}
|
||||
</div>
|
||||
<div className="text-xs uppercase tracking-[0.2em] text-slate-500">
|
||||
{metric.label}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<div className="rounded-3xl border-2 border-slate-200 bg-gradient-to-br from-slate-50 to-white overflow-hidden shadow-inner">
|
||||
<img
|
||||
src={`/marketing/images/${cs.image}`}
|
||||
alt={`${cs.company} case study`}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="rounded-3xl border border-slate-200 bg-slate-100 overflow-hidden">
|
||||
<img
|
||||
src={`/marketing/images/${cs.image}`}
|
||||
alt={`${cs.company} case study`}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</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="space-y-4">
|
||||
<h4 className="text-lg font-semibold text-slate-900">Results you can expect</h4>
|
||||
<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 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">
|
||||
<li className="flex gap-3">
|
||||
<span className="mt-1 size-1.5 rounded-full bg-brand-500" />
|
||||
30-60 day onboarding to deploy automation and Thinker governance.
|
||||
</li>
|
||||
<li className="flex gap-3">
|
||||
<span className="mt-1 size-1.5 rounded-full bg-brand-500" />
|
||||
3-5× increase in content throughput without sacrificing editorial quality.
|
||||
</li>
|
||||
<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>
|
||||
{[
|
||||
{ text: "30-60 day onboarding to deploy automation and Thinker governance.", color: "bg-[#0693e3]" },
|
||||
{ text: "3-5× increase in content throughput without sacrificing editorial quality.", color: "bg-[#0bbf87]" },
|
||||
{ text: "Clear ROI dashboards tying automation to revenue outcomes.", color: "bg-[#ff7a00]" },
|
||||
].map((item) => (
|
||||
<li key={item.text} className="flex gap-3">
|
||||
<span className={`mt-1 size-1.5 rounded-full ${item.color} shadow-sm`} />
|
||||
{item.text}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
<div className="rounded-3xl border border-slate-200 bg-white p-10 space-y-4 text-sm text-slate-600">
|
||||
<h4 className="text-lg font-semibold text-slate-900">Customer advisory board</h4>
|
||||
<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 flex items-center gap-2">
|
||||
<span className="size-2 rounded-full bg-[#0bbf87]"></span>
|
||||
Customer advisory board
|
||||
</h4>
|
||||
<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>
|
||||
<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
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -4,7 +4,7 @@ import CTASection from "../components/CTASection";
|
||||
|
||||
const Contact: React.FC = () => {
|
||||
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">
|
||||
<SectionHeading
|
||||
eyebrow="Contact"
|
||||
@@ -14,14 +14,14 @@ const Contact: React.FC = () => {
|
||||
</section>
|
||||
|
||||
<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">
|
||||
<label className="flex flex-col gap-2 text-sm text-slate-600">
|
||||
First name
|
||||
<input
|
||||
type="text"
|
||||
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 className="flex flex-col gap-2 text-sm text-slate-600">
|
||||
@@ -29,7 +29,7 @@ const Contact: React.FC = () => {
|
||||
<input
|
||||
type="text"
|
||||
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>
|
||||
</div>
|
||||
@@ -39,7 +39,7 @@ const Contact: React.FC = () => {
|
||||
<input
|
||||
type="email"
|
||||
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>
|
||||
|
||||
@@ -48,7 +48,7 @@ const Contact: React.FC = () => {
|
||||
<input
|
||||
type="text"
|
||||
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>
|
||||
|
||||
@@ -57,48 +57,52 @@ const Contact: React.FC = () => {
|
||||
<textarea
|
||||
rows={4}
|
||||
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>
|
||||
|
||||
<button
|
||||
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
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<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">
|
||||
<h3 className="text-lg font-semibold text-slate-900">Calendly placeholder</h3>
|
||||
<div className="aspect-[4/3] rounded-2xl border border-slate-200 bg-slate-100 flex items-center justify-center text-xs text-slate-500">
|
||||
<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 flex items-center gap-2">
|
||||
<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
|
||||
</div>
|
||||
<p>
|
||||
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
|
||||
</a>{" "}
|
||||
or join our community Slack.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="rounded-3xl border border-slate-200 bg-white p-8 space-y-4">
|
||||
<h3 className="text-lg font-semibold text-slate-900">Support perks</h3>
|
||||
<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 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">
|
||||
<li className="flex gap-3">
|
||||
<span className="mt-1 size-1.5 rounded-full bg-brand-500" />
|
||||
24-hour response time on all Launch+ plans.
|
||||
</li>
|
||||
<li className="flex gap-3">
|
||||
<span className="mt-1 size-1.5 rounded-full bg-brand-500" />
|
||||
Dedicated success architect for Scale and Enterprise.
|
||||
</li>
|
||||
<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>
|
||||
{[
|
||||
{ text: "24-hour response time on all Launch+ plans.", color: "bg-[#0693e3]" },
|
||||
{ text: "Dedicated success architect for Scale and Enterprise.", color: "bg-[#0bbf87]" },
|
||||
{ text: "Migration services when replacing legacy content stacks.", color: "bg-[#ff7a00]" },
|
||||
].map((item) => (
|
||||
<li key={item.text} className="flex gap-3">
|
||||
<span className={`mt-1 size-1.5 rounded-full ${item.color} shadow-sm`} />
|
||||
{item.text}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -31,7 +31,7 @@ const tiers = [
|
||||
|
||||
const Partners: React.FC = () => {
|
||||
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">
|
||||
<SectionHeading
|
||||
eyebrow="Partners"
|
||||
@@ -41,28 +41,39 @@ const Partners: React.FC = () => {
|
||||
</section>
|
||||
|
||||
<section className="max-w-6xl mx-auto px-6 pb-24 grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||
{tiers.map((tier) => (
|
||||
<div
|
||||
key={tier.title}
|
||||
className="rounded-3xl border border-slate-200 bg-white p-8 flex flex-col gap-5"
|
||||
>
|
||||
<span className="text-xs uppercase tracking-[0.3em] text-slate-500">
|
||||
Program
|
||||
</span>
|
||||
<h3 className="text-xl font-semibold text-slate-900">{tier.title}</h3>
|
||||
<ul className="space-y-3 text-sm text-slate-600">
|
||||
{tier.benefits.map((benefit) => (
|
||||
<li key={benefit} className="flex gap-3">
|
||||
<span className="mt-1 size-1.5 rounded-full bg-brand-500" />
|
||||
{benefit}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
))}
|
||||
{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
|
||||
key={tier.title}
|
||||
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 font-semibold">
|
||||
Program
|
||||
</span>
|
||||
<h3 className="text-xl font-semibold text-slate-900">{tier.title}</h3>
|
||||
<ul className="space-y-3 text-sm text-slate-600">
|
||||
{tier.benefits.map((benefit, benefitIdx) => {
|
||||
const benefitColors = ["bg-[#0693e3]", "bg-[#0bbf87]", "bg-[#ff7a00]", "bg-[#5d4ae3]"];
|
||||
return (
|
||||
<li key={benefit} className="flex gap-3">
|
||||
<span className={`mt-1 size-1.5 rounded-full ${benefitColors[benefitIdx % benefitColors.length]} shadow-sm`} />
|
||||
{benefit}
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</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="space-y-6">
|
||||
<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."
|
||||
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).
|
||||
</div>
|
||||
</div>
|
||||
<div className="rounded-3xl border border-slate-200 bg-white p-10 space-y-6">
|
||||
<h4 className="text-lg font-semibold text-slate-900">Partner resources</h4>
|
||||
<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 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">
|
||||
<li className="flex gap-3">
|
||||
<span className="mt-1 size-1.5 rounded-full bg-brand-500" />
|
||||
Sales playbooks, ROI calculators, and demo environments.
|
||||
</li>
|
||||
<li className="flex gap-3">
|
||||
<span className="mt-1 size-1.5 rounded-full bg-brand-500" />
|
||||
Shared Slack channels with Igny8 product and marketing teams.
|
||||
</li>
|
||||
<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>
|
||||
{[
|
||||
{ text: "Sales playbooks, ROI calculators, and demo environments.", color: "bg-[#0693e3]" },
|
||||
{ text: "Shared Slack channels with Igny8 product and marketing teams.", color: "bg-[#0bbf87]" },
|
||||
{ text: "Quarterly partner labs to showcase launches and integrations.", color: "bg-[#ff7a00]" },
|
||||
].map((item) => (
|
||||
<li key={item.text} className="flex gap-3">
|
||||
<span className={`mt-1 size-1.5 rounded-full ${item.color} shadow-sm`} />
|
||||
{item.text}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -149,28 +149,28 @@ const Pricing: React.FC = () => {
|
||||
<h3 className="text-xl font-semibold text-slate-900">
|
||||
Compare plan capabilities
|
||||
</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">
|
||||
<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>
|
||||
<th className="px-6 py-4 text-left">Capability</th>
|
||||
<th className="px-6 py-4 text-center">Launch</th>
|
||||
<th className="px-6 py-4 text-center">Scale</th>
|
||||
<th className="px-6 py-4 text-center">Enterprise</th>
|
||||
<th className="px-6 py-4 text-left font-semibold">Capability</th>
|
||||
<th className="px-6 py-4 text-center font-semibold">Launch</th>
|
||||
<th className="px-6 py-4 text-center font-semibold text-[#0693e3]">Scale</th>
|
||||
<th className="px-6 py-4 text-center font-semibold">Enterprise</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{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-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 className="px-6 py-5 text-center">
|
||||
{row.scale === true ? "Included" : row.scale}
|
||||
</td>
|
||||
<td className="px-6 py-5 text-center">
|
||||
{row.enterprise === true ? "Included" : row.enterprise}
|
||||
{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>
|
||||
</tr>
|
||||
))}
|
||||
@@ -179,10 +179,11 @@ const Pricing: React.FC = () => {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="bg-gradient-to-br from-[#0693e3]/5 via-slate-50/70 to-[#0bbf87]/5 border-y border-[#0693e3]/10">
|
||||
<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="space-y-4">
|
||||
<h4 className="text-lg font-semibold text-slate-900">
|
||||
<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="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 flex items-center gap-2">
|
||||
<span className="size-2 rounded-full bg-[#0693e3]"></span>
|
||||
Usage-based credits explained
|
||||
</h4>
|
||||
<p>
|
||||
@@ -192,8 +193,9 @@ const Pricing: React.FC = () => {
|
||||
Need more? Add packs instantly or set automation rules to pause when thresholds are hit.
|
||||
</p>
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
<h4 className="text-lg font-semibold text-slate-900">
|
||||
<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 flex items-center gap-2">
|
||||
<span className="size-2 rounded-full bg-[#0bbf87]"></span>
|
||||
Security & compliance
|
||||
</h4>
|
||||
<p>
|
||||
|
||||
@@ -4,7 +4,7 @@ import CTASection from "../components/CTASection";
|
||||
|
||||
const Product: React.FC = () => {
|
||||
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">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-16 items-center">
|
||||
<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."
|
||||
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">
|
||||
<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>
|
||||
<ul className="space-y-3">
|
||||
{[
|
||||
"Planner → Find, cluster, and prioritize keywords with AI scoring and SERP insights.",
|
||||
"Writer → Generate on-brand long-form content from briefs with tone, audience, and compliance controls.",
|
||||
"Thinker → Manage prompts, author profiles, and brand playbooks that feed every generation.",
|
||||
"Automation → Run scheduled workflows that move keywords to ideas, tasks, content, and images automatically.",
|
||||
{ text: "Planner → Find, cluster, and prioritize keywords with AI scoring and SERP insights.", color: "bg-[#0693e3]" },
|
||||
{ text: "Writer → Generate on-brand long-form content from briefs with tone, audience, and compliance controls.", color: "bg-[#0bbf87]" },
|
||||
{ text: "Thinker → Manage prompts, author profiles, and brand playbooks that feed every generation.", color: "bg-[#ff7a00]" },
|
||||
{ text: "Automation → Run scheduled workflows that move keywords to ideas, tasks, content, and images automatically.", color: "bg-[#5d4ae3]" },
|
||||
].map((point) => (
|
||||
<li key={point} className="flex gap-3">
|
||||
<span className="mt-1 size-2 rounded-full bg-brand-500" />
|
||||
{point}
|
||||
<li key={point.text} className="flex gap-3">
|
||||
<span className={`mt-1 size-2 rounded-full ${point.color} shadow-sm`} />
|
||||
{point.text}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
@@ -90,38 +90,50 @@ const Product: React.FC = () => {
|
||||
],
|
||||
image: "automation-timeline.png",
|
||||
},
|
||||
].map((module) => (
|
||||
<div
|
||||
key={module.title}
|
||||
className="rounded-3xl border border-slate-200 bg-white p-10 flex flex-col gap-6"
|
||||
>
|
||||
<div className="flex items-center gap-3 text-sm uppercase tracking-[0.3em] text-slate-900/40">
|
||||
<span className="size-2 rounded-full bg-brand-500" />
|
||||
{module.title}
|
||||
].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
|
||||
key={module.title}
|
||||
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-700 font-semibold">
|
||||
<span className={`size-2 rounded-full ${colorScheme.dot} shadow-sm`} />
|
||||
{module.title}
|
||||
</div>
|
||||
<h3 className="text-2xl font-semibold text-slate-900">{module.title} platform</h3>
|
||||
<ul className="space-y-3 text-sm text-slate-900/65">
|
||||
{module.copy.map((line, lineIdx) => {
|
||||
const dotColors = ["bg-[#0693e3]", "bg-[#0bbf87]", "bg-[#ff7a00]", "bg-[#5d4ae3]"];
|
||||
return (
|
||||
<li key={line} className="flex gap-3">
|
||||
<span className={`mt-1 size-1.5 rounded-full ${dotColors[lineIdx % dotColors.length]} shadow-sm`} />
|
||||
{line}
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
<div className="rounded-2xl border-2 border-slate-200 bg-gradient-to-br from-slate-50 to-white overflow-hidden shadow-inner">
|
||||
<img
|
||||
src={`/marketing/images/${module.image}`}
|
||||
alt={`${module.title} module`}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<h3 className="text-2xl font-semibold text-slate-900">{module.title} platform</h3>
|
||||
<ul className="space-y-3 text-sm text-slate-900/65">
|
||||
{module.copy.map((line) => (
|
||||
<li key={line} className="flex gap-3">
|
||||
<span className="mt-1 size-1.5 rounded-full bg-brand-500" />
|
||||
{line}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<div className="rounded-2xl border border-slate-200 bg-slate-100 overflow-hidden">
|
||||
<img
|
||||
src={`/marketing/images/${module.image}`}
|
||||
alt={`${module.title} module`}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<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">
|
||||
<SectionHeading
|
||||
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">
|
||||
{[
|
||||
"Real-time status for every automation recipe with success rates and manual interventions logged.",
|
||||
"Smart recommendations to rebalance workloads, add credits, or adjust prompts when performance shifts.",
|
||||
"Exportable reports to share results with leadership or clients in one click.",
|
||||
"Granular permissions so teams can automate while leadership maintains oversight.",
|
||||
{ text: "Real-time status for every automation recipe with success rates and manual interventions logged.", color: "bg-[#0693e3]" },
|
||||
{ text: "Smart recommendations to rebalance workloads, add credits, or adjust prompts when performance shifts.", color: "bg-[#0bbf87]" },
|
||||
{ text: "Exportable reports to share results with leadership or clients in one click.", color: "bg-[#ff7a00]" },
|
||||
{ text: "Granular permissions so teams can automate while leadership maintains oversight.", color: "bg-[#5d4ae3]" },
|
||||
].map((item) => (
|
||||
<div key={item} className="flex gap-3">
|
||||
<span className="mt-1 size-2 rounded-full bg-brand-500" />
|
||||
{item}
|
||||
<div key={item.text} className="flex gap-3">
|
||||
<span className={`mt-1 size-2 rounded-full ${item.color} shadow-sm`} />
|
||||
{item.text}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<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
|
||||
src="/marketing/images/automation-timeline.png"
|
||||
alt="Automation timeline"
|
||||
|
||||
@@ -38,7 +38,7 @@ const webinars = [
|
||||
|
||||
const Resources: React.FC = () => {
|
||||
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">
|
||||
<SectionHeading
|
||||
eyebrow="Resources"
|
||||
@@ -48,40 +48,55 @@ const Resources: React.FC = () => {
|
||||
</section>
|
||||
|
||||
<section className="max-w-6xl mx-auto px-6 pb-24 grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
{articles.map((article) => (
|
||||
<article
|
||||
key={article.title}
|
||||
className="rounded-3xl border border-slate-200 bg-white p-8 flex flex-col gap-6"
|
||||
>
|
||||
<span className="text-xs uppercase tracking-[0.3em] text-slate-500">
|
||||
{article.date}
|
||||
</span>
|
||||
<h3 className="text-xl font-semibold text-slate-900">{article.title}</h3>
|
||||
<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">
|
||||
Article cover placeholder (800×600) → /marketing/images/resource-hero.png
|
||||
</div>
|
||||
</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
|
||||
key={article.title}
|
||||
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 font-semibold">
|
||||
{article.date}
|
||||
</span>
|
||||
<h3 className="text-xl font-semibold text-slate-900">{article.title}</h3>
|
||||
<p className="text-sm text-slate-600 leading-relaxed">{article.description}</p>
|
||||
<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
|
||||
</div>
|
||||
</article>
|
||||
);
|
||||
})}
|
||||
</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">
|
||||
{webinars.map((webinar) => (
|
||||
<div
|
||||
key={webinar.title}
|
||||
className="rounded-3xl border border-slate-200 bg-white p-8 flex flex-col gap-4"
|
||||
>
|
||||
<span className="text-xs uppercase tracking-[0.3em] text-slate-500">
|
||||
{webinar.date}
|
||||
</span>
|
||||
<h3 className="text-lg font-semibold text-slate-900">{webinar.title}</h3>
|
||||
<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">
|
||||
Save my seat
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
{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
|
||||
key={webinar.title}
|
||||
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 font-semibold">
|
||||
{webinar.date}
|
||||
</span>
|
||||
<h3 className="text-lg font-semibold text-slate-900">{webinar.title}</h3>
|
||||
<p className="text-sm text-slate-600">{webinar.description}</p>
|
||||
<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
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -108,8 +123,11 @@ const Resources: React.FC = () => {
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="rounded-3xl border border-slate-200 bg-white p-10 space-y-6">
|
||||
<h3 className="text-2xl font-semibold text-slate-900">Join the Igny8 newsletter</h3>
|
||||
<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 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">
|
||||
Monthly insights on AI, SEO, and automation. No fluff—just tactical guidance and event invites.
|
||||
</p>
|
||||
@@ -117,11 +135,11 @@ const Resources: React.FC = () => {
|
||||
<input
|
||||
type="email"
|
||||
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
|
||||
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
|
||||
</button>
|
||||
|
||||
@@ -49,7 +49,7 @@ const personas = [
|
||||
|
||||
const Solutions: React.FC = () => {
|
||||
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">
|
||||
<SectionHeading
|
||||
eyebrow="Solutions"
|
||||
@@ -59,75 +59,97 @@ const Solutions: React.FC = () => {
|
||||
</section>
|
||||
|
||||
<section className="max-w-6xl mx-auto px-6 pb-24 space-y-12">
|
||||
{personas.map((persona) => (
|
||||
<div
|
||||
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"
|
||||
>
|
||||
<div className="lg:col-span-1 space-y-4">
|
||||
<span className="text-xs uppercase tracking-[0.3em] text-slate-500">
|
||||
Persona
|
||||
</span>
|
||||
<h3 className="text-2xl font-semibold">{persona.name}</h3>
|
||||
<div className="rounded-2xl border border-slate-200 bg-slate-100 overflow-hidden">
|
||||
<img
|
||||
src={`/marketing/images/${persona.image}`}
|
||||
alt={`${persona.name} workflow`}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
{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
|
||||
key={persona.name}
|
||||
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">
|
||||
<span className="text-xs uppercase tracking-[0.3em] text-slate-500">
|
||||
Persona
|
||||
</span>
|
||||
<h3 className="text-2xl font-semibold">{persona.name}</h3>
|
||||
<div className="rounded-2xl border-2 border-slate-200 bg-gradient-to-br from-slate-50 to-white overflow-hidden shadow-inner">
|
||||
<img
|
||||
src={`/marketing/images/${persona.image}`}
|
||||
alt={`${persona.name} workflow`}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-6">
|
||||
<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
|
||||
</h4>
|
||||
<ul className="space-y-4 text-sm text-slate-600">
|
||||
{persona.pains.map((pain) => (
|
||||
<li key={pain} className="flex gap-3">
|
||||
<span className="mt-1 size-1.5 rounded-full bg-rose-400 shadow-sm" />
|
||||
{pain}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
<div className="space-y-6">
|
||||
<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
|
||||
</h4>
|
||||
<ul className="space-y-4 text-sm text-slate-600">
|
||||
{persona.outcomes.map((outcome, outcomeIdx) => {
|
||||
const outcomeColors = ["bg-[#0693e3]", "bg-[#0bbf87]", "bg-[#ff7a00]"];
|
||||
return (
|
||||
<li key={outcome} className="flex gap-3">
|
||||
<span className={`mt-1 size-1.5 rounded-full ${outcomeColors[outcomeIdx % outcomeColors.length]} shadow-sm`} />
|
||||
{outcome}
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-6">
|
||||
<h4 className="text-sm uppercase tracking-[0.3em] text-slate-500">
|
||||
Pain points
|
||||
</h4>
|
||||
<ul className="space-y-4 text-sm text-slate-600">
|
||||
{persona.pains.map((pain) => (
|
||||
<li key={pain} className="flex gap-3">
|
||||
<span className="mt-1 size-1.5 rounded-full bg-rose-300" />
|
||||
{pain}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
<div className="space-y-6">
|
||||
<h4 className="text-sm uppercase tracking-[0.3em] text-slate-500">
|
||||
Outcomes with Igny8
|
||||
</h4>
|
||||
<ul className="space-y-4 text-sm text-slate-600">
|
||||
{persona.outcomes.map((outcome) => (
|
||||
<li key={outcome} className="flex gap-3">
|
||||
<span className="mt-1 size-1.5 rounded-full bg-brand-500" />
|
||||
{outcome}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</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">
|
||||
{[
|
||||
{
|
||||
metric: "3.2×",
|
||||
label: "Average lift in organic traffic within 90 days.",
|
||||
color: "border-[#0693e3]/40",
|
||||
gradient: "from-[#0693e3]/10 to-white",
|
||||
textColor: "text-[#0693e3]",
|
||||
},
|
||||
{
|
||||
metric: "48%",
|
||||
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",
|
||||
label: "Average number of point solutions replaced by Igny8.",
|
||||
color: "border-[#ff7a00]/40",
|
||||
gradient: "from-[#ff7a00]/10 to-white",
|
||||
textColor: "text-[#ff7a00]",
|
||||
},
|
||||
].map((item) => (
|
||||
<div
|
||||
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>
|
||||
</div>
|
||||
))}
|
||||
|
||||
@@ -31,45 +31,55 @@ const tourSteps = [
|
||||
|
||||
const Tour: React.FC = () => {
|
||||
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">
|
||||
<SectionHeading
|
||||
eyebrow="Guided Tour"
|
||||
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."
|
||||
/>
|
||||
<div className="rounded-3xl border border-slate-200 bg-white p-8">
|
||||
<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="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-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)
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="max-w-6xl mx-auto px-6 pb-24 space-y-12">
|
||||
{tourSteps.map((step, index) => (
|
||||
<div
|
||||
key={step.title}
|
||||
className="grid grid-cols-1 lg:grid-cols-2 gap-12 items-center"
|
||||
>
|
||||
<div className={`space-y-4 ${index % 2 === 1 ? "lg:order-2" : ""}`}>
|
||||
<span className="text-xs uppercase tracking-[0.3em] text-slate-500">
|
||||
Step {index + 1}
|
||||
</span>
|
||||
<h3 className="text-2xl font-semibold">{step.title}</h3>
|
||||
<p className="text-sm text-slate-600 leading-relaxed">{step.description}</p>
|
||||
{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
|
||||
key={step.title}
|
||||
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" : ""}`}>
|
||||
<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}
|
||||
</span>
|
||||
<h3 className="text-2xl font-semibold">{step.title}</h3>
|
||||
<p className="text-sm text-slate-600 leading-relaxed">{step.description}</p>
|
||||
</div>
|
||||
<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
|
||||
src={`/marketing/images/${step.image}`}
|
||||
alt={step.title}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className={`rounded-3xl border border-slate-200 bg-white overflow-hidden ${index % 2 === 1 ? "lg:order-1" : ""}`}>
|
||||
<img
|
||||
src={`/marketing/images/${step.image}`}
|
||||
alt={step.title}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</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">
|
||||
<SectionHeading
|
||||
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">
|
||||
{[
|
||||
{ name: "Keywords → Ideas", time: "Nightly", highlight: "Targets new opportunities" },
|
||||
{ name: "Ideas → Tasks", time: "Daily", highlight: "Staff writers automatically" },
|
||||
{ name: "Tasks → Content", time: "Hourly", highlight: "Generate & queue drafts" },
|
||||
{ name: "Content → Images", time: "On approval", highlight: "Produce branded visuals" },
|
||||
{ name: "Content → WordPress", time: "Manual launch", highlight: "One-click publish" },
|
||||
{ name: "SERP Win/Loss Alerts", time: "Real-time", highlight: "Trigger optimizations" },
|
||||
{ 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", color: "border-[#0bbf87]/40", gradient: "from-[#0bbf87]/10 to-white" },
|
||||
{ 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", color: "border-[#5d4ae3]/40", gradient: "from-[#5d4ae3]/10 to-white" },
|
||||
{ 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", color: "border-[#0bbf87]/40", gradient: "from-[#0bbf87]/10 to-white" },
|
||||
].map((recipe) => (
|
||||
<div
|
||||
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>
|
||||
<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}
|
||||
</div>
|
||||
<p>{recipe.highlight}</p>
|
||||
|
||||
@@ -19,7 +19,7 @@ const roadmapItems = [
|
||||
|
||||
const Waitlist: React.FC = () => {
|
||||
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">
|
||||
<SectionHeading
|
||||
eyebrow="Roadmap preview"
|
||||
@@ -29,30 +29,33 @@ const Waitlist: React.FC = () => {
|
||||
</section>
|
||||
|
||||
<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">
|
||||
<h3 className="text-lg font-semibold text-slate-900">Join the waitlist</h3>
|
||||
<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 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">
|
||||
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>
|
||||
<form className="space-y-4">
|
||||
<input
|
||||
type="text"
|
||||
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
|
||||
type="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
|
||||
rows={4}
|
||||
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
|
||||
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
|
||||
</button>
|
||||
@@ -60,23 +63,32 @@ const Waitlist: React.FC = () => {
|
||||
</div>
|
||||
|
||||
<div className="space-y-6">
|
||||
<div className="rounded-3xl border border-slate-200 bg-white p-8 space-y-4">
|
||||
<h4 className="text-lg font-semibold text-slate-900">What's coming</h4>
|
||||
<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 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">
|
||||
{roadmapItems.map((item) => (
|
||||
<li key={item.title} className="flex gap-3">
|
||||
<span className="mt-1 size-1.5 rounded-full bg-brand-500" />
|
||||
<div>
|
||||
<div className="font-semibold text-slate-900">{item.title}</div>
|
||||
<div>{item.description}</div>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
{roadmapItems.map((item, idx) => {
|
||||
const colors = ["bg-[#0693e3]", "bg-[#0bbf87]", "bg-[#ff7a00]"];
|
||||
return (
|
||||
<li key={item.title} className="flex gap-3">
|
||||
<span className={`mt-1 size-1.5 rounded-full ${colors[idx % colors.length]} shadow-sm`} />
|
||||
<div>
|
||||
<div className="font-semibold text-slate-900">{item.title}</div>
|
||||
<div>{item.description}</div>
|
||||
</div>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
<div className="rounded-3xl border border-slate-200 bg-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>
|
||||
<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>
|
||||
<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 flex items-center gap-2">
|
||||
<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>
|
||||
</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