Compare commits
3 Commits
8dec3e792a
...
relaunch-2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
37897f07c3 | ||
|
|
fa12df8ec6 | ||
|
|
203e4bfb41 |
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -87,8 +87,7 @@ workflows read them via the `${{ secrets.* }}` context.
|
||||
|
||||
- [ ] Fill in `src/pages/imprint.astro` `operator` object with real legal details.
|
||||
- [ ] Fill in `operatorContact` in `src/pages/privacy.astro`.
|
||||
- [ ] Review the "Why us" / nJAMS wording in `src/components/sections/WhyUs.astro` for trademark safety.
|
||||
- [ ] Confirm MID-tier retention: spec says **7 days**; `cameleer-saas/HOWTO.md` says **30 days**. Reconcile one side or the other.
|
||||
- [ ] Confirm Starter-tier retention: spec says **7 days**; `cameleer-saas/HOWTO.md` says **30 days**. Reconcile one side or the other.
|
||||
|
||||
## 5. First deploy
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ The relaunch's H1 leans Manager-outcome on purpose — the IT Manager is the che
|
||||
|
||||
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.
|
||||
1. **Founder pedigree** — "15 years building integration monitoring for banks, insurers, and logistics operators." No prior-product name is used. The pedigree claim stands on the years and the customer-segment, not on a brand reference.
|
||||
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.
|
||||
@@ -118,14 +118,12 @@ Order is deliberate — see §11 for the rationale (proof-first arc: hero → wh
|
||||
- **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`
|
||||
- **Attribution** (~13px, muted, mono): `— [Founder Name], co-founder`
|
||||
- **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.
|
||||
- `[Founder Name]` is a placeholder. Must be filled in pre-publish.
|
||||
|
||||
**Design-partner CTA target**: built inline in `SocialProofStrip.astro` using `auth.salesEmail` (not `auth.salesMailto`, which has no subject helper):
|
||||
|
||||
@@ -372,15 +370,13 @@ A traditional SaaS layout (features → benefits → how → pricing) would put
|
||||
- [ ] `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.
|
||||
This is deliberately surfaced as a code-level TODO rather than a spec-level open question so the operator can't accidentally publish with the placeholder intact.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ const year = new Date().getFullYear();
|
||||
<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">
|
||||
<img
|
||||
src="/cameleer-logo.svg"
|
||||
src="/icons/cameleer-32.png"
|
||||
width="24"
|
||||
height="24"
|
||||
alt=""
|
||||
|
||||
@@ -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=""
|
||||
|
||||
@@ -22,7 +22,7 @@ const pins: Pin[] = [
|
||||
<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"
|
||||
src="/icons/cameleer-192.png"
|
||||
width="64"
|
||||
height="64"
|
||||
alt=""
|
||||
|
||||
@@ -3,12 +3,7 @@ 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.
|
||||
// PENDING — [Founder Name] placeholder must be filled in before publish.
|
||||
const founderName = '[Founder Name]';
|
||||
const designPartnerSubject = 'Design partner enquiry — Cameleer';
|
||||
const designPartnerHref = `mailto:${auth.salesEmail}?subject=${encodeURIComponent(designPartnerSubject)}`;
|
||||
@@ -24,7 +19,7 @@ const designPartnerHref = `mailto:${auth.salesEmail}?subject=${encodeURIComponen
|
||||
“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.”
|
||||
</p>
|
||||
<footer class="text-sm font-mono text-text-muted">
|
||||
— <span class="text-text">{founderName}</span>, co-founder · ex-nJAMS
|
||||
— <span class="text-text">{founderName}</span>, co-founder
|
||||
</footer>
|
||||
</blockquote>
|
||||
<a
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
---
|
||||
// Final nJAMS-legacy wording is subject to Hendrik's trademark review before go-live
|
||||
// (see docs/superpowers/specs/2026-04-24-cameleer-website-design.md §10).
|
||||
---
|
||||
<section class="border-b border-border">
|
||||
<div class="max-w-content mx-auto px-6 py-20 md:py-24">
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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" />
|
||||
|
||||
|
||||
@@ -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'",
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user