[PULSE-45] Integrations page overhaul — 75 guides, SEO, search & filters #13
63
app/integrations/angular/page.tsx
Normal file
63
app/integrations/angular/page.tsx
Normal file
@@ -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 (
|
||||
<IntegrationGuide integration={integration}>
|
||||
<p className="lead text-xl text-neutral-600 dark:text-neutral-400">
|
||||
Add Pulse to your Angular application by placing the script in your <code>index.html</code> or by using the Angular CLI's built-in scripts array.
|
||||
</p>
|
||||
|
||||
<hr className="my-8 border-neutral-200 dark:border-neutral-800" />
|
||||
|
||||
<h3>Method 1: index.html (Recommended)</h3>
|
||||
<p>
|
||||
Add the Pulse script tag directly to the <code><head></code> section of your <code>src/index.html</code>.
|
||||
</p>
|
||||
|
||||
<CodeBlock filename="src/index.html">
|
||||
{`<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>My Angular App</title>
|
||||
<base href="/" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
|
||||
<!-- Pulse Analytics -->
|
||||
<script
|
||||
defer
|
||||
data-domain="your-site.com"
|
||||
src="https://pulse.ciphera.net/script.js"
|
||||
></script>
|
||||
</head>
|
||||
<body>
|
||||
<app-root></app-root>
|
||||
</body>
|
||||
</html>`}
|
||||
</CodeBlock>
|
||||
|
||||
<h3>Method 2: angular.json Scripts Array</h3>
|
||||
<p>
|
||||
Alternatively, reference an external script in your <code>angular.json</code> build configuration. However, for analytics scripts that need <code>defer</code> and <code>data-*</code> attributes, Method 1 is simpler and recommended.
|
||||
</p>
|
||||
|
||||
<h3>Configuration Options</h3>
|
||||
<ul>
|
||||
<li>
|
||||
<strong>data-domain</strong>: The domain name you added to your Pulse dashboard (e.g., <code>example.com</code>).
|
||||
</li>
|
||||
<li>
|
||||
<strong>defer</strong>: Ensures the script loads without blocking the page.
|
||||
</li>
|
||||
</ul>
|
||||
</IntegrationGuide>
|
||||
)
|
||||
}
|
||||
60
app/integrations/astro/page.tsx
Normal file
60
app/integrations/astro/page.tsx
Normal file
@@ -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 (
|
||||
<IntegrationGuide integration={integration}>
|
||||
<p className="lead text-xl text-neutral-600 dark:text-neutral-400">
|
||||
Astro makes it easy to add third-party scripts. Drop the Pulse snippet into your base layout and you're done.
|
||||
</p>
|
||||
|
||||
<hr className="my-8 border-neutral-200 dark:border-neutral-800" />
|
||||
|
||||
<h3>Base Layout (Recommended)</h3>
|
||||
<p>
|
||||
Add the script to the <code><head></code> of your base layout file so it loads on every page.
|
||||
</p>
|
||||
|
||||
<CodeBlock filename="src/layouts/BaseLayout.astro">
|
||||
{`---
|
||||
// Base layout used by all pages
|
||||
---
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
|
||||
<!-- Pulse Analytics -->
|
||||
<script
|
||||
defer
|
||||
data-domain="your-site.com"
|
||||
src="https://pulse.ciphera.net/script.js"
|
||||
></script>
|
||||
|
||||
<title>My Astro Site</title>
|
||||
</head>
|
||||
<body>
|
||||
<slot />
|
||||
</body>
|
||||
</html>`}
|
||||
</CodeBlock>
|
||||
|
||||
<h3>Using Astro's Script Integration</h3>
|
||||
<p>
|
||||
You can also configure the script in your <code>astro.config.mjs</code> using the <code>injectScript</code> API of an Astro integration, but the layout approach above is simpler for most projects.
|
||||
</p>
|
||||
|
||||
<h3>Astro + View Transitions</h3>
|
||||
<p>
|
||||
If you use Astro's View Transitions, the Pulse script persists across navigations automatically since it is loaded in the <code><head></code> with <code>defer</code>.
|
||||
</p>
|
||||
</IntegrationGuide>
|
||||
)
|
||||
}
|
||||
65
app/integrations/gatsby/page.tsx
Normal file
65
app/integrations/gatsby/page.tsx
Normal file
@@ -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 (
|
||||
<IntegrationGuide integration={integration}>
|
||||
<p className="lead text-xl text-neutral-600 dark:text-neutral-400">
|
||||
Add Pulse to your Gatsby site using the <code>gatsby-ssr</code> API or the Gatsby Head API.
|
||||
</p>
|
||||
|
||||
<hr className="my-8 border-neutral-200 dark:border-neutral-800" />
|
||||
|
||||
<h3>Method 1: gatsby-ssr.js (Recommended)</h3>
|
||||
<p>
|
||||
Use the <code>onRenderBody</code> API to inject the script into every page's <code><head></code>.
|
||||
</p>
|
||||
|
||||
<CodeBlock filename="gatsby-ssr.js">
|
||||
{`import React from "react"
|
||||
|
||||
export const onRenderBody = ({ setHeadComponents }) => {
|
||||
setHeadComponents([
|
||||
<script
|
||||
key="pulse-analytics"
|
||||
defer
|
||||
data-domain="your-site.com"
|
||||
src="https://pulse.ciphera.net/script.js"
|
||||
/>,
|
||||
])
|
||||
}`}
|
||||
</CodeBlock>
|
||||
|
||||
<h3>Method 2: Gatsby Head API (Gatsby 4.19+)</h3>
|
||||
<p>
|
||||
If you prefer the newer Head API, export a <code>Head</code> component from your layout or page.
|
||||
</p>
|
||||
|
||||
<CodeBlock filename="src/pages/index.tsx">
|
||||
{`export function Head() {
|
||||
return (
|
||||
<>
|
||||
<title>My Gatsby Site</title>
|
||||
<script
|
||||
defer
|
||||
data-domain="your-site.com"
|
||||
src="https://pulse.ciphera.net/script.js"
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}`}
|
||||
</CodeBlock>
|
||||
|
||||
<p>
|
||||
The <code>gatsby-ssr.js</code> approach is better for global scripts because it automatically applies to every page without needing to add it to each route individually.
|
||||
</p>
|
||||
</IntegrationGuide>
|
||||
)
|
||||
}
|
||||
55
app/integrations/ghost/page.tsx
Normal file
55
app/integrations/ghost/page.tsx
Normal file
@@ -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 GhostIntegrationPage() {
|
||||
const integration = getIntegration('ghost')
|
||||
if (!integration) return notFound()
|
||||
|
||||
return (
|
||||
<IntegrationGuide integration={integration}>
|
||||
<p className="lead text-xl text-neutral-600 dark:text-neutral-400">
|
||||
Add Pulse to your Ghost publication using the built-in Code Injection feature.
|
||||
</p>
|
||||
|
||||
<hr className="my-8 border-neutral-200 dark:border-neutral-800" />
|
||||
|
||||
<h3>Code Injection (Recommended)</h3>
|
||||
<ol>
|
||||
<li>Log in to your Ghost admin panel.</li>
|
||||
<li>Go to <strong>Settings > Code injection</strong>.</li>
|
||||
<li>In the <strong>Site Header</strong> field, paste the following snippet:</li>
|
||||
</ol>
|
||||
|
||||
<CodeBlock filename="Settings → Code injection → Site Header">
|
||||
{`<script
|
||||
defer
|
||||
data-domain="your-blog.com"
|
||||
src="https://pulse.ciphera.net/script.js"
|
||||
></script>`}
|
||||
</CodeBlock>
|
||||
|
||||
<ol start={4}>
|
||||
<li>Click <strong>Save</strong>.</li>
|
||||
</ol>
|
||||
|
||||
<h3>Theme-Level Integration (Alternative)</h3>
|
||||
<p>
|
||||
If you prefer, you can also add the script directly to your Ghost theme's <code>default.hbs</code> file, just before the closing <code></head></code> tag. This approach requires re-uploading the theme whenever you make changes.
|
||||
</p>
|
||||
|
||||
<h3>Important Notes</h3>
|
||||
<ul>
|
||||
<li>
|
||||
<strong>data-domain</strong>: Use your publication's domain (e.g., <code>blog.example.com</code>).
|
||||
</li>
|
||||
<li>
|
||||
Code Injection is available on all Ghost plans, including the free self-hosted version.
|
||||
</li>
|
||||
</ul>
|
||||
</IntegrationGuide>
|
||||
)
|
||||
}
|
||||
65
app/integrations/hugo/page.tsx
Normal file
65
app/integrations/hugo/page.tsx
Normal file
@@ -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 (
|
||||
<IntegrationGuide integration={integration}>
|
||||
<p className="lead text-xl text-neutral-600 dark:text-neutral-400">
|
||||
Add Pulse to your Hugo site by placing the script in a partial or directly in your base template.
|
||||
</p>
|
||||
|
||||
<hr className="my-8 border-neutral-200 dark:border-neutral-800" />
|
||||
|
||||
<h3>Method 1: Partial (Recommended)</h3>
|
||||
<p>
|
||||
Create an analytics partial and include it in your base template's <code><head></code>.
|
||||
</p>
|
||||
|
||||
<CodeBlock filename="layouts/partials/analytics.html">
|
||||
{`{{ if not .Site.IsServer }}
|
||||
<script
|
||||
defer
|
||||
data-domain="your-site.com"
|
||||
src="https://pulse.ciphera.net/script.js"
|
||||
></script>
|
||||
{{ end }}`}
|
||||
</CodeBlock>
|
||||
|
||||
<p>
|
||||
Then include the partial in your <code>baseof.html</code>:
|
||||
</p>
|
||||
|
||||
<CodeBlock filename="layouts/_default/baseof.html">
|
||||
{`<!DOCTYPE html>
|
||||
<html lang="{{ .Site.Language.Lang }}">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>{{ .Title }}</title>
|
||||
|
||||
{{ partial "analytics.html" . }}
|
||||
</head>
|
||||
<body>
|
||||
{{ block "main" . }}{{ end }}
|
||||
</body>
|
||||
</html>`}
|
||||
</CodeBlock>
|
||||
|
||||
<h3>Method 2: Direct Insertion</h3>
|
||||
<p>
|
||||
If you prefer, add the script tag directly to the <code><head></code> of your <code>baseof.html</code> without creating a partial.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
The <code>if not .Site.IsServer</code> guard ensures the script is excluded during local development with <code>hugo server</code>.
|
||||
</p>
|
||||
</IntegrationGuide>
|
||||
)
|
||||
}
|
||||
@@ -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 (
|
||||
<div className="relative min-h-screen flex flex-col overflow-hidden selection:bg-brand-orange/20">
|
||||
{/* * --- ATMOSPHERE (Background) --- */}
|
||||
<div className="absolute inset-0 -z-10 pointer-events-none">
|
||||
<div className="absolute top-0 left-1/4 w-[500px] h-[500px] bg-brand-orange/10 rounded-full blur-[128px] opacity-60" />
|
||||
<div className="absolute bottom-0 right-1/4 w-[500px] h-[500px] bg-neutral-500/10 dark:bg-neutral-400/10 rounded-full blur-[128px] opacity-40" />
|
||||
<div
|
||||
className="absolute inset-0 bg-grid-pattern opacity-[0.02] dark:opacity-[0.05]"
|
||||
style={{ maskImage: 'radial-gradient(ellipse at center, black 0%, transparent 70%)' }}
|
||||
/>
|
||||
</div>
|
||||
<IntegrationGuide integration={integration}>
|
||||
<p className="lead text-xl text-neutral-600 dark:text-neutral-400">
|
||||
The best way to add Pulse to your Next.js application is using the built-in <code>next/script</code> component.
|
||||
</p>
|
||||
|
||||
<div className="flex-grow w-full max-w-4xl mx-auto px-4 pt-12 pb-10 z-10">
|
||||
<Link
|
||||
href="/integrations"
|
||||
className="inline-flex items-center text-sm text-neutral-500 hover:text-brand-orange mb-8 transition-colors"
|
||||
>
|
||||
<ArrowLeftIcon className="w-4 h-4 mr-2" />
|
||||
Back to Integrations
|
||||
</Link>
|
||||
<hr className="my-8 border-neutral-200 dark:border-neutral-800" />
|
||||
|
||||
<div className="flex items-center gap-4 mb-8">
|
||||
<div className="p-3 bg-neutral-100 dark:bg-neutral-800 rounded-xl">
|
||||
<svg viewBox="0 0 128 128" className="w-10 h-10 dark:invert">
|
||||
<path d="M64 0C28.7 0 0 28.7 0 64s28.7 64 64 64 64-28.7 64-64S99.3 0 64 0zm27.6 93.9c-.8.9-2.2 1-3.1.2L42.8 52.8V88c0 1.3-1.1 2.3-2.3 2.3h-7.4c-1.3 0-2.3-1.1-2.3-2.3V40c0-1.3 1.1-2.3 2.3-2.3h7.4c1 0 1.9.6 2.2 1.5l48.6 44.8V40c0-1.3 1.1-2.3 2.3-2.3h7.4c1.3 0 2.3 1.1 2.3 2.3v48c0 1.3-1.1 2.3-2.3 2.3h-6.8c-.9 0-1.7-.5-2.1-1.3z" />
|
||||
</svg>
|
||||
</div>
|
||||
<h1 className="text-4xl md:text-5xl font-bold text-neutral-900 dark:text-white">
|
||||
Next.js Integration
|
||||
</h1>
|
||||
</div>
|
||||
<h3>Using App Router (Recommended)</h3>
|
||||
<p>
|
||||
Add the script to your root layout file (usually <code>app/layout.tsx</code> or <code>app/layout.js</code>).
|
||||
</p>
|
||||
|
||||
<div className="prose prose-neutral dark:prose-invert max-w-none">
|
||||
<p className="lead text-xl text-neutral-600 dark:text-neutral-400">
|
||||
The best way to add Pulse to your Next.js application is using the built-in <code>next/script</code> component.
|
||||
</p>
|
||||
|
||||
<hr className="my-8 border-neutral-200 dark:border-neutral-800" />
|
||||
|
||||
<h3>Using App Router (Recommended)</h3>
|
||||
<p>
|
||||
Add the script to your root layout file (usually <code>app/layout.tsx</code> or <code>app/layout.js</code>).
|
||||
</p>
|
||||
|
||||
<div className="bg-[#1e1e1e] rounded-xl overflow-hidden border border-neutral-800 my-6">
|
||||
<div className="flex items-center px-4 py-2 bg-[#252526] border-b border-neutral-800">
|
||||
<span className="text-xs text-neutral-400 font-mono">app/layout.tsx</span>
|
||||
</div>
|
||||
<div className="p-4 overflow-x-auto">
|
||||
<pre className="text-sm font-mono text-neutral-300">
|
||||
<CodeBlock filename="app/layout.tsx">
|
||||
{`import Script from 'next/script'
|
||||
|
||||
export default function RootLayout({
|
||||
@@ -75,21 +44,14 @@ export default function RootLayout({
|
||||
</html>
|
||||
)
|
||||
}`}
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
</CodeBlock>
|
||||
|
||||
<h3>Using Pages Router</h3>
|
||||
<p>
|
||||
If you are using the older Pages Router, add the script to your custom <code>_app.tsx</code> or <code>_document.tsx</code>.
|
||||
</p>
|
||||
<h3>Using Pages Router</h3>
|
||||
<p>
|
||||
If you are using the older Pages Router, add the script to your custom <code>_app.tsx</code> or <code>_document.tsx</code>.
|
||||
</p>
|
||||
|
||||
<div className="bg-[#1e1e1e] rounded-xl overflow-hidden border border-neutral-800 my-6">
|
||||
<div className="flex items-center px-4 py-2 bg-[#252526] border-b border-neutral-800">
|
||||
<span className="text-xs text-neutral-400 font-mono">pages/_app.tsx</span>
|
||||
</div>
|
||||
<div className="p-4 overflow-x-auto">
|
||||
<pre className="text-sm font-mono text-neutral-300">
|
||||
<CodeBlock filename="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) {
|
||||
</>
|
||||
)
|
||||
}`}
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
</CodeBlock>
|
||||
|
||||
<h3>Configuration Options</h3>
|
||||
<ul>
|
||||
<li>
|
||||
<strong>data-domain</strong>: The domain name you added to your Pulse dashboard (e.g., <code>example.com</code>).
|
||||
</li>
|
||||
<li>
|
||||
<strong>src</strong>: The URL of our tracking script: <code>https://pulse.ciphera.net/script.js</code>
|
||||
</li>
|
||||
<li>
|
||||
<strong>strategy</strong>: We recommend <code>afterInteractive</code> to ensure it loads quickly without blocking hydration.
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<h3>Configuration Options</h3>
|
||||
<ul>
|
||||
<li>
|
||||
<strong>data-domain</strong>: The domain name you added to your Pulse dashboard (e.g., <code>example.com</code>).
|
||||
</li>
|
||||
<li>
|
||||
<strong>src</strong>: The URL of our tracking script: <code>https://pulse.ciphera.net/script.js</code>
|
||||
</li>
|
||||
<li>
|
||||
<strong>strategy</strong>: We recommend <code>afterInteractive</code> to ensure it loads quickly without blocking hydration.
|
||||
</li>
|
||||
</ul>
|
||||
</IntegrationGuide>
|
||||
)
|
||||
}
|
||||
|
||||
71
app/integrations/nuxt/page.tsx
Normal file
71
app/integrations/nuxt/page.tsx
Normal file
@@ -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 (
|
||||
<IntegrationGuide integration={integration}>
|
||||
<p className="lead text-xl text-neutral-600 dark:text-neutral-400">
|
||||
Configure Pulse in your Nuxt application by adding the script to your <code>nuxt.config</code> file.
|
||||
</p>
|
||||
|
||||
<hr className="my-8 border-neutral-200 dark:border-neutral-800" />
|
||||
|
||||
<h3>Nuxt 3 (Recommended)</h3>
|
||||
<p>
|
||||
Add the script to the <code>app.head</code> section of your <code>nuxt.config.ts</code>.
|
||||
</p>
|
||||
|
||||
<CodeBlock filename="nuxt.config.ts">
|
||||
{`export default defineNuxtConfig({
|
||||
app: {
|
||||
head: {
|
||||
script: [
|
||||
{
|
||||
src: 'https://pulse.ciphera.net/script.js',
|
||||
defer: true,
|
||||
'data-domain': 'your-site.com'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
})`}
|
||||
</CodeBlock>
|
||||
|
||||
<h3>Nuxt 2</h3>
|
||||
<p>
|
||||
For Nuxt 2 projects, add the script to the <code>head</code> object in <code>nuxt.config.js</code>.
|
||||
</p>
|
||||
|
||||
<CodeBlock filename="nuxt.config.js">
|
||||
{`export default {
|
||||
head: {
|
||||
script: [
|
||||
{
|
||||
src: 'https://pulse.ciphera.net/script.js',
|
||||
defer: true,
|
||||
'data-domain': 'your-site.com'
|
||||
}
|
||||
]
|
||||
}
|
||||
}`}
|
||||
</CodeBlock>
|
||||
|
||||
<h3>Configuration Options</h3>
|
||||
<ul>
|
||||
<li>
|
||||
<strong>data-domain</strong>: The domain name you added to your Pulse dashboard (e.g., <code>example.com</code>).
|
||||
</li>
|
||||
<li>
|
||||
<strong>defer</strong>: Ensures the script loads without blocking rendering.
|
||||
</li>
|
||||
</ul>
|
||||
</IntegrationGuide>
|
||||
)
|
||||
}
|
||||
@@ -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: (
|
||||
<svg viewBox="0 0 128 128" className="w-8 h-8 dark:invert">
|
||||
<path d="M64 0C28.7 0 0 28.7 0 64s28.7 64 64 64 64-28.7 64-64S99.3 0 64 0zm27.6 93.9c-.8.9-2.2 1-3.1.2L42.8 52.8V88c0 1.3-1.1 2.3-2.3 2.3h-7.4c-1.3 0-2.3-1.1-2.3-2.3V40c0-1.3 1.1-2.3 2.3-2.3h7.4c1 0 1.9.6 2.2 1.5l48.6 44.8V40c0-1.3 1.1-2.3 2.3-2.3h7.4c1.3 0 2.3 1.1 2.3 2.3v48c0 1.3-1.1 2.3-2.3 2.3h-6.8c-.9 0-1.7-.5-2.1-1.3z" />
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'react',
|
||||
name: 'React',
|
||||
description: 'Integrate Pulse with any React SPA (Create React App, Vite, etc).',
|
||||
icon: (
|
||||
<svg viewBox="0 0 128 128" className="w-8 h-8 text-[#61DAFB] fill-current">
|
||||
<path d="M64 10.6c18.4 0 34.6 5.8 44.6 14.8 6.4 5.8 10.2 12.8 10.2 20.6 0 21.6-28.6 41.2-64 41.2-1.6 0-3.2-.1-4.8-.2-1.2 10.8-6.2 20.2-13.8 27.6-8.8 8.6-20.6 13.4-33.2 13.4-2.2 0-4.4-.2-6.4-.4 10.2-12.8 15.6-29.2 15.6-46.2 0-2.6-.2-5.2-.4-7.8 13.6-1.6 26.2-5.4 37.4-11 11.2-5.6 20.2-13 26.2-21.4-6.4-5.8-15.4-10-25.6-12.2-10.2-2.2-21.4-3.4-33-3.4-1.6 0-3.2.1-4.8.2 1.2-10.8 6.2-20.2 13.8-27.6 8.8-8.6 20.6-13.4 33.2-13.4 2.2 0 4.4.2 6.4.4-10.2 12.8-15.6 29.2-15.6 46.2 0 2.6.2 5.2.4 7.8-13.6 1.6-26.2 5.4-37.4 11-11.2 5.6-20.2 13-26.2 21.4 6.4 5.8 15.4 10 25.6 12.2 10.2 2.2 21.4 3.4 33 3.4zm-33.4 62c-11.2 5.6-20.2 13-26.2 21.4 6.4 5.8 15.4 10 25.6 12.2 10.2 2.2 21.4 3.4 33 3.4 1.6 0 3.2-.1 4.8-.2-1.2 10.8-6.2 20.2-13.8 27.6-8.8 8.6-20.6 13.4-33.2 13.4-2.2 0-4.4-.2-6.4-.4 10.2-12.8 15.6-29.2 15.6-46.2 0-2.6-.2-5.2-.4-7.8 13.6-1.6 26.2-5.4 37.4-11zm-15.2-16.6c-6.4-5.8-10.2-12.8-10.2-20.6 0-21.6 28.6-41.2 64-41.2 1.6 0 3.2.1 4.8.2 1.2-10.8 6.2-20.2 13.8-27.6 8.8-8.6 20.6-13.4 33.2-13.4 2.2 0 4.4.2 6.4.4-10.2 12.8-15.6 29.2-15.6 46.2 0 2.6.2 5.2.4 7.8-13.6 1.6-26.2 5.4-37.4 11-11.2 5.6-20.2 13-26.2 21.4 6.4 5.8 15.4 10 25.6 12.2 10.2 2.2 21.4 3.4 33 3.4 1.6 0 3.2-.1 4.8-.2-1.2 10.8-6.2 20.2-13.8 27.6-8.8 8.6-20.6 13.4-33.2 13.4-2.2 0-4.4-.2-6.4-.4 10.2-12.8 15.6-29.2 15.6-46.2 0-2.6-.2-5.2-.4-7.8z" />
|
||||
<circle cx="64" cy="64" r="10.6" />
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'vue',
|
||||
name: 'Vue.js',
|
||||
description: 'Simple setup for Vue 2 and Vue 3 applications.',
|
||||
icon: (
|
||||
<svg viewBox="0 0 128 128" className="w-8 h-8 text-[#4FC08D] fill-current">
|
||||
<path d="M82.8 24.6h27.8L64 103.4 17.4 24.6h27.8L64 59.4l18.8-34.8z" />
|
||||
<path d="M64 24.6H39L64 67.4l25-42.8H64z" fill="#35495E" />
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'wordpress',
|
||||
name: 'WordPress',
|
||||
description: 'Add the tracking script to your WordPress header or use a plugin.',
|
||||
icon: (
|
||||
<svg viewBox="0 0 128 128" className="w-8 h-8 text-[#21759B] fill-current">
|
||||
<path d="M116.6 64c0-19.2-10.4-36-26-45.2l28.6 78.4c-1 3.2-2.2 6.2-3.6 9.2-11.4 12.4-27.8 20.2-46 20.2-6.2 0-12.2-.8-17.8-2.4l26.2-76.4c1.2.2 2.4.4 3.6.4 5.4 0 13.8-.8 13.8-.8 2.8-.2 3.2 4 .4 4.2 0 0-2.8.2-6 .4l19 56.6 5.4-18c2.4-7.4 4.2-12.8 4.2-17.4 0-6-2.2-10.2-7.6-12.6-2.8-1.2-2.2-5.4 1.4-5.4h4.4zM64 121.2c-15.8 0-30.2-6.4-40.8-16.8L46.6 36.8c-2.8-.2-5.8-.4-5.8-.4-2.8-.2-2.4-4.4.4-4.2 0 0 8.4.8 13.6.8 5.4 0 13.6-.8 13.6-.8 2.8-.2 3.2 4 .4 4.2 0 0-2.8.2-5.8.4l18.2 54.4 10.6-31.8L64 121.2zM11.4 64c0 17 8.2 32.2 20.8 41.8L18.8 66.8c-.8-3.4-1.2-6.6-1.2-9.2 0-6.8 2.6-13 6.2-17.8C15.6 47.4 11.4 55.2 11.4 64zM64 6.8c16.2 0 30.8 6.8 41.4 17.6-1.4-.2-2.8-.2-4.2-.2-7.8 0-14.2 1.4-14.2 1.4-2.8.6-2.2 4.8.6 4.2 0 0 5-1 10.6-1 2.2 0 4.6.2 6.6.4L88.2 53 71.4 6.8h-7.4z" />
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
]
|
||||
import { getGroupedIntegrations } from '@/lib/integrations'
|
||||
|
||||
export default function IntegrationsPage() {
|
||||
const groups = getGroupedIntegrations()
|
||||
|
||||
return (
|
||||
<div className="relative min-h-screen flex flex-col overflow-hidden selection:bg-brand-orange/20">
|
||||
{/* * --- ATMOSPHERE (Background) --- */}
|
||||
<div className="absolute inset-0 -z-10 pointer-events-none">
|
||||
<div className="absolute top-0 left-1/4 w-[500px] h-[500px] bg-brand-orange/10 rounded-full blur-[128px] opacity-60" />
|
||||
<div className="absolute bottom-0 right-1/4 w-[500px] h-[500px] bg-neutral-500/10 dark:bg-neutral-400/10 rounded-full blur-[128px] opacity-40" />
|
||||
<div
|
||||
<div
|
||||
className="absolute inset-0 bg-grid-pattern opacity-[0.02] dark:opacity-[0.05]"
|
||||
style={{ maskImage: 'radial-gradient(ellipse at center, black 0%, transparent 70%)' }}
|
||||
/>
|
||||
@@ -77,61 +35,75 @@ export default function IntegrationsPage() {
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{integrations.map((integration, i) => (
|
||||
<motion.div
|
||||
key={integration.id}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
{groups.map((group) => (
|
||||
<div key={group.category} className="mb-12">
|
||||
<motion.h2
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.5, delay: i * 0.1 }}
|
||||
transition={{ duration: 0.4 }}
|
||||
className="text-lg font-semibold text-neutral-500 dark:text-neutral-400 mb-6 tracking-wide uppercase"
|
||||
>
|
||||
<Link
|
||||
href={`/integrations/${integration.id}`}
|
||||
className="group relative p-8 bg-white/50 dark:bg-neutral-900/50 backdrop-blur-sm border border-neutral-200 dark:border-neutral-800 rounded-2xl hover:border-brand-orange/50 dark:hover:border-brand-orange/50 transition-all duration-300 hover:-translate-y-1 hover:shadow-xl block focus:outline-none focus:ring-2 focus:ring-brand-orange focus:ring-offset-2"
|
||||
>
|
||||
<div className="flex items-start justify-between mb-6">
|
||||
<div className="p-3 bg-neutral-100 dark:bg-neutral-800 rounded-xl group-hover:scale-110 transition-transform duration-300">
|
||||
{integration.icon}
|
||||
</div>
|
||||
<ArrowRightIcon className="w-5 h-5 text-neutral-400 group-hover:text-brand-orange transition-colors" />
|
||||
</div>
|
||||
|
||||
<h3 className="text-xl font-bold text-neutral-900 dark:text-white mb-3">
|
||||
{integration.name}
|
||||
</h3>
|
||||
<p className="text-neutral-600 dark:text-neutral-400 leading-relaxed mb-4">
|
||||
{integration.description}
|
||||
</p>
|
||||
<span className="text-sm font-medium text-brand-orange opacity-0 group-hover:opacity-100 transition-opacity flex items-center gap-1">
|
||||
View Guide <span aria-hidden="true">→</span>
|
||||
</span>
|
||||
</Link>
|
||||
</motion.div>
|
||||
))}
|
||||
|
||||
{/* * Request Integration Card */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.5, delay: integrations.length * 0.1 }}
|
||||
className="p-8 border border-dashed border-neutral-300 dark:border-neutral-700 rounded-2xl flex flex-col items-center justify-center text-center"
|
||||
{group.label}
|
||||
</motion.h2>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{group.items.map((integration, i) => (
|
||||
<motion.div
|
||||
key={integration.id}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.5, delay: i * 0.1 }}
|
||||
>
|
||||
<Link
|
||||
href={`/integrations/${integration.id}`}
|
||||
className="group relative p-8 bg-white/50 dark:bg-neutral-900/50 backdrop-blur-sm border border-neutral-200 dark:border-neutral-800 rounded-2xl hover:border-brand-orange/50 dark:hover:border-brand-orange/50 transition-all duration-300 hover:-translate-y-1 hover:shadow-xl block focus:outline-none focus:ring-2 focus:ring-brand-orange focus:ring-offset-2"
|
||||
>
|
||||
<div className="flex items-start justify-between mb-6">
|
||||
<div className="p-3 bg-neutral-100 dark:bg-neutral-800 rounded-xl group-hover:scale-110 transition-transform duration-300">
|
||||
{integration.icon}
|
||||
</div>
|
||||
<ArrowRightIcon className="w-5 h-5 text-neutral-400 group-hover:text-brand-orange transition-colors" />
|
||||
</div>
|
||||
|
||||
<h3 className="text-xl font-bold text-neutral-900 dark:text-white mb-3">
|
||||
{integration.name}
|
||||
</h3>
|
||||
<p className="text-neutral-600 dark:text-neutral-400 leading-relaxed mb-4">
|
||||
{integration.description}
|
||||
</p>
|
||||
<span className="text-sm font-medium text-brand-orange opacity-0 group-hover:opacity-100 transition-opacity flex items-center gap-1">
|
||||
View Guide <span aria-hidden="true">→</span>
|
||||
</span>
|
||||
</Link>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
{/* * Request Integration Card */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.5 }}
|
||||
className="max-w-md mx-auto p-8 border border-dashed border-neutral-300 dark:border-neutral-700 rounded-2xl flex flex-col items-center justify-center text-center"
|
||||
>
|
||||
<h3 className="text-xl font-bold text-neutral-900 dark:text-white mb-2">
|
||||
Missing something?
|
||||
</h3>
|
||||
<p className="text-neutral-600 dark:text-neutral-400 text-sm mb-4">
|
||||
Let us know which integration you'd like to see next.
|
||||
</p>
|
||||
<a
|
||||
href="mailto:support@ciphera.net"
|
||||
className="text-sm font-medium text-brand-orange hover:underline focus:outline-none focus:ring-2 focus:ring-brand-orange focus:rounded"
|
||||
>
|
||||
<h3 className="text-xl font-bold text-neutral-900 dark:text-white mb-2">
|
||||
Missing something?
|
||||
</h3>
|
||||
<p className="text-neutral-600 dark:text-neutral-400 text-sm mb-4">
|
||||
Let us know which integration you'd like to see next.
|
||||
</p>
|
||||
<a
|
||||
href="mailto:support@ciphera.net"
|
||||
className="text-sm font-medium text-brand-orange hover:underline focus:outline-none focus:ring-2 focus:ring-brand-orange focus:rounded"
|
||||
>
|
||||
Request Integration
|
||||
</a>
|
||||
</motion.div>
|
||||
</div>
|
||||
Request Integration
|
||||
</a>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -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 (
|
||||
<div className="relative min-h-screen flex flex-col overflow-hidden selection:bg-brand-orange/20">
|
||||
{/* * --- ATMOSPHERE (Background) --- */}
|
||||
<div className="absolute inset-0 -z-10 pointer-events-none">
|
||||
<div className="absolute top-0 left-1/4 w-[500px] h-[500px] bg-brand-orange/10 rounded-full blur-[128px] opacity-60" />
|
||||
<div className="absolute bottom-0 right-1/4 w-[500px] h-[500px] bg-neutral-500/10 dark:bg-neutral-400/10 rounded-full blur-[128px] opacity-40" />
|
||||
<div
|
||||
className="absolute inset-0 bg-grid-pattern opacity-[0.02] dark:opacity-[0.05]"
|
||||
style={{ maskImage: 'radial-gradient(ellipse at center, black 0%, transparent 70%)' }}
|
||||
/>
|
||||
</div>
|
||||
<IntegrationGuide integration={integration}>
|
||||
<p className="lead text-xl text-neutral-600 dark:text-neutral-400">
|
||||
For standard React SPAs (Create React App, Vite, etc.), you can simply add the script tag to your <code>index.html</code>.
|
||||
</p>
|
||||
|
||||
<div className="flex-grow w-full max-w-4xl mx-auto px-4 pt-12 pb-10 z-10">
|
||||
<Link
|
||||
href="/integrations"
|
||||
className="inline-flex items-center text-sm text-neutral-500 hover:text-brand-orange mb-8 transition-colors"
|
||||
>
|
||||
<ArrowLeftIcon className="w-4 h-4 mr-2" />
|
||||
Back to Integrations
|
||||
</Link>
|
||||
<hr className="my-8 border-neutral-200 dark:border-neutral-800" />
|
||||
|
||||
<div className="flex items-center gap-4 mb-8">
|
||||
<div className="p-3 bg-neutral-100 dark:bg-neutral-800 rounded-xl">
|
||||
<svg viewBox="0 0 128 128" className="w-10 h-10 text-[#61DAFB] fill-current">
|
||||
<path d="M64 10.6c18.4 0 34.6 5.8 44.6 14.8 6.4 5.8 10.2 12.8 10.2 20.6 0 21.6-28.6 41.2-64 41.2-1.6 0-3.2-.1-4.8-.2-1.2 10.8-6.2 20.2-13.8 27.6-8.8 8.6-20.6 13.4-33.2 13.4-2.2 0-4.4-.2-6.4-.4 10.2-12.8 15.6-29.2 15.6-46.2 0-2.6-.2-5.2-.4-7.8 13.6-1.6 26.2-5.4 37.4-11 11.2-5.6 20.2-13 26.2-21.4-6.4-5.8-15.4-10-25.6-12.2-10.2-2.2-21.4-3.4-33-3.4-1.6 0-3.2.1-4.8.2 1.2-10.8 6.2-20.2 13.8-27.6 8.8-8.6 20.6-13.4 33.2-13.4 2.2 0 4.4.2 6.4.4-10.2 12.8-15.6 29.2-15.6 46.2 0 2.6.2 5.2.4 7.8-13.6 1.6-26.2 5.4-37.4 11-11.2 5.6-20.2 13-26.2 21.4 6.4 5.8 15.4 10 25.6 12.2 10.2 2.2 21.4 3.4 33 3.4 1.6 0 3.2-.1 4.8-.2-1.2 10.8-6.2 20.2-13.8 27.6-8.8 8.6-20.6 13.4-33.2 13.4-2.2 0-4.4-.2-6.4-.4 10.2-12.8 15.6-29.2 15.6-46.2 0-2.6-.2-5.2-.4-7.8 13.6-1.6 26.2-5.4 37.4-11zm-33.4 62c-11.2 5.6-20.2 13-26.2 21.4 6.4 5.8 15.4 10 25.6 12.2 10.2 2.2 21.4 3.4 33 3.4 1.6 0 3.2-.1 4.8-.2-1.2 10.8-6.2 20.2-13.8 27.6-8.8 8.6-20.6 13.4-33.2 13.4-2.2 0-4.4-.2-6.4-.4 10.2-12.8 15.6-29.2 15.6-46.2 0-2.6-.2-5.2-.4-7.8 13.6-1.6 26.2-5.4 37.4-11zm-15.2-16.6c-6.4-5.8-10.2-12.8-10.2-20.6 0-21.6 28.6-41.2 64-41.2 1.6 0 3.2.1 4.8.2 1.2-10.8 6.2-20.2 13.8-27.6 8.8-8.6 20.6-13.4 33.2-13.4 2.2 0 4.4.2 6.4.4-10.2 12.8-15.6 29.2-15.6 46.2 0 2.6.2 5.2.4 7.8-13.6 1.6-26.2 5.4-37.4 11-11.2 5.6-20.2 13-26.2 21.4 6.4 5.8 15.4 10 25.6 12.2 10.2 2.2 21.4 3.4 33 3.4 1.6 0 3.2-.1 4.8-.2-1.2 10.8-6.2 20.2-13.8 27.6-8.8 8.6-20.6 13.4-33.2 13.4-2.2 0-4.4-.2-6.4-.4 10.2-12.8 15.6-29.2 15.6-46.2 0-2.6-.2-5.2-.4-7.8z" />
|
||||
<circle cx="64" cy="64" r="10.6" />
|
||||
</svg>
|
||||
</div>
|
||||
<h1 className="text-4xl md:text-5xl font-bold text-neutral-900 dark:text-white">
|
||||
React Integration
|
||||
</h1>
|
||||
</div>
|
||||
<h3>Method 1: index.html (Recommended)</h3>
|
||||
<p>
|
||||
The simplest way is to add the script tag directly to the <code><head></code> of your <code>index.html</code> file.
|
||||
</p>
|
||||
|
||||
<div className="prose prose-neutral dark:prose-invert max-w-none">
|
||||
<p className="lead text-xl text-neutral-600 dark:text-neutral-400">
|
||||
For standard React SPAs (Create React App, Vite, etc.), you can simply add the script tag to your <code>index.html</code>.
|
||||
</p>
|
||||
|
||||
<hr className="my-8 border-neutral-200 dark:border-neutral-800" />
|
||||
|
||||
<h3>Method 1: index.html (Recommended)</h3>
|
||||
<p>
|
||||
The simplest way is to add the script tag directly to the <code><head></code> of your <code>index.html</code> file.
|
||||
</p>
|
||||
|
||||
<div className="bg-[#1e1e1e] rounded-xl overflow-hidden border border-neutral-800 my-6">
|
||||
<div className="flex items-center px-4 py-2 bg-[#252526] border-b border-neutral-800">
|
||||
<span className="text-xs text-neutral-400 font-mono">public/index.html</span>
|
||||
</div>
|
||||
<div className="p-4 overflow-x-auto">
|
||||
<pre className="text-sm font-mono text-neutral-300">
|
||||
<CodeBlock filename="public/index.html">
|
||||
{`<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
|
||||
|
||||
<!-- Pulse Analytics -->
|
||||
<script
|
||||
defer
|
||||
data-domain="your-site.com"
|
||||
<script
|
||||
defer
|
||||
data-domain="your-site.com"
|
||||
src="https://pulse.ciphera.net/script.js"
|
||||
></script>
|
||||
|
||||
|
||||
<title>My React App</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>`}
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
</CodeBlock>
|
||||
|
||||
<h3>Method 2: Programmatic Injection</h3>
|
||||
<p>
|
||||
If you need to load the script dynamically (e.g., only in production), you can use a <code>useEffect</code> hook in your main App component.
|
||||
</p>
|
||||
<h3>Method 2: Programmatic Injection</h3>
|
||||
<p>
|
||||
If you need to load the script dynamically (e.g., only in production), you can use a <code>useEffect</code> hook in your main App component.
|
||||
</p>
|
||||
|
||||
<div className="bg-[#1e1e1e] rounded-xl overflow-hidden border border-neutral-800 my-6">
|
||||
<div className="flex items-center px-4 py-2 bg-[#252526] border-b border-neutral-800">
|
||||
<span className="text-xs text-neutral-400 font-mono">src/App.tsx</span>
|
||||
</div>
|
||||
<div className="p-4 overflow-x-auto">
|
||||
<pre className="text-sm font-mono text-neutral-300">
|
||||
<CodeBlock filename="src/App.tsx">
|
||||
{`import { useEffect } from 'react'
|
||||
|
||||
function App() {
|
||||
@@ -109,11 +70,7 @@ function App() {
|
||||
</div>
|
||||
)
|
||||
}`}
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CodeBlock>
|
||||
</IntegrationGuide>
|
||||
)
|
||||
}
|
||||
|
||||
71
app/integrations/remix/page.tsx
Normal file
71
app/integrations/remix/page.tsx
Normal file
@@ -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 (
|
||||
<IntegrationGuide integration={integration}>
|
||||
<p className="lead text-xl text-neutral-600 dark:text-neutral-400">
|
||||
Add Pulse to your Remix application by placing the script tag in your root route's <code><head></code>.
|
||||
</p>
|
||||
|
||||
<hr className="my-8 border-neutral-200 dark:border-neutral-800" />
|
||||
|
||||
<h3>Root Route (Recommended)</h3>
|
||||
<p>
|
||||
In Remix, the <code>app/root.tsx</code> file controls the HTML shell. Add the Pulse script inside the <code><head></code> section.
|
||||
</p>
|
||||
|
||||
<CodeBlock filename="app/root.tsx">
|
||||
{`import {
|
||||
Links,
|
||||
Meta,
|
||||
Outlet,
|
||||
Scripts,
|
||||
ScrollRestoration,
|
||||
} from "@remix-run/react"
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charSet="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<Meta />
|
||||
<Links />
|
||||
|
||||
{/* Pulse Analytics */}
|
||||
<script
|
||||
defer
|
||||
data-domain="your-site.com"
|
||||
src="https://pulse.ciphera.net/script.js"
|
||||
/>
|
||||
</head>
|
||||
<body>
|
||||
<Outlet />
|
||||
<ScrollRestoration />
|
||||
<Scripts />
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}`}
|
||||
</CodeBlock>
|
||||
|
||||
<h3>Configuration Options</h3>
|
||||
<ul>
|
||||
<li>
|
||||
<strong>data-domain</strong>: The domain name you added to your Pulse dashboard (e.g., <code>example.com</code>).
|
||||
</li>
|
||||
<li>
|
||||
<strong>defer</strong>: Ensures the script loads without blocking the page.
|
||||
</li>
|
||||
</ul>
|
||||
</IntegrationGuide>
|
||||
)
|
||||
}
|
||||
53
app/integrations/shopify/page.tsx
Normal file
53
app/integrations/shopify/page.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
'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 ShopifyIntegrationPage() {
|
||||
const integration = getIntegration('shopify')
|
||||
if (!integration) return notFound()
|
||||
|
||||
return (
|
||||
<IntegrationGuide integration={integration}>
|
||||
<p className="lead text-xl text-neutral-600 dark:text-neutral-400">
|
||||
Add privacy-first analytics to your Shopify store in minutes using the theme editor — no app installation required.
|
||||
</p>
|
||||
|
||||
<hr className="my-8 border-neutral-200 dark:border-neutral-800" />
|
||||
|
||||
<h3>Method 1: Theme Settings (Easiest)</h3>
|
||||
<ol>
|
||||
<li>In your Shopify admin, go to <strong>Online Store > Themes</strong>.</li>
|
||||
<li>Click <strong>Actions > Edit code</strong> on your active theme.</li>
|
||||
<li>Open <code>layout/theme.liquid</code>.</li>
|
||||
<li>Paste the following snippet just before the closing <code></head></code> tag:</li>
|
||||
</ol>
|
||||
|
||||
<CodeBlock filename="layout/theme.liquid">
|
||||
{`<!-- Pulse Analytics -->
|
||||
<script
|
||||
defer
|
||||
data-domain="your-store.myshopify.com"
|
||||
src="https://pulse.ciphera.net/script.js"
|
||||
></script>`}
|
||||
</CodeBlock>
|
||||
|
||||
<h3>Method 2: Custom Pixels (Shopify Plus)</h3>
|
||||
<p>
|
||||
If you are on Shopify Plus, you can also use <strong>Customer Events > Custom Pixels</strong> to add the script. Go to <strong>Settings > Customer events</strong> and create a new custom pixel.
|
||||
</p>
|
||||
|
||||
<h3>Important Notes</h3>
|
||||
<ul>
|
||||
<li>
|
||||
<strong>data-domain</strong>: Use your custom domain (e.g., <code>shop.example.com</code>) if you have one, or your <code>.myshopify.com</code> domain.
|
||||
</li>
|
||||
<li>
|
||||
Pulse does not use cookies and is fully GDPR-compliant — no cookie banner changes needed.
|
||||
</li>
|
||||
</ul>
|
||||
</IntegrationGuide>
|
||||
)
|
||||
}
|
||||
52
app/integrations/squarespace/page.tsx
Normal file
52
app/integrations/squarespace/page.tsx
Normal file
@@ -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 (
|
||||
<IntegrationGuide integration={integration}>
|
||||
<p className="lead text-xl text-neutral-600 dark:text-neutral-400">
|
||||
Add Pulse to your Squarespace site using the built-in Code Injection feature — no plugins needed.
|
||||
</p>
|
||||
|
||||
<hr className="my-8 border-neutral-200 dark:border-neutral-800" />
|
||||
|
||||
<h3>Code Injection (Recommended)</h3>
|
||||
<ol>
|
||||
<li>In your Squarespace dashboard, go to <strong>Settings > Developer Tools > Code Injection</strong>.</li>
|
||||
<li>In the <strong>Header</strong> field, paste the following snippet:</li>
|
||||
</ol>
|
||||
|
||||
<CodeBlock filename="Settings → Code Injection → Header">
|
||||
{`<script
|
||||
defer
|
||||
data-domain="your-site.com"
|
||||
src="https://pulse.ciphera.net/script.js"
|
||||
></script>`}
|
||||
</CodeBlock>
|
||||
|
||||
<ol start={3}>
|
||||
<li>Click <strong>Save</strong>.</li>
|
||||
</ol>
|
||||
|
||||
<h3>Important Notes</h3>
|
||||
<ul>
|
||||
<li>
|
||||
Code Injection is available on <strong>Squarespace Business</strong> and <strong>Commerce</strong> plans.
|
||||
</li>
|
||||
<li>
|
||||
<strong>data-domain</strong>: Use your custom domain (e.g., <code>example.com</code>) rather than the <code>.squarespace.com</code> subdomain.
|
||||
</li>
|
||||
<li>
|
||||
Pulse is cookie-free, so you do not need to update your Squarespace cookie banner settings.
|
||||
</li>
|
||||
</ul>
|
||||
</IntegrationGuide>
|
||||
)
|
||||
}
|
||||
70
app/integrations/svelte/page.tsx
Normal file
70
app/integrations/svelte/page.tsx
Normal file
@@ -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 (
|
||||
<IntegrationGuide integration={integration}>
|
||||
<p className="lead text-xl text-neutral-600 dark:text-neutral-400">
|
||||
Integrating Pulse with Svelte or SvelteKit takes less than a minute. Just add the script tag to your HTML entry point.
|
||||
</p>
|
||||
|
||||
<hr className="my-8 border-neutral-200 dark:border-neutral-800" />
|
||||
|
||||
<h3>Svelte (Vite)</h3>
|
||||
<p>
|
||||
For a standard Svelte project scaffolded with Vite, add the script to the <code><head></code> of your <code>index.html</code>.
|
||||
</p>
|
||||
|
||||
<CodeBlock filename="index.html">
|
||||
{`<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
|
||||
<!-- Pulse Analytics -->
|
||||
<script
|
||||
defer
|
||||
data-domain="your-site.com"
|
||||
src="https://pulse.ciphera.net/script.js"
|
||||
></script>
|
||||
|
||||
<title>My Svelte App</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>`}
|
||||
</CodeBlock>
|
||||
|
||||
<h3>SvelteKit</h3>
|
||||
<p>
|
||||
In SvelteKit, add the script to your root layout's <code><svelte:head></code> block so it loads on every page.
|
||||
</p>
|
||||
|
||||
<CodeBlock filename="src/routes/+layout.svelte">
|
||||
{`<svelte:head>
|
||||
<script
|
||||
defer
|
||||
data-domain="your-site.com"
|
||||
src="https://pulse.ciphera.net/script.js"
|
||||
></script>
|
||||
</svelte:head>
|
||||
|
||||
<slot />`}
|
||||
</CodeBlock>
|
||||
|
||||
<p>
|
||||
Alternatively, you can add the script to <code>src/app.html</code> directly in the <code><head></code> section.
|
||||
</p>
|
||||
</IntegrationGuide>
|
||||
)
|
||||
}
|
||||
@@ -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 (
|
||||
<div className="relative min-h-screen flex flex-col overflow-hidden selection:bg-brand-orange/20">
|
||||
{/* * --- ATMOSPHERE (Background) --- */}
|
||||
<div className="absolute inset-0 -z-10 pointer-events-none">
|
||||
<div className="absolute top-0 left-1/4 w-[500px] h-[500px] bg-brand-orange/10 rounded-full blur-[128px] opacity-60" />
|
||||
<div className="absolute bottom-0 right-1/4 w-[500px] h-[500px] bg-neutral-500/10 dark:bg-neutral-400/10 rounded-full blur-[128px] opacity-40" />
|
||||
<div
|
||||
className="absolute inset-0 bg-grid-pattern opacity-[0.02] dark:opacity-[0.05]"
|
||||
style={{ maskImage: 'radial-gradient(ellipse at center, black 0%, transparent 70%)' }}
|
||||
/>
|
||||
</div>
|
||||
<IntegrationGuide integration={integration}>
|
||||
<p className="lead text-xl text-neutral-600 dark:text-neutral-400">
|
||||
Integrating Pulse with Vue.js is straightforward. Add the script to your <code>index.html</code> file.
|
||||
</p>
|
||||
|
||||
<div className="flex-grow w-full max-w-4xl mx-auto px-4 pt-12 pb-10 z-10">
|
||||
<Link
|
||||
href="/integrations"
|
||||
className="inline-flex items-center text-sm text-neutral-500 hover:text-brand-orange mb-8 transition-colors"
|
||||
>
|
||||
<ArrowLeftIcon className="w-4 h-4 mr-2" />
|
||||
Back to Integrations
|
||||
</Link>
|
||||
<hr className="my-8 border-neutral-200 dark:border-neutral-800" />
|
||||
|
||||
<div className="flex items-center gap-4 mb-8">
|
||||
<div className="p-3 bg-neutral-100 dark:bg-neutral-800 rounded-xl">
|
||||
<svg viewBox="0 0 128 128" className="w-10 h-10 text-[#4FC08D] fill-current">
|
||||
<path d="M82.8 24.6h27.8L64 103.4 17.4 24.6h27.8L64 59.4l18.8-34.8z" />
|
||||
<path d="M64 24.6H39L64 67.4l25-42.8H64z" fill="#35495E" />
|
||||
</svg>
|
||||
</div>
|
||||
<h1 className="text-4xl md:text-5xl font-bold text-neutral-900 dark:text-white">
|
||||
Vue.js Integration
|
||||
</h1>
|
||||
</div>
|
||||
<h3>index.html (Vue CLI & Vite)</h3>
|
||||
<p>
|
||||
Add the script tag to the <code><head></code> section of your <code>index.html</code> file. This works for both Vue 2 and Vue 3 projects created with Vue CLI or Vite.
|
||||
</p>
|
||||
|
||||
<div className="prose prose-neutral dark:prose-invert max-w-none">
|
||||
<p className="lead text-xl text-neutral-600 dark:text-neutral-400">
|
||||
Integrating Pulse with Vue.js is straightforward. You can add the script to your <code>index.html</code> file.
|
||||
</p>
|
||||
|
||||
<hr className="my-8 border-neutral-200 dark:border-neutral-800" />
|
||||
|
||||
<h3>Method 1: index.html (Recommended)</h3>
|
||||
<p>
|
||||
Add the script tag to the <code><head></code> section of your <code>index.html</code> file. This works for both Vue 2 and Vue 3 projects created with Vue CLI or Vite.
|
||||
</p>
|
||||
|
||||
<div className="bg-[#1e1e1e] rounded-xl overflow-hidden border border-neutral-800 my-6">
|
||||
<div className="flex items-center px-4 py-2 bg-[#252526] border-b border-neutral-800">
|
||||
<span className="text-xs text-neutral-400 font-mono">index.html</span>
|
||||
</div>
|
||||
<div className="p-4 overflow-x-auto">
|
||||
<pre className="text-sm font-mono text-neutral-300">
|
||||
<CodeBlock filename="index.html">
|
||||
{`<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
|
||||
|
||||
<!-- Pulse Analytics -->
|
||||
<script
|
||||
defer
|
||||
data-domain="your-site.com"
|
||||
<script
|
||||
defer
|
||||
data-domain="your-site.com"
|
||||
src="https://pulse.ciphera.net/script.js"
|
||||
></script>
|
||||
|
||||
|
||||
<title>My Vue App</title>
|
||||
</head>
|
||||
<body>
|
||||
@@ -75,39 +43,11 @@ export default function VueIntegrationPage() {
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
</html>`}
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
</CodeBlock>
|
||||
|
||||
<h3>Method 2: Nuxt.js</h3>
|
||||
<p>
|
||||
For Nuxt.js applications, you should add the script to your <code>nuxt.config.js</code> or <code>nuxt.config.ts</code> file.
|
||||
</p>
|
||||
|
||||
<div className="bg-[#1e1e1e] rounded-xl overflow-hidden border border-neutral-800 my-6">
|
||||
<div className="flex items-center px-4 py-2 bg-[#252526] border-b border-neutral-800">
|
||||
<span className="text-xs text-neutral-400 font-mono">nuxt.config.ts</span>
|
||||
</div>
|
||||
<div className="p-4 overflow-x-auto">
|
||||
<pre className="text-sm font-mono text-neutral-300">
|
||||
{`export default defineNuxtConfig({
|
||||
app: {
|
||||
head: {
|
||||
script: [
|
||||
{
|
||||
src: 'https://pulse.ciphera.net/script.js',
|
||||
defer: true,
|
||||
'data-domain': 'your-site.com'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
})`}
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p>
|
||||
Looking for Nuxt.js? Check the dedicated <a href="/integrations/nuxt" className="text-brand-orange hover:underline">Nuxt integration guide</a>.
|
||||
</p>
|
||||
</IntegrationGuide>
|
||||
)
|
||||
}
|
||||
|
||||
55
app/integrations/webflow/page.tsx
Normal file
55
app/integrations/webflow/page.tsx
Normal file
@@ -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 (
|
||||
<IntegrationGuide integration={integration}>
|
||||
<p className="lead text-xl text-neutral-600 dark:text-neutral-400">
|
||||
Add Pulse to your Webflow site by pasting a single snippet into your project's custom code settings.
|
||||
</p>
|
||||
|
||||
<hr className="my-8 border-neutral-200 dark:border-neutral-800" />
|
||||
|
||||
<h3>Project-Level Custom Code (Recommended)</h3>
|
||||
<ol>
|
||||
<li>Open your Webflow project and go to <strong>Project Settings</strong>.</li>
|
||||
<li>Navigate to the <strong>Custom Code</strong> tab.</li>
|
||||
<li>In the <strong>Head Code</strong> section, paste the following snippet:</li>
|
||||
</ol>
|
||||
|
||||
<CodeBlock filename="Project Settings → Head Code">
|
||||
{`<script
|
||||
defer
|
||||
data-domain="your-site.com"
|
||||
src="https://pulse.ciphera.net/script.js"
|
||||
></script>`}
|
||||
</CodeBlock>
|
||||
|
||||
<ol start={4}>
|
||||
<li>Click <strong>Save Changes</strong> and publish your site.</li>
|
||||
</ol>
|
||||
|
||||
<h3>Page-Level Custom Code</h3>
|
||||
<p>
|
||||
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 <strong>Head Code</strong> section.
|
||||
</p>
|
||||
|
||||
<h3>Important Notes</h3>
|
||||
<ul>
|
||||
<li>
|
||||
Custom code requires a <strong>Webflow paid site plan</strong> (Basic or higher).
|
||||
</li>
|
||||
<li>
|
||||
The script will not appear in the Webflow Designer preview — publish the site and view the live version to verify.
|
||||
</li>
|
||||
</ul>
|
||||
</IntegrationGuide>
|
||||
)
|
||||
}
|
||||
54
app/integrations/wix/page.tsx
Normal file
54
app/integrations/wix/page.tsx
Normal file
@@ -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 (
|
||||
<IntegrationGuide integration={integration}>
|
||||
<p className="lead text-xl text-neutral-600 dark:text-neutral-400">
|
||||
Add Pulse to your Wix site using the Custom Code feature in your site settings.
|
||||
</p>
|
||||
|
||||
<hr className="my-8 border-neutral-200 dark:border-neutral-800" />
|
||||
|
||||
<h3>Custom Code (Recommended)</h3>
|
||||
<ol>
|
||||
<li>In your Wix dashboard, go to <strong>Settings > Custom Code</strong> (under “Advanced”).</li>
|
||||
<li>Click <strong>+ Add Custom Code</strong>.</li>
|
||||
<li>Paste the following snippet:</li>
|
||||
</ol>
|
||||
|
||||
<CodeBlock filename="Custom Code Snippet">
|
||||
{`<script
|
||||
defer
|
||||
data-domain="your-site.com"
|
||||
src="https://pulse.ciphera.net/script.js"
|
||||
></script>`}
|
||||
</CodeBlock>
|
||||
|
||||
<ol start={4}>
|
||||
<li>Set the code to load in the <strong>Head</strong> of <strong>All pages</strong>.</li>
|
||||
<li>Click <strong>Apply</strong>.</li>
|
||||
</ol>
|
||||
|
||||
<h3>Important Notes</h3>
|
||||
<ul>
|
||||
<li>
|
||||
Custom Code requires a <strong>Wix Premium plan</strong> with a connected domain.
|
||||
</li>
|
||||
<li>
|
||||
<strong>data-domain</strong>: Use your connected custom domain, not the <code>.wixsite.com</code> subdomain.
|
||||
</li>
|
||||
<li>
|
||||
Pulse is cookie-free and GDPR-compliant — no consent banner changes are needed.
|
||||
</li>
|
||||
</ul>
|
||||
</IntegrationGuide>
|
||||
)
|
||||
}
|
||||
@@ -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 (
|
||||
<div className="relative min-h-screen flex flex-col overflow-hidden selection:bg-brand-orange/20">
|
||||
{/* * --- ATMOSPHERE (Background) --- */}
|
||||
<div className="absolute inset-0 -z-10 pointer-events-none">
|
||||
<div className="absolute top-0 left-1/4 w-[500px] h-[500px] bg-brand-orange/10 rounded-full blur-[128px] opacity-60" />
|
||||
<div className="absolute bottom-0 right-1/4 w-[500px] h-[500px] bg-neutral-500/10 dark:bg-neutral-400/10 rounded-full blur-[128px] opacity-40" />
|
||||
<div
|
||||
className="absolute inset-0 bg-grid-pattern opacity-[0.02] dark:opacity-[0.05]"
|
||||
style={{ maskImage: 'radial-gradient(ellipse at center, black 0%, transparent 70%)' }}
|
||||
/>
|
||||
</div>
|
||||
<IntegrationGuide integration={integration}>
|
||||
<p className="lead text-xl text-neutral-600 dark:text-neutral-400">
|
||||
You can add Pulse to your WordPress site without installing any heavy plugins, or by using a simple code snippet plugin.
|
||||
</p>
|
||||
|
||||
<div className="flex-grow w-full max-w-4xl mx-auto px-4 pt-12 pb-10 z-10">
|
||||
<Link
|
||||
href="/integrations"
|
||||
className="inline-flex items-center text-sm text-neutral-500 hover:text-brand-orange mb-8 transition-colors"
|
||||
>
|
||||
<ArrowLeftIcon className="w-4 h-4 mr-2" />
|
||||
Back to Integrations
|
||||
</Link>
|
||||
<hr className="my-8 border-neutral-200 dark:border-neutral-800" />
|
||||
|
||||
<div className="flex items-center gap-4 mb-8">
|
||||
<div className="p-3 bg-neutral-100 dark:bg-neutral-800 rounded-xl">
|
||||
<svg viewBox="0 0 128 128" className="w-10 h-10 text-[#21759B] fill-current">
|
||||
<path d="M116.6 64c0-19.2-10.4-36-26-45.2l28.6 78.4c-1 3.2-2.2 6.2-3.6 9.2-11.4 12.4-27.8 20.2-46 20.2-6.2 0-12.2-.8-17.8-2.4l26.2-76.4c1.2.2 2.4.4 3.6.4 5.4 0 13.8-.8 13.8-.8 2.8-.2 3.2 4 .4 4.2 0 0-2.8.2-6 .4l19 56.6 5.4-18c2.4-7.4 4.2-12.8 4.2-17.4 0-6-2.2-10.2-7.6-12.6-2.8-1.2-2.2-5.4 1.4-5.4h4.4zM64 121.2c-15.8 0-30.2-6.4-40.8-16.8L46.6 36.8c-2.8-.2-5.8-.4-5.8-.4-2.8-.2-2.4-4.4.4-4.2 0 0 8.4.8 13.6.8 5.4 0 13.6-.8 13.6-.8 2.8-.2 3.2 4 .4 4.2 0 0-2.8.2-5.8.4l18.2 54.4 10.6-31.8L64 121.2zM11.4 64c0 17 8.2 32.2 20.8 41.8L18.8 66.8c-.8-3.4-1.2-6.6-1.2-9.2 0-6.8 2.6-13 6.2-17.8C15.6 47.4 11.4 55.2 11.4 64zM64 6.8c16.2 0 30.8 6.8 41.4 17.6-1.4-.2-2.8-.2-4.2-.2-7.8 0-14.2 1.4-14.2 1.4-2.8.6-2.2 4.8.6 4.2 0 0 5-1 10.6-1 2.2 0 4.6.2 6.6.4L88.2 53 71.4 6.8h-7.4z" />
|
||||
</svg>
|
||||
</div>
|
||||
<h1 className="text-4xl md:text-5xl font-bold text-neutral-900 dark:text-white">
|
||||
WordPress Integration
|
||||
</h1>
|
||||
</div>
|
||||
<h3>Method 1: Using a Plugin (Easiest)</h3>
|
||||
<ol>
|
||||
<li>Install a plugin like “Insert Headers and Footers” (WPCode).</li>
|
||||
<li>Go to the plugin settings and find the “Scripts in Header” section.</li>
|
||||
<li>Paste the following code snippet:</li>
|
||||
</ol>
|
||||
|
||||
<div className="prose prose-neutral dark:prose-invert max-w-none">
|
||||
<p className="lead text-xl text-neutral-600 dark:text-neutral-400">
|
||||
You can add Pulse to your WordPress site without installing any heavy plugins, or by using a simple code snippet plugin.
|
||||
</p>
|
||||
|
||||
<hr className="my-8 border-neutral-200 dark:border-neutral-800" />
|
||||
|
||||
<h3>Method 1: Using a Plugin (Easiest)</h3>
|
||||
<ol>
|
||||
<li>Install a plugin like "Insert Headers and Footers" (WPCode).</li>
|
||||
<li>Go to the plugin settings and find the "Scripts in Header" section.</li>
|
||||
<li>Paste the following code snippet:</li>
|
||||
</ol>
|
||||
|
||||
<div className="bg-[#1e1e1e] rounded-xl overflow-hidden border border-neutral-800 my-6">
|
||||
<div className="flex items-center px-4 py-2 bg-[#252526] border-b border-neutral-800">
|
||||
<span className="text-xs text-neutral-400 font-mono">Header Script</span>
|
||||
</div>
|
||||
<div className="p-4 overflow-x-auto">
|
||||
<pre className="text-sm font-mono text-neutral-300">
|
||||
{`<script
|
||||
defer
|
||||
data-domain="your-site.com"
|
||||
<CodeBlock filename="Header Script">
|
||||
{`<script
|
||||
defer
|
||||
data-domain="your-site.com"
|
||||
src="https://pulse.ciphera.net/script.js"
|
||||
></script>`}
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
</CodeBlock>
|
||||
|
||||
<h3>Method 2: Edit Theme Files (Advanced)</h3>
|
||||
<p>
|
||||
If you are comfortable editing your theme files, you can add the script directly to your <code>header.php</code> file.
|
||||
</p>
|
||||
<ol>
|
||||
<li>Go to Appearance > Theme File Editor.</li>
|
||||
<li>Select <code>header.php</code> from the right sidebar.</li>
|
||||
<li>Paste the script tag just before the closing <code></head></code> tag.</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<h3>Method 2: Edit Theme Files (Advanced)</h3>
|
||||
<p>
|
||||
If you are comfortable editing your theme files, you can add the script directly to your <code>header.php</code> file.
|
||||
</p>
|
||||
<ol>
|
||||
<li>Go to Appearance > Theme File Editor.</li>
|
||||
<li>Select <code>header.php</code> from the right sidebar.</li>
|
||||
<li>Paste the script tag just before the closing <code></head></code> tag.</li>
|
||||
</ol>
|
||||
</IntegrationGuide>
|
||||
)
|
||||
}
|
||||
|
||||
30
components/CodeBlock.tsx
Normal file
30
components/CodeBlock.tsx
Normal file
@@ -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 (
|
||||
<div className="bg-[#1e1e1e] rounded-xl overflow-hidden border border-neutral-800 my-6">
|
||||
<div className="flex items-center px-4 py-2 bg-[#252526] border-b border-neutral-800">
|
||||
<span className="text-xs text-neutral-400 font-mono">{filename}</span>
|
||||
</div>
|
||||
<div className="p-4 overflow-x-auto">
|
||||
<pre className="text-sm font-mono text-neutral-300">{children}</pre>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
69
components/IntegrationGuide.tsx
Normal file
69
components/IntegrationGuide.tsx
Normal file
@@ -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 = (
|
||||
<div className="[&_svg]:w-10 [&_svg]:h-10">
|
||||
{integration.icon}
|
||||
</div>
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="relative min-h-screen flex flex-col overflow-hidden selection:bg-brand-orange/20">
|
||||
{/* * --- ATMOSPHERE (Background) --- */}
|
||||
<div className="absolute inset-0 -z-10 pointer-events-none">
|
||||
<div className="absolute top-0 left-1/4 w-[500px] h-[500px] bg-brand-orange/10 rounded-full blur-[128px] opacity-60" />
|
||||
<div className="absolute bottom-0 right-1/4 w-[500px] h-[500px] bg-neutral-500/10 dark:bg-neutral-400/10 rounded-full blur-[128px] opacity-40" />
|
||||
<div
|
||||
className="absolute inset-0 bg-grid-pattern opacity-[0.02] dark:opacity-[0.05]"
|
||||
style={{ maskImage: 'radial-gradient(ellipse at center, black 0%, transparent 70%)' }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex-grow w-full max-w-4xl mx-auto px-4 pt-12 pb-10 z-10">
|
||||
<Link
|
||||
href="/integrations"
|
||||
className="inline-flex items-center text-sm text-neutral-500 hover:text-brand-orange mb-8 transition-colors"
|
||||
>
|
||||
<ArrowLeftIcon className="w-4 h-4 mr-2" />
|
||||
Back to Integrations
|
||||
</Link>
|
||||
|
||||
<div className="flex items-center gap-4 mb-8">
|
||||
<div className="p-3 bg-neutral-100 dark:bg-neutral-800 rounded-xl">
|
||||
{headerIcon}
|
||||
</div>
|
||||
<h1 className="text-4xl md:text-5xl font-bold text-neutral-900 dark:text-white">
|
||||
{integration.name} Integration
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<div className="prose prose-neutral dark:prose-invert max-w-none">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
268
lib/integrations.tsx
Normal file
268
lib/integrations.tsx
Normal file
@@ -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<IntegrationCategory, string> = {
|
||||
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: (
|
||||
<svg role="img" viewBox="0 0 24 24" className="w-8 h-8 fill-current dark:invert">
|
||||
<path d="M18.665 21.978C16.758 23.255 14.465 24 12 24 5.377 24 0 18.623 0 12S5.377 0 12 0s12 5.377 12 12c0 3.583-1.574 6.801-4.067 9.001L9.219 7.2H7.2v9.596h1.615V9.251l9.85 12.727Zm-3.332-8.533 1.6 2.061V7.2h-1.6v6.245Z" />
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'react',
|
||||
name: 'React',
|
||||
description: 'Integrate Pulse with any React SPA (Create React App, Vite, etc).',
|
||||
category: 'framework',
|
||||
brandColor: '#61DAFB',
|
||||
icon: (
|
||||
<svg role="img" viewBox="0 0 24 24" className="w-8 h-8" style={{ fill: '#61DAFB' }}>
|
||||
<path d="M14.23 12.004a2.236 2.236 0 0 1-2.235 2.236 2.236 2.236 0 0 1-2.236-2.236 2.236 2.236 0 0 1 2.235-2.236 2.236 2.236 0 0 1 2.236 2.236zm2.648-10.69c-1.346 0-3.107.96-4.888 2.622-1.78-1.653-3.542-2.602-4.887-2.602-.41 0-.783.093-1.106.278-1.375.793-1.683 3.264-.973 6.365C1.98 8.917 0 10.42 0 12.004c0 1.59 1.99 3.097 5.043 4.03-.704 3.113-.39 5.588.988 6.38.32.187.69.275 1.102.275 1.345 0 3.107-.96 4.888-2.624 1.78 1.654 3.542 2.603 4.887 2.603.41 0 .783-.09 1.106-.275 1.374-.792 1.683-3.263.973-6.365C22.02 15.096 24 13.59 24 12.004c0-1.59-1.99-3.097-5.043-4.032.704-3.11.39-5.587-.988-6.38-.318-.184-.688-.277-1.092-.278zm-.005 1.09v.006c.225 0 .406.044.558.127.666.382.955 1.835.73 3.704-.054.46-.142.945-.25 1.44-.96-.236-2.006-.417-3.107-.534-.66-.905-1.345-1.727-2.035-2.447 1.592-1.48 3.087-2.292 4.105-2.295zm-9.77.02c1.012 0 2.514.808 4.11 2.28-.686.72-1.37 1.537-2.02 2.442-1.107.117-2.154.298-3.113.538-.112-.49-.195-.964-.254-1.42-.23-1.868.054-3.32.714-3.707.19-.09.4-.127.563-.132zm4.882 3.05c.455.468.91.992 1.36 1.564-.44-.02-.89-.034-1.345-.034-.46 0-.915.01-1.36.034.44-.572.895-1.096 1.345-1.565zM12 8.1c.74 0 1.477.034 2.202.093.406.582.802 1.203 1.183 1.86.372.64.71 1.29 1.018 1.946-.308.655-.646 1.31-1.013 1.95-.38.66-.773 1.288-1.18 1.87-.728.063-1.466.098-2.21.098-.74 0-1.477-.035-2.202-.093-.406-.582-.802-1.204-1.183-1.86-.372-.64-.71-1.29-1.018-1.946.303-.657.646-1.313 1.013-1.954.38-.66.773-1.286 1.18-1.868.728-.064 1.466-.098 2.21-.098zm-3.635.254c-.24.377-.48.763-.704 1.16-.225.39-.435.782-.635 1.174-.265-.656-.49-1.31-.676-1.947.64-.15 1.315-.283 2.015-.386zm7.26 0c.695.103 1.365.23 2.006.387-.18.632-.405 1.282-.66 1.933-.2-.39-.41-.783-.64-1.174-.225-.392-.465-.774-.705-1.146zm3.063.675c.484.15.944.317 1.375.498 1.732.74 2.852 1.708 2.852 2.476-.005.768-1.125 1.74-2.857 2.475-.42.18-.88.342-1.355.493-.28-.958-.646-1.956-1.1-2.98.45-1.017.81-2.01 1.085-2.964zm-13.395.004c.278.96.645 1.957 1.1 2.98-.45 1.017-.812 2.01-1.086 2.964-.484-.15-.944-.318-1.37-.5-1.732-.737-2.852-1.706-2.852-2.474 0-.768 1.12-1.742 2.852-2.476.42-.18.88-.342 1.356-.494zm11.678 4.28c.265.657.49 1.312.676 1.948-.64.157-1.316.29-2.016.39.24-.375.48-.762.705-1.158.225-.39.435-.788.636-1.18zm-9.945.02c.2.392.41.783.64 1.175.23.39.465.772.705 1.143-.695-.102-1.365-.23-2.006-.386.18-.63.406-1.282.66-1.933zM17.92 16.32c.112.493.2.968.254 1.423.23 1.868-.054 3.32-.714 3.708-.147.09-.338.128-.563.128-1.012 0-2.514-.807-4.11-2.28.686-.72 1.37-1.536 2.02-2.44 1.107-.118 2.154-.3 3.113-.54zm-11.83.01c.96.234 2.006.415 3.107.532.66.905 1.345 1.727 2.035 2.446-1.595 1.483-3.092 2.295-4.11 2.295-.22-.005-.406-.05-.553-.132-.666-.38-.955-1.834-.73-3.703.054-.46.142-.944.25-1.438zm4.56.64c.44.02.89.034 1.345.034.46 0 .915-.01 1.36-.034-.44.572-.895 1.095-1.345 1.565-.455-.47-.91-.993-1.36-1.565z" />
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'vue',
|
||||
name: 'Vue.js',
|
||||
description: 'Simple setup for Vue 2 and Vue 3 applications.',
|
||||
category: 'framework',
|
||||
brandColor: '#4FC08D',
|
||||
icon: (
|
||||
<svg role="img" viewBox="0 0 24 24" className="w-8 h-8" style={{ fill: '#4FC08D' }}>
|
||||
<path d="M24,1.61H14.06L12,5.16,9.94,1.61H0L12,22.39ZM12,14.08,5.16,2.23H9.59L12,6.41l2.41-4.18h4.43Z" />
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'angular',
|
||||
name: 'Angular',
|
||||
description: 'Add Pulse analytics to your Angular application with a simple script tag.',
|
||||
category: 'framework',
|
||||
brandColor: '#0F0F11',
|
||||
invertInDark: true,
|
||||
icon: (
|
||||
<svg role="img" viewBox="0 0 24 24" className="w-8 h-8 fill-current dark:invert">
|
||||
<path d="M16.712 17.711H7.288l-1.204 2.916L12 24l5.916-3.373-1.204-2.916ZM14.692 0l7.832 16.855.814-12.856L14.692 0ZM9.308 0 .662 3.999l.814 12.856L9.308 0Zm-.405 13.93h6.198L12 6.396 8.903 13.93Z" />
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'svelte',
|
||||
name: 'Svelte',
|
||||
description: 'Integrate Pulse with Svelte or SvelteKit in under a minute.',
|
||||
category: 'framework',
|
||||
brandColor: '#FF3E00',
|
||||
icon: (
|
||||
<svg role="img" viewBox="0 0 24 24" className="w-8 h-8" style={{ fill: '#FF3E00' }}>
|
||||
<path d="M10.354 21.125a4.44 4.44 0 0 1-4.765-1.767 4.109 4.109 0 0 1-.703-3.107 3.898 3.898 0 0 1 .134-.522l.105-.321.287.21a7.21 7.21 0 0 0 2.186 1.092l.208.063-.02.208a1.253 1.253 0 0 0 .226.83 1.337 1.337 0 0 0 1.435.533 1.231 1.231 0 0 0 .343-.15l5.59-3.562a1.164 1.164 0 0 0 .524-.778 1.242 1.242 0 0 0-.211-.937 1.338 1.338 0 0 0-1.435-.533 1.23 1.23 0 0 0-.343.15l-2.133 1.36a4.078 4.078 0 0 1-1.135.499 4.44 4.44 0 0 1-4.765-1.766 4.108 4.108 0 0 1-.702-3.108 3.855 3.855 0 0 1 1.742-2.582l5.589-3.563a4.072 4.072 0 0 1 1.135-.499 4.44 4.44 0 0 1 4.765 1.767 4.109 4.109 0 0 1 .703 3.107 3.943 3.943 0 0 1-.134.522l-.105.321-.286-.21a7.204 7.204 0 0 0-2.187-1.093l-.208-.063.02-.207a1.255 1.255 0 0 0-.226-.831 1.337 1.337 0 0 0-1.435-.532 1.231 1.231 0 0 0-.343.15L8.62 9.368a1.162 1.162 0 0 0-.524.778 1.24 1.24 0 0 0 .211.937 1.338 1.338 0 0 0 1.435.533 1.235 1.235 0 0 0 .344-.151l2.132-1.36a4.067 4.067 0 0 1 1.135-.498 4.44 4.44 0 0 1 4.765 1.766 4.108 4.108 0 0 1 .702 3.108 3.857 3.857 0 0 1-1.742 2.583l-5.589 3.562a4.072 4.072 0 0 1-1.135.499m10.358-17.95C18.484-.015 14.082-.96 10.9 1.068L5.31 4.63a6.412 6.412 0 0 0-2.896 4.295 6.753 6.753 0 0 0 .666 4.336 6.43 6.43 0 0 0-.96 2.396 6.833 6.833 0 0 0 1.168 5.167c2.229 3.19 6.63 4.135 9.812 2.108l5.59-3.562a6.41 6.41 0 0 0 2.896-4.295 6.756 6.756 0 0 0-.665-4.336 6.429 6.429 0 0 0 .958-2.396 6.831 6.831 0 0 0-1.167-5.168Z" />
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'nuxt',
|
||||
name: 'Nuxt',
|
||||
description: 'Configure Pulse in your Nuxt application via nuxt.config.',
|
||||
category: 'framework',
|
||||
brandColor: '#00DC82',
|
||||
icon: (
|
||||
<svg role="img" viewBox="0 0 24 24" className="w-8 h-8" style={{ fill: '#00DC82' }}>
|
||||
<path d="M13.4642 19.8295h8.9218c.2834 0 .5618-.0723.8072-.2098a1.5899 1.5899 0 0 0 .5908-.5732 1.5293 1.5293 0 0 0 .216-.783 1.529 1.529 0 0 0-.2167-.7828L17.7916 7.4142a1.5904 1.5904 0 0 0-.5907-.573 1.6524 1.6524 0 0 0-.807-.2099c-.2833 0-.5616.0724-.807.2098a1.5904 1.5904 0 0 0-.5907.5731L13.4642 9.99l-2.9954-5.0366a1.5913 1.5913 0 0 0-.591-.573 1.6533 1.6533 0 0 0-.8071-.2098c-.2834 0-.5617.0723-.8072.2097a1.5913 1.5913 0 0 0-.591.573L.2168 17.4808A1.5292 1.5292 0 0 0 0 18.2635c-.0001.2749.0744.545.216.783a1.59 1.59 0 0 0 .5908.5732c.2454.1375.5238.2098.8072.2098h5.6003c2.219 0 3.8554-.9454 4.9813-2.7899l2.7337-4.5922L16.3935 9.99l4.3944 7.382h-5.8586ZM7.123 17.3694l-3.9083-.0009 5.8586-9.8421 2.9232 4.921-1.9572 3.2892c-.7478 1.1967-1.5972 1.6328-2.9163 1.6328z" />
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'remix',
|
||||
name: 'Remix',
|
||||
description: 'Add Pulse to your Remix app via the root route loader.',
|
||||
category: 'framework',
|
||||
brandColor: '#000000',
|
||||
invertInDark: true,
|
||||
icon: (
|
||||
<svg role="img" viewBox="0 0 24 24" className="w-8 h-8 fill-current dark:invert">
|
||||
<path d="M21.511 18.508c.216 2.773.216 4.073.216 5.492H15.31c0-.309.006-.592.011-.878.018-.892.036-1.821-.109-3.698-.19-2.747-1.374-3.358-3.55-3.358H1.574v-5h10.396c2.748 0 4.122-.835 4.122-3.049 0-1.946-1.374-3.125-4.122-3.125H1.573V0h11.541c6.221 0 9.313 2.938 9.313 7.632 0 3.511-2.176 5.8-5.114 6.182 2.48.497 3.93 1.909 4.198 4.694ZM1.573 24v-3.727h6.784c1.133 0 1.379.84 1.379 1.342V24Z" />
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'astro',
|
||||
name: 'Astro',
|
||||
description: 'Integrate Pulse with your Astro site for lightning-fast analytics.',
|
||||
category: 'framework',
|
||||
brandColor: '#BC52EE',
|
||||
icon: (
|
||||
<svg role="img" viewBox="0 0 24 24" className="w-8 h-8" style={{ fill: '#BC52EE' }}>
|
||||
<path d="M8.358 20.162c-1.186-1.07-1.532-3.316-1.038-4.944.856 1.026 2.043 1.352 3.272 1.535 1.897.283 3.76.177 5.522-.678.202-.098.388-.229.608-.36.166.473.209.95.151 1.437-.14 1.185-.738 2.1-1.688 2.794-.38.277-.782.525-1.175.787-1.205.804-1.531 1.747-1.078 3.119l.044.148a3.158 3.158 0 0 1-1.407-1.188 3.31 3.31 0 0 1-.544-1.815c-.004-.32-.004-.642-.048-.958-.106-.769-.472-1.113-1.161-1.133-.707-.02-1.267.411-1.415 1.09-.012.053-.028.104-.045.165h.002zm-5.961-4.445s3.24-1.575 6.49-1.575l2.451-7.565c.092-.366.36-.614.662-.614.302 0 .57.248.662.614l2.45 7.565c3.85 0 6.491 1.575 6.491 1.575L16.088.727C15.93.285 15.663 0 15.303 0H8.697c-.36 0-.615.285-.784.727l-5.516 14.99z" />
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'gatsby',
|
||||
name: 'Gatsby',
|
||||
description: 'Add Pulse to your Gatsby site via gatsby-ssr or a plugin.',
|
||||
category: 'ssg',
|
||||
brandColor: '#663399',
|
||||
icon: (
|
||||
<svg role="img" viewBox="0 0 24 24" className="w-8 h-8" style={{ fill: '#663399' }}>
|
||||
<path d="M12 0C5.4 0 0 5.4 0 12s5.4 12 12 12 12-5.4 12-12S18.6 0 12 0zm0 2.571c3.171 0 5.915 1.543 7.629 3.858l-1.286 1.115C16.886 5.572 14.571 4.286 12 4.286c-3.343 0-6.171 2.143-7.286 5.143l9.857 9.857c2.486-.857 4.373-3 4.973-5.572h-4.115V12h6c0 4.457-3.172 8.228-7.372 9.17L2.83 9.944C3.772 5.743 7.543 2.57 12 2.57zm-9.429 9.6l9.344 9.258c-2.4-.086-4.801-.943-6.601-2.743-1.8-1.8-2.743-4.201-2.743-6.515z" />
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
|
||||
// * ── Static-Site Generators ────────────────────────────────────────────────
|
||||
{
|
||||
id: 'hugo',
|
||||
name: 'Hugo',
|
||||
description: 'Drop the Pulse script into your Hugo partial or base template.',
|
||||
category: 'ssg',
|
||||
brandColor: '#FF4088',
|
||||
icon: (
|
||||
<svg role="img" viewBox="0 0 24 24" className="w-8 h-8" style={{ fill: '#FF4088' }}>
|
||||
<path d="M11.754 0a3.998 3.998 0 00-2.049.596L3.33 4.532a4.252 4.252 0 00-2.017 3.615v8.03c0 1.473.79 2.838 2.067 3.574l6.486 3.733a3.88 3.88 0 003.835.018l7.043-3.966a3.817 3.817 0 001.943-3.323V7.752a3.57 3.57 0 00-1.774-3.084L13.817.541a3.998 3.998 0 00-2.063-.54zm.022 1.674c.413-.006.828.1 1.2.315l7.095 4.127c.584.34.941.96.94 1.635v8.462c0 .774-.414 1.484-1.089 1.864l-7.042 3.966a2.199 2.199 0 01-2.179-.01l-6.485-3.734a2.447 2.447 0 01-1.228-2.123v-8.03c0-.893.461-1.72 1.221-2.19l6.376-3.935a2.323 2.323 0 011.19-.347zm-4.7 3.844V18.37h2.69v-5.62h4.46v5.62h2.696V5.518h-2.696v4.681h-4.46V5.518Z" />
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
|
||||
// * ── CMS & Blogging ───────────────────────────────────────────────────────
|
||||
{
|
||||
id: 'wordpress',
|
||||
name: 'WordPress',
|
||||
description: 'Add the tracking script to your WordPress header or use a plugin.',
|
||||
category: 'cms',
|
||||
brandColor: '#21759B',
|
||||
icon: (
|
||||
<svg role="img" viewBox="0 0 24 24" className="w-8 h-8" style={{ fill: '#21759B' }}>
|
||||
<path d="M21.469 6.825c.84 1.537 1.318 3.3 1.318 5.175 0 3.979-2.156 7.456-5.363 9.325l3.295-9.527c.615-1.54.82-2.771.82-3.864 0-.405-.026-.78-.07-1.11m-7.981.105c.647-.03 1.232-.105 1.232-.105.582-.075.514-.93-.067-.899 0 0-1.755.135-2.88.135-1.064 0-2.85-.15-2.85-.15-.585-.03-.661.855-.075.885 0 0 .54.061 1.125.09l1.68 4.605-2.37 7.08L5.354 6.9c.649-.03 1.234-.1 1.234-.1.585-.075.516-.93-.065-.896 0 0-1.746.138-2.874.138-.2 0-.438-.008-.69-.015C4.911 3.15 8.235 1.215 12 1.215c2.809 0 5.365 1.072 7.286 2.833-.046-.003-.091-.009-.141-.009-1.06 0-1.812.923-1.812 1.914 0 .89.513 1.643 1.06 2.531.411.72.89 1.643.89 2.977 0 .915-.354 1.994-.821 3.479l-1.075 3.585-3.9-11.61.001.014zM12 22.784c-1.059 0-2.081-.153-3.048-.437l3.237-9.406 3.315 9.087c.024.053.05.101.078.149-1.12.393-2.325.609-3.582.609M1.211 12c0-1.564.336-3.05.935-4.39L7.29 21.709C3.694 19.96 1.212 16.271 1.211 12M12 0C5.385 0 0 5.385 0 12s5.385 12 12 12 12-5.385 12-12S18.615 0 12 0" />
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'ghost',
|
||||
name: 'Ghost',
|
||||
description: 'Inject Pulse into your Ghost theme via Code Injection settings.',
|
||||
category: 'cms',
|
||||
brandColor: '#15171A',
|
||||
invertInDark: true,
|
||||
icon: (
|
||||
<svg role="img" viewBox="0 0 24 24" className="w-8 h-8 fill-current dark:invert">
|
||||
<path d="M12 0C5.373 0 0 5.373 0 12s5.373 12 12 12 12-5.373 12-12S18.627 0 12 0zm.256 2.313c2.47.005 5.116 2.008 5.898 2.962l.244.3c1.64 1.994 3.569 4.34 3.569 6.966 0 3.719-2.98 5.808-6.158 7.508-1.433.766-2.98 1.508-4.748 1.508-4.543 0-8.366-3.569-8.366-8.112 0-.706.17-1.425.342-2.15.122-.515.244-1.033.307-1.549.548-4.539 2.967-6.795 8.422-7.408a4.29 4.29 0 01.49-.026Z" />
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
|
||||
// * ── Platforms & Builders ──────────────────────────────────────────────────
|
||||
{
|
||||
id: 'shopify',
|
||||
name: 'Shopify',
|
||||
description: 'Add privacy-first analytics to your Shopify store via theme editor.',
|
||||
category: 'platform',
|
||||
brandColor: '#7AB55C',
|
||||
icon: (
|
||||
<svg role="img" viewBox="0 0 24 24" className="w-8 h-8" style={{ fill: '#7AB55C' }}>
|
||||
<path d="M15.337 23.979l7.216-1.561s-2.604-17.613-2.625-17.73c-.018-.116-.114-.192-.211-.192s-1.929-.136-1.929-.136-1.275-1.274-1.439-1.411c-.045-.037-.075-.057-.121-.074l-.914 21.104h.023zM11.71 11.305s-.81-.424-1.774-.424c-1.447 0-1.504.906-1.504 1.141 0 1.232 3.24 1.715 3.24 4.629 0 2.295-1.44 3.76-3.406 3.76-2.354 0-3.54-1.465-3.54-1.465l.646-2.086s1.245 1.066 2.28 1.066c.675 0 .975-.545.975-.932 0-1.619-2.654-1.694-2.654-4.359-.034-2.237 1.571-4.416 4.827-4.416 1.257 0 1.875.361 1.875.361l-.945 2.715-.02.01zM11.17.83c.136 0 .271.038.405.135-.984.465-2.064 1.639-2.508 3.992-.656.213-1.293.405-1.889.578C7.697 3.75 8.951.84 11.17.84V.83zm1.235 2.949v.135c-.754.232-1.583.484-2.394.736.466-1.777 1.333-2.645 2.085-2.971.193.501.309 1.176.309 2.1zm.539-2.234c.694.074 1.141.867 1.429 1.755-.349.114-.735.231-1.158.366v-.252c0-.752-.096-1.371-.271-1.871v.002zm2.992 1.289c-.02 0-.06.021-.078.021s-.289.075-.714.21c-.423-1.233-1.176-2.37-2.508-2.37h-.115C12.135.209 11.669 0 11.265 0 8.159 0 6.675 3.877 6.21 5.846c-1.194.365-2.063.636-2.16.674-.675.213-.694.232-.772.87-.075.462-1.83 14.063-1.83 14.063L15.009 24l.927-21.166z" />
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'webflow',
|
||||
name: 'Webflow',
|
||||
description: 'Paste the Pulse snippet into your Webflow project custom code.',
|
||||
category: 'platform',
|
||||
brandColor: '#146EF5',
|
||||
icon: (
|
||||
<svg role="img" viewBox="0 0 24 24" className="w-8 h-8" style={{ fill: '#146EF5' }}>
|
||||
<path d="m24 4.515-7.658 14.97H9.149l3.205-6.204h-.144C9.566 16.713 5.621 18.973 0 19.485v-6.118s3.596-.213 5.71-2.435H0V4.515h6.417v5.278l.144-.001 2.622-5.277h4.854v5.244h.144l2.72-5.244H24Z" />
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'squarespace',
|
||||
name: 'Squarespace',
|
||||
description: 'Add Pulse to Squarespace via the Code Injection panel.',
|
||||
category: 'platform',
|
||||
brandColor: '#000000',
|
||||
invertInDark: true,
|
||||
icon: (
|
||||
<svg role="img" viewBox="0 0 24 24" className="w-8 h-8 fill-current dark:invert">
|
||||
<path d="M22.655 8.719c-1.802-1.801-4.726-1.801-6.564 0l-7.351 7.35c-.45.45-.45 1.2 0 1.65.45.449 1.2.449 1.65 0l7.351-7.351c.899-.899 2.362-.899 3.264 0 .9.9.9 2.364 0 3.264l-7.239 7.239c.9.899 2.362.899 3.263 0l5.589-5.589c1.836-1.838 1.836-4.763.037-6.563zm-2.475 2.437c-.451-.45-1.201-.45-1.65 0l-7.354 7.389c-.9.899-2.361.899-3.262 0-.45-.45-1.2-.45-1.65 0s-.45 1.2 0 1.649c1.801 1.801 4.726 1.801 6.564 0l7.351-7.35c.449-.487.449-1.239.001-1.688zm-2.439-7.35c-1.801-1.801-4.726-1.801-6.564 0l-7.351 7.351c-.45.449-.45 1.199 0 1.649s1.2.45 1.65 0l7.395-7.351c.9-.899 2.371-.899 3.27 0 .451.45 1.201.45 1.65 0 .421-.487.421-1.199-.029-1.649h-.021zm-2.475 2.437c-.45-.45-1.2-.45-1.65 0l-7.351 7.389c-.899.9-2.363.9-3.265 0-.9-.899-.9-2.363 0-3.264l7.239-7.239c-.9-.9-2.362-.9-3.263 0L1.35 8.719c-1.8 1.8-1.8 4.725 0 6.563 1.801 1.801 4.725 1.801 6.564 0l7.35-7.351c.451-.488.451-1.238 0-1.688h.002z" />
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'wix',
|
||||
name: 'Wix',
|
||||
description: 'Add Pulse to your Wix site via the Custom Code settings.',
|
||||
category: 'platform',
|
||||
brandColor: '#0C6EFC',
|
||||
icon: (
|
||||
<svg role="img" viewBox="0 0 24 24" className="w-8 h-8" style={{ fill: '#0C6EFC' }}>
|
||||
<path d="m0 7.354 2.113 9.292h.801a1.54 1.54 0 0 0 1.506-1.218l1.351-6.34a.171.171 0 0 1 .167-.137c.08 0 .15.058.167.137l1.352 6.34a1.54 1.54 0 0 0 1.506 1.218h.805l2.113-9.292h-.565c-.62 0-1.159.43-1.296 1.035l-1.26 5.545-1.106-5.176a1.76 1.76 0 0 0-2.19-1.324c-.639.176-1.113.716-1.251 1.365l-1.094 5.127-1.26-5.537A1.33 1.33 0 0 0 .563 7.354H0zm13.992 0a.951.951 0 0 0-.951.95v8.342h.635a.952.952 0 0 0 .951-.95V7.353h-.635zm1.778 0 3.158 4.66-3.14 4.632h1.325c.368 0 .712-.181.918-.486l1.756-2.59a.12.12 0 0 1 .197 0l1.754 2.59c.206.305.55.486.918.486h1.326l-3.14-4.632L24 7.354h-1.326c-.368 0-.712.181-.918.486l-1.772 2.617a.12.12 0 0 1-.197 0L18.014 7.84a1.108 1.108 0 0 0-.918-.486H15.77z" />
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
]
|
||||
|
||||
// * ─── 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)
|
||||
}
|
||||
BIN
public/Icon Padding left & right 192x192.png
Normal file
BIN
public/Icon Padding left & right 192x192.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.8 KiB |
BIN
public/Icon Padding left & right 512x512.png
Normal file
BIN
public/Icon Padding left & right 512x512.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
1
public/sw 2.js
Normal file
1
public/sw 2.js
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user