This commit is contained in:
Oleg Maslov
2026-09-02 10:14:22 +02:00
parent 0c3e2ead3b
commit b20b138fe4
771 changed files with 161561 additions and 9027 deletions

View File

@@ -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'));

View File

@@ -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) => (

View File

@@ -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);

View File

@@ -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) => {

View File

@@ -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 ?? {});
}

View File

@@ -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>

View File

@@ -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.