@oicl/openbridge-webcomponents-full-bundle 2.0.0-next.57 → 2.0.0-next.58

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/bundle/openbridge-webcomponents.bundle.js +592 -239
  2. package/bundle/openbridge-webcomponents.bundle.js.map +1 -1
  3. package/custom-elements.json +292 -8
  4. package/dist/building-blocks/instrument-radial/instrument-radial.d.ts +10 -0
  5. package/dist/building-blocks/instrument-radial/instrument-radial.d.ts.map +1 -1
  6. package/dist/building-blocks/instrument-radial/instrument-radial.js +86 -21
  7. package/dist/building-blocks/instrument-radial/instrument-radial.js.map +1 -1
  8. package/dist/navigation-instruments/gauge-radial/gauge-radial.css.js +99 -0
  9. package/dist/navigation-instruments/gauge-radial/gauge-radial.css.js.map +1 -0
  10. package/dist/navigation-instruments/gauge-radial/gauge-radial.d.ts +42 -7
  11. package/dist/navigation-instruments/gauge-radial/gauge-radial.d.ts.map +1 -1
  12. package/dist/navigation-instruments/gauge-radial/gauge-radial.js +178 -31
  13. package/dist/navigation-instruments/gauge-radial/gauge-radial.js.map +1 -1
  14. package/dist/navigation-instruments/readout/readout.css.js +4 -0
  15. package/dist/navigation-instruments/readout/readout.css.js.map +1 -1
  16. package/dist/navigation-instruments/watch/tickmark.d.ts +2 -1
  17. package/dist/navigation-instruments/watch/tickmark.d.ts.map +1 -1
  18. package/dist/navigation-instruments/watch/tickmark.js +24 -4
  19. package/dist/navigation-instruments/watch/tickmark.js.map +1 -1
  20. package/dist/navigation-instruments/watch/watch.d.ts +23 -1
  21. package/dist/navigation-instruments/watch/watch.d.ts.map +1 -1
  22. package/dist/navigation-instruments/watch/watch.js +48 -20
  23. package/dist/navigation-instruments/watch/watch.js.map +1 -1
  24. package/package.json +1 -1
  25. package/src/building-blocks/instrument-radial/instrument-radial.stories.ts +21 -0
  26. package/src/building-blocks/instrument-radial/instrument-radial.ts +118 -21
  27. package/src/navigation-instruments/gauge-radial/gauge-radial.css +85 -0
  28. package/src/navigation-instruments/gauge-radial/gauge-radial.stories.ts +149 -7
  29. package/src/navigation-instruments/gauge-radial/gauge-radial.ts +212 -33
  30. package/src/navigation-instruments/readout/readout.css +4 -0
  31. package/src/navigation-instruments/watch/tickmark.ts +33 -6
  32. package/src/navigation-instruments/watch/watch.ts +60 -25
@@ -1 +1 @@
1
- {"version":3,"file":"watch.js","sources":["../../../src/navigation-instruments/watch/watch.ts"],"sourcesContent":["import {\n LitElement,\n PropertyValues,\n SVGTemplateResult,\n html,\n nothing,\n svg,\n unsafeCSS,\n} from 'lit';\nimport {property, state} from 'lit/decorators.js';\nimport {circle} from '../../svghelpers/index.js';\nimport {roundedArch} from '../../svghelpers/roundedArch.js';\nimport {\n cssSafeAngle,\n deriveRadialSetpointConfig,\n drawSetpointMarker,\n getSetpointAnimationDurationMs,\n getSetpointOutwardOffset,\n RADIAL_SETPOINT_RADIUS,\n SetpointVisualState,\n SETPOINT_ANIMATION_CSS_VAR,\n SETPOINT_ANIMATION_DURATION_DEFAULT,\n} from '../../svghelpers/setpoint.js';\nimport {InstrumentState, Priority} from '../types.js';\nimport compentStyle from './watch.css?inline';\nimport {ResizeController} from '@lit-labs/observers/resize-controller.js';\nimport {adviceMask, AngleAdviceRaw, renderAdvice} from './advice.js';\nimport {Tickmark, TickmarkStyle, tickmark} from './tickmark.js';\nexport {TickmarkStyle};\nimport {\n RotType,\n RotPosition,\n renderRotDots,\n renderRotBarStatic,\n renderRotBarDots,\n shortestAngularDeltaDeg,\n renderRotZeroPill,\n rotBarThresholdAngle,\n ROT_ZERO_DEADBAND_DEG,\n} from '../rate-of-turn/rot-renderer.js';\nexport {RotType, RotPosition};\nimport {\n RateOfTurnController,\n disposeRotController,\n} from '../rate-of-turn/rate-of-turn.controller.js';\nimport {\n renderLabels,\n renderNorthArrow,\n getLabelPositions,\n LabelPosition,\n} from './label.js';\nimport {VesselImage, VesselImageSize, vesselImages} from './vessel.js';\nimport {renderCurrent, renderWind} from './environment.js';\nimport {customElement} from '../../decorator.js';\nimport {\n computeZoomToFitArcFrame,\n type ZoomToFitArcFrame,\n} from '../../svghelpers/arc-frame.js';\nexport {VesselImage, VesselImageSize, vesselImages};\n\nexport enum WatchCircleType {\n single = 'single',\n double = 'double',\n doubleThin = 'doubleThin',\n triple = 'triple',\n}\n\nexport interface WatchArea {\n startAngle: number;\n endAngle: number;\n roundOutsideCut: boolean;\n roundInsideCut: boolean;\n}\n\nexport interface WatchBarArea {\n startAngle: number;\n endAngle: number;\n fillColor: string;\n}\n\nexport interface WatchNeedle {\n angle: number;\n fillColor: string;\n strokeColor: string;\n}\n\nexport interface WatchVessel {\n size: VesselImageSize;\n transform: string;\n vesselImage: VesselImage;\n}\n\nexport const OUTER_RING_RADIUS = 368 / 2;\nconst RING2_RADIUS = 320 / 2;\nconst RING3_RADIUS = 224 / 2;\nconst RING3B_RADIUS = 272 / 2;\nconst RING4_RADIUS = 176 / 2;\n\nexport function innerRingRadiusFor(type: WatchCircleType): number {\n switch (type) {\n case WatchCircleType.single:\n return RING2_RADIUS;\n case WatchCircleType.double:\n return RING3_RADIUS;\n case WatchCircleType.doubleThin:\n return RING3B_RADIUS;\n case WatchCircleType.triple:\n return RING4_RADIUS;\n default:\n throw new Error(`Unknown WatchCircleType: ${type as string}`);\n }\n}\n\nconst RADIAL_SETPOINT_INWARD_ADJUST = 4;\n\n/**\n * `<obc-watch>` - Core SVG renderer for circular/radial watch-based instruments.\n *\n * This component renders all circular instrument elements including rings, tickmarks,\n * bar areas, needles, advices, setpoints, vessel images, and environmental indicators\n * (wind/current). It serves as the foundation for compass, heading, rudder, speed-gauge,\n * and other radial navigation instruments.\n *\n * ## Setpoint Behavior\n *\n * The setpoint marker visual state is derived from the combination of `atAngleSetpoint`,\n * `angleSetpoint`, and `angleSetpointAtZeroDeadband` properties:\n *\n * - **notEqual**: Value differs from setpoint (triangular marker, offset outward)\n * - **equal**: Value matches setpoint (line marker, sits on ring)\n * - **equalZero**: Value matches setpoint at zero angle (double-line marker, offset outward)\n * - **focus**: User is actively adjusting via `newAngleSetpoint` - shows focus visual state\n *\n * ## newAngleSetpoint Pattern\n *\n * When `newAngleSetpoint` is defined, TWO setpoint markers are rendered:\n * 1. Original marker at `angleSetpoint` - dimmed (0.75 opacity)\n * 2. New marker at `newAngleSetpoint` - focus visual state, full opacity\n *\n * This enables the \"adjustment preview\" UX where users can see both the current\n * and proposed setpoint positions simultaneously.\n *\n * The `RADIAL_SETPOINT_INWARD_ADJUST` constant (4px) fine-tunes radial setpoint positioning\n * to match Figma designs, applied on top of visual state offsets from setpoint.ts.\n *\n * The `colorMode` property allows overriding the derived color mode (enhanced for enhanced priority,\n * regular for other states).\n *\n * ## Setpoint Animation (`animateSetpoint`)\n *\n * When `animateSetpoint` is true and a confirm occurs (`newAngleSetpoint` → `undefined`):\n * - The original setpoint slides to the new position via CSS transition\n * - The departing new-setpoint marker fades out\n * - Angular transitions always take the shortest path via accumulated\n * CSS-safe angles (`cssSafeAngle()`), so even 350° → 10° animates +20°\n *\n * Duration: `var(--setpoint-animation-duration, 300ms)`\n *\n * Internally, `_departingNewAngleSetpoint` captures the departing angle during confirm\n * fade-out and `_animationTimer` auto-clears it after the animation duration.\n * `_setpointCssAngle` tracks the accumulated CSS angle to avoid long-way-around\n * transitions across the 0°/360° boundary.\n *\n * @property {InstrumentState} state - Instrument state (active, loading, off)\n * @property {Priority} priority - Color priority (enhanced = blue palette, regular = gray palette)\n * @property {number|undefined} angleSetpoint - Setpoint angle in degrees (0° = 12 o'clock)\n * @property {number|undefined} newAngleSetpoint - New setpoint being adjusted (focus mode)\n * @property {boolean} atAngleSetpoint - Whether value matches setpoint (within deadband)\n * @property {number} angleSetpointAtZeroDeadband - Deadband for zero detection (default 0.5°)\n * @property {boolean} setpointOverride - Override to derive setpoint color from priority regardless of state\n * @property {RotType|undefined} rotType - ROT visualization type: `'dots'` (spinning dots) or `'bar'` (arc bar with clipped dots). Undefined hides the ROT layer.\n * @property {RotPosition} rotPosition - Track on which ROT elements are placed: `'scale'` (on the outer ring) or `'innerCircle'` (default, inside the inner ring)\n * @property {number} rotStartAngle - Start angle of the ROT bar arc in degrees (0° = 12 o'clock, clockwise). Only used when `rotType` is `'bar'`.\n * @property {number} rotEndAngle - End angle of the ROT bar arc in degrees. The bar is hidden when the difference from `rotStartAngle` is less than 0.1°.\n * @property {Priority|undefined} rotPriority - Override priority for ROT color derivation. When set, ROT colors use this instead of the main `priority`. Useful when the ROT element has independent priority (e.g. compass per-element priority).\n * @property {number|undefined} rateOfTurnDegreesPerMinute - Measured rate of turn in degrees per minute (the maritime/AIS convention, see ES-TRIN 2025/1 Art. 3.02 and ITU-R M.1371). Sign controls direction (positive = starboard/clockwise). When defined, this drives both the dot animation (multiplied by `rotDotAnimationFactor`) and the port/starboard direction sign.\n * @property {number} rotDotAnimationFactor - Visual amplification factor applied only to the spinning-dot animation (not to bar extent). Default `18` keeps the legacy visual feel (≈1 rpm at 20°/min).\n * @property {number} rotationsPerMinute - **Deprecated.** Spin speed of the ROT dot ring in rotations per minute. Sign controls direction (positive = clockwise). Use `rateOfTurnDegreesPerMinute` instead.\n * @property {ZoomToFitArcFrame|undefined} arcFrame - Pre-computed zoom-to-fit arc frame. When set, the watch skips its own `computeZoomToFitArcFrame()` call and uses these values directly. Consumer instruments (e.g. rudder, instrument-radial) should compute the frame once and pass it here to avoid redundant computation.\n */\n@customElement('obc-watch')\nexport class ObcWatch extends LitElement {\n private _setpointId = `watch-setpoint-${Math.random().toString(36).slice(2, 9)}`;\n private _newSetpointId = `watch-new-setpoint-${Math.random().toString(36).slice(2, 9)}`;\n\n @property({type: String}) state: InstrumentState = InstrumentState.active;\n @property({type: String}) priority: Priority = Priority.regular;\n @property({type: String}) watchCircleType: WatchCircleType =\n WatchCircleType.single;\n @property({type: Boolean}) northArrow: boolean = false;\n @property({type: Boolean}) northArrowInside: boolean | undefined;\n @property({type: Number}) angleSetpoint: number | undefined;\n @property({type: Number}) newAngleSetpoint: number | undefined;\n @property({type: Boolean}) atAngleSetpoint: boolean = false;\n @property({type: Number}) angleSetpointAtZeroDeadband: number = 0.5;\n @property({type: Boolean}) setpointOverride: boolean = false;\n @property({type: Boolean}) touching: boolean = false;\n\n @property({type: Boolean}) animateSetpoint: boolean = false;\n\n @state() private _departingNewAngleSetpoint: number | undefined;\n private _animationTimer?: ReturnType<typeof setTimeout>;\n\n /**\n * Accumulated CSS-safe angle for the original setpoint marker.\n * Ensures CSS rotate() transitions always take the shortest path,\n * even across the 0°/360° boundary.\n */\n private _setpointCssAngle: number = 0;\n\n /** Whether the setpoint CSS angle has been initialised (to skip transition on first render). */\n private _setpointCssAngleInit = false;\n @property({type: Number}) padding: number | undefined;\n @property({type: Array, attribute: false}) areas: WatchArea[] = [];\n @property({type: Array, attribute: false}) barAreas: WatchBarArea[] = [];\n @property({type: Array, attribute: false}) needles: WatchNeedle[] = [];\n @property({type: Array, attribute: false}) tickmarks: Tickmark[] = [];\n @property({type: Boolean}) tickmarksInside: boolean = false;\n @property({type: String}) tickmarkStyle: TickmarkStyle =\n TickmarkStyle.regular;\n @property({type: Array, attribute: false}) advices: AngleAdviceRaw[] = [];\n @property({type: Boolean}) crosshairEnabled: boolean = false;\n @property({type: Boolean}) showLabels: boolean = false;\n @property({type: Array, attribute: false}) vessels: WatchVessel[] = [];\n @property({type: Number}) windKnots: number | null = null;\n @property({type: Number}) windFromDirectionDeg: number | null = null;\n @property({type: Number}) windSymbolRadius: number | null = null;\n @property({type: String}) windColor: string | undefined;\n @property({type: Number}) current: number | null = null;\n @property({type: Number}) currentFromDirectionDeg: number | null = null;\n @property({type: Number}) currentSymbolRadius: number | null = null;\n @property({type: String}) currentColor: string | undefined;\n @property({type: Boolean}) starboardPortIndicator: boolean = false;\n @property({type: Number}) clipTop: number = 0; // in percent of height\n @property({type: Number}) clipBottom: number = 0; // in percent of height\n @property({type: Number}) scaleWindIcon: number = 1;\n @property({type: Number}) rotation: number | undefined;\n @property({type: Boolean}) zoomToFitArc: boolean = false;\n @property({attribute: false}) arcFrame: ZoomToFitArcFrame | undefined;\n @property({type: Number}) tickFadeAngle: number = 0;\n\n @property({type: String}) rotType: RotType | undefined;\n @property({type: String}) rotPosition: RotPosition = RotPosition.innerCircle;\n @property({type: Number}) rotStartAngle: number = 0;\n @property({type: Number}) rotEndAngle: number = 0;\n @property({type: String}) rotPriority: Priority | undefined;\n @property({type: Boolean}) rotPortStarboard: boolean = false;\n @property({type: Number}) rotAtZeroDeadband: number = ROT_ZERO_DEADBAND_DEG;\n @property({type: Number}) rateOfTurnDegreesPerMinute: number | undefined;\n @property({type: Number}) rotDotAnimationFactor: number = 18;\n /**\n * @deprecated Use `rateOfTurnDegreesPerMinute` (and optionally `rotDotAnimationFactor`) instead.\n * Kept as a backward-compatible alias; takes effect only when\n * `rateOfTurnDegreesPerMinute` is `undefined`.\n */\n @property({type: Number})\n set rotationsPerMinute(value: number) {\n this._legacyRotationsPerMinute = value;\n }\n get rotationsPerMinute() {\n return this._legacyRotationsPerMinute;\n }\n private _legacyRotationsPerMinute = 0;\n private _rotController?: RateOfTurnController;\n\n /**\n * Effective rotations-per-minute for the spinning dot animation and\n * port/starboard direction sign. Resolves to:\n * • `(rateOfTurnDegreesPerMinute / 360) * rotDotAnimationFactor` when the\n * new physical API is in use, OR\n * • the legacy `rotationsPerMinute` value otherwise.\n */\n private get _effectiveRpm(): number {\n if (this.rateOfTurnDegreesPerMinute != null) {\n return (\n (this.rateOfTurnDegreesPerMinute / 360) * this.rotDotAnimationFactor\n );\n }\n return this._legacyRotationsPerMinute;\n }\n\n // @ts-expect-error TS6133: The controller ensures that the render\n // function is called on resize of the element\n private _resizeController = new ResizeController(this, {});\n\n override willUpdate(changed: PropertyValues): void {\n super.willUpdate(changed);\n\n // Push the resolved effective RPM into the live controller whenever any\n // of the inputs that compose it change.\n if (\n this._rotController &&\n (changed.has('rateOfTurnDegreesPerMinute') ||\n changed.has('rotDotAnimationFactor') ||\n changed.has('rotationsPerMinute'))\n ) {\n this._rotController.rotationsPerMinute = this._effectiveRpm;\n }\n\n // Detect confirm: newAngleSetpoint was defined, now undefined\n if (changed.has('newAngleSetpoint') && this.animateSetpoint) {\n const prev = changed.get('newAngleSetpoint') as number | undefined;\n if (prev !== undefined && this.newAngleSetpoint === undefined) {\n this._departingNewAngleSetpoint = prev;\n clearTimeout(this._animationTimer);\n const duration = getSetpointAnimationDurationMs(this);\n this._animationTimer = setTimeout(() => {\n this._departingNewAngleSetpoint = undefined;\n }, duration);\n }\n }\n }\n\n override disconnectedCallback(): void {\n super.disconnectedCallback();\n clearTimeout(this._animationTimer);\n this._rotController = disposeRotController(this, this._rotController);\n }\n\n override updated(changed: PropertyValues): void {\n super.updated(changed);\n const el = this.rotType\n ? this.renderRoot.querySelector('#rot-spinner')\n : null;\n if (!el) {\n this._rotController = disposeRotController(this, this._rotController);\n return;\n }\n if (!this._rotController || this._rotController.el !== el) {\n this._rotController = disposeRotController(this, this._rotController);\n this._rotController = new RateOfTurnController(\n this,\n el,\n this._effectiveRpm\n );\n }\n }\n\n private get innerRingRadius(): number {\n return innerRingRadiusFor(this.watchCircleType);\n }\n\n private _rOff = 0;\n\n private watchCircle(): SVGTemplateResult | SVGTemplateResult[] {\n const rings = [];\n if (this.state !== InstrumentState.off) {\n rings.push(svg`\n <circle\n cx=\"0\"\n cy=\"0\"\n r=\"${172 + this._rOff}\"\n stroke=\"var(--instrument-frame-primary-color)\"\n fill=\"none\"\n stroke-width=\"24\"\n />`);\n\n if (this.watchCircleType !== WatchCircleType.single) {\n const r1 = RING2_RADIUS + this._rOff;\n const r2 =\n (this.watchCircleType === WatchCircleType.doubleThin\n ? RING3B_RADIUS\n : RING3_RADIUS) + this._rOff;\n const r = (r1 + r2) / 2;\n const strokeWidth = r1 - r2;\n rings.push(\n svg`\n <circle cx=\"0\" cy=\"0\" r=${r} stroke=\"var(--instrument-frame-secondary-color)\" stroke-width=${strokeWidth} fill=\"none\" />\n <circle cx=\"0\" cy=\"0\" r=${r1} stroke=\"var(--instrument-frame-secondary-color)\" stroke-width=\"1\" fill=\"none\" vector-effect=\"non-scaling-stroke\" />\n <circle cx=\"0\" cy=\"0\" r=${r2} stroke=\"var(--instrument-frame-secondary-color)\" stroke-width=\"1\" fill=\"none\" vector-effect=\"non-scaling-stroke\" />\n `\n );\n }\n if (this.watchCircleType === WatchCircleType.triple) {\n const r1 = RING3_RADIUS + this._rOff;\n const r2 = RING4_RADIUS + this._rOff;\n const r = (r1 + r2) / 2;\n const strokeWidth = r1 - r2;\n rings.push(\n svg`<circle cx=\"0\" cy=\"0\" r=${r} stroke=\"var(--instrument-frame-primary-color)\" stroke-width=${strokeWidth} fill=\"none\" />`\n );\n }\n }\n\n const maskSize = Math.max(200, OUTER_RING_RADIUS + this._rOff + 50);\n let result = rings;\n if (this.areas.length > 0) {\n const areas = this.areas.map((area) => {\n const svgPath = roundedArch({\n startAngle: area.startAngle,\n endAngle: area.endAngle,\n R: OUTER_RING_RADIUS + this._rOff,\n r: this.innerRingRadius + this._rOff,\n roundOutsideCut: area.roundOutsideCut,\n roundInsideCut: area.roundInsideCut,\n });\n return svgPath;\n });\n const mask = svg`<mask id=\"cutMask\">\n <rect x=\"${-maskSize}\" y=\"${-maskSize}\" width=\"${maskSize * 2}\" height=\"${maskSize * 2}\" fill=\"black\" />\n ${areas.map((area) => svg`<path d=${area} fill=\"white\" vector-effect=\"non-scaling-stroke\" stroke=\"white\" stroke-width=\"1\"/>`)}\n </mask>`;\n // clipPath for ROT uses r=0 so dots at any track radius stay visible\n const rotClip = svg`<clipPath id=\"rot-arc-clip\">${this.areas.map(\n (area) =>\n svg`<path d=${roundedArch({\n startAngle: area.startAngle,\n endAngle: area.endAngle,\n R: OUTER_RING_RADIUS + this._rOff + 20,\n r: 0,\n roundOutsideCut: area.roundOutsideCut,\n roundInsideCut: area.roundInsideCut,\n })} />`\n )}</clipPath>`;\n result = [mask, rotClip, svg`<g mask=\"url(#cutMask)\">${rings}</g>`];\n areas.forEach((area) => {\n result.push(\n svg`<path d=${area} fill=\"none\" stroke=\"var(--instrument-frame-tertiary-color)\" vector-effect=\"non-scaling-stroke\"/>`\n );\n });\n } else {\n if (this.state !== InstrumentState.off) {\n result.push(\n circle('outerRing', {\n radius: OUTER_RING_RADIUS + this._rOff,\n strokeWidth: 1,\n strokeColor: 'var(--instrument-frame-tertiary-color)',\n strokePosition: 'center',\n fillColor: 'none',\n })\n );\n\n result.push(svg`\n ${circle('innerRing', {\n radius: this.innerRingRadius + this._rOff,\n strokeWidth: 1,\n strokeColor: 'var(--instrument-frame-tertiary-color)',\n strokePosition: 'center',\n fillColor: 'none',\n })}\n `);\n } else {\n result.push(svg`\n ${circle('innerRing', {\n radius: OUTER_RING_RADIUS + this._rOff,\n strokeWidth: 1,\n strokeColor: 'var(--instrument-frame-tertiary-color)',\n strokePosition: 'center',\n fillColor: 'none',\n })}\n `);\n }\n }\n return result;\n }\n\n private _renderTickFadeDefs(): SVGTemplateResult | typeof nothing {\n if (this.tickFadeAngle <= 0 || this.areas.length === 0) return nothing;\n const area = this.areas[0];\n const arcSpan = area.endAngle - area.startAngle;\n const fade = Math.min(this.tickFadeAngle, arcSpan / 4);\n if (fade < 0.5) return nothing;\n\n const {startAngle, endAngle} = area;\n const R = OUTER_RING_RADIUS + this._rOff + 200;\n const toRad = (deg: number) => (deg * Math.PI) / 180;\n const px = (deg: number) => R * Math.sin(toRad(deg));\n const py = (deg: number) => -R * Math.cos(toRad(deg));\n\n const pieSlice = (a: number, b: number): string => {\n const x1 = px(a),\n y1 = py(a);\n const x2 = px(b),\n y2 = py(b);\n const largeArc = b - a > 180 ? 1 : 0;\n return `M 0 0 L ${x1} ${y1} A ${R} ${R} 0 ${largeArc} 1 ${x2} ${y2} Z`;\n };\n\n const Rm = (OUTER_RING_RADIUS + this.innerRingRadius) / 2 + this._rOff;\n const gx = (deg: number) => Rm * Math.sin(toRad(deg));\n const gy = (deg: number) => -Rm * Math.cos(toRad(deg));\n\n return svg`\n <defs>\n <linearGradient id=\"tickFadeL\" gradientUnits=\"userSpaceOnUse\"\n x1=\"${gx(startAngle)}\" y1=\"${gy(startAngle)}\"\n x2=\"${gx(startAngle + fade)}\" y2=\"${gy(startAngle + fade)}\">\n <stop offset=\"0\" stop-color=\"black\" />\n <stop offset=\"1\" stop-color=\"white\" />\n </linearGradient>\n <linearGradient id=\"tickFadeR\" gradientUnits=\"userSpaceOnUse\"\n x1=\"${gx(endAngle - fade)}\" y1=\"${gy(endAngle - fade)}\"\n x2=\"${gx(endAngle)}\" y2=\"${gy(endAngle)}\">\n <stop offset=\"0\" stop-color=\"white\" />\n <stop offset=\"1\" stop-color=\"black\" />\n </linearGradient>\n <mask id=\"tickFadeMask\" maskUnits=\"userSpaceOnUse\"\n x=\"${-R}\" y=\"${-R}\" width=\"${R * 2}\" height=\"${R * 2}\">\n <path d=\"${pieSlice(startAngle + fade, endAngle - fade)}\" fill=\"white\" />\n <path d=\"${pieSlice(startAngle, startAngle + fade)}\" fill=\"url(#tickFadeL)\" />\n <path d=\"${pieSlice(endAngle - fade, endAngle)}\" fill=\"url(#tickFadeR)\" />\n </mask>\n </defs>\n `;\n }\n\n private renderCrosshair(\n radius: number,\n labelKnockouts?: {\n positions: LabelPosition[];\n rotation: number | undefined;\n scale: number;\n /** Inner ring radius – crosshair is hidden between labelRadius and this value. */\n innerRingRadius: number;\n }\n ): SVGTemplateResult {\n const hasMask = labelKnockouts && labelKnockouts.positions.length > 0;\n\n // Radius at which labels sit (distance from centre).\n // Any position is equally valid — they're all at the same radial distance.\n const labelRadius = hasMask\n ? Math.max(\n ...labelKnockouts!.positions.map((l) =>\n Math.abs(l.x !== 0 ? l.x : l.y)\n )\n )\n : 0;\n // Small extra padding so the crosshair doesn't start/end right at the\n // label edge — use the same visual pad as the letter knockouts.\n const ringGapPad = hasMask ? 3 / labelKnockouts!.scale : 0;\n\n return svg`\n ${\n hasMask\n ? svg`\n <defs>\n <mask\n id=\"crosshair-label-mask\"\n maskUnits=\"userSpaceOnUse\"\n x=\"-${radius}\" y=\"-${radius}\"\n width=\"${radius * 2}\" height=\"${radius * 2}\"\n >\n <rect x=\"-${radius}\" y=\"-${radius}\" width=\"${radius * 2}\" height=\"${radius * 2}\" fill=\"white\"/>\n <!-- Annular ring knockout: hide crosshair between labels and inner ring -->\n <circle cx=\"0\" cy=\"0\" r=\"${labelKnockouts!.innerRingRadius}\" fill=\"black\"/>\n <circle cx=\"0\" cy=\"0\" r=\"${labelRadius - ringGapPad}\" fill=\"white\"/>\n <!-- Per-label rectangular knockouts -->\n ${labelKnockouts!.positions.map((l) => {\n const fontSize = 12 / labelKnockouts!.scale;\n const pad = 3 / labelKnockouts!.scale;\n const size = fontSize + pad * 2;\n return svg`\n <rect\n x=\"${l.x - size / 2}\" y=\"${l.y - size / 2}\"\n width=\"${size}\" height=\"${size}\"\n fill=\"black\"\n transform=\"rotate(${-(labelKnockouts!.rotation ?? 0)})\"\n transform-origin=\"${l.x} ${l.y}\"\n />\n `;\n })}\n </mask>\n </defs>`\n : nothing\n }\n <g mask=${hasMask ? 'url(#crosshair-label-mask)' : nothing}>\n <line\n x1=\"-${radius}\"\n y1=\"0\"\n x2=\"${radius}\"\n y2=\"0\"\n stroke=\"var(--instrument-frame-tertiary-color)\"\n stroke-width=\"1\"\n vector-effect=\"non-scaling-stroke\"\n />\n <line\n x1=\"0\"\n y1=\"-${radius}\"\n x2=\"0\"\n y2=\"${radius}\"\n stroke=\"var(--instrument-frame-tertiary-color)\"\n stroke-width=\"1\"\n vector-effect=\"non-scaling-stroke\"\n />\n </g>\n `;\n }\n\n private renderBars(): SVGTemplateResult[] | typeof nothing {\n if (this.barAreas.length === 0) {\n return nothing;\n }\n return this.barAreas.map((bar, index) => {\n const startAngle = Math.min(bar.startAngle, bar.endAngle);\n const endAngle = Math.max(bar.startAngle, bar.endAngle);\n const arc = roundedArch({\n r: RING3_RADIUS + this._rOff,\n R: RING2_RADIUS + this._rOff,\n startAngle: startAngle,\n endAngle: endAngle,\n roundInsideCut: false,\n roundOutsideCut: false,\n });\n const barMaskR = RING2_RADIUS + this._rOff + 40;\n // The mask is a sector to cut out the stroke on the start and end of the bar\n const mask = svg`<mask id=\"barMask-${index}\">\n <rect x=\"${-barMaskR}\" y=\"${-barMaskR}\" width=\"${barMaskR * 2}\" height=\"${barMaskR * 2}\" fill=\"black\" />\n <path d=${roundedArch({\n r: 1,\n R: barMaskR,\n startAngle: startAngle,\n endAngle: endAngle,\n roundInsideCut: false,\n roundOutsideCut: false,\n })} fill=\"white\" />\n </mask>`;\n return svg`\n ${mask}\n <g mask=\"url(#cutMask)\">\n <path \n d=${arc} \n fill=${bar.fillColor} \n stroke=${bar.fillColor} \n stroke-width=\"1\" \n vector-effect=\"non-scaling-stroke\" \n mask=\"url(#barMask-${index})\" \n />\n </g>\n `;\n });\n }\n\n private renderNeedles(): SVGTemplateResult[] | typeof nothing {\n if (this.needles.length === 0) {\n return nothing;\n }\n return this.needles.map((needle) => {\n return svg`\n <rect \n transform=\"rotate(${needle.angle})\" \n x=\"-4\" y=\"${-(RING2_RADIUS + this._rOff)}\" width=\"8\" height=\"48\" rx=\"4\" \n fill=${needle.fillColor} \n stroke=${needle.strokeColor}\n stroke-width=\"1\"\n vector-effect=\"non-scaling-stroke\"\n paint-order=\"stroke fill\"\n />\n `;\n });\n }\n\n private getScale({width, height}: {width: number; height: number}): number {\n let clientWidth = this.clientWidth;\n let clientHeight = this.clientHeight;\n if (clientWidth === 0 || clientHeight === 0) {\n const box = this.parentElement?.getBoundingClientRect();\n if (box) {\n clientWidth = box.width;\n clientHeight = box.height;\n }\n }\n const scale = Math.min(clientWidth / width, clientHeight / height);\n if (scale === Infinity || scale < 0) {\n throw new Error('Watch scale is not valid');\n }\n return scale;\n }\n\n private getPadding(): number {\n if (this.padding !== undefined) {\n return this.padding;\n }\n const hasTickmarksWithText =\n this.tickmarks.length > 0 &&\n this.tickmarks.some((t) => t.text !== undefined);\n if (hasTickmarksWithText && !this.tickmarksInside) {\n return 24 * 2.5;\n }\n return 24;\n }\n\n override render() {\n let width: number;\n let height: number;\n let viewBox: string;\n\n if (this.arcFrame) {\n this._rOff = this.arcFrame.radiusOffset;\n width = this.arcFrame.width;\n height = this.arcFrame.height;\n viewBox = this.arcFrame.viewBox;\n } else if (this.zoomToFitArc && this.areas.length > 0) {\n const ext = this.getPadding();\n const targetSize = (176 + ext) * 2;\n const frame = computeZoomToFitArcFrame({\n areas: this.areas,\n outerRadius: OUTER_RING_RADIUS,\n innerRadius: this.innerRingRadius,\n extension: ext,\n targetSize,\n });\n this._rOff = frame.radiusOffset;\n width = frame.width;\n height = frame.height;\n viewBox = frame.viewBox;\n } else {\n this._rOff = 0;\n width = (176 + this.getPadding()) * 2;\n height = width * (1 - this.clipTop / 100 - this.clipBottom / 100);\n const top = -width / 2 + (width * this.clipTop) / 100;\n viewBox = `-${width / 2} ${top} ${width} ${height}`;\n }\n\n const rOff = this._rOff;\n const scale = this.getScale({width, height});\n const angleSetpoint = this.renderSetpoint();\n const textRadius =\n (this.tickmarksInside ? this.innerRingRadius : OUTER_RING_RADIUS) + rOff;\n const maxDigits = Math.max(\n ...this.tickmarks.map((t) => t.text?.length ?? 0)\n );\n const tickmarks = this.tickmarks.map((t) =>\n tickmark(t.angle, {\n size: t.type,\n style: this.tickmarkStyle,\n scale,\n text: this.showLabels ? undefined : t.text,\n inside: this.tickmarksInside,\n textRadius,\n rotation: this.rotation,\n maxDigits,\n color: t.color,\n radiusOffset: rOff,\n })\n );\n const advices = this.advices\n ? this.advices.map((a) => renderAdvice(a, rOff))\n : nothing;\n\n // Compute label positions once – used for both rendering and crosshair knockout.\n const insideLabels = this.tickmarksInside && this.showLabels;\n const includeNorth = !this.northArrow;\n const labelPositions = this.showLabels\n ? getLabelPositions({\n scale,\n inside: this.tickmarksInside,\n innerRadius: this.innerRingRadius + rOff,\n includeNorth,\n })\n : undefined;\n\n const labels = labelPositions\n ? renderLabels({\n scale,\n rotation: this.rotation,\n inside: this.tickmarksInside,\n innerRadius: this.innerRingRadius + rOff,\n includeNorth,\n })\n : nothing;\n const northArrowEl = this.northArrow\n ? renderNorthArrow({\n scale,\n rotation: this.rotation,\n inside: this.northArrowInside ?? this.tickmarksInside,\n })\n : nothing;\n const wind =\n this.windKnots != null && this.windFromDirectionDeg != null\n ? svg`<g transform=\"scale(${this.scaleWindIcon})\">${renderWind({\n windKnots: this.windKnots,\n fromDirectionDeg: this.windFromDirectionDeg,\n radius: this.windSymbolRadius ?? 192,\n color: this.windColor,\n })}</g>`\n : nothing;\n const current =\n this.current != null && this.currentFromDirectionDeg != null\n ? renderCurrent({\n current: this.current,\n fromDirectionDeg: this.currentFromDirectionDeg,\n radius: this.currentSymbolRadius ?? 192,\n color: this.currentColor,\n })\n : nothing;\n return html`\n <svg\n width=\"100%\"\n height=\"100%\"\n viewBox=${viewBox}\n style=\"--scale: ${scale}\"\n transform=\"rotate(${this.rotation ?? 0})\"\n >\n ${this.watchCircle()} ${this.renderBars()}\n ${this.crosshairEnabled\n ? this.renderCrosshair(\n OUTER_RING_RADIUS + rOff,\n insideLabels && labelPositions\n ? {\n positions: labelPositions,\n rotation: this.rotation,\n scale,\n innerRingRadius: this.innerRingRadius + rOff,\n }\n : undefined\n )\n : nothing}\n ${northArrowEl} ${this.renderStarboardPortIndicator()} ${current}\n ${this._renderTickFadeDefs()} ${wind}\n ${this.tickFadeAngle > 0 && this.areas.length > 0\n ? svg`<g mask=\"url(#tickFadeMask)\">${tickmarks}</g>`\n : tickmarks}\n ${this.areas.length > 0\n ? svg`<g clip-path=\"url(#rot-arc-clip)\">${this.renderRot()}</g>`\n : this.renderRot()}\n ${advices} ${angleSetpoint}\n ${this.tickFadeAngle > 0 && this.areas.length > 0\n ? svg`<g mask=\"url(#tickFadeMask)\">${labels}</g>`\n : labels}\n ${this.renderVesselImage()} ${this.renderNeedles()}\n </svg>\n `;\n }\n\n private getRotColors(): {\n dotColor: string;\n barBgColor: string;\n } {\n const p = this.rotPriority ?? this.priority;\n const isEnhanced = p === Priority.enhanced;\n\n if (this.rotPortStarboard) {\n // For bar type, derive direction from bar angles (visual direction);\n // for dots, use spinner RPM.\n let direction: number;\n if (this.rotType === RotType.bar) {\n const cwSpan =\n (((this.rotEndAngle - this.rotStartAngle) % 360) + 360) % 360;\n direction = cwSpan <= 180 ? cwSpan : cwSpan - 360;\n } else {\n direction = this._effectiveRpm;\n }\n\n if (direction > 0) {\n return {\n dotColor: 'var(--instrument-starboard-secondary-color)',\n barBgColor: 'var(--instrument-starboard-primary-color)',\n };\n }\n if (direction < 0) {\n return {\n dotColor: 'var(--instrument-port-secondary-color)',\n barBgColor: 'var(--instrument-port-primary-color)',\n };\n }\n }\n\n return {\n dotColor: isEnhanced\n ? 'var(--instrument-enhanced-tertiary-color)'\n : 'var(--instrument-regular-tertiary-color)',\n barBgColor: isEnhanced\n ? 'var(--instrument-enhanced-secondary-color)'\n : 'var(--instrument-regular-secondary-color)',\n };\n }\n\n private renderRot(): SVGTemplateResult | typeof nothing {\n if (!this.rotType) return nothing;\n\n const {dotColor, barBgColor} = this.getRotColors();\n const rOff = this._rOff;\n\n if (this.rotType === RotType.bar) {\n const angularDelta = shortestAngularDeltaDeg(\n this.rotStartAngle,\n this.rotEndAngle\n );\n const threshold = rotBarThresholdAngle(this.rotPosition, rOff);\n const zeroDb = Number.isFinite(this.rotAtZeroDeadband)\n ? this.rotAtZeroDeadband\n : ROT_ZERO_DEADBAND_DEG;\n\n if (angularDelta < Math.max(zeroDb, threshold)) {\n return renderRotZeroPill(\n barBgColor,\n this.rotStartAngle,\n this.rotPosition,\n rOff\n );\n }\n\n return svg`\n ${renderRotBarStatic({\n startAngle: this.rotStartAngle,\n endAngle: this.rotEndAngle,\n barColor: barBgColor,\n position: this.rotPosition,\n maskId: 'rot-bar-mask',\n radiusOffset: rOff,\n })}\n ${svg`<g clip-path=\"url(#rot-bar-mask)\">\n <g id=\"rot-spinner\">\n ${renderRotBarDots(dotColor, this.rotPosition, rOff)}\n </g>\n </g>`}\n `;\n }\n\n const p = this.rotPriority ?? this.priority;\n const isEnhanced = p === Priority.enhanced;\n let dotsColor: string = isEnhanced\n ? 'var(--instrument-enhanced-secondary-color)'\n : 'var(--instrument-regular-secondary-color)';\n if (this.rotPortStarboard) {\n if (this._effectiveRpm > 0) {\n dotsColor = 'var(--instrument-starboard-secondary-color)';\n } else if (this._effectiveRpm < 0) {\n dotsColor = 'var(--instrument-port-secondary-color)';\n }\n }\n return svg`\n <g id=\"rot-spinner\">\n ${renderRotDots(dotsColor, this.rotPosition, rOff)}\n </g>\n `;\n }\n\n private renderSetpoint(): SVGTemplateResult | typeof nothing {\n if (this.angleSetpoint === undefined) {\n return nothing;\n }\n\n const derived = deriveRadialSetpointConfig({\n state: this.state,\n priority: this.priority,\n atSetpoint: this.atAngleSetpoint,\n angleSetpoint: this.angleSetpoint,\n setpointAtZeroDeadband: this.angleSetpointAtZeroDeadband,\n newAngleSetpoint: this.newAngleSetpoint,\n touching: this.touching,\n setpointOverride: this.setpointOverride,\n });\n\n const {visualState, colorMode, disabled, hasNewSetpoint} = derived;\n\n const outwardOffset = getSetpointOutwardOffset(visualState);\n const radius =\n RADIAL_SETPOINT_RADIUS +\n this._rOff +\n outwardOffset -\n RADIAL_SETPOINT_INWARD_ADJUST;\n\n // Render original setpoint marker (dimmed when newAngleSetpoint is active)\n const opacity = hasNewSetpoint ? 0.75 : 1;\n const originalMarker = drawSetpointMarker({\n visualState,\n colorMode,\n disabled,\n id: this._setpointId,\n });\n\n const animate = this.animateSetpoint;\n const hasDeparting = this._departingNewAngleSetpoint !== undefined;\n\n // Compute CSS-safe accumulated angle so transitions always take the short path\n const rawAngle = this.angleSetpoint + 90;\n if (!this._setpointCssAngleInit) {\n // First render: set angle without transition\n this._setpointCssAngle = rawAngle;\n this._setpointCssAngleInit = true;\n } else {\n this._setpointCssAngle = cssSafeAngle(this._setpointCssAngle, rawAngle);\n }\n\n // Use CSS style transform when animating for smooth transition\n const originalSetpoint = animate\n ? svg`\n <g style=\"transform: rotate(${this._setpointCssAngle}deg) translateX(${-radius}px) rotate(270deg); opacity: ${opacity}; transition: transform var(${SETPOINT_ANIMATION_CSS_VAR}, ${SETPOINT_ANIMATION_DURATION_DEFAULT}) ease-out, opacity var(${SETPOINT_ANIMATION_CSS_VAR}, ${SETPOINT_ANIMATION_DURATION_DEFAULT}) ease-out;\">\n ${originalMarker}\n </g>\n `\n : svg`\n <g transform=\"rotate(${this.angleSetpoint + 90}) translate(${-radius}, 0) rotate(270)\" opacity=\"${opacity}\">\n ${originalMarker}\n </g>\n `;\n\n // Render newAngleSetpoint in focus state (always on top)\n // OR render departing newAngleSetpoint during confirm fade-out\n if (hasNewSetpoint || hasDeparting) {\n const isActive = hasNewSetpoint;\n const newAngle = isActive\n ? this.newAngleSetpoint!\n : this._departingNewAngleSetpoint!;\n const targetOpacity = isActive ? 1 : 0;\n\n const focusOutwardOffset = getSetpointOutwardOffset(\n SetpointVisualState.focus\n );\n const focusRadius =\n RADIAL_SETPOINT_RADIUS +\n this._rOff +\n focusOutwardOffset -\n RADIAL_SETPOINT_INWARD_ADJUST;\n\n const newMarker = drawSetpointMarker({\n visualState: SetpointVisualState.focus,\n colorMode,\n disabled: false, // newSetpoint is never disabled\n id: this._newSetpointId,\n });\n\n if (animate) {\n const duration = `var(${SETPOINT_ANIMATION_CSS_VAR}, ${SETPOINT_ANIMATION_DURATION_DEFAULT})`;\n return svg`\n ${originalSetpoint}\n <g style=\"transform: rotate(${newAngle + 90}deg) translateX(${-focusRadius}px) rotate(270deg); opacity: ${targetOpacity}; transition: opacity ${duration} ease-out;\">\n ${newMarker}\n </g>\n `;\n }\n\n return svg`\n ${originalSetpoint}\n <g transform=\"rotate(${newAngle + 90}) translate(${-focusRadius}, 0) rotate(270)\" opacity=\"${targetOpacity}\">\n ${newMarker}\n </g>\n `;\n }\n\n return originalSetpoint;\n }\n\n private renderVesselImage(): SVGTemplateResult[] | typeof nothing {\n if (this.vessels.length === 0) {\n return nothing;\n }\n\n return this.vessels.map((v) => {\n let size;\n switch (v.size) {\n case VesselImageSize.large:\n size = 224;\n break;\n case VesselImageSize.medium:\n size = 160;\n break;\n default:\n size = 100;\n }\n\n const scale = size / 160;\n return svg`<g style=\"transform: ${v.transform} scale(${scale}) translate(-80px, -80px) \">${vesselImages[v.vesselImage]}</g>`;\n });\n }\n\n private renderStarboardPortIndicator(): SVGTemplateResult[] | typeof nothing {\n if (!this.starboardPortIndicator) {\n return nothing;\n }\n return [\n adviceMask(\n 0,\n 180,\n 'var(--instrument-starboard-secondary-color)',\n 'var(--instrument-starboard-secondary-color)'\n ),\n adviceMask(\n 180,\n 360,\n 'var(--instrument-port-secondary-color)',\n 'var(--instrument-port-secondary-color)'\n ),\n ] as SVGTemplateResult[];\n }\n\n static override styles = unsafeCSS(compentStyle);\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'obc-watch': ObcWatch;\n }\n}\n"],"names":["WatchCircleType"],"mappings":"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"}
1
+ {"version":3,"file":"watch.js","sources":["../../../src/navigation-instruments/watch/watch.ts"],"sourcesContent":["import {\n LitElement,\n PropertyValues,\n SVGTemplateResult,\n html,\n nothing,\n svg,\n unsafeCSS,\n} from 'lit';\nimport {property, state} from 'lit/decorators.js';\nimport {circle} from '../../svghelpers/index.js';\nimport {roundedArch} from '../../svghelpers/roundedArch.js';\nimport {\n cssSafeAngle,\n deriveRadialSetpointConfig,\n drawSetpointMarker,\n getSetpointAnimationDurationMs,\n getSetpointOutwardOffset,\n RADIAL_SETPOINT_RADIUS,\n SetpointVisualState,\n SETPOINT_ANIMATION_CSS_VAR,\n SETPOINT_ANIMATION_DURATION_DEFAULT,\n} from '../../svghelpers/setpoint.js';\nimport {InstrumentState, Priority} from '../types.js';\nimport compentStyle from './watch.css?inline';\nimport {ResizeController} from '@lit-labs/observers/resize-controller.js';\nimport {adviceMask, AngleAdviceRaw, renderAdvice} from './advice.js';\nimport {Tickmark, TickmarkStyle, tickmark} from './tickmark.js';\nexport {TickmarkStyle};\nimport {\n RotType,\n RotPosition,\n renderRotDots,\n renderRotBarStatic,\n renderRotBarDots,\n shortestAngularDeltaDeg,\n renderRotZeroPill,\n rotBarThresholdAngle,\n ROT_ZERO_DEADBAND_DEG,\n} from '../rate-of-turn/rot-renderer.js';\nexport {RotType, RotPosition};\nimport {\n RateOfTurnController,\n disposeRotController,\n} from '../rate-of-turn/rate-of-turn.controller.js';\nimport {\n renderLabels,\n renderNorthArrow,\n getLabelPositions,\n LabelPosition,\n} from './label.js';\nimport {VesselImage, VesselImageSize, vesselImages} from './vessel.js';\nimport {renderCurrent, renderWind} from './environment.js';\nimport {customElement} from '../../decorator.js';\nimport {\n computeZoomToFitArcFrame,\n type ZoomToFitArcFrame,\n} from '../../svghelpers/arc-frame.js';\nexport {VesselImage, VesselImageSize, vesselImages};\n\nexport enum WatchCircleType {\n single = 'single',\n double = 'double',\n doubleThin = 'doubleThin',\n triple = 'triple',\n}\n\nexport interface WatchArea {\n startAngle: number;\n endAngle: number;\n roundOutsideCut: boolean;\n roundInsideCut: boolean;\n}\n\nexport interface WatchBarArea {\n startAngle: number;\n endAngle: number;\n fillColor: string;\n}\n\nexport interface WatchNeedle {\n angle: number;\n fillColor: string;\n strokeColor: string;\n}\n\nexport interface WatchVessel {\n size: VesselImageSize;\n transform: string;\n vesselImage: VesselImage;\n}\n\nexport const OUTER_RING_RADIUS = 368 / 2;\nconst RING2_RADIUS = 320 / 2;\nconst RING3_RADIUS = 224 / 2;\nconst RING3B_RADIUS = 272 / 2;\nconst RING4_RADIUS = 176 / 2;\n\nexport function innerRingRadiusFor(type: WatchCircleType): number {\n switch (type) {\n case WatchCircleType.single:\n return RING2_RADIUS;\n case WatchCircleType.double:\n return RING3_RADIUS;\n case WatchCircleType.doubleThin:\n return RING3B_RADIUS;\n case WatchCircleType.triple:\n return RING4_RADIUS;\n default:\n throw new Error(`Unknown WatchCircleType: ${type as string}`);\n }\n}\n\nconst RADIAL_SETPOINT_INWARD_ADJUST = 4;\n\n/**\n * `<obc-watch>` - Core SVG renderer for circular/radial watch-based instruments.\n *\n * This component renders all circular instrument elements including rings, tickmarks,\n * bar areas, needles, advices, setpoints, vessel images, and environmental indicators\n * (wind/current). It serves as the foundation for compass, heading, rudder, speed-gauge,\n * and other radial navigation instruments.\n *\n * ## Setpoint Behavior\n *\n * The setpoint marker visual state is derived from the combination of `atAngleSetpoint`,\n * `angleSetpoint`, and `angleSetpointAtZeroDeadband` properties:\n *\n * - **notEqual**: Value differs from setpoint (triangular marker, offset outward)\n * - **equal**: Value matches setpoint (line marker, sits on ring)\n * - **equalZero**: Value matches setpoint at zero angle (double-line marker, offset outward)\n * - **focus**: User is actively adjusting via `newAngleSetpoint` - shows focus visual state\n *\n * ## newAngleSetpoint Pattern\n *\n * When `newAngleSetpoint` is defined, TWO setpoint markers are rendered:\n * 1. Original marker at `angleSetpoint` - dimmed (0.75 opacity)\n * 2. New marker at `newAngleSetpoint` - focus visual state, full opacity\n *\n * This enables the \"adjustment preview\" UX where users can see both the current\n * and proposed setpoint positions simultaneously.\n *\n * The `RADIAL_SETPOINT_INWARD_ADJUST` constant (4px) fine-tunes radial setpoint positioning\n * to match Figma designs, applied on top of visual state offsets from setpoint.ts.\n *\n * The `colorMode` property allows overriding the derived color mode (enhanced for enhanced priority,\n * regular for other states).\n *\n * ## Setpoint Animation (`animateSetpoint`)\n *\n * When `animateSetpoint` is true and a confirm occurs (`newAngleSetpoint` → `undefined`):\n * - The original setpoint slides to the new position via CSS transition\n * - The departing new-setpoint marker fades out\n * - Angular transitions always take the shortest path via accumulated\n * CSS-safe angles (`cssSafeAngle()`), so even 350° → 10° animates +20°\n *\n * Duration: `var(--setpoint-animation-duration, 300ms)`\n *\n * Internally, `_departingNewAngleSetpoint` captures the departing angle during confirm\n * fade-out and `_animationTimer` auto-clears it after the animation duration.\n * `_setpointCssAngle` tracks the accumulated CSS angle to avoid long-way-around\n * transitions across the 0°/360° boundary.\n *\n * @property {InstrumentState} state - Instrument state (active, loading, off)\n * @property {Priority} priority - Color priority (enhanced = blue palette, regular = gray palette)\n * @property {number|undefined} angleSetpoint - Setpoint angle in degrees (0° = 12 o'clock)\n * @property {number|undefined} newAngleSetpoint - New setpoint being adjusted (focus mode)\n * @property {boolean} atAngleSetpoint - Whether value matches setpoint (within deadband)\n * @property {number} angleSetpointAtZeroDeadband - Deadband for zero detection (default 0.5°)\n * @property {boolean} setpointOverride - Override to derive setpoint color from priority regardless of state\n * @property {RotType|undefined} rotType - ROT visualization type: `'dots'` (spinning dots) or `'bar'` (arc bar with clipped dots). Undefined hides the ROT layer.\n * @property {RotPosition} rotPosition - Track on which ROT elements are placed: `'scale'` (on the outer ring) or `'innerCircle'` (default, inside the inner ring)\n * @property {number} rotStartAngle - Start angle of the ROT bar arc in degrees (0° = 12 o'clock, clockwise). Only used when `rotType` is `'bar'`.\n * @property {number} rotEndAngle - End angle of the ROT bar arc in degrees. The bar is hidden when the difference from `rotStartAngle` is less than 0.1°.\n * @property {Priority|undefined} rotPriority - Override priority for ROT color derivation. When set, ROT colors use this instead of the main `priority`. Useful when the ROT element has independent priority (e.g. compass per-element priority).\n * @property {number|undefined} rateOfTurnDegreesPerMinute - Measured rate of turn in degrees per minute (the maritime/AIS convention, see ES-TRIN 2025/1 Art. 3.02 and ITU-R M.1371). Sign controls direction (positive = starboard/clockwise). When defined, this drives both the dot animation (multiplied by `rotDotAnimationFactor`) and the port/starboard direction sign.\n * @property {number} rotDotAnimationFactor - Visual amplification factor applied only to the spinning-dot animation (not to bar extent). Default `18` keeps the legacy visual feel (≈1 rpm at 20°/min).\n * @property {number} rotationsPerMinute - **Deprecated.** Spin speed of the ROT dot ring in rotations per minute. Sign controls direction (positive = clockwise). Use `rateOfTurnDegreesPerMinute` instead.\n * @property {ZoomToFitArcFrame|undefined} arcFrame - Pre-computed zoom-to-fit arc frame. When set, the watch skips its own `computeZoomToFitArcFrame()` call and uses these values directly. Consumer instruments (e.g. rudder, instrument-radial) should compute the frame once and pass it here to avoid redundant computation. If you pass `arcFrame`, you own keeping it in sync with `areas` / `watchCircleType` — obc-watch will NOT recompute it, so a stale frame renders stale geometry.\n */\n@customElement('obc-watch')\nexport class ObcWatch extends LitElement {\n private _setpointId = `watch-setpoint-${Math.random().toString(36).slice(2, 9)}`;\n private _newSetpointId = `watch-new-setpoint-${Math.random().toString(36).slice(2, 9)}`;\n\n @property({type: String}) state: InstrumentState = InstrumentState.active;\n @property({type: String}) priority: Priority = Priority.regular;\n @property({type: String}) watchCircleType: WatchCircleType =\n WatchCircleType.single;\n @property({type: Boolean}) northArrow: boolean = false;\n @property({type: Boolean}) northArrowInside: boolean | undefined;\n @property({type: Number}) angleSetpoint: number | undefined;\n @property({type: Number}) newAngleSetpoint: number | undefined;\n @property({type: Boolean}) atAngleSetpoint: boolean = false;\n @property({type: Number}) angleSetpointAtZeroDeadband: number = 0.5;\n @property({type: Boolean}) setpointOverride: boolean = false;\n @property({type: Boolean}) touching: boolean = false;\n\n @property({type: Boolean}) animateSetpoint: boolean = false;\n\n @state() private _departingNewAngleSetpoint: number | undefined;\n private _animationTimer?: ReturnType<typeof setTimeout>;\n\n /**\n * Accumulated CSS-safe angle for the original setpoint marker.\n * Ensures CSS rotate() transitions always take the shortest path,\n * even across the 0°/360° boundary.\n */\n private _setpointCssAngle: number = 0;\n\n /** Whether the setpoint CSS angle has been initialised (to skip transition on first render). */\n private _setpointCssAngleInit = false;\n @property({type: Number}) padding: number | undefined;\n @property({type: Array, attribute: false}) areas: WatchArea[] = [];\n @property({type: Array, attribute: false}) barAreas: WatchBarArea[] = [];\n @property({type: Array, attribute: false}) needles: WatchNeedle[] = [];\n @property({type: Array, attribute: false}) tickmarks: Tickmark[] = [];\n @property({type: Boolean}) tickmarksInside: boolean = false;\n @property({type: String}) tickmarkStyle: TickmarkStyle =\n TickmarkStyle.regular;\n @property({type: Array, attribute: false}) advices: AngleAdviceRaw[] = [];\n @property({type: Boolean}) crosshairEnabled: boolean = false;\n @property({type: Boolean}) showLabels: boolean = false;\n @property({type: Array, attribute: false}) vessels: WatchVessel[] = [];\n @property({type: Number}) windKnots: number | null = null;\n @property({type: Number}) windFromDirectionDeg: number | null = null;\n @property({type: Number}) windSymbolRadius: number | null = null;\n @property({type: String}) windColor: string | undefined;\n @property({type: Number}) current: number | null = null;\n @property({type: Number}) currentFromDirectionDeg: number | null = null;\n @property({type: Number}) currentSymbolRadius: number | null = null;\n @property({type: String}) currentColor: string | undefined;\n @property({type: Boolean}) starboardPortIndicator: boolean = false;\n /** Top clip, % of height. Ignored when `zoomToFitArc` is true. */\n @property({type: Number}) clipTop: number = 0;\n /** Bottom clip, % of height. Ignored when `zoomToFitArc` is true. */\n @property({type: Number}) clipBottom: number = 0;\n /** Left clip, % of width — horizontal counterpart of clipTop/bottom (90° sectors). Ignored when `zoomToFitArc`. */\n @property({type: Number}) clipLeft: number = 0;\n /** Right clip, % of width — horizontal counterpart of clipTop/bottom (90° sectors). Ignored when `zoomToFitArc`. */\n @property({type: Number}) clipRight: number = 0;\n /**\n * Place the horizontal end labels (±90°, e.g. min/max) below the tick instead\n * of beside it. This is the \"Max-min\" label placement from the radial label\n * model (External / Internal / Max-min) — see PR #903 / design discussion.\n * Currently only used by the 180° sector of `obc-gauge-radial`.\n */\n @property({type: Boolean}) endLabelsMaxMin: boolean = false;\n @property({type: Number}) scaleWindIcon: number = 1;\n @property({type: Number}) rotation: number | undefined;\n @property({type: Boolean}) zoomToFitArc: boolean = false;\n @property({attribute: false}) arcFrame: ZoomToFitArcFrame | undefined;\n @property({type: Number}) tickFadeAngle: number = 0;\n\n @property({type: String}) rotType: RotType | undefined;\n @property({type: String}) rotPosition: RotPosition = RotPosition.innerCircle;\n @property({type: Number}) rotStartAngle: number = 0;\n @property({type: Number}) rotEndAngle: number = 0;\n @property({type: String}) rotPriority: Priority | undefined;\n @property({type: Boolean}) rotPortStarboard: boolean = false;\n @property({type: Number}) rotAtZeroDeadband: number = ROT_ZERO_DEADBAND_DEG;\n @property({type: Number}) rateOfTurnDegreesPerMinute: number | undefined;\n @property({type: Number}) rotDotAnimationFactor: number = 18;\n /**\n * @deprecated Use `rateOfTurnDegreesPerMinute` (and optionally `rotDotAnimationFactor`) instead.\n * Kept as a backward-compatible alias; takes effect only when\n * `rateOfTurnDegreesPerMinute` is `undefined`.\n */\n @property({type: Number})\n set rotationsPerMinute(value: number) {\n this._legacyRotationsPerMinute = value;\n }\n get rotationsPerMinute() {\n return this._legacyRotationsPerMinute;\n }\n private _legacyRotationsPerMinute = 0;\n private _rotController?: RateOfTurnController;\n\n /**\n * Effective rotations-per-minute for the spinning dot animation and\n * port/starboard direction sign. Resolves to:\n * • `(rateOfTurnDegreesPerMinute / 360) * rotDotAnimationFactor` when the\n * new physical API is in use, OR\n * • the legacy `rotationsPerMinute` value otherwise.\n */\n private get _effectiveRpm(): number {\n if (this.rateOfTurnDegreesPerMinute != null) {\n return (\n (this.rateOfTurnDegreesPerMinute / 360) * this.rotDotAnimationFactor\n );\n }\n return this._legacyRotationsPerMinute;\n }\n\n // @ts-expect-error TS6133: The controller ensures that the render\n // function is called on resize of the element\n private _resizeController = new ResizeController(this, {});\n\n override willUpdate(changed: PropertyValues): void {\n super.willUpdate(changed);\n\n // Push the resolved effective RPM into the live controller whenever any\n // of the inputs that compose it change.\n if (\n this._rotController &&\n (changed.has('rateOfTurnDegreesPerMinute') ||\n changed.has('rotDotAnimationFactor') ||\n changed.has('rotationsPerMinute'))\n ) {\n this._rotController.rotationsPerMinute = this._effectiveRpm;\n }\n\n // Detect confirm: newAngleSetpoint was defined, now undefined\n if (changed.has('newAngleSetpoint') && this.animateSetpoint) {\n const prev = changed.get('newAngleSetpoint') as number | undefined;\n if (prev !== undefined && this.newAngleSetpoint === undefined) {\n this._departingNewAngleSetpoint = prev;\n clearTimeout(this._animationTimer);\n const duration = getSetpointAnimationDurationMs(this);\n this._animationTimer = setTimeout(() => {\n this._departingNewAngleSetpoint = undefined;\n }, duration);\n }\n }\n }\n\n override disconnectedCallback(): void {\n super.disconnectedCallback();\n clearTimeout(this._animationTimer);\n this._rotController = disposeRotController(this, this._rotController);\n }\n\n override updated(changed: PropertyValues): void {\n super.updated(changed);\n const el = this.rotType\n ? this.renderRoot.querySelector('#rot-spinner')\n : null;\n if (!el) {\n this._rotController = disposeRotController(this, this._rotController);\n return;\n }\n if (!this._rotController || this._rotController.el !== el) {\n this._rotController = disposeRotController(this, this._rotController);\n this._rotController = new RateOfTurnController(\n this,\n el,\n this._effectiveRpm\n );\n }\n }\n\n private get innerRingRadius(): number {\n return innerRingRadiusFor(this.watchCircleType);\n }\n\n private _rOff = 0;\n\n /**\n * Radius for a dial-band edge under zoom: additive (`base + _rOff`), keeping\n * band thickness constant in SVG units. INVARIANT: every band-edge radius\n * (rings, value arc, bars, needles, inside-label `textRadius`) must go through\n * this — mixing additive and multiplicative offsets misaligns ticks, labels,\n * advice masks and arcs. The proportional experiments were removed for this\n * reason (PR #903).\n */\n private _bandRadius(base: number): number {\n return base + this._rOff;\n }\n\n private watchCircle(): SVGTemplateResult | SVGTemplateResult[] {\n const rings = [];\n if (this.state !== InstrumentState.off) {\n rings.push(svg`\n <circle\n cx=\"0\"\n cy=\"0\"\n r=\"${this._bandRadius(172)}\"\n stroke=\"var(--instrument-frame-primary-color)\"\n fill=\"none\"\n stroke-width=\"24\"\n />`);\n\n if (this.watchCircleType !== WatchCircleType.single) {\n const r1 = this._bandRadius(RING2_RADIUS);\n const r2 = this._bandRadius(\n this.watchCircleType === WatchCircleType.doubleThin\n ? RING3B_RADIUS\n : RING3_RADIUS\n );\n const r = (r1 + r2) / 2;\n const strokeWidth = r1 - r2;\n rings.push(\n svg`\n <circle cx=\"0\" cy=\"0\" r=${r} stroke=\"var(--instrument-frame-secondary-color)\" stroke-width=${strokeWidth} fill=\"none\" />\n <circle cx=\"0\" cy=\"0\" r=${r1} stroke=\"var(--instrument-frame-secondary-color)\" stroke-width=\"1\" fill=\"none\" vector-effect=\"non-scaling-stroke\" />\n <circle cx=\"0\" cy=\"0\" r=${r2} stroke=\"var(--instrument-frame-secondary-color)\" stroke-width=\"1\" fill=\"none\" vector-effect=\"non-scaling-stroke\" />\n `\n );\n }\n if (this.watchCircleType === WatchCircleType.triple) {\n const r1 = this._bandRadius(RING3_RADIUS);\n const r2 = this._bandRadius(RING4_RADIUS);\n const r = (r1 + r2) / 2;\n const strokeWidth = r1 - r2;\n rings.push(\n svg`<circle cx=\"0\" cy=\"0\" r=${r} stroke=\"var(--instrument-frame-primary-color)\" stroke-width=${strokeWidth} fill=\"none\" />`\n );\n }\n }\n\n const maskSize = Math.max(200, OUTER_RING_RADIUS + this._rOff + 50);\n let result = rings;\n if (this.areas.length > 0) {\n const areas = this.areas.map((area) => {\n const svgPath = roundedArch({\n startAngle: area.startAngle,\n endAngle: area.endAngle,\n R: this._bandRadius(OUTER_RING_RADIUS),\n r: this._bandRadius(this.innerRingRadius),\n roundOutsideCut: area.roundOutsideCut,\n roundInsideCut: area.roundInsideCut,\n });\n return svgPath;\n });\n const mask = svg`<mask id=\"cutMask\">\n <rect x=\"${-maskSize}\" y=\"${-maskSize}\" width=\"${maskSize * 2}\" height=\"${maskSize * 2}\" fill=\"black\" />\n ${areas.map((area) => svg`<path d=${area} fill=\"white\" vector-effect=\"non-scaling-stroke\" stroke=\"white\" stroke-width=\"1\"/>`)}\n </mask>`;\n // clipPath for ROT uses r=0 so dots at any track radius stay visible\n const rotClip = svg`<clipPath id=\"rot-arc-clip\">${this.areas.map(\n (area) =>\n svg`<path d=${roundedArch({\n startAngle: area.startAngle,\n endAngle: area.endAngle,\n R: OUTER_RING_RADIUS + this._rOff + 20,\n r: 0,\n roundOutsideCut: area.roundOutsideCut,\n roundInsideCut: area.roundInsideCut,\n })} />`\n )}</clipPath>`;\n result = [mask, rotClip, svg`<g mask=\"url(#cutMask)\">${rings}</g>`];\n areas.forEach((area) => {\n result.push(\n svg`<path d=${area} fill=\"none\" stroke=\"var(--instrument-frame-tertiary-color)\" vector-effect=\"non-scaling-stroke\"/>`\n );\n });\n } else {\n if (this.state !== InstrumentState.off) {\n result.push(\n circle('outerRing', {\n radius: this._bandRadius(OUTER_RING_RADIUS),\n strokeWidth: 1,\n strokeColor: 'var(--instrument-frame-tertiary-color)',\n strokePosition: 'center',\n fillColor: 'none',\n })\n );\n\n result.push(svg`\n ${circle('innerRing', {\n radius: this._bandRadius(this.innerRingRadius),\n strokeWidth: 1,\n strokeColor: 'var(--instrument-frame-tertiary-color)',\n strokePosition: 'center',\n fillColor: 'none',\n })}\n `);\n } else {\n result.push(svg`\n ${circle('innerRing', {\n radius: this._bandRadius(OUTER_RING_RADIUS),\n strokeWidth: 1,\n strokeColor: 'var(--instrument-frame-tertiary-color)',\n strokePosition: 'center',\n fillColor: 'none',\n })}\n `);\n }\n }\n return result;\n }\n\n private _renderTickFadeDefs(): SVGTemplateResult | typeof nothing {\n if (this.tickFadeAngle <= 0 || this.areas.length === 0) return nothing;\n const area = this.areas[0];\n const arcSpan = area.endAngle - area.startAngle;\n const fade = Math.min(this.tickFadeAngle, arcSpan / 4);\n if (fade < 0.5) return nothing;\n\n const {startAngle, endAngle} = area;\n const R = OUTER_RING_RADIUS + this._rOff + 200;\n const toRad = (deg: number) => (deg * Math.PI) / 180;\n const px = (deg: number) => R * Math.sin(toRad(deg));\n const py = (deg: number) => -R * Math.cos(toRad(deg));\n\n const pieSlice = (a: number, b: number): string => {\n const x1 = px(a),\n y1 = py(a);\n const x2 = px(b),\n y2 = py(b);\n const largeArc = b - a > 180 ? 1 : 0;\n return `M 0 0 L ${x1} ${y1} A ${R} ${R} 0 ${largeArc} 1 ${x2} ${y2} Z`;\n };\n\n const Rm =\n (this._bandRadius(OUTER_RING_RADIUS) +\n this._bandRadius(this.innerRingRadius)) /\n 2;\n const gx = (deg: number) => Rm * Math.sin(toRad(deg));\n const gy = (deg: number) => -Rm * Math.cos(toRad(deg));\n\n return svg`\n <defs>\n <linearGradient id=\"tickFadeL\" gradientUnits=\"userSpaceOnUse\"\n x1=\"${gx(startAngle)}\" y1=\"${gy(startAngle)}\"\n x2=\"${gx(startAngle + fade)}\" y2=\"${gy(startAngle + fade)}\">\n <stop offset=\"0\" stop-color=\"black\" />\n <stop offset=\"1\" stop-color=\"white\" />\n </linearGradient>\n <linearGradient id=\"tickFadeR\" gradientUnits=\"userSpaceOnUse\"\n x1=\"${gx(endAngle - fade)}\" y1=\"${gy(endAngle - fade)}\"\n x2=\"${gx(endAngle)}\" y2=\"${gy(endAngle)}\">\n <stop offset=\"0\" stop-color=\"white\" />\n <stop offset=\"1\" stop-color=\"black\" />\n </linearGradient>\n <mask id=\"tickFadeMask\" maskUnits=\"userSpaceOnUse\"\n x=\"${-R}\" y=\"${-R}\" width=\"${R * 2}\" height=\"${R * 2}\">\n <path d=\"${pieSlice(startAngle + fade, endAngle - fade)}\" fill=\"white\" />\n <path d=\"${pieSlice(startAngle, startAngle + fade)}\" fill=\"url(#tickFadeL)\" />\n <path d=\"${pieSlice(endAngle - fade, endAngle)}\" fill=\"url(#tickFadeR)\" />\n </mask>\n </defs>\n `;\n }\n\n private renderCrosshair(\n radius: number,\n labelKnockouts?: {\n positions: LabelPosition[];\n rotation: number | undefined;\n scale: number;\n /** Inner ring radius – crosshair is hidden between labelRadius and this value. */\n innerRingRadius: number;\n }\n ): SVGTemplateResult {\n const hasMask = labelKnockouts && labelKnockouts.positions.length > 0;\n\n // Radius at which labels sit (distance from centre).\n // Any position is equally valid — they're all at the same radial distance.\n const labelRadius = hasMask\n ? Math.max(\n ...labelKnockouts!.positions.map((l) =>\n Math.abs(l.x !== 0 ? l.x : l.y)\n )\n )\n : 0;\n // Small extra padding so the crosshair doesn't start/end right at the\n // label edge — use the same visual pad as the letter knockouts.\n const ringGapPad = hasMask ? 3 / labelKnockouts!.scale : 0;\n\n return svg`\n ${\n hasMask\n ? svg`\n <defs>\n <mask\n id=\"crosshair-label-mask\"\n maskUnits=\"userSpaceOnUse\"\n x=\"-${radius}\" y=\"-${radius}\"\n width=\"${radius * 2}\" height=\"${radius * 2}\"\n >\n <rect x=\"-${radius}\" y=\"-${radius}\" width=\"${radius * 2}\" height=\"${radius * 2}\" fill=\"white\"/>\n <!-- Annular ring knockout: hide crosshair between labels and inner ring -->\n <circle cx=\"0\" cy=\"0\" r=\"${labelKnockouts!.innerRingRadius}\" fill=\"black\"/>\n <circle cx=\"0\" cy=\"0\" r=\"${labelRadius - ringGapPad}\" fill=\"white\"/>\n <!-- Per-label rectangular knockouts -->\n ${labelKnockouts!.positions.map((l) => {\n const fontSize = 12 / labelKnockouts!.scale;\n const pad = 3 / labelKnockouts!.scale;\n const size = fontSize + pad * 2;\n return svg`\n <rect\n x=\"${l.x - size / 2}\" y=\"${l.y - size / 2}\"\n width=\"${size}\" height=\"${size}\"\n fill=\"black\"\n transform=\"rotate(${-(labelKnockouts!.rotation ?? 0)})\"\n transform-origin=\"${l.x} ${l.y}\"\n />\n `;\n })}\n </mask>\n </defs>`\n : nothing\n }\n <g mask=${hasMask ? 'url(#crosshair-label-mask)' : nothing}>\n <line\n x1=\"-${radius}\"\n y1=\"0\"\n x2=\"${radius}\"\n y2=\"0\"\n stroke=\"var(--instrument-frame-tertiary-color)\"\n stroke-width=\"1\"\n vector-effect=\"non-scaling-stroke\"\n />\n <line\n x1=\"0\"\n y1=\"-${radius}\"\n x2=\"0\"\n y2=\"${radius}\"\n stroke=\"var(--instrument-frame-tertiary-color)\"\n stroke-width=\"1\"\n vector-effect=\"non-scaling-stroke\"\n />\n </g>\n `;\n }\n\n private renderBars(): SVGTemplateResult[] | typeof nothing {\n if (this.barAreas.length === 0) {\n return nothing;\n }\n return this.barAreas.map((bar, index) => {\n const startAngle = Math.min(bar.startAngle, bar.endAngle);\n const endAngle = Math.max(bar.startAngle, bar.endAngle);\n const arc = roundedArch({\n r: this._bandRadius(RING3_RADIUS),\n R: this._bandRadius(RING2_RADIUS),\n startAngle: startAngle,\n endAngle: endAngle,\n roundInsideCut: false,\n roundOutsideCut: false,\n });\n const barMaskR = RING2_RADIUS + this._rOff + 40;\n // The mask is a sector to cut out the stroke on the start and end of the bar\n const mask = svg`<mask id=\"barMask-${index}\">\n <rect x=\"${-barMaskR}\" y=\"${-barMaskR}\" width=\"${barMaskR * 2}\" height=\"${barMaskR * 2}\" fill=\"black\" />\n <path d=${roundedArch({\n r: 1,\n R: barMaskR,\n startAngle: startAngle,\n endAngle: endAngle,\n roundInsideCut: false,\n roundOutsideCut: false,\n })} fill=\"white\" />\n </mask>`;\n return svg`\n ${mask}\n <g mask=\"url(#cutMask)\">\n <path \n d=${arc} \n fill=${bar.fillColor} \n stroke=${bar.fillColor} \n stroke-width=\"1\" \n vector-effect=\"non-scaling-stroke\" \n mask=\"url(#barMask-${index})\" \n />\n </g>\n `;\n });\n }\n\n private renderNeedles(): SVGTemplateResult[] | typeof nothing {\n if (this.needles.length === 0) {\n return nothing;\n }\n return this.needles.map((needle) => {\n return svg`\n <rect \n transform=\"rotate(${needle.angle})\" \n x=\"-4\" y=\"${-this._bandRadius(RING2_RADIUS)}\" width=\"8\" height=\"48\" rx=\"4\"\n fill=${needle.fillColor} \n stroke=${needle.strokeColor}\n stroke-width=\"1\"\n vector-effect=\"non-scaling-stroke\"\n paint-order=\"stroke fill\"\n />\n `;\n });\n }\n\n private getScale({width, height}: {width: number; height: number}): number {\n let clientWidth = this.clientWidth;\n let clientHeight = this.clientHeight;\n if (clientWidth === 0 || clientHeight === 0) {\n const box = this.parentElement?.getBoundingClientRect();\n if (box) {\n clientWidth = box.width;\n clientHeight = box.height;\n }\n }\n const scale = Math.min(clientWidth / width, clientHeight / height);\n if (scale === Infinity || scale < 0) {\n throw new Error('Watch scale is not valid');\n }\n return scale;\n }\n\n private getPadding(): number {\n if (this.padding !== undefined) {\n return this.padding;\n }\n const hasTickmarksWithText =\n this.tickmarks.length > 0 &&\n this.tickmarks.some((t) => t.text !== undefined);\n if (hasTickmarksWithText && !this.tickmarksInside) {\n return 24 * 2.5;\n }\n return 24;\n }\n\n override render() {\n let width: number;\n let height: number;\n let viewBox: string;\n\n if (this.arcFrame) {\n this._rOff = this.arcFrame.radiusOffset;\n width = this.arcFrame.width;\n height = this.arcFrame.height;\n viewBox = this.arcFrame.viewBox;\n } else if (this.zoomToFitArc && this.areas.length > 0) {\n const ext = this.getPadding();\n const targetSize = (176 + ext) * 2;\n const frame = computeZoomToFitArcFrame({\n areas: this.areas,\n outerRadius: OUTER_RING_RADIUS,\n innerRadius: this.innerRingRadius,\n extension: ext,\n targetSize,\n });\n this._rOff = frame.radiusOffset;\n width = frame.width;\n height = frame.height;\n viewBox = frame.viewBox;\n } else {\n this._rOff = 0;\n const full = (176 + this.getPadding()) * 2;\n width = full * (1 - this.clipLeft / 100 - this.clipRight / 100);\n height = full * (1 - this.clipTop / 100 - this.clipBottom / 100);\n const left = -full / 2 + (full * this.clipLeft) / 100;\n const top = -full / 2 + (full * this.clipTop) / 100;\n viewBox = `${left} ${top} ${width} ${height}`;\n }\n\n const rOff = this._rOff;\n const scale = this.getScale({width, height});\n const angleSetpoint = this.renderSetpoint();\n // Route through _bandRadius so inside labels track the (zoom-shifted) inner\n // band edge; the coupling is intentional (see _bandRadius INVARIANT).\n const textRadius = this.tickmarksInside\n ? this._bandRadius(this.innerRingRadius)\n : this._bandRadius(OUTER_RING_RADIUS);\n const maxDigits = Math.max(\n ...this.tickmarks.map((t) => t.text?.length ?? 0)\n );\n const tickmarks = this.tickmarks.map((t) =>\n tickmark(t.angle, {\n size: t.type,\n style: this.tickmarkStyle,\n scale,\n text: this.showLabels ? undefined : t.text,\n inside: this.tickmarksInside,\n textRadius,\n rotation: this.rotation,\n maxDigits,\n color: t.color,\n radiusOffset: rOff,\n endLabelsMaxMin: this.endLabelsMaxMin,\n })\n );\n const advices = this.advices\n ? this.advices.map((a) => renderAdvice(a, rOff))\n : nothing;\n\n // Compute label positions once – used for both rendering and crosshair knockout.\n const insideLabels = this.tickmarksInside && this.showLabels;\n const includeNorth = !this.northArrow;\n const labelPositions = this.showLabels\n ? getLabelPositions({\n scale,\n inside: this.tickmarksInside,\n innerRadius: this.innerRingRadius + rOff,\n includeNorth,\n })\n : undefined;\n\n const labels = labelPositions\n ? renderLabels({\n scale,\n rotation: this.rotation,\n inside: this.tickmarksInside,\n innerRadius: this.innerRingRadius + rOff,\n includeNorth,\n })\n : nothing;\n const northArrowEl = this.northArrow\n ? renderNorthArrow({\n scale,\n rotation: this.rotation,\n inside: this.northArrowInside ?? this.tickmarksInside,\n })\n : nothing;\n const wind =\n this.windKnots != null && this.windFromDirectionDeg != null\n ? svg`<g transform=\"scale(${this.scaleWindIcon})\">${renderWind({\n windKnots: this.windKnots,\n fromDirectionDeg: this.windFromDirectionDeg,\n radius: this.windSymbolRadius ?? 192,\n color: this.windColor,\n })}</g>`\n : nothing;\n const current =\n this.current != null && this.currentFromDirectionDeg != null\n ? renderCurrent({\n current: this.current,\n fromDirectionDeg: this.currentFromDirectionDeg,\n radius: this.currentSymbolRadius ?? 192,\n color: this.currentColor,\n })\n : nothing;\n return html`\n <svg\n width=\"100%\"\n height=\"100%\"\n viewBox=${viewBox}\n style=\"--scale: ${scale}\"\n transform=\"rotate(${this.rotation ?? 0})\"\n >\n ${this.watchCircle()} ${this.renderBars()}\n ${this.crosshairEnabled\n ? this.renderCrosshair(\n OUTER_RING_RADIUS + rOff,\n insideLabels && labelPositions\n ? {\n positions: labelPositions,\n rotation: this.rotation,\n scale,\n innerRingRadius: this.innerRingRadius + rOff,\n }\n : undefined\n )\n : nothing}\n ${northArrowEl} ${this.renderStarboardPortIndicator()} ${current}\n ${this._renderTickFadeDefs()} ${wind}\n ${this.tickFadeAngle > 0 && this.areas.length > 0\n ? svg`<g mask=\"url(#tickFadeMask)\">${tickmarks}</g>`\n : tickmarks}\n ${this.areas.length > 0\n ? svg`<g clip-path=\"url(#rot-arc-clip)\">${this.renderRot()}</g>`\n : this.renderRot()}\n ${advices} ${angleSetpoint}\n ${this.tickFadeAngle > 0 && this.areas.length > 0\n ? svg`<g mask=\"url(#tickFadeMask)\">${labels}</g>`\n : labels}\n ${this.renderVesselImage()} ${this.renderNeedles()}\n </svg>\n `;\n }\n\n private getRotColors(): {\n dotColor: string;\n barBgColor: string;\n } {\n const p = this.rotPriority ?? this.priority;\n const isEnhanced = p === Priority.enhanced;\n\n if (this.rotPortStarboard) {\n // For bar type, derive direction from bar angles (visual direction);\n // for dots, use spinner RPM.\n let direction: number;\n if (this.rotType === RotType.bar) {\n const cwSpan =\n (((this.rotEndAngle - this.rotStartAngle) % 360) + 360) % 360;\n direction = cwSpan <= 180 ? cwSpan : cwSpan - 360;\n } else {\n direction = this._effectiveRpm;\n }\n\n if (direction > 0) {\n return {\n dotColor: 'var(--instrument-starboard-secondary-color)',\n barBgColor: 'var(--instrument-starboard-primary-color)',\n };\n }\n if (direction < 0) {\n return {\n dotColor: 'var(--instrument-port-secondary-color)',\n barBgColor: 'var(--instrument-port-primary-color)',\n };\n }\n }\n\n return {\n dotColor: isEnhanced\n ? 'var(--instrument-enhanced-tertiary-color)'\n : 'var(--instrument-regular-tertiary-color)',\n barBgColor: isEnhanced\n ? 'var(--instrument-enhanced-secondary-color)'\n : 'var(--instrument-regular-secondary-color)',\n };\n }\n\n private renderRot(): SVGTemplateResult | typeof nothing {\n if (!this.rotType) return nothing;\n\n const {dotColor, barBgColor} = this.getRotColors();\n const rOff = this._rOff;\n\n if (this.rotType === RotType.bar) {\n const angularDelta = shortestAngularDeltaDeg(\n this.rotStartAngle,\n this.rotEndAngle\n );\n const threshold = rotBarThresholdAngle(this.rotPosition, rOff);\n const zeroDb = Number.isFinite(this.rotAtZeroDeadband)\n ? this.rotAtZeroDeadband\n : ROT_ZERO_DEADBAND_DEG;\n\n if (angularDelta < Math.max(zeroDb, threshold)) {\n return renderRotZeroPill(\n barBgColor,\n this.rotStartAngle,\n this.rotPosition,\n rOff\n );\n }\n\n return svg`\n ${renderRotBarStatic({\n startAngle: this.rotStartAngle,\n endAngle: this.rotEndAngle,\n barColor: barBgColor,\n position: this.rotPosition,\n maskId: 'rot-bar-mask',\n radiusOffset: rOff,\n })}\n ${svg`<g clip-path=\"url(#rot-bar-mask)\">\n <g id=\"rot-spinner\">\n ${renderRotBarDots(dotColor, this.rotPosition, rOff)}\n </g>\n </g>`}\n `;\n }\n\n const p = this.rotPriority ?? this.priority;\n const isEnhanced = p === Priority.enhanced;\n let dotsColor: string = isEnhanced\n ? 'var(--instrument-enhanced-secondary-color)'\n : 'var(--instrument-regular-secondary-color)';\n if (this.rotPortStarboard) {\n if (this._effectiveRpm > 0) {\n dotsColor = 'var(--instrument-starboard-secondary-color)';\n } else if (this._effectiveRpm < 0) {\n dotsColor = 'var(--instrument-port-secondary-color)';\n }\n }\n return svg`\n <g id=\"rot-spinner\">\n ${renderRotDots(dotsColor, this.rotPosition, rOff)}\n </g>\n `;\n }\n\n private renderSetpoint(): SVGTemplateResult | typeof nothing {\n if (this.angleSetpoint === undefined) {\n return nothing;\n }\n\n const derived = deriveRadialSetpointConfig({\n state: this.state,\n priority: this.priority,\n atSetpoint: this.atAngleSetpoint,\n angleSetpoint: this.angleSetpoint,\n setpointAtZeroDeadband: this.angleSetpointAtZeroDeadband,\n newAngleSetpoint: this.newAngleSetpoint,\n touching: this.touching,\n setpointOverride: this.setpointOverride,\n });\n\n const {visualState, colorMode, disabled, hasNewSetpoint} = derived;\n\n const outwardOffset = getSetpointOutwardOffset(visualState);\n const radius =\n RADIAL_SETPOINT_RADIUS +\n this._rOff +\n outwardOffset -\n RADIAL_SETPOINT_INWARD_ADJUST;\n\n // Render original setpoint marker (dimmed when newAngleSetpoint is active)\n const opacity = hasNewSetpoint ? 0.75 : 1;\n const originalMarker = drawSetpointMarker({\n visualState,\n colorMode,\n disabled,\n id: this._setpointId,\n });\n\n const animate = this.animateSetpoint;\n const hasDeparting = this._departingNewAngleSetpoint !== undefined;\n\n // Compute CSS-safe accumulated angle so transitions always take the short path\n const rawAngle = this.angleSetpoint + 90;\n if (!this._setpointCssAngleInit) {\n // First render: set angle without transition\n this._setpointCssAngle = rawAngle;\n this._setpointCssAngleInit = true;\n } else {\n this._setpointCssAngle = cssSafeAngle(this._setpointCssAngle, rawAngle);\n }\n\n // Use CSS style transform when animating for smooth transition\n const originalSetpoint = animate\n ? svg`\n <g style=\"transform: rotate(${this._setpointCssAngle}deg) translateX(${-radius}px) rotate(270deg); opacity: ${opacity}; transition: transform var(${SETPOINT_ANIMATION_CSS_VAR}, ${SETPOINT_ANIMATION_DURATION_DEFAULT}) ease-out, opacity var(${SETPOINT_ANIMATION_CSS_VAR}, ${SETPOINT_ANIMATION_DURATION_DEFAULT}) ease-out;\">\n ${originalMarker}\n </g>\n `\n : svg`\n <g transform=\"rotate(${this.angleSetpoint + 90}) translate(${-radius}, 0) rotate(270)\" opacity=\"${opacity}\">\n ${originalMarker}\n </g>\n `;\n\n // Render newAngleSetpoint in focus state (always on top)\n // OR render departing newAngleSetpoint during confirm fade-out\n if (hasNewSetpoint || hasDeparting) {\n const isActive = hasNewSetpoint;\n const newAngle = isActive\n ? this.newAngleSetpoint!\n : this._departingNewAngleSetpoint!;\n const targetOpacity = isActive ? 1 : 0;\n\n const focusOutwardOffset = getSetpointOutwardOffset(\n SetpointVisualState.focus\n );\n const focusRadius =\n RADIAL_SETPOINT_RADIUS +\n this._rOff +\n focusOutwardOffset -\n RADIAL_SETPOINT_INWARD_ADJUST;\n\n const newMarker = drawSetpointMarker({\n visualState: SetpointVisualState.focus,\n colorMode,\n disabled: false, // newSetpoint is never disabled\n id: this._newSetpointId,\n });\n\n if (animate) {\n const duration = `var(${SETPOINT_ANIMATION_CSS_VAR}, ${SETPOINT_ANIMATION_DURATION_DEFAULT})`;\n return svg`\n ${originalSetpoint}\n <g style=\"transform: rotate(${newAngle + 90}deg) translateX(${-focusRadius}px) rotate(270deg); opacity: ${targetOpacity}; transition: opacity ${duration} ease-out;\">\n ${newMarker}\n </g>\n `;\n }\n\n return svg`\n ${originalSetpoint}\n <g transform=\"rotate(${newAngle + 90}) translate(${-focusRadius}, 0) rotate(270)\" opacity=\"${targetOpacity}\">\n ${newMarker}\n </g>\n `;\n }\n\n return originalSetpoint;\n }\n\n private renderVesselImage(): SVGTemplateResult[] | typeof nothing {\n if (this.vessels.length === 0) {\n return nothing;\n }\n\n return this.vessels.map((v) => {\n let size;\n switch (v.size) {\n case VesselImageSize.large:\n size = 224;\n break;\n case VesselImageSize.medium:\n size = 160;\n break;\n default:\n size = 100;\n }\n\n const scale = size / 160;\n return svg`<g style=\"transform: ${v.transform} scale(${scale}) translate(-80px, -80px) \">${vesselImages[v.vesselImage]}</g>`;\n });\n }\n\n private renderStarboardPortIndicator(): SVGTemplateResult[] | typeof nothing {\n if (!this.starboardPortIndicator) {\n return nothing;\n }\n return [\n adviceMask(\n 0,\n 180,\n 'var(--instrument-starboard-secondary-color)',\n 'var(--instrument-starboard-secondary-color)'\n ),\n adviceMask(\n 180,\n 360,\n 'var(--instrument-port-secondary-color)',\n 'var(--instrument-port-secondary-color)'\n ),\n ] as SVGTemplateResult[];\n }\n\n static override styles = unsafeCSS(compentStyle);\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'obc-watch': ObcWatch;\n }\n}\n"],"names":["WatchCircleType"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4DO,IAAK,oCAAAA,qBAAL;AACLA,mBAAA,QAAA,IAAS;AACTA,mBAAA,QAAA,IAAS;AACTA,mBAAA,YAAA,IAAa;AACbA,mBAAA,QAAA,IAAS;AAJC,SAAAA;AAAA,GAAA,mBAAA,CAAA,CAAA;AAgCL,MAAM,oBAAoB,MAAM;AACvC,MAAM,eAAe,MAAM;AAC3B,MAAM,eAAe,MAAM;AAC3B,MAAM,gBAAgB,MAAM;AAC5B,MAAM,eAAe,MAAM;AAEpB,SAAS,mBAAmB,MAA+B;AAChE,UAAQ,MAAA;AAAA,IACN,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT;AACE,YAAM,IAAI,MAAM,4BAA4B,IAAc,EAAE;AAAA,EAAA;AAElE;AAEA,MAAM,gCAAgC;AAoE/B,IAAM,WAAN,cAAuB,WAAW;AAAA,EAAlC,cAAA;AAAA,UAAA,GAAA,SAAA;AACL,SAAQ,cAAc,kBAAkB,KAAK,OAAA,EAAS,SAAS,EAAE,EAAE,MAAM,GAAG,CAAC,CAAC;AAC9E,SAAQ,iBAAiB,sBAAsB,KAAK,OAAA,EAAS,SAAS,EAAE,EAAE,MAAM,GAAG,CAAC,CAAC;AAE3D,SAAA,QAAyB,gBAAgB;AACzC,SAAA,WAAqB,SAAS;AAC9B,SAAA,kBACxB;AACyB,SAAA,aAAsB;AAItB,SAAA,kBAA2B;AAC5B,SAAA,8BAAsC;AACrC,SAAA,mBAA4B;AAC5B,SAAA,WAAoB;AAEpB,SAAA,kBAA2B;AAUtD,SAAQ,oBAA4B;AAGpC,SAAQ,wBAAwB;AAEW,SAAA,QAAqB,CAAA;AACrB,SAAA,WAA2B,CAAA;AAC3B,SAAA,UAAyB,CAAA;AACzB,SAAA,YAAwB,CAAA;AACxC,SAAA,kBAA2B;AAC5B,SAAA,gBACxB,cAAc;AAC2B,SAAA,UAA4B,CAAA;AAC5C,SAAA,mBAA4B;AAC5B,SAAA,aAAsB;AACN,SAAA,UAAyB,CAAA;AAC1C,SAAA,YAA2B;AAC3B,SAAA,uBAAsC;AACtC,SAAA,mBAAkC;AAElC,SAAA,UAAyB;AACzB,SAAA,0BAAyC;AACzC,SAAA,sBAAqC;AAEpC,SAAA,yBAAkC;AAEnC,SAAA,UAAkB;AAElB,SAAA,aAAqB;AAErB,SAAA,WAAmB;AAEnB,SAAA,YAAoB;AAOnB,SAAA,kBAA2B;AAC5B,SAAA,gBAAwB;AAEvB,SAAA,eAAwB;AAEzB,SAAA,gBAAwB;AAGxB,SAAA,cAA2B,YAAY;AACvC,SAAA,gBAAwB;AACxB,SAAA,cAAsB;AAErB,SAAA,mBAA4B;AAC7B,SAAA,oBAA4B;AAE5B,SAAA,wBAAgC;AAa1D,SAAQ,4BAA4B;AAqBpC,SAAQ,oBAAoB,IAAI,iBAAiB,MAAM,CAAA,CAAE;AA2DzD,SAAQ,QAAQ;AAAA,EAAA;AAAA,EAtFhB,IAAI,mBAAmB,OAAe;AACpC,SAAK,4BAA4B;AAAA,EACnC;AAAA,EACA,IAAI,qBAAqB;AACvB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,IAAY,gBAAwB;AAClC,QAAI,KAAK,8BAA8B,MAAM;AAC3C,aACG,KAAK,6BAA6B,MAAO,KAAK;AAAA,IAEnD;AACA,WAAO,KAAK;AAAA,EACd;AAAA,EAMS,WAAW,SAA+B;AACjD,UAAM,WAAW,OAAO;AAIxB,QACE,KAAK,mBACJ,QAAQ,IAAI,4BAA4B,KACvC,QAAQ,IAAI,uBAAuB,KACnC,QAAQ,IAAI,oBAAoB,IAClC;AACA,WAAK,eAAe,qBAAqB,KAAK;AAAA,IAChD;AAGA,QAAI,QAAQ,IAAI,kBAAkB,KAAK,KAAK,iBAAiB;AAC3D,YAAM,OAAO,QAAQ,IAAI,kBAAkB;AAC3C,UAAI,SAAS,UAAa,KAAK,qBAAqB,QAAW;AAC7D,aAAK,6BAA6B;AAClC,qBAAa,KAAK,eAAe;AACjC,cAAM,WAAW,+BAA+B,IAAI;AACpD,aAAK,kBAAkB,WAAW,MAAM;AACtC,eAAK,6BAA6B;AAAA,QACpC,GAAG,QAAQ;AAAA,MACb;AAAA,IACF;AAAA,EACF;AAAA,EAES,uBAA6B;AACpC,UAAM,qBAAA;AACN,iBAAa,KAAK,eAAe;AACjC,SAAK,iBAAiB,qBAAqB,MAAM,KAAK,cAAc;AAAA,EACtE;AAAA,EAES,QAAQ,SAA+B;AAC9C,UAAM,QAAQ,OAAO;AACrB,UAAM,KAAK,KAAK,UACZ,KAAK,WAAW,cAAc,cAAc,IAC5C;AACJ,QAAI,CAAC,IAAI;AACP,WAAK,iBAAiB,qBAAqB,MAAM,KAAK,cAAc;AACpE;AAAA,IACF;AACA,QAAI,CAAC,KAAK,kBAAkB,KAAK,eAAe,OAAO,IAAI;AACzD,WAAK,iBAAiB,qBAAqB,MAAM,KAAK,cAAc;AACpE,WAAK,iBAAiB,IAAI;AAAA,QACxB;AAAA,QACA;AAAA,QACA,KAAK;AAAA,MAAA;AAAA,IAET;AAAA,EACF;AAAA,EAEA,IAAY,kBAA0B;AACpC,WAAO,mBAAmB,KAAK,eAAe;AAAA,EAChD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYQ,YAAY,MAAsB;AACxC,WAAO,OAAO,KAAK;AAAA,EACrB;AAAA,EAEQ,cAAuD;AAC7D,UAAM,QAAQ,CAAA;AACd,QAAI,KAAK,UAAU,gBAAgB,KAAK;AACtC,YAAM,KAAK;AAAA;AAAA;AAAA;AAAA,eAIF,KAAK,YAAY,GAAG,CAAC;AAAA;AAAA;AAAA;AAAA,WAIzB;AAEL,UAAI,KAAK,oBAAoB,UAAwB;AACnD,cAAM,KAAK,KAAK,YAAY,YAAY;AACxC,cAAM,KAAK,KAAK;AAAA,UACd,KAAK,oBAAoB,eACrB,gBACA;AAAA,QAAA;AAEN,cAAM,KAAK,KAAK,MAAM;AACtB,cAAM,cAAc,KAAK;AACzB,cAAM;AAAA,UACJ;AAAA,sCAC4B,CAAC,kEAAkE,WAAW;AAAA,sCAC9E,EAAE;AAAA,sCACF,EAAE;AAAA;AAAA,QAAA;AAAA,MAGlC;AACA,UAAI,KAAK,oBAAoB,UAAwB;AACnD,cAAM,KAAK,KAAK,YAAY,YAAY;AACxC,cAAM,KAAK,KAAK,YAAY,YAAY;AACxC,cAAM,KAAK,KAAK,MAAM;AACtB,cAAM,cAAc,KAAK;AACzB,cAAM;AAAA,UACJ,8BAA8B,CAAC,gEAAgE,WAAW;AAAA,QAAA;AAAA,MAE9G;AAAA,IACF;AAEA,UAAM,WAAW,KAAK,IAAI,KAAK,oBAAoB,KAAK,QAAQ,EAAE;AAClE,QAAI,SAAS;AACb,QAAI,KAAK,MAAM,SAAS,GAAG;AACzB,YAAM,QAAQ,KAAK,MAAM,IAAI,CAAC,SAAS;AACrC,cAAM,UAAU,YAAY;AAAA,UAC1B,YAAY,KAAK;AAAA,UACjB,UAAU,KAAK;AAAA,UACf,GAAG,KAAK,YAAY,iBAAiB;AAAA,UACrC,GAAG,KAAK,YAAY,KAAK,eAAe;AAAA,UACxC,iBAAiB,KAAK;AAAA,UACtB,gBAAgB,KAAK;AAAA,QAAA,CACtB;AACD,eAAO;AAAA,MACT,CAAC;AACD,YAAM,OAAO;AAAA,mBACA,CAAC,QAAQ,QAAQ,CAAC,QAAQ,YAAY,WAAW,CAAC,aAAa,WAAW,CAAC;AAAA,UACpF,MAAM,IAAI,CAAC,SAAS,cAAc,IAAI,oFAAoF,CAAC;AAAA;AAG/H,YAAM,UAAU,kCAAkC,KAAK,MAAM;AAAA,QAC3D,CAAC,SACC,cAAc,YAAY;AAAA,UACxB,YAAY,KAAK;AAAA,UACjB,UAAU,KAAK;AAAA,UACf,GAAG,oBAAoB,KAAK,QAAQ;AAAA,UACpC,GAAG;AAAA,UACH,iBAAiB,KAAK;AAAA,UACtB,gBAAgB,KAAK;AAAA,QAAA,CACtB,CAAC;AAAA,MAAA,CACL;AACD,eAAS,CAAC,MAAM,SAAS,8BAA8B,KAAK,MAAM;AAClE,YAAM,QAAQ,CAAC,SAAS;AACtB,eAAO;AAAA,UACL,cAAc,IAAI;AAAA,QAAA;AAAA,MAEtB,CAAC;AAAA,IACH,OAAO;AACL,UAAI,KAAK,UAAU,gBAAgB,KAAK;AACtC,eAAO;AAAA,UACL,OAAO,aAAa;AAAA,YAClB,QAAQ,KAAK,YAAY,iBAAiB;AAAA,YAC1C,aAAa;AAAA,YACb,aAAa;AAAA,YACb,gBAAgB;AAAA,YAChB,WAAW;AAAA,UAAA,CACZ;AAAA,QAAA;AAGH,eAAO,KAAK;AAAA,YACR,OAAO,aAAa;AAAA,UACpB,QAAQ,KAAK,YAAY,KAAK,eAAe;AAAA,UAC7C,aAAa;AAAA,UACb,aAAa;AAAA,UACb,gBAAgB;AAAA,UAChB,WAAW;AAAA,QAAA,CACZ,CAAC;AAAA,SACH;AAAA,MACH,OAAO;AACL,eAAO,KAAK;AAAA,YACR,OAAO,aAAa;AAAA,UACpB,QAAQ,KAAK,YAAY,iBAAiB;AAAA,UAC1C,aAAa;AAAA,UACb,aAAa;AAAA,UACb,gBAAgB;AAAA,UAChB,WAAW;AAAA,QAAA,CACZ,CAAC;AAAA,SACH;AAAA,MACH;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA,EAEQ,sBAA0D;AAChE,QAAI,KAAK,iBAAiB,KAAK,KAAK,MAAM,WAAW,EAAG,QAAO;AAC/D,UAAM,OAAO,KAAK,MAAM,CAAC;AACzB,UAAM,UAAU,KAAK,WAAW,KAAK;AACrC,UAAM,OAAO,KAAK,IAAI,KAAK,eAAe,UAAU,CAAC;AACrD,QAAI,OAAO,IAAK,QAAO;AAEvB,UAAM,EAAC,YAAY,SAAA,IAAY;AAC/B,UAAM,IAAI,oBAAoB,KAAK,QAAQ;AAC3C,UAAM,QAAQ,CAAC,QAAiB,MAAM,KAAK,KAAM;AACjD,UAAM,KAAK,CAAC,QAAgB,IAAI,KAAK,IAAI,MAAM,GAAG,CAAC;AACnD,UAAM,KAAK,CAAC,QAAgB,CAAC,IAAI,KAAK,IAAI,MAAM,GAAG,CAAC;AAEpD,UAAM,WAAW,CAAC,GAAW,MAAsB;AACjD,YAAM,KAAK,GAAG,CAAC,GACb,KAAK,GAAG,CAAC;AACX,YAAM,KAAK,GAAG,CAAC,GACb,KAAK,GAAG,CAAC;AACX,YAAM,WAAW,IAAI,IAAI,MAAM,IAAI;AACnC,aAAO,WAAW,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM,QAAQ,MAAM,EAAE,IAAI,EAAE;AAAA,IACpE;AAEA,UAAM,MACH,KAAK,YAAY,iBAAiB,IACjC,KAAK,YAAY,KAAK,eAAe,KACvC;AACF,UAAM,KAAK,CAAC,QAAgB,KAAK,KAAK,IAAI,MAAM,GAAG,CAAC;AACpD,UAAM,KAAK,CAAC,QAAgB,CAAC,KAAK,KAAK,IAAI,MAAM,GAAG,CAAC;AAErD,WAAO;AAAA;AAAA;AAAA,gBAGK,GAAG,UAAU,CAAC,SAAS,GAAG,UAAU,CAAC;AAAA,gBACrC,GAAG,aAAa,IAAI,CAAC,SAAS,GAAG,aAAa,IAAI,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA,gBAKnD,GAAG,WAAW,IAAI,CAAC,SAAS,GAAG,WAAW,IAAI,CAAC;AAAA,gBAC/C,GAAG,QAAQ,CAAC,SAAS,GAAG,QAAQ,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA,eAKlC,CAAC,CAAC,QAAQ,CAAC,CAAC,YAAY,IAAI,CAAC,aAAa,IAAI,CAAC;AAAA,qBACzC,SAAS,aAAa,MAAM,WAAW,IAAI,CAAC;AAAA,qBAC5C,SAAS,YAAY,aAAa,IAAI,CAAC;AAAA,qBACvC,SAAS,WAAW,MAAM,QAAQ,CAAC;AAAA;AAAA;AAAA;AAAA,EAItD;AAAA,EAEQ,gBACN,QACA,gBAOmB;AACnB,UAAM,UAAU,kBAAkB,eAAe,UAAU,SAAS;AAIpE,UAAM,cAAc,UAChB,KAAK;AAAA,MACH,GAAG,eAAgB,UAAU;AAAA,QAAI,CAAC,MAChC,KAAK,IAAI,EAAE,MAAM,IAAI,EAAE,IAAI,EAAE,CAAC;AAAA,MAAA;AAAA,IAChC,IAEF;AAGJ,UAAM,aAAa,UAAU,IAAI,eAAgB,QAAQ;AAEzD,WAAO;AAAA,QAEH,UACI;AAAA;AAAA;AAAA;AAAA;AAAA,kBAKM,MAAM,SAAS,MAAM;AAAA,qBAClB,SAAS,CAAC,aAAa,SAAS,CAAC;AAAA;AAAA,wBAE9B,MAAM,SAAS,MAAM,YAAY,SAAS,CAAC,aAAa,SAAS,CAAC;AAAA;AAAA,uCAEnD,eAAgB,eAAe;AAAA,uCAC/B,cAAc,UAAU;AAAA;AAAA,cAEjD,eAAgB,UAAU,IAAI,CAAC,MAAM;AACrC,YAAM,WAAW,KAAK,eAAgB;AACtC,YAAM,MAAM,IAAI,eAAgB;AAChC,YAAM,OAAO,WAAW,MAAM;AAC9B,aAAO;AAAA;AAAA,uBAEE,EAAE,IAAI,OAAO,CAAC,QAAQ,EAAE,IAAI,OAAO,CAAC;AAAA,2BAChC,IAAI,aAAa,IAAI;AAAA;AAAA,sCAEV,EAAE,eAAgB,YAAY,EAAE;AAAA,sCAChC,EAAE,CAAC,IAAI,EAAE,CAAC;AAAA;AAAA;AAAA,IAGpC,CAAC,CAAC;AAAA;AAAA,mBAGF,OACN;AAAA,gBACU,UAAU,+BAA+B,OAAO;AAAA;AAAA,iBAE/C,MAAM;AAAA;AAAA,gBAEP,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,iBAQL,MAAM;AAAA;AAAA,gBAEP,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOpB;AAAA,EAEQ,aAAmD;AACzD,QAAI,KAAK,SAAS,WAAW,GAAG;AAC9B,aAAO;AAAA,IACT;AACA,WAAO,KAAK,SAAS,IAAI,CAAC,KAAK,UAAU;AACvC,YAAM,aAAa,KAAK,IAAI,IAAI,YAAY,IAAI,QAAQ;AACxD,YAAM,WAAW,KAAK,IAAI,IAAI,YAAY,IAAI,QAAQ;AACtD,YAAM,MAAM,YAAY;AAAA,QACtB,GAAG,KAAK,YAAY,YAAY;AAAA,QAChC,GAAG,KAAK,YAAY,YAAY;AAAA,QAChC;AAAA,QACA;AAAA,QACA,gBAAgB;AAAA,QAChB,iBAAiB;AAAA,MAAA,CAClB;AACD,YAAM,WAAW,eAAe,KAAK,QAAQ;AAE7C,YAAM,OAAO,wBAAwB,KAAK;AAAA,mBAC7B,CAAC,QAAQ,QAAQ,CAAC,QAAQ,YAAY,WAAW,CAAC,aAAa,WAAW,CAAC;AAAA,kBAC5E,YAAY;AAAA,QACpB,GAAG;AAAA,QACH,GAAG;AAAA,QACH;AAAA,QACA;AAAA,QACA,gBAAgB;AAAA,QAChB,iBAAiB;AAAA,MAAA,CAClB,CAAC;AAAA;AAEJ,aAAO;AAAA,UACH,IAAI;AAAA;AAAA;AAAA,cAGA,GAAG;AAAA,iBACA,IAAI,SAAS;AAAA,mBACX,IAAI,SAAS;AAAA;AAAA;AAAA,+BAGD,KAAK;AAAA;AAAA;AAAA;AAAA,IAIhC,CAAC;AAAA,EACH;AAAA,EAEQ,gBAAsD;AAC5D,QAAI,KAAK,QAAQ,WAAW,GAAG;AAC7B,aAAO;AAAA,IACT;AACA,WAAO,KAAK,QAAQ,IAAI,CAAC,WAAW;AAClC,aAAO;AAAA;AAAA,8BAEiB,OAAO,KAAK;AAAA,sBACpB,CAAC,KAAK,YAAY,YAAY,CAAC;AAAA,iBACpC,OAAO,SAAS;AAAA,mBACd,OAAO,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAMjC,CAAC;AAAA,EACH;AAAA,EAEQ,SAAS,EAAC,OAAO,UAAkD;AACzE,QAAI,cAAc,KAAK;AACvB,QAAI,eAAe,KAAK;AACxB,QAAI,gBAAgB,KAAK,iBAAiB,GAAG;AAC3C,YAAM,MAAM,KAAK,eAAe,sBAAA;AAChC,UAAI,KAAK;AACP,sBAAc,IAAI;AAClB,uBAAe,IAAI;AAAA,MACrB;AAAA,IACF;AACA,UAAM,QAAQ,KAAK,IAAI,cAAc,OAAO,eAAe,MAAM;AACjE,QAAI,UAAU,YAAY,QAAQ,GAAG;AACnC,YAAM,IAAI,MAAM,0BAA0B;AAAA,IAC5C;AACA,WAAO;AAAA,EACT;AAAA,EAEQ,aAAqB;AAC3B,QAAI,KAAK,YAAY,QAAW;AAC9B,aAAO,KAAK;AAAA,IACd;AACA,UAAM,uBACJ,KAAK,UAAU,SAAS,KACxB,KAAK,UAAU,KAAK,CAAC,MAAM,EAAE,SAAS,MAAS;AACjD,QAAI,wBAAwB,CAAC,KAAK,iBAAiB;AACjD,aAAO,KAAK;AAAA,IACd;AACA,WAAO;AAAA,EACT;AAAA,EAES,SAAS;AAChB,QAAI;AACJ,QAAI;AACJ,QAAI;AAEJ,QAAI,KAAK,UAAU;AACjB,WAAK,QAAQ,KAAK,SAAS;AAC3B,cAAQ,KAAK,SAAS;AACtB,eAAS,KAAK,SAAS;AACvB,gBAAU,KAAK,SAAS;AAAA,IAC1B,WAAW,KAAK,gBAAgB,KAAK,MAAM,SAAS,GAAG;AACrD,YAAM,MAAM,KAAK,WAAA;AACjB,YAAM,cAAc,MAAM,OAAO;AACjC,YAAM,QAAQ,yBAAyB;AAAA,QACrC,OAAO,KAAK;AAAA,QACZ,aAAa;AAAA,QACb,aAAa,KAAK;AAAA,QAClB,WAAW;AAAA,QACX;AAAA,MAAA,CACD;AACD,WAAK,QAAQ,MAAM;AACnB,cAAQ,MAAM;AACd,eAAS,MAAM;AACf,gBAAU,MAAM;AAAA,IAClB,OAAO;AACL,WAAK,QAAQ;AACb,YAAM,QAAQ,MAAM,KAAK,WAAA,KAAgB;AACzC,cAAQ,QAAQ,IAAI,KAAK,WAAW,MAAM,KAAK,YAAY;AAC3D,eAAS,QAAQ,IAAI,KAAK,UAAU,MAAM,KAAK,aAAa;AAC5D,YAAM,OAAO,CAAC,OAAO,IAAK,OAAO,KAAK,WAAY;AAClD,YAAM,MAAM,CAAC,OAAO,IAAK,OAAO,KAAK,UAAW;AAChD,gBAAU,GAAG,IAAI,IAAI,GAAG,IAAI,KAAK,IAAI,MAAM;AAAA,IAC7C;AAEA,UAAM,OAAO,KAAK;AAClB,UAAM,QAAQ,KAAK,SAAS,EAAC,OAAO,QAAO;AAC3C,UAAM,gBAAgB,KAAK,eAAA;AAG3B,UAAM,aAAa,KAAK,kBACpB,KAAK,YAAY,KAAK,eAAe,IACrC,KAAK,YAAY,iBAAiB;AACtC,UAAM,YAAY,KAAK;AAAA,MACrB,GAAG,KAAK,UAAU,IAAI,CAAC,MAAM,EAAE,MAAM,UAAU,CAAC;AAAA,IAAA;AAElD,UAAM,YAAY,KAAK,UAAU;AAAA,MAAI,CAAC,MACpC,SAAS,EAAE,OAAO;AAAA,QAChB,MAAM,EAAE;AAAA,QACR,OAAO,KAAK;AAAA,QACZ;AAAA,QACA,MAAM,KAAK,aAAa,SAAY,EAAE;AAAA,QACtC,QAAQ,KAAK;AAAA,QACb;AAAA,QACA,UAAU,KAAK;AAAA,QACf;AAAA,QACA,OAAO,EAAE;AAAA,QACT,cAAc;AAAA,QACd,iBAAiB,KAAK;AAAA,MAAA,CACvB;AAAA,IAAA;AAEH,UAAM,UAAU,KAAK,UACjB,KAAK,QAAQ,IAAI,CAAC,MAAM,aAAa,GAAG,IAAI,CAAC,IAC7C;AAGJ,UAAM,eAAe,KAAK,mBAAmB,KAAK;AAClD,UAAM,eAAe,CAAC,KAAK;AAC3B,UAAM,iBAAiB,KAAK,aACxB,kBAAkB;AAAA,MAChB;AAAA,MACA,QAAQ,KAAK;AAAA,MACb,aAAa,KAAK,kBAAkB;AAAA,MACpC;AAAA,IAAA,CACD,IACD;AAEJ,UAAM,SAAS,iBACX,aAAa;AAAA,MACX;AAAA,MACA,UAAU,KAAK;AAAA,MACf,QAAQ,KAAK;AAAA,MACb,aAAa,KAAK,kBAAkB;AAAA,MACpC;AAAA,IAAA,CACD,IACD;AACJ,UAAM,eAAe,KAAK,aACtB,iBAAiB;AAAA,MACf;AAAA,MACA,UAAU,KAAK;AAAA,MACf,QAAQ,KAAK,oBAAoB,KAAK;AAAA,IAAA,CACvC,IACD;AACJ,UAAM,OACJ,KAAK,aAAa,QAAQ,KAAK,wBAAwB,OACnD,0BAA0B,KAAK,aAAa,MAAM,WAAW;AAAA,MAC3D,WAAW,KAAK;AAAA,MAChB,kBAAkB,KAAK;AAAA,MACvB,QAAQ,KAAK,oBAAoB;AAAA,MACjC,OAAO,KAAK;AAAA,IAAA,CACb,CAAC,SACF;AACN,UAAM,UACJ,KAAK,WAAW,QAAQ,KAAK,2BAA2B,OACpD,cAAc;AAAA,MACZ,SAAS,KAAK;AAAA,MACd,kBAAkB,KAAK;AAAA,MACvB,QAAQ,KAAK,uBAAuB;AAAA,MACpC,OAAO,KAAK;AAAA,IAAA,CACb,IACD;AACN,WAAO;AAAA;AAAA;AAAA;AAAA,kBAIO,OAAO;AAAA,0BACC,KAAK;AAAA,4BACH,KAAK,YAAY,CAAC;AAAA;AAAA,UAEpC,KAAK,YAAA,CAAa,IAAI,KAAK,YAAY;AAAA,UACvC,KAAK,mBACH,KAAK;AAAA,MACH,oBAAoB;AAAA,MACpB,gBAAgB,iBACZ;AAAA,QACE,WAAW;AAAA,QACX,UAAU,KAAK;AAAA,QACf;AAAA,QACA,iBAAiB,KAAK,kBAAkB;AAAA,MAAA,IAE1C;AAAA,IAAA,IAEN,OAAO;AAAA,UACT,YAAY,IAAI,KAAK,6BAAA,CAA8B,IAAI,OAAO;AAAA,UAC9D,KAAK,qBAAqB,IAAI,IAAI;AAAA,UAClC,KAAK,gBAAgB,KAAK,KAAK,MAAM,SAAS,IAC5C,mCAAmC,SAAS,SAC5C,SAAS;AAAA,UACX,KAAK,MAAM,SAAS,IAClB,wCAAwC,KAAK,UAAA,CAAW,SACxD,KAAK,UAAA,CAAW;AAAA,UAClB,OAAO,IAAI,aAAa;AAAA,UACxB,KAAK,gBAAgB,KAAK,KAAK,MAAM,SAAS,IAC5C,mCAAmC,MAAM,SACzC,MAAM;AAAA,UACR,KAAK,kBAAA,CAAmB,IAAI,KAAK,eAAe;AAAA;AAAA;AAAA,EAGxD;AAAA,EAEQ,eAGN;AACA,UAAM,IAAI,KAAK,eAAe,KAAK;AACnC,UAAM,aAAa,MAAM,SAAS;AAElC,QAAI,KAAK,kBAAkB;AAGzB,UAAI;AACJ,UAAI,KAAK,YAAY,QAAQ,KAAK;AAChC,cAAM,WACD,KAAK,cAAc,KAAK,iBAAiB,MAAO,OAAO;AAC5D,oBAAY,UAAU,MAAM,SAAS,SAAS;AAAA,MAChD,OAAO;AACL,oBAAY,KAAK;AAAA,MACnB;AAEA,UAAI,YAAY,GAAG;AACjB,eAAO;AAAA,UACL,UAAU;AAAA,UACV,YAAY;AAAA,QAAA;AAAA,MAEhB;AACA,UAAI,YAAY,GAAG;AACjB,eAAO;AAAA,UACL,UAAU;AAAA,UACV,YAAY;AAAA,QAAA;AAAA,MAEhB;AAAA,IACF;AAEA,WAAO;AAAA,MACL,UAAU,aACN,8CACA;AAAA,MACJ,YAAY,aACR,+CACA;AAAA,IAAA;AAAA,EAER;AAAA,EAEQ,YAAgD;AACtD,QAAI,CAAC,KAAK,QAAS,QAAO;AAE1B,UAAM,EAAC,UAAU,eAAc,KAAK,aAAA;AACpC,UAAM,OAAO,KAAK;AAElB,QAAI,KAAK,YAAY,QAAQ,KAAK;AAChC,YAAM,eAAe;AAAA,QACnB,KAAK;AAAA,QACL,KAAK;AAAA,MAAA;AAEP,YAAM,YAAY,qBAAqB,KAAK,aAAa,IAAI;AAC7D,YAAM,SAAS,OAAO,SAAS,KAAK,iBAAiB,IACjD,KAAK,oBACL;AAEJ,UAAI,eAAe,KAAK,IAAI,QAAQ,SAAS,GAAG;AAC9C,eAAO;AAAA,UACL;AAAA,UACA,KAAK;AAAA,UACL,KAAK;AAAA,UACL;AAAA,QAAA;AAAA,MAEJ;AAEA,aAAO;AAAA,UACH,mBAAmB;AAAA,QACnB,YAAY,KAAK;AAAA,QACjB,UAAU,KAAK;AAAA,QACf,UAAU;AAAA,QACV,UAAU,KAAK;AAAA,QACf,QAAQ;AAAA,QACR,cAAc;AAAA,MAAA,CACf,CAAC;AAAA,UACA;AAAA;AAAA,gBAEM,iBAAiB,UAAU,KAAK,aAAa,IAAI,CAAC;AAAA;AAAA,eAEnD;AAAA;AAAA,IAEX;AAEA,UAAM,IAAI,KAAK,eAAe,KAAK;AACnC,UAAM,aAAa,MAAM,SAAS;AAClC,QAAI,YAAoB,aACpB,+CACA;AACJ,QAAI,KAAK,kBAAkB;AACzB,UAAI,KAAK,gBAAgB,GAAG;AAC1B,oBAAY;AAAA,MACd,WAAW,KAAK,gBAAgB,GAAG;AACjC,oBAAY;AAAA,MACd;AAAA,IACF;AACA,WAAO;AAAA;AAAA,UAED,cAAc,WAAW,KAAK,aAAa,IAAI,CAAC;AAAA;AAAA;AAAA,EAGxD;AAAA,EAEQ,iBAAqD;AAC3D,QAAI,KAAK,kBAAkB,QAAW;AACpC,aAAO;AAAA,IACT;AAEA,UAAM,UAAU,2BAA2B;AAAA,MACzC,OAAO,KAAK;AAAA,MACZ,UAAU,KAAK;AAAA,MACf,YAAY,KAAK;AAAA,MACjB,eAAe,KAAK;AAAA,MACpB,wBAAwB,KAAK;AAAA,MAC7B,kBAAkB,KAAK;AAAA,MACvB,UAAU,KAAK;AAAA,MACf,kBAAkB,KAAK;AAAA,IAAA,CACxB;AAED,UAAM,EAAC,aAAa,WAAW,UAAU,mBAAkB;AAE3D,UAAM,gBAAgB,yBAAyB,WAAW;AAC1D,UAAM,SACJ,yBACA,KAAK,QACL,gBACA;AAGF,UAAM,UAAU,iBAAiB,OAAO;AACxC,UAAM,iBAAiB,mBAAmB;AAAA,MACxC;AAAA,MACA;AAAA,MACA;AAAA,MACA,IAAI,KAAK;AAAA,IAAA,CACV;AAED,UAAM,UAAU,KAAK;AACrB,UAAM,eAAe,KAAK,+BAA+B;AAGzD,UAAM,WAAW,KAAK,gBAAgB;AACtC,QAAI,CAAC,KAAK,uBAAuB;AAE/B,WAAK,oBAAoB;AACzB,WAAK,wBAAwB;AAAA,IAC/B,OAAO;AACL,WAAK,oBAAoB,aAAa,KAAK,mBAAmB,QAAQ;AAAA,IACxE;AAGA,UAAM,mBAAmB,UACrB;AAAA,sCAC8B,KAAK,iBAAiB,mBAAmB,CAAC,MAAM,gCAAgC,OAAO,+BAA+B,0BAA0B,KAAK,mCAAmC,2BAA2B,0BAA0B,KAAK,mCAAmC;AAAA,YAC/S,cAAc;AAAA;AAAA,UAGlB;AAAA,+BACuB,KAAK,gBAAgB,EAAE,eAAe,CAAC,MAAM,8BAA8B,OAAO;AAAA,YACrG,cAAc;AAAA;AAAA;AAMtB,QAAI,kBAAkB,cAAc;AAClC,YAAM,WAAW;AACjB,YAAM,WAAW,WACb,KAAK,mBACL,KAAK;AACT,YAAM,gBAAgB,WAAW,IAAI;AAErC,YAAM,qBAAqB;AAAA,QACzB,oBAAoB;AAAA,MAAA;AAEtB,YAAM,cACJ,yBACA,KAAK,QACL,qBACA;AAEF,YAAM,YAAY,mBAAmB;AAAA,QACnC,aAAa,oBAAoB;AAAA,QACjC;AAAA,QACA,UAAU;AAAA;AAAA,QACV,IAAI,KAAK;AAAA,MAAA,CACV;AAED,UAAI,SAAS;AACX,cAAM,WAAW,OAAO,0BAA0B,KAAK,mCAAmC;AAC1F,eAAO;AAAA,YACH,gBAAgB;AAAA,wCACY,WAAW,EAAE,mBAAmB,CAAC,WAAW,gCAAgC,aAAa,yBAAyB,QAAQ;AAAA,cACpJ,SAAS;AAAA;AAAA;AAAA,MAGjB;AAEA,aAAO;AAAA,UACH,gBAAgB;AAAA,+BACK,WAAW,EAAE,eAAe,CAAC,WAAW,8BAA8B,aAAa;AAAA,YACtG,SAAS;AAAA;AAAA;AAAA,IAGjB;AAEA,WAAO;AAAA,EACT;AAAA,EAEQ,oBAA0D;AAChE,QAAI,KAAK,QAAQ,WAAW,GAAG;AAC7B,aAAO;AAAA,IACT;AAEA,WAAO,KAAK,QAAQ,IAAI,CAAC,MAAM;AAC7B,UAAI;AACJ,cAAQ,EAAE,MAAA;AAAA,QACR,KAAK,gBAAgB;AACnB,iBAAO;AACP;AAAA,QACF,KAAK,gBAAgB;AACnB,iBAAO;AACP;AAAA,QACF;AACE,iBAAO;AAAA,MAAA;AAGX,YAAM,QAAQ,OAAO;AACrB,aAAO,2BAA2B,EAAE,SAAS,UAAU,KAAK,+BAA+B,aAAa,EAAE,WAAW,CAAC;AAAA,IACxH,CAAC;AAAA,EACH;AAAA,EAEQ,+BAAqE;AAC3E,QAAI,CAAC,KAAK,wBAAwB;AAChC,aAAO;AAAA,IACT;AACA,WAAO;AAAA,MACL;AAAA,QACE;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MAAA;AAAA,MAEF;AAAA,QACE;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MAAA;AAAA,IACF;AAAA,EAEJ;AAGF;AAp6Ba,SAm6BK,SAAS,UAAU,YAAY;AA/5BrB,gBAAA;AAAA,EAAzB,SAAS,EAAC,MAAM,OAAA,CAAO;AAAA,GAJb,SAIe,WAAA,SAAA,CAAA;AACA,gBAAA;AAAA,EAAzB,SAAS,EAAC,MAAM,OAAA,CAAO;AAAA,GALb,SAKe,WAAA,YAAA,CAAA;AACA,gBAAA;AAAA,EAAzB,SAAS,EAAC,MAAM,OAAA,CAAO;AAAA,GANb,SAMe,WAAA,mBAAA,CAAA;AAEC,gBAAA;AAAA,EAA1B,SAAS,EAAC,MAAM,QAAA,CAAQ;AAAA,GARd,SAQgB,WAAA,cAAA,CAAA;AACA,gBAAA;AAAA,EAA1B,SAAS,EAAC,MAAM,QAAA,CAAQ;AAAA,GATd,SASgB,WAAA,oBAAA,CAAA;AACD,gBAAA;AAAA,EAAzB,SAAS,EAAC,MAAM,OAAA,CAAO;AAAA,GAVb,SAUe,WAAA,iBAAA,CAAA;AACA,gBAAA;AAAA,EAAzB,SAAS,EAAC,MAAM,OAAA,CAAO;AAAA,GAXb,SAWe,WAAA,oBAAA,CAAA;AACC,gBAAA;AAAA,EAA1B,SAAS,EAAC,MAAM,QAAA,CAAQ;AAAA,GAZd,SAYgB,WAAA,mBAAA,CAAA;AACD,gBAAA;AAAA,EAAzB,SAAS,EAAC,MAAM,OAAA,CAAO;AAAA,GAbb,SAae,WAAA,+BAAA,CAAA;AACC,gBAAA;AAAA,EAA1B,SAAS,EAAC,MAAM,QAAA,CAAQ;AAAA,GAdd,SAcgB,WAAA,oBAAA,CAAA;AACA,gBAAA;AAAA,EAA1B,SAAS,EAAC,MAAM,QAAA,CAAQ;AAAA,GAfd,SAegB,WAAA,YAAA,CAAA;AAEA,gBAAA;AAAA,EAA1B,SAAS,EAAC,MAAM,QAAA,CAAQ;AAAA,GAjBd,SAiBgB,WAAA,mBAAA,CAAA;AAEV,gBAAA;AAAA,EAAhB,MAAA;AAAM,GAnBI,SAmBM,WAAA,8BAAA,CAAA;AAYS,gBAAA;AAAA,EAAzB,SAAS,EAAC,MAAM,OAAA,CAAO;AAAA,GA/Bb,SA+Be,WAAA,WAAA,CAAA;AACiB,gBAAA;AAAA,EAA1C,SAAS,EAAC,MAAM,OAAO,WAAW,OAAM;AAAA,GAhC9B,SAgCgC,WAAA,SAAA,CAAA;AACA,gBAAA;AAAA,EAA1C,SAAS,EAAC,MAAM,OAAO,WAAW,OAAM;AAAA,GAjC9B,SAiCgC,WAAA,YAAA,CAAA;AACA,gBAAA;AAAA,EAA1C,SAAS,EAAC,MAAM,OAAO,WAAW,OAAM;AAAA,GAlC9B,SAkCgC,WAAA,WAAA,CAAA;AACA,gBAAA;AAAA,EAA1C,SAAS,EAAC,MAAM,OAAO,WAAW,OAAM;AAAA,GAnC9B,SAmCgC,WAAA,aAAA,CAAA;AAChB,gBAAA;AAAA,EAA1B,SAAS,EAAC,MAAM,QAAA,CAAQ;AAAA,GApCd,SAoCgB,WAAA,mBAAA,CAAA;AACD,gBAAA;AAAA,EAAzB,SAAS,EAAC,MAAM,OAAA,CAAO;AAAA,GArCb,SAqCe,WAAA,iBAAA,CAAA;AAEiB,gBAAA;AAAA,EAA1C,SAAS,EAAC,MAAM,OAAO,WAAW,OAAM;AAAA,GAvC9B,SAuCgC,WAAA,WAAA,CAAA;AAChB,gBAAA;AAAA,EAA1B,SAAS,EAAC,MAAM,QAAA,CAAQ;AAAA,GAxCd,SAwCgB,WAAA,oBAAA,CAAA;AACA,gBAAA;AAAA,EAA1B,SAAS,EAAC,MAAM,QAAA,CAAQ;AAAA,GAzCd,SAyCgB,WAAA,cAAA,CAAA;AACgB,gBAAA;AAAA,EAA1C,SAAS,EAAC,MAAM,OAAO,WAAW,OAAM;AAAA,GA1C9B,SA0CgC,WAAA,WAAA,CAAA;AACjB,gBAAA;AAAA,EAAzB,SAAS,EAAC,MAAM,OAAA,CAAO;AAAA,GA3Cb,SA2Ce,WAAA,aAAA,CAAA;AACA,gBAAA;AAAA,EAAzB,SAAS,EAAC,MAAM,OAAA,CAAO;AAAA,GA5Cb,SA4Ce,WAAA,wBAAA,CAAA;AACA,gBAAA;AAAA,EAAzB,SAAS,EAAC,MAAM,OAAA,CAAO;AAAA,GA7Cb,SA6Ce,WAAA,oBAAA,CAAA;AACA,gBAAA;AAAA,EAAzB,SAAS,EAAC,MAAM,OAAA,CAAO;AAAA,GA9Cb,SA8Ce,WAAA,aAAA,CAAA;AACA,gBAAA;AAAA,EAAzB,SAAS,EAAC,MAAM,OAAA,CAAO;AAAA,GA/Cb,SA+Ce,WAAA,WAAA,CAAA;AACA,gBAAA;AAAA,EAAzB,SAAS,EAAC,MAAM,OAAA,CAAO;AAAA,GAhDb,SAgDe,WAAA,2BAAA,CAAA;AACA,gBAAA;AAAA,EAAzB,SAAS,EAAC,MAAM,OAAA,CAAO;AAAA,GAjDb,SAiDe,WAAA,uBAAA,CAAA;AACA,gBAAA;AAAA,EAAzB,SAAS,EAAC,MAAM,OAAA,CAAO;AAAA,GAlDb,SAkDe,WAAA,gBAAA,CAAA;AACC,gBAAA;AAAA,EAA1B,SAAS,EAAC,MAAM,QAAA,CAAQ;AAAA,GAnDd,SAmDgB,WAAA,0BAAA,CAAA;AAED,gBAAA;AAAA,EAAzB,SAAS,EAAC,MAAM,OAAA,CAAO;AAAA,GArDb,SAqDe,WAAA,WAAA,CAAA;AAEA,gBAAA;AAAA,EAAzB,SAAS,EAAC,MAAM,OAAA,CAAO;AAAA,GAvDb,SAuDe,WAAA,cAAA,CAAA;AAEA,gBAAA;AAAA,EAAzB,SAAS,EAAC,MAAM,OAAA,CAAO;AAAA,GAzDb,SAyDe,WAAA,YAAA,CAAA;AAEA,gBAAA;AAAA,EAAzB,SAAS,EAAC,MAAM,OAAA,CAAO;AAAA,GA3Db,SA2De,WAAA,aAAA,CAAA;AAOC,gBAAA;AAAA,EAA1B,SAAS,EAAC,MAAM,QAAA,CAAQ;AAAA,GAlEd,SAkEgB,WAAA,mBAAA,CAAA;AACD,gBAAA;AAAA,EAAzB,SAAS,EAAC,MAAM,OAAA,CAAO;AAAA,GAnEb,SAmEe,WAAA,iBAAA,CAAA;AACA,gBAAA;AAAA,EAAzB,SAAS,EAAC,MAAM,OAAA,CAAO;AAAA,GApEb,SAoEe,WAAA,YAAA,CAAA;AACC,gBAAA;AAAA,EAA1B,SAAS,EAAC,MAAM,QAAA,CAAQ;AAAA,GArEd,SAqEgB,WAAA,gBAAA,CAAA;AACG,gBAAA;AAAA,EAA7B,SAAS,EAAC,WAAW,MAAA,CAAM;AAAA,GAtEjB,SAsEmB,WAAA,YAAA,CAAA;AACJ,gBAAA;AAAA,EAAzB,SAAS,EAAC,MAAM,OAAA,CAAO;AAAA,GAvEb,SAuEe,WAAA,iBAAA,CAAA;AAEA,gBAAA;AAAA,EAAzB,SAAS,EAAC,MAAM,OAAA,CAAO;AAAA,GAzEb,SAyEe,WAAA,WAAA,CAAA;AACA,gBAAA;AAAA,EAAzB,SAAS,EAAC,MAAM,OAAA,CAAO;AAAA,GA1Eb,SA0Ee,WAAA,eAAA,CAAA;AACA,gBAAA;AAAA,EAAzB,SAAS,EAAC,MAAM,OAAA,CAAO;AAAA,GA3Eb,SA2Ee,WAAA,iBAAA,CAAA;AACA,gBAAA;AAAA,EAAzB,SAAS,EAAC,MAAM,OAAA,CAAO;AAAA,GA5Eb,SA4Ee,WAAA,eAAA,CAAA;AACA,gBAAA;AAAA,EAAzB,SAAS,EAAC,MAAM,OAAA,CAAO;AAAA,GA7Eb,SA6Ee,WAAA,eAAA,CAAA;AACC,gBAAA;AAAA,EAA1B,SAAS,EAAC,MAAM,QAAA,CAAQ;AAAA,GA9Ed,SA8EgB,WAAA,oBAAA,CAAA;AACD,gBAAA;AAAA,EAAzB,SAAS,EAAC,MAAM,OAAA,CAAO;AAAA,GA/Eb,SA+Ee,WAAA,qBAAA,CAAA;AACA,gBAAA;AAAA,EAAzB,SAAS,EAAC,MAAM,OAAA,CAAO;AAAA,GAhFb,SAgFe,WAAA,8BAAA,CAAA;AACA,gBAAA;AAAA,EAAzB,SAAS,EAAC,MAAM,OAAA,CAAO;AAAA,GAjFb,SAiFe,WAAA,yBAAA,CAAA;AAOtB,gBAAA;AAAA,EADH,SAAS,EAAC,MAAM,OAAA,CAAO;AAAA,GAvFb,SAwFP,WAAA,sBAAA,CAAA;AAxFO,WAAN,gBAAA;AAAA,EADN,cAAc,WAAW;AAAA,GACb,QAAA;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oicl/openbridge-webcomponents-full-bundle",
3
- "version": "2.0.0-next.57",
3
+ "version": "2.0.0-next.58",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -21,6 +21,9 @@ const meta: Meta<typeof ObcInstrumentRadial> = {
21
21
  showLabels: {control: 'boolean'},
22
22
  minAngle: {control: false, table: {disable: true}},
23
23
  maxAngle: {control: false, table: {disable: true}},
24
+ // Only acts on labels at the horizontal ends (±90°), i.e. a 180° sweep.
25
+ // Hidden by default; the MaxMinEndLabels story re-enables it.
26
+ endLabelsMaxMin: {table: {disable: true}},
24
27
  },
25
28
  } satisfies Meta<ObcInstrumentRadial>;
26
29
 
@@ -92,6 +95,24 @@ export const WithLabels: Story = {
92
95
  },
93
96
  };
94
97
 
98
+ // `endLabelsMaxMin` only repositions labels at the horizontal ends (±90°). This
99
+ // 180° sweep puts the min/max labels there, so toggling `endLabelsMaxMin` (and
100
+ // `tickmarksInside`) visibly moves them below/centered.
101
+ export const MaxMinEndLabels: Story = {
102
+ args: {
103
+ value: 50,
104
+ maxValue: 100,
105
+ minValue: 0,
106
+ type: ObcGaugeRadialType.filled,
107
+ showLabels: true,
108
+ getAngle: (v: number) => (v / 100) * 180 - 90, // top 180° arc: 0 left, 100 right
109
+ endLabelsMaxMin: true,
110
+ },
111
+ argTypes: {
112
+ endLabelsMaxMin: {control: 'boolean', table: {disable: false}},
113
+ },
114
+ };
115
+
95
116
  export const WithAdvices: Story = {
96
117
  args: {
97
118
  value: 50,
@@ -21,6 +21,10 @@ import {
21
21
  type ZoomToFitArcFrame,
22
22
  } from '../../svghelpers/arc-frame.js';
23
23
 
24
+ // Must match obc-watch's un-zoomed viewBox `(176 + getPadding()) * 2`
25
+ // (default padding 48 → 448); instrument-radial overlays obc-watch 1:1.
26
+ const WATCH_DEFAULT_VIEWBOX = 448;
27
+
24
28
  export enum ObcGaugeRadialType {
25
29
  filled = 'filled',
26
30
  bar = 'bar',
@@ -34,6 +38,11 @@ export interface GaugeRadialAdvice {
34
38
  hinted: boolean;
35
39
  }
36
40
 
41
+ const NEEDLE_TIP_RADIUS = 160;
42
+ const NEEDLE_TIP_GAP = 5; // tip stops this far short of the scale
43
+ const NEEDLE_WIDTH = 8;
44
+ const NEEDLE_HUB_RADIUS = 16;
45
+
37
46
  function rangeIncludesZero(minValue: number, maxValue: number): boolean {
38
47
  return minValue <= 0 && maxValue >= 0;
39
48
  }
@@ -55,6 +64,56 @@ function strongerTickmarkType(
55
64
  return priority[candidate] > priority[existing] ? candidate : existing;
56
65
  }
57
66
 
67
+ interface Clips {
68
+ top: number;
69
+ bottom: number;
70
+ left: number;
71
+ right: number;
72
+ }
73
+
74
+ /** Clamp a clip percentage to [0, 100]; non-finite returns 0. */
75
+ function clampClipPercent(n: number): number {
76
+ return Number.isFinite(n) ? Math.min(Math.max(n, 0), 100) : 0;
77
+ }
78
+
79
+ /**
80
+ * Clamp the four clips to [0, 100] and drop any opposite pair that would
81
+ * collapse the viewBox (sum >= 100), so a bad clip can't produce a zero- or
82
+ * negative-size box. Valid clips pass through unchanged.
83
+ */
84
+ function normalizeClips(clips: Clips): Clips {
85
+ let top = clampClipPercent(clips.top);
86
+ let bottom = clampClipPercent(clips.bottom);
87
+ let left = clampClipPercent(clips.left);
88
+ let right = clampClipPercent(clips.right);
89
+ if (top + bottom >= 100) {
90
+ top = 0;
91
+ bottom = 0;
92
+ }
93
+ if (left + right >= 100) {
94
+ left = 0;
95
+ right = 0;
96
+ }
97
+ return {top, bottom, left, right};
98
+ }
99
+
100
+ /**
101
+ * Fallback value-to-angle mapping when no `getAngle` is supplied: linear over
102
+ * the historical 270° sweep (-135 to 135). Returns -135 for a non-positive or
103
+ * non-finite span.
104
+ */
105
+ function defaultGaugeAngle(
106
+ value: number,
107
+ minValue: number,
108
+ maxValue: number
109
+ ): number {
110
+ const span = maxValue - minValue;
111
+ if (!Number.isFinite(span) || span <= 0) {
112
+ return -135;
113
+ }
114
+ return ((value - minValue) / span) * 270 - 135;
115
+ }
116
+
58
117
  @customElement('obc-instrument-radial')
59
118
  export class ObcInstrumentRadial extends SetpointMixin(LitElement) {
60
119
  // setpoint, newSetpoint, atSetpoint, touching, autoAtSetpoint,
@@ -97,6 +156,14 @@ export class ObcInstrumentRadial extends SetpointMixin(LitElement) {
97
156
  @property({type: Array, attribute: false}) advices: GaugeRadialAdvice[] = [];
98
157
  @property({type: Number}) clipTop: number = 0; // in percent of height
99
158
  @property({type: Number}) clipBottom: number = 0; // in percent of height
159
+ @property({type: Number}) clipLeft: number = 0; // in percent of width
160
+ @property({type: Number}) clipRight: number = 0; // in percent of width
161
+ /**
162
+ * Place the horizontal end labels (±90°, e.g. min/max) below the tick instead
163
+ * of beside it — the "Max-min" placement from the radial label model
164
+ * (External / Internal / Max-min). See PR #903 / design discussion.
165
+ */
166
+ @property({type: Boolean}) endLabelsMaxMin: boolean = false;
100
167
  @property({type: Boolean}) zoomToFitArc: boolean = false;
101
168
 
102
169
  private _radiusOffset = 0;
@@ -109,11 +176,33 @@ export class ObcInstrumentRadial extends SetpointMixin(LitElement) {
109
176
  }
110
177
 
111
178
  private get minAngle(): number {
112
- return this.getAngle(this.minValue);
179
+ return this.mapAngle(this.minValue);
113
180
  }
114
181
 
115
182
  private get maxAngle(): number {
116
- return this.getAngle(this.maxValue);
183
+ return this.mapAngle(this.maxValue);
184
+ }
185
+
186
+ // Map a value to an angle via the consumer's `getAngle`, guarding a missing
187
+ // or non-finite mapping so a misconfigured consumer can't emit NaN geometry.
188
+ private mapAngle(value: number): number {
189
+ const fn = this.getAngle;
190
+ const angle =
191
+ typeof fn === 'function'
192
+ ? fn(value)
193
+ : defaultGaugeAngle(value, this.minValue, this.maxValue);
194
+ return Number.isFinite(angle) ? angle : 0;
195
+ }
196
+
197
+ // Clamped clips, reused for the overlay viewBox and the clips forwarded to
198
+ // obc-watch.
199
+ private get safeClips(): Clips {
200
+ return normalizeClips({
201
+ top: this.clipTop,
202
+ bottom: this.clipBottom,
203
+ left: this.clipLeft,
204
+ right: this.clipRight,
205
+ });
117
206
  }
118
207
 
119
208
  private get _derivedNeedleColor(): string {
@@ -150,10 +239,10 @@ export class ObcInstrumentRadial extends SetpointMixin(LitElement) {
150
239
  const barStartValue = Math.max(this.minValue, Math.min(0, this.maxValue));
151
240
  const value = this.clampedValue;
152
241
  const setpointAngle =
153
- this.setpoint !== undefined ? this.getAngle(this.setpoint) : undefined;
242
+ this.setpoint !== undefined ? this.mapAngle(this.setpoint) : undefined;
154
243
  const newSetpointAngle =
155
244
  this.newSetpoint !== undefined
156
- ? this.getAngle(this.newSetpoint)
245
+ ? this.mapAngle(this.newSetpoint)
157
246
  : undefined;
158
247
 
159
248
  const barAreas =
@@ -161,8 +250,8 @@ export class ObcInstrumentRadial extends SetpointMixin(LitElement) {
161
250
  ? []
162
251
  : [
163
252
  {
164
- startAngle: this.getAngle(barStartValue),
165
- endAngle: this.getAngle(value),
253
+ startAngle: this.mapAngle(barStartValue),
254
+ endAngle: this.mapAngle(value),
166
255
  fillColor: barColor,
167
256
  },
168
257
  ];
@@ -181,6 +270,7 @@ export class ObcInstrumentRadial extends SetpointMixin(LitElement) {
181
270
  ? WatchCircleType.single
182
271
  : WatchCircleType.double;
183
272
 
273
+ const clips = this.safeClips;
184
274
  let viewBox: string;
185
275
  if (this.zoomToFitArc) {
186
276
  const ext = 48;
@@ -192,16 +282,18 @@ export class ObcInstrumentRadial extends SetpointMixin(LitElement) {
192
282
  extension: ext,
193
283
  targetSize,
194
284
  });
195
- viewBox = frame.viewBox;
196
285
  this._radiusOffset = frame.radiusOffset;
286
+ viewBox = frame.viewBox;
197
287
  this._arcFrame = frame;
198
288
  } else {
199
289
  this._radiusOffset = 0;
200
290
  this._arcFrame = undefined;
201
- const width = 448;
202
- const height = width * (1 - this.clipTop / 100 - this.clipBottom / 100);
203
- const top = -width / 2 + (width * this.clipTop) / 100;
204
- viewBox = `${-width / 2} ${top} ${width} ${height}`;
291
+ const full = WATCH_DEFAULT_VIEWBOX;
292
+ const w = full * (1 - clips.left / 100 - clips.right / 100);
293
+ const h = full * (1 - clips.top / 100 - clips.bottom / 100);
294
+ const left = -full / 2 + (full * clips.left) / 100;
295
+ const top = -full / 2 + (full * clips.top) / 100;
296
+ viewBox = `${left} ${top} ${w} ${h}`;
205
297
  }
206
298
 
207
299
  return html`
@@ -223,8 +315,11 @@ export class ObcInstrumentRadial extends SetpointMixin(LitElement) {
223
315
  .areas=${areas}
224
316
  .watchCircleType=${watchCircleType}
225
317
  .barAreas=${barAreas}
226
- .clipTop=${this.zoomToFitArc ? 0 : this.clipTop}
227
- .clipBottom=${this.zoomToFitArc ? 0 : this.clipBottom}
318
+ .clipTop=${this.zoomToFitArc ? 0 : clips.top}
319
+ .clipBottom=${this.zoomToFitArc ? 0 : clips.bottom}
320
+ .clipLeft=${this.zoomToFitArc ? 0 : clips.left}
321
+ .clipRight=${this.zoomToFitArc ? 0 : clips.right}
322
+ .endLabelsMaxMin=${this.endLabelsMaxMin}
228
323
  .zoomToFitArc=${this.zoomToFitArc}
229
324
  .arcFrame=${this._arcFrame}
230
325
  ></obc-watch>
@@ -241,14 +336,16 @@ export class ObcInstrumentRadial extends SetpointMixin(LitElement) {
241
336
  const rOff = this._radiusOffset;
242
337
  const value = this.clampedValue;
243
338
  if (this.type === ObcGaugeRadialType.needle) {
244
- return svg`<g transform="rotate(${this.getAngle(value)}) translate(-256, -256)">
245
- <circle cx="256" cy="256" r="14" fill=${needleColor}/>
246
- <rect x="250" y="${96 - rOff}" width="12" height="${192 + rOff}" rx="6" fill=${needleColor}/>
247
- <rect x="252" y="${98 - rOff}" width="8" height="${188 + rOff}" rx="4" stroke=${needleColor} fill=${needleColor} stroke-width="4"/>
339
+ // Rod runs from the value tip down to the center hub. Width is constant;
340
+ // the tip shifts outward additively under zoom.
341
+ const tipY = 256 - (NEEDLE_TIP_RADIUS - NEEDLE_TIP_GAP) - rOff;
342
+ return svg`<g transform="rotate(${this.mapAngle(value)}) translate(-256, -256)">
343
+ <rect x="${256 - NEEDLE_WIDTH / 2}" y="${tipY}" width="${NEEDLE_WIDTH}" height="${256 - tipY}" rx="${NEEDLE_WIDTH / 2}" fill=${needleColor} stroke=${needleColor}/>
344
+ <circle cx="256" cy="256" r="${NEEDLE_HUB_RADIUS}" fill=${needleColor}/>
248
345
  </g>
249
346
  `;
250
347
  } else {
251
- return svg`<g transform="rotate(${this.getAngle(value)}) translate(-256, -256)">
348
+ return svg`<g transform="rotate(${this.mapAngle(value)}) translate(-256, -256)">
252
349
  <rect x="252" y="${96 - rOff}" width="8" height="48" rx="4" fill=${needleColor} stroke="var(--border-silhouette-color)"/>
253
350
  </g>
254
351
  `;
@@ -284,7 +381,7 @@ export class ObcInstrumentRadial extends SetpointMixin(LitElement) {
284
381
  }
285
382
 
286
383
  tickmarksByValue.set(normalizedValue, {
287
- angle: this.getAngle(normalizedValue),
384
+ angle: this.mapAngle(normalizedValue),
288
385
  type,
289
386
  text,
290
387
  });
@@ -370,8 +467,8 @@ export class ObcInstrumentRadial extends SetpointMixin(LitElement) {
370
467
  const value = this.clampedValue;
371
468
 
372
469
  return this.advices.map((advice) => {
373
- const minAngle = this.getAngle(advice.minValue);
374
- const maxAngle = this.getAngle(advice.maxValue);
470
+ const minAngle = this.mapAngle(advice.minValue);
471
+ const maxAngle = this.mapAngle(advice.maxValue);
375
472
  let state = advice.hinted ? AdviceState.hinted : AdviceState.regular;
376
473
  if (value >= advice.minValue && value <= advice.maxValue) {
377
474
  state = AdviceState.triggered;