Components
Pin
A world-anchored waypoint marker (ring + dot, name + distance above) placed at a projected screen point. You project: derive x from the target's relative bearing (lib/geo) — the platform gives heading + GPS, not 3D pose. Never mirrored under RTL.
Installation
npx @glasskit-ui/cli add pinInstall the SDK (it provides GlassViewport, useDpad and the stylesheet), then copy these files into your project:
npm install @glasskit-ui/react// components/lib/utils.tsexport type ClassValue = string | number | null | undefined | false;/** * Join truthy class names. Dependency-free on purpose: the lens components * style via bespoke semantic classes (no conflicting Tailwind utilities to * de-dupe), so this needs no clsx/tailwind-merge and resolves from anywhere * the registry is vendored. */export function cn(...inputs: ClassValue[]): string { return inputs.filter(Boolean).join(" ");}/** * Accessible name from a free-form `label` prop: the label itself when it's a * plain string, otherwise undefined (a ReactNode can't become an aria-label). */export function stringLabel(label: unknown): string | undefined { return typeof label === "string" ? label : undefined;}// components/glasskit/pin.tsximport type { ReactNode } from "react";import { cn } from "../lib/utils";/** * <Pin> — a world-anchored waypoint marker: a ring + dot at a screen point with * the name and distance stacked above. `x`/`y` are 0–100 (% of the lens), * projected by the consumer from the world position. * * WORLD-ANCHORED — placed by an SVG `transform="translate()"` *attribute* (not * inline style), absolute, so it is never mirrored under RTL: a flipped pin sits * over the wrong thing. The overlay is non-interactive (pointer-events: none). * * Projection: the Display gives web apps heading (`useDeviceOrientation`) and * phone-proxied GPS (`useGeolocation`) but no 3D pose, so a stable projection * is bearing-based: `relativeBearing(bearingBetween(me, target), heading)` * (see `lib/geo`) → map ±FOV/2 onto x 0–100, pick a fixed y band. Off-screen * targets are your call — hide the Pin or switch to <DirectionArrow>. */export function Pin({ x, y, label, distance, className,}: { /** 0–100, % of the lens width. */ x: number; /** 0–100, % of the lens height. */ y: number; label?: ReactNode; distance?: ReactNode; className?: string;}) { const cx = Math.round((x / 100) * 600); const cy = Math.round((y / 100) * 600); return ( <svg viewBox="0 0 600 600" className={cn("gk-worldlayer", className)} role="img" aria-label={typeof label === "string" ? label : "Waypoint"} > <g transform={`translate(${cx} ${cy})`}> {distance != null ? ( <text y={-58} className="gk-pin__dist"> {distance} </text> ) : null} {label != null ? ( <text y={-33} className="gk-pin__label"> {label} </text> ) : null} <circle r={20} className="gk-pin__ring" /> <circle r={8} className="gk-pin__dot" /> </g> </svg> );}Usage
// x/y projected from the world position each frame<Pin x={x} y={y} label="Blue Bottle" distance="120 m" />Props
Prop
Type
DirectionArrow
Points toward a real-world bearing. World-anchored: it rotates via an SVG transform attribute and is never mirrored under RTL — a flipped arrow points the wrong way.
Reticle
An aim-to-select target: four corner brackets framing the center of the lens. No gaze API exists for web apps — active is app-driven state (e.g. a projected point entering the center region).