diff --git a/assets/dist/app.min.css b/assets/dist/app.min.css index 4c72cd7..b9bd760 100644 --- a/assets/dist/app.min.css +++ b/assets/dist/app.min.css @@ -1,2 +1,2 @@ /*! tailwindcss v4.1.18 | MIT License | https://tailwindcss.com */ -@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-space-y-reverse:0;--tw-divide-y-reverse:0;--tw-border-style:solid;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-font-weight:initial;--tw-tracking:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-duration:initial;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-red-50:oklch(97.1% .013 17.38);--color-red-100:oklch(93.6% .032 17.717);--color-red-200:oklch(88.5% .062 18.334);--color-red-300:oklch(80.8% .114 19.571);--color-red-400:oklch(70.4% .191 22.216);--color-red-500:oklch(63.7% .237 25.331);--color-red-600:oklch(57.7% .245 27.325);--color-red-700:oklch(50.5% .213 27.518);--color-red-800:oklch(44.4% .177 26.899);--color-red-900:oklch(39.6% .141 25.723);--color-amber-50:oklch(98.7% .022 95.277);--color-amber-100:oklch(96.2% .059 95.617);--color-amber-200:oklch(92.4% .12 95.746);--color-amber-300:oklch(87.9% .169 91.605);--color-amber-400:oklch(82.8% .189 84.429);--color-amber-500:oklch(76.9% .188 70.08);--color-amber-600:oklch(66.6% .179 58.318);--color-amber-700:oklch(55.5% .163 48.998);--color-amber-800:oklch(47.3% .137 46.201);--color-amber-900:oklch(41.4% .112 45.904);--color-yellow-50:oklch(98.7% .026 102.212);--color-yellow-100:oklch(97.3% .071 103.193);--color-yellow-200:oklch(94.5% .129 101.54);--color-yellow-300:oklch(90.5% .182 98.111);--color-yellow-500:oklch(79.5% .184 86.047);--color-yellow-600:oklch(68.1% .162 75.834);--color-yellow-800:oklch(47.6% .114 61.907);--color-yellow-900:oklch(42.1% .095 57.708);--color-green-50:oklch(98.2% .018 155.826);--color-green-100:oklch(96.2% .044 156.743);--color-green-200:oklch(92.5% .084 155.995);--color-green-300:oklch(87.1% .15 154.449);--color-green-400:oklch(79.2% .209 151.711);--color-green-500:oklch(72.3% .219 149.579);--color-green-600:oklch(62.7% .194 149.214);--color-green-700:oklch(52.7% .154 150.069);--color-green-800:oklch(44.8% .119 151.328);--color-green-900:oklch(39.3% .095 152.535);--color-cyan-100:oklch(95.6% .045 203.388);--color-cyan-200:oklch(91.7% .08 205.041);--color-cyan-500:oklch(71.5% .143 215.221);--color-cyan-600:oklch(60.9% .126 221.723);--color-cyan-900:oklch(39.8% .07 227.392);--color-blue-50:oklch(97% .014 254.604);--color-blue-100:oklch(93.2% .032 255.585);--color-blue-200:oklch(88.2% .059 254.128);--color-blue-300:oklch(80.9% .105 251.813);--color-blue-400:oklch(70.7% .165 254.624);--color-blue-500:oklch(62.3% .214 259.815);--color-blue-600:oklch(54.6% .245 262.881);--color-blue-700:oklch(48.8% .243 264.376);--color-blue-800:oklch(42.4% .199 265.638);--color-blue-900:oklch(37.9% .146 265.522);--color-indigo-100:oklch(93% .034 272.788);--color-indigo-500:oklch(58.5% .233 277.117);--color-indigo-600:oklch(51.1% .262 276.966);--color-indigo-900:oklch(35.9% .144 278.697);--color-purple-100:oklch(94.6% .033 307.174);--color-purple-200:oklch(90.2% .063 306.703);--color-purple-600:oklch(55.8% .288 302.321);--color-purple-800:oklch(43.8% .218 303.724);--color-purple-900:oklch(38.1% .176 304.987);--color-rose-900:oklch(41% .159 10.272);--color-gray-50:oklch(98.5% .002 247.839);--color-gray-100:oklch(96.7% .003 264.542);--color-gray-200:oklch(92.8% .006 264.531);--color-gray-300:oklch(87.2% .01 258.338);--color-gray-400:oklch(70.7% .022 261.325);--color-gray-500:oklch(55.1% .027 264.364);--color-gray-600:oklch(44.6% .03 256.802);--color-gray-700:oklch(37.3% .034 259.733);--color-gray-800:oklch(27.8% .033 256.848);--color-gray-900:oklch(21% .034 264.665);--color-black:#000;--color-white:#fff;--spacing:.25rem;--container-md:28rem;--container-lg:32rem;--container-2xl:42rem;--container-4xl:56rem;--container-7xl:80rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-base:1rem;--text-base--line-height:calc(1.5/1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75/1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2/1.5);--text-6xl:3.75rem;--text-6xl--line-height:1;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--tracking-wide:.025em;--tracking-wider:.05em;--radius-md:.375rem;--radius-lg:.5rem;--radius-xl:.75rem;--animate-spin:spin 1s linear infinite;--animate-pulse:pulse 2s cubic-bezier(.4,0,.6,1)infinite;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--animate-progress-stripe:progress-stripe 1s linear infinite}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}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;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);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}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.pointer-events-none{pointer-events:none}.visible{visibility:visible}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.sticky{position:sticky}.inset-0{inset:calc(var(--spacing)*0)}.top-0{top:calc(var(--spacing)*0)}.z-50{z-index:50}.\!container{width:100%!important}@media (min-width:40rem){.\!container{max-width:40rem!important}}@media (min-width:48rem){.\!container{max-width:48rem!important}}@media (min-width:64rem){.\!container{max-width:64rem!important}}@media (min-width:80rem){.\!container{max-width:80rem!important}}@media (min-width:96rem){.\!container{max-width:96rem!important}}.container{width:100%}@media (min-width:40rem){.container{max-width:40rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}.-mx-1\.5{margin-inline:calc(var(--spacing)*-1.5)}.mx-2{margin-inline:calc(var(--spacing)*2)}.mx-auto{margin-inline:auto}.-my-1\.5{margin-block:calc(var(--spacing)*-1.5)}.my-6{margin-block:calc(var(--spacing)*6)}.mt-1{margin-top:calc(var(--spacing)*1)}.mt-2{margin-top:calc(var(--spacing)*2)}.mt-3{margin-top:calc(var(--spacing)*3)}.mt-4{margin-top:calc(var(--spacing)*4)}.mr-1{margin-right:calc(var(--spacing)*1)}.mr-2{margin-right:calc(var(--spacing)*2)}.mb-1{margin-bottom:calc(var(--spacing)*1)}.mb-2{margin-bottom:calc(var(--spacing)*2)}.mb-3{margin-bottom:calc(var(--spacing)*3)}.mb-4{margin-bottom:calc(var(--spacing)*4)}.mb-6{margin-bottom:calc(var(--spacing)*6)}.ml-1{margin-left:calc(var(--spacing)*1)}.ml-2{margin-left:calc(var(--spacing)*2)}.ml-3{margin-left:calc(var(--spacing)*3)}.ml-4{margin-left:calc(var(--spacing)*4)}.ml-auto{margin-left:auto}.block{display:block}.contents{display:contents}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.table{display:table}.h-2{height:calc(var(--spacing)*2)}.h-2\.5{height:calc(var(--spacing)*2.5)}.h-4{height:calc(var(--spacing)*4)}.h-5{height:calc(var(--spacing)*5)}.h-6{height:calc(var(--spacing)*6)}.h-8{height:calc(var(--spacing)*8)}.h-10{height:calc(var(--spacing)*10)}.h-12{height:calc(var(--spacing)*12)}.h-16{height:calc(var(--spacing)*16)}.h-24{height:calc(var(--spacing)*24)}.h-64{height:calc(var(--spacing)*64)}.h-full{height:100%}.max-h-\[60vh\]{max-height:60vh}.max-h-\[400px\]{max-height:400px}.min-h-\[60vh\]{min-height:60vh}.min-h-\[400px\]{min-height:400px}.min-h-full{min-height:100%}.min-h-screen{min-height:100vh}.w-2{width:calc(var(--spacing)*2)}.w-4{width:calc(var(--spacing)*4)}.w-5{width:calc(var(--spacing)*5)}.w-6{width:calc(var(--spacing)*6)}.w-8{width:calc(var(--spacing)*8)}.w-10{width:calc(var(--spacing)*10)}.w-12{width:calc(var(--spacing)*12)}.w-16{width:calc(var(--spacing)*16)}.w-24{width:calc(var(--spacing)*24)}.w-full{width:100%}.max-w-2xl{max-width:var(--container-2xl)}.max-w-4xl{max-width:var(--container-4xl)}.max-w-7xl{max-width:var(--container-7xl)}.max-w-\[90vw\]{max-width:90vw}.max-w-\[200px\]{max-width:200px}.max-w-lg{max-width:var(--container-lg)}.max-w-md{max-width:var(--container-md)}.min-w-0{min-width:calc(var(--spacing)*0)}.min-w-\[44px\]{min-width:44px}.flex-1{flex:1}.flex-shrink-0{flex-shrink:0}.-translate-y-1{--tw-translate-y:calc(var(--spacing)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.rotate-180{rotate:180deg}.transform{transform:var(--tw-rotate-x,)var(--tw-rotate-y,)var(--tw-rotate-z,)var(--tw-skew-x,)var(--tw-skew-y,)}.animate-progress-stripe{animation:var(--animate-progress-stripe)}.animate-pulse{animation:var(--animate-pulse)}.animate-spin{animation:var(--animate-spin)}.cursor-help{cursor:help}.cursor-pointer{cursor:pointer}.resize{resize:both}.list-inside{list-style-position:inside}.list-decimal{list-style-type:decimal}.list-disc{list-style-type:disc}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.gap-1{gap:calc(var(--spacing)*1)}.gap-2{gap:calc(var(--spacing)*2)}.gap-3{gap:calc(var(--spacing)*3)}.gap-4{gap:calc(var(--spacing)*4)}.gap-6{gap:calc(var(--spacing)*6)}:where(.space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*1)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*1)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*2)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-3>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*3)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*3)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*4)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*4)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-6>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*6)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*6)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-8>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*8)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*8)*calc(1 - var(--tw-space-y-reverse)))}:where(.divide-y>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px*var(--tw-divide-y-reverse));border-bottom-width:calc(1px*calc(1 - var(--tw-divide-y-reverse)))}:where(.divide-gray-200>:not(:last-child)){border-color:var(--color-gray-200)}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.rounded-xl{border-radius:var(--radius-xl)}.border{border-style:var(--tw-border-style);border-width:1px}.border-2{border-style:var(--tw-border-style);border-width:2px}.border-4{border-style:var(--tw-border-style);border-width:4px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-b-2{border-bottom-style:var(--tw-border-style);border-bottom-width:2px}.border-dashed{--tw-border-style:dashed;border-style:dashed}.border-blue-200{border-color:var(--color-blue-200)}.border-blue-500{border-color:var(--color-blue-500)}.border-gray-200{border-color:var(--color-gray-200)}.border-gray-300{border-color:var(--color-gray-300)}.border-green-200{border-color:var(--color-green-200)}.border-red-200{border-color:var(--color-red-200)}.border-transparent{border-color:#0000}.border-white{border-color:var(--color-white)}.border-yellow-200{border-color:var(--color-yellow-200)}.border-t-blue-600{border-top-color:var(--color-blue-600)}.bg-amber-50{background-color:var(--color-amber-50)}.bg-amber-100{background-color:var(--color-amber-100)}.bg-amber-500{background-color:var(--color-amber-500)}.bg-black{background-color:var(--color-black)}.bg-black\/50{background-color:#00000080}@supports (color:color-mix(in lab, red, red)){.bg-black\/50{background-color:color-mix(in oklab,var(--color-black)50%,transparent)}}.bg-blue-50{background-color:var(--color-blue-50)}.bg-blue-100{background-color:var(--color-blue-100)}.bg-blue-600{background-color:var(--color-blue-600)}.bg-cyan-100{background-color:var(--color-cyan-100)}.bg-cyan-500{background-color:var(--color-cyan-500)}.bg-gray-50{background-color:var(--color-gray-50)}.bg-gray-100{background-color:var(--color-gray-100)}.bg-gray-200{background-color:var(--color-gray-200)}.bg-gray-900{background-color:var(--color-gray-900)}.bg-green-50{background-color:var(--color-green-50)}.bg-green-100{background-color:var(--color-green-100)}.bg-green-500{background-color:var(--color-green-500)}.bg-green-600{background-color:var(--color-green-600)}.bg-indigo-100{background-color:var(--color-indigo-100)}.bg-purple-100{background-color:var(--color-purple-100)}.bg-red-50{background-color:var(--color-red-50)}.bg-red-100{background-color:var(--color-red-100)}.bg-red-200{background-color:var(--color-red-200)}.bg-red-500{background-color:var(--color-red-500)}.bg-red-600{background-color:var(--color-red-600)}.bg-transparent{background-color:#0000}.bg-white{background-color:var(--color-white)}.bg-white\/90{background-color:#ffffffe6}@supports (color:color-mix(in lab, red, red)){.bg-white\/90{background-color:color-mix(in oklab,var(--color-white)90%,transparent)}}.bg-yellow-50{background-color:var(--color-yellow-50)}.bg-yellow-100{background-color:var(--color-yellow-100)}.bg-yellow-500{background-color:var(--color-yellow-500)}.bg-gradient-to-r{--tw-gradient-position:to right in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.from-blue-500{--tw-gradient-from:var(--color-blue-500);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.from-cyan-600{--tw-gradient-from:var(--color-cyan-600);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.to-blue-600{--tw-gradient-to:var(--color-blue-600);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.to-rose-900{--tw-gradient-to:var(--color-rose-900);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.bg-\[length\:1rem_1rem\]{background-size:1rem 1rem}.fill-current{fill:currentColor}.fill-indigo-500{fill:var(--color-indigo-500)}.p-0{padding:calc(var(--spacing)*0)}.p-0\.5{padding:calc(var(--spacing)*.5)}.p-1{padding:calc(var(--spacing)*1)}.p-1\.5{padding:calc(var(--spacing)*1.5)}.p-2{padding:calc(var(--spacing)*2)}.p-3{padding:calc(var(--spacing)*3)}.p-4{padding:calc(var(--spacing)*4)}.p-6{padding:calc(var(--spacing)*6)}.p-8{padding:calc(var(--spacing)*8)}.p-12{padding:calc(var(--spacing)*12)}.px-1{padding-inline:calc(var(--spacing)*1)}.px-1\.5{padding-inline:calc(var(--spacing)*1.5)}.px-2{padding-inline:calc(var(--spacing)*2)}.px-2\.5{padding-inline:calc(var(--spacing)*2.5)}.px-3{padding-inline:calc(var(--spacing)*3)}.px-4{padding-inline:calc(var(--spacing)*4)}.px-6{padding-inline:calc(var(--spacing)*6)}.px-8{padding-inline:calc(var(--spacing)*8)}.py-0{padding-block:calc(var(--spacing)*0)}.py-0\.5{padding-block:calc(var(--spacing)*.5)}.py-1{padding-block:calc(var(--spacing)*1)}.py-1\.5{padding-block:calc(var(--spacing)*1.5)}.py-2{padding-block:calc(var(--spacing)*2)}.py-3{padding-block:calc(var(--spacing)*3)}.py-4{padding-block:calc(var(--spacing)*4)}.py-6{padding-block:calc(var(--spacing)*6)}.py-8{padding-block:calc(var(--spacing)*8)}.py-12{padding-block:calc(var(--spacing)*12)}.pt-4{padding-top:calc(var(--spacing)*4)}.text-center{text-align:center}.text-left{text-align:left}.text-right{text-align:right}.font-mono{font-family:var(--font-mono)}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-6xl{font-size:var(--text-6xl);line-height:var(--tw-leading,var(--text-6xl--line-height))}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-wide{--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide)}.tracking-wider{--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider)}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-line{white-space:pre-line}.whitespace-pre-wrap{white-space:pre-wrap}.text-amber-600{color:var(--color-amber-600)}.text-amber-700{color:var(--color-amber-700)}.text-amber-800{color:var(--color-amber-800)}.text-blue-500{color:var(--color-blue-500)}.text-blue-600{color:var(--color-blue-600)}.text-blue-700{color:var(--color-blue-700)}.text-blue-800{color:var(--color-blue-800)}.text-cyan-100{color:var(--color-cyan-100)}.text-cyan-600{color:var(--color-cyan-600)}.text-gray-100{color:var(--color-gray-100)}.text-gray-400{color:var(--color-gray-400)}.text-gray-500{color:var(--color-gray-500)}.text-gray-600{color:var(--color-gray-600)}.text-gray-700{color:var(--color-gray-700)}.text-gray-800{color:var(--color-gray-800)}.text-gray-900{color:var(--color-gray-900)}.text-green-500{color:var(--color-green-500)}.text-green-600{color:var(--color-green-600)}.text-green-700{color:var(--color-green-700)}.text-green-800{color:var(--color-green-800)}.text-indigo-600{color:var(--color-indigo-600)}.text-purple-600{color:var(--color-purple-600)}.text-purple-800{color:var(--color-purple-800)}.text-red-500{color:var(--color-red-500)}.text-red-600{color:var(--color-red-600)}.text-red-700{color:var(--color-red-700)}.text-red-800{color:var(--color-red-800)}.text-white{color:var(--color-white)}.text-yellow-500{color:var(--color-yellow-500)}.text-yellow-600{color:var(--color-yellow-600)}.text-yellow-800{color:var(--color-yellow-800)}.uppercase{text-transform:uppercase}.italic{font-style:italic}.no-underline{text-decoration-line:none}.opacity-0{opacity:0}.opacity-25{opacity:.25}.opacity-50{opacity:.5}.opacity-75{opacity:.75}.opacity-80{opacity:.8}.opacity-100{opacity:1}.shadow{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-2xl{--tw-shadow:0 25px 50px -12px var(--tw-shadow-color,#00000040);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-md{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a),0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-xl{--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a),0 8px 10px -6px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.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,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-150{--tw-duration:.15s;transition-duration:.15s}.duration-200{--tw-duration:.2s;transition-duration:.2s}.duration-300{--tw-duration:.3s;transition-duration:.3s}.duration-500{--tw-duration:.5s;transition-duration:.5s}.even\:bg-gray-50:nth-child(2n){background-color:var(--color-gray-50)}@media (hover:hover){.hover\:scale-105:hover{--tw-scale-x:105%;--tw-scale-y:105%;--tw-scale-z:105%;scale:var(--tw-scale-x)var(--tw-scale-y)}.hover\:border-gray-400:hover{border-color:var(--color-gray-400)}.hover\:bg-amber-600:hover{background-color:var(--color-amber-600)}.hover\:bg-black\/10:hover{background-color:#0000001a}@supports (color:color-mix(in lab, red, red)){.hover\:bg-black\/10:hover{background-color:color-mix(in oklab,var(--color-black)10%,transparent)}}.hover\:bg-blue-700:hover{background-color:var(--color-blue-700)}.hover\:bg-cyan-600:hover{background-color:var(--color-cyan-600)}.hover\:bg-gray-50:hover{background-color:var(--color-gray-50)}.hover\:bg-gray-100:hover{background-color:var(--color-gray-100)}.hover\:bg-gray-200:hover{background-color:var(--color-gray-200)}.hover\:bg-green-600:hover{background-color:var(--color-green-600)}.hover\:bg-green-700:hover{background-color:var(--color-green-700)}.hover\:bg-red-50:hover{background-color:var(--color-red-50)}.hover\:bg-red-700:hover{background-color:var(--color-red-700)}.hover\:text-gray-600:hover{color:var(--color-gray-600)}.hover\:text-gray-700:hover{color:var(--color-gray-700)}.hover\:text-red-700:hover{color:var(--color-red-700)}.hover\:underline:hover{text-decoration-line:underline}.hover\:opacity-90:hover{opacity:.9}}.focus\:ring-2:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:ring-blue-500:focus{--tw-ring-color:var(--color-blue-500)}.focus\:ring-offset-2:focus{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color)}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}.active\:scale-95:active{--tw-scale-x:95%;--tw-scale-y:95%;--tw-scale-z:95%;scale:var(--tw-scale-x)var(--tw-scale-y)}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-50:disabled{opacity:.5}@media (min-width:40rem){.sm\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.sm\:flex-row{flex-direction:row}.sm\:px-6{padding-inline:calc(var(--spacing)*6)}}@media (min-width:48rem){.md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.md\:grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}}@media (min-width:64rem){.lg\:grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}.lg\:px-8{padding-inline:calc(var(--spacing)*8)}}:where(.dark\:divide-gray-700:where([data-theme=dark],[data-theme=dark] *)>:not(:last-child)){border-color:var(--color-gray-700)}.dark\:border-blue-800:where([data-theme=dark],[data-theme=dark] *){border-color:var(--color-blue-800)}.dark\:border-gray-600:where([data-theme=dark],[data-theme=dark] *){border-color:var(--color-gray-600)}.dark\:border-gray-700:where([data-theme=dark],[data-theme=dark] *){border-color:var(--color-gray-700)}.dark\:border-green-800:where([data-theme=dark],[data-theme=dark] *){border-color:var(--color-green-800)}.dark\:border-red-800:where([data-theme=dark],[data-theme=dark] *){border-color:var(--color-red-800)}.dark\:border-yellow-800:where([data-theme=dark],[data-theme=dark] *){border-color:var(--color-yellow-800)}.dark\:bg-amber-900\/20:where([data-theme=dark],[data-theme=dark] *){background-color:#7b330633}@supports (color:color-mix(in lab, red, red)){.dark\:bg-amber-900\/20:where([data-theme=dark],[data-theme=dark] *){background-color:color-mix(in oklab,var(--color-amber-900)20%,transparent)}}.dark\:bg-amber-900\/30:where([data-theme=dark],[data-theme=dark] *){background-color:#7b33064d}@supports (color:color-mix(in lab, red, red)){.dark\:bg-amber-900\/30:where([data-theme=dark],[data-theme=dark] *){background-color:color-mix(in oklab,var(--color-amber-900)30%,transparent)}}.dark\:bg-amber-900\/50:where([data-theme=dark],[data-theme=dark] *){background-color:#7b330680}@supports (color:color-mix(in lab, red, red)){.dark\:bg-amber-900\/50:where([data-theme=dark],[data-theme=dark] *){background-color:color-mix(in oklab,var(--color-amber-900)50%,transparent)}}.dark\:bg-blue-800:where([data-theme=dark],[data-theme=dark] *){background-color:var(--color-blue-800)}.dark\:bg-blue-800\/30:where([data-theme=dark],[data-theme=dark] *){background-color:#193cb84d}@supports (color:color-mix(in lab, red, red)){.dark\:bg-blue-800\/30:where([data-theme=dark],[data-theme=dark] *){background-color:color-mix(in oklab,var(--color-blue-800)30%,transparent)}}.dark\:bg-blue-900\/20:where([data-theme=dark],[data-theme=dark] *){background-color:#1c398e33}@supports (color:color-mix(in lab, red, red)){.dark\:bg-blue-900\/20:where([data-theme=dark],[data-theme=dark] *){background-color:color-mix(in oklab,var(--color-blue-900)20%,transparent)}}.dark\:bg-blue-900\/30:where([data-theme=dark],[data-theme=dark] *){background-color:#1c398e4d}@supports (color:color-mix(in lab, red, red)){.dark\:bg-blue-900\/30:where([data-theme=dark],[data-theme=dark] *){background-color:color-mix(in oklab,var(--color-blue-900)30%,transparent)}}.dark\:bg-blue-900\/50:where([data-theme=dark],[data-theme=dark] *){background-color:#1c398e80}@supports (color:color-mix(in lab, red, red)){.dark\:bg-blue-900\/50:where([data-theme=dark],[data-theme=dark] *){background-color:color-mix(in oklab,var(--color-blue-900)50%,transparent)}}.dark\:bg-cyan-900\/30:where([data-theme=dark],[data-theme=dark] *){background-color:#104e644d}@supports (color:color-mix(in lab, red, red)){.dark\:bg-cyan-900\/30:where([data-theme=dark],[data-theme=dark] *){background-color:color-mix(in oklab,var(--color-cyan-900)30%,transparent)}}.dark\:bg-gray-600:where([data-theme=dark],[data-theme=dark] *){background-color:var(--color-gray-600)}.dark\:bg-gray-700:where([data-theme=dark],[data-theme=dark] *){background-color:var(--color-gray-700)}.dark\:bg-gray-800:where([data-theme=dark],[data-theme=dark] *){background-color:var(--color-gray-800)}.dark\:bg-gray-900:where([data-theme=dark],[data-theme=dark] *){background-color:var(--color-gray-900)}.dark\:bg-green-900\/20:where([data-theme=dark],[data-theme=dark] *){background-color:#0d542b33}@supports (color:color-mix(in lab, red, red)){.dark\:bg-green-900\/20:where([data-theme=dark],[data-theme=dark] *){background-color:color-mix(in oklab,var(--color-green-900)20%,transparent)}}.dark\:bg-green-900\/30:where([data-theme=dark],[data-theme=dark] *){background-color:#0d542b4d}@supports (color:color-mix(in lab, red, red)){.dark\:bg-green-900\/30:where([data-theme=dark],[data-theme=dark] *){background-color:color-mix(in oklab,var(--color-green-900)30%,transparent)}}.dark\:bg-green-900\/40:where([data-theme=dark],[data-theme=dark] *){background-color:#0d542b66}@supports (color:color-mix(in lab, red, red)){.dark\:bg-green-900\/40:where([data-theme=dark],[data-theme=dark] *){background-color:color-mix(in oklab,var(--color-green-900)40%,transparent)}}.dark\:bg-green-900\/50:where([data-theme=dark],[data-theme=dark] *){background-color:#0d542b80}@supports (color:color-mix(in lab, red, red)){.dark\:bg-green-900\/50:where([data-theme=dark],[data-theme=dark] *){background-color:color-mix(in oklab,var(--color-green-900)50%,transparent)}}.dark\:bg-indigo-900\/30:where([data-theme=dark],[data-theme=dark] *){background-color:#312c854d}@supports (color:color-mix(in lab, red, red)){.dark\:bg-indigo-900\/30:where([data-theme=dark],[data-theme=dark] *){background-color:color-mix(in oklab,var(--color-indigo-900)30%,transparent)}}.dark\:bg-purple-900\/30:where([data-theme=dark],[data-theme=dark] *){background-color:#59168b4d}@supports (color:color-mix(in lab, red, red)){.dark\:bg-purple-900\/30:where([data-theme=dark],[data-theme=dark] *){background-color:color-mix(in oklab,var(--color-purple-900)30%,transparent)}}.dark\:bg-purple-900\/50:where([data-theme=dark],[data-theme=dark] *){background-color:#59168b80}@supports (color:color-mix(in lab, red, red)){.dark\:bg-purple-900\/50:where([data-theme=dark],[data-theme=dark] *){background-color:color-mix(in oklab,var(--color-purple-900)50%,transparent)}}.dark\:bg-red-800:where([data-theme=dark],[data-theme=dark] *){background-color:var(--color-red-800)}.dark\:bg-red-900\/20:where([data-theme=dark],[data-theme=dark] *){background-color:#82181a33}@supports (color:color-mix(in lab, red, red)){.dark\:bg-red-900\/20:where([data-theme=dark],[data-theme=dark] *){background-color:color-mix(in oklab,var(--color-red-900)20%,transparent)}}.dark\:bg-red-900\/30:where([data-theme=dark],[data-theme=dark] *){background-color:#82181a4d}@supports (color:color-mix(in lab, red, red)){.dark\:bg-red-900\/30:where([data-theme=dark],[data-theme=dark] *){background-color:color-mix(in oklab,var(--color-red-900)30%,transparent)}}.dark\:bg-red-900\/40:where([data-theme=dark],[data-theme=dark] *){background-color:#82181a66}@supports (color:color-mix(in lab, red, red)){.dark\:bg-red-900\/40:where([data-theme=dark],[data-theme=dark] *){background-color:color-mix(in oklab,var(--color-red-900)40%,transparent)}}.dark\:bg-red-900\/50:where([data-theme=dark],[data-theme=dark] *){background-color:#82181a80}@supports (color:color-mix(in lab, red, red)){.dark\:bg-red-900\/50:where([data-theme=dark],[data-theme=dark] *){background-color:color-mix(in oklab,var(--color-red-900)50%,transparent)}}.dark\:bg-yellow-900\/20:where([data-theme=dark],[data-theme=dark] *){background-color:#733e0a33}@supports (color:color-mix(in lab, red, red)){.dark\:bg-yellow-900\/20:where([data-theme=dark],[data-theme=dark] *){background-color:color-mix(in oklab,var(--color-yellow-900)20%,transparent)}}.dark\:bg-yellow-900\/30:where([data-theme=dark],[data-theme=dark] *){background-color:#733e0a4d}@supports (color:color-mix(in lab, red, red)){.dark\:bg-yellow-900\/30:where([data-theme=dark],[data-theme=dark] *){background-color:color-mix(in oklab,var(--color-yellow-900)30%,transparent)}}.dark\:text-amber-200:where([data-theme=dark],[data-theme=dark] *){color:var(--color-amber-200)}.dark\:text-amber-300:where([data-theme=dark],[data-theme=dark] *){color:var(--color-amber-300)}.dark\:text-amber-400:where([data-theme=dark],[data-theme=dark] *){color:var(--color-amber-400)}.dark\:text-blue-200:where([data-theme=dark],[data-theme=dark] *){color:var(--color-blue-200)}.dark\:text-blue-300:where([data-theme=dark],[data-theme=dark] *){color:var(--color-blue-300)}.dark\:text-blue-400:where([data-theme=dark],[data-theme=dark] *){color:var(--color-blue-400)}.dark\:text-cyan-200:where([data-theme=dark],[data-theme=dark] *){color:var(--color-cyan-200)}.dark\:text-gray-100:where([data-theme=dark],[data-theme=dark] *){color:var(--color-gray-100)}.dark\:text-gray-200:where([data-theme=dark],[data-theme=dark] *){color:var(--color-gray-200)}.dark\:text-gray-300:where([data-theme=dark],[data-theme=dark] *){color:var(--color-gray-300)}.dark\:text-gray-400:where([data-theme=dark],[data-theme=dark] *){color:var(--color-gray-400)}.dark\:text-gray-500:where([data-theme=dark],[data-theme=dark] *){color:var(--color-gray-500)}.dark\:text-green-200:where([data-theme=dark],[data-theme=dark] *){color:var(--color-green-200)}.dark\:text-green-300:where([data-theme=dark],[data-theme=dark] *){color:var(--color-green-300)}.dark\:text-green-400:where([data-theme=dark],[data-theme=dark] *){color:var(--color-green-400)}.dark\:text-purple-200:where([data-theme=dark],[data-theme=dark] *){color:var(--color-purple-200)}.dark\:text-red-200:where([data-theme=dark],[data-theme=dark] *){color:var(--color-red-200)}.dark\:text-red-300:where([data-theme=dark],[data-theme=dark] *){color:var(--color-red-300)}.dark\:text-red-400:where([data-theme=dark],[data-theme=dark] *){color:var(--color-red-400)}.dark\:text-white:where([data-theme=dark],[data-theme=dark] *){color:var(--color-white)}.dark\:text-yellow-300:where([data-theme=dark],[data-theme=dark] *){color:var(--color-yellow-300)}.dark\:even\:bg-gray-800\/50:where([data-theme=dark],[data-theme=dark] *):nth-child(2n){background-color:#1e293980}@supports (color:color-mix(in lab, red, red)){.dark\:even\:bg-gray-800\/50:where([data-theme=dark],[data-theme=dark] *):nth-child(2n){background-color:color-mix(in oklab,var(--color-gray-800)50%,transparent)}}@media (hover:hover){.dark\:hover\:border-gray-500:where([data-theme=dark],[data-theme=dark] *):hover{border-color:var(--color-gray-500)}.dark\:hover\:bg-gray-600:where([data-theme=dark],[data-theme=dark] *):hover{background-color:var(--color-gray-600)}.dark\:hover\:bg-gray-700:where([data-theme=dark],[data-theme=dark] *):hover{background-color:var(--color-gray-700)}.dark\:hover\:bg-gray-700\/50:where([data-theme=dark],[data-theme=dark] *):hover{background-color:#36415380}@supports (color:color-mix(in lab, red, red)){.dark\:hover\:bg-gray-700\/50:where([data-theme=dark],[data-theme=dark] *):hover{background-color:color-mix(in oklab,var(--color-gray-700)50%,transparent)}}.dark\:hover\:bg-gray-800:where([data-theme=dark],[data-theme=dark] *):hover{background-color:var(--color-gray-800)}.dark\:hover\:bg-red-900\/20:where([data-theme=dark],[data-theme=dark] *):hover{background-color:#82181a33}@supports (color:color-mix(in lab, red, red)){.dark\:hover\:bg-red-900\/20:where([data-theme=dark],[data-theme=dark] *):hover{background-color:color-mix(in oklab,var(--color-red-900)20%,transparent)}}.dark\:hover\:text-gray-200:where([data-theme=dark],[data-theme=dark] *):hover{color:var(--color-gray-200)}.dark\:hover\:text-gray-300:where([data-theme=dark],[data-theme=dark] *):hover{color:var(--color-gray-300)}}}@keyframes progress-stripe{0%{background-position:1rem 0}to{background-position:0 0}}.icon{vertical-align:middle;fill:currentColor;width:1em;height:1em;display:inline-block}.icon-lg{width:1.5em;height:1.5em}*,:before,:after{transition:background-color .3s,border-color .3s,color .3s}.btn{text-align:center;cursor:pointer;border:none;border-radius:.375rem;justify-content:center;align-items:center;padding:.5rem 1rem;font-size:.875rem;font-weight:500;line-height:1.25rem;text-decoration:none;transition:all .15s;display:inline-flex}.btn:hover{transform:scale(1.05);box-shadow:0 10px 15px -3px #0000001a,0 4px 6px -4px #0000001a}.btn:active{transform:scale(.95)}.btn:focus{outline-offset:2px;outline:2px solid #0000}.btn:focus-visible{box-shadow:0 0 0 2px #fff,0 0 0 4px #3b82f6}.btn-sm{padding:.375rem .75rem;font-size:.75rem}.btn-icon{vertical-align:middle;min-width:2.25rem;min-height:2.25rem;padding:.5rem .75rem}.btn-blue{color:#fff;background-color:#2563eb}.btn-blue:hover{background-color:#1d4ed8}.btn-green{color:#fff;background-color:#22c55e}.btn-green:hover{background-color:#16a34a}.btn-red{color:#fff;background-color:#ef4444}.btn-red:hover{background-color:#dc2626}.btn-amber{color:#fff;background-color:#f59e0b}.btn-amber:hover{background-color:#d97706}.btn-cyan{color:#fff;background-color:#06b6d4}.btn-cyan:hover{background-color:#0891b2}.btn-purple{color:#fff;background-color:#a855f7}.btn-purple:hover{background-color:#9333ea}.btn-indigo{color:#fff;background-color:#6366f1}.btn-indigo:hover{background-color:#4f46e5}.btn-gray{color:#fff;background-color:#6b7280}.btn-gray:hover{background-color:#4b5563}.btn-secondary{color:#374151;background-color:#e5e7eb}.btn-secondary:hover{background-color:#d1d5db}[data-theme=dark] .btn-secondary{color:#e5e7eb;background-color:#374151}[data-theme=dark] .btn-secondary:hover{background-color:#4b5563}.btn-danger-ghost{color:#b91c1c;background-color:#fee2e2}.btn-danger-ghost:hover{background-color:#fecaca}[data-theme=dark] .btn-danger-ghost{color:#fca5a5;background-color:#7f1d1d4d}[data-theme=dark] .btn-danger-ghost:hover{background-color:#7f1d1d80}.alert{border-style:solid;border-width:1px;border-radius:.5rem;margin-bottom:1rem;padding:.75rem 1rem;font-size:.875rem;line-height:1.25rem}.alert-success{color:#166534;background-color:#dcfce7;border-color:#86efac}[data-theme=dark] .alert-success{color:#bbf7d0;background-color:#22c55e33;border-color:#166534}.alert-error{color:#991b1b;background-color:#fee2e2;border-color:#fca5a5}[data-theme=dark] .alert-error{color:#fecaca;background-color:#ef444433;border-color:#7f1d1d}.alert-warning{color:#92400e;background-color:#fef3c7;border-color:#fcd34d}[data-theme=dark] .alert-warning{color:#fde68a;background-color:#f59e0b33;border-color:#78350f}.alert-info{color:#155e75;background-color:#ecfeff;border-color:#a5f3fc}[data-theme=dark] .alert-info{color:#a5f3fc;background-color:#06b6d433;border-color:#164e63}.alert-lg{border-radius:.75rem;margin-bottom:1.5rem;padding:1.5rem}.card{background-color:#fff;border:1px solid #e5e7eb;border-radius:.5rem;box-shadow:0 1px 2px #0000000d}[data-theme=dark] .card{background-color:#1f2937;border-color:#374151}.card-body{padding:1.25rem}.card-header{text-transform:uppercase;letter-spacing:.05em;color:#6b7280;border-bottom:1px solid #e5e7eb;padding:.75rem 1.5rem;font-size:.75rem;font-weight:600}[data-theme=dark] .card-header{color:#9ca3af;border-color:#374151}.info-box{color:#fff;background:linear-gradient(90deg,#0891b2,#0e7490);border-radius:.75rem;margin-bottom:1.5rem;padding:1rem 1.5rem}[data-theme=dark] .info-box{background:linear-gradient(90deg,#155e75,#164e63)}.stat-box{text-align:center;background-color:#fff;border:1px solid #e5e7eb;border-radius:.5rem;padding:1.25rem;transition:all .2s;box-shadow:0 1px 2px #0000000d}[data-theme=dark] .stat-box{background-color:#1f2937;border-color:#374151}.stat-box:hover{transform:translateY(-.25rem);box-shadow:0 10px 15px -3px #0000001a,0 4px 6px -4px #0000001a}.stat-value{color:#111827;font-size:1.875rem;font-weight:700}[data-theme=dark] .stat-value{color:#f3f4f6}.stat-label{text-transform:uppercase;letter-spacing:.05em;color:#6b7280;margin-top:.5rem;font-size:.75rem}[data-theme=dark] .stat-label{color:#9ca3af}.metric-box{text-align:center;background-color:#f9fafb;border:1px solid #e5e7eb;border-radius:.5rem;padding:.75rem}[data-theme=dark] .metric-box{background-color:#374151;border-color:#4b5563}.metric-label{text-transform:uppercase;letter-spacing:.05em;color:#6b7280;margin-bottom:.25rem;font-size:.75rem;display:block}[data-theme=dark] .metric-label{color:#9ca3af}.metric-value{color:#111827;font-size:.875rem;font-weight:600;display:block}[data-theme=dark] .metric-value{color:#f3f4f6}.table{border-collapse:collapse;background-color:#fff;border-radius:.5rem;width:100%;overflow:hidden;box-shadow:0 1px 2px #0000000d}[data-theme=dark] .table{background-color:#1f2937}.table thead{background-color:#f9fafb}[data-theme=dark] .table thead{background-color:#374151}.table th{text-align:left;text-transform:uppercase;letter-spacing:.05em;color:#6b7280;border-bottom:2px solid #e5e7eb;padding:.75rem 1rem;font-size:.75rem;font-weight:600}[data-theme=dark] .table th{color:#9ca3af;border-color:#4b5563}.table td{color:#111827;border-bottom:1px solid #e5e7eb;padding:.75rem 1rem}[data-theme=dark] .table td{color:#f3f4f6;border-color:#374151}.table tbody tr{transition:background-color .15s}.table tbody tr:hover{background-color:#f9fafb}[data-theme=dark] .table tbody tr:hover{background-color:#374151}.table tbody tr:nth-child(2n){background-color:#f9fafb}[data-theme=dark] .table tbody tr:nth-child(2n){background-color:#1f293780}.badge{border-radius:.25rem;padding:.25rem .5rem;font-size:.75rem;font-weight:500;display:inline-block}.badge-blue{color:#1e40af;background-color:#dbeafe}[data-theme=dark] .badge-blue{color:#93c5fd;background-color:#3b82f64d}.badge-green{color:#166534;background-color:#dcfce7}[data-theme=dark] .badge-green{color:#86efac;background-color:#22c55e4d}.badge-amber{color:#92400e;background-color:#fef3c7}[data-theme=dark] .badge-amber{color:#fcd34d;background-color:#f59e0b4d}.badge-red{color:#991b1b;background-color:#fee2e2}[data-theme=dark] .badge-red{color:#fca5a5;background-color:#ef44444d}.badge-purple{color:#7c3aed;background-color:#f3e8ff}[data-theme=dark] .badge-purple{color:#c4b5fd;background-color:#8b5cf64d}.badge-gray{color:#4b5563;background-color:#f3f4f6}[data-theme=dark] .badge-gray{color:#d1d5db;background-color:#374151}.code{background-color:#e5e7eb;border-radius:.25rem;padding:.125rem .25rem;font-family:ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,monospace;font-size:.875rem}[data-theme=dark] .code{background-color:#374151}.modal-overlay{z-index:50;background-color:#00000080;justify-content:center;align-items:center;display:flex;position:fixed;inset:0}.modal-overlay.hidden{display:none}.modal{background-color:#fff;border-radius:.75rem;flex-direction:column;width:100%;max-width:56rem;max-height:90vh;margin:1rem;display:flex;box-shadow:0 25px 50px -12px #00000040}[data-theme=dark] .modal{background-color:#1f2937}.modal-header{border-bottom:1px solid #e5e7eb;justify-content:space-between;align-items:center;padding:1rem 1.5rem;display:flex}[data-theme=dark] .modal-header{border-color:#374151}.modal-title{color:#111827;font-size:1.125rem;font-weight:600}[data-theme=dark] .modal-title{color:#f3f4f6}.modal-subtitle{color:#6b7280;font-size:.875rem}[data-theme=dark] .modal-subtitle{color:#9ca3af}.modal-close{color:#9ca3af;cursor:pointer;background:0 0;border:none;padding:.5rem;transition:color .15s}.modal-close:hover{color:#4b5563}[data-theme=dark] .modal-close:hover{color:#e5e7eb}.modal-body{flex-direction:column;flex:1;padding:1.5rem;display:flex;overflow:hidden}.modal-footer{border-top:1px solid #e5e7eb;justify-content:flex-end;gap:.75rem;padding:1rem 1.5rem;display:flex}[data-theme=dark] .modal-footer{border-color:#374151}.dropzone{text-align:center;cursor:pointer;background-color:#f9fafb;border:2px dashed #d1d5db;border-radius:.75rem;padding:2rem;transition:all .2s}[data-theme=dark] .dropzone{background-color:#1f293780;border-color:#4b5563}.dropzone:hover{background-color:#eff6ff;border-color:#3b82f6;transform:scale(1.01);box-shadow:0 10px 15px -3px #0000001a}[data-theme=dark] .dropzone:hover{background-color:#3b82f61a}.dropzone.drag-over{background-color:#eff6ff;border-color:#3b82f6}[data-theme=dark] .dropzone.drag-over{background-color:#1e3a5f}.progress{background-color:#e5e7eb;border-radius:.375rem;height:.625rem;overflow:hidden}[data-theme=dark] .progress{background-color:#374151}.progress-bar{background:linear-gradient(45deg,#ffffff26 25%,#0000 25% 50%,#ffffff26 50% 75%,#0000 75%,#0000) 0 0/1rem 1rem;border-radius:.375rem;height:100%;transition:width .3s;animation:1s linear infinite progress-stripe}.spinner{border:4px solid #e5e7eb;border-top-color:#3b82f6;border-radius:50%;animation:1s linear infinite spin}.spinner-sm{border-width:2px;width:1.5rem;height:1.5rem}.spinner-md{width:2.5rem;height:2.5rem}.spinner-lg{width:3rem;height:3rem}@keyframes spin{to{transform:rotate(360deg)}}.tooltip-trigger{cursor:help;color:#6b7280;background-color:#e5e7eb;border-radius:9999px;justify-content:center;align-items:center;width:1rem;height:1rem;margin-left:.25rem;font-size:.65rem;display:inline-flex;position:relative}[data-theme=dark] .tooltip-trigger{color:#9ca3af;background-color:#4b5563}.tooltip-content{white-space:nowrap;opacity:0;visibility:hidden;z-index:100;color:#fff;background-color:#1f2937;border-radius:.375rem;max-width:300px;margin-bottom:.5rem;padding:.5rem .75rem;font-size:.75rem;font-weight:400;transition:opacity .2s,visibility .2s;position:absolute;bottom:100%;left:50%;transform:translate(-50%)}[data-theme=dark] .tooltip-content{color:#1f2937;background-color:#f3f4f6}.tooltip-content:after{content:"";border:6px solid #0000;border-top-color:#1f2937;position:absolute;top:100%;left:50%;transform:translate(-50%)}[data-theme=dark] .tooltip-content:after{border-top-color:#f3f4f6}.tooltip-multiline{white-space:normal;text-align:left;line-height:1.4}.tooltip-trigger:hover .tooltip-content,.tooltip-trigger:focus .tooltip-content{opacity:1;visibility:visible}.loading-overlay{z-index:9999;background:#00000080;justify-content:center;align-items:center;display:none;position:fixed;inset:0}.loading-overlay.active{display:flex}.file-upload__input{opacity:0;cursor:pointer;width:100%;height:100%;position:absolute;top:0;left:0}.file-upload__dropzone{position:relative}.file-upload__item{border-radius:.5rem;justify-content:space-between;align-items:center;margin-bottom:.5rem;padding:.75rem 1rem;display:flex}.file-upload__item-info{align-items:center;gap:.75rem;display:flex}.file-upload__item-icon{border-radius:.375rem;justify-content:center;align-items:center;width:2rem;height:2rem;display:flex}.file-upload__item-progress{border-radius:2px;height:4px;margin-top:.5rem;overflow:hidden}.file-upload__item-progress-bar{height:100%;transition:width .3s}details.error-details summary{list-style:none}details.error-details summary::-webkit-details-marker{display:none}details.error-details[open] .error-details__chevron{transform:rotate(180deg)}input:focus,select:focus,textarea:focus{outline:none;box-shadow:0 0 0 2px #fff,0 0 0 4px #3b82f6}[data-theme=dark] input:focus,[data-theme=dark] select:focus,[data-theme=dark] textarea:focus{box-shadow:0 0 0 2px #1f2937,0 0 0 4px #3b82f6}.modal-close:focus{outline:none}.modal-close:focus-visible{border-radius:.375rem;box-shadow:0 0 0 2px #fff,0 0 0 4px #3b82f6}[data-theme=dark] .modal-close:focus-visible{box-shadow:0 0 0 2px #1f2937,0 0 0 4px #3b82f6}.preview-tab:focus{outline:none}.preview-tab:focus-visible{border-radius:.25rem;box-shadow:0 0 0 2px #fff,0 0 0 4px #3b82f6}[data-theme=dark] .preview-tab:focus-visible{box-shadow:0 0 0 2px #1f2937,0 0 0 4px #3b82f6}a:focus{outline:none}a:focus-visible{border-radius:.25rem;box-shadow:0 0 0 2px #fff,0 0 0 4px #3b82f6}[data-theme=dark] a:focus-visible{box-shadow:0 0 0 2px #1f2937,0 0 0 4px #3b82f6}.dropzone:focus-within{border-color:#3b82f6;box-shadow:0 0 0 2px #fff,0 0 0 4px #3b82f6}[data-theme=dark] .dropzone:focus-within{box-shadow:0 0 0 2px #1f2937,0 0 0 4px #3b82f6}details.error-details summary:focus{outline:none}details.error-details summary:focus-visible{border-radius:.375rem;box-shadow:0 0 0 2px #fff,0 0 0 4px #3b82f6}[data-theme=dark] details.error-details summary:focus-visible{box-shadow:0 0 0 2px #1f2937,0 0 0 4px #3b82f6}.btn-blue:focus-visible{box-shadow:0 0 0 2px #fff,0 0 0 4px #fbbf24}[data-theme=dark] .btn-blue:focus-visible{box-shadow:0 0 0 2px #1f2937,0 0 0 4px #fbbf24}::-webkit-scrollbar{width:8px;height:8px}::-webkit-scrollbar-track{background:#f1f1f1;border-radius:4px}::-webkit-scrollbar-thumb{background:#c1c1c1;border-radius:4px}::-webkit-scrollbar-thumb:hover{background:#a1a1a1}[data-theme=dark] ::-webkit-scrollbar-track{background:#2d2d3f}[data-theme=dark] ::-webkit-scrollbar-thumb{background:#4a4a5a}[data-theme=dark] ::-webkit-scrollbar-thumb:hover{background:#5a5a6a}.text-muted{color:#6b7280}[data-theme=dark] .text-muted{color:#9ca3af}.flex-center{justify-content:center;align-items:center;display:flex}.container-70{width:100%;max-width:70vw;margin-left:auto;margin-right:auto}@media (max-width:768px){.container-70{max-width:95vw}}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-divide-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-gradient-position{syntax:"*";inherits:false}@property --tw-gradient-from{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-via{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-to{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-stops{syntax:"*";inherits:false}@property --tw-gradient-via-stops{syntax:"*";inherits:false}@property --tw-gradient-from-position{syntax:"";inherits:false;initial-value:0%}@property --tw-gradient-via-position{syntax:"";inherits:false;initial-value:50%}@property --tw-gradient-to-position{syntax:"";inherits:false;initial-value:100%}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-scale-x{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-y{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-z{syntax:"*";inherits:false;initial-value:1}@keyframes pulse{50%{opacity:.5}} \ No newline at end of file +@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-space-y-reverse:0;--tw-divide-y-reverse:0;--tw-border-style:solid;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-font-weight:initial;--tw-tracking:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-duration:initial;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-red-50:oklch(97.1% .013 17.38);--color-red-100:oklch(93.6% .032 17.717);--color-red-200:oklch(88.5% .062 18.334);--color-red-300:oklch(80.8% .114 19.571);--color-red-400:oklch(70.4% .191 22.216);--color-red-500:oklch(63.7% .237 25.331);--color-red-600:oklch(57.7% .245 27.325);--color-red-700:oklch(50.5% .213 27.518);--color-red-800:oklch(44.4% .177 26.899);--color-red-900:oklch(39.6% .141 25.723);--color-amber-50:oklch(98.7% .022 95.277);--color-amber-100:oklch(96.2% .059 95.617);--color-amber-200:oklch(92.4% .12 95.746);--color-amber-300:oklch(87.9% .169 91.605);--color-amber-400:oklch(82.8% .189 84.429);--color-amber-500:oklch(76.9% .188 70.08);--color-amber-600:oklch(66.6% .179 58.318);--color-amber-700:oklch(55.5% .163 48.998);--color-amber-800:oklch(47.3% .137 46.201);--color-amber-900:oklch(41.4% .112 45.904);--color-yellow-50:oklch(98.7% .026 102.212);--color-yellow-100:oklch(97.3% .071 103.193);--color-yellow-200:oklch(94.5% .129 101.54);--color-yellow-300:oklch(90.5% .182 98.111);--color-yellow-500:oklch(79.5% .184 86.047);--color-yellow-600:oklch(68.1% .162 75.834);--color-yellow-800:oklch(47.6% .114 61.907);--color-yellow-900:oklch(42.1% .095 57.708);--color-green-50:oklch(98.2% .018 155.826);--color-green-100:oklch(96.2% .044 156.743);--color-green-200:oklch(92.5% .084 155.995);--color-green-300:oklch(87.1% .15 154.449);--color-green-400:oklch(79.2% .209 151.711);--color-green-500:oklch(72.3% .219 149.579);--color-green-600:oklch(62.7% .194 149.214);--color-green-700:oklch(52.7% .154 150.069);--color-green-800:oklch(44.8% .119 151.328);--color-green-900:oklch(39.3% .095 152.535);--color-cyan-100:oklch(95.6% .045 203.388);--color-cyan-200:oklch(91.7% .08 205.041);--color-cyan-500:oklch(71.5% .143 215.221);--color-cyan-600:oklch(60.9% .126 221.723);--color-cyan-900:oklch(39.8% .07 227.392);--color-blue-50:oklch(97% .014 254.604);--color-blue-100:oklch(93.2% .032 255.585);--color-blue-200:oklch(88.2% .059 254.128);--color-blue-300:oklch(80.9% .105 251.813);--color-blue-400:oklch(70.7% .165 254.624);--color-blue-500:oklch(62.3% .214 259.815);--color-blue-600:oklch(54.6% .245 262.881);--color-blue-700:oklch(48.8% .243 264.376);--color-blue-800:oklch(42.4% .199 265.638);--color-blue-900:oklch(37.9% .146 265.522);--color-indigo-100:oklch(93% .034 272.788);--color-indigo-500:oklch(58.5% .233 277.117);--color-indigo-600:oklch(51.1% .262 276.966);--color-indigo-900:oklch(35.9% .144 278.697);--color-purple-100:oklch(94.6% .033 307.174);--color-purple-200:oklch(90.2% .063 306.703);--color-purple-600:oklch(55.8% .288 302.321);--color-purple-800:oklch(43.8% .218 303.724);--color-purple-900:oklch(38.1% .176 304.987);--color-rose-900:oklch(41% .159 10.272);--color-gray-50:oklch(98.5% .002 247.839);--color-gray-100:oklch(96.7% .003 264.542);--color-gray-200:oklch(92.8% .006 264.531);--color-gray-300:oklch(87.2% .01 258.338);--color-gray-400:oklch(70.7% .022 261.325);--color-gray-500:oklch(55.1% .027 264.364);--color-gray-600:oklch(44.6% .03 256.802);--color-gray-700:oklch(37.3% .034 259.733);--color-gray-800:oklch(27.8% .033 256.848);--color-gray-900:oklch(21% .034 264.665);--color-black:#000;--color-white:#fff;--spacing:.25rem;--container-md:28rem;--container-lg:32rem;--container-2xl:42rem;--container-4xl:56rem;--container-7xl:80rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-base:1rem;--text-base--line-height:calc(1.5/1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75/1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2/1.5);--text-6xl:3.75rem;--text-6xl--line-height:1;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--tracking-wide:.025em;--tracking-wider:.05em;--radius-md:.375rem;--radius-lg:.5rem;--radius-xl:.75rem;--animate-spin:spin 1s linear infinite;--animate-pulse:pulse 2s cubic-bezier(.4,0,.6,1)infinite;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--animate-progress-stripe:progress-stripe 1s linear infinite}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}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;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);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}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.pointer-events-none{pointer-events:none}.visible{visibility:visible}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.sticky{position:sticky}.inset-0{inset:calc(var(--spacing)*0)}.top-0{top:calc(var(--spacing)*0)}.z-50{z-index:50}.\!container{width:100%!important}@media (min-width:40rem){.\!container{max-width:40rem!important}}@media (min-width:48rem){.\!container{max-width:48rem!important}}@media (min-width:64rem){.\!container{max-width:64rem!important}}@media (min-width:80rem){.\!container{max-width:80rem!important}}@media (min-width:96rem){.\!container{max-width:96rem!important}}.container{width:100%}@media (min-width:40rem){.container{max-width:40rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}.-mx-1\.5{margin-inline:calc(var(--spacing)*-1.5)}.mx-2{margin-inline:calc(var(--spacing)*2)}.mx-auto{margin-inline:auto}.-my-1\.5{margin-block:calc(var(--spacing)*-1.5)}.my-6{margin-block:calc(var(--spacing)*6)}.mt-1{margin-top:calc(var(--spacing)*1)}.mt-2{margin-top:calc(var(--spacing)*2)}.mt-3{margin-top:calc(var(--spacing)*3)}.mt-4{margin-top:calc(var(--spacing)*4)}.mr-1{margin-right:calc(var(--spacing)*1)}.mr-2{margin-right:calc(var(--spacing)*2)}.mb-1{margin-bottom:calc(var(--spacing)*1)}.mb-2{margin-bottom:calc(var(--spacing)*2)}.mb-3{margin-bottom:calc(var(--spacing)*3)}.mb-4{margin-bottom:calc(var(--spacing)*4)}.mb-6{margin-bottom:calc(var(--spacing)*6)}.ml-1{margin-left:calc(var(--spacing)*1)}.ml-2{margin-left:calc(var(--spacing)*2)}.ml-3{margin-left:calc(var(--spacing)*3)}.ml-4{margin-left:calc(var(--spacing)*4)}.ml-auto{margin-left:auto}.block{display:block}.contents{display:contents}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.table{display:table}.h-2{height:calc(var(--spacing)*2)}.h-2\.5{height:calc(var(--spacing)*2.5)}.h-4{height:calc(var(--spacing)*4)}.h-5{height:calc(var(--spacing)*5)}.h-6{height:calc(var(--spacing)*6)}.h-8{height:calc(var(--spacing)*8)}.h-10{height:calc(var(--spacing)*10)}.h-12{height:calc(var(--spacing)*12)}.h-16{height:calc(var(--spacing)*16)}.h-24{height:calc(var(--spacing)*24)}.h-64{height:calc(var(--spacing)*64)}.h-full{height:100%}.max-h-\[60vh\]{max-height:60vh}.max-h-\[400px\]{max-height:400px}.min-h-\[60vh\]{min-height:60vh}.min-h-\[400px\]{min-height:400px}.min-h-full{min-height:100%}.min-h-screen{min-height:100vh}.w-2{width:calc(var(--spacing)*2)}.w-4{width:calc(var(--spacing)*4)}.w-5{width:calc(var(--spacing)*5)}.w-6{width:calc(var(--spacing)*6)}.w-8{width:calc(var(--spacing)*8)}.w-10{width:calc(var(--spacing)*10)}.w-12{width:calc(var(--spacing)*12)}.w-16{width:calc(var(--spacing)*16)}.w-24{width:calc(var(--spacing)*24)}.w-full{width:100%}.max-w-2xl{max-width:var(--container-2xl)}.max-w-4xl{max-width:var(--container-4xl)}.max-w-7xl{max-width:var(--container-7xl)}.max-w-\[90vw\]{max-width:90vw}.max-w-\[200px\]{max-width:200px}.max-w-lg{max-width:var(--container-lg)}.max-w-md{max-width:var(--container-md)}.min-w-0{min-width:calc(var(--spacing)*0)}.min-w-\[44px\]{min-width:44px}.flex-1{flex:1}.flex-shrink-0{flex-shrink:0}.-translate-y-1{--tw-translate-y:calc(var(--spacing)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.rotate-180{rotate:180deg}.transform{transform:var(--tw-rotate-x,)var(--tw-rotate-y,)var(--tw-rotate-z,)var(--tw-skew-x,)var(--tw-skew-y,)}.animate-progress-stripe{animation:var(--animate-progress-stripe)}.animate-pulse{animation:var(--animate-pulse)}.animate-spin{animation:var(--animate-spin)}.cursor-help{cursor:help}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.resize{resize:both}.list-inside{list-style-position:inside}.list-decimal{list-style-type:decimal}.list-disc{list-style-type:disc}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.gap-1{gap:calc(var(--spacing)*1)}.gap-2{gap:calc(var(--spacing)*2)}.gap-3{gap:calc(var(--spacing)*3)}.gap-4{gap:calc(var(--spacing)*4)}.gap-6{gap:calc(var(--spacing)*6)}:where(.space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*1)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*1)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*2)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-3>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*3)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*3)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*4)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*4)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-6>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*6)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*6)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-8>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*8)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*8)*calc(1 - var(--tw-space-y-reverse)))}:where(.divide-y>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px*var(--tw-divide-y-reverse));border-bottom-width:calc(1px*calc(1 - var(--tw-divide-y-reverse)))}:where(.divide-gray-200>:not(:last-child)){border-color:var(--color-gray-200)}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.rounded-xl{border-radius:var(--radius-xl)}.border{border-style:var(--tw-border-style);border-width:1px}.border-2{border-style:var(--tw-border-style);border-width:2px}.border-4{border-style:var(--tw-border-style);border-width:4px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-b-2{border-bottom-style:var(--tw-border-style);border-bottom-width:2px}.border-dashed{--tw-border-style:dashed;border-style:dashed}.border-amber-500{border-color:var(--color-amber-500)}.border-blue-200{border-color:var(--color-blue-200)}.border-blue-500{border-color:var(--color-blue-500)}.border-gray-200{border-color:var(--color-gray-200)}.border-gray-300{border-color:var(--color-gray-300)}.border-green-200{border-color:var(--color-green-200)}.border-red-200{border-color:var(--color-red-200)}.border-transparent{border-color:#0000}.border-white{border-color:var(--color-white)}.border-yellow-200{border-color:var(--color-yellow-200)}.border-t-blue-600{border-top-color:var(--color-blue-600)}.bg-amber-50{background-color:var(--color-amber-50)}.bg-amber-100{background-color:var(--color-amber-100)}.bg-amber-500{background-color:var(--color-amber-500)}.bg-black{background-color:var(--color-black)}.bg-black\/50{background-color:#00000080}@supports (color:color-mix(in lab, red, red)){.bg-black\/50{background-color:color-mix(in oklab,var(--color-black)50%,transparent)}}.bg-blue-50{background-color:var(--color-blue-50)}.bg-blue-100{background-color:var(--color-blue-100)}.bg-blue-600{background-color:var(--color-blue-600)}.bg-cyan-100{background-color:var(--color-cyan-100)}.bg-cyan-500{background-color:var(--color-cyan-500)}.bg-gray-50{background-color:var(--color-gray-50)}.bg-gray-100{background-color:var(--color-gray-100)}.bg-gray-200{background-color:var(--color-gray-200)}.bg-gray-900{background-color:var(--color-gray-900)}.bg-green-50{background-color:var(--color-green-50)}.bg-green-100{background-color:var(--color-green-100)}.bg-green-500{background-color:var(--color-green-500)}.bg-green-600{background-color:var(--color-green-600)}.bg-indigo-100{background-color:var(--color-indigo-100)}.bg-purple-100{background-color:var(--color-purple-100)}.bg-red-50{background-color:var(--color-red-50)}.bg-red-100{background-color:var(--color-red-100)}.bg-red-200{background-color:var(--color-red-200)}.bg-red-500{background-color:var(--color-red-500)}.bg-red-600{background-color:var(--color-red-600)}.bg-transparent{background-color:#0000}.bg-white{background-color:var(--color-white)}.bg-white\/90{background-color:#ffffffe6}@supports (color:color-mix(in lab, red, red)){.bg-white\/90{background-color:color-mix(in oklab,var(--color-white)90%,transparent)}}.bg-yellow-50{background-color:var(--color-yellow-50)}.bg-yellow-100{background-color:var(--color-yellow-100)}.bg-yellow-500{background-color:var(--color-yellow-500)}.bg-gradient-to-r{--tw-gradient-position:to right in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.from-blue-500{--tw-gradient-from:var(--color-blue-500);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.from-cyan-600{--tw-gradient-from:var(--color-cyan-600);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.to-blue-600{--tw-gradient-to:var(--color-blue-600);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.to-rose-900{--tw-gradient-to:var(--color-rose-900);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.bg-\[length\:1rem_1rem\]{background-size:1rem 1rem}.fill-current{fill:currentColor}.fill-indigo-500{fill:var(--color-indigo-500)}.p-0{padding:calc(var(--spacing)*0)}.p-0\.5{padding:calc(var(--spacing)*.5)}.p-1{padding:calc(var(--spacing)*1)}.p-1\.5{padding:calc(var(--spacing)*1.5)}.p-2{padding:calc(var(--spacing)*2)}.p-3{padding:calc(var(--spacing)*3)}.p-4{padding:calc(var(--spacing)*4)}.p-6{padding:calc(var(--spacing)*6)}.p-8{padding:calc(var(--spacing)*8)}.p-12{padding:calc(var(--spacing)*12)}.px-1{padding-inline:calc(var(--spacing)*1)}.px-1\.5{padding-inline:calc(var(--spacing)*1.5)}.px-2{padding-inline:calc(var(--spacing)*2)}.px-2\.5{padding-inline:calc(var(--spacing)*2.5)}.px-3{padding-inline:calc(var(--spacing)*3)}.px-4{padding-inline:calc(var(--spacing)*4)}.px-6{padding-inline:calc(var(--spacing)*6)}.px-8{padding-inline:calc(var(--spacing)*8)}.py-0{padding-block:calc(var(--spacing)*0)}.py-0\.5{padding-block:calc(var(--spacing)*.5)}.py-1{padding-block:calc(var(--spacing)*1)}.py-1\.5{padding-block:calc(var(--spacing)*1.5)}.py-2{padding-block:calc(var(--spacing)*2)}.py-3{padding-block:calc(var(--spacing)*3)}.py-4{padding-block:calc(var(--spacing)*4)}.py-6{padding-block:calc(var(--spacing)*6)}.py-8{padding-block:calc(var(--spacing)*8)}.py-12{padding-block:calc(var(--spacing)*12)}.pt-4{padding-top:calc(var(--spacing)*4)}.text-center{text-align:center}.text-left{text-align:left}.text-right{text-align:right}.font-mono{font-family:var(--font-mono)}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-6xl{font-size:var(--text-6xl);line-height:var(--tw-leading,var(--text-6xl--line-height))}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-wide{--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide)}.tracking-wider{--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider)}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-line{white-space:pre-line}.whitespace-pre-wrap{white-space:pre-wrap}.text-amber-600{color:var(--color-amber-600)}.text-amber-700{color:var(--color-amber-700)}.text-amber-800{color:var(--color-amber-800)}.text-blue-500{color:var(--color-blue-500)}.text-blue-600{color:var(--color-blue-600)}.text-blue-700{color:var(--color-blue-700)}.text-blue-800{color:var(--color-blue-800)}.text-cyan-100{color:var(--color-cyan-100)}.text-cyan-600{color:var(--color-cyan-600)}.text-gray-100{color:var(--color-gray-100)}.text-gray-400{color:var(--color-gray-400)}.text-gray-500{color:var(--color-gray-500)}.text-gray-600{color:var(--color-gray-600)}.text-gray-700{color:var(--color-gray-700)}.text-gray-800{color:var(--color-gray-800)}.text-gray-900{color:var(--color-gray-900)}.text-green-500{color:var(--color-green-500)}.text-green-600{color:var(--color-green-600)}.text-green-700{color:var(--color-green-700)}.text-green-800{color:var(--color-green-800)}.text-indigo-600{color:var(--color-indigo-600)}.text-purple-600{color:var(--color-purple-600)}.text-purple-800{color:var(--color-purple-800)}.text-red-500{color:var(--color-red-500)}.text-red-600{color:var(--color-red-600)}.text-red-700{color:var(--color-red-700)}.text-red-800{color:var(--color-red-800)}.text-white{color:var(--color-white)}.text-yellow-500{color:var(--color-yellow-500)}.text-yellow-600{color:var(--color-yellow-600)}.text-yellow-800{color:var(--color-yellow-800)}.uppercase{text-transform:uppercase}.italic{font-style:italic}.no-underline{text-decoration-line:none}.opacity-0{opacity:0}.opacity-25{opacity:.25}.opacity-50{opacity:.5}.opacity-75{opacity:.75}.opacity-80{opacity:.8}.opacity-100{opacity:1}.shadow{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-2xl{--tw-shadow:0 25px 50px -12px var(--tw-shadow-color,#00000040);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-md{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a),0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-xl{--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a),0 8px 10px -6px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.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,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-150{--tw-duration:.15s;transition-duration:.15s}.duration-200{--tw-duration:.2s;transition-duration:.2s}.duration-300{--tw-duration:.3s;transition-duration:.3s}.duration-500{--tw-duration:.5s;transition-duration:.5s}.even\:bg-gray-50:nth-child(2n){background-color:var(--color-gray-50)}@media (hover:hover){.hover\:scale-105:hover{--tw-scale-x:105%;--tw-scale-y:105%;--tw-scale-z:105%;scale:var(--tw-scale-x)var(--tw-scale-y)}.hover\:border-gray-400:hover{border-color:var(--color-gray-400)}.hover\:bg-amber-600:hover{background-color:var(--color-amber-600)}.hover\:bg-black\/10:hover{background-color:#0000001a}@supports (color:color-mix(in lab, red, red)){.hover\:bg-black\/10:hover{background-color:color-mix(in oklab,var(--color-black)10%,transparent)}}.hover\:bg-blue-700:hover{background-color:var(--color-blue-700)}.hover\:bg-cyan-600:hover{background-color:var(--color-cyan-600)}.hover\:bg-gray-50:hover{background-color:var(--color-gray-50)}.hover\:bg-gray-100:hover{background-color:var(--color-gray-100)}.hover\:bg-gray-200:hover{background-color:var(--color-gray-200)}.hover\:bg-green-600:hover{background-color:var(--color-green-600)}.hover\:bg-green-700:hover{background-color:var(--color-green-700)}.hover\:bg-red-50:hover{background-color:var(--color-red-50)}.hover\:bg-red-700:hover{background-color:var(--color-red-700)}.hover\:text-gray-600:hover{color:var(--color-gray-600)}.hover\:text-gray-700:hover{color:var(--color-gray-700)}.hover\:text-red-700:hover{color:var(--color-red-700)}.hover\:underline:hover{text-decoration-line:underline}.hover\:opacity-90:hover{opacity:.9}}.focus\:ring-2:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:ring-blue-500:focus{--tw-ring-color:var(--color-blue-500)}.focus\:ring-offset-2:focus{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color)}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}.active\:scale-95:active{--tw-scale-x:95%;--tw-scale-y:95%;--tw-scale-z:95%;scale:var(--tw-scale-x)var(--tw-scale-y)}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-50:disabled{opacity:.5}@media (min-width:40rem){.sm\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.sm\:flex-row{flex-direction:row}.sm\:px-6{padding-inline:calc(var(--spacing)*6)}}@media (min-width:48rem){.md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.md\:grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}}@media (min-width:64rem){.lg\:grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}.lg\:px-8{padding-inline:calc(var(--spacing)*8)}}:where(.dark\:divide-gray-700:where([data-theme=dark],[data-theme=dark] *)>:not(:last-child)){border-color:var(--color-gray-700)}.dark\:border-blue-800:where([data-theme=dark],[data-theme=dark] *){border-color:var(--color-blue-800)}.dark\:border-gray-600:where([data-theme=dark],[data-theme=dark] *){border-color:var(--color-gray-600)}.dark\:border-gray-700:where([data-theme=dark],[data-theme=dark] *){border-color:var(--color-gray-700)}.dark\:border-green-800:where([data-theme=dark],[data-theme=dark] *){border-color:var(--color-green-800)}.dark\:border-red-800:where([data-theme=dark],[data-theme=dark] *){border-color:var(--color-red-800)}.dark\:border-yellow-800:where([data-theme=dark],[data-theme=dark] *){border-color:var(--color-yellow-800)}.dark\:bg-amber-900\/20:where([data-theme=dark],[data-theme=dark] *){background-color:#7b330633}@supports (color:color-mix(in lab, red, red)){.dark\:bg-amber-900\/20:where([data-theme=dark],[data-theme=dark] *){background-color:color-mix(in oklab,var(--color-amber-900)20%,transparent)}}.dark\:bg-amber-900\/30:where([data-theme=dark],[data-theme=dark] *){background-color:#7b33064d}@supports (color:color-mix(in lab, red, red)){.dark\:bg-amber-900\/30:where([data-theme=dark],[data-theme=dark] *){background-color:color-mix(in oklab,var(--color-amber-900)30%,transparent)}}.dark\:bg-amber-900\/50:where([data-theme=dark],[data-theme=dark] *){background-color:#7b330680}@supports (color:color-mix(in lab, red, red)){.dark\:bg-amber-900\/50:where([data-theme=dark],[data-theme=dark] *){background-color:color-mix(in oklab,var(--color-amber-900)50%,transparent)}}.dark\:bg-blue-800:where([data-theme=dark],[data-theme=dark] *){background-color:var(--color-blue-800)}.dark\:bg-blue-800\/30:where([data-theme=dark],[data-theme=dark] *){background-color:#193cb84d}@supports (color:color-mix(in lab, red, red)){.dark\:bg-blue-800\/30:where([data-theme=dark],[data-theme=dark] *){background-color:color-mix(in oklab,var(--color-blue-800)30%,transparent)}}.dark\:bg-blue-900\/20:where([data-theme=dark],[data-theme=dark] *){background-color:#1c398e33}@supports (color:color-mix(in lab, red, red)){.dark\:bg-blue-900\/20:where([data-theme=dark],[data-theme=dark] *){background-color:color-mix(in oklab,var(--color-blue-900)20%,transparent)}}.dark\:bg-blue-900\/30:where([data-theme=dark],[data-theme=dark] *){background-color:#1c398e4d}@supports (color:color-mix(in lab, red, red)){.dark\:bg-blue-900\/30:where([data-theme=dark],[data-theme=dark] *){background-color:color-mix(in oklab,var(--color-blue-900)30%,transparent)}}.dark\:bg-blue-900\/50:where([data-theme=dark],[data-theme=dark] *){background-color:#1c398e80}@supports (color:color-mix(in lab, red, red)){.dark\:bg-blue-900\/50:where([data-theme=dark],[data-theme=dark] *){background-color:color-mix(in oklab,var(--color-blue-900)50%,transparent)}}.dark\:bg-cyan-900\/30:where([data-theme=dark],[data-theme=dark] *){background-color:#104e644d}@supports (color:color-mix(in lab, red, red)){.dark\:bg-cyan-900\/30:where([data-theme=dark],[data-theme=dark] *){background-color:color-mix(in oklab,var(--color-cyan-900)30%,transparent)}}.dark\:bg-gray-600:where([data-theme=dark],[data-theme=dark] *){background-color:var(--color-gray-600)}.dark\:bg-gray-700:where([data-theme=dark],[data-theme=dark] *){background-color:var(--color-gray-700)}.dark\:bg-gray-800:where([data-theme=dark],[data-theme=dark] *){background-color:var(--color-gray-800)}.dark\:bg-gray-900:where([data-theme=dark],[data-theme=dark] *){background-color:var(--color-gray-900)}.dark\:bg-green-900\/20:where([data-theme=dark],[data-theme=dark] *){background-color:#0d542b33}@supports (color:color-mix(in lab, red, red)){.dark\:bg-green-900\/20:where([data-theme=dark],[data-theme=dark] *){background-color:color-mix(in oklab,var(--color-green-900)20%,transparent)}}.dark\:bg-green-900\/30:where([data-theme=dark],[data-theme=dark] *){background-color:#0d542b4d}@supports (color:color-mix(in lab, red, red)){.dark\:bg-green-900\/30:where([data-theme=dark],[data-theme=dark] *){background-color:color-mix(in oklab,var(--color-green-900)30%,transparent)}}.dark\:bg-green-900\/40:where([data-theme=dark],[data-theme=dark] *){background-color:#0d542b66}@supports (color:color-mix(in lab, red, red)){.dark\:bg-green-900\/40:where([data-theme=dark],[data-theme=dark] *){background-color:color-mix(in oklab,var(--color-green-900)40%,transparent)}}.dark\:bg-green-900\/50:where([data-theme=dark],[data-theme=dark] *){background-color:#0d542b80}@supports (color:color-mix(in lab, red, red)){.dark\:bg-green-900\/50:where([data-theme=dark],[data-theme=dark] *){background-color:color-mix(in oklab,var(--color-green-900)50%,transparent)}}.dark\:bg-indigo-900\/30:where([data-theme=dark],[data-theme=dark] *){background-color:#312c854d}@supports (color:color-mix(in lab, red, red)){.dark\:bg-indigo-900\/30:where([data-theme=dark],[data-theme=dark] *){background-color:color-mix(in oklab,var(--color-indigo-900)30%,transparent)}}.dark\:bg-purple-900\/30:where([data-theme=dark],[data-theme=dark] *){background-color:#59168b4d}@supports (color:color-mix(in lab, red, red)){.dark\:bg-purple-900\/30:where([data-theme=dark],[data-theme=dark] *){background-color:color-mix(in oklab,var(--color-purple-900)30%,transparent)}}.dark\:bg-purple-900\/50:where([data-theme=dark],[data-theme=dark] *){background-color:#59168b80}@supports (color:color-mix(in lab, red, red)){.dark\:bg-purple-900\/50:where([data-theme=dark],[data-theme=dark] *){background-color:color-mix(in oklab,var(--color-purple-900)50%,transparent)}}.dark\:bg-red-800:where([data-theme=dark],[data-theme=dark] *){background-color:var(--color-red-800)}.dark\:bg-red-900\/20:where([data-theme=dark],[data-theme=dark] *){background-color:#82181a33}@supports (color:color-mix(in lab, red, red)){.dark\:bg-red-900\/20:where([data-theme=dark],[data-theme=dark] *){background-color:color-mix(in oklab,var(--color-red-900)20%,transparent)}}.dark\:bg-red-900\/30:where([data-theme=dark],[data-theme=dark] *){background-color:#82181a4d}@supports (color:color-mix(in lab, red, red)){.dark\:bg-red-900\/30:where([data-theme=dark],[data-theme=dark] *){background-color:color-mix(in oklab,var(--color-red-900)30%,transparent)}}.dark\:bg-red-900\/40:where([data-theme=dark],[data-theme=dark] *){background-color:#82181a66}@supports (color:color-mix(in lab, red, red)){.dark\:bg-red-900\/40:where([data-theme=dark],[data-theme=dark] *){background-color:color-mix(in oklab,var(--color-red-900)40%,transparent)}}.dark\:bg-red-900\/50:where([data-theme=dark],[data-theme=dark] *){background-color:#82181a80}@supports (color:color-mix(in lab, red, red)){.dark\:bg-red-900\/50:where([data-theme=dark],[data-theme=dark] *){background-color:color-mix(in oklab,var(--color-red-900)50%,transparent)}}.dark\:bg-yellow-900\/20:where([data-theme=dark],[data-theme=dark] *){background-color:#733e0a33}@supports (color:color-mix(in lab, red, red)){.dark\:bg-yellow-900\/20:where([data-theme=dark],[data-theme=dark] *){background-color:color-mix(in oklab,var(--color-yellow-900)20%,transparent)}}.dark\:bg-yellow-900\/30:where([data-theme=dark],[data-theme=dark] *){background-color:#733e0a4d}@supports (color:color-mix(in lab, red, red)){.dark\:bg-yellow-900\/30:where([data-theme=dark],[data-theme=dark] *){background-color:color-mix(in oklab,var(--color-yellow-900)30%,transparent)}}.dark\:text-amber-200:where([data-theme=dark],[data-theme=dark] *){color:var(--color-amber-200)}.dark\:text-amber-300:where([data-theme=dark],[data-theme=dark] *){color:var(--color-amber-300)}.dark\:text-amber-400:where([data-theme=dark],[data-theme=dark] *){color:var(--color-amber-400)}.dark\:text-blue-200:where([data-theme=dark],[data-theme=dark] *){color:var(--color-blue-200)}.dark\:text-blue-300:where([data-theme=dark],[data-theme=dark] *){color:var(--color-blue-300)}.dark\:text-blue-400:where([data-theme=dark],[data-theme=dark] *){color:var(--color-blue-400)}.dark\:text-cyan-200:where([data-theme=dark],[data-theme=dark] *){color:var(--color-cyan-200)}.dark\:text-gray-100:where([data-theme=dark],[data-theme=dark] *){color:var(--color-gray-100)}.dark\:text-gray-200:where([data-theme=dark],[data-theme=dark] *){color:var(--color-gray-200)}.dark\:text-gray-300:where([data-theme=dark],[data-theme=dark] *){color:var(--color-gray-300)}.dark\:text-gray-400:where([data-theme=dark],[data-theme=dark] *){color:var(--color-gray-400)}.dark\:text-gray-500:where([data-theme=dark],[data-theme=dark] *){color:var(--color-gray-500)}.dark\:text-green-100:where([data-theme=dark],[data-theme=dark] *){color:var(--color-green-100)}.dark\:text-green-200:where([data-theme=dark],[data-theme=dark] *){color:var(--color-green-200)}.dark\:text-green-300:where([data-theme=dark],[data-theme=dark] *){color:var(--color-green-300)}.dark\:text-green-400:where([data-theme=dark],[data-theme=dark] *){color:var(--color-green-400)}.dark\:text-green-500:where([data-theme=dark],[data-theme=dark] *){color:var(--color-green-500)}.dark\:text-purple-200:where([data-theme=dark],[data-theme=dark] *){color:var(--color-purple-200)}.dark\:text-red-200:where([data-theme=dark],[data-theme=dark] *){color:var(--color-red-200)}.dark\:text-red-300:where([data-theme=dark],[data-theme=dark] *){color:var(--color-red-300)}.dark\:text-red-400:where([data-theme=dark],[data-theme=dark] *){color:var(--color-red-400)}.dark\:text-white:where([data-theme=dark],[data-theme=dark] *){color:var(--color-white)}.dark\:text-yellow-300:where([data-theme=dark],[data-theme=dark] *){color:var(--color-yellow-300)}.dark\:even\:bg-gray-800\/50:where([data-theme=dark],[data-theme=dark] *):nth-child(2n){background-color:#1e293980}@supports (color:color-mix(in lab, red, red)){.dark\:even\:bg-gray-800\/50:where([data-theme=dark],[data-theme=dark] *):nth-child(2n){background-color:color-mix(in oklab,var(--color-gray-800)50%,transparent)}}@media (hover:hover){.dark\:hover\:border-gray-500:where([data-theme=dark],[data-theme=dark] *):hover{border-color:var(--color-gray-500)}.dark\:hover\:bg-gray-600:where([data-theme=dark],[data-theme=dark] *):hover{background-color:var(--color-gray-600)}.dark\:hover\:bg-gray-700:where([data-theme=dark],[data-theme=dark] *):hover{background-color:var(--color-gray-700)}.dark\:hover\:bg-gray-700\/50:where([data-theme=dark],[data-theme=dark] *):hover{background-color:#36415380}@supports (color:color-mix(in lab, red, red)){.dark\:hover\:bg-gray-700\/50:where([data-theme=dark],[data-theme=dark] *):hover{background-color:color-mix(in oklab,var(--color-gray-700)50%,transparent)}}.dark\:hover\:bg-gray-800:where([data-theme=dark],[data-theme=dark] *):hover{background-color:var(--color-gray-800)}.dark\:hover\:bg-red-900\/20:where([data-theme=dark],[data-theme=dark] *):hover{background-color:#82181a33}@supports (color:color-mix(in lab, red, red)){.dark\:hover\:bg-red-900\/20:where([data-theme=dark],[data-theme=dark] *):hover{background-color:color-mix(in oklab,var(--color-red-900)20%,transparent)}}.dark\:hover\:text-gray-200:where([data-theme=dark],[data-theme=dark] *):hover{color:var(--color-gray-200)}.dark\:hover\:text-gray-300:where([data-theme=dark],[data-theme=dark] *):hover{color:var(--color-gray-300)}}}@keyframes progress-stripe{0%{background-position:1rem 0}to{background-position:0 0}}.icon{vertical-align:middle;fill:currentColor;width:1em;height:1em;display:inline-block}.icon-lg{width:1.5em;height:1.5em}*,:before,:after{transition:background-color .3s,border-color .3s,color .3s}.btn{text-align:center;cursor:pointer;border:none;border-radius:.375rem;justify-content:center;align-items:center;padding:.5rem 1rem;font-size:.875rem;font-weight:500;line-height:1.25rem;text-decoration:none;transition:all .15s;display:inline-flex}.btn:hover{transform:scale(1.05);box-shadow:0 10px 15px -3px #0000001a,0 4px 6px -4px #0000001a}.btn:active{transform:scale(.95)}.btn:focus{outline-offset:2px;outline:2px solid #0000}.btn:focus-visible{box-shadow:0 0 0 2px #fff,0 0 0 4px #3b82f6}.btn-sm{padding:.375rem .75rem;font-size:.75rem}.btn-icon{vertical-align:middle;min-width:2.25rem;min-height:2.25rem;padding:.5rem .75rem}.btn-blue{color:#fff;background-color:#2563eb}.btn-blue:hover{background-color:#1d4ed8}.btn-green{color:#fff;background-color:#22c55e}.btn-green:hover{background-color:#16a34a}.btn-red{color:#fff;background-color:#ef4444}.btn-red:hover{background-color:#dc2626}.btn-amber{color:#fff;background-color:#f59e0b}.btn-amber:hover{background-color:#d97706}.btn-cyan{color:#fff;background-color:#06b6d4}.btn-cyan:hover{background-color:#0891b2}.btn-purple{color:#fff;background-color:#a855f7}.btn-purple:hover{background-color:#9333ea}.btn-indigo{color:#fff;background-color:#6366f1}.btn-indigo:hover{background-color:#4f46e5}.btn-gray{color:#fff;background-color:#6b7280}.btn-gray:hover{background-color:#4b5563}.btn-secondary{color:#374151;background-color:#e5e7eb}.btn-secondary:hover{background-color:#d1d5db}[data-theme=dark] .btn-secondary{color:#e5e7eb;background-color:#374151}[data-theme=dark] .btn-secondary:hover{background-color:#4b5563}.btn-danger-ghost{color:#b91c1c;background-color:#fee2e2}.btn-danger-ghost:hover{background-color:#fecaca}[data-theme=dark] .btn-danger-ghost{color:#fca5a5;background-color:#7f1d1d4d}[data-theme=dark] .btn-danger-ghost:hover{background-color:#7f1d1d80}.alert{border-style:solid;border-width:1px;border-radius:.5rem;margin-bottom:1rem;padding:.75rem 1rem;font-size:.875rem;line-height:1.25rem}.alert-success{color:#166534;background-color:#dcfce7;border-color:#86efac}[data-theme=dark] .alert-success{color:#bbf7d0;background-color:#22c55e33;border-color:#166534}.alert-error{color:#991b1b;background-color:#fee2e2;border-color:#fca5a5}[data-theme=dark] .alert-error{color:#fecaca;background-color:#ef444433;border-color:#7f1d1d}.alert-warning{color:#92400e;background-color:#fef3c7;border-color:#fcd34d}[data-theme=dark] .alert-warning{color:#fde68a;background-color:#f59e0b33;border-color:#78350f}.alert-info{color:#155e75;background-color:#ecfeff;border-color:#a5f3fc}[data-theme=dark] .alert-info{color:#a5f3fc;background-color:#06b6d433;border-color:#164e63}.alert-lg{border-radius:.75rem;margin-bottom:1.5rem;padding:1.5rem}.card{background-color:#fff;border:1px solid #e5e7eb;border-radius:.5rem;box-shadow:0 1px 2px #0000000d}[data-theme=dark] .card{background-color:#1f2937;border-color:#374151}.card-body{padding:1.25rem}.card-header{text-transform:uppercase;letter-spacing:.05em;color:#6b7280;border-bottom:1px solid #e5e7eb;padding:.75rem 1.5rem;font-size:.75rem;font-weight:600}[data-theme=dark] .card-header{color:#9ca3af;border-color:#374151}.info-box{color:#fff;background:linear-gradient(90deg,#0891b2,#0e7490);border-radius:.75rem;margin-bottom:1.5rem;padding:1rem 1.5rem}[data-theme=dark] .info-box{background:linear-gradient(90deg,#155e75,#164e63)}.stat-box{text-align:center;background-color:#fff;border:1px solid #e5e7eb;border-radius:.5rem;padding:1.25rem;transition:all .2s;box-shadow:0 1px 2px #0000000d}[data-theme=dark] .stat-box{background-color:#1f2937;border-color:#374151}.stat-box:hover{transform:translateY(-.25rem);box-shadow:0 10px 15px -3px #0000001a,0 4px 6px -4px #0000001a}.stat-value{color:#111827;font-size:1.875rem;font-weight:700}[data-theme=dark] .stat-value{color:#f3f4f6}.stat-label{text-transform:uppercase;letter-spacing:.05em;color:#6b7280;margin-top:.5rem;font-size:.75rem}[data-theme=dark] .stat-label{color:#9ca3af}.metric-box{text-align:center;background-color:#f9fafb;border:1px solid #e5e7eb;border-radius:.5rem;padding:.75rem}[data-theme=dark] .metric-box{background-color:#374151;border-color:#4b5563}.metric-label{text-transform:uppercase;letter-spacing:.05em;color:#6b7280;margin-bottom:.25rem;font-size:.75rem;display:block}[data-theme=dark] .metric-label{color:#9ca3af}.metric-value{color:#111827;font-size:.875rem;font-weight:600;display:block}[data-theme=dark] .metric-value{color:#f3f4f6}.table{border-collapse:collapse;background-color:#fff;border-radius:.5rem;width:100%;overflow:hidden;box-shadow:0 1px 2px #0000000d}[data-theme=dark] .table{background-color:#1f2937}.table thead{background-color:#f9fafb}[data-theme=dark] .table thead{background-color:#374151}.table th{text-align:left;text-transform:uppercase;letter-spacing:.05em;color:#6b7280;border-bottom:2px solid #e5e7eb;padding:.75rem 1rem;font-size:.75rem;font-weight:600}[data-theme=dark] .table th{color:#9ca3af;border-color:#4b5563}.table td{color:#111827;border-bottom:1px solid #e5e7eb;padding:.75rem 1rem}[data-theme=dark] .table td{color:#f3f4f6;border-color:#374151}.table tbody tr{transition:background-color .15s}.table tbody tr:hover{background-color:#f9fafb}[data-theme=dark] .table tbody tr:hover{background-color:#374151}.table tbody tr:nth-child(2n){background-color:#f9fafb}[data-theme=dark] .table tbody tr:nth-child(2n){background-color:#1f293780}.badge{border-radius:.25rem;padding:.25rem .5rem;font-size:.75rem;font-weight:500;display:inline-block}.badge-blue{color:#1e40af;background-color:#dbeafe}[data-theme=dark] .badge-blue{color:#93c5fd;background-color:#3b82f64d}.badge-green{color:#166534;background-color:#dcfce7}[data-theme=dark] .badge-green{color:#86efac;background-color:#22c55e4d}.badge-amber{color:#92400e;background-color:#fef3c7}[data-theme=dark] .badge-amber{color:#fcd34d;background-color:#f59e0b4d}.badge-red{color:#991b1b;background-color:#fee2e2}[data-theme=dark] .badge-red{color:#fca5a5;background-color:#ef44444d}.badge-purple{color:#7c3aed;background-color:#f3e8ff}[data-theme=dark] .badge-purple{color:#c4b5fd;background-color:#8b5cf64d}.badge-gray{color:#4b5563;background-color:#f3f4f6}[data-theme=dark] .badge-gray{color:#d1d5db;background-color:#374151}.code{background-color:#e5e7eb;border-radius:.25rem;padding:.125rem .25rem;font-family:ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,monospace;font-size:.875rem}[data-theme=dark] .code{background-color:#374151}.modal-overlay{z-index:50;background-color:#00000080;justify-content:center;align-items:center;display:flex;position:fixed;inset:0}.modal-overlay.hidden{display:none}.modal{background-color:#fff;border-radius:.75rem;flex-direction:column;width:100%;max-width:56rem;max-height:90vh;margin:1rem;display:flex;box-shadow:0 25px 50px -12px #00000040}[data-theme=dark] .modal{background-color:#1f2937}.modal-header{border-bottom:1px solid #e5e7eb;justify-content:space-between;align-items:center;padding:1rem 1.5rem;display:flex}[data-theme=dark] .modal-header{border-color:#374151}.modal-title{color:#111827;font-size:1.125rem;font-weight:600}[data-theme=dark] .modal-title{color:#f3f4f6}.modal-subtitle{color:#6b7280;font-size:.875rem}[data-theme=dark] .modal-subtitle{color:#9ca3af}.modal-close{color:#9ca3af;cursor:pointer;background:0 0;border:none;padding:.5rem;transition:color .15s}.modal-close:hover{color:#4b5563}[data-theme=dark] .modal-close:hover{color:#e5e7eb}.modal-body{flex-direction:column;flex:1;padding:1.5rem;display:flex;overflow:hidden}.modal-footer{border-top:1px solid #e5e7eb;justify-content:flex-end;gap:.75rem;padding:1rem 1.5rem;display:flex}[data-theme=dark] .modal-footer{border-color:#374151}.dropzone{text-align:center;cursor:pointer;background-color:#f9fafb;border:2px dashed #d1d5db;border-radius:.75rem;padding:2rem;transition:all .2s}[data-theme=dark] .dropzone{background-color:#1f293780;border-color:#4b5563}.dropzone:hover{background-color:#eff6ff;border-color:#3b82f6;transform:scale(1.01);box-shadow:0 10px 15px -3px #0000001a}[data-theme=dark] .dropzone:hover{background-color:#3b82f61a}.dropzone.drag-over{background-color:#eff6ff;border-color:#3b82f6}[data-theme=dark] .dropzone.drag-over{background-color:#1e3a5f}.progress{background-color:#e5e7eb;border-radius:.375rem;height:.625rem;overflow:hidden}[data-theme=dark] .progress{background-color:#374151}.progress-bar{background:linear-gradient(45deg,#ffffff26 25%,#0000 25% 50%,#ffffff26 50% 75%,#0000 75%,#0000) 0 0/1rem 1rem;border-radius:.375rem;height:100%;transition:width .3s;animation:1s linear infinite progress-stripe}.spinner{border:4px solid #e5e7eb;border-top-color:#3b82f6;border-radius:50%;animation:1s linear infinite spin}.spinner-sm{border-width:2px;width:1.5rem;height:1.5rem}.spinner-md{width:2.5rem;height:2.5rem}.spinner-lg{width:3rem;height:3rem}@keyframes spin{to{transform:rotate(360deg)}}.tooltip-trigger{cursor:help;color:#6b7280;background-color:#e5e7eb;border-radius:9999px;justify-content:center;align-items:center;width:1rem;height:1rem;margin-left:.25rem;font-size:.65rem;display:inline-flex;position:relative}[data-theme=dark] .tooltip-trigger{color:#9ca3af;background-color:#4b5563}.tooltip-content{white-space:nowrap;opacity:0;visibility:hidden;z-index:100;color:#fff;background-color:#1f2937;border-radius:.375rem;max-width:300px;margin-bottom:.5rem;padding:.5rem .75rem;font-size:.75rem;font-weight:400;transition:opacity .2s,visibility .2s;position:absolute;bottom:100%;left:50%;transform:translate(-50%)}[data-theme=dark] .tooltip-content{color:#1f2937;background-color:#f3f4f6}.tooltip-content:after{content:"";border:6px solid #0000;border-top-color:#1f2937;position:absolute;top:100%;left:50%;transform:translate(-50%)}[data-theme=dark] .tooltip-content:after{border-top-color:#f3f4f6}.tooltip-multiline{white-space:normal;text-align:left;line-height:1.4}.tooltip-trigger:hover .tooltip-content,.tooltip-trigger:focus .tooltip-content{opacity:1;visibility:visible}.loading-overlay{z-index:9999;background:#00000080;justify-content:center;align-items:center;display:none;position:fixed;inset:0}.loading-overlay.active{display:flex}.file-upload__input{opacity:0;cursor:pointer;width:100%;height:100%;position:absolute;top:0;left:0}.file-upload__dropzone{position:relative}.file-upload__item{border-radius:.5rem;justify-content:space-between;align-items:center;margin-bottom:.5rem;padding:.75rem 1rem;display:flex}.file-upload__item-info{align-items:center;gap:.75rem;display:flex}.file-upload__item-icon{border-radius:.375rem;justify-content:center;align-items:center;width:2rem;height:2rem;display:flex}.file-upload__item-progress{border-radius:2px;height:4px;margin-top:.5rem;overflow:hidden}.file-upload__item-progress-bar{height:100%;transition:width .3s}details.error-details summary{list-style:none}details.error-details summary::-webkit-details-marker{display:none}details.error-details[open] .error-details__chevron{transform:rotate(180deg)}input:focus,select:focus,textarea:focus{outline:none;box-shadow:0 0 0 2px #fff,0 0 0 4px #3b82f6}[data-theme=dark] input:focus,[data-theme=dark] select:focus,[data-theme=dark] textarea:focus{box-shadow:0 0 0 2px #1f2937,0 0 0 4px #3b82f6}.modal-close:focus{outline:none}.modal-close:focus-visible{border-radius:.375rem;box-shadow:0 0 0 2px #fff,0 0 0 4px #3b82f6}[data-theme=dark] .modal-close:focus-visible{box-shadow:0 0 0 2px #1f2937,0 0 0 4px #3b82f6}.preview-tab:focus{outline:none}.preview-tab:focus-visible{border-radius:.25rem;box-shadow:0 0 0 2px #fff,0 0 0 4px #3b82f6}[data-theme=dark] .preview-tab:focus-visible{box-shadow:0 0 0 2px #1f2937,0 0 0 4px #3b82f6}a:focus{outline:none}a:focus-visible{border-radius:.25rem;box-shadow:0 0 0 2px #fff,0 0 0 4px #3b82f6}[data-theme=dark] a:focus-visible{box-shadow:0 0 0 2px #1f2937,0 0 0 4px #3b82f6}.dropzone:focus-within{border-color:#3b82f6;box-shadow:0 0 0 2px #fff,0 0 0 4px #3b82f6}[data-theme=dark] .dropzone:focus-within{box-shadow:0 0 0 2px #1f2937,0 0 0 4px #3b82f6}details.error-details summary:focus{outline:none}details.error-details summary:focus-visible{border-radius:.375rem;box-shadow:0 0 0 2px #fff,0 0 0 4px #3b82f6}[data-theme=dark] details.error-details summary:focus-visible{box-shadow:0 0 0 2px #1f2937,0 0 0 4px #3b82f6}.btn-blue:focus-visible{box-shadow:0 0 0 2px #fff,0 0 0 4px #fbbf24}[data-theme=dark] .btn-blue:focus-visible{box-shadow:0 0 0 2px #1f2937,0 0 0 4px #fbbf24}::-webkit-scrollbar{width:8px;height:8px}::-webkit-scrollbar-track{background:#f1f1f1;border-radius:4px}::-webkit-scrollbar-thumb{background:#c1c1c1;border-radius:4px}::-webkit-scrollbar-thumb:hover{background:#a1a1a1}[data-theme=dark] ::-webkit-scrollbar-track{background:#2d2d3f}[data-theme=dark] ::-webkit-scrollbar-thumb{background:#4a4a5a}[data-theme=dark] ::-webkit-scrollbar-thumb:hover{background:#5a5a6a}.warning-box{background-color:#fffbeb;border:1px solid #fcd34d;border-radius:.75rem;margin-bottom:1.5rem;padding:1.5rem}[data-theme=dark] .warning-box{background-color:#f59e0b26;border-color:#92400e}.warning-icon{color:#f59e0b}[data-theme=dark] .warning-icon{color:#fbbf24}.warning-details-list{background-color:#fef3c7;border-radius:.5rem;max-height:16rem;padding:.75rem;overflow-y:auto}[data-theme=dark] .warning-details-list{background-color:#f59e0b33}.warning-details-item{border-bottom:1px solid #fcd34d;padding:.5rem 0}.warning-details-item:last-child{border-bottom:none}[data-theme=dark] .warning-details-item{border-color:#92400e}.warning-line-badge{color:#92400e;background-color:#fde68a;border-radius:.25rem;margin-right:.5rem;padding:.125rem .375rem;font-family:ui-monospace,monospace;font-size:.75rem}[data-theme=dark] .warning-line-badge{color:#fef3c7;background-color:#92400e}.warning-message{color:#92400e;font-size:.875rem}[data-theme=dark] .warning-message{color:#fef3c7}.warning-title{color:#92400e;font-size:1.125rem;font-weight:600}[data-theme=dark] .warning-title{color:#fde68a}.warning-subtitle{color:#b45309;margin-top:.25rem;font-size:.875rem}[data-theme=dark] .warning-subtitle{color:#fcd34d}.warning-summary{cursor:pointer;color:#b45309;align-items:center;gap:.5rem;font-size:.875rem;font-weight:500;display:flex}.warning-summary:hover{color:#92400e}[data-theme=dark] .warning-summary{color:#fcd34d}[data-theme=dark] .warning-summary:hover{color:#fde68a}.warning-limit-notice{color:#d97706;margin-top:.5rem;font-size:.75rem;font-style:italic}[data-theme=dark] .warning-limit-notice{color:#fbbf24}.text-muted{color:#6b7280}[data-theme=dark] .text-muted{color:#9ca3af}.flex-center{justify-content:center;align-items:center;display:flex}.container-70{width:100%;max-width:70vw;margin-left:auto;margin-right:auto}@media (max-width:768px){.container-70{max-width:95vw}}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-divide-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-gradient-position{syntax:"*";inherits:false}@property --tw-gradient-from{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-via{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-to{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-stops{syntax:"*";inherits:false}@property --tw-gradient-via-stops{syntax:"*";inherits:false}@property --tw-gradient-from-position{syntax:"";inherits:false;initial-value:0%}@property --tw-gradient-via-position{syntax:"";inherits:false;initial-value:50%}@property --tw-gradient-to-position{syntax:"";inherits:false;initial-value:100%}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-scale-x{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-y{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-z{syntax:"*";inherits:false;initial-value:1}@keyframes pulse{50%{opacity:.5}} \ No newline at end of file diff --git a/assets/dist/bigdump.min.js b/assets/dist/bigdump.min.js index bc05420..a8ff830 100644 --- a/assets/dist/bigdump.min.js +++ b/assets/dist/bigdump.min.js @@ -1 +1 @@ -(function(){"use strict";function o(){var e=document.getElementById("loadingOverlay");e&&document.querySelectorAll("form").forEach(function(t){t.querySelector('input[name="fn"]')&&t.addEventListener("submit",function(i){e.classList.add("active");var n=t.querySelector('input[name="fn"]');if(n){var u=n.value,r=e.querySelector(".loading-subtext");r&&(r.textContent="Loading "+u)}})})}function d(){var e=document.getElementById("darkModeToggle");e&&e.addEventListener("click",function(){var t=document.documentElement,i=t.getAttribute("data-theme")||"light",n=i==="dark"?"light":"dark";t.setAttribute("data-theme",n),localStorage.setItem("bigdump-theme",n)})}function a(){o(),d()}document.readyState==="loading"?document.addEventListener("DOMContentLoaded",a):a()})(); +!function(){"use strict";function e(){var e,t;(e=document.getElementById("loadingOverlay"))&&document.querySelectorAll("form").forEach(function(t){t.querySelector('input[name="fn"]')&&t.addEventListener("submit",function(n){e.classList.add("active");var a=t.querySelector('input[name="fn"]');if(a){var o=a.value,r=e.querySelector(".loading-subtext");r&&(r.textContent="Loading "+o)}})}),(t=document.getElementById("darkModeToggle"))&&t.addEventListener("click",function(){var e=document.documentElement,t="dark"===(e.getAttribute("data-theme")||"light")?"light":"dark";e.setAttribute("data-theme",t),localStorage.setItem("bigdump-theme",t)}),document.querySelectorAll("form").forEach(function(e){var t=e.querySelector('button[type="submit"]');t&&t.querySelector(".btn-icon-swap")&&e.addEventListener("submit",function(){!function(e){e.disabled=!0,e.classList.add("opacity-50","cursor-not-allowed");var t=e.querySelector(".btn-icon-swap");t&&(t.innerHTML='',t.classList.add("animate-spin"))}(t)})})}"loading"===document.readyState?document.addEventListener("DOMContentLoaded",e):e()}(); \ No newline at end of file diff --git a/assets/dist/celebration.min.js b/assets/dist/celebration.min.js new file mode 100644 index 0000000..5d80a74 --- /dev/null +++ b/assets/dist/celebration.min.js @@ -0,0 +1,466 @@ +/** + * BigDump - Celebration Effects Module (Enhanced) + * Creates realistic fireworks and confetti animations on import completion. + * Features: particle trails, glow effects, 3D confetti rotation, air resistance. + */ +(function() { + 'use strict'; + + var canvas = null; + var ctx = null; + var animationId = null; + var particles = []; + var confetti = []; + var trails = []; + var isRunning = false; + var startTime = 0; + var fireworksDuration = 20000; // 20 seconds of fireworks + var confettiForever = true; // Confetti falls indefinitely + + // Vibrant colors for fireworks + var fireworkPalettes = [ + ['#FF6B6B', '#FF8E8E', '#FFB4B4'], // Red + ['#4ECDC4', '#7EDCD6', '#A8EBE7'], // Teal + ['#45B7D1', '#6FC9DE', '#99DBEB'], // Blue + ['#FFD93D', '#FFE469', '#FFEF9A'], // Yellow + ['#6BCB77', '#8DD896', '#AFE5B5'], // Green + ['#FF8C00', '#FFA533', '#FFBE66'], // Orange + ['#DA70D6', '#E494E0', '#EEB8EA'], // Orchid + ['#9B59B6', '#B07CC6', '#C59FD6'], // Purple + ['#E74C3C', '#ED7669', '#F3A096'], // Coral + ['#3498DB', '#5DADE2', '#85C1E9'] // Sky blue + ]; + + var confettiColors = [ + '#FF6B6B', '#4ECDC4', '#45B7D1', '#FFD93D', '#6BCB77', + '#FF8C00', '#DA70D6', '#9B59B6', '#E74C3C', '#3498DB', + '#F39C12', '#1ABC9C', '#E91E63', '#00BCD4', '#8BC34A' + ]; + + /** + * Enhanced Particle class with trails and glow + */ + function Particle(x, y, color, velocity, options) { + options = options || {}; + this.x = x; + this.y = y; + this.color = color; + this.velocity = velocity; + this.gravity = options.gravity || 0.06; + this.decay = options.decay || 0.015; + this.alpha = 1; + this.size = options.size || 3; + this.hasTrail = options.hasTrail !== false; + this.glowSize = options.glowSize || this.size * 2; + this.friction = options.friction || 0.98; + this.trail = []; + this.maxTrailLength = options.trailLength || 5; + } + + Particle.prototype.update = function() { + // Store trail position + if (this.hasTrail && this.alpha > 0.3) { + this.trail.push({ x: this.x, y: this.y, alpha: this.alpha }); + if (this.trail.length > this.maxTrailLength) { + this.trail.shift(); + } + } + + this.velocity.x *= this.friction; + this.velocity.y *= this.friction; + this.velocity.y += this.gravity; + this.x += this.velocity.x; + this.y += this.velocity.y; + this.alpha -= this.decay; + return this.alpha > 0; + }; + + Particle.prototype.draw = function(ctx) { + // Draw trail + if (this.hasTrail && this.trail.length > 0) { + for (var i = 0; i < this.trail.length; i++) { + var t = this.trail[i]; + var trailAlpha = (i / this.trail.length) * this.alpha * 0.5; + var trailSize = this.size * (0.3 + (i / this.trail.length) * 0.5); + ctx.save(); + ctx.globalAlpha = trailAlpha; + ctx.beginPath(); + ctx.arc(t.x, t.y, trailSize, 0, Math.PI * 2); + ctx.fillStyle = this.color; + ctx.fill(); + ctx.restore(); + } + } + + // Draw glow + ctx.save(); + ctx.globalAlpha = this.alpha * 0.4; + ctx.beginPath(); + ctx.arc(this.x, this.y, this.glowSize, 0, Math.PI * 2); + var gradient = ctx.createRadialGradient(this.x, this.y, 0, this.x, this.y, this.glowSize); + gradient.addColorStop(0, this.color); + gradient.addColorStop(1, 'transparent'); + ctx.fillStyle = gradient; + ctx.fill(); + ctx.restore(); + + // Draw core + ctx.save(); + ctx.globalAlpha = this.alpha; + ctx.beginPath(); + ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2); + ctx.fillStyle = this.color; + ctx.fill(); + + // Bright center + ctx.beginPath(); + ctx.arc(this.x, this.y, this.size * 0.5, 0, Math.PI * 2); + ctx.fillStyle = '#FFFFFF'; + ctx.globalAlpha = this.alpha * 0.8; + ctx.fill(); + ctx.restore(); + }; + + /** + * Enhanced Confetti with 3D rotation and air resistance + */ + function ConfettiPiece(x, y, color) { + this.x = x; + this.y = y; + this.color = color; + this.width = Math.random() * 12 + 6; + this.height = Math.random() * 8 + 4; + this.velocity = { + x: (Math.random() - 0.5) * 8, + y: Math.random() * 2 + 1 + }; + // 3D rotation angles + this.rotationX = Math.random() * 360; + this.rotationY = Math.random() * 360; + this.rotationZ = Math.random() * 360; + this.rotationSpeedX = (Math.random() - 0.5) * 8; + this.rotationSpeedY = (Math.random() - 0.5) * 12; + this.rotationSpeedZ = (Math.random() - 0.5) * 6; + // Oscillation (swaying) + this.oscillationSpeed = Math.random() * 0.08 + 0.03; + this.oscillationDistance = Math.random() * 60 + 30; + this.initialX = x; + this.time = Math.random() * 100; + // Air resistance + this.drag = 0.98 + Math.random() * 0.015; + this.terminalVelocity = 4 + Math.random() * 2; + } + + ConfettiPiece.prototype.update = function(canvasHeight) { + this.time += this.oscillationSpeed; + + // Horizontal oscillation (swaying in wind) + this.x = this.initialX + Math.sin(this.time) * this.oscillationDistance; + this.initialX += this.velocity.x * 0.1; // Drift + + // Apply drag + this.velocity.x *= this.drag; + this.velocity.y += 0.08; // Gravity + + // Terminal velocity + if (this.velocity.y > this.terminalVelocity) { + this.velocity.y = this.terminalVelocity; + } + + this.y += this.velocity.y; + + // Tumbling rotation + this.rotationX += this.rotationSpeedX; + this.rotationY += this.rotationSpeedY; + this.rotationZ += this.rotationSpeedZ; + + // Slow down rotation over time + this.rotationSpeedX *= 0.999; + this.rotationSpeedY *= 0.999; + + return this.y < canvasHeight + 50; + }; + + ConfettiPiece.prototype.draw = function(ctx) { + ctx.save(); + ctx.translate(this.x, this.y); + + // Apply 3D-like rotation (fake perspective) + var scaleX = Math.cos(this.rotationY * Math.PI / 180); + var scaleY = Math.cos(this.rotationX * Math.PI / 180); + ctx.rotate(this.rotationZ * Math.PI / 180); + ctx.scale(scaleX, scaleY); + + // Draw confetti piece with slight shadow + ctx.shadowColor = 'rgba(0,0,0,0.2)'; + ctx.shadowBlur = 2; + ctx.shadowOffsetY = 1; + + ctx.fillStyle = this.color; + ctx.fillRect(-this.width / 2, -this.height / 2, this.width, this.height); + + // Add shine effect + ctx.fillStyle = 'rgba(255,255,255,0.3)'; + ctx.fillRect(-this.width / 2, -this.height / 2, this.width * 0.4, this.height); + + ctx.restore(); + }; + + /** + * Create an enhanced firework explosion + */ + function createFirework(x, y) { + var palette = fireworkPalettes[Math.floor(Math.random() * fireworkPalettes.length)]; + var particleCount = 60 + Math.floor(Math.random() * 40); + var burstSpeed = 4 + Math.random() * 3; + + // Main explosion particles + for (var i = 0; i < particleCount; i++) { + var angle = (Math.PI * 2 / particleCount) * i + (Math.random() - 0.5) * 0.2; + var speed = burstSpeed * (0.6 + Math.random() * 0.8); + var colorIndex = Math.floor(Math.random() * palette.length); + var velocity = { + x: Math.cos(angle) * speed, + y: Math.sin(angle) * speed + }; + particles.push(new Particle(x, y, palette[colorIndex], velocity, { + gravity: 0.05, + decay: 0.010 + Math.random() * 0.008, + size: 2 + Math.random() * 2, + trailLength: 4, + glowSize: 6 + })); + } + + // Inner ring (brighter, faster decay) + var innerCount = 20 + Math.floor(Math.random() * 15); + for (var j = 0; j < innerCount; j++) { + var innerAngle = Math.random() * Math.PI * 2; + var innerSpeed = burstSpeed * 1.3 * Math.random(); + var innerVelocity = { + x: Math.cos(innerAngle) * innerSpeed, + y: Math.sin(innerAngle) * innerSpeed + }; + particles.push(new Particle(x, y, '#FFFFFF', innerVelocity, { + gravity: 0.04, + decay: 0.025, + size: 1.5, + trailLength: 3, + glowSize: 4, + hasTrail: true + })); + } + + // Sparkle particles (twinkle effect) + var sparkleCount = 15 + Math.floor(Math.random() * 10); + for (var k = 0; k < sparkleCount; k++) { + var sparkAngle = Math.random() * Math.PI * 2; + var sparkSpeed = burstSpeed * 0.3 + Math.random() * burstSpeed * 0.8; + var sparkVelocity = { + x: Math.cos(sparkAngle) * sparkSpeed, + y: Math.sin(sparkAngle) * sparkSpeed + }; + particles.push(new Particle(x, y, '#FFFACD', sparkVelocity, { + gravity: 0.08, + decay: 0.03 + Math.random() * 0.02, + size: 1, + glowSize: 3, + hasTrail: false + })); + } + } + + /** + * Create confetti burst from top + */ + function createConfettiBurst(count) { + if (!canvas) return; + + for (var i = 0; i < count; i++) { + var x = Math.random() * canvas.width; + var color = confettiColors[Math.floor(Math.random() * confettiColors.length)]; + confetti.push(new ConfettiPiece(x, -20 - Math.random() * 50, color)); + } + } + + /** + * Create confetti cannon from sides + */ + function createConfettiCannon(fromLeft) { + if (!canvas) return; + + var startX = fromLeft ? -10 : canvas.width + 10; + var angle = fromLeft ? -Math.PI / 4 : -Math.PI * 3 / 4; + + for (var i = 0; i < 30; i++) { + var spread = (Math.random() - 0.5) * 0.5; + var speed = 8 + Math.random() * 6; + var color = confettiColors[Math.floor(Math.random() * confettiColors.length)]; + var piece = new ConfettiPiece(startX, canvas.height * 0.7, color); + piece.velocity.x = Math.cos(angle + spread) * speed; + piece.velocity.y = Math.sin(angle + spread) * speed; + piece.initialX = startX; + confetti.push(piece); + } + } + + /** + * Initialize canvas + */ + function init() { + if (canvas) return; + + canvas = document.createElement('canvas'); + canvas.id = 'celebration-canvas'; + canvas.style.cssText = 'position: fixed; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; z-index: 9999;'; + document.body.appendChild(canvas); + + ctx = canvas.getContext('2d'); + resizeCanvas(); + + window.addEventListener('resize', resizeCanvas); + } + + /** + * Resize canvas to match window + */ + function resizeCanvas() { + if (!canvas) return; + canvas.width = window.innerWidth; + canvas.height = window.innerHeight; + } + + /** + * Animation loop + */ + function animate() { + if (!isRunning) return; + + var elapsed = Date.now() - startTime; + + // Clear canvas with slight fade for trail effect + ctx.fillStyle = 'rgba(0, 0, 0, 0.1)'; + ctx.fillRect(0, 0, canvas.width, canvas.height); + ctx.clearRect(0, 0, canvas.width, canvas.height); + + // Update and draw particles + particles = particles.filter(function(p) { + var alive = p.update(); + if (alive) p.draw(ctx); + return alive; + }); + + // Update and draw confetti + confetti = confetti.filter(function(c) { + var alive = c.update(canvas.height); + if (alive) c.draw(ctx); + return alive; + }); + + // Create new fireworks periodically for 20 seconds + if (elapsed < fireworksDuration) { + // More frequent fireworks at start, gradually decreasing + var fireworkChance = elapsed < 5000 ? 0.08 : (elapsed < 10000 ? 0.05 : 0.03); + if (Math.random() < fireworkChance) { + var x = Math.random() * canvas.width * 0.7 + canvas.width * 0.15; + var y = Math.random() * canvas.height * 0.4 + canvas.height * 0.08; + createFirework(x, y); + } + } + + // Add confetti continuously forever (or until stopped) + if (confettiForever && Math.random() < 0.15) { + createConfettiBurst(1); + } + + // Confetti cannons at specific intervals during first 15 seconds + if (elapsed < 15000) { + var cannonInterval = 2000; // Every 2 seconds + var cannonWindow = 30; + if (elapsed % cannonInterval < cannonWindow && Math.random() < 0.5) { + createConfettiCannon(Math.random() < 0.5); + } + } + + // Continue animation as long as running (confetti forever!) + if (isRunning) { + animationId = requestAnimationFrame(animate); + } else { + cleanup(); + } + } + + /** + * Clean up canvas and reset state + */ + function cleanup() { + isRunning = false; + if (animationId) { + cancelAnimationFrame(animationId); + animationId = null; + } + if (canvas && canvas.parentNode) { + canvas.parentNode.removeChild(canvas); + } + canvas = null; + ctx = null; + particles = []; + confetti = []; + trails = []; + window.removeEventListener('resize', resizeCanvas); + } + + /** + * Start the celebration effects + */ + function start() { + if (isRunning) return; + + init(); + isRunning = true; + startTime = Date.now(); + particles = []; + confetti = []; + trails = []; + + // Initial burst of fireworks (staggered) + var fireworkPositions = [ + { x: 0.3, y: 0.25, delay: 0 }, + { x: 0.7, y: 0.2, delay: 150 }, + { x: 0.5, y: 0.15, delay: 300 }, + { x: 0.25, y: 0.3, delay: 450 }, + { x: 0.75, y: 0.28, delay: 600 } + ]; + + fireworkPositions.forEach(function(pos) { + setTimeout(function() { + if (!isRunning || !canvas) return; + createFirework(canvas.width * pos.x, canvas.height * pos.y); + }, pos.delay); + }); + + // Initial confetti burst + createConfettiBurst(80); + + animate(); + console.log('Celebration started!'); + } + + /** + * Stop the celebration effects immediately + */ + function stop() { + if (!isRunning) return; + cleanup(); + console.log('Celebration stopped'); + } + + // Expose API + window.BigDump = window.BigDump || {}; + window.BigDump.celebration = { + start: start, + stop: stop, + isRunning: function() { return isRunning; } + }; + +})(); diff --git a/assets/dist/favicon-animator.min.js b/assets/dist/favicon-animator.min.js new file mode 100644 index 0000000..7a03b6c --- /dev/null +++ b/assets/dist/favicon-animator.min.js @@ -0,0 +1,145 @@ +/** + * BigDump - Animated Favicon Module + * Creates a bouncing arrow animation during import process. + * Uses Canvas API to dynamically generate favicon frames. + */ +(function() { + 'use strict'; + + var canvas = null; + var ctx = null; + var animationId = null; + var originalFavicon = null; + var linkElement = null; + var frame = 0; + var isAnimating = false; + + // Animation settings + var SIZE = 32; // Favicon size + var ARROW_COLOR = '#3B82F6'; // Blue-500 + var BG_COLOR = '#1E3A5F'; // Dark blue background + var BOUNCE_HEIGHT = 6; // Pixels to bounce + var FRAME_RATE = 60; // ms between frames + + /** + * Initialize canvas and get original favicon + */ + function init() { + if (canvas) return; + + canvas = document.createElement('canvas'); + canvas.width = SIZE; + canvas.height = SIZE; + ctx = canvas.getContext('2d'); + + // Find and store original favicon + linkElement = document.querySelector('link[rel="icon"][type="image/png"]') || + document.querySelector('link[rel="icon"]'); + + if (linkElement) { + originalFavicon = linkElement.href; + } + } + + /** + * Draw a frame of the bouncing arrow animation + * @param {number} offset - Vertical bounce offset + */ + function drawFrame(offset) { + // Clear canvas + ctx.clearRect(0, 0, SIZE, SIZE); + + // Draw circular background + ctx.beginPath(); + ctx.arc(SIZE / 2, SIZE / 2, SIZE / 2 - 1, 0, Math.PI * 2); + ctx.fillStyle = BG_COLOR; + ctx.fill(); + + // Calculate arrow position with bounce + var centerX = SIZE / 2; + var centerY = SIZE / 2 + offset; + + // Draw downward arrow + ctx.beginPath(); + ctx.strokeStyle = ARROW_COLOR; + ctx.fillStyle = ARROW_COLOR; + ctx.lineWidth = 3; + ctx.lineCap = 'round'; + ctx.lineJoin = 'round'; + + // Arrow shaft + ctx.moveTo(centerX, centerY - 8); + ctx.lineTo(centerX, centerY + 4); + ctx.stroke(); + + // Arrow head (triangle) + ctx.beginPath(); + ctx.moveTo(centerX - 6, centerY); + ctx.lineTo(centerX, centerY + 8); + ctx.lineTo(centerX + 6, centerY); + ctx.closePath(); + ctx.fill(); + + // Update favicon + if (linkElement) { + linkElement.href = canvas.toDataURL('image/png'); + } + } + + /** + * Animation loop using eased bounce + */ + function animate() { + if (!isAnimating) return; + + // Eased bounce: sin wave for smooth up/down motion + var bounce = Math.sin(frame * 0.15) * BOUNCE_HEIGHT; + drawFrame(bounce); + + frame++; + animationId = setTimeout(animate, FRAME_RATE); + } + + /** + * Start the favicon animation + */ + function start() { + if (isAnimating) return; + + init(); + isAnimating = true; + frame = 0; + animate(); + console.log('Favicon animation started'); + } + + /** + * Stop the animation and restore original favicon + */ + function stop() { + if (!isAnimating) return; + + isAnimating = false; + + if (animationId) { + clearTimeout(animationId); + animationId = null; + } + + // Restore original favicon + if (linkElement && originalFavicon) { + linkElement.href = originalFavicon; + } + + console.log('Favicon animation stopped'); + } + + // Expose API + window.BigDump = window.BigDump || {}; + window.BigDump.faviconAnimator = { + start: start, + stop: stop, + isAnimating: function() { return isAnimating; } + }; + +})(); diff --git a/assets/dist/filepolling.min.js b/assets/dist/filepolling.min.js index e2dca40..7e64a07 100644 --- a/assets/dist/filepolling.min.js +++ b/assets/dist/filepolling.min.js @@ -1 +1 @@ -(function(){"use strict";var C=document.getElementById("bigdump-config");if(!C)return;var B=JSON.parse(C.dataset.dbConfigured||"false"),M=JSON.parse(C.dataset.gzipSupported||"false"),G=4e3,p=null,g=new Set,f=new Set;function O(e){switch(e){case"SQL":return"bg-blue-100 dark:bg-blue-900/50 text-blue-800 dark:text-blue-200";case"GZip":return"bg-purple-100 dark:bg-purple-900/50 text-purple-800 dark:text-purple-200";case"CSV":return"bg-green-100 dark:bg-green-900/50 text-green-800 dark:text-green-200";default:return"bg-blue-100 dark:bg-blue-900/50 text-blue-800 dark:text-blue-200"}}function Z(e){var t=e.split(".").pop().toLowerCase();return t==="sql"?"SQL":t==="gz"?"GZip":t==="csv"?"CSV":"SQL"}function R(){var e=document.getElementById("previewModal"),t=document.getElementById("historyModal");return e&&!e.classList.contains("hidden")||t&&!t.classList.contains("hidden")}function z(e){var t=document.getElementById("noFilesMessage"),r=document.getElementById("filesTableContainer");!t||!r||(e?(t.classList.remove("hidden"),r.classList.add("hidden")):(t.classList.add("hidden"),r.classList.remove("hidden")))}function T(e,t,r){var n=document.createElement("tr");n.className="hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors duration-150",n.setAttribute("data-filename",e.name),t&&(n.classList.add("bg-green-50","dark:bg-green-900/20","animate-pulse"),setTimeout(function(){n.classList.remove("bg-green-50","dark:bg-green-900/20","animate-pulse")},3e3)),r&&n.classList.add("bg-amber-50","dark:bg-amber-900/20");var u=B&&(e.type!=="GZip"||M),a="px-4 py-3 border-b border-gray-200 dark:border-gray-700 text-gray-900 dark:text-gray-100",i=document.createElement("td");i.className=a;var l=document.createElement("strong");if(l.textContent=e.name,i.appendChild(l),r){var v=document.createElement("span");v.className="ml-2 text-amber-600 dark:text-amber-400 text-xs";var x=document.createElementNS("http://www.w3.org/2000/svg","svg");x.setAttribute("class","icon w-4 h-4 fill-current inline-block animate-spin");var P=document.createElementNS("http://www.w3.org/2000/svg","use");P.setAttributeNS("http://www.w3.org/1999/xlink","href","assets/icons.svg#spinner"),x.appendChild(P),v.appendChild(x),v.appendChild(document.createTextNode(" Uploading...")),i.appendChild(v)}else if(t){var E=document.createElement("span");E.className="ml-2 px-1.5 py-0.5 bg-green-500 text-white text-xs rounded font-medium",E.textContent="NEW",i.appendChild(E)}n.appendChild(i);var y=document.createElement("td");y.className=a,y.textContent=r?"--":e.sizeFormatted,n.appendChild(y);var N=document.createElement("td");N.className=a,N.textContent=r?"--":e.date,n.appendChild(N);var F=document.createElement("td");F.className=a;var k=document.createElement("span");k.className="px-2 py-1 rounded text-xs font-medium "+O(e.type),k.textContent=e.type,F.appendChild(k),n.appendChild(F);var d=document.createElement("td");if(d.className=a+" text-center",r){var S=document.createElement("span");S.className="text-gray-400",S.textContent="--",d.appendChild(S)}else{if(u){var o=document.createElement("button");o.type="button",o.className="btn btn-icon btn-purple",o.title="Preview SQL content",o.onclick=function(){window.previewFile(e.name)};var L=document.createElementNS("http://www.w3.org/2000/svg","svg");L.setAttribute("class","icon w-4 h-4 fill-current");var A=document.createElementNS("http://www.w3.org/2000/svg","use");A.setAttributeNS("http://www.w3.org/1999/xlink","href","assets/icons.svg#eye"),L.appendChild(A),o.appendChild(L),d.appendChild(o),d.appendChild(document.createTextNode(" "));var s=document.createElement("form");s.method="post",s.action="",s.style.display="inline";var b=document.createElement("input");b.type="hidden",b.name="fn",b.value=e.name,s.appendChild(b);var w=document.createElement("button");w.type="submit",w.className="btn btn-green",w.textContent="Import",s.appendChild(w),d.appendChild(s),d.appendChild(document.createTextNode(" "))}else if(e.type==="GZip"){var D=document.createElement("span");D.className="text-muted",D.textContent="GZip not supported",d.appendChild(D),d.appendChild(document.createTextNode(" "))}var m=document.createElement("a");m.href="?delete="+encodeURIComponent(e.name),m.className="btn btn-red",m.textContent="Delete",m.onclick=function(){return confirm("Delete "+e.name+"?")},d.appendChild(m)}return n.appendChild(d),n}function Q(e,t){var r=document.getElementById("fileTableBody");if(r){var n=new Set(t.map(function(a){return a.name})),u=document.createDocumentFragment();f.forEach(function(a){if(!e.find(function(l){return l.name===a})){var i={name:a,type:Z(a),sizeFormatted:"--",date:"--"};u.appendChild(T(i,!1,!0))}}),e.forEach(function(a){var i=n.has(a.name),l=f.has(a.name);u.appendChild(T(a,i,l))}),r.replaceChildren(u)}}function U(){document.querySelectorAll("#fileTableBody tr[data-filename]").forEach(function(e){g.add(e.getAttribute("data-filename"))})}function h(){U(),p=setInterval(c,G)}function I(){p&&(clearInterval(p),p=null)}function c(){R()||fetch("/files/list").then(function(e){return e.json()}).then(function(e){if(e.success){var t=new Set(e.files.map(function(n){return n.name})),r=e.files.filter(function(n){return!g.has(n.name)});Q(e.files,r),g=t,z(e.files.length===0)}}).catch(function(e){console.error("File polling error:",e)})}function V(e){f.add(e),c()}function _(e){f.delete(e),g.add(e),c()}document.addEventListener("visibilitychange",function(){document.hidden?I():h()}),document.readyState==="loading"?document.addEventListener("DOMContentLoaded",h):h(),window.BigDump=window.BigDump||{},window.BigDump.refreshFileList=c,window.BigDump.trackUploadStart=V,window.BigDump.trackUploadEnd=_,window.BigDump.startFilePolling=h,window.BigDump.stopFilePolling=I,window.refreshFileList=c})(); +!function(){"use strict";var e=document.getElementById("bigdump-config");if(e){var t=JSON.parse(e.dataset.dbConfigured||"false"),n=JSON.parse(e.dataset.gzipSupported||"false"),a=null,r=new Set,d=new Set;document.addEventListener("visibilitychange",function(){document.hidden?o():l()}),"loading"===document.readyState?document.addEventListener("DOMContentLoaded",l):l(),window.BigDump=window.BigDump||{},window.BigDump.refreshFileList=c,window.BigDump.trackUploadStart=function(e){d.add(e),c()},window.BigDump.trackUploadEnd=function(e){d.delete(e),r.add(e),c()},window.BigDump.startFilePolling=l,window.BigDump.stopFilePolling=o,window.refreshFileList=c}function i(e){var t=e.split(".").pop().toLowerCase();return"sql"===t?"SQL":"gz"===t?"GZip":"csv"===t?"CSV":"SQL"}function s(e,a,r){var d=document.createElement("tr");d.className="hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors duration-150",d.setAttribute("data-filename",e.name),a&&(d.classList.add("bg-green-50","dark:bg-green-900/20","animate-pulse"),setTimeout(function(){d.classList.remove("bg-green-50","dark:bg-green-900/20","animate-pulse")},3e3)),r&&d.classList.add("bg-amber-50","dark:bg-amber-900/20");var i=t&&("GZip"!==e.type||n),s="px-4 py-3 border-b border-gray-200 dark:border-gray-700 text-gray-900 dark:text-gray-100",l=document.createElement("td");l.className=s;var o=document.createElement("strong");if(o.textContent=e.name,l.appendChild(o),r){var c=document.createElement("span");c.className="ml-2 text-amber-600 dark:text-amber-400 text-xs";var p=document.createElementNS("http://www.w3.org/2000/svg","svg");p.setAttribute("class","icon w-4 h-4 fill-current inline-block animate-spin");var m=document.createElementNS("http://www.w3.org/2000/svg","use");m.setAttributeNS("http://www.w3.org/1999/xlink","href","assets/icons2.svg#spinner"),p.appendChild(m),c.appendChild(p),c.appendChild(document.createTextNode(" Uploading...")),l.appendChild(c)}else if(a){var u=document.createElement("span");u.className="ml-2 px-1.5 py-0.5 bg-green-500 text-white text-xs rounded font-medium",u.textContent="NEW",l.appendChild(u)}d.appendChild(l);var g=document.createElement("td");g.className=s,g.textContent=r?"--":e.sizeFormatted,d.appendChild(g);var w=document.createElement("td");w.className=s,w.textContent=r?"--":e.date,d.appendChild(w);var h=document.createElement("td");h.className=s;var f=document.createElement("span");f.className="px-2 py-1 rounded text-xs font-medium "+function(e){switch(e){case"SQL":default:return"bg-blue-100 dark:bg-blue-900/50 text-blue-800 dark:text-blue-200";case"GZip":return"bg-purple-100 dark:bg-purple-900/50 text-purple-800 dark:text-purple-200";case"CSV":return"bg-green-100 dark:bg-green-900/50 text-green-800 dark:text-green-200"}}(e.type),f.textContent=e.type,h.appendChild(f),d.appendChild(h);var v=document.createElement("td");if(v.className=s+" text-center",r){var b=document.createElement("span");b.className="text-gray-400",b.textContent="--",v.appendChild(b)}else{if(i){var C=document.createElement("button");C.type="button",C.className="btn btn-icon btn-purple",C.title="Preview SQL content",C.onclick=function(){window.previewFile(e.name)};var x=document.createElementNS("http://www.w3.org/2000/svg","svg");x.setAttribute("class","icon w-4 h-4 fill-current");var E=document.createElementNS("http://www.w3.org/2000/svg","use");E.setAttributeNS("http://www.w3.org/1999/xlink","href","assets/icons2.svg#eye"),x.appendChild(E),C.appendChild(x),v.appendChild(C),v.appendChild(document.createTextNode(" "));var N=document.createElement("form");N.method="post",N.action="",N.style.cssText="display:inline; vertical-align:middle";var y=document.createElement("input");y.type="hidden",y.name="fn",y.value=e.name,N.appendChild(y);var S=document.createElement("button");S.type="submit",S.className="btn btn-green";var k=document.createElementNS("http://www.w3.org/2000/svg","svg");k.setAttribute("class","icon w-4 h-4 fill-current mr-1");var L=document.createElementNS("http://www.w3.org/2000/svg","use");L.setAttributeNS("http://www.w3.org/1999/xlink","href","assets/icons2.svg#download"),k.appendChild(L),S.appendChild(k),S.appendChild(document.createTextNode("Import")),N.onsubmit=function(){return S.disabled=!0,S.classList.add("opacity-50","cursor-not-allowed"),k.classList.add("animate-spin"),L.setAttributeNS("http://www.w3.org/1999/xlink","href","assets/icons2.svg#spinner"),!0},N.appendChild(S),v.appendChild(N),v.appendChild(document.createTextNode(" "))}else if("GZip"===e.type){var B=document.createElement("span");B.className="text-muted",B.textContent="GZip not supported",v.appendChild(B),v.appendChild(document.createTextNode(" "))}var A=document.createElement("a");A.href="?delete="+encodeURIComponent(e.name),A.className="btn btn-icon btn-red",A.title="Delete file";var D=document.createElementNS("http://www.w3.org/2000/svg","svg");D.setAttribute("class","icon w-4 h-4 fill-current");var F=document.createElementNS("http://www.w3.org/2000/svg","use");F.setAttributeNS("http://www.w3.org/1999/xlink","href","assets/icons2.svg#trash"),D.appendChild(F),A.appendChild(D),A.onclick=function(t){return A.classList.contains("disabled")?(t.preventDefault(),!1):!!confirm("Delete "+e.name+"?")&&(A.classList.add("disabled","opacity-50","cursor-not-allowed","pointer-events-none"),D.classList.add("animate-spin"),F.setAttributeNS("http://www.w3.org/1999/xlink","href","assets/icons2.svg#spinner"),!0)},v.appendChild(A)}return d.appendChild(v),d}function l(){document.querySelectorAll("#fileTableBody tr[data-filename]").forEach(function(e){r.add(e.getAttribute("data-filename"))}),a=setInterval(c,4e3)}function o(){a&&(clearInterval(a),a=null)}function c(){var e,t;(e=document.getElementById("previewModal"),t=document.getElementById("historyModal"),e&&!e.classList.contains("hidden")||t&&!t.classList.contains("hidden"))||fetch("/files/list").then(function(e){return e.json()}).then(function(e){if(e.success){var t,n,a,l=new Set(e.files.map(function(e){return e.name})),o=e.files.filter(function(e){return!r.has(e.name)});!function(e,t){var n=document.getElementById("fileTableBody");if(n){var a=new Set(t.map(function(e){return e.name})),r=document.createDocumentFragment();d.forEach(function(t){if(!e.find(function(e){return e.name===t})){var n={name:t,type:i(t),sizeFormatted:"--",date:"--"};r.appendChild(s(n,!1,!0))}}),e.forEach(function(e){var t=a.has(e.name),n=d.has(e.name);r.appendChild(s(e,t,n))}),n.replaceChildren(r)}}(e.files,o),r=l,t=0===e.files.length,n=document.getElementById("noFilesMessage"),a=document.getElementById("filesTableContainer"),n&&a&&(t?(n.classList.remove("hidden"),a.classList.add("hidden")):(n.classList.add("hidden"),a.classList.remove("hidden")))}}).catch(function(e){console.error("File polling error:",e)})}}(); \ No newline at end of file diff --git a/assets/icons.svg b/assets/icons2.svg similarity index 92% rename from assets/icons.svg rename to assets/icons2.svg index a66a408..74a4294 100644 --- a/assets/icons.svg +++ b/assets/icons2.svg @@ -51,4 +51,7 @@ + + + diff --git a/assets/js/bigdump.js b/assets/js/bigdump.js index a9252a7..f40f9dc 100644 --- a/assets/js/bigdump.js +++ b/assets/js/bigdump.js @@ -34,6 +34,41 @@ }); } + /** + * Disable a button and swap its icon to a spinner + * @param {HTMLElement} button - The button element + */ + function disableButtonWithSpinner(button) { + button.disabled = true; + button.classList.add('opacity-50', 'cursor-not-allowed'); + // Find the swappable icon and change it to spinner + var iconSwap = button.querySelector('.btn-icon-swap'); + if (iconSwap) { + iconSwap.innerHTML = ''; + iconSwap.classList.add('animate-spin'); + } + } + + /** + * Initialize button disable behavior for import and delete forms + * Prevents double-click by disabling buttons after first click + */ + function initButtonDisable() { + // Handle forms with swappable icons (new style) + document.querySelectorAll('form').forEach(function(form) { + var submitBtn = form.querySelector('button[type="submit"]'); + if (!submitBtn) return; + + // Only add handler if button has swappable icon + var iconSwap = submitBtn.querySelector('.btn-icon-swap'); + if (iconSwap) { + form.addEventListener('submit', function() { + disableButtonWithSpinner(submitBtn); + }); + } + }); + } + /** * Initialize dark mode toggle */ @@ -56,6 +91,7 @@ function init() { initLoadingOverlay(); initDarkModeToggle(); + initButtonDisable(); } // Initialize when DOM is ready diff --git a/assets/js/filepolling.js b/assets/js/filepolling.js index 6f76eb1..82500f9 100644 --- a/assets/js/filepolling.js +++ b/assets/js/filepolling.js @@ -126,8 +126,11 @@ if (isUploading) { var uploadSpan = document.createElement('span'); uploadSpan.className = 'ml-2 text-amber-600 dark:text-amber-400 text-xs'; - var spinner = document.createElement('i'); - spinner.className = 'fa-solid fa-spinner fa-spin'; + var spinner = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); + spinner.setAttribute('class', 'icon w-4 h-4 fill-current inline-block animate-spin'); + var spinnerUse = document.createElementNS('http://www.w3.org/2000/svg', 'use'); + spinnerUse.setAttributeNS('http://www.w3.org/1999/xlink', 'href', 'assets/icons2.svg#spinner'); + spinner.appendChild(spinnerUse); uploadSpan.appendChild(spinner); uploadSpan.appendChild(document.createTextNode(' Uploading...')); td1.appendChild(uploadSpan); @@ -177,8 +180,11 @@ previewBtn.className = 'btn btn-icon btn-purple'; previewBtn.title = 'Preview SQL content'; previewBtn.onclick = function() { window.previewFile(file.name); }; - var eyeIcon = document.createElement('i'); - eyeIcon.className = 'fa-solid fa-eye'; + var eyeIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); + eyeIcon.setAttribute('class', 'icon w-4 h-4 fill-current'); + var useEl = document.createElementNS('http://www.w3.org/2000/svg', 'use'); + useEl.setAttributeNS('http://www.w3.org/1999/xlink', 'href', 'assets/icons2.svg#eye'); + eyeIcon.appendChild(useEl); previewBtn.appendChild(eyeIcon); td5.appendChild(previewBtn); td5.appendChild(document.createTextNode(' ')); @@ -187,7 +193,7 @@ var form = document.createElement('form'); form.method = 'post'; form.action = ''; - form.style.display = 'inline'; + form.style.cssText = 'display:inline; vertical-align:middle'; var hiddenInput = document.createElement('input'); hiddenInput.type = 'hidden'; hiddenInput.name = 'fn'; @@ -196,7 +202,23 @@ var importBtn = document.createElement('button'); importBtn.type = 'submit'; importBtn.className = 'btn btn-green'; - importBtn.textContent = 'Import'; + // Add database icon before text + var dbIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); + dbIcon.setAttribute('class', 'icon w-4 h-4 fill-current mr-1'); + var dbUse = document.createElementNS('http://www.w3.org/2000/svg', 'use'); + dbUse.setAttributeNS('http://www.w3.org/1999/xlink', 'href', 'assets/icons2.svg#download'); + dbIcon.appendChild(dbUse); + importBtn.appendChild(dbIcon); + importBtn.appendChild(document.createTextNode('Import')); + // Disable button on click and swap icon to spinner + form.onsubmit = function() { + importBtn.disabled = true; + importBtn.classList.add('opacity-50', 'cursor-not-allowed'); + // Swap icon to spinner + dbIcon.classList.add('animate-spin'); + dbUse.setAttributeNS('http://www.w3.org/1999/xlink', 'href', 'assets/icons2.svg#spinner'); + return true; // Allow form submission + }; form.appendChild(importBtn); td5.appendChild(form); td5.appendChild(document.createTextNode(' ')); @@ -211,9 +233,28 @@ // Delete button var deleteLink = document.createElement('a'); deleteLink.href = '?delete=' + encodeURIComponent(file.name); - deleteLink.className = 'btn btn-red'; - deleteLink.textContent = 'Delete'; - deleteLink.onclick = function() { return confirm('Delete ' + file.name + '?'); }; + deleteLink.className = 'btn btn-icon btn-red'; + deleteLink.title = 'Delete file'; + var trashIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); + trashIcon.setAttribute('class', 'icon w-4 h-4 fill-current'); + var trashUse = document.createElementNS('http://www.w3.org/2000/svg', 'use'); + trashUse.setAttributeNS('http://www.w3.org/1999/xlink', 'href', 'assets/icons2.svg#trash'); + trashIcon.appendChild(trashUse); + deleteLink.appendChild(trashIcon); + deleteLink.onclick = function(e) { + if (deleteLink.classList.contains('disabled')) { + e.preventDefault(); + return false; + } + if (!confirm('Delete ' + file.name + '?')) { + return false; + } + // Disable button and swap icon to spinner + deleteLink.classList.add('disabled', 'opacity-50', 'cursor-not-allowed', 'pointer-events-none'); + trashIcon.classList.add('animate-spin'); + trashUse.setAttributeNS('http://www.w3.org/1999/xlink', 'href', 'assets/icons2.svg#spinner'); + return true; + }; td5.appendChild(deleteLink); } diff --git a/assets/src/css/tailwind.css b/assets/src/css/tailwind.css index 44ac9de..1bd781a 100644 --- a/assets/src/css/tailwind.css +++ b/assets/src/css/tailwind.css @@ -1119,6 +1119,120 @@ details.error-details summary:focus-visible { background: #5a5a6a; } +/* ======================================== + WARNING DETAILS (SSE Import Warnings) + ======================================== */ + +.warning-box { + background-color: #fffbeb; + border: 1px solid #fcd34d; + border-radius: 0.75rem; + padding: 1.5rem; + margin-bottom: 1.5rem; +} +[data-theme="dark"] .warning-box { + background-color: rgba(245, 158, 11, 0.15); + border-color: #92400e; +} + +.warning-icon { + color: #f59e0b; +} +[data-theme="dark"] .warning-icon { + color: #fbbf24; +} + +.warning-details-list { + background-color: #fef3c7; + border-radius: 0.5rem; + padding: 0.75rem; + max-height: 16rem; + overflow-y: auto; +} +[data-theme="dark"] .warning-details-list { + background-color: rgba(245, 158, 11, 0.2); +} + +.warning-details-item { + padding: 0.5rem 0; + border-bottom: 1px solid #fcd34d; +} +.warning-details-item:last-child { + border-bottom: none; +} +[data-theme="dark"] .warning-details-item { + border-color: #92400e; +} + +.warning-line-badge { + font-family: ui-monospace, monospace; + font-size: 0.75rem; + background-color: #fde68a; + color: #92400e; + padding: 0.125rem 0.375rem; + border-radius: 0.25rem; + margin-right: 0.5rem; +} +[data-theme="dark"] .warning-line-badge { + background-color: #92400e; + color: #fef3c7; +} + +.warning-message { + font-size: 0.875rem; + color: #92400e; +} +[data-theme="dark"] .warning-message { + color: #fef3c7; +} + +.warning-title { + font-size: 1.125rem; + font-weight: 600; + color: #92400e; +} +[data-theme="dark"] .warning-title { + color: #fde68a; +} + +.warning-subtitle { + font-size: 0.875rem; + color: #b45309; + margin-top: 0.25rem; +} +[data-theme="dark"] .warning-subtitle { + color: #fcd34d; +} + +.warning-summary { + cursor: pointer; + display: flex; + align-items: center; + gap: 0.5rem; + font-size: 0.875rem; + font-weight: 500; + color: #b45309; +} +.warning-summary:hover { + color: #92400e; +} +[data-theme="dark"] .warning-summary { + color: #fcd34d; +} +[data-theme="dark"] .warning-summary:hover { + color: #fde68a; +} + +.warning-limit-notice { + margin-top: 0.5rem; + font-size: 0.75rem; + font-style: italic; + color: #d97706; +} +[data-theme="dark"] .warning-limit-notice { + color: #fbbf24; +} + /* ======================================== UTILITY CLASSES ======================================== */ diff --git a/assets/src/js/bigdump.js b/assets/src/js/bigdump.js index a9252a7..f40f9dc 100644 --- a/assets/src/js/bigdump.js +++ b/assets/src/js/bigdump.js @@ -34,6 +34,41 @@ }); } + /** + * Disable a button and swap its icon to a spinner + * @param {HTMLElement} button - The button element + */ + function disableButtonWithSpinner(button) { + button.disabled = true; + button.classList.add('opacity-50', 'cursor-not-allowed'); + // Find the swappable icon and change it to spinner + var iconSwap = button.querySelector('.btn-icon-swap'); + if (iconSwap) { + iconSwap.innerHTML = ''; + iconSwap.classList.add('animate-spin'); + } + } + + /** + * Initialize button disable behavior for import and delete forms + * Prevents double-click by disabling buttons after first click + */ + function initButtonDisable() { + // Handle forms with swappable icons (new style) + document.querySelectorAll('form').forEach(function(form) { + var submitBtn = form.querySelector('button[type="submit"]'); + if (!submitBtn) return; + + // Only add handler if button has swappable icon + var iconSwap = submitBtn.querySelector('.btn-icon-swap'); + if (iconSwap) { + form.addEventListener('submit', function() { + disableButtonWithSpinner(submitBtn); + }); + } + }); + } + /** * Initialize dark mode toggle */ @@ -56,6 +91,7 @@ function init() { initLoadingOverlay(); initDarkModeToggle(); + initButtonDisable(); } // Initialize when DOM is ready diff --git a/assets/src/js/filepolling.js b/assets/src/js/filepolling.js index b2a5397..82500f9 100644 --- a/assets/src/js/filepolling.js +++ b/assets/src/js/filepolling.js @@ -129,7 +129,7 @@ var spinner = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); spinner.setAttribute('class', 'icon w-4 h-4 fill-current inline-block animate-spin'); var spinnerUse = document.createElementNS('http://www.w3.org/2000/svg', 'use'); - spinnerUse.setAttributeNS('http://www.w3.org/1999/xlink', 'href', 'assets/icons.svg#spinner'); + spinnerUse.setAttributeNS('http://www.w3.org/1999/xlink', 'href', 'assets/icons2.svg#spinner'); spinner.appendChild(spinnerUse); uploadSpan.appendChild(spinner); uploadSpan.appendChild(document.createTextNode(' Uploading...')); @@ -182,9 +182,9 @@ previewBtn.onclick = function() { window.previewFile(file.name); }; var eyeIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); eyeIcon.setAttribute('class', 'icon w-4 h-4 fill-current'); - var eyeUse = document.createElementNS('http://www.w3.org/2000/svg', 'use'); - eyeUse.setAttributeNS('http://www.w3.org/1999/xlink', 'href', 'assets/icons.svg#eye'); - eyeIcon.appendChild(eyeUse); + var useEl = document.createElementNS('http://www.w3.org/2000/svg', 'use'); + useEl.setAttributeNS('http://www.w3.org/1999/xlink', 'href', 'assets/icons2.svg#eye'); + eyeIcon.appendChild(useEl); previewBtn.appendChild(eyeIcon); td5.appendChild(previewBtn); td5.appendChild(document.createTextNode(' ')); @@ -193,7 +193,7 @@ var form = document.createElement('form'); form.method = 'post'; form.action = ''; - form.style.display = 'inline'; + form.style.cssText = 'display:inline; vertical-align:middle'; var hiddenInput = document.createElement('input'); hiddenInput.type = 'hidden'; hiddenInput.name = 'fn'; @@ -202,7 +202,23 @@ var importBtn = document.createElement('button'); importBtn.type = 'submit'; importBtn.className = 'btn btn-green'; - importBtn.textContent = 'Import'; + // Add database icon before text + var dbIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); + dbIcon.setAttribute('class', 'icon w-4 h-4 fill-current mr-1'); + var dbUse = document.createElementNS('http://www.w3.org/2000/svg', 'use'); + dbUse.setAttributeNS('http://www.w3.org/1999/xlink', 'href', 'assets/icons2.svg#download'); + dbIcon.appendChild(dbUse); + importBtn.appendChild(dbIcon); + importBtn.appendChild(document.createTextNode('Import')); + // Disable button on click and swap icon to spinner + form.onsubmit = function() { + importBtn.disabled = true; + importBtn.classList.add('opacity-50', 'cursor-not-allowed'); + // Swap icon to spinner + dbIcon.classList.add('animate-spin'); + dbUse.setAttributeNS('http://www.w3.org/1999/xlink', 'href', 'assets/icons2.svg#spinner'); + return true; // Allow form submission + }; form.appendChild(importBtn); td5.appendChild(form); td5.appendChild(document.createTextNode(' ')); @@ -217,9 +233,28 @@ // Delete button var deleteLink = document.createElement('a'); deleteLink.href = '?delete=' + encodeURIComponent(file.name); - deleteLink.className = 'btn btn-red'; - deleteLink.textContent = 'Delete'; - deleteLink.onclick = function() { return confirm('Delete ' + file.name + '?'); }; + deleteLink.className = 'btn btn-icon btn-red'; + deleteLink.title = 'Delete file'; + var trashIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); + trashIcon.setAttribute('class', 'icon w-4 h-4 fill-current'); + var trashUse = document.createElementNS('http://www.w3.org/2000/svg', 'use'); + trashUse.setAttributeNS('http://www.w3.org/1999/xlink', 'href', 'assets/icons2.svg#trash'); + trashIcon.appendChild(trashUse); + deleteLink.appendChild(trashIcon); + deleteLink.onclick = function(e) { + if (deleteLink.classList.contains('disabled')) { + e.preventDefault(); + return false; + } + if (!confirm('Delete ' + file.name + '?')) { + return false; + } + // Disable button and swap icon to spinner + deleteLink.classList.add('disabled', 'opacity-50', 'cursor-not-allowed', 'pointer-events-none'); + trashIcon.classList.add('animate-spin'); + trashUse.setAttributeNS('http://www.w3.org/1999/xlink', 'href', 'assets/icons2.svg#spinner'); + return true; + }; td5.appendChild(deleteLink); } diff --git a/assets/src/js/history.js b/assets/src/js/history.js index 8ba40ba..10fc725 100644 --- a/assets/src/js/history.js +++ b/assets/src/js/history.js @@ -59,14 +59,11 @@ // Status icon var tdStatus = document.createElement('td'); tdStatus.className = 'px-4 py-3 border-b border-gray-200 dark:border-gray-700 text-center'; - var iconName = entry.success ? 'circle-check' : 'circle-xmark'; - var iconColor = entry.success ? 'text-green-500' : 'text-red-500'; - var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); - svg.setAttribute('class', 'icon w-5 h-5 fill-current inline-block ' + iconColor); - var use = document.createElementNS('http://www.w3.org/2000/svg', 'use'); - use.setAttributeNS('http://www.w3.org/1999/xlink', 'href', 'assets/icons.svg#' + iconName); - svg.appendChild(use); - tdStatus.appendChild(svg); + var icon = document.createElement('i'); + icon.className = entry.success + ? 'fa-solid fa-circle-check text-green-500' + : 'fa-solid fa-circle-xmark text-red-500'; + tdStatus.appendChild(icon); // Filename var tdFile = document.createElement('td'); diff --git a/assets/src/js/preview.js b/assets/src/js/preview.js index d7660a2..3ff551b 100644 --- a/assets/src/js/preview.js +++ b/assets/src/js/preview.js @@ -93,7 +93,7 @@ document.getElementById('previewModalSubtitle').textContent = data.fileSizeFormatted + (data.isGzip ? ' (GZip compressed)' : ''); document.getElementById('previewFileSize').textContent = data.fileSizeFormatted; document.getElementById('previewFileType').textContent = data.isGzip ? 'GZip' : 'SQL'; - document.getElementById('previewTotalLines').textContent = data.totalLines.toLocaleString(); + document.getElementById('previewLinesCount').textContent = data.linesPreview; document.getElementById('previewQueriesCount').textContent = data.queriesPreview; document.getElementById('tabQueriesCount').textContent = data.queriesPreview; diff --git a/src/Core/Application.php b/src/Core/Application.php index f1ca042..6f7c0ee 100644 --- a/src/Core/Application.php +++ b/src/Core/Application.php @@ -23,7 +23,7 @@ class Application /** * Application version. */ - public const VERSION = '2.27'; + public const VERSION = '2.28'; /** * Configuration instance. diff --git a/src/Core/Request.php b/src/Core/Request.php index a501d02..2e29e44 100644 --- a/src/Core/Request.php +++ b/src/Core/Request.php @@ -1,345 +1,345 @@ - - */ - private array $get; - - /** - * Sanitized POST data. - * @var array - */ - private array $post; - - /** - * FILES data. - * @var array - */ - private array $files; - - /** - * SERVER data. - * @var array - */ - private array $server; - - /** - * Requested action. - * @var string - */ - private string $action; - - /** - * Constructor - Initializes the request with superglobals. - */ - public function __construct() - { - $this->get = $this->sanitizeInput($_GET); - $this->post = $this->sanitizeInput($_POST); - $this->files = $_FILES; - $this->server = $_SERVER; - $this->action = $this->determineAction(); - } - - /** - * Sanitizes input data securely. - * - * Unlike the original which removed too many characters, - * this version preserves valid UTF-8 characters while - * removing dangerous control characters. - * - * @param array $data Data to sanitize. - * @return array Sanitized data. - */ - private function sanitizeInput(array $data): array - { - $sanitized = []; - - foreach ($data as $key => $value) { - // Sanitize key - $cleanKey = $this->sanitizeKey($key); - - if (is_array($value)) { - $sanitized[$cleanKey] = $this->sanitizeInput($value); - } else { - $sanitized[$cleanKey] = $this->sanitizeValue((string) $value); - } - } - - return $sanitized; - } - - /** - * Sanitizes a parameter key. - * - * @param string $key Key to sanitize. - * @return string Sanitized key. - */ - private function sanitizeKey(string $key): string - { - // Keys should only contain alphanumeric characters and underscore - return preg_replace('/[^a-zA-Z0-9_]/', '', $key) ?? ''; - } - - /** - * Sanitizes a parameter value. - * - * Preserves valid UTF-8 characters, only removes - * control characters (except tab, newline, carriage return). - * - * @param string $value Value to sanitize. - * @return string Sanitized value. - */ - private function sanitizeValue(string $value): string - { - // Remove control characters except \t, \n, \r - $value = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/', '', $value) ?? ''; - - // Remove null byte sequences (injection) - $value = str_replace("\0", '', $value); - - return $value; - } - - /** - * Determines the action to execute based on request parameters. - * - * Priority: explicit action > form actions > parameter-based actions - * - * @return string Action name. - */ - private function determineAction(): string - { - // 1. Explicit action parameter takes highest priority (for SSE, AJAX, etc.) - if ($this->has('action')) { - $action = $this->input('action', ''); - // Validate against known actions to prevent injection - $validActions = ['home', 'upload', 'delete', 'import', 'start_import', 'stop_import', 'sse_import', 'drop_restart', 'restart_import', 'preview', 'history', 'files_list']; - if (in_array($action, $validActions, true)) { - return $action; - } - } - - // 2. Form-based actions (button submissions) - if ($this->has('uploadbutton')) { - return 'upload'; - } - - if ($this->has('delete')) { - return 'delete'; - } - - // 3. POST with fn parameter = start import - if ($this->has('fn') && ($_SERVER['REQUEST_METHOD'] ?? '') === 'POST') { - return 'start_import'; - } - - return 'home'; - } - - /** - * Gets a GET value. - * - * @param string $key Parameter key. - * @param mixed $default Default value. - * @return mixed Parameter value. - */ - public function get(string $key, mixed $default = null): mixed - { - return $this->get[$key] ?? $default; - } - - /** - * Gets a POST value. - * - * @param string $key Parameter key. - * @param mixed $default Default value. - * @return mixed Parameter value. - */ - public function post(string $key, mixed $default = null): mixed - { - return $this->post[$key] ?? $default; - } - - /** - * Gets a GET or POST value (GET takes priority). - * - * @param string $key Parameter key. - * @param mixed $default Default value. - * @return mixed Parameter value. - */ - public function input(string $key, mixed $default = null): mixed - { - return $this->get[$key] ?? $this->post[$key] ?? $default; - } - - /** - * Checks if a parameter exists (GET or POST). - * - * @param string $key Parameter key. - * @return bool True if parameter exists. - */ - public function has(string $key): bool - { - return isset($this->get[$key]) || isset($this->post[$key]); - } - - /** - * Gets an integer from parameters. - * - * Safely converts input to integer, rejecting scientific notation - * and values that would overflow PHP's integer range. - * - * @param string $key Parameter key. - * @param int $default Default value. - * @return int Integer value. - */ - public function getInt(string $key, int $default = 0): int - { - $value = $this->input($key); - - if ($value === null) { - return $default; - } - - // Only accept simple integer or decimal strings, reject scientific notation - // This prevents 1e10 being accepted as 10000000000 - if (!is_numeric($value) || preg_match('/[eE]/', (string) $value)) { - return $default; - } - - $floatValue = (float) $value; - - // Check for integer overflow - if ($floatValue > PHP_INT_MAX || $floatValue < PHP_INT_MIN) { - return $default; - } - - return (int) floor($floatValue); - } - - /** - * Gets information about an uploaded file. - * - * @param string $key File key. - * @return array|null File information or null. - */ - public function file(string $key): ?array - { - return $this->files[$key] ?? null; - } - - /** - * Checks if a file has been uploaded correctly. - * - * @param string $key File key. - * @return bool True if file is valid. - */ - public function hasValidFile(string $key): bool - { - $file = $this->file($key); - - if ($file === null) { - return false; - } - - return isset($file['tmp_name']) - && is_uploaded_file($file['tmp_name']) - && ($file['error'] ?? UPLOAD_ERR_NO_FILE) === UPLOAD_ERR_OK; - } - - /** - * Gets the requested action. - * - * @return string Action name. - */ - public function getAction(): string - { - return $this->action; - } - - /** - * Gets a SERVER value. - * - * @param string $key Parameter key. - * @param mixed $default Default value. - * @return mixed Parameter value. - */ - public function server(string $key, mixed $default = null): mixed - { - return $this->server[$key] ?? $default; - } - - /** - * Gets the PHP script name. - * - * @return string Script name. - */ - public function getScriptName(): string - { - return basename($this->server('SCRIPT_FILENAME', 'index.php')); - } - - /** - * Gets the script URI (base path without index.php). - * - * @return string Script URI (e.g., '/bigdump' instead of '/bigdump/index.php'). - */ - public function getScriptUri(): string - { - $phpSelf = $this->server('PHP_SELF', '/index.php'); - // Remove /index.php suffix to get clean base URL - $uri = preg_replace('#/index\.php$#', '', $phpSelf); - - // If empty (app at root), return empty string - callers will handle query params - // e.g., getScriptUri() . '?action=import' = '?action=import' (correct) - return $uri === '' ? '' : $uri; - } - - /** - * Checks if the request is an AJAX request. - * - * @return bool True if AJAX request. - */ - public function isAjax(): bool - { - return $this->has('ajaxrequest') - || strtolower($this->server('HTTP_X_REQUESTED_WITH', '')) === 'xmlhttprequest'; - } - - /** - * Gets all GET data. - * - * @return array GET data. - */ - public function getAllGet(): array - { - return $this->get; - } - - /** - * Gets all POST data. - * - * @return array POST data. - */ - public function getAllPost(): array - { - return $this->post; - } -} + + */ + private array $get; + + /** + * Sanitized POST data. + * @var array + */ + private array $post; + + /** + * FILES data. + * @var array + */ + private array $files; + + /** + * SERVER data. + * @var array + */ + private array $server; + + /** + * Requested action. + * @var string + */ + private string $action; + + /** + * Constructor - Initializes the request with superglobals. + */ + public function __construct() + { + $this->get = $this->sanitizeInput($_GET); + $this->post = $this->sanitizeInput($_POST); + $this->files = $_FILES; + $this->server = $_SERVER; + $this->action = $this->determineAction(); + } + + /** + * Sanitizes input data securely. + * + * Unlike the original which removed too many characters, + * this version preserves valid UTF-8 characters while + * removing dangerous control characters. + * + * @param array $data Data to sanitize. + * @return array Sanitized data. + */ + private function sanitizeInput(array $data): array + { + $sanitized = []; + + foreach ($data as $key => $value) { + // Sanitize key + $cleanKey = $this->sanitizeKey($key); + + if (is_array($value)) { + $sanitized[$cleanKey] = $this->sanitizeInput($value); + } else { + $sanitized[$cleanKey] = $this->sanitizeValue((string) $value); + } + } + + return $sanitized; + } + + /** + * Sanitizes a parameter key. + * + * @param string $key Key to sanitize. + * @return string Sanitized key. + */ + private function sanitizeKey(string $key): string + { + // Keys should only contain alphanumeric characters and underscore + return preg_replace('/[^a-zA-Z0-9_]/', '', $key) ?? ''; + } + + /** + * Sanitizes a parameter value. + * + * Preserves valid UTF-8 characters, only removes + * control characters (except tab, newline, carriage return). + * + * @param string $value Value to sanitize. + * @return string Sanitized value. + */ + private function sanitizeValue(string $value): string + { + // Remove control characters except \t, \n, \r + $value = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/', '', $value) ?? ''; + + // Remove null byte sequences (injection) + $value = str_replace("\0", '', $value); + + return $value; + } + + /** + * Determines the action to execute based on request parameters. + * + * Priority: explicit action > form actions > parameter-based actions + * + * @return string Action name. + */ + private function determineAction(): string + { + // 1. Explicit action parameter takes highest priority (for SSE, AJAX, etc.) + if ($this->has('action')) { + $action = $this->input('action', ''); + // Validate against known actions to prevent injection + $validActions = ['home', 'upload', 'delete', 'import', 'start_import', 'stop_import', 'sse_import', 'drop_restart', 'restart_import', 'preview', 'history', 'files_list', 'sse_files']; + if (in_array($action, $validActions, true)) { + return $action; + } + } + + // 2. Form-based actions (button submissions) + if ($this->has('uploadbutton')) { + return 'upload'; + } + + if ($this->has('delete')) { + return 'delete'; + } + + // 3. POST with fn parameter = start import + if ($this->has('fn') && ($_SERVER['REQUEST_METHOD'] ?? '') === 'POST') { + return 'start_import'; + } + + return 'home'; + } + + /** + * Gets a GET value. + * + * @param string $key Parameter key. + * @param mixed $default Default value. + * @return mixed Parameter value. + */ + public function get(string $key, mixed $default = null): mixed + { + return $this->get[$key] ?? $default; + } + + /** + * Gets a POST value. + * + * @param string $key Parameter key. + * @param mixed $default Default value. + * @return mixed Parameter value. + */ + public function post(string $key, mixed $default = null): mixed + { + return $this->post[$key] ?? $default; + } + + /** + * Gets a GET or POST value (GET takes priority). + * + * @param string $key Parameter key. + * @param mixed $default Default value. + * @return mixed Parameter value. + */ + public function input(string $key, mixed $default = null): mixed + { + return $this->get[$key] ?? $this->post[$key] ?? $default; + } + + /** + * Checks if a parameter exists (GET or POST). + * + * @param string $key Parameter key. + * @return bool True if parameter exists. + */ + public function has(string $key): bool + { + return isset($this->get[$key]) || isset($this->post[$key]); + } + + /** + * Gets an integer from parameters. + * + * Safely converts input to integer, rejecting scientific notation + * and values that would overflow PHP's integer range. + * + * @param string $key Parameter key. + * @param int $default Default value. + * @return int Integer value. + */ + public function getInt(string $key, int $default = 0): int + { + $value = $this->input($key); + + if ($value === null) { + return $default; + } + + // Only accept simple integer or decimal strings, reject scientific notation + // This prevents 1e10 being accepted as 10000000000 + if (!is_numeric($value) || preg_match('/[eE]/', (string) $value)) { + return $default; + } + + $floatValue = (float) $value; + + // Check for integer overflow + if ($floatValue > PHP_INT_MAX || $floatValue < PHP_INT_MIN) { + return $default; + } + + return (int) floor($floatValue); + } + + /** + * Gets information about an uploaded file. + * + * @param string $key File key. + * @return array|null File information or null. + */ + public function file(string $key): ?array + { + return $this->files[$key] ?? null; + } + + /** + * Checks if a file has been uploaded correctly. + * + * @param string $key File key. + * @return bool True if file is valid. + */ + public function hasValidFile(string $key): bool + { + $file = $this->file($key); + + if ($file === null) { + return false; + } + + return isset($file['tmp_name']) + && is_uploaded_file($file['tmp_name']) + && ($file['error'] ?? UPLOAD_ERR_NO_FILE) === UPLOAD_ERR_OK; + } + + /** + * Gets the requested action. + * + * @return string Action name. + */ + public function getAction(): string + { + return $this->action; + } + + /** + * Gets a SERVER value. + * + * @param string $key Parameter key. + * @param mixed $default Default value. + * @return mixed Parameter value. + */ + public function server(string $key, mixed $default = null): mixed + { + return $this->server[$key] ?? $default; + } + + /** + * Gets the PHP script name. + * + * @return string Script name. + */ + public function getScriptName(): string + { + return basename($this->server('SCRIPT_FILENAME', 'index.php')); + } + + /** + * Gets the script URI (base path without index.php or .phar entry point). + * + * @return string Script URI (e.g., '/bigdump' instead of '/bigdump/index.php'). + */ + public function getScriptUri(): string + { + $phpSelf = $this->server('PHP_SELF', '/index.php'); + // Remove /index.php or /anything.phar suffix to get clean base URL + $uri = preg_replace('#/(index\.php|[^/]+\.phar)$#', '', $phpSelf); + + // If empty (app at root), return empty string - callers will handle query params + // e.g., getScriptUri() . '?action=import' = '?action=import' (correct) + return $uri === '' ? '' : $uri; + } + + /** + * Checks if the request is an AJAX request. + * + * @return bool True if AJAX request. + */ + public function isAjax(): bool + { + return $this->has('ajaxrequest') + || strtolower($this->server('HTTP_X_REQUESTED_WITH', '')) === 'xmlhttprequest'; + } + + /** + * Gets all GET data. + * + * @return array GET data. + */ + public function getAllGet(): array + { + return $this->get; + } + + /** + * Gets all POST data. + * + * @return array POST data. + */ + public function getAllPost(): array + { + return $this->post; + } +} diff --git a/src/Core/View.php b/src/Core/View.php index 60c3a7b..01e7c15 100644 --- a/src/Core/View.php +++ b/src/Core/View.php @@ -313,6 +313,8 @@ private function loadInlinedAssets(): void 'modal.min.js', 'filepolling.min.js', 'fileupload.min.js', + 'celebration.min.js', + 'favicon-animator.min.js', ]; foreach ($jsOrder as $jsFile) { @@ -451,4 +453,18 @@ public function has(string $key): bool { return isset($this->data[$key]) && $this->data[$key] !== ''; } + + /** + * Returns the correct SVG icon href for a element. + * + * In PHAR mode, icons are inlined in the DOM so we reference by fragment only (#id). + * In normal mode, we reference the external sprite file (assets/icons2.svg#id). + * + * @param string $id Icon identifier (e.g., 'eye', 'trash', 'spinner'). + * @return string The href value for a element. + */ + public function iconRef(string $id): string + { + return $this->isPharMode ? '#' . $id : 'assets/icons2.svg#' . $id; + } } diff --git a/src/Models/FileHandler.php b/src/Models/FileHandler.php index 0388193..d41d2a5 100644 --- a/src/Models/FileHandler.php +++ b/src/Models/FileHandler.php @@ -259,7 +259,7 @@ private function ensureUploadDir(): void /** * Lists available dump files * - * @return array List of files + * @return array List of files sorted by date (newest first) */ public function listFiles(): array { @@ -297,10 +297,12 @@ public function listFiles(): array continue; } + $mtime = filemtime($filepath) ?: 0; $files[] = [ 'name' => $filename, 'size' => filesize($filepath) ?: 0, - 'date' => date('Y-m-d H:i:s', filemtime($filepath) ?: 0), + 'date' => date('Y-m-d H:i:s', $mtime), + 'timestamp' => $mtime, 'type' => $this->getFileType($extension), 'path' => $filepath, ]; @@ -308,8 +310,8 @@ public function listFiles(): array closedir($handle); - // Sort by name - usort($files, fn($a, $b) => strcasecmp($a['name'], $b['name'])); + // Sort by modification date (newest first) + usort($files, fn($a, $b) => $b['timestamp'] <=> $a['timestamp']); return $files; } diff --git a/src/Services/AjaxService.php b/src/Services/AjaxService.php index 9a00b8a..9e99abd 100644 --- a/src/Services/AjaxService.php +++ b/src/Services/AjaxService.php @@ -135,8 +135,11 @@ function displayErrorInPage(message, stats) { helpText + '
' + actionButtons + - 'Resume' + - 'Back to Home' + + '
' + + '' + + '' + + '
' + + 'Back to home' + '
'; // Find main content area and insert error at the beginning @@ -160,7 +163,6 @@ function displayErrorInPage(message, stats) { /** * Displays an import success message directly in the page. * Creates a styled success container matching the error display pattern. - * Optionally displays warnings in an orange section below the success message. * * @param {object} stats Statistics object * @param {array} warnings Array of warning objects (optional) @@ -168,12 +170,11 @@ function displayErrorInPage(message, stats) { * @param {boolean} warningsLimitReached Whether max warnings limit was reached * @param {number} maxWarnings Maximum warnings limit */ - function displaySuccessInPage(stats, warnings, warningsCount, warningsLimitReached, maxWarnings) { + function displaySuccessInPage(stats, warnings, warningsCount, warningsLimitReached, maxWarnings, warnings, warningsCount, warningsLimitReached, maxWarnings) { // Start celebration effects (fireworks + confetti) if (window.BigDump && window.BigDump.celebration) { window.BigDump.celebration.start(); } - var queriesDone = stats && stats.queries_done ? stats.queries_done.toLocaleString() : '0'; var linesDone = stats && stats.lines_done ? stats.lines_done.toLocaleString() : '0'; var bytesDone = stats && stats.bytes_done ? formatBytes(stats.bytes_done) : '0 B'; @@ -188,39 +189,39 @@ function displaySuccessInPage(stats, warnings, warningsCount, warningsLimitReach var warningsHtml = ''; if (hasWarnings) { var warningItems = warnings.map(function(w) { - return '
  • ' + - 'Line ' + w.line + '' + - '' + escapeHtml(w.message.split('\\n')[0]) + '' + + return '
  • ' + + 'Line ' + w.line + '' + + '' + escapeHtml(w.message.split('\\n')[0]) + '' + '
  • '; }).join(''); var limitNotice = ''; if (warningsLimitReached) { - limitNotice = '
    ' + + limitNotice = '
    ' + '⚠️ Warning limit reached (' + maxWarnings + '). Additional errors may have occurred but were not recorded.' + '
    '; } - warningsHtml = '
    ' + + warningsHtml = '
    ' + '
    ' + '
    ' + - '' + + '' + '' + '' + '
    ' + '
    ' + - '

    ' + displayWarningsCount + ' Warning' + (displayWarningsCount !== 1 ? 's' : '') + '

    ' + - '
    Some SQL statements failed but import continued (continue_on_error enabled).
    ' + + '

    ' + displayWarningsCount + ' Warning' + (displayWarningsCount !== 1 ? 's' : '') + '

    ' + + '
    Some SQL statements failed but import continued (continue_on_error enabled).
    ' + '
    ' + '
    ' + '
    ' + - '' + + '' + '' + '' + '' + 'Show Warning Details' + '' + - '
      ' + warningItems + '
    ' + + '
      ' + warningItems + '
    ' + limitNotice + '
    ' + '
    '; @@ -244,29 +245,30 @@ function displaySuccessInPage(stats, warnings, warningsCount, warningsLimitReach '

    ' + successTitle + '

    ' + '
    ' + successSubtitle + '
    ' + '
    ' + + '
    ' + '
    ' + - '
    ' + - '
    ' + queriesDone + '
    ' + - '
    Requêtes
    ' + + '
    ' + + '
    ' + queriesDone + '
    ' + + '
    Queries
    ' + '
    ' + - '
    ' + - '
    ' + linesDone + '
    ' + - '
    Lignes
    ' + + '
    ' + + '
    ' + linesDone + '
    ' + + '
    Lines
    ' + '
    ' + - '
    ' + - '
    ' + bytesDone + '
    ' + - '
    Traité
    ' + + '
    ' + + '
    ' + bytesDone + '
    ' + + '
    Processed
    ' + '
    ' + - '
    ' + - '
    ' + elapsedTime + '
    ' + - '
    Durée
    ' + + '
    ' + + '
    ' + elapsedTime + '
    ' + + '
    Duration
    ' + '
    ' + '
    ' + '
    ' + warningsHtml + ''; // Find main content area and replace content @@ -306,6 +308,7 @@ function escapeHtml(text) { // Elapsed timer variables var elapsedTimerInterval = null; + var timerStarted = false; // Prevent timer reset on SSE reconnect /** * Formats elapsed seconds into HH:MM:SS format. @@ -330,10 +333,14 @@ function updateElapsedTimer() { /** * Starts the elapsed timer (call once when import begins). + * Will not reset if already started (prevents reset on SSE reconnect). */ function startElapsedTimer() { - // Reset start time - startTime = Date.now(); + // Only reset start time on first call (not on SSE reconnect) + if (!timerStarted) { + startTime = Date.now(); + timerStarted = true; + } // Clear any existing interval if (elapsedTimerInterval) { clearInterval(elapsedTimerInterval); @@ -351,6 +358,8 @@ function stopElapsedTimer() { clearInterval(elapsedTimerInterval); elapsedTimerInterval = null; } + // Reset flag so next import can start fresh + timerStarted = false; } /** @@ -901,10 +910,12 @@ function createConnection() { if (source) source.close(); }); - // Handle Stop Import button - close SSE before navigation + // Handle Stop Import button - close SSE and show stopped message document.addEventListener('click', function(e) { - var stopLink = e.target.closest('a[href*="stop_import"]'); + var stopLink = e.target.closest('a[href*="import/stop"]'); if (stopLink) { + e.preventDefault(); + // Close SSE connection immediately intentionalClose = true; stopElapsedTimer(); @@ -914,11 +925,36 @@ function createConnection() { source = null; console.log('SSE: Closed by user (Stop Import)'); } - // Small delay to ensure connection is closed before navigation - e.preventDefault(); - setTimeout(function() { - window.location.href = stopLink.href; - }, 100); + + // Stop progress bar animation + var progressBar = document.querySelector('.progress-bar'); + if (progressBar) { + progressBar.style.animation = 'none'; + progressBar.style.backgroundImage = 'none'; + } + + // Get elapsed time for display + var elapsedEl = document.getElementById('elapsedTime'); + var elapsedTime = elapsedEl ? elapsedEl.textContent : '00:00:00'; + + // Show stopped message in page + var stoppedHtml = '
    ' + + '
    ⏹️
    ' + + '

    Import Stopped

    ' + + '

    The import was manually stopped after ' + elapsedTime + '.

    ' + + '' + + '🏠 Return to Home' + + '' + + '
    '; + + // Replace main content + var mainContent = document.querySelector('main'); + if (mainContent) { + mainContent.innerHTML = stoppedHtml; + } + + // Also call the server to clear session (fire and forget) + fetch(stopLink.href, { method: 'GET' }).catch(function() {}); } }); })(); diff --git a/templates/error.php b/templates/error.php index aca9db6..94cc5c0 100644 --- a/templates/error.php +++ b/templates/error.php @@ -37,7 +37,7 @@

    How to fix it:

      -
    1. Click "Back to Home" below
    2. +
    3. Click "Back to home" below
    4. Select your SQL dump file from the file list
    5. Click "Start Import" to begin
    6. Wait for the import to complete without refreshing the page
    7. @@ -70,7 +70,7 @@

      How to fix it:

        -
      1. Click "Back to Home" below
      2. +
      3. Click "Back to home" below
      4. Check the file list to see available files
      5. Upload your SQL dump file again if needed
      6. Make sure the file has .sql, .gz, or .csv extension
      7. @@ -86,6 +86,6 @@ diff --git a/templates/home.php b/templates/home.php index 4b8fbcd..d8c2716 100644 --- a/templates/home.php +++ b/templates/home.php @@ -1,439 +1,441 @@ - - - - - - -
        - Test Mode Enabled - Queries will be parsed but not executed. -
        - - - - -
        e($uploadResult['message']) ?>
        - -
        e($uploadResult['message']) ?>
        - - - - - -
        e($deleteResult['message']) ?>
        - -
        e($deleteResult['message']) ?>
        - - - - - -
        - Database not configured!
        - Please edit config/config.php and set your database credentials. -
        - -
        - Database connection failed!
        - e($connectionInfo['message']) ?> -
        - -
        - Connected to e($dbName) ?> at e($dbServer) ?>
        - Connection charset: e($connectionInfo['charset']) ?> - (Your dump file must use the same charset) -
        - - - -
        - Predefined file: e($predefinedFile) ?>
        -
        - - -
        -
        - - -
        -

        Available Dump Files

        - -
        - - -
        - No dump files found in e($uploadDir) ?>
        - Upload a .sql, .gz or .csv file using the form below, or via FTP. -
        - - -
        - - - - - - - - - - - - - - - - - - - - - -
        FilenameSizeDateTypeActions
        e($file['name']) ?>formatBytes($file['size']) ?>e($file['date']) ?> - 'badge badge-blue', - 'GZip' => 'badge badge-purple', - 'BZ2' => 'badge badge-purple', - 'CSV' => 'badge badge-green', - default => 'badge badge-blue' - }; - ?> - e($file['type']) ?> - - - - - -
        - - -
        - - not supported - - -
        - - - -
        -
        -
        - -
        - -

        Upload Dump Files

        - - -
        - Upload disabled. Directory e($uploadDir) ?> is not writable.
        - Set permissions to 755 or 777, or upload files via FTP. -
        - - -

        - Maximum file size: formatBytes($uploadMaxSize) ?> - (php.ini: upload_max_filesize, post_max_size) - • Allowed types:
        - For larger files, use FTP to upload directly to e($uploadDir) ?> -

        - -
        - -
        -
        - - - -
        -
        - Click to upload or drag and drop -
        -
        - SQL, GZip or CSV files up to formatBytes($uploadMaxSize) ?> -
        - -
        - - -
        - - - - - - -
        - - - - - - -
        -
        -
        -
        Preparing import...
        -
        Loading file
        -
        -
        - - - - - - + + + + + + +
        + Test Mode Enabled - Queries will be parsed but not executed. +
        + + + + +
        e($uploadResult['message']) ?>
        + +
        e($uploadResult['message']) ?>
        + + + + + +
        e($deleteResult['message']) ?>
        + +
        e($deleteResult['message']) ?>
        + + + + + +
        + Database not configured!
        + Please edit config/config.php and set your database credentials. +
        + +
        + Database connection failed!
        + e($connectionInfo['message']) ?> +
        + +
        + Connected to e($dbName) ?> at e($dbServer) ?>
        + Connection charset: e($connectionInfo['charset']) ?> + (Your dump file must use the same charset) +
        + + + +
        + Predefined file: e($predefinedFile) ?>
        +
        + + +
        +
        + + +
        +

        Available Dump Files

        + +
        + + +
        + No dump files found in e($uploadDir) ?>
        + Upload a .sql, .gz or .csv file using the form below, or via FTP. +
        + + +
        + + + + + + + + + + + + + + + + + + + + + +
        FilenameSizeDate AddedTypeActions
        e($file['name']) ?>formatBytes($file['size']) ?>e($file['date']) ?> + 'badge badge-blue', + 'GZip' => 'badge badge-purple', + 'BZ2' => 'badge badge-purple', + 'CSV' => 'badge badge-green', + default => 'badge badge-blue' + }; + ?> + e($file['type']) ?> + + + + + +
        + + +
        + + not supported + + + + + +
        +
        + +
        + +

        Upload Dump Files

        + + +
        + Upload disabled. Directory e($uploadDir) ?> is not writable.
        + Set permissions to 755 or 777, or upload files via FTP. +
        + + +

        + Maximum file size: formatBytes($uploadMaxSize) ?> + (php.ini: upload_max_filesize, post_max_size) + • Allowed types:
        + For larger files, use FTP to upload directly to e($uploadDir) ?> +

        + +
        + +
        +
        + + + +
        +
        + Click to upload or drag and drop +
        +
        + SQL, GZip or CSV files up to formatBytes($uploadMaxSize) ?> +
        + +
        + + +
        + + + + + + +
        + + + + + + +
        +
        +
        +
        Preparing import...
        +
        Loading file
        +
        +
        + + + + + + diff --git a/templates/import.php b/templates/import.php index 7748198..dadcfb6 100644 --- a/templates/import.php +++ b/templates/import.php @@ -342,7 +342,7 @@ hasError()): ?> @@ -388,8 +388,11 @@ class="btn btn-amber" or - Start Over (resume) - Back to Home +
        + + +
        + Back to home (DROP old tables before restarting) diff --git a/templates/layout.php b/templates/layout.php index 2221d5c..783e7b6 100644 --- a/templates/layout.php +++ b/templates/layout.php @@ -75,8 +75,8 @@
    @@ -99,5 +99,7 @@ + + diff --git a/templates/layout_phar.php b/templates/layout_phar.php index 0f7ed0c..dbc96f4 100644 --- a/templates/layout_phar.php +++ b/templates/layout_phar.php @@ -1,122 +1,122 @@ - - - - - - - - - - BigDump v<?= $view->e($version) ?> - MySQL Dump Importer (PHAR Mode) - - - - - - - -
    - getInlinedIcons() ?> -
    - -
    - -
    - -
    - - -
    - content() ?> -
    - - -
    - BigDump ve($version) ?> - PHAR Edition
    - Original by Alexey Ozerov (2003) | Refactored with improved stability and security | Made by w3spi5 (wespify.com) -
    -
    - - - - - + + + + + + + + + + BigDump v<?= $view->e($version) ?> - MySQL Dump Importer (PHAR Mode) + + + + + + + +
    + getInlinedIcons() ?> +
    + +
    + +
    + +
    + + +
    + content() ?> +
    + + +
    + BigDump ve($version) ?> - PHAR Edition
    + Original by Alexey Ozerov (2003) | Refactored with improved stability and security | Made by w3spi5 (wespify.com) +
    +
    + + + + + diff --git a/tests/RequestTest.php b/tests/RequestTest.php new file mode 100644 index 0000000..9dd2916 --- /dev/null +++ b/tests/RequestTest.php @@ -0,0 +1,100 @@ +test('getScriptUri() strips /index.php from subdirectory path', function () use ($runner) { + $request = makeRequestWithPhpSelf('/bigdump/index.php'); + $runner->assertEquals('/bigdump', $request->getScriptUri()); +}); + +// Test 2: index.php at root +$runner->test('getScriptUri() returns empty string for /index.php at root', function () use ($runner) { + $request = makeRequestWithPhpSelf('/index.php'); + $runner->assertEquals('', $request->getScriptUri()); +}); + +// Test 3: PHAR file at subdirectory +$runner->test('getScriptUri() strips /bigdump.phar from subdirectory path', function () use ($runner) { + $request = makeRequestWithPhpSelf('/tools/bigdump.phar'); + $runner->assertEquals('/tools', $request->getScriptUri()); +}); + +// Test 4: PHAR file at root +$runner->test('getScriptUri() returns empty string for /bigdump.phar at root', function () use ($runner) { + $request = makeRequestWithPhpSelf('/bigdump.phar'); + $runner->assertEquals('', $request->getScriptUri()); +}); + +// Test 5: PHAR with different name +$runner->test('getScriptUri() strips any .phar filename', function () use ($runner) { + $request = makeRequestWithPhpSelf('/subdir/my-app-v2.28.phar'); + $runner->assertEquals('/subdir', $request->getScriptUri()); +}); + +// Test 6: Deep nested path with index.php +$runner->test('getScriptUri() handles deep nested paths with index.php', function () use ($runner) { + $request = makeRequestWithPhpSelf('/a/b/c/index.php'); + $runner->assertEquals('/a/b/c', $request->getScriptUri()); +}); + +// Test 7: Deep nested path with .phar +$runner->test('getScriptUri() handles deep nested paths with .phar', function () use ($runner) { + $request = makeRequestWithPhpSelf('/a/b/c/dump.phar'); + $runner->assertEquals('/a/b/c', $request->getScriptUri()); +}); + +// Test 8: Path without index.php or .phar is left untouched +$runner->test('getScriptUri() leaves non-matching path untouched', function () use ($runner) { + $request = makeRequestWithPhpSelf('/bigdump/other.php'); + $runner->assertEquals('/bigdump/other.php', $request->getScriptUri()); +}); + +echo "\n"; +exit($runner->summary());