diff --git a/web/src/landing-copy.js b/web/src/landing-copy.js index 89f1ccf..0a0e55f 100644 --- a/web/src/landing-copy.js +++ b/web/src/landing-copy.js @@ -21,6 +21,16 @@ const COPY = { pi: { title: 'Machen Sie Ihren Raspberry Pi zur privaten Cloud', subtitle: 'Image aufspielen, einstecken, fertig. Nextcloud, Fotos und Passwort-Manager installieren Sie danach per Klick.' + }, + access: { + title: 'Ihr Server läuft, ist aber von außen nicht erreichbar?', + subtitle: 'DS-Lite, CGNAT oder eine wechselnde IP-Adresse: ohne öffentliche IP hilft auch keine Portfreigabe. Syncloud stellt die Verbindung über einen Relay her.', + points: [ + 'Erreichbar ohne Portfreigabe, auch hinter DS-Lite und CGNAT', + 'Eigene Adresse wie ihrname.syncloud.it, die IP-Wechsel automatisch nachzieht', + 'HTTPS-Zertifikat wird ausgestellt und selbstständig erneuert', + 'E-Mail-Versand über einen Relay, damit Ihre Apps nicht im Spam landen' + ] } } }, @@ -43,6 +53,16 @@ const COPY = { pi: { title: 'Turn your Raspberry Pi into a private cloud', subtitle: 'Write the image, plug it in, done. Nextcloud, photos and a password manager install with one click afterwards.' + }, + access: { + title: 'Your server runs, but you cannot reach it from outside', + subtitle: 'Behind CGNAT, DS-Lite or a changing IP there is no port to forward. Syncloud reaches your server through a relay instead.', + points: [ + 'Reachable with no port forwarding, even behind CGNAT', + 'An address like yourname.syncloud.it that follows your IP when it changes', + 'An HTTPS certificate, issued and renewed for you', + 'Outgoing mail through a relay, so your apps are not treated as spam' + ] } } } @@ -59,7 +79,7 @@ export function landingCopy (variant, language = DEFAULT_LANGUAGE) { cta: copy.cta, price: copy.price, shotAlt: copy.shotAlt, - points: copy.points, + points: chosen.points || copy.points, trust: copy.trust } } diff --git a/web/src/router/index.js b/web/src/router/index.js index 43084b9..7d72e6a 100644 --- a/web/src/router/index.js +++ b/web/src/router/index.js @@ -30,6 +30,18 @@ const routes = [ component: () => import('../views/Landing.vue'), meta: { variant: 'pi', language: 'de', noindex: true, bare: true } }, + { + path: '/en/remote-access', + name: 'LandingAccessEn', + component: () => import('../views/Landing.vue'), + meta: { variant: 'access', language: 'en', noindex: true, bare: true } + }, + { + path: '/de/remote-access', + name: 'LandingAccessDe', + component: () => import('../views/Landing.vue'), + meta: { variant: 'access', language: 'de', noindex: true, bare: true } + }, { path: '/:catchAll(.*)', redirect: '/' } ] diff --git a/web/tests/landing.test.js b/web/tests/landing.test.js index e2a4650..e97d96d 100644 --- a/web/tests/landing.test.js +++ b/web/tests/landing.test.js @@ -115,3 +115,31 @@ describe('German landing pages', () => { } }) }) + +describe('the remote access variant', () => { + it('leads with not being reachable, not with what the product is', () => { + const wrapper = landing('access', 'de') + expect(wrapper.get('[data-testid="landing-title"]').text()) + .toBe(landingCopy('access', 'de').title) + expect(wrapper.get('[data-testid="landing-subtitle"]').text()).toContain('CGNAT') + }) + + it('answers the access problem rather than listing apps', () => { + for (const language of LANGUAGES) { + const copy = landingCopy('access', language) + const points = copy.points.join(' ').toLowerCase() + expect(points, language).not.toContain('40') + expect(points, language).toMatch(/relay|port|cgnat/) + expect(copy.points, language).not.toEqual(landingCopy('cloud', language).points) + } + }) + + it('keeps the shared price and call to action', () => { + for (const language of LANGUAGES) { + expect(landingCopy('access', language).price) + .toBe(landingCopy('cloud', language).price) + expect(landingCopy('access', language).cta) + .toBe(landingCopy('cloud', language).cta) + } + }) +})