{"version":3,"sources":["../src/components/use-resolved-color-mode.ts","../src/components/otdr-theme.ts","../src/utils/a11y.ts","../src/components/OtdrTraceStrip.tsx"],"names":["LIGHT_QUERY","subscribeToSystemScheme","onChange","query","readSystemScheme","noopSubscribe","useResolvedColorMode","colorMode","system","useSyncExternalStore","OTDR_COLOR_TOKENS","TOKEN_KEYS","resolveMode","mode","otdrTraceStripPalette","resolved","out","key","otdrTraceStripCssPalette","token","isActivationKey","activateOnEnterOrSpace","activate","event","VIEW_WIDTH","MARGIN","COMPACT_WIDTH_PX","LABEL_CLEARANCE_PX","useIsomorphicLayoutEffect","React","formatOtdrDistance","metres","kindOf","type","markerForKind","kind","x","y","colors","scale","atFault","r","jsx","half","arm","jsxs","LEGEND_ORDER","LEGEND_LABEL","otdrEventKindLabel","eventAccessibleName","OtdrTraceStrip","events","trace","focusRangeM","lengthM","totalLossDb","wavelengthNm","directionLabel","faultDistanceM","selectedEventIndex","density","height","onEventClick","resolvedColorMode","faultM","svgRef","useRef","measured","setMeasured","useState","measure","useCallback","width","widthPx","previous","el","observer","entries","compact","fontScale","pxPerUnit","ordered","useMemo","a","b","axisEndM","maxEvent","traceEnd","candidate","cumulativeSegments","hasSlopes","running","previousM","sectionRise","before","after","lastMeasuredEventIndex","index","cumulativeTraceTotal","cumulativeTotal","cumulativeAllTotal","lossScaleMax","innerWidth","fibreY","lossTop","lossBottom","xFor","yForLoss","loss","cumulativePath","parts","measuredEndM","traceClip","stepM","levelsDb","startM","sampleCount","tracePath","minDb","maxDb","i","v","span","yFor","db","buckets","perBucket","from","to","lo","hi","xLeft","xRight","clipNote","visibleClipNote","traceSourceNote","labelVisible","visible","lastShown","clearance","row","focus","inFocus","caption","click","svg","bounds","pointerX","pointerY","targetRadiusX","targetRadiusY","nearestIndex","nearestDistance","distance","Fragment","isSelected","labelY","beyondFault","present","cursor","cy","label","glyphX","textX"],"mappings":"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","file":"otdr.mjs","sourcesContent":["import { useSyncExternalStore } from 'react';\nimport type { ColorMode } from '../utils/react-flow-theming';\n\nconst LIGHT_QUERY = '(prefers-color-scheme: light)';\n\nfunction subscribeToSystemScheme(onChange: () => void): () => void {\n  if (typeof window === 'undefined' || !window.matchMedia) {\n    return () => {};\n  }\n  const query = window.matchMedia(LIGHT_QUERY);\n  query.addEventListener('change', onChange);\n  return () => query.removeEventListener('change', onChange);\n}\n\nfunction readSystemScheme(): 'light' | 'dark' {\n  if (typeof window !== 'undefined' && window.matchMedia) {\n    return window.matchMedia(LIGHT_QUERY).matches ? 'light' : 'dark';\n  }\n  return 'dark';\n}\n\nconst noopSubscribe = () => () => {};\n\n/**\n * Resolve a ColorMode to the concrete 'light' | 'dark' it renders as, staying\n * subscribed to the OS preference while mode is 'system'. This is what makes\n * the library's own theme class reactive without a remount — React Flow's\n * native v12 `colorMode` handles its internals the same way.\n */\nexport function useResolvedColorMode(colorMode: ColorMode): 'light' | 'dark' {\n  const system = useSyncExternalStore(\n    colorMode === 'system' ? subscribeToSystemScheme : noopSubscribe,\n    readSystemScheme,\n    () => 'dark' as const,\n  );\n  return colorMode === 'system' ? system : colorMode;\n}\n","import type { ColorMode } from \"../utils/react-flow-theming\";\n\n/**\n * The single source of truth for the OTDR strip's colours.\n *\n * Every entry produces BOTH sides of the contract so they cannot drift:\n *  - the hex values returned by {@link otdrTraceStripPalette}, which hosts use\n *    to paint linked surfaces (route maps, event tables) in the strip's own\n *    colours; and\n *  - the `--rn-otdr-*` custom properties declared in `src/styles/index.css`,\n *    which the rendered SVG consumes as `var(--rn-otdr-…, <hex fallback>)` so a\n *    consumer who forgot to import the stylesheet still gets these exact\n *    colours.\n *\n * A test asserts the stylesheet's declarations match this table verbatim.\n */\nexport const OTDR_COLOR_TOKENS = {\n  text: { cssVar: \"--rn-otdr-text\", light: \"#0f172a\", dark: \"#e2e8f0\" },\n  subtext: { cssVar: \"--rn-otdr-subtext\", light: \"#64748b\", dark: \"#94a3b8\" },\n  axis: { cssVar: \"--rn-otdr-axis\", light: \"#cbd5e1\", dark: \"#475569\" },\n  fibre: { cssVar: \"--rn-otdr-fibre\", light: \"#0284c7\", dark: \"#38bdf8\" },\n  cumulative: { cssVar: \"--rn-otdr-cumulative\", light: \"#d97706\", dark: \"#f59e0b\" },\n  eventFill: { cssVar: \"--rn-otdr-event-fill\", light: \"#0369a1\", dark: \"#0ea5e9\" },\n  end: { cssVar: \"--rn-otdr-end\", light: \"#16a34a\", dark: \"#22c55e\" },\n  fault: { cssVar: \"--rn-otdr-fault\", light: \"#be123c\", dark: \"#f43f5e\" },\n} as const;\n\nexport type OtdrColorToken = keyof typeof OTDR_COLOR_TOKENS;\n\n/** Every colour key the strip paints with, including the non-tokenised ones. */\nexport interface OtdrTraceStripColors {\n  text: string;\n  subtext: string;\n  axis: string;\n  fibre: string;\n  cumulative: string;\n  eventFill: string;\n  end: string;\n  fault: string;\n  /** Always `\"transparent\"` — the strip inherits its host's surface. */\n  background: string;\n}\n\nconst TOKEN_KEYS = Object.keys(OTDR_COLOR_TOKENS) as OtdrColorToken[];\n\n/**\n * Resolve a ColorMode without React. `'system'` reads the OS preference once;\n * the component itself stays reactive via `useResolvedColorMode`.\n */\nfunction resolveMode(mode: ColorMode): \"light\" | \"dark\" {\n  if (mode !== \"system\") return mode;\n  if (typeof window !== \"undefined\" && window.matchMedia) {\n    return window.matchMedia(\"(prefers-color-scheme: light)\").matches ? \"light\" : \"dark\";\n  }\n  return \"dark\";\n}\n\n/**\n * The strip's colour palette for a given mode, as literal hex values. Exported\n * so hosts can paint linked surfaces — a route map, an event list — in the same\n * colours the strip uses for events, faults, the fibre axis and the cumulative\n * line. `'system'` resolves against the OS preference (and falls back to dark\n * when there is no `window`).\n */\nexport const otdrTraceStripPalette = (mode: ColorMode): OtdrTraceStripColors => {\n  const resolved = resolveMode(mode);\n  const out = { background: \"transparent\" } as OtdrTraceStripColors;\n  for (const key of TOKEN_KEYS) {\n    out[key] = OTDR_COLOR_TOKENS[key][resolved];\n  }\n  return out;\n};\n\n/**\n * The same palette expressed as `var(--rn-otdr-…, <hex>)` references — what the\n * SVG actually paints with, so a host can retheme the strip through CSS while\n * a host that never imported the stylesheet still renders the hex fallbacks.\n */\nexport const otdrTraceStripCssPalette = (mode: \"light\" | \"dark\"): OtdrTraceStripColors => {\n  const out = { background: \"transparent\" } as OtdrTraceStripColors;\n  for (const key of TOKEN_KEYS) {\n    const token = OTDR_COLOR_TOKENS[key];\n    out[key] = `var(${token.cssVar}, ${token[mode]})`;\n  }\n  return out;\n};\n","/**\n * Accessibility helpers shared by the node renderers.\n *\n * Two concerns live here:\n *\n * 1. `buildPortAriaLabel` — the single builder for the accessible name of every\n *    port, bay, tube and fibre element. Node renderers already compose a `title`\n *    string from the same data; routing both through one builder keeps the\n *    announced name and the tooltip in step.\n * 2. `activateOnEnterOrSpace` — the keyboard half of a mouse-only click handler.\n *    An element given `role=\"button\"` must fire on Enter and Space, so a click\n *    handler on a non-native control is always paired with this.\n *\n * Internal module: nothing here is exported from the package entry points.\n */\n\nimport type React from \"react\";\n\n/** Inputs to {@link buildPortAriaLabel}. Everything but `kind` is optional. */\nexport interface PortAriaLabelParts {\n  /** Noun for the element type, e.g. `\"Port\"`, `\"Bay\"`, `\"Tube\"`, `\"Fibre\"`. */\n  kind: string;\n  /** Owning group/block, e.g. a device port-block name or a cable label. */\n  group?: string;\n  /** The element's own label, e.g. `\"Ethernet 1/1\"` or `\"Drive Bay 0\"`. */\n  label?: string | number;\n  /**\n   * Status words appended after the name, in order, e.g. `[\"connected\"]`.\n   * Falsy entries are dropped so callers can inline conditionals.\n   */\n  status?: Array<string | number | false | null | undefined>;\n}\n\nfunction toText(value: string | number | false | null | undefined): string {\n  if (value === undefined || value === null || value === false) return \"\";\n  return String(value).trim();\n}\n\n/**\n * Build the accessible name for an interactive diagram element.\n *\n * `buildPortAriaLabel({ kind: \"Port\", group: \"Ethernet\", label: \"1/1\",\n * status: [\"connected\"] })` → `\"Port Ethernet 1/1, connected\"`.\n */\nexport function buildPortAriaLabel({\n  kind,\n  group,\n  label,\n  status,\n}: PortAriaLabelParts): string {\n  const name = [toText(kind), toText(group), toText(label)]\n    .filter(Boolean)\n    .join(\" \");\n  const suffixes = (status ?? []).map(toText).filter(Boolean);\n  return suffixes.length > 0 ? `${name}, ${suffixes.join(\", \")}` : name;\n}\n\n/** True for the two keys that must activate an element with `role=\"button\"`. */\nexport function isActivationKey(key: string): boolean {\n  // \"Spacebar\" is the legacy IE/Edge key name; jsdom and every current browser\n  // report \" \", but matching both costs nothing.\n  return key === \"Enter\" || key === \" \" || key === \"Spacebar\";\n}\n\n/**\n * Wrap an activation callback as an `onKeyDown` handler that fires on Enter and\n * Space and swallows the event, so the keypress neither scrolls the page (Space)\n * nor bubbles to React Flow's node-level key handling.\n */\nexport function activateOnEnterOrSpace<T extends Element>(\n  activate: (event: React.KeyboardEvent<T>) => void,\n): React.KeyboardEventHandler<T> {\n  return (event) => {\n    if (!isActivationKey(event.key)) return;\n    event.preventDefault();\n    event.stopPropagation();\n    activate(event);\n  };\n}\n","import React, { useCallback, useMemo, useRef, useState } from \"react\";\nimport type { ColorMode } from \"../utils/react-flow-theming\";\nimport { useResolvedColorMode } from \"./use-resolved-color-mode\";\nimport { otdrTraceStripCssPalette, type OtdrTraceStripColors } from \"./otdr-theme\";\nimport { activateOnEnterOrSpace } from \"../utils/a11y\";\n\n/**\n * OTDR loss-budget strip: a standalone SVG visualisation of an optical\n * time-domain reflectometer trace's key events — ordered along the fibre by\n * distance, each annotated with its splice/insertion loss (and reflectance\n * when present), with the cumulative loss running along a secondary axis.\n *\n * Deliberately renders MEASURED data only: the component never estimates,\n * interpolates missing events, or scales distances to fit — callers pass the\n * parsed events (e.g. from a Telcordia SR-4731 .sor file) and, optionally, a\n * fault distance to highlight. No React Flow dependency; safe to render\n * anywhere.\n */\n\n/**\n * Event vocabulary. The semantic kinds (`connector`, `splice`) say what the\n * plant IS — pass them when the caller knows. The raw kinds (`reflective`,\n * `non-reflective`, `saturated`) say only what the OTDR saw: a reflective\n * event is a connector OR a mechanical splice, a non-reflective one a fusion\n * splice or a bend. The legend words raw kinds honestly instead of guessing —\n * a trace full of mechanical splices must not be captioned \"connectors\".\n */\nexport type OtdrTraceStripEventKind =\n  | \"connector\"\n  | \"splice\"\n  | \"reflective\"\n  | \"non-reflective\"\n  | \"saturated\"\n  | \"end\";\n\nexport interface OtdrTraceStripEvent {\n  /** Metres from the launch end. */\n  distanceM: number;\n  /** Splice/insertion loss at this event in dB. */\n  lossDb: number;\n  /** Reflectance in dB (negative), when the event is reflective. */\n  reflectanceDb?: number | null;\n  /**\n   * Measured attenuation slope of the fibre section LEADING INTO this event,\n   * in dB/km (SR-4731 semantics, as .sor parsers report it). When any event\n   * carries one, the cumulative line slopes across each section — the way a\n   * real trace's backscatter falls between events — and lands on the measured\n   * total instead of just the sum of event losses. Absent slopes render the\n   * legacy flat step-line; the strip never estimates one.\n   */\n  slopeDbPerKm?: number | null;\n  /** An OtdrTraceStripEvent kind; unrecognised strings render as a plain event. */\n  type?: string;\n  comment?: string;\n}\n\n/**\n * A measured backscatter waveform (SR-4731 DataPts): uniformly sampled power\n * levels in dB. When provided, the strip draws the real trace — noise, dead\n * zones, reflection spikes, post-end noise floor — instead of the derived\n * cumulative-loss line. Levels are displayed power (higher = more light),\n * exactly as decoded from the file; nothing is smoothed or synthesised.\n *\n * A capture usually runs past the fibre: samples beyond the axis end are\n * clipped rather than drawn, so the waveform and the axis always agree. The\n * waveform itself never extends the axis — only events or `faultDistanceM`\n * beyond `lengthM` do that, and the waveform is then drawn out to wherever\n * they put the axis end (see `lengthM`).\n */\nexport interface OtdrTraceStripTrace {\n  /** Metres between consecutive samples. */\n  stepM: number;\n  /** Power level per sample, in dB. */\n  levelsDb: number[];\n  /** Distance of the first sample from the launch end; defaults to 0 m. */\n  startM?: number;\n}\n\nexport interface OtdrTraceStripProps {\n  events: OtdrTraceStripEvent[];\n  /**\n   * Measured DataPts waveform; replaces the cumulative-loss line when set.\n   * The waveform is clipped to the axis end and never extends it — the tail of\n   * a capture is post-end noise, not plant. Note the axis end is not always\n   * `lengthM`: an event or `faultDistanceM` beyond the declared length pushes\n   * it out, and the waveform is then drawn that far.\n   */\n  trace?: OtdrTraceStripTrace | null;\n  /**\n   * Fibre-metre span to spotlight — typically the stretch currently visible\n   * on a linked route map. The strip draws a soft band over it and dims\n   * events outside, so the strip reads as the overview while the map is the\n   * working surface.\n   *\n   * The strip renders exactly the span it is given (clamped to the axis) and\n   * applies no \"that is basically everything\" heuristic of its own: the\n   * producer of the range knows how much of the route is on screen, so it\n   * decides when there is nothing to spotlight and passes null.\n   */\n  focusRangeM?: [number, number] | null;\n  /**\n   * Fibre length for the axis; defaults to the furthest event distance (or,\n   * with no events, the end of `trace`). When supplied it is the authority on\n   * how long the fibre is against the capture: a longer `trace` does not\n   * stretch the axis — only an event or `faultDistanceM` beyond it can, and\n   * the waveform is clipped to whatever axis end those produce (the strip\n   * captions the clip when samples were dropped).\n   */\n  lengthM?: number | null;\n  /** Measured end-to-end loss; defaults to the sum of event losses. */\n  totalLossDb?: number | null;\n  wavelengthNm?: number | null;\n  /** e.g. \"A → Z\" — purely a caption, the strip draws launch-end left. */\n  directionLabel?: string;\n  /** Distance to highlight as an actual fault/break, in metres. Reserve for\n   * real anomalies — ordinary events (splices/connectors/end) are not faults. */\n  faultDistanceM?: number | null;\n  /** Index (into the sorted events) to render as selected/emphasised — e.g.\n   * the event a consumer is locating on a route map. */\n  selectedEventIndex?: number | null;\n  /**\n   * `'light'` (the default, kept for backwards compatibility), `'dark'`, or\n   * `'system'` to follow the OS `prefers-color-scheme` reactively — the same\n   * `ColorMode` the rest of the library takes.\n   */\n  colorMode?: ColorMode;\n  /**\n   * Label density. `'auto'` (the default) drops the legend and the per-event\n   * dB labels — the selected event always keeps its label — and scales the\n   * remaining text up once the strip renders narrower than 640 CSS pixels.\n   * `'compact'` forces that phone treatment at any width; `'full'` keeps the\n   * legend and labels however narrow the strip is, for hosts that would rather\n   * have a dense strip than a summarised one.\n   */\n  density?: \"auto\" | \"full\" | \"compact\";\n  /** Pixel height of the strip (width is fluid). Default 190. */\n  height?: number;\n  onEventClick?: (event: OtdrTraceStripEvent, index: number) => void;\n}\n\nconst VIEW_WIDTH = 1000;\nconst MARGIN = { left: 46, right: 46, top: 34, bottom: 30 };\n/** Rendered CSS width below which `density: 'auto'` switches to compact. */\nconst COMPACT_WIDTH_PX = 640;\n/** Readability gap between two rendered dB labels, in CSS pixels. */\nconst LABEL_CLEARANCE_PX = 40;\n\nconst useIsomorphicLayoutEffect =\n  typeof window !== \"undefined\" ? React.useLayoutEffect : React.useEffect;\n\n/**\n * Format a fibre distance the way the strip's own axis and captions do:\n * metres below 1 km, then 2 decimals of km, dropping to 1 decimal past 10 km.\n * Exported so a host's surrounding chrome (map popups, event tables) can label\n * the same distances identically instead of rounding them its own way.\n */\nexport const formatOtdrDistance = (metres: number): string =>\n  metres >= 10_000\n    ? `${(metres / 1000).toFixed(1)} km`\n    : metres >= 1000\n      ? `${(metres / 1000).toFixed(2)} km`\n      : `${metres.toFixed(0)} m`;\n\nexport {\n  otdrTraceStripPalette,\n  OTDR_COLOR_TOKENS,\n  type OtdrColorToken,\n  type OtdrTraceStripColors,\n} from \"./otdr-theme\";\n\n/** Collapse an event's type string onto the glyph/legend vocabulary. */\nconst kindOf = (type: string | undefined): OtdrTraceStripEventKind | \"event\" => {\n  switch (type) {\n    case \"connector\":\n    case \"splice\":\n    case \"reflective\":\n    case \"non-reflective\":\n    case \"saturated\":\n    case \"end\":\n      return type;\n    default:\n      return \"event\";\n  }\n};\n\n/** Diamond = reflective plant (connectors, mech. splices); circle = loss-only. */\nconst markerForKind = (\n  kind: OtdrTraceStripEventKind | \"event\",\n  x: number,\n  y: number,\n  colors: OtdrTraceStripColors,\n  scale = 1,\n  atFault = false,\n): React.ReactElement => {\n  switch (kind) {\n    case \"connector\":\n    case \"reflective\": {\n      const r = 6 * scale;\n      return (\n        <path\n          d={`M ${x} ${y - r} L ${x + r} ${y} L ${x} ${y + r} L ${x - r} ${y} Z`}\n          fill={colors.eventFill}\n        />\n      );\n    }\n    case \"splice\": {\n      // Splices are drawn hollow so a connector (solid diamond) and a splice\n      // (open circle) stay distinguishable at a glance.\n      return (\n        <circle\n          cx={x}\n          cy={y}\n          r={4.6 * scale}\n          fill=\"none\"\n          stroke={colors.eventFill}\n          strokeWidth={2 * scale}\n        />\n      );\n    }\n    case \"end\": {\n      // An end event AT the fault distance IS the break — colour it as such\n      // instead of the healthy fibre-end green.\n      const half = 5 * scale;\n      return (\n        <rect\n          x={x - half}\n          y={y - half}\n          width={half * 2}\n          height={half * 2}\n          fill={atFault ? colors.fault : colors.end}\n        />\n      );\n    }\n    case \"saturated\": {\n      const arm = 5 * scale;\n      return (\n        <g stroke={colors.eventFill} strokeWidth={2.4 * scale} strokeLinecap=\"round\">\n          <line x1={x - arm} y1={y - arm} x2={x + arm} y2={y + arm} />\n          <line x1={x - arm} y1={y + arm} x2={x + arm} y2={y - arm} />\n        </g>\n      );\n    }\n    default:\n      return <circle cx={x} cy={y} r={5.2 * scale} fill={colors.eventFill} />;\n  }\n};\n\n/**\n * Legend entries derive from the kinds actually present — and never claim more\n * than the data says: semantic kinds get their name, raw kinds get the honest\n * either/or reading.\n */\nconst LEGEND_ORDER: Array<OtdrTraceStripEventKind | \"event\"> = [\n  \"connector\",\n  \"splice\",\n  \"reflective\",\n  \"non-reflective\",\n  \"saturated\",\n  \"end\",\n  \"event\",\n];\n\nconst LEGEND_LABEL: Record<OtdrTraceStripEventKind | \"event\", string> = {\n  connector: \"connector\",\n  splice: \"splice\",\n  reflective: \"reflective (connector or mech. splice)\",\n  \"non-reflective\": \"loss only (fusion splice or bend)\",\n  saturated: \"saturated reflection\",\n  end: \"end of fibre\",\n  event: \"event\",\n};\n\n/**\n * The strip's legend wording for an event `type`, e.g. `'reflective'` →\n * `\"reflective (connector or mech. splice)\"`. Exported so a host's popups and\n * tables word an event exactly as the legend does — never claiming more than\n * the file says — instead of maintaining a second vocabulary. Unrecognised\n * types read as a plain `\"event\"`.\n */\nexport const otdrEventKindLabel = (type: string | undefined): string =>\n  LEGEND_LABEL[kindOf(type)];\n\n/** Accessible name for a clickable event marker; mirrors its tooltip. */\nconst eventAccessibleName = (event: OtdrTraceStripEvent): string =>\n  `${otdrEventKindLabel(event.type)} at ${formatOtdrDistance(event.distanceM)}, loss ${event.lossDb.toFixed(2)} dB` +\n  (event.reflectanceDb !== null && event.reflectanceDb !== undefined\n    ? `, reflectance ${event.reflectanceDb.toFixed(2)} dB`\n    : \"\") +\n  (event.comment ? `, ${event.comment}` : \"\");\n\nexport const OtdrTraceStrip: React.FC<OtdrTraceStripProps> = ({\n  events,\n  trace,\n  focusRangeM,\n  lengthM,\n  totalLossDb,\n  wavelengthNm,\n  directionLabel,\n  faultDistanceM,\n  selectedEventIndex,\n  colorMode = \"light\",\n  density = \"auto\",\n  height = 190,\n  onEventClick,\n}) => {\n  const resolvedColorMode = useResolvedColorMode(colorMode);\n  // The SVG paints with `var(--rn-otdr-..., <hex>)` so hosts can retheme through\n  // the stylesheet; the hex fallbacks keep the strip correct without it.\n  const colors = otdrTraceStripCssPalette(resolvedColorMode);\n  // One nullable-fault guard for the whole component.\n  const faultM = faultDistanceM ?? null;\n\n  // Every density decision below is pixel-scale — how wide a dB label is, how\n  // many waveform vertices a screen can resolve — so they all run off the\n  // measured CSS width, with `pxPerUnit` converting to viewBox units. The\n  // measurement is taken in a layout effect (before paint), so a phone never\n  // flashes the full-width layout before compacting. Only derived values reach\n  // state: whole pixels and the compact flag, so sub-pixel resize ticks don't\n  // re-render the SVG.\n  const svgRef = useRef<SVGSVGElement | null>(null);\n  const [measured, setMeasured] = useState<{\n    widthPx: number;\n    compact: boolean;\n  } | null>(null);\n  const measure = useCallback((width: number) => {\n    const widthPx = Math.round(width);\n    if (widthPx <= 0) return;\n    setMeasured((previous) =>\n      previous && previous.widthPx === widthPx\n        ? previous\n        : { widthPx, compact: widthPx < COMPACT_WIDTH_PX },\n    );\n  }, []);\n  useIsomorphicLayoutEffect(() => {\n    const el = svgRef.current;\n    if (!el) return undefined;\n    measure(el.getBoundingClientRect().width);\n    if (typeof ResizeObserver === \"undefined\") return undefined;\n    const observer = new ResizeObserver((entries) => {\n      const width = entries[0]?.contentRect.width;\n      if (width) measure(width);\n    });\n    observer.observe(el);\n    return () => observer.disconnect();\n  }, [measure]);\n  const compact =\n    density === \"auto\" ? (measured?.compact ?? false) : density === \"compact\";\n  const fontScale = compact ? 1.55 : 1;\n  /** Rendered CSS pixels per viewBox unit; 1 until the strip is measured. */\n  const pxPerUnit = measured !== null ? measured.widthPx / VIEW_WIDTH : 1;\n\n  const ordered = useMemo(\n    () => [...events].sort((a, b) => a.distanceM - b.distanceM),\n    [events],\n  );\n\n  // Axis end. A supplied lengthM is the authority on how long the fibre is: an\n  // OTDR captures well past the far end, and folding that tail in would leave\n  // the axis reading 8 km under a \"Length 3.79 km\" caption. Events and a fault\n  // still extend the axis — they are plant the caller located — but the\n  // waveform is clipped instead. With no lengthM the trace's own end is the\n  // best length available, so it may define the axis.\n  const axisEndM = useMemo(() => {\n    const maxEvent = ordered.length > 0 ? ordered[ordered.length - 1].distanceM : 0;\n    const traceEnd =\n      trace && (lengthM === null || lengthM === undefined)\n        ? (trace.startM ?? 0) + trace.stepM * (trace.levelsDb.length - 1)\n        : 0;\n    const candidate = Math.max(lengthM ?? 0, maxEvent, faultM ?? 0, traceEnd);\n    return candidate > 0 ? candidate : 1;\n  }, [ordered, lengthM, faultM, trace]);\n\n  // Cumulative loss at each event: `before` is the value on arrival (after\n  // the preceding fibre section's slope, when measured slopes are present),\n  // `after` adds the event's own loss. With slopes the line ends at the\n  // measured link loss; without them it degrades to the flat event-only sum.\n  const cumulativeSegments = useMemo(() => {\n    const hasSlopes = ordered.some(\n      (event) => typeof event.slopeDbPerKm === \"number\" && event.slopeDbPerKm !== 0,\n    );\n    let running = 0;\n    let previousM = 0;\n    return ordered.map((event) => {\n      const sectionRise = hasSlopes\n        ? Math.max(0, (event.slopeDbPerKm ?? 0) * ((event.distanceM - previousM) / 1000))\n        : 0;\n      const before = running + sectionRise;\n      const after = before + event.lossDb;\n      running = after;\n      previousM = event.distanceM;\n      return { before, after };\n    });\n  }, [ordered]);\n  // The last event this trace actually measured: a fault ends the measurable\n  // fibre, so anything beyond it is plant the caller knows about, not something\n  // the instrument saw. Null when a fault precedes every event.\n  const lastMeasuredEventIndex = useMemo(() => {\n    if (faultM === null) return ordered.length - 1;\n    for (let index = ordered.length - 1; index >= 0; index -= 1) {\n      if (ordered[index].distanceM <= faultM) return index;\n    }\n    return -1;\n  }, [ordered, faultM]);\n  const cumulativeTraceTotal =\n    lastMeasuredEventIndex >= 0\n      ? cumulativeSegments[lastMeasuredEventIndex].after\n      : null;\n\n  const cumulativeTotal = totalLossDb ?? cumulativeTraceTotal;\n  // The loss axis is scaled by every event, not just the measured ones: a fault\n  // before the first event measures nothing, and a 0.1 dB axis would throw the\n  // known downstream plant clean off the top of the strip.\n  const cumulativeAllTotal =\n    cumulativeSegments.length > 0\n      ? cumulativeSegments[cumulativeSegments.length - 1].after\n      : 0;\n  const lossScaleMax = Math.max(\n    cumulativeTotal ?? 0,\n    cumulativeTraceTotal ?? 0,\n    cumulativeAllTotal,\n    0.1,\n  );\n\n  const innerWidth = VIEW_WIDTH - MARGIN.left - MARGIN.right;\n  const fibreY = height - MARGIN.bottom - 34;\n  const lossTop = MARGIN.top + 12;\n  const lossBottom = fibreY - 18;\n\n  const xFor = (metres: number) => MARGIN.left + (metres / axisEndM) * innerWidth;\n  const yForLoss = (loss: number) =>\n    lossBottom - (Math.max(0, loss) / lossScaleMax) * (lossBottom - lossTop);\n\n  const cumulativePath = useMemo(() => {\n    if (ordered.length === 0) return \"\";\n    const parts: string[] = [`M ${MARGIN.left} ${yForLoss(0)}`];\n    let previous = 0;\n    ordered.forEach((event, index) => {\n      // A fault ends the measurable fibre. Callers may still supply known\n      // downstream plant events, but their attenuation is not present in this\n      // trace and must not be drawn beyond the break.\n      if (faultM !== null && event.distanceM > faultM) {\n        return;\n      }\n      const x = xFor(event.distanceM);\n      // Slope across the section (a straight rise when measured slopes are\n      // present — `before` differs from `previous`), then the event's step.\n      parts.push(`L ${x} ${yForLoss(cumulativeSegments[index].before)}`);\n      parts.push(`L ${x} ${yForLoss(cumulativeSegments[index].after)}`);\n      previous = cumulativeSegments[index].after;\n    });\n    const measuredEndM = faultM !== null ? Math.min(faultM, axisEndM) : axisEndM;\n    parts.push(`L ${xFor(measuredEndM)} ${yForLoss(previous)}`);\n    return parts.join(\" \");\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [ordered, cumulativeSegments, axisEndM, lossScaleMax, height, faultM]);\n\n  // Real waveform, drawn the way instrument software draws it: a connected\n  // jagged polyline. Each pixel-ish bucket contributes its max (keeping\n  // reflection spikes and the backscatter line), and buckets with a large\n  // internal range — the noise floor — also contribute their min at the\n  // bucket's far edge, giving the characteristic hash without painting a\n  // solid block. Values are the file's own samples; nothing is smoothed.\n  // Samples past the axis end are the capture's post-end tail: clipped, not\n  // squeezed in, so the drawn waveform and the axis agree about the fibre.\n  // `clippedSamples` is how many the caller's declared length discarded — the\n  // strip says so rather than silently dropping measured data.\n  const traceClip = useMemo(() => {\n    if (!trace || trace.levelsDb.length < 2)\n      return { sampleCount: 0, clippedSamples: 0 };\n    const { stepM, levelsDb } = trace;\n    const startM = trace.startM ?? 0;\n    const sampleCount =\n      stepM > 0\n        ? Math.max(\n            0,\n            Math.min(levelsDb.length, Math.floor((axisEndM - startM) / stepM) + 1),\n          )\n        : levelsDb.length;\n    return { sampleCount, clippedSamples: levelsDb.length - sampleCount };\n  }, [trace, axisEndM]);\n\n  const tracePath = useMemo(() => {\n    if (!trace || trace.levelsDb.length < 2) return \"\";\n    const { stepM, levelsDb } = trace;\n    const startM = trace.startM ?? 0;\n    const { sampleCount } = traceClip;\n    if (sampleCount < 2) return \"\";\n    let minDb = Infinity;\n    let maxDb = -Infinity;\n    for (let i = 0; i < sampleCount; i += 1) {\n      const v = levelsDb[i];\n      if (v < minDb) minDb = v;\n      if (v > maxDb) maxDb = v;\n    }\n    const span = maxDb - minDb || 1;\n    const yFor = (db: number) =>\n      lossBottom - ((db - minDb) / span) * (lossBottom - lossTop);\n    // One bucket per rendered pixel of the plot area: a phone gets a few\n    // hundred vertices instead of a fixed 908, a 2000px screen is not\n    // undersampled. Falls back to the viewBox width until measured.\n    const buckets = Math.min(sampleCount, Math.max(1, Math.round(innerWidth * pxPerUnit)));\n    const perBucket = sampleCount / buckets;\n    const parts: string[] = [];\n    for (let b = 0; b < buckets; b += 1) {\n      const from = Math.floor(b * perBucket);\n      const to = Math.min(sampleCount, Math.max(from + 1, Math.floor((b + 1) * perBucket)));\n      let lo = Infinity;\n      let hi = -Infinity;\n      for (let i = from; i < to; i += 1) {\n        const v = levelsDb[i];\n        if (v < lo) lo = v;\n        if (v > hi) hi = v;\n      }\n      const xLeft = xFor(startM + from * stepM).toFixed(1);\n      parts.push(`${parts.length === 0 ? \"M\" : \"L\"} ${xLeft} ${yFor(hi).toFixed(1)}`);\n      // Only genuinely noisy buckets get their floor drawn — the smooth\n      // backscatter stays a line instead of a filled band.\n      if (hi - lo > 1.5) {\n        const xRight = xFor(startM + (to - 1) * stepM).toFixed(1);\n        parts.push(`L ${xRight} ${yFor(lo).toFixed(1)}`);\n      }\n    }\n    return parts.join(\" \");\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [trace, traceClip, axisEndM, height, pxPerUnit]);\n\n  // Clipping discards measured samples, so it is stated even when fewer than\n  // two samples survive and no measured path can be drawn. In that edge case\n  // the visible/accessible explanation also names the event-derived fallback;\n  // otherwise the derived line can look like a measured trace.\n  const clipNote =\n    traceClip.clippedSamples > 0\n      ? `capture clipped at ${\n          lengthM != null && axisEndM === lengthM\n            ? `${formatOtdrDistance(lengthM)} declared length`\n            : `${formatOtdrDistance(axisEndM)} axis end`\n        }${\n          traceClip.sampleCount < 2\n            ? `; ${\n                traceClip.sampleCount === 0\n                  ? \"no measured samples remain\"\n                  : \"only one measured sample remains\"\n              } — ${\n                ordered.length > 0\n                  ? \"showing derived cumulative loss from events\"\n                  : \"no events are available, so no trace can be drawn\"\n              }`\n            : \"\"\n        }`\n      : null;\n  // Keep the plotted caption short enough for a compact strip. The complete\n  // location/source explanation remains in the SVG name and this text's title.\n  const visibleClipNote =\n    clipNote && traceClip.sampleCount < 2\n      ? `capture clipped; ${\n          traceClip.sampleCount === 0 ? \"no measured samples\" : \"1 measured sample\"\n        } — ${ordered.length > 0 ? \"derived loss shown\" : \"no trace available\"}`\n      : clipNote;\n  const traceSourceNote = tracePath\n    ? \"measured backscatter shown\"\n    : ordered.length > 0\n      ? trace\n        ? \"measured trace unavailable; derived cumulative loss from events shown\"\n        : \"derived cumulative loss from events shown\"\n      : null;\n\n  // Greedy label thinning: a dB label renders only when its row (labels\n  // alternate above/below the fibre) has ~40 CSS pixels of clearance since the\n  // previous rendered label — a readability distance, so it is measured in\n  // rendered pixels and converted to viewBox units, not baked in as units.\n  // Dense circuits collapse to clean markers; the selected event always keeps\n  // its label.\n  const labelVisible = useMemo(() => {\n    const visible: boolean[] = new Array(ordered.length).fill(false);\n    const lastShown: Record<number, number> = { 0: -Infinity, 1: -Infinity };\n    const clearance = LABEL_CLEARANCE_PX / pxPerUnit;\n    ordered.forEach((event, index) => {\n      const row = index % 2;\n      const x = xFor(event.distanceM);\n      if (x - lastShown[row] >= clearance) {\n        visible[index] = true;\n        lastShown[row] = x;\n      }\n    });\n    return visible;\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [ordered, axisEndM, pxPerUnit]);\n\n  // Focus span from a linked map viewport, clamped to the axis and otherwise\n  // rendered exactly as given: whether \"everything is visible, so spotlight\n  // nothing\" is the producer's call — it knows what fraction of the route is on\n  // screen — and it says so by passing null.\n  const focus = useMemo(() => {\n    if (!focusRangeM) return null;\n    const [a, b] = focusRangeM;\n    const lo = Math.max(0, Math.min(a, b));\n    const hi = Math.min(axisEndM, Math.max(a, b));\n    return hi - lo > 0 ? ([lo, hi] as const) : null;\n  }, [focusRangeM, axisEndM]);\n  const inFocus = (metres: number) =>\n    focus === null || (metres >= focus[0] && metres <= focus[1]);\n\n  const caption = [\n    cumulativeTotal !== null && cumulativeTotal !== undefined\n      ? `Total loss ${cumulativeTotal.toFixed(2)} dB`\n      : null,\n    lengthM ? `Length ${formatOtdrDistance(lengthM)}` : null,\n    wavelengthNm ? `${wavelengthNm} nm` : null,\n    directionLabel ?? null,\n  ]\n    .filter(Boolean)\n    .join(\"  ·  \");\n\n  const handleCompactStripClick = (click: React.MouseEvent<SVGSVGElement>) => {\n    if (!compact || !onEventClick || ordered.length === 0) return;\n    const svg = svgRef.current;\n    if (!svg) return;\n    const bounds = svg.getBoundingClientRect();\n    if (bounds.width <= 0 || bounds.height <= 0) return;\n\n    const pointerX = ((click.clientX - bounds.left) / bounds.width) * VIEW_WIDTH;\n    const pointerY = ((click.clientY - bounds.top) / bounds.height) * height;\n    // Keep a roughly 48px touch target regardless of how far the viewBox has\n    // been scaled down. Resolving the nearest event avoids overlapping large\n    // hit circles choosing whichever marker happens to be painted last.\n    const targetRadiusX = (24 / bounds.width) * VIEW_WIDTH;\n    const targetRadiusY = (24 / bounds.height) * height;\n    if (Math.abs(pointerY - fibreY) > targetRadiusY) return;\n\n    let nearestIndex = 0;\n    let nearestDistance = Infinity;\n    ordered.forEach((event, index) => {\n      const distance = Math.abs(pointerX - xFor(event.distanceM));\n      if (distance < nearestDistance) {\n        nearestDistance = distance;\n        nearestIndex = index;\n      }\n    });\n    if (nearestDistance <= targetRadiusX) {\n      onEventClick(ordered[nearestIndex], nearestIndex);\n    }\n  };\n\n  return (\n    <svg\n      ref={svgRef}\n      onClick={compact && onEventClick ? handleCompactStripClick : undefined}\n      viewBox={`0 0 ${VIEW_WIDTH} ${height}`}\n      // A static strip is one graphic. With `onEventClick` its events are\n      // controls, and `img` would make every child presentational — so the\n      // strip becomes a named group whose event markers are buttons.\n      role={onEventClick ? \"group\" : \"img\"}\n      aria-label={`OTDR trace, ${ordered.length} events${caption ? `, ${caption}` : \"\"}${\n        clipNote ? `, ${clipNote}` : \"\"\n      }${traceSourceNote ? `, ${traceSourceNote}` : \"\"}`}\n      className={`rn-otdr-strip rn-otdr-${resolvedColorMode}`}\n      style={{\n        width: \"100%\",\n        height: \"auto\",\n        display: \"block\",\n        background: colors.background,\n        touchAction: compact && onEventClick ? \"manipulation\" : undefined,\n      }}\n      data-testid=\"otdr-trace-strip\"\n    >\n      <text x={MARGIN.left} y={18} fontSize={13 * fontScale} fontWeight={600} fill={colors.text}>\n        {caption || \"OTDR trace\"}\n      </text>\n\n      {/* measured backscatter waveform, when the caller has DataPts */}\n      {tracePath ? (\n        <path\n          d={tracePath}\n          fill=\"none\"\n          stroke={colors.cumulative}\n          strokeWidth={0.9}\n          strokeLinejoin=\"round\"\n          data-testid=\"otdr-backscatter\"\n        />\n      ) : null}\n      {tracePath ? (\n        <text\n          x={VIEW_WIDTH - MARGIN.right}\n          y={lossTop - 4}\n          fontSize={11}\n          textAnchor=\"end\"\n          fill={colors.cumulative}\n        >\n          measured backscatter (dB)\n        </text>\n      ) : null}\n      {clipNote ? (\n        <text\n          x={MARGIN.left}\n          y={lossTop - 4}\n          fontSize={9.5}\n          fill={colors.subtext}\n          data-testid=\"otdr-trace-clipped\"\n        >\n          <title>{clipNote}</title>\n          {visibleClipNote}\n        </text>\n      ) : null}\n\n      {/* cumulative loss step-line (derived) — superseded by the waveform */}\n      {ordered.length > 0 && !tracePath ? (\n        <>\n          <path\n            d={cumulativePath}\n            fill=\"none\"\n            stroke={colors.cumulative}\n            strokeWidth={1.8}\n            strokeDasharray=\"1 0\"\n            data-testid=\"otdr-cumulative\"\n          />\n          <text\n            x={VIEW_WIDTH - MARGIN.right}\n            y={yForLoss(cumulativeTraceTotal ?? 0) - 6}\n            fontSize={11}\n            textAnchor=\"end\"\n            fill={colors.cumulative}\n          >\n            {/* With a fault ahead of every event the trace measured no loss at\n                all — say which, rather than reading as a pristine 0.00 dB. */}\n            {cumulativeTraceTotal === null\n              ? \"0.00 dB cumulative to fault\"\n              : `${cumulativeTraceTotal.toFixed(2)} dB cumulative`}\n          </text>\n        </>\n      ) : null}\n\n      {/* fibre axis */}\n      <line\n        x1={MARGIN.left}\n        y1={fibreY}\n        x2={MARGIN.left + innerWidth}\n        y2={fibreY}\n        stroke={colors.fibre}\n        strokeWidth={3}\n        strokeLinecap=\"round\"\n      />\n      {/* Axis captions sit below the event-label band (fibreY + 20) so an\n          event near either end can't collide with them. */}\n      <text x={MARGIN.left} y={fibreY + 34} fontSize={11 * fontScale} fill={colors.subtext}>\n        0 m\n      </text>\n      <text\n        x={MARGIN.left + innerWidth}\n        y={fibreY + 34}\n        fontSize={11 * fontScale}\n        textAnchor=\"end\"\n        fill={colors.subtext}\n      >\n        {formatOtdrDistance(axisEndM)}\n      </text>\n\n      {/* focus band: the stretch currently visible on the linked map */}\n      {focus !== null ? (\n        <rect\n          x={xFor(focus[0])}\n          y={lossTop - 6}\n          width={Math.max(2, xFor(focus[1]) - xFor(focus[0]))}\n          height={fibreY + 10 - (lossTop - 6)}\n          fill={colors.eventFill}\n          opacity={0.08}\n          data-testid=\"otdr-focus-band\"\n        />\n      ) : null}\n\n      {/* events */}\n      {ordered.map((event, index) => {\n        const x = xFor(event.distanceM);\n        const isSelected = selectedEventIndex === index;\n        const labelAbove = index % 2 === 0;\n        const labelY = labelAbove ? fibreY - 14 : fibreY + 20;\n        // Past the fault the trace measured nothing — the derived line already\n        // stops there, so the markers beyond it are dimmed the same way an\n        // out-of-focus event is: still drawn, plainly not measured.\n        const beyondFault = faultM !== null && event.distanceM > faultM;\n        return (\n          <g\n            key={`${event.distanceM}-${index}`}\n            onClick={onEventClick && !compact ? () => onEventClick(event, index) : undefined}\n            // Keyboard path for the same action. Compact strips resolve\n            // pointer clicks at the SVG level (nearest event), but each\n            // event stays individually focusable and activatable.\n            onKeyDown={\n              onEventClick\n                ? activateOnEnterOrSpace<SVGGElement>(() => onEventClick(event, index))\n                : undefined\n            }\n            role={onEventClick ? \"button\" : undefined}\n            tabIndex={onEventClick ? 0 : undefined}\n            aria-label={onEventClick ? eventAccessibleName(event) : undefined}\n            aria-current={onEventClick && isSelected ? \"true\" : undefined}\n            className={onEventClick ? \"rn-otdr-event-clickable\" : undefined}\n            opacity={\n              (inFocus(event.distanceM) && !beyondFault) || isSelected ? 1 : 0.3\n            }\n            data-testid=\"otdr-event\"\n          >\n            <title>\n              {`${event.type ?? \"event\"} @ ${formatOtdrDistance(event.distanceM)} — loss ${event.lossDb.toFixed(3)} dB` +\n                (event.reflectanceDb !== null && event.reflectanceDb !== undefined\n                  ? `, reflectance ${event.reflectanceDb.toFixed(2)} dB`\n                  : \"\") +\n                (event.comment ? ` — ${event.comment}` : \"\")}\n            </title>\n            <line\n              x1={x}\n              y1={fibreY - 8}\n              x2={x}\n              y2={fibreY + 8}\n              stroke={colors.axis}\n              strokeWidth={1}\n            />\n            {onEventClick ? (\n              <>\n                {/* invisible enlarged hit target + visible affordance ring */}\n                <circle cx={x} cy={fibreY} r={14} fill=\"transparent\" />\n                <circle\n                  className=\"rn-otdr-event-ring\"\n                  cx={x}\n                  cy={fibreY}\n                  r={9.5}\n                  fill=\"none\"\n                  stroke={colors.eventFill}\n                  strokeWidth={isSelected ? 2.4 : 1.4}\n                  style={isSelected ? { opacity: 1 } : undefined}\n                  data-testid={isSelected ? \"otdr-event-ring-selected\" : \"otdr-event-ring\"}\n                />\n              </>\n            ) : null}\n            {markerForKind(\n              kindOf(event.type),\n              x,\n              fibreY,\n              colors,\n              1,\n              faultM !== null && Math.abs(event.distanceM - faultM) < 1,\n            )}\n            {(compact ? isSelected : labelVisible[index] || isSelected) ? (\n              <text\n                x={x}\n                y={labelY}\n                fontSize={10 * fontScale}\n                textAnchor=\"middle\"\n                fill={colors.text}\n              >\n                {event.lossDb.toFixed(2)} dB\n              </text>\n            ) : null}\n          </g>\n        );\n      })}\n\n      {/* legend: only the kinds present, worded no stronger than the data —\n          a raw reflective event is a connector OR a mechanical splice. */}\n      {ordered.length > 0 && !compact\n        ? (() => {\n            const present = new Set(ordered.map((event) => kindOf(event.type)));\n            const entries = LEGEND_ORDER.filter((kind) => present.has(kind));\n            let cursor = MARGIN.left;\n            const cy = height - 12;\n            return (\n              <g fontSize={9.5} fill={colors.subtext} data-testid=\"otdr-legend\">\n                {entries.map((kind) => {\n                  const label = LEGEND_LABEL[kind];\n                  const glyphX = cursor + 6;\n                  const textX = cursor + 15;\n                  cursor = textX + label.length * 5.4 + 16;\n                  return (\n                    <g key={kind}>\n                      {markerForKind(kind, glyphX, cy, colors, 0.8)}\n                      <text x={textX} y={cy + 3}>\n                        {label}\n                      </text>\n                    </g>\n                  );\n                })}\n              </g>\n            );\n          })()\n        : null}\n\n      {/* fault highlight */}\n      {faultM !== null ? (\n        <g data-testid=\"otdr-fault\">\n          <line\n            x1={xFor(Math.min(faultM, axisEndM))}\n            y1={lossTop}\n            x2={xFor(Math.min(faultM, axisEndM))}\n            y2={fibreY + 10}\n            stroke={colors.fault}\n            strokeWidth={1.6}\n            strokeDasharray=\"5 4\"\n          />\n          <text\n            x={xFor(Math.min(faultM, axisEndM))}\n            y={lossTop - 4}\n            fontSize={11}\n            fontWeight={600}\n            textAnchor=\"middle\"\n            fill={colors.fault}\n          >\n            fault @ {formatOtdrDistance(faultM)}\n          </text>\n        </g>\n      ) : null}\n    </svg>\n  );\n};\n\nexport default OtdrTraceStrip;\n"]}