:root {
    margin-left: 0 !important;
    margin-right: 0 !important;
}

.table thead th {
    text-transform: none;
}

.required-label::before {
    content: "*";
    color: red;
    margin-right: 5px;
}

.min-w-0 {
    min-width: 0 !important;
}

/* select2 默认按初始化时源 select 元素的渲染宽度写死一个 px 值，
   缩小窗口后不会跟着收缩，导致内容溢出；这里兜底限制不超过父容器宽度 */
.select2-container {
    max-width: 100%;
}

/* select2 默认主题的选择框高度写死 28px（内部 line-height 也是写死的），
   比 Tabler 的 .form-control 矮一截——大多数表单里每个字段各占一行看不
   出来，但只要跟别的输入框摆在同一行（比如产品详情页"BOM 明细"面板的
   物料选择 + 单位用量）就会明显对不齐。这里改成跟 .form-control 同一套
   高度公式（见 tabler.min.css 的 .form-control min-height），选择框、
   内部文字、下拉箭头三处都要改，不然文字和箭头没法跟着新高度居中 */
.select2-container--default .select2-selection--single {
    height: calc(1.25rem + 1.125rem + calc(var(--tblr-border-width) * 2));
    /* __clear 用 top:50% + translateY(-50%) 定位，需要这里是定位祖先 */
    position: relative;
}

.select2-container--default .select2-selection--single .select2-selection__rendered {
    line-height: calc(1.25rem + 1.125rem);
}

.select2-container--default .select2-selection--single .select2-selection__arrow {
    height: calc(1.25rem + 1.125rem + calc(var(--tblr-border-width) * 2));
}

/* allowClear 的清除按钮（"×"）默认是 float:right 贴在选择框顶部，靠自己
   height 跟 line-height 相等来撑出"看起来居中"的效果——choice 框变高之后
   这个假居中就对不上了。改用绝对定位 + translateY(-50%) 直接按选择框的
   实际高度取中点，不管选择框多高都不会跑偏（下拉箭头本来就是这么定位的） */
.select2-container--default .select2-selection--single .select2-selection__clear {
    position: absolute;
    top: 50%;
    right: 20px;
    float: none;
    height: auto;
    line-height: normal;
    margin-right: 0;
    transform: translateY(-50%);
}

/* 多选框（比如排产表单的"人员"）用的是 select2-selection--multiple，
   跟单选框是完全不同的一套 DOM/CSS class，上面对 --single 的高度改动
   套不到它头上——默认 min-height 只有 32px，比 .form-control 矮，跟同一行
   的其它输入框、提交按钮对不齐。

   前两次改法都往 display/flex 上动了手（外层 flex 居中、或者连内层的
   select2-selection__rendered 也一起接管），结果都出了问题——尤其是内层
   一旦被强制 display:flex，会打乱 select2 自己对搜索框（打字过滤用的
   <input>，插入位置是它自己 JS 算好挂进 DOM 的）的定位逻辑。这次完全不碰
   display/flex，只用 select2 原生就在用的"padding 撑间距"这套机制：
   选中项自带 margin-top:5px，容器原生带 padding-bottom:5px，一高一矮基本
   对称，只是这套间距是按原来固定 32px 设计的。改高 min-height 之后，
   只需要把多出来的高度，用同样的 padding-top 补在上面，让新增的空间
   一半在顶一半在底，不改变 select2 原有的排布方式，风险最低 */
.select2-container--default .select2-selection--multiple {
    min-height: calc(1.25rem + 1.125rem + calc(var(--tblr-border-width) * 2));
    padding-top: 4px;
}

/* tabler.min.css 里 .steps 这个类名被定义了两遍：一遍是横向 flex 进度条
   （订单详情页"订单进度"用的就是这个），另一遍是不相关的竖排时间线组件，
   后者的 border-left/margin-left/padding-left 都覆盖到了前者头上——横向
   进度条左边多出一根没意义的竖线，还只在左边多出 1rem 外边距 + 2rem 内
   边距、右边没有，窄屏下尤其明显（可用宽度小，这块固定偏移占比更大），
   看着就是左右留白不对称。这里把这几个属性都清掉 */
.steps {
    border-left: none;
    margin-left: 0;
    padding-left: 0;
}

.navbar-brand-logo {
    object-fit: contain;
}

.list-body-row {
    display: flex;
    width: 100%;
    gap: 1rem;
    min-width: 0;
}

.list-main-col {
    flex: 1 1 auto;
    min-width: 0;
}

.filter-sidebar-col {
    width: 220px;
    flex-shrink: 0;
}

.filter-lookup-option {
    display: block;
}

/* 选中态那一项内部要左右排（标签 + 清除按钮），单独用自己的类设置 display，
   不用 Bootstrap 的 d-flex（带 !important，narrow 断点下会盖掉下面的
   inline-flex，导致选中项撑满一整行、把其它选项挤到下一行错位） */
.filter-lookup-option-active {
    display: flex;
}

/* 窄屏下筛选栏跟列表并排会挤压表格，改成上下堆叠，筛选栏占满宽度 */
@media (max-width: 991.98px) {
    .list-body-row {
        flex-direction: column;
    }

    .filter-sidebar-col {
        width: 100%;
        /* DOM 里筛选栏在表格后面，竖排堆叠时默认会排在表格下面；
           用 order 只调视觉顺序、不用改 DOM，让筛选栏排到表格上面 */
        order: -1;
    }

    /* 筛选栏占满宽度后横向空间足够，选项从竖排列表改成横向流式排列，
       避免选项一个个往下堆、把页面撑得很高 */
    .filter-lookup-options {
        display: flex;
        flex-wrap: wrap;
        gap: 0.375rem;
    }

    .filter-lookup-option,
    .filter-lookup-option-active {
        display: inline-flex;
        width: auto;
    }
}

/* 列表页操作图标：原来紧挨在一起、点击区域只有图标本身大小，容易误触。
   这里放大点击区域并拉开间距，删除用红色跟查看/编辑区分开 */
.row-actions {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
}

.row-action-btn,
.row-action-btn:hover {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2rem;
    height: 2rem;
    border-radius: 6px;
    font-size: 1rem;
    text-decoration: none;
}

.row-action-btn-view {
    background: rgba(var(--tblr-primary-rgb), 0.1);
    color: var(--tblr-primary);
}

.row-action-btn-view:hover {
    background: var(--tblr-primary);
    color: #fff;
}

.row-action-btn-edit {
    background: rgba(var(--tblr-warning-rgb), 0.15);
    color: var(--tblr-warning);
}

.row-action-btn-edit:hover {
    background: var(--tblr-warning);
    color: #fff;
}

.row-action-btn-danger {
    background: rgba(var(--tblr-danger-rgb), 0.1);
    color: rgba(var(--tblr-danger-rgb), 0.6);
}

.row-action-btn-danger:hover {
    background: var(--tblr-danger);
    color: #fff;
}

.admin-toast {
    width: auto;
}

.filter-form {
    gap: 8px;
}

.filter-form-actions {
    gap: 8px;
}

/* 折叠状态下（窄屏）让菜单展开按钮始终固定在右侧、垂直居中。
   展开的菜单用绝对定位挂在按钮所在行下方，脱离文档流，
   不会撑高按钮所在的这一行，按钮/logo 的位置和高度
   不会因为菜单展开收起而跟着变化或位移。 */
@media (max-width: 991.98px) {
    .navbar-vertical .container-fluid {
        position: relative;
        flex-wrap: wrap;
        align-items: center;
    }

    .navbar-vertical #navbar-menu {
        display: flex;
        align-items: center;
        flex: 1 1 auto;
    }

    .navbar-vertical #navbar-menu .navbar-toggler {
        margin-left: auto;
        align-self: center;
    }

    .navbar-vertical #navbar-menu .navbar-collapse {
        position: absolute;
        top: 100%;
        right: 0;
        left: auto;
        width: max-content;
        min-width: 220px;
        max-width: min(90vw, 360px);
        z-index: 1030;
        background: var(--tblr-navbar-bg, #1a2234);
        max-height: calc(100vh - 100%);
        overflow-y: auto;
    }
}

/* 隐藏侧边菜单栏的滚动条，保留滚动功能 */
.navbar-vertical {
    scrollbar-width: none;
    -ms-overflow-style: none;
}

.navbar-vertical::-webkit-scrollbar {
    display: none;
}

/* 二级菜单（分类）用 Bootstrap Collapse 实现折叠/展开，
   始终在正常文档流内变化高度，不会被定位成浮层撑宽菜单、挤动一级菜单。 */
.navbar-vertical .category-collapse-caret {
    font-size: 0.7rem;
    color: var(--tblr-nav-link-color, inherit);
    transition: transform 0.2s ease;
}

.navbar-vertical [aria-expanded="true"] .category-collapse-caret {
    transform: rotate(180deg);
}

/* 宽屏（>=992px）下侧边栏收起为纯图标模式的折叠按钮，状态记忆在 localStorage。
   折叠按钮与 logo 同排显示，避免单独占一行。
   固定行高：折叠后 logo 整行隐藏，避免这一行变矮导致下方菜单跟着上下位移。
   width:100% 只在宽屏（container-fluid 纵向排列）时需要，窄屏下 container-fluid
   是横向 flex-wrap 布局，让这一行占满宽度会把 navbar-toggler 挤到第二行去。 */
.navbar-vertical .navbar-brand-row {
    min-height: 3.5rem;
    padding-left: 0.75rem;
    padding-right: 0.75rem;
}

@media (min-width: 992px) {
    .navbar-vertical .navbar-brand-row {
        width: 100%;
    }
}

.navbar-vertical .navbar-brand-row .navbar-brand {
    min-width: 0;
    justify-content: flex-start;
}

.navbar-vertical .navbar-brand-title {
    margin: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* navbar-brand-title 外层的 <a> 是裸标签、没挂 .navbar-brand 类（那个类在
   它外面的 <h1> 上），tabler.min.css 全局的 a:hover{text-decoration:underline}
   会盖到它头上，鼠标移上去标题会带下划线，这里单独去掉 */
.navbar-vertical .navbar-brand-row a:hover {
    text-decoration: none;
}

.sidebar-collapse-toggle {
    flex-shrink: 0;
    margin-left: 0.25rem;
}

.sidebar-collapse-toggle i {
    transition: transform 0.2s ease;
}

body.sidebar-collapsed .sidebar-collapse-toggle i {
    transform: rotate(180deg);
}

@media (min-width: 992px) {
    body.sidebar-collapsed .navbar-vertical {
        width: 4.25rem !important;
    }

    body.sidebar-collapsed .navbar-vertical ~ .page-wrapper {
        margin-left: 4.25rem !important;
    }

    body.sidebar-collapsed .navbar-vertical .container-fluid {
        padding-left: 0.5rem;
        padding-right: 0.5rem;
    }

    body.sidebar-collapsed .navbar-vertical .navbar-brand,
    body.sidebar-collapsed .navbar-vertical .nav-link-title,
    body.sidebar-collapsed .navbar-vertical .category-collapse-caret {
        display: none !important;
    }

    /* 折叠后第一行只保留折叠按钮，居中显示；行内边距清零，避免和
       container-fluid 的内边距叠加后把窄栏里的按钮挤得放不下 */
    body.sidebar-collapsed .navbar-vertical .navbar-brand-row {
        justify-content: center;
        padding-left: 0;
        padding-right: 0;
    }

    body.sidebar-collapsed .navbar-vertical .sidebar-collapse-toggle {
        margin-left: 0;
    }

    body.sidebar-collapsed .navbar-vertical .nav-link {
        justify-content: center;
        padding-left: 0;
        padding-right: 0;
    }

    body.sidebar-collapsed .navbar-vertical .nav-link.ps-lg-4 {
        padding-left: 0 !important;
    }
}

/* tabler.min.css 把 .form-check-label 写死成 display:block——label 撑满
   整个 .form-check 容器的宽度，点击文字右边那一大片空白区域也会触发
   `for` 属性联动到复选框（比如"新建物料"这类开关，容器一整行都能点，
   文字后面明明是空白却点了会勾选/取消勾选，容易误触）。改回
   inline-block，label 的可点击范围收缩回文字实际占据的宽度，复选框
   本身不受影响（它是独立元素，不在这条规则里） */
.form-check-label {
    display: inline-block;
}

/* 右上角工具栏（刷新/通知/用户菜单）默认跟着页面内容一起滚动走了，往下
   滑动到长列表/详情页底部之后想点这几个功能得先滚回顶部——改成 sticky
   固定在顶部，上下滑动时始终可见。用 sticky 不用 fixed：还是占着自己在
   文档流里的位置，不用另外给下面的内容加 padding-top 补位。

   z-index 一开始写的是 1030，跟移动端"折叠侧边栏展开的导航下拉菜单"
   （见上面 .navbar-vertical #navbar-menu .navbar-collapse 那条规则）撞了
   同一个值——两个都是 position 元素、z-index 又相等，层叠顺序退化成按
   DOM 先后比：这个工具栏在 .page-wrapper 里，DOM 顺序排在 aside.navbar-
   vertical 后面，打平之后反而是工具栏盖住导航菜单，移动端点开汉堡菜单
   完全展开不出来（被这个工具栏挡住）。改成 1020（Bootstrap sticky-top
   工具类本来的默认值），比导航菜单的 1030 低一档，展开导航菜单时能正常
   盖在工具栏上面；工具栏自己下拉出来的通知/用户菜单是它内部子元素，不受
   这个外层 z-index 影响，该盖住页面正文照样盖得住，不会因为这次调低受
   影响。背景色/下边框显式写死，不依赖 Tabler 有没有给这个次级 navbar 配
   不透明背景——不写的话固定住之后，滚动到它下面的内容会从缝隙透上来 */
.topbar-sticky {
    position: sticky;
    top: 0;
    z-index: 1020;
    background-color: var(--tblr-bg-surface, #fff);
    border-bottom: 1px solid var(--tblr-border-color, #e6e7e9);
}

/* 详情页里"卡片导览"（见 sqladmin/details.html 里 order/purchase-order
   两个分支下的 <nav class="details-quick-nav">）：浮在视口右边、垂直居中
   的一竖排短横线，每条线代表一张卡片，参考的是常见博客/文档站那种"阅读
   进度刻度"——平时收起来
   只是几条线，不占地方；鼠标 hover 或者触屏点一下，线段展开成完整的卡片
   名称列表可以直接点。position:fixed + top:50% + translateY(-50%) 让它
   贴着右边缘垂直居中，不跟随滚动位置变化，也不占页面自己的排版空间。

   窄屏（<1200px）下直接隐藏——这个宽度以下页面主体内容本来就没有多余的
   右侧留白，浮层放上去大概率会盖住卡片右边的操作按钮，不如干脆不显示，
   这类屏幕本来也更适合直接用手指/滚轮慢慢翻。

   align-items:flex-end 让每一条线/展开后的名称都贴右边缘对齐——收起时
   是短线贴右边，展开时名称从右边缘向左"长出来"，不会因为文字变长而
   把线段的右边缘位置也带偏 */
.details-quick-nav {
    position: fixed;
    top: 50%;
    right: 12px;
    transform: translateY(-50%);
    z-index: 1010;
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 7px;
    max-height: calc(100vh - 48px);
    overflow-y: auto;
    overflow-x: hidden;
    padding: 4px;
    /* 收起态显式给宽度（而不是让 position:fixed 元素自己走 shrink-to-fit）
       ——之前没写 width，容器的实际可 hover 范围由浏览器按"内容自然宽度"
       撑出来，跟视觉上那几条 22px 的短线不一定完全对得上，导致这个元素
       的真实命中范围可能比肉眼看到的短线宽（比如往左多出一截贴在卡片右
       边缘上方），于是"鼠标在卡片上 hover"实际上还是落在这个容器的命中
       范围里，main.js 里绑的 mouseenter 会被错误触发；反过来，鼠标离开
       短线但还没真正移出这块隐形范围时 mouseleave 也不会触发，就是"移到
       卡片上没自动收起"。给收起态一个刚好包住短线的固定宽度，展开态另开
       一个固定宽度（见 .expanded 那条规则），两个状态的命中范围都变得
       明确、可预期，不再依赖浏览器的自动宽度计算 */
    width: 30px;
    transition: width .15s ease;
}

@media (max-width: 1199.98px) {
    .details-quick-nav {
        display: none;
    }
}

/* 收起态：一条短横线，颜色是唯一的信息载体——当前卡片对应的那条是黑色，
   其它是灰色（见下面 .active）。宽高写死+overflow:hidden，卡片名称文字
   还在 <a> 里（不删掉，屏幕阅读器/展开态都要用），只是被裁掉只剩这一小条
   看不出文字来 */
.details-quick-nav a {
    display: block;
    width: 22px;
    height: 3px;
    border-radius: 2px;
    background-color: var(--tblr-border-color, #c5cbd6);
    color: transparent;
    text-decoration: none;
    white-space: nowrap;
    overflow: hidden;
    transition: width .15s ease, height .15s ease, padding .15s ease, background-color .15s ease, color .15s ease;
}

/* 当前滚动到的是哪张卡片——main.js 用 IntersectionObserver 判断，命中的
   那条链接加这个类，不是靠计算滚动像素反推，滚动性能更好也更准。收起态
   下用黑色区分（其它线段是灰色），展开态下另外再加深背景色（见下面），
   两种状态各自的"当前位置"提示方式不一样，因为展开之后线段本身的颜色差异
   会被文字撑开的背景挡住看不出来 */
.details-quick-nav a.active {
    background-color: #111;
}

/* 展开态：只认 .expanded 这一个类，不用 CSS :hover——试过用 :hover 直接
   控制展开，实测"鼠标从导览移到卡片上"这一下经常不收起、有时候鼠标明明
   在卡片上也会触发展开。根源是容器（.details-quick-nav）本身没写死
   宽度，展开前后都是靠浏览器按内容自动撑出宽度，实际可 hover 的范围跟
   肉眼看到的短线/文字条不一定对得上，容易往卡片那一侧多出一截摸不着
   边界的隐形区域。现在容器收起态、展开态都各自写死了 width（分别见上面
   .details-quick-nav 和下面 .expanded 容器本身那条规则），可 hover 的
   范围变成完全确定的矩形；展开/收起改用 main.js 的 mouseenter/mouseleave
   显式切这个类，鼠标一移出容器的（现在是精确已知的）边界就必定收起。
   触屏设备上没有 hover 概念，main.js 另外用 touchstart 切这同一个类。 */
.details-quick-nav.expanded {
    width: 200px;
}

.details-quick-nav.expanded a {
    width: auto;
    height: auto;
    padding: 5px 10px;
    border-radius: 4px;
    background-color: var(--tblr-bg-surface, #fff);
    color: var(--tblr-secondary, #6c757d);
    font-size: 0.8rem;
    box-shadow: var(--tblr-shadow-sm, 0 1px 2px rgba(0, 0, 0, .08));
}

/* 展开态下"当前是哪张卡片"改用加深背景色 + 强调字色来提示，不再是线段
   颜色（原因见上面 .active 的注释） */
.details-quick-nav.expanded a.active {
    color: var(--tblr-primary, #206bc4);
    background-color: var(--tblr-bg-surface-secondary, #e4e8f0);
    font-weight: 600;
}

/* 详情页里每一张能被锚点跳转定位到的卡片都挂这个类——"XX进度"面板推进
   状态后带锚点跳回来（app/admin/views/order.py/purchase_order.py 的
   advance_status），或者上面 .details-quick-nav 导览点过来，两种情况都
   一样：浏览器原生锚点定位只会把目标元素的顶边对齐到视口最顶部，不知道
   上面叠着 .topbar-sticky，定位完卡片顶上一截会被挡住。scroll-margin-top
   让浏览器定位时提前多留出这么多空间，效果上是"往下多滚一点"，正好空出
   工具栏占的高度——导览本身是 position:fixed 浮在右侧，不占页面顶部的
   排版空间，这里不用再像横排版本那样额外扣一层导览自己的高度。

   --topbar-height 是 main.js 在页面加载（和窗口 resize）时量出来的工具栏
   实际渲染高度，不是写死猜的数字——写死过一次（76px），估多了，滚动之后
   反而空出一截、把上一张卡片的底边露出来，观感不好；量出来的真实高度
   不用再猜，工具栏样式以后怎么改都不用回来改这个数字。8px 是留一点点
   呼吸空间，不贴死 */
.details-panel {
    scroll-margin-top: calc(var(--topbar-height, 60px) + 8px);
}

/* 工艺路线面板拖动排序的手柄（见 _route_steps_panel.html + main.js 里
   #route-step-tbody 相关处理）。cursor:grab 只挂在手柄图标本身，不是整行——
   只有从手柄按下才会触发拖动，行里其它地方（行内编辑输入框、按钮）保持
   正常的点击/文本光标，不受影响。 */
.route-step-drag-handle {
    cursor: grab;
    color: var(--tblr-secondary, #6c7a91);
    padding: 0 .5rem;
}

.route-step-drag-handle:active {
    cursor: grabbing;
}

/* 拖动中的那一行降低透明度，给个"正在被拿起"的视觉反馈——不用另外画一个
   跟手的拖拽影子/占位条，原生 HTML5 拖放自带的半透明拖拽预览已经够用，
   这里只处理原始行本身在文档流里的样子 */
.route-step-dragging {
    opacity: .4;
}
