@hyxf/pairdrop 0.0.0-stage → 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2 @@
1
+ /*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */
2
+ @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-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-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-leading:initial;--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-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-duration:initial;--tw-outline-style:solid;--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0}}}@layer theme{:root,:host{--font-sans:-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, 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-amber-400:oklch(82.8% .189 84.429);--color-emerald-500:oklch(69.6% .17 162.48);--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-900:oklch(37.9% .146 265.522);--color-indigo-900:oklch(35.9% .144 278.697);--color-violet-200:oklch(89.4% .057 293.283);--color-rose-50:oklch(96.9% .015 12.422);--color-rose-300:oklch(81% .117 11.638);--color-rose-400:oklch(71.2% .194 13.428);--color-rose-500:oklch(64.5% .246 16.439);--color-rose-600:oklch(58.6% .253 17.585);--color-rose-700:oklch(51.4% .222 16.935);--color-slate-50:oklch(98.4% .003 247.858);--color-slate-100:oklch(96.8% .007 247.896);--color-slate-200:oklch(92.9% .013 255.508);--color-slate-300:oklch(86.9% .022 252.894);--color-slate-400:oklch(70.4% .04 256.788);--color-slate-500:oklch(55.4% .046 257.417);--color-slate-600:oklch(44.6% .043 257.281);--color-slate-700:oklch(37.2% .044 257.287);--color-slate-900:oklch(20.8% .042 265.755);--color-slate-950:oklch(12.9% .042 264.695);--color-black:#000;--color-white:#fff;--spacing:.25rem;--container-xs:20rem;--container-md:28rem;--container-lg:32rem;--container-2xl:42rem;--container-5xl:64rem;--container-6xl:72rem;--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-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-4xl:2.25rem;--text-4xl--line-height:calc(2.5 / 2.25);--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--radius-md:.375rem;--radius-lg:.5rem;--radius-xl:.75rem;--radius-2xl:1rem;--radius-3xl:1.5rem;--animate-pulse:pulse 2s cubic-bezier(.4, 0, .6, 1) infinite;--blur-sm:8px;--blur-md:12px;--blur-xl:24px;--blur-2xl:40px;--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)}}@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,-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, 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;-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:where(:not(iframe)){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}.inset-0{inset:0}.-top-1{top:calc(var(--spacing) * -1)}.-top-44{top:calc(var(--spacing) * -44)}.-right-1{right:calc(var(--spacing) * -1)}.-right-36{right:calc(var(--spacing) * -36)}.right-4{right:calc(var(--spacing) * 4)}.-bottom-52{bottom:calc(var(--spacing) * -52)}.bottom-\[calc\(1rem\+env\(safe-area-inset-bottom\)\)\]{bottom:calc(1rem + env(safe-area-inset-bottom))}.-left-32{left:calc(var(--spacing) * -32)}.z-10{z-index:10}.z-20{z-index:20}.z-30{z-index:30}.col-start-2{grid-column-start:2}.row-span-4{grid-row:span 4/span 4}.mx-1{margin-inline:var(--spacing)}.mx-2{margin-inline:calc(var(--spacing) * 2)}.mx-auto{margin-inline:auto}.my-5{margin-block:calc(var(--spacing) * 5)}.my-6{margin-block:calc(var(--spacing) * 6)}.mt-0\.5{margin-top:calc(var(--spacing) * .5)}.mt-1{margin-top:var(--spacing)}.mt-1\.5{margin-top:calc(var(--spacing) * 1.5)}.mt-2{margin-top:calc(var(--spacing) * 2)}.mt-3{margin-top:calc(var(--spacing) * 3)}.mt-4{margin-top:calc(var(--spacing) * 4)}.mt-5{margin-top:calc(var(--spacing) * 5)}.mt-6{margin-top:calc(var(--spacing) * 6)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.mb-3{margin-bottom:calc(var(--spacing) * 3)}.mb-5{margin-bottom:calc(var(--spacing) * 5)}.mb-7{margin-bottom:calc(var(--spacing) * 7)}.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-flex{display:inline-flex}.size-1\.5{width:calc(var(--spacing) * 1.5);height:calc(var(--spacing) * 1.5)}.size-2{width:calc(var(--spacing) * 2);height:calc(var(--spacing) * 2)}.size-9{width:calc(var(--spacing) * 9);height:calc(var(--spacing) * 9)}.size-10{width:calc(var(--spacing) * 10);height:calc(var(--spacing) * 10)}.size-11{width:calc(var(--spacing) * 11);height:calc(var(--spacing) * 11)}.size-12{width:calc(var(--spacing) * 12);height:calc(var(--spacing) * 12)}.size-14{width:calc(var(--spacing) * 14);height:calc(var(--spacing) * 14)}.size-17{width:calc(var(--spacing) * 17);height:calc(var(--spacing) * 17)}.size-36{width:calc(var(--spacing) * 36);height:calc(var(--spacing) * 36)}.size-52{width:calc(var(--spacing) * 52);height:calc(var(--spacing) * 52)}.size-110{width:calc(var(--spacing) * 110);height:calc(var(--spacing) * 110)}.size-130{width:calc(var(--spacing) * 130);height:calc(var(--spacing) * 130)}.h-1{height:var(--spacing)}.h-3{height:calc(var(--spacing) * 3)}.h-10{height:calc(var(--spacing) * 10)}.h-\[min\(720px\,calc\(100dvh-1\.5rem\)\)\]{height:min(720px,100dvh - 1.5rem)}.h-full{height:100%}.h-px{height:1px}.max-h-36{max-height:calc(var(--spacing) * 36)}.max-h-48{max-height:calc(var(--spacing) * 48)}.max-h-64{max-height:calc(var(--spacing) * 64)}.max-h-\[calc\(100dvh-2rem\)\]{max-height:calc(100dvh - 2rem)}.min-h-0{min-height:0}.min-h-4{min-height:calc(var(--spacing) * 4)}.min-h-10{min-height:calc(var(--spacing) * 10)}.min-h-11{min-height:calc(var(--spacing) * 11)}.min-h-16{min-height:calc(var(--spacing) * 16)}.min-h-19{min-height:calc(var(--spacing) * 19)}.min-h-20{min-height:calc(var(--spacing) * 20)}.min-h-52{min-height:calc(var(--spacing) * 52)}.min-h-65{min-height:calc(var(--spacing) * 65)}.min-h-dvh{min-height:100dvh}.w-full{width:100%}.w-px{width:1px}.max-w-2xl{max-width:var(--container-2xl)}.max-w-5xl{max-width:var(--container-5xl)}.max-w-6xl{max-width:var(--container-6xl)}.max-w-\[85\%\]{max-width:85%}.max-w-\[calc\(100vw-2rem\)\]{max-width:calc(100vw - 2rem)}.max-w-lg{max-width:var(--container-lg)}.max-w-md{max-width:var(--container-md)}.max-w-xs{max-width:var(--container-xs)}.min-w-0{min-width:0}.min-w-4{min-width:calc(var(--spacing) * 4)}.min-w-5{min-width:calc(var(--spacing) * 5)}.flex-1{flex:1}.shrink-0{flex-shrink:0}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.animate-pulse{animation:var(--animate-pulse)}.resize-y{resize:vertical}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-\[60px_minmax\(0\,1fr\)\]{grid-template-columns:60px minmax(0,1fr)}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.place-items-center{place-items:center}.items-center{align-items:center}.items-end{align-items:flex-end}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.justify-start{justify-content:flex-start}.justify-items-stretch{justify-items:stretch}.gap-0\.5{gap:calc(var(--spacing) * .5)}.gap-1{gap:var(--spacing)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-4{gap:calc(var(--spacing) * 4)}: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)))}.gap-x-3{column-gap:calc(var(--spacing) * 3)}.gap-x-4{column-gap:calc(var(--spacing) * 4)}.gap-y-2{row-gap:calc(var(--spacing) * 2)}.self-start{align-self:flex-start}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-hidden{overflow-x:hidden}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-2xl{border-radius:var(--radius-2xl)}.rounded-3xl{border-radius:var(--radius-3xl)}.rounded-\[14px\]{border-radius:14px}.rounded-\[20px\]{border-radius:20px}.rounded-\[22px\]{border-radius:22px}.rounded-\[28px\]{border-radius:28px}.rounded-\[30px\]{border-radius:30px}.rounded-full{border-radius:2147483647px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.rounded-xl{border-radius:var(--radius-xl)}.rounded-br-md{border-bottom-right-radius:var(--radius-md)}.rounded-bl-md{border-bottom-left-radius:var(--radius-md)}.border{border-style:var(--tw-border-style);border-width:1px}.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-\[\#dfe6ef\]{border-color:#dfe6ef}.border-\[\#e5e9ef\]{border-color:#e5e9ef}.border-\[\#e8edf4\]{border-color:#e8edf4}.border-\[\#e9edf3\]{border-color:#e9edf3}.border-\[\#edf0f4\]{border-color:#edf0f4}.border-black\/\[\.035\]{border-color:#00000009}@supports (color:color-mix(in lab, red, red)){.border-black\/\[\.035\]{border-color:color-mix(in oklab, var(--color-black) 3.5%, transparent)}}.border-blue-100\/80{border-color:#dbeafecc}@supports (color:color-mix(in lab, red, red)){.border-blue-100\/80{border-color:color-mix(in oklab, var(--color-blue-100) 80%, transparent)}}.border-blue-200{border-color:var(--color-blue-200)}.border-slate-200{border-color:var(--color-slate-200)}.border-white{border-color:var(--color-white)}.border-white\/70{border-color:#ffffffb3}@supports (color:color-mix(in lab, red, red)){.border-white\/70{border-color:color-mix(in oklab, var(--color-white) 70%, transparent)}}.border-white\/80{border-color:#fffc}@supports (color:color-mix(in lab, red, red)){.border-white\/80{border-color:color-mix(in oklab, var(--color-white) 80%, transparent)}}.border-white\/90{border-color:#ffffffe6}@supports (color:color-mix(in lab, red, red)){.border-white\/90{border-color:color-mix(in oklab, var(--color-white) 90%, transparent)}}.bg-\[\#1d1d1f\]{background-color:#1d1d1f}.bg-\[\#30a46c\]{background-color:#30a46c}.bg-\[\#0071e3\]{background-color:#0071e3}.bg-\[\#0878e8\]{background-color:#0878e8}.bg-\[\#101828\]\/45{background-color:oklab(20.9913% -.00390208 -.0339112/.45)}.bg-\[\#eef4fb\]{background-color:#eef4fb}.bg-\[\#eef5ff\]{background-color:#eef5ff}.bg-\[\#f0f4fa\]{background-color:#f0f4fa}.bg-\[\#f1f4f8\]{background-color:#f1f4f8}.bg-\[\#f3f5f8\]{background-color:#f3f5f8}.bg-\[\#f4f7fc\]{background-color:#f4f7fc}.bg-\[\#f7f9fc\]{background-color:#f7f9fc}.bg-\[\#fbfcff\]{background-color:#fbfcff}.bg-amber-400{background-color:var(--color-amber-400)}.bg-black\/25{background-color:#00000040}@supports (color:color-mix(in lab, red, red)){.bg-black\/25{background-color:color-mix(in oklab, var(--color-black) 25%, transparent)}}.bg-black\/35{background-color:#00000059}@supports (color:color-mix(in lab, red, red)){.bg-black\/35{background-color:color-mix(in oklab, var(--color-black) 35%, transparent)}}.bg-blue-50{background-color:var(--color-blue-50)}.bg-blue-200\/45{background-color:#bedbff73}@supports (color:color-mix(in lab, red, red)){.bg-blue-200\/45{background-color:color-mix(in oklab, var(--color-blue-200) 45%, transparent)}}.bg-blue-500{background-color:var(--color-blue-500)}.bg-blue-600{background-color:var(--color-blue-600)}.bg-emerald-500{background-color:var(--color-emerald-500)}.bg-rose-400{background-color:var(--color-rose-400)}.bg-rose-600{background-color:var(--color-rose-600)}.bg-slate-50{background-color:var(--color-slate-50)}.bg-slate-200{background-color:var(--color-slate-200)}.bg-slate-300{background-color:var(--color-slate-300)}.bg-slate-900{background-color:var(--color-slate-900)}.bg-transparent{background-color:#0000}.bg-violet-200\/35{background-color:#ddd6ff59}@supports (color:color-mix(in lab, red, red)){.bg-violet-200\/35{background-color:color-mix(in oklab, var(--color-violet-200) 35%, transparent)}}.bg-white{background-color:var(--color-white)}.bg-white\/65{background-color:#ffffffa6}@supports (color:color-mix(in lab, red, red)){.bg-white\/65{background-color:color-mix(in oklab, var(--color-white) 65%, transparent)}}.bg-white\/70{background-color:#ffffffb3}@supports (color:color-mix(in lab, red, red)){.bg-white\/70{background-color:color-mix(in oklab, var(--color-white) 70%, transparent)}}.bg-white\/75{background-color:#ffffffbf}@supports (color:color-mix(in lab, red, red)){.bg-white\/75{background-color:color-mix(in oklab, var(--color-white) 75%, transparent)}}.bg-white\/85{background-color:#ffffffd9}@supports (color:color-mix(in lab, red, red)){.bg-white\/85{background-color:color-mix(in oklab, var(--color-white) 85%, transparent)}}.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-white\/95{background-color:#fffffff2}@supports (color:color-mix(in lab, red, red)){.bg-white\/95{background-color:color-mix(in oklab, var(--color-white) 95%, transparent)}}.bg-linear-to-br{--tw-gradient-position:to bottom right}@supports (background-image:linear-gradient(in lab, red, red)){.bg-linear-to-br{--tw-gradient-position:to bottom right in oklab}}.bg-linear-to-br{background-image:linear-gradient(var(--tw-gradient-stops))}.from-\[\#4d9cff\]{--tw-gradient-from:#4d9cff;--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-\[\#1769e0\]{--tw-gradient-to:#1769e0;--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))}.p-0\.5{padding:calc(var(--spacing) * .5)}.p-1{padding:var(--spacing)}.p-2{padding:calc(var(--spacing) * 2)}.p-3{padding:calc(var(--spacing) * 3)}.p-4{padding:calc(var(--spacing) * 4)}.p-5{padding:calc(var(--spacing) * 5)}.p-6{padding:calc(var(--spacing) * 6)}.px-1{padding-inline:var(--spacing)}.px-1\.5{padding-inline:calc(var(--spacing) * 1.5)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-4{padding-inline:calc(var(--spacing) * 4)}.px-5{padding-inline:calc(var(--spacing) * 5)}.py-0\.5{padding-block:calc(var(--spacing) * .5)}.py-1{padding-block:var(--spacing)}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)}.py-2\.5{padding-block:calc(var(--spacing) * 2.5)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-4{padding-block:calc(var(--spacing) * 4)}.py-5{padding-block:calc(var(--spacing) * 5)}.py-6{padding-block:calc(var(--spacing) * 6)}.pt-2{padding-top:calc(var(--spacing) * 2)}.pt-3{padding-top:calc(var(--spacing) * 3)}.pt-5{padding-top:calc(var(--spacing) * 5)}.pt-6{padding-top:calc(var(--spacing) * 6)}.pt-\[env\(safe-area-inset-top\)\]{padding-top:env(safe-area-inset-top)}.pb-1{padding-bottom:var(--spacing)}.pb-8{padding-bottom:calc(var(--spacing) * 8)}.pb-\[max\(16px\,env\(safe-area-inset-bottom\)\)\]{padding-bottom:max(16px, env(safe-area-inset-bottom))}.text-center{text-align:center}.text-left{text-align:left}.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-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--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))}.text-\[9px\]{font-size:9px}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.text-\[15px\]{font-size:15px}.text-\[clamp\(2\.1rem\,10vw\,2\.65rem\)\]{font-size:clamp(2.1rem,10vw,2.65rem)}.leading-5{--tw-leading:calc(var(--spacing) * 5);line-height:calc(var(--spacing) * 5)}.leading-6{--tw-leading:calc(var(--spacing) * 6);line-height:calc(var(--spacing) * 6)}.leading-\[1\.04\]{--tw-leading:1.04;line-height:1.04}.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-\[-\.01em\]{--tw-tracking:-.01em;letter-spacing:-.01em}.tracking-\[-\.02em\]{--tw-tracking:-.02em;letter-spacing:-.02em}.tracking-\[-\.03em\]{--tw-tracking:-.03em;letter-spacing:-.03em}.tracking-\[-\.065em\]{--tw-tracking:-.065em;letter-spacing:-.065em}.tracking-\[\.3em\]{--tw-tracking:.3em;letter-spacing:.3em}.tracking-\[\.12em\]{--tw-tracking:.12em;letter-spacing:.12em}.tracking-\[\.15em\]{--tw-tracking:.15em;letter-spacing:.15em}.tracking-\[\.17em\]{--tw-tracking:.17em;letter-spacing:.17em}.wrap-break-word{overflow-wrap:break-word}.whitespace-pre-wrap{white-space:pre-wrap}.text-\[\#1d1d1f\]{color:#1d1d1f}.text-\[\#4b98e9\]{color:#4b98e9}.text-\[\#6b94c4\]{color:#6b94c4}.text-\[\#6e6e73\]{color:#6e6e73}.text-\[\#6e7682\]{color:#6e7682}.text-\[\#7b8491\]{color:#7b8491}.text-\[\#8c94a1\]{color:#8c94a1}.text-\[\#0071e3\]{color:#0071e3}.text-\[\#98a1ae\]{color:#98a1ae}.text-\[\#757c87\]{color:#757c87}.text-\[\#929ba8\]{color:#929ba8}.text-\[\#9299a4\]{color:#9299a4}.text-\[\#86868b\]{color:#86868b}.text-\[\#92969e\]{color:#92969e}.text-\[\#263244\]{color:#263244}.text-\[\#283344\]{color:#283344}.text-\[\#424245\]{color:#424245}.text-\[\#677386\]{color:#677386}.text-\[\#747982\]{color:#747982}.text-\[\#c0c6cf\]{color:#c0c6cf}.text-blue-600{color:var(--color-blue-600)}.text-blue-700{color:var(--color-blue-700)}.text-blue-900{color:var(--color-blue-900)}.text-rose-500{color:var(--color-rose-500)}.text-rose-600{color:var(--color-rose-600)}.text-slate-400{color:var(--color-slate-400)}.text-slate-500{color:var(--color-slate-500)}.text-slate-600{color:var(--color-slate-600)}.text-slate-700{color:var(--color-slate-700)}.text-slate-950{color:var(--color-slate-950)}.text-white{color:var(--color-white)}.text-white\/70{color:#ffffffb3}@supports (color:color-mix(in lab, red, red)){.text-white\/70{color:color-mix(in oklab, var(--color-white) 70%, transparent)}}.uppercase{text-transform:uppercase}.opacity-60{opacity:.6}.shadow-\[0_3px_8px_rgba\(0\,113\,227\,\.2\)\]{--tw-shadow:0 3px 8px var(--tw-shadow-color,#0071e333);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.shadow-\[0_4px_16px_rgba\(0\,0\,0\,\.055\)\]{--tw-shadow:0 4px 16px var(--tw-shadow-color,#0000000e);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.shadow-\[0_4px_18px_rgba\(27\,55\,91\,\.06\)\]{--tw-shadow:0 4px 18px var(--tw-shadow-color,#1b375b0f);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.shadow-\[0_5px_16px_rgba\(0\,113\,227\,\.26\)\]{--tw-shadow:0 5px 16px var(--tw-shadow-color,#0071e342);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.shadow-\[0_5px_20px_rgba\(30\,55\,90\,\.06\)\]{--tw-shadow:0 5px 20px var(--tw-shadow-color,#1e375a0f);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.shadow-\[0_24px_80px_rgba\(0\,0\,0\,\.18\)\]{--tw-shadow:0 24px 80px var(--tw-shadow-color,#0000002e);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.shadow-\[0_24px_80px_rgba\(41\,69\,108\,\.09\)\]{--tw-shadow:0 24px 80px var(--tw-shadow-color,#29456c17);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.shadow-\[0_30px_100px_rgba\(13\,32\,65\,\.28\)\]{--tw-shadow:0 30px 100px var(--tw-shadow-color,#0d204147);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-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)}.ring-2{--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)}.ring-blue-300{--tw-ring-color:var(--color-blue-300)}.blur-\[100px\]{--tw-blur:blur(100px);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,)}.blur-\[110px\]{--tw-blur:blur(110px);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.backdrop-blur-2xl{--tw-backdrop-blur:blur(var(--blur-2xl));-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-blur-md{--tw-backdrop-blur:blur(var(--blur-md));-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-blur-sm{--tw-backdrop-blur:blur(var(--blur-sm));-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-blur-xl{--tw-backdrop-blur:blur(var(--blur-xl));-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.transition{transition-property:color,background-color,border-color,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))}.duration-200{--tw-duration:.2s;transition-duration:.2s}.outline-none{--tw-outline-style:none;outline-style:none}.placeholder\:text-\[\#a3acb8\]::placeholder{color:#a3acb8}@media (hover:hover){.hover\:border-blue-200:hover{border-color:var(--color-blue-200)}.hover\:border-white:hover{border-color:var(--color-white)}.hover\:bg-\[\#006bd2\]:hover{background-color:#006bd2}.hover\:bg-\[\#0064cc\]:hover{background-color:#0064cc}.hover\:bg-\[\#0071e3\]\/8:hover{background-color:oklab(56.2923% -.0462607 -.187713/.08)}.hover\:bg-\[\#f0f3f8\]:hover{background-color:#f0f3f8}.hover\:bg-\[\#f7f9fc\]:hover{background-color:#f7f9fc}.hover\:bg-black:hover{background-color:var(--color-black)}.hover\:bg-black\/4:hover{background-color:#0000000a}@supports (color:color-mix(in lab, red, red)){.hover\:bg-black\/4:hover{background-color:color-mix(in oklab, var(--color-black) 4%, transparent)}}.hover\:bg-blue-50:hover{background-color:var(--color-blue-50)}.hover\:bg-blue-50\/50:hover{background-color:#eff6ff80}@supports (color:color-mix(in lab, red, red)){.hover\:bg-blue-50\/50:hover{background-color:color-mix(in oklab, var(--color-blue-50) 50%, transparent)}}.hover\:bg-blue-100:hover{background-color:var(--color-blue-100)}.hover\:bg-rose-50:hover{background-color:var(--color-rose-50)}.hover\:bg-rose-700:hover{background-color:var(--color-rose-700)}.hover\:bg-slate-100:hover{background-color:var(--color-slate-100)}.hover\:bg-slate-500\/8:hover{background-color:#62748e14}@supports (color:color-mix(in lab, red, red)){.hover\:bg-slate-500\/8:hover{background-color:color-mix(in oklab, var(--color-slate-500) 8%, transparent)}}.hover\:bg-white:hover{background-color:var(--color-white)}.hover\:text-\[\#1d1d1f\]:hover{color:#1d1d1f}.hover\:text-\[\#0071e3\]:hover{color:#0071e3}.hover\:text-blue-600:hover{color:var(--color-blue-600)}.hover\:text-rose-600:hover{color:var(--color-rose-600)}.hover\:text-slate-700:hover{color:var(--color-slate-700)}.hover\:underline:hover{text-decoration-line:underline}.hover\:opacity-100:hover{opacity:1}}.focus\:border-blue-400:focus{border-color:var(--color-blue-400)}.focus-visible\:outline-2:focus-visible{outline-style:var(--tw-outline-style);outline-width:2px}.focus-visible\:outline-offset-2:focus-visible{outline-offset:2px}.focus-visible\:outline-\[\#0071e3\]:focus-visible{outline-color:#0071e3}.active\:scale-\[\.98\]:active{scale:.98}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:bg-\[\#e2e9f1\]:disabled{background-color:#e2e9f1}.disabled\:text-\[\#9aa6b5\]:disabled{color:#9aa6b5}.disabled\:opacity-40:disabled{opacity:.4}.disabled\:opacity-50:disabled{opacity:.5}.disabled\:opacity-60:disabled{opacity:.6}.disabled\:shadow-none:disabled{--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.disabled\:grayscale:disabled{--tw-grayscale:grayscale(100%);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,)}@media (width>=40rem){.sm\:mt-3{margin-top:calc(var(--spacing) * 3)}.sm\:mb-11{margin-bottom:calc(var(--spacing) * 11)}.sm\:block{display:block}.sm\:flex{display:flex}.sm\:hidden{display:none}.sm\:inline{display:inline}.sm\:size-7{width:calc(var(--spacing) * 7);height:calc(var(--spacing) * 7)}.sm\:size-8{width:calc(var(--spacing) * 8);height:calc(var(--spacing) * 8)}.sm\:size-19{width:calc(var(--spacing) * 19);height:calc(var(--spacing) * 19)}.sm\:h-9{height:calc(var(--spacing) * 9)}.sm\:h-\[min\(720px\,calc\(100dvh-3rem\)\)\]{height:min(720px,100dvh - 3rem)}.sm\:min-h-7{min-height:calc(var(--spacing) * 7)}.sm\:w-auto{width:auto}.sm\:max-w-37{max-width:calc(var(--spacing) * 37)}.sm\:max-w-\[75\%\]{max-width:75%}.sm\:max-w-full{max-width:100%}.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.sm\:flex-col{flex-direction:column}.sm\:justify-items-center{justify-items:center}.sm\:gap-1{gap:var(--spacing)}.sm\:gap-x-4{column-gap:calc(var(--spacing) * 4)}.sm\:gap-x-5{column-gap:calc(var(--spacing) * 5)}.sm\:gap-y-5{row-gap:calc(var(--spacing) * 5)}.sm\:self-auto{align-self:auto}.sm\:border-transparent{border-color:#0000}.sm\:bg-transparent{background-color:#0000}.sm\:p-6{padding:calc(var(--spacing) * 6)}.sm\:p-7{padding:calc(var(--spacing) * 7)}.sm\:px-2{padding-inline:calc(var(--spacing) * 2)}.sm\:px-2\.5{padding-inline:calc(var(--spacing) * 2.5)}.sm\:px-3{padding-inline:calc(var(--spacing) * 3)}.sm\:px-6{padding-inline:calc(var(--spacing) * 6)}.sm\:px-7{padding-inline:calc(var(--spacing) * 7)}.sm\:px-8{padding-inline:calc(var(--spacing) * 8)}.sm\:py-3{padding-block:calc(var(--spacing) * 3)}.sm\:py-5{padding-block:calc(var(--spacing) * 5)}.sm\:py-7{padding-block:calc(var(--spacing) * 7)}.sm\:pt-12{padding-top:calc(var(--spacing) * 12)}.sm\:pb-5{padding-bottom:calc(var(--spacing) * 5)}.sm\:pb-10{padding-bottom:calc(var(--spacing) * 10)}.sm\:text-center{text-align:center}.sm\:text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.sm\:text-\[58px\]{font-size:58px}.sm\:font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.sm\:text-\[\#6e6e73\]{color:#6e6e73}.sm\:text-\[\#0071e3\]{color:#0071e3}.sm\:shadow-none{--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}@media (hover:hover){.sm\:hover\:-translate-y-0\.5:hover{--tw-translate-y:calc(var(--spacing) * -.5);translate:var(--tw-translate-x) var(--tw-translate-y)}.sm\:hover\:bg-\[\#0071e3\]\/\[\.07\]:hover{background-color:oklab(56.2923% -.0462607 -.187713/.07)}.sm\:hover\:bg-black\/4\.5:hover{background-color:#0000000b}@supports (color:color-mix(in lab, red, red)){.sm\:hover\:bg-black\/4\.5:hover{background-color:color-mix(in oklab, var(--color-black) 4.5%, transparent)}}}}@media (width>=48rem){.md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}}@media (prefers-color-scheme:dark){.dark\:border-blue-300\/10{border-color:#90c5ff1a}@supports (color:color-mix(in lab, red, red)){.dark\:border-blue-300\/10{border-color:color-mix(in oklab, var(--color-blue-300) 10%, transparent)}}.dark\:border-white\/6{border-color:#ffffff0f}@supports (color:color-mix(in lab, red, red)){.dark\:border-white\/6{border-color:color-mix(in oklab, var(--color-white) 6%, transparent)}}.dark\:border-white\/8{border-color:#ffffff14}@supports (color:color-mix(in lab, red, red)){.dark\:border-white\/8{border-color:color-mix(in oklab, var(--color-white) 8%, transparent)}}.dark\:border-white\/10{border-color:#ffffff1a}@supports (color:color-mix(in lab, red, red)){.dark\:border-white\/10{border-color:color-mix(in oklab, var(--color-white) 10%, transparent)}}.dark\:border-white\/12{border-color:#ffffff1f}@supports (color:color-mix(in lab, red, red)){.dark\:border-white\/12{border-color:color-mix(in oklab, var(--color-white) 12%, transparent)}}.dark\:bg-\[\#1b1e25\]\/75{background-color:oklab(23.5029% -.000753984 -.0142083/.75)}.dark\:bg-\[\#1c1c1e\]\/95{background-color:oklab(22.728% .00105973 -.00367355/.95)}.dark\:bg-\[\#191d26\]{background-color:#191d26}.dark\:bg-\[\#191d26\]\/80{background-color:oklab(23.0802% -.00123414 -.0184478/.8)}.dark\:bg-\[\#111318\]{background-color:#111318}.dark\:bg-\[\#222833\]{background-color:#222833}.dark\:bg-black\/25{background-color:#00000040}@supports (color:color-mix(in lab, red, red)){.dark\:bg-black\/25{background-color:color-mix(in oklab, var(--color-black) 25%, transparent)}}.dark\:bg-blue-400\/10{background-color:#54a2ff1a}@supports (color:color-mix(in lab, red, red)){.dark\:bg-blue-400\/10{background-color:color-mix(in oklab, var(--color-blue-400) 10%, transparent)}}.dark\:bg-blue-500\/15{background-color:#3080ff26}@supports (color:color-mix(in lab, red, red)){.dark\:bg-blue-500\/15{background-color:color-mix(in oklab, var(--color-blue-500) 15%, transparent)}}.dark\:bg-blue-900\/20{background-color:#1c398e33}@supports (color:color-mix(in lab, red, red)){.dark\:bg-blue-900\/20{background-color:color-mix(in oklab, var(--color-blue-900) 20%, transparent)}}.dark\:bg-indigo-900\/15{background-color:#312c8526}@supports (color:color-mix(in lab, red, red)){.dark\:bg-indigo-900\/15{background-color:color-mix(in oklab, var(--color-indigo-900) 15%, transparent)}}.dark\:bg-white{background-color:var(--color-white)}.dark\:bg-white\/3{background-color:#ffffff08}@supports (color:color-mix(in lab, red, red)){.dark\:bg-white\/3{background-color:color-mix(in oklab, var(--color-white) 3%, transparent)}}.dark\:bg-white\/4{background-color:#ffffff0a}@supports (color:color-mix(in lab, red, red)){.dark\:bg-white\/4{background-color:color-mix(in oklab, var(--color-white) 4%, transparent)}}.dark\:bg-white\/5{background-color:#ffffff0d}@supports (color:color-mix(in lab, red, red)){.dark\:bg-white\/5{background-color:color-mix(in oklab, var(--color-white) 5%, transparent)}}.dark\:bg-white\/6{background-color:#ffffff0f}@supports (color:color-mix(in lab, red, red)){.dark\:bg-white\/6{background-color:color-mix(in oklab, var(--color-white) 6%, transparent)}}.dark\:bg-white\/7{background-color:#ffffff12}@supports (color:color-mix(in lab, red, red)){.dark\:bg-white\/7{background-color:color-mix(in oklab, var(--color-white) 7%, transparent)}}.dark\:bg-white\/8{background-color:#ffffff14}@supports (color:color-mix(in lab, red, red)){.dark\:bg-white\/8{background-color:color-mix(in oklab, var(--color-white) 8%, transparent)}}.dark\:bg-white\/10{background-color:#ffffff1a}@supports (color:color-mix(in lab, red, red)){.dark\:bg-white\/10{background-color:color-mix(in oklab, var(--color-white) 10%, transparent)}}.dark\:bg-white\/\[\.06\]{background-color:#ffffff0f}@supports (color:color-mix(in lab, red, red)){.dark\:bg-white\/\[\.06\]{background-color:color-mix(in oklab, var(--color-white) 6%, transparent)}}.dark\:text-\[\#f5f5f7\]{color:#f5f5f7}.dark\:text-black{color:var(--color-black)}.dark\:text-blue-300{color:var(--color-blue-300)}.dark\:text-slate-100{color:var(--color-slate-100)}.dark\:text-slate-200{color:var(--color-slate-200)}.dark\:text-slate-300{color:var(--color-slate-300)}.dark\:text-slate-400{color:var(--color-slate-400)}.dark\:text-slate-500{color:var(--color-slate-500)}.dark\:text-white{color:var(--color-white)}.dark\:shadow-black\/10{--tw-shadow-color:#0000001a}@supports (color:color-mix(in lab, red, red)){.dark\:shadow-black\/10{--tw-shadow-color:color-mix(in oklab, color-mix(in oklab, var(--color-black) 10%, transparent) var(--tw-shadow-alpha), transparent)}}.dark\:shadow-black\/20{--tw-shadow-color:#0003}@supports (color:color-mix(in lab, red, red)){.dark\:shadow-black\/20{--tw-shadow-color:color-mix(in oklab, color-mix(in oklab, var(--color-black) 20%, transparent) var(--tw-shadow-alpha), transparent)}}.dark\:ring-blue-400\/50{--tw-ring-color:#54a2ff80}@supports (color:color-mix(in lab, red, red)){.dark\:ring-blue-400\/50{--tw-ring-color:color-mix(in oklab, var(--color-blue-400) 50%, transparent)}}.dark\:placeholder\:text-slate-500::placeholder{color:var(--color-slate-500)}@media (hover:hover){.dark\:hover\:bg-blue-400\/10:hover{background-color:#54a2ff1a}@supports (color:color-mix(in lab, red, red)){.dark\:hover\:bg-blue-400\/10:hover{background-color:color-mix(in oklab, var(--color-blue-400) 10%, transparent)}}.dark\:hover\:bg-rose-400\/10:hover{background-color:#ff667f1a}@supports (color:color-mix(in lab, red, red)){.dark\:hover\:bg-rose-400\/10:hover{background-color:color-mix(in oklab, var(--color-rose-400) 10%, transparent)}}.dark\:hover\:bg-white\/4:hover{background-color:#ffffff0a}@supports (color:color-mix(in lab, red, red)){.dark\:hover\:bg-white\/4:hover{background-color:color-mix(in oklab, var(--color-white) 4%, transparent)}}.dark\:hover\:bg-white\/7:hover{background-color:#ffffff12}@supports (color:color-mix(in lab, red, red)){.dark\:hover\:bg-white\/7:hover{background-color:color-mix(in oklab, var(--color-white) 7%, transparent)}}.dark\:hover\:bg-white\/10:hover{background-color:#ffffff1a}@supports (color:color-mix(in lab, red, red)){.dark\:hover\:bg-white\/10:hover{background-color:color-mix(in oklab, var(--color-white) 10%, transparent)}}.dark\:hover\:text-rose-300:hover{color:var(--color-rose-300)}.dark\:hover\:text-slate-200:hover{color:var(--color-slate-200)}.dark\:hover\:text-slate-300:hover{color:var(--color-slate-300)}.dark\:hover\:text-white:hover{color:var(--color-white)}}.dark\:disabled\:bg-white\/8:disabled{background-color:#ffffff14}@supports (color:color-mix(in lab, red, red)){.dark\:disabled\:bg-white\/8:disabled{background-color:color-mix(in oklab, var(--color-white) 8%, transparent)}}.dark\:disabled\:text-slate-500:disabled{color:var(--color-slate-500)}}@media (width>=40rem){@media (prefers-color-scheme:dark){.sm\:dark\:border-transparent{border-color:#0000}.sm\:dark\:bg-transparent{background-color:#0000}}}}:root{font-synthesis:none;text-rendering:optimizelegibility;color:#1d1d1f;font-smooth:always;-webkit-font-smoothing:antialiased;background:#f5f5f7;font-family:-apple-system,BlinkMacSystemFont,SF Pro Text,SF Pro Display,Helvetica Neue,Arial,sans-serif}*{box-sizing:border-box}body{min-width:320px;min-height:100dvh;margin:0}button,textarea,input{font:inherit}button{cursor:pointer;-webkit-tap-highlight-color:transparent}::selection{background:#0071e32e}:focus-visible{outline-offset:2px;outline:3px solid #0071e36b}textarea.chat-message-input:focus,textarea.chat-message-input:focus-visible{box-shadow:none;outline:none}@media (width<=639px){main>.fixed.inset-0.z-20{align-items:end;padding:0}main>.fixed.inset-0.z-20>:is(section,form){width:100%;max-width:none;max-height:calc(100dvh - env(safe-area-inset-top,0px) - 12px);overscroll-behavior:contain;padding-bottom:max(24px, env(safe-area-inset-bottom,0px));border-radius:28px 28px 0 0;overflow-y:auto}main>.fixed.inset-0.z-20 :is(input,textarea){font-size:16px}}@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-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-gradient-position{syntax:"*";inherits:false}@property --tw-gradient-from{syntax:"<color>";inherits:false;initial-value:#0000}@property --tw-gradient-via{syntax:"<color>";inherits:false;initial-value:#0000}@property --tw-gradient-to{syntax:"<color>";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:"<length-percentage>";inherits:false;initial-value:0%}@property --tw-gradient-via-position{syntax:"<length-percentage>";inherits:false;initial-value:50%}@property --tw-gradient-to-position{syntax:"<length-percentage>";inherits:false;initial-value:100%}@property --tw-leading{syntax:"*";inherits:false}@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:"<percentage>";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:"<percentage>";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:"<length>";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:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@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}@keyframes pulse{50%{opacity:.5}}
@@ -0,0 +1,14 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40" fill="none">
2
+ <defs>
3
+ <linearGradient id="background" x1="0" y1="0" x2="40" y2="40" gradientUnits="userSpaceOnUse">
4
+ <stop stop-color="#4d9cff" />
5
+ <stop offset="1" stop-color="#1769e0" />
6
+ </linearGradient>
7
+ </defs>
8
+ <rect width="40" height="40" rx="14" fill="url(#background)" />
9
+ <g transform="translate(3.2 3.2) scale(1.4)" stroke="#fff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8">
10
+ <path d="M12 13v8" />
11
+ <path d="M4 14.899A7 7 0 1 1 15.71 8h1.79a4.5 4.5 0 0 1 2.5 8.242" />
12
+ <path d="m8 17 4-4 4 4" />
13
+ </g>
14
+ </svg>
@@ -0,0 +1,25 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
6
+ <meta
7
+ name="viewport"
8
+ content="width=device-width, initial-scale=1.0, viewport-fit=cover"
9
+ />
10
+ <meta name="theme-color" content="#3367d6" />
11
+ <meta name="color-scheme" content="dark light" />
12
+ <meta
13
+ name="description"
14
+ content="Instantly share images, videos, PDFs, and links with people nearby. Peer-to-peer and open source. No setup, no signup."
15
+ />
16
+ <title>
17
+ PairDrop | Transfer Files Cross-Platform. No Setup, No Signup.
18
+ </title>
19
+ <script type="module" crossorigin src="/assets/index-BFMfhZgb.js"></script>
20
+ <link rel="stylesheet" crossorigin href="/assets/index-Cy2qcAPB.css">
21
+ </head>
22
+ <body translate="no">
23
+ <div id="root"></div>
24
+ </body>
25
+ </html>
@@ -0,0 +1,96 @@
1
+ import fs from 'node:fs';
2
+ import { isIP } from 'node:net';
3
+ const defaultRtcConfig = {
4
+ sdpSemantics: 'unified-plan',
5
+ iceServers: [{ urls: 'stun:stun.l.google.com:19302' }],
6
+ };
7
+ function readRateLimit(env, args) {
8
+ if (env.RATE_LIMIT && !['true', 'false'].includes(env.RATE_LIMIT))
9
+ throw new Error('RATE_LIMIT must be true or false. Configure trusted proxies with TRUSTED_PROXY_IPS.');
10
+ return args.includes('--rate-limit') || env.RATE_LIMIT === 'true';
11
+ }
12
+ function readTrustedProxyIps(env) {
13
+ const addresses = (env.TRUSTED_PROXY_IPS ?? '')
14
+ .split(',')
15
+ .map((address) => address.trim())
16
+ .filter(Boolean);
17
+ for (const address of addresses) {
18
+ if (!isIP(address))
19
+ throw new Error(`TRUSTED_PROXY_IPS contains an invalid IP address: ${address}`);
20
+ }
21
+ return addresses.map((address) => address.replace(/^::ffff:/, ''));
22
+ }
23
+ function readButtons(env) {
24
+ const buttonNames = [
25
+ 'donation_button',
26
+ 'twitter_button',
27
+ 'mastodon_button',
28
+ 'bluesky_button',
29
+ 'custom_button',
30
+ 'privacypolicy_button',
31
+ ];
32
+ return Object.fromEntries(buttonNames.map((name) => {
33
+ const prefix = name.toUpperCase();
34
+ return [
35
+ name,
36
+ {
37
+ active: env[`${prefix}_ACTIVE`],
38
+ link: env[`${prefix}_LINK`],
39
+ title: env[`${prefix}_TITLE`],
40
+ },
41
+ ];
42
+ }));
43
+ }
44
+ function normalizeSignalingServer(value) {
45
+ if (!value)
46
+ return false;
47
+ const validCharacters = /^[a-z0-9._~:/?#\]@!$&'()*+,;=-]+$/i.test(value);
48
+ if (!validCharacters || value.includes('://')) {
49
+ throw new Error('SIGNALING_SERVER must be a valid URL without the protocol prefix. Examples: ' +
50
+ '`pairdrop.net`, `pairdrop.example.com:3000`, `example.com/pairdrop`');
51
+ }
52
+ return value.endsWith('/') ? value : `${value}/`;
53
+ }
54
+ /** Build and validate the runtime configuration from environment variables and CLI flags. */
55
+ export function loadConfig(env = process.env, args = process.argv) {
56
+ const signalingServer = normalizeSignalingServer(env.SIGNALING_SERVER && env.SIGNALING_SERVER !== 'false'
57
+ ? env.SIGNALING_SERVER
58
+ : false);
59
+ const config = {
60
+ debugMode: env.DEBUG_MODE === 'true',
61
+ port: Number(env.PORT) || (env.NODE_ENV === 'production' ? 3000 : 3001),
62
+ wsFallback: args.includes('--include-ws-fallback') ||
63
+ env.WS_FALLBACK === 'true' ||
64
+ (env.WS_FALLBACK !== 'false' && !signalingServer),
65
+ // TYPE-EXCEPTION: RTC_CONFIG 沿用原样透传 | 新增格式拒绝会改变配置加载和 WebRTC 初始化行为
66
+ rtcConfig: env.RTC_CONFIG && env.RTC_CONFIG !== 'false'
67
+ ? JSON.parse(fs.readFileSync(env.RTC_CONFIG, 'utf8'))
68
+ : defaultRtcConfig,
69
+ signalingServer,
70
+ ipv6Localize: Number.parseInt(env.IPV6_LOCALIZE ?? '', 10) || false,
71
+ rateLimit: readRateLimit(env, args),
72
+ trustedProxyIps: readTrustedProxyIps(env),
73
+ buttons: readButtons(env),
74
+ autoStart: args.includes('--auto-restart'),
75
+ localhostOnly: args.includes('--localhost-only'),
76
+ };
77
+ if (config.ipv6Localize &&
78
+ !(config.ipv6Localize > 0 && config.ipv6Localize < 8)) {
79
+ throw new Error('IPV6_LOCALIZE must be an integer between 1 and 7.');
80
+ }
81
+ if (config.ipv6Localize) {
82
+ console.log('IPv6 client IPs will be localized to', config.ipv6Localize, config.ipv6Localize === 1 ? 'segment' : 'segments');
83
+ }
84
+ if (config.signalingServer &&
85
+ (env.RTC_CONFIG || config.wsFallback || config.ipv6Localize)) {
86
+ throw new Error('SIGNALING_SERVER cannot be used alongside WS_FALLBACK, RTC_CONFIG, or IPV6_LOCALIZE. ' +
87
+ 'These settings must be configured on the signaling server.');
88
+ }
89
+ if (config.debugMode) {
90
+ console.log('DEBUG_MODE is active. To protect privacy, do not use in production.');
91
+ console.debug('\n----DEBUG ENVIRONMENT VARIABLES----');
92
+ console.debug(JSON.stringify(config, null, 4));
93
+ console.debug('\n');
94
+ }
95
+ return config;
96
+ }
@@ -0,0 +1,346 @@
1
+ import crypto from 'node:crypto';
2
+ import { WebSocket } from 'ws';
3
+ import RoomManager, {} from '../rooms/room-manager.js';
4
+ import { randomizer } from '../../shared/helper.js';
5
+ const PAIR_CODE_TTL_MS = 5 * 60 * 1000;
6
+ const PAIR_REQUEST_TTL_MS = 60 * 1000;
7
+ const isRoomSecret = (value) => typeof value === 'string' &&
8
+ value.length >= 64 &&
9
+ value.length <= 256 &&
10
+ [...value].every((character) => character.charCodeAt(0) <= 127);
11
+ /** Owns temporary pairing codes and the secret rooms created by pairing. */
12
+ export default class PairingManager {
13
+ rooms;
14
+ send;
15
+ pairings = new Map();
16
+ requests = new Map();
17
+ requestAttempts = new WeakMap();
18
+ constructor(rooms, send) {
19
+ this.rooms = rooms;
20
+ this.send = send;
21
+ }
22
+ joinSecretRooms(sender, message) {
23
+ if (!Array.isArray(message.roomSecrets))
24
+ return;
25
+ const secrets = message.roomSecrets.filter(isRoomSecret);
26
+ this.rooms.joinSecretRooms(sender, secrets);
27
+ }
28
+ deleteSecretRooms(sender, message) {
29
+ if (!Array.isArray(message.roomSecrets))
30
+ return;
31
+ for (const secret of message.roomSecrets) {
32
+ if (typeof secret !== 'string' || !sender.roomSecrets.includes(secret))
33
+ continue;
34
+ for (const peer of this.rooms.getPeers(secret)) {
35
+ this.rooms.leaveSecretRoom(peer, secret, true);
36
+ this.send(peer, { type: 'secret-room-deleted', roomSecret: secret });
37
+ }
38
+ }
39
+ }
40
+ initiate(sender) {
41
+ const roomSecret = randomizer.getRandomString(256);
42
+ const pairKey = this.createPairKey(sender, roomSecret);
43
+ if (sender.pairKey) {
44
+ const oldSecret = this.pairings.get(sender.pairKey)?.roomSecret;
45
+ this.removePairKey(sender.pairKey);
46
+ if (oldSecret)
47
+ this.rooms.leaveSecretRoom(sender, oldSecret);
48
+ }
49
+ sender.pairKey = pairKey;
50
+ this.send(sender, { type: 'pair-device-initiated', roomSecret, pairKey });
51
+ this.rooms.joinSecretRoom(sender, roomSecret);
52
+ }
53
+ join(sender, message) {
54
+ if (sender.rateLimitReached()) {
55
+ this.send(sender, { type: 'join-key-rate-limit' });
56
+ return;
57
+ }
58
+ const pairing = typeof message.pairKey === 'string'
59
+ ? this.pairings.get(message.pairKey)
60
+ : undefined;
61
+ if (!pairing || sender.id === pairing.creator.id) {
62
+ this.send(sender, { type: 'pair-device-join-key-invalid' });
63
+ return;
64
+ }
65
+ const { roomSecret, creator } = pairing;
66
+ // TYPE-EXCEPTION: pairing 存在时 pairKey 必为有效字符串 | 再加分支会改变现有配对流程
67
+ this.removePairKey(message.pairKey);
68
+ this.send(sender, {
69
+ type: 'pair-device-joined',
70
+ roomSecret,
71
+ peerId: creator.id,
72
+ });
73
+ this.send(creator, {
74
+ type: 'pair-device-joined',
75
+ roomSecret,
76
+ peerId: sender.id,
77
+ });
78
+ this.rooms.joinSecretRoom(sender, roomSecret);
79
+ if (sender.pairKey) {
80
+ const oldSecret = this.pairings.get(sender.pairKey)?.roomSecret;
81
+ this.removePairKey(sender.pairKey);
82
+ if (oldSecret)
83
+ this.rooms.leaveSecretRoom(sender, oldSecret);
84
+ }
85
+ }
86
+ cancel(sender) {
87
+ const pairKey = sender.pairKey;
88
+ if (!pairKey)
89
+ return;
90
+ const roomSecret = this.pairings.get(pairKey)?.roomSecret;
91
+ this.removePairKey(pairKey);
92
+ if (roomSecret)
93
+ this.rooms.leaveSecretRoom(sender, roomSecret);
94
+ this.send(sender, { type: 'pair-device-canceled', pairKey });
95
+ }
96
+ request(sender, message) {
97
+ const peerId = message.peerId;
98
+ const attemptId = message.attemptId;
99
+ if (typeof peerId !== 'string' ||
100
+ typeof attemptId !== 'string' ||
101
+ attemptId.length > 64)
102
+ return;
103
+ const recipient = this.rooms.getPeer(sender.ip, peerId);
104
+ const reason = !sender.directPairingSupported ||
105
+ !recipient?.directPairingSupported ||
106
+ sender.socket.readyState !== WebSocket.OPEN ||
107
+ recipient.socket.readyState !== WebSocket.OPEN ||
108
+ this.rooms.getPeer(sender.ip, sender.id) !== sender ||
109
+ recipient === sender
110
+ ? 'unavailable'
111
+ : this.arePaired(sender, recipient)
112
+ ? 'already-paired'
113
+ : this.hasPendingRequest(sender) || this.hasPendingRequest(recipient)
114
+ ? 'busy'
115
+ : null;
116
+ if (reason) {
117
+ this.send(sender, {
118
+ type: 'pair-request-rejected',
119
+ peerId,
120
+ attemptId,
121
+ reason,
122
+ });
123
+ return;
124
+ }
125
+ const now = Date.now();
126
+ const attempts = (this.requestAttempts.get(sender) ?? []).filter((time) => now - time < 60_000);
127
+ if (attempts.length >= 5) {
128
+ this.send(sender, {
129
+ type: 'pair-request-rejected',
130
+ peerId,
131
+ attemptId,
132
+ reason: 'rate-limited',
133
+ });
134
+ return;
135
+ }
136
+ attempts.push(now);
137
+ this.requestAttempts.set(sender, attempts);
138
+ const id = crypto.randomUUID();
139
+ const timer = setTimeout(() => this.endRequest(id, 'expired'), PAIR_REQUEST_TTL_MS);
140
+ this.requests.set(id, {
141
+ id,
142
+ attemptId,
143
+ sender,
144
+ recipient,
145
+ timer,
146
+ confirmed: new Set(),
147
+ });
148
+ this.send(sender, {
149
+ type: 'pair-request-sent',
150
+ peerId,
151
+ attemptId,
152
+ requestId: id,
153
+ });
154
+ this.send(recipient, {
155
+ type: 'pair-request-incoming',
156
+ requestId: id,
157
+ peerId: sender.id,
158
+ peerName: sender.name.displayName,
159
+ });
160
+ }
161
+ respond(recipient, message) {
162
+ if (typeof message.requestId !== 'string')
163
+ return;
164
+ const request = this.requests.get(message.requestId);
165
+ if (!request || request.recipient !== recipient)
166
+ return;
167
+ if (request.roomSecret)
168
+ return;
169
+ if (message.accept !== true) {
170
+ this.endRequest(request.id, 'declined');
171
+ return;
172
+ }
173
+ const { sender } = request;
174
+ if (this.rooms.getPeer(sender.ip, sender.id) !== sender ||
175
+ this.rooms.getPeer(sender.ip, recipient.id) !== recipient ||
176
+ sender.socket.readyState !== WebSocket.OPEN ||
177
+ recipient.socket.readyState !== WebSocket.OPEN ||
178
+ this.arePaired(sender, recipient) ||
179
+ !sender.directPairingSupported ||
180
+ !recipient.directPairingSupported) {
181
+ this.endRequest(request.id, 'unavailable');
182
+ return;
183
+ }
184
+ const roomSecret = randomizer.getRandomString(256);
185
+ request.roomSecret = roomSecret;
186
+ clearTimeout(request.timer);
187
+ request.timer = setTimeout(() => this.endRequest(request.id, 'expired'), 15_000);
188
+ for (const peer of [sender, recipient]) {
189
+ this.send(peer, {
190
+ type: 'pair-request-prepare',
191
+ requestId: request.id,
192
+ roomSecret,
193
+ });
194
+ }
195
+ }
196
+ confirmStorage(peer, message) {
197
+ if (typeof message.requestId !== 'string')
198
+ return;
199
+ const request = this.requests.get(message.requestId);
200
+ if (!request?.roomSecret ||
201
+ (request.sender !== peer && request.recipient !== peer))
202
+ return;
203
+ if (message.saved !== true) {
204
+ this.endRequest(request.id, 'storage-failed');
205
+ return;
206
+ }
207
+ request.confirmed.add(peer);
208
+ if (request.confirmed.size !== 2)
209
+ return;
210
+ if (request.sender.socket.readyState !== WebSocket.OPEN ||
211
+ request.recipient.socket.readyState !== WebSocket.OPEN ||
212
+ this.rooms.getPeer(request.sender.ip, request.sender.id) !==
213
+ request.sender ||
214
+ this.rooms.getPeer(request.sender.ip, request.recipient.id) !==
215
+ request.recipient) {
216
+ this.endRequest(request.id, 'unavailable');
217
+ return;
218
+ }
219
+ this.removeRequest(request.id);
220
+ this.cancelCodeSilently(request.sender);
221
+ this.cancelCodeSilently(request.recipient);
222
+ this.rooms.joinSecretRoom(request.sender, request.roomSecret);
223
+ this.rooms.joinSecretRoom(request.recipient, request.roomSecret);
224
+ this.send(request.sender, {
225
+ type: 'pair-device-joined',
226
+ method: 'direct',
227
+ requestId: request.id,
228
+ roomSecret: request.roomSecret,
229
+ peerId: request.recipient.id,
230
+ });
231
+ this.send(request.recipient, {
232
+ type: 'pair-device-joined',
233
+ method: 'direct',
234
+ requestId: request.id,
235
+ roomSecret: request.roomSecret,
236
+ peerId: request.sender.id,
237
+ });
238
+ }
239
+ cancelRequest(sender, message) {
240
+ if (typeof message.peerId !== 'string')
241
+ return;
242
+ const request = [...this.requests.values()].find((entry) => entry.sender === sender && entry.recipient.id === message.peerId);
243
+ if (request)
244
+ this.endRequest(request.id, 'canceled');
245
+ }
246
+ removePeer(sender) {
247
+ for (const request of [...this.requests.values()]) {
248
+ if (request.sender === sender || request.recipient === sender)
249
+ this.endRequest(request.id, 'unavailable');
250
+ }
251
+ if (sender.pairKey)
252
+ this.removePairKey(sender.pairKey);
253
+ sender.pairKey = null;
254
+ }
255
+ arePaired(sender, recipient) {
256
+ return sender.roomSecrets.some((secret) => recipient.roomSecrets.includes(secret));
257
+ }
258
+ cancelCodeSilently(peer) {
259
+ const pairKey = peer.pairKey;
260
+ if (!pairKey)
261
+ return;
262
+ const roomSecret = this.pairings.get(pairKey)?.roomSecret;
263
+ this.removePairKey(pairKey);
264
+ if (roomSecret)
265
+ this.rooms.leaveSecretRoom(peer, roomSecret);
266
+ }
267
+ hasPendingRequest(peer) {
268
+ return [...this.requests.values()].some((request) => request.sender === peer || request.recipient === peer);
269
+ }
270
+ removeRequest(id) {
271
+ const request = this.requests.get(id);
272
+ if (!request)
273
+ return;
274
+ clearTimeout(request.timer);
275
+ this.requests.delete(id);
276
+ return request;
277
+ }
278
+ endRequest(id, reason) {
279
+ const request = this.removeRequest(id);
280
+ if (!request)
281
+ return;
282
+ if (request.roomSecret) {
283
+ for (const peer of [request.sender, request.recipient]) {
284
+ this.send(peer, {
285
+ type: 'pair-request-rollback',
286
+ requestId: id,
287
+ roomSecret: request.roomSecret,
288
+ });
289
+ }
290
+ }
291
+ this.send(request.sender, {
292
+ type: 'pair-request-ended',
293
+ requestId: id,
294
+ peerId: request.recipient.id,
295
+ attemptId: request.attemptId,
296
+ reason,
297
+ });
298
+ this.send(request.recipient, {
299
+ type: 'pair-request-ended',
300
+ requestId: id,
301
+ peerId: request.sender.id,
302
+ reason,
303
+ });
304
+ }
305
+ regenerateSecret(sender, message) {
306
+ const oldRoomSecret = message.roomSecret;
307
+ if (typeof oldRoomSecret !== 'string' ||
308
+ !sender.roomSecrets.includes(oldRoomSecret) ||
309
+ !this.rooms.hasRoom(oldRoomSecret))
310
+ return;
311
+ const newRoomSecret = randomizer.getRandomString(256);
312
+ for (const peer of this.rooms.getPeers(oldRoomSecret)) {
313
+ this.send(peer, {
314
+ type: 'room-secret-regenerated',
315
+ oldRoomSecret,
316
+ newRoomSecret,
317
+ });
318
+ }
319
+ this.rooms.clearSecretRoom(oldRoomSecret);
320
+ }
321
+ createPairKey(creator, roomSecret) {
322
+ let pairKey;
323
+ do {
324
+ pairKey = crypto.randomInt(1000000, 1999999).toString().substring(1);
325
+ } while (this.pairings.has(pairKey));
326
+ const timer = setTimeout(() => {
327
+ this.removePairKey(pairKey);
328
+ this.rooms.leaveSecretRoom(creator, roomSecret);
329
+ this.send(creator, {
330
+ type: 'pair-device-canceled',
331
+ pairKey,
332
+ reason: 'expired',
333
+ });
334
+ }, PAIR_CODE_TTL_MS);
335
+ this.pairings.set(pairKey, { roomSecret, creator, timer });
336
+ return pairKey;
337
+ }
338
+ removePairKey(pairKey) {
339
+ const pairing = this.pairings.get(pairKey);
340
+ if (!pairing)
341
+ return;
342
+ clearTimeout(pairing.timer);
343
+ pairing.creator.pairKey = null;
344
+ this.pairings.delete(pairKey);
345
+ }
346
+ }