diff --git a/app/globals.css b/app/globals.css
index 2f5f444..61abe73 100644
--- a/app/globals.css
+++ b/app/globals.css
@@ -610,3 +610,317 @@ main,
html { scroll-behavior: auto; }
*, *::before, *::after { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; }
}
+
+/* IP Lens */
+.ip-lens-promo {
+ display: grid;
+ grid-template-columns: auto minmax(0, 1fr) auto;
+ align-items: center;
+ gap: 1.2rem;
+ margin-top: 1rem;
+ padding: 1rem 1.1rem;
+ border: 1px solid var(--line);
+ border-radius: 12px;
+ background: linear-gradient(115deg, rgba(105, 227, 176, 0.045), rgba(155, 135, 245, 0.055));
+}
+
+.ip-lens-promo-icon {
+ width: 46px;
+ height: 46px;
+ border-radius: 10px;
+ box-shadow: 0 8px 24px rgba(25, 66, 230, 0.28);
+}
+
+.ip-lens-promo .section-kicker { margin: 0 0 0.2rem; color: var(--green); font-size: 0.54rem; }
+.ip-lens-promo h2 { font-size: 1rem; }
+.ip-lens-promo p { margin-top: 0.15rem; color: var(--muted); font-size: 0.75rem; }
+.ip-lens-promo > a { color: var(--purple-bright); font-family: var(--font-mono); font-size: 0.67rem; }
+.ip-lens-promo > a span { margin-left: 0.5rem; }
+
+.ip-lens-page { --product-line: rgba(255, 255, 255, 0.105); }
+
+.product-hero {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) minmax(330px, 0.72fr);
+ align-items: center;
+ gap: clamp(3rem, 8vw, 7rem);
+ min-height: 720px;
+ padding: clamp(4.5rem, 8vw, 7rem) 0;
+}
+
+.product-hero-copy { max-width: 650px; }
+.product-hero .eyebrow { color: var(--green); }
+.product-hero .live-dot { background: var(--green); box-shadow: 0 0 12px rgba(105, 227, 176, 0.62); }
+
+.product-identity {
+ display: flex;
+ align-items: center;
+ gap: 0.9rem;
+ margin-bottom: 1.15rem;
+}
+
+.product-app-icon {
+ width: 58px;
+ height: 58px;
+ border: 1px solid rgba(255, 255, 255, 0.17);
+ border-radius: 13px;
+ box-shadow: 0 12px 34px rgba(25, 66, 230, 0.28);
+}
+
+.product-name {
+ color: var(--purple-bright);
+ font-family: var(--font-mono);
+ font-size: 0.92rem;
+ font-weight: 700;
+ letter-spacing: -0.03em;
+}
+
+.product-name span { display: block; margin-top: 0.2rem; color: var(--faint); font-size: 0.5rem; font-weight: 500; letter-spacing: 0.05em; text-transform: uppercase; }
+
+.product-hero h1 {
+ max-width: 690px;
+ font-size: clamp(3.4rem, 6.7vw, 5.75rem);
+ line-height: 0.98;
+ letter-spacing: -0.072em;
+}
+
+.product-hero-description {
+ max-width: 620px;
+ margin-top: 1.5rem;
+ color: var(--muted);
+ font-size: 1rem;
+ line-height: 1.75;
+}
+
+.app-store-cta { gap: 0.85rem; }
+.app-store-cta small { font-family: var(--font-mono); font-size: 0.52rem; font-weight: 700; text-transform: uppercase; }
+.app-store-cta.is-disabled,
+.app-store-cta.is-disabled:disabled { cursor: not-allowed; opacity: 0.72; }
+
+.product-badges {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.5rem;
+ padding: 0;
+ margin: 1.35rem 0 0;
+ list-style: none;
+}
+
+.product-badges li {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.45rem;
+ min-height: 30px;
+ padding: 0 0.62rem;
+ border: 1px solid var(--line);
+ border-radius: 999px;
+ color: var(--faint);
+ background: rgba(255, 255, 255, 0.022);
+ font-family: var(--font-mono);
+ font-size: 0.56rem;
+}
+
+.product-badges i,
+.placeholder-status i,
+.privacy-signal i {
+ width: 5px;
+ height: 5px;
+ border-radius: 50%;
+ background: var(--green);
+ box-shadow: 0 0 8px rgba(105, 227, 176, 0.48);
+}
+
+.hero-app-preview { position: relative; display: grid; place-items: center; min-width: 0; }
+.preview-orbit { position: absolute; width: min(46vw, 500px); aspect-ratio: 1; border: 1px solid rgba(155, 135, 245, 0.12); border-radius: 50%; background: radial-gradient(circle, rgba(155, 135, 245, 0.11), transparent 62%); }
+.preview-orbit::before,
+.preview-orbit::after { position: absolute; inset: 14%; border: 1px solid rgba(255, 255, 255, 0.045); border-radius: inherit; content: ""; }
+.preview-orbit::after { inset: 31%; border-color: rgba(105, 227, 176, 0.08); }
+
+.app-screenshot-frame { position: relative; z-index: 1; width: min(100%, 250px); margin: 0; }
+.app-screenshot-frame.is-featured { width: min(100%, 348px); transform: rotate(1.25deg); }
+
+.app-device-shell {
+ position: relative;
+ overflow: hidden;
+ aspect-ratio: 1290 / 2796;
+ padding: 10px;
+ border: 1px solid rgba(255, 255, 255, 0.24);
+ border-radius: 38px;
+ background: #07070b;
+ box-shadow: 0 34px 85px rgba(0, 0, 0, 0.55), 0 0 55px rgba(116, 87, 231, 0.12), inset 0 0 0 2px rgba(255, 255, 255, 0.025);
+}
+
+.app-device-shell img { display: block; width: 100%; height: 100%; border-radius: 29px; object-fit: cover; }
+.app-device-speaker { position: absolute; top: 17px; left: 50%; z-index: 3; width: 30%; height: 20px; border-radius: 999px; background: #060609; transform: translateX(-50%); }
+
+.app-screenshot-placeholder {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ height: 100%;
+ padding: 3.2rem 1.15rem 1.2rem;
+ border: 1px solid rgba(255, 255, 255, 0.07);
+ border-radius: 29px;
+ background:
+ linear-gradient(rgba(255, 255, 255, 0.025) 1px, transparent 1px),
+ linear-gradient(90deg, rgba(255, 255, 255, 0.025) 1px, transparent 1px),
+ linear-gradient(155deg, #12121a, #0b0b11);
+ background-size: 28px 28px, 28px 28px, auto;
+}
+
+.placeholder-command {
+ align-self: flex-start;
+ padding: 0.4rem 0.55rem;
+ border: 1px solid rgba(155, 135, 245, 0.3);
+ border-radius: 6px;
+ color: var(--purple-bright);
+ background: rgba(155, 135, 245, 0.08);
+ font-family: var(--font-mono);
+ font-size: 0.58rem;
+}
+
+.app-screenshot-placeholder small { display: block; margin-bottom: 0.65rem; color: var(--faint); font-family: var(--font-mono); font-size: 0.52rem; letter-spacing: 0.1em; text-transform: uppercase; }
+.app-screenshot-placeholder strong { display: block; font-size: clamp(1.1rem, 2vw, 1.4rem); line-height: 1.15; }
+.app-screenshot-placeholder p { margin-top: 0.7rem; color: var(--faint); font-size: 0.65rem; line-height: 1.55; }
+.app-screenshot-placeholder code { color: var(--muted); font-family: var(--font-mono); overflow-wrap: anywhere; }
+.placeholder-status { display: flex; align-items: center; gap: 0.45rem; color: var(--faint); font-family: var(--font-mono); font-size: 0.5rem; text-transform: uppercase; }
+.app-screenshot-frame figcaption { display: flex; justify-content: space-between; gap: 0.75rem; margin-top: 0.75rem; color: var(--muted); font-size: 0.68rem; }
+.app-screenshot-frame figcaption span { color: var(--purple-bright); font-family: var(--font-mono); font-size: 0.58rem; }
+
+.product-section { padding: 7rem 0; border-top: 1px solid var(--product-line); }
+.product-section-header { display: grid; grid-template-columns: minmax(0, 1.05fr) minmax(280px, 0.75fr); align-items: end; gap: 4rem; margin-bottom: 3rem; }
+.product-section-header > div { display: grid; grid-template-columns: auto 1fr; align-items: center; gap: 0 0.75rem; }
+.product-section-header .section-kicker { margin: 0; }
+.product-section-header h2 { grid-column: 1 / -1; margin-top: 0.9rem; font-size: clamp(2.3rem, 4.7vw, 3.9rem); line-height: 1.07; letter-spacing: -0.055em; }
+.product-section-header > p { max-width: 500px; color: var(--muted); font-size: 0.9rem; line-height: 1.75; }
+
+.screenshot-section { overflow: hidden; }
+.screenshot-gallery { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); align-items: start; justify-items: center; gap: clamp(1rem, 4vw, 3rem); padding: 1.5rem clamp(0rem, 3vw, 2rem) 0; }
+.screenshot-gallery > :nth-child(2) { margin-top: 3rem; }
+
+.ip-lens-feature-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 0.75rem; }
+.ip-lens-feature-card { min-height: 230px; }
+.ip-lens-feature-card h3 { font-size: 1.05rem; }
+.ip-lens-feature-card p { font-size: 0.8rem; line-height: 1.65; }
+
+.sample-workflow { overflow: hidden; border: 1px solid var(--line-strong); border-radius: var(--radius); background: rgba(13, 13, 19, 0.92); box-shadow: 0 30px 90px rgba(0, 0, 0, 0.35); }
+.sample-workflow-bar { display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; min-height: 46px; padding: 0 1rem; border-bottom: 1px solid var(--line); color: var(--faint); font-family: var(--font-mono); font-size: 0.6rem; }
+.sample-workflow-bar .terminal-dots { justify-self: start; }
+.sample-workflow-bar strong { color: var(--muted); font-weight: 500; text-transform: uppercase; }
+.sample-workflow-bar > :last-child { justify-self: end; }
+.sample-workflow-body { display: grid; grid-template-columns: 0.72fr 1.28fr; min-height: 370px; }
+.sample-address { display: flex; flex-direction: column; justify-content: center; padding: clamp(1.5rem, 4vw, 3.5rem); border-right: 1px solid var(--line); background: radial-gradient(circle at 10% 15%, rgba(155, 135, 245, 0.12), transparent 65%); }
+.sample-address > span { color: var(--green); font-family: var(--font-mono); font-size: 0.63rem; }
+.sample-address strong { margin-top: 0.8rem; font-family: var(--font-mono); font-size: clamp(1.5rem, 3.5vw, 2.8rem); letter-spacing: -0.06em; overflow-wrap: anywhere; }
+.sample-address p { max-width: 340px; margin-top: 1.2rem; color: var(--faint); font-size: 0.72rem; line-height: 1.65; }
+.sample-workflow dl { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); align-content: center; padding: clamp(1rem, 3vw, 2rem); }
+.sample-workflow dl div { min-width: 0; padding: 1rem; border-bottom: 1px solid var(--line); }
+.sample-workflow dt { color: var(--faint); font-family: var(--font-mono); font-size: 0.55rem; letter-spacing: 0.06em; text-transform: uppercase; }
+.sample-workflow dd { margin-top: 0.35rem; color: #d8d4e2; font-family: var(--font-mono); font-size: 0.72rem; overflow-wrap: anywhere; }
+
+.privacy-callout { display: grid; grid-template-columns: 1.25fr 0.75fr; overflow: hidden; border: 1px solid rgba(105, 227, 176, 0.2); border-radius: var(--radius); background: linear-gradient(125deg, rgba(105, 227, 176, 0.055), rgba(155, 135, 245, 0.035)); }
+.privacy-copy { padding: clamp(1.5rem, 5vw, 4rem); }
+.privacy-signal { display: inline-flex; align-items: center; gap: 0.5rem; color: var(--green); font-family: var(--font-mono); font-size: 0.57rem; text-transform: uppercase; }
+.privacy-copy h3 { max-width: 570px; margin-top: 1.2rem; font-size: clamp(1.65rem, 3.5vw, 2.75rem); letter-spacing: -0.045em; }
+.privacy-copy > p { max-width: 650px; margin-top: 1rem; color: var(--muted); font-size: 0.87rem; line-height: 1.75; }
+.privacy-links { display: flex; flex-wrap: wrap; gap: 1rem 1.5rem; margin-top: 1.75rem; }
+.privacy-links a { color: var(--purple-bright); font-family: var(--font-mono); font-size: 0.63rem; }
+.privacy-facts { display: grid; align-content: center; padding: 1rem clamp(1rem, 3vw, 2rem); margin: 0; border-left: 1px solid rgba(105, 227, 176, 0.15); list-style: none; }
+.privacy-facts li { display: grid; grid-template-columns: auto 1fr; align-items: center; gap: 0.9rem; min-height: 62px; border-bottom: 1px solid var(--line); }
+.privacy-facts li:last-child { border-bottom: 0; }
+.privacy-facts span { color: var(--green); font-family: var(--font-mono); font-size: 0.55rem; }
+.privacy-facts strong { font-size: 0.78rem; font-weight: 500; }
+
+.audience-list { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); padding: 0; margin: 0; border-top: 1px solid var(--line); border-left: 1px solid var(--line); list-style: none; }
+.audience-list li { display: flex; align-items: center; gap: 1rem; min-height: 76px; padding: 1rem; border-right: 1px solid var(--line); border-bottom: 1px solid var(--line); color: #d8d4e2; font-size: 0.82rem; }
+.audience-list span { color: var(--purple); font-family: var(--font-mono); font-size: 0.56rem; }
+
+.pricing-panel { display: grid; grid-template-columns: 1fr 1fr; border: 1px solid var(--line-strong); border-radius: var(--radius); background: rgba(13, 13, 19, 0.9); }
+.pricing-panel > div { padding: clamp(1.5rem, 5vw, 4rem); }
+.pricing-status { color: var(--green); font-family: var(--font-mono); font-size: 0.6rem; letter-spacing: 0.08em; text-transform: uppercase; }
+.pricing-panel h3 { margin-top: 1rem; font-size: clamp(1.7rem, 3.3vw, 2.6rem); letter-spacing: -0.045em; }
+.pricing-panel p { margin: 0.6rem 0 1.6rem; color: var(--muted); font-size: 0.78rem; }
+.pricing-panel > ul { display: grid; align-content: center; gap: 0; padding: 2rem clamp(1.5rem, 5vw, 4rem); margin: 0; border-left: 1px solid var(--line); list-style: none; }
+.pricing-panel li { display: flex; align-items: center; gap: 0.8rem; min-height: 48px; border-bottom: 1px solid var(--line); color: var(--muted); font-size: 0.78rem; }
+.pricing-panel li:last-child { border-bottom: 0; }
+.pricing-panel li span { color: var(--green); font-family: var(--font-mono); }
+
+.faq-list { border-top: 1px solid var(--line-strong); }
+.faq-list details { border-bottom: 1px solid var(--line); }
+.faq-list summary { display: grid; grid-template-columns: 2rem 1fr auto; align-items: center; gap: 1rem; min-height: 72px; padding: 0.8rem 0.25rem; cursor: pointer; color: #dcd8e5; font-size: 0.9rem; list-style: none; }
+.faq-list summary::-webkit-details-marker { display: none; }
+.faq-list summary::after { color: var(--purple-bright); content: "+"; font-family: var(--font-mono); font-size: 1rem; }
+.faq-list details[open] summary::after { content: "−"; }
+.faq-list summary span { color: var(--faint); font-family: var(--font-mono); font-size: 0.55rem; }
+.faq-list details > div { max-width: 800px; padding: 0 1rem 1.4rem 3.25rem; }
+.faq-list details p { color: var(--muted); font-size: 0.8rem; line-height: 1.7; }
+.faq-list details a { display: inline-block; margin-top: 0.8rem; color: var(--purple-bright); font-family: var(--font-mono); font-size: 0.62rem; }
+
+.product-final-cta { display: grid; grid-template-columns: 1fr auto; align-items: end; gap: 3rem; margin: 1rem 0 8rem; padding: clamp(1.5rem, 4vw, 3rem); border: 1px solid var(--line-strong); border-radius: var(--radius); background: linear-gradient(120deg, rgba(155, 135, 245, 0.08), rgba(105, 227, 176, 0.035)); }
+.product-final-cta .section-kicker { color: var(--green); }
+.product-final-cta h2 { font-size: clamp(2rem, 4.5vw, 3.5rem); line-height: 1.08; letter-spacing: -0.055em; }
+.product-final-cta p { margin-top: 0.8rem; color: var(--muted); font-size: 0.86rem; }
+.product-final-cta .hero-actions { justify-content: flex-end; margin: 0; }
+
+.product-legal-page .breadcrumbs { margin-bottom: 3rem; }
+.product-legal-page .section-kicker { color: var(--green); }
+.product-legal-page > p + p { margin-top: 1rem; }
+.support-warning { display: grid; grid-template-columns: auto 1fr; gap: 1rem; margin: 2rem 0; padding: 1rem; border: 1px solid rgba(243, 187, 107, 0.25); border-radius: 10px; background: rgba(243, 187, 107, 0.055); }
+.support-warning > span { display: grid; place-items: center; width: 28px; height: 28px; border: 1px solid rgba(243, 187, 107, 0.35); border-radius: 7px; color: var(--amber); font-family: var(--font-mono); }
+.support-warning p { font-size: 0.78rem; }
+.support-warning strong { color: #e2d5bd; }
+.inline-support-button { width: max-content; margin: 0.5rem 0; color: #d6d2df !important; }
+
+@media (max-width: 960px) {
+ .product-hero { grid-template-columns: 1fr; gap: 4rem; }
+ .hero-app-preview { width: min(100%, 600px); }
+ .preview-orbit { width: min(90vw, 520px); }
+ .product-section-header { grid-template-columns: 1fr; gap: 1.2rem; }
+ .product-section-header > p { max-width: 650px; }
+ .ip-lens-feature-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
+ .product-final-cta { grid-template-columns: 1fr; align-items: start; }
+ .product-final-cta .hero-actions { justify-content: flex-start; }
+}
+
+@media (max-width: 700px) {
+ .ip-lens-promo { grid-template-columns: auto 1fr; margin-top: 0.75rem; padding: 0.85rem; }
+ .ip-lens-promo > a { grid-column: 1 / -1; display: flex; align-items: center; justify-content: space-between; min-height: 44px; padding: 0 0.75rem; border: 1px solid var(--line); border-radius: 8px; }
+ .product-hero { gap: 3rem; min-height: 0; padding: 3.5rem 0 4.5rem; }
+ .product-hero h1 { font-size: clamp(3rem, 15vw, 4.45rem); }
+ .product-hero-description { margin-top: 1.1rem; font-size: 0.9rem; line-height: 1.65; }
+ .product-hero .hero-actions > * { flex: 1 1 150px; }
+ .product-badges { gap: 0.4rem; }
+ .product-badges li { font-size: 0.52rem; }
+ .hero-app-preview { width: 100%; }
+ .app-screenshot-frame.is-featured { width: min(84vw, 330px); transform: none; }
+ .product-section { padding: 4.25rem 0; }
+ .product-section-header { margin-bottom: 2rem; }
+ .product-section-header h2 { font-size: clamp(2rem, 11.5vw, 3rem); }
+ .product-section-header > p { font-size: 0.84rem; line-height: 1.65; }
+ .screenshot-gallery { display: flex; justify-content: start; gap: 1rem; width: calc(100% + 14px); padding: 0 14px 1rem 0; overflow-x: auto; scroll-snap-type: x mandatory; scrollbar-width: thin; }
+ .screenshot-gallery .app-screenshot-frame { flex: 0 0 min(75vw, 260px); scroll-snap-align: start; }
+ .screenshot-gallery > :nth-child(2) { margin-top: 0; }
+ .ip-lens-feature-grid { grid-template-columns: 1fr; gap: 0.55rem; }
+ .ip-lens-feature-card { display: flex; min-height: 190px; }
+ .sample-workflow-bar { grid-template-columns: 1fr auto; }
+ .sample-workflow-bar strong { display: none; }
+ .sample-workflow-body { grid-template-columns: 1fr; }
+ .sample-address { min-height: 250px; border-right: 0; border-bottom: 1px solid var(--line); }
+ .sample-workflow dl { grid-template-columns: 1fr; padding: 0.75rem; }
+ .sample-workflow dl div { padding: 0.85rem 0.5rem; }
+ .privacy-callout { grid-template-columns: 1fr; }
+ .privacy-facts { border-top: 1px solid rgba(105, 227, 176, 0.15); border-left: 0; }
+ .privacy-links { flex-direction: column; }
+ .privacy-links a { display: flex; align-items: center; justify-content: space-between; min-height: 44px; padding: 0 0.75rem; border: 1px solid var(--line); border-radius: 8px; }
+ .audience-list { grid-template-columns: 1fr; }
+ .audience-list li { min-height: 62px; }
+ .pricing-panel { grid-template-columns: 1fr; }
+ .pricing-panel > ul { border-top: 1px solid var(--line); border-left: 0; }
+ .pricing-panel .app-store-cta { width: 100%; }
+ .faq-list summary { min-height: 64px; font-size: 0.82rem; }
+ .faq-list details > div { padding-left: 3.25rem; }
+ .product-final-cta { margin-bottom: 5rem; }
+ .product-final-cta .hero-actions { width: 100%; }
+ .product-final-cta .hero-actions > * { flex: 1 1 150px; }
+ .product-legal-page .breadcrumbs { margin-bottom: 2rem; }
+}
diff --git a/app/ip-lens/page.tsx b/app/ip-lens/page.tsx
new file mode 100644
index 0000000..ec37958
--- /dev/null
+++ b/app/ip-lens/page.tsx
@@ -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 (
+
+ );
+}
+
+export default function IPLensPage() {
+ const [heroScreenshot, ...galleryScreenshots] = ipLensConfig.screenshots;
+
+ return (
+
+
+
+
+
native://ios
+
+
+
{ipLensConfig.appName}IP lookup and network tools
+
+
See the network more clearly.
+
IP lookup, DNS inspection, registration data, subnet calculations, and more—built into one focused networking toolkit for iPhone.
+
+
+ {ipLensConfig.badges.map((badge) => {badge} )}
+
+
+
+
+
+
+
+
+ {galleryScreenshots.map((screenshot) =>
)}
+
+
+
+
+
+
+ {ipLensConfig.features.map((feature) => )}
+
+
+
+
+
+
+
+
+ Illustrative sample
+ reserved address
+
+
+
+
lookup://sample
+
203.0.113.42
+
Reserved documentation address. The values below are fictional and demonstrate layout only.
+
+
+ {sampleFields.map(([term, value]) =>
{term} {value} )}
+
+
+
+
+
+
+
+
+
+
device → provider
+
Requests go directly from your device.
+
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}.
+
+ Read the IP Lens privacy notice →
+ Contact support →
+
+
+
+ 01 No account required
+ 02 No advertisements
+ 03 No analytics or tracking
+ 04 No IP Lens lookup proxy
+ 05 Saved data stays on-device
+ 06 No GPS permission
+
+
+
+
+
+
+
+ {ipLensConfig.audiences.map((audience, index) => 0{index + 1} {audience} )}
+
+
+
+
+
+
+
+
{ipLensConfig.releaseStatus}
+
{ipLensConfig.priceText}
+
{ipLensConfig.priceStatus}
+
+
+
+ {ipLensConfig.purchaseIncludes.map((item) => ✓ {item} )}
+
+
+
+
+
+
+
+ {ipLensConfig.faq.map((item, index) => (
+
+ {String(index + 1).padStart(2, "0")} {item.question}
+
+
{item.answer}
+ {index === ipLensConfig.faq.length - 1 &&
Open support
→ }
+
+
+ ))}
+
+
+
+
+
+
native://ios
+
A clearer view of the network.
+
Focused tools, native performance, and no recurring subscription.
+
+
+
+
+
+ );
+}
diff --git a/app/ip-lens/privacy/page.tsx b/app/ip-lens/privacy/page.tsx
new file mode 100644
index 0000000..db0d25f
--- /dev/null
+++ b/app/ip-lens/privacy/page.tsx
@@ -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 (
+
+
+
+ Home/ IP Lens/ Privacy
+ IP Lens / Privacy
+ Your lookups are yours.
+ IP Lens is a native iPhone networking utility. It has no accounts, advertising, analytics, tracking, attribution SDK, developer-operated backend, or recurring subscription.
+
+ How Lookups Work
+ 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.
+
+ Third-Party Providers
+
+ Free IP lookups use {ipLensConfig.providers.freeIPData}. Personal-key mode uses {ipLensConfig.providers.personalIPData}.
+ DNS queries go only to the resolver selected in the app: {ipLensConfig.providers.dns}.
+ Domain, IP, and ASN registration queries use {ipLensConfig.providers.bootstrap} and go directly to the {ipLensConfig.providers.registration}.
+
+ 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.
+
+ Data Stored on Your iPhone
+ 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'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.
+
+ Personal Provider Keys and Exports
+ 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.
+
+ Offline and Location Behavior
+ 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.
+
+ Accounts, Ads, and Purchases
+ 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.
+
+ Location Accuracy
+ 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.
+
+ Deletion
+ 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.
+
+ Contact
+ For privacy questions, email {ipLensConfig.supportEmail} or visit IP Lens support.
+
+ Last Updated
+ July 18, 2026
+ Back to IP Lens
+
+
+
+ );
+}
diff --git a/app/ip-lens/support/page.tsx b/app/ip-lens/support/page.tsx
new file mode 100644
index 0000000..f3da5db
--- /dev/null
+++ b/app/ip-lens/support/page.tsx
@@ -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 (
+
+
+
+ Home/ IP Lens/ Support
+ IP Lens / Support
+ Clear reports help.
+ For help with IP Lens, email {ipLensConfig.supportEmail} . A concise description and a few technical details are usually enough to begin.
+
+
+ !
+ Keep sensitive data out of support requests. Do not submit API keys or private, internal, or personally identifying IP information unless it is necessary to explain the problem.
+
+
+ Common Troubleshooting
+
+ Confirm the iPhone has a working internet connection before running an online lookup.
+ Check the address or domain for extra spaces and typing errors.
+ Try the request again after a short delay if a provider is temporarily unavailable.
+ For DNS issues, confirm whether Cloudflare or Quad9 is selected in Settings.
+ Install the latest available IP Lens and iOS updates, then restart the app.
+
+
+ Report a Bug
+ 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.
+
+ Version Information
+ 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.
+
+ Request a Feature
+ 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.
+
+ Contact
+ Email IP Lens support
+ Back to IP Lens · Read the privacy notice
+
+
+
+ );
+}
diff --git a/app/layout.tsx b/app/layout.tsx
index 46c0f62..c9ff388 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -44,7 +44,7 @@ export default function RootLayout({
children: React.ReactNode;
}>) {
return (
-
+
{children}
diff --git a/app/page.tsx b/app/page.tsx
index e66a753..68cab88 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -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";
@@ -34,6 +35,8 @@ export default function HomePage() {
+
+
The toolbox
From quick network checks to everyday data transforms. Browser-ready tools stay local; live diagnostics run through a restricted same-origin API.
diff --git a/app/sitemap.ts b/app/sitemap.ts
index 34d0186..96bfc0a 100644
--- a/app/sitemap.ts
+++ b/app/sitemap.ts
@@ -5,14 +5,14 @@ const baseUrl = "https://buffer.lol";
export default function sitemap(): MetadataRoute.Sitemap {
const lastModified = new Date();
- const staticRoutes = ["", "/privacy", "/terms"];
+ const staticRoutes = ["", "/privacy", "/terms", "/ip-lens", "/ip-lens/privacy", "/ip-lens/support"];
return [
...staticRoutes.map((route) => ({
url: `${baseUrl}${route}`,
lastModified,
- changeFrequency: route ? "yearly" as const : "weekly" as const,
- priority: route ? 0.3 : 1
+ changeFrequency: route === "/ip-lens" ? "monthly" as const : route ? "yearly" as const : "weekly" as const,
+ priority: route === "/ip-lens" ? 0.8 : route ? 0.3 : 1
})),
...tools.map((tool) => ({
url: `${baseUrl}/tools/${tool.slug}`,
diff --git a/components/ip-lens/AppScreenshotFrame.tsx b/components/ip-lens/AppScreenshotFrame.tsx
new file mode 100644
index 0000000..f2748f3
--- /dev/null
+++ b/components/ip-lens/AppScreenshotFrame.tsx
@@ -0,0 +1,38 @@
+import Image from "next/image";
+import type { IPLensScreenshot } from "@/data/ip-lens";
+
+export function AppScreenshotFrame({
+ screenshot,
+ featured = false
+}: {
+ screenshot: IPLensScreenshot;
+ featured?: boolean;
+}) {
+ return (
+
+
+ {!screenshot.src &&
}
+ {screenshot.src ? (
+
+ ) : (
+
+
{screenshot.command}
+
+
Screenshot slot
+
{screenshot.label}
+
Add the final app capture in /public/ip-lens/screenshots.
+
+
Ready for artwork
+
+ )}
+
+ {screenshot.command} {screenshot.label}
+
+ );
+}
diff --git a/components/ip-lens/AppStoreCTA.tsx b/components/ip-lens/AppStoreCTA.tsx
new file mode 100644
index 0000000..cdad0ad
--- /dev/null
+++ b/components/ip-lens/AppStoreCTA.tsx
@@ -0,0 +1,30 @@
+import { ipLensConfig } from "@/data/ip-lens";
+
+export function AppStoreCTA({ className = "primary-button" }: { className?: string }) {
+ if (ipLensConfig.appStoreUrl) {
+ return (
+
+ View on the App Store
+ ↗
+
+ );
+ }
+
+ return (
+
+ View on the App Store
+ {ipLensConfig.releaseStatus}
+
+ );
+}
diff --git a/components/ip-lens/IPLensFeatureCard.tsx b/components/ip-lens/IPLensFeatureCard.tsx
new file mode 100644
index 0000000..93f6122
--- /dev/null
+++ b/components/ip-lens/IPLensFeatureCard.tsx
@@ -0,0 +1,16 @@
+import type { IPLensFeature } from "@/data/ip-lens";
+
+export function IPLensFeatureCard({ feature }: { feature: IPLensFeature }) {
+ return (
+
+
+ {feature.command}
+ Included
+
+
+
{feature.title}
+
{feature.description}
+
+
+ );
+}
diff --git a/components/ip-lens/IPLensPromo.tsx b/components/ip-lens/IPLensPromo.tsx
new file mode 100644
index 0000000..b2d318c
--- /dev/null
+++ b/components/ip-lens/IPLensPromo.tsx
@@ -0,0 +1,17 @@
+import Image from "next/image";
+import Link from "next/link";
+import { ipLensConfig } from "@/data/ip-lens";
+
+export function IPLensPromo() {
+ return (
+
+ );
+}
diff --git a/components/landing/SiteChrome.tsx b/components/landing/SiteChrome.tsx
index 2ed352b..9250fbf 100644
--- a/components/landing/SiteChrome.tsx
+++ b/components/landing/SiteChrome.tsx
@@ -1,5 +1,6 @@
import Link from "next/link";
import { ToolLauncher } from "@/components/tools/ToolDiscovery";
+import { ipLensConfig } from "@/data/ip-lens";
type SiteChromeProps = {
children: React.ReactNode;
@@ -20,6 +21,7 @@ export function SiteHeader({ navHomePrefix = "" }: OmitNetworking
IP tools
Developer
+ IP Lens
{docsUrl && Docs }
@@ -33,8 +35,8 @@ export function SiteFooter() {
return (
>_ buffer
.lol Fast, simple networking tools.
- Networking
IP tools
Developer{docsUrl &&
Docs }
-
+ Networking
IP tools
Developer
IP Lens{docsUrl &&
Docs }
+ IP Lens privacy
Support & feature requests{ipLensConfig.appStoreUrl &&
App Store ↗ }
GitHub ↗ Site privacy
Terms
© 2026 buffer.lol Built for the curious.
);
diff --git a/data/ip-lens.ts b/data/ip-lens.ts
new file mode 100644
index 0000000..f56b92e
--- /dev/null
+++ b/data/ip-lens.ts
@@ -0,0 +1,173 @@
+export type IPLensScreenshot = {
+ id: string;
+ label: string;
+ command: string;
+ alt: string;
+ src: string | null;
+ width: number;
+ height: number;
+};
+
+export type IPLensFeature = {
+ command: string;
+ title: string;
+ description: string;
+};
+
+export const ipLensConfig = {
+ appName: "IP Lens",
+ platform: "iPhone",
+ minimumOS: "iOS 17 or later",
+ supportedPlatforms: ["iPhone running iOS 17 or later"],
+ appIcon: "/ip-lens/app-icon.png",
+ releaseStatus: "Coming soon",
+ appStoreUrl: null as string | null,
+ priceText: "$4.99 one-time purchase",
+ priceStatus: "Planned App Store price · USD",
+ supportEmail: "support@buffer.lol",
+ providers: {
+ ipData: "ipwho.is / ipwhois.pro",
+ freeIPData: "ipwho.is",
+ personalIPData: "ipwhois.pro",
+ dns: "Cloudflare or Quad9",
+ registration: "authoritative RDAP registry",
+ bootstrap: "IANA bootstrap data"
+ },
+ badges: ["No ads", "No subscription", "Native SwiftUI", "iOS 17+", "One-time purchase"],
+ screenshots: [
+ {
+ id: "ip-lookup",
+ label: "IP Lookup",
+ command: "whois",
+ alt: "IP Lens IP Lookup screen showing an IPv4 result with approximate location and map",
+ src: "/ip-lens/screenshots/iplookup.png",
+ width: 1206,
+ height: 2622
+ },
+ {
+ id: "dns-records",
+ label: "DNS Records",
+ command: "dig",
+ alt: "IP Lens DNS Lookup screen showing a Cloudflare query and returned A records",
+ src: "/ip-lens/screenshots/dnsrecords.png",
+ width: 1206,
+ height: 2622
+ },
+ {
+ id: "batch-lookup",
+ label: "Batch Lookup",
+ command: "batch",
+ alt: "IP Lens Batch Lookup screen showing organized results for multiple public and reserved addresses",
+ src: "/ip-lens/screenshots/batchlookup.png",
+ width: 1206,
+ height: 2622
+ },
+ {
+ id: "subnet-calculator",
+ label: "Subnet Calculator",
+ command: "cidr",
+ alt: "IP Lens Subnet Calculator screen showing an offline IPv4 CIDR calculation",
+ src: "/ip-lens/screenshots/subnetcalc.png",
+ width: 1206,
+ height: 2622
+ }
+ ] satisfies readonly IPLensScreenshot[],
+ features: [
+ {
+ command: "whois",
+ title: "IP intelligence",
+ description:
+ "View approximate location, ASN, ISP, organization, timezone, and network details for public IPv4 and IPv6 addresses."
+ },
+ {
+ command: "dig",
+ title: "DNS records",
+ description:
+ "Inspect A, AAAA, CNAME, MX, NS, TXT, and PTR records through your selected DNS-over-HTTPS resolver."
+ },
+ {
+ command: "batch",
+ title: "Batch lookup",
+ description: "Analyze up to 50 IP addresses in one organized workflow."
+ },
+ {
+ command: "rdap",
+ title: "Registration data",
+ description:
+ "Review domain, IP, and ASN registration data directly from the authoritative RDAP registry."
+ },
+ {
+ command: "cidr",
+ title: "Subnet calculator",
+ description:
+ "Calculate IPv4 and IPv6 networks, address ranges, masks, prefixes, and capacity entirely offline."
+ },
+ {
+ command: "ptr",
+ title: "Reverse DNS",
+ description: "Look up the hostname associated with an IP address when one is published."
+ },
+ {
+ command: "save",
+ title: "Offline results",
+ description: "Keep lookup history, favorites, and saved DNS, RDAP, and batch captures available on-device."
+ },
+ {
+ command: "export",
+ title: "Local export",
+ description: "Prepare CSV or JSON files locally, then choose where to share them with the iOS share sheet."
+ }
+ ] satisfies readonly IPLensFeature[],
+ audiences: [
+ "Developers",
+ "Network administrators",
+ "IT students",
+ "Homelab users",
+ "Website owners",
+ "Curious users"
+ ],
+ purchaseIncludes: [
+ "Pay once",
+ "All included tools",
+ "Future maintenance updates",
+ "No advertisements",
+ "No account required"
+ ],
+ faq: [
+ {
+ question: "Does IP Lens require an account?",
+ answer: "No. IP Lens does not require an account."
+ },
+ {
+ question: "Does IP Lens have advertisements?",
+ answer: "No. The app does not contain advertisements."
+ },
+ {
+ question: "Is IP Lens a subscription?",
+ answer: "No. IP Lens is intended to be a one-time App Store purchase."
+ },
+ {
+ question: "Does it work without an internet connection?",
+ answer:
+ "New IP, DNS, reverse DNS, and registration lookups require a connection. Saved captures and the IPv4/IPv6 subnet calculator work offline."
+ },
+ {
+ question: "Where does lookup data come from?",
+ answer:
+ "Free IP lookups use ipwho.is, while personal-key mode uses ipwhois.pro. DNS queries go only to the selected Cloudflare or Quad9 resolver. Registration queries go directly to the authoritative RDAP registry selected with IANA bootstrap data."
+ },
+ {
+ question: "Is IP geolocation exact?",
+ answer:
+ "No. IP geolocation is an estimate and may not represent a device's physical position."
+ },
+ {
+ question: "Which Apple devices are supported?",
+ answer: "IP Lens supports iPhone models running iOS 17 or later. iPad and Mac layouts are not part of version 1.0."
+ },
+ {
+ question: "How can I request a feature or report a problem?",
+ answer: "Use the IP Lens support page for reporting details and contact information."
+ }
+ ]
+} as const;
diff --git a/public/ip-lens/app-icon.png b/public/ip-lens/app-icon.png
new file mode 100644
index 0000000..5d7425f
Binary files /dev/null and b/public/ip-lens/app-icon.png differ
diff --git a/public/ip-lens/screenshots/README.md b/public/ip-lens/screenshots/README.md
new file mode 100644
index 0000000..8bb31e5
--- /dev/null
+++ b/public/ip-lens/screenshots/README.md
@@ -0,0 +1,14 @@
+# IP Lens screenshots
+
+Place final, locally hosted App Store-ready screenshots in this directory.
+
+Then update each matching `src`, `width`, `height`, and `alt` value in
+`data/ip-lens.ts`. Suggested filenames:
+
+- `iplookup.png`
+- `dnsrecords.png`
+- `batchlookup.png`
+- `subnetcalc.png`
+
+Until a `src` is configured, the landing page renders a labeled placeholder and
+does not request a remote image.
diff --git a/public/ip-lens/screenshots/batchlookup.png b/public/ip-lens/screenshots/batchlookup.png
new file mode 100644
index 0000000..b8998a9
Binary files /dev/null and b/public/ip-lens/screenshots/batchlookup.png differ
diff --git a/public/ip-lens/screenshots/dnsrecords.png b/public/ip-lens/screenshots/dnsrecords.png
new file mode 100644
index 0000000..3583913
Binary files /dev/null and b/public/ip-lens/screenshots/dnsrecords.png differ
diff --git a/public/ip-lens/screenshots/iplookup.png b/public/ip-lens/screenshots/iplookup.png
new file mode 100644
index 0000000..3c5f0cd
Binary files /dev/null and b/public/ip-lens/screenshots/iplookup.png differ
diff --git a/public/ip-lens/screenshots/subnetcalc.png b/public/ip-lens/screenshots/subnetcalc.png
new file mode 100644
index 0000000..4f9e7af
Binary files /dev/null and b/public/ip-lens/screenshots/subnetcalc.png differ