23 Commits

Author SHA1 Message Date
8f5e84523e Merge pull request 'chore(auth): redirect sign-in/sign-up to app.cameleer.io' (#5) from relaunch-2026-04-25 into main
All checks were successful
ci / build-test (push) Successful in 4m17s
Reviewed-on: #5
2026-04-25 09:33:23 +02:00
hsiegeln
fa12df8ec6 chore(auth): redirect sign-in/sign-up to app.cameleer.io
All checks were successful
ci / build-test (push) Successful in 3m41s
ci / build-test (pull_request) Successful in 4m12s
Both auth flows now navigate to the app domain rather than the
auth.cameleer.io subdomain:

  PUBLIC_AUTH_SIGNIN_URL → https://app.cameleer.io/sign-in
  PUBLIC_AUTH_SIGNUP_URL → https://app.cameleer.io/sign-in?first_screen=register

Updated:
- .env.example (the canonical reference values)
- OPERATOR-CHECKLIST.md (deploy-time secret values)
- src/config/auth.test.ts (test fixtures)
- src/middleware.ts (CSP-comment about <a> navigation target)
- src/pages/privacy.astro (visitor-facing external-links section
  in §6 of the privacy policy)

The auth.ts validator stays strict-https — the new URLs are still
absolute https URLs, just on a different host.  Logto itself may
still run at auth.cameleer.io as the OIDC backend; only the
visitor-facing /sign-in entry point moved.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 09:28:02 +02:00
3b184488bb Merge pull request 'relaunch-2026-04-25' (#4) from relaunch-2026-04-25 into main
All checks were successful
ci / build-test (push) Successful in 4m21s
Reviewed-on: #4
2026-04-25 08:08:54 +02:00
hsiegeln
203e4bfb41 perf: replace 1.5 MB cameleer-logo.svg refs with optimised PNGs
All checks were successful
ci / build-test (push) Successful in 3m44s
ci / build-test (pull_request) Successful in 4m17s
The Inkscape-exported cameleer-logo.svg in public/ is 1.5 MB —
loaded eagerly in the SiteHeader (32×32) and Hero (64×64), it was
the dominant hit on the homepage's largest-contentful-paint. The
relaunch's added above-the-fold DOM nudged Lighthouse perf from
0.95 to 0.92 and tipped CI's >=0.95 threshold red.

Switch all four SVG references to the pre-optimised PNG icons that
already ship in public/icons/:
  - SiteHeader (32-displayed): /icons/cameleer-48.png   (4.4 KB)
  - Hero       (64-displayed): /icons/cameleer-192.png  (36 KB)
  - SiteFooter (24-displayed): /icons/cameleer-32.png   (2.4 KB)
  - BaseLayout favicon link: drop the SVG, keep the existing
    32 PNG fallback (already declared on the next line).

Local Lighthouse (http-server, no gzip) before: perf 0.72,
LCP 10.0s. After: perf 0.94, LCP 1.6s. CI on Linux + LH static
server should comfortably clear the 0.95 gate.

The SVG file itself is left in place — unreferenced, but kept in
case any external link still points at it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 07:59:30 +02:00
hsiegeln
8dec3e792a chore(sections): delete retired DualValueProps + ProductShowcase
Some checks failed
ci / build-test (push) Failing after 3m44s
These two sections were collapsed into ThreeAmWalkthrough.astro
(Task 3). No remaining consumers — removed from the tree.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 02:37:33 +02:00
hsiegeln
9cded54ce3 feat(homepage): wire SocialProofStrip + ThreeAmWalkthrough
Updates the homepage section order to the proof-first arc:
  Hero → SocialProofStrip → ThreeAmWalkthrough → HowItWorks
  → WhyUs → PricingTeaser → FinalCTA

The retired DualValueProps and ProductShowcase imports are dropped
here; the unused .astro files themselves are deleted in Task 11.

Page <title> + <meta description> updated to lead with the new H1.

Verified: dist/index.html contains the #walkthrough anchor target.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 02:37:17 +02:00
hsiegeln
21c1122369 refactor(pricing-page): rename tiers Trial/Starter/Scale/Enterprise
Customer-facing tier names replace the internally-coded
MID/HIGH/BUSINESS labels. Pricing structure (envs, apps,
retention, features) is unchanged. CTA labels updated to match.

The 'Everything in X' feature lines reference the new neighbour
names. Footer note 'HIGH and BUSINESS' updated to 'Scale and
Enterprise'.

Verified: dist/ contains no remaining MID/HIGH/BUSINESS strings.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 02:36:43 +02:00
hsiegeln
5f06e5ccad refactor(pricing-teaser): rename tiers, show only 2 cards
- Tier rename: MID → Starter (Scale and Enterprise live on /pricing).
- Homepage shows Trial + Starter only. Starter retains the
  ★ MOST POPULAR ribbon.
- 'See full comparison →' inline link replaced by a clearer
  'See all plans (Scale, Enterprise) →' line below the cards.
- Trial card price stays 'Free'; the tier name stays honest about
  the 14-day cap.

The full /pricing.astro page still shows all four renamed tiers —
updated separately in the next commit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 02:36:07 +02:00
hsiegeln
04b930de62 refactor(final-cta): bookend the hero, drop camel pun
H2 now echoes the Hero (intentional bookend pattern). Sub line
loses the 'No camels harmed' aside. Single primary CTA — no
secondary.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 02:35:41 +02:00
hsiegeln
b1b6b52f3f refactor(why-us): drop 03:00 watermark, reword card 2
The decorative giant '03:00' watermark on card 2 plus its 'live'
ops-desk timestamp gimmick was the third repetition of the 3 AM
metaphor on the homepage — the post-launch review flagged that
five hits turns a sharp pain point into a slogan.

Card 2 reworded to lead with 'operated integration in production
for 15 years' — same pedigree claim, no second 3 AM reference.
The walkthrough section already does the 3 AM beat in full.

Card 1 unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 02:35:24 +02:00
hsiegeln
0ad067847c refactor(how-it-works): slim repetitive 'no SDK' boilerplate
The 'no code changes / no SDK / no rewrite' line is already said
clearly in the Hero subhead and the WhyUs cards. Repeating it on
step 1 of HowItWorks adds nothing. Step 3's tail 'Nothing to
instrument. Nothing to maintain.' is two sentences saying the
same thing — both removed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 02:34:58 +02:00
hsiegeln
d67a89bacb feat(hero): single H1, annotation pins, microline, anchor CTA
- Drop the rotating headline and its <script> block
- Replace with single category-defining H1: 'Ship Camel integrations.
  Sleep through the night.'
- Add price microline under the CTAs (14-day trial · from €20/mo)
- Replace 'Sign in' secondary CTA with 'See it in action ↓' anchor
  to #walkthrough
- Add three numbered annotation pins overlaid on the screenshot,
  with a 3-up legend below the image (correlation ID, failure
  context, error pinned)

The eyebrow pill is retained — the only surviving camel pun on
the homepage per the pun-budget decision in the spec.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 02:34:37 +02:00
hsiegeln
54bbb46755 feat(sections): add ThreeAmWalkthrough component
Replaces DualValueProps + ProductShowcase with a single before/after
split: a styled <pre> block (the 'without' state) next to the
existing /product/error-detail.png screenshot (the 'with' state).
Three short callouts below.

Section anchor #walkthrough is the target for the Hero's
'See it in action ↓' secondary CTA (added in Task 4).

The 'without' panel is implemented as a styled <pre> per the spec —
no asset production required. A future phase may swap to a recorded
terminal screencap; that swap is a one-component change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 02:33:50 +02:00
hsiegeln
29c2d13776 feat(sections): add SocialProofStrip component
Founder pedigree quote plus design-partner mailto CTA.
Uses auth.salesEmail (not auth.salesMailto) so we can pass a subject.

Two PENDING gates documented in the component:
  - [Founder Name] placeholder
  - 'ex-nJAMS' wording subject to trademark clearance

Component is created but not yet wired into index.astro.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 02:33:07 +02:00
hsiegeln
ce314adf2d feat(styles): enable smooth in-page anchor scrolling
Adds html { scroll-behavior: smooth } with a prefers-reduced-motion
override. Required for the relaunch hero's 'See it in action' anchor
CTA to feel natural.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 02:32:34 +02:00
hsiegeln
ad4288c3ed docs(plans): cameleer website relaunch implementation plan
Twelve atomic tasks, each ending with its own commit:
  1. Add scroll-behavior:smooth to global.css
  2. Create SocialProofStrip.astro
  3. Create ThreeAmWalkthrough.astro
  4. Rebuild Hero.astro
  5. Slim HowItWorks.astro
  6. Refresh WhyUs.astro
  7. Rebuild FinalCTA.astro
  8. Refresh PricingTeaser.astro
  9. Refresh /pricing.astro tier names
 10. Wire new sections into index.astro
 11. Delete DualValueProps + ProductShowcase
 12. Run full quality gates

Order is build-safe: each task leaves the build green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 02:31:33 +02:00
hsiegeln
e3383471d1 docs(specs): cameleer website relaunch design (2026-04-25)
Structural relaunch of the homepage to address the conversion gaps
identified in the post-launch design review:

- Single H1 (kill the rotating one)
- New social proof strip (founder quote + design-partner CTA)
- 3 AM walkthrough section with before/after split (replaces
  DualValueProps + ProductShowcase)
- Pricing tier rename: Trial / Starter / Scale / Enterprise
- Pricing teaser slimmed to 2 cards on the homepage
- Pun budget cut from 5+ to 1 (eyebrow pill survives)

Static stack, hosting, and security posture unchanged from the
2026-04-24 spec.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 02:26:46 +02:00
hsiegeln
b7b58dd948 feat(design): click-to-enlarge on product screenshots
Some checks failed
ci / build-test (push) Failing after 3m41s
Lightbox.astro — reusable native HTMLDialogElement wrapper:
- Trigger: <button> wrapping the <img>, cursor: zoom-in, amber zoom-pill
  badge fades in on hover/focus.
- Dialog: showModal() opens a full-viewport modal (≤1800x1200 cap) with
  blurred amber-tinted backdrop.
- Close paths: native form[method=dialog] submit (Escape + close button),
  click on backdrop, click on the image itself.
- Accessibility: aria-labelledby + visually-hidden heading avoids both
  aria-label-misuse and no-redundant-role validator conflicts. Focus
  returns to trigger on close (native HTMLDialogElement behavior).
- Motion: 220ms fade+scale open, disabled under prefers-reduced-motion.
- CSP: <script> is Astro-bundled to an external file — script-src 'self'
  respected.

Hero and ProductShowcase now use <Lightbox> instead of a raw <img> for
the product screenshots. The existing frame styling (border, glow, ring
overlay) is untouched — the lightbox trigger is a block-level button
that fills the frame.
2026-04-25 00:31:48 +02:00
hsiegeln
4d4c072834 feat(design): atmosphere + WhyUs editorial 3-AM treatment
TopographicBg now actually reads:
- Per-line stroke width varies (triangle wave — contour-interval feel)
- Per-line opacity varies by vertical depth (darker mid-section, lighter
  edges)
- One line in four rendered in cyan (echo of cross-route correlation)
- Radial-mask soft edge fade so lines dissolve into the section boundary
- Default opacity bumped from 0.12 to 0.35; section callers still scale it
  down via the opacity prop, but the new internal variation makes the
  atmosphere visible where before it was invisible

WhyUs second tile: 3-AM storytelling moment now lands typographically:
- Decorative 03:00 glyph (amber/4% alpha) in the top-right corner
- Eyebrow log-entry treatment: pulsing amber dot + mono 03:00:47.218
  timestamp + OPS DESK label — reads like a product UI log row
- The rest of the tile unchanged

ProductShowcase figure: figcaption moved to last child (HTML spec
requires figcaption to be first or last in a figure; a div after it was
a validation error).
2026-04-25 00:26:16 +02:00
hsiegeln
c4395eb245 feat(design): card motion + Pricing MID tier hierarchy
- DualValueProps: 110ms staggered rise-in on load (cubic-bezier ease),
  reduced-motion users see cards pre-populated, no animation.
- All card sections (DualValueProps, HowItWorks, WhyUs, Pricing) gain a
  subtle hover lift: -translate-y-0.5, amber/40 border, soft amber drop
  shadow. 200ms ease-out — tactile but not noisy.
- Pricing MID tier now looks like the highlighted option: ring-2 accent,
  amber-tinted drop shadow, lg:-translate-y-2 (sits above the others),
  and a 'MOST POPULAR' ribbon pill. The 1px border swap was invisible.
2026-04-25 00:23:54 +02:00
hsiegeln
073ff2ad48 feat(design): new ProductShowcase section — 'When something breaks'
Editorial section between DualValueProps and HowItWorks. Breaks the
identical-rectangle cascade with an asymmetric 8/4 grid: large
error-detail screenshot with subtle cyan/amber backlight on the left,
three numbered callout captions on the right.

The screenshot (cross-route correlation chain + circuit breaker +
fallback + Java stack trace) makes the 'deep tracing, replay, live
control' claims concrete in a way the abstract RouteDiagram never did.

Cyan kicker on this section (vs. amber elsewhere) signals 'this one is
different' and echoes the cross-route correlation color in the product.
2026-04-25 00:22:28 +02:00
hsiegeln
ad8312b7f0 chore: gitignore .claude/ session state
Accidentally committed .claude/scheduled_tasks.lock in the previous
commit. Untrack it and add .claude/ to .gitignore so local Claude Code
session state does not leak into the repo.
2026-04-25 00:21:07 +02:00
hsiegeln
8c77db02ac feat(design): Hero asymmetric layout with real product UI + bug fixes
- Hero restructured from stacked to 2-col grid on lg+ (copy left, product
  screenshot right). Replaces the abstract RouteDiagram with the actual
  exchange-detail view — the product doing the thing the copy promises.
- Kicker broken out of the shared uppercase-mono pattern: italic pill with
  a soft amber fill/border, scaled up to 14px. The humor now wears a
  different costume from the other section kickers.
- Hero brand mark scaled to 64px and given a slow 7s sway (reduced-motion
  guarded) — living atmosphere, not ambient animation.
- H1 min-height raised to 2.5em to absorb the 2-line wrap of line 1 at
  mobile sizes without layout shift on rotation.
- Amber radial glow behind the product shot + subtle bevel + frame ring.
- Footer placeholder 3-wavy-lines SVG replaced with real camel logo
  (spec gap from earlier refresh — header got swapped, footer didn't).
- Screenshot assets imported under public/product/.
2026-04-25 00:20:39 +02:00
26 changed files with 2166 additions and 197 deletions

View File

@@ -1,5 +1,5 @@
# Logto auth endpoints — the marketing site only performs <a href> navigations to these.
# No tokens, no cookies, no XHR — these are plain hyperlinks.
PUBLIC_AUTH_SIGNIN_URL=https://auth.cameleer.io/sign-in
PUBLIC_AUTH_SIGNUP_URL=https://auth.cameleer.io/sign-in?first_screen=register
PUBLIC_AUTH_SIGNIN_URL=https://app.cameleer.io/sign-in
PUBLIC_AUTH_SIGNUP_URL=https://app.cameleer.io/sign-in?first_screen=register
PUBLIC_SALES_EMAIL=sales@cameleer.io

3
.gitignore vendored
View File

@@ -22,6 +22,9 @@ Thumbs.db
# Brainstorming / visual companion previews
.superpowers/
# Claude Code session state (local tooling)
.claude/
# Logs
*.log
npm-debug.log*

View File

@@ -75,8 +75,8 @@ Add these under Repository settings → Actions → Secrets (or variables):
| `SFTP_PATH` | secret | Absolute path to the Apache vhost docroot configured in konsoleH (typically `/usr/www/users/<login>/public_html`). Mismatch → 404 on origin. |
| `SFTP_KEY` | secret | Contents of `~/.ssh/cameleer-website-deploy` (private key, PEM) |
| `SFTP_KNOWN_HOSTS` | secret | Contents of `hetzner-known-hosts.txt` (captured via `ssh-keyscan`) |
| `PUBLIC_AUTH_SIGNIN_URL` | secret | `https://auth.cameleer.io/sign-in` |
| `PUBLIC_AUTH_SIGNUP_URL` | secret | `https://auth.cameleer.io/sign-in?first_screen=register` |
| `PUBLIC_AUTH_SIGNIN_URL` | secret | `https://app.cameleer.io/sign-in` |
| `PUBLIC_AUTH_SIGNUP_URL` | secret | `https://app.cameleer.io/sign-in?first_screen=register` |
| `PUBLIC_SALES_EMAIL` | secret | `sales@cameleer.io` (or whatever sales alias you set up) |
These three are not actually secret (they end up in the built HTML), but Gitea's

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,417 @@
# Cameleer website — relaunch design spec
**Date**: 2026-04-25
**Status**: Approved for implementation planning
**Project**: `cameleer-website`
**Supersedes (homepage composition only)**: `2026-04-24-cameleer-website-design.md` §6 (homepage layout) and `2026-04-24-cameleer-website-copy-refresh-design.md` §2 (rotating hero)
**Preserved from prior specs**: architecture, hosting, security posture, auth flow, audience model, tech stack, brand mark, design tokens
---
## 1. Overview
The current marketing site, while well-crafted at the component level, is strategically under-powered: a rotating H1 fails the 5-second test, the page leans 5+ camel puns deep, "3 AM" is repeated to slogan-status, there's no social proof anywhere, the screenshot is unannotated, and the homepage shows four pricing tiers (two of which say "Contact"). This spec rebuilds the homepage composition and copy to address those gaps.
**Three goals:**
1. Pass the 5-second test for two audiences in parallel (DevOps Engineers + IT Managers).
2. Carry the page on credible trust anchors despite having zero customer logos to ship.
3. Ship within the same static-only Astro 5 stack — no new dependencies, no backend, no forms, no analytics.
The pricing page (`/pricing.astro`) gets a tier-naming refresh; everything else outside the homepage stays as-is.
---
## 2. Non-goals (preserved from prior spec)
These remain explicitly **out of scope** — same list as `2026-04-24-cameleer-website-design.md` §3:
- Blog, docs, features page, about page, changelog, case studies (Docs/Changelog nav stubs deferred to a future phase — see §10)
- Contact forms, newsletter signup, lead-capture, email automation
- Interactive product demos, video players
- Analytics, tracking pixels, cookie consent banners
- Custom sign-up or sign-in forms (all auth redirects to Logto)
- Backend code on the marketing host
- Customer logos, attributed customer quotes (no customers ready to publish yet — see §4)
---
## 3. Audiences (unchanged)
Same two-audience model as the prior spec. Every section continues to label its primary audience lean:
| Audience | Role | What they need from the page |
|----------|------|------------------------------|
| **Managers** (amber) | Integration leads, architects who sign the check | Business outcomes, risk reduction, pedigree, pricing clarity |
| **Engineers** (cyan) | Camel developers, DevOps, SREs | Capability, mechanism, coverage, zero-friction mechanics |
The relaunch's H1 leans Manager-outcome on purpose — the IT Manager is the check-signer, "sleep through the night" speaks to both audiences (one runs the pager, one carries the cost of a bad pager night).
---
## 4. Trust anchors (the social proof problem)
The original site shipped with no social proof. This relaunch can't fix that with logos or attributed customer quotes — **none are ready to publish**. The two anchors we *can* lean on:
1. **Founder pedigree** (nJAMS lineage). Subject to trademark clearance — same gating as `WhyUs.astro`'s existing comment. Until cleared, the wording is held in a `<!-- PENDING -->` HTML comment.
2. **Design-partner program**. Reframes the pre-customer state as a feature ("hand-picked early partners"), with a `mailto:` CTA to `PUBLIC_SALES_EMAIL` that the visitor can use to apply.
Both anchors live in a new dedicated **Social Proof Strip** section (§6.2) immediately below the hero.
---
## 5. Page structure — final 7 sections
| # | Section | Status | Source files |
|---|---------|--------|--------------|
| 1 | **Hero** | Rebuilt-in-place | `Hero.astro` |
| 2 | **Social proof strip** | NEW | `SocialProofStrip.astro` (new) |
| 3 | **3 AM walkthrough** | Replaces DualValueProps + ProductShowcase | `ThreeAmWalkthrough.astro` (new); old files deleted |
| 4 | **How it works** | Rebuilt-in-place | `HowItWorks.astro` |
| 5 | **Why Cameleer** | Rebuilt-in-place | `WhyUs.astro` |
| 6 | **Pricing teaser** | Rebuilt-in-place | `PricingTeaser.astro` |
| 7 | **Final CTA** | Rebuilt-in-place | `FinalCTA.astro` |
Order is deliberate — see §11 for the rationale (proof-first arc: hero → who built it → product walkthrough → how → why → pricing → close).
---
## 6. Section content
### 6.1 Hero (rebuilt)
**Audience lean**: Manager-outcome, with engineer-readable proof on the right.
**Layout**: existing 5/7 grid retained — left column copy, right column screenshot.
**Left column copy:**
- **Eyebrow pill** (mono, accent, italic — kept from current): `✦ Your camels called. They want a GPS.` — this is the *only* surviving camel pun on the homepage.
- **H1** (single, no rotator): **Ship Camel integrations. Sleep through the night.**
- **Sub** (~18px, muted): "Cameleer is the hosted runtime and observability platform for Apache Camel — auto-traced, replay-ready, cross-service correlated. The 3 AM page becomes a 30-second answer."
- **Primary CTA**: `Start free trial` (existing `CTAButtons` primary, no changes).
- **Secondary CTA**: `See it in action ↓` — anchor link to `#walkthrough`. New, replaces the existing `Sign in` button which moves to top-right nav only.
- **Microline** (mono, muted, ~12px) under CTAs: `14-day trial · from €20/mo · no credit card`.
**Right column**:
- Existing `Lightbox` of `/product/exchange-detail.png` is retained.
- Three numbered annotation pins overlaid on the image via positioned absolute spans (no PNG re-bake). Pins coordinate with three short callout labels rendered *below* the image (not on it):
1. **Correlation ID** — "Click to follow one exchange across services."
2. **Failure in context** — "Circuit breaker tripped. Fallback ran. Tried `backend:80`."
3. **Full error pinned** — "Exception, stack trace, headers, payload — all here."
- The pin-to-callout mapping is achieved with `aria-describedby` so screen-readers reach the explanation.
- The hero-mark sway and topographic background are retained.
**Hero rotator removed.** The three rotating lines are not rehomed — the eyebrow pill carries the brand voice, the H1 carries the product claim. This is a deliberate reduction in pun count from 5+ to 1.
### 6.2 Social proof strip (NEW)
**Audience lean**: Both — managers see pedigree, engineers see honest framing.
**Component**: `SocialProofStrip.astro` (new, in `src/components/sections/`).
**Layout**: full-width section, dark, no card-style border. Visually quieter than the surrounding sections so it reads as a *trust line*, not a feature block. Vertical padding ~py-16.
**Content** (single content column, `max-w-3xl mx-auto px-6`):
- **Eyebrow** (mono, amber, ~12px): `// Built by people who've done this before`
- **Quote block** (italic, ~17px, max 62ch, accent-colored 3px left border, padding-left ~20px):
> *"We spent 15 years building integration monitoring for banks that couldn't afford downtime. Cameleer is what we'd build today — purpose-built for Apache Camel, no retrofit."*
- **Attribution** (~13px, muted, mono): `— [Founder Name], co-founder · ex-nJAMS`
- **Below attribution** (~24px gap, then a single `mailto:`-styled CTA in mono+cyan): `Apply to the design-partner program →`
**`<!-- PENDING -->` gates** (do not ship without resolving):
- `[Founder Name]` is a placeholder.
- `ex-nJAMS` mention is gated on trademark clearance (same as `WhyUs.astro`'s existing §10 caveat).
- Either gate may be deferred by removing the affected line — the section still works as "pedigree quote, design-partner CTA" without the nJAMS-specific phrase.
**Design-partner CTA target**: built inline in `SocialProofStrip.astro` using `auth.salesEmail` (not `auth.salesMailto`, which has no subject helper):
```astro
href={`mailto:${auth.salesEmail}?subject=${encodeURIComponent('Design partner enquiry — Cameleer')}`}
```
No body — let the applicant introduce themselves.
### 6.3 3 AM walkthrough (replaces DualValueProps + ProductShowcase)
**Audience lean**: Engineer-pain on the left, Engineer-resolution on the right; Manager reads the contrast.
**Component**: `ThreeAmWalkthrough.astro` (new). Old `DualValueProps.astro` and `ProductShowcase.astro` are deleted.
**Section id**: `walkthrough` (anchor target for the hero secondary CTA).
**Layout**: a two-column responsive split that collapses to stacked at `md` and below. Below the split, a single row of three short callouts.
**Header block**:
- **Eyebrow** (mono, cyan): `// When something breaks`
- **H2**: **The 3 AM page. With and without Cameleer.**
- **Sub** (one line): "Same Camel app. Same failed exchange. Different night."
**Split — left column ("Without Cameleer")**:
- Container: rounded card, `bg-bg`, dashed border, mono font.
- Top tag (`text-text-faint`, mono, uppercase, tracking): `Without Cameleer · 03:12 AM`.
- Body: a styled `<pre>` block with realistic Camel-ish log content. Approximate content (final wording in implementation):
```
$ kubectl logs camel-router-7d4f8c
... [stack-trace excerpt — Camel-style line refs] ...
$ grep "order-842" *.log
router-3.log: WARN exchange order-842 stuck in saga-fulfillment
router-3.log: ERROR processor backend:80 → connect timeout
$ ssh prod-integration-3
prod-integration-3 $ kubectl logs ...
> slack #integration-team
"anyone know why order-842 is stuck??"
[3 of 4 reactions]
~47 min later: someone wakes up an SRE.
```
- Tone: muted/red-tinged for the stack trace lines, neutral muted for the rest, accent (sad amber) on the "47 min" closer.
**Split — right column ("With Cameleer")**:
- Container: rounded card, `bg-bg-elevated`, accent-tinted border, retains the same `box-shadow` glow as the existing showcase.
- Top tag (mono, accent): `With Cameleer · 30 sec`.
- Body: existing `Lightbox` of `/product/error-detail.png` (or a tighter crop — implementation may use the existing image). No annotation pins here — the hero already does the annotated-image pattern; this section is about the *contrast*, not the deep-dive.
- Below the image, a single line: `▸ Open exchange order-842 → see the failure pinned → click Replay after fix.`
**Below the split — three short callouts** (3-up grid at `lg`, stacks below):
1. **Cross-service correlation** — "Every exchange carries its correlation ID forward. One click jumps to what the downstream route did with the same message."
2. **Runtime detail, not guesswork** — "Circuit breaker tripped. Fallback path ran. Request tried `backend:80`. The pieces a 3 AM page actually needs — already captured."
3. **The whole story of a failure** — "Exception class, message, stack trace, headers, payload — all pinned to the exchange. No log-grepping tour."
(These three are the existing `ProductShowcase.astro` callouts, kept verbatim. They earn their place after the dramatic split — they explain *what* you saw on the right.)
**Asset note**: the "Without Cameleer" content is implemented as a styled `<pre>` block within the Astro component — no PNG asset required, no Photoshop, no licensing. A future phase may swap to a recorded terminal screenshot if it tests better; the swap is a one-component change.
### 6.4 How it works (rebuilt-in-place)
**Audience lean**: Engineer.
Same 3-step layout, same component. Slim the copy:
| Step | Title | Body (final) |
|------|-------|--------------|
| 01 | Point us at your Camel app | Drop it in, or connect one you already run. No code changes. |
| 02 | We take it from there | Every route, every processor, every exchange — discovered and traced automatically. Sensitive fields are masked by default. |
| 03 | Watch it run | Browse executions, tap live traffic, replay failed exchanges, follow flows across services. |
**Cuts**: the redundant "No SDK. Nothing to rewrite." line on step 1 (already said in Hero), and the duplicate "Nothing to instrument. Nothing to maintain." at the end of step 3 (two consecutive sentences saying the same thing).
### 6.5 Why Cameleer (rebuilt-in-place)
**Audience lean**: Manager.
Same 2-card layout. Two changes:
1. **Cut the giant `03:00` decorative watermark** on card 2. The 3 AM beat is now told in the walkthrough; repeating it as a wall-decal here is the "five hits on one metaphor" the roast flagged.
2. **Reword card 2** to remove the second 3 AM reference. New body (replacing both paragraphs):
- **H3**: **Built by people who've operated integration in production for 15 years.**
- **P1**: "We spent over a decade building integration monitoring for banks, insurers, and logistics operators — the kind of shops where a stuck exchange is a regulatory event, not just an inconvenience."
- **P2**: "Cameleer is what we'd build today, purpose-built for Apache Camel. No legacy, no retrofit, no assumptions about a generic middleware platform."
The "3 AM" phrasing is dropped; the pedigree claim is now told once in §6.2 (with the founder face) and again here (with the philosophy). Two distinct beats, not the same beat twice.
Card 1 (Generic APMs) stays as-is.
### 6.6 Pricing teaser (rebuilt-in-place)
**Audience lean**: Both.
**Tier rename across both `index.astro` and `/pricing.astro`**:
| Old name | New name | Price (unchanged) | Homepage teaser? |
|----------|----------|--------------------|-------------------|
| Trial | **Trial** | Free · 14 days | ✓ |
| MID | **Starter** | 20 € /mo | ✓ (highlight) |
| HIGH | **Scale** | Contact | — link only |
| BUSINESS | **Enterprise** | Contact | — link only |
The first tier keeps the name **Trial** (not "Free") because it's time-limited; the price column shows "Free" but the tier name stays honest about the 14-day cap.
**Homepage teaser layout**: 2 cards (Trial + Starter), Starter retains the `★ MOST POPULAR` ribbon.
**CTA labels** on the two cards:
- Trial card: `Start free trial` → `auth.signUpUrl`
- Starter card: `Start on Starter` → `auth.signUpUrl` (was: `Start on MID`)
**Below the cards**: a single line link: `See all plans (Scale, Enterprise) →` to `/pricing`. Replaces the inline "See full comparison →" link in the heading area.
**`/pricing.astro` page**: identical structure to today, but tier names updated to the renamed taxonomy and CTA labels updated accordingly.
### 6.7 Final CTA (rebuilt-in-place)
- **H2**: **Ship integrations. Sleep through the night.** (echoes H1 — bookend pattern, intentional repetition).
- **Sub**: "14-day free trial. Your first Camel app, hosted, traced, and running in under ten minutes. No code changes."
- **CTA**: `Start free trial` (single, primary). `CTAButtons` rendered with `showSecondary={false}`.
**Cut**: the entire `Your camels called. Time to ride.` line and the `No camels harmed.` aside.
---
## 7. Navigation (unchanged)
`SiteHeader.astro` keeps its current structure: Logo · Pricing · `[Sign in]` · `[Start free trial]`.
Docs / Changelog nav stubs are deferred to a follow-up phase. A "coming soon" stub page is worse than no nav entry. When real Docs or a real Changelog exists, this nav grows.
---
## 8. Voice and pun budget
**One camel pun on the homepage.** The eyebrow pill `Your camels called. They want a GPS.` is retained — it sits under the H1 (doesn't compete), it's witty without being a punchline, and it gives the brand voice one signature beat.
**Removed from the homepage:**
- `Camel integrations, minus the baggage.` (rotator line 2)
- `Your camels, our caravan. You just ride.` (rotator line 3)
- `Your camels called. Time to ride.` (FinalCTA H2 — replaced)
- `No camels harmed.` (FinalCTA sub — removed)
**3 AM mentions** are now told as one beat in §6.3 (the walkthrough) and one micro-mention in the H1 sub. The `03:00` decorative watermark on `WhyUs.astro` card 2 is cut. Three references → two. Slogan-status → narrative status.
---
## 9. Component-level changes
**New files:**
- `src/components/sections/SocialProofStrip.astro`
- `src/components/sections/ThreeAmWalkthrough.astro`
**Deleted files:**
- `src/components/sections/DualValueProps.astro`
- `src/components/sections/ProductShowcase.astro`
**Modified files:**
- `src/components/sections/Hero.astro` — rotator removed, eyebrow + H1 + sub + secondary CTA + microline + annotation pins
- `src/components/sections/HowItWorks.astro` — body copy slimmed (steps 1, 3)
- `src/components/sections/WhyUs.astro` — `03:00` watermark removed, card 2 reworded, founder/3-AM language refactored
- `src/components/sections/PricingTeaser.astro` — tier renaming + 2-card homepage layout + "See all plans" link
- `src/components/sections/FinalCTA.astro` — H2 + sub + single-CTA reworked
- `src/components/CTAButtons.astro` — accept new optional `secondaryLabel`/`secondaryHref` overrides for hero's `See it in action ↓` (already supported by existing prop interface — no API change required)
- `src/pages/index.astro` — section order updated, deleted/new components wired
- `src/pages/pricing.astro` — tier renaming applied to the full table
**Unchanged:**
- `src/components/SiteHeader.astro`
- `src/components/SiteFooter.astro`
- `src/components/Lightbox.astro`
- `src/components/RouteDiagram.astro`
- `src/components/TopographicBg.astro`
- `src/config/auth.ts`
- `src/middleware.ts`
- `tailwind.config.mjs` (no new tokens needed)
- `astro.config.mjs`
**Also modified:**
- `src/styles/global.css` — add `html { scroll-behavior: smooth; }` plus a `@media (prefers-reduced-motion: reduce)` override resetting it to `auto`. Required for the hero secondary CTA's anchor scroll to feel natural.
---
## 10. Asset deliverables
**No new image assets required for v1 of the relaunch.** Specifically:
- The "Without Cameleer" panel in §6.3 is a styled `<pre>` block. No screenshot needed.
- The "With Cameleer" panel reuses the existing `/product/error-detail.png`.
- Hero annotation pins are HTML/CSS overlays on the existing `/product/exchange-detail.png` — no PNG re-bake.
**Future enhancement candidates** (not blocking this relaunch):
- Replace the `<pre>` "without" panel with a recorded terminal screencap (PNG/SVG).
- Re-bake `/product/exchange-detail.png` with annotations baked in if HTML overlays prove brittle on responsive breakpoints.
---
## 11. Why this order works (proof-first arc)
The H1 makes a strong claim ("30-second answer at 3 AM"). The page below has to deliver proof in order:
1. **Hero** — claim + annotated screenshot (claim + a thumbnail of the proof).
2. **Social proof strip** — *who* is making this claim, why you should believe them.
3. **3 AM walkthrough** — *show* the 30-second answer side-by-side with the alternative.
4. **How it works** — by now the visitor wants to know "ok how do I get this".
5. **Why Cameleer** — manager-voiced reassurance: "purpose-built, not generic."
6. **Pricing teaser** — clarity, two cards, no contact-sales wall.
7. **Final CTA** — the bookend.
A traditional SaaS layout (features → benefits → how → pricing) would put proof in third-party logos before the product walkthrough. We don't have those, so the walkthrough *is* the proof, and the strip + Why Cameleer carry the credibility load.
---
## 12. Validation
**CI gates that must pass on the branch before merge** (existing infrastructure):
- `npm run test` — vitest passes (auth config + middleware tests; the relaunch shouldn't touch these).
- `npm run build` — Astro static build completes with no errors.
- `npm run lint:html` — html-validate passes on `dist/`.
- `npm run lint:links` — linkinator passes on `dist/` (the new `#walkthrough` anchor is internal — link-checker should accept it).
- `npm run lh` — Lighthouse CI ≥ 0.95 on all 4 categories.
**Manual QA checklist** (operator runs before publish):
- [ ] Hero secondary CTA "See it in action ↓" scrolls smoothly to `#walkthrough` (requires adding `html { scroll-behavior: smooth; }` to `src/styles/global.css`, with a `@media (prefers-reduced-motion: reduce)` override that sets it back to `auto` — see §9).
- [ ] At `<= md` breakpoint, the walkthrough split stacks with no horizontal scroll.
- [ ] Annotation pins on the hero screenshot remain positioned correctly across the breakpoints we currently support.
- [ ] `prefers-reduced-motion: reduce` disables the hero-mark sway and any tile-rise animations (existing handling preserved).
- [ ] Tab focus order on the homepage is: nav → hero primary → hero secondary → social-proof CTA → walkthrough CTA targets … → final CTA.
- [ ] All `mailto:` links open with the correct subject (design-partner CTA + sales contacts).
- [ ] Trademark `<!-- PENDING -->` gate in `SocialProofStrip.astro` and `WhyUs.astro` is reviewed before publish.
- [ ] Founder name placeholder is filled in `SocialProofStrip.astro` before publish.
**Pre-publish blockers** (recorded in code as `<!-- PENDING -->` HTML comments):
1. `[Founder Name]` placeholder in `SocialProofStrip.astro` — must be replaced with a real name.
2. nJAMS / `ex-nJAMS` wording — must clear trademark review (existing pattern from `WhyUs.astro`).
These are deliberately surfaced as code-level TODOs rather than spec-level open questions so the operator can't accidentally publish with the placeholders intact.
---
## 13. Out-of-scope future enhancements
Listed here so they aren't lost — none are part of this spec:
- Customer logo strip in the social-proof section once at least one named pilot agrees to publish.
- Attributed customer quote + concrete metric (replaces the founder quote when available).
- Competitive comparison table (cut from this relaunch — revisit after a clear competitive narrative emerges with real customers).
- Docs and Changelog nav entries (gated on real content existing).
- Cloudflare Web Analytics opt-in (pending DSGVO review and a need that isn't met by Cloudflare's own zone analytics).
- Recorded terminal screencap for the "Without Cameleer" panel.
- A second variant of the H1 (or different secondary CTA) once enough traffic exists for an honest A/B test.
---
## 14. Decision log (for future reference)
Decisions made during the brainstorming session that aren't otherwise visible in the document:
| Decision | Choice | Why |
|----------|--------|-----|
| Relaunch ambition | Structural relaunch (composition + copy, not full reposition) | Static stack works; the gap is composition, not infrastructure |
| H1 voice | Manager-outcome ("Sleep through the night") | Check-signer audience; "sleep" works for both engineers and managers |
| Hero rotator | Killed | Two of three rotator lines were vibe-only — coin-flip on the 5-second test |
| Trust anchor | Founder quote + design-partner CTA | No customer logos available; pedigree + honest pre-customer framing |
| Comparison table | Cut | Not enough competitive narrative to build it honestly today |
| Tier names | Trial / Starter / Scale / Enterprise | Procurement-friendly, no internally-coded labels |
| Hero secondary CTA | `See it in action ↓` to `#walkthrough` | Low-commitment escape hatch; keeps visitor on-page |
| 3 AM walkthrough | Before/after split with real-feeling content | Strongest dramatic contrast; no competitor exposure |
| "Without Cameleer" image format | Styled `<pre>` block | No asset production blocking the relaunch |
| Nav additions (Docs/Changelog) | Deferred | Stub pages worse than no entry; revisit when real content exists |
| Pun budget | 1 (eyebrow pill) | Cut from 5+ to 1; voice survives, novelty tax doesn't |

Binary file not shown.

After

Width:  |  Height:  |  Size: 644 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 551 KiB

View File

@@ -0,0 +1,233 @@
---
interface Props {
src: string;
alt: string;
width: number;
height: number;
loading?: 'eager' | 'lazy';
caption?: string;
triggerClass?: string;
imgClass?: string;
}
const {
src,
alt,
width,
height,
loading = 'lazy',
caption,
triggerClass = '',
imgClass = 'block w-full h-auto',
} = Astro.props;
// Unique per-instance id so multiple lightboxes on a page do not collide.
const dialogId = `lb-${Math.random().toString(36).slice(2, 10)}`;
---
<button
type="button"
class={`lightbox-trigger group ${triggerClass}`.trim()}
data-lightbox-open={dialogId}
aria-label={`Enlarge: ${alt}`}
>
<img
src={src}
alt={alt}
width={width}
height={height}
loading={loading}
decoding="async"
class={imgClass}
/>
<span aria-hidden="true" class="lightbox-zoom-badge">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="11" cy="11" r="7.5"/>
<path d="m20 20-3.5-3.5"/>
<line x1="11" y1="8" x2="11" y2="14"/>
<line x1="8" y1="11" x2="14" y2="11"/>
</svg>
</span>
</button>
<dialog id={dialogId} class="lightbox-dialog" aria-modal="true" aria-labelledby={`${dialogId}-title`}>
<h2 id={`${dialogId}-title`} class="sr-only">{alt}</h2>
<form method="dialog" class="lightbox-close-form">
<button type="submit" class="lightbox-close" aria-label="Close">
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round">
<path d="M18 6 6 18"/>
<path d="m6 6 12 12"/>
</svg>
</button>
</form>
<img src={src} alt={alt} class="lightbox-image" />
{caption && <p class="lightbox-caption">{caption}</p>}
</dialog>
<style>
/* Trigger: reset native button chrome, keep block layout matching the old <img>. */
.lightbox-trigger {
all: unset;
display: block;
position: relative;
cursor: zoom-in;
width: 100%;
}
.lightbox-trigger:focus-visible {
outline: 2px solid #f0b429;
outline-offset: 4px;
border-radius: 0.5rem;
}
/* Zoom pill (fades in on hover / keyboard focus) */
.lightbox-zoom-badge {
position: absolute;
top: 0.85rem;
right: 0.85rem;
display: inline-flex;
align-items: center;
justify-content: center;
width: 2.25rem;
height: 2.25rem;
border-radius: 9999px;
background: rgba(240, 180, 41, 0.92);
color: #060a13;
opacity: 0;
transform: translateY(-3px);
transition: opacity 180ms ease-out, transform 180ms ease-out;
pointer-events: none;
box-shadow: 0 4px 12px -4px rgba(0, 0, 0, 0.5);
}
.lightbox-trigger:hover .lightbox-zoom-badge,
.lightbox-trigger:focus-visible .lightbox-zoom-badge {
opacity: 1;
transform: translateY(0);
}
/* Dialog */
.lightbox-dialog {
padding: 0;
margin: auto;
width: min(98vw, 1800px);
height: min(96vh, 1200px);
background: #060a13;
color: #e8eaed;
border: 1px solid rgba(240, 180, 41, 0.25);
border-radius: 0.75rem;
overflow: hidden;
box-shadow: 0 40px 100px -30px rgba(0, 0, 0, 0.8);
}
.lightbox-dialog::backdrop {
background: rgba(6, 10, 19, 0.82);
backdrop-filter: blur(6px);
-webkit-backdrop-filter: blur(6px);
}
.lightbox-close-form {
position: absolute;
top: 0.75rem;
right: 0.75rem;
margin: 0;
z-index: 2;
}
.lightbox-close {
all: unset;
cursor: pointer;
display: inline-flex;
align-items: center;
justify-content: center;
width: 2.5rem;
height: 2.5rem;
border-radius: 9999px;
color: #f0b429;
background: rgba(12, 17, 26, 0.92);
border: 1px solid rgba(240, 180, 41, 0.35);
transition: background 160ms ease-out, transform 160ms ease-out, border-color 160ms ease-out;
}
.lightbox-close:hover {
background: rgba(240, 180, 41, 0.18);
border-color: rgba(240, 180, 41, 0.7);
transform: scale(1.06);
}
.lightbox-close:focus-visible {
outline: 2px solid #f0b429;
outline-offset: 2px;
}
.lightbox-image {
display: block;
width: 100%;
height: 100%;
object-fit: contain;
background: #060a13;
cursor: zoom-out;
}
.lightbox-caption {
position: absolute;
left: 0;
right: 0;
bottom: 0.5rem;
margin: 0;
text-align: center;
color: #9aa3b2;
font-size: 0.875rem;
padding: 0 1rem;
}
/* Open animation */
@keyframes lightbox-in {
from { opacity: 0; transform: scale(0.985); }
to { opacity: 1; transform: scale(1); }
}
@keyframes lightbox-backdrop-in {
from { opacity: 0; }
to { opacity: 1; }
}
.lightbox-dialog[open] {
animation: lightbox-in 220ms cubic-bezier(0.16, 1, 0.3, 1);
}
.lightbox-dialog[open]::backdrop {
animation: lightbox-backdrop-in 220ms ease-out;
}
@media (prefers-reduced-motion: reduce) {
.lightbox-zoom-badge {
transition: none;
}
.lightbox-dialog[open],
.lightbox-dialog[open]::backdrop {
animation: none;
}
.lightbox-close {
transition: none;
}
}
</style>
<script>
// Open lightbox on trigger click. Close on click of the image itself or
// the backdrop. Bundled by Astro — CSP script-src 'self'.
const triggers = document.querySelectorAll<HTMLButtonElement>('[data-lightbox-open]');
triggers.forEach((trigger) => {
trigger.addEventListener('click', () => {
const id = trigger.getAttribute('data-lightbox-open');
if (!id) return;
const dialog = document.getElementById(id);
if (dialog instanceof HTMLDialogElement) {
dialog.showModal();
}
});
});
const dialogs = document.querySelectorAll<HTMLDialogElement>('dialog.lightbox-dialog');
dialogs.forEach((dialog) => {
dialog.addEventListener('click', (event) => {
const target = event.target;
if (!(target instanceof HTMLElement)) return;
// Click on the dialog element itself (the backdrop area) or the image
// closes. Clicks on the close button are already handled by the native
// form[method=dialog].
if (target === dialog || target.classList.contains('lightbox-image')) {
dialog.close();
}
});
});
</script>

View File

@@ -4,14 +4,14 @@ const year = new Date().getFullYear();
<footer class="border-t border-border mt-24">
<div class="max-w-content mx-auto px-6 py-12 flex flex-col md:flex-row md:items-center md:justify-between gap-8">
<div class="flex items-center gap-3">
<svg width="24" height="24" viewBox="0 0 32 32" aria-hidden="true">
<rect width="32" height="32" rx="6" fill="#0c111a"/>
<g fill="none" stroke="#f0b429" stroke-width="1.6" stroke-linecap="round">
<path d="M4 10 Q10 6 16 12 T28 10"/>
<path d="M4 16 Q10 12 16 18 T28 16"/>
<path d="M4 22 Q10 18 16 24 T28 22"/>
</g>
</svg>
<img
src="/icons/cameleer-32.png"
width="24"
height="24"
alt=""
decoding="async"
class="shrink-0 opacity-80"
/>
<span class="text-text-muted text-sm">© {year} Cameleer</span>
</div>
<nav class="flex items-center gap-8 text-sm text-text-muted" aria-label="Footer">

View File

@@ -5,7 +5,7 @@ import CTAButtons from './CTAButtons.astro';
<div class="max-w-content mx-auto px-6 h-16 flex items-center justify-between gap-6">
<a href="/" class="flex items-center gap-2 group" aria-label="Cameleer home">
<img
src="/cameleer-logo.svg"
src="/icons/cameleer-48.png"
width="32"
height="32"
alt=""

View File

@@ -3,24 +3,66 @@ interface Props {
opacity?: number;
lines?: number;
}
const { opacity = 0.12, lines = 8 } = Astro.props;
const { opacity = 0.35, lines = 9 } = Astro.props;
const paths: string[] = [];
interface Line {
d: string;
width: number; // stroke width in CSS px (non-scaling)
lineOpacity: number; // per-line opacity (0..1) — varies depth
tone: 'amber' | 'cyan';
}
const out: Line[] = [];
const stepY = 100 / (lines + 1);
for (let i = 1; i <= lines; i++) {
const y = i * stepY;
const amp = 4 + (i % 3) * 2;
paths.push(`M0,${y} Q25,${y - amp} 50,${y + amp * 0.6} T100,${y}`);
// Mix two frequencies so adjacent lines don't read parallel.
const amp = 3 + (i % 3) * 2 + Math.sin(i * 1.7) * 1.2;
const phase = (i * 13) % 25; // shift crests horizontally
const d = `M0,${y} Q${25 + phase / 3},${y - amp} ${50 + phase / 5},${y + amp * 0.6} T100,${y + (i % 2 ? 1 : -1)}`;
// Vary stroke weight with a triangle wave — gives the feel of cartographic contour intervals.
const triangle = Math.abs(((i + 2) % 4) - 2) / 2;
const width = 0.6 + triangle * 0.9;
// Depth: middle lines darker, edges lighter.
const depth = 1 - Math.abs((i - (lines + 1) / 2)) / ((lines + 1) / 2);
const lineOpacity = 0.35 + depth * 0.65;
// One cyan line roughly every 4th — echo of the cross-route correlation color.
const tone: 'amber' | 'cyan' = i % 4 === 2 ? 'cyan' : 'amber';
out.push({ d, width, lineOpacity, tone });
}
---
<div
class="topo-wrap absolute inset-0 pointer-events-none"
aria-hidden="true"
style={`--topo-opacity:${opacity}`}
>
<svg
class="absolute inset-0 w-full h-full pointer-events-none"
class="topo-svg absolute inset-0 w-full h-full"
viewBox="0 0 100 100"
preserveAspectRatio="none"
aria-hidden="true"
style={`opacity:${opacity}`}
>
<g fill="none" stroke="#f0b429" stroke-width="0.15" vector-effect="non-scaling-stroke">
{paths.map((d) => <path d={d} />)}
<g fill="none" vector-effect="non-scaling-stroke" stroke-linecap="round">
{out.map((l) => (
<path
d={l.d}
stroke={l.tone === 'cyan' ? '#5cc8ff' : '#f0b429'}
stroke-width={l.width}
stroke-opacity={l.lineOpacity}
/>
))}
</g>
</svg>
</div>
<style>
.topo-wrap {
opacity: var(--topo-opacity, 0.35);
/* Soft edge fade — lines should feel like they dissolve at the section
boundaries rather than hit them hard. */
-webkit-mask-image: radial-gradient(ellipse at 50% 45%, black 55%, transparent 95%);
mask-image: radial-gradient(ellipse at 50% 45%, black 55%, transparent 95%);
}
.topo-svg {
filter: blur(0.15px);
}
</style>

View File

@@ -1,40 +0,0 @@
---
interface Tile {
outcome: string;
capability: string;
}
// tile.capability is a compile-time constant defined below — never feed
// user-supplied or CMS content into set:html further down (XSS risk).
const tiles: Tile[] = [
{
outcome: 'Ship integrations, then sleep.',
capability:
'Every route, every processor, every exchange — traced automatically. When something breaks at 3 AM, the answer is already waiting for you. So you do not have to be.',
},
{
outcome: 'Debug in daylight.',
capability:
'Replay the exact exchange that failed. Follow a single request across services. See payloads before and after each processor. The pieces your ops team needs at 3 AM, captured already — so 3 AM stays quiet.',
},
{
outcome: 'Keep what you built. Keep what you chose.',
capability:
'You picked Apache Camel on purpose — open, portable, yours. Cameleer runs and understands your Camel apps as they are. No code changes, no SDK, no rewrite, no lock-in.',
},
];
---
<section class="border-b border-border">
<div class="max-w-content mx-auto px-6 py-20 md:py-24">
<div class="grid md:grid-cols-3 gap-6 md:gap-8">
{tiles.map((tile) => (
<div class="rounded-lg border border-border bg-bg-elevated p-7 md:p-8 hover:border-border-strong transition-colors">
<h2 class="text-xl md:text-2xl font-bold text-text mb-3 leading-snug">
{tile.outcome}
</h2>
<p class="text-text-muted leading-relaxed" set:html={tile.capability.replace(/`([^`]+)`/g, '<code class="font-mono text-accent bg-bg border border-border rounded px-1 py-0.5 text-sm">$1</code>')}></p>
</div>
))}
</div>
</div>
</section>

View File

@@ -6,13 +6,13 @@ import TopographicBg from '../TopographicBg.astro';
<TopographicBg opacity={0.18} lines={6} />
<div class="relative max-w-content mx-auto px-6 py-24 md:py-32 text-center">
<h2 class="text-display font-bold text-text mb-6">
Your camels called. Time to ride.
Ship integrations. Sleep through the night.
</h2>
<p class="text-lg md:text-xl text-text-muted max-w-prose mx-auto mb-10">
14-day free trial. Your first Camel app, hosted, traced, and running in under ten minutes. No code changes. No camels harmed.
14-day free trial. Your first Camel app, hosted, traced, and running in under ten minutes. No code changes.
</p>
<div class="flex justify-center">
<CTAButtons size="lg" />
<CTAButtons size="lg" showSecondary={false} />
</div>
</div>
</section>

View File

@@ -1,95 +1,127 @@
---
import CTAButtons from '../CTAButtons.astro';
import RouteDiagram from '../RouteDiagram.astro';
import TopographicBg from '../TopographicBg.astro';
import Lightbox from '../Lightbox.astro';
interface Pin {
label: string;
body: string;
top: string;
left: string;
}
const pins: Pin[] = [
{ label: '01', body: 'Correlation ID — click to follow one exchange across services.', top: '14%', left: '12%' },
{ label: '02', body: 'Failure in context — circuit breaker tripped, fallback ran, tried backend:80.', top: '46%', left: '52%' },
{ label: '03', body: 'Full error pinned — exception, stack trace, headers, payload.', top: '78%', left: '78%' },
];
---
<section class="relative overflow-hidden border-b border-border">
<TopographicBg opacity={0.14} lines={10} />
<div class="relative max-w-content mx-auto px-6 pt-20 pb-24 md:pt-28 md:pb-32">
<div class="max-w-3xl">
<div class="flex items-center gap-3 mb-6">
<TopographicBg opacity={0.22} lines={11} />
<div class="relative max-w-content mx-auto px-6 pt-16 pb-20 md:pt-24 md:pb-24 lg:pt-28">
<div class="grid lg:grid-cols-12 gap-10 lg:gap-14 items-center">
<div class="lg:col-span-5">
<img
src="/cameleer-logo.svg"
width="48"
height="48"
src="/icons/cameleer-192.png"
width="64"
height="64"
alt=""
decoding="async"
class="shrink-0"
class="shrink-0 mb-5 hero-mark"
/>
<p class="text-accent font-mono text-xs tracking-[0.25em] uppercase">
<p
class="inline-flex items-center gap-2 mb-7 rounded-full border border-accent/30 bg-accent/[0.08] text-accent px-3.5 py-1 text-sm italic font-medium"
>
<span aria-hidden="true" class="text-base">✦</span>
Your camels called. They want a GPS.
</p>
</div>
<h1
class="text-display font-bold text-text mb-6 hero-rotator"
aria-live="off"
data-hero-rotator
>
<span class="hero-line" data-active aria-hidden="false">Run Apache Camel without running Apache Camel.</span>
<span class="hero-line" aria-hidden="true">Camel integrations, minus the baggage.</span>
<span class="hero-line" aria-hidden="true">Your camels, our caravan. You just ride.</span>
<h1 class="font-bold text-text mb-6 hero-h1">
Ship Camel integrations. Sleep through the night.
</h1>
<p class="text-lg md:text-xl text-text-muted max-w-prose leading-relaxed mb-10">
The hosted home for your Camel integrations — with deep tracing, replay, and live control built in. Because you chose Camel to stay free, not to stay up all night.
<p class="text-lg md:text-xl text-text-muted max-w-prose leading-relaxed mb-8">
Cameleer is the hosted runtime and observability platform for Apache Camel — auto-traced, replay-ready, cross-service correlated. The 3 AM page becomes a 30-second answer.
</p>
<CTAButtons
size="lg"
secondaryLabel="See it in action ↓"
secondaryHref="#walkthrough"
/>
<p class="mt-4 font-mono text-xs text-text-faint">
14-day trial · from €20/mo · no credit card
</p>
<CTAButtons size="lg" />
</div>
<div class="mt-16 md:mt-20">
<RouteDiagram />
<div class="lg:col-span-7 relative">
<div class="hero-shot relative rounded-lg border border-border-strong bg-bg-elevated overflow-hidden">
<Lightbox
src="/product/exchange-detail.png"
alt="Cameleer Mission Control — route execution detail with processor-level trace"
width={1920}
height={945}
loading="eager"
/>
<div class="absolute inset-0 ring-1 ring-inset ring-accent/10 pointer-events-none rounded-lg"></div>
{pins.map((pin) => (
<span
aria-hidden="true"
class="hero-pin absolute inline-flex items-center justify-center w-7 h-7 rounded-full bg-accent text-bg font-mono text-xs font-bold pointer-events-none"
style={`top:${pin.top};left:${pin.left}`}
>
{pin.label}
</span>
))}
</div>
<ul class="hero-pin-legend mt-5 grid sm:grid-cols-3 gap-3 text-text-muted">
{pins.map((pin) => (
<li class="flex items-start gap-2 text-sm leading-snug">
<span class="font-mono text-accent text-xs mt-0.5">{pin.label}</span>
<span>{pin.body}</span>
</li>
))}
</ul>
<div aria-hidden="true" class="hero-shot-glow"></div>
</div>
</div>
</div>
</section>
<style>
.hero-rotator {
position: relative;
display: block;
/* Reserve height for the tallest line so no layout shift on swap.
Two lines at current H1 size handles all three on most viewports. */
min-height: 2.2em;
.hero-h1 {
font-size: clamp(2.25rem, 4.5vw, 4rem);
line-height: 1.05;
letter-spacing: -0.02em;
}
.hero-line {
display: block;
opacity: 0;
transition: opacity 700ms ease-in-out;
/* Stack all lines on top of each other — only [data-active] is visible. */
.hero-shot {
box-shadow:
0 1px 0 rgba(240, 180, 41, 0.08) inset,
0 30px 60px -20px rgba(0, 0, 0, 0.6),
0 10px 25px -10px rgba(0, 0, 0, 0.5);
}
.hero-pin {
box-shadow: 0 0 0 4px rgba(240, 180, 41, 0.22), 0 4px 10px -2px rgba(0, 0, 0, 0.5);
transform: translate(-50%, -50%);
}
.hero-shot-glow {
position: absolute;
inset: 0;
inset: 10% -5% 10% -5%;
background: radial-gradient(
60% 60% at 50% 50%,
rgba(240, 180, 41, 0.18),
transparent 70%
);
filter: blur(40px);
z-index: -1;
}
.hero-line[data-active] {
opacity: 1;
position: relative;
@keyframes hero-mark-sway {
0%, 100% { transform: translateY(0) rotate(0deg); }
50% { transform: translateY(-2px) rotate(-1.5deg); }
}
.hero-mark {
animation: hero-mark-sway 7s ease-in-out infinite;
transform-origin: 50% 90%;
}
@media (prefers-reduced-motion: reduce) {
.hero-line {
transition: none;
}
.hero-mark { animation: none; }
}
</style>
<script>
// Hero rotating headline. Bundled by Astro (CSP: script-src 'self').
const rotator = document.querySelector<HTMLElement>('[data-hero-rotator]');
if (rotator) {
const lines = Array.from(rotator.querySelectorAll<HTMLElement>('.hero-line'));
const reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
if (!reduced && lines.length > 1) {
let index = 0;
let paused = false;
const pause = () => { paused = true; };
const resume = () => { paused = false; };
rotator.addEventListener('mouseenter', pause);
rotator.addEventListener('mouseleave', resume);
rotator.addEventListener('focusin', pause);
rotator.addEventListener('focusout', resume);
setInterval(() => {
if (paused) return;
lines[index].removeAttribute('data-active');
lines[index].setAttribute('aria-hidden', 'true');
index = (index + 1) % lines.length;
lines[index].setAttribute('data-active', '');
lines[index].setAttribute('aria-hidden', 'false');
}, 10000);
}
}
</script>

View File

@@ -10,7 +10,7 @@ const steps: Step[] = [
{
n: '01',
title: 'Point us at your Camel app',
body: 'Drop it in, or connect one you already run. No code changes. No SDK. Nothing to rewrite.',
body: 'Drop it in, or connect one you already run. No code changes.',
},
{
n: '02',
@@ -20,7 +20,7 @@ const steps: Step[] = [
{
n: '03',
title: 'Watch it run',
body: 'Browse executions, tap live traffic, replay failed exchanges, follow flows across services. Nothing to instrument. Nothing to maintain.',
body: 'Browse executions, tap live traffic, replay failed exchanges, follow flows across services.',
},
];
---
@@ -29,11 +29,11 @@ const steps: Step[] = [
<div class="max-w-2xl mb-16">
<p class="text-cyan font-mono text-xs tracking-[0.25em] uppercase mb-4">For engineers</p>
<h2 class="text-hero font-bold text-text mb-4">How it works</h2>
<p class="text-text-muted text-lg">Three steps. No code changes. Nothing to maintain.</p>
<p class="text-text-muted text-lg">Three steps. Nothing to maintain.</p>
</div>
<ol class="grid md:grid-cols-3 gap-6 md:gap-8">
{steps.map((step) => (
<li class="relative rounded-lg border border-border bg-bg-elevated p-7">
<li class="relative rounded-lg border border-border bg-bg-elevated p-7 transition-all duration-200 ease-out hover:-translate-y-0.5 hover:border-accent/40 hover:shadow-[0_12px_32px_-12px_rgba(240,180,41,0.18)]">
<div class="font-mono text-accent text-sm tracking-wider mb-3">{step.n}</div>
<h3 class="text-lg font-bold text-text mb-3">{step.title}</h3>
<p class="text-text-muted leading-relaxed mb-4">{step.body}</p>

View File

@@ -21,27 +21,13 @@ const tiers: Tier[] = [
cta: 'Start free trial',
},
{
name: 'MID',
name: 'Starter',
price: '20 € /mo',
sub: '2 environments · 10 apps · 7-day retention',
href: auth.signUpUrl,
cta: 'Start on MID',
cta: 'Start on Starter',
highlight: true,
},
{
name: 'HIGH',
price: 'Contact',
sub: 'Unlimited envs · 50 apps · 90-day retention · Debugger, Replay',
href: auth.salesMailto,
cta: 'Talk to sales',
},
{
name: 'BUSINESS',
price: 'Contact',
sub: 'Unlimited everything · 365-day retention · all features',
href: auth.salesMailto,
cta: 'Talk to sales',
},
];
---
<section class="border-b border-border">
@@ -51,14 +37,24 @@ const tiers: Tier[] = [
<h2 class="text-hero font-bold text-text mb-4">Start free. Grow when you need to.</h2>
<p class="text-text-muted text-lg">
No credit card. No sales call. Just a working trial in ten minutes.
<a href="/pricing" class="text-accent hover:underline">See full comparison →</a>
</p>
</div>
<div class="grid md:grid-cols-2 lg:grid-cols-4 gap-5">
<div class="grid md:grid-cols-2 gap-5 lg:items-stretch max-w-3xl">
{tiers.map((tier) => (
<div class={`rounded-lg border bg-bg-elevated p-6 flex flex-col ${tier.highlight ? 'border-accent' : 'border-border'}`}>
<div
class={`relative rounded-lg bg-bg-elevated p-6 flex flex-col transition-all duration-200 ease-out hover:-translate-y-0.5
${tier.highlight
? 'ring-2 ring-accent shadow-[0_20px_50px_-20px_rgba(240,180,41,0.35)] md:-translate-y-2 md:pt-8 md:pb-7'
: 'border border-border hover:border-accent/40 hover:shadow-[0_12px_32px_-12px_rgba(240,180,41,0.18)]'}`}
>
{tier.highlight && (
<span class="absolute -top-3 left-6 inline-flex items-center gap-1.5 rounded-full bg-accent text-bg px-3 py-0.5 text-[11px] font-bold tracking-wide font-mono">
<span aria-hidden="true">★</span>
MOST POPULAR
</span>
)}
<div class="mb-4">
<div class="font-mono text-xs tracking-wider text-text-muted mb-2">{tier.name.toUpperCase()}</div>
<div class={`font-mono text-xs tracking-wider mb-2 ${tier.highlight ? 'text-accent' : 'text-text-muted'}`}>{tier.name.toUpperCase()}</div>
<div class="text-2xl font-bold text-text">{tier.price}</div>
</div>
<p class="text-text-muted text-sm leading-relaxed flex-grow mb-5">{tier.sub}</p>
@@ -74,5 +70,10 @@ const tiers: Tier[] = [
</div>
))}
</div>
<p class="mt-8 font-mono text-sm text-text-muted">
<a href="/pricing" class="text-cyan hover:text-accent transition-colors">
See all plans (Scale, Enterprise) →
</a>
</p>
</div>
</section>

View File

@@ -0,0 +1,38 @@
---
import { getAuthConfig } from '../../config/auth';
const auth = getAuthConfig();
// PENDING — must be filled in before publish:
// 1. [Founder Name] placeholder below.
// 2. The "ex-nJAMS" mention is gated on Hendrik's trademark review
// (same pattern as WhyUs.astro §10 caveat). If the review is not
// cleared by publish time, drop the " · ex-nJAMS" suffix from the
// attribution line.
const founderName = '[Founder Name]';
const designPartnerSubject = 'Design partner enquiry — Cameleer';
const designPartnerHref = `mailto:${auth.salesEmail}?subject=${encodeURIComponent(designPartnerSubject)}`;
---
<section class="border-b border-border">
<div class="max-w-content mx-auto px-6 py-16 md:py-20">
<div class="max-w-3xl mx-auto">
<p class="text-accent font-mono text-xs tracking-[0.25em] uppercase mb-4">
// Built by people who've done this before
</p>
<blockquote class="border-l-[3px] border-accent pl-5 max-w-[62ch]">
<p class="text-lg md:text-xl text-text italic leading-relaxed mb-3">
“We spent 15 years building integration monitoring for banks that couldnt afford downtime. Cameleer is what wed build today — purpose-built for Apache Camel, no retrofit.”
</p>
<footer class="text-sm font-mono text-text-muted">
— <span class="text-text">{founderName}</span>, co-founder · ex-nJAMS
</footer>
</blockquote>
<a
href={designPartnerHref}
class="inline-flex items-center gap-2 mt-7 font-mono text-sm text-cyan hover:text-accent transition-colors"
>
Apply to the design-partner program <span aria-hidden="true">→</span>
</a>
</div>
</div>
</section>

View File

@@ -0,0 +1,104 @@
---
import Lightbox from '../Lightbox.astro';
interface Callout {
title: string;
body: string;
}
const callouts: Callout[] = [
{
title: 'Cross-service correlation.',
body: 'Every exchange carries its correlation ID forward. One click jumps to what the downstream route did with the same message.',
},
{
title: 'Runtime detail, not guesswork.',
body: 'Circuit breaker tripped. Fallback path ran. Request tried backend:80. The pieces a 3 AM page actually needs — already captured.',
},
{
title: 'The whole story of a failure.',
body: 'Exception class, message, stack trace, headers, payload — all pinned to the exchange. No log-grepping tour.',
},
];
---
<section id="walkthrough" class="border-b border-border bg-bg">
<div class="max-w-content mx-auto px-6 py-24 md:py-32">
<div class="max-w-3xl mb-14 md:mb-20">
<p class="text-cyan font-mono text-xs tracking-[0.25em] uppercase mb-4">// When something breaks</p>
<h2 class="text-hero font-bold text-text mb-5">
The 3 AM page. With and without Cameleer.
</h2>
<p class="text-lg text-text-muted leading-relaxed">
Same Camel app. Same failed exchange. Different night.
</p>
</div>
<div class="grid md:grid-cols-2 gap-6 md:gap-8 items-stretch">
<div class="without-card relative rounded-lg border border-dashed border-border-strong bg-bg overflow-hidden">
<div class="px-5 pt-5 pb-3 font-mono text-[11px] tracking-[0.2em] uppercase text-text-faint border-b border-border">
Without Cameleer · 03:12 AM
</div>
<pre class="font-mono text-[13px] leading-[1.65] text-text-muted px-5 py-5 overflow-x-auto whitespace-pre"><code><span class="text-text">$</span> kubectl logs camel-router-7d4f8c
<span class="text-rose">ERROR</span> org.apache.camel.CamelExecutionException
at org.apache.camel.processor.SendProcessor.process
at org.apache.camel.processor.Pipeline.process
...
<span class="text-text">$</span> grep "order-842" *.log
router-3.log: <span class="text-accent">WARN</span> exchange order-842 stuck in saga-fulfillment
router-3.log: <span class="text-rose">ERROR</span> processor backend:80 → connect timeout
<span class="text-text">$</span> ssh prod-integration-3
prod-integration-3 $ kubectl logs ...
&gt; <span class="text-cyan">slack #integration-team</span>
"anyone know why order-842 is stuck??"
<span class="text-text-faint">[3 of 4 reactions, no answer]</span>
<span class="text-accent">~47 min later: someone wakes up an SRE.</span></code></pre>
</div>
<figure class="with-card relative rounded-lg border border-border-strong bg-bg-elevated overflow-hidden">
<div class="px-5 pt-5 pb-3 font-mono text-[11px] tracking-[0.2em] uppercase text-accent border-b border-border">
With Cameleer · 30 sec
</div>
<Lightbox
src="/product/error-detail.png"
alt="Cameleer Mission Control — failed exchange order-842 with full execution context"
width={1920}
height={945}
loading="lazy"
imgClass="block w-full h-auto"
/>
<div class="px-5 py-4 font-mono text-[13px] leading-[1.6] text-text-muted border-t border-border">
<span class="text-accent">▸</span> Open exchange <span class="text-text">order-842</span> → see the failure pinned → click <span class="text-text">Replay</span> after fix.
</div>
<div class="absolute inset-0 ring-1 ring-inset ring-accent/10 pointer-events-none rounded-lg"></div>
</figure>
</div>
<ul class="grid md:grid-cols-3 gap-6 md:gap-8 mt-14">
{callouts.map((c, i) => (
<li class="relative pl-10">
<span
class="absolute left-0 top-0 inline-flex items-center justify-center w-7 h-7 rounded-full border border-accent/40 bg-accent/10 text-accent font-mono text-xs"
aria-hidden="true"
>
{String(i + 1).padStart(2, '0')}
</span>
<h3 class="text-text font-semibold mb-1.5">{c.title}</h3>
<p class="text-text-muted leading-relaxed">{c.body}</p>
</li>
))}
</ul>
</div>
</section>
<style>
.with-card {
box-shadow:
0 1px 0 rgba(240, 180, 41, 0.08) inset,
0 30px 60px -25px rgba(0, 0, 0, 0.7),
0 12px 30px -12px rgba(0, 0, 0, 0.5);
}
</style>

View File

@@ -11,7 +11,7 @@
</h2>
</div>
<div class="grid md:grid-cols-2 gap-8 md:gap-12">
<div class="rounded-lg border border-border bg-bg-elevated p-8">
<div class="rounded-lg border border-border bg-bg-elevated p-8 transition-all duration-200 ease-out hover:-translate-y-0.5 hover:border-accent/40 hover:shadow-[0_12px_32px_-12px_rgba(240,180,41,0.18)]">
<h3 class="text-xl font-bold text-text mb-4">Generic APMs do not understand Camel. Cameleer does.</h3>
<p class="text-text-muted leading-relaxed mb-4">
Most monitoring tools see your app as a Java process and a pile of HTTP calls. Cameleer understands that you are running a Camel app — choices, splits, multicasts, error handlers, and every other EIP pattern as first-class citizens.
@@ -20,13 +20,13 @@
So when you ask "why did this exchange fail?", you get an answer, not a log tail. And you can reach back into a running app to replay a message, deep-trace a correlation ID, or toggle recording — observability that does things, not just shows them.
</p>
</div>
<div class="rounded-lg border border-border bg-bg-elevated p-8">
<h3 class="text-xl font-bold text-text mb-4">Built by people who know what 3 AM looks like.</h3>
<div class="rounded-lg border border-border bg-bg-elevated p-8 transition-all duration-200 ease-out hover:-translate-y-0.5 hover:border-accent/40 hover:shadow-[0_12px_32px_-12px_rgba(240,180,41,0.18)]">
<h3 class="text-xl font-bold text-text mb-4">Built by people who've operated integration in production for 15 years.</h3>
<p class="text-text-muted leading-relaxed mb-4">
We spent years building integration monitoring for banks, insurers, and logistics operators — the kind of shops where a stuck exchange at 3 AM means someone's phone is ringing. We know what integration teams actually need then, and what they never use.
We spent over a decade building integration monitoring for banks, insurers, and logistics operators — the kind of shops where a stuck exchange is a regulatory event, not just an inconvenience.
</p>
<p class="text-text-muted leading-relaxed">
Cameleer is what we would build today, purpose-built for Apache Camel. No legacy, no retrofit, no assumptions about a generic middleware platform.
Cameleer is what we'd build today, purpose-built for Apache Camel. No legacy, no retrofit, no assumptions about a generic middleware platform.
</p>
</div>
</div>

View File

@@ -4,57 +4,57 @@ import { resolveAuthConfig } from './auth';
describe('resolveAuthConfig', () => {
it('returns both URLs and sales email from env', () => {
const cfg = resolveAuthConfig({
PUBLIC_AUTH_SIGNIN_URL: 'https://auth.cameleer.io/sign-in',
PUBLIC_AUTH_SIGNUP_URL: 'https://auth.cameleer.io/sign-in?first_screen=register',
PUBLIC_AUTH_SIGNIN_URL: 'https://app.cameleer.io/sign-in',
PUBLIC_AUTH_SIGNUP_URL: 'https://app.cameleer.io/sign-in?first_screen=register',
PUBLIC_SALES_EMAIL: 'sales@cameleer.io',
});
expect(cfg.signInUrl).toBe('https://auth.cameleer.io/sign-in');
expect(cfg.signUpUrl).toBe('https://auth.cameleer.io/sign-in?first_screen=register');
expect(cfg.signInUrl).toBe('https://app.cameleer.io/sign-in');
expect(cfg.signUpUrl).toBe('https://app.cameleer.io/sign-in?first_screen=register');
expect(cfg.salesEmail).toBe('sales@cameleer.io');
});
it('throws if PUBLIC_AUTH_SIGNIN_URL is missing', () => {
expect(() => resolveAuthConfig({
PUBLIC_AUTH_SIGNUP_URL: 'https://auth.cameleer.io/sign-in?first_screen=register',
PUBLIC_AUTH_SIGNUP_URL: 'https://app.cameleer.io/sign-in?first_screen=register',
PUBLIC_SALES_EMAIL: 'sales@cameleer.io',
})).toThrow(/PUBLIC_AUTH_SIGNIN_URL/);
});
it('throws if a URL is not https', () => {
expect(() => resolveAuthConfig({
PUBLIC_AUTH_SIGNIN_URL: 'http://auth.cameleer.io/sign-in',
PUBLIC_AUTH_SIGNUP_URL: 'https://auth.cameleer.io/sign-in?first_screen=register',
PUBLIC_AUTH_SIGNIN_URL: 'http://app.cameleer.io/sign-in',
PUBLIC_AUTH_SIGNUP_URL: 'https://app.cameleer.io/sign-in?first_screen=register',
PUBLIC_SALES_EMAIL: 'sales@cameleer.io',
})).toThrow(/must be https/);
});
it('throws if sales email is not a valid mailto target', () => {
expect(() => resolveAuthConfig({
PUBLIC_AUTH_SIGNIN_URL: 'https://auth.cameleer.io/sign-in',
PUBLIC_AUTH_SIGNUP_URL: 'https://auth.cameleer.io/sign-in?first_screen=register',
PUBLIC_AUTH_SIGNIN_URL: 'https://app.cameleer.io/sign-in',
PUBLIC_AUTH_SIGNUP_URL: 'https://app.cameleer.io/sign-in?first_screen=register',
PUBLIC_SALES_EMAIL: 'not-an-email',
})).toThrow(/PUBLIC_SALES_EMAIL/);
});
it('throws if PUBLIC_AUTH_SIGNUP_URL is missing', () => {
expect(() => resolveAuthConfig({
PUBLIC_AUTH_SIGNIN_URL: 'https://auth.cameleer.io/sign-in',
PUBLIC_AUTH_SIGNIN_URL: 'https://app.cameleer.io/sign-in',
PUBLIC_SALES_EMAIL: 'sales@cameleer.io',
})).toThrow(/PUBLIC_AUTH_SIGNUP_URL/);
});
it('throws if PUBLIC_AUTH_SIGNUP_URL is not https', () => {
expect(() => resolveAuthConfig({
PUBLIC_AUTH_SIGNIN_URL: 'https://auth.cameleer.io/sign-in',
PUBLIC_AUTH_SIGNUP_URL: 'http://auth.cameleer.io/sign-in?first_screen=register',
PUBLIC_AUTH_SIGNIN_URL: 'https://app.cameleer.io/sign-in',
PUBLIC_AUTH_SIGNUP_URL: 'http://app.cameleer.io/sign-in?first_screen=register',
PUBLIC_SALES_EMAIL: 'sales@cameleer.io',
})).toThrow(/must be https/);
});
it('exposes signUpUrl distinct from signInUrl', () => {
const cfg = resolveAuthConfig({
PUBLIC_AUTH_SIGNIN_URL: 'https://auth.cameleer.io/sign-in',
PUBLIC_AUTH_SIGNUP_URL: 'https://auth.cameleer.io/sign-in?first_screen=register',
PUBLIC_AUTH_SIGNIN_URL: 'https://app.cameleer.io/sign-in',
PUBLIC_AUTH_SIGNUP_URL: 'https://app.cameleer.io/sign-in?first_screen=register',
PUBLIC_SALES_EMAIL: 'sales@cameleer.io',
});
expect(cfg.signUpUrl).not.toBe(cfg.signInUrl);

View File

@@ -31,7 +31,6 @@ const ogUrl = new URL(ogImage, Astro.site ?? 'https://www.cameleer.io').toString
<meta name="description" content={description} />
<link rel="canonical" href={canonical} />
<link rel="icon" type="image/svg+xml" href="/cameleer-logo.svg" />
<link rel="icon" type="image/png" sizes="32x32" href="/icons/cameleer-32.png" />
<link rel="apple-touch-icon" href="/icons/cameleer-180.png" />

View File

@@ -20,7 +20,7 @@ export function buildSecurityHeaders(): Record<string, string> {
"connect-src 'self'",
"frame-ancestors 'none'",
"base-uri 'self'",
// No forms on this marketing site today (all auth redirects go to auth.cameleer.io
// No forms on this marketing site today (all auth redirects go to app.cameleer.io
// as plain <a> navigations). If a future form is added, relax to 'self' or an allow-list.
"form-action 'none'",
"object-src 'none'",

View File

@@ -3,20 +3,22 @@ import BaseLayout from '../layouts/BaseLayout.astro';
import SiteHeader from '../components/SiteHeader.astro';
import SiteFooter from '../components/SiteFooter.astro';
import Hero from '../components/sections/Hero.astro';
import DualValueProps from '../components/sections/DualValueProps.astro';
import SocialProofStrip from '../components/sections/SocialProofStrip.astro';
import ThreeAmWalkthrough from '../components/sections/ThreeAmWalkthrough.astro';
import HowItWorks from '../components/sections/HowItWorks.astro';
import WhyUs from '../components/sections/WhyUs.astro';
import PricingTeaser from '../components/sections/PricingTeaser.astro';
import FinalCTA from '../components/sections/FinalCTA.astro';
---
<BaseLayout
title="Cameleer — Run Apache Camel without running Apache Camel"
description="The hosted home for your Camel integrations — with deep tracing, replay, and live control built in. Because you chose Camel to stay free, not to stay up all night."
title="Cameleer — Ship Camel integrations. Sleep through the night."
description="The hosted runtime and observability platform for Apache Camel. Auto-traced, replay-ready, cross-service correlated — so the 3 AM page becomes a 30-second answer."
>
<SiteHeader />
<main>
<Hero />
<DualValueProps />
<SocialProofStrip />
<ThreeAmWalkthrough />
<HowItWorks />
<WhyUs />
<PricingTeaser />

View File

@@ -33,7 +33,7 @@ const tiers: FullTier[] = [
cta: 'Start free trial',
},
{
name: 'MID',
name: 'Starter',
price: '20 €',
priceNote: 'per month',
envs: '2 environments',
@@ -41,28 +41,28 @@ const tiers: FullTier[] = [
retention: '7-day retention',
features: ['Everything in Trial', 'Data flow lineage', 'Cross-service correlation'],
href: auth.signUpUrl,
cta: 'Start on MID',
cta: 'Start on Starter',
highlight: true,
},
{
name: 'HIGH',
name: 'Scale',
price: 'Contact',
priceNote: 'sales',
envs: 'Unlimited environments',
apps: '50 apps',
retention: '90-day retention',
features: ['Everything in MID', 'Live debugger', 'Exchange replay', 'Live tap'],
features: ['Everything in Starter', 'Live debugger', 'Exchange replay', 'Live tap'],
href: auth.salesMailto,
cta: 'Talk to sales',
},
{
name: 'BUSINESS',
name: 'Enterprise',
price: 'Contact',
priceNote: 'sales',
envs: 'Unlimited environments',
apps: 'Unlimited apps',
retention: '365-day retention',
features: ['Everything in HIGH', 'Priority support', 'SLA', 'Dedicated success contact'],
features: ['Everything in Scale', 'Priority support', 'SLA', 'Dedicated success contact'],
href: auth.salesMailto,
cta: 'Talk to sales',
},
@@ -117,7 +117,7 @@ const tiers: FullTier[] = [
))}
</div>
<p class="text-center text-text-faint text-sm mt-10">
Prices in EUR, excluding VAT. Billed monthly. Annual billing available for HIGH and BUSINESS — <a href={auth.salesMailto} class="text-accent hover:underline">talk to sales</a>.
Prices in EUR, excluding VAT. Billed monthly. Annual billing available for Scale and Enterprise — <a href={auth.salesMailto} class="text-accent hover:underline">talk to sales</a>.
</p>
</div>
</section>

View File

@@ -67,7 +67,7 @@ const lastUpdated = '2026-04-24';
<section class="mb-10">
<h2 class="text-lg font-bold text-text mb-3">6. External links</h2>
<p class="text-text-muted leading-relaxed">
Sign-in and sign-up links on this site navigate you to <span class="font-mono text-accent">auth.cameleer.io</span> (Logto identity service) and subsequently <span class="font-mono text-accent">platform.cameleer.io</span>. Those services have their own privacy policies, which apply from the moment you arrive there.
Sign-in and sign-up links on this site navigate you to <span class="font-mono text-accent">app.cameleer.io</span> (the Cameleer app, where authentication is handled by Logto). That service has its own privacy policy, which applies from the moment you arrive there.
</p>
</section>

View File

@@ -18,6 +18,13 @@
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-rendering: optimizeLegibility;
scroll-behavior: smooth;
}
@media (prefers-reduced-motion: reduce) {
html {
scroll-behavior: auto;
}
}
body {
@apply min-h-screen;