/**
 * 插件市场视图（仅桌面端注入 DOM）
 *
 * 容器 #pluginMarketView 与 .knowledge-area、#knowledgeGraphView、#knowledgeStatsView
 * 四者互斥显隐（见 NavigationManager.setViewMode），显隐口径与 knowledge-stats.css 完全同构——
 * 那边的两条硬约束这里一条都不能省：
 *   1. 显隐只能靠 body 类，不能写内联 display（mobile.css 里 .knowledge-area 带 !important）；
 *   2. 高度必须写死成视口高度，不能用 100%（父容器是 flex:1 的项，给不出百分比基准）。
 */

.plugin-market-view {
    display: none;
    flex-direction: column;
    height: calc(100dvh - 64px - 30px);
    min-height: 0;
    background: var(--bg-primary);
}

@media (display-mode: standalone) {
    .plugin-market-view {
        height: calc(100dvh - 64px);
    }
}

body.pwa-mode .plugin-market-view {
    height: calc(100dvh - 64px);
}

body.kb-plugins-mode .plugin-market-view {
    display: flex !important;
}

body.kb-plugins-mode .knowledge-area {
    display: none !important;
}

/* 移动端的搜索框与分页条是 .knowledge-area 的兄弟节点，藏了列表它们还会留在上下两端 */
body.kb-plugins-mode .mobile-search-bar,
body.kb-plugins-mode .mobile-pagination-bar {
    display: none !important;
}

/* 侧栏里「插件」是模板的子项，靠缩进表达从属关系 */
.btn-nav-sub {
    padding-left: 1.25rem;
}

/* ---------------- 工具条 ---------------- */

.plugin-market-toolbar {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 16px 20px 8px;
    flex-shrink: 0;
}

/* 搜索框直接复用 .search-container/.search-input-wrapper 那一套，与知识库列表一致 */
.plugin-market-toolbar .search-container {
    flex: 1 1 auto;
    max-width: 420px;
}

/* 从本地文件安装。放在搜索框右边、与它同高，属于「次要但常在」的入口 */
.plugin-market-local {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    flex-shrink: 0;
    padding: 8px 14px;
    border: 1px solid var(--border-color, #e5e7eb);
    border-radius: 8px;
    background: transparent;
    color: var(--text-secondary, #4b5563);
    font-size: 0.8125rem;
    cursor: pointer;
    transition: background-color 0.2s ease, border-color 0.2s ease, color 0.2s ease;
}

.plugin-market-local:hover {
    border-color: var(--kb-accent, #2563eb);
    color: var(--kb-accent, #2563eb);
    background: rgba(37, 99, 235, 0.06);
}

[data-theme="dark"] .plugin-market-local,
html.dark .plugin-market-local {
    border-color: #374151;
}

/* ---------------- 分类 ---------------- */

.plugin-market-categories {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    padding: 8px 20px 12px;
    flex-shrink: 0;
}

.plugin-market-category {
    /* inline-flex 而不是默认的 inline-block：模板里文字与计数 <span> 之间有换行空白，
       inline-block 下那段空白会撑出额外行盒，文字看起来贴着上边框 */
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 5px 12px;
    border: 1px solid var(--border-color, #e5e7eb);
    border-radius: 999px;
    background: transparent;
    color: var(--text-secondary, #6b7280);
    font-size: 0.8125rem;
    line-height: 1;
    cursor: pointer;
}

.plugin-market-category.active {
    border-color: var(--kb-accent, #2563eb);
    background: rgba(37, 99, 235, 0.08);
    color: var(--kb-accent, #2563eb);
}

.plugin-market-category-count {
    opacity: 0.7;
}

/* ---------------- 卡片网格 ---------------- */

.plugin-market-viewport {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    padding: 0 20px 24px;
}

.plugin-market-grid {
    display: grid;
    /* 列最窄 320px：卡片内宽 = 320 - 2*14 padding - 2 边框 = 290px，
       「重新授权 / 配置 / 停用 / 卸载」四个键约需 258px 才能排在一行；260px 时「卸载」会被挤到第二行 */
    grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
    gap: 12px;
}

.plugin-card {
    display: flex;
    flex-direction: column;
    gap: 8px;
    /* 网格项的 min-width 默认是 auto，长名字会把整列撑宽而不是省略 */
    min-width: 0;
    padding: 14px;
    border: 1px solid var(--border-color, #e5e7eb);
    border-radius: 10px;
    background: var(--bg-secondary, #ffffff);
    cursor: pointer;
    transition: border-color 0.2s ease, box-shadow 0.2s ease;
}

.plugin-card:hover,
.plugin-card:focus-visible {
    border-color: var(--kb-accent, #2563eb);
    box-shadow: 0 2px 10px rgba(15, 23, 42, 0.08);
    outline: none;
}

.plugin-card-head {
    display: flex;
    /* 右侧是标题 / 副标题 / 徽标三行，图标顶对齐标题；居中的话有徽标时它会悬在中间 */
    align-items: flex-start;
    gap: 10px;
    min-width: 0;
}

/* 图标槽位：回退层（拼图字形）与 <img> 叠在一起，<img> 加载失败时被藏掉、露出回退层。
   见 js/utils/plugin-icon.js —— 图标地址来自我们不掌控的外部服务，
   「失败」和「压根没配图标」在界面上就该长得一样，而不是一个浏览器的碎图 */
.plugin-card-icon-slot {
    position: relative;
    flex: 0 0 auto;
    display: inline-flex;
    width: 36px;
    height: 36px;
}

.plugin-card-icon {
    flex: 0 0 auto;
    width: 36px;
    height: 36px;
    border-radius: 8px;
    /* contain 而非 cover：非正方形的品牌 logo 一裁就认不出是谁 */
    object-fit: contain;
    background: rgba(148, 163, 184, 0.16);
}

.plugin-card-icon-slot .plugin-card-icon {
    position: absolute;
    inset: 0;
}

/* 底片固定浅色、不随主题反色：单色深色 logo（GitHub 的 #181717 那类）在深色底上会直接消失，
   而 <img> 的颜色不归 CSS 管，靠 --kb-* 主题变量救不回来 */
.plugin-card-icon-slot img.plugin-card-icon {
    padding: 3px;
    background: #fff;
    border: 1px solid rgba(148, 163, 184, 0.3);
}

.plugin-card-icon--fallback {
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--text-secondary, #6b7280);
}

.plugin-card-heading {
    flex: 1 1 auto;
    min-width: 0;
}

/* 标题独占一行；徽标单独一行排在副标题（类型 + 版本）之后 */
.plugin-card-name {
    display: flex;
    align-items: center;
    min-width: 0;
    font-weight: 600;
    font-size: 0.9375rem;
}

/* 名字长了省略 */
.plugin-card-name > span:first-child {
    flex: 1 1 auto;
    min-width: 0;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.plugin-card-meta {
    display: flex;
    gap: 8px;
    color: var(--text-secondary, #6b7280);
    font-size: 0.75rem;
}

/* 描述固定两行：卡片高度参差会让网格看起来是坏的 */
.plugin-card-desc {
    color: var(--text-secondary, #6b7280);
    font-size: 0.8125rem;
    line-height: 1.5;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    min-height: 2.4em;
}

.plugin-card-badges {
    display: flex;
    /* 徽标块内能自己折行；min-width:0 让它在窄卡片里收得下 */
    flex-wrap: wrap;
    min-width: 0;
    gap: 6px;
    row-gap: 4px;
    margin-top: 4px;
}

.plugin-card-badge {
    /* 与标题并排后要跟着基线走，inline-block 的行盒会把徽标顶高半格 */
    display: inline-flex;
    align-items: center;
    flex-shrink: 0;
    /* 折行只发生在徽标之间，「可升级 v1.0.1」不能从中间断开 */
    white-space: nowrap;
    padding: 1px 6px;
    border-radius: 4px;
    border: 1px solid var(--border-color, #e5e7eb);
    color: var(--text-secondary, #6b7280);
    font-size: 0.6875rem;
    line-height: 16px;
}

.plugin-card-badge--warn {
    border-color: #f59e0b;
    color: #b45309;
}

.plugin-card-badge--info {
    border-color: #3b82f6;
    color: #1d4ed8;
}

.plugin-card-badge--ok {
    border-color: #10b981;
    color: #047857;
}

.plugin-card-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin-top: auto;
}

.plugin-card-action {
    padding: 5px 12px;
    border: 1px solid var(--border-color, #e5e7eb);
    border-radius: 6px;
    background: transparent;
    color: inherit;
    font-size: 0.8125rem;
    /* 键名不折行：宁可整个键换到下一行，也不要「重新授权」被拆成两行 */
    white-space: nowrap;
    cursor: pointer;
}

.plugin-card-action--primary {
    border-color: var(--kb-accent, #2563eb);
    background: var(--kb-accent, #2563eb);
    /* 不能写死白色：深色阅读主题下 --kb-accent 是浅沙色(#d8cb99)，白字看不见。
       --text-inverse 会跟着主题翻面，与 components.css 里的主按钮同一口径 */
    color: var(--text-inverse, #ffffff);
}

.plugin-card-action--danger {
    color: #dc2626;
}

/* 禁用态统一走语义变量，不用 opacity 表达（见 docs/theme-control-states.md） */
.plugin-card-action:disabled {
    cursor: not-allowed;
    border-color: var(--kb-control-disabled-border);
    background: var(--kb-control-disabled-bg);
    color: var(--kb-control-disabled-text);
}

/*
 * 插件弹框的主按钮（详情里的「安装 / 授权」、配置里的「保存并启用」）。
 *
 * 不能用 Dialog 默认的 .bg-primary：那条规则把文字写死成 #ffffff，而深色主题下
 * --btn-primary-bg 取的是 --kb-accent(#d8cb99) 这种浅沙色，白字直接糊在底色上看不清。
 * 这里跟插件卡片上的「安装」按钮同一口径：底色 --kb-accent，文字用会随主题翻面的
 * --text-inverse，深浅两套主题下都读得出来。
 */
/*
 * .plugin-dialog-primary 的配色已经上收到 css/dialog.css，与通用弹框主按钮
 * .kb-dialog-btn--primary 共用同一条规则——「插件详情」的「安装」当初就是全站
 * 唯一配色正确的主按钮，现在它成了所有弹框主按钮的标准，不再是这里的特例。
 */

.plugin-market-empty {
    padding: 32px 0;
    text-align: center;
    color: var(--text-secondary, #6b7280);
    font-size: 0.875rem;
}

.plugin-market-loading {
    display: none;
    align-items: center;
    justify-content: center;
    padding: 32px 0;
    color: var(--text-secondary, #6b7280);
}

.plugin-market-loading-content {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 0.875rem;
}

.plugin-market-loading-content i {
    animation: spin 1s linear infinite;
}

/* 加载失败时的重试按钮：插件页失败后本来没有任何自助重试入口 */
.plugin-market-retry {
    margin-top: 12px;
    padding: 6px 18px;
    border: 1px solid var(--border-color, #e5e7eb);
    border-radius: 8px;
    background: var(--bg-secondary, #ffffff);
    color: inherit;
    font-size: 0.875rem;
    cursor: pointer;
    transition: border-color 0.2s ease, color 0.2s ease;
}

.plugin-market-retry:hover {
    border-color: var(--kb-accent, #2563eb);
    color: var(--kb-accent, #2563eb);
}

/* ---------------- 配置表单 ---------------- */

.plugin-config-form {
    display: flex;
    flex-direction: column;
    gap: 22px;
    /* 整个表单钉成左对齐：text-align 会继承，弹框外层但凡有一处 center，
       标题文字就会飘到输入框中间去。在最外层拦一道，里面的规则只是加固 */
    text-align: left;
}

/* 引导横幅：图标 + 标题 + 说明 + 帮助链接 */
.plugin-config-intro {
    display: flex;
    align-items: flex-start;
    gap: 12px;
    padding: 14px 16px;
    border: 1px solid var(--border-color, #e5e7eb);
    border-radius: 12px;
    background: rgba(37, 99, 235, 0.06);
}

.plugin-config-intro-icon {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 36px;
    height: 36px;
    border-radius: 10px;
    background: rgba(37, 99, 235, 0.12);
    color: var(--kb-accent, #2563eb);
    font-size: 1.125rem;
}

.plugin-config-intro-copy {
    display: flex;
    flex-direction: column;
    gap: 4px;
    min-width: 0;
}

.plugin-config-intro-copy strong {
    font-size: 0.9375rem;
    font-weight: 600;
}

.plugin-config-intro-copy p {
    margin: 0;
    color: var(--text-secondary, #6b7280);
    font-size: 0.8125rem;
    line-height: 1.5;
}

.plugin-config-link {
    display: inline-flex;
    align-items: center;
    /* base.css 给所有 button 设了 justify-content:center，而这里的父级是 column flex，
       默认 stretch 会把按钮拉满整行——两条叠一起，链接就飘到正中去了。
       align-self 让它收缩成内容宽度，与上方的标题左对齐 */
    align-self: flex-start;
    justify-content: flex-start;
    text-align: left;
    gap: 4px;
    margin-top: 2px;
    padding: 0;
    border: none;
    background: none;
    color: var(--kb-accent, #2563eb);
    font-size: 0.8125rem;
    font-weight: 500;
    cursor: pointer;
}

.plugin-config-link:hover {
    text-decoration: underline;
}

/*
 * 字段区：两列网格，默认整行，窄字段（见 _resolveHalfWidthFields）占一列并排。
 *
 * 用 grid 而不是 flex-wrap：flex 下半宽字段要写 `flex: 1 1 calc(50% - 8px)`，
 * 一旦落单就会被拉满整行，与整行字段看不出区别；grid 的列宽由轨道决定，
 * 落单也稳稳占一列，版式不会随字段个数跳来跳去。
 * minmax(0, 1fr) 里的 0 不能省——默认 min-width:auto 会被长 placeholder 顶宽，
 * 两列就挤出容器了。
 */
.plugin-config-fields {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    /* 网格项默认就该铺满列宽；写死是防止外层的 justify-items:center 漏进来
       把每个字段缩成内容宽度再居中 */
    justify-items: stretch;
    gap: 18px 16px;
}

.plugin-config-field {
    display: flex;
    /* 默认横跨两列 = 整行 */
    grid-column: span 2;
    flex-direction: column;
    /* stretch + start 都写死：这两个值只要被外层继承或覆盖成 center，
       label 和输入框就会缩成内容宽度并居中——正是「表单看起来乱」的成因 */
    align-items: stretch;
    justify-items: stretch;
    gap: 8px;
    min-width: 0;
    text-align: left;
}

.plugin-config-field--half {
    grid-column: span 1;
}

/*
 * ⚠️ 必须比 `.plugin-config-field` 更"重"，否则标题会被挤到输入框中间去。
 *
 * form-unified.css 有一条给复选框行准备的规则：
 *     .checkbox-container label, .radio-container label, .flex.items-center label
 *         { display: flex; align-items: center; }
 * 而 `flex items-center` 是 Tailwind 里最常见的组合之一——dialog.js 的遮罩层正是
 * `class="utils-dialog ... flex items-center justify-center"`（用来把弹框居中），
 * 于是**弹框里的每个 <label> 都被这条规则命中**。本文件的字段恰好就是 <label>。
 *
 * 它是 (0,2,1) 且在 index.html 里排在第 1289 行，本文件在第 217 行——特异度和顺序
 * 两头都压过 `.plugin-config-field`(0,1,0)。拿到 align-items:center 之后，字段的两个
 * 子元素（标题、输入框容器）都缩成内容宽度并居中：标题只剩 88px 飘在中间，
 * 输入框则因为 .plugin-config-control 上写了 width:100% 才幸免。这正是「标题居中、
 * 输入框却是满宽」那个怪现象的由来。
 *
 * 这里用三层选择器把特异度抬到 (0,3,1) 压回去。没有去改 form-unified 那条规则：
 * 它作用面覆盖全站所有 `flex items-center` 容器里的 label，改动波及登录页「记住我」
 * 一类的既有排版，风险远大于在本文件里局部压制。
 */
.plugin-config-form .plugin-config-fields label.plugin-config-field {
    align-items: stretch;
}

/*
 * 窄屏收成单列。阈值与 _openConfig 里 minHeight 用的 640 保持一致。
 * 640 以下弹框本身也就三百多宽，再分两列每列只剩 ~148px，
 * 连 imap.163.com 这种主机名都显示不全。
 * 两条都要写：单列网格里留着 span 2 会让 grid 自己长出第二列，等于没收。
 */
@media (max-width: 639px) {
    .plugin-config-fields {
        grid-template-columns: minmax(0, 1fr);
    }

    .plugin-config-field,
    .plugin-config-field--half {
        grid-column: span 1;
    }
}

/*
 * min-height 是为了两列对齐：带「去获取」按钮的标签行会被按钮撑高，
 * 不带的那一列只有文字高度，两列的输入框顶边因此错开二十来像素
 * （实测 19px vs 40px）。下面的 .plugin-config-field-help 把按钮压回文字高度，
 * 这里再给所有标签行一个统一下限，两条要一起看。
 */
.plugin-config-label {
    display: flex;
    align-items: center;
    justify-content: flex-start;
    min-height: 20px;
    font-size: 0.8125rem;
    font-weight: 500;
    text-align: left;
}

/*
 * 字段旁的「去获取」入口。
 *
 * 用 margin-left:auto 顶到标签行最右侧，而不是紧跟标签文字——不同插件的标签长短
 * 差很多，跟排的话每个字段的按钮位置都不一样，扫一眼找不到。
 * 父级 .plugin-config-label 是 flex，这里同样要压掉 base.css 给 button 的
 * justify-content:center 与拉伸行为（同 .plugin-config-link 的处理）。
 */
.plugin-config-field-help {
    display: inline-flex;
    align-items: center;
    justify-content: flex-start;
    flex-shrink: 0;
    gap: 3px;
    margin-left: auto;
    padding: 0 0 0 8px;
    /* form-unified.css 有一条全局的 `button, .btn { min-height: 40px }`，
       不归零就会把整个标签行撑到 40px，只有这一列被顶高、输入框跟着往下掉。
       与 css/nav-controls.css、css/desktop-download.css 里同样的压制手法 */
    min-height: 0;
    border: none;
    background: none;
    color: var(--kb-accent, #2563eb);
    font-size: 0.75rem;
    font-weight: 500;
    line-height: 1.4;
    cursor: pointer;
}

.plugin-config-field-help:hover {
    text-decoration: underline;
}

.plugin-config-field-help i {
    font-size: 0.875em;
}

.plugin-config-required {
    margin-right: 4px;
    color: #dc2626;
}

.plugin-config-control {
    position: relative;
    display: flex;
    align-items: center;
    /* 必须占满字段宽度：缩成内容宽度的话，里面 width:100% 的 input
       会退回浏览器默认的那个 ~200px 固有宽度，再被居中，就成了截图里那种版式 */
    width: 100%;
}

/*
 * ⚠️ 只能写 background-color，不能写 background 简写。
 *
 * 这个类同时套在 <input> 和 <select> 上（有 options 的字段渲染成 select，
 * 见 _renderConfigForm）。form-unified.css 给 select 配了箭头背景图：
 *     select { appearance:none; background-image:url(…); background-repeat:no-repeat;
 *              background-position:right 8px center; background-size:16px }
 *     [data-theme="dark"] select { background-image:url(…深色箭头…) }
 * 简写 `background:` 是 (0,1,0)，压得过 `select` 的 (0,0,1)，却压不过
 * `[data-theme="dark"] select` 的 (0,1,1)。于是深色主题下箭头图活了下来、
 * repeat/size/position 却被简写重置成 repeat/auto/0% 0%——SVG 没有内在尺寸，
 * 按 44×44 平铺，一个下拉框里排出四五个箭头（就是截图里那个现象）；
 * 浅色主题下则相反，整条规则被压掉，一个箭头都没有。
 */
.plugin-config-input {
    width: 100%;
    height: 44px;
    padding: 0 14px;
    border: 1px solid var(--border-color, #e5e7eb);
    border-radius: 10px;
    background-color: var(--bg-secondary, #ffffff);
    color: inherit;
    font-size: 0.9375rem;
    outline: none;
    transition: border-color 0.2s ease, box-shadow 0.2s ease;
}

/*
 * 下拉框的箭头自己画一遍，不去继承 form-unified 那份。
 * 多带一层 .plugin-config-form 是为了把特异度抬到 (0,2,1)/(0,3,1)，
 * 压过 `[data-theme="dark"] select` 的 (0,1,1)——同分时它排在本文件之后会赢。
 * 箭头 data-URI 与 css/form-unified.css 保持同一份，改配色要两处一起改。
 */
.plugin-config-form select.plugin-config-input {
    -webkit-appearance: none;
    -moz-appearance: none;
    appearance: none;
    padding-right: 36px;
    background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%239ca3af' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");
    background-repeat: no-repeat;
    background-position: right 12px center;
    background-size: 16px;
    cursor: pointer;
}

[data-theme="dark"] .plugin-config-form select.plugin-config-input,
html.dark .plugin-config-form select.plugin-config-input {
    background-image: url("data:image/svg+xml,%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 8l4 4 4-4'/%3e%3c/svg%3e");
}

.plugin-config-input[data-secret="1"] {
    padding-right: 40px;
}

.plugin-config-input:focus {
    border-color: var(--kb-accent, #2563eb);
    box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.15);
}

.plugin-config-secret-toggle {
    position: absolute;
    right: 6px;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 30px;
    height: 30px;
    border: none;
    border-radius: 6px;
    background: none;
    color: var(--text-secondary, #6b7280);
    cursor: pointer;
    transition: background 0.15s ease;
}

.plugin-config-secret-toggle:hover {
    background: rgba(148, 163, 184, 0.16);
}

.plugin-config-hint {
    color: var(--text-secondary, #6b7280);
    font-size: 0.75rem;
}

.plugin-config-security-note {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    padding: 12px 14px;
    border: 1px solid var(--border-color, #e5e7eb);
    border-radius: 10px;
    background: rgba(16, 185, 129, 0.06);
    color: var(--text-secondary, #6b7280);
    font-size: 0.8125rem;
    line-height: 1.5;
}

.plugin-config-security-note i {
    flex: 0 0 auto;
    margin-top: 1px;
    color: #10b981;
}

@media (max-width: 1023px) {
    /**
     * .mobile-main-content 的 119px / 95px 外边距是为固定定位的搜索栏与分页条让位的，
     * 插件模式下这两条已经隐藏（见文件上方的 display:none），边距不收回来底部就空一大块。
     * 保留的 64px 是顶部导航、30px 是底部备案条。与图谱、统计看板同一套处理。
     */
    body.kb-plugins-mode .mobile-main-content {
        margin-top: 64px !important;
        margin-bottom: calc(30px + env(safe-area-inset-bottom, 0px)) !important;
    }

    body.kb-plugins-mode.pwa-mode .mobile-main-content {
        margin-bottom: 0 !important;
    }

    /* 高度要和上面那两条外边距对齐，否则要么顶出屏幕、要么底下空一截 */
    .plugin-market-view {
        height: calc(100dvh - 64px - 30px - env(safe-area-inset-bottom, 0px));
    }

    body.pwa-mode .plugin-market-view {
        height: calc(100dvh - 64px);
    }

    .plugin-market-grid {
        grid-template-columns: 1fr;
    }

    /* 移动端触摸目标 ≥ 44px */
    .plugin-card-action {
        min-height: 36px;
        padding: 8px 14px;
    }
}

@media (display-mode: standalone) and (max-width: 1023px) {
    body.kb-plugins-mode .mobile-main-content {
        margin-bottom: 0 !important;
    }
}

/* ---------- 扫码授权弹窗 ---------- */

.plugin-auth-body {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 12px;
    padding: 8px 0 4px;
    min-height: 240px;
    justify-content: center;
}

/*
 * 设备流的授权码。等宽 + 加大字号 + 字距：用户要拿它跟浏览器页面上的码逐字符核对，
 * 比例字体下 0/O、1/l 分不清。
 */
.plugin-auth-code {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 26px;
    font-weight: 700;
    letter-spacing: .16em;
    padding: 10px 16px;
    border-radius: var(--radius-sm, 6px);
    background: var(--bg-secondary, #f6f8fa);
    user-select: all;
}

.plugin-auth-open {
    margin-top: 4px;
}

.plugin-auth-qrcode {
    width: 200px;
    height: 200px;
    /* 二维码必须像素对齐，插值会让边角糊掉、扫不出来 */
    image-rendering: pixelated;
    border-radius: var(--radius-sm, 6px);
    background: #fff;
    padding: 8px;
}

.plugin-auth-tip {
    color: var(--text-primary, #111827);
    font-size: 0.875rem;
    text-align: center;
}

/*
 * 授权失败的原因来自 CLI 原文，是**多行**的（第一行说现象，后面几行才是「找谁开权限」
 * 「入口在哪」这些能让用户动起来的信息）。默认的 pre-wrap 折行 + 居中会把它挤成一坨，
 * 所以这里单独左对齐并保留换行。
 */
.plugin-auth-tip--error {
    color: #b91c1c;
    text-align: left;
    white-space: pre-line;
    line-height: 1.65;
    max-height: 240px;
    overflow-y: auto;
    overscroll-behavior: contain;
    word-break: break-word;
}

[data-theme="dark"] .plugin-auth-tip--error {
    color: #f2a2a2;
}

.plugin-auth-hint {
    color: var(--text-secondary, #6b7280);
    font-size: 0.75rem;
    text-align: center;
}

/* ---------------- 插件详情弹窗 ---------------- */

.plugin-detail-head {
    display: flex;
    align-items: center;
    gap: 12px;
    margin-bottom: 12px;
}

.plugin-detail-icon-slot {
    position: relative;
    flex: 0 0 auto;
    display: inline-flex;
    width: 44px;
    height: 44px;
}

.plugin-detail-icon {
    flex: 0 0 auto;
    width: 44px;
    height: 44px;
    border-radius: 10px;
    object-fit: contain;
}

.plugin-detail-icon-slot .plugin-detail-icon {
    position: absolute;
    inset: 0;
}

.plugin-detail-icon-slot img.plugin-detail-icon {
    padding: 4px;
    background: #fff;
    border: 1px solid rgba(148, 163, 184, 0.3);
}

.plugin-detail-icon--fallback {
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(148, 163, 184, 0.16);
    color: var(--text-secondary, #6b7280);
    font-size: 1.25rem;
}

.plugin-detail-name {
    font-size: 1rem;
    font-weight: 600;
}

.plugin-detail-badges {
    margin-top: 4px;
}

.plugin-detail-desc {
    margin: 0 0 16px;
    color: var(--text-secondary, #6b7280);
    font-size: 0.9375rem;
    line-height: 1.7;
}

.plugin-detail-rows {
    margin: 0;
    display: grid;
    /* 标签列定宽：几行标签字数不一，auto 会让值参差不齐，像没对过 */
    grid-template-columns: 5.5rem 1fr;
    gap: 10px 16px;
    padding: 14px 16px;
    border: 1px solid var(--border-color, #e5e7eb);
    border-radius: 8px;
    font-size: 0.875rem;
}

.plugin-detail-row {
    display: contents;
}

.plugin-detail-rows dt {
    color: var(--text-secondary, #6b7280);
}

.plugin-detail-rows dd {
    margin: 0;
    /* 插件代码可能很长，不断行会把弹窗撑破 */
    word-break: break-all;
}

.plugin-detail-section {
    margin: 16px 0 8px;
    font-size: 0.8125rem;
    font-weight: 500;
}

.plugin-detail-config {
    margin: 0;
    padding-left: 18px;
    font-size: 0.8125rem;
    line-height: 1.9;
}

/*
 * 详情弹框里的标签（徽标 / 必填 / 加密存储）统一走同一套盒模型：
 * 1px 边框、4px 圆角、同样的内边距与行高，颜色只用来区分语义。
 *
 * 「必填」原来的描边也是 1px，却明显比旁边的「加密存储」重——因为它用的是满饱和的
 * #f59e0b，而中性描边是 --kb-paper-line（alpha 才 0.13~0.15）。同宽不同色，
 * 观感差在对比度上，所以彩色描边一律降成半透明，把视觉重量拉回同一条线。
 */
.plugin-detail-badges .plugin-card-badge,
.plugin-detail-required,
.plugin-detail-optional,
.plugin-detail-secret {
    display: inline-flex;
    align-items: center;
    padding: 1px 6px;
    border: 1px solid var(--border-color, #e5e7eb);
    border-radius: 4px;
    font-size: 0.6875rem;
    line-height: 16px;
}

.plugin-detail-required,
.plugin-detail-optional,
.plugin-detail-secret {
    margin-left: 6px;
}

/*
 * 「选填」走中性色，与「必填」的琥珀色拉开。两者同宽同高、只有颜色不同——
 * 扫一眼就能看出哪几项非填不可，而不是去数哪几行没有徽标。
 */
.plugin-detail-optional {
    color: var(--text-secondary, #6b7280);
}

.plugin-detail-required,
.plugin-detail-badges .plugin-card-badge--warn {
    border-color: color-mix(in srgb, #f59e0b 35%, transparent);
    color: #b45309;
}

.plugin-detail-badges .plugin-card-badge--info {
    border-color: color-mix(in srgb, #3b82f6 35%, transparent);
}

.plugin-detail-badges .plugin-card-badge--ok {
    border-color: color-mix(in srgb, #10b981 35%, transparent);
}

.plugin-detail-secret {
    color: var(--text-secondary, #6b7280);
}

.plugin-detail-note {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    margin-top: 16px;
    padding: 10px 12px;
    border-radius: 6px;
    background: rgba(245, 158, 11, 0.1);
    color: #b45309;
    font-size: 0.75rem;
    line-height: 1.6;
}

/*
 * 图标要跟**首行文字**对齐，不是跟整段顶端对齐。
 *
 * align-items:flex-start 让两个子项的顶边平齐，但图标字号比正文大、又没有行高，
 * 于是看起来比第一行文字高出一截。给它同样的 line-height，让两者共用一个行盒基准。
 */
.plugin-detail-note > i {
    flex: 0 0 auto;
    /*
     * 高度取「正文字号 × 行高」= 首行文字的行盒高度，再把字形在其中居中。
     * 这样父级的 align-items:flex-start 把两个盒子顶边对齐时，图标正好落在第一行正中。
     *
     * 不靠 line-height 对齐：图标字体的字形高度与基线和正文不一致，
     * 同样的 line-height 出来的视觉位置仍会差几个像素。
     */
    display: flex;
    align-items: center;
    height: calc(0.75rem * 1.6);
    font-size: 0.875rem;
}
