{
  "name": "text-emphasis",
  "type": "registry:lib",
  "description": "Word-level phrase matching and marker geometry for scenes that sweep emphasis across text",
  "dependencies": [],
  "registryDependencies": [],
  "files": [
    {
      "path": "registry/bases/default/lib/text-emphasis.ts",
      "type": "registry:lib",
      "content": "/**\n * Word-level emphasis matching for scenes that sweep a marker across a phrase.\n *\n * Splitting to words — rather than wrapping the phrase in one span — keeps the\n * marker tracking the text once the line wraps, and lets each word be swept in\n * turn so the highlight reads as a stroke instead of a block appearing at once.\n */\n\nexport type EmphasisWord = {\n  /** The word as it appears in the source string. */\n  text: string;\n  /** True when the word falls inside the emphasised phrase. */\n  marked: boolean;\n  /** Index of this word among the marked ones, for staggering. Otherwise -1. */\n  order: number;\n};\n\n/**\n * Splits `text` into words, flagging the ones covered by `phrase`.\n * Matching is case-insensitive; an absent or unmatched phrase marks nothing.\n */\nexport function markEmphasis(text: string, phrase?: string): EmphasisWord[] {\n  const words = text.split(/\\s+/).filter(Boolean);\n  const plain = () =>\n    words.map((word) => ({ text: word, marked: false, order: -1 }));\n\n  if (!phrase || !phrase.trim()) return plain();\n\n  const needle = phrase.trim().toLowerCase();\n  const start = text.toLowerCase().indexOf(needle);\n  if (start < 0) return plain();\n\n  const end = start + needle.length;\n  let cursor = 0;\n  let order = 0;\n\n  return words.map((word) => {\n    const at = text.indexOf(word, cursor);\n    cursor = at + word.length;\n    const marked = at < end && cursor > start;\n    return { text: word, marked, order: marked ? order++ : -1 };\n  });\n}\n\n/**\n * Rounded corners and insets for a marker behind `words[index]`, bridging the\n * space to a neighbouring marked word so a phrase reads as one stroke.\n */\nexport function markerEdges(\n  words: EmphasisWord[],\n  index: number,\n  gap: string,\n  overhang = 5,\n  radius = 5,\n): {\n  left: number;\n  right: number | string;\n  borderTopLeftRadius: number;\n  borderBottomLeftRadius: number;\n  borderTopRightRadius: number;\n  borderBottomRightRadius: number;\n} {\n  const joinsLeft = index > 0 && words[index - 1].marked;\n  const joinsRight = Boolean(words[index + 1]?.marked);\n\n  return {\n    left: joinsLeft ? 0 : -overhang,\n    right: joinsRight ? `-${gap}` : -overhang,\n    borderTopLeftRadius: joinsLeft ? 0 : radius,\n    borderBottomLeftRadius: joinsLeft ? 0 : radius,\n    borderTopRightRadius: joinsRight ? 0 : radius,\n    borderBottomRightRadius: joinsRight ? 0 : radius,\n  };\n}\n"
    }
  ]
}