Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 | 'use client' /** * Renders a single Euclid entity span with per-type styling. * * - Geometric entities (seg, tri, ang, pt): blue, dotted underline → canvas glow on hover * - Definitions: red → CitationPopover on hover * - Postulates: gold → CitationPopover on hover * - Common Notions: blue → CitationPopover on hover * - Propositions: dark → CitationPopover on hover */ import { useRef, useState, useCallback } from 'react' import { BYRNE } from '../types' import type { EuclidEntityRef, FoundationEntityRef } from './parseGeometricEntities' import { isGeometricEntity, isFoundationEntity, foundationToCitationKey, } from './parseGeometricEntities' /** Color for each entity type, using the Byrne palette. */ function entityColor(entity: EuclidEntityRef): string { if (isGeometricEntity(entity)) return BYRNE.blue switch (entity.type) { case 'definition': return BYRNE.red case 'postulate': return BYRNE.yellow case 'commonNotion': return BYRNE.blue case 'proposition': return BYRNE.given } } /** Underline opacity — geometric gets dotted, foundations get solid but subtle */ function entityUnderline(entity: EuclidEntityRef): string { const color = entityColor(entity) if (isGeometricEntity(entity)) return `1px dotted ${color}66` return `1px solid ${color}55` } interface EuclidEntitySpanProps { entity: EuclidEntityRef displayText: string onHighlightGeometric: (entity: EuclidEntityRef | null) => void onHighlightFoundation: (entity: FoundationEntityRef, anchorRect: DOMRect) => void onUnhighlightFoundation: () => void /** When true, inherit parent styling but still trigger glow/popover on hover */ subtle?: boolean } export function EuclidEntitySpan({ entity, displayText, onHighlightGeometric, onHighlightFoundation, onUnhighlightFoundation, subtle, }: EuclidEntitySpanProps) { const spanRef = useRef<HTMLSpanElement>(null) const [hovered, setHovered] = useState(false) const handleMouseEnter = useCallback(() => { setHovered(true) if (isGeometricEntity(entity)) { onHighlightGeometric(entity) } else if (isFoundationEntity(entity)) { const rect = spanRef.current?.getBoundingClientRect() if (rect) onHighlightFoundation(entity, rect) } }, [entity, onHighlightGeometric, onHighlightFoundation]) const handleMouseLeave = useCallback(() => { setHovered(false) if (isGeometricEntity(entity)) { onHighlightGeometric(null) } else if (isFoundationEntity(entity)) { onUnhighlightFoundation() } }, [entity, onHighlightGeometric, onUnhighlightFoundation]) const handlePointerDown = useCallback( (e: React.PointerEvent) => { // Mobile: tap foundation entities to show popover if (!isFoundationEntity(entity)) return e.stopPropagation() const rect = spanRef.current?.getBoundingClientRect() if (rect) onHighlightFoundation(entity, rect) }, [entity, onHighlightFoundation] ) const color = entityColor(entity) const style: React.CSSProperties = subtle ? { color: hovered ? color : 'inherit', fontWeight: 'inherit', cursor: 'pointer', borderBottom: 'none', transition: 'color 0.15s ease', } : { color, fontWeight: 600, cursor: 'pointer', borderBottom: entityUnderline(entity), } return ( <span ref={spanRef} data-element="entity-ref" data-entity-type={entity.type} data-citation-key={isFoundationEntity(entity) ? foundationToCitationKey(entity) : undefined} style={style} onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave} onPointerDown={handlePointerDown} > {displayText} </span> ) } |