@danhachuel/pandanews 0.4.3 → 0.4.5
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.
- package/CHANGELOG.md +20 -0
- package/backend/app/main.py +1 -1
- package/backend/core/llm_router.py +81 -41
- package/backend/core/proc.py +89 -0
- package/frontend/dist/assets/{index-BLjBdAFC.js → index-BA6xr3Hb.js} +14 -14
- package/frontend/dist/assets/index-tVJXH-iW.css +1 -0
- package/frontend/dist/index.html +2 -2
- package/package.json +1 -1
- package/scripts/build.js +8 -2
- package/scripts/install.js +20 -3
- package/scripts/test-start-isolation.mjs +194 -31
- package/frontend/dist/assets/index-CUCWGfn9.css +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
*,:before,:after{--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: rgb(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: rgb(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: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{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,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,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,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,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{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}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.inset-0{top:0;right:0;bottom:0;left:0}.left-1{left:.25rem}.left-11{left:2.75rem}.left-9{left:2.25rem}.top-1{top:.25rem}.z-50{z-index:50}.mx-2{margin-left:.5rem;margin-right:.5rem}.mx-auto{margin-left:auto;margin-right:auto}.mb-1{margin-bottom:.25rem}.mb-1\.5{margin-bottom:.375rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-5{margin-bottom:1.25rem}.mb-6{margin-bottom:1.5rem}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-6{margin-top:1.5rem}.line-clamp-2{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2}.block{display:block}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.hidden{display:none}.h-10{height:2.5rem}.h-16{height:4rem}.h-2{height:.5rem}.h-40{height:10rem}.h-44{height:11rem}.h-6{height:1.5rem}.h-8{height:2rem}.h-auto{height:auto}.h-full{height:100%}.max-h-48{max-height:12rem}.max-h-56{max-height:14rem}.max-h-\[85vh\]{max-height:85vh}.max-h-\[90vh\]{max-height:90vh}.min-h-screen{min-height:100vh}.w-10{width:2.5rem}.w-16{width:4rem}.w-20{width:5rem}.w-40{width:10rem}.w-44{width:11rem}.w-6{width:1.5rem}.w-60{width:15rem}.w-8{width:2rem}.w-full{width:100%}.min-w-0{min-width:0px}.max-w-2xl{max-width:42rem}.max-w-6xl{max-width:72rem}.max-w-lg{max-width:32rem}.max-w-md{max-width:28rem}.max-w-sm{max-width:24rem}.flex-1{flex:1 1 0%}.shrink-0{flex-shrink:0}.rotate-180{--tw-rotate: 180deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(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)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.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}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-1{gap:.25rem}.gap-2{gap:.5rem}.gap-2\.5{gap:.625rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.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-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-6>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.5rem * var(--tw-space-y-reverse))}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-xl{border-radius:.75rem}.rounded-t-md{border-top-left-radius:.375rem;border-top-right-radius:.375rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-r{border-right-width:1px}.border-t{border-top-width:1px}.border-dashed{border-style:dashed}.border-amber-400\/40{border-color:#fbbf2466}.border-panda\/40{border-color:#22c55e66}.border-red-400\/40{border-color:#f8717166}.border-slate-100{--tw-border-opacity: 1;border-color:rgb(241 245 249 / var(--tw-border-opacity, 1))}.border-slate-200{--tw-border-opacity: 1;border-color:rgb(226 232 240 / var(--tw-border-opacity, 1))}.border-slate-300{--tw-border-opacity: 1;border-color:rgb(203 213 225 / var(--tw-border-opacity, 1))}.bg-amber-100{--tw-bg-opacity: 1;background-color:rgb(254 243 199 / var(--tw-bg-opacity, 1))}.bg-amber-500\/10{background-color:#f59e0b1a}.bg-black\/60{background-color:#0009}.bg-emerald-100{--tw-bg-opacity: 1;background-color:rgb(209 250 229 / var(--tw-bg-opacity, 1))}.bg-night{--tw-bg-opacity: 1;background-color:rgb(11 18 32 / var(--tw-bg-opacity, 1))}.bg-panda{--tw-bg-opacity: 1;background-color:rgb(34 197 94 / var(--tw-bg-opacity, 1))}.bg-panda\/10{background-color:#22c55e1a}.bg-panda\/15{background-color:#22c55e26}.bg-panda\/30{background-color:#22c55e4d}.bg-panda\/70{background-color:#22c55eb3}.bg-red-100{--tw-bg-opacity: 1;background-color:rgb(254 226 226 / var(--tw-bg-opacity, 1))}.bg-red-500\/10{background-color:#ef44441a}.bg-red-600{--tw-bg-opacity: 1;background-color:rgb(220 38 38 / var(--tw-bg-opacity, 1))}.bg-sky-100{--tw-bg-opacity: 1;background-color:rgb(224 242 254 / var(--tw-bg-opacity, 1))}.bg-slate-200{--tw-bg-opacity: 1;background-color:rgb(226 232 240 / var(--tw-bg-opacity, 1))}.bg-slate-300{--tw-bg-opacity: 1;background-color:rgb(203 213 225 / var(--tw-bg-opacity, 1))}.bg-transparent{background-color:transparent}.bg-white{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity, 1))}.object-cover{-o-object-fit:cover;object-fit:cover}.p-3{padding:.75rem}.p-4{padding:1rem}.p-5{padding:1.25rem}.p-6{padding:1.5rem}.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-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-14{padding-top:3.5rem;padding-bottom:3.5rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-2\.5{padding-top:.625rem;padding-bottom:.625rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-5{padding-top:1.25rem;padding-bottom:1.25rem}.pb-2{padding-bottom:.5rem}.pr-4{padding-right:1rem}.pt-2{padding-top:.5rem}.text-left{text-align:left}.text-center{text-align:center}.text-2xl{font-size:1.5rem;line-height:2rem}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-4xl{font-size:2.25rem;line-height:2.5rem}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-extrabold{font-weight:800}.font-medium{font-weight:500}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.leading-none{line-height:1}.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-emerald-300{--tw-text-opacity: 1;color:rgb(110 231 183 / var(--tw-text-opacity, 1))}.text-emerald-700{--tw-text-opacity: 1;color:rgb(4 120 87 / var(--tw-text-opacity, 1))}.text-panda{--tw-text-opacity: 1;color:rgb(34 197 94 / var(--tw-text-opacity, 1))}.text-panda-dark{--tw-text-opacity: 1;color:rgb(22 163 74 / var(--tw-text-opacity, 1))}.text-red-400{--tw-text-opacity: 1;color:rgb(248 113 113 / var(--tw-text-opacity, 1))}.text-red-600{--tw-text-opacity: 1;color:rgb(220 38 38 / var(--tw-text-opacity, 1))}.text-sky-700{--tw-text-opacity: 1;color:rgb(3 105 161 / var(--tw-text-opacity, 1))}.text-slate-400{--tw-text-opacity: 1;color:rgb(148 163 184 / var(--tw-text-opacity, 1))}.text-slate-500{--tw-text-opacity: 1;color:rgb(100 116 139 / var(--tw-text-opacity, 1))}.text-slate-600{--tw-text-opacity: 1;color:rgb(71 85 105 / var(--tw-text-opacity, 1))}.text-slate-700{--tw-text-opacity: 1;color:rgb(51 65 85 / var(--tw-text-opacity, 1))}.text-slate-800{--tw-text-opacity: 1;color:rgb(30 41 59 / var(--tw-text-opacity, 1))}.text-slate-900{--tw-text-opacity: 1;color:rgb(15 23 42 / var(--tw-text-opacity, 1))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.line-through{text-decoration-line:line-through}.placeholder-slate-400::-moz-placeholder{--tw-placeholder-opacity: 1;color:rgb(148 163 184 / var(--tw-placeholder-opacity, 1))}.placeholder-slate-400::placeholder{--tw-placeholder-opacity: 1;color:rgb(148 163 184 / var(--tw-placeholder-opacity, 1))}.shadow{--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / .1), 0 1px 2px -1px rgb(0 0 0 / .1);--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-sm{--tw-shadow: 0 1px 2px 0 rgb(0 0 0 / .05);--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)}.outline-none{outline:2px solid transparent;outline-offset:2px}.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)}.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-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}body{--tw-bg-opacity: 1;background-color:rgb(11 18 32 / var(--tw-bg-opacity, 1));font-family:Inter,system-ui,sans-serif;--tw-text-opacity: 1;color:rgb(226 232 240 / var(--tw-text-opacity, 1));-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}html:not(.dark) body{--tw-bg-opacity: 1;background-color:rgb(241 245 249 / var(--tw-bg-opacity, 1));--tw-text-opacity: 1;color:rgb(30 41 59 / var(--tw-text-opacity, 1))}.last\:border-0:last-child{border-width:0px}.hover\:border-slate-400:hover{--tw-border-opacity: 1;border-color:rgb(148 163 184 / var(--tw-border-opacity, 1))}.hover\:bg-panda-dark:hover{--tw-bg-opacity: 1;background-color:rgb(22 163 74 / var(--tw-bg-opacity, 1))}.hover\:bg-red-700:hover{--tw-bg-opacity: 1;background-color:rgb(185 28 28 / var(--tw-bg-opacity, 1))}.hover\:bg-slate-100:hover{--tw-bg-opacity: 1;background-color:rgb(241 245 249 / var(--tw-bg-opacity, 1))}.hover\:text-slate-800:hover{--tw-text-opacity: 1;color:rgb(30 41 59 / var(--tw-text-opacity, 1))}.hover\:underline:hover{text-decoration-line:underline}.focus\:border-panda:focus{--tw-border-opacity: 1;border-color:rgb(34 197 94 / var(--tw-border-opacity, 1))}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-40:disabled{opacity:.4}.dark\:border-slate-700:is(.dark *){--tw-border-opacity: 1;border-color:rgb(51 65 85 / var(--tw-border-opacity, 1))}.dark\:border-slate-800:is(.dark *){--tw-border-opacity: 1;border-color:rgb(30 41 59 / var(--tw-border-opacity, 1))}.dark\:bg-amber-500\/15:is(.dark *){background-color:#f59e0b26}.dark\:bg-card:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(16 26 44 / var(--tw-bg-opacity, 1))}.dark\:bg-emerald-500\/15:is(.dark *){background-color:#10b98126}.dark\:bg-night:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(11 18 32 / var(--tw-bg-opacity, 1))}.dark\:bg-red-500\/15:is(.dark *){background-color:#ef444426}.dark\:bg-sky-500\/15:is(.dark *){background-color:#0ea5e926}.dark\:bg-slate-700:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(51 65 85 / var(--tw-bg-opacity, 1))}.dark\:bg-slate-700\/60:is(.dark *){background-color:#33415599}.dark\:bg-slate-800:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(30 41 59 / var(--tw-bg-opacity, 1))}.dark\:bg-slate-900:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(15 23 42 / var(--tw-bg-opacity, 1))}.dark\:text-amber-400:is(.dark *){--tw-text-opacity: 1;color:rgb(251 191 36 / 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-panda:is(.dark *){--tw-text-opacity: 1;color:rgb(34 197 94 / var(--tw-text-opacity, 1))}.dark\:text-red-400:is(.dark *){--tw-text-opacity: 1;color:rgb(248 113 113 / var(--tw-text-opacity, 1))}.dark\:text-sky-400:is(.dark *){--tw-text-opacity: 1;color:rgb(56 189 248 / var(--tw-text-opacity, 1))}.dark\:text-slate-200:is(.dark *){--tw-text-opacity: 1;color:rgb(226 232 240 / var(--tw-text-opacity, 1))}.dark\:text-slate-300:is(.dark *){--tw-text-opacity: 1;color:rgb(203 213 225 / var(--tw-text-opacity, 1))}.dark\:text-slate-400:is(.dark *){--tw-text-opacity: 1;color:rgb(148 163 184 / var(--tw-text-opacity, 1))}.dark\:text-slate-500:is(.dark *){--tw-text-opacity: 1;color:rgb(100 116 139 / var(--tw-text-opacity, 1))}.dark\:text-slate-600:is(.dark *){--tw-text-opacity: 1;color:rgb(71 85 105 / var(--tw-text-opacity, 1))}.dark\:placeholder-slate-500:is(.dark *)::-moz-placeholder{--tw-placeholder-opacity: 1;color:rgb(100 116 139 / var(--tw-placeholder-opacity, 1))}.dark\:placeholder-slate-500:is(.dark *)::placeholder{--tw-placeholder-opacity: 1;color:rgb(100 116 139 / var(--tw-placeholder-opacity, 1))}.dark\:hover\:border-slate-500:hover:is(.dark *){--tw-border-opacity: 1;border-color:rgb(100 116 139 / var(--tw-border-opacity, 1))}.dark\:hover\:bg-slate-800\/60:hover:is(.dark *){background-color:#1e293b99}.dark\:hover\:text-slate-200:hover:is(.dark *){--tw-text-opacity: 1;color:rgb(226 232 240 / var(--tw-text-opacity, 1))}@media (min-width: 640px){.sm\:inline{display:inline}.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}@media (min-width: 768px){.md\:flex{display:flex}.md\:hidden{display:none}.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}@media (min-width: 1024px){.lg\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}}@media (min-width: 1280px){.xl\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}
|
package/frontend/dist/index.html
CHANGED
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
9
9
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
10
10
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet" />
|
|
11
|
-
<script type="module" crossorigin src="/assets/index-
|
|
12
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
11
|
+
<script type="module" crossorigin src="/assets/index-BA6xr3Hb.js"></script>
|
|
12
|
+
<link rel="stylesheet" crossorigin href="/assets/index-tVJXH-iW.css">
|
|
13
13
|
</head>
|
|
14
14
|
<body>
|
|
15
15
|
<div id="root"></div>
|
package/package.json
CHANGED
package/scripts/build.js
CHANGED
|
@@ -15,12 +15,18 @@ export async function runBuild() {
|
|
|
15
15
|
return;
|
|
16
16
|
}
|
|
17
17
|
const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
18
|
+
// Regra permanente (AGENTS.md): NENHUM spawn pode abrir janela de console
|
|
19
|
+
// no computador do usuário. No Windows o filho nasce com CREATE_NO_WINDOW
|
|
20
|
+
// (windowsHide: true) — com `shell: true` o wrapper cmd.exe também nasce
|
|
21
|
+
// oculto, sem piscar tela preta. O stdio segue 'inherit': a saída fica no
|
|
22
|
+
// console que o usuário já abriu.
|
|
23
|
+
const SILENT_WINDOWS = { windowsHide: true };
|
|
18
24
|
console.log('Instalando dependências do frontend...');
|
|
19
25
|
// shell: true — obrigatório para .cmd no Windows (Node >= 18)
|
|
20
|
-
const install = spawnSync(npmCmd, ['install'], { cwd: frontendDir, stdio: 'inherit', shell: true });
|
|
26
|
+
const install = spawnSync(npmCmd, ['install'], { cwd: frontendDir, stdio: 'inherit', shell: true, ...SILENT_WINDOWS });
|
|
21
27
|
if (install.status !== 0) throw new Error('Falha ao instalar dependências do frontend.');
|
|
22
28
|
console.log('Compilando o frontend...');
|
|
23
|
-
const build = spawnSync(npmCmd, ['run', 'build'], { cwd: frontendDir, stdio: 'inherit', shell: true });
|
|
29
|
+
const build = spawnSync(npmCmd, ['run', 'build'], { cwd: frontendDir, stdio: 'inherit', shell: true, ...SILENT_WINDOWS });
|
|
24
30
|
if (build.status !== 0) throw new Error('Falha ao compilar o frontend.');
|
|
25
31
|
console.log('Frontend compilado em frontend/dist.');
|
|
26
32
|
}
|
package/scripts/install.js
CHANGED
|
@@ -21,6 +21,20 @@ import fse from 'fs-extra';
|
|
|
21
21
|
const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
|
22
22
|
export const PKG_ROOT = join(__dirname, '..');
|
|
23
23
|
|
|
24
|
+
// ------------------------------------------------------------------------
|
|
25
|
+
// Regra permanente (AGENTS.md): é PROIBIDO abrir janelas de terminal (CMD,
|
|
26
|
+
// PowerShell, console Python) no computador do usuário. Todo processo filho
|
|
27
|
+
// lançado pelo instalador nasce no Windows com `windowsHide: true`, que aplica
|
|
28
|
+
// a flag CREATE_NO_WINDOW — nenhuma tela preta aparece.
|
|
29
|
+
//
|
|
30
|
+
// `detached: true` NÃO é usado aqui de propósito: essas chamadas são curtas
|
|
31
|
+
// (tar/venv/pip) e o `stdio: 'inherit'` mantém o progresso no console que o
|
|
32
|
+
// usuário JÁ abriu. Com CREATE_NO_WINDOW o filho escreve nos handles herdados
|
|
33
|
+
// sem alocar uma janela extra. O teste scripts/test-start-isolation.mjs varre
|
|
34
|
+
// este arquivo e falha o build se qualquer spawn perder o windowsHide.
|
|
35
|
+
// ------------------------------------------------------------------------
|
|
36
|
+
const SILENT_WINDOWS = { windowsHide: true };
|
|
37
|
+
|
|
24
38
|
const PYTHON_VERSION = '3.12.7';
|
|
25
39
|
const PYTHON_RELEASE = '20241016';
|
|
26
40
|
|
|
@@ -152,7 +166,7 @@ async function downloadPython(home, platform) {
|
|
|
152
166
|
const buf = Buffer.from(await res.arrayBuffer());
|
|
153
167
|
writeFileSync(archive, buf);
|
|
154
168
|
console.log(`Download concluído (${(buf.length / 1024 / 1024).toFixed(1)} MB). Extraindo...`);
|
|
155
|
-
const tar = spawnSync('tar', ['-xzf', archive, '-C', runtimeDir]);
|
|
169
|
+
const tar = spawnSync('tar', ['-xzf', archive, '-C', runtimeDir], SILENT_WINDOWS);
|
|
156
170
|
if (tar.status !== 0) throw new Error('Falha ao extrair o Python portátil (tar).');
|
|
157
171
|
if (!existsSync(pythonBin(home))) {
|
|
158
172
|
throw new Error('Extração do Python concluída, mas o binário não foi encontrado.');
|
|
@@ -162,7 +176,10 @@ async function downloadPython(home, platform) {
|
|
|
162
176
|
|
|
163
177
|
function createVenv(home) {
|
|
164
178
|
console.log('Criando virtualenv...');
|
|
165
|
-
const venv = spawnSync(pythonBin(home), ['-m', 'venv', join(home, '.runtime', 'venv')], {
|
|
179
|
+
const venv = spawnSync(pythonBin(home), ['-m', 'venv', join(home, '.runtime', 'venv')], {
|
|
180
|
+
stdio: 'inherit',
|
|
181
|
+
...SILENT_WINDOWS,
|
|
182
|
+
});
|
|
166
183
|
if (venv.status !== 0) throw new Error('Falha ao criar o virtualenv.');
|
|
167
184
|
if (!existsSync(venvBin(home))) throw new Error('Virtualenv criado, mas o binário não foi encontrado.');
|
|
168
185
|
}
|
|
@@ -173,7 +190,7 @@ function installDeps(home) {
|
|
|
173
190
|
const pip = spawnSync(
|
|
174
191
|
venvBin(home),
|
|
175
192
|
['-m', 'pip', 'install', '--disable-pip-version-check', '-r', req],
|
|
176
|
-
{ stdio: 'inherit' },
|
|
193
|
+
{ stdio: 'inherit', ...SILENT_WINDOWS },
|
|
177
194
|
);
|
|
178
195
|
if (pip.status !== 0) throw new Error('Falha ao instalar as dependências do backend.');
|
|
179
196
|
}
|
|
@@ -1,20 +1,134 @@
|
|
|
1
|
-
// Teste de regressão do isolamento de processos do
|
|
1
|
+
// Teste de regressão do isolamento de processos (regra permanente do AGENTS.md):
|
|
2
|
+
// NENHUM spawn/exec do launcher/instalador pode abrir janela de terminal (CMD,
|
|
3
|
+
// PowerShell, console Python) no computador do usuário.
|
|
2
4
|
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
5
|
+
// Cobre TODOS os pontos de entrada:
|
|
6
|
+
// scripts/start.js — backend uvicorn + taskkill
|
|
7
|
+
// scripts/install.js — tar / venv / pip
|
|
8
|
+
// scripts/build.js — npm.cmd install e run build (shell: true)
|
|
9
|
+
// bin/cli.js — despacho de comandos
|
|
10
|
+
// backend/**/*.py — nenhuma chamada direta a subprocess/os.system
|
|
7
11
|
//
|
|
8
12
|
// Roda em qualquer plataforma, inclusive no CI (Linux/ubuntu) — sem exigir o
|
|
9
13
|
// runtime Python completo.
|
|
10
|
-
import { readFileSync } from 'node:fs';
|
|
14
|
+
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
11
15
|
import { join, dirname } from 'node:path';
|
|
12
16
|
import { fileURLToPath } from 'node:url';
|
|
13
17
|
|
|
14
18
|
const root = join(dirname(fileURLToPath(import.meta.url)), '..');
|
|
15
|
-
const src = readFileSync(join(root, 'scripts', 'start.js'), 'utf8');
|
|
16
19
|
|
|
17
|
-
const
|
|
20
|
+
const falhas = [];
|
|
21
|
+
|
|
22
|
+
function assert(condicao, mensagem) {
|
|
23
|
+
console.log(`${condicao ? 'PASS' : 'FAIL'} — ${mensagem}`);
|
|
24
|
+
if (!condicao) falhas.push(mensagem);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// ---------------------------------------------------------------- utilidades
|
|
28
|
+
|
|
29
|
+
// Extrai o conteúdo entre delimitadores balanceados, começando em `inicio`
|
|
30
|
+
// (índice do delimitador de abertura). Ignora delimitadores dentro de strings.
|
|
31
|
+
function ateOFechamento(src, inicio, abre, fecha) {
|
|
32
|
+
let profundidade = 1;
|
|
33
|
+
let aspa = null;
|
|
34
|
+
let escapando = false;
|
|
35
|
+
for (let i = inicio + 1; i < src.length; i++) {
|
|
36
|
+
const c = src[i];
|
|
37
|
+
if (escapando) {
|
|
38
|
+
escapando = false;
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
if (c === '\\') {
|
|
42
|
+
escapando = true;
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (aspa) {
|
|
46
|
+
if (c === aspa) aspa = null;
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
if (c === "'" || c === '"' || c === '`') {
|
|
50
|
+
aspa = c;
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
if (c === abre) profundidade += 1;
|
|
54
|
+
else if (c === fecha) {
|
|
55
|
+
profundidade -= 1;
|
|
56
|
+
if (profundidade === 0) return src.slice(inicio + 1, i);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return src.slice(inicio + 1);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Texto completo dos argumentos de uma chamada, a partir do '(' encontrado.
|
|
63
|
+
function argumentosDaChamada(src, indiceAbre) {
|
|
64
|
+
return ateOFechamento(src, indiceAbre, '(', ')');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Objetos literais de nível de módulo (const NOME = { ... }) — servem para
|
|
68
|
+
// resolver spreads como `...SILENT_WINDOWS` dentro das opções de spawn.
|
|
69
|
+
function constantesDeModulo(src) {
|
|
70
|
+
const mapa = new Map();
|
|
71
|
+
const regex = /const\s+([A-Za-z_$][\w$]*)\s*=\s*\{/g;
|
|
72
|
+
let m;
|
|
73
|
+
while ((m = regex.exec(src)) !== null) {
|
|
74
|
+
const inicioChaves = m.index + m[0].length - 1;
|
|
75
|
+
mapa.set(m[1], ateOFechamento(src, inicioChaves, '{', '}'));
|
|
76
|
+
}
|
|
77
|
+
return mapa;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Uma referência a constante (solta ou via spread `...NOME`) é silenciosa
|
|
81
|
+
// apenas se a constante NOME trouxer windowsHide.
|
|
82
|
+
function referenciaConstanteSilenciosa(argTexto, constantes) {
|
|
83
|
+
const regex = /([A-Za-z_$][\w$]*)/g;
|
|
84
|
+
let m;
|
|
85
|
+
while ((m = regex.exec(argTexto)) !== null) {
|
|
86
|
+
const definicao = constantes.get(m[1]);
|
|
87
|
+
if (definicao && /windowsHide:\s*true/.test(definicao)) return true;
|
|
88
|
+
}
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// ------------------------------------------------------------ varredura JS
|
|
93
|
+
|
|
94
|
+
const PONTOS_DE_ENTRADA_JS = ['scripts/start.js', 'scripts/install.js', 'scripts/build.js', 'bin/cli.js'];
|
|
95
|
+
|
|
96
|
+
for (const relativo of PONTOS_DE_ENTRADA_JS) {
|
|
97
|
+
const caminho = join(root, relativo);
|
|
98
|
+
if (!existsSync(caminho)) {
|
|
99
|
+
assert(false, `${relativo} — arquivo ausente`);
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
const src = readFileSync(caminho, 'utf8');
|
|
103
|
+
const constantes = constantesDeModulo(src);
|
|
104
|
+
|
|
105
|
+
const chamadas = [...src.matchAll(/\b(spawn|spawnSync|exec|execSync|execFile|execFileSync)\s*\(/g)];
|
|
106
|
+
let silenciosas = 0;
|
|
107
|
+
for (const chamada of chamadas) {
|
|
108
|
+
const indiceAbre = chamada.index + chamada[0].length - 1;
|
|
109
|
+
const texto = argumentosDaChamada(src, indiceAbre);
|
|
110
|
+
if (/windowsHide:\s*true/.test(texto) || referenciaConstanteSilenciosa(texto, constantes)) {
|
|
111
|
+
silenciosas += 1;
|
|
112
|
+
} else {
|
|
113
|
+
const marca = `${chamada[1]}(${texto.slice(0, 60).replace(/\s+/g, ' ')}…)`;
|
|
114
|
+
assert(false, `${relativo} — ${marca} sem windowsHide: true (abre janela de console no usuário)`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (chamadas.length === 0) {
|
|
118
|
+
assert(true, `${relativo} — nenhuma chamada de processo direta (sem risco de janela de console)`);
|
|
119
|
+
} else {
|
|
120
|
+
assert(
|
|
121
|
+
silenciosas === chamadas.length,
|
|
122
|
+
`${relativo} — ${silenciosas}/${chamadas.length} chamadas de processo com windowsHide: true`,
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// ------------------------------------------- isolamento do backend (start.js)
|
|
128
|
+
|
|
129
|
+
const startSrc = readFileSync(join(root, 'scripts', 'start.js'), 'utf8');
|
|
130
|
+
|
|
131
|
+
for (const [regex, descricao] of [
|
|
18
132
|
[/detached:\s*true/, 'detached: true — grupo de processos próprio (CREATE_NEW_PROCESS_GROUP no Windows)'],
|
|
19
133
|
[/windowsHide:\s*true/, 'windowsHide: true — sem janela de console fantasma'],
|
|
20
134
|
[/stdio:\s*\['ignore',\s*logFd,\s*logFd\]/, "stdio: ['ignore', logFd, logFd] — saída do backend no arquivo (fd)"],
|
|
@@ -24,32 +138,81 @@ const verificacoes = [
|
|
|
24
138
|
[/\.unref\(\)/, 'unref() — launcher não acopla o ciclo de vida ao backend'],
|
|
25
139
|
[/taskkill/, 'encerramento no Windows via taskkill /PID /T (árvore de processos)'],
|
|
26
140
|
[/process\.kill/, 'encerramento no Unix via signal no grupo de processos'],
|
|
141
|
+
]) {
|
|
142
|
+
assert(regex.test(startSrc), descricao);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Uvicorn em produção JAMAIS com --reload/--workers: cada worker é um processo
|
|
146
|
+
// filho extra, que no Windows pode alocar console próprio e ignorar as flags
|
|
147
|
+
// do processo pai.
|
|
148
|
+
const uvicornExtras = startSrc.match(/['"]--(?:reload|workers)['"]/g);
|
|
149
|
+
assert(
|
|
150
|
+
!uvicornExtras,
|
|
151
|
+
`uvicorn sem --reload/--workers (nenhum processo filho extra no Windows)${
|
|
152
|
+
uvicornExtras ? ` — encontrado: ${uvicornExtras.join(', ')}` : ''
|
|
153
|
+
}`,
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
// ------------------------------------------------ varredura Python (backend)
|
|
157
|
+
|
|
158
|
+
const PROC_HELPER = join(root, 'backend', 'core', 'proc.py');
|
|
159
|
+
assert(existsSync(PROC_HELPER), 'backend/core/proc.py — helper silencioso existe (ponto único de subprocesso)');
|
|
160
|
+
if (existsSync(PROC_HELPER)) {
|
|
161
|
+
const procSrc = readFileSync(PROC_HELPER, 'utf8');
|
|
162
|
+
assert(/CREATE_NO_WINDOW\s*=\s*0x08000000/.test(procSrc), 'CREATE_NO_WINDOW = 0x08000000 definido no helper');
|
|
163
|
+
assert(/def run_silent_process/.test(procSrc), 'run_silent_process disponível no helper');
|
|
164
|
+
assert(/creationflags/.test(procSrc), 'helper aplica creationflags no Windows');
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Lista recursiva dos .py do backend (sem __pycache__).
|
|
168
|
+
function listarPy(diretorio, acumulador = []) {
|
|
169
|
+
for (const entrada of readdirSync(diretorio, { withFileTypes: true })) {
|
|
170
|
+
if (entrada.name === '__pycache__') continue;
|
|
171
|
+
const caminho = join(diretorio, entrada.name);
|
|
172
|
+
if (entrada.isDirectory()) listarPy(caminho, acumulador);
|
|
173
|
+
else if (entrada.name.endsWith('.py')) acumulador.push(caminho);
|
|
174
|
+
}
|
|
175
|
+
return acumulador;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const PADROES_PROIBIDOS = [
|
|
179
|
+
[/subprocess\.run\(/, 'subprocess.run('],
|
|
180
|
+
[/subprocess\.Popen\(/, 'subprocess.Popen('],
|
|
181
|
+
[/subprocess\.call\(/, 'subprocess.call('],
|
|
182
|
+
[/subprocess\.check_output\(/, 'subprocess.check_output('],
|
|
183
|
+
[/subprocess\.check_call\(/, 'subprocess.check_call('],
|
|
184
|
+
[/os\.system\(/, 'os.system('],
|
|
185
|
+
[/os\.popen\(/, 'os.popen('],
|
|
27
186
|
];
|
|
28
187
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
console.log('PASS — spawn com windowsHide + detached (sem janela de console no usuário)');
|
|
45
|
-
} else {
|
|
46
|
-
console.log(`FAIL — spawn sem windowsHide/detached: ${trecho.slice(0, 80).replace(/\n/g, ' ')}…`);
|
|
47
|
-
falhas += 1;
|
|
188
|
+
const backendDir = join(root, 'backend');
|
|
189
|
+
let arquivosPy = 0;
|
|
190
|
+
let diretasProibidas = 0;
|
|
191
|
+
if (existsSync(backendDir)) {
|
|
192
|
+
for (const arquivo of listarPy(backendDir)) {
|
|
193
|
+
if (arquivo.endsWith(join('core', 'proc.py'))) continue; // helper autorizado
|
|
194
|
+
arquivosPy += 1;
|
|
195
|
+
const conteudo = readFileSync(arquivo, 'utf8');
|
|
196
|
+
const relativo = arquivo.slice(root.length + 1);
|
|
197
|
+
for (const [regex, rotulo] of PADROES_PROIBIDOS) {
|
|
198
|
+
if (regex.test(conteudo)) {
|
|
199
|
+
diretasProibidas += 1;
|
|
200
|
+
assert(false, `${relativo} — uso direto de ${rotulo} sem CREATE_NO_WINDOW (use backend.core.proc)`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
48
203
|
}
|
|
49
204
|
}
|
|
205
|
+
assert(arquivosPy > 0, `backend/ — ${arquivosPy} arquivos .py varridos`);
|
|
206
|
+
assert(
|
|
207
|
+
diretasProibidas === 0,
|
|
208
|
+
'nenhuma chamada direta a subprocess/os.system no backend (todas via helper silencioso)',
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
// -------------------------------------------------------------- veredicto
|
|
50
212
|
|
|
51
|
-
if (falhas
|
|
52
|
-
console.
|
|
53
|
-
process.exit(
|
|
213
|
+
if (falhas.length === 0) {
|
|
214
|
+
console.log('Isolamento de processos: todas as verificações passaram.');
|
|
215
|
+
process.exit(0);
|
|
54
216
|
}
|
|
55
|
-
console.
|
|
217
|
+
console.error(`Isolamento de processos: ${falhas.length} verificação(ões) falharam.`);
|
|
218
|
+
process.exit(1);
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
*,:before,:after{--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: rgb(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: rgb(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: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{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,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,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,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,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{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}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.inset-0{top:0;right:0;bottom:0;left:0}.left-1{left:.25rem}.left-11{left:2.75rem}.left-9{left:2.25rem}.top-1{top:.25rem}.z-50{z-index:50}.mx-2{margin-left:.5rem;margin-right:.5rem}.mx-auto{margin-left:auto;margin-right:auto}.mb-1{margin-bottom:.25rem}.mb-1\.5{margin-bottom:.375rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-5{margin-bottom:1.25rem}.mb-6{margin-bottom:1.5rem}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-6{margin-top:1.5rem}.line-clamp-2{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2}.block{display:block}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.hidden{display:none}.h-10{height:2.5rem}.h-16{height:4rem}.h-2{height:.5rem}.h-40{height:10rem}.h-44{height:11rem}.h-6{height:1.5rem}.h-8{height:2rem}.h-auto{height:auto}.h-full{height:100%}.max-h-48{max-height:12rem}.max-h-56{max-height:14rem}.max-h-\[85vh\]{max-height:85vh}.max-h-\[90vh\]{max-height:90vh}.min-h-screen{min-height:100vh}.w-10{width:2.5rem}.w-16{width:4rem}.w-20{width:5rem}.w-40{width:10rem}.w-44{width:11rem}.w-6{width:1.5rem}.w-60{width:15rem}.w-8{width:2rem}.w-full{width:100%}.min-w-0{min-width:0px}.max-w-2xl{max-width:42rem}.max-w-6xl{max-width:72rem}.max-w-lg{max-width:32rem}.max-w-md{max-width:28rem}.max-w-sm{max-width:24rem}.flex-1{flex:1 1 0%}.shrink-0{flex-shrink:0}.rotate-180{--tw-rotate: 180deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(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)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.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}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-1{gap:.25rem}.gap-2{gap:.5rem}.gap-2\.5{gap:.625rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.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-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-6>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.5rem * var(--tw-space-y-reverse))}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-xl{border-radius:.75rem}.rounded-t-md{border-top-left-radius:.375rem;border-top-right-radius:.375rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-r{border-right-width:1px}.border-t{border-top-width:1px}.border-dashed{border-style:dashed}.border-amber-400\/40{border-color:#fbbf2466}.border-panda\/40{border-color:#22c55e66}.border-red-400\/40{border-color:#f8717166}.border-slate-100{--tw-border-opacity: 1;border-color:rgb(241 245 249 / var(--tw-border-opacity, 1))}.border-slate-200{--tw-border-opacity: 1;border-color:rgb(226 232 240 / var(--tw-border-opacity, 1))}.border-slate-300{--tw-border-opacity: 1;border-color:rgb(203 213 225 / var(--tw-border-opacity, 1))}.bg-amber-100{--tw-bg-opacity: 1;background-color:rgb(254 243 199 / var(--tw-bg-opacity, 1))}.bg-amber-500\/10{background-color:#f59e0b1a}.bg-black\/60{background-color:#0009}.bg-emerald-100{--tw-bg-opacity: 1;background-color:rgb(209 250 229 / var(--tw-bg-opacity, 1))}.bg-night{--tw-bg-opacity: 1;background-color:rgb(11 18 32 / var(--tw-bg-opacity, 1))}.bg-panda{--tw-bg-opacity: 1;background-color:rgb(34 197 94 / var(--tw-bg-opacity, 1))}.bg-panda\/10{background-color:#22c55e1a}.bg-panda\/15{background-color:#22c55e26}.bg-panda\/30{background-color:#22c55e4d}.bg-panda\/70{background-color:#22c55eb3}.bg-red-100{--tw-bg-opacity: 1;background-color:rgb(254 226 226 / var(--tw-bg-opacity, 1))}.bg-red-500\/10{background-color:#ef44441a}.bg-red-600{--tw-bg-opacity: 1;background-color:rgb(220 38 38 / var(--tw-bg-opacity, 1))}.bg-sky-100{--tw-bg-opacity: 1;background-color:rgb(224 242 254 / var(--tw-bg-opacity, 1))}.bg-slate-200{--tw-bg-opacity: 1;background-color:rgb(226 232 240 / var(--tw-bg-opacity, 1))}.bg-slate-300{--tw-bg-opacity: 1;background-color:rgb(203 213 225 / var(--tw-bg-opacity, 1))}.bg-transparent{background-color:transparent}.bg-white{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity, 1))}.object-cover{-o-object-fit:cover;object-fit:cover}.p-3{padding:.75rem}.p-4{padding:1rem}.p-5{padding:1.25rem}.p-6{padding:1.5rem}.px-2\.5{padding-left:.625rem;padding-right:.625rem}.px-3{padding-left:.75rem;padding-right:.75rem}.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-14{padding-top:3.5rem;padding-bottom:3.5rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-2\.5{padding-top:.625rem;padding-bottom:.625rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-5{padding-top:1.25rem;padding-bottom:1.25rem}.pb-2{padding-bottom:.5rem}.pr-4{padding-right:1rem}.pt-2{padding-top:.5rem}.text-left{text-align:left}.text-center{text-align:center}.text-2xl{font-size:1.5rem;line-height:2rem}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-4xl{font-size:2.25rem;line-height:2.5rem}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-extrabold{font-weight:800}.font-medium{font-weight:500}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.leading-none{line-height:1}.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-emerald-300{--tw-text-opacity: 1;color:rgb(110 231 183 / var(--tw-text-opacity, 1))}.text-emerald-700{--tw-text-opacity: 1;color:rgb(4 120 87 / var(--tw-text-opacity, 1))}.text-panda{--tw-text-opacity: 1;color:rgb(34 197 94 / var(--tw-text-opacity, 1))}.text-panda-dark{--tw-text-opacity: 1;color:rgb(22 163 74 / var(--tw-text-opacity, 1))}.text-red-400{--tw-text-opacity: 1;color:rgb(248 113 113 / var(--tw-text-opacity, 1))}.text-red-600{--tw-text-opacity: 1;color:rgb(220 38 38 / var(--tw-text-opacity, 1))}.text-sky-700{--tw-text-opacity: 1;color:rgb(3 105 161 / var(--tw-text-opacity, 1))}.text-slate-400{--tw-text-opacity: 1;color:rgb(148 163 184 / var(--tw-text-opacity, 1))}.text-slate-500{--tw-text-opacity: 1;color:rgb(100 116 139 / var(--tw-text-opacity, 1))}.text-slate-600{--tw-text-opacity: 1;color:rgb(71 85 105 / var(--tw-text-opacity, 1))}.text-slate-700{--tw-text-opacity: 1;color:rgb(51 65 85 / var(--tw-text-opacity, 1))}.text-slate-800{--tw-text-opacity: 1;color:rgb(30 41 59 / var(--tw-text-opacity, 1))}.text-slate-900{--tw-text-opacity: 1;color:rgb(15 23 42 / var(--tw-text-opacity, 1))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.line-through{text-decoration-line:line-through}.placeholder-slate-400::-moz-placeholder{--tw-placeholder-opacity: 1;color:rgb(148 163 184 / var(--tw-placeholder-opacity, 1))}.placeholder-slate-400::placeholder{--tw-placeholder-opacity: 1;color:rgb(148 163 184 / var(--tw-placeholder-opacity, 1))}.shadow{--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / .1), 0 1px 2px -1px rgb(0 0 0 / .1);--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-sm{--tw-shadow: 0 1px 2px 0 rgb(0 0 0 / .05);--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)}.outline-none{outline:2px solid transparent;outline-offset:2px}.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)}.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-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}body{--tw-bg-opacity: 1;background-color:rgb(11 18 32 / var(--tw-bg-opacity, 1));font-family:Inter,system-ui,sans-serif;--tw-text-opacity: 1;color:rgb(226 232 240 / var(--tw-text-opacity, 1));-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}html:not(.dark) body{--tw-bg-opacity: 1;background-color:rgb(241 245 249 / var(--tw-bg-opacity, 1));--tw-text-opacity: 1;color:rgb(30 41 59 / var(--tw-text-opacity, 1))}.last\:border-0:last-child{border-width:0px}.hover\:border-slate-400:hover{--tw-border-opacity: 1;border-color:rgb(148 163 184 / var(--tw-border-opacity, 1))}.hover\:bg-panda-dark:hover{--tw-bg-opacity: 1;background-color:rgb(22 163 74 / var(--tw-bg-opacity, 1))}.hover\:bg-red-700:hover{--tw-bg-opacity: 1;background-color:rgb(185 28 28 / var(--tw-bg-opacity, 1))}.hover\:bg-slate-100:hover{--tw-bg-opacity: 1;background-color:rgb(241 245 249 / var(--tw-bg-opacity, 1))}.hover\:text-slate-800:hover{--tw-text-opacity: 1;color:rgb(30 41 59 / var(--tw-text-opacity, 1))}.hover\:underline:hover{text-decoration-line:underline}.focus\:border-panda:focus{--tw-border-opacity: 1;border-color:rgb(34 197 94 / var(--tw-border-opacity, 1))}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-40:disabled{opacity:.4}.dark\:border-slate-700:is(.dark *){--tw-border-opacity: 1;border-color:rgb(51 65 85 / var(--tw-border-opacity, 1))}.dark\:border-slate-800:is(.dark *){--tw-border-opacity: 1;border-color:rgb(30 41 59 / var(--tw-border-opacity, 1))}.dark\:bg-amber-500\/15:is(.dark *){background-color:#f59e0b26}.dark\:bg-card:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(16 26 44 / var(--tw-bg-opacity, 1))}.dark\:bg-emerald-500\/15:is(.dark *){background-color:#10b98126}.dark\:bg-night:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(11 18 32 / var(--tw-bg-opacity, 1))}.dark\:bg-red-500\/15:is(.dark *){background-color:#ef444426}.dark\:bg-sky-500\/15:is(.dark *){background-color:#0ea5e926}.dark\:bg-slate-700:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(51 65 85 / var(--tw-bg-opacity, 1))}.dark\:bg-slate-700\/60:is(.dark *){background-color:#33415599}.dark\:bg-slate-800:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(30 41 59 / var(--tw-bg-opacity, 1))}.dark\:bg-slate-900:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(15 23 42 / var(--tw-bg-opacity, 1))}.dark\:text-amber-400:is(.dark *){--tw-text-opacity: 1;color:rgb(251 191 36 / 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-panda:is(.dark *){--tw-text-opacity: 1;color:rgb(34 197 94 / var(--tw-text-opacity, 1))}.dark\:text-red-400:is(.dark *){--tw-text-opacity: 1;color:rgb(248 113 113 / var(--tw-text-opacity, 1))}.dark\:text-sky-400:is(.dark *){--tw-text-opacity: 1;color:rgb(56 189 248 / var(--tw-text-opacity, 1))}.dark\:text-slate-200:is(.dark *){--tw-text-opacity: 1;color:rgb(226 232 240 / var(--tw-text-opacity, 1))}.dark\:text-slate-300:is(.dark *){--tw-text-opacity: 1;color:rgb(203 213 225 / var(--tw-text-opacity, 1))}.dark\:text-slate-400:is(.dark *){--tw-text-opacity: 1;color:rgb(148 163 184 / var(--tw-text-opacity, 1))}.dark\:text-slate-500:is(.dark *){--tw-text-opacity: 1;color:rgb(100 116 139 / var(--tw-text-opacity, 1))}.dark\:text-slate-600:is(.dark *){--tw-text-opacity: 1;color:rgb(71 85 105 / var(--tw-text-opacity, 1))}.dark\:placeholder-slate-500:is(.dark *)::-moz-placeholder{--tw-placeholder-opacity: 1;color:rgb(100 116 139 / var(--tw-placeholder-opacity, 1))}.dark\:placeholder-slate-500:is(.dark *)::placeholder{--tw-placeholder-opacity: 1;color:rgb(100 116 139 / var(--tw-placeholder-opacity, 1))}.dark\:hover\:border-slate-500:hover:is(.dark *){--tw-border-opacity: 1;border-color:rgb(100 116 139 / var(--tw-border-opacity, 1))}.dark\:hover\:bg-slate-800\/60:hover:is(.dark *){background-color:#1e293b99}.dark\:hover\:text-slate-200:hover:is(.dark *){--tw-text-opacity: 1;color:rgb(226 232 240 / var(--tw-text-opacity, 1))}@media (min-width: 640px){.sm\:inline{display:inline}.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}@media (min-width: 768px){.md\:flex{display:flex}.md\:hidden{display:none}.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}@media (min-width: 1024px){.lg\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}}@media (min-width: 1280px){.xl\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}
|