diff --git a/app/integrations/angular/page.tsx b/app/integrations/angular/page.tsx new file mode 100644 index 0000000..e8833cb --- /dev/null +++ b/app/integrations/angular/page.tsx @@ -0,0 +1,63 @@ +'use client' + +import { IntegrationGuide } from '@/components/IntegrationGuide' +import { CodeBlock } from '@/components/CodeBlock' +import { getIntegration } from '@/lib/integrations' +import { notFound } from 'next/navigation' + +export default function AngularIntegrationPage() { + const integration = getIntegration('angular') + if (!integration) return notFound() + + return ( + +

+ Add Pulse to your Angular application by placing the script in your index.html or by using the Angular CLI's built-in scripts array. +

+ +
+ +

Method 1: index.html (Recommended)

+

+ Add the Pulse script tag directly to the <head> section of your src/index.html. +

+ + +{` + + + + My Angular App + + + + + + + + + +`} + + +

Method 2: angular.json Scripts Array

+

+ Alternatively, reference an external script in your angular.json build configuration. However, for analytics scripts that need defer and data-* attributes, Method 1 is simpler and recommended. +

+ +

Configuration Options

+ +
+ ) +} diff --git a/app/integrations/astro/page.tsx b/app/integrations/astro/page.tsx new file mode 100644 index 0000000..004c687 --- /dev/null +++ b/app/integrations/astro/page.tsx @@ -0,0 +1,60 @@ +'use client' + +import { IntegrationGuide } from '@/components/IntegrationGuide' +import { CodeBlock } from '@/components/CodeBlock' +import { getIntegration } from '@/lib/integrations' +import { notFound } from 'next/navigation' + +export default function AstroIntegrationPage() { + const integration = getIntegration('astro') + if (!integration) return notFound() + + return ( + +

+ Astro makes it easy to add third-party scripts. Drop the Pulse snippet into your base layout and you're done. +

+ +
+ +

Base Layout (Recommended)

+

+ Add the script to the <head> of your base layout file so it loads on every page. +

+ + +{`--- +// Base layout used by all pages +--- + + + + + + + + + My Astro Site + + + + +`} + + +

Using Astro's Script Integration

+

+ You can also configure the script in your astro.config.mjs using the injectScript API of an Astro integration, but the layout approach above is simpler for most projects. +

+ +

Astro + View Transitions

+

+ If you use Astro's View Transitions, the Pulse script persists across navigations automatically since it is loaded in the <head> with defer. +

+
+ ) +} diff --git a/app/integrations/gatsby/page.tsx b/app/integrations/gatsby/page.tsx new file mode 100644 index 0000000..5191b88 --- /dev/null +++ b/app/integrations/gatsby/page.tsx @@ -0,0 +1,65 @@ +'use client' + +import { IntegrationGuide } from '@/components/IntegrationGuide' +import { CodeBlock } from '@/components/CodeBlock' +import { getIntegration } from '@/lib/integrations' +import { notFound } from 'next/navigation' + +export default function GatsbyIntegrationPage() { + const integration = getIntegration('gatsby') + if (!integration) return notFound() + + return ( + +

+ Add Pulse to your Gatsby site using the gatsby-ssr API or the Gatsby Head API. +

+ +
+ +

Method 1: gatsby-ssr.js (Recommended)

+

+ Use the onRenderBody API to inject the script into every page's <head>. +

+ + +{`import React from "react" + +export const onRenderBody = ({ setHeadComponents }) => { + setHeadComponents([ + `} + + +
    +
  1. Click Save.
  2. +
+ +

Theme-Level Integration (Alternative)

+

+ If you prefer, you can also add the script directly to your Ghost theme's default.hbs file, just before the closing </head> tag. This approach requires re-uploading the theme whenever you make changes. +

+ +

Important Notes

+ +
+ ) +} diff --git a/app/integrations/hugo/page.tsx b/app/integrations/hugo/page.tsx new file mode 100644 index 0000000..2aa0b6f --- /dev/null +++ b/app/integrations/hugo/page.tsx @@ -0,0 +1,65 @@ +'use client' + +import { IntegrationGuide } from '@/components/IntegrationGuide' +import { CodeBlock } from '@/components/CodeBlock' +import { getIntegration } from '@/lib/integrations' +import { notFound } from 'next/navigation' + +export default function HugoIntegrationPage() { + const integration = getIntegration('hugo') + if (!integration) return notFound() + + return ( + +

+ Add Pulse to your Hugo site by placing the script in a partial or directly in your base template. +

+ +
+ +

Method 1: Partial (Recommended)

+

+ Create an analytics partial and include it in your base template's <head>. +

+ + +{`{{ if not .Site.IsServer }} + +{{ end }}`} + + +

+ Then include the partial in your baseof.html: +

+ + +{` + + + + + {{ .Title }} + + {{ partial "analytics.html" . }} + + + {{ block "main" . }}{{ end }} + +`} + + +

Method 2: Direct Insertion

+

+ If you prefer, add the script tag directly to the <head> of your baseof.html without creating a partial. +

+ +

+ The if not .Site.IsServer guard ensures the script is excluded during local development with hugo server. +

+
+ ) +} diff --git a/app/integrations/nextjs/page.tsx b/app/integrations/nextjs/page.tsx index 270e73a..5704ab3 100644 --- a/app/integrations/nextjs/page.tsx +++ b/app/integrations/nextjs/page.tsx @@ -1,59 +1,28 @@ 'use client' -import Link from 'next/link' -import { ArrowLeftIcon } from '@ciphera-net/ui' +import { IntegrationGuide } from '@/components/IntegrationGuide' +import { CodeBlock } from '@/components/CodeBlock' +import { getIntegration } from '@/lib/integrations' +import { notFound } from 'next/navigation' export default function NextJsIntegrationPage() { + const integration = getIntegration('nextjs') + if (!integration) return notFound() + return ( -
- {/* * --- ATMOSPHERE (Background) --- */} -
-
-
-
-
+ +

+ The best way to add Pulse to your Next.js application is using the built-in next/script component. +

-
- - - Back to Integrations - +
-
-
- - - -
-

- Next.js Integration -

-
+

Using App Router (Recommended)

+

+ Add the script to your root layout file (usually app/layout.tsx or app/layout.js). +

-
-

- The best way to add Pulse to your Next.js application is using the built-in next/script component. -

- -
- -

Using App Router (Recommended)

-

- Add the script to your root layout file (usually app/layout.tsx or app/layout.js). -

- -
-
- app/layout.tsx -
-
-
+      
 {`import Script from 'next/script'
 
 export default function RootLayout({
@@ -75,21 +44,14 @@ export default function RootLayout({
     
   )
 }`}
-              
-
-
+ -

Using Pages Router

-

- If you are using the older Pages Router, add the script to your custom _app.tsx or _document.tsx. -

+

Using Pages Router

+

+ If you are using the older Pages Router, add the script to your custom _app.tsx or _document.tsx. +

-
-
- pages/_app.tsx -
-
-
+      
 {`import Script from 'next/script'
 import type { AppProps } from 'next/app'
 
@@ -106,24 +68,20 @@ export default function App({ Component, pageProps }: AppProps) {
     
   )
 }`}
-              
-
-
+ -

Configuration Options

-
    -
  • - data-domain: The domain name you added to your Pulse dashboard (e.g., example.com). -
  • -
  • - src: The URL of our tracking script: https://pulse.ciphera.net/script.js -
  • -
  • - strategy: We recommend afterInteractive to ensure it loads quickly without blocking hydration. -
  • -
-
-
-
+

Configuration Options

+
    +
  • + data-domain: The domain name you added to your Pulse dashboard (e.g., example.com). +
  • +
  • + src: The URL of our tracking script: https://pulse.ciphera.net/script.js +
  • +
  • + strategy: We recommend afterInteractive to ensure it loads quickly without blocking hydration. +
  • +
+ ) } diff --git a/app/integrations/nuxt/page.tsx b/app/integrations/nuxt/page.tsx new file mode 100644 index 0000000..2b5241c --- /dev/null +++ b/app/integrations/nuxt/page.tsx @@ -0,0 +1,71 @@ +'use client' + +import { IntegrationGuide } from '@/components/IntegrationGuide' +import { CodeBlock } from '@/components/CodeBlock' +import { getIntegration } from '@/lib/integrations' +import { notFound } from 'next/navigation' + +export default function NuxtIntegrationPage() { + const integration = getIntegration('nuxt') + if (!integration) return notFound() + + return ( + +

+ Configure Pulse in your Nuxt application by adding the script to your nuxt.config file. +

+ +
+ +

Nuxt 3 (Recommended)

+

+ Add the script to the app.head section of your nuxt.config.ts. +

+ + +{`export default defineNuxtConfig({ + app: { + head: { + script: [ + { + src: 'https://pulse.ciphera.net/script.js', + defer: true, + 'data-domain': 'your-site.com' + } + ] + } + } +})`} + + +

Nuxt 2

+

+ For Nuxt 2 projects, add the script to the head object in nuxt.config.js. +

+ + +{`export default { + head: { + script: [ + { + src: 'https://pulse.ciphera.net/script.js', + defer: true, + 'data-domain': 'your-site.com' + } + ] + } +}`} + + +

Configuration Options

+
    +
  • + data-domain: The domain name you added to your Pulse dashboard (e.g., example.com). +
  • +
  • + defer: Ensures the script loads without blocking rendering. +
  • +
+
+ ) +} diff --git a/app/integrations/page.tsx b/app/integrations/page.tsx index dabb37e..7d9df36 100644 --- a/app/integrations/page.tsx +++ b/app/integrations/page.tsx @@ -3,60 +3,18 @@ import Link from 'next/link' import { motion } from 'framer-motion' import { ArrowRightIcon } from '@ciphera-net/ui' - -const integrations = [ - { - id: 'nextjs', - name: 'Next.js', - description: 'Add privacy-friendly analytics to your Next.js application using next/script.', - icon: ( - - - - ), - }, - { - id: 'react', - name: 'React', - description: 'Integrate Pulse with any React SPA (Create React App, Vite, etc).', - icon: ( - - - - - ), - }, - { - id: 'vue', - name: 'Vue.js', - description: 'Simple setup for Vue 2 and Vue 3 applications.', - icon: ( - - - - - ), - }, - { - id: 'wordpress', - name: 'WordPress', - description: 'Add the tracking script to your WordPress header or use a plugin.', - icon: ( - - - - ), - }, -] +import { getGroupedIntegrations } from '@/lib/integrations' export default function IntegrationsPage() { + const groups = getGroupedIntegrations() + return (
{/* * --- ATMOSPHERE (Background) --- */}
-
@@ -77,61 +35,75 @@ export default function IntegrationsPage() {

-
- {integrations.map((integration, i) => ( - ( +
+ - -
-
- {integration.icon} -
- -
- -

- {integration.name} -

-

- {integration.description} -

- - View Guide - - - - ))} - - {/* * Request Integration Card */} - + +
+ {group.items.map((integration, i) => ( + + +
+
+ {integration.icon} +
+ +
+ +

+ {integration.name} +

+

+ {integration.description} +

+ + View Guide + + +
+ ))} +
+
+ ))} + + {/* * Request Integration Card */} + +

+ Missing something? +

+

+ Let us know which integration you'd like to see next. +

+ -

- Missing something? -

-

- Let us know which integration you'd like to see next. -

-
- Request Integration - -
-
+ Request Integration + +
) diff --git a/app/integrations/react/page.tsx b/app/integrations/react/page.tsx index d8d07cb..bee5df9 100644 --- a/app/integrations/react/page.tsx +++ b/app/integrations/react/page.tsx @@ -1,94 +1,55 @@ 'use client' -import Link from 'next/link' -import { ArrowLeftIcon } from '@ciphera-net/ui' +import { IntegrationGuide } from '@/components/IntegrationGuide' +import { CodeBlock } from '@/components/CodeBlock' +import { getIntegration } from '@/lib/integrations' +import { notFound } from 'next/navigation' export default function ReactIntegrationPage() { + const integration = getIntegration('react') + if (!integration) return notFound() + return ( -
- {/* * --- ATMOSPHERE (Background) --- */} -
-
-
-
-
+ +

+ For standard React SPAs (Create React App, Vite, etc.), you can simply add the script tag to your index.html. +

-
- - - Back to Integrations - +
-
-
- - - - -
-

- React Integration -

-
+

Method 1: index.html (Recommended)

+

+ The simplest way is to add the script tag directly to the <head> of your index.html file. +

-
-

- For standard React SPAs (Create React App, Vite, etc.), you can simply add the script tag to your index.html. -

- -
- -

Method 1: index.html (Recommended)

-

- The simplest way is to add the script tag directly to the <head> of your index.html file. -

- -
-
- public/index.html -
-
-
+      
 {`
 
   
     
     
-    
+
     
-    
-    
+
     My React App
   
   
     
`} -
-
-
+ -

Method 2: Programmatic Injection

-

- If you need to load the script dynamically (e.g., only in production), you can use a useEffect hook in your main App component. -

+

Method 2: Programmatic Injection

+

+ If you need to load the script dynamically (e.g., only in production), you can use a useEffect hook in your main App component. +

-
-
- src/App.tsx -
-
-
+      
 {`import { useEffect } from 'react'
 
 function App() {
@@ -109,11 +70,7 @@ function App() {
     
) }`} - -
-
-
-
-
+ + ) } diff --git a/app/integrations/remix/page.tsx b/app/integrations/remix/page.tsx new file mode 100644 index 0000000..a7d7bcc --- /dev/null +++ b/app/integrations/remix/page.tsx @@ -0,0 +1,71 @@ +'use client' + +import { IntegrationGuide } from '@/components/IntegrationGuide' +import { CodeBlock } from '@/components/CodeBlock' +import { getIntegration } from '@/lib/integrations' +import { notFound } from 'next/navigation' + +export default function RemixIntegrationPage() { + const integration = getIntegration('remix') + if (!integration) return notFound() + + return ( + +

+ Add Pulse to your Remix application by placing the script tag in your root route's <head>. +

+ +
+ +

Root Route (Recommended)

+

+ In Remix, the app/root.tsx file controls the HTML shell. Add the Pulse script inside the <head> section. +

+ + +{`import { + Links, + Meta, + Outlet, + Scripts, + ScrollRestoration, +} from "@remix-run/react" + +export default function App() { + return ( + + + + + + + + {/* Pulse Analytics */} + `} + + +

Method 2: Custom Pixels (Shopify Plus)

+

+ If you are on Shopify Plus, you can also use Customer Events > Custom Pixels to add the script. Go to Settings > Customer events and create a new custom pixel. +

+ +

Important Notes

+
    +
  • + data-domain: Use your custom domain (e.g., shop.example.com) if you have one, or your .myshopify.com domain. +
  • +
  • + Pulse does not use cookies and is fully GDPR-compliant — no cookie banner changes needed. +
  • +
+
+ ) +} diff --git a/app/integrations/squarespace/page.tsx b/app/integrations/squarespace/page.tsx new file mode 100644 index 0000000..a428a61 --- /dev/null +++ b/app/integrations/squarespace/page.tsx @@ -0,0 +1,52 @@ +'use client' + +import { IntegrationGuide } from '@/components/IntegrationGuide' +import { CodeBlock } from '@/components/CodeBlock' +import { getIntegration } from '@/lib/integrations' +import { notFound } from 'next/navigation' + +export default function SquarespaceIntegrationPage() { + const integration = getIntegration('squarespace') + if (!integration) return notFound() + + return ( + +

+ Add Pulse to your Squarespace site using the built-in Code Injection feature — no plugins needed. +

+ +
+ +

Code Injection (Recommended)

+
    +
  1. In your Squarespace dashboard, go to Settings > Developer Tools > Code Injection.
  2. +
  3. In the Header field, paste the following snippet:
  4. +
+ + +{``} + + +
    +
  1. Click Save.
  2. +
+ +

Important Notes

+
    +
  • + Code Injection is available on Squarespace Business and Commerce plans. +
  • +
  • + data-domain: Use your custom domain (e.g., example.com) rather than the .squarespace.com subdomain. +
  • +
  • + Pulse is cookie-free, so you do not need to update your Squarespace cookie banner settings. +
  • +
+
+ ) +} diff --git a/app/integrations/svelte/page.tsx b/app/integrations/svelte/page.tsx new file mode 100644 index 0000000..b123581 --- /dev/null +++ b/app/integrations/svelte/page.tsx @@ -0,0 +1,70 @@ +'use client' + +import { IntegrationGuide } from '@/components/IntegrationGuide' +import { CodeBlock } from '@/components/CodeBlock' +import { getIntegration } from '@/lib/integrations' +import { notFound } from 'next/navigation' + +export default function SvelteIntegrationPage() { + const integration = getIntegration('svelte') + if (!integration) return notFound() + + return ( + +

+ Integrating Pulse with Svelte or SvelteKit takes less than a minute. Just add the script tag to your HTML entry point. +

+ +
+ +

Svelte (Vite)

+

+ For a standard Svelte project scaffolded with Vite, add the script to the <head> of your index.html. +

+ + +{` + + + + + + + + + My Svelte App + + +
+ + +`} +
+ +

SvelteKit

+

+ In SvelteKit, add the script to your root layout's <svelte:head> block so it loads on every page. +

+ + +{` + + + +`} + + +

+ Alternatively, you can add the script to src/app.html directly in the <head> section. +

+
+ ) +} diff --git a/app/integrations/vue/page.tsx b/app/integrations/vue/page.tsx index f5bd66e..c9278df 100644 --- a/app/integrations/vue/page.tsx +++ b/app/integrations/vue/page.tsx @@ -1,73 +1,41 @@ 'use client' -import Link from 'next/link' -import { ArrowLeftIcon } from '@ciphera-net/ui' +import { IntegrationGuide } from '@/components/IntegrationGuide' +import { CodeBlock } from '@/components/CodeBlock' +import { getIntegration } from '@/lib/integrations' +import { notFound } from 'next/navigation' export default function VueIntegrationPage() { + const integration = getIntegration('vue') + if (!integration) return notFound() + return ( -
- {/* * --- ATMOSPHERE (Background) --- */} -
-
-
-
-
+ +

+ Integrating Pulse with Vue.js is straightforward. Add the script to your index.html file. +

-
- - - Back to Integrations - +
-
-
- - - - -
-

- Vue.js Integration -

-
+

index.html (Vue CLI & Vite)

+

+ Add the script tag to the <head> section of your index.html file. This works for both Vue 2 and Vue 3 projects created with Vue CLI or Vite. +

-
-

- Integrating Pulse with Vue.js is straightforward. You can add the script to your index.html file. -

- -
- -

Method 1: index.html (Recommended)

-

- Add the script tag to the <head> section of your index.html file. This works for both Vue 2 and Vue 3 projects created with Vue CLI or Vite. -

- -
-
- index.html -
-
-
+      
 {`
 
   
     
     
-    
+
     
-    
-    
+
     My Vue App
   
   
@@ -75,39 +43,11 @@ export default function VueIntegrationPage() {
     
   
 `}
-              
-
-
+ -

Method 2: Nuxt.js

-

- For Nuxt.js applications, you should add the script to your nuxt.config.js or nuxt.config.ts file. -

- -
-
- nuxt.config.ts -
-
-
-{`export default defineNuxtConfig({
-  app: {
-    head: {
-      script: [
-        {
-          src: 'https://pulse.ciphera.net/script.js',
-          defer: true,
-          'data-domain': 'your-site.com'
-        }
-      ]
-    }
-  }
-})`}
-              
-
-
-
-
-
+

+ Looking for Nuxt.js? Check the dedicated Nuxt integration guide. +

+ ) } diff --git a/app/integrations/webflow/page.tsx b/app/integrations/webflow/page.tsx new file mode 100644 index 0000000..a6a3e93 --- /dev/null +++ b/app/integrations/webflow/page.tsx @@ -0,0 +1,55 @@ +'use client' + +import { IntegrationGuide } from '@/components/IntegrationGuide' +import { CodeBlock } from '@/components/CodeBlock' +import { getIntegration } from '@/lib/integrations' +import { notFound } from 'next/navigation' + +export default function WebflowIntegrationPage() { + const integration = getIntegration('webflow') + if (!integration) return notFound() + + return ( + +

+ Add Pulse to your Webflow site by pasting a single snippet into your project's custom code settings. +

+ +
+ +

Project-Level Custom Code (Recommended)

+
    +
  1. Open your Webflow project and go to Project Settings.
  2. +
  3. Navigate to the Custom Code tab.
  4. +
  5. In the Head Code section, paste the following snippet:
  6. +
+ + +{``} + + +
    +
  1. Click Save Changes and publish your site.
  2. +
+ +

Page-Level Custom Code

+

+ If you only want to track specific pages, you can add the script to individual page settings instead of the project-level settings. Go to the page's settings panel and paste the snippet in the Head Code section. +

+ +

Important Notes

+
    +
  • + Custom code requires a Webflow paid site plan (Basic or higher). +
  • +
  • + The script will not appear in the Webflow Designer preview — publish the site and view the live version to verify. +
  • +
+
+ ) +} diff --git a/app/integrations/wix/page.tsx b/app/integrations/wix/page.tsx new file mode 100644 index 0000000..db49e28 --- /dev/null +++ b/app/integrations/wix/page.tsx @@ -0,0 +1,54 @@ +'use client' + +import { IntegrationGuide } from '@/components/IntegrationGuide' +import { CodeBlock } from '@/components/CodeBlock' +import { getIntegration } from '@/lib/integrations' +import { notFound } from 'next/navigation' + +export default function WixIntegrationPage() { + const integration = getIntegration('wix') + if (!integration) return notFound() + + return ( + +

+ Add Pulse to your Wix site using the Custom Code feature in your site settings. +

+ +
+ +

Custom Code (Recommended)

+
    +
  1. In your Wix dashboard, go to Settings > Custom Code (under “Advanced”).
  2. +
  3. Click + Add Custom Code.
  4. +
  5. Paste the following snippet:
  6. +
+ + +{``} + + +
    +
  1. Set the code to load in the Head of All pages.
  2. +
  3. Click Apply.
  4. +
+ +

Important Notes

+
    +
  • + Custom Code requires a Wix Premium plan with a connected domain. +
  • +
  • + data-domain: Use your connected custom domain, not the .wixsite.com subdomain. +
  • +
  • + Pulse is cookie-free and GDPR-compliant — no consent banner changes are needed. +
  • +
+
+ ) +} diff --git a/app/integrations/wordpress/page.tsx b/app/integrations/wordpress/page.tsx index 873a2cf..c8d9a6b 100644 --- a/app/integrations/wordpress/page.tsx +++ b/app/integrations/wordpress/page.tsx @@ -1,81 +1,46 @@ 'use client' -import Link from 'next/link' -import { ArrowLeftIcon } from '@ciphera-net/ui' +import { IntegrationGuide } from '@/components/IntegrationGuide' +import { CodeBlock } from '@/components/CodeBlock' +import { getIntegration } from '@/lib/integrations' +import { notFound } from 'next/navigation' export default function WordPressIntegrationPage() { + const integration = getIntegration('wordpress') + if (!integration) return notFound() + return ( -
- {/* * --- ATMOSPHERE (Background) --- */} -
-
-
-
-
+ +

+ You can add Pulse to your WordPress site without installing any heavy plugins, or by using a simple code snippet plugin. +

-
- - - Back to Integrations - +
-
-
- - - -
-

- WordPress Integration -

-
+

Method 1: Using a Plugin (Easiest)

+
    +
  1. Install a plugin like “Insert Headers and Footers” (WPCode).
  2. +
  3. Go to the plugin settings and find the “Scripts in Header” section.
  4. +
  5. Paste the following code snippet:
  6. +
-
-

- You can add Pulse to your WordPress site without installing any heavy plugins, or by using a simple code snippet plugin. -

- -
- -

Method 1: Using a Plugin (Easiest)

-
    -
  1. Install a plugin like "Insert Headers and Footers" (WPCode).
  2. -
  3. Go to the plugin settings and find the "Scripts in Header" section.
  4. -
  5. Paste the following code snippet:
  6. -
- -
-
- Header Script -
-
-
-{``}
-              
-
-
+ -

Method 2: Edit Theme Files (Advanced)

-

- If you are comfortable editing your theme files, you can add the script directly to your header.php file. -

-
    -
  1. Go to Appearance > Theme File Editor.
  2. -
  3. Select header.php from the right sidebar.
  4. -
  5. Paste the script tag just before the closing </head> tag.
  6. -
-
-
-
+

Method 2: Edit Theme Files (Advanced)

+

+ If you are comfortable editing your theme files, you can add the script directly to your header.php file. +

+
    +
  1. Go to Appearance > Theme File Editor.
  2. +
  3. Select header.php from the right sidebar.
  4. +
  5. Paste the script tag just before the closing </head> tag.
  6. +
+ ) } diff --git a/components/CodeBlock.tsx b/components/CodeBlock.tsx new file mode 100644 index 0000000..48f4c81 --- /dev/null +++ b/components/CodeBlock.tsx @@ -0,0 +1,30 @@ +'use client' + +/** + * @file Reusable code block component for integration guide pages. + * + * Renders a VS-Code-style code block with a filename tab header. + */ + +interface CodeBlockProps { + /** Filename displayed in the tab header */ + filename: string + /** The code string to render inside the block */ + children: string +} + +/** + * Renders a dark-themed code snippet with a filename tab. + */ +export function CodeBlock({ filename, children }: CodeBlockProps) { + return ( +
+
+ {filename} +
+
+
{children}
+
+
+ ) +} diff --git a/components/IntegrationGuide.tsx b/components/IntegrationGuide.tsx new file mode 100644 index 0000000..236fb8e --- /dev/null +++ b/components/IntegrationGuide.tsx @@ -0,0 +1,69 @@ +'use client' + +/** + * @file Shared layout component for individual integration guide pages. + * + * Provides the background atmosphere, back-link, header (logo + title), + * and prose-styled content area used by every integration sub-page. + */ + +import Link from 'next/link' +import { ArrowLeftIcon } from '@ciphera-net/ui' +import { type ReactNode } from 'react' +import { type Integration } from '@/lib/integrations' + +interface IntegrationGuideProps { + /** Integration metadata (name, icon, etc.) */ + integration: Integration + /** Guide content rendered inside the prose area */ + children: ReactNode +} + +/** + * Renders the full-page layout for a single integration guide. + */ +export function IntegrationGuide({ integration, children }: IntegrationGuideProps) { + // * Scale the icon up for the detail-page header (w-10 h-10) + const headerIcon = ( +
+ {integration.icon} +
+ ) + + return ( +
+ {/* * --- ATMOSPHERE (Background) --- */} +
+
+
+
+
+ +
+ + + Back to Integrations + + +
+
+ {headerIcon} +
+

+ {integration.name} Integration +

+
+ +
+ {children} +
+
+
+ ) +} diff --git a/lib/integrations.tsx b/lib/integrations.tsx new file mode 100644 index 0000000..18aefd4 --- /dev/null +++ b/lib/integrations.tsx @@ -0,0 +1,268 @@ +/** + * @file Integration metadata and official SVG logos. + * + * ! SVG paths sourced from simple-icons (https://simpleicons.org). + * All icons use a 24×24 viewBox. + */ + +import { type ReactNode } from 'react' + +// * ─── Types ────────────────────────────────────────────────────────────────── + +export type IntegrationCategory = 'framework' | 'cms' | 'ssg' | 'platform' + +export interface Integration { + id: string + name: string + description: string + category: IntegrationCategory + /** Brand hex colour (with #) */ + brandColor: string + /** Whether the icon needs `dark:invert` (black-only logos) */ + invertInDark?: boolean + /** Official 24×24 SVG as a React node */ + icon: ReactNode +} + +// * ─── Category labels (for UI grouping) ────────────────────────────────────── + +export const categoryLabels: Record = { + framework: 'Frameworks', + cms: 'CMS & Blogging', + ssg: 'Static-Site Generators', + platform: 'Platforms & Builders', +} + +export const categoryOrder: IntegrationCategory[] = [ + 'framework', + 'platform', + 'ssg', + 'cms', +] + +// * ─── Integration registry ────────────────────────────────────────────────── + +export const integrations: Integration[] = [ + // * ── Frameworks ─────────────────────────────────────────────────────────── + { + id: 'nextjs', + name: 'Next.js', + description: 'Add privacy-friendly analytics to your Next.js application using next/script.', + category: 'framework', + brandColor: '#000000', + invertInDark: true, + icon: ( + + + + ), + }, + { + id: 'react', + name: 'React', + description: 'Integrate Pulse with any React SPA (Create React App, Vite, etc).', + category: 'framework', + brandColor: '#61DAFB', + icon: ( + + + + ), + }, + { + id: 'vue', + name: 'Vue.js', + description: 'Simple setup for Vue 2 and Vue 3 applications.', + category: 'framework', + brandColor: '#4FC08D', + icon: ( + + + + ), + }, + { + id: 'angular', + name: 'Angular', + description: 'Add Pulse analytics to your Angular application with a simple script tag.', + category: 'framework', + brandColor: '#0F0F11', + invertInDark: true, + icon: ( + + + + ), + }, + { + id: 'svelte', + name: 'Svelte', + description: 'Integrate Pulse with Svelte or SvelteKit in under a minute.', + category: 'framework', + brandColor: '#FF3E00', + icon: ( + + + + ), + }, + { + id: 'nuxt', + name: 'Nuxt', + description: 'Configure Pulse in your Nuxt application via nuxt.config.', + category: 'framework', + brandColor: '#00DC82', + icon: ( + + + + ), + }, + { + id: 'remix', + name: 'Remix', + description: 'Add Pulse to your Remix app via the root route loader.', + category: 'framework', + brandColor: '#000000', + invertInDark: true, + icon: ( + + + + ), + }, + { + id: 'astro', + name: 'Astro', + description: 'Integrate Pulse with your Astro site for lightning-fast analytics.', + category: 'framework', + brandColor: '#BC52EE', + icon: ( + + + + ), + }, + { + id: 'gatsby', + name: 'Gatsby', + description: 'Add Pulse to your Gatsby site via gatsby-ssr or a plugin.', + category: 'ssg', + brandColor: '#663399', + icon: ( + + + + ), + }, + + // * ── Static-Site Generators ──────────────────────────────────────────────── + { + id: 'hugo', + name: 'Hugo', + description: 'Drop the Pulse script into your Hugo partial or base template.', + category: 'ssg', + brandColor: '#FF4088', + icon: ( + + + + ), + }, + + // * ── CMS & Blogging ─────────────────────────────────────────────────────── + { + id: 'wordpress', + name: 'WordPress', + description: 'Add the tracking script to your WordPress header or use a plugin.', + category: 'cms', + brandColor: '#21759B', + icon: ( + + + + ), + }, + { + id: 'ghost', + name: 'Ghost', + description: 'Inject Pulse into your Ghost theme via Code Injection settings.', + category: 'cms', + brandColor: '#15171A', + invertInDark: true, + icon: ( + + + + ), + }, + + // * ── Platforms & Builders ────────────────────────────────────────────────── + { + id: 'shopify', + name: 'Shopify', + description: 'Add privacy-first analytics to your Shopify store via theme editor.', + category: 'platform', + brandColor: '#7AB55C', + icon: ( + + + + ), + }, + { + id: 'webflow', + name: 'Webflow', + description: 'Paste the Pulse snippet into your Webflow project custom code.', + category: 'platform', + brandColor: '#146EF5', + icon: ( + + + + ), + }, + { + id: 'squarespace', + name: 'Squarespace', + description: 'Add Pulse to Squarespace via the Code Injection panel.', + category: 'platform', + brandColor: '#000000', + invertInDark: true, + icon: ( + + + + ), + }, + { + id: 'wix', + name: 'Wix', + description: 'Add Pulse to your Wix site via the Custom Code settings.', + category: 'platform', + brandColor: '#0C6EFC', + icon: ( + + + + ), + }, +] + +// * ─── Helpers ──────────────────────────────────────────────────────────────── + +/** Retrieve a single integration by its route slug. */ +export function getIntegration(id: string): Integration | undefined { + return integrations.find((i) => i.id === id) +} + +/** Group integrations by category, preserving category ordering. */ +export function getGroupedIntegrations(): { category: IntegrationCategory; label: string; items: Integration[] }[] { + return categoryOrder + .map((cat) => ({ + category: cat, + label: categoryLabels[cat], + items: integrations.filter((i) => i.category === cat), + })) + .filter((group) => group.items.length > 0) +} diff --git a/public/Icon Padding left & right 192x192.png b/public/Icon Padding left & right 192x192.png new file mode 100644 index 0000000..c3b532c Binary files /dev/null and b/public/Icon Padding left & right 192x192.png differ diff --git a/public/Icon Padding left & right 512x512.png b/public/Icon Padding left & right 512x512.png new file mode 100644 index 0000000..a9bf4b0 Binary files /dev/null and b/public/Icon Padding left & right 512x512.png differ diff --git a/public/sw 2.js b/public/sw 2.js new file mode 100644 index 0000000..15a7685 --- /dev/null +++ b/public/sw 2.js @@ -0,0 +1 @@ +if(!self.define){let e,s={};const a=(a,n)=>(a=new URL(a+".js",n).href,s[a]||new Promise(s=>{if("document"in self){const e=document.createElement("script");e.src=a,e.onload=s,document.head.appendChild(e)}else e=a,importScripts(a),s()}).then(()=>{let e=s[a];if(!e)throw new Error(`Module ${a} didn’t register its module`);return e}));self.define=(n,c)=>{const i=e||("document"in self?document.currentScript.src:"")||location.href;if(s[i])return;let t={};const r=e=>a(e,i),d={module:{uri:i},exports:t,require:r};s[i]=Promise.all(n.map(e=>d[e]||r(e))).then(e=>(c(...e),t))}}define(["./workbox-f1770938"],function(e){"use strict";importScripts(),self.skipWaiting(),e.clientsClaim(),e.precacheAndRoute([{url:"/Icon Padding left & right 192x192.png",revision:"a9bae788a2253361a0e295deadbcc083"},{url:"/Icon Padding left & right 512x512.png",revision:"d2efb02d7b9a0a627387d334201e6ea7"},{url:"/_next/static/chunks/0e5ce63c-0d9b48ee367f70a2.js",revision:"0d9b48ee367f70a2"},{url:"/_next/static/chunks/164f4fb6-67211e558fa5de32.js",revision:"67211e558fa5de32"},{url:"/_next/static/chunks/1795-59339c2cb7f5fe6c.js",revision:"59339c2cb7f5fe6c"},{url:"/_next/static/chunks/1992.d220c685821eae19.js",revision:"d220c685821eae19"},{url:"/_next/static/chunks/201a89a4-3d566664e60259ff.js",revision:"3d566664e60259ff"},{url:"/_next/static/chunks/2170a4aa-a1f2fd30878a3e91.js",revision:"a1f2fd30878a3e91"},{url:"/_next/static/chunks/2f0b94e8-ce53c98b232310fc.js",revision:"ce53c98b232310fc"},{url:"/_next/static/chunks/30a37ab2-30992cf05be9404a.js",revision:"30992cf05be9404a"},{url:"/_next/static/chunks/4573-3b13da9808e514ac.js",revision:"3b13da9808e514ac"},{url:"/_next/static/chunks/4bd1b696-e5d7c65570c947b7.js",revision:"e5d7c65570c947b7"},{url:"/_next/static/chunks/5902.6a7448e0dec4f9e1.js",revision:"6a7448e0dec4f9e1"},{url:"/_next/static/chunks/5909-cf645368838a6b20.js",revision:"cf645368838a6b20"},{url:"/_next/static/chunks/7620-7f79d135fa03ba32.js",revision:"7f79d135fa03ba32"},{url:"/_next/static/chunks/795d4814-4c5954750245c540.js",revision:"4c5954750245c540"},{url:"/_next/static/chunks/8500-98e13bcce54aa7a0.js",revision:"98e13bcce54aa7a0"},{url:"/_next/static/chunks/8928-835918834d3b3798.js",revision:"835918834d3b3798"},{url:"/_next/static/chunks/8e1d74a4-407f656e5bcc2171.js",revision:"407f656e5bcc2171"},{url:"/_next/static/chunks/ad2866b8.6c51983a1eb56136.js",revision:"6c51983a1eb56136"},{url:"/_next/static/chunks/app/_global-error/page-85628b53985e66de.js",revision:"85628b53985e66de"},{url:"/_next/static/chunks/app/_not-found/page-85628b53985e66de.js",revision:"85628b53985e66de"},{url:"/_next/static/chunks/app/about/page-961134dbb15bfcbf.js",revision:"961134dbb15bfcbf"},{url:"/_next/static/chunks/app/api/auth/refresh/route-85628b53985e66de.js",revision:"85628b53985e66de"},{url:"/_next/static/chunks/app/auth/callback/page-e02d5207bc967b15.js",revision:"e02d5207bc967b15"},{url:"/_next/static/chunks/app/faq/page-fac70f1a93ec3606.js",revision:"fac70f1a93ec3606"},{url:"/_next/static/chunks/app/installation/page-f2243076dd49d3d4.js",revision:"f2243076dd49d3d4"},{url:"/_next/static/chunks/app/integrations/nextjs/page-ba7437bf718723f9.js",revision:"ba7437bf718723f9"},{url:"/_next/static/chunks/app/integrations/page-cef8333acda65483.js",revision:"cef8333acda65483"},{url:"/_next/static/chunks/app/integrations/react/page-410fd66b109e333c.js",revision:"410fd66b109e333c"},{url:"/_next/static/chunks/app/integrations/vue/page-d815efd4c9a39306.js",revision:"d815efd4c9a39306"},{url:"/_next/static/chunks/app/integrations/wordpress/page-f03e2378c00b904a.js",revision:"f03e2378c00b904a"},{url:"/_next/static/chunks/app/layout-ce4924adc7c42dcc.js",revision:"ce4924adc7c42dcc"},{url:"/_next/static/chunks/app/login/page-96640fc203cae231.js",revision:"96640fc203cae231"},{url:"/_next/static/chunks/app/not-found-833b37ab1663c8a1.js",revision:"833b37ab1663c8a1"},{url:"/_next/static/chunks/app/onboarding/page-091dd498ed5d5805.js",revision:"091dd498ed5d5805"},{url:"/_next/static/chunks/app/org-settings/page-c3e177ad6171617e.js",revision:"c3e177ad6171617e"},{url:"/_next/static/chunks/app/page-1d0749d506de7405.js",revision:"1d0749d506de7405"},{url:"/_next/static/chunks/app/pricing/page-ff5fab384b0203d3.js",revision:"ff5fab384b0203d3"},{url:"/_next/static/chunks/app/settings/page-890f2c485c894bb2.js",revision:"890f2c485c894bb2"},{url:"/_next/static/chunks/app/share/%5Bid%5D/page-88df4e94e7b55109.js",revision:"88df4e94e7b55109"},{url:"/_next/static/chunks/app/signup/page-b17cb3afb210ddbb.js",revision:"b17cb3afb210ddbb"},{url:"/_next/static/chunks/app/sites/%5Bid%5D/page-616162d6a1d6e47c.js",revision:"616162d6a1d6e47c"},{url:"/_next/static/chunks/app/sites/%5Bid%5D/realtime/page-2a2d04563947fa67.js",revision:"2a2d04563947fa67"},{url:"/_next/static/chunks/app/sites/%5Bid%5D/settings/page-b0fd17efb70e114b.js",revision:"b0fd17efb70e114b"},{url:"/_next/static/chunks/app/sites/new/page-c8dddbd443e2a8ac.js",revision:"c8dddbd443e2a8ac"},{url:"/_next/static/chunks/bc98253f.1c4ca5773e357da2.js",revision:"1c4ca5773e357da2"},{url:"/_next/static/chunks/c9d33fe5-86a53cc7ffd40559.js",revision:"86a53cc7ffd40559"},{url:"/_next/static/chunks/ee560e2c-d269d85d3d10b933.js",revision:"d269d85d3d10b933"},{url:"/_next/static/chunks/f4898fe8-8bc2ae55335cc57d.js",revision:"8bc2ae55335cc57d"},{url:"/_next/static/chunks/framework-81b2e59ffe13bb24.js",revision:"81b2e59ffe13bb24"},{url:"/_next/static/chunks/main-2aa77a4a2c547977.js",revision:"2aa77a4a2c547977"},{url:"/_next/static/chunks/main-app-e646227faa58df82.js",revision:"e646227faa58df82"},{url:"/_next/static/chunks/next/dist/client/components/builtin/app-error-85628b53985e66de.js",revision:"85628b53985e66de"},{url:"/_next/static/chunks/next/dist/client/components/builtin/forbidden-85628b53985e66de.js",revision:"85628b53985e66de"},{url:"/_next/static/chunks/next/dist/client/components/builtin/global-error-68b36e74ca32f8b6.js",revision:"68b36e74ca32f8b6"},{url:"/_next/static/chunks/next/dist/client/components/builtin/unauthorized-85628b53985e66de.js",revision:"85628b53985e66de"},{url:"/_next/static/chunks/polyfills-42372ed130431b0a.js",revision:"846118c33b2c0e922d7b3a7676f81f6f"},{url:"/_next/static/chunks/webpack-4c5e072e59638097.js",revision:"4c5e072e59638097"},{url:"/_next/static/css/9f42e6b7c3cab03c.css",revision:"9f42e6b7c3cab03c"},{url:"/_next/static/media/636a5ac981f94f8b-s.p.woff2",revision:"52d04440a9faae0db9adc6cdc844099b"},{url:"/_next/static/media/6fe53d21e6e7ebd8-s.woff2",revision:"2591db816b61d44b6e87ba79d13622b2"},{url:"/_next/static/media/8ebc6e9dde468c4a-s.woff2",revision:"196acbb650c75807ea2f0ef36edbd186"},{url:"/_next/static/media/9e7b0a821b9dfcb4-s.woff2",revision:"5ffe46eeb00dd9fa8a70cb10ccc3817e"},{url:"/_next/static/yV0hJZnpczr3hupoivroz/_buildManifest.js",revision:"f603baae985f0e99def0a2c51922a169"},{url:"/_next/static/yV0hJZnpczr3hupoivroz/_ssgManifest.js",revision:"b6652df95db52feb4daf4eca35380933"},{url:"/ciphera_icon_no_margins.png",revision:"eccd951fa34bd0901dcc37461b605e5b"},{url:"/favicon.ico",revision:"d256b0f8c516bbd3e93edf343876b462"},{url:"/icon-192x192.png",revision:"a9bae788a2253361a0e295deadbcc083"},{url:"/icon-512x512.png",revision:"d2efb02d7b9a0a627387d334201e6ea7"},{url:"/manifest.json",revision:"9c84335d179dcab05bf43ff586f2420d"},{url:"/pulse_icon_no_margins.png",revision:"a7cd4faa1c8e42ea5654451a31565642"},{url:"/pulse_logo_no_margins.png",revision:"6d6553cc7ea96597ffdcb69db9e0d11f"},{url:"/script.js",revision:"c579f58e59235c8575fb8262a26f411f"}],{ignoreURLParametersMatching:[/^utm_/,/^fbclid$/]}),e.cleanupOutdatedCaches(),e.registerRoute("/",new e.NetworkFirst({cacheName:"start-url",plugins:[{cacheWillUpdate:async({response:e})=>e&&"opaqueredirect"===e.type?new Response(e.body,{status:200,statusText:"OK",headers:e.headers}):e}]}),"GET"),e.registerRoute(/^https:\/\/fonts\.(?:gstatic)\.com\/.*/i,new e.CacheFirst({cacheName:"google-fonts-webfonts",plugins:[new e.ExpirationPlugin({maxEntries:4,maxAgeSeconds:31536e3})]}),"GET"),e.registerRoute(/^https:\/\/fonts\.(?:googleapis)\.com\/.*/i,new e.StaleWhileRevalidate({cacheName:"google-fonts-stylesheets",plugins:[new e.ExpirationPlugin({maxEntries:4,maxAgeSeconds:604800})]}),"GET"),e.registerRoute(/\.(?:eot|otf|ttc|ttf|woff|woff2|font.css)$/i,new e.StaleWhileRevalidate({cacheName:"static-font-assets",plugins:[new e.ExpirationPlugin({maxEntries:4,maxAgeSeconds:604800})]}),"GET"),e.registerRoute(/\.(?:jpg|jpeg|gif|png|svg|ico|webp)$/i,new e.StaleWhileRevalidate({cacheName:"static-image-assets",plugins:[new e.ExpirationPlugin({maxEntries:64,maxAgeSeconds:2592e3})]}),"GET"),e.registerRoute(/\/_next\/static.+\.js$/i,new e.CacheFirst({cacheName:"next-static-js-assets",plugins:[new e.ExpirationPlugin({maxEntries:64,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\/_next\/image\?url=.+$/i,new e.StaleWhileRevalidate({cacheName:"next-image",plugins:[new e.ExpirationPlugin({maxEntries:64,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\.(?:mp3|wav|ogg)$/i,new e.CacheFirst({cacheName:"static-audio-assets",plugins:[new e.RangeRequestsPlugin,new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\.(?:mp4|webm)$/i,new e.CacheFirst({cacheName:"static-video-assets",plugins:[new e.RangeRequestsPlugin,new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\.(?:js)$/i,new e.StaleWhileRevalidate({cacheName:"static-js-assets",plugins:[new e.ExpirationPlugin({maxEntries:48,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\.(?:css|less)$/i,new e.StaleWhileRevalidate({cacheName:"static-style-assets",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\/_next\/data\/.+\/.+\.json$/i,new e.StaleWhileRevalidate({cacheName:"next-data",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\.(?:json|xml|csv)$/i,new e.NetworkFirst({cacheName:"static-data-assets",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(({sameOrigin:e,url:{pathname:s}})=>!(!e||s.startsWith("/api/auth/callback")||!s.startsWith("/api/")),new e.NetworkFirst({cacheName:"apis",networkTimeoutSeconds:10,plugins:[new e.ExpirationPlugin({maxEntries:16,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(({request:e,url:{pathname:s},sameOrigin:a})=>"1"===e.headers.get("RSC")&&"1"===e.headers.get("Next-Router-Prefetch")&&a&&!s.startsWith("/api/"),new e.NetworkFirst({cacheName:"pages-rsc-prefetch",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(({request:e,url:{pathname:s},sameOrigin:a})=>"1"===e.headers.get("RSC")&&a&&!s.startsWith("/api/"),new e.NetworkFirst({cacheName:"pages-rsc",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(({url:{pathname:e},sameOrigin:s})=>s&&!e.startsWith("/api/"),new e.NetworkFirst({cacheName:"pages",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(({sameOrigin:e})=>!e,new e.NetworkFirst({cacheName:"cross-origin",networkTimeoutSeconds:10,plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:3600})]}),"GET")});