beads-map 0.3.5 → 0.3.6

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 (30) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-build-manifest.json +2 -2
  3. package/.next/app-path-routes-manifest.json +1 -1
  4. package/.next/build-manifest.json +2 -2
  5. package/.next/next-server.js.nft.json +1 -1
  6. package/.next/prerender-manifest.json +1 -1
  7. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  8. package/.next/server/app/_not-found.html +1 -1
  9. package/.next/server/app/_not-found.rsc +1 -1
  10. package/.next/server/app/api/beads.body +1 -1
  11. package/.next/server/app/index.html +1 -1
  12. package/.next/server/app/index.rsc +2 -2
  13. package/.next/server/app/page.js +3 -3
  14. package/.next/server/app/page_client-reference-manifest.js +1 -1
  15. package/.next/server/app-paths-manifest.json +2 -2
  16. package/.next/server/functions-config-manifest.json +1 -1
  17. package/.next/server/pages/404.html +1 -1
  18. package/.next/server/pages/500.html +1 -1
  19. package/.next/server/server-reference-manifest.json +1 -1
  20. package/.next/static/chunks/app/page-40b0a256ec8af9de.js +1 -0
  21. package/.next/static/css/454d96c40653b263.css +3 -0
  22. package/components/DescriptionModal.tsx +220 -27
  23. package/components/SettingsModal.tsx +6 -4
  24. package/lib/settings.ts +1 -1
  25. package/lib/tts.ts +274 -14
  26. package/package.json +1 -1
  27. package/.next/static/chunks/app/page-7a8908706a09b720.js +0 -1
  28. package/.next/static/css/a506e3d172da58ef.css +0 -3
  29. /package/.next/static/{e6v54SLUeGDtx1DXW7JjL → msGLXabX0J07WQq7roVpb}/_buildManifest.js +0 -0
  30. /package/.next/static/{e6v54SLUeGDtx1DXW7JjL → msGLXabX0J07WQq7roVpb}/_ssgManifest.js +0 -0
@@ -0,0 +1,3 @@
1
+ *,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }/*
2
+ ! tailwindcss v3.4.19 | MIT License | https://tailwindcss.com
3
+ */*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:Inter,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:JetBrains Mono,Fira Code,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}body,html{height:100%;overflow:hidden}body{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity,1));--tw-text-opacity:1;color:rgb(39 39 42/var(--tw-text-opacity,1));-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-feature-settings:"rlig" 1,"calt" 1}.\!container{width:100%!important}.container{width:100%}@media (min-width:640px){.\!container{max-width:640px!important}.container{max-width:640px}}@media (min-width:768px){.\!container{max-width:768px!important}.container{max-width:768px}}@media (min-width:1024px){.\!container{max-width:1024px!important}.container{max-width:1024px}}@media (min-width:1280px){.\!container{max-width:1280px!important}.container{max-width:1280px}}@media (min-width:1536px){.\!container{max-width:1536px!important}.container{max-width:1536px}}.status-badge{display:inline-flex;align-items:center;border-radius:9999px;padding:.125rem .5rem;font-size:.75rem;line-height:1rem;font-weight:500}.custom-scrollbar::-webkit-scrollbar{width:4px}.custom-scrollbar::-webkit-scrollbar-track{background-color:transparent}.custom-scrollbar::-webkit-scrollbar-thumb{border-radius:9999px;--tw-bg-opacity:1;background-color:rgb(228 228 231/var(--tw-bg-opacity,1))}.custom-scrollbar::-webkit-scrollbar-thumb:hover{--tw-bg-opacity:1;background-color:rgb(212 212 216/var(--tw-bg-opacity,1))}.visible{visibility:visible}.invisible{visibility:hidden}.collapse{visibility:collapse}.static{position:static}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{inset:0}.inset-x-0{left:0;right:0}.bottom-0{bottom:0}.bottom-2{bottom:.5rem}.bottom-4{bottom:1rem}.left-0{left:0}.left-2{left:.5rem}.left-3{left:.75rem}.left-4{left:1rem}.right-0{right:0}.right-2{right:.5rem}.right-3{right:.75rem}.top-0{top:0}.top-1\/2{top:50%}.top-2{top:.5rem}.top-3{top:.75rem}.top-full{top:100%}.isolate{isolation:isolate}.z-10{z-index:10}.z-20{z-index:20}.z-30{z-index:30}.z-50{z-index:50}.z-\[100\]{z-index:100}.z-\[110\]{z-index:110}.z-\[1\]{z-index:1}.z-\[55\]{z-index:55}.z-\[56\]{z-index:56}.z-\[60\]{z-index:60}.mx-2{margin-left:.5rem;margin-right:.5rem}.mx-4{margin-left:1rem;margin-right:1rem}.mx-auto{margin-left:auto;margin-right:auto}.my-1{margin-top:.25rem;margin-bottom:.25rem}.-mr-0\.5{margin-right:-.125rem}.mb-1{margin-bottom:.25rem}.mb-1\.5{margin-bottom:.375rem}.mb-2{margin-bottom:.5rem}.mb-2\.5{margin-bottom:.625rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-5{margin-bottom:1.25rem}.mb-6{margin-bottom:1.5rem}.ml-1{margin-left:.25rem}.ml-2{margin-left:.5rem}.ml-4{margin-left:1rem}.ml-auto{margin-left:auto}.mr-1\.5{margin-right:.375rem}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-1\.5{margin-top:.375rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-5{margin-top:1.25rem}.mt-\[6px\]{margin-top:6px}.line-clamp-2{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2}.block{display:block}.inline{display:inline}.flex{display:flex}.inline-flex{display:inline-flex}.grid{display:grid}.hidden{display:none}.h-1{height:.25rem}.h-1\.5{height:.375rem}.h-12{height:3rem}.h-14{height:3.5rem}.h-2{height:.5rem}.h-2\.5{height:.625rem}.h-3{height:.75rem}.h-3\.5{height:.875rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-7{height:1.75rem}.h-8{height:2rem}.h-full{height:100%}.h-px{height:1px}.h-screen{height:100vh}.max-h-40{max-height:10rem}.max-h-72{max-height:18rem}.max-h-\[260px\]{max-height:260px}.max-h-\[60vh\]{max-height:60vh}.max-h-\[70vh\]{max-height:70vh}.max-h-\[80vh\]{max-height:80vh}.min-h-\[300px\]{min-height:300px}.w-0\.5{width:.125rem}.w-1{width:.25rem}.w-1\.5{width:.375rem}.w-12{width:3rem}.w-14{width:3.5rem}.w-16{width:4rem}.w-2{width:.5rem}.w-2\.5{width:.625rem}.w-3{width:.75rem}.w-3\.5{width:.875rem}.w-4{width:1rem}.w-44{width:11rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-7{width:1.75rem}.w-72{width:18rem}.w-8{width:2rem}.w-\[360px\]{width:360px}.w-\[90vw\]{width:90vw}.w-full{width:100%}.w-px{width:1px}.min-w-0{min-width:0}.min-w-\[18px\]{min-width:18px}.min-w-\[280px\]{min-width:280px}.max-w-2xl{max-width:42rem}.max-w-\[100px\]{max-width:100px}.max-w-\[140px\]{max-width:140px}.max-w-\[180px\]{max-width:180px}.max-w-\[480px\]{max-width:480px}.max-w-\[80px\]{max-width:80px}.max-w-md{max-width:28rem}.max-w-sm{max-width:24rem}.flex-1{flex:1 1 0%}.flex-shrink-0,.shrink-0{flex-shrink:0}.-translate-y-1\/2{--tw-translate-y:-50%}.-translate-y-1\/2,.translate-x-0{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-x-0{--tw-translate-x:0px}.translate-x-full{--tw-translate-x:100%}.translate-x-full,.translate-y-0{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-y-0{--tw-translate-y:0px}.translate-y-full{--tw-translate-y:100%}.transform,.translate-y-full{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}@keyframes spin{to{transform:rotate(1turn)}}.animate-spin{animation:spin 1s linear infinite}.cursor-crosshair{cursor:crosshair}.cursor-e-resize{cursor:e-resize}.cursor-n-resize{cursor:n-resize}.cursor-ne-resize{cursor:ne-resize}.cursor-pointer{cursor:pointer}.cursor-wait{cursor:wait}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.resize-none{resize:none}.resize{resize:both}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-center{align-items:center}.items-baseline{align-items:baseline}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-0\.5{gap:.125rem}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-2\.5{gap:.625rem}.gap-3{gap:.75rem}.gap-x-3{-moz-column-gap:.75rem;column-gap:.75rem}.gap-x-4{-moz-column-gap:1rem;column-gap:1rem}.gap-y-1{row-gap:.25rem}.gap-y-1\.5{row-gap:.375rem}.space-y-0>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(0px * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(0px * var(--tw-space-y-reverse))}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem * var(--tw-space-y-reverse))}.space-y-1\.5>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.375rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.375rem * var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem * var(--tw-space-y-reverse))}.space-y-5>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.25rem * var(--tw-space-y-reverse))}.divide-y>:not([hidden])~:not([hidden]){--tw-divide-y-reverse:0;border-top-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)));border-bottom-width:calc(1px * var(--tw-divide-y-reverse))}.divide-zinc-50>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:rgb(250 250 250/var(--tw-divide-opacity,1))}.self-stretch{align-self:stretch}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis}.truncate,.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.break-words{overflow-wrap:break-word}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded-sm{border-radius:.125rem}.rounded-xl{border-radius:.75rem}.rounded-r-lg{border-top-right-radius:.5rem;border-bottom-right-radius:.5rem}.rounded-t-2xl{border-top-left-radius:1rem;border-top-right-radius:1rem}.rounded-t-lg{border-top-left-radius:.5rem}.rounded-t-lg,.rounded-tr-lg{border-top-right-radius:.5rem}.border{border-width:1px}.border-2{border-width:2px}.border-b{border-bottom-width:1px}.border-l{border-left-width:1px}.border-t{border-top-width:1px}.border-emerald-200{--tw-border-opacity:1;border-color:rgb(167 243 208/var(--tw-border-opacity,1))}.border-emerald-500{--tw-border-opacity:1;border-color:rgb(16 185 129/var(--tw-border-opacity,1))}.border-red-100{--tw-border-opacity:1;border-color:rgb(254 226 226/var(--tw-border-opacity,1))}.border-transparent{border-color:transparent}.border-zinc-100{--tw-border-opacity:1;border-color:rgb(244 244 245/var(--tw-border-opacity,1))}.border-zinc-100\/60{border-color:hsla(240,5%,96%,.6)}.border-zinc-100\/80{border-color:hsla(240,5%,96%,.8)}.border-zinc-200{--tw-border-opacity:1;border-color:rgb(228 228 231/var(--tw-border-opacity,1))}.border-zinc-200\/60{border-color:hsla(240,6%,90%,.6)}.border-zinc-200\/80{border-color:hsla(240,6%,90%,.8)}.border-zinc-50{--tw-border-opacity:1;border-color:rgb(250 250 250/var(--tw-border-opacity,1))}.border-t-emerald-500{--tw-border-opacity:1;border-top-color:rgb(16 185 129/var(--tw-border-opacity,1))}.border-t-zinc-400{--tw-border-opacity:1;border-top-color:rgb(161 161 170/var(--tw-border-opacity,1))}.bg-amber-400{--tw-bg-opacity:1;background-color:rgb(251 191 36/var(--tw-bg-opacity,1))}.bg-amber-500{--tw-bg-opacity:1;background-color:rgb(245 158 11/var(--tw-bg-opacity,1))}.bg-black\/20{background-color:rgba(0,0,0,.2)}.bg-black\/40{background-color:rgba(0,0,0,.4)}.bg-blue-400{--tw-bg-opacity:1;background-color:rgb(96 165 250/var(--tw-bg-opacity,1))}.bg-emerald-100{--tw-bg-opacity:1;background-color:rgb(209 250 229/var(--tw-bg-opacity,1))}.bg-emerald-400{--tw-bg-opacity:1;background-color:rgb(52 211 153/var(--tw-bg-opacity,1))}.bg-emerald-50{--tw-bg-opacity:1;background-color:rgb(236 253 245/var(--tw-bg-opacity,1))}.bg-emerald-50\/30{background-color:rgba(236,253,245,.3)}.bg-emerald-50\/90{background-color:rgba(236,253,245,.9)}.bg-emerald-500{--tw-bg-opacity:1;background-color:rgb(16 185 129/var(--tw-bg-opacity,1))}.bg-emerald-600{--tw-bg-opacity:1;background-color:rgb(5 150 105/var(--tw-bg-opacity,1))}.bg-red-300{--tw-bg-opacity:1;background-color:rgb(252 165 165/var(--tw-bg-opacity,1))}.bg-red-50{--tw-bg-opacity:1;background-color:rgb(254 242 242/var(--tw-bg-opacity,1))}.bg-red-500{--tw-bg-opacity:1;background-color:rgb(239 68 68/var(--tw-bg-opacity,1))}.bg-rose-300{--tw-bg-opacity:1;background-color:rgb(253 164 175/var(--tw-bg-opacity,1))}.bg-transparent{background-color:transparent}.bg-violet-500{--tw-bg-opacity:1;background-color:rgb(139 92 246/var(--tw-bg-opacity,1))}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity,1))}.bg-white\/90{background-color:hsla(0,0%,100%,.9)}.bg-white\/95{background-color:hsla(0,0%,100%,.95)}.bg-zinc-100{--tw-bg-opacity:1;background-color:rgb(244 244 245/var(--tw-bg-opacity,1))}.bg-zinc-200{--tw-bg-opacity:1;background-color:rgb(228 228 231/var(--tw-bg-opacity,1))}.bg-zinc-300{--tw-bg-opacity:1;background-color:rgb(212 212 216/var(--tw-bg-opacity,1))}.bg-zinc-400{--tw-bg-opacity:1;background-color:rgb(161 161 170/var(--tw-bg-opacity,1))}.bg-zinc-50{--tw-bg-opacity:1;background-color:rgb(250 250 250/var(--tw-bg-opacity,1))}.bg-zinc-50\/30{background-color:hsla(0,0%,98%,.3)}.bg-zinc-50\/50{background-color:hsla(0,0%,98%,.5)}.bg-zinc-50\/80{background-color:hsla(0,0%,98%,.8)}.bg-zinc-50\/95{background-color:hsla(0,0%,98%,.95)}.bg-zinc-800{--tw-bg-opacity:1;background-color:rgb(39 39 42/var(--tw-bg-opacity,1))}.object-cover{-o-object-fit:cover;object-fit:cover}.p-0\.5{padding:.125rem}.p-1{padding:.25rem}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-6{padding:1.5rem}.p-\[18px_20px\]{padding:18px 20px}.px-1{padding-left:.25rem;padding-right:.25rem}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-2\.5{padding-left:.625rem;padding-right:.625rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-3\.5{padding-left:.875rem;padding-right:.875rem}.px-4{padding-left:1rem;padding-right:1rem}.px-5{padding-left:1.25rem;padding-right:1.25rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-12{padding-top:3rem;padding-bottom:3rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-2\.5{padding-top:.625rem;padding-bottom:.625rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-3\.5{padding-top:.875rem;padding-bottom:.875rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-6{padding-top:1.5rem;padding-bottom:1.5rem}.py-8{padding-top:2rem;padding-bottom:2rem}.pb-1{padding-bottom:.25rem}.pb-2{padding-bottom:.5rem}.pb-3{padding-bottom:.75rem}.pb-6{padding-bottom:1.5rem}.pl-3{padding-left:.75rem}.pr-1{padding-right:.25rem}.pr-10{padding-right:2.5rem}.pr-3{padding-right:.75rem}.pt-3{padding-top:.75rem}.pt-4{padding-top:1rem}.pt-\[20vh\]{padding-top:20vh}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.font-mono{font-family:JetBrains Mono,Fira Code,monospace}.text-2xl{font-size:1.5rem;line-height:2rem}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.text-\[12px\]{font-size:12px}.text-\[13px\]{font-size:13px}.text-\[15px\]{font-size:15px}.text-\[7px\]{font-size:7px}.text-\[8px\]{font-size:8px}.text-\[9px\]{font-size:9px}.text-base{font-size:1rem;line-height:1.5rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-normal{font-weight:400}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.italic{font-style:italic}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure) var(--tw-numeric-spacing) var(--tw-numeric-fraction)}.leading-relaxed{line-height:1.625}.leading-snug{line-height:1.375}.leading-tight{line-height:1.25}.tracking-tight{letter-spacing:-.025em}.tracking-wider{letter-spacing:.05em}.tracking-widest{letter-spacing:.1em}.text-amber-500{--tw-text-opacity:1;color:rgb(245 158 11/var(--tw-text-opacity,1))}.text-blue-500{--tw-text-opacity:1;color:rgb(59 130 246/var(--tw-text-opacity,1))}.text-emerald-500{--tw-text-opacity:1;color:rgb(16 185 129/var(--tw-text-opacity,1))}.text-emerald-600{--tw-text-opacity:1;color:rgb(5 150 105/var(--tw-text-opacity,1))}.text-emerald-700{--tw-text-opacity:1;color:rgb(4 120 87/var(--tw-text-opacity,1))}.text-red-400{--tw-text-opacity:1;color:rgb(248 113 113/var(--tw-text-opacity,1))}.text-red-500{--tw-text-opacity:1;color:rgb(239 68 68/var(--tw-text-opacity,1))}.text-rose-400{--tw-text-opacity:1;color:rgb(251 113 133/var(--tw-text-opacity,1))}.text-rose-500{--tw-text-opacity:1;color:rgb(244 63 94/var(--tw-text-opacity,1))}.text-teal-500{--tw-text-opacity:1;color:rgb(20 184 166/var(--tw-text-opacity,1))}.text-teal-600{--tw-text-opacity:1;color:rgb(13 148 136/var(--tw-text-opacity,1))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.text-zinc-200{--tw-text-opacity:1;color:rgb(228 228 231/var(--tw-text-opacity,1))}.text-zinc-300{--tw-text-opacity:1;color:rgb(212 212 216/var(--tw-text-opacity,1))}.text-zinc-400{--tw-text-opacity:1;color:rgb(161 161 170/var(--tw-text-opacity,1))}.text-zinc-500{--tw-text-opacity:1;color:rgb(113 113 122/var(--tw-text-opacity,1))}.text-zinc-600{--tw-text-opacity:1;color:rgb(82 82 91/var(--tw-text-opacity,1))}.text-zinc-700{--tw-text-opacity:1;color:rgb(63 63 70/var(--tw-text-opacity,1))}.text-zinc-800{--tw-text-opacity:1;color:rgb(39 39 42/var(--tw-text-opacity,1))}.text-zinc-900{--tw-text-opacity:1;color:rgb(24 24 27/var(--tw-text-opacity,1))}.underline{text-decoration-line:underline}.underline-offset-2{text-underline-offset:2px}.placeholder-zinc-400::-moz-placeholder{--tw-placeholder-opacity:1;color:rgb(161 161 170/var(--tw-placeholder-opacity,1))}.placeholder-zinc-400::placeholder{--tw-placeholder-opacity:1;color:rgb(161 161 170/var(--tw-placeholder-opacity,1))}.opacity-0{opacity:0}.opacity-25{opacity:.25}.opacity-50{opacity:.5}.opacity-75{opacity:.75}.opacity-\[0\.03\]{opacity:.03}.shadow{--tw-shadow:0 1px 3px 0 rgba(0,0,0,.1),0 1px 2px -1px rgba(0,0,0,.1);--tw-shadow-colored:0 1px 3px 0 var(--tw-shadow-color),0 1px 2px -1px var(--tw-shadow-color)}.shadow,.shadow-2xl{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-2xl{--tw-shadow:0 25px 50px -12px rgba(0,0,0,.25);--tw-shadow-colored:0 25px 50px -12px var(--tw-shadow-color)}.shadow-lg{--tw-shadow:0 10px 15px -3px rgba(0,0,0,.1),0 4px 6px -4px rgba(0,0,0,.1);--tw-shadow-colored:0 10px 15px -3px var(--tw-shadow-color),0 4px 6px -4px var(--tw-shadow-color)}.shadow-lg,.shadow-sm{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 2px 0 rgba(0,0,0,.05);--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color)}.shadow-xl{--tw-shadow:0 20px 25px -5px rgba(0,0,0,.1),0 8px 10px -6px rgba(0,0,0,.1);--tw-shadow-colored:0 20px 25px -5px var(--tw-shadow-color),0 8px 10px -6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.outline-none{outline:2px solid transparent;outline-offset:2px}.ring{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color)}.ring,.ring-1{box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.ring-1{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color)}.ring-2{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.ring-emerald-400\/60{--tw-ring-color:rgba(52,211,153,.6)}.ring-zinc-100{--tw-ring-opacity:1;--tw-ring-color:rgb(244 244 245/var(--tw-ring-opacity,1))}.blur{--tw-blur:blur(8px)}.blur,.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.backdrop-blur-sm{--tw-backdrop-blur:blur(4px);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\[right\]{transition-property:right;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-opacity{transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-300{transition-duration:.3s}.ease-out{transition-timing-function:cubic-bezier(0,0,.2,1)}@keyframes fadeIn{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}.animate-fade-in{animation:fadeIn .3s ease-out forwards}@keyframes pulseSoft{0%,to{opacity:1}50%{opacity:.6}}.animate-pulse-soft{animation:pulseSoft 2s ease-in-out infinite}@keyframes beadTooltipFade{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}.description-markdown h1,.description-markdown h2,.description-markdown h3,.description-markdown h4{margin-top:.5rem;margin-bottom:.25rem;font-weight:600;--tw-text-opacity:1;color:rgb(63 63 70/var(--tw-text-opacity,1))}.description-markdown h1{font-size:.85rem}.description-markdown h2{font-size:.8rem}.description-markdown h3{font-size:.75rem}.description-markdown h4{font-size:.7rem}.description-markdown p{margin-bottom:.375rem}.description-markdown p:last-child{margin-bottom:0}.description-markdown ol,.description-markdown ul{margin-bottom:.375rem}.description-markdown ol>:not([hidden])~:not([hidden]),.description-markdown ul>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.125rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.125rem * var(--tw-space-y-reverse))}.description-markdown ol,.description-markdown ul{padding-left:1rem}.description-markdown ul{list-style-type:disc}.description-markdown ol{list-style-type:decimal}.description-markdown code{border-radius:.25rem;background-color:hsla(240,6%,90%,.6);padding:.125rem .25rem;font-family:JetBrains Mono,Fira Code,monospace;font-size:10px;--tw-text-opacity:1;color:rgb(63 63 70/var(--tw-text-opacity,1))}.description-markdown pre{margin-bottom:.375rem;overflow-x:auto;border-radius:.375rem;background-color:hsla(240,6%,90%,.6);padding:.5rem}.description-markdown pre code{background-color:transparent;padding:0;font-size:10px}.description-markdown a{--tw-text-opacity:1;color:rgb(5 150 105/var(--tw-text-opacity,1));text-decoration-line:underline;text-underline-offset:2px}.description-markdown a:hover{--tw-text-opacity:1;color:rgb(4 120 87/var(--tw-text-opacity,1))}.description-markdown blockquote{margin-top:.375rem;margin-bottom:.375rem;border-left-width:2px;--tw-border-opacity:1;border-color:rgb(212 212 216/var(--tw-border-opacity,1));padding-left:.5rem;font-style:italic;--tw-text-opacity:1;color:rgb(113 113 122/var(--tw-text-opacity,1))}.description-markdown table{margin-bottom:.375rem;width:100%;border-collapse:collapse;font-size:10px}.description-markdown th{border-bottom-width:1px;--tw-border-opacity:1;border-color:rgb(228 228 231/var(--tw-border-opacity,1));padding-bottom:.125rem;padding-right:.5rem;text-align:left;font-weight:600;--tw-text-opacity:1;color:rgb(82 82 91/var(--tw-text-opacity,1))}.description-markdown td{border-bottom-width:1px;--tw-border-opacity:1;border-color:rgb(244 244 245/var(--tw-border-opacity,1));padding-top:.125rem;padding-bottom:.125rem;padding-right:.5rem}.description-markdown hr{margin-top:.5rem;margin-bottom:.5rem;--tw-border-opacity:1;border-color:rgb(228 228 231/var(--tw-border-opacity,1))}.description-markdown strong{font-weight:600;--tw-text-opacity:1;color:rgb(63 63 70/var(--tw-text-opacity,1))}.description-markdown img{max-width:100%;border-radius:.25rem}.timeline-slider{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:transparent;cursor:pointer}.timeline-slider::-webkit-slider-runnable-track{height:4px;background:#e4e4e7;border-radius:2px}.timeline-slider::-webkit-slider-thumb{-webkit-appearance:none;width:12px;height:12px;background:#10b981;border-radius:50%;margin-top:-4px;cursor:pointer;-webkit-transition:transform .1s ease;transition:transform .1s ease}.timeline-slider::-webkit-slider-thumb:hover{transform:scale(1.2)}.timeline-slider::-moz-range-track{height:4px;background:#e4e4e7;border-radius:2px;border:none}.timeline-slider::-moz-range-thumb{width:12px;height:12px;background:#10b981;border-radius:50%;border:none;cursor:pointer}.force-graph-container canvas{border-radius:.5rem;touch-action:none}.placeholder\:text-zinc-300::-moz-placeholder{--tw-text-opacity:1;color:rgb(212 212 216/var(--tw-text-opacity,1))}.placeholder\:text-zinc-300::placeholder{--tw-text-opacity:1;color:rgb(212 212 216/var(--tw-text-opacity,1))}.placeholder\:text-zinc-400::-moz-placeholder{--tw-text-opacity:1;color:rgb(161 161 170/var(--tw-text-opacity,1))}.placeholder\:text-zinc-400::placeholder{--tw-text-opacity:1;color:rgb(161 161 170/var(--tw-text-opacity,1))}.first\:mt-0:first-child{margin-top:0}.hover\:border-zinc-200:hover{--tw-border-opacity:1;border-color:rgb(228 228 231/var(--tw-border-opacity,1))}.hover\:border-zinc-300:hover{--tw-border-opacity:1;border-color:rgb(212 212 216/var(--tw-border-opacity,1))}.hover\:bg-amber-50:hover{--tw-bg-opacity:1;background-color:rgb(255 251 235/var(--tw-bg-opacity,1))}.hover\:bg-emerald-100:hover{--tw-bg-opacity:1;background-color:rgb(209 250 229/var(--tw-bg-opacity,1))}.hover\:bg-emerald-200\/50:hover{background-color:rgba(167,243,208,.5)}.hover\:bg-emerald-50:hover{--tw-bg-opacity:1;background-color:rgb(236 253 245/var(--tw-bg-opacity,1))}.hover\:bg-emerald-600:hover{--tw-bg-opacity:1;background-color:rgb(5 150 105/var(--tw-bg-opacity,1))}.hover\:bg-emerald-700:hover{--tw-bg-opacity:1;background-color:rgb(4 120 87/var(--tw-bg-opacity,1))}.hover\:bg-red-50:hover{--tw-bg-opacity:1;background-color:rgb(254 242 242/var(--tw-bg-opacity,1))}.hover\:bg-white:hover{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity,1))}.hover\:bg-zinc-100:hover{--tw-bg-opacity:1;background-color:rgb(244 244 245/var(--tw-bg-opacity,1))}.hover\:bg-zinc-100\/50:hover{background-color:hsla(240,5%,96%,.5)}.hover\:bg-zinc-200\/60:hover{background-color:hsla(240,6%,90%,.6)}.hover\:bg-zinc-300\/40:hover{background-color:hsla(240,5%,84%,.4)}.hover\:bg-zinc-50:hover{--tw-bg-opacity:1;background-color:rgb(250 250 250/var(--tw-bg-opacity,1))}.hover\:bg-zinc-50\/50:hover{background-color:hsla(0,0%,98%,.5)}.hover\:bg-zinc-50\/60:hover{background-color:hsla(0,0%,98%,.6)}.hover\:text-amber-500:hover{--tw-text-opacity:1;color:rgb(245 158 11/var(--tw-text-opacity,1))}.hover\:text-emerald-300:hover{--tw-text-opacity:1;color:rgb(110 231 183/var(--tw-text-opacity,1))}.hover\:text-emerald-500:hover{--tw-text-opacity:1;color:rgb(16 185 129/var(--tw-text-opacity,1))}.hover\:text-emerald-600:hover{--tw-text-opacity:1;color:rgb(5 150 105/var(--tw-text-opacity,1))}.hover\:text-emerald-700:hover{--tw-text-opacity:1;color:rgb(4 120 87/var(--tw-text-opacity,1))}.hover\:text-red-400:hover{--tw-text-opacity:1;color:rgb(248 113 113/var(--tw-text-opacity,1))}.hover\:text-red-500:hover{--tw-text-opacity:1;color:rgb(239 68 68/var(--tw-text-opacity,1))}.hover\:text-red-600:hover{--tw-text-opacity:1;color:rgb(220 38 38/var(--tw-text-opacity,1))}.hover\:text-rose-500:hover{--tw-text-opacity:1;color:rgb(244 63 94/var(--tw-text-opacity,1))}.hover\:text-teal-600:hover{--tw-text-opacity:1;color:rgb(13 148 136/var(--tw-text-opacity,1))}.hover\:text-zinc-500:hover{--tw-text-opacity:1;color:rgb(113 113 122/var(--tw-text-opacity,1))}.hover\:text-zinc-600:hover{--tw-text-opacity:1;color:rgb(82 82 91/var(--tw-text-opacity,1))}.hover\:text-zinc-700:hover{--tw-text-opacity:1;color:rgb(63 63 70/var(--tw-text-opacity,1))}.hover\:text-zinc-900:hover{--tw-text-opacity:1;color:rgb(24 24 27/var(--tw-text-opacity,1))}.hover\:opacity-80:hover{opacity:.8}.hover\:shadow-md:hover{--tw-shadow:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -2px rgba(0,0,0,.1);--tw-shadow-colored:0 4px 6px -1px var(--tw-shadow-color),0 2px 4px -2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.focus\:border-emerald-400:focus{--tw-border-opacity:1;border-color:rgb(52 211 153/var(--tw-border-opacity,1))}.focus\:border-emerald-500:focus{--tw-border-opacity:1;border-color:rgb(16 185 129/var(--tw-border-opacity,1))}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-1:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color)}.focus\:ring-1:focus,.focus\:ring-2:focus{box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.focus\:ring-2:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color)}.focus\:ring-emerald-500:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(16 185 129/var(--tw-ring-opacity,1))}.focus\:ring-emerald-500\/30:focus{--tw-ring-color:rgba(16,185,129,.3)}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-40:disabled{opacity:.4}.disabled\:opacity-50:disabled{opacity:.5}.group:hover .group-hover\:scale-105{--tw-scale-x:1.05;--tw-scale-y:1.05;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.group:hover .group-hover\:text-emerald-500{--tw-text-opacity:1;color:rgb(16 185 129/var(--tw-text-opacity,1))}.group:hover .group-hover\:text-emerald-700{--tw-text-opacity:1;color:rgb(4 120 87/var(--tw-text-opacity,1))}.group:hover .group-hover\:text-zinc-700{--tw-text-opacity:1;color:rgb(63 63 70/var(--tw-text-opacity,1))}.group:hover .group-hover\:opacity-100{opacity:1}@media (min-width:640px){.sm\:left-4{left:1rem}.sm\:right-4{right:1rem}.sm\:top-4{top:1rem}.sm\:block{display:block}.sm\:inline-block{display:inline-block}.sm\:inline{display:inline}.sm\:flex{display:flex}.sm\:hidden{display:none}.sm\:gap-2{gap:.5rem}}@media (min-width:768px){.md\:flex{display:flex}.md\:hidden{display:none}}
@@ -8,7 +8,10 @@ import type { GraphNode } from "@/lib/types";
8
8
  import { buildDescriptionCopyText } from "@/lib/utils";
9
9
  import {
10
10
  speakWithElevenLabs,
11
+ speakSelection,
11
12
  stopTts,
13
+ pauseTts,
14
+ resumeTts,
12
15
  stripMarkdown,
13
16
  setTtsPlaybackRate,
14
17
  type TtsState,
@@ -46,6 +49,17 @@ export function DescriptionModal({
46
49
  const speedBtnRef = useRef<HTMLButtonElement>(null);
47
50
  const speedMenuRef = useRef<HTMLDivElement>(null);
48
51
 
52
+ // Selection tooltip state
53
+ const [selectionTooltip, setSelectionTooltip] = useState<{
54
+ text: string;
55
+ x: number;
56
+ y: number;
57
+ } | null>(null);
58
+ const selectionTooltipRef = useRef<HTMLDivElement>(null);
59
+ // Guard: when true, selectionchange listener won't clear the tooltip
60
+ // (prevents race where clicking the tooltip clears browser selection before handler runs)
61
+ const ttsStartingRef = useRef(false);
62
+
49
63
  const handleCopy = () => {
50
64
  if (!node.description) return;
51
65
  navigator.clipboard
@@ -56,6 +70,13 @@ export function DescriptionModal({
56
70
  });
57
71
  };
58
72
 
73
+ // --- TTS handlers -------------------------------------------------------
74
+
75
+ const ttsStateChange = useCallback((state: TtsState, error?: string) => {
76
+ setTtsState(state);
77
+ if (error) setTtsError(error);
78
+ }, []);
79
+
59
80
  const handleTts = useCallback(() => {
60
81
  if (!hasApiKey()) {
61
82
  onOpenSettings?.();
@@ -64,11 +85,8 @@ export function DescriptionModal({
64
85
  const plainText = stripMarkdown(node.description || "");
65
86
  if (!plainText) return;
66
87
  setTtsError(null);
67
- speakWithElevenLabs(plainText, (state, error) => {
68
- setTtsState(state);
69
- if (error) setTtsError(error);
70
- });
71
- }, [node.description, onOpenSettings]);
88
+ speakWithElevenLabs(plainText, ttsStateChange);
89
+ }, [node.description, onOpenSettings, ttsStateChange]);
72
90
 
73
91
  const handleStopTts = useCallback(() => {
74
92
  stopTts();
@@ -77,6 +95,16 @@ export function DescriptionModal({
77
95
  setShowCustomInput(false);
78
96
  }, []);
79
97
 
98
+ const handlePauseTts = useCallback(() => {
99
+ pauseTts();
100
+ setTtsState("paused");
101
+ }, []);
102
+
103
+ const handleResumeTts = useCallback(() => {
104
+ resumeTts();
105
+ setTtsState("playing");
106
+ }, []);
107
+
80
108
  const handleSpeedChange = useCallback((speed: number) => {
81
109
  const clamped = Math.max(0.25, Math.min(4, speed));
82
110
  setTtsSpeedState(clamped);
@@ -111,11 +139,70 @@ export function DescriptionModal({
111
139
  };
112
140
  }, [speedMenuOpen]);
113
141
 
142
+ // --- Selection tooltip handlers -----------------------------------------
143
+
144
+ const handleSelectionMouseUp = useCallback(() => {
145
+ // Small delay to let the browser finalize the selection
146
+ setTimeout(() => {
147
+ const sel = window.getSelection();
148
+ if (!sel || sel.isCollapsed) return;
149
+ const selectedText = sel.toString().trim();
150
+ if (selectedText.length < 3) return;
151
+ try {
152
+ const range = sel.getRangeAt(0);
153
+ const rect = range.getBoundingClientRect();
154
+ setSelectionTooltip({
155
+ text: selectedText,
156
+ x: rect.left + rect.width / 2,
157
+ y: rect.top - 8,
158
+ });
159
+ } catch {
160
+ // getRangeAt can throw if selection is weird
161
+ }
162
+ }, 10);
163
+ }, []);
164
+
165
+ // Clear tooltip when selection is lost (guarded during TTS initiation)
166
+ useEffect(() => {
167
+ const handler = () => {
168
+ if (ttsStartingRef.current) return;
169
+ const sel = window.getSelection();
170
+ if (!sel || sel.isCollapsed || !sel.toString().trim()) {
171
+ setSelectionTooltip(null);
172
+ }
173
+ };
174
+ document.addEventListener("selectionchange", handler);
175
+ return () => document.removeEventListener("selectionchange", handler);
176
+ }, []);
177
+
178
+ const handleSelectionTts = useCallback(() => {
179
+ if (!selectionTooltip) return;
180
+ const text = selectionTooltip.text;
181
+ if (!text.trim()) return;
182
+
183
+ if (!hasApiKey()) {
184
+ onOpenSettings?.();
185
+ setSelectionTooltip(null);
186
+ return;
187
+ }
188
+
189
+ ttsStartingRef.current = true; // Guard against selectionchange race
190
+ setTtsError(null);
191
+ setSelectionTooltip(null);
192
+
193
+ // speakSelection() checks cache first — zero API call if full text was played
194
+ speakSelection(text, ttsStateChange);
195
+
196
+ // Release guard after a tick (React state updates are batched)
197
+ setTimeout(() => { ttsStartingRef.current = false; }, 100);
198
+ }, [selectionTooltip, onOpenSettings, ttsStateChange]);
199
+
114
200
  // Stop TTS on unmount / modal close
115
201
  useEffect(() => {
116
202
  return () => {
117
203
  stopTts();
118
204
  setSpeedMenuOpen(false);
205
+ setSelectionTooltip(null);
119
206
  };
120
207
  }, []);
121
208
 
@@ -178,7 +265,7 @@ export function DescriptionModal({
178
265
  )}
179
266
  </button>
180
267
 
181
- {/* TTS button */}
268
+ {/* TTS buttons — 4 states: idle, loading, playing, paused */}
182
269
  {ttsState === "loading" ? (
183
270
  <button
184
271
  disabled
@@ -206,26 +293,93 @@ export function DescriptionModal({
206
293
  </svg>
207
294
  </button>
208
295
  ) : ttsState === "playing" ? (
209
- <button
210
- onClick={handleStopTts}
211
- className="p-1 text-emerald-500 hover:text-red-500 hover:bg-red-50 rounded transition-colors"
212
- title="Stop reading"
213
- >
214
- <svg
215
- className="w-4 h-4"
216
- fill="none"
217
- viewBox="0 0 24 24"
218
- strokeWidth={1.5}
219
- stroke="currentColor"
296
+ <>
297
+ {/* Pause button */}
298
+ <button
299
+ onClick={handlePauseTts}
300
+ className="p-1 text-emerald-500 hover:text-amber-500 hover:bg-amber-50 rounded transition-colors"
301
+ title="Pause"
220
302
  >
221
- <path
222
- strokeLinecap="round"
223
- strokeLinejoin="round"
224
- d="M5.25 7.5A2.25 2.25 0 017.5 5.25h9a2.25 2.25 0 012.25 2.25v9a2.25 2.25 0 01-2.25 2.25h-9a2.25 2.25 0 01-2.25-2.25v-9z"
225
- />
226
- </svg>
227
- </button>
303
+ <svg
304
+ className="w-4 h-4"
305
+ fill="none"
306
+ viewBox="0 0 24 24"
307
+ strokeWidth={1.5}
308
+ stroke="currentColor"
309
+ >
310
+ <path
311
+ strokeLinecap="round"
312
+ strokeLinejoin="round"
313
+ d="M15.75 5.25v13.5m-7.5-13.5v13.5"
314
+ />
315
+ </svg>
316
+ </button>
317
+ {/* Stop button */}
318
+ <button
319
+ onClick={handleStopTts}
320
+ className="p-1 text-zinc-400 hover:text-red-500 hover:bg-red-50 rounded transition-colors"
321
+ title="Stop"
322
+ >
323
+ <svg
324
+ className="w-4 h-4"
325
+ fill="none"
326
+ viewBox="0 0 24 24"
327
+ strokeWidth={1.5}
328
+ stroke="currentColor"
329
+ >
330
+ <path
331
+ strokeLinecap="round"
332
+ strokeLinejoin="round"
333
+ d="M5.25 7.5A2.25 2.25 0 017.5 5.25h9a2.25 2.25 0 012.25 2.25v9a2.25 2.25 0 01-2.25 2.25h-9a2.25 2.25 0 01-2.25-2.25v-9z"
334
+ />
335
+ </svg>
336
+ </button>
337
+ </>
338
+ ) : ttsState === "paused" ? (
339
+ <>
340
+ {/* Resume button */}
341
+ <button
342
+ onClick={handleResumeTts}
343
+ className="p-1 text-amber-500 hover:text-emerald-500 hover:bg-emerald-50 rounded transition-colors"
344
+ title="Resume"
345
+ >
346
+ <svg
347
+ className="w-4 h-4"
348
+ fill="none"
349
+ viewBox="0 0 24 24"
350
+ strokeWidth={1.5}
351
+ stroke="currentColor"
352
+ >
353
+ <path
354
+ strokeLinecap="round"
355
+ strokeLinejoin="round"
356
+ d="M5.25 5.653c0-.856.917-1.398 1.667-.986l11.54 6.347a1.125 1.125 0 010 1.972l-11.54 6.347a1.125 1.125 0 01-1.667-.986V5.653z"
357
+ />
358
+ </svg>
359
+ </button>
360
+ {/* Stop button */}
361
+ <button
362
+ onClick={handleStopTts}
363
+ className="p-1 text-zinc-400 hover:text-red-500 hover:bg-red-50 rounded transition-colors"
364
+ title="Stop"
365
+ >
366
+ <svg
367
+ className="w-4 h-4"
368
+ fill="none"
369
+ viewBox="0 0 24 24"
370
+ strokeWidth={1.5}
371
+ stroke="currentColor"
372
+ >
373
+ <path
374
+ strokeLinecap="round"
375
+ strokeLinejoin="round"
376
+ d="M5.25 7.5A2.25 2.25 0 017.5 5.25h9a2.25 2.25 0 012.25 2.25v9a2.25 2.25 0 01-2.25 2.25h-9a2.25 2.25 0 01-2.25-2.25v-9z"
377
+ />
378
+ </svg>
379
+ </button>
380
+ </>
228
381
  ) : (
382
+ /* Idle — play/speaker button */
229
383
  <button
230
384
  onClick={handleTts}
231
385
  className="p-1 text-zinc-400 hover:text-zinc-600 transition-colors"
@@ -247,8 +401,8 @@ export function DescriptionModal({
247
401
  </button>
248
402
  )}
249
403
 
250
- {/* Speed selector — visible during playback/loading */}
251
- {(ttsState === "playing" || ttsState === "loading") && (
404
+ {/* Speed selector — visible during playback/loading/paused */}
405
+ {(ttsState === "playing" || ttsState === "loading" || ttsState === "paused") && (
252
406
  <div className="relative">
253
407
  <button
254
408
  ref={speedBtnRef}
@@ -385,12 +539,51 @@ export function DescriptionModal({
385
539
  )}
386
540
 
387
541
  {/* Modal body */}
388
- <div className="flex-1 overflow-y-auto px-5 py-4 custom-scrollbar description-markdown text-sm text-zinc-700 leading-relaxed">
542
+ <div
543
+ className="flex-1 overflow-y-auto px-5 py-4 custom-scrollbar description-markdown text-sm text-zinc-700 leading-relaxed"
544
+ onMouseUp={handleSelectionMouseUp}
545
+ >
389
546
  <ReactMarkdown remarkPlugins={[remarkGfm]}>
390
547
  {node.description}
391
548
  </ReactMarkdown>
392
549
  </div>
393
550
  </div>
551
+
552
+ {/* Selection TTS tooltip — outside modal card, inside portal backdrop */}
553
+ {selectionTooltip && (
554
+ <div
555
+ ref={selectionTooltipRef}
556
+ className="fixed z-[110] flex items-center gap-1.5 px-2.5 py-1.5 bg-zinc-800 text-white text-xs rounded-lg shadow-lg select-none"
557
+ style={{
558
+ left: selectionTooltip.x,
559
+ top: selectionTooltip.y,
560
+ transform: "translate(-50%, -100%)",
561
+ pointerEvents: "auto",
562
+ }}
563
+ onMouseDown={(e) => e.stopPropagation()}
564
+ onClick={(e) => e.stopPropagation()}
565
+ >
566
+ <button
567
+ onClick={handleSelectionTts}
568
+ className="flex items-center gap-1 hover:text-emerald-300 transition-colors"
569
+ >
570
+ <svg
571
+ className="w-3.5 h-3.5"
572
+ fill="none"
573
+ viewBox="0 0 24 24"
574
+ strokeWidth={1.5}
575
+ stroke="currentColor"
576
+ >
577
+ <path
578
+ strokeLinecap="round"
579
+ strokeLinejoin="round"
580
+ d="M19.114 5.636a9 9 0 010 12.728M16.463 8.288a5.25 5.25 0 010 7.424M6.75 8.25l4.72-4.72a.75.75 0 011.28.53v15.88a.75.75 0 01-1.28.53l-4.72-4.72H4.51c-.88 0-1.704-.507-1.938-1.354A9.009 9.009 0 012.25 12c0-.83.112-1.633.322-2.396C2.806 8.756 3.63 8.25 4.51 8.25H6.75z"
581
+ />
582
+ </svg>
583
+ <span>Read aloud</span>
584
+ </button>
585
+ </div>
586
+ )}
394
587
  </div>,
395
588
  document.body
396
589
  );
@@ -50,7 +50,7 @@ export function SettingsModal({ isOpen, onClose }: SettingsModalProps) {
50
50
  const updates: Partial<BeadsMapSettings> = {
51
51
  elevenLabsApiKey: apiKey.trim() || undefined,
52
52
  elevenLabsVoiceId: voiceId.trim() || "UgBBYS2sOqTuMpoF3BR0",
53
- elevenLabsModel: model || "eleven_multilingual_v2",
53
+ elevenLabsModel: model || "eleven_turbo_v2_5",
54
54
  };
55
55
  saveSettings(updates);
56
56
  onClose();
@@ -202,13 +202,15 @@ export function SettingsModal({ isOpen, onClose }: SettingsModalProps) {
202
202
  onChange={(e) => setModel(e.target.value)}
203
203
  className="w-full rounded-lg border border-zinc-200 bg-zinc-50 px-3 py-2 text-sm text-zinc-900 focus:outline-none focus:ring-2 focus:ring-emerald-500/30 focus:border-emerald-500"
204
204
  >
205
- <option value="eleven_multilingual_v2">
206
- Multilingual v2 (recommended)
205
+ <option value="eleven_turbo_v2_5">
206
+ Turbo v2.5 (default, fast &amp; cheap)
207
207
  </option>
208
- <option value="eleven_turbo_v2_5">Turbo v2.5 (faster)</option>
209
208
  <option value="eleven_flash_v2_5">
210
209
  Flash v2.5 (fastest, cheapest)
211
210
  </option>
211
+ <option value="eleven_multilingual_v2">
212
+ Multilingual v2 (highest quality)
213
+ </option>
212
214
  </select>
213
215
  </div>
214
216
  </div>
package/lib/settings.ts CHANGED
@@ -12,7 +12,7 @@ const STORAGE_KEY = "beads-map-settings";
12
12
 
13
13
  const DEFAULTS: BeadsMapSettings = {
14
14
  elevenLabsVoiceId: "UgBBYS2sOqTuMpoF3BR0", // Mark - Natural Conversations
15
- elevenLabsModel: "eleven_multilingual_v2",
15
+ elevenLabsModel: "eleven_turbo_v2_5",
16
16
  };
17
17
 
18
18
  /** Read settings from localStorage, merged with defaults */