moving
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useState, type CSSProperties, type ReactNode } from 'react';
|
||||
import { useSyncExternalStore, type CSSProperties, type ReactNode } from 'react';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { detectOSFromUserAgent, type OSId } from '../_lib/os-detection';
|
||||
import { emit, events } from '../_lib/event-taxonomy';
|
||||
@@ -15,6 +15,10 @@ interface DownloadCTAProps {
|
||||
|
||||
const DOWNLOAD_URL = '/download';
|
||||
|
||||
const subscribeToClientEnvironment = () => () => {};
|
||||
const getClientOS = () => detectOSFromUserAgent(navigator.userAgent);
|
||||
const getServerOS = (): null => null;
|
||||
|
||||
/**
|
||||
* OS-aware download CTA. Renders a generic "Download" label at SSR + first
|
||||
* paint, then swaps to "Download for {os}" after hydration via
|
||||
@@ -33,13 +37,11 @@ export default function DownloadCTA({
|
||||
style,
|
||||
}: DownloadCTAProps) {
|
||||
const t = useTranslations('landing.download_cta');
|
||||
const [os, setOS] = useState<OSId | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof navigator !== 'undefined') {
|
||||
setOS(detectOSFromUserAgent(navigator.userAgent));
|
||||
}
|
||||
}, []);
|
||||
const os = useSyncExternalStore<OSId | null>(
|
||||
subscribeToClientEnvironment,
|
||||
getClientOS,
|
||||
getServerOS,
|
||||
);
|
||||
|
||||
const label = children ?? (os ? t('with_os', { os }) : t('default'));
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import Link from 'next/link';
|
||||
import { SignInButton, UserButton, Show } from '@clerk/nextjs';
|
||||
import BrandMark from './BrandMark';
|
||||
import DownloadCTA from './DownloadCTA';
|
||||
@@ -47,9 +48,9 @@ export default function Navbar() {
|
||||
return (
|
||||
<header className={headerClass}>
|
||||
<div className={styles.inner}>
|
||||
<a href="/" className={styles.brand} aria-label={t('aria.home')}>
|
||||
<Link href="/" className={styles.brand} aria-label={t('aria.home')}>
|
||||
<BrandMark withWordmark />
|
||||
</a>
|
||||
</Link>
|
||||
|
||||
<nav className={styles.nav} aria-label={t('aria.primary')}>
|
||||
{NAV_ITEMS.map((item) => (
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useCallback, useState, useSyncExternalStore } from 'react';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import DownloadCTA from './DownloadCTA';
|
||||
import { emit, events } from '../_lib/event-taxonomy';
|
||||
@@ -59,15 +59,19 @@ const STRIPE_ENDPOINT =
|
||||
|
||||
const KVARK_URL = 'https://www.kvark.ai';
|
||||
|
||||
const subscribeToLocation = () => () => {};
|
||||
const getCheckoutCancelled = () =>
|
||||
new URLSearchParams(window.location.search).get('checkout') === 'cancelled';
|
||||
const getServerCheckoutCancelled = () => false;
|
||||
|
||||
export default function Pricing() {
|
||||
const t = useTranslations('landing.pricing');
|
||||
const [billing, setBilling] = useState<BillingPeriod>('monthly');
|
||||
const [checkoutCancelled, setCheckoutCancelled] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
setCheckoutCancelled(params.get('checkout') === 'cancelled');
|
||||
}, []);
|
||||
const checkoutCancelled = useSyncExternalStore(
|
||||
subscribeToLocation,
|
||||
getCheckoutCancelled,
|
||||
getServerCheckoutCancelled,
|
||||
);
|
||||
|
||||
const handleBillingChange = useCallback((mode: BillingPeriod) => {
|
||||
setBilling(mode);
|
||||
|
||||
@@ -41,8 +41,8 @@ export default function Reveal({
|
||||
const node = nodeRef.current;
|
||||
if (!node) return;
|
||||
if (typeof IntersectionObserver === 'undefined') {
|
||||
setVisible(true);
|
||||
return;
|
||||
const fallback = window.setTimeout(() => setVisible(true), 0);
|
||||
return () => window.clearTimeout(fallback);
|
||||
}
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
|
||||
@@ -19,6 +19,5 @@ export const events = {
|
||||
|
||||
export function emit(event: LandingEvent): void {
|
||||
if (process.env.NODE_ENV === 'production') return;
|
||||
// eslint-disable-next-line no-console
|
||||
console.info('[landing.event]', event.name, event.properties ?? {});
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { readFileSync } from 'node:fs';
|
||||
import { resolve } from 'node:path';
|
||||
import type { CSSProperties } from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import Link from 'next/link';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
import BrandMark from '../../_components/BrandMark';
|
||||
@@ -41,11 +42,11 @@ export default function MethodologyPage() {
|
||||
return (
|
||||
<main style={pageStyle}>
|
||||
<header style={headerStyle}>
|
||||
<a href="/" style={brandLinkStyle}>
|
||||
<Link href="/" style={brandLinkStyle}>
|
||||
<BrandMark withWordmark />
|
||||
<span style={separatorStyle}>·</span>
|
||||
<span style={crumbStyle}>Methodology</span>
|
||||
</a>
|
||||
</Link>
|
||||
</header>
|
||||
|
||||
<article style={articleStyle} className="methodology-prose">
|
||||
@@ -53,9 +54,9 @@ export default function MethodologyPage() {
|
||||
</article>
|
||||
|
||||
<footer style={footerStyle}>
|
||||
<a href="/" style={backLinkStyle}>
|
||||
<Link href="/" style={backLinkStyle}>
|
||||
← Back to Waggle
|
||||
</a>
|
||||
</Link>
|
||||
</footer>
|
||||
|
||||
<style>{scopedCss}</style>
|
||||
|
||||
@@ -218,7 +218,6 @@ export default function RootLayout({ children }: { children: ReactNode }) {
|
||||
<meta name="twitter:image" content={META_OG_IMAGE} />
|
||||
<script
|
||||
type="application/ld+json"
|
||||
// eslint-disable-next-line react/no-danger
|
||||
dangerouslySetInnerHTML={{
|
||||
// Static compile-time object (no user input); escape `<` per the
|
||||
// standard JSON-LD embedding guidance to rule out </script> breaks.
|
||||
|
||||
Reference in New Issue
Block a user