From 570a84889ad302222ebcfe119fd74618559079d9 Mon Sep 17 00:00:00 2001 From: Usman Baig Date: Fri, 13 Mar 2026 13:43:44 +0100 Subject: [PATCH 1/6] fix: increase hover hitbox on map location markers --- CHANGELOG.md | 1 + components/dashboard/DottedMap.tsx | 26 +++++++++++++------------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e86d4e..109ab31 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), ### Improved - **Cleaner page paths in your reports.** Pages like `/products?_t=123456` or `/about?session=abc` now correctly show as `/products` and `/about`. Only marketing attribution parameters (like UTM tags) are preserved for traffic source tracking — all other junk parameters are automatically removed, so your Top Pages and Journeys stay clean without us having to chase down every new parameter format. +- **Easier to hover country dots on the map.** The orange location markers on the world map are now much easier to interact with — you no longer need pixel-perfect aim to see the tooltip. - **Refreshed chart background.** The dashboard chart now has subtle horizontal lines instead of the old dotted background, giving the chart area a cleaner look with soft faded edges. - **Smoother loading transitions.** When your data finishes loading, the page now fades in smoothly instead of appearing all at once. This applies across Dashboard, Journeys, Funnels, Uptime, Settings, Notifications, and shared dashboards. If your data was already cached from a previous visit, it still loads instantly with no animation — the fade only kicks in when you're actually waiting for fresh data. - **Faster tab switching across the board.** Switching between Settings, Funnels, Uptime, and other tabs now shows your data instantly instead of flashing a loading skeleton every time. Previously visited tabs remember their data and show it right away, while quietly refreshing in the background so you always see the latest numbers without the wait. diff --git a/components/dashboard/DottedMap.tsx b/components/dashboard/DottedMap.tsx index a9be59a..8d6ecf6 100644 --- a/components/dashboard/DottedMap.tsx +++ b/components/dashboard/DottedMap.tsx @@ -118,30 +118,30 @@ export default function DottedMap({ data, className }: DottedMapProps) { const rowIndex = _stagger.yToRowIndex.get(marker.y) ?? 0 const offsetX = rowIndex % 2 === 1 ? _stagger.xStep / 2 : 0 const info = markerData[index] + const cx = marker.x + offsetX + const cy = marker.y return ( - { if (info) { - const rect = (e.target as SVGCircleElement).closest('svg')!.getBoundingClientRect() - const svgX = marker.x + offsetX - const svgY = marker.y + const rect = (e.target as SVGElement).closest('svg')!.getBoundingClientRect() setTooltip({ - x: rect.left + (svgX / MAP_WIDTH) * rect.width, - y: rect.top + (svgY / MAP_HEIGHT) * rect.height, + x: rect.left + (cx / MAP_WIDTH) * rect.width, + y: rect.top + (cy / MAP_HEIGHT) * rect.height, country: info.country, pageviews: info.pageviews, }) } }} onMouseLeave={() => setTooltip(null)} - /> + > + {/* Invisible larger hitbox */} + + {/* Visible dot */} + + ) })} From 58f42f945c5e42737c041b1b198a20d0d8749f5a Mon Sep 17 00:00:00 2001 From: Usman Baig Date: Fri, 13 Mar 2026 13:47:26 +0100 Subject: [PATCH 2/6] style: smooth chart curves with natural spline and add area fill Switch from monotone to natural interpolation for rounder peaks. Add transparent orange gradient area fill beneath the line. --- CHANGELOG.md | 3 ++- components/dashboard/Chart.tsx | 18 ++++++++++++++---- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 109ab31..aaac997 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), - **Cleaner page paths in your reports.** Pages like `/products?_t=123456` or `/about?session=abc` now correctly show as `/products` and `/about`. Only marketing attribution parameters (like UTM tags) are preserved for traffic source tracking — all other junk parameters are automatically removed, so your Top Pages and Journeys stay clean without us having to chase down every new parameter format. - **Easier to hover country dots on the map.** The orange location markers on the world map are now much easier to interact with — you no longer need pixel-perfect aim to see the tooltip. -- **Refreshed chart background.** The dashboard chart now has subtle horizontal lines instead of the old dotted background, giving the chart area a cleaner look with soft faded edges. +- **Smoother chart curves and filled area.** The dashboard chart line now flows with natural curves instead of sharp flat tops at peaks. The area beneath the line is filled with a soft transparent orange gradient that fades toward the bottom, making trends easier to read at a glance. +- **Refreshed chart background.** The dashboard chart now has subtle horizontal lines instead of the old dotted background, giving the chart area a cleaner look. - **Smoother loading transitions.** When your data finishes loading, the page now fades in smoothly instead of appearing all at once. This applies across Dashboard, Journeys, Funnels, Uptime, Settings, Notifications, and shared dashboards. If your data was already cached from a previous visit, it still loads instantly with no animation — the fade only kicks in when you're actually waiting for fresh data. - **Faster tab switching across the board.** Switching between Settings, Funnels, Uptime, and other tabs now shows your data instantly instead of flashing a loading skeleton every time. Previously visited tabs remember their data and show it right away, while quietly refreshing in the background so you always see the latest numbers without the wait. - **Smoother loading on the Journeys page.** The Journeys tab now shows a polished skeleton placeholder while data loads, matching the loading experience on other tabs. diff --git a/components/dashboard/Chart.tsx b/components/dashboard/Chart.tsx index 50062ab..0af1c67 100644 --- a/components/dashboard/Chart.tsx +++ b/components/dashboard/Chart.tsx @@ -2,7 +2,7 @@ import { useState, useMemo, useRef, useCallback, useEffect } from 'react' import { useTheme } from '@ciphera-net/ui' -import { CartesianGrid, Line, LineChart, XAxis, YAxis, ReferenceLine } from 'recharts' +import { Area, CartesianGrid, ComposedChart, Line, XAxis, YAxis, ReferenceLine } from 'recharts' import { ChartContainer, ChartTooltip, type ChartConfig } from '@/components/ui/line-charts-6' import { Card, CardContent, CardHeader } from '@/components/ui/card' import { formatNumber, formatDuration, formatUpdatedAgo, DatePicker } from '@ciphera-net/ui' @@ -456,12 +456,16 @@ export default function Chart({ config={chartConfig} className="h-96 w-full overflow-visible [&_.recharts-curve.recharts-tooltip-cursor]:stroke-[initial]" > - + + + + - + )} From 87f5905bd60859c2190249f7a7afd081adaa3a2c Mon Sep 17 00:00:00 2001 From: Usman Baig Date: Fri, 13 Mar 2026 13:50:27 +0100 Subject: [PATCH 3/6] fix: clip chart overflow from natural spline overshoot --- components/dashboard/Chart.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/dashboard/Chart.tsx b/components/dashboard/Chart.tsx index 0af1c67..c68b696 100644 --- a/components/dashboard/Chart.tsx +++ b/components/dashboard/Chart.tsx @@ -451,7 +451,7 @@ export default function Chart({

) : ( -
+
Date: Fri, 13 Mar 2026 13:53:38 +0100 Subject: [PATCH 4/6] fix: switch from natural to bump interpolation to prevent overshoot --- components/dashboard/Chart.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/dashboard/Chart.tsx b/components/dashboard/Chart.tsx index c68b696..a685190 100644 --- a/components/dashboard/Chart.tsx +++ b/components/dashboard/Chart.tsx @@ -451,7 +451,7 @@ export default function Chart({

) : ( -
+
Date: Fri, 13 Mar 2026 14:30:01 +0100 Subject: [PATCH 5/6] style: add skeleton loading & fade transition to behavior page --- CHANGELOG.md | 2 +- app/sites/[id]/behavior/page.tsx | 8 +++++- components/skeletons.tsx | 49 ++++++++++++++++++++++++++++++++ 3 files changed, 57 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aaac997..dbdea1a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), - **Easier to hover country dots on the map.** The orange location markers on the world map are now much easier to interact with — you no longer need pixel-perfect aim to see the tooltip. - **Smoother chart curves and filled area.** The dashboard chart line now flows with natural curves instead of sharp flat tops at peaks. The area beneath the line is filled with a soft transparent orange gradient that fades toward the bottom, making trends easier to read at a glance. - **Refreshed chart background.** The dashboard chart now has subtle horizontal lines instead of the old dotted background, giving the chart area a cleaner look. -- **Smoother loading transitions.** When your data finishes loading, the page now fades in smoothly instead of appearing all at once. This applies across Dashboard, Journeys, Funnels, Uptime, Settings, Notifications, and shared dashboards. If your data was already cached from a previous visit, it still loads instantly with no animation — the fade only kicks in when you're actually waiting for fresh data. +- **Smoother loading transitions.** When your data finishes loading, the page now fades in smoothly instead of appearing all at once. This applies across Dashboard, Journeys, Funnels, Behavior, Uptime, Settings, Notifications, and shared dashboards. If your data was already cached from a previous visit, it still loads instantly with no animation — the fade only kicks in when you're actually waiting for fresh data. - **Faster tab switching across the board.** Switching between Settings, Funnels, Uptime, and other tabs now shows your data instantly instead of flashing a loading skeleton every time. Previously visited tabs remember their data and show it right away, while quietly refreshing in the background so you always see the latest numbers without the wait. - **Smoother loading on the Journeys page.** The Journeys tab now shows a polished skeleton placeholder while data loads, matching the loading experience on other tabs. - **Consistent chart colors.** All dashboard charts — Unique Visitors, Total Pageviews, Bounce Rate, and Visit Duration — now use the same brand orange color for a cleaner, more cohesive look. diff --git a/app/sites/[id]/behavior/page.tsx b/app/sites/[id]/behavior/page.tsx index a172a39..eab1f50 100644 --- a/app/sites/[id]/behavior/page.tsx +++ b/app/sites/[id]/behavior/page.tsx @@ -11,6 +11,7 @@ import FrustrationTable from '@/components/behavior/FrustrationTable' import FrustrationByPageTable from '@/components/behavior/FrustrationByPageTable' import FrustrationTrend from '@/components/behavior/FrustrationTrend' import { useDashboard, useBehavior } from '@/lib/swr/dashboard' +import { BehaviorSkeleton, useMinimumLoading, useSkeletonFade } from '@/components/skeletons' const ScrollDepth = dynamic(() => import('@/components/dashboard/ScrollDepth')) @@ -42,6 +43,9 @@ export default function BehaviorPage() { // Fetch dashboard data for scroll depth (goal_counts + stats) const { data: dashboard } = useDashboard(siteId, dateRange.start, dateRange.end) + const showSkeleton = useMinimumLoading(loading && !behavior) + const fadeClass = useSkeletonFade(showSkeleton) + useEffect(() => { const domain = dashboard?.site?.domain document.title = domain ? `Behavior · ${domain} | Pulse` : 'Behavior | Pulse' @@ -63,8 +67,10 @@ export default function BehaviorPage() { const deadClicks = behavior?.dead_clicks ?? { items: [], total: 0 } const byPage = behavior?.by_page ?? [] + if (showSkeleton) return + return ( -
+
{/* Header */}
diff --git a/components/skeletons.tsx b/components/skeletons.tsx index 8e9483a..52d6e92 100644 --- a/components/skeletons.tsx +++ b/components/skeletons.tsx @@ -370,6 +370,55 @@ export function PricingCardsSkeleton() { ) } +// ─── Behavior page skeleton ───────────────────────────────── + +export function BehaviorSkeleton() { + return ( +
+ {/* Header */} +
+
+ + +
+ +
+ + {/* Summary cards (3 cols) */} +
+ {Array.from({ length: 3 }).map((_, i) => ( +
+ + + +
+ ))} +
+ + {/* Rage clicks + Dead clicks side by side */} +
+ + +
+ + {/* By-page table */} +
+
+ + + +
+
+ + {/* Scroll depth + Frustration trend */} +
+ + +
+
+ ) +} + // ─── Organization settings skeleton (members, billing, etc) ─ export function MembersListSkeleton() { From 2a2a64f6d76de4952043358bd0bee718b0569049 Mon Sep 17 00:00:00 2001 From: Usman Baig Date: Fri, 13 Mar 2026 14:40:41 +0100 Subject: [PATCH 6/6] feat: add sitemap.xml, robots.txt, and llms.txt for SEO --- app/robots.ts | 50 +++++++++++++++++++++++++++++++++++++++++++++++++ app/sitemap.ts | 38 +++++++++++++++++++++++++++++++++++++ public/llms.txt | 50 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 138 insertions(+) create mode 100644 app/robots.ts create mode 100644 app/sitemap.ts create mode 100644 public/llms.txt diff --git a/app/robots.ts b/app/robots.ts new file mode 100644 index 0000000..888f8bb --- /dev/null +++ b/app/robots.ts @@ -0,0 +1,50 @@ +import type { MetadataRoute } from 'next' + +export default function robots(): MetadataRoute.Robots { + return { + rules: [ + { + userAgent: '*', + allow: [ + '/', + '/about', + '/features', + '/pricing', + '/faq', + '/changelog', + '/installation', + '/integrations', + ], + disallow: [ + '/api/', + '/admin/', + '/sites/', + '/notifications/', + '/onboarding/', + '/org-settings/', + '/welcome/', + '/auth/', + '/actions/', + '/share/', + ], + }, + { + userAgent: 'GPTBot', + disallow: ['/'], + }, + { + userAgent: 'ChatGPT-User', + disallow: ['/'], + }, + { + userAgent: 'Google-Extended', + disallow: ['/'], + }, + { + userAgent: 'CCBot', + disallow: ['/'], + }, + ], + sitemap: 'https://pulse.ciphera.net/sitemap.xml', + } +} diff --git a/app/sitemap.ts b/app/sitemap.ts new file mode 100644 index 0000000..0a31eda --- /dev/null +++ b/app/sitemap.ts @@ -0,0 +1,38 @@ +import type { MetadataRoute } from 'next' + +const BASE_URL = 'https://pulse.ciphera.net' + +export default function sitemap(): MetadataRoute.Sitemap { + const integrationSlugs = [ + 'nextjs', + 'react', + 'vue', + 'wordpress', + ] + + const publicRoutes = [ + { url: '', priority: 1.0, changeFrequency: 'weekly' as const }, + { url: '/about', priority: 0.8, changeFrequency: 'monthly' as const }, + { url: '/features', priority: 0.9, changeFrequency: 'monthly' as const }, + { url: '/pricing', priority: 0.9, changeFrequency: 'monthly' as const }, + { url: '/faq', priority: 0.7, changeFrequency: 'monthly' as const }, + { url: '/changelog', priority: 0.6, changeFrequency: 'weekly' as const }, + { url: '/installation', priority: 0.8, changeFrequency: 'monthly' as const }, + { url: '/integrations', priority: 0.8, changeFrequency: 'monthly' as const }, + ] + + const integrationRoutes = integrationSlugs.map((slug) => ({ + url: `/integrations/${slug}`, + priority: 0.7, + changeFrequency: 'monthly' as const, + })) + + const allRoutes = [...publicRoutes, ...integrationRoutes] + + return allRoutes.map((route) => ({ + url: `${BASE_URL}${route.url}`, + lastModified: new Date(), + changeFrequency: route.changeFrequency, + priority: route.priority, + })) +} diff --git a/public/llms.txt b/public/llms.txt new file mode 100644 index 0000000..3aa4d2b --- /dev/null +++ b/public/llms.txt @@ -0,0 +1,50 @@ +# Pulse by Ciphera +> Privacy-first web analytics. No cookies, no fingerprinting, no personal data collection. GDPR compliant by architecture. + +Pulse is a lightweight, privacy-focused website analytics platform built by Ciphera. It provides meaningful traffic insights without compromising visitor privacy. The tracking script is under 2KB and requires no cookie banners. + +## Key Features +- No cookies, no fingerprinting, no personal data collection +- GDPR, CCPA, and PECR compliant by design — no consent banners needed +- Lightweight tracking script (under 2KB, no impact on page speed) +- Real-time dashboard with pageviews, visitors, referrers, and geographic data +- UTM campaign tracking and custom event tracking +- Public/shared dashboard support +- Organization and team management +- Scheduled email reports + +## Integrations +- [Next.js](https://pulse.ciphera.net/integrations/nextjs) +- [React](https://pulse.ciphera.net/integrations/react) +- [Vue](https://pulse.ciphera.net/integrations/vue) +- [WordPress](https://pulse.ciphera.net/integrations/wordpress) +- Works with any website via a single script tag + +## Installation +Add one script tag to your site: +```html + +``` + +## Pages +- [Home](https://pulse.ciphera.net): Product overview and dashboard +- [Features](https://pulse.ciphera.net/features): Full feature list +- [Pricing](https://pulse.ciphera.net/pricing): Plans and pricing +- [Installation](https://pulse.ciphera.net/installation): Setup guide +- [Integrations](https://pulse.ciphera.net/integrations): Framework-specific guides +- [FAQ](https://pulse.ciphera.net/faq): Frequently asked questions +- [About](https://pulse.ciphera.net/about): About Pulse and Ciphera +- [Changelog](https://pulse.ciphera.net/changelog): Release notes and updates +- [Documentation](https://pulse.ciphera.net/docs): Full API and usage docs + +## About Ciphera +- Founded: 2024, Diegem, Belgium +- Infrastructure: Swiss-hosted servers (FADP protected) +- Open source: https://github.com/ciphera-net +- Website: https://ciphera.net +- Contact: hello@ciphera.net + +## Policies +- Privacy policy: https://ciphera.net/privacy +- Terms of service: https://ciphera.net/terms +- AI training on this site's content is not permitted. Search indexing is allowed.