diff --git a/Dockerfile b/Dockerfile index e00c4dd..7015a55 100644 --- a/Dockerfile +++ b/Dockerfile @@ -107,9 +107,9 @@ RUN php -r "file_exists('.env') || copy('.env.example', '.env');" \ # 改 src/ 里这三个文件后必须同步更新这里的 md5(故意做成"改了不更新就构建失败"), # 并重新生成 patches/ios-longpress.patch(tools/diff-vs-upstream.sh)。 RUN printf '%s\n' \ - 'b916555ab122d2b3db99153d9316a887 ./public/js/context-js/context-js.js' \ - 'b916555ab122d2b3db99153d9316a887 ./resources/js/context-js.js' \ - '00f38dbf29c3d5cd9800dfd1d6709fb9 ./resources/views/user/images.blade.php' \ + '3614ec17912e56dd6439b7acfccffd2c ./public/js/context-js/context-js.js' \ + '3614ec17912e56dd6439b7acfccffd2c ./resources/js/context-js.js' \ + '09ba35f39ace53533513f168c739e0ab ./resources/views/user/images.blade.php' \ | md5sum -c - \ && grep -q "assetVersion('js/context-js/context-js.js')" ./resources/views/user/images.blade.php \ && grep -q 'isIOSWebKit' ./public/js/context-js/context-js.js \ @@ -129,8 +129,8 @@ RUN printf '%s\n' \ RUN printf '%s\n' \ "fork_sha=${FORK_SHA}" \ "lsky_commit=${LSKY_COMMIT}" \ - "context_js_md5=b916555ab122d2b3db99153d9316a887" \ - "images_blade_md5=00f38dbf29c3d5cd9800dfd1d6709fb9" \ + "context_js_md5=3614ec17912e56dd6439b7acfccffd2c" \ + "images_blade_md5=09ba35f39ace53533513f168c739e0ab" \ > .code-revision \ && cat .code-revision diff --git a/patches/ios-longpress.patch b/patches/ios-longpress.patch index d5c491a..87409ef 100644 --- a/patches/ios-longpress.patch +++ b/patches/ios-longpress.patch @@ -34,9 +34,9 @@ # 桌面端不受影响:仍是 hover 侧开 + fitSubmenu 钳制。 # # 三个文件各自的 md5(与 Dockerfile「自证 2」里的期望值一致): -# b916555ab122d2b3db99153d9316a887 src/resources/js/context-js.js -# b916555ab122d2b3db99153d9316a887 src/public/js/context-js/context-js.js -# 00f38dbf29c3d5cd9800dfd1d6709fb9 src/resources/views/user/images.blade.php +# 3614ec17912e56dd6439b7acfccffd2c src/resources/js/context-js.js +# 3614ec17912e56dd6439b7acfccffd2c src/public/js/context-js/context-js.js +# 09ba35f39ace53533513f168c739e0ab src/resources/views/user/images.blade.php # # 上游原文件 md5(改动前,供比对): # c8e57f6232848ca8277341ddf1a3a7a6 resources/js/context-js.js @@ -95,7 +95,7 @@ let options = { fadeSpeed: 100, filter: function ($obj) { -@@ -15,14 +55,736 @@ +@@ -15,14 +55,819 @@ compress: false }; @@ -252,6 +252,9 @@ + let touchGestureId = 0; // 每一次「手指按下」算一个新手势 + let armedGestureId = 0; // 二级菜单是在哪一次手势里被打开的 + let submenuArmingTimer = null; // 「刚弹出、先别高亮」的收尾定时器 // 因"点了菜单外"而关菜单后,要吞掉的那一发 click ++ let armedMenuGestureId = 0; // 主菜单是在哪一次手势里被打开的 ++ let menuArmingTimer = null; // 主菜单「刚弹出、先别高亮」的收尾定时器 ++ let fingerOnScreen = false; // 屏幕上还有没有手指按着(主菜单 arming 窗口据此跟手势续期,而不是死定时器) + let menuStylesInjected = false; + let menuGuardInstalled = false; + @@ -285,6 +288,15 @@ + + ' min-width: 0 !important; margin: 0 !important; padding: 0 !important;' + + ' border: 0 !important; border-radius: 0 !important; box-shadow: none !important;' + + ' background: transparent !important; }' ++ // 主菜单版(与下面二级菜单那份同一套 arming 机制,见 armMenuHoverGuard): ++ // 长按弹出的主菜单同样会顶在手指底下,真机触摸的粘滞 hover 会把手指底下那一项当成 hover 目标 ++ // → 弹出瞬间那项就跳高亮(老师截图里的「复制图片」)。这里连 li 一起关指针事件: ++ // 只关 a 的话,主题里的 `.dropdown-submenu:hover > a` 仍会因为 li 被 hover 而给「复制链接」上色。 ++ // 另外 hover 那套是「底色 + 文字变白」两件一起上(见 context-js.less 的 li>a:hover), ++ // 所以文字色也一并还原 —— 万一 :hover/:focus 还是粘住了,那一项也保持原样,不会变成白底白字。 ++ + '.dropdown-context.menu-arming > li,' ++ + ' .dropdown-context.menu-arming > li > a {' ++ + ' pointer-events: none !important; background-color: transparent !important; color: inherit !important; }' + // 刚弹出的一瞬间:项不接受指针事件(浏览器就不会把它当成 hover 目标 → 不会高亮), + // 同时把 hover 底色压平做双保险;不改变任何尺寸/位置,面板本身照旧接收点击(由守卫吞掉)。 + + '.dropdown-context.submenu-inplace.submenu-arming > li.submenu-active > .dropdown-menu > li > a,' @@ -390,6 +402,9 @@ + const EDGE_MARGIN = 8; + const SUBMENU_GUARD = 350; // 二级菜单「就地替换」后这段毫秒内,触摸点击一律吞掉(防按住误选) + const SUBMENU_ARM_MS = 420; // 二级菜单刚弹出的这段毫秒内,先别接受指针指向(防那一下高亮跳色) ++ const MENU_ARM_MS = 420; // 主菜单(长按弹出)同理:弹出后这段时间内的项不接受指针指向 ++ const MENU_ARM_TAIL_MS = 260; // 抬手之后再压这么多毫秒 —— 老师原话「手抬起来的那一瞬间留冗余」, ++ // 与二级菜单那份抬手冗余同一个数(见 armMenuHoverGuard / touchend) + + function viewportSize() { + const de = document.documentElement; @@ -542,6 +557,50 @@ + $('.dropdown-context').removeClass('submenu-arming'); + } + ++ // 主菜单版(同一套机制,老师要求「复用到长按弹出的主菜单」): ++ // 主菜单弹在手指底下时,那一项会被浏览器判成 hover 目标 → 弹出瞬间跳高亮,功能和以前一样、就是看着脏。 ++ // 做法与二级菜单那份一致:挂 .menu-arming(里面的项不接受指针事件 + hover 底色压平), ++ // 抬手后由 touchend 再续 MENU_ARM_TAIL_MS —— 窗口跟着手势走,不是弹出时定死的一个定时器。 ++ // 只在「这一发菜单确实是紧随一次触摸打开」时才挂(判据见 openMenu):桌面右键路径因此一行都不动。 ++ function armMenuHoverGuard(ms) { ++ $('.dropdown-context:not(.dropdown-context-sub)').addClass('menu-arming'); ++ clearTimeout(menuArmingTimer); ++ menuArmingTimer = setTimeout(function () { ++ // 手指还按着(而且就是按出这个菜单的那次手势)→ 续期:按住多久都不跳色; ++ // 抬手那条路径会再压 MENU_ARM_TAIL_MS 收尾。 ++ if (fingerOnScreen && armedMenuGestureId && armedMenuGestureId === touchGestureId) { ++ armMenuHoverGuard(MENU_ARM_MS); ++ return; ++ } ++ $('.dropdown-context').removeClass('menu-arming'); ++ }, ms); ++ } ++ ++ function clearMenuHoverGuard() { ++ clearTimeout(menuArmingTimer); ++ $('.dropdown-context').removeClass('menu-arming'); ++ } ++ ++ // arming 生效时面板里的项不接受指针事件(这正是"不跳高亮"的关键),于是手指按在菜单项上时 ++ // e.target 拿到的是面板本身 —— 「按一下就展开二级菜单」这条触摸路径会被吃掉。 ++ // 这里按触摸坐标把手指底下那一行找回来,只给这一个分支用,保证窗口内的第一次点按与平时一致。 ++ function rowUnderTouch(e) { ++ if (! e || ! e.touches || ! e.touches.length) { ++ return null; ++ } ++ ++ const t = e.touches[0], x = t.clientX, y = t.clientY; ++ const rows = document.querySelectorAll('.dropdown-context:not(.dropdown-context-sub) > li'); ++ for (let i = 0; i < rows.length; i++) { ++ const r = rows[i].getBoundingClientRect(); ++ if (r.width && r.height && x >= r.left && x <= r.right && y >= r.top && y <= r.bottom) { ++ return rows[i]; ++ } ++ } ++ ++ return null; ++ } ++ + // 打开某个父项的二级菜单:手机惯例是「按下就开」,这样抬手那发合成 click 也有东西可吞。 + function openSubmenuFor($li) { + $li.siblings('.dropdown-submenu').removeClass('touch-open'); @@ -729,13 +788,23 @@ + // 等抬手才处理就晚了(那时 click 已经落在菜单项上了)。 + // 抬手:面板若还在「刚弹出」窗口内,把窗口续到抬手之后 —— 按住再久,那一刻也不跳高亮。 + ['touchend', 'touchcancel'].forEach(function (evt) { -+ document.addEventListener(evt, function () { ++ document.addEventListener(evt, function (e) { ++ // 屏幕上还有手指按着吗(多指时抬手一根不算)——主菜单 arming 窗口据此决定要不要续期 ++ fingerOnScreen = !! (e && e.touches && e.touches.length); ++ + // 判据是「这一发手指就是打开二级菜单的那一发」——不能看 .submenu-arming 还在不在: + // 按住超过 SUBMENU_ARM_MS 时,那个类早就被定时器摘掉了(踩过)。 + if (armedGestureId && armedGestureId === touchGestureId + && $('.dropdown-context.submenu-inplace').length) { + armSubmenuHoverGuard(260); // 抬手后再压 260ms,抬手那一刻也不会跳色 + } ++ ++ // 主菜单同理(老师原话:「只要给我手抬起来的那一瞬间留冗余就够」)—— ++ // 判据同样是「这一发手指就是打开主菜单的那一发」,按住再久也不会在抬手那一刻跳色。 ++ if (armedMenuGestureId && armedMenuGestureId === touchGestureId ++ && $('.dropdown-context:not(.dropdown-context-sub)').length) { ++ armMenuHoverGuard(MENU_ARM_TAIL_MS); ++ } + }, true); + }); + @@ -744,7 +813,17 @@ + + // 只看「手指底下这个 a 的直接父级」—— 二级菜单的项嵌在父 li 里面, + // 用 closest('li.dropdown-submenu') 会把二级菜单里的项也误判成父行。 -+ const $a = $(e.target).closest('a'); ++ let $a = $(e.target).closest('a'); ++ // 主菜单 arming 窗口里,项不接受指针事件 → 手指按在「复制链接」上时 e.target 只是面板, ++ // 这一发就按不到那个 a。按坐标把行找回来,只补「按一下就展开二级菜单」这一条路 ++ // (叶子项靠抬手补发的那发 click,本来就不受影响;就地替换态不兜底,那时的行高/内容都变了)。 ++ if (! $a.length && $('.dropdown-context.menu-arming').length) { ++ const row = rowUnderTouch(e); ++ if (row && row.classList.contains('dropdown-submenu') ++ && ! row.classList.contains('submenu-active')) { ++ $a = $(row).children('a'); ++ } ++ } + const $row = $a.parent(); + if ($row.hasClass('dropdown-submenu') && $row.closest('.dropdown-context').length) { + if ($row.hasClass('submenu-active')) { @@ -754,13 +833,17 @@ + } + submenuInplaceAt = Date.now(); + armedGestureId = touchGestureId; // 武装到本次手势:这根手指抬起来的那发 click 必吞 -+ } else if ($('.dropdown-context.submenu-arming').length) { -+ clearSubmenuHoverGuard(); // 手指挪去点别的东西了:立刻恢复正常 hover/点击 ++ } else { ++ // 手指挪去点别的东西了(包括点菜单项、滑菜单):立刻恢复正常 hover/点击。 ++ // 两套 arming 都清 —— 它们各自只对「按出它来的那一次手势」负责,新手势一律失效。 ++ clearSubmenuHoverGuard(); ++ clearMenuHoverGuard(); + } + }, true); + + document.addEventListener('touchstart', function (e) { + lastTouchAt = Date.now(); ++ fingerOnScreen = true; // 手指按下:主菜单 arming 窗口可以跟手势续期 + + if (! menuOnScreen() || isInsideMenu(e.target)) { + return; @@ -835,7 +918,7 @@ }); if (options.preventDoubleContext) { $(document).on('contextmenu', '.dropdown-context', function (e) { -@@ -30,13 +792,8 @@ +@@ -30,13 +875,8 @@ }); } $(document).on('mouseenter', '.dropdown-submenu', function () { @@ -851,7 +934,7 @@ }); } -@@ -130,12 +887,15 @@ +@@ -130,12 +970,15 @@ let data = opts.data || {}; let id = new Date().getTime(); @@ -872,7 +955,7 @@ let $menu = buildMenu(item, data, id); // clear dropdowns -@@ -144,42 +904,108 @@ +@@ -144,42 +987,117 @@ $('body').append($menu); $('.dropdown-context:not(.dropdown-context-sub)').hide(); @@ -917,6 +1000,15 @@ + menuOpenedAt = Date.now(); + menuClosingUntil = 0; + ++ // 刚弹出的主菜单会顶在手指底下:那一项马上会被当成 hover 目标 → 弹出瞬间就跳高亮。 ++ // 复用二级菜单那套 arming:挂 .menu-arming(项不接受指针事件 + hover 底色压平),抬手后再续 260ms。 ++ // **只在「这一发菜单确实紧随一次触摸」时才挂**:鼠标右键路径(lastTouchAt 是 0 或很久以前) ++ // 与上游完全一致 —— 桌面本来没有粘滞 hover,不该为此改动付出任何代价。 ++ if ((Date.now() - lastTouchAt) < MENU_OPEN_GRACE) { ++ armedMenuGestureId = touchGestureId; ++ armMenuHoverGuard(MENU_ARM_MS); ++ } ++ + logMenuEvent('open trigger=' + (evt ? 'contextmenu' : 'longpress') + + ' item=' + describeNode(item) + + ' at=' + Math.round(pageX) + ',' + Math.round(pageY)); @@ -993,7 +1085,7 @@ })(); --- a/public/js/context-js/context-js.js +++ b/public/js/context-js/context-js.js -@@ -1 +1,1011 @@ +@@ -1 +1,1103 @@ -window.context=window.context||function(){let e={fadeSpeed:100,filter:function(e){},above:"auto",preventDoubleContext:!0,compress:!1};function t(o,n,d,i){let a=i?" dropdown-context-sub":"",s=e.compress?" compressed-context":"",f=$(''),l=0,r="";for(;l');else if(void 0!==n[l].header)f.append('");else{let e;void 0===n[l].href&&(n[l].href="javascript:void(0)"),void 0!==n[l].target&&(r=' target="'+n[l].target+'"'),e=void 0!==n[l].subMenu?$('"):$('
  • "+n[l].text+"
  • "),"function"==typeof n[l].visible&&(n[l].visible(o)||e.hide());let i=e.find("a");if(void 0!==n[l].classes)for(const e in n[l].classes)i.addClass(n[l].classes[e]);if(void 0!==n[l].attributes)for(const e in n[l].attributes)i.attr(e,n[l].attributes[e]);if(void 0!==n[l].action){let e="event-"+(new Date).getTime()*Math.floor(1e5*Math.random()),t=n[l].action;i.attr("id",e),$("#"+e).addClass("context-event"),$(document).on("click","#"+e,(function(){t.call(this,o)}))}if(f.append(e),void 0!==n[l].subMenu){let e=t(o,n[l].subMenu,d,!0);f.find("li:last").append(e)}}"function"==typeof e.filter&&e.filter(f.find("li:last"))}return f}return{init:function(t){e=$.extend({},e,t),$(document).on("click","html",(function(){$(".dropdown-context").fadeOut(e.fadeSpeed,(function(){$(".dropdown-context").css({display:""}).find(".drop-left").removeClass("drop-left")}))})),e.preventDoubleContext&&$(document).on("contextmenu",".dropdown-context",(function(e){e.preventDefault()})),$(document).on("mouseenter",".dropdown-submenu",(function(){let e=$(this).find(".dropdown-context-sub:first");e.width()+e.offset().left>window.innerWidth&&e.addClass("drop-left")}))},settings:function(t){e=$.extend({},e,t)},attach:function(o,n){let d=(n=n||{}).data||{},i=(new Date).getTime();$(document).on("contextmenu",o,(function(a){a.preventDefault(),a.stopPropagation();let s=a.target.closest(o);"function"==typeof n.beforeOpen&&n.beforeOpen.call(a,s);let f=t(s,d,i);$("body .dropdown-menu.dropdown-context").remove(),$("body").append(f),$(".dropdown-context:not(.dropdown-context-sub)").hide();let l=$("#dropdown-"+i);if("boolean"==typeof e.above&&e.above)l.addClass("dropdown-context-up").css({top:a.pageY-20-$("#dropdown-"+i).height(),left:a.pageX-13}).fadeIn(e.fadeSpeed);else if("string"==typeof e.above&&"auto"===e.above){l.removeClass("dropdown-context-up");let t=l.height()+12;a.pageY+t>$("html").height()?l.addClass("dropdown-context-up").css({top:a.pageY-20-t,left:a.pageX-13}).fadeIn(e.fadeSpeed):l.css({top:a.pageY+10,left:a.pageX-13}).fadeIn(e.fadeSpeed)}"function"==typeof n.afterOpen&&n.afterOpen.call(a,s,l.get(0))}))},destroy:function(e){$(document).off("contextmenu",e).off("click",".context-event")}}}(); +/* + * Context.js @@ -1205,6 +1297,9 @@ + let touchGestureId = 0; // 每一次「手指按下」算一个新手势 + let armedGestureId = 0; // 二级菜单是在哪一次手势里被打开的 + let submenuArmingTimer = null; // 「刚弹出、先别高亮」的收尾定时器 // 因"点了菜单外"而关菜单后,要吞掉的那一发 click ++ let armedMenuGestureId = 0; // 主菜单是在哪一次手势里被打开的 ++ let menuArmingTimer = null; // 主菜单「刚弹出、先别高亮」的收尾定时器 ++ let fingerOnScreen = false; // 屏幕上还有没有手指按着(主菜单 arming 窗口据此跟手势续期,而不是死定时器) + let menuStylesInjected = false; + let menuGuardInstalled = false; + @@ -1238,6 +1333,15 @@ + + ' min-width: 0 !important; margin: 0 !important; padding: 0 !important;' + + ' border: 0 !important; border-radius: 0 !important; box-shadow: none !important;' + + ' background: transparent !important; }' ++ // 主菜单版(与下面二级菜单那份同一套 arming 机制,见 armMenuHoverGuard): ++ // 长按弹出的主菜单同样会顶在手指底下,真机触摸的粘滞 hover 会把手指底下那一项当成 hover 目标 ++ // → 弹出瞬间那项就跳高亮(老师截图里的「复制图片」)。这里连 li 一起关指针事件: ++ // 只关 a 的话,主题里的 `.dropdown-submenu:hover > a` 仍会因为 li 被 hover 而给「复制链接」上色。 ++ // 另外 hover 那套是「底色 + 文字变白」两件一起上(见 context-js.less 的 li>a:hover), ++ // 所以文字色也一并还原 —— 万一 :hover/:focus 还是粘住了,那一项也保持原样,不会变成白底白字。 ++ + '.dropdown-context.menu-arming > li,' ++ + ' .dropdown-context.menu-arming > li > a {' ++ + ' pointer-events: none !important; background-color: transparent !important; color: inherit !important; }' + // 刚弹出的一瞬间:项不接受指针事件(浏览器就不会把它当成 hover 目标 → 不会高亮), + // 同时把 hover 底色压平做双保险;不改变任何尺寸/位置,面板本身照旧接收点击(由守卫吞掉)。 + + '.dropdown-context.submenu-inplace.submenu-arming > li.submenu-active > .dropdown-menu > li > a,' @@ -1343,6 +1447,9 @@ + const EDGE_MARGIN = 8; + const SUBMENU_GUARD = 350; // 二级菜单「就地替换」后这段毫秒内,触摸点击一律吞掉(防按住误选) + const SUBMENU_ARM_MS = 420; // 二级菜单刚弹出的这段毫秒内,先别接受指针指向(防那一下高亮跳色) ++ const MENU_ARM_MS = 420; // 主菜单(长按弹出)同理:弹出后这段时间内的项不接受指针指向 ++ const MENU_ARM_TAIL_MS = 260; // 抬手之后再压这么多毫秒 —— 老师原话「手抬起来的那一瞬间留冗余」, ++ // 与二级菜单那份抬手冗余同一个数(见 armMenuHoverGuard / touchend) + + function viewportSize() { + const de = document.documentElement; @@ -1495,6 +1602,50 @@ + $('.dropdown-context').removeClass('submenu-arming'); + } + ++ // 主菜单版(同一套机制,老师要求「复用到长按弹出的主菜单」): ++ // 主菜单弹在手指底下时,那一项会被浏览器判成 hover 目标 → 弹出瞬间跳高亮,功能和以前一样、就是看着脏。 ++ // 做法与二级菜单那份一致:挂 .menu-arming(里面的项不接受指针事件 + hover 底色压平), ++ // 抬手后由 touchend 再续 MENU_ARM_TAIL_MS —— 窗口跟着手势走,不是弹出时定死的一个定时器。 ++ // 只在「这一发菜单确实是紧随一次触摸打开」时才挂(判据见 openMenu):桌面右键路径因此一行都不动。 ++ function armMenuHoverGuard(ms) { ++ $('.dropdown-context:not(.dropdown-context-sub)').addClass('menu-arming'); ++ clearTimeout(menuArmingTimer); ++ menuArmingTimer = setTimeout(function () { ++ // 手指还按着(而且就是按出这个菜单的那次手势)→ 续期:按住多久都不跳色; ++ // 抬手那条路径会再压 MENU_ARM_TAIL_MS 收尾。 ++ if (fingerOnScreen && armedMenuGestureId && armedMenuGestureId === touchGestureId) { ++ armMenuHoverGuard(MENU_ARM_MS); ++ return; ++ } ++ $('.dropdown-context').removeClass('menu-arming'); ++ }, ms); ++ } ++ ++ function clearMenuHoverGuard() { ++ clearTimeout(menuArmingTimer); ++ $('.dropdown-context').removeClass('menu-arming'); ++ } ++ ++ // arming 生效时面板里的项不接受指针事件(这正是"不跳高亮"的关键),于是手指按在菜单项上时 ++ // e.target 拿到的是面板本身 —— 「按一下就展开二级菜单」这条触摸路径会被吃掉。 ++ // 这里按触摸坐标把手指底下那一行找回来,只给这一个分支用,保证窗口内的第一次点按与平时一致。 ++ function rowUnderTouch(e) { ++ if (! e || ! e.touches || ! e.touches.length) { ++ return null; ++ } ++ ++ const t = e.touches[0], x = t.clientX, y = t.clientY; ++ const rows = document.querySelectorAll('.dropdown-context:not(.dropdown-context-sub) > li'); ++ for (let i = 0; i < rows.length; i++) { ++ const r = rows[i].getBoundingClientRect(); ++ if (r.width && r.height && x >= r.left && x <= r.right && y >= r.top && y <= r.bottom) { ++ return rows[i]; ++ } ++ } ++ ++ return null; ++ } ++ + // 打开某个父项的二级菜单:手机惯例是「按下就开」,这样抬手那发合成 click 也有东西可吞。 + function openSubmenuFor($li) { + $li.siblings('.dropdown-submenu').removeClass('touch-open'); @@ -1682,13 +1833,23 @@ + // 等抬手才处理就晚了(那时 click 已经落在菜单项上了)。 + // 抬手:面板若还在「刚弹出」窗口内,把窗口续到抬手之后 —— 按住再久,那一刻也不跳高亮。 + ['touchend', 'touchcancel'].forEach(function (evt) { -+ document.addEventListener(evt, function () { ++ document.addEventListener(evt, function (e) { ++ // 屏幕上还有手指按着吗(多指时抬手一根不算)——主菜单 arming 窗口据此决定要不要续期 ++ fingerOnScreen = !! (e && e.touches && e.touches.length); ++ + // 判据是「这一发手指就是打开二级菜单的那一发」——不能看 .submenu-arming 还在不在: + // 按住超过 SUBMENU_ARM_MS 时,那个类早就被定时器摘掉了(踩过)。 + if (armedGestureId && armedGestureId === touchGestureId + && $('.dropdown-context.submenu-inplace').length) { + armSubmenuHoverGuard(260); // 抬手后再压 260ms,抬手那一刻也不会跳色 + } ++ ++ // 主菜单同理(老师原话:「只要给我手抬起来的那一瞬间留冗余就够」)—— ++ // 判据同样是「这一发手指就是打开主菜单的那一发」,按住再久也不会在抬手那一刻跳色。 ++ if (armedMenuGestureId && armedMenuGestureId === touchGestureId ++ && $('.dropdown-context:not(.dropdown-context-sub)').length) { ++ armMenuHoverGuard(MENU_ARM_TAIL_MS); ++ } + }, true); + }); + @@ -1697,7 +1858,17 @@ + + // 只看「手指底下这个 a 的直接父级」—— 二级菜单的项嵌在父 li 里面, + // 用 closest('li.dropdown-submenu') 会把二级菜单里的项也误判成父行。 -+ const $a = $(e.target).closest('a'); ++ let $a = $(e.target).closest('a'); ++ // 主菜单 arming 窗口里,项不接受指针事件 → 手指按在「复制链接」上时 e.target 只是面板, ++ // 这一发就按不到那个 a。按坐标把行找回来,只补「按一下就展开二级菜单」这一条路 ++ // (叶子项靠抬手补发的那发 click,本来就不受影响;就地替换态不兜底,那时的行高/内容都变了)。 ++ if (! $a.length && $('.dropdown-context.menu-arming').length) { ++ const row = rowUnderTouch(e); ++ if (row && row.classList.contains('dropdown-submenu') ++ && ! row.classList.contains('submenu-active')) { ++ $a = $(row).children('a'); ++ } ++ } + const $row = $a.parent(); + if ($row.hasClass('dropdown-submenu') && $row.closest('.dropdown-context').length) { + if ($row.hasClass('submenu-active')) { @@ -1707,13 +1878,17 @@ + } + submenuInplaceAt = Date.now(); + armedGestureId = touchGestureId; // 武装到本次手势:这根手指抬起来的那发 click 必吞 -+ } else if ($('.dropdown-context.submenu-arming').length) { -+ clearSubmenuHoverGuard(); // 手指挪去点别的东西了:立刻恢复正常 hover/点击 ++ } else { ++ // 手指挪去点别的东西了(包括点菜单项、滑菜单):立刻恢复正常 hover/点击。 ++ // 两套 arming 都清 —— 它们各自只对「按出它来的那一次手势」负责,新手势一律失效。 ++ clearSubmenuHoverGuard(); ++ clearMenuHoverGuard(); + } + }, true); + + document.addEventListener('touchstart', function (e) { + lastTouchAt = Date.now(); ++ fingerOnScreen = true; // 手指按下:主菜单 arming 窗口可以跟手势续期 + + if (! menuOnScreen() || isInsideMenu(e.target)) { + return; @@ -1934,6 +2109,15 @@ + menuOpenedAt = Date.now(); + menuClosingUntil = 0; + ++ // 刚弹出的主菜单会顶在手指底下:那一项马上会被当成 hover 目标 → 弹出瞬间就跳高亮。 ++ // 复用二级菜单那套 arming:挂 .menu-arming(项不接受指针事件 + hover 底色压平),抬手后再续 260ms。 ++ // **只在「这一发菜单确实紧随一次触摸」时才挂**:鼠标右键路径(lastTouchAt 是 0 或很久以前) ++ // 与上游完全一致 —— 桌面本来没有粘滞 hover,不该为此改动付出任何代价。 ++ if ((Date.now() - lastTouchAt) < MENU_OPEN_GRACE) { ++ armedMenuGestureId = touchGestureId; ++ armMenuHoverGuard(MENU_ARM_MS); ++ } ++ + logMenuEvent('open trigger=' + (evt ? 'contextmenu' : 'longpress') + + ' item=' + describeNode(item) + + ' at=' + Math.round(pageX) + ',' + Math.round(pageY)); @@ -2385,7 +2569,20 @@ } html += item; -@@ -486,10 +569,10 @@ +@@ -386,6 +469,12 @@ + $albums.off('click', '.delete').on('click', '.delete', function (e) { + e.stopPropagation(); + Swal.fire({ ++ // 与新弹窗(x-modal)一套:不动背景页面。 ++ // heightAuto 关掉 html/body 上的 swal2-height-auto;scrollbarPadding 关掉 ++ // sweetalert2 往 body 写 padding-right 的那一步(它按「滚动条宽度」补内边距, ++ // 手机上明明没有占位滚动条,补 8px 却真的会把内容挤窄 → 工具栏换行、整页下移)。 ++ heightAuto: false, ++ scrollbarPadding: false, + title: '确认删除该相册?', + text: "删除后相册中的图片将会被移出。", + icon: 'warning', +@@ -486,10 +575,10 @@ operates = ['refresh']; } if (selected.length === 1) { @@ -2398,7 +2595,7 @@ } if (selected.length && selectedAlbum.id !== undefined) { operates.push('remove'); -@@ -509,6 +592,26 @@ +@@ -509,6 +598,26 @@ ds.subscribe('elementselect', _ => bindOperates()); ds.subscribe('elementunselect', _ => bindOperates()); @@ -2425,7 +2622,7 @@ $photos.on('click', '.image-selector', function () { ds.toggleSelection($(this).closest('a')); bindOperates(); -@@ -535,25 +638,94 @@ +@@ -535,25 +644,94 @@ const methods = { movements() { @@ -2537,16 +2734,43 @@ }, remove() { let selected = ds.getSelection().map(item => $(item).data('id')); -@@ -575,7 +747,7 @@ +@@ -574,8 +752,12 @@ + }, permission() { Swal.fire({ ++ // 与新弹窗(x-modal)一套:不动背景页面(heightAuto 去 swal2-height-auto, ++ // scrollbarPadding 去 body 的 padding-right 注入 —— 否则整页会有细微位移) ++ heightAuto: false, ++ scrollbarPadding: false, title: '选择一个权限', - text: '选择公开将会出现在画廊中(若平台开启了画廊)', + text: '选择公开后所有用户都能看到这张图片', input: 'select', inputOptions: { public: '公开', -@@ -684,6 +856,7 @@ +@@ -613,6 +795,10 @@ + rename(e) { + let item = $(e).data('json'); + Swal.fire({ ++ // 与新弹窗(x-modal)一套:不动背景页面(heightAuto 去 swal2-height-auto, ++ // scrollbarPadding 去 body 的 padding-right 注入 —— 否则整页会有细微位移) ++ heightAuto: false, ++ scrollbarPadding: false, + title: '请输入图片名称', + input: 'text', + inputValue: item.filename, +@@ -649,6 +835,10 @@ + }, + delete() { + Swal.fire({ ++ // 与新弹窗(x-modal)一套:不动背景页面(heightAuto 去 swal2-height-auto, ++ // scrollbarPadding 去 body 的 padding-right 注入 —— 否则整页会有细微位移) ++ heightAuto: false, ++ scrollbarPadding: false, + title: '确认要删除选中的图片?', + text: "删除后不可恢复,记录和文件同时删除", + icon: 'warning', +@@ -684,6 +874,7 @@ if (response.data.status) { let image = response.data.data.image; let content = $('#image-detail-tpl').html() @@ -2554,7 +2778,7 @@ .replace(/__album_name__/g, image.album ? image.album.name : '-') .replace(/__strategy_name__/g, image.strategy ? image.strategy.name : '-') .replace(/__filename__/g, image.filename.replace(/\$/g, '$$$$')) -@@ -697,7 +870,9 @@ +@@ -697,7 +888,9 @@ .replace(/__permission__/g, image.permission === 1 ? '公开' : '私有') .replace(/__uploaded_ip__/g, image.uploaded_ip) .replace(/__created_at__/g, image.created_at) @@ -2565,7 +2789,7 @@ } else { toastr.error(response.data.message); } -@@ -853,6 +1028,10 @@ +@@ -853,6 +1046,10 @@ case 'delete': // 删除 methods.delete(); break; diff --git a/src/public/css/app.css b/src/public/css/app.css index 688fbcd..b7be7ee 100644 --- a/src/public/css/app.css +++ b/src/public/css/app.css @@ -1 +1 @@ -/*! tailwindcss v3.0.23 | MIT License | https://tailwindcss.com*/*,:after,:before{border:0 solid #e5e7eb;box-sizing:border-box}:after,:before{--tw-content:""}html{-webkit-text-size-adjust:100%;font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,PingFang SC,Hiragino Sans GB,Microsoft YaHei,Noto Sans SC,ui-sans-serif,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4}body{line-height:inherit;margin:0}hr{border-top-width:1px;color:inherit;height:0}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{border-collapse:collapse;border-color:inherit;text-indent:0}button,input,optgroup,select,textarea{color:inherit;font-family:inherit;font-size:100%;line-height:inherit;margin:0;padding:0}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{color:#9ca3af;opacity:1}input::placeholder,textarea::placeholder{color:#9ca3af;opacity:1}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{height:auto;max-width:100%}[hidden]{display:none}[multiple],[type=date],[type=datetime-local],[type=email],[type=month],[type=number],[type=password],[type=search],[type=tel],[type=text],[type=time],[type=url],[type=week],select,textarea{--tw-shadow:0 0 #0000;-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:#fff;border-color:#6b7280;border-radius:0;border-width:1px;font-size:1rem;line-height:1.5rem;padding:.5rem .75rem}[multiple]:focus,[type=date]:focus,[type=datetime-local]:focus,[type=email]:focus,[type=month]:focus,[type=number]:focus,[type=password]:focus,[type=search]:focus,[type=tel]:focus,[type=text]:focus,[type=time]:focus,[type=url]:focus,[type=week]:focus,select:focus,textarea:focus{--tw-ring-inset:var(--tw-empty,/*!*/ /*!*/);--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#2563eb;--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);border-color:#2563eb;box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);outline:2px solid transparent;outline-offset:2px}input::-moz-placeholder,textarea::-moz-placeholder{color:#6b7280;opacity:1}input::placeholder,textarea::placeholder{color:#6b7280;opacity:1}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-date-and-time-value{min-height:1.5em}::-webkit-datetime-edit,::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-meridiem-field,::-webkit-datetime-edit-millisecond-field,::-webkit-datetime-edit-minute-field,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-second-field,::-webkit-datetime-edit-year-field{padding-bottom:0;padding-top:0}select{-webkit-print-color-adjust:exact;background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3E%3Cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3E%3C/svg%3E");background-position:right .5rem center;background-repeat:no-repeat;background-size:1.5em 1.5em;color-adjust:exact;padding-right:2.5rem}[multiple]{-webkit-print-color-adjust:unset;background-image:none;background-position:0 0;background-repeat:unset;background-size:initial;color-adjust:unset;padding-right:.75rem}[type=checkbox],[type=radio]{-webkit-print-color-adjust:exact;--tw-shadow:0 0 #0000;-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:#fff;background-origin:border-box;border-color:#6b7280;border-width:1px;color:#2563eb;color-adjust:exact;display:inline-block;flex-shrink:0;height:1rem;padding:0;-webkit-user-select:none;-moz-user-select:none;user-select:none;vertical-align:middle;width:1rem}[type=checkbox]{border-radius:0}[type=radio]{border-radius:100%}[type=checkbox]:focus,[type=radio]:focus{--tw-ring-inset:var(--tw-empty,/*!*/ /*!*/);--tw-ring-offset-width:2px;--tw-ring-offset-color:#fff;--tw-ring-color:#2563eb;--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);outline:2px solid transparent;outline-offset:2px}[type=checkbox]:checked,[type=radio]:checked{background-color:currentColor;background-position:50%;background-repeat:no-repeat;background-size:100% 100%;border-color:transparent}[type=checkbox]:checked{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 16 16' fill='%23fff' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12.207 4.793a1 1 0 0 1 0 1.414l-5 5a1 1 0 0 1-1.414 0l-2-2a1 1 0 0 1 1.414-1.414L6.5 9.086l4.293-4.293a1 1 0 0 1 1.414 0z'/%3E%3C/svg%3E")}[type=radio]:checked{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 16 16' fill='%23fff' xmlns='http://www.w3.org/2000/svg'%3E%3Ccircle cx='8' cy='8' r='3'/%3E%3C/svg%3E")}[type=checkbox]:checked:focus,[type=checkbox]:checked:hover,[type=radio]:checked:focus,[type=radio]:checked:hover{background-color:currentColor;border-color:transparent}[type=checkbox]:indeterminate{background-color:currentColor;background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 16 16'%3E%3Cpath stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M4 8h8'/%3E%3C/svg%3E");background-position:50%;background-repeat:no-repeat;background-size:100% 100%;border-color:transparent}[type=checkbox]:indeterminate:focus,[type=checkbox]:indeterminate:hover{background-color:currentColor;border-color:transparent}[type=file]{background:unset;border-color:inherit;border-radius:0;border-width:0;font-size:unset;line-height:inherit;padding:0}[type=file]:focus{outline:1px auto -webkit-focus-ring-color}:root{--lsky-bg:#f6f7f9;--lsky-surface:#fff;--lsky-surface-2:#fafbfc;--lsky-surface-3:#f1f3f6;--lsky-border:#e9ecf0;--lsky-border-2:#dde2e8;--lsky-text:#16191d;--lsky-text-2:#5a6472;--lsky-text-3:#8b95a3;--lsky-accent:#2563eb;--lsky-accent-hover:#1d4ed8;--lsky-accent-soft:#eef4ff;--lsky-danger:#dc2626;--lsky-danger-soft:#fef2f2;--lsky-shadow-card:0 1px 2px rgba(16,24,40,.04),0 1px 3px rgba(16,24,40,.05);--lsky-shadow-pop:0 6px 18px rgba(16,24,40,.08),0 2px 6px rgba(16,24,40,.04);--lsky-shadow-modal:0 20px 50px rgba(16,24,40,.18)}html.dark{--lsky-bg:#0f1216;--lsky-surface:#161a20;--lsky-surface-2:#1b2027;--lsky-surface-3:#1f252e;--lsky-border:#252b34;--lsky-border-2:#313945;--lsky-text:#e7ebf0;--lsky-text-2:#9aa4b2;--lsky-text-3:#6d7784;--lsky-accent:#3b82f6;--lsky-accent-hover:#60a5fa;--lsky-accent-soft:#16233b;--lsky-danger:#f87171;--lsky-danger-soft:#2a1618;--lsky-shadow-card:0 1px 2px rgba(0,0,0,.35),0 1px 3px rgba(0,0,0,.3);--lsky-shadow-pop:0 8px 22px rgba(0,0,0,.45);--lsky-shadow-modal:0 24px 60px rgba(0,0,0,.6)}html{color-scheme:light}html.dark{color-scheme:dark}body{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;background-color:var(--lsky-bg);color:var(--lsky-text);font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,PingFang SC,Hiragino Sans GB,Microsoft YaHei,Noto Sans SC,ui-sans-serif,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;transition:background-color .2s ease,color .2s ease}::-moz-selection{background:var(--lsky-accent);color:#fff}::selection{background:var(--lsky-accent);color:#fff}:focus-visible{outline:2px solid var(--lsky-accent);outline-offset:1px}*,:after,:before{--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.ls-card{background-color:var(--lsky-surface);border-color:var(--lsky-border);border-radius:10px;border-width:1px;box-shadow:var(--lsky-shadow-card)}.ls-card-hd{border-bottom-width:1px;border-color:var(--lsky-border);display:flex;font-size:14px;font-weight:600;justify-content:space-between;padding:.75rem 1rem}.ls-btn,.ls-card-hd{align-items:center;color:var(--lsky-text)}.ls-btn{background-color:var(--lsky-surface);border-color:var(--lsky-border-2);border-radius:.5rem;border-width:1px;cursor:pointer;display:inline-flex;font-size:13.5px;font-weight:500;gap:.375rem;height:2.25rem;justify-content:center;padding-left:.875rem;padding-right:.875rem;transition:background-color .15s ease,border-color .15s ease,color .15s ease,box-shadow .15s ease;white-space:nowrap}.ls-btn:hover{background-color:var(--lsky-surface-2)}.ls-btn.disabled,.ls-btn:disabled{cursor:not-allowed;opacity:.5}.ls-btn-primary{--tw-text-opacity:1;background-color:var(--lsky-accent);border-color:var(--lsky-accent);color:rgb(255 255 255/var(--tw-text-opacity))}.ls-btn-primary:hover{background:var(--lsky-accent-hover);border-color:var(--lsky-accent-hover)}.ls-btn-sm{font-size:13.5px;height:2rem}.ls-btn-sm,.ls-input{padding-left:.75rem;padding-right:.75rem}.ls-input{background-color:var(--lsky-surface);border-color:var(--lsky-border-2);border-radius:.5rem;border-width:1px;color:var(--lsky-text);display:block;font-size:14px;height:2.25rem;transition:border-color .15s ease,box-shadow .15s ease;width:100%}.ls-input:focus{border-color:var(--lsky-accent);box-shadow:0 0 0 3px var(--lsky-accent-soft)}.ls-input::-moz-placeholder{color:var(--lsky-text-3)}.ls-input::placeholder{color:var(--lsky-text-3)}.ls-label{color:var(--lsky-text-2);display:block;font-size:13.5px;font-weight:500;margin-bottom:.375rem}.ls-th{color:var(--lsky-text-3);font-size:12.5px;font-weight:600;letter-spacing:.05em;padding:.625rem 1rem;text-align:left;text-transform:uppercase;white-space:nowrap}.ls-badge{align-items:center;background-color:var(--lsky-surface-3);border-radius:9999px;color:var(--lsky-text-2);display:inline-flex;font-size:12.5px;font-weight:500;padding:.125rem .5rem}.ls-group-title{color:var(--lsky-text-3);font-size:12.5px;font-weight:600;letter-spacing:.05em;margin-bottom:.375rem;text-transform:uppercase}.ls-group-title,.ls-nav-item{padding-left:.75rem;padding-right:.75rem}.ls-nav-item{align-items:center;border-radius:.5rem;color:var(--lsky-text-2);display:flex;font-size:14px;gap:.625rem;height:2.25rem;transition:background-color .15s ease,color .15s ease}.ls-nav-item:hover{background-color:var(--lsky-surface-2);color:var(--lsky-text)}.ls-nav-item.is-active{background-color:var(--lsky-accent-soft);color:var(--lsky-accent);font-weight:600}.ls-menu{background-color:var(--lsky-surface);border-color:var(--lsky-border);border-radius:10px;border-width:1px;box-shadow:var(--lsky-shadow-pop);overflow:hidden;padding-bottom:.25rem;padding-top:.25rem}.ls-menu-item{color:var(--lsky-text-2);cursor:pointer;display:block;font-size:13.5px;overflow:hidden;padding:.5rem .875rem;text-overflow:ellipsis;white-space:nowrap}.ls-menu-item:hover{background-color:var(--lsky-surface-2);color:var(--lsky-text)}@media (min-width:640px){html.sidebar-collapsed #app-sidebar{width:4rem}html.sidebar-collapsed #app-sidebar .ls-brand{justify-content:center;padding-left:0;padding-right:0}html.sidebar-collapsed #app-sidebar #capacity-progress,html.sidebar-collapsed #app-sidebar .ls-brand-name,html.sidebar-collapsed #app-sidebar .ls-group-title,html.sidebar-collapsed #app-sidebar .ls-nav-item>span{display:none}html.sidebar-collapsed #app-sidebar .ls-nav-item{justify-content:center;padding-left:0;padding-right:0}html.sidebar-collapsed .app-content{margin-left:4rem}}.sr-only{clip:rect(0,0,0,0);border-width:0;height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;white-space:nowrap;width:1px}.visible{visibility:visible}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{bottom:0;left:0;right:0;top:0}.top-0{top:0}.bottom-0{bottom:0}.left-0{left:0}.right-0{right:0}.top-4{top:1rem}.right-4{right:1rem}.top-2{top:.5rem}.right-2{right:.5rem}.top-14{top:3.5rem}.-right-\[1000px\]{right:-1000px}.left-2{left:.5rem}.bottom-2{bottom:.5rem}.top-1{top:.25rem}.left-1{left:.25rem}.-left-\[600px\]{left:-600px}.z-0{z-index:0}.z-\[9\]{z-index:9}.z-10{z-index:10}.z-\[3\]{z-index:3}.z-\[2\]{z-index:2}.z-\[1\]{z-index:1}.z-\[10\]{z-index:10}.col-span-6{grid-column:span 6/span 6}.col-span-3{grid-column:span 3/span 3}.float-right{float:right}.m-2{margin:.5rem}.mx-auto{margin-left:auto;margin-right:auto}.my-6{margin-bottom:1.5rem;margin-top:1.5rem}.my-2{margin-bottom:.5rem;margin-top:.5rem}.my-4{margin-bottom:1rem;margin-top:1rem}.mx-0\.5{margin-left:.125rem;margin-right:.125rem}.mx-0{margin-left:0;margin-right:0}.-my-2{margin-bottom:-.5rem;margin-top:-.5rem}.ml-3{margin-left:.75rem}.-ml-px{margin-left:-1px}.mt-10{margin-top:2.5rem}.mt-5{margin-top:1.25rem}.mt-1{margin-top:.25rem}.mb-4{margin-bottom:1rem}.mb-2{margin-bottom:.5rem}.mt-3{margin-top:.75rem}.mt-1\.5{margin-top:.375rem}.ml-auto{margin-left:auto}.mb-10{margin-bottom:2.5rem}.mt-2{margin-top:.5rem}.mt-4{margin-top:1rem}.-mb-14{margin-bottom:-3.5rem}.ml-4{margin-left:1rem}.mb-3{margin-bottom:.75rem}.mb-8{margin-bottom:2rem}.mt-0\.5{margin-top:.125rem}.mt-0{margin-top:0}.-ml-1{margin-left:-.25rem}.mr-2{margin-right:.5rem}.mr-4{margin-right:1rem}.mt-6{margin-top:1.5rem}.ml-2{margin-left:.5rem}.mb-5{margin-bottom:1.25rem}.mb-16{margin-bottom:4rem}.ml-2\.5{margin-left:.625rem}.mr-3{margin-right:.75rem}.ml-0\.5{margin-left:.125rem}.ml-0{margin-left:0}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.hidden{display:none}.h-5{height:1.25rem}.h-10{height:2.5rem}.h-14{height:3.5rem}.h-full{height:100%}.h-8{height:2rem}.h-\[1\.5px\]{height:1.5px}.h-\[36px\]{height:36px}.h-\[28px\]{height:28px}.h-20{height:5rem}.h-7{height:1.75rem}.h-16{height:4rem}.h-11{height:2.75rem}.h-4{height:1rem}.h-80{height:20rem}.h-9{height:2.25rem}.h-36{height:9rem}.h-6{height:1.5rem}.h-24{height:6rem}.h-screen{height:100vh}.h-1\.5{height:.375rem}.h-1{height:.25rem}.h-auto{height:auto}.h-\[100dvh\]{height:100dvh}.max-h-\[50vh\]{max-height:50vh}.max-h-\[500px\]{max-height:500px}.min-h-screen{min-height:100vh}.min-h-\[200px\]{min-height:200px}.min-h-\[44px\]{min-height:44px}.min-h-\[100dvh\]{min-height:100dvh}.w-5{width:1.25rem}.w-10{width:2.5rem}.w-full{width:100%}.w-8{width:2rem}.w-14{width:3.5rem}.w-\[0\%\]{width:0}.w-\[36px\]{width:36px}.w-\[28px\]{width:28px}.w-48{width:12rem}.w-20{width:5rem}.w-64{width:16rem}.w-\[90\%\]{width:90%}.w-\[80\%\]{width:80%}.w-16{width:4rem}.w-4{width:1rem}.w-36{width:9rem}.w-9{width:2.25rem}.w-\[95\%\]{width:95%}.w-6{width:1.5rem}.w-24{width:6rem}.w-3\/4{width:75%}.w-7{width:1.75rem}.w-28{width:7rem}.min-w-0{min-width:0}.min-w-full{min-width:100%}.max-w-\[70\%\]{max-width:70%}.max-w-2xl{max-width:42rem}.max-w-xl{max-width:36rem}.flex-1{flex:1 1 0%}.shrink-0{flex-shrink:0}.origin-top-right{transform-origin:top right}.origin-top-left{transform-origin:top left}.translate-y-9{--tw-translate-y:2.25rem}.translate-y-0,.translate-y-9{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-y-0{--tw-translate-y:0px}.-translate-y-1{--tw-translate-y:-0.25rem}.-translate-y-1,.translate-y-4{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-y-4{--tw-translate-y:1rem}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes spin{to{transform:rotate(1turn)}}.animate-spin{animation:spin 1s linear infinite}.cursor-default{cursor:default}.cursor-pointer{cursor:pointer}.cursor-not-allowed{cursor:not-allowed}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.select-all{-webkit-user-select:all;-moz-user-select:all;user-select:all}.resize-y{resize:vertical}.resize{resize:both}.list-inside{list-style-position:inside}.list-disc{list-style-type:disc}.grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.flex-row{flex-direction:row}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.justify-start{justify-content:flex-start}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.justify-items-center{justify-items:center}.gap-2{gap:.5rem}.gap-6{gap:1.5rem}.gap-4{gap:1rem}.gap-1\.5{gap:.375rem}.gap-1{gap:.25rem}.gap-3{gap:.75rem}.gap-0\.5{gap:.125rem}.gap-0{gap:0}.gap-2\.5{gap:.625rem}.gap-x-3{-moz-column-gap:.75rem;column-gap:.75rem}.gap-y-1{row-gap:.25rem}.space-x-4>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-left:calc(1rem*(1 - var(--tw-space-x-reverse)));margin-right:calc(1rem*var(--tw-space-x-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(.5rem*var(--tw-space-y-reverse));margin-top:calc(.5rem*(1 - var(--tw-space-y-reverse)))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(1rem*var(--tw-space-y-reverse));margin-top:calc(1rem*(1 - var(--tw-space-y-reverse)))}.space-x-2>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-left:calc(.5rem*(1 - var(--tw-space-x-reverse)));margin-right:calc(.5rem*var(--tw-space-x-reverse))}.space-x-1>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-left:calc(.25rem*(1 - var(--tw-space-x-reverse)));margin-right:calc(.25rem*var(--tw-space-x-reverse))}.space-x-3>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-left:calc(.75rem*(1 - var(--tw-space-x-reverse)));margin-right:calc(.75rem*var(--tw-space-x-reverse))}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(.25rem*var(--tw-space-y-reverse));margin-top:calc(.25rem*(1 - var(--tw-space-y-reverse)))}.divide-y>:not([hidden])~:not([hidden]){--tw-divide-y-reverse:0;border-bottom-width:calc(1px*var(--tw-divide-y-reverse));border-top-width:calc(1px*(1 - var(--tw-divide-y-reverse)))}.divide-line>:not([hidden])~:not([hidden]){border-color:var(--lsky-border)}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-scroll{overflow:scroll}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.overflow-y-scroll{overflow-y:scroll}.overscroll-contain{overscroll-behavior:contain}.truncate{overflow:hidden;text-overflow:ellipsis}.truncate,.whitespace-nowrap{white-space:nowrap}.break-words{overflow-wrap:break-word}.break-all{word-break:break-all}.rounded-md{border-radius:.375rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-xl{border-radius:.75rem}.rounded-2xl{border-radius:1rem}.rounded{border-radius:.25rem}.rounded-sm{border-radius:.125rem}.rounded-l-md{border-bottom-left-radius:.375rem;border-top-left-radius:.375rem}.rounded-r-md{border-bottom-right-radius:.375rem;border-top-right-radius:.375rem}.rounded-t-lg{border-top-left-radius:.5rem;border-top-right-radius:.5rem}.rounded-l-none{border-bottom-left-radius:0;border-top-left-radius:0}.border{border-width:1px}.border-2{border-width:2px}.border-0{border-width:0}.border-b{border-bottom-width:1px}.border-b-2{border-bottom-width:2px}.border-r{border-right-width:1px}.border-solid{border-style:solid}.border-dotted{border-style:dotted}.border-gray-300{--tw-border-opacity:1;border-color:rgb(209 213 219/var(--tw-border-opacity))}.border-line{border-color:var(--lsky-border)}.border-line-2{border-color:var(--lsky-border-2)}.border-brand{border-color:var(--lsky-accent)}.border-transparent{border-color:transparent}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}.bg-surface{background-color:var(--lsky-surface)}.bg-surface-3{background-color:var(--lsky-surface-3)}.bg-surface-2{background-color:var(--lsky-surface-2)}.bg-bg{background-color:var(--lsky-bg)}.bg-brand-soft{background-color:var(--lsky-accent-soft)}.bg-line{background-color:var(--lsky-border)}.bg-brand{background-color:var(--lsky-accent)}.bg-yellow-500{--tw-bg-opacity:1;background-color:rgb(234 179 8/var(--tw-bg-opacity))}.bg-red-500{--tw-bg-opacity:1;background-color:rgb(239 68 68/var(--tw-bg-opacity))}.bg-danger-soft{background-color:var(--lsky-danger-soft)}.bg-sky-500{--tw-bg-opacity:1;background-color:rgb(14 165 233/var(--tw-bg-opacity))}.bg-black\/50{background-color:rgba(0,0,0,.5)}.bg-black\/60{background-color:rgba(0,0,0,.6)}.bg-opacity-50{--tw-bg-opacity:0.5}.bg-gradient-to-t{background-image:linear-gradient(to top,var(--tw-gradient-stops))}.from-black{--tw-gradient-from:#000;--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to,transparent)}.fill-current{fill:currentColor}.object-cover{-o-object-fit:cover;object-fit:cover}.object-center{-o-object-position:center;object-position:center}.p-4{padding:1rem}.p-6{padding:1.5rem}.p-2\.5{padding:.625rem}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-1{padding:.25rem}.p-0{padding:0}.px-4{padding-left:1rem;padding-right:1rem}.py-2{padding-bottom:.5rem;padding-top:.5rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-0{padding-left:0;padding-right:0}.py-14{padding-bottom:3.5rem;padding-top:3.5rem}.px-5{padding-left:1.25rem;padding-right:1.25rem}.px-3{padding-left:.75rem;padding-right:.75rem}.py-5{padding-bottom:1.25rem;padding-top:1.25rem}.py-3{padding-bottom:.75rem;padding-top:.75rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.py-10{padding-bottom:2.5rem;padding-top:2.5rem}.py-1{padding-bottom:.25rem;padding-top:.25rem}.px-2\.5{padding-left:.625rem;padding-right:.625rem}.py-1\.5{padding-bottom:.375rem;padding-top:.375rem}.py-4{padding-bottom:1rem;padding-top:1rem}.py-2\.5{padding-bottom:.625rem;padding-top:.625rem}.px-1{padding-left:.25rem;padding-right:.25rem}.py-0{padding-bottom:0;padding-top:0}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.pb-6{padding-bottom:1.5rem}.pt-2{padding-top:.5rem}.pb-2{padding-bottom:.5rem}.pl-6{padding-left:1.5rem}.pl-10{padding-left:2.5rem}.pr-1{padding-right:.25rem}.pt-6{padding-top:1.5rem}.pb-12{padding-bottom:3rem}.pt-14{padding-top:3.5rem}.pb-8{padding-bottom:2rem}.pb-14{padding-bottom:3.5rem}.pt-3{padding-top:.75rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.align-middle{vertical-align:middle}.font-sans{font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,PingFang SC,Hiragino Sans GB,Microsoft YaHei,Noto Sans SC,ui-sans-serif,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-sm{font-size:.875rem;line-height:1.25rem}.text-\[13\.5px\]{font-size:13.5px}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-5xl{font-size:3rem;line-height:1}.text-\[22px\]{font-size:22px}.text-\[15px\]{font-size:15px}.text-\[13px\]{font-size:13px}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.text-4xl{font-size:2.25rem;line-height:2.5rem}.text-\[16px\]{font-size:16px}.text-\[14px\]{font-size:14px}.text-\[21px\]{font-size:21px}.text-\[17px\]{font-size:17px}.text-\[12\.5px\]{font-size:12.5px}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-base{font-size:1rem;line-height:1.5rem}.font-medium{font-weight:500}.font-semibold{font-weight:600}.font-normal{font-weight:400}.uppercase{text-transform:uppercase}.italic{font-style:italic}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure) var(--tw-numeric-spacing) var(--tw-numeric-fraction)}.leading-5{line-height:1.25rem}.leading-relaxed{line-height:1.625}.leading-6{line-height:1.5rem}.tracking-wider{letter-spacing:.05em}.text-gray-500{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity))}.text-gray-700{--tw-text-opacity:1;color:rgb(55 65 81/var(--tw-text-opacity))}.text-ink-2{color:var(--lsky-text-2)}.text-ink{color:var(--lsky-text)}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.text-danger{color:var(--lsky-danger)}.text-ink-3{color:var(--lsky-text-3)}.text-brand{color:var(--lsky-accent)}.text-green-500{--tw-text-opacity:1;color:rgb(34 197 94/var(--tw-text-opacity))}.text-green-800{--tw-text-opacity:1;color:rgb(22 101 52/var(--tw-text-opacity))}.text-red-300{--tw-text-opacity:1;color:rgb(252 165 165/var(--tw-text-opacity))}.text-sky-500{--tw-text-opacity:1;color:rgb(14 165 233/var(--tw-text-opacity))}.text-orange-500{--tw-text-opacity:1;color:rgb(249 115 22/var(--tw-text-opacity))}.text-yellow-500{--tw-text-opacity:1;color:rgb(234 179 8/var(--tw-text-opacity))}.text-green-600{--tw-text-opacity:1;color:rgb(22 163 74/var(--tw-text-opacity))}.text-red-100{--tw-text-opacity:1;color:rgb(254 226 226/var(--tw-text-opacity))}.text-rose-500{--tw-text-opacity:1;color:rgb(244 63 94/var(--tw-text-opacity))}.text-teal-500{--tw-text-opacity:1;color:rgb(20 184 166/var(--tw-text-opacity))}.text-teal-600{--tw-text-opacity:1;color:rgb(13 148 136/var(--tw-text-opacity))}.underline{-webkit-text-decoration-line:underline;text-decoration-line:underline}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.opacity-40{opacity:.4}.opacity-0{opacity:0}.opacity-100{opacity:1}.opacity-25{opacity:.25}.opacity-75{opacity:.75}.shadow-sm{--tw-shadow:0 1px 2px 0 rgba(0,0,0,.05);--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color)}.shadow-card,.shadow-sm{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-card{--tw-shadow:var(--lsky-shadow-card);--tw-shadow-colored:var(--lsky-shadow-card)}.outline-none{outline:2px solid transparent;outline-offset:2px}.outline{outline-style:solid}.outline-2{outline-width:2px}.outline-offset-2{outline-offset:2px}.outline-transparent{outline-color:transparent}.ring-gray-300{--tw-ring-opacity:1;--tw-ring-color:rgb(209 213 219/var(--tw-ring-opacity))}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.backdrop-blur-\[2px\]{--tw-backdrop-blur:blur(2px);-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.transition{transition-duration:.15s;transition-property:color,background-color,border-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-text-decoration-color,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-text-decoration-color,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1)}.transition-all{transition-duration:.15s;transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1)}.transition-colors{transition-duration:.15s;transition-property:color,background-color,border-color,fill,stroke,-webkit-text-decoration-color;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,-webkit-text-decoration-color;transition-timing-function:cubic-bezier(.4,0,.2,1)}.transition-opacity{transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1)}.duration-150,.transition-opacity{transition-duration:.15s}.duration-200{transition-duration:.2s}.duration-75{transition-duration:75ms}.duration-300{transition-duration:.3s}.duration-100{transition-duration:.1s}.duration-500{transition-duration:.5s}.ease-in-out{transition-timing-function:cubic-bezier(.4,0,.2,1)}.ease-out{transition-timing-function:cubic-bezier(0,0,.2,1)}.ease-in{transition-timing-function:cubic-bezier(.4,0,1,1)}.hover\:bg-surface-2:hover{background-color:var(--lsky-surface-2)}.hover\:bg-surface-3:hover{background-color:var(--lsky-surface-3)}.hover\:bg-surface:hover{background-color:var(--lsky-surface)}.hover\:text-gray-500:hover{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity))}.hover\:text-gray-400:hover{--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity))}.hover\:text-ink:hover{color:var(--lsky-text)}.hover\:text-danger:hover{color:var(--lsky-danger)}.hover\:text-brand:hover{color:var(--lsky-accent)}.hover\:text-teal-900:hover{--tw-text-opacity:1;color:rgb(19 78 74/var(--tw-text-opacity))}.hover\:underline:hover{-webkit-text-decoration-line:underline;text-decoration-line:underline}.hover\:opacity-100:hover{opacity:1}.hover\:opacity-80:hover{opacity:.8}.focus\:z-10:focus{z-index:10}.focus\:border-blue-300:focus{--tw-border-opacity:1;border-color:rgb(147 197 253/var(--tw-border-opacity))}.focus\:border-brand:focus{border-color:var(--lsky-accent)}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color)}.focus\:ring-2:focus,.focus\:ring:focus{box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.focus\:ring-2:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color)}.focus\:ring-brand:focus{--tw-ring-color:var(--lsky-accent)}.focus\:ring-brand-soft:focus{--tw-ring-color:var(--lsky-accent-soft)}.focus\:ring-opacity-50:focus{--tw-ring-opacity:0.5}.focus\:ring-offset-2:focus{--tw-ring-offset-width:2px}.active\:bg-gray-100:active{--tw-bg-opacity:1;background-color:rgb(243 244 246/var(--tw-bg-opacity))}.active\:text-gray-700:active{--tw-text-opacity:1;color:rgb(55 65 81/var(--tw-text-opacity))}.active\:text-gray-500:active{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity))}.group:hover .group-hover\:block{display:block}.group:hover .group-hover\:flex{display:flex}.group:hover .group-hover\:hidden{display:none}.group:hover .group-hover\:bg-surface{background-color:var(--lsky-surface)}.group:hover .group-hover\:text-brand{color:var(--lsky-accent)}.group:hover .group-hover\:brightness-50{--tw-brightness:brightness(.5);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 (min-width:640px){.sm\:left-0{left:0}.sm\:top-4{top:1rem}.sm\:right-4{right:1rem}.sm\:col-span-3{grid-column:span 3/span 3}.sm\:col-span-2{grid-column:span 2/span 2}.sm\:-mx-6{margin-left:-1.5rem;margin-right:-1.5rem}.sm\:ml-64{margin-left:16rem}.sm\:mt-0{margin-top:0}.sm\:block{display:block}.sm\:inline-block{display:inline-block}.sm\:flex{display:flex}.sm\:grid{display:grid}.sm\:hidden{display:none}.sm\:h-9{height:2.25rem}.sm\:min-h-\[320px\]{min-height:320px}.sm\:w-auto{width:auto}.sm\:w-28{width:7rem}.sm\:w-64{width:16rem}.sm\:max-w-md{max-width:28rem}.sm\:flex-1{flex:1 1 0%}.sm\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.sm\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.sm\:flex-row{flex-direction:row}.sm\:items-start{align-items:flex-start}.sm\:items-center{align-items:center}.sm\:justify-start{justify-content:flex-start}.sm\:justify-center{justify-content:center}.sm\:justify-between{justify-content:space-between}.sm\:gap-5{gap:1.25rem}.sm\:gap-4{gap:1rem}.sm\:rounded-md{border-radius:.375rem}.sm\:rounded-xl2{border-radius:12px}.sm\:rounded-\[10px\]{border-radius:10px}.sm\:p-6{padding:1.5rem}.sm\:px-3{padding-left:.75rem;padding-right:.75rem}.sm\:px-10{padding-left:2.5rem;padding-right:2.5rem}.sm\:px-6{padding-left:1.5rem;padding-right:1.5rem}.sm\:pt-0{padding-top:0}.sm\:pt-8{padding-top:2rem}.sm\:pl-1{padding-left:.25rem}.sm\:pr-2{padding-right:.5rem}.sm\:text-left{text-align:left}.sm\:text-2xl{font-size:1.5rem;line-height:2rem}}@media (min-width:768px){.md\:top-3{top:.75rem}.md\:right-3{right:.75rem}.md\:col-span-2{grid-column:span 2/span 2}.md\:my-9{margin-bottom:2.25rem;margin-top:2.25rem}.md\:my-8{margin-bottom:2rem;margin-top:2rem}.md\:mt-0{margin-top:0}.md\:mt-4{margin-top:1rem}.md\:block{display:block}.md\:inline-block{display:inline-block}.md\:h-screen{height:100vh}.md\:max-h-\[18rem\]{max-height:18rem}.md\:w-36{width:9rem}.md\:w-72{width:18rem}.md\:w-\[600px\]{width:600px}.md\:w-\[70\%\]{width:70%}.md\:max-w-2xl{max-width:42rem}.md\:translate-y-0{--tw-translate-y:0px}.md\:scale-95,.md\:translate-y-0{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.md\:scale-95{--tw-scale-x:.95;--tw-scale-y:.95}.md\:scale-100{--tw-scale-x:1;--tw-scale-y:1;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:gap-4{gap:1rem}.md\:gap-6{gap:1.5rem}.md\:space-y-6>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(1.5rem*var(--tw-space-y-reverse));margin-top:calc(1.5rem*(1 - var(--tw-space-y-reverse)))}.md\:overflow-y-auto{overflow-y:auto}.md\:overscroll-contain{overscroll-behavior:contain}.md\:rounded-xl2{border-radius:12px}.md\:p-6{padding:1.5rem}.md\:p-5{padding:1.25rem}.md\:px-10{padding-left:2.5rem;padding-right:2.5rem}.md\:py-20{padding-bottom:5rem;padding-top:5rem}.md\:py-5{padding-bottom:1.25rem;padding-top:1.25rem}.md\:px-2{padding-left:.5rem;padding-right:.5rem}.md\:px-4{padding-left:1rem;padding-right:1rem}.md\:align-middle{vertical-align:middle}.md\:focus\:w-52:focus,.md\:hover\:w-52:hover{width:13rem}}@media (min-width:1024px){.lg\:top-4{top:1rem}.lg\:right-4{right:1rem}.lg\:-mx-8{margin-left:-2rem;margin-right:-2rem}.lg\:flex{display:flex}.lg\:hidden{display:none}.lg\:w-\[60\%\]{width:60%}.lg\:max-w-4xl{max-width:56rem}.lg\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.lg\:grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}.lg\:p-8{padding:2rem}.lg\:px-10{padding-left:2.5rem;padding-right:2.5rem}.lg\:py-7{padding-bottom:1.75rem;padding-top:1.75rem}.lg\:px-4{padding-left:1rem;padding-right:1rem}.lg\:px-8{padding-left:2rem;padding-right:2rem}}@media (min-width:1280px){.xl\:grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}.xl\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.xl\:px-10{padding-left:2.5rem;padding-right:2.5rem}}@media (min-width:1536px){.\32xl\:grid-cols-8{grid-template-columns:repeat(8,minmax(0,1fr))}.\32xl\:px-60{padding-left:15rem;padding-right:15rem}} +/*! tailwindcss v3.0.23 | MIT License | https://tailwindcss.com*/*,:after,:before{border:0 solid #e5e7eb;box-sizing:border-box}:after,:before{--tw-content:""}html{-webkit-text-size-adjust:100%;font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,PingFang SC,Hiragino Sans GB,Microsoft YaHei,Noto Sans SC,ui-sans-serif,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4}body{line-height:inherit;margin:0}hr{border-top-width:1px;color:inherit;height:0}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{border-collapse:collapse;border-color:inherit;text-indent:0}button,input,optgroup,select,textarea{color:inherit;font-family:inherit;font-size:100%;line-height:inherit;margin:0;padding:0}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{color:#9ca3af;opacity:1}input::placeholder,textarea::placeholder{color:#9ca3af;opacity:1}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{height:auto;max-width:100%}[hidden]{display:none}[multiple],[type=date],[type=datetime-local],[type=email],[type=month],[type=number],[type=password],[type=search],[type=tel],[type=text],[type=time],[type=url],[type=week],select,textarea{--tw-shadow:0 0 #0000;-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:#fff;border-color:#6b7280;border-radius:0;border-width:1px;font-size:1rem;line-height:1.5rem;padding:.5rem .75rem}[multiple]:focus,[type=date]:focus,[type=datetime-local]:focus,[type=email]:focus,[type=month]:focus,[type=number]:focus,[type=password]:focus,[type=search]:focus,[type=tel]:focus,[type=text]:focus,[type=time]:focus,[type=url]:focus,[type=week]:focus,select:focus,textarea:focus{--tw-ring-inset:var(--tw-empty,/*!*/ /*!*/);--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#2563eb;--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);border-color:#2563eb;box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);outline:2px solid transparent;outline-offset:2px}input::-moz-placeholder,textarea::-moz-placeholder{color:#6b7280;opacity:1}input::placeholder,textarea::placeholder{color:#6b7280;opacity:1}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-date-and-time-value{min-height:1.5em}::-webkit-datetime-edit,::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-meridiem-field,::-webkit-datetime-edit-millisecond-field,::-webkit-datetime-edit-minute-field,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-second-field,::-webkit-datetime-edit-year-field{padding-bottom:0;padding-top:0}select{-webkit-print-color-adjust:exact;background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3E%3Cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3E%3C/svg%3E");background-position:right .5rem center;background-repeat:no-repeat;background-size:1.5em 1.5em;color-adjust:exact;padding-right:2.5rem}[multiple]{-webkit-print-color-adjust:unset;background-image:none;background-position:0 0;background-repeat:unset;background-size:initial;color-adjust:unset;padding-right:.75rem}[type=checkbox],[type=radio]{-webkit-print-color-adjust:exact;--tw-shadow:0 0 #0000;-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:#fff;background-origin:border-box;border-color:#6b7280;border-width:1px;color:#2563eb;color-adjust:exact;display:inline-block;flex-shrink:0;height:1rem;padding:0;-webkit-user-select:none;-moz-user-select:none;user-select:none;vertical-align:middle;width:1rem}[type=checkbox]{border-radius:0}[type=radio]{border-radius:100%}[type=checkbox]:focus,[type=radio]:focus{--tw-ring-inset:var(--tw-empty,/*!*/ /*!*/);--tw-ring-offset-width:2px;--tw-ring-offset-color:#fff;--tw-ring-color:#2563eb;--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);outline:2px solid transparent;outline-offset:2px}[type=checkbox]:checked,[type=radio]:checked{background-color:currentColor;background-position:50%;background-repeat:no-repeat;background-size:100% 100%;border-color:transparent}[type=checkbox]:checked{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 16 16' fill='%23fff' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12.207 4.793a1 1 0 0 1 0 1.414l-5 5a1 1 0 0 1-1.414 0l-2-2a1 1 0 0 1 1.414-1.414L6.5 9.086l4.293-4.293a1 1 0 0 1 1.414 0z'/%3E%3C/svg%3E")}[type=radio]:checked{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 16 16' fill='%23fff' xmlns='http://www.w3.org/2000/svg'%3E%3Ccircle cx='8' cy='8' r='3'/%3E%3C/svg%3E")}[type=checkbox]:checked:focus,[type=checkbox]:checked:hover,[type=radio]:checked:focus,[type=radio]:checked:hover{background-color:currentColor;border-color:transparent}[type=checkbox]:indeterminate{background-color:currentColor;background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 16 16'%3E%3Cpath stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M4 8h8'/%3E%3C/svg%3E");background-position:50%;background-repeat:no-repeat;background-size:100% 100%;border-color:transparent}[type=checkbox]:indeterminate:focus,[type=checkbox]:indeterminate:hover{background-color:currentColor;border-color:transparent}[type=file]{background:unset;border-color:inherit;border-radius:0;border-width:0;font-size:unset;line-height:inherit;padding:0}[type=file]:focus{outline:1px auto -webkit-focus-ring-color}:root{--lsky-bg:#f6f7f9;--lsky-surface:#fff;--lsky-surface-2:#fafbfc;--lsky-surface-3:#f1f3f6;--lsky-border:#e9ecf0;--lsky-border-2:#dde2e8;--lsky-text:#16191d;--lsky-text-2:#5a6472;--lsky-text-3:#8b95a3;--lsky-accent:#2563eb;--lsky-accent-hover:#1d4ed8;--lsky-accent-soft:#eef4ff;--lsky-danger:#dc2626;--lsky-danger-soft:#fef2f2;--lsky-shadow-card:0 1px 2px rgba(16,24,40,.04),0 1px 3px rgba(16,24,40,.05);--lsky-shadow-pop:0 6px 18px rgba(16,24,40,.08),0 2px 6px rgba(16,24,40,.04);--lsky-shadow-modal:0 20px 50px rgba(16,24,40,.18)}html.dark{--lsky-bg:#0f1216;--lsky-surface:#161a20;--lsky-surface-2:#1b2027;--lsky-surface-3:#1f252e;--lsky-border:#252b34;--lsky-border-2:#313945;--lsky-text:#e7ebf0;--lsky-text-2:#9aa4b2;--lsky-text-3:#6d7784;--lsky-accent:#3b82f6;--lsky-accent-hover:#60a5fa;--lsky-accent-soft:#16233b;--lsky-danger:#f87171;--lsky-danger-soft:#2a1618;--lsky-shadow-card:0 1px 2px rgba(0,0,0,.35),0 1px 3px rgba(0,0,0,.3);--lsky-shadow-pop:0 8px 22px rgba(0,0,0,.45);--lsky-shadow-modal:0 24px 60px rgba(0,0,0,.6)}html{color-scheme:light}html.dark{color-scheme:dark}body{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;background-color:var(--lsky-bg);color:var(--lsky-text);font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,PingFang SC,Hiragino Sans GB,Microsoft YaHei,Noto Sans SC,ui-sans-serif,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;transition:background-color .2s ease,color .2s ease}::-moz-selection{background:var(--lsky-accent);color:#fff}::selection{background:var(--lsky-accent);color:#fff}:focus-visible{outline:2px solid var(--lsky-accent);outline-offset:1px}*,:after,:before{--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.ls-card{background-color:var(--lsky-surface);border-color:var(--lsky-border);border-radius:10px;border-width:1px;box-shadow:var(--lsky-shadow-card)}.ls-card-hd{border-bottom-width:1px;border-color:var(--lsky-border);display:flex;font-size:14px;font-weight:600;justify-content:space-between;padding:.75rem 1rem}.ls-btn,.ls-card-hd{align-items:center;color:var(--lsky-text)}.ls-btn{background-color:var(--lsky-surface);border-color:var(--lsky-border-2);border-radius:.5rem;border-width:1px;cursor:pointer;display:inline-flex;font-size:13.5px;font-weight:500;gap:.375rem;height:2.25rem;justify-content:center;padding-left:.875rem;padding-right:.875rem;transition:background-color .15s ease,border-color .15s ease,color .15s ease,box-shadow .15s ease;white-space:nowrap}.ls-btn:hover{background-color:var(--lsky-surface-2)}.ls-btn.disabled,.ls-btn:disabled{cursor:not-allowed;opacity:.5}.ls-btn-primary{--tw-text-opacity:1;background-color:var(--lsky-accent);border-color:var(--lsky-accent);color:rgb(255 255 255/var(--tw-text-opacity))}.ls-btn-primary:hover{background:var(--lsky-accent-hover);border-color:var(--lsky-accent-hover)}.ls-btn-sm{font-size:13.5px;height:2rem}.ls-btn-sm,.ls-input{padding-left:.75rem;padding-right:.75rem}.ls-input{background-color:var(--lsky-surface);border-color:var(--lsky-border-2);border-radius:.5rem;border-width:1px;color:var(--lsky-text);display:block;font-size:14px;height:2.25rem;transition:border-color .15s ease,box-shadow .15s ease;width:100%}.ls-input:focus{border-color:var(--lsky-accent);box-shadow:0 0 0 3px var(--lsky-accent-soft)}.ls-input::-moz-placeholder{color:var(--lsky-text-3)}.ls-input::placeholder{color:var(--lsky-text-3)}.ls-label{color:var(--lsky-text-2);display:block;font-size:13.5px;font-weight:500;margin-bottom:.375rem}.ls-th{color:var(--lsky-text-3);font-size:12.5px;font-weight:600;letter-spacing:.05em;padding:.625rem 1rem;text-align:left;text-transform:uppercase;white-space:nowrap}.ls-badge{align-items:center;background-color:var(--lsky-surface-3);border-radius:9999px;color:var(--lsky-text-2);display:inline-flex;font-size:12.5px;font-weight:500;padding:.125rem .5rem}.ls-group-title{color:var(--lsky-text-3);font-size:12.5px;font-weight:600;letter-spacing:.05em;margin-bottom:.375rem;text-transform:uppercase}.ls-group-title,.ls-nav-item{padding-left:.75rem;padding-right:.75rem}.ls-nav-item{align-items:center;border-radius:.5rem;color:var(--lsky-text-2);display:flex;font-size:14px;gap:.625rem;height:2.25rem;transition:background-color .15s ease,color .15s ease}.ls-nav-item:hover{background-color:var(--lsky-surface-2);color:var(--lsky-text)}.ls-nav-item.is-active{background-color:var(--lsky-accent-soft);color:var(--lsky-accent);font-weight:600}.ls-menu{background-color:var(--lsky-surface);border-color:var(--lsky-border);border-radius:10px;border-width:1px;box-shadow:var(--lsky-shadow-pop);overflow:hidden;padding-bottom:.25rem;padding-top:.25rem}.ls-menu-item{color:var(--lsky-text-2);cursor:pointer;display:block;font-size:13.5px;overflow:hidden;padding:.5rem .875rem;text-overflow:ellipsis;white-space:nowrap}.ls-menu-item:hover{background-color:var(--lsky-surface-2);color:var(--lsky-text)}@media (min-width:640px){html.sidebar-collapsed #app-sidebar{width:4rem}html.sidebar-collapsed #app-sidebar .ls-brand{justify-content:center;padding-left:0;padding-right:0}html.sidebar-collapsed #app-sidebar #capacity-progress,html.sidebar-collapsed #app-sidebar .ls-brand-name,html.sidebar-collapsed #app-sidebar .ls-group-title,html.sidebar-collapsed #app-sidebar .ls-nav-item>span{display:none}html.sidebar-collapsed #app-sidebar .ls-nav-item{justify-content:center;padding-left:0;padding-right:0}html.sidebar-collapsed .app-content{margin-left:4rem}}.sr-only{clip:rect(0,0,0,0);border-width:0;height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;white-space:nowrap;width:1px}.visible{visibility:visible}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{bottom:0;left:0;right:0;top:0}.top-0{top:0}.bottom-0{bottom:0}.left-0{left:0}.right-0{right:0}.top-4{top:1rem}.right-4{right:1rem}.top-2{top:.5rem}.right-2{right:.5rem}.top-14{top:3.5rem}.-right-\[1000px\]{right:-1000px}.left-2{left:.5rem}.bottom-2{bottom:.5rem}.top-1{top:.25rem}.left-1{left:.25rem}.-left-\[600px\]{left:-600px}.z-0{z-index:0}.z-\[9\]{z-index:9}.z-10{z-index:10}.z-\[3\]{z-index:3}.z-\[2\]{z-index:2}.z-\[1\]{z-index:1}.z-\[10\]{z-index:10}.col-span-6{grid-column:span 6/span 6}.col-span-3{grid-column:span 3/span 3}.float-right{float:right}.m-2{margin:.5rem}.mx-auto{margin-left:auto;margin-right:auto}.my-6{margin-bottom:1.5rem;margin-top:1.5rem}.my-2{margin-bottom:.5rem;margin-top:.5rem}.my-4{margin-bottom:1rem;margin-top:1rem}.mx-0\.5{margin-left:.125rem;margin-right:.125rem}.mx-0{margin-left:0;margin-right:0}.-my-2{margin-bottom:-.5rem;margin-top:-.5rem}.ml-3{margin-left:.75rem}.-ml-px{margin-left:-1px}.mt-10{margin-top:2.5rem}.mt-5{margin-top:1.25rem}.mt-1{margin-top:.25rem}.mb-4{margin-bottom:1rem}.mb-2{margin-bottom:.5rem}.mt-3{margin-top:.75rem}.mt-1\.5{margin-top:.375rem}.ml-auto{margin-left:auto}.mb-10{margin-bottom:2.5rem}.mt-2{margin-top:.5rem}.mt-4{margin-top:1rem}.-mb-14{margin-bottom:-3.5rem}.ml-4{margin-left:1rem}.mb-3{margin-bottom:.75rem}.mb-8{margin-bottom:2rem}.mt-0\.5{margin-top:.125rem}.mt-0{margin-top:0}.-ml-1{margin-left:-.25rem}.mr-2{margin-right:.5rem}.mr-4{margin-right:1rem}.mt-6{margin-top:1.5rem}.ml-2{margin-left:.5rem}.mb-5{margin-bottom:1.25rem}.mb-16{margin-bottom:4rem}.ml-2\.5{margin-left:.625rem}.mr-3{margin-right:.75rem}.ml-0\.5{margin-left:.125rem}.ml-0{margin-left:0}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.hidden{display:none}.h-5{height:1.25rem}.h-10{height:2.5rem}.h-14{height:3.5rem}.h-full{height:100%}.h-8{height:2rem}.h-\[1\.5px\]{height:1.5px}.h-\[36px\]{height:36px}.h-\[28px\]{height:28px}.h-20{height:5rem}.h-7{height:1.75rem}.h-16{height:4rem}.h-11{height:2.75rem}.h-4{height:1rem}.h-80{height:20rem}.h-9{height:2.25rem}.h-36{height:9rem}.h-6{height:1.5rem}.h-24{height:6rem}.h-screen{height:100vh}.h-1\.5{height:.375rem}.h-1{height:.25rem}.h-auto{height:auto}.h-\[100dvh\]{height:100dvh}.max-h-\[50vh\]{max-height:50vh}.max-h-\[500px\]{max-height:500px}.min-h-screen{min-height:100vh}.min-h-\[200px\]{min-height:200px}.min-h-\[44px\]{min-height:44px}.min-h-\[100dvh\]{min-height:100dvh}.w-5{width:1.25rem}.w-10{width:2.5rem}.w-full{width:100%}.w-8{width:2rem}.w-14{width:3.5rem}.w-\[0\%\]{width:0}.w-\[36px\]{width:36px}.w-\[28px\]{width:28px}.w-48{width:12rem}.w-20{width:5rem}.w-64{width:16rem}.w-\[90\%\]{width:90%}.w-\[80\%\]{width:80%}.w-16{width:4rem}.w-4{width:1rem}.w-36{width:9rem}.w-9{width:2.25rem}.w-\[95\%\]{width:95%}.w-6{width:1.5rem}.w-24{width:6rem}.w-3\/4{width:75%}.w-7{width:1.75rem}.w-28{width:7rem}.min-w-0{min-width:0}.min-w-full{min-width:100%}.max-w-\[70\%\]{max-width:70%}.max-w-2xl{max-width:42rem}.max-w-xl{max-width:36rem}.flex-1{flex:1 1 0%}.shrink-0{flex-shrink:0}.origin-top-right{transform-origin:top right}.origin-top-left{transform-origin:top left}.translate-y-9{--tw-translate-y:2.25rem}.translate-y-0,.translate-y-9{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-y-0{--tw-translate-y:0px}.-translate-y-1{--tw-translate-y:-0.25rem}.-translate-y-1,.translate-y-4{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-y-4{--tw-translate-y:1rem}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes spin{to{transform:rotate(1turn)}}.animate-spin{animation:spin 1s linear infinite}.cursor-default{cursor:default}.cursor-pointer{cursor:pointer}.cursor-not-allowed{cursor:not-allowed}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.select-all{-webkit-user-select:all;-moz-user-select:all;user-select:all}.resize-y{resize:vertical}.resize{resize:both}.list-inside{list-style-position:inside}.list-disc{list-style-type:disc}.grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.flex-row{flex-direction:row}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.justify-start{justify-content:flex-start}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.justify-items-center{justify-items:center}.gap-2{gap:.5rem}.gap-6{gap:1.5rem}.gap-4{gap:1rem}.gap-1\.5{gap:.375rem}.gap-1{gap:.25rem}.gap-3{gap:.75rem}.gap-0\.5{gap:.125rem}.gap-0{gap:0}.gap-2\.5{gap:.625rem}.gap-x-3{-moz-column-gap:.75rem;column-gap:.75rem}.gap-y-1{row-gap:.25rem}.space-x-4>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-left:calc(1rem*(1 - var(--tw-space-x-reverse)));margin-right:calc(1rem*var(--tw-space-x-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(.5rem*var(--tw-space-y-reverse));margin-top:calc(.5rem*(1 - var(--tw-space-y-reverse)))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(1rem*var(--tw-space-y-reverse));margin-top:calc(1rem*(1 - var(--tw-space-y-reverse)))}.space-x-2>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-left:calc(.5rem*(1 - var(--tw-space-x-reverse)));margin-right:calc(.5rem*var(--tw-space-x-reverse))}.space-x-1>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-left:calc(.25rem*(1 - var(--tw-space-x-reverse)));margin-right:calc(.25rem*var(--tw-space-x-reverse))}.space-x-3>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-left:calc(.75rem*(1 - var(--tw-space-x-reverse)));margin-right:calc(.75rem*var(--tw-space-x-reverse))}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(.25rem*var(--tw-space-y-reverse));margin-top:calc(.25rem*(1 - var(--tw-space-y-reverse)))}.divide-y>:not([hidden])~:not([hidden]){--tw-divide-y-reverse:0;border-bottom-width:calc(1px*var(--tw-divide-y-reverse));border-top-width:calc(1px*(1 - var(--tw-divide-y-reverse)))}.divide-line>:not([hidden])~:not([hidden]){border-color:var(--lsky-border)}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-scroll{overflow:scroll}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.overflow-y-scroll{overflow-y:scroll}.overscroll-contain{overscroll-behavior:contain}.truncate{overflow:hidden;text-overflow:ellipsis}.truncate,.whitespace-nowrap{white-space:nowrap}.break-words{overflow-wrap:break-word}.break-all{word-break:break-all}.rounded-md{border-radius:.375rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-xl{border-radius:.75rem}.rounded-2xl{border-radius:1rem}.rounded{border-radius:.25rem}.rounded-sm{border-radius:.125rem}.rounded-l-md{border-bottom-left-radius:.375rem;border-top-left-radius:.375rem}.rounded-r-md{border-bottom-right-radius:.375rem;border-top-right-radius:.375rem}.rounded-t-lg{border-top-left-radius:.5rem;border-top-right-radius:.5rem}.rounded-l-none{border-bottom-left-radius:0;border-top-left-radius:0}.border{border-width:1px}.border-2{border-width:2px}.border-0{border-width:0}.border-b{border-bottom-width:1px}.border-b-2{border-bottom-width:2px}.border-r{border-right-width:1px}.border-solid{border-style:solid}.border-dotted{border-style:dotted}.border-gray-300{--tw-border-opacity:1;border-color:rgb(209 213 219/var(--tw-border-opacity))}.border-line{border-color:var(--lsky-border)}.border-line-2{border-color:var(--lsky-border-2)}.border-brand{border-color:var(--lsky-accent)}.border-transparent{border-color:transparent}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}.bg-surface{background-color:var(--lsky-surface)}.bg-surface-3{background-color:var(--lsky-surface-3)}.bg-surface-2{background-color:var(--lsky-surface-2)}.bg-bg{background-color:var(--lsky-bg)}.bg-brand-soft{background-color:var(--lsky-accent-soft)}.bg-line{background-color:var(--lsky-border)}.bg-brand{background-color:var(--lsky-accent)}.bg-yellow-500{--tw-bg-opacity:1;background-color:rgb(234 179 8/var(--tw-bg-opacity))}.bg-red-500{--tw-bg-opacity:1;background-color:rgb(239 68 68/var(--tw-bg-opacity))}.bg-danger-soft{background-color:var(--lsky-danger-soft)}.bg-sky-500{--tw-bg-opacity:1;background-color:rgb(14 165 233/var(--tw-bg-opacity))}.bg-black\/50{background-color:rgba(0,0,0,.5)}.bg-black\/60{background-color:rgba(0,0,0,.6)}.bg-opacity-50{--tw-bg-opacity:0.5}.bg-gradient-to-t{background-image:linear-gradient(to top,var(--tw-gradient-stops))}.from-black{--tw-gradient-from:#000;--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to,transparent)}.fill-current{fill:currentColor}.object-cover{-o-object-fit:cover;object-fit:cover}.object-center{-o-object-position:center;object-position:center}.p-4{padding:1rem}.p-6{padding:1.5rem}.p-2\.5{padding:.625rem}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-1{padding:.25rem}.p-0{padding:0}.px-4{padding-left:1rem;padding-right:1rem}.py-2{padding-bottom:.5rem;padding-top:.5rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-0{padding-left:0;padding-right:0}.py-14{padding-bottom:3.5rem;padding-top:3.5rem}.px-5{padding-left:1.25rem;padding-right:1.25rem}.px-3{padding-left:.75rem;padding-right:.75rem}.py-5{padding-bottom:1.25rem;padding-top:1.25rem}.py-3{padding-bottom:.75rem;padding-top:.75rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.py-10{padding-bottom:2.5rem;padding-top:2.5rem}.py-1{padding-bottom:.25rem;padding-top:.25rem}.px-2\.5{padding-left:.625rem;padding-right:.625rem}.py-1\.5{padding-bottom:.375rem;padding-top:.375rem}.py-4{padding-bottom:1rem;padding-top:1rem}.py-2\.5{padding-bottom:.625rem;padding-top:.625rem}.px-1{padding-left:.25rem;padding-right:.25rem}.py-0{padding-bottom:0;padding-top:0}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.pb-6{padding-bottom:1.5rem}.pt-2{padding-top:.5rem}.pb-2{padding-bottom:.5rem}.pl-6{padding-left:1.5rem}.pl-10{padding-left:2.5rem}.pr-1{padding-right:.25rem}.pt-6{padding-top:1.5rem}.pb-12{padding-bottom:3rem}.pt-14{padding-top:3.5rem}.pb-8{padding-bottom:2rem}.pb-14{padding-bottom:3.5rem}.pt-3{padding-top:.75rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.align-middle{vertical-align:middle}.font-sans{font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,PingFang SC,Hiragino Sans GB,Microsoft YaHei,Noto Sans SC,ui-sans-serif,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-sm{font-size:.875rem;line-height:1.25rem}.text-\[13\.5px\]{font-size:13.5px}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-5xl{font-size:3rem;line-height:1}.text-\[22px\]{font-size:22px}.text-\[15px\]{font-size:15px}.text-\[13px\]{font-size:13px}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.text-4xl{font-size:2.25rem;line-height:2.5rem}.text-\[16px\]{font-size:16px}.text-\[14px\]{font-size:14px}.text-\[21px\]{font-size:21px}.text-\[17px\]{font-size:17px}.text-\[12\.5px\]{font-size:12.5px}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-base{font-size:1rem;line-height:1.5rem}.text-\[11px\]{font-size:11px}.font-medium{font-weight:500}.font-semibold{font-weight:600}.font-normal{font-weight:400}.uppercase{text-transform:uppercase}.italic{font-style:italic}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure) var(--tw-numeric-spacing) var(--tw-numeric-fraction)}.leading-5{line-height:1.25rem}.leading-relaxed{line-height:1.625}.leading-6{line-height:1.5rem}.tracking-wider{letter-spacing:.05em}.text-gray-500{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity))}.text-gray-700{--tw-text-opacity:1;color:rgb(55 65 81/var(--tw-text-opacity))}.text-ink-2{color:var(--lsky-text-2)}.text-ink{color:var(--lsky-text)}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.text-danger{color:var(--lsky-danger)}.text-ink-3{color:var(--lsky-text-3)}.text-brand{color:var(--lsky-accent)}.text-green-500{--tw-text-opacity:1;color:rgb(34 197 94/var(--tw-text-opacity))}.text-green-800{--tw-text-opacity:1;color:rgb(22 101 52/var(--tw-text-opacity))}.text-red-300{--tw-text-opacity:1;color:rgb(252 165 165/var(--tw-text-opacity))}.text-sky-500{--tw-text-opacity:1;color:rgb(14 165 233/var(--tw-text-opacity))}.text-orange-500{--tw-text-opacity:1;color:rgb(249 115 22/var(--tw-text-opacity))}.text-yellow-500{--tw-text-opacity:1;color:rgb(234 179 8/var(--tw-text-opacity))}.text-green-600{--tw-text-opacity:1;color:rgb(22 163 74/var(--tw-text-opacity))}.text-red-100{--tw-text-opacity:1;color:rgb(254 226 226/var(--tw-text-opacity))}.text-rose-500{--tw-text-opacity:1;color:rgb(244 63 94/var(--tw-text-opacity))}.text-teal-500{--tw-text-opacity:1;color:rgb(20 184 166/var(--tw-text-opacity))}.text-teal-600{--tw-text-opacity:1;color:rgb(13 148 136/var(--tw-text-opacity))}.underline{-webkit-text-decoration-line:underline;text-decoration-line:underline}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.opacity-40{opacity:.4}.opacity-0{opacity:0}.opacity-100{opacity:1}.opacity-25{opacity:.25}.opacity-75{opacity:.75}.shadow-sm{--tw-shadow:0 1px 2px 0 rgba(0,0,0,.05);--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color)}.shadow-card,.shadow-sm{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-card{--tw-shadow:var(--lsky-shadow-card);--tw-shadow-colored:var(--lsky-shadow-card)}.outline-none{outline:2px solid transparent;outline-offset:2px}.outline{outline-style:solid}.outline-2{outline-width:2px}.outline-offset-2{outline-offset:2px}.outline-transparent{outline-color:transparent}.ring-gray-300{--tw-ring-opacity:1;--tw-ring-color:rgb(209 213 219/var(--tw-ring-opacity))}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.backdrop-blur-\[2px\]{--tw-backdrop-blur:blur(2px);-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.transition{transition-duration:.15s;transition-property:color,background-color,border-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-text-decoration-color,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-text-decoration-color,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1)}.transition-all{transition-duration:.15s;transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1)}.transition-colors{transition-duration:.15s;transition-property:color,background-color,border-color,fill,stroke,-webkit-text-decoration-color;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,-webkit-text-decoration-color;transition-timing-function:cubic-bezier(.4,0,.2,1)}.transition-opacity{transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1)}.duration-150,.transition-opacity{transition-duration:.15s}.duration-200{transition-duration:.2s}.duration-75{transition-duration:75ms}.duration-300{transition-duration:.3s}.duration-100{transition-duration:.1s}.duration-500{transition-duration:.5s}.ease-in-out{transition-timing-function:cubic-bezier(.4,0,.2,1)}.ease-out{transition-timing-function:cubic-bezier(0,0,.2,1)}.ease-in{transition-timing-function:cubic-bezier(.4,0,1,1)}.hover\:bg-surface-2:hover{background-color:var(--lsky-surface-2)}.hover\:bg-surface-3:hover{background-color:var(--lsky-surface-3)}.hover\:bg-surface:hover{background-color:var(--lsky-surface)}.hover\:text-gray-500:hover{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity))}.hover\:text-gray-400:hover{--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity))}.hover\:text-ink:hover{color:var(--lsky-text)}.hover\:text-danger:hover{color:var(--lsky-danger)}.hover\:text-brand:hover{color:var(--lsky-accent)}.hover\:text-teal-900:hover{--tw-text-opacity:1;color:rgb(19 78 74/var(--tw-text-opacity))}.hover\:underline:hover{-webkit-text-decoration-line:underline;text-decoration-line:underline}.hover\:opacity-100:hover{opacity:1}.hover\:opacity-80:hover{opacity:.8}.focus\:z-10:focus{z-index:10}.focus\:border-blue-300:focus{--tw-border-opacity:1;border-color:rgb(147 197 253/var(--tw-border-opacity))}.focus\:border-brand:focus{border-color:var(--lsky-accent)}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color)}.focus\:ring-2:focus,.focus\:ring:focus{box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.focus\:ring-2:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color)}.focus\:ring-brand:focus{--tw-ring-color:var(--lsky-accent)}.focus\:ring-brand-soft:focus{--tw-ring-color:var(--lsky-accent-soft)}.focus\:ring-opacity-50:focus{--tw-ring-opacity:0.5}.focus\:ring-offset-2:focus{--tw-ring-offset-width:2px}.active\:bg-gray-100:active{--tw-bg-opacity:1;background-color:rgb(243 244 246/var(--tw-bg-opacity))}.active\:text-gray-700:active{--tw-text-opacity:1;color:rgb(55 65 81/var(--tw-text-opacity))}.active\:text-gray-500:active{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity))}.group:hover .group-hover\:block{display:block}.group:hover .group-hover\:flex{display:flex}.group:hover .group-hover\:hidden{display:none}.group:hover .group-hover\:bg-surface{background-color:var(--lsky-surface)}.group:hover .group-hover\:text-brand{color:var(--lsky-accent)}.group:hover .group-hover\:brightness-50{--tw-brightness:brightness(.5);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 (min-width:640px){.sm\:left-0{left:0}.sm\:top-4{top:1rem}.sm\:right-4{right:1rem}.sm\:col-span-3{grid-column:span 3/span 3}.sm\:col-span-2{grid-column:span 2/span 2}.sm\:-mx-6{margin-left:-1.5rem;margin-right:-1.5rem}.sm\:ml-64{margin-left:16rem}.sm\:mt-0{margin-top:0}.sm\:block{display:block}.sm\:inline-block{display:inline-block}.sm\:flex{display:flex}.sm\:grid{display:grid}.sm\:hidden{display:none}.sm\:h-9{height:2.25rem}.sm\:min-h-\[320px\]{min-height:320px}.sm\:w-auto{width:auto}.sm\:w-28{width:7rem}.sm\:w-64{width:16rem}.sm\:max-w-md{max-width:28rem}.sm\:flex-1{flex:1 1 0%}.sm\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.sm\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.sm\:flex-row{flex-direction:row}.sm\:items-start{align-items:flex-start}.sm\:items-center{align-items:center}.sm\:justify-start{justify-content:flex-start}.sm\:justify-center{justify-content:center}.sm\:justify-between{justify-content:space-between}.sm\:gap-5{gap:1.25rem}.sm\:gap-4{gap:1rem}.sm\:rounded-md{border-radius:.375rem}.sm\:rounded-xl2{border-radius:12px}.sm\:rounded-\[10px\]{border-radius:10px}.sm\:p-6{padding:1.5rem}.sm\:px-3{padding-left:.75rem;padding-right:.75rem}.sm\:px-10{padding-left:2.5rem;padding-right:2.5rem}.sm\:px-6{padding-left:1.5rem;padding-right:1.5rem}.sm\:pt-0{padding-top:0}.sm\:pt-8{padding-top:2rem}.sm\:pl-1{padding-left:.25rem}.sm\:pr-2{padding-right:.5rem}.sm\:text-left{text-align:left}.sm\:text-2xl{font-size:1.5rem;line-height:2rem}}@media (min-width:768px){.md\:top-3{top:.75rem}.md\:right-3{right:.75rem}.md\:col-span-2{grid-column:span 2/span 2}.md\:my-9{margin-bottom:2.25rem;margin-top:2.25rem}.md\:my-8{margin-bottom:2rem;margin-top:2rem}.md\:mt-0{margin-top:0}.md\:mt-4{margin-top:1rem}.md\:block{display:block}.md\:inline-block{display:inline-block}.md\:h-screen{height:100vh}.md\:max-h-\[18rem\]{max-height:18rem}.md\:w-36{width:9rem}.md\:w-72{width:18rem}.md\:w-\[600px\]{width:600px}.md\:w-\[70\%\]{width:70%}.md\:max-w-2xl{max-width:42rem}.md\:translate-y-0{--tw-translate-y:0px}.md\:scale-95,.md\:translate-y-0{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.md\:scale-95{--tw-scale-x:.95;--tw-scale-y:.95}.md\:scale-100{--tw-scale-x:1;--tw-scale-y:1;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:gap-4{gap:1rem}.md\:gap-6{gap:1.5rem}.md\:space-y-6>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(1.5rem*var(--tw-space-y-reverse));margin-top:calc(1.5rem*(1 - var(--tw-space-y-reverse)))}.md\:overflow-y-auto{overflow-y:auto}.md\:overscroll-contain{overscroll-behavior:contain}.md\:rounded-xl2{border-radius:12px}.md\:p-6{padding:1.5rem}.md\:p-5{padding:1.25rem}.md\:px-10{padding-left:2.5rem;padding-right:2.5rem}.md\:py-20{padding-bottom:5rem;padding-top:5rem}.md\:py-5{padding-bottom:1.25rem;padding-top:1.25rem}.md\:px-2{padding-left:.5rem;padding-right:.5rem}.md\:px-4{padding-left:1rem;padding-right:1rem}.md\:align-middle{vertical-align:middle}.md\:focus\:w-52:focus,.md\:hover\:w-52:hover{width:13rem}}@media (min-width:1024px){.lg\:top-4{top:1rem}.lg\:right-4{right:1rem}.lg\:-mx-8{margin-left:-2rem;margin-right:-2rem}.lg\:flex{display:flex}.lg\:hidden{display:none}.lg\:w-\[60\%\]{width:60%}.lg\:max-w-4xl{max-width:56rem}.lg\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.lg\:grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}.lg\:p-8{padding:2rem}.lg\:px-10{padding-left:2.5rem;padding-right:2.5rem}.lg\:py-7{padding-bottom:1.75rem;padding-top:1.75rem}.lg\:px-4{padding-left:1rem;padding-right:1rem}.lg\:px-8{padding-left:2rem;padding-right:2rem}}@media (min-width:1280px){.xl\:grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}.xl\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.xl\:px-10{padding-left:2.5rem;padding-right:2.5rem}}@media (min-width:1536px){.\32xl\:grid-cols-8{grid-template-columns:repeat(8,minmax(0,1fr))}.\32xl\:px-60{padding-left:15rem;padding-right:15rem}} diff --git a/src/public/css/common.css b/src/public/css/common.css index 1f7f418..2af02b5 100644 --- a/src/public/css/common.css +++ b/src/public/css/common.css @@ -1 +1 @@ -.toast-title{font-weight:700}.toast-message{-ms-word-wrap:break-word;word-wrap:break-word}.toast-message a,.toast-message label{color:#fff}.toast-message a:hover{color:#ccc;text-decoration:none}.toast-close-button{color:#fff;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=80);filter:alpha(opacity=80);float:right;font-size:20px;font-weight:700;line-height:1;opacity:.8;position:relative;right:-.3em;-webkit-text-shadow:0 1px 0 #fff;text-shadow:0 1px 0 #fff;top:-.3em}.toast-close-button:focus,.toast-close-button:hover{color:#000;cursor:pointer;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=40);filter:alpha(opacity=40);opacity:.4;text-decoration:none}.rtl .toast-close-button{float:left;left:-.3em;right:.3em}button.toast-close-button{-webkit-appearance:none;background:0 0;border:0;cursor:pointer;padding:0}.toast-top-center{right:0;top:0;width:100%}.toast-bottom-center{bottom:0;right:0;width:100%}.toast-top-full-width{right:0;top:0;width:100%}.toast-bottom-full-width{bottom:0;right:0;width:100%}.toast-top-left{left:12px;top:12px}.toast-top-right{right:12px;top:12px}.toast-bottom-right{bottom:12px;right:12px}.toast-bottom-left{bottom:12px;left:12px}#toast-container{pointer-events:none;position:fixed;z-index:999999}#toast-container *{box-sizing:border-box}#toast-container>div{background-position:15px;background-repeat:no-repeat;border-radius:3px;box-shadow:0 0 12px #999;color:#fff;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=80);filter:alpha(opacity=80);margin:0 0 6px;opacity:.8;overflow:hidden;padding:15px 15px 15px 50px;pointer-events:auto;position:relative;width:300px}#toast-container>div.rtl{background-position:right 15px center;direction:rtl;padding:15px 50px 15px 15px}#toast-container>div:hover{box-shadow:0 0 12px #000;cursor:pointer;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=100);filter:alpha(opacity=100);opacity:1}#toast-container>.toast-info{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGwSURBVEhLtZa9SgNBEMc9sUxxRcoUKSzSWIhXpFMhhYWFhaBg4yPYiWCXZxBLERsLRS3EQkEfwCKdjWJAwSKCgoKCcudv4O5YLrt7EzgXhiU3/4+b2ckmwVjJSpKkQ6wAi4gwhT+z3wRBcEz0yjSseUTrcRyfsHsXmD0AmbHOC9Ii8VImnuXBPglHpQ5wwSVM7sNnTG7Za4JwDdCjxyAiH3nyA2mtaTJufiDZ5dCaqlItILh1NHatfN5skvjx9Z38m69CgzuXmZgVrPIGE763Jx9qKsRozWYw6xOHdER+nn2KkO+Bb+UV5CBN6WC6QtBgbRVozrahAbmm6HtUsgtPC19tFdxXZYBOfkbmFJ1VaHA1VAHjd0pp70oTZzvR+EVrx2Ygfdsq6eu55BHYR8hlcki+n+kERUFG8BrA0BwjeAv2M8WLQBtcy+SD6fNsmnB3AlBLrgTtVW1c2QN4bVWLATaIS60J2Du5y1TiJgjSBvFVZgTmwCU+dAZFoPxGEEs8nyHC9Bwe2GvEJv2WXZb0vjdyFT4Cxk3e/kIqlOGoVLwwPevpYHT+00T+hWwXDf4AJAOUqWcDhbwAAAAASUVORK5CYII=)!important}#toast-container>.toast-error{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHOSURBVEhLrZa/SgNBEMZzh0WKCClSCKaIYOED+AAKeQQLG8HWztLCImBrYadgIdY+gIKNYkBFSwu7CAoqCgkkoGBI/E28PdbLZmeDLgzZzcx83/zZ2SSXC1j9fr+I1Hq93g2yxH4iwM1vkoBWAdxCmpzTxfkN2RcyZNaHFIkSo10+8kgxkXIURV5HGxTmFuc75B2RfQkpxHG8aAgaAFa0tAHqYFfQ7Iwe2yhODk8+J4C7yAoRTWI3w/4klGRgR4lO7Rpn9+gvMyWp+uxFh8+H+ARlgN1nJuJuQAYvNkEnwGFck18Er4q3egEc/oO+mhLdKgRyhdNFiacC0rlOCbhNVz4H9FnAYgDBvU3QIioZlJFLJtsoHYRDfiZoUyIxqCtRpVlANq0EU4dApjrtgezPFad5S19Wgjkc0hNVnuF4HjVA6C7QrSIbylB+oZe3aHgBsqlNqKYH48jXyJKMuAbiyVJ8KzaB3eRc0pg9VwQ4niFryI68qiOi3AbjwdsfnAtk0bCjTLJKr6mrD9g8iq/S/B81hguOMlQTnVyG40wAcjnmgsCNESDrjme7wfftP4P7SP4N3CJZdvzoNyGq2c/HWOXJGsvVg+RA/k2MC/wN6I2YA2Pt8GkAAAAASUVORK5CYII=)!important}#toast-container>.toast-success{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADsSURBVEhLY2AYBfQMgf///3P8+/evAIgvA/FsIF+BavYDDWMBGroaSMMBiE8VC7AZDrIFaMFnii3AZTjUgsUUWUDA8OdAH6iQbQEhw4HyGsPEcKBXBIC4ARhex4G4BsjmweU1soIFaGg/WtoFZRIZdEvIMhxkCCjXIVsATV6gFGACs4Rsw0EGgIIH3QJYJgHSARQZDrWAB+jawzgs+Q2UO49D7jnRSRGoEFRILcdmEMWGI0cm0JJ2QpYA1RDvcmzJEWhABhD/pqrL0S0CWuABKgnRki9lLseS7g2AlqwHWQSKH4oKLrILpRGhEQCw2LiRUIa4lwAAAABJRU5ErkJggg==)!important}#toast-container>.toast-warning{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGYSURBVEhL5ZSvTsNQFMbXZGICMYGYmJhAQIJAICYQPAACiSDB8AiICQQJT4CqQEwgJvYASAQCiZiYmJhAIBATCARJy+9rTsldd8sKu1M0+dLb057v6/lbq/2rK0mS/TRNj9cWNAKPYIJII7gIxCcQ51cvqID+GIEX8ASG4B1bK5gIZFeQfoJdEXOfgX4QAQg7kH2A65yQ87lyxb27sggkAzAuFhbbg1K2kgCkB1bVwyIR9m2L7PRPIhDUIXgGtyKw575yz3lTNs6X4JXnjV+LKM/m3MydnTbtOKIjtz6VhCBq4vSm3ncdrD2lk0VgUXSVKjVDJXJzijW1RQdsU7F77He8u68koNZTz8Oz5yGa6J3H3lZ0xYgXBK2QymlWWA+RWnYhskLBv2vmE+hBMCtbA7KX5drWyRT/2JsqZ2IvfB9Y4bWDNMFbJRFmC9E74SoS0CqulwjkC0+5bpcV1CZ8NMej4pjy0U+doDQsGyo1hzVJttIjhQ7GnBtRFN1UarUlH8F3xict+HY07rEzoUGPlWcjRFRr4/gChZgc3ZL2d8oAAAAASUVORK5CYII=)!important}#toast-container.toast-bottom-center>div,#toast-container.toast-top-center>div{margin-left:auto;margin-right:auto;width:300px}#toast-container.toast-bottom-full-width>div,#toast-container.toast-top-full-width>div{margin-left:auto;margin-right:auto;width:96%}.toast{background-color:#030303}.toast-success{background-color:#51a351}.toast-error{background-color:#bd362f}.toast-info{background-color:#2f96b4}.toast-warning{background-color:#f89406}.toast-progress{background-color:#000;bottom:0;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=40);filter:alpha(opacity=40);height:4px;left:0;opacity:.4;position:absolute}@media (max-width:240px){#toast-container>div{padding:8px 8px 8px 50px;width:11em}#toast-container>div.rtl{padding:8px 50px 8px 8px}#toast-container .toast-close-button{right:-.2em;top:-.2em}#toast-container .rtl .toast-close-button{left:-.2em;right:.2em}}@media (min-width:241px) and (max-width:480px){#toast-container>div{padding:8px 8px 8px 50px;width:18em}#toast-container>div.rtl{padding:8px 50px 8px 8px}#toast-container .toast-close-button{right:-.2em;top:-.2em}#toast-container .rtl .toast-close-button{left:-.2em;right:.2em}}@media (min-width:481px) and (max-width:768px){#toast-container>div{padding:15px 15px 15px 50px;width:25em}#toast-container>div.rtl{padding:15px 50px 15px 15px}}[x-cloak]{display:none!important}.scrollbar-none::-webkit-scrollbar{display:none}.scrollbar-none{-ms-overflow-style:none;scrollbar-width:none}::-webkit-scrollbar{height:100%;min-height:8px;width:8px}::-webkit-scrollbar-thumb{background:var(--lsky-border-2);border-radius:6px}::-webkit-scrollbar-track{background:var(--lsky-surface-2);border-radius:6px}progress::-webkit-progress-bar{background-color:var(--lsky-surface-3)}progress::-webkit-progress-value{background-color:var(--lsky-accent)}#capacity-progress progress.green::-webkit-progress-value{background:#00ab00}#capacity-progress progress.yellowgreen::-webkit-progress-value{background:#9acd32}#capacity-progress progress.yellow::-webkit-progress-value{background:#b9b900}#capacity-progress progress.orange::-webkit-progress-value{background:#f90}#capacity-progress progress.red::-webkit-progress-value{background:#ff4141}#toast-container>div{font-size:13.5px;padding:10px 10px 10px 50px}#toast-container>div,#toast-container>div:hover{box-shadow:var(--lsky-shadow-pop)}.toast-close-button{right:-.15em}.viewer-backdrop{background-color:rgba(0,0,0,.75)}.infinite-scroll{display:flex;justify-content:center;justify-items:center;padding:1rem 0;width:100%}.infinite-scroll span{color:var(--lsky-text-2);cursor:pointer;display:inline-flex;font-size:.9em}.infinite-scroll span.disabled{color:var(--lsky-text-3);cursor:not-allowed}#image-movements-modal .infinite-scroll{display:none}#images-grid a.ds-selected,#images-grid a:hover{outline-color:var(--lsky-accent)}#images-grid a.ds-selected .image-selector,#images-grid a:hover .image-selector{display:block}#images-grid a.ds-selected.ds-selected .image-selector i,#images-grid a:hover.ds-selected .image-selector i{border-color:#fff;color:var(--lsky-accent)}.switch{display:inline-block;height:30px;position:relative;width:55px}.switch input{display:none}.slider{background-color:var(--lsky-border-2);bottom:0;cursor:pointer;left:0;right:0;top:0}.slider,.slider:before{position:absolute;transition:.4s}.slider:before{background-color:#fff;bottom:3px;content:"";height:24px;left:4px;width:24px}input:checked+.slider{background-color:var(--lsky-accent)}input:focus+.slider{box-shadow:0 0 0 3px var(--lsky-accent-soft)}input:checked+.slider:before{transform:translateX(23px)}.slider.round{border-radius:34px}.slider.round:before{border-radius:50%}html .swal2-container.swal2-backdrop-show,html .swal2-container.swal2-noanimation{-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);background:rgba(0,0,0,.5)}html .swal2-popup{background:var(--lsky-surface);border-color:var(--lsky-border);border-radius:12px;border-style:solid;border-width:1px;box-shadow:var(--lsky-shadow-modal);font-family:inherit;max-width:calc(100% - 2rem);padding:20px;width:32em}html .swal2-popup,html .swal2-title{color:var(--lsky-text);text-align:left}html .swal2-title{font-size:16px;font-weight:600;line-height:1.5;margin:0;padding:0}html .swal2-html-container{color:var(--lsky-text-2);font-size:14px;line-height:1.6;margin:8px 0 0;padding:0;text-align:left}html .swal2-icon{border-color:var(--lsky-border-2);color:var(--lsky-border-2);font-size:11px;margin:2px 0 14px}html .swal2-icon.swal2-error,html .swal2-icon.swal2-warning{border-color:var(--lsky-danger);color:var(--lsky-danger)}html .swal2-actions{gap:8px;justify-content:flex-end;margin:20px 0 0}html .swal2-styled,html .swal2-styled.swal2-cancel,html .swal2-styled.swal2-confirm,html .swal2-styled.swal2-deny{background-image:none;border:1px solid transparent;border-radius:8px;box-shadow:none;font-size:13.5px;font-weight:500;height:36px;margin:0;padding:0 14px}html .swal2-styled.swal2-confirm{background-color:var(--lsky-accent);border-color:var(--lsky-accent);color:#fff}html .swal2-styled.swal2-confirm:hover{background-color:var(--lsky-accent-hover);border-color:var(--lsky-accent-hover)}html .swal2-styled.swal2-confirm:focus{box-shadow:0 0 0 3px var(--lsky-accent-soft)}html .swal2-styled.swal2-cancel{background-color:var(--lsky-surface);border-color:var(--lsky-border-2);color:var(--lsky-text)}html .swal2-styled.swal2-cancel:hover{background-color:var(--lsky-surface-2)}html .swal2-styled.swal2-cancel:focus{box-shadow:0 0 0 3px var(--lsky-accent-soft)}html .swal2-styled.swal2-deny{background-color:var(--lsky-danger-soft);color:var(--lsky-danger)}html .swal2-popup .swal2-actions .swal2-styled:active,html .swal2-popup .swal2-actions .swal2-styled:hover{background-image:none}html .swal2-input,html .swal2-select,html .swal2-textarea{background-color:var(--lsky-surface-2);border-color:var(--lsky-border-2);border-radius:8px;border-style:solid;border-width:1px;box-shadow:none;color:var(--lsky-text);font-size:14px;height:38px;margin:12px 0 0;padding:0 12px;width:100%}html .swal2-input:focus,html .swal2-select:focus,html .swal2-textarea:focus{border-color:var(--lsky-accent);box-shadow:0 0 0 3px var(--lsky-accent-soft)}html .swal2-input::-moz-placeholder,html .swal2-textarea::-moz-placeholder{color:var(--lsky-text-3)}html .swal2-input::placeholder,html .swal2-textarea::placeholder{color:var(--lsky-text-3)}html .swal2-select{background-position:right 10px center;padding-right:28px}html .swal2-validation-message{background-color:var(--lsky-danger-soft);border-radius:8px;color:var(--lsky-danger);font-size:13px;font-weight:400;line-height:1.5;margin:12px 0 0;padding:8px 12px}html .swal2-loader{border-color:var(--lsky-accent) transparent var(--lsky-accent) transparent}html .swal2-close{color:var(--lsky-text-3);font-size:20px;height:1.2em;width:1.2em}html .swal2-close:hover{color:var(--lsky-text)}html.dark #toast-container>div{background-color:var(--lsky-surface);background-image:none;border:1px solid var(--lsky-border);color:var(--lsky-text);opacity:.97}html.dark #toast-container>div:hover{box-shadow:var(--lsky-shadow-pop)}html.dark #toast-container .toast-progress{background-color:var(--lsky-accent)}html.dark .viewer-toolbar>ul>li{background-color:hsla(0,0%,100%,.12)}.dropdown-context .submenu-back>a>i{margin-right:7px!important} +.toast-title{font-weight:700}.toast-message{-ms-word-wrap:break-word;word-wrap:break-word}.toast-message a,.toast-message label{color:#fff}.toast-message a:hover{color:#ccc;text-decoration:none}.toast-close-button{color:#fff;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=80);filter:alpha(opacity=80);float:right;font-size:20px;font-weight:700;line-height:1;opacity:.8;position:relative;right:-.3em;-webkit-text-shadow:0 1px 0 #fff;text-shadow:0 1px 0 #fff;top:-.3em}.toast-close-button:focus,.toast-close-button:hover{color:#000;cursor:pointer;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=40);filter:alpha(opacity=40);opacity:.4;text-decoration:none}.rtl .toast-close-button{float:left;left:-.3em;right:.3em}button.toast-close-button{-webkit-appearance:none;background:0 0;border:0;cursor:pointer;padding:0}.toast-top-center{right:0;top:0;width:100%}.toast-bottom-center{bottom:0;right:0;width:100%}.toast-top-full-width{right:0;top:0;width:100%}.toast-bottom-full-width{bottom:0;right:0;width:100%}.toast-top-left{left:12px;top:12px}.toast-top-right{right:12px;top:12px}.toast-bottom-right{bottom:12px;right:12px}.toast-bottom-left{bottom:12px;left:12px}#toast-container{pointer-events:none;position:fixed;z-index:999999}#toast-container *{box-sizing:border-box}#toast-container>div{background-position:15px;background-repeat:no-repeat;border-radius:3px;box-shadow:0 0 12px #999;color:#fff;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=80);filter:alpha(opacity=80);margin:0 0 6px;opacity:.8;overflow:hidden;padding:15px 15px 15px 50px;pointer-events:auto;position:relative;width:300px}#toast-container>div.rtl{background-position:right 15px center;direction:rtl;padding:15px 50px 15px 15px}#toast-container>div:hover{box-shadow:0 0 12px #000;cursor:pointer;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=100);filter:alpha(opacity=100);opacity:1}#toast-container>.toast-info{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGwSURBVEhLtZa9SgNBEMc9sUxxRcoUKSzSWIhXpFMhhYWFhaBg4yPYiWCXZxBLERsLRS3EQkEfwCKdjWJAwSKCgoKCcudv4O5YLrt7EzgXhiU3/4+b2ckmwVjJSpKkQ6wAi4gwhT+z3wRBcEz0yjSseUTrcRyfsHsXmD0AmbHOC9Ii8VImnuXBPglHpQ5wwSVM7sNnTG7Za4JwDdCjxyAiH3nyA2mtaTJufiDZ5dCaqlItILh1NHatfN5skvjx9Z38m69CgzuXmZgVrPIGE763Jx9qKsRozWYw6xOHdER+nn2KkO+Bb+UV5CBN6WC6QtBgbRVozrahAbmm6HtUsgtPC19tFdxXZYBOfkbmFJ1VaHA1VAHjd0pp70oTZzvR+EVrx2Ygfdsq6eu55BHYR8hlcki+n+kERUFG8BrA0BwjeAv2M8WLQBtcy+SD6fNsmnB3AlBLrgTtVW1c2QN4bVWLATaIS60J2Du5y1TiJgjSBvFVZgTmwCU+dAZFoPxGEEs8nyHC9Bwe2GvEJv2WXZb0vjdyFT4Cxk3e/kIqlOGoVLwwPevpYHT+00T+hWwXDf4AJAOUqWcDhbwAAAAASUVORK5CYII=)!important}#toast-container>.toast-error{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHOSURBVEhLrZa/SgNBEMZzh0WKCClSCKaIYOED+AAKeQQLG8HWztLCImBrYadgIdY+gIKNYkBFSwu7CAoqCgkkoGBI/E28PdbLZmeDLgzZzcx83/zZ2SSXC1j9fr+I1Hq93g2yxH4iwM1vkoBWAdxCmpzTxfkN2RcyZNaHFIkSo10+8kgxkXIURV5HGxTmFuc75B2RfQkpxHG8aAgaAFa0tAHqYFfQ7Iwe2yhODk8+J4C7yAoRTWI3w/4klGRgR4lO7Rpn9+gvMyWp+uxFh8+H+ARlgN1nJuJuQAYvNkEnwGFck18Er4q3egEc/oO+mhLdKgRyhdNFiacC0rlOCbhNVz4H9FnAYgDBvU3QIioZlJFLJtsoHYRDfiZoUyIxqCtRpVlANq0EU4dApjrtgezPFad5S19Wgjkc0hNVnuF4HjVA6C7QrSIbylB+oZe3aHgBsqlNqKYH48jXyJKMuAbiyVJ8KzaB3eRc0pg9VwQ4niFryI68qiOi3AbjwdsfnAtk0bCjTLJKr6mrD9g8iq/S/B81hguOMlQTnVyG40wAcjnmgsCNESDrjme7wfftP4P7SP4N3CJZdvzoNyGq2c/HWOXJGsvVg+RA/k2MC/wN6I2YA2Pt8GkAAAAASUVORK5CYII=)!important}#toast-container>.toast-success{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADsSURBVEhLY2AYBfQMgf///3P8+/evAIgvA/FsIF+BavYDDWMBGroaSMMBiE8VC7AZDrIFaMFnii3AZTjUgsUUWUDA8OdAH6iQbQEhw4HyGsPEcKBXBIC4ARhex4G4BsjmweU1soIFaGg/WtoFZRIZdEvIMhxkCCjXIVsATV6gFGACs4Rsw0EGgIIH3QJYJgHSARQZDrWAB+jawzgs+Q2UO49D7jnRSRGoEFRILcdmEMWGI0cm0JJ2QpYA1RDvcmzJEWhABhD/pqrL0S0CWuABKgnRki9lLseS7g2AlqwHWQSKH4oKLrILpRGhEQCw2LiRUIa4lwAAAABJRU5ErkJggg==)!important}#toast-container>.toast-warning{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGYSURBVEhL5ZSvTsNQFMbXZGICMYGYmJhAQIJAICYQPAACiSDB8AiICQQJT4CqQEwgJvYASAQCiZiYmJhAIBATCARJy+9rTsldd8sKu1M0+dLb057v6/lbq/2rK0mS/TRNj9cWNAKPYIJII7gIxCcQ51cvqID+GIEX8ASG4B1bK5gIZFeQfoJdEXOfgX4QAQg7kH2A65yQ87lyxb27sggkAzAuFhbbg1K2kgCkB1bVwyIR9m2L7PRPIhDUIXgGtyKw575yz3lTNs6X4JXnjV+LKM/m3MydnTbtOKIjtz6VhCBq4vSm3ncdrD2lk0VgUXSVKjVDJXJzijW1RQdsU7F77He8u68koNZTz8Oz5yGa6J3H3lZ0xYgXBK2QymlWWA+RWnYhskLBv2vmE+hBMCtbA7KX5drWyRT/2JsqZ2IvfB9Y4bWDNMFbJRFmC9E74SoS0CqulwjkC0+5bpcV1CZ8NMej4pjy0U+doDQsGyo1hzVJttIjhQ7GnBtRFN1UarUlH8F3xict+HY07rEzoUGPlWcjRFRr4/gChZgc3ZL2d8oAAAAASUVORK5CYII=)!important}#toast-container.toast-bottom-center>div,#toast-container.toast-top-center>div{margin-left:auto;margin-right:auto;width:300px}#toast-container.toast-bottom-full-width>div,#toast-container.toast-top-full-width>div{margin-left:auto;margin-right:auto;width:96%}.toast{background-color:#030303}.toast-success{background-color:#51a351}.toast-error{background-color:#bd362f}.toast-info{background-color:#2f96b4}.toast-warning{background-color:#f89406}.toast-progress{background-color:#000;bottom:0;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=40);filter:alpha(opacity=40);height:4px;left:0;opacity:.4;position:absolute}@media (max-width:240px){#toast-container>div{padding:8px 8px 8px 50px;width:11em}#toast-container>div.rtl{padding:8px 50px 8px 8px}#toast-container .toast-close-button{right:-.2em;top:-.2em}#toast-container .rtl .toast-close-button{left:-.2em;right:.2em}}@media (min-width:241px) and (max-width:480px){#toast-container>div{padding:8px 8px 8px 50px;width:18em}#toast-container>div.rtl{padding:8px 50px 8px 8px}#toast-container .toast-close-button{right:-.2em;top:-.2em}#toast-container .rtl .toast-close-button{left:-.2em;right:.2em}}@media (min-width:481px) and (max-width:768px){#toast-container>div{padding:15px 15px 15px 50px;width:25em}#toast-container>div.rtl{padding:15px 50px 15px 15px}}[x-cloak]{display:none!important}.scrollbar-none::-webkit-scrollbar{display:none}.scrollbar-none{-ms-overflow-style:none;scrollbar-width:none}::-webkit-scrollbar{height:100%;min-height:8px;width:8px}::-webkit-scrollbar-thumb{background:var(--lsky-border-2);border-radius:6px}::-webkit-scrollbar-track{background:var(--lsky-surface-2);border-radius:6px}progress::-webkit-progress-bar{background-color:var(--lsky-surface-3)}progress::-webkit-progress-value{background-color:var(--lsky-accent)}#capacity-progress progress.green::-webkit-progress-value{background:#00ab00}#capacity-progress progress.yellowgreen::-webkit-progress-value{background:#9acd32}#capacity-progress progress.yellow::-webkit-progress-value{background:#b9b900}#capacity-progress progress.orange::-webkit-progress-value{background:#f90}#capacity-progress progress.red::-webkit-progress-value{background:#ff4141}#toast-container>div{font-size:13.5px;padding:10px 10px 10px 50px}#toast-container>div,#toast-container>div:hover{box-shadow:var(--lsky-shadow-pop)}.toast-close-button{right:-.15em}.viewer-backdrop{background-color:rgba(0,0,0,.75)}.infinite-scroll{display:flex;justify-content:center;justify-items:center;padding:1rem 0;width:100%}.infinite-scroll span{color:var(--lsky-text-2);cursor:pointer;display:inline-flex;font-size:.9em}.infinite-scroll span.disabled{color:var(--lsky-text-3);cursor:not-allowed}#image-movements-modal .infinite-scroll{display:none}#images-grid a.ds-selected,#images-grid a:hover{outline-color:var(--lsky-accent)}#images-grid a.ds-selected .image-selector,#images-grid a:hover .image-selector{display:block}#images-grid a.ds-selected.ds-selected .image-selector i,#images-grid a:hover.ds-selected .image-selector i{border-color:#fff;color:var(--lsky-accent)}.switch{display:inline-block;height:30px;position:relative;width:55px}.switch input{display:none}.slider{background-color:var(--lsky-border-2);bottom:0;cursor:pointer;left:0;right:0;top:0}.slider,.slider:before{position:absolute;transition:.4s}.slider:before{background-color:#fff;bottom:3px;content:"";height:24px;left:4px;width:24px}input:checked+.slider{background-color:var(--lsky-accent)}input:focus+.slider{box-shadow:0 0 0 3px var(--lsky-accent-soft)}input:checked+.slider:before{transform:translateX(23px)}.slider.round{border-radius:34px}.slider.round:before{border-radius:50%}html .swal2-container.swal2-backdrop-show,html .swal2-container.swal2-noanimation{-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);background:rgba(0,0,0,.5)}html .swal2-popup{background:var(--lsky-surface);border-color:var(--lsky-border);border-radius:12px;border-style:solid;border-width:1px;box-shadow:var(--lsky-shadow-modal);font-family:inherit;max-width:calc(100% - 2rem);padding:20px;width:32em}html .swal2-popup,html .swal2-title{color:var(--lsky-text);text-align:left}html .swal2-title{font-size:16px;font-weight:600;line-height:1.5;margin:0;padding:0}html .swal2-html-container{color:var(--lsky-text-2);font-size:14px;line-height:1.6;margin:8px 0 0;padding:0;text-align:left}html .swal2-icon{border-color:var(--lsky-border-2);color:var(--lsky-border-2);font-size:11px;margin:2px 0 14px}html .swal2-icon.swal2-error,html .swal2-icon.swal2-warning{border-color:var(--lsky-danger);color:var(--lsky-danger)}html .swal2-actions{gap:8px;justify-content:flex-end;margin:20px 0 0}html .swal2-styled,html .swal2-styled.swal2-cancel,html .swal2-styled.swal2-confirm,html .swal2-styled.swal2-deny{background-image:none;border:1px solid transparent;border-radius:8px;box-shadow:none;font-size:13.5px;font-weight:500;height:36px;margin:0;padding:0 14px}html .swal2-styled.swal2-confirm{background-color:var(--lsky-accent);border-color:var(--lsky-accent);color:#fff}html .swal2-styled.swal2-confirm:hover{background-color:var(--lsky-accent-hover);border-color:var(--lsky-accent-hover)}html .swal2-styled.swal2-confirm:focus{box-shadow:0 0 0 3px var(--lsky-accent-soft)}html .swal2-styled.swal2-cancel{background-color:var(--lsky-surface);border-color:var(--lsky-border-2);color:var(--lsky-text)}html .swal2-styled.swal2-cancel:hover{background-color:var(--lsky-surface-2)}html .swal2-styled.swal2-cancel:focus{box-shadow:0 0 0 3px var(--lsky-accent-soft)}html .swal2-styled.swal2-deny{background-color:var(--lsky-danger-soft);color:var(--lsky-danger)}html .swal2-popup .swal2-actions .swal2-styled:active,html .swal2-popup .swal2-actions .swal2-styled:hover{background-image:none}html .swal2-input,html .swal2-select,html .swal2-textarea{background-color:var(--lsky-surface-2);border-color:var(--lsky-border-2);border-radius:8px;border-style:solid;border-width:1px;box-shadow:none;color:var(--lsky-text);font-size:14px;height:38px;margin:12px 0 0;padding:0 12px;width:100%}html .swal2-input:focus,html .swal2-select:focus,html .swal2-textarea:focus{border-color:var(--lsky-accent);box-shadow:0 0 0 3px var(--lsky-accent-soft)}html .swal2-input::-moz-placeholder,html .swal2-textarea::-moz-placeholder{color:var(--lsky-text-3)}html .swal2-input::placeholder,html .swal2-textarea::placeholder{color:var(--lsky-text-3)}html .swal2-select{background-position:right 10px center;padding-right:28px}html .swal2-validation-message{background-color:var(--lsky-danger-soft);border-radius:8px;color:var(--lsky-danger);font-size:13px;font-weight:400;line-height:1.5;margin:12px 0 0;padding:8px 12px}html .swal2-loader{border-color:var(--lsky-accent) transparent var(--lsky-accent) transparent}html .swal2-close{color:var(--lsky-text-3);font-size:20px;height:1.2em;width:1.2em}html .swal2-close:hover{color:var(--lsky-text)}html .swal2-show{animation:lsky-swal-in .3s cubic-bezier(0,0,.2,1) both}html .swal2-hide{animation:lsky-swal-out .2s cubic-bezier(.4,0,1,1) both}@media (max-width:767px){html .swal2-show{animation-name:lsky-swal-in-mobile}html .swal2-hide{animation-name:lsky-swal-out-mobile}}html .swal2-container{transition:background-color .2s cubic-bezier(.4,0,1,1)}html .swal2-container.swal2-backdrop-show,html .swal2-container.swal2-noanimation{transition-duration:.3s;transition-timing-function:cubic-bezier(0,0,.2,1)}html body.swal2-shown:not(.swal2-no-backdrop):not(.swal2-toast-shown){overflow:visible}@keyframes lsky-swal-in{0%{opacity:0;transform:scale(.95)}to{opacity:1;transform:scale(1)}}@keyframes lsky-swal-out{0%{opacity:1;transform:scale(1)}to{opacity:0;transform:scale(.95)}}@keyframes lsky-swal-in-mobile{0%{opacity:0;transform:translateY(16px)}to{opacity:1;transform:translateY(0)}}@keyframes lsky-swal-out-mobile{0%{opacity:1;transform:translateY(0)}to{opacity:0;transform:translateY(16px)}}html.dark #toast-container>div{background-color:var(--lsky-surface);background-image:none;border:1px solid var(--lsky-border);color:var(--lsky-text);opacity:.97}html.dark #toast-container>div:hover{box-shadow:var(--lsky-shadow-pop)}html.dark #toast-container .toast-progress{background-color:var(--lsky-accent)}html.dark .viewer-toolbar>ul>li{background-color:hsla(0,0%,100%,.12)}.dropdown-context .submenu-back>a>i{margin-right:7px!important} diff --git a/src/public/js/context-js/context-js.js b/src/public/js/context-js/context-js.js index af1f991..03d6555 100644 --- a/src/public/js/context-js/context-js.js +++ b/src/public/js/context-js/context-js.js @@ -208,6 +208,9 @@ window.context = window.context || (function () { let touchGestureId = 0; // 每一次「手指按下」算一个新手势 let armedGestureId = 0; // 二级菜单是在哪一次手势里被打开的 let submenuArmingTimer = null; // 「刚弹出、先别高亮」的收尾定时器 // 因"点了菜单外"而关菜单后,要吞掉的那一发 click + let armedMenuGestureId = 0; // 主菜单是在哪一次手势里被打开的 + let menuArmingTimer = null; // 主菜单「刚弹出、先别高亮」的收尾定时器 + let fingerOnScreen = false; // 屏幕上还有没有手指按着(主菜单 arming 窗口据此跟手势续期,而不是死定时器) let menuStylesInjected = false; let menuGuardInstalled = false; @@ -241,6 +244,15 @@ window.context = window.context || (function () { + ' min-width: 0 !important; margin: 0 !important; padding: 0 !important;' + ' border: 0 !important; border-radius: 0 !important; box-shadow: none !important;' + ' background: transparent !important; }' + // 主菜单版(与下面二级菜单那份同一套 arming 机制,见 armMenuHoverGuard): + // 长按弹出的主菜单同样会顶在手指底下,真机触摸的粘滞 hover 会把手指底下那一项当成 hover 目标 + // → 弹出瞬间那项就跳高亮(老师截图里的「复制图片」)。这里连 li 一起关指针事件: + // 只关 a 的话,主题里的 `.dropdown-submenu:hover > a` 仍会因为 li 被 hover 而给「复制链接」上色。 + // 另外 hover 那套是「底色 + 文字变白」两件一起上(见 context-js.less 的 li>a:hover), + // 所以文字色也一并还原 —— 万一 :hover/:focus 还是粘住了,那一项也保持原样,不会变成白底白字。 + + '.dropdown-context.menu-arming > li,' + + ' .dropdown-context.menu-arming > li > a {' + + ' pointer-events: none !important; background-color: transparent !important; color: inherit !important; }' // 刚弹出的一瞬间:项不接受指针事件(浏览器就不会把它当成 hover 目标 → 不会高亮), // 同时把 hover 底色压平做双保险;不改变任何尺寸/位置,面板本身照旧接收点击(由守卫吞掉)。 + '.dropdown-context.submenu-inplace.submenu-arming > li.submenu-active > .dropdown-menu > li > a,' @@ -346,6 +358,9 @@ window.context = window.context || (function () { const EDGE_MARGIN = 8; const SUBMENU_GUARD = 350; // 二级菜单「就地替换」后这段毫秒内,触摸点击一律吞掉(防按住误选) const SUBMENU_ARM_MS = 420; // 二级菜单刚弹出的这段毫秒内,先别接受指针指向(防那一下高亮跳色) + const MENU_ARM_MS = 420; // 主菜单(长按弹出)同理:弹出后这段时间内的项不接受指针指向 + const MENU_ARM_TAIL_MS = 260; // 抬手之后再压这么多毫秒 —— 老师原话「手抬起来的那一瞬间留冗余」, + // 与二级菜单那份抬手冗余同一个数(见 armMenuHoverGuard / touchend) function viewportSize() { const de = document.documentElement; @@ -498,6 +513,50 @@ window.context = window.context || (function () { $('.dropdown-context').removeClass('submenu-arming'); } + // 主菜单版(同一套机制,老师要求「复用到长按弹出的主菜单」): + // 主菜单弹在手指底下时,那一项会被浏览器判成 hover 目标 → 弹出瞬间跳高亮,功能和以前一样、就是看着脏。 + // 做法与二级菜单那份一致:挂 .menu-arming(里面的项不接受指针事件 + hover 底色压平), + // 抬手后由 touchend 再续 MENU_ARM_TAIL_MS —— 窗口跟着手势走,不是弹出时定死的一个定时器。 + // 只在「这一发菜单确实是紧随一次触摸打开」时才挂(判据见 openMenu):桌面右键路径因此一行都不动。 + function armMenuHoverGuard(ms) { + $('.dropdown-context:not(.dropdown-context-sub)').addClass('menu-arming'); + clearTimeout(menuArmingTimer); + menuArmingTimer = setTimeout(function () { + // 手指还按着(而且就是按出这个菜单的那次手势)→ 续期:按住多久都不跳色; + // 抬手那条路径会再压 MENU_ARM_TAIL_MS 收尾。 + if (fingerOnScreen && armedMenuGestureId && armedMenuGestureId === touchGestureId) { + armMenuHoverGuard(MENU_ARM_MS); + return; + } + $('.dropdown-context').removeClass('menu-arming'); + }, ms); + } + + function clearMenuHoverGuard() { + clearTimeout(menuArmingTimer); + $('.dropdown-context').removeClass('menu-arming'); + } + + // arming 生效时面板里的项不接受指针事件(这正是"不跳高亮"的关键),于是手指按在菜单项上时 + // e.target 拿到的是面板本身 —— 「按一下就展开二级菜单」这条触摸路径会被吃掉。 + // 这里按触摸坐标把手指底下那一行找回来,只给这一个分支用,保证窗口内的第一次点按与平时一致。 + function rowUnderTouch(e) { + if (! e || ! e.touches || ! e.touches.length) { + return null; + } + + const t = e.touches[0], x = t.clientX, y = t.clientY; + const rows = document.querySelectorAll('.dropdown-context:not(.dropdown-context-sub) > li'); + for (let i = 0; i < rows.length; i++) { + const r = rows[i].getBoundingClientRect(); + if (r.width && r.height && x >= r.left && x <= r.right && y >= r.top && y <= r.bottom) { + return rows[i]; + } + } + + return null; + } + // 打开某个父项的二级菜单:手机惯例是「按下就开」,这样抬手那发合成 click 也有东西可吞。 function openSubmenuFor($li) { $li.siblings('.dropdown-submenu').removeClass('touch-open'); @@ -685,13 +744,23 @@ window.context = window.context || (function () { // 等抬手才处理就晚了(那时 click 已经落在菜单项上了)。 // 抬手:面板若还在「刚弹出」窗口内,把窗口续到抬手之后 —— 按住再久,那一刻也不跳高亮。 ['touchend', 'touchcancel'].forEach(function (evt) { - document.addEventListener(evt, function () { + document.addEventListener(evt, function (e) { + // 屏幕上还有手指按着吗(多指时抬手一根不算)——主菜单 arming 窗口据此决定要不要续期 + fingerOnScreen = !! (e && e.touches && e.touches.length); + // 判据是「这一发手指就是打开二级菜单的那一发」——不能看 .submenu-arming 还在不在: // 按住超过 SUBMENU_ARM_MS 时,那个类早就被定时器摘掉了(踩过)。 if (armedGestureId && armedGestureId === touchGestureId && $('.dropdown-context.submenu-inplace').length) { armSubmenuHoverGuard(260); // 抬手后再压 260ms,抬手那一刻也不会跳色 } + + // 主菜单同理(老师原话:「只要给我手抬起来的那一瞬间留冗余就够」)—— + // 判据同样是「这一发手指就是打开主菜单的那一发」,按住再久也不会在抬手那一刻跳色。 + if (armedMenuGestureId && armedMenuGestureId === touchGestureId + && $('.dropdown-context:not(.dropdown-context-sub)').length) { + armMenuHoverGuard(MENU_ARM_TAIL_MS); + } }, true); }); @@ -700,7 +769,17 @@ window.context = window.context || (function () { // 只看「手指底下这个 a 的直接父级」—— 二级菜单的项嵌在父 li 里面, // 用 closest('li.dropdown-submenu') 会把二级菜单里的项也误判成父行。 - const $a = $(e.target).closest('a'); + let $a = $(e.target).closest('a'); + // 主菜单 arming 窗口里,项不接受指针事件 → 手指按在「复制链接」上时 e.target 只是面板, + // 这一发就按不到那个 a。按坐标把行找回来,只补「按一下就展开二级菜单」这一条路 + // (叶子项靠抬手补发的那发 click,本来就不受影响;就地替换态不兜底,那时的行高/内容都变了)。 + if (! $a.length && $('.dropdown-context.menu-arming').length) { + const row = rowUnderTouch(e); + if (row && row.classList.contains('dropdown-submenu') + && ! row.classList.contains('submenu-active')) { + $a = $(row).children('a'); + } + } const $row = $a.parent(); if ($row.hasClass('dropdown-submenu') && $row.closest('.dropdown-context').length) { if ($row.hasClass('submenu-active')) { @@ -710,13 +789,17 @@ window.context = window.context || (function () { } submenuInplaceAt = Date.now(); armedGestureId = touchGestureId; // 武装到本次手势:这根手指抬起来的那发 click 必吞 - } else if ($('.dropdown-context.submenu-arming').length) { - clearSubmenuHoverGuard(); // 手指挪去点别的东西了:立刻恢复正常 hover/点击 + } else { + // 手指挪去点别的东西了(包括点菜单项、滑菜单):立刻恢复正常 hover/点击。 + // 两套 arming 都清 —— 它们各自只对「按出它来的那一次手势」负责,新手势一律失效。 + clearSubmenuHoverGuard(); + clearMenuHoverGuard(); } }, true); document.addEventListener('touchstart', function (e) { lastTouchAt = Date.now(); + fingerOnScreen = true; // 手指按下:主菜单 arming 窗口可以跟手势续期 if (! menuOnScreen() || isInsideMenu(e.target)) { return; @@ -937,6 +1020,15 @@ window.context = window.context || (function () { menuOpenedAt = Date.now(); menuClosingUntil = 0; + // 刚弹出的主菜单会顶在手指底下:那一项马上会被当成 hover 目标 → 弹出瞬间就跳高亮。 + // 复用二级菜单那套 arming:挂 .menu-arming(项不接受指针事件 + hover 底色压平),抬手后再续 260ms。 + // **只在「这一发菜单确实紧随一次触摸」时才挂**:鼠标右键路径(lastTouchAt 是 0 或很久以前) + // 与上游完全一致 —— 桌面本来没有粘滞 hover,不该为此改动付出任何代价。 + if ((Date.now() - lastTouchAt) < MENU_OPEN_GRACE) { + armedMenuGestureId = touchGestureId; + armMenuHoverGuard(MENU_ARM_MS); + } + logMenuEvent('open trigger=' + (evt ? 'contextmenu' : 'longpress') + ' item=' + describeNode(item) + ' at=' + Math.round(pageX) + ',' + Math.round(pageY)); diff --git a/src/resources/css/common.less b/src/resources/css/common.less index 6d61326..079ae6c 100644 --- a/src/resources/css/common.less +++ b/src/resources/css/common.less @@ -391,6 +391,112 @@ html { .swal2-close:hover { color: var(--lsky-text); } + + // ----------------------------------------------------------------------- + // 弹出/关闭动画:与 x-modal 的 x-transition 对齐 + // x-modal:enter = transition ease-out duration-300(桌面 opacity 0→1 + scale .95→1; + // 手机 opacity 0→1 + translate-y-4→0) + // leave = transition ease-in duration-200(同起止反向) + // sweetalert2 自带的 .swal2-show 是 300ms 线性 scale(.7→1.05→.95→1)、.swal2-hide 是 + // 150ms 线性 scale(1→.5),都没有淡入淡出 → 观感与新弹窗不是一套。 + // 这里直接改这两个类的 animation(sweetalert2 的可见性判断只看「有没有动画 + 动画结束事件」, + // 时长/关键帧换成我们的照样会等动画结束再移除节点,关闭动画因此也一致)。 + // ease-out = cubic-bezier(0,0,.2,1),ease-in = cubic-bezier(.4,0,1,1)(就是 Tailwind 的两个缓动)。 + // ----------------------------------------------------------------------- + .swal2-show { + animation: lsky-swal-in .3s cubic-bezier(0, 0, .2, 1) both; + } + + .swal2-hide { + animation: lsky-swal-out .2s cubic-bezier(.4, 0, 1, 1) both; + } + + @media (max-width: 767px) { + // md 以下(<768px)= x-modal 的「不上缩、整块往上滑 16px」那一档 + .swal2-show { + animation-name: lsky-swal-in-mobile; + } + + .swal2-hide { + animation-name: lsky-swal-out-mobile; + } + } + + // 遮罩跟着弹窗一起淡:容器原本是 `transition: background-color .1s`(关闭时闪一下、跟弹窗不是一套)。 + // 注意只能写 background-color 这一个属性:如果把 backdrop-filter 也列进 transition, + // Chromium 下 background-color 的过渡会整条不生效(实测遮罩 0.5 → 0 是硬切)。 + .swal2-container { + transition: background-color .2s cubic-bezier(.4, 0, 1, 1); + } + + .swal2-container.swal2-backdrop-show, + .swal2-container.swal2-noanimation { + transition-duration: .3s; + transition-timing-function: cubic-bezier(0, 0, .2, 1); + } + + // ----------------------------------------------------------------------- + // 斩断 sweetalert2 对 的动手脚(这是「弹出后背景元素会挪一下」的真因) + // ----------------------------------------------------------------------- + // 它打开时会:① 给 html/body 加 swal2-shown(默认样式里 body.swal2-shown 是 overflow:hidden, + // 滚动条被拿走 → 内容宽度变了 → 整页重排);② 由 JS 往 body.style 写 padding-right 来「抵消」 + // 滚动条宽度(行内样式,CSS 盖不住,只能在调用处用 scrollbarPadding:false 关掉,见 images.blade.php)。 + // 这里负责 ①:权重 (0,3,2) > 它的 (0,3,1),不用 !important。用可见值(body 打开前的原值就是 + // visible)而不是 auto —— auto 会让 body 自己变成滚动容器,弹窗开着的那几秒里 sticky 定位的 + // 参照滚动盒会变。背景能像 x-modal 那样照常滚动,也就不会重排 —— 手机上新弹窗本来也是不锁背景 + // 滚动的,两端行为一致。 + body.swal2-shown:not(.swal2-no-backdrop):not(.swal2-toast-shown) { + overflow: visible; + } +} + +/* sweetalert2 弹出/关闭动画的关键帧(= x-modal 的 enter/leave 起止值) */ +@keyframes lsky-swal-in { + from { + opacity: 0; + transform: scale(.95); + } + + to { + opacity: 1; + transform: scale(1); + } +} + +@keyframes lsky-swal-out { + from { + opacity: 1; + transform: scale(1); + } + + to { + opacity: 0; + transform: scale(.95); + } +} + +@keyframes lsky-swal-in-mobile { + from { + opacity: 0; + transform: translateY(16px); + } + + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes lsky-swal-out-mobile { + from { + opacity: 1; + transform: translateY(0); + } + + to { + opacity: 0; + transform: translateY(16px); + } } /* --------------------------------------------------------------------------- diff --git a/src/resources/js/context-js.js b/src/resources/js/context-js.js index af1f991..03d6555 100644 --- a/src/resources/js/context-js.js +++ b/src/resources/js/context-js.js @@ -208,6 +208,9 @@ window.context = window.context || (function () { let touchGestureId = 0; // 每一次「手指按下」算一个新手势 let armedGestureId = 0; // 二级菜单是在哪一次手势里被打开的 let submenuArmingTimer = null; // 「刚弹出、先别高亮」的收尾定时器 // 因"点了菜单外"而关菜单后,要吞掉的那一发 click + let armedMenuGestureId = 0; // 主菜单是在哪一次手势里被打开的 + let menuArmingTimer = null; // 主菜单「刚弹出、先别高亮」的收尾定时器 + let fingerOnScreen = false; // 屏幕上还有没有手指按着(主菜单 arming 窗口据此跟手势续期,而不是死定时器) let menuStylesInjected = false; let menuGuardInstalled = false; @@ -241,6 +244,15 @@ window.context = window.context || (function () { + ' min-width: 0 !important; margin: 0 !important; padding: 0 !important;' + ' border: 0 !important; border-radius: 0 !important; box-shadow: none !important;' + ' background: transparent !important; }' + // 主菜单版(与下面二级菜单那份同一套 arming 机制,见 armMenuHoverGuard): + // 长按弹出的主菜单同样会顶在手指底下,真机触摸的粘滞 hover 会把手指底下那一项当成 hover 目标 + // → 弹出瞬间那项就跳高亮(老师截图里的「复制图片」)。这里连 li 一起关指针事件: + // 只关 a 的话,主题里的 `.dropdown-submenu:hover > a` 仍会因为 li 被 hover 而给「复制链接」上色。 + // 另外 hover 那套是「底色 + 文字变白」两件一起上(见 context-js.less 的 li>a:hover), + // 所以文字色也一并还原 —— 万一 :hover/:focus 还是粘住了,那一项也保持原样,不会变成白底白字。 + + '.dropdown-context.menu-arming > li,' + + ' .dropdown-context.menu-arming > li > a {' + + ' pointer-events: none !important; background-color: transparent !important; color: inherit !important; }' // 刚弹出的一瞬间:项不接受指针事件(浏览器就不会把它当成 hover 目标 → 不会高亮), // 同时把 hover 底色压平做双保险;不改变任何尺寸/位置,面板本身照旧接收点击(由守卫吞掉)。 + '.dropdown-context.submenu-inplace.submenu-arming > li.submenu-active > .dropdown-menu > li > a,' @@ -346,6 +358,9 @@ window.context = window.context || (function () { const EDGE_MARGIN = 8; const SUBMENU_GUARD = 350; // 二级菜单「就地替换」后这段毫秒内,触摸点击一律吞掉(防按住误选) const SUBMENU_ARM_MS = 420; // 二级菜单刚弹出的这段毫秒内,先别接受指针指向(防那一下高亮跳色) + const MENU_ARM_MS = 420; // 主菜单(长按弹出)同理:弹出后这段时间内的项不接受指针指向 + const MENU_ARM_TAIL_MS = 260; // 抬手之后再压这么多毫秒 —— 老师原话「手抬起来的那一瞬间留冗余」, + // 与二级菜单那份抬手冗余同一个数(见 armMenuHoverGuard / touchend) function viewportSize() { const de = document.documentElement; @@ -498,6 +513,50 @@ window.context = window.context || (function () { $('.dropdown-context').removeClass('submenu-arming'); } + // 主菜单版(同一套机制,老师要求「复用到长按弹出的主菜单」): + // 主菜单弹在手指底下时,那一项会被浏览器判成 hover 目标 → 弹出瞬间跳高亮,功能和以前一样、就是看着脏。 + // 做法与二级菜单那份一致:挂 .menu-arming(里面的项不接受指针事件 + hover 底色压平), + // 抬手后由 touchend 再续 MENU_ARM_TAIL_MS —— 窗口跟着手势走,不是弹出时定死的一个定时器。 + // 只在「这一发菜单确实是紧随一次触摸打开」时才挂(判据见 openMenu):桌面右键路径因此一行都不动。 + function armMenuHoverGuard(ms) { + $('.dropdown-context:not(.dropdown-context-sub)').addClass('menu-arming'); + clearTimeout(menuArmingTimer); + menuArmingTimer = setTimeout(function () { + // 手指还按着(而且就是按出这个菜单的那次手势)→ 续期:按住多久都不跳色; + // 抬手那条路径会再压 MENU_ARM_TAIL_MS 收尾。 + if (fingerOnScreen && armedMenuGestureId && armedMenuGestureId === touchGestureId) { + armMenuHoverGuard(MENU_ARM_MS); + return; + } + $('.dropdown-context').removeClass('menu-arming'); + }, ms); + } + + function clearMenuHoverGuard() { + clearTimeout(menuArmingTimer); + $('.dropdown-context').removeClass('menu-arming'); + } + + // arming 生效时面板里的项不接受指针事件(这正是"不跳高亮"的关键),于是手指按在菜单项上时 + // e.target 拿到的是面板本身 —— 「按一下就展开二级菜单」这条触摸路径会被吃掉。 + // 这里按触摸坐标把手指底下那一行找回来,只给这一个分支用,保证窗口内的第一次点按与平时一致。 + function rowUnderTouch(e) { + if (! e || ! e.touches || ! e.touches.length) { + return null; + } + + const t = e.touches[0], x = t.clientX, y = t.clientY; + const rows = document.querySelectorAll('.dropdown-context:not(.dropdown-context-sub) > li'); + for (let i = 0; i < rows.length; i++) { + const r = rows[i].getBoundingClientRect(); + if (r.width && r.height && x >= r.left && x <= r.right && y >= r.top && y <= r.bottom) { + return rows[i]; + } + } + + return null; + } + // 打开某个父项的二级菜单:手机惯例是「按下就开」,这样抬手那发合成 click 也有东西可吞。 function openSubmenuFor($li) { $li.siblings('.dropdown-submenu').removeClass('touch-open'); @@ -685,13 +744,23 @@ window.context = window.context || (function () { // 等抬手才处理就晚了(那时 click 已经落在菜单项上了)。 // 抬手:面板若还在「刚弹出」窗口内,把窗口续到抬手之后 —— 按住再久,那一刻也不跳高亮。 ['touchend', 'touchcancel'].forEach(function (evt) { - document.addEventListener(evt, function () { + document.addEventListener(evt, function (e) { + // 屏幕上还有手指按着吗(多指时抬手一根不算)——主菜单 arming 窗口据此决定要不要续期 + fingerOnScreen = !! (e && e.touches && e.touches.length); + // 判据是「这一发手指就是打开二级菜单的那一发」——不能看 .submenu-arming 还在不在: // 按住超过 SUBMENU_ARM_MS 时,那个类早就被定时器摘掉了(踩过)。 if (armedGestureId && armedGestureId === touchGestureId && $('.dropdown-context.submenu-inplace').length) { armSubmenuHoverGuard(260); // 抬手后再压 260ms,抬手那一刻也不会跳色 } + + // 主菜单同理(老师原话:「只要给我手抬起来的那一瞬间留冗余就够」)—— + // 判据同样是「这一发手指就是打开主菜单的那一发」,按住再久也不会在抬手那一刻跳色。 + if (armedMenuGestureId && armedMenuGestureId === touchGestureId + && $('.dropdown-context:not(.dropdown-context-sub)').length) { + armMenuHoverGuard(MENU_ARM_TAIL_MS); + } }, true); }); @@ -700,7 +769,17 @@ window.context = window.context || (function () { // 只看「手指底下这个 a 的直接父级」—— 二级菜单的项嵌在父 li 里面, // 用 closest('li.dropdown-submenu') 会把二级菜单里的项也误判成父行。 - const $a = $(e.target).closest('a'); + let $a = $(e.target).closest('a'); + // 主菜单 arming 窗口里,项不接受指针事件 → 手指按在「复制链接」上时 e.target 只是面板, + // 这一发就按不到那个 a。按坐标把行找回来,只补「按一下就展开二级菜单」这一条路 + // (叶子项靠抬手补发的那发 click,本来就不受影响;就地替换态不兜底,那时的行高/内容都变了)。 + if (! $a.length && $('.dropdown-context.menu-arming').length) { + const row = rowUnderTouch(e); + if (row && row.classList.contains('dropdown-submenu') + && ! row.classList.contains('submenu-active')) { + $a = $(row).children('a'); + } + } const $row = $a.parent(); if ($row.hasClass('dropdown-submenu') && $row.closest('.dropdown-context').length) { if ($row.hasClass('submenu-active')) { @@ -710,13 +789,17 @@ window.context = window.context || (function () { } submenuInplaceAt = Date.now(); armedGestureId = touchGestureId; // 武装到本次手势:这根手指抬起来的那发 click 必吞 - } else if ($('.dropdown-context.submenu-arming').length) { - clearSubmenuHoverGuard(); // 手指挪去点别的东西了:立刻恢复正常 hover/点击 + } else { + // 手指挪去点别的东西了(包括点菜单项、滑菜单):立刻恢复正常 hover/点击。 + // 两套 arming 都清 —— 它们各自只对「按出它来的那一次手势」负责,新手势一律失效。 + clearSubmenuHoverGuard(); + clearMenuHoverGuard(); } }, true); document.addEventListener('touchstart', function (e) { lastTouchAt = Date.now(); + fingerOnScreen = true; // 手指按下:主菜单 arming 窗口可以跟手势续期 if (! menuOnScreen() || isInsideMenu(e.target)) { return; @@ -937,6 +1020,15 @@ window.context = window.context || (function () { menuOpenedAt = Date.now(); menuClosingUntil = 0; + // 刚弹出的主菜单会顶在手指底下:那一项马上会被当成 hover 目标 → 弹出瞬间就跳高亮。 + // 复用二级菜单那套 arming:挂 .menu-arming(项不接受指针事件 + hover 底色压平),抬手后再续 260ms。 + // **只在「这一发菜单确实紧随一次触摸」时才挂**:鼠标右键路径(lastTouchAt 是 0 或很久以前) + // 与上游完全一致 —— 桌面本来没有粘滞 hover,不该为此改动付出任何代价。 + if ((Date.now() - lastTouchAt) < MENU_OPEN_GRACE) { + armedMenuGestureId = touchGestureId; + armMenuHoverGuard(MENU_ARM_MS); + } + logMenuEvent('open trigger=' + (evt ? 'contextmenu' : 'longpress') + ' item=' + describeNode(item) + ' at=' + Math.round(pageX) + ',' + Math.round(pageY)); diff --git a/src/resources/views/components/modal.blade.php b/src/resources/views/components/modal.blade.php index bfd5853..0a8dd88 100644 --- a/src/resources/views/components/modal.blade.php +++ b/src/resources/views/components/modal.blade.php @@ -1,12 +1,17 @@ @props(['id' => 'modal']) -{{-- 高度一律用动态视口单位 dvh:手机地址栏可见时 100vh 比"看得见的区域"高, - 弹窗会伸出屏幕外(下滑就能看到底下一块空白)。100dvh 跟着地址栏收起/展开实时变, - 两种情形都刚好铺满。--}} -
    merge(['id' => $id, 'class' => "fixed z-10 inset-0 h-[100dvh] overflow-y-auto"]) }} role="dialog" aria-modal="true" x-data x-cloak x-show="$store.modal.isOpen('{{ $id }}')"> - {{-- min-h-screen(100vh) 保留在前面当兜底:浏览器不认识 dvh 时整条声明作废,退回 100vh(旧行为)。 - 桌面上 100dvh === 100vh,所以这一行对桌面零影响。 --}} -
    +{{-- 高度一律用动态视口单位 dvh:手机地址栏可见时 100vh 比"看得见的区域"高,越界那一截就是底部空带。 + 100dvh 跟着地址栏收起/展开实时变,两种情形都刚好铺满。--}} +{{-- overscroll-contain(= overscroll-behavior:contain)加在弹窗自己的滚动容器上:弹窗里滚到尽头后 + 不再把滚动"接力"给背后的页面。安卓上滚动一旦传给页面,地址栏会跟着收起、可视视口变高, + 弹窗底部就空出一块——这条"接力"才是那块空带的入口。 + 触发链:弹窗内滚到底 → 滚动接力到页面 → 地址栏收起/视口变高 → 弹窗底部露出空带(已实测复现)。--}} +
    merge(['id' => $id, 'class' => "fixed z-10 inset-0 h-[100dvh] overflow-y-auto overscroll-contain"]) }} role="dialog" aria-modal="true" x-data x-cloak x-show="$store.modal.isOpen('{{ $id }}')"> + {{-- 内层 min-height 用「vh 声明在前、dvh 声明在后」的内联写法:同一对大括号里后者胜, + 与 Tailwind 的生成顺序无关(两个工具类同名属性谁赢取决于样式表顺序,不稳)。 + 支持 dvh 的浏览器用 dvh(= 真实可见视口);不认识的旧浏览器整条 dvh 声明作废, + 自动退回紧邻的 100vh(= 旧行为)。桌面上 100dvh === 100vh,函数等价。 --}} +
    - + {{-- 桌面端垂直居中的撑高符同样走「vh 在前、dvh 在后」,链路上不再留 md:h-screen(100vh)。 --}} +
    @@ -21,3 +36,24 @@ + +{{-- 头像的延迟加载:等页面 load 完(其它资源都好了)再把 data-avatar-src 接到 img.src 上。 + 不引入任何依赖;header / welcome 都会 include 本文件,重复 include 也安全(一个 flag + querySelectorAll 幂等)。 --}} + diff --git a/src/resources/views/user/images.blade.php b/src/resources/views/user/images.blade.php index 86be97b..a94e34a 100644 --- a/src/resources/views/user/images.blade.php +++ b/src/resources/views/user/images.blade.php @@ -469,6 +469,12 @@ classes: ['dragselect'], $albums.off('click', '.delete').on('click', '.delete', function (e) { e.stopPropagation(); Swal.fire({ + // 与新弹窗(x-modal)一套:不动背景页面。 + // heightAuto 关掉 html/body 上的 swal2-height-auto;scrollbarPadding 关掉 + // sweetalert2 往 body 写 padding-right 的那一步(它按「滚动条宽度」补内边距, + // 手机上明明没有占位滚动条,补 8px 却真的会把内容挤窄 → 工具栏换行、整页下移)。 + heightAuto: false, + scrollbarPadding: false, title: '确认删除该相册?', text: "删除后相册中的图片将会被移出。", icon: 'warning', @@ -746,6 +752,10 @@ classes: ['dragselect'], }, permission() { Swal.fire({ + // 与新弹窗(x-modal)一套:不动背景页面(heightAuto 去 swal2-height-auto, + // scrollbarPadding 去 body 的 padding-right 注入 —— 否则整页会有细微位移) + heightAuto: false, + scrollbarPadding: false, title: '选择一个权限', text: '选择公开后所有用户都能看到这张图片', input: 'select', @@ -785,6 +795,10 @@ classes: ['dragselect'], rename(e) { let item = $(e).data('json'); Swal.fire({ + // 与新弹窗(x-modal)一套:不动背景页面(heightAuto 去 swal2-height-auto, + // scrollbarPadding 去 body 的 padding-right 注入 —— 否则整页会有细微位移) + heightAuto: false, + scrollbarPadding: false, title: '请输入图片名称', input: 'text', inputValue: item.filename, @@ -821,6 +835,10 @@ classes: ['dragselect'], }, delete() { Swal.fire({ + // 与新弹窗(x-modal)一套:不动背景页面(heightAuto 去 swal2-height-auto, + // scrollbarPadding 去 body 的 padding-right 注入 —— 否则整页会有细微位移) + heightAuto: false, + scrollbarPadding: false, title: '确认要删除选中的图片?', text: "删除后不可恢复,记录和文件同时删除", icon: 'warning',