import { useState, useEffect, useRef } from "react"; import { Link } from "react-router-dom"; import { useAuth } from "../context/AuthContext"; import { MessageSquare, Highlighter, Users, ArrowRight, Github, Database, Shield, Zap, } from "lucide-react"; import { SiFirefox, SiGooglechrome, SiBluesky } from "react-icons/si"; import { FaEdge } from "react-icons/fa"; import logo from "../assets/logo.svg"; const isFirefox = typeof navigator !== "undefined" && /Firefox/i.test(navigator.userAgent); const isEdge = typeof navigator !== "undefined" && /Edg/i.test(navigator.userAgent); function getExtensionInfo() { if (isFirefox) { return { url: "https://addons.mozilla.org/en-US/firefox/addon/margin/", Icon: SiFirefox, label: "Firefox", }; } if (isEdge) { return { url: "https://microsoftedge.microsoft.com/addons/detail/margin/nfjnmllpdgcdnhmmggjihjbidmeadddn", Icon: FaEdge, label: "Edge", }; } return { url: "https://chromewebstore.google.com/detail/margin/cgpmbiiagnehkikhcbnhiagfomajncpa/", Icon: SiGooglechrome, label: "Chrome", }; } import { getAnnotations, normalizeAnnotation } from "../api/client"; import { formatDistanceToNow } from "date-fns"; function DemoAnnotation() { const [annotations, setAnnotations] = useState([]); const [loading, setLoading] = useState(true); const [hoverPos, setHoverPos] = useState(null); const [hoverVisible, setHoverVisible] = useState(false); const [hoverAuthors, setHoverAuthors] = useState([]); const [showPopover, setShowPopover] = useState(false); const [popoverPos, setPopoverPos] = useState(null); const [popoverAnnotations, setPopoverAnnotations] = useState([]); const highlightRef = useRef(null); const articleRef = useRef(null); useEffect(() => { getAnnotations({ source: "https://en.wikipedia.org/wiki/AT_Protocol" }) .then((res) => { const rawItems = res.items || (Array.isArray(res) ? res : []); const normalized = rawItems.map(normalizeAnnotation); setAnnotations(normalized); }) .catch((err) => { console.error("Failed to fetch demo annotations:", err); }) .finally(() => { setLoading(false); }); }, []); useEffect(() => { if (!showPopover) return; const handleClickOutside = () => setShowPopover(false); document.addEventListener("click", handleClickOutside); return () => document.removeEventListener("click", handleClickOutside); }, [showPopover]); const getMatches = () => { return annotations.filter( (a) => (a.selector?.exact && a.selector.exact.includes("A handle serves as")) || (a.quote && a.quote.includes("A handle serves as")), ); }; const handleMouseEnter = () => { const matches = getMatches(); const authorsMap = new Map(); matches.forEach((a) => { const author = a.author || a.creator || { handle: "unknown" }; const id = author.did || author.handle; if (!authorsMap.has(id)) authorsMap.set(id, author); }); const unique = Array.from(authorsMap.values()); setHoverAuthors(unique); if (highlightRef.current && articleRef.current) { const spanRect = highlightRef.current.getBoundingClientRect(); const articleRect = articleRef.current.getBoundingClientRect(); const visibleCount = Math.min(unique.length, 3); const hasOverflow = unique.length > 3; const countForCalc = visibleCount + (hasOverflow ? 1 : 0); const width = countForCalc > 0 ? countForCalc * 18 + 10 : 0; const top = spanRect.top - articleRect.top + spanRect.height / 2 - 14; const left = spanRect.left - articleRect.left - width; setHoverPos({ top, left }); setHoverVisible(true); } }; const handleMouseLeave = () => { setHoverVisible(false); }; const handleHighlightClick = (e) => { e.stopPropagation(); const matches = getMatches(); setPopoverAnnotations(matches); if (highlightRef.current && articleRef.current) { const spanRect = highlightRef.current.getBoundingClientRect(); const articleRect = articleRef.current.getBoundingClientRect(); const top = spanRect.top - articleRect.top + spanRect.height + 10; let left = spanRect.left - articleRect.left; if (left + 300 > articleRect.width) { left = articleRect.width - 300; } setPopoverPos({ top, left }); setShowPopover(true); } }; const maxShow = 3; const displayHoverAuthors = hoverAuthors.slice(0, maxShow); const hoverOverflow = hoverAuthors.length - maxShow; return (
The AT Protocol utilizes a dual identifier system: a mutable handle, in the form of a domain name, and an immutable decentralized identifier (DID).
A handle serves as a verifiable user identifier. {" "} Verification is by either of two equivalent methods proving control of the domain name: Either a DNS query of a resource record with the same name as the handle, or a request for a text file from a Web service with the same name.
DIDs resolve to DID documents, which contain references to key user metadata, such as the user's handle, public keys, and data repository. While any DID method could, in theory, be used by the protocol if its components provide support for the method, in practice only two methods are supported ('blessed') by the protocol's reference implementations: did:plc and did:web. The validity of these identifiers can be verified by a registry which hosts the DID's associated document and a file that is hosted at a well-known location on the connected domain name, respectively.
“{ann.selector.exact}”
)}{ann.text || ann.body?.value}
Margin is a social layer for reading online. Highlight passages, leave thoughts in the margins, and see what others are thinking about the pages you read.
Also available for{" "} {isFirefox ? ( <> Edge {" "} and{" "} Chrome > ) : isEdge ? ( <> Firefox {" "} and{" "} Chrome > ) : ( <> Firefox {" "} and{" "} Edge > )}
Add Margin to your browser and sign in with your AT Protocol handle. No new account needed, just your existing handle.
Highlight text on any page. Leave notes in the margins, ask questions, or add context to the conversation precisely where it belongs.
Your annotations are published to your PDS. Discover what the community is reading and discussing across the web.
Save passages from any article, paper, or post. Your collection travels with you, independent of any single platform.
Move the discussion out of the comments section. Contextual conversations that live right alongside the content.
Your data, your handle, your graph. Built on the AT Protocol for true ownership and portability.
See the web with fresh eyes. Discover highlights and notes from other readers directly on the page.
Margin is built on the{" "} AT Protocol , the same open protocol that powers Bluesky. Sign in with your existing Bluesky account or create a new one in your preferred PDS.
Your annotations are stored in your PDS. You can export them anytime, use them with other apps, or self-host your own server. No vendor lock-in.
Free and open source. Sign in with ATProto to get started.