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
314 changes: 314 additions & 0 deletions app/globals.css

Large diffs are not rendered by default.

235 changes: 235 additions & 0 deletions app/ip-lens/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
import type { Metadata } from "next";
import Image from "next/image";
import Link from "next/link";
import { AppScreenshotFrame } from "@/components/ip-lens/AppScreenshotFrame";
import { AppStoreCTA } from "@/components/ip-lens/AppStoreCTA";
import { IPLensFeatureCard } from "@/components/ip-lens/IPLensFeatureCard";
import { SiteChrome } from "@/components/landing/SiteChrome";
import { ipLensConfig } from "@/data/ip-lens";

const description =
"IP Lens is a focused iPhone networking toolkit for IP lookup, DNS records, registration data, subnet calculations, batch analysis, and more—with no ads or subscription.";

export const metadata: Metadata = {
title: { absolute: "IP Lens — Networking Tools for iPhone | buffer.lol" },
description,
alternates: { canonical: "/ip-lens" },
robots: { index: true, follow: true },
openGraph: {
title: "IP Lens — Networking Tools for iPhone",
description,
type: "website",
url: "/ip-lens",
siteName: "buffer.lol"
},
twitter: {
card: "summary",
title: "IP Lens — Networking Tools for iPhone",
description
}
};

const sampleFields = [
["Address", "203.0.113.42"],
["IP version", "IPv4"],
["ASN", "AS64500 · sample"],
["Provider", "Example Network"],
["Reverse DNS", "host-42.example.test"],
["Network range", "203.0.113.0/24"],
["Approximate region", "Example region"]
] as const;

function SectionHeader({ id, number, label, title, text }: { id: string; number: string; label: string; title: string; text: string }) {
return (
<header className="product-section-header">
<div>
<span className="category-number">{number}</span>
<span className="section-kicker">{label}</span>
<h2 id={id}>{title}</h2>
</div>
<p>{text}</p>
</header>
);
}

export default function IPLensPage() {
const [heroScreenshot, ...galleryScreenshots] = ipLensConfig.screenshots;

return (
<SiteChrome navHomePrefix="/">
<main className="ip-lens-page" id="main-content">
<section className="product-hero" aria-labelledby="ip-lens-title">
<div className="product-hero-copy">
<div className="eyebrow"><span className="live-dot" /> native://ios</div>
<div className="product-identity">
<Image className="product-app-icon" src={ipLensConfig.appIcon} width={144} height={144} alt="IP Lens app icon" priority />
<p className="product-name">{ipLensConfig.appName}<span>IP lookup and network tools</span></p>
</div>
<h1 id="ip-lens-title">See the network more clearly.</h1>
<p className="product-hero-description">IP lookup, DNS inspection, registration data, subnet calculations, and more—built into one focused networking toolkit for iPhone.</p>
<div className="hero-actions">
<AppStoreCTA />
<Link className="secondary-button" href="#features">Explore features <span aria-hidden="true">↓</span></Link>
</div>
<ul className="product-badges" aria-label="Product highlights">
{ipLensConfig.badges.map((badge) => <li key={badge}><i aria-hidden="true" />{badge}</li>)}
</ul>
</div>
<div className="hero-app-preview">
<div className="preview-orbit" aria-hidden="true" />
<AppScreenshotFrame screenshot={heroScreenshot} featured />
</div>
</section>

<section className="product-section screenshot-section" aria-labelledby="preview-heading">
<SectionHeader
id="preview-heading"
number="01"
label="App preview"
title="Built for the screen in your hand."
text="A native, focused workspace for moving from an address or domain to the details that matter—across individual lookups, DNS records, batch analysis, and offline subnet math."
/>
<div className="screenshot-gallery">
{galleryScreenshots.map((screenshot) => <AppScreenshotFrame screenshot={screenshot} key={screenshot.id} />)}
</div>
</section>

<section className="product-section" id="features" aria-labelledby="features-heading">
<SectionHeader
id="features-heading"
number="02"
label="The toolkit"
title="Related tools. One place."
text="Inspect an address, resolve a domain, review registration data, or calculate a network without jumping between disconnected utilities."
/>
<div className="ip-lens-feature-grid">
{ipLensConfig.features.map((feature) => <IPLensFeatureCard feature={feature} key={feature.command} />)}
</div>
</section>

<section className="product-section workflow-section" aria-labelledby="workflow-heading">
<SectionHeader
id="workflow-heading"
number="03"
label="Focused workflow"
title="One address. A clearer picture."
text="IP Lens organizes related network details into a compact hierarchy, so the next useful signal is close to the first."
/>
<div className="sample-workflow">
<div className="sample-workflow-bar">
<span className="terminal-dots" aria-hidden="true"><i /><i /><i /></span>
<strong>Illustrative sample</strong>
<span className="status-success">reserved address</span>
</div>
<div className="sample-workflow-body">
<div className="sample-address">
<span>lookup://sample</span>
<strong>203.0.113.42</strong>
<p>Reserved documentation address. The values below are fictional and demonstrate layout only.</p>
</div>
<dl>
{sampleFields.map(([term, value]) => <div key={term}><dt>{term}</dt><dd>{value}</dd></div>)}
</dl>
</div>
</div>
</section>

<section className="product-section privacy-section" aria-labelledby="privacy-heading">
<SectionHeader
id="privacy-heading"
number="04"
label="Privacy"
title="Your lookups are yours."
text="IP Lens keeps the request path direct and understandable. It does not operate its own lookup backend."
/>
<div className="privacy-callout">
<div className="privacy-copy">
<span className="privacy-signal"><i aria-hidden="true" /> device → provider</span>
<h3>Requests go directly from your device.</h3>
<p>Free IP lookups go to {ipLensConfig.providers.freeIPData}; personal-key lookups go to {ipLensConfig.providers.personalIPData}. DNS queries go only to the selected {ipLensConfig.providers.dns} resolver, while registration queries go directly to the {ipLensConfig.providers.registration}.</p>
<div className="privacy-links">
<Link href="/ip-lens/privacy">Read the IP Lens privacy notice <span aria-hidden="true">→</span></Link>
<Link href="/ip-lens/support">Contact support <span aria-hidden="true">→</span></Link>
</div>
</div>
<ul className="privacy-facts">
<li><span>01</span><strong>No account required</strong></li>
<li><span>02</span><strong>No advertisements</strong></li>
<li><span>03</span><strong>No analytics or tracking</strong></li>
<li><span>04</span><strong>No IP Lens lookup proxy</strong></li>
<li><span>05</span><strong>Saved data stays on-device</strong></li>
<li><span>06</span><strong>No GPS permission</strong></li>
</ul>
</div>
</section>

<section className="product-section audience-section" aria-labelledby="audience-heading">
<SectionHeader
id="audience-heading"
number="05"
label="Built for"
title="Technical tools, without the ceremony."
text="For people who want a useful answer without an account, a dashboard, or an ad between them and the result."
/>
<ul className="audience-list">
{ipLensConfig.audiences.map((audience, index) => <li key={audience}><span>0{index + 1}</span>{audience}</li>)}
</ul>
</section>

<section className="product-section pricing-section" aria-labelledby="pricing-heading">
<SectionHeader
id="pricing-heading"
number="06"
label="Pricing"
title="One purchase. No recurring bill."
text="IP Lens is planned as a $4.99 paid App Store download. One purchase, with no advertising, StoreKit upsells, or recurring subscription."
/>
<div className="pricing-panel">
<div>
<span className="pricing-status">{ipLensConfig.releaseStatus}</span>
<h3>{ipLensConfig.priceText}</h3>
<p>{ipLensConfig.priceStatus}</p>
<AppStoreCTA className="secondary-button" />
</div>
<ul>
{ipLensConfig.purchaseIncludes.map((item) => <li key={item}><span aria-hidden="true">✓</span>{item}</li>)}
</ul>
</div>
</section>

<section className="product-section faq-section" aria-labelledby="faq-heading">
<SectionHeader
id="faq-heading"
number="07"
label="FAQ"
title="Short answers."
text="What to expect from IP Lens before it reaches the App Store."
/>
<div className="faq-list">
{ipLensConfig.faq.map((item, index) => (
<details key={item.question}>
<summary><span>{String(index + 1).padStart(2, "0")}</span>{item.question}</summary>
<div>
<p>{item.answer}</p>
{index === ipLensConfig.faq.length - 1 && <Link href="/ip-lens/support">Open support <span aria-hidden="true">→</span></Link>}
</div>
</details>
))}
</div>
</section>

<section className="product-final-cta" aria-labelledby="final-cta-heading">
<div>
<span className="section-kicker">native://ios</span>
<h2 id="final-cta-heading">A clearer view of the network.</h2>
<p>Focused tools, native performance, and no recurring subscription.</p>
</div>
<div className="hero-actions">
<AppStoreCTA />
<Link className="secondary-button" href="/ip-lens/support">Request a feature</Link>
</div>
</section>
</main>
</SiteChrome>
);
}
67 changes: 67 additions & 0 deletions app/ip-lens/privacy/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import type { Metadata } from "next";
import Link from "next/link";
import { SiteChrome } from "@/components/landing/SiteChrome";
import { ipLensConfig } from "@/data/ip-lens";

export const metadata: Metadata = {
title: "IP Lens Privacy",
description: "Privacy notice for the IP Lens iPhone app.",
alternates: { canonical: "/ip-lens/privacy" },
openGraph: {
title: "IP Lens Privacy | buffer.lol",
description: "How IP Lens sends lookup requests and uses third-party data providers.",
url: "/ip-lens/privacy",
type: "website"
}
};

export default function IPLensPrivacyPage() {
return (
<SiteChrome navHomePrefix="/">
<main className="simple-main" id="main-content">
<article className="simple-page product-legal-page">
<nav className="breadcrumbs" aria-label="Breadcrumb"><Link href="/">Home</Link><span>/</span><Link href="/ip-lens">IP Lens</Link><span>/</span><strong>Privacy</strong></nav>
<span className="section-kicker">IP Lens / Privacy</span>
<h1>Your lookups are yours.</h1>
<p>IP Lens is a native iPhone networking utility. It has no accounts, advertising, analytics, tracking, attribution SDK, developer-operated backend, or recurring subscription.</p>

<h2>How Lookups Work</h2>
<p>IP Lens does not operate its own lookup backend. Automatic current-IP lookups and addresses you enter are sent directly from your device to the selected IP data provider. Other network utilities disclose their destination before sending a request.</p>

<h2>Third-Party Providers</h2>
<ul>
<li>Free IP lookups use {ipLensConfig.providers.freeIPData}. Personal-key mode uses {ipLensConfig.providers.personalIPData}.</li>
<li>DNS queries go only to the resolver selected in the app: {ipLensConfig.providers.dns}.</li>
<li>Domain, IP, and ASN registration queries use {ipLensConfig.providers.bootstrap} and go directly to the {ipLensConfig.providers.registration}.</li>
</ul>
<p>These services receive the submitted query and ordinary connection metadata, including your public source IP, needed to respond. Their handling of that information is governed by their own terms and privacy policies.</p>

<h2>Data Stored on Your iPhone</h2>
<p>Successful IP lookups enter a history of up to 100 recent unique addresses. Favorites and explicitly saved DNS, RDAP, and batch captures remain in the app&apos;s on-device SwiftData store until you delete them. Resolver, API mode, appearance, and onboarding preferences use on-device settings. IP Lens does not use iCloud sync.</p>

<h2>Personal Provider Keys and Exports</h2>
<p>An optional personal ipwhois.pro API key is stored in Apple Keychain. It is not included in preferences, lookup snapshots, exports, or logs. CSV and JSON exports are prepared locally; IP Lens sends them only to the destination you choose through the iOS share sheet.</p>

<h2>Offline and Location Behavior</h2>
<p>The IPv4/IPv6 subnet calculator runs entirely on-device. Saved results remain available offline. IP Lens does not request GPS or Core Location permission; map positions come from approximate provider-supplied coordinates.</p>

<h2>Accounts, Ads, and Purchases</h2>
<p>IP Lens does not require an account and does not include advertisements, subscriptions, or in-app StoreKit purchases. It is planned as a one-time paid App Store download.</p>

<h2>Location Accuracy</h2>
<p>IP geolocation is approximate. It describes an estimated network location and should not be treated as the exact physical location of a device or person.</p>

<h2>Deletion</h2>
<p>You can delete individual favorites and saved captures or use Settings → Clear History and Saved Utilities. Removing the app deletes its app-container data. Remove a personal provider key in Settings before uninstalling if you want it explicitly removed from Keychain.</p>

<h2>Contact</h2>
<p>For privacy questions, email <a href={`mailto:${ipLensConfig.supportEmail}`}>{ipLensConfig.supportEmail}</a> or visit <Link href="/ip-lens/support">IP Lens support</Link>.</p>

<h2>Last Updated</h2>
<p>July 18, 2026</p>
<p><Link href="/ip-lens">Back to IP Lens</Link></p>
</article>
</main>
</SiteChrome>
);
}
58 changes: 58 additions & 0 deletions app/ip-lens/support/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import type { Metadata } from "next";
import Link from "next/link";
import { SiteChrome } from "@/components/landing/SiteChrome";
import { ipLensConfig } from "@/data/ip-lens";

export const metadata: Metadata = {
title: "IP Lens Support",
description: "Troubleshooting, bug reports, and feature requests for IP Lens.",
alternates: { canonical: "/ip-lens/support" },
openGraph: {
title: "IP Lens Support | buffer.lol",
description: "Get help with IP Lens or send a focused bug report or feature request.",
url: "/ip-lens/support",
type: "website"
}
};

export default function IPLensSupportPage() {
return (
<SiteChrome navHomePrefix="/">
<main className="simple-main" id="main-content">
<article className="simple-page product-legal-page">
<nav className="breadcrumbs" aria-label="Breadcrumb"><Link href="/">Home</Link><span>/</span><Link href="/ip-lens">IP Lens</Link><span>/</span><strong>Support</strong></nav>
<span className="section-kicker">IP Lens / Support</span>
<h1>Clear reports help.</h1>
<p>For help with IP Lens, email <a href={`mailto:${ipLensConfig.supportEmail}`}>{ipLensConfig.supportEmail}</a>. A concise description and a few technical details are usually enough to begin.</p>

<aside className="support-warning">
<span aria-hidden="true">!</span>
<p><strong>Keep sensitive data out of support requests.</strong> Do not submit API keys or private, internal, or personally identifying IP information unless it is necessary to explain the problem.</p>
</aside>

<h2>Common Troubleshooting</h2>
<ul>
<li>Confirm the iPhone has a working internet connection before running an online lookup.</li>
<li>Check the address or domain for extra spaces and typing errors.</li>
<li>Try the request again after a short delay if a provider is temporarily unavailable.</li>
<li>For DNS issues, confirm whether Cloudflare or Quad9 is selected in Settings.</li>
<li>Install the latest available IP Lens and iOS updates, then restart the app.</li>
</ul>

<h2>Report a Bug</h2>
<p>Include what you tried, what you expected, what happened instead, and whether the issue repeats. Add the lookup type and record type when relevant, but redact sensitive targets.</p>

<h2>Version Information</h2>
<p>In IP Lens, open Settings and note the Version shown under About. Include that value and your iOS version from Settings → General → About. Mention your iPhone model only when it appears relevant to the issue.</p>

<h2>Request a Feature</h2>
<p>Describe the networking task, the input you start with, and the result you need. A short real-world workflow is more useful than a broad feature name.</p>

<h2>Contact</h2>
<p><a className="secondary-button inline-support-button" href={`mailto:${ipLensConfig.supportEmail}?subject=IP%20Lens%20Support`}>Email IP Lens support</a></p>
<p><Link href="/ip-lens">Back to IP Lens</Link> · <Link href="/ip-lens/privacy">Read the privacy notice</Link></p>
</article>
</main>
</SiteChrome>
);
}
2 changes: 1 addition & 1 deletion app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export default function RootLayout({
children: React.ReactNode;
}>) {
return (
<html lang="en">
<html lang="en" data-scroll-behavior="smooth">
<body>
{children}
</body>
Expand Down
3 changes: 3 additions & 0 deletions app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Link from "next/link";
import type { Metadata } from "next";
import { IPLensPromo } from "@/components/ip-lens/IPLensPromo";
import { HeroTerminal } from "@/components/landing/HeroTerminal";
import { SiteChrome } from "@/components/landing/SiteChrome";
import { ToolCard } from "@/components/tools/ToolCard";
Expand Down Expand Up @@ -34,6 +35,8 @@ export default function HomePage() {

<QuickAccess />

<IPLensPromo />

<section className="tool-intro" aria-labelledby="toolbox-heading">
<div><span className="section-kicker">The toolbox</span><h2 id="toolbox-heading">Everything you need.<br />Nothing you don&apos;t.</h2></div>
<p>From quick network checks to everyday data transforms. Browser-ready tools stay local; live diagnostics run through a restricted same-origin API.</p>
Expand Down
Loading
Loading