Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion resources/css/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,14 @@
--text-xs--line-height: 1.5;
--text-xs--letter-spacing: 0.0005em;
--text-sm: 0.875rem;
--text-sm--line-height: 1.55;
/*
* 1.625, not the 1.55 the curve suggests. Ninety-four elements overrode it
* with `leading-relaxed`, and the overrides were right: `text-sm` is the
* site's body copy rung, and 14px prose wants more air than a 14px table
* cell. A scale that every prose paragraph has to argue with is not a scale,
* so the scale changed rather than the ninety-four.
*/
--text-sm--line-height: 1.625;
--text-sm--letter-spacing: -0.0062em;
--text-base: 1rem;
--text-base--line-height: 1.6;
Expand Down
55 changes: 5 additions & 50 deletions resources/js/components/landing/agents-mcp.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import { useEffect, useRef, useState } from 'react';
import { Check, Copy } from 'lucide-react';
import { toast } from 'sonner';
import { CodeBlock } from '@/components/ui/code';
import CopyButton from '@/components/ui/copy-button';
import Container from '@/components/ui/container';
import { FullLine } from '@/components/ui/full-line';
import { Ledger, LedgerRow } from '@/components/ui/ledger';
import SectionShell from '@/components/ui/section-shell';
import { relocatedFaq } from '@/data/home-faqs';
import { cellBorders, type ColumnMap } from '@/components/ui/grid-cell';
import { ITEM_TITLE, cellBorders, type ColumnMap } from '@/components/ui/grid-cell';

/** Kept byte-identical to the tokenized block below, since this is what gets copied. */
const CONFIG_JSON = `{
Expand Down Expand Up @@ -70,49 +68,6 @@ function Tool({ children }: { children: string }) {
return <span className="font-mono text-sm">{children}</span>;
}

function CopyButton() {
const [copied, setCopied] = useState(false);
const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);

useEffect(
() => () => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
},
[],
);

async function handleCopy() {
try {
await navigator.clipboard.writeText(CONFIG_JSON);
toast('Copied');
setCopied(true);

if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
timeoutRef.current = setTimeout(() => setCopied(false), 1200);
} catch {
toast.error('Could not copy. Select the text and copy it by hand.');
}
}

return (
<button
type="button"
onClick={handleCopy}
aria-label="Copy configuration"
className="inline-flex size-7 items-center justify-center rounded-key text-muted-foreground transition-colors hover:text-foreground"
>
{copied ? (
<Check className="size-4 text-primary-strong" strokeWidth={1.75} aria-hidden="true" />
) : (
<Copy className="size-4" strokeWidth={1.75} aria-hidden="true" />
)}
</button>
);
}

const aiSafetyFaq = relocatedFaq('Can the AI drop a table on production?');

Expand All @@ -139,7 +94,7 @@ export default function AgentsMcp() {
<span className="font-mono text-2xs text-muted-foreground">
claude_desktop_config.json
</span>
<CopyButton />
<CopyButton value={CONFIG_JSON} label="the MCP configuration" />
</div>

<CodeBlock label="MCP client configuration">
Expand Down Expand Up @@ -229,8 +184,8 @@ export default function AgentsMcp() {
*/}
<Container>
<div className="border-l-2 border-primary p-4 sm:p-6">
<h3 className="text-base font-semibold text-foreground">{aiSafetyFaq.question}</h3>
<p className="mt-2 max-w-3xl text-sm leading-relaxed text-muted-foreground">
<h3 className={ITEM_TITLE}>{aiSafetyFaq.question}</h3>
<p className="mt-2 max-w-3xl text-sm text-muted-foreground">
{aiSafetyFaq.answer}
</p>
</div>
Expand Down
6 changes: 3 additions & 3 deletions resources/js/components/landing/architecture.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ export default function Architecture() {
>
<FullLine />
<Container>
<p className="max-w-4xl px-4 py-5 text-base leading-relaxed text-muted-foreground text-pretty">
<p className="max-w-4xl px-4 py-5 text-base text-muted-foreground text-pretty">
Native macOS database clients come in three shapes today. Single database and open source, like
Sequel Ace and Postico. Multi database and closed source, like TablePlus. Multi database but not
native, like DBeaver on the JVM or Beekeeper Studio and DBGate on Electron.{' '}
Expand All @@ -95,7 +95,7 @@ export default function Architecture() {
<h4 className="font-mono text-2xs font-semibold tracking-widest text-primary-strong uppercase">
{material.label}
</h4>
<p className="mt-3 text-sm leading-relaxed text-muted-foreground text-pretty">
<p className="mt-3 text-sm text-muted-foreground text-pretty">
{material.body}
</p>
</div>
Expand All @@ -116,7 +116,7 @@ export default function Architecture() {
className={`${CELL_DENSITY.default} ${cellBorders(i, COLS, BEHAVIOURS.length)} border-rule`}
>
<h4 className="text-base font-semibold text-pretty">{behaviour.title}</h4>
<p className="mt-3 text-sm leading-relaxed text-muted-foreground text-pretty">
<p className="mt-3 text-sm text-muted-foreground text-pretty">
{behaviour.body}
</p>
</div>
Expand Down
49 changes: 49 additions & 0 deletions resources/js/components/landing/closing-cta.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { ReactNode } from 'react';
import Button from '@/components/ui/button';
import Container from '@/components/ui/container';
import { FullLine } from '@/components/ui/full-line';
import { AppleGlyph } from '@/components/ui/glyph';

interface ClosingCtaProps {
/**
* Anything to place beside the download button — a secondary link, a second
* platform. Given one, the row goes horizontal from sm.
*/
children?: ReactNode;
}

/**
* The block that closes a comparison or database page.
*
* It existed twice, differing only in one sentence and in whether its button
* row could hold a second control — so the two pages disagreed on the OS
* requirement line, which is the sentence a reader actually checks before
* downloading. It says the same thing on both now, and it says the thing every
* credible Mac app puts under its download button: the OS floor and the
* architectures.
*/
export default function ClosingCta({ children }: ClosingCtaProps) {
return (
<>
<div className="h-12 sm:h-16 lg:h-24" />

<Container>
<FullLine />
<div className="px-4 py-12 text-center sm:py-16">
<h2 className="text-3xl font-bold sm:text-4xl">Try TablePro for free.</h2>
<p className="mt-3 text-muted-foreground">
Free and open source. macOS 14+, Apple Silicon and Intel. No account.
</p>
<div className="mt-6 flex flex-col items-center gap-3 sm:flex-row sm:justify-center">
<Button href="/download">
<AppleGlyph />
Download for Mac
</Button>
{children}
</div>
</div>
<FullLine />
</Container>
</>
);
}
2 changes: 1 addition & 1 deletion resources/js/components/landing/database-grid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ export default function DatabaseGrid() {
>
<span className="flex size-10 items-center justify-center">
<Plus
className="size-7 text-muted-foreground opacity-40 transition-opacity group-hover:opacity-100"
className="size-7 text-muted-foreground opacity-40 transition-opacity group-hover:opacity-100 group-focus-within:opacity-100"
strokeWidth={1.5}
aria-hidden="true"
/>
Expand Down
2 changes: 1 addition & 1 deletion resources/js/components/landing/depth-grid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export default function DepthGrid() {
{String(i + 1).padStart(2, '0')}
</p>
<h3 className="mt-3 text-base font-semibold">{item.title}</h3>
<p className="mt-2 text-sm leading-relaxed text-muted-foreground">{item.body}</p>
<p className="mt-2 text-sm text-muted-foreground">{item.body}</p>
</GridCell>
))}
</div>
Expand Down
49 changes: 8 additions & 41 deletions resources/js/components/landing/footer-cta.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ import { useEffect, useRef, useState } from 'react';
import { Check, Copy } from 'lucide-react';
import { toast } from 'sonner';
import { buttonClasses } from '@/components/ui/button';
import CopyButton from '@/components/ui/copy-button';
import Container from '@/components/ui/container';
import { FullLine } from '@/components/ui/full-line';
import { cellBorders, GridCell, type ColumnMap } from '@/components/ui/grid-cell';
import { PANEL_TITLE, cellBorders, GridCell, type ColumnMap } from '@/components/ui/grid-cell';
import SectionShell from '@/components/ui/section-shell';
import Button from '@/components/ui/button';
import { AppleGlyph } from '@/components/ui/glyph';
Expand Down Expand Up @@ -97,7 +98,6 @@ const SUBMIT_CLASS = buttonClasses('primary', 'sm', 'shrink-0 disabled:opacity-5
export default function FooterCTA() {
const [showBeta, setShowBeta] = useState(false);
const [subscriberCount, setSubscriberCount] = useState<number | null>(null);
const [copied, setCopied] = useState(false);
const copyTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const statsAnchorRef = useRef<HTMLDivElement>(null);
const betaForm = useEmailForm('/beta/signup');
Expand Down Expand Up @@ -149,14 +149,6 @@ export default function FooterCTA() {
};
}, []);

useEffect(
() => () => {
if (copyTimeoutRef.current) {
clearTimeout(copyTimeoutRef.current);
}
},
[],
);

function trackEvent(name: string, payload: Record<string, string>) {
if (typeof window === 'undefined') {
Expand All @@ -172,20 +164,6 @@ export default function FooterCTA() {
}
}

async function handleCopyBrew() {
try {
await navigator.clipboard.writeText(BREW_COMMAND);
toast('Copied');
setCopied(true);

if (copyTimeoutRef.current) {
clearTimeout(copyTimeoutRef.current);
}
copyTimeoutRef.current = setTimeout(() => setCopied(false), 1200);
} catch {
toast.error('Could not copy. Select the text and copy it by hand.');
}
}

function handleBetaSubmit(event: React.FormEvent) {
event.preventDefault();
Expand All @@ -209,8 +187,8 @@ export default function FooterCTA() {
<Container>
<div className="grid grid-cols-1 items-start sm:grid-cols-2">
<GridCell className={`p-6 sm:p-8 ${cellBorders(0, COLS, 2)}`}>
<h3 className="text-lg font-semibold text-foreground">Download</h3>
<p className="mt-2 text-sm leading-relaxed text-muted-foreground">
<h3 className={PANEL_TITLE}>Download</h3>
<p className="mt-2 text-sm text-muted-foreground">
macOS 14 or later. Apple Silicon and Intel. About 20 MB.
</p>

Expand All @@ -237,21 +215,10 @@ export default function FooterCTA() {
<code tabIndex={0} className="min-w-0 flex-1 overflow-x-auto rounded-lg border border-rule px-3 py-2 font-mono text-xs whitespace-nowrap text-muted-foreground">
{BREW_COMMAND}
</code>
<button
type="button"
onClick={handleCopyBrew}
aria-label={`Copy ${BREW_COMMAND}`}
className="inline-flex size-9 shrink-0 items-center justify-center rounded-lg border border-rule text-muted-foreground transition-colors hover:text-foreground"
>
{copied ? (
<Check className="size-4 text-primary-strong" strokeWidth={1.75} aria-hidden="true" />
) : (
<Copy className="size-4" strokeWidth={1.75} aria-hidden="true" />
)}
</button>
<CopyButton value={BREW_COMMAND} label="the Homebrew command" className="size-9 rounded-lg border border-rule" />
</div>

<p className="mt-4 text-sm leading-relaxed text-muted-foreground">
<p className="mt-4 text-sm text-muted-foreground">
With no connection to hand,{' '}
<span className="font-mono text-xs">File &gt; Try Sample Database</span> opens a bundled
Chinook SQLite.
Expand Down Expand Up @@ -297,8 +264,8 @@ export default function FooterCTA() {

<GridCell className={`p-6 sm:p-8 ${cellBorders(1, COLS, 2)}`}>
<div ref={statsAnchorRef}>
<h3 className="text-lg font-semibold text-foreground">Stay updated</h3>
<p className="mt-2 text-sm leading-relaxed text-muted-foreground">
<h3 className={PANEL_TITLE}>Stay updated</h3>
<p className="mt-2 text-sm text-muted-foreground">
Release notes and database notes. About one email a month, unsubscribe any time.
</p>
{subscriberCount !== null && subscriberCount > 0 && (
Expand Down
2 changes: 1 addition & 1 deletion resources/js/components/landing/objection-row.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export default function ObjectionRow() {
className={`p-6 sm:p-8 ${cellBorders(i, COLS, QUESTIONS.length)} border-rule`}
>
<h2 className="text-base font-semibold text-foreground">{faq.question}</h2>
<p className="mt-2 text-sm leading-relaxed text-muted-foreground">{faq.answer}</p>
<p className="mt-2 text-sm text-muted-foreground">{faq.answer}</p>
</div>
);
})}
Expand Down
2 changes: 1 addition & 1 deletion resources/js/components/landing/open-source.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export default function OpenSource({ latestRelease }: Props) {
<Container>
<div className="lg:grid lg:grid-cols-[1fr_18rem]">
<div className="border-rule max-lg:border-b lg:border-r">
<div className="max-w-2xl space-y-4 p-6 text-base leading-relaxed text-muted-foreground text-pretty sm:p-8">
<div className="max-w-2xl space-y-4 p-6 text-base text-muted-foreground text-pretty sm:p-8">
<p>
TablePro is AGPLv3. All 25 databases, the SQL editor, the data grid, the AI assistant,
the MCP server, Safe Mode with Touch ID, SSH tunnels, ER diagrams, the server dashboard,
Expand Down
5 changes: 3 additions & 2 deletions resources/js/components/landing/pricing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { Ledger, LedgerRow } from '@/components/ui/ledger';
import SectionShell from '@/components/ui/section-shell';
import { Availability, CheckGlyph } from '@/components/ui/glyph';
import { PROSE_LINK } from '@/components/ui/prose-link';
import { PANEL_TITLE } from '@/components/ui/grid-cell';

type BillingCycle = 'monthly' | 'yearly' | 'lifetime';

Expand Down Expand Up @@ -153,7 +154,7 @@ function PricingCard({ tier, cycle, discountCode, discount, paymentProvider, tea
return (
<div className={`flex h-full flex-col border-rule p-6 sm:p-8 ${tier.featured ? 'bg-primary/5' : ''}`}>
<div>
<h3 className="text-lg font-semibold text-foreground">{tier.name}</h3>
<h3 className={PANEL_TITLE}>{tier.name}</h3>
<p className="mt-1 text-sm text-muted-foreground">{tier.description}</p>
</div>

Expand Down Expand Up @@ -225,7 +226,7 @@ function PricingCard({ tier, cycle, discountCode, discount, paymentProvider, tea

<div className="mt-6">
{tier.summary ? (
<p className="text-sm leading-relaxed text-muted-foreground">
<p className="text-sm text-muted-foreground">
{tier.summary}
</p>
) : (
Expand Down
2 changes: 1 addition & 1 deletion resources/js/components/landing/safety.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ export default function Safety() {
<h3 className="font-mono text-2xs font-semibold tracking-widest text-primary-strong uppercase">
{cell.label}
</h3>
<p className="mt-3 text-sm leading-relaxed text-muted-foreground">{cell.body}</p>
<p className="mt-3 text-sm text-muted-foreground">{cell.body}</p>
</GridCell>
))}
</div>
Expand Down
Loading
Loading