heartbeads 0.6.2 → 0.6.4

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 (64) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/build-manifest.json +2 -2
  3. package/.next/required-server-files.json +1 -1
  4. package/.next/server/app/_global-error.html +2 -2
  5. package/.next/server/app/_global-error.rsc +1 -1
  6. package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  7. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  8. package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  9. package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  10. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  11. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  12. package/.next/server/app/_not-found.html +1 -1
  13. package/.next/server/app/_not-found.rsc +2 -2
  14. package/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  15. package/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  16. package/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
  17. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  18. package/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  19. package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  20. package/.next/server/app/api/docs/page_client-reference-manifest.js +1 -1
  21. package/.next/server/app/api/docs.html +2 -2
  22. package/.next/server/app/api/docs.rsc +2 -2
  23. package/.next/server/app/api/docs.segments/_full.segment.rsc +2 -2
  24. package/.next/server/app/api/docs.segments/_head.segment.rsc +1 -1
  25. package/.next/server/app/api/docs.segments/_index.segment.rsc +2 -2
  26. package/.next/server/app/api/docs.segments/_tree.segment.rsc +2 -2
  27. package/.next/server/app/api/docs.segments/api/docs/__PAGE__.segment.rsc +1 -1
  28. package/.next/server/app/api/docs.segments/api/docs.segment.rsc +1 -1
  29. package/.next/server/app/api/docs.segments/api.segment.rsc +1 -1
  30. package/.next/server/app/index.html +1 -1
  31. package/.next/server/app/index.rsc +3 -3
  32. package/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  33. package/.next/server/app/index.segments/_full.segment.rsc +3 -3
  34. package/.next/server/app/index.segments/_head.segment.rsc +1 -1
  35. package/.next/server/app/index.segments/_index.segment.rsc +2 -2
  36. package/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  37. package/.next/server/app/login/page_client-reference-manifest.js +1 -1
  38. package/.next/server/app/login.html +1 -1
  39. package/.next/server/app/login.rsc +2 -2
  40. package/.next/server/app/login.segments/_full.segment.rsc +2 -2
  41. package/.next/server/app/login.segments/_head.segment.rsc +1 -1
  42. package/.next/server/app/login.segments/_index.segment.rsc +2 -2
  43. package/.next/server/app/login.segments/_tree.segment.rsc +2 -2
  44. package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +1 -1
  45. package/.next/server/app/login.segments/login.segment.rsc +1 -1
  46. package/.next/server/app/page_client-reference-manifest.js +1 -1
  47. package/.next/server/chunks/ssr/[root-of-the-server]__04e6e565._.js +3 -3
  48. package/.next/server/chunks/ssr/[root-of-the-server]__04e6e565._.js.map +1 -1
  49. package/.next/server/middleware/middleware-manifest.json +1 -1
  50. package/.next/server/middleware-manifest.json +1 -1
  51. package/.next/server/pages/404.html +1 -1
  52. package/.next/server/pages/500.html +2 -2
  53. package/.next/static/chunks/{7e4fcecd4ca504d5.js → 2394a2e7d0fff196.js} +3 -3
  54. package/.next/static/chunks/295ab055d1c2269f.css +1 -0
  55. package/app/page.tsx +163 -0
  56. package/components/AllCommentsPanel.tsx +177 -128
  57. package/components/BeadsGraph.tsx +8 -2
  58. package/components/DescriptionModal.tsx +51 -0
  59. package/components/ProfilePanel.tsx +107 -86
  60. package/package.json +1 -1
  61. package/.next/static/chunks/5d4996401af7e2d1.css +0 -1
  62. /package/.next/static/{zq8Ew2hci2YIJ61-i832n → 9kusRFLCVoWHASQ-qfwqV}/_buildManifest.js +0 -0
  63. /package/.next/static/{zq8Ew2hci2YIJ61-i832n → 9kusRFLCVoWHASQ-qfwqV}/_clientMiddlewareManifest.json +0 -0
  64. /package/.next/static/{zq8Ew2hci2YIJ61-i832n → 9kusRFLCVoWHASQ-qfwqV}/_ssgManifest.js +0 -0
@@ -0,0 +1 @@
1
+ *,:before,:after,::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:#3b82f680;--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: }*,:before,:after{box-sizing:border-box;border:0 solid #e5e7eb}:before,:after{--tw-content:""}html,:host{-webkit-text-size-adjust:100%;tab-size:4;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent;font-family:Inter,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;line-height:1.5}body{line-height:inherit;margin:0}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;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-feature-settings:normal;font-variation-settings:normal;font-family:JetBrains Mono,Fira Code,monospace;font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-feature-settings:inherit;font-variation-settings:inherit;font-family: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:#0000;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button{height:auto}::-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,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{margin:0;padding:0;list-style:none}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder{opacity:1;color:#9ca3af}textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}html,body{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}body:is(.dark *){--tw-bg-opacity:1;background-color:rgb(0 0 0/var(--tw-bg-opacity,1));--tw-text-opacity:1;color:rgb(228 228 231/var(--tw-text-opacity,1))}body{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{border-radius:9999px;align-items:center;padding:.125rem .5rem;font-size:.75rem;font-weight:500;line-height:1rem;display:inline-flex}.custom-scrollbar::-webkit-scrollbar{width:4px}.custom-scrollbar::-webkit-scrollbar-track{background-color:#0000}.custom-scrollbar::-webkit-scrollbar-thumb{--tw-bg-opacity:1;background-color:rgb(228 228 231/var(--tw-bg-opacity,1));border-radius:9999px}.custom-scrollbar:is(.dark *)::-webkit-scrollbar-thumb{--tw-bg-opacity:1;background-color:rgb(63 63 70/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))}.custom-scrollbar:is(.dark *)::-webkit-scrollbar-thumb:hover{--tw-bg-opacity:1;background-color:rgb(82 82 91/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}.z-\[99\]{z-index:99}.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}.my-2{margin-top:.5rem;margin-bottom:.5rem}.my-4{margin-top:1rem;margin-bottom:1rem}.-mr-0\.5{margin-right:-.125rem}.mb-1{margin-bottom:.25rem}.mb-1\.5{margin-bottom:.375rem}.mb-10{margin-bottom:2.5rem}.mb-12{margin-bottom:3rem}.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}.mb-8{margin-bottom:2rem}.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-10{margin-top:2.5rem}.mt-12{margin-top:3rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-5{margin-top:1.25rem}.mt-\[6px\]{margin-top:6px}.line-clamp-2{-webkit-line-clamp:2;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.block{display:block}.inline{display:inline}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.contents{display:contents}.hidden{display:none}.h-1{height:.25rem}.h-1\.5{height:.375rem}.h-10{height:2.5rem}.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-9{height:2.25rem}.h-\[env\(safe-area-inset-bottom\)\]{height:env(safe-area-inset-bottom)}.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-\[220px\]{max-height:220px}.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}.min-h-screen{min-height:100vh}.w-0\.5{width:.125rem}.w-1{width:.25rem}.w-1\.5{width:.375rem}.w-10{width:2.5rem}.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-60{width:15rem}.w-7{width:1.75rem}.w-72{width:18rem}.w-8{width:2rem}.w-9{width:2.25rem}.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}.min-w-\[3ch\]{min-width:3ch}.max-w-2xl{max-width:42rem}.max-w-3xl{max-width:48rem}.max-w-\[100px\]{max-width:100px}.max-w-\[180px\]{max-width:180px}.max-w-\[480px\]{max-width:480px}.max-w-\[calc\(100vw-2rem\)\]{max-width:calc(100vw - 2rem)}.max-w-md{max-width:28rem}.max-w-sm{max-width:24rem}.max-w-xs{max-width:20rem}.flex-1{flex:1}.flex-shrink-0,.shrink-0{flex-shrink:0}.border-collapse{border-collapse:collapse}.-translate-y-1\/2{--tw-translate-y:-50%;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;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-full{--tw-translate-x:100%;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;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-full{--tw-translate-y:100%;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))}.transform{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:2s cubic-bezier(.4,0,.6,1) infinite pulse}@keyframes spin{to{transform:rotate(360deg)}}.animate-spin{animation:1s linear infinite spin}.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;user-select:none}.resize-none{resize:none}.resize{resize:both}.scroll-mt-8{scroll-margin-top:2rem}.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-end{align-items:flex-end}.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-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.75rem*calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem*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-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.break-words{overflow-wrap:break-word}.break-all{word-break:break-all}.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;border-top-right-radius:.5rem}.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-red-100{--tw-border-opacity:1;border-color:rgb(254 226 226/var(--tw-border-opacity,1))}.border-transparent{border-color:#0000}.border-white\/30{border-color:#ffffff4d}.border-zinc-100{--tw-border-opacity:1;border-color:rgb(244 244 245/var(--tw-border-opacity,1))}.border-zinc-100\/60{border-color:#f4f4f599}.border-zinc-100\/80{border-color:#f4f4f5cc}.border-zinc-200{--tw-border-opacity:1;border-color:rgb(228 228 231/var(--tw-border-opacity,1))}.border-zinc-200\/60{border-color:#e4e4e799}.border-zinc-200\/80{border-color:#e4e4e7cc}.border-zinc-50{--tw-border-opacity:1;border-color:rgb(250 250 250/var(--tw-border-opacity,1))}.border-t-white{--tw-border-opacity:1;border-top-color:rgb(255 255 255/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:#0003}.bg-black\/30{background-color:#0000004d}.bg-black\/40{background-color:#0006}.bg-blue-400{--tw-bg-opacity:1;background-color:rgb(96 165 250/var(--tw-bg-opacity,1))}.bg-blue-50{--tw-bg-opacity:1;background-color:rgb(239 246 255/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:#ecfdf54d}.bg-emerald-50\/90{background-color:#ecfdf5e6}.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-teal-100{--tw-bg-opacity:1;background-color:rgb(204 251 241/var(--tw-bg-opacity,1))}.bg-teal-500{--tw-bg-opacity:1;background-color:rgb(20 184 166/var(--tw-bg-opacity,1))}.bg-transparent{background-color:#0000}.bg-violet-400{--tw-bg-opacity:1;background-color:rgb(167 139 250/var(--tw-bg-opacity,1))}.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:#ffffffe6}.bg-white\/95{background-color:#fffffff2}.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-200\/80{background-color:#e4e4e7cc}.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:#fafafa4d}.bg-zinc-50\/50{background-color:#fafafa80}.bg-zinc-50\/80{background-color:#fafafacc}.bg-zinc-50\/95{background-color:#fafafaf2}.bg-zinc-800{--tw-bg-opacity:1;background-color:rgb(39 39 42/var(--tw-bg-opacity,1))}.bg-zinc-950{--tw-bg-opacity:1;background-color:rgb(9 9 11/var(--tw-bg-opacity,1))}.object-cover{-o-object-fit:cover;object-fit:cover}.p-0\.5{padding:.125rem}.p-1{padding:.25rem}.p-1\.5{padding:.375rem}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.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}.pl-7{padding-left:1.75rem}.pr-1{padding-right:.25rem}.pr-10{padding-right:2.5rem}.pr-3{padding-right:.75rem}.pr-4{padding-right:1rem}.pr-7{padding-right:1.75rem}.pt-2{padding-top:.5rem}.pt-3{padding-top:.75rem}.pt-4{padding-top:1rem}.pt-6{padding-top:1.5rem}.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-7xl{font-size:4.5rem;line-height:1}.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-extralight{font-weight:200}.font-light{font-weight:300}.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-amber-700{--tw-text-opacity:1;color:rgb(180 83 9/var(--tw-text-opacity,1))}.text-blue-500{--tw-text-opacity:1;color:rgb(59 130 246/var(--tw-text-opacity,1))}.text-blue-600{--tw-text-opacity:1;color:rgb(37 99 235/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-violet-500{--tw-text-opacity:1;color:rgb(139 92 246/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}.decoration-emerald-300{text-decoration-color:#6ee7b7}.underline-offset-2{text-underline-offset:2px}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.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-40{opacity:.4}.opacity-50{opacity:.5}.opacity-75{opacity:.75}.opacity-\[0\.03\]{opacity:.03}.shadow{--tw-shadow:0 1px 3px 0 #0000001a,0 1px 2px -1px #0000001a;--tw-shadow-colored:0 1px 3px 0 var(--tw-shadow-color),0 1px 2px -1px var(--tw-shadow-color);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 #00000040;--tw-shadow-colored:0 25px 50px -12px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px #0000001a,0 4px 6px -4px #0000001a;--tw-shadow-colored:0 10px 15px -3px var(--tw-shadow-color),0 4px 6px -4px var(--tw-shadow-color);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 #0000000d;--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-xl{--tw-shadow:0 20px 25px -5px #0000001a,0 8px 10px -6px #0000001a;--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-offset:2px;outline:2px solid #0000}.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);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);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.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:#34d39999}.ring-zinc-100{--tw-ring-opacity:1;--tw-ring-color:rgb(244 244 245/var(--tw-ring-opacity,1))}.ring-zinc-300{--tw-ring-opacity:1;--tw-ring-color:rgb(212 212 216/var(--tw-ring-opacity,1))}.blur{--tw-blur:blur(8px);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)}.\!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)!important}.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-\[2px\]{--tw-backdrop-blur:blur(2px);-webkit-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);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)}.backdrop-blur-sm{--tw-backdrop-blur:blur(4px);-webkit-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);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,-webkit-backdrop-filter,backdrop-filter;transition-duration:.15s;transition-timing-function:cubic-bezier(.4,0,.2,1)}.transition-\[right\]{transition-property:right;transition-duration:.15s;transition-timing-function:cubic-bezier(.4,0,.2,1)}.transition-all{transition-property:all;transition-duration:.15s;transition-timing-function:cubic-bezier(.4,0,.2,1)}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-duration:.15s;transition-timing-function:cubic-bezier(.4,0,.2,1)}.transition-opacity{transition-property:opacity;transition-duration:.15s;transition-timing-function:cubic-bezier(.4,0,.2,1)}.transition-transform{transition-property:transform;transition-duration:.15s;transition-timing-function:cubic-bezier(.4,0,.2,1)}.duration-200{transition-duration:.2s}.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:.3s ease-out forwards fadeIn}@keyframes pulseSoft{0%,to{opacity:1}50%{opacity:.6}}.animate-pulse-soft{animation:2s ease-in-out infinite pulseSoft}@keyframes beadTooltipFade{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}@keyframes slideInRight{0%{transform:translate(100%)}to{transform:translate(0)}}.animate-slide-in-right{animation:.25s ease-out slideInRight}@keyframes slideUpSheet{0%{transform:translateY(100%)}to{transform:translateY(0)}}.animate-slide-up-sheet{animation:.2s ease-out slideUpSheet}.description-markdown h1,.description-markdown h2,.description-markdown h3,.description-markdown h4{--tw-text-opacity:1;color:rgb(63 63 70/var(--tw-text-opacity,1));margin-top:.5rem;margin-bottom:.25rem;font-weight:600}.description-markdown h1:is(.dark *),.description-markdown h2:is(.dark *),.description-markdown h3:is(.dark *),.description-markdown h4:is(.dark *){--tw-text-opacity:1;color:rgb(212 212 216/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 ul,.description-markdown ol{margin-bottom:.375rem}.description-markdown ul>:not([hidden])~:not([hidden]),.description-markdown ol>: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 ul,.description-markdown ol{padding-left:1rem}.description-markdown ul{list-style-type:disc}.description-markdown ol{list-style-type:decimal}.description-markdown code{--tw-text-opacity:1;color:rgb(63 63 70/var(--tw-text-opacity,1));background-color:#e4e4e799;border-radius:.25rem;padding:.125rem .25rem;font-family:JetBrains Mono,Fira Code,monospace;font-size:10px}.description-markdown code:is(.dark *){--tw-text-opacity:1;color:rgb(212 212 216/var(--tw-text-opacity,1));background-color:#3f3f4699}.description-markdown pre{background-color:#e4e4e799;border-radius:.375rem;margin-bottom:.375rem;padding:.5rem;overflow-x:auto}.description-markdown pre:is(.dark *){background-color:#27272a99}.description-markdown pre code{background-color:#0000;padding:0;font-size:10px}.description-markdown a{--tw-text-opacity:1;color:rgb(5 150 105/var(--tw-text-opacity,1));text-underline-offset:2px;text-decoration-line:underline}.description-markdown a:hover{--tw-text-opacity:1;color:rgb(4 120 87/var(--tw-text-opacity,1))}.description-markdown a:is(.dark *){--tw-text-opacity:1;color:rgb(52 211 153/var(--tw-text-opacity,1))}.description-markdown a:hover:is(.dark *){--tw-text-opacity:1;color:rgb(110 231 183/var(--tw-text-opacity,1))}.description-markdown blockquote{--tw-border-opacity:1;border-left-width:2px;border-color:rgb(212 212 216/var(--tw-border-opacity,1));--tw-text-opacity:1;color:rgb(113 113 122/var(--tw-text-opacity,1));margin-top:.375rem;margin-bottom:.375rem;padding-left:.5rem;font-style:italic}.description-markdown blockquote:is(.dark *){--tw-border-opacity:1;border-color:rgb(82 82 91/var(--tw-border-opacity,1));--tw-text-opacity:1;color:rgb(161 161 170/var(--tw-text-opacity,1))}.description-markdown table{border-collapse:collapse;width:100%;margin-bottom:.375rem;font-size:10px}.description-markdown th{--tw-border-opacity:1;border-bottom-width:1px;border-color:rgb(228 228 231/var(--tw-border-opacity,1));text-align:left;--tw-text-opacity:1;color:rgb(82 82 91/var(--tw-text-opacity,1));padding-bottom:.125rem;padding-right:.5rem;font-weight:600}.description-markdown th:is(.dark *){--tw-border-opacity:1;border-color:rgb(63 63 70/var(--tw-border-opacity,1));--tw-text-opacity:1;color:rgb(161 161 170/var(--tw-text-opacity,1))}.description-markdown td{--tw-border-opacity:1;border-bottom-width:1px;border-color:rgb(244 244 245/var(--tw-border-opacity,1));padding-top:.125rem;padding-bottom:.125rem;padding-right:.5rem}.description-markdown td:is(.dark *){--tw-border-opacity:1;border-color:rgb(39 39 42/var(--tw-border-opacity,1))}.description-markdown hr{--tw-border-opacity:1;border-color:rgb(228 228 231/var(--tw-border-opacity,1));margin-top:.5rem;margin-bottom:.5rem}.description-markdown hr:is(.dark *){--tw-border-opacity:1;border-color:rgb(63 63 70/var(--tw-border-opacity,1))}.description-markdown strong{--tw-text-opacity:1;color:rgb(63 63 70/var(--tw-text-opacity,1));font-weight:600}.description-markdown strong:is(.dark *){--tw-text-opacity:1;color:rgb(212 212 216/var(--tw-text-opacity,1))}.description-markdown img{border-radius:.25rem;max-width:100%}.timeline-slider{appearance:none;cursor:pointer;background:0 0}.timeline-slider::-webkit-slider-runnable-track{background:#e4e4e7;border-radius:2px;height:4px}.dark .timeline-slider::-webkit-slider-runnable-track{background:#3f3f46}.timeline-slider::-webkit-slider-thumb{-webkit-appearance:none;cursor:pointer;background:#10b981;border-radius:50%;width:12px;height:12px;margin-top:-4px;transition:transform .1s}.timeline-slider::-webkit-slider-thumb:hover{transform:scale(1.2)}.timeline-slider::-moz-range-track{background:#e4e4e7;border:none;border-radius:2px;height:4px}.dark .timeline-slider::-moz-range-track{background:#3f3f46}.timeline-slider::-moz-range-thumb{cursor:pointer;background:#10b981;border:none;border-radius:50%;width:12px;height:12px}.force-graph-container canvas{touch-action:none;border-radius:.5rem}.placeholder\:text-zinc-300::placeholder{--tw-text-opacity:1;color:rgb(212 212 216/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:#a7f3d080}.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:#f4f4f580}.hover\:bg-zinc-200:hover{--tw-bg-opacity:1;background-color:rgb(228 228 231/var(--tw-bg-opacity,1))}.hover\:bg-zinc-200\/60:hover{background-color:#e4e4e799}.hover\:bg-zinc-300\/40:hover{background-color:#d4d4d866}.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:#fafafa80}.hover\:bg-zinc-50\/60:hover{background-color:#fafafa99}.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-teal-700:hover{--tw-text-opacity:1;color:rgb(15 118 110/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\:underline:hover{text-decoration-line:underline}.hover\:decoration-emerald-500:hover{text-decoration-color:#10b981}.hover\:opacity-70:hover{opacity:.7}.hover\:opacity-80:hover{opacity:.8}.hover\:shadow-md:hover{--tw-shadow:0 4px 6px -1px #0000001a,0 2px 4px -2px #0000001a;--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-offset:2px;outline:2px solid #0000}.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);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);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.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:#10b9814d}.active\:bg-emerald-50:active{--tw-bg-opacity:1;background-color:rgb(236 253 245/var(--tw-bg-opacity,1))}.active\:bg-red-50:active{--tw-bg-opacity:1;background-color:rgb(254 242 242/var(--tw-bg-opacity,1))}.active\:bg-zinc-50:active{--tw-bg-opacity:1;background-color:rgb(250 250 250/var(--tw-bg-opacity,1))}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-40:disabled{opacity:.4}.disabled\:opacity-50:disabled{opacity:.5}.disabled\:hover\:bg-emerald-500:hover:disabled{--tw-bg-opacity:1;background-color:rgb(16 185 129/var(--tw-bg-opacity,1))}.disabled\:hover\:shadow-sm:hover:disabled{--tw-shadow:0 1px 2px 0 #0000000d;--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.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-600{--tw-text-opacity:1;color:rgb(5 150 105/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}.dark\:divide-zinc-800:is(.dark *)>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:rgb(39 39 42/var(--tw-divide-opacity,1))}.dark\:border-emerald-800:is(.dark *){--tw-border-opacity:1;border-color:rgb(6 95 70/var(--tw-border-opacity,1))}.dark\:border-red-800:is(.dark *){--tw-border-opacity:1;border-color:rgb(153 27 27/var(--tw-border-opacity,1))}.dark\:border-zinc-600:is(.dark *){--tw-border-opacity:1;border-color:rgb(82 82 91/var(--tw-border-opacity,1))}.dark\:border-zinc-700:is(.dark *){--tw-border-opacity:1;border-color:rgb(63 63 70/var(--tw-border-opacity,1))}.dark\:border-zinc-700\/60:is(.dark *){border-color:#3f3f4699}.dark\:border-zinc-700\/80:is(.dark *){border-color:#3f3f46cc}.dark\:border-zinc-800:is(.dark *){--tw-border-opacity:1;border-color:rgb(39 39 42/var(--tw-border-opacity,1))}.dark\:border-zinc-800\/80:is(.dark *){border-color:#27272acc}.dark\:bg-black:is(.dark *){--tw-bg-opacity:1;background-color:rgb(0 0 0/var(--tw-bg-opacity,1))}.dark\:bg-black\/95:is(.dark *){background-color:#000000f2}.dark\:bg-blue-950:is(.dark *){--tw-bg-opacity:1;background-color:rgb(23 37 84/var(--tw-bg-opacity,1))}.dark\:bg-emerald-950:is(.dark *){--tw-bg-opacity:1;background-color:rgb(2 44 34/var(--tw-bg-opacity,1))}.dark\:bg-emerald-950\/30:is(.dark *){background-color:#022c224d}.dark\:bg-emerald-950\/90:is(.dark *){background-color:#022c22e6}.dark\:bg-red-950:is(.dark *){--tw-bg-opacity:1;background-color:rgb(69 10 10/var(--tw-bg-opacity,1))}.dark\:bg-teal-900:is(.dark *){--tw-bg-opacity:1;background-color:rgb(19 78 74/var(--tw-bg-opacity,1))}.dark\:bg-zinc-600:is(.dark *){--tw-bg-opacity:1;background-color:rgb(82 82 91/var(--tw-bg-opacity,1))}.dark\:bg-zinc-700:is(.dark *){--tw-bg-opacity:1;background-color:rgb(63 63 70/var(--tw-bg-opacity,1))}.dark\:bg-zinc-700\/80:is(.dark *){background-color:#3f3f46cc}.dark\:bg-zinc-800:is(.dark *){--tw-bg-opacity:1;background-color:rgb(39 39 42/var(--tw-bg-opacity,1))}.dark\:bg-zinc-800\/30:is(.dark *){background-color:#27272a4d}.dark\:bg-zinc-800\/50:is(.dark *){background-color:#27272a80}.dark\:bg-zinc-800\/80:is(.dark *){background-color:#27272acc}.dark\:bg-zinc-800\/95:is(.dark *){background-color:#27272af2}.dark\:bg-zinc-900:is(.dark *){--tw-bg-opacity:1;background-color:rgb(24 24 27/var(--tw-bg-opacity,1))}.dark\:bg-zinc-900\/90:is(.dark *){background-color:#18181be6}.dark\:bg-zinc-900\/95:is(.dark *){background-color:#18181bf2}.dark\:text-blue-400:is(.dark *){--tw-text-opacity:1;color:rgb(96 165 250/var(--tw-text-opacity,1))}.dark\:text-emerald-300:is(.dark *){--tw-text-opacity:1;color:rgb(110 231 183/var(--tw-text-opacity,1))}.dark\:text-emerald-400:is(.dark *){--tw-text-opacity:1;color:rgb(52 211 153/var(--tw-text-opacity,1))}.dark\:text-teal-400:is(.dark *){--tw-text-opacity:1;color:rgb(45 212 191/var(--tw-text-opacity,1))}.dark\:text-zinc-100:is(.dark *){--tw-text-opacity:1;color:rgb(244 244 245/var(--tw-text-opacity,1))}.dark\:text-zinc-200:is(.dark *){--tw-text-opacity:1;color:rgb(228 228 231/var(--tw-text-opacity,1))}.dark\:text-zinc-300:is(.dark *){--tw-text-opacity:1;color:rgb(212 212 216/var(--tw-text-opacity,1))}.dark\:text-zinc-400:is(.dark *){--tw-text-opacity:1;color:rgb(161 161 170/var(--tw-text-opacity,1))}.dark\:text-zinc-500:is(.dark *){--tw-text-opacity:1;color:rgb(113 113 122/var(--tw-text-opacity,1))}.dark\:text-zinc-600:is(.dark *){--tw-text-opacity:1;color:rgb(82 82 91/var(--tw-text-opacity,1))}.dark\:text-zinc-700:is(.dark *){--tw-text-opacity:1;color:rgb(63 63 70/var(--tw-text-opacity,1))}.dark\:placeholder-zinc-500:is(.dark *)::-moz-placeholder{--tw-placeholder-opacity:1;color:rgb(113 113 122/var(--tw-placeholder-opacity,1))}.dark\:placeholder-zinc-500:is(.dark *)::placeholder{--tw-placeholder-opacity:1;color:rgb(113 113 122/var(--tw-placeholder-opacity,1))}.dark\:opacity-\[0\.06\]:is(.dark *){opacity:.06}.dark\:shadow-2xl:is(.dark *){--tw-shadow:0 25px 50px -12px #00000040;--tw-shadow-colored:0 25px 50px -12px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.dark\:shadow-black\/20:is(.dark *){--tw-shadow-color:#0003;--tw-shadow:var(--tw-shadow-colored)}.dark\:ring-emerald-500\/60:is(.dark *){--tw-ring-color:#10b98199}.dark\:ring-zinc-600:is(.dark *){--tw-ring-opacity:1;--tw-ring-color:rgb(82 82 91/var(--tw-ring-opacity,1))}.dark\:ring-zinc-700:is(.dark *){--tw-ring-opacity:1;--tw-ring-color:rgb(63 63 70/var(--tw-ring-opacity,1))}.dark\:placeholder\:text-zinc-500:is(.dark *)::-moz-placeholder{--tw-text-opacity:1;color:rgb(113 113 122/var(--tw-text-opacity,1))}.dark\:placeholder\:text-zinc-500:is(.dark *)::placeholder{--tw-text-opacity:1;color:rgb(113 113 122/var(--tw-text-opacity,1))}.dark\:hover\:border-zinc-600:hover:is(.dark *){--tw-border-opacity:1;border-color:rgb(82 82 91/var(--tw-border-opacity,1))}.dark\:hover\:border-zinc-700:hover:is(.dark *){--tw-border-opacity:1;border-color:rgb(63 63 70/var(--tw-border-opacity,1))}.dark\:hover\:bg-emerald-900:hover:is(.dark *){--tw-bg-opacity:1;background-color:rgb(6 78 59/var(--tw-bg-opacity,1))}.dark\:hover\:bg-emerald-900\/50:hover:is(.dark *){background-color:#064e3b80}.dark\:hover\:bg-emerald-950:hover:is(.dark *){--tw-bg-opacity:1;background-color:rgb(2 44 34/var(--tw-bg-opacity,1))}.dark\:hover\:bg-red-950:hover:is(.dark *){--tw-bg-opacity:1;background-color:rgb(69 10 10/var(--tw-bg-opacity,1))}.dark\:hover\:bg-zinc-700:hover:is(.dark *){--tw-bg-opacity:1;background-color:rgb(63 63 70/var(--tw-bg-opacity,1))}.dark\:hover\:bg-zinc-700\/50:hover:is(.dark *){background-color:#3f3f4680}.dark\:hover\:bg-zinc-700\/60:hover:is(.dark *){background-color:#3f3f4699}.dark\:hover\:bg-zinc-800:hover:is(.dark *){--tw-bg-opacity:1;background-color:rgb(39 39 42/var(--tw-bg-opacity,1))}.dark\:hover\:bg-zinc-800\/50:hover:is(.dark *){background-color:#27272a80}.dark\:hover\:bg-zinc-800\/60:hover:is(.dark *){background-color:#27272a99}.dark\:hover\:bg-zinc-900:hover:is(.dark *){--tw-bg-opacity:1;background-color:rgb(24 24 27/var(--tw-bg-opacity,1))}.dark\:hover\:text-emerald-300:hover:is(.dark *){--tw-text-opacity:1;color:rgb(110 231 183/var(--tw-text-opacity,1))}.dark\:hover\:text-emerald-400:hover:is(.dark *){--tw-text-opacity:1;color:rgb(52 211 153/var(--tw-text-opacity,1))}.dark\:hover\:text-teal-300:hover:is(.dark *){--tw-text-opacity:1;color:rgb(94 234 212/var(--tw-text-opacity,1))}.dark\:hover\:text-zinc-100:hover:is(.dark *){--tw-text-opacity:1;color:rgb(244 244 245/var(--tw-text-opacity,1))}.dark\:hover\:text-zinc-200:hover:is(.dark *){--tw-text-opacity:1;color:rgb(228 228 231/var(--tw-text-opacity,1))}.dark\:hover\:text-zinc-300:hover:is(.dark *){--tw-text-opacity:1;color:rgb(212 212 216/var(--tw-text-opacity,1))}.dark\:hover\:text-zinc-400:hover:is(.dark *){--tw-text-opacity:1;color:rgb(161 161 170/var(--tw-text-opacity,1))}.dark\:active\:bg-emerald-950:active:is(.dark *){--tw-bg-opacity:1;background-color:rgb(2 44 34/var(--tw-bg-opacity,1))}.dark\:active\:bg-red-950:active:is(.dark *){--tw-bg-opacity:1;background-color:rgb(69 10 10/var(--tw-bg-opacity,1))}.dark\:active\:bg-zinc-800:active:is(.dark *){--tw-bg-opacity:1;background-color:rgb(39 39 42/var(--tw-bg-opacity,1))}.group:hover .dark\:group-hover\:text-emerald-300:is(.dark *){--tw-text-opacity:1;color:rgb(110 231 183/var(--tw-text-opacity,1))}.group:hover .dark\:group-hover\:text-emerald-400:is(.dark *){--tw-text-opacity:1;color:rgb(52 211 153/var(--tw-text-opacity,1))}.group:hover .dark\:group-hover\:text-zinc-200:is(.dark *){--tw-text-opacity:1;color:rgb(228 228 231/var(--tw-text-opacity,1))}@media (min-width:640px){.sm\:bottom-4{bottom:1rem}.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}.sm\:px-3{padding-left:.75rem;padding-right:.75rem}.sm\:px-4{padding-left:1rem;padding-right:1rem}.sm\:px-6{padding-left:1.5rem;padding-right:1.5rem}.sm\:py-2{padding-top:.5rem;padding-bottom:.5rem}}@media (min-width:768px){.md\:flex{display:flex}.md\:hidden{display:none}}
package/app/page.tsx CHANGED
@@ -653,6 +653,159 @@ export default function Home() {
653
653
  const [descriptionModalNode, setDescriptionModalNode] =
654
654
  useState<GraphNode | null>(null);
655
655
 
656
+ // Description modal navigation: topological sort of connected component
657
+ const descriptionModalFamily = useMemo(() => {
658
+ if (!descriptionModalNode || !data) return [];
659
+ const nodeId = descriptionModalNode.id;
660
+ const allNodes = data.graphData.nodes;
661
+ const allLinks = data.graphData.links;
662
+
663
+ // Helper: force-graph mutates link source/target to objects after simulation
664
+ const getId = (x: any): string | undefined =>
665
+ typeof x === "string" ? x : x?.id;
666
+
667
+ // Build undirected adjacency (for finding connected component)
668
+ // and directed dependency map (for topological sort)
669
+ const neighbors = new Map<string, Set<string>>();
670
+ const dependsOn = new Map<string, Set<string>>(); // node → nodes it depends on (upstream)
671
+
672
+ for (const link of allLinks) {
673
+ if ((link as any)._removeTime) continue; // Skip links being removed (fade-out animation)
674
+ const src = getId(link.source);
675
+ const tgt = getId(link.target);
676
+ if (!src || !tgt) continue;
677
+
678
+ // Undirected for BFS
679
+ if (!neighbors.has(src)) neighbors.set(src, new Set());
680
+ if (!neighbors.has(tgt)) neighbors.set(tgt, new Set());
681
+ neighbors.get(src)!.add(tgt);
682
+ neighbors.get(tgt)!.add(src);
683
+
684
+ // Directed: target depends on source (source is upstream)
685
+ if (!dependsOn.has(tgt)) dependsOn.set(tgt, new Set());
686
+ dependsOn.get(tgt)!.add(src);
687
+ }
688
+
689
+ // BFS to find connected component containing current node
690
+ const component = new Set<string>();
691
+ const bfsQueue = [nodeId];
692
+ component.add(nodeId);
693
+ while (bfsQueue.length > 0) {
694
+ const current = bfsQueue.shift()!;
695
+ for (const neighbor of neighbors.get(current) || []) {
696
+ if (!component.has(neighbor)) {
697
+ component.add(neighbor);
698
+ bfsQueue.push(neighbor);
699
+ }
700
+ }
701
+ }
702
+
703
+ // Single node — no navigation
704
+ if (component.size <= 1) return [];
705
+
706
+ // Tiebreaker: sort by ID with numeric suffix awareness
707
+ // e.g., "xxx.2" < "xxx.10" (numeric, not lexicographic)
708
+ const compareIds = (a: string, b: string): number => {
709
+ const aParts = a.split(".");
710
+ const bParts = b.split(".");
711
+ // Compare base part first
712
+ if (aParts[0] !== bParts[0]) return a.localeCompare(b);
713
+ // Same base: compare numeric suffixes depth by depth
714
+ for (let i = 1; i < Math.max(aParts.length, bParts.length); i++) {
715
+ const aNum = i < aParts.length ? parseInt(aParts[i], 10) : -1;
716
+ const bNum = i < bParts.length ? parseInt(bParts[i], 10) : -1;
717
+ if (aNum !== bNum) return aNum - bNum;
718
+ }
719
+ return 0;
720
+ };
721
+
722
+ // Kahn's topological sort with sorted ready queue (stable, deterministic)
723
+ const inDegree = new Map<string, number>();
724
+ for (const id of component) {
725
+ inDegree.set(id, 0);
726
+ }
727
+ for (const id of component) {
728
+ for (const dep of dependsOn.get(id) || []) {
729
+ if (component.has(dep)) {
730
+ inDegree.set(id, (inDegree.get(id) || 0) + 1);
731
+ }
732
+ }
733
+ }
734
+
735
+ // Build reverse adjacency: node -> set of nodes that depend on it
736
+ const dependents = new Map<string, Set<string>>();
737
+ for (const id of component) {
738
+ for (const dep of dependsOn.get(id) || []) {
739
+ if (component.has(dep)) {
740
+ if (!dependents.has(dep)) dependents.set(dep, new Set());
741
+ dependents.get(dep)!.add(id);
742
+ }
743
+ }
744
+ }
745
+
746
+ const sorted: string[] = [];
747
+ // Start with nodes that have no dependencies (in-degree 0)
748
+ const ready = Array.from(component)
749
+ .filter(id => (inDegree.get(id) || 0) === 0)
750
+ .sort(compareIds);
751
+
752
+ while (ready.length > 0) {
753
+ const node = ready.shift()!;
754
+ sorted.push(node);
755
+
756
+ // Reduce in-degree for nodes that depend on this one
757
+ for (const dependent of dependents.get(node) || []) {
758
+ const newDeg = (inDegree.get(dependent) || 1) - 1;
759
+ inDegree.set(dependent, newDeg);
760
+ if (newDeg === 0) {
761
+ // Insert in sorted position (maintain order in ready queue)
762
+ const insertIdx = ready.findIndex(r => compareIds(r, dependent) > 0);
763
+ if (insertIdx === -1) ready.push(dependent);
764
+ else ready.splice(insertIdx, 0, dependent);
765
+ }
766
+ }
767
+ }
768
+
769
+ // Handle cycles: append remaining nodes in sorted ID order
770
+ if (sorted.length < component.size) {
771
+ const sortedSet = new Set(sorted);
772
+ const remaining = Array.from(component)
773
+ .filter(id => !sortedSet.has(id))
774
+ .sort(compareIds);
775
+ sorted.push(...remaining);
776
+ }
777
+
778
+ // Map to GraphNode objects, filter to nodes with descriptions only
779
+ const nodeMap = new Map(allNodes.map(n => [n.id, n]));
780
+ return sorted
781
+ .map(id => nodeMap.get(id))
782
+ .filter((n): n is GraphNode => n != null && !!n.description);
783
+ }, [descriptionModalNode, data]);
784
+
785
+ // Description modal navigation handler
786
+ const handleDescriptionNavigate = useCallback((direction: "prev" | "next") => {
787
+ if (!descriptionModalNode || descriptionModalFamily.length <= 1) return;
788
+ const currentIndex = descriptionModalFamily.findIndex(n => n.id === descriptionModalNode.id);
789
+ if (currentIndex === -1) return;
790
+
791
+ let nextIndex: number;
792
+ if (direction === "next") {
793
+ nextIndex = (currentIndex + 1) % descriptionModalFamily.length;
794
+ } else {
795
+ nextIndex = (currentIndex - 1 + descriptionModalFamily.length) % descriptionModalFamily.length;
796
+ }
797
+
798
+ setDescriptionModalNode(descriptionModalFamily[nextIndex]);
799
+ }, [descriptionModalNode, descriptionModalFamily]);
800
+
801
+ // Description modal navigation label
802
+ const descriptionNavLabel = useMemo(() => {
803
+ if (!descriptionModalNode || descriptionModalFamily.length <= 1) return undefined;
804
+ const idx = descriptionModalFamily.findIndex(n => n.id === descriptionModalNode.id);
805
+ if (idx === -1) return undefined;
806
+ return `${idx + 1}/${descriptionModalFamily.length}`;
807
+ }, [descriptionModalNode, descriptionModalFamily]);
808
+
656
809
  // Settings modal state
657
810
  const [settingsModalOpen, setSettingsModalOpen] = useState(false);
658
811
 
@@ -1302,6 +1455,14 @@ export default function Home() {
1302
1455
  // --- End timeline replay logic ---
1303
1456
 
1304
1457
  const handleNodeClick = useCallback((node: GraphNode, event?: MouseEvent) => {
1458
+ // Cmd+Click (Mac) or Ctrl+Click (Windows/Linux): open description modal
1459
+ if ((event?.metaKey || event?.ctrlKey) && node.description) {
1460
+ setDescriptionModalNode(node);
1461
+ setAllCommentsPanelOpen(false);
1462
+ setProfilePanelHandle(null);
1463
+ return;
1464
+ }
1465
+
1305
1466
  if (event?.shiftKey) {
1306
1467
  // Shift+click: toggle node in multi-selection
1307
1468
  setSelectedNodeIds((prev) => {
@@ -2710,6 +2871,8 @@ export default function Home() {
2710
2871
  onClose={() => setDescriptionModalNode(null)}
2711
2872
  repoUrl={repoUrls[descriptionModalNode.prefix]}
2712
2873
  onOpenSettings={() => setSettingsModalOpen(true)}
2874
+ onNavigate={descriptionModalFamily.length > 1 ? handleDescriptionNavigate : undefined}
2875
+ navigationLabel={descriptionNavLabel}
2713
2876
  />
2714
2877
  )}
2715
2878
 
@@ -83,166 +83,215 @@ export default function AllCommentsPanel({
83
83
  const regex = globToRegex(filter.trim());
84
84
  return allComments.filter(c => regex.test(c.nodeId));
85
85
  }, [allComments, filter]);
86
- return (
87
- <aside
88
- className={`hidden md:flex absolute top-0 right-0 h-full w-[360px] bg-white dark:bg-zinc-900 border-l border-zinc-200 dark:border-zinc-700 flex-col shadow-xl z-30 transform transition-transform duration-300 ease-out ${
89
- isOpen ? "translate-x-0" : "translate-x-full"
90
- }`}
91
- >
92
- {/* Header */}
93
- <div className="shrink-0 px-5 py-3 border-b border-zinc-100 dark:border-zinc-800 flex items-center justify-between">
94
- <div className="flex items-center gap-2">
95
- <h2 className="text-xs font-semibold text-zinc-400 uppercase tracking-wider">
96
- All Comments
97
- </h2>
98
- {(filter ? filteredComments.length : allComments.length) > 0 && (
99
- <span className="px-1.5 py-0.5 bg-emerald-50 dark:bg-emerald-950 text-emerald-600 dark:text-emerald-400 rounded-full text-[10px] font-medium">
100
- {filter ? filteredComments.length : allComments.length}
101
- </span>
102
- )}
86
+
87
+ // Extract shared content to avoid duplication between desktop and mobile
88
+ const filterBarContent = (
89
+ <div className="shrink-0 px-5 py-2 border-b border-zinc-100 dark:border-zinc-800">
90
+ <div className="relative">
91
+ <input
92
+ type="text"
93
+ value={filter}
94
+ onChange={(e) => setFilter(e.target.value)}
95
+ placeholder="Filter by target (e.g. beads-map-*)"
96
+ className="w-full pl-7 pr-7 py-1.5 text-xs border border-zinc-200 dark:border-zinc-700 rounded-md bg-zinc-50 dark:bg-zinc-800 text-zinc-700 dark:text-zinc-300 placeholder-zinc-400 dark:placeholder-zinc-500 focus:outline-none focus:ring-1 focus:ring-emerald-500 focus:border-emerald-500"
97
+ />
98
+ {/* Search icon */}
99
+ <svg className="absolute left-2 top-1/2 -translate-y-1/2 w-3.5 h-3.5 text-zinc-400" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor">
100
+ <path strokeLinecap="round" strokeLinejoin="round" d="M12 3c2.755 0 5.455.232 8.083.678.533.09.917.556.917 1.096v1.044a2.25 2.25 0 01-.659 1.591l-5.432 5.432a2.25 2.25 0 00-.659 1.591v2.927a2.25 2.25 0 01-1.244 2.013L9.75 21v-6.568a2.25 2.25 0 00-.659-1.591L3.659 7.409A2.25 2.25 0 013 5.818V4.774c0-.54.384-1.006.917-1.096A48.32 48.32 0 0112 3z" />
101
+ </svg>
102
+ {/* Clear button */}
103
+ {filter && (
104
+ <button
105
+ onClick={() => setFilter("")}
106
+ className="absolute right-2 top-1/2 -translate-y-1/2 text-zinc-400 hover:text-zinc-600 dark:hover:text-zinc-300"
107
+ >
108
+ <svg className="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" strokeWidth={2} stroke="currentColor">
109
+ <path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
110
+ </svg>
111
+ </button>
112
+ )}
113
+ </div>
114
+ {/* Quick filter chips */}
115
+ {prefixes && prefixes.length > 1 && !filter && (
116
+ <div className="flex flex-wrap gap-1 mt-1.5">
117
+ {prefixes.map(p => (
118
+ <button
119
+ key={p}
120
+ onClick={() => setFilter(p + '-*')}
121
+ className="px-1.5 py-0.5 text-[10px] font-mono bg-zinc-100 dark:bg-zinc-800 text-zinc-500 dark:text-zinc-400 rounded hover:bg-zinc-200 dark:hover:bg-zinc-700 transition-colors"
122
+ >
123
+ {p}-*
124
+ </button>
125
+ ))}
103
126
  </div>
104
- <button
105
- onClick={onClose}
106
- className="p-1 text-zinc-400 dark:text-zinc-500 hover:text-zinc-600 dark:hover:text-zinc-300 transition-colors rounded-full hover:bg-zinc-100 dark:hover:bg-zinc-800"
107
- >
127
+ )}
128
+ </div>
129
+ );
130
+
131
+ const commentListContent = (
132
+ <div className="space-y-0">
133
+ {filteredComments.length === 0 && filter ? (
134
+ <div className="flex flex-col items-center justify-center py-12 text-center">
135
+ <p className="text-xs text-zinc-400 dark:text-zinc-500">No comments matching &ldquo;{filter}&rdquo;</p>
136
+ <button onClick={() => setFilter("")} className="text-[10px] text-emerald-600 dark:text-emerald-400 hover:underline mt-1">
137
+ Clear filter
138
+ </button>
139
+ </div>
140
+ ) : filteredComments.length === 0 ? (
141
+ <div className="flex flex-col items-center justify-center py-12 text-center">
108
142
  <svg
109
- className="w-4 h-4"
143
+ className="w-8 h-8 text-zinc-200 dark:text-zinc-700 mb-3"
110
144
  fill="none"
111
- stroke="currentColor"
112
145
  viewBox="0 0 24 24"
113
146
  strokeWidth={1.5}
147
+ stroke="currentColor"
114
148
  >
115
149
  <path
116
150
  strokeLinecap="round"
117
151
  strokeLinejoin="round"
118
- d="M6 18L18 6M6 6l12 12"
152
+ d="M2.25 12.76c0 1.6 1.123 2.994 2.707 3.227 1.087.16 2.185.283 3.293.369V21l4.076-4.076a1.526 1.526 0 011.037-.443 48.282 48.282 0 005.68-.494c1.584-.233 2.707-1.626 2.707-3.228V6.741c0-1.602-1.123-2.995-2.707-3.228A48.394 48.394 0 0012 3c-2.392 0-4.744.175-7.043.513C3.373 3.746 2.25 5.14 2.25 6.741v6.018z"
119
153
  />
120
154
  </svg>
121
- </button>
122
- </div>
155
+ <p className="text-xs text-zinc-400 dark:text-zinc-500">No comments yet</p>
156
+ <p className="text-[10px] text-zinc-300 dark:text-zinc-600 mt-1">
157
+ Right-click a node to leave a comment
158
+ </p>
159
+ </div>
160
+ ) : (
161
+ <>
162
+ {/* Only show root comments (without replyTo) — replies are nested */}
163
+ {filteredComments.filter(c => !c.replyTo).map((comment) => (
164
+ <AllCommentCard
165
+ key={comment.uri}
166
+ comment={comment}
167
+ currentDid={currentDid}
168
+ isAuthenticated={isAuthenticated}
169
+ onNodeNavigate={onNodeNavigate}
170
+ onLike={onLikeComment}
171
+ onDelete={onDeleteComment}
172
+ onStartReply={(c) => setReplyingToUri(c.uri)}
173
+ replyingToUri={replyingToUri}
174
+ replyText={replyText}
175
+ onReplyTextChange={setReplyText}
176
+ onSubmitReply={handleSubmitReply}
177
+ onCancelReply={() => {
178
+ setReplyingToUri(null);
179
+ setReplyText("");
180
+ }}
181
+ isSubmittingReply={isSubmittingReply}
182
+ depth={0}
183
+ prefixes={prefixes}
184
+ />
185
+ ))}
186
+ </>
187
+ )}
188
+ </div>
189
+ );
123
190
 
124
- {/* Filter bar */}
125
- <div className="shrink-0 px-5 py-2 border-b border-zinc-100 dark:border-zinc-800">
126
- <div className="relative">
127
- <input
128
- type="text"
129
- value={filter}
130
- onChange={(e) => setFilter(e.target.value)}
131
- placeholder="Filter by target (e.g. beads-map-*)"
132
- className="w-full pl-7 pr-7 py-1.5 text-xs border border-zinc-200 dark:border-zinc-700 rounded-md bg-zinc-50 dark:bg-zinc-800 text-zinc-700 dark:text-zinc-300 placeholder-zinc-400 dark:placeholder-zinc-500 focus:outline-none focus:ring-1 focus:ring-emerald-500 focus:border-emerald-500"
133
- />
134
- {/* Search icon */}
135
- <svg className="absolute left-2 top-1/2 -translate-y-1/2 w-3.5 h-3.5 text-zinc-400" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor">
136
- <path strokeLinecap="round" strokeLinejoin="round" d="M12 3c2.755 0 5.455.232 8.083.678.533.09.917.556.917 1.096v1.044a2.25 2.25 0 01-.659 1.591l-5.432 5.432a2.25 2.25 0 00-.659 1.591v2.927a2.25 2.25 0 01-1.244 2.013L9.75 21v-6.568a2.25 2.25 0 00-.659-1.591L3.659 7.409A2.25 2.25 0 013 5.818V4.774c0-.54.384-1.006.917-1.096A48.32 48.32 0 0112 3z" />
137
- </svg>
138
- {/* Clear button */}
139
- {filter && (
140
- <button
141
- onClick={() => setFilter("")}
142
- className="absolute right-2 top-1/2 -translate-y-1/2 text-zinc-400 hover:text-zinc-600 dark:hover:text-zinc-300"
143
- >
144
- <svg className="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" strokeWidth={2} stroke="currentColor">
145
- <path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
146
- </svg>
147
- </button>
148
- )}
191
+ const composeContent = (
192
+ <div className="shrink-0 px-5 py-3 border-t border-zinc-100 dark:border-zinc-800 bg-zinc-50/50 dark:bg-zinc-800/50">
193
+ {isAuthenticated ? (
194
+ <GeneralCommentCompose
195
+ prefixes={prefixes || []}
196
+ onSubmit={onPostComment}
197
+ />
198
+ ) : (
199
+ <div className="text-[10px] text-zinc-400 dark:text-zinc-500">
200
+ Sign in to comment
149
201
  </div>
150
- {/* Quick filter chips */}
151
- {prefixes && prefixes.length > 1 && !filter && (
152
- <div className="flex flex-wrap gap-1 mt-1.5">
153
- {prefixes.map(p => (
154
- <button
155
- key={p}
156
- onClick={() => setFilter(p + '-*')}
157
- className="px-1.5 py-0.5 text-[10px] font-mono bg-zinc-100 dark:bg-zinc-800 text-zinc-500 dark:text-zinc-400 rounded hover:bg-zinc-200 dark:hover:bg-zinc-700 transition-colors"
158
- >
159
- {p}-*
160
- </button>
161
- ))}
162
- </div>
202
+ )}
203
+ <div className="text-[10px] text-zinc-400 dark:text-zinc-500 mt-2">
204
+ {filter ? (
205
+ <>{filteredComments.length} of {allComments.length} comments matching &ldquo;{filter}&rdquo;</>
206
+ ) : (
207
+ <>{allComments.length} comment{allComments.length !== 1 ? 's' : ''} across all issues</>
163
208
  )}
164
209
  </div>
210
+ </div>
211
+ );
165
212
 
166
- {/* Comment list */}
167
- <div className="flex-1 overflow-y-auto custom-scrollbar px-5 py-3">
168
- {filteredComments.length === 0 && filter ? (
169
- <div className="flex flex-col items-center justify-center py-12 text-center">
170
- <p className="text-xs text-zinc-400 dark:text-zinc-500">No comments matching &ldquo;{filter}&rdquo;</p>
171
- <button onClick={() => setFilter("")} className="text-[10px] text-emerald-600 dark:text-emerald-400 hover:underline mt-1">
172
- Clear filter
173
- </button>
213
+ return (
214
+ <>
215
+ {/* Desktop: right sidebar */}
216
+ <aside
217
+ className={`hidden md:flex absolute top-0 right-0 h-full w-[360px] bg-white dark:bg-zinc-900 border-l border-zinc-200 dark:border-zinc-700 flex-col shadow-xl z-30 transform transition-transform duration-300 ease-out ${
218
+ isOpen ? "translate-x-0" : "translate-x-full"
219
+ }`}
220
+ >
221
+ {/* Header */}
222
+ <div className="shrink-0 px-5 py-3 border-b border-zinc-100 dark:border-zinc-800 flex items-center justify-between">
223
+ <div className="flex items-center gap-2">
224
+ <h2 className="text-xs font-semibold text-zinc-400 uppercase tracking-wider">
225
+ All Comments
226
+ </h2>
227
+ {(filter ? filteredComments.length : allComments.length) > 0 && (
228
+ <span className="px-1.5 py-0.5 bg-emerald-50 dark:bg-emerald-950 text-emerald-600 dark:text-emerald-400 rounded-full text-[10px] font-medium">
229
+ {filter ? filteredComments.length : allComments.length}
230
+ </span>
231
+ )}
174
232
  </div>
175
- ) : filteredComments.length === 0 ? (
176
- <div className="flex flex-col items-center justify-center py-12 text-center">
233
+ <button
234
+ onClick={onClose}
235
+ className="p-1 text-zinc-400 dark:text-zinc-500 hover:text-zinc-600 dark:hover:text-zinc-300 transition-colors rounded-full hover:bg-zinc-100 dark:hover:bg-zinc-800"
236
+ >
177
237
  <svg
178
- className="w-8 h-8 text-zinc-200 dark:text-zinc-700 mb-3"
238
+ className="w-4 h-4"
179
239
  fill="none"
240
+ stroke="currentColor"
180
241
  viewBox="0 0 24 24"
181
242
  strokeWidth={1.5}
182
- stroke="currentColor"
183
243
  >
184
244
  <path
185
245
  strokeLinecap="round"
186
246
  strokeLinejoin="round"
187
- d="M2.25 12.76c0 1.6 1.123 2.994 2.707 3.227 1.087.16 2.185.283 3.293.369V21l4.076-4.076a1.526 1.526 0 011.037-.443 48.282 48.282 0 005.68-.494c1.584-.233 2.707-1.626 2.707-3.228V6.741c0-1.602-1.123-2.995-2.707-3.228A48.394 48.394 0 0012 3c-2.392 0-4.744.175-7.043.513C3.373 3.746 2.25 5.14 2.25 6.741v6.018z"
247
+ d="M6 18L18 6M6 6l12 12"
188
248
  />
189
249
  </svg>
190
- <p className="text-xs text-zinc-400 dark:text-zinc-500">No comments yet</p>
191
- <p className="text-[10px] text-zinc-300 dark:text-zinc-600 mt-1">
192
- Right-click a node to leave a comment
193
- </p>
194
- </div>
195
- ) : (
196
- <div className="space-y-0">
197
- {/* Only show root comments (without replyTo) — replies are nested */}
198
- {filteredComments.filter(c => !c.replyTo).map((comment) => (
199
- <AllCommentCard
200
- key={comment.uri}
201
- comment={comment}
202
- currentDid={currentDid}
203
- isAuthenticated={isAuthenticated}
204
- onNodeNavigate={onNodeNavigate}
205
- onLike={onLikeComment}
206
- onDelete={onDeleteComment}
207
- onStartReply={(c) => setReplyingToUri(c.uri)}
208
- replyingToUri={replyingToUri}
209
- replyText={replyText}
210
- onReplyTextChange={setReplyText}
211
- onSubmitReply={handleSubmitReply}
212
- onCancelReply={() => {
213
- setReplyingToUri(null);
214
- setReplyText("");
215
- }}
216
- isSubmittingReply={isSubmittingReply}
217
- depth={0}
218
- prefixes={prefixes}
219
- />
220
- ))}
250
+ </button>
251
+ </div>
252
+
253
+ {/* Filter bar */}
254
+ {filterBarContent}
255
+
256
+ {/* Comment list */}
257
+ <div className="flex-1 overflow-y-auto custom-scrollbar px-5 py-3">
258
+ {commentListContent}
259
+ </div>
260
+
261
+ {/* Compose area */}
262
+ {composeContent}
263
+ </aside>
264
+
265
+ {/* Mobile: bottom drawer */}
266
+ <div
267
+ className={`md:hidden fixed inset-x-0 bottom-0 transform transition-transform duration-300 ease-out z-20 ${
268
+ isOpen ? "translate-y-0" : "translate-y-full"
269
+ }`}
270
+ >
271
+ <div className="bg-white dark:bg-zinc-900 rounded-t-2xl shadow-2xl border-t border-zinc-200 dark:border-zinc-700 max-h-[70vh] flex flex-col">
272
+ {/* Header with title + close button */}
273
+ <div className="flex items-center justify-between px-5 py-3 border-b border-zinc-100 dark:border-zinc-800 shrink-0">
274
+ <h2 className="text-xs font-semibold text-zinc-400 dark:text-zinc-400 uppercase tracking-wider">All Comments</h2>
275
+ <button onClick={onClose} className="p-1 text-zinc-400 hover:text-zinc-600 dark:hover:text-zinc-300 rounded-full hover:bg-zinc-100 dark:hover:bg-zinc-800 transition-colors">
276
+ <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" strokeWidth={2}>
277
+ <path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
278
+ </svg>
279
+ </button>
221
280
  </div>
222
- )}
223
- </div>
224
281
 
225
- {/* Compose area */}
226
- <div className="shrink-0 px-5 py-3 border-t border-zinc-100 dark:border-zinc-800 bg-zinc-50/50 dark:bg-zinc-800/50">
227
- {isAuthenticated ? (
228
- <GeneralCommentCompose
229
- prefixes={prefixes || []}
230
- onSubmit={onPostComment}
231
- />
232
- ) : (
233
- <div className="text-[10px] text-zinc-400 dark:text-zinc-500">
234
- Sign in to comment
282
+ {/* Filter bar */}
283
+ {filterBarContent}
284
+
285
+ {/* Scrollable content: same comment list as desktop */}
286
+ <div className="flex-1 overflow-y-auto custom-scrollbar px-5 py-3">
287
+ {commentListContent}
235
288
  </div>
236
- )}
237
- <div className="text-[10px] text-zinc-400 dark:text-zinc-500 mt-2">
238
- {filter ? (
239
- <>{filteredComments.length} of {allComments.length} comments matching &ldquo;{filter}&rdquo;</>
240
- ) : (
241
- <>{allComments.length} comment{allComments.length !== 1 ? 's' : ''} across all issues</>
242
- )}
289
+
290
+ {/* Compose area (same as desktop) */}
291
+ {composeContent}
243
292
  </div>
244
293
  </div>
245
- </aside>
294
+ </>
246
295
  );
247
296
  }
248
297