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), since 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.tsimport { clsx, type ClassValue } from "clsx";import { twMerge } from "tailwind-merge";export type { ClassValue };/** * Merge class names the shadcn way: clsx joins conditionals, tailwind-merge * de-dupes conflicting Tailwind utilities so a consumer's `className` override * wins (e.g. passing `px-2` beats the component's `px-6`). Lens components are * Tailwind utilities + `--gk-*` tokens, so this de-dupe matters. */export function cn(...inputs: ClassValue[]): string { return twMerge(clsx(inputs));}/** * 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 | Default | Description |
|---|---|---|---|
x / y | number | — | 0–100, % of the lens (you project from the world). |
label | ReactNode | — | Place name. |
distance | ReactNode | — | Optional distance. |
DirectionArrow
Points toward a real-world bearing. World-anchored: it rotates via an SVG transform attribute and is never mirrored under RTL, since a flipped arrow points the wrong way.
MapView
A real moving map for the lens, built on Leaflet (~42KB). Dark raster tiles, locked to follow your position; route + you-are-here marker + focusable photo place markers draw on top. Keyless CARTO dark tiles by default; pass tileUrl for MapTiler/Stadia in production.