From a341616e8a0cf4689b2031485fb76dabb0c6f5ec Mon Sep 17 00:00:00 2001 From: Irina Nazarova Date: Fri, 12 Dec 2025 18:53:43 -0800 Subject: [PATCH 01/15] feat: add new website pages and navigation - Add /features page showcasing AnyCable capabilities with feature grid - Add /compare hub with comparison pages for Action Cable, Pusher, and Ably - Add /customers showcase page with case studies and industry breakdown - Add /learn hub with learning paths, documentation links, and resources - Update navigation header with new page links - Add comprehensive SCSS styling for all new pages following existing design system - Maintain consistent red accent (#f64343) and clean, modern aesthetic --- WEBSITE_IMPROVEMENTS_SPEC.md | 732 +++++++++++++++++++++++ src/compare/ably/index.html | 131 ++++ src/compare/action-cable/index.html | 321 ++++++++++ src/compare/index.html | 230 +++++++ src/compare/pusher/index.html | 220 +++++++ src/customers/index.html | 270 +++++++++ src/features/index.html | 230 +++++++ src/index.scss | 4 + src/learn/index.html | 295 +++++++++ src/modules/blocks/compare.scss | 892 ++++++++++++++++++++++++++++ src/modules/blocks/customers.scss | 505 ++++++++++++++++ src/modules/blocks/features.scss | 335 +++++++++++ src/modules/blocks/learn.scss | 610 +++++++++++++++++++ src/partials/header.hbs | 4 +- 14 files changed, 4778 insertions(+), 1 deletion(-) create mode 100644 WEBSITE_IMPROVEMENTS_SPEC.md create mode 100644 src/compare/ably/index.html create mode 100644 src/compare/action-cable/index.html create mode 100644 src/compare/index.html create mode 100644 src/compare/pusher/index.html create mode 100644 src/customers/index.html create mode 100644 src/features/index.html create mode 100644 src/learn/index.html create mode 100644 src/modules/blocks/compare.scss create mode 100644 src/modules/blocks/customers.scss create mode 100644 src/modules/blocks/features.scss create mode 100644 src/modules/blocks/learn.scss diff --git a/WEBSITE_IMPROVEMENTS_SPEC.md b/WEBSITE_IMPROVEMENTS_SPEC.md new file mode 100644 index 0000000..189e12b --- /dev/null +++ b/WEBSITE_IMPROVEMENTS_SPEC.md @@ -0,0 +1,732 @@ +# AnyCable Website Improvements Specification + +**Repository:** anycable-web (anycable.io) +**Date:** December 12, 2025 +**Owner:** Irina +**Timeline:** Q1 2026 (8 weeks) + +--- + +## Current State Analysis + +### Tech Stack +- **Build:** Vite + Handlebars templates + Pug +- **Styling:** SCSS/PostCSS +- **JavaScript:** @anycable/web, Splide (carousel) +- **Deployment:** Netlify + +### Existing Structure +``` +src/ +├─ index.html (homepage) +├─ blog/ (blog posts) +├─ case-studies/ (3 case studies: Healthie, Vito, Callbell) +├─ anycasts/ (video content) +├─ pro/ (Pro product page) +├─ eula/ (legal) +└─ partials/ (header, footer, etc.) +``` + +### What's Working Well +✅ Clean, modern design +✅ Use case sections on homepage (5 industries) +✅ Case studies exist (3 published) +✅ Pro product page +✅ Blog infrastructure +✅ Fast performance (Vite + Netlify) + +### Critical Gaps +❌ **No /features page** - Features not showcased +❌ **No /compare page** - No competitor comparisons +❌ **No /customers page** - Case studies buried +❌ **No /learn page** - No central learning hub +❌ **Pricing incomplete** - AnyCable+ tiers not defined +❌ **No video hub** - Anycasts exist but not prominent +❌ **Weak CTAs** - Just "Sign up" button +❌ **No calculator** - Can't estimate costs + +--- + +## Phase One: New Pages & Content + +### 1. Features Showcase Page (/features) + +**Purpose:** Central page showing all AnyCable capabilities + +**Sections:** + +**Hero** +``` +"What Can AnyCable Do?" +Real-time features that scale, without the complexity. +``` + +**Feature Grid** (6-8 cards) +``` +┌─────────────────────┬─────────────────────┬─────────────────────┐ +│ 🔴 Presence │ 📨 Reliable Delivery│ 🔐 Signed Streams │ +│ Know who's online │ Never lose messages │ Zero-config Hotwire │ +│ [3-line code] │ [3-line code] │ [3-line code] │ +│ Learn more → │ Learn more → │ Learn more → │ +├─────────────────────┼─────────────────────┼─────────────────────┤ +│ 📊 Instrumentation │ 🚀 High Performance │ 🔧 Multiple Backends│ +│ Built-in monitoring │ 10K+ concurrent │ Rails, Laravel, JS │ +│ [Screenshot] │ [Benchmark chart] │ [Code examples] │ +│ Learn more → │ Learn more → │ Learn more → │ +└─────────────────────┴─────────────────────┴─────────────────────┘ +``` + +**Feature Comparison Table** +| Feature | AnyCable Open Source | AnyCable Pro | AnyCable+ (Managed) | Action Cable | Pusher | +|---------|---------------------|--------------|---------------------|--------------|--------| +| Performance | ⚡️⚡️⚡️ | ⚡️⚡️⚡️⚡️ | ⚡️⚡️⚡️⚡️ | ⚡️ | ⚡️⚡️ | +| Presence | ✓ | ✓ | ✓ | ✗ | ✓ | +| Message History | ✓ | ✓ | ✓ | ✗ | ✓ | +| Self-hosted | ✓ | ✓ | ✗ | ✓ | ✗ | +| Managed Option | ✗ | ✗ | ✓ | ✗ | ✓ | +| Pricing | Free | $490/yr | $29-299/mo | Free | $49+/mo | + +**Architecture Diagram** +- Visual showing: Browser → AnyCable-Go → Rails → Redis +- Highlight performance benefits + +**CTA Section** +``` +"Ready to add real-time features?" +[Get Started - Free] [See Pricing] [Book a Demo] +``` + +**File to create:** `src/features/index.html` + +--- + +### 2. Comparison Hub (/compare) + +**Purpose:** Help developers choose between AnyCable and alternatives + +**Pages to create:** + +**`/compare/action-cable`** +``` +# AnyCable vs Action Cable + +## TL;DR +AnyCable is 10x faster and scales better, but requires running an additional server (AnyCable-Go). +Use Action Cable if: Simple app, < 500 concurrent connections +Use AnyCable if: Need performance, > 500 connections, advanced features + +## Performance Comparison +[Benchmark charts: throughput, latency, memory] + +## Feature Comparison +[Side-by-side table] + +## Cost Comparison +[Infrastructure cost calculator] + +## When to Migrate +- [ ] More than 500 concurrent connections +- [ ] CPU usage > 60% on Action Cable +- [ ] Need presence tracking +- [ ] Need message history +- [ ] Deploying to resource-constrained platforms + +[Read Migration Guide →] [See Pricing →] +``` + +**`/compare/pusher`** +``` +# AnyCable vs Pusher + +## TL;DR +AnyCable gives you full control and costs less at scale, but you manage infrastructure. +Use Pusher if: Just getting started, want zero ops +Use AnyCable if: Cost-conscious, need Rails integration, want control + +## Pricing Comparison +[Calculator: connections → monthly cost comparison] + +AnyCable+ (managed): $29-299/mo +Pusher: $49-499/mo (same scale) + +Self-hosted AnyCable: ~$50-100/mo infrastructure + +## Feature Parity +✓ Both have presence +✓ Both have message history +✓ AnyCable has native Rails integration +✓ Pusher has more SDKs + +[Try AnyCable+ Free →] [Migration Guide →] +``` + +**`/compare/ably`** +``` +# AnyCable vs Ably + +Similar comparison format to Pusher +``` + +**`/compare`** (hub page) +``` +"Find the Right Real-Time Solution" + +Compare AnyCable with: +- [Action Cable] - Rails default +- [Pusher] - Popular SaaS +- [Ably] - Enterprise SaaS +- [Socket.io] - Node.js WebSockets + +Or jump to: +- [Feature Comparison Matrix] +- [Pricing Calculator] +- [Migration Guides] +``` + +**Files to create:** +- `src/compare/index.html` +- `src/compare/action-cable/index.html` +- `src/compare/pusher/index.html` +- `src/compare/ably/index.html` + +--- + +### 3. Customer Showcase (/customers) + +**Purpose:** Social proof and diverse use cases + +**Structure:** + +**Hero** +``` +"Who Uses AnyCable?" +Trusted by companies building real-time features that scale. +``` + +**Logo Wall** +- Healthie, Callbell, Vito (existing) +- + 10-15 more logos (collect from users) + +**Featured Case Studies** (expand existing) + +Currently have: Healthie, Callbell, Vito +Add: 3-5 more diverse examples + +**Format for each:** +``` +[Company Logo] + +"Quote from CTO/Developer about results" + +Industry: Healthcare / SaaS / E-commerce +Use Case: Chat / Collaboration / Notifications +Scale: 50K connections, 1M messages/day +Results: 10x performance improvement, 60% cost reduction + +[Read Full Story →] +``` + +**Industry Breakdown** +``` +Healthcare: Healthie, [others] +SaaS: Callbell, [others] +E-commerce: [collect examples] +Gaming: [collect examples] +Fintech: [collect examples] +``` + +**Community Projects** +- Open source projects using AnyCable +- Example apps +- Contributions + +**CTA** +``` +"Want to be featured?" +Share your AnyCable story +[Submit Your Story →] +``` + +**Files to create:** +- `src/customers/index.html` +- Expand existing case studies with more details + +--- + +### 4. Learning Hub (/learn) + +**Purpose:** Central place for all educational content + +**Structure:** + +**Hero** +``` +"Learn AnyCable" +From zero to real-time in 30 minutes. +``` + +**Getting Started Paths** +``` +┌──────────────────┬──────────────────┬──────────────────┐ +│ New to AnyCable │ Migrating │ Advanced │ +├──────────────────┼──────────────────┼──────────────────┤ +│ Quick Start │ From Action Cable│ Performance │ +│ First Channel │ From Pusher │ Scaling │ +│ Deploy to Heroku │ From Ably │ Architecture │ +│ 30 min total │ 1-2 hours │ Deep dives │ +│ [Start →] │ [Start →] │ [Start →] │ +└──────────────────┴──────────────────┴──────────────────┘ +``` + +**Video Tutorials** (when created) +``` +📺 Video Library + +[Thumbnail] What is AnyCable? (5 min) +[Thumbnail] Setup with Rails 8 (10 min) +[Thumbnail] Building a Chat App (25 min) +[Thumbnail] AnyCable Pro Features (8 min) + +[See All Videos →] +``` + +**Blog Posts by Topic** +``` +📝 Latest Articles + +Getting Started +→ Rails 8 + AnyCable: Perfect Match +→ Your First Real-time Feature + +Use Cases +→ Building Production Chat +→ Real-time Collaboration + +Performance +→ Handling 10K Connections +→ Benchmarks & Optimization + +[See All Posts →] +``` + +**Documentation** +``` +📚 Documentation + +[Icon] Getting Started Guide +[Icon] Features Overview +[Icon] API Reference +[Icon] Troubleshooting + +[Browse Docs →] +``` + +**Community** +``` +👥 Community & Support + +[Discord] Join Discord - Live chat +[GitHub] GitHub Discussions - Q&A +[Twitter] Follow @any_cable - Updates + +[Get Help →] +``` + +**File to create:** `src/learn/index.html` + +--- + +### 5. Enhanced Pricing Page (/pricing) + +**Current state:** Has Pro pricing, no AnyCable+ tiers + +**New structure:** + +**Hero** +``` +"Pricing That Scales With You" +Choose the deployment that fits your needs. +``` + +**Pricing Tiers** (3 main options) + +``` +┌─────────────────┬─────────────────┬─────────────────┐ +│ Open Source │ AnyCable+ │ AnyCable Pro │ +│ Free Forever │ Managed Service │ On-Premise │ +├─────────────────┼─────────────────┼─────────────────┤ +│ $0 │ Starting at $29 │ $490/year │ +│ │ │ │ +│ ✓ Core features │ Everything in │ Everything + │ +│ ✓ Self-hosted │ Open Source + │ │ +│ ✓ Unlimited │ ✓ Hosted infra │ ✓ JWT auth │ +│ ✓ Community │ ✓ Auto-scaling │ ✓ GraphQL │ +│ │ ✓ Monitoring │ ✓ Binary │ +│ Best for: │ ✓ Email support │ ✓ Long polling │ +│ Side projects │ │ ✓ Hot reload │ +│ Learning │ Best for: │ │ +│ │ Production apps │ Best for: │ +│ [Get Started] │ Small-mid SaaS │ Enterprise │ +│ │ │ High security │ +│ │ [Try Free] │ [Buy Now] │ +└─────────────────┴─────────────────┴─────────────────┘ +``` + +**AnyCable+ Detailed Tiers** + +``` +Managed Service (AnyCable+) + +┌─────────────────────────────────────────────────────┐ +│ Free Tier │ +│ $0/month │ +│ • Up to 100 concurrent connections │ +│ • US East region only │ +│ • Community support │ +│ • Perfect for: Side projects, prototypes │ +│ [Start Free] │ +├─────────────────────────────────────────────────────┤ +│ Starter │ +│ $29/month │ +│ • Up to 1,000 concurrent connections │ +│ • 3 regions (US, EU, Asia) │ +│ • Email support │ +│ • 99% uptime SLA │ +│ • Perfect for: Small SaaS, MVP │ +│ [Start Trial] │ +├─────────────────────────────────────────────────────┤ +│ Growth │ +│ $99/month │ +│ • Up to 10,000 concurrent connections │ +│ • Global regions │ +│ • Priority support │ +│ • Custom domain (wss://realtime.yourapp.com) │ +│ • 99.9% uptime SLA │ +│ • Perfect for: Production apps, growing SaaS │ +│ [Start Trial] │ +├─────────────────────────────────────────────────────┤ +│ Scale │ +│ $299/month │ +│ • Up to 100,000 concurrent connections │ +│ • Dedicated resources │ +│ • Premium support │ +│ • 99.95% uptime SLA │ +│ • Perfect for: Large apps, enterprise │ +│ [Contact Sales] │ +└─────────────────────────────────────────────────────┘ +``` + +**Feature Comparison Table** +| Feature | Open Source | AnyCable+ | Pro | +|---------|-------------|-----------|-----| +| WebSocket server | ✓ | ✓ | ✓ | +| Presence tracking | ✓ | ✓ | ✓ | +| Reliable streams | ✓ | ✓ | ✓ | +| Signed streams | ✓ | ✓ | ✓ | +| Instrumentation | ✓ | ✓ | ✓ | +| Infrastructure | Self-host | Managed | Self-host | +| Monitoring | DIY | Included | DIY | +| Support | Community | Email/Priority | Email | +| JWT auth | ✗ | ✗ | ✓ | +| GraphQL | ✗ | ✗ | ✓ | +| Binary formats | ✗ | ✗ | ✓ | +| Long polling | ✗ | ✗ | ✓ | +| Hot reload | ✗ | ✗ | ✓ | + +**Cost Calculator** (interactive) +``` +"Estimate Your Costs" + +Concurrent connections: [slider: 100 - 100,000] +Messages per second: [slider: 10 - 10,000] + +Your estimated costs: + +AnyCable+ (managed): $XX/month + Recommended tier: Starter/Growth/Scale + +Self-hosted AnyCable: $XX/month + Infrastructure: AWS t3.medium ($35) + Redis: ElastiCache ($50) + Total: ~$85/month + +Pusher (equivalent): $XXX/month + +[Sign Up for AnyCable+] [Try Self-Hosted] +``` + +**FAQ Section** +``` +Common Questions + +Q: What happens if I exceed my connection limit? +A: We'll notify you and help upgrade. No service interruption. + +Q: Can I switch between plans? +A: Yes, upgrade/downgrade anytime. Prorated billing. + +Q: What's included in support? +A: Starter: Email (48h), Growth: Priority (12h), Scale: Premium (4h) + +Q: Do you offer discounts for nonprofits? +A: Yes! Contact sales@anycable.io + +[See All FAQs →] +``` + +**Files to update:** +- `src/pricing/index.html` (major enhancement) +- Add calculator JavaScript +- Add pricing comparison logic + +--- + +### 6. Homepage Enhancements + +**Current homepage is good, but add:** + +**Feature Highlight Section** (above use cases) +``` +"Why AnyCable?" + +[Icon] Performance at Scale +Handle 10K+ concurrent connections +[Learn More →] + +[Icon] Reliable Delivery +Never lose messages, automatic recovery +[Learn More →] + +[Icon] Flexible Deployment +Open source, managed, or Pro +[Compare Options →] +``` + +**Social Proof Section** (before footer) +``` +"Trusted by Teams Worldwide" + +[Logo Grid: 12-16 company logos] + +"AnyCable handles our 50K concurrent connections effortlessly" +— CTO, Healthcare SaaS + +[See Customer Stories →] +``` + +**Better CTAs** +- Primary: "Try AnyCable+ Free" (not just "Sign up") +- Secondary: "View Pricing" +- Tertiary: "Read Docs" + +**Files to update:** +- `src/index.html` + +--- + +## Phase One Deliverables Summary + +### New Pages (6 pages) +- ✅ `/features` - Feature showcase +- ✅ `/compare` - Comparison hub (4 sub-pages) +- ✅ `/customers` - Customer showcase +- ✅ `/learn` - Learning hub +- ✅ Enhanced `/pricing` with calculator +- ✅ Enhanced homepage + +### New Content +- ✅ Feature comparison tables +- ✅ Cost calculator (interactive) +- ✅ Competitor comparisons (3 pages) +- ✅ Logo wall (collect 12-16 logos) +- ✅ 3-5 new case studies +- ✅ FAQ section + +### Interactive Elements +- ✅ Pricing calculator +- ✅ Feature comparison filters +- ✅ Video embeds (when created) + +--- + +## Technical Implementation + +### File Structure +``` +src/ +├─ index.html (enhanced) +├─ features/ +│ └─ index.html (new) +├─ compare/ +│ ├─ index.html (new) +│ ├─ action-cable/index.html (new) +│ ├─ pusher/index.html (new) +│ └─ ably/index.html (new) +├─ customers/ +│ └─ index.html (new) +├─ learn/ +│ └─ index.html (new) +├─ pricing/ +│ └─ index.html (enhanced) +├─ js/ +│ └─ calculator.js (new) +└─ partials/ + ├─ pricing_table.hbs (new) + ├─ comparison_table.hbs (new) + └─ feature_card.hbs (new) +``` + +### New Components Needed + +**Pricing Calculator** (`js/calculator.js`) +```javascript +// Interactive slider +// Calculates costs based on connections/messages +// Shows recommendations +// GTM tracking for interactions +``` + +**Comparison Table** (`partials/comparison_table.hbs`) +```handlebars +{{! Reusable comparison table component }} +{{! Takes data array, renders side-by-side comparison }} +``` + +**Feature Card** (`partials/feature_card.hbs`) +```handlebars +{{! Reusable feature showcase card }} +{{! Icon, title, description, code snippet, CTA }} +``` + +### SEO Optimization + +**Meta tags for new pages:** +- `/features`: "AnyCable Features | Real-time WebSocket Server" +- `/compare/action-cable`: "AnyCable vs Action Cable | Performance Comparison" +- `/customers`: "AnyCable Customer Stories | Case Studies" +- `/learn`: "Learn AnyCable | Tutorials & Guides" + +**Schema.org markup:** +- Product schema for pricing +- Review schema for case studies +- FAQ schema for pricing questions + +--- + +## Success Metrics + +### Traffic +- Organic search: +200% +- Direct traffic: +50% +- Referral traffic: +100% + +### Engagement +- Bounce rate: 45% → 30% +- Pages per session: 2.1 → 3.5 +- Time on site: 1:30 → 3:00 + +### Conversion +- Homepage → Trial: 3% → 7% +- Pricing page → Trial: 8% → 15% +- Features page → Trial: Track new +- Compare pages → Trial: Track new + +### SEO Rankings +- "anycable vs action cable" → Position #1 +- "rails websocket performance" → Top 5 +- "action cable alternative" → Position #1-3 +- "websocket server rails" → Top 5 + +--- + +## Implementation Timeline + +### Week 1-2: Foundation +- Features showcase page +- Comparison hub structure +- Pricing page redesign + +### Week 3-4: Comparison & Customers +- 3 comparison pages (Action Cable, Pusher, Ably) +- Customer showcase page +- Collect case studies (3-5 new) + +### Week 5-6: Learning & Interactive +- Learning hub page +- Pricing calculator +- Interactive comparisons + +### Week 7-8: Polish & Launch +- Homepage enhancements +- SEO optimization +- Cross-linking +- Testing +- Launch announcement + +--- + +## Content Needs + +### To Collect +- [ ] 12-16 company logos (permission to use) +- [ ] 3-5 new customer stories (interviews) +- [ ] Benchmark data (vs Action Cable, Pusher) +- [ ] Infrastructure cost data (AWS, Heroku pricing) +- [ ] Feature screenshots (monitoring, dashboards) + +### To Write +- [ ] Feature descriptions (8 features) +- [ ] Comparison content (3 pages, ~1000 words each) +- [ ] Case study write-ups (3-5 stories) +- [ ] Pricing FAQs (10-15 questions) +- [ ] Meta descriptions (all new pages) + +### To Design +- [ ] Feature icons (8 custom icons) +- [ ] Comparison charts (performance, cost) +- [ ] Architecture diagrams (can reuse from docs) +- [ ] Calculator UI +- [ ] Logo wall layout + +--- + +## Questions & Decisions + +1. **AnyCable+ Pricing - Confirm tiers:** + - Free: 100 connections? + - Starter: $29/mo for 1K? + - Growth: $99/mo for 10K? + - Scale: $299/mo for 100K? + +2. **Calculator scope:** + - Just connection-based pricing? + - Include infrastructure costs? + - Show Pusher/Ably comparison? + +3. **Case studies:** + - Which customers can we feature? + - Do we have permission for logos? + - Need new interviews? + +4. **Video content:** + - Embed on /learn page? + - Separate /videos page? + - YouTube channel setup? + +5. **A/B testing:** + - Test different CTAs? + - Test pricing presentation? + - Tools: Netlify edge functions? + +--- + +## Ready to Start + +Can begin immediately with: +1. Features showcase page (/features) +2. Pricing page enhancement with tiers +3. Comparison page (vs Action Cable) + +These require minimal design work and leverage existing content. + +Let's make anycable.io the best WebSocket product site! 🚀 diff --git a/src/compare/ably/index.html b/src/compare/ably/index.html new file mode 100644 index 0000000..66fa5af --- /dev/null +++ b/src/compare/ably/index.html @@ -0,0 +1,131 @@ + + + {{> dochead title="AnyCable vs Ably | Rails Real-Time Comparison" description="Compare AnyCable and Ably for Rails applications. Native integration vs managed service."}} + +
+ {{> header}} +
+ +
+
+
+

AnyCable vs Ably

+

+ Native Rails integration vs enterprise managed platform +

+
+
+
+ + +
+
+

TL;DR

+
+

+ AnyCable offers native Rails integration and full control, while Ably provides an enterprise-grade managed platform with extensive SDKs. +

+
+
+

Use Ably if:

+
    +
  • Need enterprise SLAs
  • +
  • Want managed infrastructure
  • +
  • Using multiple platforms/languages
  • +
  • Need global edge delivery
  • +
+
+
+

Use AnyCable if:

+
    +
  • Building with Rails
  • +
  • Want native Action Cable compatibility
  • +
  • Prefer self-hosting option
  • +
  • Need flexible deployment
  • +
+
+
+
+
+
+ + +
+
+

Feature Comparison

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FeatureAblyAnyCable
DeploymentManaged onlySelf-hosted or Managed (AnyCable+)
Rails IntegrationSDK/Adapter✓ Native Action Cable
Presence
Message History✓ Extensive
Open Source
Starting Price$29/moFree / $29/mo / $490/yr
SDKs25+ languagesRails, JavaScript, any via RPC
Data ControlAbly's infrastructureYour infrastructure
+
+
+
+ + +
+
+

Rails-Native Real-Time

+

+ Get started with AnyCable's native Rails integration +

+ +
+
+
+ {{> footer }} +
+ + + diff --git a/src/compare/action-cable/index.html b/src/compare/action-cable/index.html new file mode 100644 index 0000000..ae6693f --- /dev/null +++ b/src/compare/action-cable/index.html @@ -0,0 +1,321 @@ + + + {{> dochead title="AnyCable vs Action Cable | Performance Comparison" description="AnyCable is 10x faster than Action Cable and scales better. Learn when to migrate and how to get started."}} + +
+ {{> header}} +
+ +
+
+
+

AnyCable vs Action Cable

+

+ The performance upgrade for your Rails real-time features +

+
+
+
+ + +
+
+

TL;DR

+
+

+ AnyCable is 10x faster and scales better than Action Cable, but requires running an additional server (AnyCable-Go). +

+
+
+

Use Action Cable if:

+
    +
  • Simple app with minimal real-time features
  • +
  • Less than 500 concurrent connections
  • +
  • You want zero additional infrastructure
  • +
+
+
+

Use AnyCable if:

+
    +
  • Need high performance (> 500 connections)
  • +
  • CPU usage > 60% on Action Cable
  • +
  • Need advanced features (presence, history)
  • +
  • Deploying to resource-constrained platforms
  • +
+
+
+
+
+
+ + +
+
+
+

Performance Comparison

+
+

+ Action Cable runs in Ruby, which means every WebSocket connection consumes Ruby process resources. + This becomes a bottleneck as your application scales. +

+

+ AnyCable-Go is written in Go, a compiled language designed for concurrency. + It handles WebSocket connections efficiently while delegating business logic to your Rails app via RPC. +

+
+ +
+
+
10,000+
+
Concurrent connections per server
+
AnyCable
+
+
+
~500
+
Concurrent connections per server
+
Action Cable
+
+
+ +
+
+
< 100 MB
+
Memory usage (10K connections)
+
AnyCable
+
+
+
~2 GB
+
Memory usage (10K connections)
+
Action Cable
+
+
+
+
+
+ + +
+
+

Feature Comparison

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FeatureAction CableAnyCable
Drop-in compatibilityN/A✓ 100% compatible
Concurrent connections~500 per server10,000+ per server
Memory usageHigh (Ruby processes)Low (Go efficiency)
Presence tracking✗ (manual implementation)✓ Built-in
Reliable streams✓ Message history & recovery
Signed streams (Hotwire)✓ Zero-config support
InstrumentationBasicComprehensive metrics
Deployment complexitySimple (included in Rails)Additional server required
Infrastructure costHigher at scaleLower at scale
+
+
+
+ + +
+
+

When to Migrate

+
+

+ Consider migrating to AnyCable if you're experiencing any of these: +

+ +
+
+
+
+ High server load: CPU usage consistently > 60% on WebSocket servers +
+
+
+
+
+ Connection limits: More than 500 concurrent WebSocket connections +
+
+
+
+
+ High infrastructure costs: Paying for multiple servers just for WebSockets +
+
+
+
+
+ Need presence: Want to show who's online without building it yourself +
+
+
+
+
+ Message reliability: Need message history and automatic recovery +
+
+
+
+
+ Platform constraints: Deploying to Heroku, Fly.io, or other resource-limited platforms +
+
+
+ + +
+
+
+ + +
+
+

How AnyCable Works with Rails

+
+
+
+
+
Browser
+
+
↓ WebSocket
+
+
AnyCable-Go
+
Handles connections in Go
+
+
↓ gRPC
+
+
Rails App
+
Your business logic
+
+
+
+
Redis
+
Pub/Sub
+
+
+
+
+

+ AnyCable separates connection handling from business logic: +

+
    +
  • AnyCable-Go maintains WebSocket connections efficiently in Go
  • +
  • Your Rails app handles channel subscriptions and business logic via gRPC
  • +
  • Redis is used for pub/sub, just like Action Cable
  • +
  • No code changes needed—AnyCable is 100% compatible with Action Cable API
  • +
+
+
+
+
+ + +
+
+

Getting Started

+
+

+ Migrating from Action Cable to AnyCable takes about 15 minutes: +

+ +
+
+
1
+

Add the gem

+
gem 'anycable-rails'
+
+ +
+
2
+

Install AnyCable-Go

+
brew install anycable-go
+# or download binary
+
+ +
+
3
+

Start the server

+
anycable-go
+
+ +
+
4
+

Done!

+

Your existing Action Cable code works without changes

+
+
+ + +
+
+
+ + +
+
+

Ready to Upgrade?

+

+ Keep your Action Cable code, get 10x better performance +

+ +
+
+
+ {{> footer }} +
+ + + diff --git a/src/compare/index.html b/src/compare/index.html new file mode 100644 index 0000000..16a5ae7 --- /dev/null +++ b/src/compare/index.html @@ -0,0 +1,230 @@ + + + {{> dochead title="Compare | AnyCable" description="Compare AnyCable with other real-time solutions: Action Cable, Pusher, Ably, and Socket.io"}} + +
+ {{> header}} +
+ +
+
+
+

Find the Right Real-Time Solution

+

+ Compare AnyCable with other popular WebSocket solutions +

+
+
+
+ + +
+ +
+ + +
+
+

Quick Feature Comparison

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FeatureAnyCableAction CablePusherAbly
DeploymentSelf-hosted + ManagedSelf-hosted onlyManaged onlyManaged only
Rails Integration✓ Native✓ Built-in✗ SDK only✗ SDK only
Concurrent Connections10,000+~500100,000+100,000+
Presence Tracking
Message History
Open Source
Starting PriceFree / $29/moFree$49/mo$29/mo
+
+ +
+

+ 💡 Need help choosing? Each solution has its strengths. + Click through to see detailed comparisons for your specific use case. +

+
+
+
+ + +
+
+

Comparison Tools

+ +
+
+
💰
+

Pricing Calculator

+

+ Estimate your costs across different solutions based on your connection volume. +

+ Try Calculator → +
+ +
+
📚
+

Migration Guides

+

+ Step-by-step guides for migrating from Action Cable, Pusher, or other solutions. +

+ View Guides → +
+ +
+
+

Performance Benchmarks

+

+ Real-world performance data comparing throughput, latency, and resource usage. +

+ See Benchmarks → +
+
+
+
+ + +
+
+

Ready to Try AnyCable?

+

+ Start with open source or try our managed service +

+ +
+
+
+ {{> footer }} +
+ + + diff --git a/src/compare/pusher/index.html b/src/compare/pusher/index.html new file mode 100644 index 0000000..415c033 --- /dev/null +++ b/src/compare/pusher/index.html @@ -0,0 +1,220 @@ + + + {{> dochead title="AnyCable vs Pusher | Cost & Feature Comparison" description="AnyCable gives you full control and costs 60% less at scale. Compare features, pricing, and Rails integration."}} + +
+ {{> header}} +
+ +
+
+
+

AnyCable vs Pusher

+

+ Full control, better Rails integration, lower costs +

+
+
+
+ + +
+
+

TL;DR

+
+

+ AnyCable gives you full control and costs 60% less at scale, but you manage infrastructure (or use AnyCable+). +

+
+
+

Use Pusher if:

+
    +
  • Just getting started
  • +
  • Want zero ops overhead
  • +
  • Need global edge network
  • +
  • Don't use Rails
  • +
+
+
+

Use AnyCable if:

+
    +
  • Cost-conscious at scale
  • +
  • Need Rails integration
  • +
  • Want full control over data
  • +
  • Prefer self-hosting
  • +
+
+
+
+
+
+ + +
+
+
+

Pricing Comparison

+
+

+ Pusher's pricing scales with connections, which can become expensive as your app grows. + With AnyCable, you pay once for the Pro license or a flat monthly fee for managed hosting. +

+
+ +
+
+
$29-299
+
Per month (100-10K connections)
+
AnyCable+ (Managed)
+
+
+
$49-499
+
Per month (100-10K connections)
+
Pusher
+
+
+ +
+
+
$490/yr
+
One-time license (unlimited)
+
AnyCable Pro
+
+
+
~$50-100
+
Monthly infrastructure (self-hosted)
+
AnyCable Open Source
+
+
+
+
+
+ + +
+
+

Feature Comparison

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FeaturePusherAnyCable
DeploymentManaged onlySelf-hosted or Managed (AnyCable+)
Rails IntegrationSDK only✓ Native Action Cable compatibility
Presence
Message History
Data ControlPusher's serversYour infrastructure
Open Source
Price ScalingIncreases with connectionsFlat (Pro) or predictable tiers (AnyCable+)
SDKs20+ languagesRails, JavaScript, any backend via RPC
Global Edge✓ Built-inVia deployment strategy
+
+
+
+ + +
+
+

Migrating from Pusher

+
+

+ If you're using Pusher with Rails, migrating to AnyCable gives you better integration and lower costs: +

+ +
+
+
+
+ Replace Pusher SDK with native Action Cable channels +
+
+
+
+
+ Use cable_ready or turbo_stream for server-to-client updates +
+
+
+
+
+ Deploy AnyCable-Go for high-performance WebSocket handling +
+
+
+
+
+ Keep your business logic in Rails where it belongs +
+
+
+ + +
+
+
+ + +
+
+

Ready to Save on Real-Time?

+

+ Try AnyCable+ free or self-host with open source +

+ +
+
+
+ {{> footer }} +
+ + + diff --git a/src/customers/index.html b/src/customers/index.html new file mode 100644 index 0000000..cc40c0d --- /dev/null +++ b/src/customers/index.html @@ -0,0 +1,270 @@ + + + {{> dochead title="Customers | AnyCable" description="Companies and teams building real-time features that scale with AnyCable. Customer stories and case studies."}} + +
+ {{> header}} +
+ +
+
+
+

Who Uses AnyCable?

+

+ Trusted by companies building real-time features that scale +

+
+
+
+ + +
+
+

Trusted By

+
+ + + + + + + + + + + +
+
+
+ + + + + +
+
+

Industries Using AnyCable

+ +
+
+
🏥
+

Healthcare

+

+ HIPAA-compliant patient-doctor communication, secure messaging, real-time health monitoring +

+
+ Healthie, Joint Academy, and others +
+
+ +
+
💬
+

SaaS & Collaboration

+

+ Team chat, customer support, real-time collaboration tools, notifications +

+
+ Callbell, Vito, Circle, LiveVoice, Welcome +
+
+ +
+
🎓
+

Education & Events

+

+ Live polling, Q&A sessions, interactive presentations, virtual events +

+
+ Poll Everywhere +
+
+ +
+
🤖
+

AI & Automation

+

+ Streaming AI responses, voice processing, real-time data pipelines +

+
+ Canfy, Sera, and others +
+
+ +
+
🛒
+

E-commerce & Marketing

+

+ Real-time updates, live notifications, Hotwire-powered interfaces +

+
+ ClickFunnels, and others +
+
+ +
+
🚗
+

IoT & Infrastructure

+

+ GPS tracking, EV charging data (OCPP), device monitoring +

+
+ Various IoT platforms +
+
+
+
+
+ + +
+
+

Open Source Community

+

+ Join thousands of developers building with AnyCable +

+ +
+
+
1.9K+
+
GitHub Stars
+
+
+
100K+
+
Downloads
+
+
+
50+
+
Contributors
+
+
+ + +
+
+ + +
+
+

Want to Be Featured?

+

+ Share your AnyCable success story with the community +

+ +
+
+
+ {{> footer }} +
+ + + diff --git a/src/features/index.html b/src/features/index.html new file mode 100644 index 0000000..e6ea464 --- /dev/null +++ b/src/features/index.html @@ -0,0 +1,230 @@ + + + {{> dochead title="Features | AnyCable" description="Real-time features that scale, without the complexity. Presence tracking, reliable delivery, signed streams, and more."}} + +
+ {{> header}} +
+ +
+
+
+

What Can AnyCable Do?

+

+ Real-time features that scale, without the complexity. +

+
+
+
+ + +
+
+ +
+
🔴
+

Presence Tracking

+

Know who's online in real-time. Track user status across channels with built-in presence support.

+
cable.presenceChannel(
+  "chat:lobby"
+)
+ Learn more → +
+ +
+
📨
+

Reliable Delivery

+

Never lose messages. Automatic recovery and message history ensure reliable real-time communication.

+
channel.on("message",
+  { history: 100 }
+)
+ Learn more → +
+ +
+
🔐
+

Signed Streams

+

Zero-config Hotwire integration. Secure, server-signed stream names work out of the box.

+
turbo_stream_from
+  current_user
+
+ Learn more → +
+ + +
+
📊
+

Instrumentation

+

Built-in monitoring and metrics. Track connections, messages, and performance out of the box.

+
+
Grafana Dashboard
+
+ Learn more → +
+ +
+
🚀
+

High Performance

+

Handle 10,000+ concurrent connections with ease. Built in Go for maximum throughput and minimal latency.

+
+
Performance Chart
+
+ Learn more → +
+ +
+
🔧
+

Multiple Backends

+

Works with Rails Action Cable, but also supports JavaScript/TypeScript and any backend via RPC.

+
// Pure JS
+anycable.subscribe(
+  { channel: "chat" }
+)
+ Learn more → +
+ + +
+
Pro
+
🔑
+

JWT Authentication

+

Stateless authentication with JWT tokens. No backend required for connection verification.

+ Learn more → +
+ +
+
Pro
+
📦
+

Binary Protocols

+

MessagePack and Protobuf support for faster, more efficient data transfer.

+ Learn more → +
+ +
+
Pro
+
🔄
+

Hot Reload

+

Update server configuration without dropping connections. Zero-downtime deployments.

+ Learn more → +
+
+
+ + +
+
+

Compare Features Across Editions

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FeatureOpen SourceAnyCable ProAnyCable+Action CablePusher
Performance⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️
Presence Tracking
Message History
Signed Streams
Self-hosted
Managed Option
JWT Auth
Binary Protocols
Starting PriceFree$490/yr$29/moFree$49/mo
+
+ +
+
+ + +
+
+

Ready to Add Real-Time Features?

+

+ Choose the deployment that fits your needs +

+ +
+
+
+ {{> footer }} +
+ + + diff --git a/src/index.scss b/src/index.scss index 58b459a..d30dd8a 100644 --- a/src/index.scss +++ b/src/index.scss @@ -29,4 +29,8 @@ @import './modules/blocks/blog.scss'; @import './modules/blocks/blog-header.scss'; @import './modules/blocks/demo.scss'; +@import './modules/blocks/features.scss'; +@import './modules/blocks/compare.scss'; +@import './modules/blocks/customers.scss'; +@import './modules/blocks/learn.scss'; @import './modules/common.scss'; diff --git a/src/learn/index.html b/src/learn/index.html new file mode 100644 index 0000000..555b898 --- /dev/null +++ b/src/learn/index.html @@ -0,0 +1,295 @@ + + + {{> dochead title="Learn AnyCable | Tutorials & Guides" description="Learn AnyCable from zero to real-time in 30 minutes. Tutorials, guides, videos, and documentation."}} + +
+ {{> header}} +
+ +
+
+
+

Learn AnyCable

+

+ From zero to real-time in 30 minutes +

+
+
+
+ + +
+
+

Choose Your Path

+ +
+
+
Beginner
+

New to AnyCable

+

+ Start here if you're adding real-time features to your Rails app for the first time. +

+
    +
  • Quick Start Guide
  • +
  • Your First Channel
  • +
  • Deploy to Production
  • +
  • Basic Troubleshooting
  • +
+
⏱ ~30 minutes
+ Start Learning → +
+ +
+
Intermediate
+

Migrating

+

+ Already using Action Cable, Pusher, or Ably? Learn how to migrate to AnyCable. +

+
    +
  • From Action Cable
  • +
  • From Pusher/Ably
  • +
  • Testing Strategy
  • +
  • Zero-downtime Migration
  • +
+
⏱ ~1-2 hours
+ Start Migrating → +
+ +
+
Advanced
+

Deep Dives

+

+ Master AnyCable's advanced features and optimize for production scale. +

+
    +
  • Performance Tuning
  • +
  • Scaling Strategies
  • +
  • Architecture Patterns
  • +
  • Pro Features
  • +
+
⏱ Deep dives
+ Explore Guides → +
+
+
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+

Ready to Build?

+

+ Start adding real-time features to your app today +

+ +
+
+
+ {{> footer }} +
+ + + diff --git a/src/modules/blocks/compare.scss b/src/modules/blocks/compare.scss new file mode 100644 index 0000000..cd9b9a1 --- /dev/null +++ b/src/modules/blocks/compare.scss @@ -0,0 +1,892 @@ +// Compare Hero +$className: 'compare-hero'; + +.#{$className} { + display: flex; + justify-content: center; + width: 100%; + min-height: 40vh; + background-color: $backgroundSecondaryColor; + border-bottom: 1px solid $borderPrimaryColor; + + &__content { + max-width: 1200px; + padding: 100px 64px 60px; + text-align: center; + + @include mediaMax($tablet) { + padding: 80px 24px 40px; + } + } + + &__title { + font-size: 69px; + line-height: 1.2; + font-weight: 700; + margin-bottom: 24px; + + @include mediaMax($tablet) { + font-size: 46px; + } + + @include mediaMax($mobile) { + font-size: 40px; + } + } + + &__subtitle { + font-size: 24px; + line-height: 1.6; + color: $fontSecondaryColor; + + @include mediaMax($mobile) { + font-size: 20px; + } + } +} + +// Compare Options +$className: 'compare-options'; + +.#{$className} { + display: flex; + justify-content: center; + padding: 80px 64px; + + @include mediaMax($tablet) { + padding: 60px 24px; + } + + &__wrapper { + max-width: 1400px; + width: 100%; + } + + &__title { + font-size: 32px; + line-height: 1.3; + font-weight: 700; + margin-bottom: 48px; + + @include mediaMax($mobile) { + font-size: 24px; + margin-bottom: 32px; + } + } + + &__grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 32px; + + @include mediaMax($tablet) { + grid-template-columns: 1fr; + gap: 24px; + } + } +} + +// Compare Card +$className: 'compare-card'; + +.#{$className} { + display: flex; + flex-direction: column; + padding: 32px; + background-color: $backgroundPrimaryColor; + border: 2px solid $borderPrimaryColor; + border-radius: 8px; + text-decoration: none; + color: inherit; + transition: all 200ms; + position: relative; + + &:hover { + border-color: $accentPrimaryColor; + transform: translateY(-2px); + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1); + } + + &_coming-soon { + opacity: 0.6; + pointer-events: none; + } + + &__header { + display: flex; + justify-content: space-between; + align-items: flex-start; + margin-bottom: 16px; + } + + &__title { + font-size: 28px; + line-height: 1.3; + font-weight: 700; + } + + &__badge { + padding: 4px 12px; + background-color: #e0e0e0; + color: $fontSecondaryColor; + font-size: 12px; + font-weight: 600; + border-radius: 4px; + text-transform: uppercase; + letter-spacing: 0.5px; + white-space: nowrap; + + &_saas { + background-color: #e3f2fd; + color: #1976d2; + } + + &_node { + background-color: #e8f5e9; + color: #388e3c; + } + } + + &__description { + font-size: 17px; + line-height: 1.6; + color: $fontSecondaryColor; + margin-bottom: 24px; + flex-grow: 1; + } + + &__highlights { + display: flex; + gap: 12px; + margin-bottom: 20px; + flex-wrap: wrap; + } + + &__highlight { + padding: 8px 16px; + background-color: $backgroundSecondaryColor; + border-radius: 4px; + font-size: 14px; + line-height: 1.4; + + strong { + color: $accentPrimaryColor; + } + } + + &__link { + font-size: 17px; + line-height: 1.4; + color: $accentPrimaryColor; + font-weight: 600; + } + + &__coming-soon { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + font-size: 24px; + font-weight: 700; + color: $fontSecondaryColor; + background-color: $backgroundPrimaryColor; + padding: 16px 32px; + border-radius: 8px; + } +} + +// Comparison Matrix +$className: 'comparison-matrix'; + +.#{$className} { + display: flex; + justify-content: center; + padding: 80px 64px; + background-color: $backgroundSecondaryColor; + border-bottom: 1px solid $borderPrimaryColor; + + @include mediaMax($tablet) { + padding: 60px 24px; + } + + &__wrapper { + max-width: 1400px; + width: 100%; + } + + &__title { + font-size: 46px; + line-height: 1.2; + font-weight: 700; + text-align: center; + margin-bottom: 48px; + + @include mediaMax($mobile) { + font-size: 32px; + margin-bottom: 32px; + } + } + + &__note { + margin-top: 32px; + padding: 24px; + background-color: $backgroundPrimaryColor; + border-left: 4px solid $accentPrimaryColor; + border-radius: 4px; + + p { + font-size: 17px; + line-height: 1.6; + margin: 0; + } + } +} + +// Compare Tools +$className: 'compare-tools'; + +.#{$className} { + display: flex; + justify-content: center; + padding: 80px 64px; + + @include mediaMax($tablet) { + padding: 60px 24px; + } + + &__wrapper { + max-width: 1400px; + width: 100%; + } + + &__title { + font-size: 46px; + line-height: 1.2; + font-weight: 700; + text-align: center; + margin-bottom: 48px; + + @include mediaMax($mobile) { + font-size: 32px; + margin-bottom: 32px; + } + } + + &__grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 32px; + + @include mediaMax($desktop) { + grid-template-columns: 1fr; + gap: 24px; + } + } +} + +// Compare Tool Card +$className: 'compare-tool-card'; + +.#{$className} { + display: flex; + flex-direction: column; + padding: 40px 32px; + background-color: $backgroundSecondaryColor; + border: 1px solid $borderPrimaryColor; + border-radius: 8px; + text-align: center; + + &__icon { + font-size: 48px; + margin-bottom: 20px; + } + + &__title { + font-size: 24px; + line-height: 1.3; + font-weight: 700; + margin-bottom: 16px; + } + + &__description { + font-size: 17px; + line-height: 1.6; + color: $fontSecondaryColor; + margin-bottom: 24px; + flex-grow: 1; + } + + &__link { + font-size: 17px; + line-height: 1.4; + color: $accentPrimaryColor; + text-decoration: none; + font-weight: 600; + transition: $opacityPrimaryTransition; + + &:hover { + opacity: $opacityPrimaryValue; + } + } +} + +// Comparison Hero (detailed pages) +$className: 'comparison-hero'; + +.#{$className} { + display: flex; + justify-content: center; + width: 100%; + min-height: 40vh; + background-color: $backgroundSecondaryColor; + border-bottom: 1px solid $borderPrimaryColor; + + &__content { + max-width: 1200px; + padding: 100px 64px 60px; + text-align: center; + + @include mediaMax($tablet) { + padding: 80px 24px 40px; + } + } + + &__title { + font-size: 69px; + line-height: 1.2; + font-weight: 700; + margin-bottom: 24px; + + @include mediaMax($tablet) { + font-size: 46px; + } + + @include mediaMax($mobile) { + font-size: 40px; + } + } + + &__subtitle { + font-size: 24px; + line-height: 1.6; + color: $fontSecondaryColor; + + @include mediaMax($mobile) { + font-size: 20px; + } + } +} + +// TL;DR Section +$className: 'tldr-section'; + +.#{$className} { + display: flex; + justify-content: center; + padding: 80px 64px; + background-color: #fffbf0; + border-bottom: 1px solid $borderPrimaryColor; + + @include mediaMax($tablet) { + padding: 60px 24px; + } + + &__wrapper { + max-width: 1200px; + width: 100%; + } + + &__title { + font-size: 32px; + line-height: 1.3; + font-weight: 700; + margin-bottom: 24px; + } + + &__main { + font-size: 20px; + line-height: 1.6; + margin-bottom: 32px; + } + + &__recommendations { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 32px; + + @include mediaMax($tablet) { + grid-template-columns: 1fr; + gap: 24px; + } + } + + &__recommendation { + padding: 24px; + background-color: $backgroundPrimaryColor; + border: 2px solid $borderPrimaryColor; + border-radius: 8px; + + &_highlight { + border-color: $accentPrimaryColor; + background-color: #fff5f5; + } + + h3 { + font-size: 20px; + line-height: 1.4; + font-weight: 700; + margin-bottom: 16px; + } + + ul { + list-style: '✓ '; + padding-left: 20px; + + li { + font-size: 17px; + line-height: 1.6; + margin-bottom: 8px; + } + } + } +} + +// Comparison Detail +$className: 'comparison-detail'; + +.#{$className} { + display: flex; + justify-content: center; + padding: 80px 64px; + + @include mediaMax($tablet) { + padding: 60px 24px; + } + + &__wrapper { + max-width: 1200px; + width: 100%; + } + + &__content { + margin-bottom: 48px; + } + + &__title { + font-size: 46px; + line-height: 1.2; + font-weight: 700; + margin-bottom: 32px; + + @include mediaMax($mobile) { + font-size: 32px; + } + } + + &__text { + font-size: 17px; + line-height: 1.8; + + p { + margin-bottom: 20px; + } + } +} + +// Benchmark Cards +$className: 'benchmark-cards'; + +.#{$className} { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 24px; + margin-bottom: 32px; + + @include mediaMax($tablet) { + grid-template-columns: 1fr; + } +} + +// Benchmark Card +$className: 'benchmark-card'; + +.#{$className} { + padding: 32px; + background-color: $backgroundSecondaryColor; + border: 2px solid $borderPrimaryColor; + border-radius: 8px; + text-align: center; + + &__metric { + font-size: 48px; + line-height: 1.2; + font-weight: 700; + color: $accentPrimaryColor; + margin-bottom: 12px; + } + + &__label { + font-size: 17px; + line-height: 1.6; + color: $fontSecondaryColor; + margin-bottom: 16px; + } + + &__badge { + display: inline-block; + padding: 6px 16px; + background-color: #e0e0e0; + color: $fontSecondaryColor; + font-size: 14px; + font-weight: 600; + border-radius: 4px; + + &_anycable { + background-color: $accentPrimaryColor; + color: $fontPrimaryInvertedColor; + } + } +} + +// Comparison Features +$className: 'comparison-features'; + +.#{$className} { + display: flex; + justify-content: center; + padding: 80px 64px; + background-color: $backgroundSecondaryColor; + border-bottom: 1px solid $borderPrimaryColor; + + @include mediaMax($tablet) { + padding: 60px 24px; + } + + &__wrapper { + max-width: 1200px; + width: 100%; + } + + &__title { + font-size: 46px; + line-height: 1.2; + font-weight: 700; + text-align: center; + margin-bottom: 48px; + + @include mediaMax($mobile) { + font-size: 32px; + margin-bottom: 32px; + } + } +} + +// Migration Section +$className: 'migration-section'; + +.#{$className} { + display: flex; + justify-content: center; + padding: 80px 64px; + + @include mediaMax($tablet) { + padding: 60px 24px; + } + + &__wrapper { + max-width: 1200px; + width: 100%; + } + + &__title { + font-size: 46px; + line-height: 1.2; + font-weight: 700; + margin-bottom: 32px; + + @include mediaMax($mobile) { + font-size: 32px; + } + } + + &__intro { + font-size: 20px; + line-height: 1.6; + margin-bottom: 32px; + } + + &__cta { + display: flex; + justify-content: center; + margin-top: 48px; + } +} + +// Migration Checklist +$className: 'migration-checklist'; + +.#{$className} { + display: flex; + flex-direction: column; + gap: 16px; + + &__item { + display: flex; + align-items: flex-start; + gap: 16px; + padding: 20px; + background-color: $backgroundSecondaryColor; + border-left: 4px solid $accentPrimaryColor; + border-radius: 4px; + } + + &__icon { + font-size: 24px; + color: $accentPrimaryColor; + flex-shrink: 0; + } + + &__text { + font-size: 17px; + line-height: 1.6; + + strong { + font-weight: 700; + } + } +} + +// How It Works +$className: 'how-it-works'; + +.#{$className} { + display: flex; + justify-content: center; + padding: 80px 64px; + background-color: $backgroundSecondaryColor; + border-bottom: 1px solid $borderPrimaryColor; + + @include mediaMax($tablet) { + padding: 60px 24px; + } + + &__wrapper { + max-width: 1200px; + width: 100%; + } + + &__title { + font-size: 46px; + line-height: 1.2; + font-weight: 700; + text-align: center; + margin-bottom: 48px; + + @include mediaMax($mobile) { + font-size: 32px; + margin-bottom: 32px; + } + } + + &__content { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 48px; + align-items: center; + + @include mediaMax($tablet) { + grid-template-columns: 1fr; + gap: 32px; + } + } + + &__text { + font-size: 17px; + line-height: 1.8; + + p { + margin-bottom: 20px; + } + + ul { + list-style: '→ '; + padding-left: 20px; + + li { + margin-bottom: 12px; + } + } + } +} + +// Architecture Diagram +$className: 'architecture-diagram'; + +.#{$className} { + display: flex; + flex-direction: column; + align-items: center; + padding: 40px; + background-color: $backgroundPrimaryColor; + border: 1px solid $borderPrimaryColor; + border-radius: 8px; + + &__layer { + display: flex; + flex-direction: column; + align-items: center; + } + + &__box { + padding: 20px 40px; + background-color: #f8f8f8; + border: 2px solid $borderPrimaryColor; + border-radius: 8px; + font-size: 20px; + font-weight: 600; + text-align: center; + min-width: 200px; + + &_highlight { + background-color: $accentPrimaryColor; + color: $fontPrimaryInvertedColor; + border-color: $accentPrimaryColor; + } + } + + &__note { + margin-top: 8px; + font-size: 14px; + color: $fontSecondaryColor; + text-align: center; + } + + &__arrow { + padding: 16px 0; + font-size: 24px; + color: $fontSecondaryColor; + display: flex; + align-items: center; + gap: 8px; + font-size: 14px; + } +} + +// Getting Started Section +$className: 'getting-started-section'; + +.#{$className} { + display: flex; + justify-content: center; + padding: 80px 64px; + + @include mediaMax($tablet) { + padding: 60px 24px; + } + + &__wrapper { + max-width: 1200px; + width: 100%; + } + + &__title { + font-size: 46px; + line-height: 1.2; + font-weight: 700; + text-align: center; + margin-bottom: 32px; + + @include mediaMax($mobile) { + font-size: 32px; + } + } + + &__intro { + font-size: 20px; + line-height: 1.6; + text-align: center; + margin-bottom: 48px; + } + + &__links { + display: flex; + gap: 16px; + justify-content: center; + margin-top: 48px; + flex-wrap: wrap; + + @include mediaMax($mobile) { + flex-direction: column; + } + } +} + +// Steps Grid +$className: 'steps-grid'; + +.#{$className} { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 24px; + + @include mediaMax($desktop) { + grid-template-columns: repeat(2, 1fr); + } + + @include mediaMax($mobile) { + grid-template-columns: 1fr; + } +} + +// Step Card +$className: 'step-card'; + +.#{$className} { + padding: 24px; + background-color: $backgroundSecondaryColor; + border: 1px solid $borderPrimaryColor; + border-radius: 8px; + text-align: center; + + &__number { + width: 40px; + height: 40px; + margin: 0 auto 16px; + background-color: $accentPrimaryColor; + color: $fontPrimaryInvertedColor; + font-size: 20px; + font-weight: 700; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + } + + &__title { + font-size: 20px; + line-height: 1.4; + font-weight: 700; + margin-bottom: 16px; + } + + &__code { + padding: 12px; + background-color: $backgroundPrimaryColor; + border: 1px solid $borderPrimaryColor; + border-radius: 4px; + text-align: left; + + code { + font-family: 'Menlo', 'Monaco', 'Courier New', monospace; + font-size: 13px; + line-height: 1.5; + color: #333; + } + } + + &__description { + font-size: 17px; + line-height: 1.6; + color: $fontSecondaryColor; + } +} diff --git a/src/modules/blocks/customers.scss b/src/modules/blocks/customers.scss new file mode 100644 index 0000000..ff8c50e --- /dev/null +++ b/src/modules/blocks/customers.scss @@ -0,0 +1,505 @@ +// Customers Hero +$className: 'customers-hero'; + +.#{$className} { + display: flex; + justify-content: center; + width: 100%; + min-height: 40vh; + background-color: $backgroundSecondaryColor; + border-bottom: 1px solid $borderPrimaryColor; + + &__content { + max-width: 1200px; + padding: 100px 64px 60px; + text-align: center; + + @include mediaMax($tablet) { + padding: 80px 24px 40px; + } + } + + &__title { + font-size: 69px; + line-height: 1.2; + font-weight: 700; + margin-bottom: 24px; + + @include mediaMax($tablet) { + font-size: 46px; + } + + @include mediaMax($mobile) { + font-size: 40px; + } + } + + &__subtitle { + font-size: 24px; + line-height: 1.6; + color: $fontSecondaryColor; + + @include mediaMax($mobile) { + font-size: 20px; + } + } +} + +// Logo Wall +$className: 'logo-wall'; + +.#{$className} { + display: flex; + justify-content: center; + padding: 80px 64px; + background-color: $backgroundPrimaryColor; + + @include mediaMax($tablet) { + padding: 60px 24px; + } + + &__wrapper { + max-width: 1400px; + width: 100%; + } + + &__title { + font-size: 32px; + line-height: 1.3; + font-weight: 700; + text-align: center; + margin-bottom: 48px; + color: $fontSecondaryColor; + text-transform: uppercase; + letter-spacing: 2px; + font-size: 14px; + + @include mediaMax($mobile) { + margin-bottom: 32px; + } + } + + &__grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 48px 32px; + align-items: center; + justify-items: center; + + @include mediaMax($mobile) { + grid-template-columns: repeat(2, 1fr); + gap: 32px 24px; + } + } + + &__logo { + opacity: 0.7; + transition: opacity 200ms; + max-width: 100%; + height: auto; + + &:hover { + opacity: 1; + } + } +} + +// Featured Cases +$className: 'featured-cases'; + +.#{$className} { + display: flex; + justify-content: center; + padding: 80px 64px; + background-color: $backgroundSecondaryColor; + border-bottom: 1px solid $borderPrimaryColor; + + @include mediaMax($tablet) { + padding: 60px 24px; + } + + &__wrapper { + max-width: 1200px; + width: 100%; + } + + &__title { + font-size: 46px; + line-height: 1.2; + font-weight: 700; + text-align: center; + margin-bottom: 64px; + + @include mediaMax($mobile) { + font-size: 32px; + margin-bottom: 48px; + } + } +} + +// Customer Story +$className: 'customer-story'; + +.#{$className} { + padding: 64px; + background-color: $backgroundPrimaryColor; + border: 1px solid $borderPrimaryColor; + border-radius: 8px; + margin-bottom: 48px; + + @include mediaMax($tablet) { + padding: 40px 32px; + margin-bottom: 32px; + } + + @include mediaMax($mobile) { + padding: 32px 24px; + } + + &:last-child { + margin-bottom: 0; + } + + &_reversed { + background-color: $backgroundSecondaryColor; + } + + &__content { + max-width: 900px; + margin: 0 auto; + } + + &__logo { + margin-bottom: 32px; + max-width: 200px; + height: auto; + + @include mediaMax($mobile) { + max-width: 150px; + margin-bottom: 24px; + } + } + + &__quote { + font-size: 24px; + line-height: 1.6; + font-style: italic; + color: $fontPrimaryColor; + margin-bottom: 32px; + quotes: """ """ "'" "'"; + + @include mediaMax($tablet) { + font-size: 20px; + } + + @include mediaMax($mobile) { + font-size: 18px; + margin-bottom: 24px; + } + + &::before { + content: open-quote; + } + + &::after { + content: close-quote; + } + } + + &__meta { + display: flex; + gap: 24px; + margin-bottom: 32px; + flex-wrap: wrap; + + @include mediaMax($mobile) { + gap: 16px; + margin-bottom: 24px; + } + } + + &__industry, + &__use-case { + padding: 8px 16px; + background-color: $backgroundSecondaryColor; + border-radius: 4px; + font-size: 14px; + font-weight: 600; + color: $fontSecondaryColor; + + .#{$className}_reversed & { + background-color: $backgroundPrimaryColor; + } + } + + &__stats { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 32px; + margin-bottom: 32px; + + @include mediaMax($mobile) { + grid-template-columns: 1fr; + gap: 20px; + margin-bottom: 24px; + } + } + + &__stat { + text-align: center; + } + + &__stat-value { + font-size: 36px; + line-height: 1.2; + font-weight: 700; + color: $accentPrimaryColor; + margin-bottom: 8px; + + @include mediaMax($mobile) { + font-size: 28px; + } + } + + &__stat-label { + font-size: 14px; + line-height: 1.4; + color: $fontSecondaryColor; + text-transform: uppercase; + letter-spacing: 0.5px; + } + + &__link { + font-size: 17px; + line-height: 1.4; + color: $accentPrimaryColor; + text-decoration: none; + font-weight: 600; + transition: $opacityPrimaryTransition; + display: inline-block; + + &:hover { + opacity: $opacityPrimaryValue; + } + } +} + +// Industry Breakdown +$className: 'industry-breakdown'; + +.#{$className} { + display: flex; + justify-content: center; + padding: 80px 64px; + + @include mediaMax($tablet) { + padding: 60px 24px; + } + + &__wrapper { + max-width: 1400px; + width: 100%; + } + + &__title { + font-size: 46px; + line-height: 1.2; + font-weight: 700; + text-align: center; + margin-bottom: 64px; + + @include mediaMax($mobile) { + font-size: 32px; + margin-bottom: 48px; + } + } +} + +// Industry Grid +$className: 'industry-grid'; + +.#{$className} { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 32px; + + @include mediaMax($desktop) { + grid-template-columns: repeat(2, 1fr); + } + + @include mediaMax($tablet) { + grid-template-columns: 1fr; + gap: 24px; + } +} + +// Industry Card +$className: 'industry-card'; + +.#{$className} { + padding: 40px 32px; + background-color: $backgroundSecondaryColor; + border: 1px solid $borderPrimaryColor; + border-radius: 8px; + text-align: center; + + &__icon { + font-size: 48px; + margin-bottom: 20px; + } + + &__title { + font-size: 24px; + line-height: 1.3; + font-weight: 700; + margin-bottom: 16px; + } + + &__description { + font-size: 17px; + line-height: 1.6; + color: $fontSecondaryColor; + margin-bottom: 20px; + } + + &__companies { + font-size: 14px; + line-height: 1.4; + color: $fontSecondaryColor; + font-style: italic; + } +} + +// Community Section +$className: 'community-section'; + +.#{$className} { + display: flex; + justify-content: center; + padding: 80px 64px; + background-color: $backgroundSecondaryColor; + border-bottom: 1px solid $borderPrimaryColor; + + @include mediaMax($tablet) { + padding: 60px 24px; + } + + &__wrapper { + max-width: 1200px; + width: 100%; + text-align: center; + } + + &__title { + font-size: 46px; + line-height: 1.2; + font-weight: 700; + margin-bottom: 16px; + + @include mediaMax($mobile) { + font-size: 32px; + } + } + + &__subtitle { + font-size: 20px; + line-height: 1.6; + color: $fontSecondaryColor; + margin-bottom: 48px; + + @include mediaMax($mobile) { + font-size: 17px; + margin-bottom: 32px; + } + } +} + +// Community Stats +$className: 'community-stats'; + +.#{$className} { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 48px; + margin-bottom: 48px; + + @include mediaMax($tablet) { + gap: 32px; + margin-bottom: 32px; + } + + @include mediaMax($mobile) { + grid-template-columns: 1fr; + gap: 24px; + } +} + +// Community Stat +$className: 'community-stat'; + +.#{$className} { + &__value { + font-size: 48px; + line-height: 1.2; + font-weight: 700; + color: $accentPrimaryColor; + margin-bottom: 8px; + + @include mediaMax($mobile) { + font-size: 36px; + } + } + + &__label { + font-size: 17px; + line-height: 1.4; + color: $fontSecondaryColor; + } +} + +// Community Links +$className: 'community-links'; + +.#{$className} { + display: flex; + gap: 24px; + justify-content: center; + flex-wrap: wrap; + + @include mediaMax($mobile) { + flex-direction: column; + gap: 16px; + } +} + +// Community Link +$className: 'community-link'; + +.#{$className} { + display: flex; + align-items: center; + gap: 12px; + padding: 16px 32px; + background-color: $backgroundPrimaryColor; + border: 2px solid $borderPrimaryColor; + border-radius: 8px; + text-decoration: none; + color: inherit; + transition: all 200ms; + + &:hover { + border-color: $accentPrimaryColor; + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + } + + &__icon { + font-size: 24px; + } + + &__text { + font-size: 17px; + line-height: 1.4; + font-weight: 600; + } +} diff --git a/src/modules/blocks/features.scss b/src/modules/blocks/features.scss new file mode 100644 index 0000000..6627773 --- /dev/null +++ b/src/modules/blocks/features.scss @@ -0,0 +1,335 @@ +// Features Hero +$className: 'features-hero'; + +.#{$className} { + display: flex; + justify-content: center; + width: 100%; + min-height: 50vh; + background-color: $backgroundSecondaryColor; + border-bottom: 1px solid $borderPrimaryColor; + + &__content { + max-width: 1200px; + padding: 120px 64px 80px; + text-align: center; + + @include mediaMax($tablet) { + padding: 80px 24px 60px; + } + } + + &__title { + font-size: 69px; + line-height: 1.2; + font-weight: 700; + margin-bottom: 24px; + + @include mediaMax($tablet) { + font-size: 46px; + } + + @include mediaMax($mobile) { + font-size: 40px; + } + } + + &__subtitle { + font-size: 24px; + line-height: 1.6; + color: $fontSecondaryColor; + + @include mediaMax($mobile) { + font-size: 20px; + } + } +} + +// Feature Grid +$className: 'features-grid'; + +.#{$className} { + display: flex; + justify-content: center; + padding: 0; + + &__wrapper { + display: grid; + grid-template-columns: repeat(3, 1fr); + max-width: 1920px; + width: 100%; + + @include mediaMax($desktop) { + grid-template-columns: repeat(2, 1fr); + } + + @include mediaMax($tablet) { + grid-template-columns: 1fr; + } + } +} + +// Feature Card +$className: 'feature-card'; + +.#{$className} { + position: relative; + display: flex; + flex-direction: column; + padding: 48px 40px; + border-right: 1px solid $borderPrimaryColor; + border-bottom: 1px solid $borderPrimaryColor; + background-color: $backgroundPrimaryColor; + min-height: 400px; + + @include mediaMax($tablet) { + border-right: none; + padding: 48px 24px; + } + + &_pro { + background-color: $backgroundSecondaryColor; + } + + &__badge { + position: absolute; + top: 16px; + right: 16px; + padding: 4px 12px; + background-color: $accentPrimaryColor; + color: $fontPrimaryInvertedColor; + font-size: 12px; + font-weight: 700; + border-radius: 4px; + text-transform: uppercase; + letter-spacing: 0.5px; + } + + &__icon { + font-size: 48px; + margin-bottom: 20px; + line-height: 1; + } + + &__title { + font-size: 24px; + line-height: 1.3; + font-weight: 700; + margin-bottom: 16px; + } + + &__description { + font-size: 17px; + line-height: 1.6; + color: $fontSecondaryColor; + margin-bottom: 24px; + flex-grow: 1; + } + + &__code { + margin-bottom: 24px; + padding: 16px; + background-color: #f8f8f8; + border: 1px solid $borderPrimaryColor; + border-radius: 4px; + overflow-x: auto; + + code { + font-family: 'Menlo', 'Monaco', 'Courier New', monospace; + font-size: 14px; + line-height: 1.5; + color: #333; + } + } + + &__screenshot, + &__chart, + &__placeholder { + margin-bottom: 24px; + padding: 60px 20px; + background-color: #f8f8f8; + border: 1px solid $borderPrimaryColor; + border-radius: 4px; + text-align: center; + color: $fontSecondaryColor; + font-size: 14px; + } + + &__link { + font-size: 17px; + line-height: 1.4; + color: $accentPrimaryColor; + text-decoration: none; + font-weight: 500; + transition: $opacityPrimaryTransition; + align-self: flex-start; + + &:hover { + opacity: $opacityPrimaryValue; + } + } +} + +// Comparison Section +$className: 'comparison-section'; + +.#{$className} { + display: flex; + justify-content: center; + padding: 80px 64px; + background-color: $backgroundSecondaryColor; + border-bottom: 1px solid $borderPrimaryColor; + + @include mediaMax($tablet) { + padding: 60px 24px; + } + + &__wrapper { + max-width: 1400px; + width: 100%; + } + + &__title { + font-size: 46px; + line-height: 1.2; + font-weight: 700; + text-align: center; + margin-bottom: 48px; + + @include mediaMax($mobile) { + font-size: 32px; + margin-bottom: 32px; + } + } + + &__cta { + display: flex; + justify-content: center; + margin-top: 48px; + + @include mediaMax($mobile) { + margin-top: 32px; + } + } +} + +// Comparison Table +$className: 'comparison-table'; + +.#{$className} { + overflow-x: auto; + margin-bottom: 24px; + + &__table { + width: 100%; + border-collapse: collapse; + background-color: $backgroundPrimaryColor; + border: 1px solid $borderPrimaryColor; + border-radius: 8px; + overflow: hidden; + + thead { + background-color: #fafafa; + + tr { + th { + padding: 16px 20px; + text-align: left; + font-size: 14px; + font-weight: 700; + color: $fontSecondaryColor; + text-transform: uppercase; + letter-spacing: 0.5px; + border-bottom: 2px solid $borderPrimaryColor; + + @include mediaMax($mobile) { + padding: 12px 10px; + font-size: 12px; + } + } + } + } + + tbody { + tr { + &:not(:last-child) { + border-bottom: 1px solid $borderPrimaryColor; + } + + &:hover { + background-color: #fafafa; + } + + td { + padding: 16px 20px; + font-size: 17px; + line-height: 1.6; + + @include mediaMax($mobile) { + padding: 12px 10px; + font-size: 14px; + } + + &:first-child { + font-weight: 500; + } + } + } + } + } +} + +// CTA Section +$className: 'cta-section'; + +.#{$className} { + display: flex; + justify-content: center; + padding: 120px 64px; + background-color: $backgroundPrimaryColor; + + @include mediaMax($tablet) { + padding: 80px 24px; + } + + &__wrapper { + max-width: 1000px; + width: 100%; + text-align: center; + } + + &__title { + font-size: 46px; + line-height: 1.2; + font-weight: 700; + margin-bottom: 24px; + + @include mediaMax($mobile) { + font-size: 32px; + } + } + + &__subtitle { + font-size: 20px; + line-height: 1.6; + color: $fontSecondaryColor; + margin-bottom: 48px; + + @include mediaMax($mobile) { + font-size: 17px; + margin-bottom: 32px; + } + } + + &__buttons { + display: flex; + gap: 16px; + justify-content: center; + flex-wrap: wrap; + + @include mediaMax($mobile) { + flex-direction: column; + gap: 12px; + } + } +} diff --git a/src/modules/blocks/learn.scss b/src/modules/blocks/learn.scss new file mode 100644 index 0000000..d8aa436 --- /dev/null +++ b/src/modules/blocks/learn.scss @@ -0,0 +1,610 @@ +// Learn Hero +$className: 'learn-hero'; + +.#{$className} { + display: flex; + justify-content: center; + width: 100%; + min-height: 40vh; + background-color: $backgroundSecondaryColor; + border-bottom: 1px solid $borderPrimaryColor; + + &__content { + max-width: 1200px; + padding: 100px 64px 60px; + text-align: center; + + @include mediaMax($tablet) { + padding: 80px 24px 40px; + } + } + + &__title { + font-size: 69px; + line-height: 1.2; + font-weight: 700; + margin-bottom: 24px; + + @include mediaMax($tablet) { + font-size: 46px; + } + + @include mediaMax($mobile) { + font-size: 40px; + } + } + + &__subtitle { + font-size: 24px; + line-height: 1.6; + color: $fontSecondaryColor; + + @include mediaMax($mobile) { + font-size: 20px; + } + } +} + +// Learning Paths +$className: 'learning-paths'; + +.#{$className} { + display: flex; + justify-content: center; + padding: 80px 64px; + + @include mediaMax($tablet) { + padding: 60px 24px; + } + + &__wrapper { + max-width: 1400px; + width: 100%; + } + + &__title { + font-size: 46px; + line-height: 1.2; + font-weight: 700; + text-align: center; + margin-bottom: 64px; + + @include mediaMax($mobile) { + font-size: 32px; + margin-bottom: 48px; + } + } + + &__grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 32px; + + @include mediaMax($desktop) { + grid-template-columns: 1fr; + gap: 24px; + } + } +} + +// Learning Path Card +$className: 'learning-path-card'; + +.#{$className} { + padding: 40px 32px; + background-color: $backgroundSecondaryColor; + border: 2px solid $borderPrimaryColor; + border-radius: 8px; + display: flex; + flex-direction: column; + + &_advanced { + background-color: $accentPrimaryColor; + border-color: $accentPrimaryColor; + color: $fontPrimaryInvertedColor; + + .#{$className}__badge { + background-color: rgba(255, 255, 255, 0.2); + color: $fontPrimaryInvertedColor; + } + + .#{$className}__description, + .#{$className}__duration { + color: rgba(255, 255, 255, 0.9); + } + + .#{$className}__list { + color: $fontPrimaryInvertedColor; + } + } + + &__badge { + display: inline-block; + width: fit-content; + padding: 6px 12px; + background-color: #e0e0e0; + color: $fontSecondaryColor; + font-size: 12px; + font-weight: 700; + border-radius: 4px; + text-transform: uppercase; + letter-spacing: 0.5px; + margin-bottom: 16px; + } + + &__title { + font-size: 28px; + line-height: 1.3; + font-weight: 700; + margin-bottom: 16px; + } + + &__description { + font-size: 17px; + line-height: 1.6; + color: $fontSecondaryColor; + margin-bottom: 24px; + } + + &__list { + list-style: '→ '; + padding-left: 20px; + margin-bottom: 24px; + flex-grow: 1; + + li { + font-size: 17px; + line-height: 1.8; + margin-bottom: 8px; + } + } + + &__duration { + font-size: 14px; + line-height: 1.4; + color: $fontSecondaryColor; + margin-bottom: 24px; + font-weight: 600; + } +} + +// Documentation Section +$className: 'documentation-section'; + +.#{$className} { + display: flex; + justify-content: center; + padding: 80px 64px; + background-color: $backgroundSecondaryColor; + border-bottom: 1px solid $borderPrimaryColor; + + @include mediaMax($tablet) { + padding: 60px 24px; + } + + &__wrapper { + max-width: 1400px; + width: 100%; + } + + &__title { + font-size: 46px; + line-height: 1.2; + font-weight: 700; + text-align: center; + margin-bottom: 64px; + + @include mediaMax($mobile) { + font-size: 32px; + margin-bottom: 48px; + } + } +} + +// Doc Grid +$className: 'doc-grid'; + +.#{$className} { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 24px; + + @include mediaMax($desktop) { + grid-template-columns: repeat(2, 1fr); + } + + @include mediaMax($tablet) { + grid-template-columns: 1fr; + } +} + +// Doc Card +$className: 'doc-card'; + +.#{$className} { + padding: 32px 24px; + background-color: $backgroundPrimaryColor; + border: 1px solid $borderPrimaryColor; + border-radius: 8px; + text-decoration: none; + color: inherit; + transition: all 200ms; + text-align: center; + + &:hover { + border-color: $accentPrimaryColor; + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + } + + &__icon { + font-size: 48px; + margin-bottom: 16px; + } + + &__title { + font-size: 20px; + line-height: 1.4; + font-weight: 700; + margin-bottom: 12px; + } + + &__description { + font-size: 15px; + line-height: 1.6; + color: $fontSecondaryColor; + } +} + +// Blog Section +$className: 'blog-section'; + +.#{$className} { + display: flex; + justify-content: center; + padding: 80px 64px; + + @include mediaMax($tablet) { + padding: 60px 24px; + } + + &__wrapper { + max-width: 1400px; + width: 100%; + } + + &__title { + font-size: 46px; + line-height: 1.2; + font-weight: 700; + text-align: center; + margin-bottom: 64px; + + @include mediaMax($mobile) { + font-size: 32px; + margin-bottom: 48px; + } + } + + &__cta { + display: flex; + justify-content: center; + margin-top: 48px; + } +} + +// Blog Grid +$className: 'blog-grid'; + +.#{$className} { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 32px; + + @include mediaMax($desktop) { + grid-template-columns: 1fr; + gap: 24px; + } +} + +// Blog Card +$className: 'blog-card'; + +.#{$className} { + padding: 32px; + background-color: $backgroundSecondaryColor; + border: 1px solid $borderPrimaryColor; + border-radius: 8px; + text-decoration: none; + color: inherit; + transition: all 200ms; + display: flex; + flex-direction: column; + + &:hover { + border-color: $accentPrimaryColor; + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + } + + &__category { + display: inline-block; + width: fit-content; + padding: 4px 12px; + background-color: $accentPrimaryColor; + color: $fontPrimaryInvertedColor; + font-size: 12px; + font-weight: 700; + border-radius: 4px; + text-transform: uppercase; + letter-spacing: 0.5px; + margin-bottom: 16px; + } + + &__title { + font-size: 24px; + line-height: 1.3; + font-weight: 700; + margin-bottom: 12px; + } + + &__excerpt { + font-size: 17px; + line-height: 1.6; + color: $fontSecondaryColor; + margin-bottom: 20px; + flex-grow: 1; + } + + &__meta { + display: flex; + align-items: center; + gap: 16px; + } + + &__reading-time { + font-size: 14px; + line-height: 1.4; + color: $fontSecondaryColor; + } +} + +// Video Section +$className: 'video-section'; + +.#{$className} { + display: flex; + justify-content: center; + padding: 80px 64px; + background-color: $backgroundSecondaryColor; + border-bottom: 1px solid $borderPrimaryColor; + + @include mediaMax($tablet) { + padding: 60px 24px; + } + + &__wrapper { + max-width: 1400px; + width: 100%; + } + + &__title { + font-size: 46px; + line-height: 1.2; + font-weight: 700; + text-align: center; + margin-bottom: 16px; + + @include mediaMax($mobile) { + font-size: 32px; + } + } + + &__subtitle { + font-size: 20px; + line-height: 1.6; + color: $fontSecondaryColor; + text-align: center; + margin-bottom: 64px; + + @include mediaMax($mobile) { + font-size: 17px; + margin-bottom: 48px; + } + } + + &__cta { + display: flex; + justify-content: center; + margin-top: 48px; + } +} + +// Video Grid +$className: 'video-grid'; + +.#{$className} { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 32px; + + @include mediaMax($desktop) { + grid-template-columns: 1fr; + gap: 24px; + } +} + +// Video Card +$className: 'video-card'; + +.#{$className} { + text-decoration: none; + color: inherit; + display: flex; + flex-direction: column; + transition: all 200ms; + + &:hover { + transform: translateY(-2px); + + .#{$className}__thumbnail { + border-color: $accentPrimaryColor; + } + } + + &__thumbnail { + position: relative; + width: 100%; + aspect-ratio: 16 / 9; + background-color: #000; + border: 2px solid $borderPrimaryColor; + border-radius: 8px; + display: flex; + align-items: center; + justify-content: center; + margin-bottom: 16px; + transition: border-color 200ms; + } + + &__play { + font-size: 48px; + color: $fontPrimaryInvertedColor; + opacity: 0.9; + } + + &__duration { + position: absolute; + bottom: 12px; + right: 12px; + padding: 4px 8px; + background-color: rgba(0, 0, 0, 0.8); + color: $fontPrimaryInvertedColor; + font-size: 12px; + font-weight: 600; + border-radius: 4px; + } + + &__title { + font-size: 20px; + line-height: 1.4; + font-weight: 700; + margin-bottom: 8px; + } + + &__description { + font-size: 15px; + line-height: 1.6; + color: $fontSecondaryColor; + } +} + +// Community Support +$className: 'community-support'; + +.#{$className} { + display: flex; + justify-content: center; + padding: 80px 64px; + + @include mediaMax($tablet) { + padding: 60px 24px; + } + + &__wrapper { + max-width: 1400px; + width: 100%; + } + + &__title { + font-size: 46px; + line-height: 1.2; + font-weight: 700; + text-align: center; + margin-bottom: 16px; + + @include mediaMax($mobile) { + font-size: 32px; + } + } + + &__subtitle { + font-size: 20px; + line-height: 1.6; + color: $fontSecondaryColor; + text-align: center; + margin-bottom: 64px; + + @include mediaMax($mobile) { + font-size: 17px; + margin-bottom: 48px; + } + } +} + +// Support Grid +$className: 'support-grid'; + +.#{$className} { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 24px; + + @include mediaMax($tablet) { + grid-template-columns: 1fr; + } +} + +// Support Card +$className: 'support-card'; + +.#{$className} { + padding: 32px; + background-color: $backgroundSecondaryColor; + border: 2px solid $borderPrimaryColor; + border-radius: 8px; + text-decoration: none; + color: inherit; + text-align: center; + transition: all 200ms; + display: flex; + flex-direction: column; + align-items: center; + + &:hover { + border-color: $accentPrimaryColor; + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + } + + &_pro { + background-color: #fff5f5; + border-color: $accentPrimaryColor; + } + + &__icon { + font-size: 48px; + margin-bottom: 16px; + } + + &__title { + font-size: 24px; + line-height: 1.3; + font-weight: 700; + margin-bottom: 12px; + } + + &__description { + font-size: 17px; + line-height: 1.6; + color: $fontSecondaryColor; + margin-bottom: 20px; + flex-grow: 1; + } + + &__link { + font-size: 17px; + line-height: 1.4; + color: $accentPrimaryColor; + font-weight: 600; + } +} diff --git a/src/partials/header.hbs b/src/partials/header.hbs index 502f9ff..2de4352 100644 --- a/src/partials/header.hbs +++ b/src/partials/header.hbs @@ -3,8 +3,10 @@
From 034c98209ab93502c3f8bceea69dad146e882a3d Mon Sep 17 00:00:00 2001 From: Irina Nazarova Date: Fri, 12 Dec 2025 19:00:40 -0800 Subject: [PATCH 02/15] feat: add comprehensive pricing page with PLG strategy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PRICING STRUCTURE (Monthly-first PLG approach): Self-Hosted: - Open Source: Free forever - Pro License: $99/month or $990/year (save 17%) Managed (plus.anycable.io): - Free: $0 - up to 100 connections - Starter: $29/mo - up to 1K connections - Pro: $99/mo - up to 10K connections (Most Popular) - Scale: $299/mo - up to 50K connections - Enterprise: Custom pricing KEY FEATURES: - Monthly pricing with annual discounts (17% savings) - Clear upgrade path from free to enterprise - Interactive pricing calculator comparing AnyCable, Pusher, self-hosted - Comprehensive feature comparison table - Detailed FAQ section (8 common questions) - Enterprise CTA with gradient background - Toggle between monthly/annual billing - Real-time cost estimates based on connections PLG BEST PRACTICES IMPLEMENTED: ✓ Start free (no credit card) ✓ Usage-based scaling ✓ Self-serve signup ✓ Clear value progression ✓ Transparent pricing ✓ Easy upgrade path ✓ Calculator shows competitive advantage Technical: - New /pricing page with dedicated route - pricing.ts with calculator logic - pricing.scss with responsive design - Updated header navigation link --- src/index.scss | 1 + src/modules/blocks/pricing.scss | 637 ++++++++++++++++++++++++++++++++ src/partials/header.hbs | 2 +- src/pricing.ts | 176 +++++++++ src/pricing/index.html | 495 +++++++++++++++++++++++++ 5 files changed, 1310 insertions(+), 1 deletion(-) create mode 100644 src/modules/blocks/pricing.scss create mode 100644 src/pricing.ts create mode 100644 src/pricing/index.html diff --git a/src/index.scss b/src/index.scss index d30dd8a..75b24f6 100644 --- a/src/index.scss +++ b/src/index.scss @@ -33,4 +33,5 @@ @import './modules/blocks/compare.scss'; @import './modules/blocks/customers.scss'; @import './modules/blocks/learn.scss'; +@import './modules/blocks/pricing.scss'; @import './modules/common.scss'; diff --git a/src/modules/blocks/pricing.scss b/src/modules/blocks/pricing.scss new file mode 100644 index 0000000..c8ff8ca --- /dev/null +++ b/src/modules/blocks/pricing.scss @@ -0,0 +1,637 @@ +// Pricing Hero +$className: 'pricing-hero'; + +.#{$className} { + display: flex; + justify-content: center; + width: 100%; + min-height: 50vh; + background-color: $backgroundSecondaryColor; + border-bottom: 1px solid $borderPrimaryColor; + + &__content { + max-width: 1200px; + padding: 120px 64px 80px; + text-align: center; + + @include mediaMax($tablet) { + padding: 80px 24px 60px; + } + } + + &__title { + font-size: 69px; + line-height: 1.2; + font-weight: 700; + margin-bottom: 24px; + + @include mediaMax($tablet) { + font-size: 46px; + } + + @include mediaMax($mobile) { + font-size: 40px; + } + } + + &__subtitle { + font-size: 24px; + line-height: 1.6; + color: $fontSecondaryColor; + margin-bottom: 48px; + + @include mediaMax($mobile) { + font-size: 20px; + margin-bottom: 32px; + } + } +} + +// Pricing Toggle +$className: 'pricing-toggle'; + +.#{$className} { + display: inline-flex; + gap: 8px; + padding: 8px; + background-color: $backgroundPrimaryColor; + border: 1px solid $borderPrimaryColor; + border-radius: 8px; + + &__option { + position: relative; + cursor: pointer; + user-select: none; + + input[type='radio'] { + position: absolute; + opacity: 0; + pointer-events: none; + + &:checked + span { + background-color: $accentPrimaryColor; + color: $fontPrimaryInvertedColor; + } + } + + span { + display: block; + padding: 12px 24px; + font-size: 17px; + font-weight: 600; + border-radius: 6px; + transition: all 200ms; + + @include mediaMax($mobile) { + padding: 10px 16px; + font-size: 15px; + } + } + } + + &__badge { + display: inline-block; + margin-left: 8px; + padding: 2px 8px; + background-color: rgba(255, 255, 255, 0.3); + border-radius: 4px; + font-size: 12px; + font-weight: 700; + } +} + +// Pricing Tiers +$className: 'pricing-tiers'; + +.#{$className} { + display: flex; + justify-content: center; + padding: 80px 64px; + + @include mediaMax($tablet) { + padding: 60px 24px; + } + + &__wrapper { + max-width: 1600px; + width: 100%; + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 32px; + + @include mediaMax($wideDesktop) { + grid-template-columns: repeat(2, 1fr); + } + + @include mediaMax($tablet) { + grid-template-columns: 1fr; + gap: 24px; + } + } +} + +// Pricing Card +$className: 'pricing-card'; + +.#{$className} { + position: relative; + display: flex; + flex-direction: column; + padding: 40px 32px; + background-color: $backgroundSecondaryColor; + border: 2px solid $borderPrimaryColor; + border-radius: 8px; + transition: all 200ms; + + &:hover { + transform: translateY(-4px); + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12); + } + + &_popular { + border-color: $accentPrimaryColor; + background-color: $backgroundPrimaryColor; + box-shadow: 0 4px 16px rgba(246, 67, 67, 0.15); + } + + &_pro-license { + background-color: #fffbf0; + } + + &__badge { + position: absolute; + top: -12px; + left: 50%; + transform: translateX(-50%); + padding: 6px 16px; + background-color: $accentPrimaryColor; + color: $fontPrimaryInvertedColor; + font-size: 12px; + font-weight: 700; + border-radius: 4px; + text-transform: uppercase; + letter-spacing: 0.5px; + white-space: nowrap; + } + + &__header { + margin-bottom: 24px; + } + + &__title { + font-size: 28px; + line-height: 1.3; + font-weight: 700; + margin-bottom: 4px; + } + + &__subtitle { + font-size: 14px; + line-height: 1.4; + color: $fontSecondaryColor; + font-weight: 600; + } + + &__price { + margin-bottom: 20px; + } + + &__amount { + font-size: 48px; + line-height: 1.2; + font-weight: 700; + color: $accentPrimaryColor; + + @include mediaMax($mobile) { + font-size: 40px; + } + } + + &__period { + font-size: 17px; + line-height: 1.4; + color: $fontSecondaryColor; + margin-left: 4px; + } + + &__description { + font-size: 17px; + line-height: 1.6; + color: $fontSecondaryColor; + margin-bottom: 24px; + } + + &__features { + list-style: none; + padding: 0; + margin-bottom: 32px; + flex-grow: 1; + + li { + font-size: 15px; + line-height: 1.6; + margin-bottom: 12px; + padding-left: 0; + + strong { + font-weight: 700; + color: $accentPrimaryColor; + } + } + } + + &__footer { + margin-top: 20px; + padding-top: 20px; + border-top: 1px solid $borderPrimaryColor; + font-size: 14px; + line-height: 1.4; + color: $fontSecondaryColor; + font-style: italic; + text-align: center; + } +} + +// Button Full Width +.button_fullwidth { + width: 100%; + justify-content: center; +} + +// Pricing Comparison +$className: 'pricing-comparison'; + +.#{$className} { + display: flex; + justify-content: center; + padding: 80px 64px; + background-color: $backgroundSecondaryColor; + border-bottom: 1px solid $borderPrimaryColor; + + @include mediaMax($tablet) { + padding: 60px 24px; + } + + &__wrapper { + max-width: 1600px; + width: 100%; + } + + &__title { + font-size: 46px; + line-height: 1.2; + font-weight: 700; + text-align: center; + margin-bottom: 48px; + + @include mediaMax($mobile) { + font-size: 32px; + margin-bottom: 32px; + } + } + + &__note { + margin-top: 24px; + font-size: 14px; + line-height: 1.4; + color: $fontSecondaryColor; + font-style: italic; + } +} + +// Pricing Calculator Section +$className: 'pricing-calculator-section'; + +.#{$className} { + display: flex; + justify-content: center; + padding: 80px 64px; + + @include mediaMax($tablet) { + padding: 60px 24px; + } + + &__wrapper { + max-width: 1200px; + width: 100%; + } + + &__title { + font-size: 46px; + line-height: 1.2; + font-weight: 700; + text-align: center; + margin-bottom: 16px; + + @include mediaMax($mobile) { + font-size: 32px; + } + } + + &__subtitle { + font-size: 20px; + line-height: 1.6; + color: $fontSecondaryColor; + text-align: center; + margin-bottom: 48px; + + @include mediaMax($mobile) { + font-size: 17px; + margin-bottom: 32px; + } + } +} + +// Pricing Calculator +$className: 'pricing-calculator'; + +.#{$className} { + padding: 48px; + background-color: $backgroundSecondaryColor; + border: 1px solid $borderPrimaryColor; + border-radius: 8px; + + @include mediaMax($tablet) { + padding: 32px 24px; + } + + &__inputs { + margin-bottom: 48px; + } + + &__input-group { + label { + display: block; + font-size: 17px; + line-height: 1.4; + font-weight: 600; + margin-bottom: 16px; + } + + input[type='range'] { + width: 100%; + height: 8px; + background: $borderPrimaryColor; + border-radius: 4px; + outline: none; + -webkit-appearance: none; + margin-bottom: 16px; + + &::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + width: 24px; + height: 24px; + background: $accentPrimaryColor; + border-radius: 50%; + cursor: pointer; + transition: all 200ms; + + &:hover { + transform: scale(1.1); + } + } + + &::-moz-range-thumb { + width: 24px; + height: 24px; + background: $accentPrimaryColor; + border-radius: 50%; + cursor: pointer; + border: none; + transition: all 200ms; + + &:hover { + transform: scale(1.1); + } + } + } + } + + &__value { + font-size: 32px; + line-height: 1.2; + font-weight: 700; + color: $accentPrimaryColor; + text-align: center; + } + + &__results { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 24px; + + @include mediaMax($tablet) { + grid-template-columns: 1fr; + } + } +} + +// Pricing Result Card +$className: 'pricing-result-card'; + +.#{$className} { + position: relative; + padding: 32px 24px; + background-color: $backgroundPrimaryColor; + border: 2px solid $borderPrimaryColor; + border-radius: 8px; + text-align: center; + + &_recommended { + border-color: $accentPrimaryColor; + background-color: #fff5f5; + } + + &__badge { + position: absolute; + top: -12px; + left: 50%; + transform: translateX(-50%); + padding: 4px 12px; + background-color: $accentPrimaryColor; + color: $fontPrimaryInvertedColor; + font-size: 12px; + font-weight: 700; + border-radius: 4px; + text-transform: uppercase; + letter-spacing: 0.5px; + } + + &__title { + font-size: 20px; + line-height: 1.4; + font-weight: 700; + margin-bottom: 16px; + } + + &__price { + font-size: 48px; + line-height: 1.2; + font-weight: 700; + color: $accentPrimaryColor; + margin-bottom: 12px; + + span { + font-size: 17px; + color: $fontSecondaryColor; + font-weight: 400; + } + } + + &__features { + font-size: 14px; + line-height: 1.6; + color: $fontSecondaryColor; + } + + &__savings { + font-size: 14px; + line-height: 1.4; + color: #d32f2f; + font-weight: 600; + } +} + +// Pricing FAQ +$className: 'pricing-faq'; + +.#{$className} { + display: flex; + justify-content: center; + padding: 80px 64px; + background-color: $backgroundSecondaryColor; + border-bottom: 1px solid $borderPrimaryColor; + + @include mediaMax($tablet) { + padding: 60px 24px; + } + + &__wrapper { + max-width: 1200px; + width: 100%; + } + + &__title { + font-size: 46px; + line-height: 1.2; + font-weight: 700; + text-align: center; + margin-bottom: 64px; + + @include mediaMax($mobile) { + font-size: 32px; + margin-bottom: 48px; + } + } +} + +// FAQ Grid +$className: 'faq-grid'; + +.#{$className} { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 32px; + + @include mediaMax($tablet) { + grid-template-columns: 1fr; + gap: 24px; + } +} + +// FAQ Item +$className: 'faq-item'; + +.#{$className} { + &__question { + font-size: 20px; + line-height: 1.4; + font-weight: 700; + margin-bottom: 12px; + + @include mediaMax($mobile) { + font-size: 18px; + } + } + + &__answer { + font-size: 17px; + line-height: 1.6; + color: $fontSecondaryColor; + + a { + color: $accentPrimaryColor; + text-decoration: none; + transition: opacity 200ms; + + &:hover { + opacity: $opacityPrimaryValue; + } + } + + strong { + font-weight: 600; + color: $fontPrimaryColor; + } + } +} + +// Enterprise CTA +$className: 'enterprise-cta'; + +.#{$className} { + display: flex; + justify-content: center; + padding: 120px 64px; + background: linear-gradient(135deg, $accentPrimaryColor 0%, #d93636 100%); + + @include mediaMax($tablet) { + padding: 80px 24px; + } + + &__wrapper { + max-width: 1000px; + width: 100%; + } + + &__content { + text-align: center; + color: $fontPrimaryInvertedColor; + } + + &__title { + font-size: 46px; + line-height: 1.2; + font-weight: 700; + margin-bottom: 24px; + color: $fontPrimaryInvertedColor; + + @include mediaMax($mobile) { + font-size: 32px; + } + } + + &__description { + font-size: 20px; + line-height: 1.6; + margin-bottom: 48px; + opacity: 0.95; + + @include mediaMax($mobile) { + font-size: 17px; + margin-bottom: 32px; + } + } + + &__buttons { + display: flex; + gap: 16px; + justify-content: center; + flex-wrap: wrap; + + @include mediaMax($mobile) { + flex-direction: column; + } + } +} diff --git a/src/partials/header.hbs b/src/partials/header.hbs index 2de4352..95a2418 100644 --- a/src/partials/header.hbs +++ b/src/partials/header.hbs @@ -7,7 +7,7 @@ Customers Learn Docs - Pricing + Pricing
diff --git a/src/pricing.ts b/src/pricing.ts new file mode 100644 index 0000000..ca88c79 --- /dev/null +++ b/src/pricing.ts @@ -0,0 +1,176 @@ +import './index.scss' + +// Pricing toggle (Monthly/Annual) +const billingRadios = document.querySelectorAll('input[name="billing"]') +const priceAmounts = document.querySelectorAll('.pricing-card__amount') + +billingRadios.forEach(radio => { + radio.addEventListener('change', () => { + const isAnnual = radio.value === 'annual' + + priceAmounts.forEach(amount => { + const monthly = amount.getAttribute('data-monthly') + const annual = amount.getAttribute('data-annual') + + if (monthly && annual) { + amount.textContent = isAnnual ? `$${annual}` : `$${monthly}` + } + }) + }) +}) + +// Pricing Calculator +const connectionsSlider = document.getElementById('connections-slider') as HTMLInputElement +const connectionsValue = document.getElementById('connections-value') +const anycablePrice = document.getElementById('anycable-price') +const pusherPrice = document.getElementById('pusher-price') +const selfHostedPrice = document.getElementById('self-hosted-price') +const pusherSavings = document.getElementById('pusher-savings') + +function formatNumber(num: number): string { + if (num >= 1000) { + return (num / 1000).toFixed(num % 1000 === 0 ? 0 : 1) + 'K' + } + return num.toString() +} + +function calculatePricing(connections: number) { + // AnyCable Managed pricing + let anycableCost = 0 + let anycableTier = 'Free' + + if (connections <= 100) { + anycableCost = 0 + anycableTier = 'Managed Free' + } else if (connections <= 1000) { + anycableCost = 29 + anycableTier = 'Managed Starter' + } else if (connections <= 10000) { + anycableCost = 99 + anycableTier = 'Managed Pro' + } else if (connections <= 50000) { + anycableCost = 299 + anycableTier = 'Managed Scale' + } else { + anycableCost = 999 + anycableTier = 'Enterprise' + } + + // Pusher pricing (approximate) + let pusherCost = 0 + if (connections <= 100) { + pusherCost = 0 + } else if (connections <= 500) { + pusherCost = 49 + } else if (connections <= 1000) { + pusherCost = 49 + } else if (connections <= 2000) { + pusherCost = 99 + } else if (connections <= 5000) { + pusherCost = 199 + } else if (connections <= 10000) { + pusherCost = 299 + } else if (connections <= 20000) { + pusherCost = 499 + } else { + pusherCost = Math.ceil(connections / 20000) * 499 + } + + // Self-hosted (rough estimate) + let selfHostedCost = 0 + if (connections <= 1000) { + selfHostedCost = 85 // t3.medium + redis + } else if (connections <= 5000) { + selfHostedCost = 150 // t3.large + redis + } else if (connections <= 10000) { + selfHostedCost = 250 // t3.xlarge + redis + } else { + selfHostedCost = 350 + Math.floor((connections - 10000) / 10000) * 100 + } + + return { + anycable: { cost: anycableCost, tier: anycableTier }, + pusher: pusherCost, + selfHosted: selfHostedCost + } +} + +function updateCalculator() { + if (!connectionsSlider || !connectionsValue || !anycablePrice || !pusherPrice || !selfHostedPrice || !pusherSavings) { + return + } + + const connections = parseInt(connectionsSlider.value) + connectionsValue.textContent = formatNumber(connections) + + const pricing = calculatePricing(connections) + + // Update AnyCable + if (pricing.anycable.cost === 0) { + anycablePrice.innerHTML = '$0/month' + const recommendedCard = anycablePrice.closest('.pricing-result-card') + if (recommendedCard) { + const title = recommendedCard.querySelector('.pricing-result-card__title') + if (title) title.textContent = 'AnyCable ' + pricing.anycable.tier + const features = recommendedCard.querySelector('.pricing-result-card__features') + if (features) features.textContent = `• Up to ${formatNumber(connections)} connections\n• Free forever\n• Community support` + } + } else if (pricing.anycable.tier === 'Enterprise') { + anycablePrice.innerHTML = 'Custom' + } else { + anycablePrice.innerHTML = `$${pricing.anycable.cost}/month` + const recommendedCard = anycablePrice.closest('.pricing-result-card') + if (recommendedCard) { + const title = recommendedCard.querySelector('.pricing-result-card__title') + if (title) title.textContent = 'AnyCable ' + pricing.anycable.tier + } + } + + // Update Pusher + if (pricing.pusher === 0) { + pusherPrice.innerHTML = '$0/month' + } else { + pusherPrice.innerHTML = `$${pricing.pusher}/month` + } + + // Update Self-hosted + selfHostedPrice.innerHTML = `~$${pricing.selfHosted}/month` + + // Calculate savings + if (pricing.anycable.cost === 0 || pricing.pusher === 0) { + pusherSavings.textContent = '' + } else { + const savings = Math.round(((pricing.pusher - pricing.anycable.cost) / pricing.pusher) * 100) + if (savings > 0) { + pusherSavings.textContent = `Save ${savings}%` + pusherSavings.style.color = '#4caf50' + } else { + const extraCost = Math.round((Math.abs(savings) / 100) * pricing.anycable.cost) + pusherSavings.textContent = `+${Math.abs(savings)}% cost` + pusherSavings.style.color = '#d32f2f' + } + } +} + +if (connectionsSlider) { + connectionsSlider.addEventListener('input', updateCalculator) + // Initialize + updateCalculator() +} + +// Load GitHub stars +async function loadGitHubStars() { + try { + const response = await fetch('https://api.github.com/repos/anycable/anycable') + const data = await response.json() + const stars = data.stargazers_count + const counter = document.getElementById('gh-stars-counter') + if (counter) { + counter.textContent = (stars / 1000).toFixed(1) + 'K' + } + } catch (error) { + console.error('Failed to load GitHub stars:', error) + } +} + +loadGitHubStars() diff --git a/src/pricing/index.html b/src/pricing/index.html new file mode 100644 index 0000000..a033191 --- /dev/null +++ b/src/pricing/index.html @@ -0,0 +1,495 @@ + + + {{> dochead title="Pricing | AnyCable" description="Simple, transparent pricing for real-time infrastructure. Start free, scale as you grow."}} + +
+ {{> header}} +
+ +
+
+
+

Pricing That Scales With You

+

+ Start free, upgrade when you need more. No surprises. +

+ + +
+ + +
+
+
+
+ + +
+
+ + +
+
+

Open Source

+
Self-Hosted
+
+
+ $0 + forever +
+

+ Deploy to your own infrastructure. Full control, zero cost. +

+
    +
  • ✓ Unlimited connections
  • +
  • ✓ Core features (presence, history)
  • +
  • ✓ Hotwire/Turbo support
  • +
  • ✓ Prometheus metrics
  • +
  • ✓ Community support
  • +
  • ✓ Deploy anywhere (AWS, Heroku, Fly.io)
  • +
+ + Get Started + + +
+ + +
+
+

Managed Free

+
plus.anycable.io
+
+
+ $0 + /month +
+

+ Try our managed platform. No credit card required. +

+
    +
  • ✓ Up to 100 connections
  • +
  • ✓ All core features
  • +
  • ✓ 1 region (US East)
  • +
  • ✓ Community support
  • +
  • ✓ wss://[your-app].anycable.io
  • +
  • ⚡ 5-minute setup
  • +
+ + Start Free + + +
+ + +
+
+

Managed Starter

+
plus.anycable.io
+
+
+ $29 + /month +
+

+ Production-ready managed infrastructure for growing apps. +

+
    +
  • ✓ Up to 1,000 connections
  • +
  • ✓ All regions (US, EU, Asia)
  • +
  • ✓ Email support (48h)
  • +
  • ✓ 99.5% uptime SLA
  • +
  • ✓ SSL/TLS included
  • +
  • ✓ Usage analytics
  • +
+ + Start 14-Day Trial + + +
+ + + + + +
+
+

Managed Scale

+
plus.anycable.io
+
+
+ $299 + /month +
+

+ Enterprise-grade managed platform for mission-critical apps. +

+
    +
  • ✓ Up to 50,000 connections
  • +
  • ✓ Premium support (4h)
  • +
  • ✓ Dedicated resources
  • +
  • ✓ 99.95% uptime SLA
  • +
  • ✓ Slack channel access
  • +
  • ✓ Architecture review
  • +
  • ✓ Priority feature requests
  • +
+ + Start 14-Day Trial + + +
+ + +
+
+

Self-Hosted Pro

+
License + Updates
+
+
+ $99 + /month +
+

+ All Pro features for your own infrastructure. Unlimited scale. +

+
    +
  • Unlimited connections
  • +
  • ✓ Unlimited instances
  • +
  • ✓ All Pro features
  • +
  • ✓ Binary compression
  • +
  • ✓ GraphQL support
  • +
  • ✓ Long-polling fallback
  • +
  • ✓ Email support
  • +
  • ✓ 2-month free trial
  • +
+ + Start Free Trial + + +
+ +
+
+ + +
+
+

Compare All Features

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FeatureOpen SourceManaged FreeManaged StarterManaged ProManaged ScaleSelf-Hosted Pro
ConnectionsUnlimited*1001,00010,00050,000Unlimited*
HostingSelf-hostedManagedManagedManagedManagedSelf-hosted
RegionsYour choiceUS EastAll regionsAll regionsAll regionsYour choice
Presence Tracking
Reliable Streams
Signed Streams
Custom DomainN/AN/A
Binary Compression
GraphQL Support
Long-polling Fallback
SupportCommunityCommunityEmail (48h)Priority (12h)Premium (4h)Email
Uptime SLADIYBest effort99.5%99.9%99.95%DIY
+
+ +
+

* Limited by your infrastructure resources

+
+
+
+ + +
+
+

Estimate Your Costs

+

+ See how AnyCable compares to other solutions at your scale +

+ +
+
+
+ + +
1,000
+
+
+ +
+ + +
+

Pusher (equivalent)

+
$49/month
+
+69% cost
+
+ +
+

Self-Hosted (est.)

+
$85/month
+
+ AWS t3.medium + Redis
+ (infrastructure only) +
+
+
+
+
+
+ + +
+
+

Frequently Asked Questions

+ +
+
+

What happens if I exceed my connection limit?

+

+ We'll notify you via email when you approach your limit. You can upgrade anytime to a higher tier. + Your service won't be interrupted—we'll work with you to find the right plan. +

+
+ +
+

Can I switch between plans?

+

+ Yes! You can upgrade or downgrade anytime. Upgrades take effect immediately. + Downgrades take effect at the next billing cycle. All billing is prorated. +

+
+ +
+

What's included in support?

+

+ Community: GitHub Discussions, Discord
+ Email: Email support with response time based on plan
+ Premium: Slack channel, architecture reviews, priority features +

+
+ +
+

Do you offer discounts for nonprofits or open source?

+

+ Yes! We offer 50% discounts for nonprofits, educational institutions, and significant open-source projects. + Contact us at anycable@evilmartians.com +

+
+ +
+

What payment methods do you accept?

+

+ Credit cards (Visa, Mastercard, Amex) via Stripe. Annual plans can be paid via invoice for larger teams. +

+
+ +
+

Is there a free trial?

+

+ Yes! All paid managed plans include a 14-day free trial (no credit card required). + Self-Hosted Pro includes a 2-month free trial. +

+
+ +
+

What's your refund policy?

+

+ We offer a 30-day money-back guarantee for annual plans. Monthly plans can be cancelled anytime with no penalties. +

+
+ +
+

Need more than 50K connections?

+

+ Contact us for Enterprise pricing. We can support millions of connections with dedicated infrastructure, + custom SLAs, and white-glove support. +

+
+
+
+
+ + +
+
+
+

Need Enterprise Features?

+

+ Custom SLAs • Dedicated support • On-premises deployment • Security reviews • Training • Volume discounts +

+ +
+
+
+
+ {{> footer }} +
+ + + From d14fca2f00e5b7718b66fa46ce8126c2654c4719 Mon Sep 17 00:00:00 2001 From: Irina Nazarova Date: Fri, 12 Dec 2025 19:03:25 -0800 Subject: [PATCH 03/15] fix: update pricing for Self-Hosted Pro and Enterprise - Update Self-Hosted Pro from $99/mo to $130/mo ($108/mo annual) - Reframe Enterprise section to focus on source-available licensing - Highlight main Enterprise value: extending AnyCable with custom Go modules - Update Enterprise description to emphasize customization capabilities --- src/pricing/index.html | 453 ++++++++++++++++++++++++++--------------- 1 file changed, 286 insertions(+), 167 deletions(-) diff --git a/src/pricing/index.html b/src/pricing/index.html index a033191..626eef9 100644 --- a/src/pricing/index.html +++ b/src/pricing/index.html @@ -1,28 +1,32 @@ - {{> dochead title="Pricing | AnyCable" description="Simple, transparent pricing for real-time infrastructure. Start free, scale as you grow."}} + {{> dochead title="Pricing | AnyCable" description="Simple, transparent + pricing for real-time infrastructure. Start free, scale as you grow."}}
{{> header}}
-
-
-
-

Pricing That Scales With You

-

+

+
+
+

Pricing That Scales With You

+

Start free, upgrade when you need more. No surprises.

-
-
@@ -30,23 +34,22 @@

Pricing That Scales With You

-
-
- +
+
-
-
-

Open Source

-
Self-Hosted
+
+
+

Open Source

+
Self-Hosted
-
- $0 - forever +
+ $0 + forever
-

+

Deploy to your own infrastructure. Full control, zero cost.

-
    +
    • ✓ Unlimited connections
    • ✓ Core features (presence, history)
    • ✓ Hotwire/Turbo support
    • @@ -54,28 +57,33 @@

      Open Source

    • ✓ Community support
    • ✓ Deploy anywhere (AWS, Heroku, Fly.io)
    - + Get Started - -
    -
    -

    Managed Free

    -
    plus.anycable.io
    +
    +
    +

    Managed Free

    +
    plus.anycable.io
    -
    - $0 - /month +
    + $0 + /month
    -

    +

    Try our managed platform. No credit card required.

    -
      +
      • ✓ Up to 100 connections
      • ✓ All core features
      • ✓ 1 region (US East)
      • @@ -83,28 +91,38 @@

        Managed Free

      • ✓ wss://[your-app].anycable.io
      • ⚡ 5-minute setup
      - + Start Free - -
      -
      -

      Managed Starter

      -
      plus.anycable.io
      +
      +
      +

      Managed Starter

      +
      plus.anycable.io
      -
      - $29 - /month +
      + $29 + /month
      -

      +

      Production-ready managed infrastructure for growing apps.

      -
        +
        • ✓ Up to 1,000 connections
        • ✓ All regions (US, EU, Asia)
        • ✓ Email support (48h)
        • @@ -112,29 +130,39 @@

          Managed Starter

        • ✓ SSL/TLS included
        • ✓ Usage analytics
        - + Start 14-Day Trial - -
-
-
-

Compare All Features

+
+
+

Compare All Features

-
- +
+
@@ -346,52 +398,80 @@

Compare All Features

Feature
-
+

* Limited by your infrastructure resources

-
-
-

Estimate Your Costs

-

+

+
+

+ Estimate Your Costs +

+

See how AnyCable compares to other solutions at your scale

-
-
-
+
+
+
- -
1,000
+ +
+ 1,000 +
-
-
-
-
-

Frequently Asked Questions

+
+
+

Frequently Asked Questions

-
-
-

What happens if I exceed my connection limit?

-

- We'll notify you via email when you approach your limit. You can upgrade anytime to a higher tier. - Your service won't be interrupted—we'll work with you to find the right plan. +

+
+

+ What happens if I exceed my connection limit? +

+

+ We'll notify you via email when you approach your limit. You + can upgrade anytime to a higher tier. Your service won't be + interrupted—we'll work with you to find the right plan.

-
-

Can I switch between plans?

-

- Yes! You can upgrade or downgrade anytime. Upgrades take effect immediately. - Downgrades take effect at the next billing cycle. All billing is prorated. +

+

Can I switch between plans?

+

+ Yes! You can upgrade or downgrade anytime. Upgrades take + effect immediately. Downgrades take effect at the next billing + cycle. All billing is prorated.

-
-

What's included in support?

-

- Community: GitHub Discussions, Discord
- Email: Email support with response time based on plan
- Premium: Slack channel, architecture reviews, priority features +

+

What's included in support?

+

+ Community: GitHub Discussions, Discord
+ Email: Email support with response time based + on plan
+ Premium: Slack channel, architecture reviews, + priority features

-
-

Do you offer discounts for nonprofits or open source?

-

- Yes! We offer 50% discounts for nonprofits, educational institutions, and significant open-source projects. - Contact us at anycable@evilmartians.com +

+

+ Do you offer discounts for nonprofits or open source? +

+

+ Yes! We offer 50% discounts for nonprofits, educational + institutions, and significant open-source projects. Contact us + at + anycable@evilmartians.com

-
-

What payment methods do you accept?

-

- Credit cards (Visa, Mastercard, Amex) via Stripe. Annual plans can be paid via invoice for larger teams. +

+

+ What payment methods do you accept? +

+

+ Credit cards (Visa, Mastercard, Amex) via Stripe. Annual plans + can be paid via invoice for larger teams.

-
-

Is there a free trial?

-

- Yes! All paid managed plans include a 14-day free trial (no credit card required). - Self-Hosted Pro includes a 2-month free trial. +

+

Is there a free trial?

+

+ Yes! All paid managed plans include a 14-day free trial (no + credit card required). Self-Hosted Pro includes a 2-month free + trial.

-
-

What's your refund policy?

-

- We offer a 30-day money-back guarantee for annual plans. Monthly plans can be cancelled anytime with no penalties. +

+

What's your refund policy?

+

+ We offer a 30-day money-back guarantee for annual plans. + Monthly plans can be cancelled anytime with no penalties.

-
-

Need more than 50K connections?

-

- Contact us for Enterprise pricing. We can support millions of connections with dedicated infrastructure, - custom SLAs, and white-glove support. +

+

+ Need more than 50K connections? +

+

+ Contact us for Enterprise pricing. We can support millions of + connections with dedicated infrastructure, custom SLAs, and + white-glove support.

@@ -473,16 +573,35 @@

Need more than 50K connections?

-
-
-
-

Need Enterprise Features?

-

- Custom SLAs • Dedicated support • On-premises deployment • Security reviews • Training • Volume discounts +

+
+
+

+ Need Source-Available License? +

+

+ Extend AnyCable with custom Go modules for your + specific use cases +

+

+ Source-available license • Custom Go extensions • Dedicated + architecture support • On-premises deployment • Security reviews + • Custom SLAs

-
From c4c794db75e50c05064b49bc8030a4c517f2653a Mon Sep 17 00:00:00 2001 From: Irina Nazarova Date: Fri, 12 Dec 2025 19:05:48 -0800 Subject: [PATCH 04/15] fix: escape HTML entities in customers page - Fix HTML parsing error: change '< 100ms' to '< 100ms' - This was breaking Vite's HTML parser and preventing proper page loading --- src/customers/index.html | 445 +++++++++++++++++++++++++-------------- 1 file changed, 289 insertions(+), 156 deletions(-) diff --git a/src/customers/index.html b/src/customers/index.html index cc40c0d..d4be315 100644 --- a/src/customers/index.html +++ b/src/customers/index.html @@ -1,16 +1,18 @@ - {{> dochead title="Customers | AnyCable" description="Companies and teams building real-time features that scale with AnyCable. Customer stories and case studies."}} + {{> dochead title="Customers | AnyCable" description="Companies and teams + building real-time features that scale with AnyCable. Customer stories and + case studies."}}
{{> header}}
-
-
-
-

Who Uses AnyCable?

-

+

+
+
+

Who Uses AnyCable?

+

Trusted by companies building real-time features that scale

@@ -18,190 +20,295 @@

Who Uses AnyCable?

-
-
-

Trusted By

-
- - - - - - - - - - - +
+
+

Trusted By

+
+ + + + + + + + + + +
-
{{> footer }}
- + From 6a4120ab4601473eea90072a613eb3b91b1f83cb Mon Sep 17 00:00:00 2001 From: Irina Nazarova Date: Fri, 12 Dec 2025 19:25:20 -0800 Subject: [PATCH 06/15] fix: join dochead attribute line in pricing page - Fix line break in Handlebars dochead partial call - Multiline attributes can break Handlebars parsing --- src/pricing/index.html | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/pricing/index.html b/src/pricing/index.html index 6ce769c..26b615f 100644 --- a/src/pricing/index.html +++ b/src/pricing/index.html @@ -1,7 +1,6 @@ - {{> dochead title="Pricing | AnyCable" description="Simple, transparent - pricing for real-time infrastructure. Start free, scale as you grow."}} + {{> dochead title="Pricing | AnyCable" description="Simple, transparent pricing for real-time infrastructure. Start free, scale as you grow."}}
{{> header}} From 4492272f6e200a552e3fad6f5ef0bde139bf7ca8 Mon Sep 17 00:00:00 2001 From: Irina Nazarova Date: Fri, 12 Dec 2025 19:28:38 -0800 Subject: [PATCH 07/15] Fix typo: rename veriables.scss to variables.scss --- src/index-blog.scss | 2 +- src/index.scss | 2 +- src/modules/common.scss | 2 +- src/modules/{veriables.scss => variables.scss} | 0 4 files changed, 3 insertions(+), 3 deletions(-) rename src/modules/{veriables.scss => variables.scss} (100%) diff --git a/src/index-blog.scss b/src/index-blog.scss index fee5864..cedaa7f 100644 --- a/src/index-blog.scss +++ b/src/index-blog.scss @@ -2,7 +2,7 @@ @import url(./vendor/reset.scss); @import url(./vendor/fonts.css); @import './blog/styles/main.scss'; //old blog styles -@import './modules/veriables.scss'; +@import './modules/variables.scss'; @import './modules/mixins.scss'; @import './modules/blocks/button.scss'; @import './modules/blocks/footer.scss'; diff --git a/src/index.scss b/src/index.scss index 75b24f6..fb03ee9 100644 --- a/src/index.scss +++ b/src/index.scss @@ -1,7 +1,7 @@ @charset "utf-8"; @import url(./vendor/reset.scss); @import url(./vendor/fonts.css); -@import './modules/veriables.scss'; +@import './modules/variables.scss'; @import './modules/mixins.scss'; @import './modules/blocks/link.scss'; @import './modules/blocks/page.scss'; diff --git a/src/modules/common.scss b/src/modules/common.scss index f4f9bc4..47fae24 100644 --- a/src/modules/common.scss +++ b/src/modules/common.scss @@ -1,4 +1,4 @@ -@import './veriables.scss'; +@import './variables.scss'; @import './mixins.scss'; @font-face { diff --git a/src/modules/veriables.scss b/src/modules/variables.scss similarity index 100% rename from src/modules/veriables.scss rename to src/modules/variables.scss From c78a76c7f1f652225eed6dfa7e0d9e0645b61084 Mon Sep 17 00:00:00 2001 From: Irina Nazarova Date: Fri, 12 Dec 2025 19:32:00 -0800 Subject: [PATCH 08/15] Fix HTML entity in action-cable comparison page --- src/compare/action-cable/index.html | 364 +++++++++++++++++----------- 1 file changed, 226 insertions(+), 138 deletions(-) diff --git a/src/compare/action-cable/index.html b/src/compare/action-cable/index.html index ae6693f..fc636d1 100644 --- a/src/compare/action-cable/index.html +++ b/src/compare/action-cable/index.html @@ -1,16 +1,18 @@ - {{> dochead title="AnyCable vs Action Cable | Performance Comparison" description="AnyCable is 10x faster than Action Cable and scales better. Learn when to migrate and how to get started."}} + {{> dochead title="AnyCable vs Action Cable | Performance Comparison" + description="AnyCable is 10x faster than Action Cable and scales better. Learn + when to migrate and how to get started."}}
{{> header}}
-
-
-
-

AnyCable vs Action Cable

-

+

+
+
+

AnyCable vs Action Cable

+

The performance upgrade for your Rails real-time features

@@ -18,15 +20,17 @@

AnyCable vs Action Cable

-
-
-

TL;DR

-
-

- AnyCable is 10x faster and scales better than Action Cable, but requires running an additional server (AnyCable-Go). +

+
+

TL;DR

+
+

+ AnyCable is 10x faster and scales better than + Action Cable, but requires running an additional server + (AnyCable-Go).

-
-
+
+

Use Action Cable if:

  • Simple app with minimal real-time features
  • @@ -34,7 +38,9 @@

    Use Action Cable if:

  • You want zero additional infrastructure
-
+

Use AnyCable if:

  • Need high performance (> 500 connections)
  • @@ -49,44 +55,63 @@

    Use AnyCable if:

-
-
-
-

Performance Comparison

-
+
+
+
+

Performance Comparison

+

- Action Cable runs in Ruby, which means every WebSocket connection consumes Ruby process resources. - This becomes a bottleneck as your application scales. + Action Cable runs in Ruby, which means every + WebSocket connection consumes Ruby process resources. This + becomes a bottleneck as your application scales.

- AnyCable-Go is written in Go, a compiled language designed for concurrency. - It handles WebSocket connections efficiently while delegating business logic to your Rails app via RPC. + AnyCable-Go is written in Go, a compiled + language designed for concurrency. It handles WebSocket + connections efficiently while delegating business logic to + your Rails app via RPC.

-
-
-
10,000+
-
Concurrent connections per server
-
AnyCable
+
+
+
10,000+
+
+ Concurrent connections per server +
+
+ AnyCable +
-
-
~500
-
Concurrent connections per server
-
Action Cable
+
+
~500
+
+ Concurrent connections per server +
+
Action Cable
-
-
-
< 100 MB
-
Memory usage (10K connections)
-
AnyCable
+
+
+
< 100 MB
+
+ Memory usage (10K connections) +
+
+ AnyCable +
-
-
~2 GB
-
Memory usage (10K connections)
-
Action Cable
+
+
~2 GB
+
+ Memory usage (10K connections) +
+
Action Cable
@@ -94,12 +119,12 @@

Performance Comparison

-
-
-

Feature Comparison

+
+
+

Feature Comparison

-
- +
+
@@ -160,96 +185,132 @@

Feature Comparison

-
-
-

When to Migrate

-
-

- Consider migrating to AnyCable if you're experiencing any of these: +

+
+

When to Migrate

+
+

+ Consider migrating to AnyCable if you're experiencing any of + these:

-
-
-
-
- High server load: CPU usage consistently > 60% on WebSocket servers +
+
+
+
+ High server load: CPU usage consistently > + 60% on WebSocket servers
-
-
-
- Connection limits: More than 500 concurrent WebSocket connections +
+
+
+ Connection limits: More than 500 concurrent + WebSocket connections
-
-
-
- High infrastructure costs: Paying for multiple servers just for WebSockets +
+
+
+ High infrastructure costs: Paying for + multiple servers just for WebSockets
-
-
-
- Need presence: Want to show who's online without building it yourself +
+
+
+ Need presence: Want to show who's online + without building it yourself
-
-
-
- Message reliability: Need message history and automatic recovery +
+
+
+ Message reliability: Need message history + and automatic recovery
-
-
-
- Platform constraints: Deploying to Heroku, Fly.io, or other resource-limited platforms +
+
+
+ Platform constraints: Deploying to Heroku, + Fly.io, or other resource-limited platforms
-
-
-
-

How AnyCable Works with Rails

-
-
-
-
-
Browser
+
+
+

How AnyCable Works with Rails

+
+
+
+
+
Browser
-
↓ WebSocket
-
-
AnyCable-Go
-
Handles connections in Go
+
↓ WebSocket
+
+
+ AnyCable-Go +
+
+ Handles connections in Go +
-
↓ gRPC
-
-
Rails App
-
Your business logic
+
↓ gRPC
+
+
Rails App
+
+ Your business logic +
-
-
-
Redis
-
Pub/Sub
+
+
+
Redis
+
Pub/Sub
-
+

- AnyCable separates connection handling from business logic: + AnyCable separates connection handling from business + logic:

    -
  • AnyCable-Go maintains WebSocket connections efficiently in Go
  • -
  • Your Rails app handles channel subscriptions and business logic via gRPC
  • -
  • Redis is used for pub/sub, just like Action Cable
  • -
  • No code changes needed—AnyCable is 100% compatible with Action Cable API
  • +
  • + AnyCable-Go maintains WebSocket connections + efficiently in Go +
  • +
  • + Your Rails app handles channel + subscriptions and business logic via gRPC +
  • +
  • + Redis is used for pub/sub, just like Action + Cable +
  • +
  • + No code changes needed—AnyCable is 100% + compatible with Action Cable API +
@@ -257,59 +318,86 @@

How AnyCable Works with Rails

-
-
-

Getting Started

-
-

+

+
+

Getting Started

+
+

Migrating from Action Cable to AnyCable takes about 15 minutes:

-
-
-
1
-

Add the gem

-
gem 'anycable-rails'
+
+
+
1
+

Add the gem

+
gem 'anycable-rails'
-
-
2
-

Install AnyCable-Go

-
brew install anycable-go
+                
+
2
+

Install AnyCable-Go

+
brew install anycable-go
 # or download binary
-
-
3
-

Start the server

-
anycable-go
+
+
3
+

Start the server

+
anycable-go
-
-
4
-

Done!

-

Your existing Action Cable code works without changes

+
+
4
+

Done!

+

+ Your existing Action Cable code works without changes +

-
-
-
-

Ready to Upgrade?

-

+

+
+

Ready to Upgrade?

+

Keep your Action Cable code, get 10x better performance

-
From aeaaca1170e68e60a60e6d0a9c31ec500720483f Mon Sep 17 00:00:00 2001 From: Irina Nazarova Date: Fri, 12 Dec 2025 23:48:19 -0800 Subject: [PATCH 09/15] Fix CSS loading in dev mode for subdirectory pages --- src/partials/dochead.hbs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/partials/dochead.hbs b/src/partials/dochead.hbs index 8ba7edd..7f49d27 100644 --- a/src/partials/dochead.hbs +++ b/src/partials/dochead.hbs @@ -19,4 +19,5 @@ {{> analytics }} {{> meta }} {{> schemaorg }} + From 4c916ae47b9483d4af4cb8addcc8b891bd32319e Mon Sep 17 00:00:00 2001 From: Irina Nazarova Date: Fri, 12 Dec 2025 23:52:50 -0800 Subject: [PATCH 10/15] Add Ably to pricing calculator comparison - Add Ably pricing tiers to calculator logic - Display Ably as 4th comparison option in calculator - Calculate and show savings compared to Ably - Update pricing estimates based on Ably's pricing page --- src/js/components/pricing-calculator.ts | 69 ++++++++++++++++++++----- src/pricing/index.html | 23 ++++++--- 2 files changed, 72 insertions(+), 20 deletions(-) diff --git a/src/js/components/pricing-calculator.ts b/src/js/components/pricing-calculator.ts index 5545b4d..18cca0d 100644 --- a/src/js/components/pricing-calculator.ts +++ b/src/js/components/pricing-calculator.ts @@ -28,8 +28,10 @@ const connectionsSlider = document.getElementById( const connectionsValue = document.getElementById('connections-value'); const anycablePrice = document.getElementById('anycable-price'); const pusherPrice = document.getElementById('pusher-price'); +const ablyPrice = document.getElementById('ably-price'); const selfHostedPrice = document.getElementById('self-hosted-price'); const pusherSavings = document.getElementById('pusher-savings'); +const ablySavings = document.getElementById('ably-savings'); function formatNumber(num: number): string { if (num >= 1000) { @@ -60,27 +62,43 @@ function calculatePricing(connections: number) { anycableTier = 'Enterprise'; } - // Pusher pricing (approximate) + // Pusher pricing (approximate based on their pricing page) let pusherCost = 0; if (connections <= 100) { - pusherCost = 0; + pusherCost = 0; // Free tier } else if (connections <= 500) { - pusherCost = 49; + pusherCost = 49; // Standard plan } else if (connections <= 1000) { pusherCost = 49; } else if (connections <= 2000) { - pusherCost = 99; + pusherCost = 99; // Pro plan } else if (connections <= 5000) { pusherCost = 199; } else if (connections <= 10000) { - pusherCost = 299; + pusherCost = 299; // Business plan } else if (connections <= 20000) { pusherCost = 499; } else { pusherCost = Math.ceil(connections / 20000) * 499; } - // Self-hosted (rough estimate) + // Ably pricing (approximate based on their pricing page) + let ablyCost = 0; + if (connections <= 200) { + ablyCost = 0; // Free tier (200 concurrent connections) + } else if (connections <= 500) { + ablyCost = 29; // Starter + } else if (connections <= 2000) { + ablyCost = 79; // Growth + } else if (connections <= 10000) { + ablyCost = 299; // Business + } else if (connections <= 50000) { + ablyCost = 799; + } else { + ablyCost = Math.ceil(connections / 50000) * 799; + } + + // Self-hosted (rough estimate - AWS/infrastructure costs) let selfHostedCost = 0; if (connections <= 1000) { selfHostedCost = 85; // t3.medium + redis @@ -88,13 +106,16 @@ function calculatePricing(connections: number) { selfHostedCost = 150; // t3.large + redis } else if (connections <= 10000) { selfHostedCost = 250; // t3.xlarge + redis + } else if (connections <= 50000) { + selfHostedCost = 450; // t3.2xlarge + redis cluster } else { - selfHostedCost = 350 + Math.floor((connections - 10000) / 10000) * 100; + selfHostedCost = 450 + Math.floor((connections - 50000) / 50000) * 200; } return { anycable: { cost: anycableCost, tier: anycableTier }, pusher: pusherCost, + ably: ablyCost, selfHosted: selfHostedCost, }; } @@ -105,8 +126,10 @@ function updateCalculator() { !connectionsValue || !anycablePrice || !pusherPrice || + !ablyPrice || !selfHostedPrice || - !pusherSavings + !pusherSavings || + !ablySavings ) { return; } @@ -153,10 +176,17 @@ function updateCalculator() { pusherPrice.innerHTML = `$${pricing.pusher}/month`; } + // Update Ably + if (pricing.ably === 0) { + ablyPrice.innerHTML = '$0/month'; + } else { + ablyPrice.innerHTML = `$${pricing.ably}/month`; + } + // Update Self-hosted selfHostedPrice.innerHTML = `~$${pricing.selfHosted}/month`; - // Calculate savings + // Calculate Pusher savings if (pricing.anycable.cost === 0 || pricing.pusher === 0) { pusherSavings.textContent = ''; } else { @@ -167,13 +197,26 @@ function updateCalculator() { pusherSavings.textContent = `Save ${savings}%`; pusherSavings.style.color = '#4caf50'; } else { - const extraCost = Math.round( - (Math.abs(savings) / 100) * pricing.anycable.cost - ); - pusherSavings.textContent = `+${Math.abs(savings)}% cost`; + pusherSavings.textContent = `+${Math.abs(savings)}% more`; pusherSavings.style.color = '#d32f2f'; } } + + // Calculate Ably savings + if (pricing.anycable.cost === 0 || pricing.ably === 0) { + ablySavings.textContent = ''; + } else { + const savings = Math.round( + ((pricing.ably - pricing.anycable.cost) / pricing.ably) * 100 + ); + if (savings > 0) { + ablySavings.textContent = `Save ${savings}%`; + ablySavings.style.color = '#4caf50'; + } else { + ablySavings.textContent = `+${Math.abs(savings)}% more`; + ablySavings.style.color = '#d32f2f'; + } + } } if (connectionsSlider) { diff --git a/src/pricing/index.html b/src/pricing/index.html index 26b615f..efeb6b6 100644 --- a/src/pricing/index.html +++ b/src/pricing/index.html @@ -1,6 +1,7 @@ - {{> dochead title="Pricing | AnyCable" description="Simple, transparent pricing for real-time infrastructure. Start free, scale as you grow."}} + {{> dochead title="Pricing | AnyCable" description="Simple, transparent + pricing for real-time infrastructure. Start free, scale as you grow."}}
{{> header}} @@ -450,24 +451,32 @@

-

- Pusher (equivalent) -

+

Pusher

$49/month
- +69% cost + Save 41% +
+
+ +
+

Ably

+
+ $79/month +
+
+ Save 63%
-

Self-Hosted (est.)

+

Self-Hosted

- $85/month + ~$85/month
AWS t3.medium + Redis
From 67224807e10cf8aa239cb6698a6b3b017bbdbf63 Mon Sep 17 00:00:00 2001 From: Irina Nazarova Date: Thu, 2 Apr 2026 16:04:58 -0700 Subject: [PATCH 11/15] Redesign main page: industry showcase, customer modals, updated content Main page: - Replace use-cases section with industry showcase (Healthtech, Fintech, Field Services & IoT, Communities, And more) featuring 36 companies with descriptions, links, and clickable "learn more" modals - Add Doximity podcast quote from On Rails podcast - Update "Why AnyCable?" text with new positioning (framework-agnostic, delivery guarantees, any backend) - Redesign resources section using two-halves layout with doc icon cards, video thumbnails with play overlays, and newsletter list - Add company modal with logos, detail text, and case study links - Floating animation on hero logo - Smooth scroll with header offset for anchor navigation Navigation: - Header links now anchor to page sections (Features, Customers, Pricing, Docs, Resources) instead of separate pages - Remove underline on header link hover New content: - Socket.io comparison page (/compare/socket-io) with reliability focus - AnyCable vs Socket.io blog post draft - llms.txt for LLM discoverability - 7 new Evil Martians blog posts added to blog index - Compare index updated with Socket.io, Action Cable, Pusher/Ably, Centrifugo, Laravel Reverb, Django Channels comparisons Removed pages: - /features, /learn, /pricing, /pro, /customers (consolidated to main) - /compare/ably, /compare/pusher, /compare/action-cable Assets: - 25 new company logos (favicons + SVGs for Doximity, CoinGecko, Agero) - JS and Laravel doc icons - Company modal JS Styling: - Consistent border grid across all two-halves sections - Standardized button styles (primary, secondary, tab active states) - Footer background darkened (#1a1a1a), Discord link removed - Smooth scrolling with scroll-margin-top for sticky header --- src/blog/anycable-vs-socket-io/index.md | 108 ++++ src/compare/ably/index.html | 131 ----- src/compare/action-cable/index.html | 409 --------------- src/compare/index.html | 187 ++++--- src/compare/pusher/index.html | 220 -------- src/compare/socket-io/index.html | 255 ++++++++++ src/customers/index.html | 403 --------------- src/features/index.html | 230 --------- src/images/js-icon.svg | 5 + src/images/laravel-icon.svg | 4 + src/images/logos/agero.png | Bin 0 -> 1895 bytes src/images/logos/agero.svg | 117 +++++ src/images/logos/calltrackingmetrics.png | Bin 0 -> 735 bytes src/images/logos/coingecko.png | Bin 0 -> 3139 bytes src/images/logos/coingecko.svg | 35 ++ src/images/logos/companycam.png | Bin 0 -> 7896 bytes src/images/logos/dext.png | Bin 0 -> 556 bytes src/images/logos/doximity.png | Bin 0 -> 1486 bytes src/images/logos/doximity.svg | 5 + src/images/logos/ev-connection.png | Bin 0 -> 1165 bytes src/images/logos/floatcard.png | Bin 0 -> 829 bytes src/images/logos/freeagent.png | Bin 0 -> 1947 bytes src/images/logos/fullscript.png | Bin 0 -> 795 bytes src/images/logos/headway.png | Bin 0 -> 574 bytes src/images/logos/jane.png | Bin 0 -> 1604 bytes src/images/logos/jobber.png | Bin 0 -> 1811 bytes src/images/logos/mighty-networks.png | Bin 0 -> 1067 bytes src/images/logos/qualified.png | Bin 0 -> 1150 bytes src/images/logos/rangee.png | Bin 0 -> 726 bytes src/images/logos/sessionshealth.png | Bin 0 -> 1134 bytes src/images/logos/tasktag.png | Bin 0 -> 726 bytes src/images/logos/uscreen.png | Bin 0 -> 845 bytes src/images/logos/uula.png | Bin 0 -> 2227 bytes src/images/logos/via.png | Bin 0 -> 664 bytes src/images/logos/vinita.png | Bin 0 -> 2396 bytes src/images/logos/wawafertility.png | Bin 0 -> 1181 bytes src/images/logos/wealthbox.png | Bin 0 -> 1404 bytes src/images/logos/yay.png | Bin 0 -> 2113 bytes src/images/logos/zyda.png | Bin 0 -> 876 bytes src/index.html | 16 + src/js/company-modal.ts | 96 ++++ src/learn/index.html | 295 ----------- src/modules/blocks/about-slide.scss | 29 +- src/modules/blocks/cases-slide.scss | 153 +++++- src/modules/blocks/customers.scss | 381 +++++++++----- src/modules/blocks/footer.scss | 2 +- src/modules/blocks/header.scss | 1 - src/modules/blocks/main-slide.scss | 13 + src/modules/blocks/popup.scss | 82 +++ src/modules/blocks/resources-slide.scss | 180 +++++-- src/modules/blocks/slide.scss | 7 +- src/modules/common.scss | 4 + src/partials/blog_index_body.hbs | 16 + src/partials/dochead.hbs | 2 +- src/partials/footer.hbs | 8 - src/partials/header.hbs | 10 +- src/partials/meta.hbs | 16 +- src/partials/schemaorg.hbs | 2 +- src/partials/slides/about-slide.hbs | 22 +- src/partials/slides/cases-slide.hbs | 332 ++++++++++-- src/partials/slides/main-slide.hbs | 10 +- src/partials/slides/plans-slide.hbs | 4 +- src/partials/slides/resources-slide.hbs | 280 ++++------ src/pricing/index.html | 622 ----------------------- src/pro/index.html | 9 - src/public/llms.txt | 164 ++++++ 66 files changed, 2031 insertions(+), 2834 deletions(-) create mode 100644 src/blog/anycable-vs-socket-io/index.md delete mode 100644 src/compare/ably/index.html delete mode 100644 src/compare/action-cable/index.html delete mode 100644 src/compare/pusher/index.html create mode 100644 src/compare/socket-io/index.html delete mode 100644 src/customers/index.html delete mode 100644 src/features/index.html create mode 100644 src/images/js-icon.svg create mode 100644 src/images/laravel-icon.svg create mode 100644 src/images/logos/agero.png create mode 100644 src/images/logos/agero.svg create mode 100644 src/images/logos/calltrackingmetrics.png create mode 100644 src/images/logos/coingecko.png create mode 100644 src/images/logos/coingecko.svg create mode 100644 src/images/logos/companycam.png create mode 100644 src/images/logos/dext.png create mode 100644 src/images/logos/doximity.png create mode 100644 src/images/logos/doximity.svg create mode 100644 src/images/logos/ev-connection.png create mode 100644 src/images/logos/floatcard.png create mode 100644 src/images/logos/freeagent.png create mode 100644 src/images/logos/fullscript.png create mode 100644 src/images/logos/headway.png create mode 100644 src/images/logos/jane.png create mode 100644 src/images/logos/jobber.png create mode 100644 src/images/logos/mighty-networks.png create mode 100644 src/images/logos/qualified.png create mode 100644 src/images/logos/rangee.png create mode 100644 src/images/logos/sessionshealth.png create mode 100644 src/images/logos/tasktag.png create mode 100644 src/images/logos/uscreen.png create mode 100644 src/images/logos/uula.png create mode 100644 src/images/logos/via.png create mode 100644 src/images/logos/vinita.png create mode 100644 src/images/logos/wawafertility.png create mode 100644 src/images/logos/wealthbox.png create mode 100644 src/images/logos/yay.png create mode 100644 src/images/logos/zyda.png create mode 100644 src/js/company-modal.ts delete mode 100644 src/learn/index.html delete mode 100644 src/pricing/index.html delete mode 100644 src/pro/index.html create mode 100644 src/public/llms.txt diff --git a/src/blog/anycable-vs-socket-io/index.md b/src/blog/anycable-vs-socket-io/index.md new file mode 100644 index 0000000..5e5ae86 --- /dev/null +++ b/src/blog/anycable-vs-socket-io/index.md @@ -0,0 +1,108 @@ +# AnyCable vs Socket.io: built-in reliability vs roll-your-own + +April 1, 2026 +{date} + +
+ +Socket.io is the most popular WebSocket library in the JavaScript ecosystem. It's battle-tested, well-documented, and free. So why would you use AnyCable instead? Because Socket.io doesn't guarantee delivery — and in production, that's the problem that breaks everything else. +{intro} + +
+ +## Socket.io doesn't guarantee delivery + +This is the core difference between the two tools, and everything else flows from it. + +Socket.io provides **at-most-once delivery**. Their own [documentation][socketio-delivery] says it plainly: "if the connection is broken while an event is being sent, then there is no guarantee that the other side has received it." There is no retry, no buffering for disconnected clients, and no catch-up on reconnection. + +AnyCable provides **at-least-once delivery**. Publications are stored in logs. The client tracks its stream position. On reconnection, it automatically recovers every missed message. Your application code doesn't change — you broadcast messages, and every client receives every one of them, even through disconnections. + +This matters more than you think. WebSocket connections are not as reliable as they appear in development. In production, micro-disconnections happen on stable connections, longer interruptions occur during commutes and subway rides, and every server deploy severs active connections. Without delivery guarantees, every one of these events silently corrupts your client state. + +### What this looks like in practice + +**Live chat:** A user enters a tunnel for 3 seconds. Two messages arrive in the group chat during that window. With Socket.io, those messages are gone — the user sees an incomplete conversation. With AnyCable, the client catches up automatically on reconnection. + +**LLM/AI streaming:** You're streaming an AI response word by word. The client briefly loses connection mid-sentence. With Socket.io, the response is truncated or garbled — chunks are lost with no recovery. With AnyCable, every chunk is recovered in order. As we described in our article on [the pitfalls of LLM streaming][llm-streaming], this is a real production problem that breaks AI-powered features. + +**Real-time dashboards:** A monitoring dashboard shows live metrics. A 200ms network blip causes a gap in the data. With Socket.io, that gap is permanent. With AnyCable, the missed data points are recovered and the chart stays complete. + +**Deploys:** You ship code. With Socket.io, every WebSocket connection is severed — every client must reconnect, and any in-flight messages are lost. With AnyCable, the Go-based WebSocket server is a separate process from your application. When you deploy your app, the WebSocket server stays up. Connections are never interrupted. Users never notice a deployment. + +As Doximity engineers [described on the On Rails podcast][doximity-podcast]: + +> "AnyCable allows them to keep that connection open. That Go service stays up, and you can continue shipping your Rails application as normal." + +## A standalone server, not an embedded library + +Socket.io is a library you embed in your Node.js application. It runs in the same process as your business logic, competing for the same CPU and memory. + +AnyCable is a standalone server written in Go. It handles WebSocket connections independently — your application communicates with it via HTTP API. This separation has three consequences: + +1. **Scale independently.** Need more WebSocket capacity? Scale AnyCable. Need more app server capacity? Scale your app. They don't compete for resources. + +2. **Deploy independently.** Ship code to your app without touching WebSocket connections. This is why connections survive deploys. + +3. **Use any backend language.** Socket.io locks you into Node.js. AnyCable works with Rails, Laravel, Node, Python/FastAPI, or any backend that can make HTTP requests. Broadcast a message from any language: + +``` +POST /api/v1/broadcasts +{ "stream": "chat/42", "data": "{\"message\": \"hello\"}" } +``` + +### Why Go for WebSockets + +WebSocket connections are long-lived — a user can hold one open for hours. In Node.js, each connection consumes meaningful memory in the V8 runtime. At 10,000 concurrent connections, this adds up fast. + +Go's goroutine-based concurrency model handles long-lived connections with minimal overhead. AnyCable serves 10,000+ concurrent connections per server using a fraction of the memory that Node.js or Ruby would need. And Go compiles to a single binary — no `node_modules`, no runtime version conflicts. + +## What you don't have to build + +With Socket.io, you get a WebSocket transport and rooms. Everything else is your responsibility: + +| What you need | Socket.io | AnyCable | +|---------------|-----------|----------| +| Reliable delivery | No (at-most-once) | Built-in (at-least-once) | +| Missed message recovery | No catch-up mechanism | Automatic on reconnection | +| Presence (who's online) | Build it yourself | Built-in | +| Authentication | Build your own middleware | JWT, signed streams | +| Pub/sub clustering | Redis adapter (separate setup) | Embedded NATS (zero extra infra) | +| Monitoring | Add your own | Prometheus & StatsD built-in | +| Binary compression | No | Yes (Pro) | +| Deploy resilience | Not possible (same process) | Built-in (separate server) | + +Each of these is weeks of engineering work to build properly, and months to battle-test in production. AnyCable ships them as built-in primitives because we've been doing this since 2017. + +## Proven at scale + +AnyCable has been powering real-time features in production since 2017, for companies like: + +- **Doximity** — telehealth for 80% of US physicians +- **CoinGecko** — cryptocurrency market data at massive scale +- **Jobber** — field service management ($167M revenue) +- **Headway** — mental health therapy ($2.3B valuation) +- **Circle** — community platform ($30M+ raised) +- **ClickFunnels** — sales funnels (~$265M revenue) + +The project is actively developed with regular releases, a dedicated team, commercial Pro support, and a growing ecosystem including [Laravel support][laravel-post], [Pusher protocol compatibility](https://docs.anycable.io/guides/pusher), and the emerging [Durable Streams](https://docs.anycable.io/guides/durable_streams) standard. + +## When Socket.io is the right choice + +To be fair: if you're building a small Node.js app, prototyping, or need full control over a custom protocol, Socket.io is a fine choice. It's well-documented, has a massive community, and is free. + +But if you need delivery guarantees — and in production, you almost certainly do — you'll end up building them yourself on top of Socket.io. AnyCable gives you delivery guarantees, presence, authentication, and scaling out of the box, with any backend language, tested in production by companies serving millions of users. + +## Get started + +- [Documentation](https://docs.anycable.io) +- [Rails getting started guide](https://docs.anycable.io/rails/getting_started) +- [Laravel guide](https://docs.anycable.io/guides/laravel) +- [JavaScript/TypeScript client](https://github.com/anycable/anycable-client) +- [GitHub](https://github.com/anycable/anycable) +- [AnyCable Pro](https://plus.anycable.io/pro) — free 2-month trial + +[socketio-delivery]: https://socket.io/docs/v4/delivery-guarantees +[llm-streaming]: https://evilmartians.com/chronicles/anycable-rails-and-the-pitfalls-of-llm-streaming +[doximity-podcast]: https://podcast.rubyonrails.org/2462975/episodes/17653501 +[laravel-post]: https://evilmartians.com/chronicles/anycable-for-laravel diff --git a/src/compare/ably/index.html b/src/compare/ably/index.html deleted file mode 100644 index 66fa5af..0000000 --- a/src/compare/ably/index.html +++ /dev/null @@ -1,131 +0,0 @@ - - - {{> dochead title="AnyCable vs Ably | Rails Real-Time Comparison" description="Compare AnyCable and Ably for Rails applications. Native integration vs managed service."}} - -
- {{> header}} -
- -
-
-
-

AnyCable vs Ably

-

- Native Rails integration vs enterprise managed platform -

-
-
-
- - -
-
-

TL;DR

-
-

- AnyCable offers native Rails integration and full control, while Ably provides an enterprise-grade managed platform with extensive SDKs. -

-
-
-

Use Ably if:

-
    -
  • Need enterprise SLAs
  • -
  • Want managed infrastructure
  • -
  • Using multiple platforms/languages
  • -
  • Need global edge delivery
  • -
-
-
-

Use AnyCable if:

-
    -
  • Building with Rails
  • -
  • Want native Action Cable compatibility
  • -
  • Prefer self-hosting option
  • -
  • Need flexible deployment
  • -
-
-
-
-
-
- - -
-
-

Feature Comparison

- -
-
Feature
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FeatureAblyAnyCable
DeploymentManaged onlySelf-hosted or Managed (AnyCable+)
Rails IntegrationSDK/Adapter✓ Native Action Cable
Presence
Message History✓ Extensive
Open Source
Starting Price$29/moFree / $29/mo / $490/yr
SDKs25+ languagesRails, JavaScript, any via RPC
Data ControlAbly's infrastructureYour infrastructure
-
-
-
- - -
-
-

Rails-Native Real-Time

-

- Get started with AnyCable's native Rails integration -

- -
-
-
- {{> footer }} -
- - - diff --git a/src/compare/action-cable/index.html b/src/compare/action-cable/index.html deleted file mode 100644 index fc636d1..0000000 --- a/src/compare/action-cable/index.html +++ /dev/null @@ -1,409 +0,0 @@ - - - {{> dochead title="AnyCable vs Action Cable | Performance Comparison" - description="AnyCable is 10x faster than Action Cable and scales better. Learn - when to migrate and how to get started."}} - -
- {{> header}} -
- -
-
-
-

AnyCable vs Action Cable

-

- The performance upgrade for your Rails real-time features -

-
-
-
- - -
-
-

TL;DR

-
-

- AnyCable is 10x faster and scales better than - Action Cable, but requires running an additional server - (AnyCable-Go). -

-
-
-

Use Action Cable if:

-
    -
  • Simple app with minimal real-time features
  • -
  • Less than 500 concurrent connections
  • -
  • You want zero additional infrastructure
  • -
-
-
-

Use AnyCable if:

-
    -
  • Need high performance (> 500 connections)
  • -
  • CPU usage > 60% on Action Cable
  • -
  • Need advanced features (presence, history)
  • -
  • Deploying to resource-constrained platforms
  • -
-
-
-
-
-
- - -
-
-
-

Performance Comparison

-
-

- Action Cable runs in Ruby, which means every - WebSocket connection consumes Ruby process resources. This - becomes a bottleneck as your application scales. -

-

- AnyCable-Go is written in Go, a compiled - language designed for concurrency. It handles WebSocket - connections efficiently while delegating business logic to - your Rails app via RPC. -

-
- -
-
-
10,000+
-
- Concurrent connections per server -
-
- AnyCable -
-
-
-
~500
-
- Concurrent connections per server -
-
Action Cable
-
-
- -
-
-
< 100 MB
-
- Memory usage (10K connections) -
-
- AnyCable -
-
-
-
~2 GB
-
- Memory usage (10K connections) -
-
Action Cable
-
-
-
-
-
- - -
-
-

Feature Comparison

- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FeatureAction CableAnyCable
Drop-in compatibilityN/A✓ 100% compatible
Concurrent connections~500 per server10,000+ per server
Memory usageHigh (Ruby processes)Low (Go efficiency)
Presence tracking✗ (manual implementation)✓ Built-in
Reliable streams✓ Message history & recovery
Signed streams (Hotwire)✓ Zero-config support
InstrumentationBasicComprehensive metrics
Deployment complexitySimple (included in Rails)Additional server required
Infrastructure costHigher at scaleLower at scale
-
-
-
- - -
-
-

When to Migrate

-
-

- Consider migrating to AnyCable if you're experiencing any of - these: -

- -
-
-
-
- High server load: CPU usage consistently > - 60% on WebSocket servers -
-
-
-
-
- Connection limits: More than 500 concurrent - WebSocket connections -
-
-
-
-
- High infrastructure costs: Paying for - multiple servers just for WebSockets -
-
-
-
-
- Need presence: Want to show who's online - without building it yourself -
-
-
-
-
- Message reliability: Need message history - and automatic recovery -
-
-
-
-
- Platform constraints: Deploying to Heroku, - Fly.io, or other resource-limited platforms -
-
-
- - -
-
-
- - -
-
-

How AnyCable Works with Rails

-
-
-
-
-
Browser
-
-
↓ WebSocket
-
-
- AnyCable-Go -
-
- Handles connections in Go -
-
-
↓ gRPC
-
-
Rails App
-
- Your business logic -
-
-
-
-
Redis
-
Pub/Sub
-
-
-
-
-

- AnyCable separates connection handling from business - logic: -

-
    -
  • - AnyCable-Go maintains WebSocket connections - efficiently in Go -
  • -
  • - Your Rails app handles channel - subscriptions and business logic via gRPC -
  • -
  • - Redis is used for pub/sub, just like Action - Cable -
  • -
  • - No code changes needed—AnyCable is 100% - compatible with Action Cable API -
  • -
-
-
-
-
- - -
-
-

Getting Started

-
-

- Migrating from Action Cable to AnyCable takes about 15 minutes: -

- -
-
-
1
-

Add the gem

-
gem 'anycable-rails'
-
- -
-
2
-

Install AnyCable-Go

-
brew install anycable-go
-# or download binary
-
- -
-
3
-

Start the server

-
anycable-go
-
- -
-
4
-

Done!

-

- Your existing Action Cable code works without changes -

-
-
- - -
-
-
- - -
-
-

Ready to Upgrade?

-

- Keep your Action Cable code, get 10x better performance -

- -
-
-
- {{> footer }} -
- - - diff --git a/src/compare/index.html b/src/compare/index.html index 16a5ae7..4888a42 100644 --- a/src/compare/index.html +++ b/src/compare/index.html @@ -1,6 +1,6 @@ - {{> dochead title="Compare | AnyCable" description="Compare AnyCable with other real-time solutions: Action Cable, Pusher, Ably, and Socket.io"}} + {{> dochead title="Compare AnyCable vs Socket.io, Pusher, Ably, Action Cable, Centrifugo" description="Compare AnyCable with Socket.io, Pusher, Ably, Action Cable, Laravel Reverb, Django Channels, and Centrifugo. Built-in delivery guarantees, presence tracking, and message ordering for any backend."}}
{{> header}} @@ -23,72 +23,117 @@

Find the Right Real-Time Solution

Compare AnyCable with:

- +

More comparisons

+ +
+ - + -
+
-

Socket.io

- Node.js +

Centrifugo

+ Go

- Popular Node.js WebSocket library. Compare ecosystem, performance, and backend flexibility. + Both are Go-based WebSocket servers with pub/sub. AnyCable has native Rails and Laravel SDKs, Action Cable protocol compatibility, and a managed SaaS option (AnyCable+). Centrifugo uses its own protocol and is self-hosted only.

- Coming Soon +
+
+ Native Rails & Laravel SDKs +
+
+ Managed SaaS option +
+
+
+ +
+
+

Laravel Reverb

+ Laravel +
+

+ Laravel Reverb does not provide delivery guarantees, message ordering, or presence tracking. AnyCable's native Laravel SDK provides all three out of the box, plus embedded NATS for zero-infrastructure clustering. +

+
+
+ Reliable delivery +
+
+ Presence tracking +
+
+
+ +
+
+

Django Channels & FastAPI WebSockets

+ Python +
+

+ Django Channels and FastAPI's built-in WebSocket support give you transport — delivery guarantees, presence, and message ordering are DIY. AnyCable runs alongside your Python app as a standalone server, broadcasting via HTTP API. +

+
+
+ Standalone server +
+
+ HTTP API broadcasting +
+
@@ -105,60 +150,74 @@

Quick Feature Comparison

Feature AnyCable + Socket.io + Pusher / Ably Action Cable - Pusher - Ably - Deployment - Self-hosted + Managed - Self-hosted only - Managed only - Managed only + Type + Standalone server (Go) + Library (Node.js) + Managed service + Built into Rails - Rails Integration - ✓ Native + Reliable Delivery + ✓ Built-in + ✗ Build your own ✓ Built-in - ✗ SDK only - ✗ SDK only + ✗ No - Concurrent Connections - 10,000+ - ~500 - 100,000+ - 100,000+ + Presence Tracking + ✓ Built-in + ✗ Build your own + ✓ Built-in + ✗ No - Presence Tracking - ✓ - ✗ - ✓ - ✓ + Missed Message Recovery + ✓ Automatic catch-up + ✗ No + ✓ Built-in + ✗ No + + + Backend Language + Any (Rails, Laravel, Python, Node, HTTP API) + Node.js only + Any (SDK/API) + Rails only + + + Deploy Resilience + ✓ Connections survive + ✗ Restart = disconnect + ✓ Managed + ✗ Restart = disconnect - Message History - ✓ - ✗ - ✓ - ✓ + Self-Hosted / HIPAA + ✓ On your servers + ✓ Self-hosted + ✗ Third-party servers + ✓ Self-hosted - Open Source - ✓ - ✓ - ✗ - ✗ + Scaling + 10,000+ per server + Varies (Node.js) + Managed + ~500 per server Starting Price - Free / $29/mo - Free - $49/mo - $29/mo + Free / Pro $1,490/yr + Free (+ infra) + From $49/mo (grows with connections) + Free (included in Rails) diff --git a/src/compare/pusher/index.html b/src/compare/pusher/index.html deleted file mode 100644 index 415c033..0000000 --- a/src/compare/pusher/index.html +++ /dev/null @@ -1,220 +0,0 @@ - - - {{> dochead title="AnyCable vs Pusher | Cost & Feature Comparison" description="AnyCable gives you full control and costs 60% less at scale. Compare features, pricing, and Rails integration."}} - -
- {{> header}} -
- -
-
-
-

AnyCable vs Pusher

-

- Full control, better Rails integration, lower costs -

-
-
-
- - -
-
-

TL;DR

-
-

- AnyCable gives you full control and costs 60% less at scale, but you manage infrastructure (or use AnyCable+). -

-
-
-

Use Pusher if:

-
    -
  • Just getting started
  • -
  • Want zero ops overhead
  • -
  • Need global edge network
  • -
  • Don't use Rails
  • -
-
-
-

Use AnyCable if:

-
    -
  • Cost-conscious at scale
  • -
  • Need Rails integration
  • -
  • Want full control over data
  • -
  • Prefer self-hosting
  • -
-
-
-
-
-
- - -
-
-
-

Pricing Comparison

-
-

- Pusher's pricing scales with connections, which can become expensive as your app grows. - With AnyCable, you pay once for the Pro license or a flat monthly fee for managed hosting. -

-
- -
-
-
$29-299
-
Per month (100-10K connections)
-
AnyCable+ (Managed)
-
-
-
$49-499
-
Per month (100-10K connections)
-
Pusher
-
-
- -
-
-
$490/yr
-
One-time license (unlimited)
-
AnyCable Pro
-
-
-
~$50-100
-
Monthly infrastructure (self-hosted)
-
AnyCable Open Source
-
-
-
-
-
- - -
-
-

Feature Comparison

- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FeaturePusherAnyCable
DeploymentManaged onlySelf-hosted or Managed (AnyCable+)
Rails IntegrationSDK only✓ Native Action Cable compatibility
Presence
Message History
Data ControlPusher's serversYour infrastructure
Open Source
Price ScalingIncreases with connectionsFlat (Pro) or predictable tiers (AnyCable+)
SDKs20+ languagesRails, JavaScript, any backend via RPC
Global Edge✓ Built-inVia deployment strategy
-
-
-
- - -
-
-

Migrating from Pusher

-
-

- If you're using Pusher with Rails, migrating to AnyCable gives you better integration and lower costs: -

- -
-
-
-
- Replace Pusher SDK with native Action Cable channels -
-
-
-
-
- Use cable_ready or turbo_stream for server-to-client updates -
-
-
-
-
- Deploy AnyCable-Go for high-performance WebSocket handling -
-
-
-
-
- Keep your business logic in Rails where it belongs -
-
-
- - -
-
-
- - -
-
-

Ready to Save on Real-Time?

-

- Try AnyCable+ free or self-host with open source -

- -
-
-
- {{> footer }} -
- - - diff --git a/src/compare/socket-io/index.html b/src/compare/socket-io/index.html new file mode 100644 index 0000000..a49c9f7 --- /dev/null +++ b/src/compare/socket-io/index.html @@ -0,0 +1,255 @@ + + + {{> dochead title="AnyCable vs Socket.io | WebSocket Server Comparison" description="Compare AnyCable and Socket.io for real-time applications. Built-in reliability, presence tracking, and framework-agnostic architecture vs DIY on Node.js."}} + +
+ {{> header}} +
+ +
+
+
+

AnyCable vs Socket.io

+

+ Built-in reliability and high-level primitives vs roll-your-own on Node.js +

+
+
+
+ + +
+
+

The reliability problem

+
+

+ WebSocket connections are not as reliable as they appear. On a real-time scale, connectivity is not that solid and smooth: micro-disconnections happen even on stable connections, longer interruptions occur during commutes and subway rides, and every server deploy severs active connections. Most WebSocket libraries, including Socket.io, give you a transport layer and leave you to solve these problems yourself. +

+
+ +
+
+

Message ordering

+

+ When you broadcast multiple messages to the same client in rapid succession, concurrent threads can deliver them out of order. This is especially critical for AI/LLM streaming, where chunks must arrive sequentially. Socket.io provides no ordering guarantees. AnyCable maintains publication logs with position metadata, ensuring messages always arrive in order. +

+
+ +
+

Missed messages during disconnections

+

+ When a client briefly loses connection — even for a few hundred milliseconds — any messages sent during that gap are lost forever. Socket.io offers automatic reconnection but no catch-up mechanism. AnyCable provides at-least-once delivery: publications are stored in logs, the client tracks its position, and automatically recovers all missed messages on reconnection. +

+
+ +
+

Deploys drop all connections

+

+ When you deploy your Socket.io server, every WebSocket connection is severed and clients must reconnect. For products like video calls, live collaboration, or real-time monitoring, this is unacceptable. AnyCable's Go server stays up during application deploys — connections are never interrupted, and users never notice a deployment. +

+
+ +
+

AnyCable's solution

+

+ AnyCable implements the Action Cable Extended protocol with built-in reliable delivery. Your application code stays the same — broadcast messages one by one, and the client receives all of them in the same order, even through disconnections. No custom retry logic, no client-side reordering, no accumulated state management. +

+
+
+
+
+ + +
+
+

Why a separate WebSocket server

+
+

+ Think of your server as a phone line: HTTP requests are like brief phone calls that come and go. WebSocket connections are like a dial-up modem — they stay connected and consume resources the entire time. When both compete for the same process, WebSockets consume everything. Real-time connections should be serviced and scaled independently from your HTTP application. +

+

+ Socket.io runs inside your Node.js application process, coupling WebSocket handling with your business logic. AnyCable is a standalone server: it handles WebSocket connections independently while your application (Rails, Node, or anything else) handles business logic via gRPC or HTTP. This separation means you can scale, deploy, and restart your application without affecting real-time connections. +

+
+
+
+ + +
+
+

Why Go

+
+

+ WebSocket connections are long-lived — a single user can hold a connection open for hours. In Node.js (Socket.io) or Ruby (Action Cable), each connection consumes significant memory in the runtime. Go's goroutine-based concurrency model handles long-lived connections with minimal memory overhead: AnyCable serves 10,000+ concurrent connections per server using a fraction of the memory that Node.js or Ruby would need. +

+

+ Go also compiles to a single binary — no dependency management, no runtime version conflicts. Deploy AnyCable as a Docker image or a standalone binary alongside your application in any language. +

+
+
+
+ + +
+
+

Why AnyCable

+
+

+ AnyCable is not a new project. It has been in production since 2017, powering real-time features for companies like Doximity (telehealth for 80% of US physicians), CoinGecko (crypto market data), Jobber ($167M revenue field service platform), and 50+ others across healthcare, fintech, and SaaS. +

+

+ The project is actively developed with regular releases (v1.6 is the current stable line, with patch releases every few weeks), a dedicated team, commercial support via AnyCable Pro, and a growing ecosystem including Laravel support, Pusher protocol compatibility, and the emerging Durable Streams standard. +

+
+
+
+ + +
+
+

Feature Comparison

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FeatureSocket.ioAnyCable
TypeJavaScript library (Node.js)Standalone server (Go binary)
ProtocolCustom (not standard WebSocket)Standard WebSocket + Action Cable protocol
Reliable DeliveryBuild your ownBuilt-in with automatic recovery
Missed Message RecoveryNo catch-up mechanismAutomatic recovery on reconnection
Presence TrackingBuild your ownBuilt-in
AuthenticationBuild your own middlewareJWT, signed streams, Action Cable auth
Pub/Sub ClusteringRedis adapter (separate setup)Embedded NATS (zero extra infra)
Backend LanguageNode.js onlyAny (Rails, Node, Laravel, HTTP pub/sub API)
Deploy ResilienceRestart = disconnect all clientsConnections survive app deploys
Memory per 10K connectionsHigh (Node.js runtime overhead)Minimal (Go goroutines)
Binary CompressionNoYes (Pro)
MonitoringAdd your ownPrometheus and StatsD built-in
Open SourceYesYes
In production since20142016
PriceFree (+ infrastructure)Free / Pro $1,490/yr / Managed from $29/mo
+
+
+
+ + +
+
+

Migrating from Socket.io

+
+

+ If you're currently using Socket.io with a Node.js backend, AnyCable's HTTP pub/sub API lets you keep your existing backend and offload WebSocket handling to AnyCable. If you're using Rails, the migration is even simpler — AnyCable is a drop-in replacement for Action Cable. +

+
    +
  • Replace Socket.io server with AnyCable (single Go binary or Docker image)
  • +
  • Use AnyCable's pub/sub HTTP API to broadcast from any backend
  • +
  • Get presence, reliable delivery, and authentication for free
  • +
  • Remove custom reconnection/retry code from your clients
  • +
+ Read the Docs +
+
+
+ + +
+
+

Further reading

+
+ +
+
+
+ + +
+
+

Stop Building Infrastructure

+

+ Get reliable delivery, presence, and authentication out of the box +

+ +
+
+
+ {{> footer }} +
+ + + diff --git a/src/customers/index.html b/src/customers/index.html deleted file mode 100644 index d4be315..0000000 --- a/src/customers/index.html +++ /dev/null @@ -1,403 +0,0 @@ - - - {{> dochead title="Customers | AnyCable" description="Companies and teams - building real-time features that scale with AnyCable. Customer stories and - case studies."}} - -
- {{> header}} -
- -
-
-
-

Who Uses AnyCable?

-

- Trusted by companies building real-time features that scale -

-
-
-
- - -
-
-

Trusted By

-
- - - - - - - - - - - -
-
-
- - - - - -
-
-

Industries Using AnyCable

- -
-
-
🏥
-

Healthcare

-

- HIPAA-compliant patient-doctor communication, secure - messaging, real-time health monitoring -

-
- Healthie, Joint Academy, and others -
-
- -
-
💬
-

SaaS & Collaboration

-

- Team chat, customer support, real-time collaboration tools, - notifications -

-
- Callbell, Vito, Circle, LiveVoice, Welcome -
-
- -
-
🎓
-

Education & Events

-

- Live polling, Q&A sessions, interactive presentations, virtual - events -

-
Poll Everywhere
-
- -
-
🤖
-

AI & Automation

-

- Streaming AI responses, voice processing, real-time data - pipelines -

-
- Canfy, Sera, and others -
-
- -
-
🛒
-

E-commerce & Marketing

-

- Real-time updates, live notifications, Hotwire-powered - interfaces -

-
- ClickFunnels, and others -
-
- -
-
🚗
-

IoT & Infrastructure

-

- GPS tracking, EV charging data (OCPP), device monitoring -

-
- Various IoT platforms -
-
-
-
-
- - -
-
-

Open Source Community

-

- Join thousands of developers building with AnyCable -

- -
-
-
1.9K+
-
GitHub Stars
-
-
-
100K+
-
Downloads
-
-
-
50+
-
Contributors
-
-
- - -
-
- - -
-
-

Want to Be Featured?

-

- Share your AnyCable success story with the community -

- -
-
-
- {{> footer }} -
- - - diff --git a/src/features/index.html b/src/features/index.html deleted file mode 100644 index e6ea464..0000000 --- a/src/features/index.html +++ /dev/null @@ -1,230 +0,0 @@ - - - {{> dochead title="Features | AnyCable" description="Real-time features that scale, without the complexity. Presence tracking, reliable delivery, signed streams, and more."}} - -
- {{> header}} -
- -
-
-
-

What Can AnyCable Do?

-

- Real-time features that scale, without the complexity. -

-
-
-
- - -
-
- -
-
🔴
-

Presence Tracking

-

Know who's online in real-time. Track user status across channels with built-in presence support.

-
cable.presenceChannel(
-  "chat:lobby"
-)
- Learn more → -
- -
-
📨
-

Reliable Delivery

-

Never lose messages. Automatic recovery and message history ensure reliable real-time communication.

-
channel.on("message",
-  { history: 100 }
-)
- Learn more → -
- -
-
🔐
-

Signed Streams

-

Zero-config Hotwire integration. Secure, server-signed stream names work out of the box.

-
turbo_stream_from
-  current_user
-
- Learn more → -
- - -
-
📊
-

Instrumentation

-

Built-in monitoring and metrics. Track connections, messages, and performance out of the box.

-
-
Grafana Dashboard
-
- Learn more → -
- -
-
🚀
-

High Performance

-

Handle 10,000+ concurrent connections with ease. Built in Go for maximum throughput and minimal latency.

-
-
Performance Chart
-
- Learn more → -
- -
-
🔧
-

Multiple Backends

-

Works with Rails Action Cable, but also supports JavaScript/TypeScript and any backend via RPC.

-
// Pure JS
-anycable.subscribe(
-  { channel: "chat" }
-)
- Learn more → -
- - -
-
Pro
-
🔑
-

JWT Authentication

-

Stateless authentication with JWT tokens. No backend required for connection verification.

- Learn more → -
- -
-
Pro
-
📦
-

Binary Protocols

-

MessagePack and Protobuf support for faster, more efficient data transfer.

- Learn more → -
- -
-
Pro
-
🔄
-

Hot Reload

-

Update server configuration without dropping connections. Zero-downtime deployments.

- Learn more → -
-
-
- - -
-
-

Compare Features Across Editions

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FeatureOpen SourceAnyCable ProAnyCable+Action CablePusher
Performance⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️
Presence Tracking
Message History
Signed Streams
Self-hosted
Managed Option
JWT Auth
Binary Protocols
Starting PriceFree$490/yr$29/moFree$49/mo
-
- -
-
- - -
-
-

Ready to Add Real-Time Features?

-

- Choose the deployment that fits your needs -

- -
-
-
- {{> footer }} -
- - - diff --git a/src/images/js-icon.svg b/src/images/js-icon.svg new file mode 100644 index 0000000..3c17dce --- /dev/null +++ b/src/images/js-icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/images/laravel-icon.svg b/src/images/laravel-icon.svg new file mode 100644 index 0000000..431545d --- /dev/null +++ b/src/images/laravel-icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/images/logos/agero.png b/src/images/logos/agero.png new file mode 100644 index 0000000000000000000000000000000000000000..1100130d9ca1bd7c49ec85185cf4c4f4a2a85dda GIT binary patch literal 1895 zcmV-t2blPYP)C00015P)t-sM{rE= zad+)nV)KcR>p)89AS~%EIO;%2<{v5QGd}pczU^OT=`1+>-rwpvMCKnV^`ofpe1iY~ z|NsC0{pIKW@$=&k7U(E6=q57eA1eR;{^%|{>NP{`Pgk%a8;$?~02p*qPE!ER%FE4A zY|$Fa{H|*v{R?Vp;zG z-#lp^LK-B%y!_$PVmDQ(R87;H!rrVarP>(0hydb&%&-r+N%giQDYWmsf+k#(8_2Z|6lK|1CUP(cENMnn`$@Vv|N(>Q?y!zb$$ zkX2xQWhOc+@E8(E9R=p6!0AMe2YeF}1s%U21s>xHAg+rPXh?u51tvVuomO~YkOC^i z&P3NXh&?+DN?{Y#)7*)Vc0VC9q{62ul2;9*)z&GIw@l9|1;QnM= zGB%3zLn4oKL{@@(Fn}ScPKyva7 zfF4L*Q$V1ie@F3?SQ+Zr53>r9f~W##8Vn*0<^CPR7p!l7W43F-_1}-@iPr%oZq$-n zOCVtace5Z8gbKyiZ%mTpMUm&`v7?#a2j&uJtO9pZfFlhVz+W19LixjwzDL@iv0tqYN#OD+guM)T$MZk~T226=?E-6XQW7qVcEF_s?v?-|Mu4k;;%A#HpnFEESDXM*(6Qm!RDsh> z1&Tfe+bf{*Hb#Jp07?;}@8yAZQ~{J=wgO)-5>O%q#h?nP3^Q2M3j8L>fPB!9=LdUW z8CM|Q0beD+rgI6H^TLlH(0Ml`#fAh-*U^u6R2)?RDb&{z*7_8n02R-wEwhMM0Z`a5 z1r;nlo3aEGop5sna(7r)0S|~G*iV60O?kjpL3!Os0bt-B8QlwuP#-WHF#NTgB*6HW z5V$vR4y%p5v52L>v>)@#4m==`WtCapdCj8{xjUvRpbof(K$YeCC#ZmXa}-e^TdzQF zG;j$xEE*|5AO*-^1p$yC^Fw0`b_9qjfC*UV0Uh4)K>z*?mSH=(7wGWM)gF)v*j;c> z4}A6t00QqH^nmOGe7<;@z#qckhJruIndyPLzM3`N0VQh0kN~+*@db9g3cv<+37gRT z4rmGd!4)7tBs>A)3g9NVfPju3BtW-j$bhfUlGdD0U>E*aOX|0?%|G z0`xHsy?j3t$v$9$KX+8XwxQU`$PidG2TT*-{5#uGpbzLVU?&eKHo>$4yH$R2sFM0nwAgf0|L;3 z_aHzX@Y}$F3UC!z1;U;1Kn21R(e56w->Qi01vSRsfPnZMK{*Npt_Lguh`weHkm%{2M$ue+Y8!b6hJr%I1)G} zpl<_hPk?c`RAyD|0{WV + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/images/logos/calltrackingmetrics.png b/src/images/logos/calltrackingmetrics.png new file mode 100644 index 0000000000000000000000000000000000000000..9f998b7fd3882cbeec0948fb983f08bba1cfb2f7 GIT binary patch literal 735 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&nFaWSxVjhku+G2Axc3_nu}?b% zMBJsz*k)hgDOkW4*8}82#35|BNb>QLLqHSwOM?7@8BF&*-0^y=g7A*Cm@8>B)ODK| zGB7ZCdb&7t@j*1D@7I_7fIsc;Eluq{`W<6DQ(r^{Ul0rtHD&`(ElxO4s?# zwJQC0@dtN#Ou4$>-VaI`gs{?l(c;`Dse?zcBe8O`%4Urq= z{YwH9uB)vQxuG1O<#69+Ez=XFdZs? z?cuP2QAA*uI}`J^*&J*+tQiNM<;n6z#B*01$P(6N=wAL?t>LHIEQV+MC+0A0&OXTT z!Io9-K@`uA81Ax$a%SrT(Fq(gSf(`ue%QdmGy8_+y>R1q70-A5o@w^|!v2Ci%aZpj zGw;~*@mI2keV9qCv($R_iaQ#gFL*mtudBHPBG+&#aE35W3BJH8Af>_liFXU*&HctJ znTvS8FiJ8qOt|O5qM+Hp5LBOjh(U-K=mWJy3>Vo99DEv>UHlk1ILsZGnwT^cO1L5% z7BEU|$ZcTDkvhQmnyuo%sz$~H%?%7Xeg|0$@)MY6aP~0VuU64Mu)I<7LB8-?hWBi3 z4W0>{68;B83~GwEvhIHq)$ literal 0 HcmV?d00001 diff --git a/src/images/logos/coingecko.png b/src/images/logos/coingecko.png new file mode 100644 index 0000000000000000000000000000000000000000..3edd4039c383e29dacfe85d7387bc06048384c67 GIT binary patch literal 3139 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 literal 0 HcmV?d00001 diff --git a/src/images/logos/coingecko.svg b/src/images/logos/coingecko.svg new file mode 100644 index 0000000..fc86e0b --- /dev/null +++ b/src/images/logos/coingecko.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/images/logos/companycam.png b/src/images/logos/companycam.png new file mode 100644 index 0000000000000000000000000000000000000000..0a5f2a5346b269cd467758effd38f9921f1b1c15 GIT binary patch literal 7896 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 literal 0 HcmV?d00001 diff --git a/src/images/logos/doximity.svg b/src/images/logos/doximity.svg new file mode 100644 index 0000000..1c7895d --- /dev/null +++ b/src/images/logos/doximity.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/images/logos/ev-connection.png b/src/images/logos/ev-connection.png new file mode 100644 index 0000000000000000000000000000000000000000..7a47b43dcad712950459fefb32413292ac15d571 GIT binary patch literal 1165 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&*#!86xVjhkysuDw2S$71WFZV7 zc{N)B&IU4g*cgF2AOOe(awd3)-Fe8|0TdD}3GxeOxRy3a;8@*DInynEi)R#h9POW| zGJ}DE`Msx$V@SoEw-MLNo+$8?F5J~O_y7OvrS3u;HkRuCtA9`75z7)+3=CyzbWjlB zU_nqHmIs`xet#w0XrJNX3%t3H_fPIFsb2iH=up|QgH1LTExrq{j0S2rX{a`%`G_R zUV5{Dam7W!=gl8P4$IWP4%i&pJzar6k;k+5@y*QOODr!oT>8Wl!NA<>eWz^c#@+YN zePr*jYS!g2X)|=$-eWAucgHj2k9Z(+gYd5l0%0=*80KG<`}6qy-o{tzC6#ZO1NRBP zzTNaKLC62{4(40Gj2qOM6guy&oNF7A@z7#j&C~wVy6g|$Ot^Zl>WGE!=j{r1$0Q#x zR3BLLtt+LbUvW*M)Y8KuSJ(=yl;4Uj4D4SU&ttUUsg~Z zDpj&(<~6*pwU{ZDzPD|WY;KNl>kGzNCo)ONyU}SoZ!&WsTGN$#Zjk74m+Gai*K z25J9fS{3SMKYHcuR~NYn-2L0i&h>B7Bk3HY^+&}u;?5pd zsCvx2toUX3I?3D*WjG Z$S)G~coEyJ^!uQq!qe5yWt~$(69AmB4)Xv2 literal 0 HcmV?d00001 diff --git a/src/images/logos/floatcard.png b/src/images/logos/floatcard.png new file mode 100644 index 0000000000000000000000000000000000000000..1cdf73dabaa0cf99269fc1168076b61e6d1519de GIT binary patch literal 829 zcmV-D1H$}?P)C0001BP)t-s|Ns9Y zA|e0)0N2;o85|lPA0H+rCd9?W`uh5$q@>c)(iIgIAtNFj930!*+wbr1y1Kd!4h}mz zJC~Q2Gcz;g<>hQ_Y)wr~dwY98KtQgpuFTBLQc_Z2VPJ}iiUtS=Un;850007*NklH1Rr2IXd(Cj^^RJ>2iRX9N1q4Kyps}w z4{%bt6?}k~aZL;}DKywIeJ`k`BZaxfPayPy_ z0B|MQvI2DE#|Hx}3ZMYe07ZLFKu52+*G~Z?9ba-Up8{xo%D;RZu%Pz_v3>;5(Ixxl zCjc$&YS8jQfTAUA`2e7!!`gmB?q7ljko4w^MD~~5|3nush~9qDv`Nq?R(zif z7nt02Hh+G+T`a)>e(st!Y3z|v0Il5I{cMvUfY#4lt4%@xBwd}{eT=7I055l&gFPV0 z4;Z}MU21niksHvsxqIIEO)7Lk;*O`_0DnA%2An!QT^;L}wd;q^q`p&AY9v}kfyes8z z2GHiS3km`xZO-|YzX6_oU;xl&A5Ycv+~O%fH`3Y7*E{+_i2a=@Y|%k zfN>sFxkn!}o~oaBiR_EPg*Tpp0PLd=$lU}0le=+1moIm}1dtW*`v;GJw9=UcFrCuD z0eMfrUjlrjGa~>M25|r=4pRWg2_X$Iy}*VUAO$y|Du90-u+piq6abJO%s~LD(If^C ze0C5En9WY!y$LuVGN1erf#y#2O#w=~JD@00000NkvXX Hu0mjfpdM|y literal 0 HcmV?d00001 diff --git a/src/images/logos/freeagent.png b/src/images/logos/freeagent.png new file mode 100644 index 0000000000000000000000000000000000000000..ba89c84acc91d6fe04881972cfb7e5f00c22c8e6 GIT binary patch literal 1947 zcmV;M2W0q(P)C0000jP)t-s0DIs7 zc-#PezGoL3$K!fbUZHsq?Ws|I_>+j1z9fXBga!Uop5HzhLkW z_>{qa;J;pU{Vn$sc%lF-fx=BJmY5zW0HGyiiOuqrlkP!utNomR)NPC;EIU;xLTBH*k} zkuq+NSi$q<{t2Tzs`8t`UL9YBHmO;Y2i9zf>WI-c4cixKh`}%cLrjTtU_I+fqhoAV z6$uP4f?YxIzfUd%p?Wr!N(aK$nc{}FAKeYn0e(%HNR{=pDJD2U2PPNrGrCIxs^@*F z!r=Wnwhegqt+2?F2VztQRH>|DXcfAZ4)!N6h+xa=&VlJ)uic8iwKf{~RLD*+b1O0$VwtaK%RIqPibTMHGW?FonGzOJJ*9zyPOKL84Oi zEDXSN&ialFfsXCe)j`TRa3q<5rd0^dpu#QzvSkJ5=o<-`TC|n`XsD4E#foBjIjwt5CLNEzSTV zMMA}(sZ%^w>ED&OjW`|tv8;Fk?LTd;Tnp3oBr*DV5c=f`xWD^oOaT(+?`5n5PJ+ z@(m81FOL0T${@%%36X)w1sIX{zz_kQbrMMIfl(QGJ)~Mnh6vE0^dFX71U@EE$iO0K zI^ZJ$0}g2K(Dx8%24E*J$#6np!dZQ!^ZYgt@F(VU1p4r@zQTF_&0-6Fa!KSOqLs6$ zy25#G5h#N-H$0H=QYV}>J#dDAf4@vLa~_y{HcdHiWU(xvjah~Cox+Y$DJzngB&oNv zVxXLu-{cMjiw9*PU20|KYz@*Lo{fm;c1e;NNX0dTkIxK#*@kpp1Fgeib!Q~1UvcSk7DF{?mixPL6l-H15^a3(~L@D%6Y(NDsc5m`&+3pZQRxqRwpt2SXTvZ>^rkeG9dQc^jT?H<8Pm=(EG~1XztH zjO7G>>~@jCM?ych`)B-rU{HT@`Oo+_GvL4c9axQ@3IEyeeNO0odwqiwn&u}j{FKAt h$M2EDPe?pj^gq@zsq`sj`$hl&002ovPDHLkV1kHMiH!gN literal 0 HcmV?d00001 diff --git a/src/images/logos/fullscript.png b/src/images/logos/fullscript.png new file mode 100644 index 0000000000000000000000000000000000000000..a13fb62e79eb166ef491f68064b5f52ea04b38c4 GIT binary patch literal 795 zcmV+$1LXXPP)~O<`1afd*_WAM8-?!1>x~Ro`p1*9Dx?%D5>FV*|AfZvX0007ZNklXNRJ7Ki)BpeFMUr3w_4MwCE=t&B4k~4lWNo%~d8*El=G|&eC48@n z5MzJ+O1kzV9m$T}E@IGwuQe@7exRfMSVsan6SS=K#4!qPk&ildthf%Ow=Y)Gon%a> zzephlaAB*KIIj-XU~6`mkma964Y1xt`LQ&(4|H(lah~odzu3RISxs(*4w25Q+Q^~L zkr2Bvm{rMKmDwzW$%xm!b1{{|JS8MN@;VnX@c50FFEZ1)KBK7*>X}im#O8RNFS522 zkcG(?s-AodB#(W&S7YG0#R4vlVTTNmHZ8)ER1L0HmqdefIQpal$AlC6;TlxLRsh|$ z?Aflk>!lKDu+?x&Jzi>K@WM0Q883QxJSd=%^Ii~EvY~|_&Vo0~$Zimqr^o}==EQhV zzvMQ?vxnQL8_<>ga^hi;K2vdZfpz9~7jQAEkO%3A#=E;2699F+xwPJKaDg7|#`2gO z4e1%dUWeElLk5}+O125{wkq5=%t!hdIG3H1}u?leLEy8^V$ou$*wbr&H_1 zd?eD$Glq@PRxT(oI~EZ7n9JQDllMWhQxL(dnjfHa+sSoU2^!pVgX)q^59KwBz1cel zwc)Cp?TOb8Y~UlH4I>xwsxT|m2hATmjIkV4SkAgTg;V*1>%HU^(BC zFTCb*MpNp2p_DBi?~Tf$dI@R9M|zQUkuu(WqSASseWtiKQ%?=@8q(#Jjs$znOZID5 z%{k53dR#9t{q-reBMHAd%rwh?u&l99k9ApplPnshQ&0b+%URpvQ?dQ>D?nVe)z>^* Z`~$9`7rC9H9fSY?002ovPDHLkV1l7)mRSG* literal 0 HcmV?d00001 diff --git a/src/images/logos/headway.png b/src/images/logos/headway.png new file mode 100644 index 0000000000000000000000000000000000000000..3044a55fe51fad3989b360d30c5e927680772144 GIT binary patch literal 574 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 BO!EK$ literal 0 HcmV?d00001 diff --git a/src/images/logos/mighty-networks.png b/src/images/logos/mighty-networks.png new file mode 100644 index 0000000000000000000000000000000000000000..9d01656e70b867bfa49191cb6df960bf9409bf77 GIT binary patch literal 1067 zcmV+`1l0S9P)C0001QP)t-s2`euD z|Nj60001W{00|HP78?Kn1ppEk_xbt1#KZs{A>H8M03RYPLPSPZSliv*eus$b?d<>n z3$C=Z02v+7)YP@QyN;HZpQffgP*MORC#S8hUTbV+adOGc&gJLm8#OpKNlS2hd`@0p zm!6;BI#ZOEcg988@dKYddM5&E5KMh7VZq ziwmT28~^|S000000000000000004l^a6v@9+$4}B{Nwpq?7PV&Tb$o_CGp=HgoB#I zJuR()w;;e3_=^H0(gFvzO`tCo{TB^Lparhm1n^P!zI{<3YxIyM|3u*U&38a^^#;gN z)As~IDhZvM$X!L$c$mr^Ca4}q+~b>_pW6;t_0J}Oka@B?!;?VIsEo~2FO$5rDlRRe zoj*<`F>2Mr=7Vr}U&yhO6w7d|_Bt|}rOL=?K=(ogWL5wTxh!-|SU&`0b8%*s>{GPL zpdM7f_^Sas=tM7FLy87llG%h$x*(T@wvXDYDIp^;9Bj3J#8yDjqGU>*tWw2p@jdYALN1$xHq#8)!g1>#eI zPNsT&*8{{didQVe+bLUj_I$b?VXKIbf%N!*87(ZhZP0J_$VTka|3GmPv;3y+GCk zQ4}Qol$+snQy{&}1$`eu^c!pRIiORsS^Rag6_ein2@zrU*Zr5@YY$9j z`A0;M2{`S#D~I-)LGj4qvOyyowyZ5jEi2wiq00000 l0000000000006+}`~&>yJ0wlsFD3v0002ovPDHLkV1h-V;}ZY? literal 0 HcmV?d00001 diff --git a/src/images/logos/qualified.png b/src/images/logos/qualified.png new file mode 100644 index 0000000000000000000000000000000000000000..451b4e3fad3013d289905a3305d4c32e0ebdf985 GIT binary patch literal 1150 zcmV-^1cCdBP)C0000pP)t-sM{rD~ z*50ev-)FhU`2794-{siv^8f$;>Gk)Z)7)UU#agt(v)kgJ(%X^C)qcaz*6i5R-~Van02P#w z@DbqOPKA370U~Ezm(_;(JLHX!%b%u}20Op+8yYBnLj%KaW^nTRUbZl>_)TJP@MR-| zgFnqbvW3CLAG%Ki1DoF+1{E{W<<~d3^J^PC`E?C^{F(-Semw(-U&{dI*D(n3YZ!$2 z{011G+W_bD8c_J01~fjO0hP~XK;F;`wC_Qurke()i^JF#a&l zBc}n%|4wpcG{E^ggwcS+-wBK0F(C5)4FE!(0h!Na5W#0Lpz^t9fyzJc2MQa|`9t?+ zFo@xI3W|(gyMTvIZ&qk_Kt~at5heAGbyEdB4O zGr*rBR6b^)(|p>zUDXUo?=q$HF#}b<_|sghV02>mh=HDGZ=Q(>S^{x=#9#r}fl2}S z&F6;=)I@ueKr6 zHTHTKsBYfX1@i#|ordDzLn~IdQ4b{LZBwA}{iCbQpGK2lp-)50z_h18knfMdY~Lti zeAnO^0gUf6nC&eS<+}!V0i5qKu=-H=u7SG>DE!AVH{bkpzGL7NNY4E<+-?^BuenDwLskkPXX%rPmV!!qn`hK24(np1||4evrvSeZcvJ!YEX`!W>Au!Vo;VJ zZ%~>aYfzpaXTZUaG2r3T4Y>GJ13o^@fRj%#;N{~6+C_>e(8 zzTcoG-)B&l|1_x0zZ=x&-)=M;@NI)O{O2uBD}#1?ZP1ecZ_t*%8|3o;08a{E?7FYn Q?*IS*07*qoM6N<$f?owRcK`qY literal 0 HcmV?d00001 diff --git a/src/images/logos/rangee.png b/src/images/logos/rangee.png new file mode 100644 index 0000000000000000000000000000000000000000..1e660b740906b95894663a8785846d0c410402bc GIT binary patch literal 726 zcmV;{0xA88P)@(4yGRbW8k2Tc*C=3xZqXT^aB%>1XK{VTBTWNyDi=BLalE`YtY1tPV)B6zWI2+ zH}e})yvV)=p=E~-^0+IbgOcDLG7R;zhlO3F!> z)2iIRJEo0~^@0fz+?alB*Y%peFVsF;F6+_Wpx_m|+XR*ftW9PJiUO>xY#!{&`5f=E zExr4o#BDZn_UYn`MhNic!GUwLmCEt-;3Z!m*eO7>!wSPg9dJ}JpwrJox9AbxY-WYU zCuwJ>#{(Oi`|}K3<_toNFxYCj8lAa4O2K%Bw@d4LsFHXE2Q)D>cW#Xa&=+ISKnV<^ z7V8SQy^+h}4{!E8W(jb)TO4$Dq@6&asIh-Li09p2T|bGTff8WBCMNm?bUH29uJ!@n zOs|Ayy9F-vcwt9AgvrSP;V`d-(U+`6Rdb&8@Z^kd@9?$KY6PRBnO(K7lu!lqRap$` zga8!`M2QS{!KW_;R)gQskdb|`4k!)w5CZJ;j;!#$c9BqAXZ?MdmSKxr0XqP`b_a|{ zf}j+tu(h2dZlIYEw7DIr7p%`e{b?KO_d`x8(~{PAA@Mp(=bly$o;`nifH8PL2^hoX zJ~EQvt$&lx>{+#MK*+LR)f{KD%>7Eh=$S%>#TV8xjU-q=M9~n`rB^aMh_7Z^T-=hB zk_iFT0J)9y4E1{8;iJ{)^74jvYU-kZJjfR-z<2_wgmjR}7IG~-xAcBniDL{Vr&%~X zOp&k{){T>U*k`p=b>ggGk%cx#(#|!_ttIx-&Oe8#0rOw>?^q!lUgi3;(*OVf07*qo IM6N<$f|IpWF#rGn literal 0 HcmV?d00001 diff --git a/src/images/logos/sessionshealth.png b/src/images/logos/sessionshealth.png new file mode 100644 index 0000000000000000000000000000000000000000..36efc8134f4bcf453d01d410a4fa11c7224eb685 GIT binary patch literal 1134 zcmV-!1d;oRP)C0000UP)t-sM{rCG znAZ)N*$tfB4V>Eznb-}P*$tT24Vc#rm({Y#ZN2~i010$bPE!CFz*u~nHr((QPcCQx z00Z_(L_t(|ob8+0cHO`MC7jEdf-{G66#9Z|he`9QQM8@iz`%-en`^^rOjEOl(Nz6BzFMw=ku z`}0YhH!;wt=FRhHqVujnCIRa?Am@#vZYArt4GKT ze-1GA#&On5L6jeo?+4py`^fvZm0qmh2#vSgct302Li4ns_v)xQNe+&mEC8e~67#7z z?`oV;wq^qW)fp1}V*b(0KOygI1;FSVN4-aXwzN;pWWWRWBlhL(eHWB~&2K;d-aN`a zNq#`Ve`Xy1xe~C@eVlx|SAGvn!e6+5YafQ+BlmOp8I&YVKL_C1L1#j;A8_*7^os%D zzFUgJz~qkXO90^Tm*PM{8S9+}Fi1o86e=I6odv*+Y@cdKD>waw0Psp;Y4siYKMR1% zf3N<0N9PnkWL(8Hai($x0NXFKkFGX+>@U^h9$0Qw5BjE0xg85q!5 z34qSuQfWs8&@UKL00s?a4w%v>0ia(<0d!6U6p**HH5lj?0_Z9(pUprxJA?o_rGo^t zO&|=85P*k<=nQCUAX=Y=0Ge0)9-vczvLgdvz(7M2&}}_c0IJJns?p<}{w+WS!1v>5 z0CIl{qXJOZRyF|e6igMMSR#ucd{_hE6ZE2t1OPp&0q|)VEUjp#&Irz^2?446A1%R#n=gS#T0mwr&-vNlXPEvIM z&Q5-{0d8bK2jI?Ox(dMcIiLe@dibvmaO-=DMjyD70iE}P1+V}XzyeqR3*avRNZhS1 zg3oX+0wgOY=^DWFjnbs*OAddiomVnOOl2gYMER0r!u}yByzF* zRE}QjzY>C@`&7(_7jemluT=U;VDIyGqTUNmR~T|7TU3rOG`_Q-*bVsm%vk8Pcp=ID z@+-Y#{vl@4cnbIS7yExgi3#)1R7;jDS+a!658)QqG`m9y`~Uy|07*qoM6N<$g6U!m A_5c6? literal 0 HcmV?d00001 diff --git a/src/images/logos/tasktag.png b/src/images/logos/tasktag.png new file mode 100644 index 0000000000000000000000000000000000000000..1e660b740906b95894663a8785846d0c410402bc GIT binary patch literal 726 zcmV;{0xA88P)@(4yGRbW8k2Tc*C=3xZqXT^aB%>1XK{VTBTWNyDi=BLalE`YtY1tPV)B6zWI2+ zH}e})yvV)=p=E~-^0+IbgOcDLG7R;zhlO3F!> z)2iIRJEo0~^@0fz+?alB*Y%peFVsF;F6+_Wpx_m|+XR*ftW9PJiUO>xY#!{&`5f=E zExr4o#BDZn_UYn`MhNic!GUwLmCEt-;3Z!m*eO7>!wSPg9dJ}JpwrJox9AbxY-WYU zCuwJ>#{(Oi`|}K3<_toNFxYCj8lAa4O2K%Bw@d4LsFHXE2Q)D>cW#Xa&=+ISKnV<^ z7V8SQy^+h}4{!E8W(jb)TO4$Dq@6&asIh-Li09p2T|bGTff8WBCMNm?bUH29uJ!@n zOs|Ayy9F-vcwt9AgvrSP;V`d-(U+`6Rdb&8@Z^kd@9?$KY6PRBnO(K7lu!lqRap$` zga8!`M2QS{!KW_;R)gQskdb|`4k!)w5CZJ;j;!#$c9BqAXZ?MdmSKxr0XqP`b_a|{ zf}j+tu(h2dZlIYEw7DIr7p%`e{b?KO_d`x8(~{PAA@Mp(=bly$o;`nifH8PL2^hoX zJ~EQvt$&lx>{+#MK*+LR)f{KD%>7Eh=$S%>#TV8xjU-q=M9~n`rB^aMh_7Z^T-=hB zk_iFT0J)9y4E1{8;iJ{)^74jvYU-kZJjfR-z<2_wgmjR}7IG~-xAcBniDL{Vr&%~X zOp&k{){T>U*k`p=b>ggGk%cx#(#|!_ttIx-&Oe8#0rOw>?^q!lUgi3;(*OVf07*qo IM6N<$f|IpWF#rGn literal 0 HcmV?d00001 diff --git a/src/images/logos/uscreen.png b/src/images/logos/uscreen.png new file mode 100644 index 0000000000000000000000000000000000000000..851f4220d23e8fd87d3d18f709bc093b1d04cc10 GIT binary patch literal 845 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 literal 0 HcmV?d00001 diff --git a/src/images/logos/via.png b/src/images/logos/via.png new file mode 100644 index 0000000000000000000000000000000000000000..97da7f731d92f6cd23157c7f1f5caaec0ca6ab80 GIT binary patch literal 664 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 O0000C0000mP)t-s6g}S+ zJl_>O-y1>S6+GYn|Nj6j-T)ol3OC=};`_kK_NuhopAjoeOX|xd87Hbbt=f0Xjej=l~s{19X56&;dF? z2l!(E*}Mw0OFuXB0rK6v79KiHv1W7KKoa~hfb^P2E$leIpj~;)8p1d~{buht$pyHj zl$Q!&y5%IkS5itZdHcHAyH$RC8!S}=Z4{I&3O9+Kbwpo8agAoljiU|2j5T=Zfn~e7 zN^U3E5*n)kU;-3OZLt6n08A#p(+i_cMA*rL6&U>rOnL!yfb!sxpJrsy2drk3mxewS zLvB`W5!1d!cJXuooI#B5%4x>vh2`d!Q3t?oXrv~mO{1Ham(vB!)es!$scn$FogRw1 zfB|O%1RT)l8lTPHnp^`G06I|XN3+0_w^MuT)>Uto0dqsrc0ie7M^Kyj2222Opnab* z8#ByVT5rw10TTcnXoM35hRHy4u=ob-Js~(yGRU9K0S@X)Q6(J%HUKzKQd>TO!9m?y zDG$eh4S)`+2*kH`PS}@Jy>*)g9Rtp`9BNzbq$o2=m@i3Ht!Wzc3^)M3gK`802UU=% zVZeJZ0S6)pD*RS(a3JrMS_V7-a3F>_W%lWE#jez6vT*M20e}P1y%MVm4A@kMa1D3> z#dbiM?M#U;NLZ=dG~hm*f`j^zXaA&_3sTu9iyr}O2N`Mt4zdj38}J{`d1^cn%Ir%7 zpuAE<<{R(%8Ui(4%iK8hG-b@0j#w)iA({MeLS@d zG>&NW{SI;Q@s>E~H{}#C&;Wqcjs=M_gUWKyqf*4#GSC3f9pt;5b74U`IOl+Y)+w2` z->D1;2m7CL#6SxG9DE|vXWFWVm7>AfG|&P72cM3=4m!C%00&#vG+8taG!H5@2bBS_ zDcB~%m9uT22~h7j>0b37v8q%F8E6934pyXkm&`#aZ{lnlXrC2x2V3O3vPZ>D!A=w~ z&<2ouPAW-V*}^wwYvohSKmb5Yv+E$#9>Ms6^!__>1`Gs_>%l>yf7S+sgN-v{AOL_v zoTe8oFl-7QB?k=z0AL)v^3ystLkCOHK;(@HEOqN`Gy`JRUO7VsA^_;XFd!VPNA4j5 zApn?*>@(Sn&0+>ZFRjFZpl+Z#xQrPH0f-kgR$}7lHfA6MfRXLYuu?GtvG;~KmKjtk zXdniF>w%eB(KpBc2f!niSqgFf0Kg-AnXyVm4MYK^P-bjn7BvtA_}cX|OCio50KSp^ z%#QbHVFO`+-GTY$V{7&dasB{syohg9c5fEG|DXF03>}~Ybbt=f0Xjej=l~s{19X56 v&;dHY2>>Vl^nJZ9=l}TqdgjmHFT?d8!par#hp8Vt00000NkvXXu0mjfpf~&? literal 0 HcmV?d00001 diff --git a/src/images/logos/wealthbox.png b/src/images/logos/wealthbox.png new file mode 100644 index 0000000000000000000000000000000000000000..4437e7928f692b5f9e8a5b361e7ea5da2c05af45 GIT binary patch literal 1404 zcmV-?1%vvDP)C0001WP)t-s5N_1} z|Nj7F(#zTM0AJAnYSRF0)BtGH0A|wr{r>Rx{Qz{<_4)m?&F=tV(K&$G*5vlV*z@1& z_;;h?LXX~zw&{MVbagdf1=8>$%eLp~mhreAsfQ0qGF$w^Mv6VX58pT2FKZJbFbi;U!ef>e0aM14|+J9=Ql}8 zTpKxiP81;)q1o=trxDb>aqJ5^`UPW!kBu06b-qmiB?z%~MhW+R+$$y#wl`rgmsg* z46_#9NIr0lgOe4{CIeEuNqTT5nq!yl%kAW9>&8lz+MN77H2}j*@0gme4-|lzBCb)h z**grBs6Pcasd1|0mo{d)5qffQ(70!Onr+IXmjJTa-@$D>-Y=?t=XKq=>}t3Ba*|rt zu)8>&)2R9Ct9S2^H|`9lIn|Jp1IuA#nr{_0K-ugx7r zukDNaQvy+3FCUb%$3vo@=db?ofXHhCBFf@uIsv1()+xDcO0B*oAnBVXAmV|9F`0l_ zPbFt|Z5bm7@Dp!mwarj-GJ#4URr1#FR|Gh6g|>cAB0!6+^ezEuThQ4E^mb$nfm)y- zAP5Kof`A|(2>kN|K<)(V%M#$9AW_u*Tv?I;5Hsb0$~Jw>3ob_hAaI+!q2&hmZ^p$4 z0Ewgfr;i@Rw@p}x0H|3zZ+JACeAFTYpiIU{9h8Y1ScU*VjHRP=E9NI*0RjVgL#fUS zexCrvk`H&B$kz!V>6b@541bBhc>Es(j=n!w#aV)Y9W#cda~C4u9L{C`!1;jIG3O*e zFPP=$L45%L336muyUh1_PLy6XK#SPlPy}Rbyh=B9DSlcb_7oJ>GE-Sd7~SoyhQm|>~!{y4zZm!=btoZM_Sfh2~e z8OcylvONye%v*y;bS~{T2=tTHn|+h4e$h8{8;9L~Ga*@x zrRz^*{QIDi)1-7gTCmX5u{951DRM!)Q?M8)*;sKfreqVuNSXi5lFdNTN*}g>8z@@r zd%I{=mF=)znd9>UTv_ZxLMF$5yKFbO79M^NmgPYS`r-PheH)k$FWu^!7_S^rh`Xj& z;^WdS87^K&$d2;ek7h1jHx#fiT+eBn0w^@cVzY_m`)X?ZLqz|5Xd>i){UYM$Xke(O z$6Gk;g56p;Sb>)L-MN~12AWv5rz;P~IEm{CIVdwWv5>(w9w*Nfn&p_kn6-_k1m4J` zS-yd&XZ}VevVJQUS-qL9rm$)|A6dVlvBbJ9oh8<8YAq?y1Z(R}7HlkDxv`nlt=$iL zSh}^lqOfjrJ9%jDx7TU!uPDeg2;LvbbQ@VhFfPjF&zkxsB*;B?Afh`*V0000< KMNUMnLSTYJnXSP9 literal 0 HcmV?d00001 diff --git a/src/images/logos/yay.png b/src/images/logos/yay.png new file mode 100644 index 0000000000000000000000000000000000000000..dafeb7de0feb27346c358edadd63b1f337fc05fc GIT binary patch literal 2113 zcmV-H2)_4;P)C-@P}3qxN=Rx@NwH#@;2%iP3xkS^nsB8TUf_+v zKd6avg$b$Rp9^b*XfzaqF_cmR1O$R5EwmNdZo8%1Zg+dm%sgJqcg}9rFZnVv-}%lw z&-*;@%)Fnm7mh-!SUHkQtbPfmt1!*Vcn!0o)-36aptg zhkYKxuR6w`c7MxGF3#9q@4NTZCRhF$VzUw*6+<~788&>)+J6kd4ZOAkaJ&T%5F9)j zAwp;%M0%YL&&b0!p19AR9};-~myU13d6c57m1Py&DcM23gy$mQQt)Bl4T57{33z}I zt%5fYBQ#yqbnLaz_?>^a$DX$jeC5bgNX_qx%1z{ADp2x(KtS>u;ZoWCzuGV$;K;27 zuaSF%&=|3y-d$+ArlwVQVrF zxLRPqO}N?6DThu7+7X+auP?cvlCdO8HpoJ)5~y70b1+bi4@BiuFmp6epnYywjovB= zGhuF(j2j_pOANWJ_M9j=V;M-G9Iy$tV{*3njxdl;l{-2(R_^RX13)pqh2zc#zStlM^{*JZD0IskI}hL=GRyC})hos;R)(%E0uD zIJ2;ra1?5KXPgBw{l6$q(t z*&62u4_w5zzqyK4%OU1M$RZXIRNF>vUm_v`A{ezrtmugQZ;#xy#c5n^3RF#eS?)8M z6C*hh7y7)k=Op9fk*!-N7;E4Q_pIWQOCmcTeutT(eXdv^xbE79wUe-FnOHXkBSEa2 zG}cTQ9j_#oHR9q)m|PAQuQb+7!139-s@u&7GlRv3GI6e7qyG2Fg4L|TX?M&OIkP+`mgU4~O6c@?p4&amhAT(8J+69pL+XpOr!$9-WG-3bT)NtrS^>A+7@0XEo_!^A`D#PJ z;S(Y-$dS4`#xZ4jDx5EwC|F4F(i6wocGu<1OecQvz%hKGaMOoZaP&x@pZ|D@PMr~swF=a2I*u!IUWrB` z7Knmw&H%jl)GQ|tE-*R~nVgE8m`>2kble#2)G&^|o$xFUy_rY}uD>R5)fIt*Zx#OW z&xB(zk05Fv{yx90I=c}LWaPYWz_=!Hn1`1Ra;)D`i z(woan@1JKRfKuVlkDO%9r5(22K2EPE&dwI*=L#p^N{pEBB;I@@vGK~t zz8Ctu`f8t!8RK0TTV|{o7mY!Z2%^meBQDcov5|w33GDyNG_U>r9LEnCU2iB3M_yUr zCtp9phW9Sx>^X7jRG~W_IWZ%;W^}tSIbqB%h~0lZ4}H<;7_T2pJh*e7vk6KMV%KmP zF5xm_V}TT)k?6=ta^`0G%uUY|^C+XyC~n9g&1PPIW{wgabpmszg*DFA*NpJ7PmQtR z%7!O*FL3x^BDx_75*&WJU=4WJ>seHU7sIoN(VZyhxJYsmfkYtV6pUz4LOqX4dnhV^ ztnT|lY`8Sgn0Wj*XL)vSkETY3N_*#m1%q6$RI6ssh8HJB4>U5eLhXHiLQ6n34}4&SkQIbi zCDdvnr>GsvkSYlVB!;!+@IDcv`O|W3FSKWV`yDVS1N#i32e~L_ReH0U6E?7dmQ1O@ z1(fT1Dy;=A5i_f01-37CEbKDlqfk1ARkV6kU}iL}GNp}P-l}nd3T^alwNxaE3}lp| zng_N>!fJ)uL-y-vuq!%uOW}hR*DSO~TyAbGq#=~T7pD!Un zw4R*jO2@zaiSMpo{5Tz4uim_Os@v7AeGi|>^6RtW>Vz8u0!I7&2c^yEGPK3D6uc4} rY+OE5OnY+lUHzn8+aKC+YJmA4@{5?71=W)900000NkvXXu0mjf*TVZ3 literal 0 HcmV?d00001 diff --git a/src/images/logos/zyda.png b/src/images/logos/zyda.png new file mode 100644 index 0000000000000000000000000000000000000000..c1ab4eb437f59b626b530d73eb7f1019d87a486c GIT binary patch literal 876 zcmV-y1C#uTP)C0000gP)t-sM{rCG z7cLAJE)5qh4Hqs97cTDa@(mX*000`3m!aX}=efMXc6x?FNm+`g@@4=400wkYPE!DL zJ?P7zi@cGC0008(Nkl7J~s+@akr)enc|3pySzeiA)_uuzU9+b?#-JDD;roW@Lzyz4B!UkXq0W1J(2p}0a zTLY8eYE}px5}?D*z%&QY8L)Ez)PS`Ln*ufoU{z?g7Mp`M3*i9Ke;FheCx`>Q9s(QX z0K@Yxuvat&nE@VX0C)s`06%~)0x*d`@CX<*06YSp0-&-M7@Ex<&Gs^x!g5U9ko3kI zyM1rQ_>LqFK(ps@BfqUWo!!00;?yT7WbF z!u}~MkPg5wD)<)wI0AHc0dxUM0fGf6*}niFEWqT;Z~^uL^mv-mt^zFq%a0Oo%89|sf!NN}1h0-prn-jpi|aL#}w zF9HuPspXPA1+=741mI<%egqOZ@+RRMqV2hnb|DH=81FYj1ekT`I|`BLDAy}?d9`Q9 z{0#~GI3FAUexwtAs1<&!7k;oAezY5ZxE+4HAKu`AZ}EUPx!~J;;EhiBRxfz78@}BS z-|&cU`NTK9;@f_2@W$r}0o(dLO$4_2eP$@x{{IJOh^&Q{ popup popupClass='try-now-popup'}} {{> popup popupClass='contact-us-popup' popupTitle = 'Contact us' typeformLink='https://form.typeform.com/c/wAHm0sRP'}} + + = { + 'Doximity': '/images/logos/doximity.svg', + 'Healthie': '/images/logos/healthie-text.png', + 'Headway': '/images/logos/headway.png', + 'Jane': '/images/logos/jane.png', + 'Fullscript': '/images/logos/fullscript.png', + 'Joint Academy': '/images/logos/joint-academy-text.png', + 'Wawa Fertility': '/images/logos/wawafertility.png', + 'Sessions Health': '/images/logos/sessionshealth.png', + 'Vinita': '/images/logos/vinita.png', + 'CoinGecko': '/images/logos/coingecko.svg', + 'Dext': '/images/logos/dext.png', + 'FreeAgent': '/images/logos/freeagent.png', + 'Wealthbox': '/images/logos/wealthbox.png', + 'Floatcard': '/images/logos/floatcard.png', + 'Jobber': '/images/logos/jobber.png', + 'CompanyCam': '/images/logos/companycam.png', + 'EV Connection': '/images/logos/ev-connection.png', + 'Via Transportation': '/images/logos/via.png', + 'Agero': '/images/logos/agero.svg', + 'rang.ee': '/images/logos/rangee.png', + 'Tasktag': '/images/logos/tasktag.png', + 'Circle': '/images/logos/circle.png', + 'Mighty Networks': '/images/logos/mighty-networks.png', + 'LiveVoice': '/images/logos/live-voice.png', + 'Welcome': '/images/logos/welcome.png', + 'Uula': '/images/logos/uula.png', + 'Yay!': '/images/logos/yay.png', + 'ClickFunnels': '/images/logos/clickfunnels.png', + 'Poll Everywhere': '/images/logos/poll-everywhere.png', + 'Callbell': '/images/logos/callbell-text.png', + 'Qualified': '/images/logos/qualified.png', + 'Uscreen': '/images/logos/uscreen.png', + 'CallTrackingMetrics': '/images/logos/calltrackingmetrics.png', + 'Zyda': '/images/logos/zyda.png', + 'Sera': '/images/logos/sera-text.png', + 'Vito': '/images/logos/vito.png', +}; + +const modal = document.getElementById('company-modal'); +if (modal) { + const popup = new Popup('#company-modal'); + popup.init(); + + const logoEl = document.getElementById('company-modal-logo') as HTMLImageElement; + const nameEl = document.getElementById('company-modal-name'); + const descEl = document.getElementById('company-modal-desc'); + const detailEl = document.getElementById('company-modal-detail'); + const linkEl = document.getElementById('company-modal-link') as HTMLAnchorElement; + const caseStudyEl = document.getElementById('company-modal-case-study') as HTMLAnchorElement; + + document.querySelectorAll('.cases-slide__company-card').forEach(card => { + card.addEventListener('click', (e) => { + e.preventDefault(); + const name = card.querySelector('.cases-slide__company-name')?.textContent || ''; + const desc = card.querySelector('.cases-slide__company-desc')?.textContent || ''; + const detail = card.getAttribute('data-detail') || ''; + const url = card.getAttribute('data-url') || '#'; + const caseStudyUrl = card.getAttribute('data-case-study'); + const caseStudyTitle = card.getAttribute('data-case-study-title'); + const logo = LOGOS[name]; + + if (logoEl) { + if (logo) { + logoEl.src = logo; + logoEl.alt = name; + logoEl.style.display = ''; + } else { + logoEl.style.display = 'none'; + } + } + + if (nameEl) nameEl.textContent = name; + if (descEl) descEl.textContent = desc; + if (detailEl) detailEl.textContent = detail; + if (linkEl) { + linkEl.href = url; + linkEl.textContent = `Visit ${name}`; + } + + if (caseStudyEl) { + if (caseStudyUrl) { + caseStudyEl.href = caseStudyUrl; + caseStudyEl.textContent = caseStudyTitle || 'Read case study'; + caseStudyEl.style.display = ''; + } else { + caseStudyEl.style.display = 'none'; + } + } + + popup.open(); + }); + }); +} diff --git a/src/learn/index.html b/src/learn/index.html deleted file mode 100644 index 555b898..0000000 --- a/src/learn/index.html +++ /dev/null @@ -1,295 +0,0 @@ - - - {{> dochead title="Learn AnyCable | Tutorials & Guides" description="Learn AnyCable from zero to real-time in 30 minutes. Tutorials, guides, videos, and documentation."}} - -
- {{> header}} -
- -
-
-
-

Learn AnyCable

-

- From zero to real-time in 30 minutes -

-
-
-
- - -
-
-

Choose Your Path

- -
-
-
Beginner
-

New to AnyCable

-

- Start here if you're adding real-time features to your Rails app for the first time. -

-
    -
  • Quick Start Guide
  • -
  • Your First Channel
  • -
  • Deploy to Production
  • -
  • Basic Troubleshooting
  • -
-
⏱ ~30 minutes
- Start Learning → -
- -
-
Intermediate
-

Migrating

-

- Already using Action Cable, Pusher, or Ably? Learn how to migrate to AnyCable. -

-
    -
  • From Action Cable
  • -
  • From Pusher/Ably
  • -
  • Testing Strategy
  • -
  • Zero-downtime Migration
  • -
-
⏱ ~1-2 hours
- Start Migrating → -
- -
-
Advanced
-

Deep Dives

-

- Master AnyCable's advanced features and optimize for production scale. -

-
    -
  • Performance Tuning
  • -
  • Scaling Strategies
  • -
  • Architecture Patterns
  • -
  • Pro Features
  • -
-
⏱ Deep dives
- Explore Guides → -
-
-
-
- - -
- -
- - -
- -
- - -
- -
- - -
- -
- - -
-
-

Ready to Build?

-

- Start adding real-time features to your app today -

- -
-
-
- {{> footer }} -
- - - diff --git a/src/modules/blocks/about-slide.scss b/src/modules/blocks/about-slide.scss index 38f3216..4ff0c09 100644 --- a/src/modules/blocks/about-slide.scss +++ b/src/modules/blocks/about-slide.scss @@ -21,6 +21,7 @@ $className: 'about-slide'; &:last-of-type { margin-bottom: 0; + border-bottom: 1px solid $borderPrimaryColor; } @include mediaMax($tablet) { @@ -97,7 +98,6 @@ $className: 'about-slide'; margin-top: 120px; margin-bottom: 56px; - @include mediaMax($mobile) { font-size: 40px; line-height: 1.3; @@ -120,7 +120,6 @@ $className: 'about-slide'; align-self: flex-start; margin-bottom: 84px; - @include mediaMax($mobile) { margin-bottom: 48px; } @@ -133,29 +132,15 @@ $className: 'about-slide'; align-items: center; } - &__btn { + &__tab { padding: 0 12px; box-shadow: none; - background-color: $accentSecondaryColor; - color: black; - } - - &__btn-ruby.active-tab { - background-color: $accentPrimaryColor; - color: $backgroundPrimaryColor; - } - &__btn-pubsub.active-tab { - box-shadow: none; - background-color: transparent; - color: $accentPrimaryColor; - border: 2px solid $accentPrimaryColor; - } - - &__btn-js.active-tab { - // js logo yellow color - background-color: #f7df1e; - color: black; + &.active-tab { + background-color: $accentPrimaryColor; + color: $fontPrimaryInvertedColor; + border-color: $accentPrimaryColor; + } } &__section-image { diff --git a/src/modules/blocks/cases-slide.scss b/src/modules/blocks/cases-slide.scss index ab55063..25d56a9 100644 --- a/src/modules/blocks/cases-slide.scss +++ b/src/modules/blocks/cases-slide.scss @@ -4,6 +4,7 @@ $className: 'cases-slide'; display: flex; justify-content: center; padding: 0; + border-top: 1px solid $borderPrimaryColor; &__wrapper { width: 100%; @@ -18,6 +19,16 @@ $className: 'cases-slide'; width: 100%; align-items: stretch; position: relative; + border-bottom: 1px solid $borderPrimaryColor; + + // Vertical border between the two halves + & > :first-child { + border-right: 1px solid $borderPrimaryColor; + + @include mediaMax($tablet) { + border-right: none; + } + } &:last-of-type { margin-bottom: 0; @@ -26,7 +37,6 @@ $className: 'cases-slide'; @include mediaMax($tablet) { flex-direction: column; padding: 0 24px; - border-bottom: 1px solid $borderPrimaryColor; } &-mobile-reverse { @@ -43,14 +53,12 @@ $className: 'cases-slide'; align-items: stretch; height: auto; background-color: $backgroundSecondaryColor; - border-right: 1px solid $borderPrimaryColor; padding: 64px; z-index: 2; position: relative; @include mediaMax($tablet) { width: 100%; - border: none; background-color: $backgroundPrimaryColor; padding: 48px 0px; } @@ -140,4 +148,143 @@ $className: 'cases-slide'; } } } + + &__logo-text { + font-size: 24px; + font-weight: 700; + color: $fontPrimaryColor; + } + + &__quote-section { + justify-content: center; + background-color: $backgroundSecondaryColor; + } + + &__quote-block { + max-width: 900px; + padding: 80px 64px; + text-align: center; + + @include mediaMax($tablet) { + padding: 60px 24px; + } + } + + &__quote { + font-size: 24px; + line-height: 1.6; + font-style: italic; + margin-bottom: 32px; + color: $fontPrimaryColor; + + @include mediaMax($tablet) { + font-size: 20px; + } + + @include mediaMax($mobile) { + font-size: 18px; + } + } + + &__quote-attribution { + display: flex; + flex-direction: column; + gap: 4px; + } + + &__quote-author { + font-size: 17px; + font-weight: 700; + line-height: 1.4; + } + + &__quote-role { + font-size: 15px; + line-height: 1.4; + color: $fontSecondaryColor; + + a { + color: $accentPrimaryColor; + text-decoration: none; + } + } + + &__quote-source { + font-size: 14px; + line-height: 1.4; + + a { + color: $accentPrimaryColor; + text-decoration: none; + + &:hover { + text-decoration: underline; + } + } + } + + &__companies { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 12px; + width: 100%; + padding: 32px 0; + + @include mediaMax($mobile) { + grid-template-columns: 1fr; + } + } + + &__company-card { + display: block; + padding: 14px 16px; + border: 1px solid $borderPrimaryColor; + border-radius: 6px; + text-decoration: none; + text-align: left; + color: inherit; + background: none; + font-family: inherit; + cursor: pointer; + transition: all 200ms; + + &:hover { + border-color: $accentPrimaryColor; + transform: translateY(-1px); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); + } + + &_cta { + background-color: $accentPrimaryColor; + border-color: $accentPrimaryColor; + + .#{$className}__company-name { + color: $fontPrimaryInvertedColor; + } + + .#{$className}__company-desc { + color: rgba(255, 255, 255, 0.8); + } + + &:hover { + border-color: $accentPrimaryColor; + opacity: 0.9; + } + } + } + + &__company-name { + display: block; + font-size: 16px; + font-weight: 700; + line-height: 1.3; + margin-bottom: 2px; + } + + &__company-desc { + display: block; + font-size: 13px; + line-height: 1.4; + color: $fontSecondaryColor; + } } diff --git a/src/modules/blocks/customers.scss b/src/modules/blocks/customers.scss index ff8c50e..466a044 100644 --- a/src/modules/blocks/customers.scss +++ b/src/modules/blocks/customers.scss @@ -45,58 +45,109 @@ $className: 'customers-hero'; } } -// Logo Wall -$className: 'logo-wall'; +// Industry Showcase +$className: 'industry-showcase'; .#{$className} { - display: flex; - justify-content: center; - padding: 80px 64px; - background-color: $backgroundPrimaryColor; + &__block { + display: flex; + align-items: center; + gap: 64px; + padding: 80px 64px; + background-color: $backgroundPrimaryColor; + border-bottom: 1px solid $borderPrimaryColor; - @include mediaMax($tablet) { - padding: 60px 24px; + @include mediaMax($tablet) { + flex-direction: column; + padding: 60px 24px; + gap: 40px; + } + + &_alt { + background-color: $backgroundSecondaryColor; + } } - &__wrapper { - max-width: 1400px; - width: 100%; + &__content { + flex: 0 0 380px; + + @include mediaMax($tablet) { + flex: none; + width: 100%; + } } &__title { - font-size: 32px; - line-height: 1.3; + font-size: 46px; + line-height: 1.2; font-weight: 700; - text-align: center; - margin-bottom: 48px; - color: $fontSecondaryColor; - text-transform: uppercase; - letter-spacing: 2px; - font-size: 14px; + margin-bottom: 20px; @include mediaMax($mobile) { - margin-bottom: 32px; + font-size: 32px; } } - &__grid { + &__text { + font-size: 17px; + line-height: 1.8; + color: $fontSecondaryColor; + } + + &__companies { + flex: 1; display: grid; - grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); - gap: 48px 32px; - align-items: center; - justify-items: center; + grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); + gap: 16px; - @include mediaMax($mobile) { - grid-template-columns: repeat(2, 1fr); - gap: 32px 24px; + @include mediaMax($tablet) { + grid-template-columns: 1fr; } } - &__logo { - opacity: 0.7; + &__company { + display: block; + padding: 16px 20px; + border: 1px solid $borderPrimaryColor; + border-radius: 6px; + text-decoration: none; + color: inherit; + transition: all 200ms; + + .#{$className}__block_alt & { + border-color: $borderPrimaryColor; + } + + &:hover { + border-color: $accentPrimaryColor; + transform: translateY(-1px); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); + } + } + + &__company-name { + display: block; + font-size: 17px; + font-weight: 700; + line-height: 1.3; + margin-bottom: 4px; + } + + &__company-desc { + display: block; + font-size: 14px; + line-height: 1.5; + color: $fontSecondaryColor; + } + + // Keep for use in featured stories / modals + &__logo-text { + font-size: 22px; + font-weight: 700; + color: $fontPrimaryColor; + opacity: 0.6; transition: opacity 200ms; - max-width: 100%; - height: auto; + white-space: nowrap; &:hover { opacity: 1; @@ -284,91 +335,6 @@ $className: 'customer-story'; } } -// Industry Breakdown -$className: 'industry-breakdown'; - -.#{$className} { - display: flex; - justify-content: center; - padding: 80px 64px; - - @include mediaMax($tablet) { - padding: 60px 24px; - } - - &__wrapper { - max-width: 1400px; - width: 100%; - } - - &__title { - font-size: 46px; - line-height: 1.2; - font-weight: 700; - text-align: center; - margin-bottom: 64px; - - @include mediaMax($mobile) { - font-size: 32px; - margin-bottom: 48px; - } - } -} - -// Industry Grid -$className: 'industry-grid'; - -.#{$className} { - display: grid; - grid-template-columns: repeat(3, 1fr); - gap: 32px; - - @include mediaMax($desktop) { - grid-template-columns: repeat(2, 1fr); - } - - @include mediaMax($tablet) { - grid-template-columns: 1fr; - gap: 24px; - } -} - -// Industry Card -$className: 'industry-card'; - -.#{$className} { - padding: 40px 32px; - background-color: $backgroundSecondaryColor; - border: 1px solid $borderPrimaryColor; - border-radius: 8px; - text-align: center; - - &__icon { - font-size: 48px; - margin-bottom: 20px; - } - - &__title { - font-size: 24px; - line-height: 1.3; - font-weight: 700; - margin-bottom: 16px; - } - - &__description { - font-size: 17px; - line-height: 1.6; - color: $fontSecondaryColor; - margin-bottom: 20px; - } - - &__companies { - font-size: 14px; - line-height: 1.4; - color: $fontSecondaryColor; - font-style: italic; - } -} // Community Section $className: 'community-section'; @@ -503,3 +469,184 @@ $className: 'community-link'; font-weight: 600; } } + +// More Stories +$className: 'more-stories'; + +.#{$className} { + display: flex; + justify-content: center; + padding: 80px 64px; + + @include mediaMax($tablet) { + padding: 60px 24px; + } + + &__wrapper { + max-width: 1200px; + width: 100%; + } + + &__title { + font-size: 46px; + line-height: 1.2; + font-weight: 700; + text-align: center; + margin-bottom: 64px; + + @include mediaMax($mobile) { + font-size: 32px; + margin-bottom: 48px; + } + } + + &__grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 32px; + + @include mediaMax($tablet) { + grid-template-columns: 1fr; + gap: 24px; + } + } + + &__card { + display: flex; + flex-direction: column; + align-items: center; + text-align: center; + padding: 48px 40px; + background-color: $backgroundSecondaryColor; + border: 1px solid $borderPrimaryColor; + border-radius: 8px; + cursor: pointer; + transition: all 200ms; + font-family: inherit; + color: inherit; + + &:hover { + border-color: $accentPrimaryColor; + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + } + } + + &__logo { + margin-bottom: 16px; + } + + &__card-title { + font-size: 24px; + line-height: 1.3; + font-weight: 700; + margin-bottom: 12px; + } + + &__card-text { + font-size: 17px; + line-height: 1.6; + color: $fontSecondaryColor; + margin-bottom: 20px; + } + + &__read-more { + font-size: 17px; + line-height: 1.4; + color: $accentPrimaryColor; + font-weight: 600; + } +} + +// Case Study Popup +$className: 'case-study-popup'; + +.#{$className} { + &__container { + width: 90%; + max-width: 720px; + height: auto; + max-height: 90vh; + margin-top: 5vh; + border-radius: 8px; + overflow-y: auto; + transform: translateY(20px); + opacity: 0; + transition: transform 200ms, opacity 200ms; + + .popup_opened & { + transform: translateY(0); + opacity: 1; + } + + @include mediaMax($tablet) { + width: 100%; + max-height: 100vh; + margin-top: 0; + border-radius: 0; + } + } + + &__content { + padding: 64px; + + @include mediaMax($tablet) { + padding: 48px 32px; + } + + @include mediaMax($mobile) { + padding: 40px 24px; + } + } + + &__logo { + margin-bottom: 24px; + } + + &__title { + font-size: 32px; + line-height: 1.3; + font-weight: 700; + margin-bottom: 32px; + + @include mediaMax($mobile) { + font-size: 24px; + margin-bottom: 24px; + } + } + + &__body { + font-size: 17px; + line-height: 1.8; + + p { + margin-bottom: 20px; + } + + h3 { + font-size: 20px; + font-weight: 700; + margin-bottom: 16px; + margin-top: 32px; + } + + ul { + list-style: none; + padding: 0; + margin-bottom: 20px; + } + + li { + padding-left: 20px; + position: relative; + margin-bottom: 12px; + + &::before { + content: '—'; + position: absolute; + left: 0; + color: $accentPrimaryColor; + } + } + } +} diff --git a/src/modules/blocks/footer.scss b/src/modules/blocks/footer.scss index 929d1b3..b32a336 100644 --- a/src/modules/blocks/footer.scss +++ b/src/modules/blocks/footer.scss @@ -7,7 +7,7 @@ $className: 'footer'; justify-content: center; min-height: 220px; padding: 52px 52px 0 126px; - background-color: #363636; + background-color: #1a1a1a; overflow: hidden; @include mediaMax($tablet) { diff --git a/src/modules/blocks/header.scss b/src/modules/blocks/header.scss index e9683ec..c774735 100644 --- a/src/modules/blocks/header.scss +++ b/src/modules/blocks/header.scss @@ -80,7 +80,6 @@ $className: 'header'; &:hover { opacity: 0.8; - text-decoration: underline; } &:not(:first-child) { diff --git a/src/modules/blocks/main-slide.scss b/src/modules/blocks/main-slide.scss index 7959923..a9e6b3c 100644 --- a/src/modules/blocks/main-slide.scss +++ b/src/modules/blocks/main-slide.scss @@ -33,6 +33,10 @@ $className: 'main-slide'; align-items: center; border-right: 1px solid $borderPrimaryColor; + & > svg { + animation: logo-float 4s ease-in-out infinite; + } + @include mediaMax($tablet) { max-width: 100%; margin-top: -12px; @@ -225,3 +229,12 @@ $className: 'main-slide'; } } } + +@keyframes logo-float { + 0%, 100% { + transform: translateY(0); + } + 50% { + transform: translateY(-8px); + } +} diff --git a/src/modules/blocks/popup.scss b/src/modules/blocks/popup.scss index c2cf537..f0425c5 100644 --- a/src/modules/blocks/popup.scss +++ b/src/modules/blocks/popup.scss @@ -93,3 +93,85 @@ $className: 'popup'; width: 100%; } } + +// Company Modal +$className: 'company-modal'; + +.#{$className} { + &__container { + width: 90%; + max-width: 520px; + height: auto; + margin-top: 15vh; + border-radius: 8px; + overflow-y: auto; + transform: translateY(20px); + opacity: 0; + transition: transform 200ms, opacity 200ms; + + .popup_opened & { + transform: translateY(0); + opacity: 1; + } + + @include mediaMax($tablet) { + width: 100%; + margin-top: 10vh; + border-radius: 0; + } + } + + &__content { + padding: 48px 40px; + + @include mediaMax($mobile) { + padding: 40px 24px; + } + } + + &__logo { + max-width: 120px; + max-height: 48px; + object-fit: contain; + margin-bottom: 16px; + } + + &__name { + font-size: 28px; + line-height: 1.3; + font-weight: 700; + margin-bottom: 8px; + } + + &__desc { + font-size: 15px; + line-height: 1.5; + color: $fontSecondaryColor; + margin-bottom: 24px; + } + + &__detail { + font-size: 17px; + line-height: 1.7; + margin-bottom: 24px; + } + + &__links { + display: flex; + gap: 24px; + flex-wrap: wrap; + } + + &__link { + display: inline-block; + font-size: 17px; + font-weight: 600; + color: $accentPrimaryColor; + text-decoration: none; + transition: $opacityPrimaryTransition; + + &:hover { + opacity: $opacityPrimaryValue; + } + } +} diff --git a/src/modules/blocks/resources-slide.scss b/src/modules/blocks/resources-slide.scss index cc81cac..8e1ca25 100644 --- a/src/modules/blocks/resources-slide.scss +++ b/src/modules/blocks/resources-slide.scss @@ -11,68 +11,138 @@ $className: 'resources-slide'; padding: 0 24px; padding-top: 48px; } +} - &__media { - padding-top: 64px; - padding-bottom: 64px; +// Resource Icon Cards (Docs) +$className: 'resource-cards'; - @include mediaMax($tablet) { - padding-bottom: 24px; - margin-bottom: 0; - } +.#{$className} { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 12px; + width: 100%; + padding: 32px 0; + + @include mediaMax($mobile) { + grid-template-columns: 1fr; + } +} + +$className: 'resource-card'; + +.#{$className} { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 8px; + padding: 24px 16px; + border: 1px solid $borderPrimaryColor; + border-radius: 8px; + background-color: $backgroundPrimaryColor; + text-decoration: none; + color: inherit; + transition: all 200ms; + + &:hover { + border-color: $accentPrimaryColor; + transform: translateY(-1px); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); } - &__section { - &_type { - &_case-studies { - margin-top: 24px; - grid-area: caseStudies; - - @include mediaMax($tablet) { - grid-area: auto; - } - } - - &_docs { - grid-area: docs; - - @include mediaMax($tablet) { - grid-area: auto; - } - } - - &_learn-more { - grid-area: learnMore; - - @include mediaMax($tablet) { - grid-area: auto; - } - } - - &_videos { - margin-top: 24px; - grid-area: videos; - - @include mediaMax($tablet) { - grid-area: auto; - } - } - - &_community { - margin-top: 24px; - grid-area: community; - - @include mediaMax($tablet) { - grid-area: auto; - } - } + &__icon { + width: 48px; + height: 48px; + } + + &__title { + font-size: 15px; + line-height: 1.4; + font-weight: 700; + color: $accentPrimaryColor; + text-align: center; + } + + &_all { + border-style: dashed; + + .#{$className}__title { + font-size: 17px; } } +} + +// Video Cards (Thumbnails with overlay) +$className: 'video-cards'; + +.#{$className} { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 12px; + width: 100%; + padding: 32px 0; + + @include mediaMax($mobile) { + grid-template-columns: 1fr; + } +} + +$className: 'video-card'; + +.#{$className} { + display: grid; + align-items: end; + width: 100%; + border-radius: 8px; + overflow: hidden; + text-decoration: none; + transition: all 200ms; + + &:hover { + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + } - &__heading { - font-size: 20px; - line-height: 1.6; + &__thumb { + grid-column: 1 / 2; + grid-row: 1 / 2; + width: 100%; + display: block; + } + + &__overlay { + grid-column: 1 / 2; + grid-row: 1 / 2; + position: relative; + display: flex; + flex-direction: column; + justify-content: flex-end; + padding: 10px 10px 8px 36px; + min-height: 60px; + background: linear-gradient(360deg, rgba(0, 0, 0, 0.9) 0%, rgba(0, 0, 0, 0.5) 60%, rgba(0, 0, 0, 0) 100%); + } + + &__play { + position: absolute; + left: 10px; + bottom: 10px; + width: 20px; + height: 20px; + background-image: url('./images/play-icon.svg'); + background-repeat: no-repeat; + background-size: contain; + } + + &__title { + color: $fontPrimaryInvertedColor; + font-size: 12px; + line-height: 1.3; font-weight: 700; - margin-bottom: 24px; + } + + &__speaker { + color: rgba(255, 255, 255, 0.7); + font-size: 11px; + line-height: 1.3; } } diff --git a/src/modules/blocks/slide.scss b/src/modules/blocks/slide.scss index 88bfd2c..71b0710 100644 --- a/src/modules/blocks/slide.scss +++ b/src/modules/blocks/slide.scss @@ -5,7 +5,12 @@ $className: 'slide'; flex-direction: column; align-items: center; padding: 120px 64px 0; - border-top: 1px solid $borderPrimaryColor; + border-top: none; + scroll-margin-top: 64px; + + &[id] { + scroll-margin-top: 64px; + } @include mediaMax($tablet) { padding: 48px 64px 0; diff --git a/src/modules/common.scss b/src/modules/common.scss index 47fae24..5d1c28e 100644 --- a/src/modules/common.scss +++ b/src/modules/common.scss @@ -13,6 +13,10 @@ box-sizing: border-box; } +html { + scroll-behavior: smooth; +} + :root { font-family: 'Stem', Arial, sans-serif; font-size: 18px; diff --git a/src/partials/blog_index_body.hbs b/src/partials/blog_index_body.hbs index 522d032..4d1ad0a 100644 --- a/src/partials/blog_index_body.hbs +++ b/src/partials/blog_index_body.hbs @@ -5,6 +5,13 @@

Engineering

    + {{> blog_article date='March 17, 2026' em-icon=true title='Web slides are web apps: live interactivity for Reveal.js and Slidev' link="https://evilmartians.com/chronicles/web-slides-are-web-apps-live-interactivity-for-revealjs-and-slidev"}} + {{> blog_article date='December 18, 2025' em-icon=true title='AnyCable, Rails, and the pitfalls of LLM-streaming' link="https://evilmartians.com/chronicles/anycable-rails-and-the-pitfalls-of-llm-streaming"}} + {{> blog_article date='September 15, 2025' em-icon=true title='Baking with Rails at scale: recipes in Ruby, cookware from Go, C, and Rust' link="https://evilmartians.com/chronicles/baking-with-rails-at-scale-recipes-in-ruby-cookware-from-go-c-rust"}} + {{> blog_article date='July 29, 2025' em-icon=true title='AnyCable for Laravel: reliable WebSocket infrastructure' link="https://evilmartians.com/chronicles/anycable-for-laravel"}} + {{> blog_article date='March 18, 2025' em-icon=true title='Simple Declarative Presence for Hotwire apps with AnyCable' link="https://evilmartians.com/chronicles/simple-declarative-presence-for-hotwire-apps-with-anycable"}} + {{> blog_article date='February 25, 2025' em-icon=true title='How Doximity brought real-time Go power to their Rails app' link="https://evilmartians.com/chronicles/growing-pains-and-a-dose-of-go-real-time-features-for-this-rails-app"}} + {{> blog_article date='November 12, 2024' em-icon=true title='Hey, AnyCable speaking! Needing help with a Twilio-OpenAI connection?' link="https://evilmartians.com/chronicles/anycable-speaking-needing-help-with-a-twilio-openai-connection"}} {{> blog_article date='July 25, 2023' video-icon=true title='AnyCable v1.4: reliable real-time for all: Ruby, Rails, Hotwire and beyond' link="/anycasts/anycable-v1-4-reliable-real-time-for-all"}} {{> blog_article date='March 24, 2023' video-icon=true title='Flying multi-regionally with NATS' link="/anycasts/flying-multi-regionally-with-nats"}} {{> blog_article date='March 21, 2023' em-icon=true title='AnyCable off Rails: connecting Twilio streams with Hanami' link="https://evilmartians.com/chronicles/anycable-goes-off-rails-connecting-twilio-streams-with-hanami"}} @@ -24,6 +31,15 @@

    Use Cases

      + {{> + use_case + background-color='#1a73e8' + url='https://evilmartians.com/chronicles/growing-pains-and-a-dose-of-go-real-time-features-for-this-rails-app' + img-src='/images/logos/callbell-white.svg' + img-alt='Doximity' + date='February 25, 2025' + title='How Doximity brought real-time Go power to their Rails app' + }} {{> use_case background-color='rgba(226, 232, 240, 1)' diff --git a/src/partials/dochead.hbs b/src/partials/dochead.hbs index 7f49d27..0b608d4 100644 --- a/src/partials/dochead.hbs +++ b/src/partials/dochead.hbs @@ -1,7 +1,7 @@ {{coalesce pageTitle - 'AnyCable: realtime server for reliable two-way communication' + 'AnyCable: realtime server with delivery guarantees for Rails, Laravel, Node.js, Python, and any backend' }} diff --git a/src/partials/footer.hbs b/src/partials/footer.hbs index 2fc42ca..6dfd917 100644 --- a/src/partials/footer.hbs +++ b/src/partials/footer.hbs @@ -70,14 +70,6 @@