/* ==========================================================================
   拼豆图纸转换器 · 样式表
   移动端优先，暖色系，详见 docs/04-UI设计文档.md
   ========================================================================== */


/* --------------------------------------------------------------------------
   1. 变量与重置
   -------------------------------------------------------------------------- */

:root {
    /* 主色 */
    --c-primary: #ff7a45;
    --c-primary-dark: #e85d2a;
    --c-primary-light: #fff2ec;
    --c-success: #52c41a;
    --c-warning: #faad14;
    --c-danger: #ff4d4f;
    /* 文字 */
    --c-text: #262626;
    --c-text-sub: #8c8c8c;
    --c-text-weak: #bfbfbf;
    /* 背景与边框 */
    --c-bg: #f7f7f8;
    --c-card: #ffffff;
    --c-border: #e8e8e8;
    /* 间距 */
    --sp: 8px;
    --radius-card: 12px;
    --radius-btn: 8px;
    /* 阴影 */
    --shadow-card: 0 1px 3px rgba(0, 0, 0, 0.06);
    --shadow-topbar: 0 2px 8px rgba(0, 0, 0, 0.08);
    --shadow-primary: 0 2px 6px rgba(255, 122, 69, 0.3);
    /* 布局 */
    --max-width: 600px;
    --topbar-height: 56px;
}


/* hidden 属性必须始终生效：类选择器的 display 会覆盖 UA 默认的 [hidden] */

[hidden] {
    display: none !important;
}

* {
    box-sizing: border-box;
    -webkit-tap-highlight-color: transparent;
}

html {
    -webkit-text-size-adjust: 100%;
}

body {
    margin: 0;
    padding: 0;
    font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Microsoft YaHei", "Helvetica Neue", Arial, sans-serif;
    font-size: 14px;
    line-height: 1.6;
    color: var(--c-text);
    background: var(--c-bg);
    overflow-x: hidden;
}

h1,
h2,
h3,
p {
    margin: 0;
}

button {
    font-family: inherit;
    cursor: pointer;
}

input,
select {
    font-family: inherit;
}


/* 屏幕阅读器专用 */

.sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}


/* --------------------------------------------------------------------------
   2. 顶部吸顶栏
   -------------------------------------------------------------------------- */

.topbar {
    position: sticky;
    top: 0;
    z-index: 100;
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: var(--sp);
    min-height: var(--topbar-height);
    padding: 0 16px;
    background: var(--c-card);
    box-shadow: var(--shadow-topbar);
}


/* 色卡数据来源与版本标注 */

.palette-source {
    flex-basis: 100%;
    margin: 0 0 6px;
    font-size: 11px;
    line-height: 1.4;
    color: var(--c-text-muted, #8c8c8c);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}


/* V2.3（C-1）免注册叙事：顶部提示条 */

.topbar__note {
    flex-basis: 100%;
    margin: 0 0 6px;
    font-size: 11px;
    line-height: 1.4;
    color: var(--c-text-muted, #8c8c8c);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.topbar__title {
    font-size: 18px;
    font-weight: 600;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.topbar__right {
    display: flex;
    align-items: center;
    gap: var(--sp);
    flex-shrink: 0;
}


/* T-35 阶段G-1（问题12/13）：角色开关
   设计模式 / 拼豆模式互斥切换，视觉上做成「分段控件」，
   让用户一眼看出当前处于哪种角色，而不是在一堆面板里找。 */

.role-switch {
    display: inline-flex;
    align-items: stretch;
    padding: 2px;
    border-radius: 999px;
    background: var(--c-bg-sub);
    border: 1px solid var(--c-border);
    flex-shrink: 0;
}

.role-switch__btn {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 5px 12px;
    border: none;
    border-radius: 999px;
    background: transparent;
    color: var(--c-text-sub);
    font-size: 13px;
    font-weight: 600;
    line-height: 1.2;
    cursor: pointer;
    transition: background 0.18s ease, color 0.18s ease;
}

.role-switch__btn:hover {
    color: var(--c-text);
}

.role-switch__btn.is-active {
    background: var(--c-card);
    color: var(--c-primary, #FF7A45);
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12);
}

.role-switch__btn:focus-visible {
    outline: 2px solid var(--c-primary, #FF7A45);
    outline-offset: 1px;
}

.role-switch__icon {
    font-size: 14px;
    line-height: 1;
}

.role-switch__label {
    white-space: nowrap;
}

@media (max-width: 480px) {
    .role-switch__label {
        display: none;
    }
    .role-switch__btn {
        padding: 5px 9px;
    }
}

.badge {
    display: inline-flex;
    align-items: center;
    padding: 2px 8px;
    border-radius: 10px;
    font-size: 11px;
    font-weight: 600;
    white-space: nowrap;
}

.badge--pro {
    color: #fff;
    background: var(--c-success);
}


/* V1.2（L2）：免费版标识 */

.badge--free {
    color: var(--c-text-sub);
    background: var(--c-bg-sub);
    border: 1px solid var(--c-border);
}


/* V1.6：本地开发主机下徽标可点击（仅 127.0.0.1 / localhost 生效） */

.badge--dev {
    cursor: pointer;
    outline: 1px dashed var(--c-primary);
    outline-offset: 1px;
}

.badge--dev:hover {
    filter: brightness(0.96);
}


/* V1.6：升级弹窗内的「本地开发」一键解锁区块 */

.dev-unlock {
    margin-top: 12px;
    padding: 10px 12px;
    border: 1px dashed var(--c-primary);
    border-radius: var(--radius-btn);
    background: var(--c-bg-sub);
}

.dev-unlock__title {
    margin: 0 0 4px;
    font-size: 13px;
    font-weight: 600;
    color: var(--c-primary);
}

.dev-unlock__note {
    margin: 0 0 8px;
    font-size: 12px;
    line-height: 1.5;
    color: var(--c-text-sub);
}

.dev-unlock__row {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
}

.palette-select select {
    appearance: none;
    -webkit-appearance: none;
    padding: 6px 26px 6px 10px;
    border: 1px solid var(--c-border);
    border-radius: var(--radius-btn);
    background: var(--c-card) url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath fill='%238c8c8c' d='M0 0l5 6 5-6z'/%3E%3C/svg%3E") no-repeat right 8px center;
    font-size: 13px;
    color: var(--c-text);
    max-width: 130px;
}


/* --------------------------------------------------------------------------
   3. 主容器
   -------------------------------------------------------------------------- */

.main {
    max-width: var(--max-width);
    margin: 0 auto;
    padding: 16px;
    display: flex;
    flex-direction: column;
    gap: 16px;
}


/* --------------------------------------------------------------------------
   4. 卡片
   -------------------------------------------------------------------------- */

.card {
    background: var(--c-card);
    border-radius: var(--radius-card);
    padding: 16px;
    box-shadow: var(--shadow-card);
}

.card__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp);
    margin-bottom: 12px;
}

.card__title {
    font-size: 16px;
    font-weight: 600;
    margin-bottom: 12px;
}

.card__head .card__title {
    margin-bottom: 0;
}


/* --------------------------------------------------------------------------
   5. 按钮
   -------------------------------------------------------------------------- */

.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    min-height: 44px;
    padding: 10px 16px;
    border: 1px solid transparent;
    border-radius: var(--radius-btn);
    font-size: 15px;
    font-weight: 500;
    line-height: 1.2;
    transition: opacity 0.15s, background-color 0.15s, transform 0.1s;
    user-select: none;
    /* T-35 阶段K-8（修【6】）：上一轮为什么没修好 ——
       J-5 只在 .color-list-modal__actions .btn 上加了 nowrap，
       但用户反馈的是「弹窗里所有按钮」：底部「关闭」左边的按钮文字看不到、
       快捷区「清除块数最少的颜色（A01 · 3 颗）」被压成两行后又被裁。
       根因是 .btn 基类没有 nowrap，任何 flex 压缩都会让文字折行，
       折行后 min-height 固定 44px 装不下两行 → 文字被 overflow 吃掉。
       这里在基类统一 nowrap，从根上消除「文字折行被裁」这一类问题。 */
    white-space: nowrap;
    /* 允许收缩但不允许压到文字以下（配合 nowrap 形成「最小内容宽度」下限） */
    min-width: 0;
    flex-shrink: 0;
}

.btn:active:not(:disabled) {
    transform: scale(0.98);
}

.btn:disabled {
    opacity: 0.45;
    cursor: not-allowed;
}

.btn--block {
    display: flex;
    width: 100%;
}

.btn--primary {
    color: #fff;
    background: var(--c-primary);
    box-shadow: var(--shadow-primary);
}

.btn--primary:active:not(:disabled) {
    background: var(--c-primary-dark);
}

.btn--outline {
    color: var(--c-primary);
    background: var(--c-card);
    border-color: var(--c-primary);
}

.btn--outline:active:not(:disabled) {
    background: var(--c-primary-light);
}

.btn--ghost {
    color: var(--c-text-sub);
    background: var(--c-card);
    border-color: var(--c-border);
}

.btn--text {
    color: var(--c-text-sub);
    background: transparent;
    font-size: 13px;
    min-height: 40px;
}

.btn--text:active:not(:disabled) {
    color: var(--c-primary);
}

.btn--sm {
    min-height: 34px;
    padding: 6px 12px;
    font-size: 13px;
}

.btn--upload {
    min-height: 52px;
    font-size: 16px;
    font-weight: 600;
}

.btn__icon {
    font-size: 18px;
}


/* ==========================================================================
   T-48 v1.1：统一 SVG 线性图标（sprite + <use>）
   --------------------------------------------------------------------------
   规格（与 js/icons.js 头部注释一致）：
     24×24 viewBox、stroke=currentColor、stroke-width 2、
     fill none、圆角线帽 / 线接。
   颜色一律走 currentColor，因此暗色场景无需第二套图标 —— 图标自动跟随
   所在按钮的文字色（.tool 的 color / .btn 的 color / 选中态的主色）。

   零依赖：路径全部手绘在 js/icons.js 与 index.html 的 sprite 里，
   不引入任何第三方图标库文件。
   ========================================================================== */

.icon {
    display: inline-block;
    width: 20px;
    height: 20px;
    flex: 0 0 auto;
    vertical-align: -0.15em;
    fill: none;
    stroke: currentColor;
    stroke-width: 2;
    stroke-linecap: round;
    stroke-linejoin: round;
    /* 选中态由 .tool.is-active .icon 提到 2.5，这里给个平滑过渡 */
    transition: stroke-width 0.15s;
}


/* 小号：面板关闭按钮、视口条重置等紧凑位置 */

.icon--sm {
    width: 16px;
    height: 16px;
}


/* 大号：空状态插画位 */

.icon--xl {
    width: 48px;
    height: 48px;
    stroke-width: 1.5;
}


/* sprite 容器本身不占位、不可见（防御性声明，index.html 已内联同样规则） */

.icon-sprite {
    position: absolute;
    width: 0;
    height: 0;
    overflow: hidden;
}

.btn-row {
    display: flex;
    gap: var(--sp);
}

.btn-row .btn {
    flex: 1;
}


/* --------------------------------------------------------------------------
   6. 提示文字
   -------------------------------------------------------------------------- */

.hint {
    margin-top: 8px;
    font-size: 12px;
    color: var(--c-text-sub);
    line-height: 1.5;
}

.hint--center {
    text-align: center;
}


/* T-45 T45-5：豆板预设下方的引导提示
   文案由 boards.js 的 BOARD_PRESET_HINT 统一提供，这里只压紧间距，
   让它紧贴预设按钮行，视觉上属于同一组。 */

.hint--preset {
    margin-top: 4px;
    margin-bottom: 10px;
    font-size: 12px;
    color: var(--c-text-muted, #888);
}


/* 注：T-32 已删除 .hint--warn —— 全站 HTML/JS 均未引用（页面级警示统一用 .page-note--warn） */


/* --------------------------------------------------------------------------
   7. 控件组
   -------------------------------------------------------------------------- */

.control-group {
    margin-top: 16px;
    display: flex;
    flex-direction: column;
    gap: 16px;
}


/* --------------------------------------------------------------------------
   T-39 块1（D1~D4）：参数摘要条 + 参数区折叠
   D1：折叠后「上传图片」按钮保留在摘要条上方（不包进 #param-body）
   D2：生成成功后自动折叠（由 js/main.js 的 setParamCollapsed(true) 触发）
   D3：折叠状态不持久化，刷新即恢复展开
   D4：摘要内容为「色数 + 格数」双指标（复用 formatSummary）
   M2：折叠动画 ≤200ms，尊重 prefers-reduced-motion
   -------------------------------------------------------------------------- */

.param-summary {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-bottom: 12px;
    padding: 10px 12px;
    border: 1px solid var(--c-border);
    border-radius: var(--radius-btn);
    background: var(--c-bg);
}

.param-summary[hidden] {
    display: none;
}

.param-summary__icon {
    flex: 0 0 auto;
    font-size: 16px;
    line-height: 1;
}

.param-summary__text {
    flex: 1 1 auto;
    min-width: 0;
    font-size: 13px;
    color: var(--c-text);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.param-summary .btn {
    flex: 0 0 auto;
}


/* 折叠态：整块参数区隐藏，只留「上传图片」+ 摘要条 */

#control-panel.is-collapsed .param-body {
    display: none;
}


/* 折叠动画（M2）：≤200ms，尊重系统「减少动态效果」 */

.param-body {
    transition: opacity 160ms ease;
}

@media (prefers-reduced-motion: reduce) {
    .param-body {
        transition: none;
    }
}


/* 生成后滚动定位到画布：抵消 sticky 顶栏高度，避免标题被遮住 */

#canvas-panel {
    scroll-margin-top: calc(var(--topbar-height) + 8px);
}


/* V2.3 P3（U-1）：高级选项折叠组（默认收起，降低首屏认知负担） */

.adv-group {
    border: 1px solid var(--c-border);
    border-radius: var(--radius-btn);
    background: var(--c-bg);
    overflow: hidden;
}

.adv-group__summary {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 10px 12px;
    cursor: pointer;
    list-style: none;
    user-select: none;
    font-size: 14px;
    font-weight: 500;
    color: var(--c-text-sub);
}

.adv-group__summary::-webkit-details-marker {
    display: none;
}

.adv-group__title {
    flex: 0 0 auto;
    color: var(--c-text);
}

.adv-group__hint {
    flex: 1 1 auto;
    font-size: 12px;
    font-weight: 400;
    color: var(--c-text-weak);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.adv-group__arrow {
    flex: 0 0 auto;
    font-size: 12px;
    color: var(--c-text-weak);
    transition: transform 0.15s;
}

.adv-group[open] .adv-group__arrow {
    transform: rotate(180deg);
}

.adv-group__body {
    display: flex;
    flex-direction: column;
    gap: 16px;
    padding: 4px 12px 14px;
    border-top: 1px solid var(--c-border);
}

.slider-row__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 6px;
}

.slider-row__head label {
    font-size: 14px;
    font-weight: 500;
}

.slider-row__value {
    font-size: 13px;
    color: var(--c-text-sub);
}

.slider-row__value b {
    color: var(--c-primary);
    font-size: 15px;
}

.slider-row__scale {
    display: flex;
    justify-content: space-between;
    margin-top: 2px;
    font-size: 11px;
    color: var(--c-text-weak);
}


/* 滑块 */

input[type="range"] {
    -webkit-appearance: none;
    appearance: none;
    width: 100%;
    height: 24px;
    background: transparent;
    margin: 0;
}

input[type="range"]::-webkit-slider-runnable-track {
    height: 6px;
    border-radius: 3px;
    background: var(--c-border);
}

input[type="range"]::-webkit-slider-thumb {
    -webkit-appearance: none;
    appearance: none;
    width: 22px;
    height: 22px;
    margin-top: -8px;
    border-radius: 50%;
    background: var(--c-primary);
    border: 2px solid #fff;
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.2);
}

input[type="range"]::-moz-range-track {
    height: 6px;
    border-radius: 3px;
    background: var(--c-border);
}

input[type="range"]::-moz-range-thumb {
    width: 22px;
    height: 22px;
    border-radius: 50%;
    background: var(--c-primary);
    border: 2px solid #fff;
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.2);
}


/* 开关 */

.switch-row {
    display: flex;
    align-items: center;
}

.switch-row--split {
    justify-content: space-between;
}

.switch {
    display: inline-flex;
    align-items: center;
    gap: 10px;
    /* V2.4（T-25）：保证触摸目标 ≥44px */
    min-height: 44px;
    cursor: pointer;
    user-select: none;
}

.switch input {
    position: absolute;
    opacity: 0;
    width: 0;
    height: 0;
}

.switch__track {
    position: relative;
    display: inline-block;
    width: 46px;
    height: 26px;
    border-radius: 13px;
    background: #d9d9d9;
    transition: background-color 0.2s;
    flex-shrink: 0;
}

.switch__thumb {
    position: absolute;
    top: 3px;
    left: 3px;
    width: 20px;
    height: 20px;
    border-radius: 50%;
    background: #fff;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
    transition: transform 0.2s;
}

.switch input:checked+.switch__track {
    background: var(--c-primary);
}

.switch input:checked+.switch__track .switch__thumb {
    transform: translateX(20px);
}

.switch input:focus-visible+.switch__track {
    outline: 2px solid var(--c-primary);
    outline-offset: 2px;
}

.switch__label {
    font-size: 14px;
}


/* 输入框 */

.input {
    width: 100%;
    min-height: 44px;
    padding: 10px 12px;
    border: 1px solid var(--c-border);
    border-radius: var(--radius-btn);
    font-size: 15px;
    color: var(--c-text);
    background: var(--c-card);
    outline: none;
}

.input:focus {
    border-color: var(--c-primary);
}

.input--search {
    min-height: 38px;
    font-size: 14px;
}


/* --------------------------------------------------------------------------
   8. 画布区
   -------------------------------------------------------------------------- */

.canvas-wrap {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 200px;
    padding: 8px;
    border: 1px solid var(--c-border);
    border-radius: var(--radius-btn);
    background: #fafafa;
    /* T-34 阶段二：画布尺寸固定为容器尺寸，缩放/平移由 canvas 内部处理，
       因此这里不再需要滚动条（overflow: auto 会在浮点误差下产生 1px 滚动条） */
    overflow: hidden;
}

.canvas {
    display: block;
    max-width: 100%;
    background: #fff;
    border-radius: 2px;
    cursor: crosshair;
    touch-action: none;
}


/* T-35 阶段C-1（问题3 / 问题13）：常驻「当前色」指示器
   现场反馈：「当前色显示不显眼，吸管取色后根本看不到变化」。
   放在画布左上角浮层，大色块（36×36）+ 色号 + 色名，始终可见。 */

.current-color-badge {
    position: absolute;
    top: 12px;
    left: 12px;
    z-index: 4;
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 6px 12px 6px 6px;
    border: 1px solid var(--c-border);
    border-radius: 999px;
    background: rgba(255, 255, 255, 0.96);
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.12);
    backdrop-filter: blur(4px);
    /* T-45 T45-3：徽章改为「选色」入口二，必须可点。
       旧值 pointer-events:none 是为了不挡画布绘制，现在只让徽章本体可点，
       它本来就浮在画布左上角、面积很小，不影响正常绘制。 */
    pointer-events: auto;
    cursor: pointer;
    /* 色块换色时用 transition 让变化可见 */
    transition: box-shadow 0.2s ease, transform 0.15s ease;
}


/* T-45 T45-3：可点提示（悬停/键盘聚焦时轻微抬起） */

.current-color-badge:hover {
    box-shadow: 0 4px 14px rgba(0, 0, 0, 0.18);
}

.current-color-badge:focus-visible {
    outline: 2px solid var(--c-primary);
    outline-offset: 2px;
}

.current-color-badge:active {
    transform: scale(0.97);
}

.current-color-badge[hidden] {
    display: none;
}

.current-color-badge__swatch {
    display: block;
    width: 36px;
    height: 36px;
    flex: 0 0 36px;
    border-radius: 50%;
    border: 2px solid rgba(0, 0, 0, 0.12);
    background: #fff;
    /* 棋盘格底纹，让白色/浅色也能看清边界 */
    background-image: linear-gradient(45deg, #e0e0e0 25%, transparent 25%), linear-gradient(-45deg, #e0e0e0 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #e0e0e0 75%), linear-gradient(-45deg, transparent 75%, #e0e0e0 75%);
    background-size: 8px 8px;
    background-position: 0 0, 0 4px, 4px -4px, -4px 0;
}

.current-color-badge__meta {
    display: flex;
    flex-direction: column;
    line-height: 1.2;
    min-width: 0;
}

.current-color-badge__code {
    font-size: 14px;
    font-weight: 700;
    color: var(--c-text);
    letter-spacing: 0.02em;
}

.current-color-badge__name {
    font-size: 11px;
    color: var(--c-text-muted, #888);
    max-width: 96px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}


/* 取色 / 选色生效时的脉冲动画（200ms，明确「操作已生效」） */

.current-color-badge.is-pulse {
    animation: current-color-pulse 0.2s ease-out;
}

@keyframes current-color-pulse {
    0% {
        box-shadow: 0 0 0 0 rgba(64, 158, 255, 0.55), 0 2px 10px rgba(0, 0, 0, 0.12);
        transform: scale(1);
    }
    50% {
        box-shadow: 0 0 0 8px rgba(64, 158, 255, 0), 0 2px 10px rgba(0, 0, 0, 0.12);
        transform: scale(1.06);
    }
    100% {
        box-shadow: 0 0 0 0 rgba(64, 158, 255, 0), 0 2px 10px rgba(0, 0, 0, 0.12);
        transform: scale(1);
    }
}

@media (prefers-reduced-motion: reduce) {
    .current-color-badge.is-pulse {
        animation: none;
    }
}


/* T-45 T45-4：最近使用色条
   放在色板面板顶部（搜索框之上），最多 8 个圆形色块，倒序（最近的在最左）。
   空态由 JS 设 hidden，这里只负责有内容时的排版。 */

.recent-colors {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 6px 2px 8px;
    margin-bottom: 4px;
    border-bottom: 1px dashed var(--c-border);
}

.recent-colors[hidden] {
    display: none;
}

.recent-colors__label {
    flex: 0 0 auto;
    font-size: 12px;
    color: var(--c-text-muted, #888);
    white-space: nowrap;
}

.recent-colors__list {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    min-width: 0;
}

.recent-color {
    width: 26px;
    height: 26px;
    flex: 0 0 26px;
    padding: 0;
    border: 2px solid rgba(0, 0, 0, 0.12);
    border-radius: 50%;
    cursor: pointer;
    /* 浅色/白色色块也要能看清边界 */
    box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.6);
    transition: transform 0.12s ease, border-color 0.12s ease;
}

.recent-color:hover {
    transform: scale(1.12);
    border-color: var(--c-primary);
}

.recent-color:focus-visible {
    outline: 2px solid var(--c-primary);
    outline-offset: 2px;
}

.recent-color.is-active {
    border-color: var(--c-primary);
    box-shadow: 0 0 0 2px var(--c-primary-light);
}

@media (prefers-reduced-motion: reduce) {
    .recent-color {
        transition: none;
    }
    .recent-color:hover {
        transform: none;
    }
}


/* T-47 T47-3：画布常用色条
   位置：画布左上角「当前色」徽章右侧（top 12px，left 由徽章宽度决定不了，
   所以用 flex 容器包不住 —— 这里直接绝对定位到徽章右侧的固定偏移）。
   现场反馈：「每次换色都要滚下去开色板，来回好几屏」。
   最多 6 个 24px 圆点，点一下即换色；空态由 JS 设 hidden，不占位。
   拼豆模式同样可见（换色是两种角色的共同需求）。 */

.canvas-recent-colors {
    position: absolute;
    top: 12px;
    /* 徽章宽度约 150px（36 色块 + 色号 + 色名 + padding），留 8px 间隙 */
    left: 168px;
    z-index: 4;
    display: flex;
    align-items: center;
    gap: 5px;
    padding: 5px 8px;
    border: 1px solid var(--c-border);
    border-radius: 999px;
    background: rgba(255, 255, 255, 0.96);
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.12);
    backdrop-filter: blur(4px);
    pointer-events: auto;
    /* 窄屏时允许横向滚动，避免把画布挤满 */
    max-width: calc(100% - 190px);
    overflow-x: auto;
    scrollbar-width: none;
}

.canvas-recent-colors::-webkit-scrollbar {
    display: none;
}

.canvas-recent-colors[hidden] {
    display: none;
}

.canvas-recent-color {
    width: 24px;
    height: 24px;
    flex: 0 0 24px;
    padding: 0;
    border: 2px solid rgba(0, 0, 0, 0.12);
    border-radius: 50%;
    cursor: pointer;
    box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.6);
    transition: transform 0.12s ease, border-color 0.12s ease;
}

.canvas-recent-color:hover {
    transform: scale(1.14);
    border-color: var(--c-primary);
}

.canvas-recent-color:focus-visible {
    outline: 2px solid var(--c-primary);
    outline-offset: 2px;
}

.canvas-recent-color.is-active {
    border-color: var(--c-primary);
    box-shadow: 0 0 0 2px var(--c-primary-light);
}

@media (prefers-reduced-motion: reduce) {
    .canvas-recent-color {
        transition: none;
    }
    .canvas-recent-color:hover {
        transform: none;
    }
}


/* T-47 T47-4：选色面板「当前色」定位闪烁
   打开面板时自动滚到当前色并闪一次，解决「打开面板不知道当前是哪个色」。
   只闪一次（animation-iteration-count: 1），不循环打扰。 */

.swatch.is-flash {
    animation: swatch-flash 0.9s ease-out 1;
}

@keyframes swatch-flash {
    0% {
        box-shadow: 0 0 0 0 rgba(255, 122, 69, 0.85);
        transform: scale(1);
    }
    40% {
        box-shadow: 0 0 0 6px rgba(255, 122, 69, 0.35);
        transform: scale(1.18);
    }
    100% {
        box-shadow: 0 0 0 0 rgba(255, 122, 69, 0);
        transform: scale(1);
    }
}

@media (prefers-reduced-motion: reduce) {
    .swatch.is-flash {
        animation: none;
        outline: 2px solid var(--c-primary);
        outline-offset: 2px;
    }
}


/* T-47 T47-4：选色面板顶部操作提示 */

.palette-hint {
    margin: 0 0 6px;
    font-size: 12px;
    line-height: 1.5;
    color: var(--c-text-muted, #888);
}


/* T-34 阶段二（B-03）：视口控制条 */

.zoom-bar {
    position: absolute;
    right: 12px;
    bottom: 12px;
    z-index: 3;
    display: flex;
    align-items: center;
    gap: 2px;
    padding: 3px;
    border: 1px solid var(--c-border);
    border-radius: 999px;
    background: rgba(255, 255, 255, 0.94);
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
    backdrop-filter: blur(4px);
}

.zoom-bar__btn,
.zoom-bar__value {
    display: flex;
    align-items: center;
    justify-content: center;
    min-width: 30px;
    height: 30px;
    padding: 0 6px;
    border: none;
    border-radius: 999px;
    background: transparent;
    color: var(--c-text-sub);
    font-size: 16px;
    line-height: 1;
    transition: background 0.15s, color 0.15s;
}

.zoom-bar__value {
    min-width: 52px;
    font-size: 12px;
    font-variant-numeric: tabular-nums;
}

.zoom-bar__btn:hover:not(:disabled),
.zoom-bar__value:hover {
    background: rgba(0, 0, 0, 0.06);
    color: var(--c-text);
}

.zoom-bar__btn:disabled {
    opacity: 0.3;
    cursor: not-allowed;
}


/* T-46 T46-3：视图开关（网格线 / 格子内色号）并入视口控制条同行
 *
 * 为什么放这里：这两个开关只影响「看」，不影响「导出」，
 * 放在参数区会让人误以为是转换参数。挪到画布上方的视口条后，
 * 语义变成「当前视图怎么显示」，与缩放同级。
 * 用一条竖分隔线与缩放按钮区分开，避免误读成缩放的一部分。 */

.view-toggles {
    display: flex;
    align-items: center;
    gap: 2px;
    margin-left: 2px;
    padding-left: 4px;
    border-left: 1px solid var(--c-border);
}

.view-toggle {
    display: flex;
    align-items: center;
    gap: 4px;
    height: 30px;
    padding: 0 8px;
    border-radius: 999px;
    color: var(--c-text-sub);
    font-size: 12px;
    line-height: 1;
    white-space: nowrap;
    cursor: pointer;
    transition: background 0.15s, color 0.15s;
}

.view-toggle:hover {
    background: rgba(0, 0, 0, 0.06);
    color: var(--c-text);
}

.view-toggle input[type="checkbox"] {
    width: 14px;
    height: 14px;
    margin: 0;
    accent-color: var(--c-primary);
    cursor: pointer;
}

.view-toggle__text {
    user-select: none;
}


/* 窄屏：文字收起，只留勾选框，避免视口条撑出画布 */

@media (max-width: 640px) {
    .view-toggle__text {
        display: none;
    }
    .view-toggle {
        padding: 0 6px;
    }
}


/* T-34 阶段二：拖动工具 / 双指手势的光标反馈 */

.canvas-wrap.is-pan-mode canvas {
    cursor: grab;
}

.canvas-wrap.is-panning canvas {
    cursor: grabbing;
}


/* T-34 阶段六A（B-07）：框选工具的光标反馈 —— 十字准星表示「可拉框」 */

.canvas-wrap.is-select-mode canvas {
    cursor: crosshair;
}


/* T-34 阶段六B（B-07）：填充工具的光标反馈 —— 复制光标暗示「把颜色倒进去」 */

.canvas-wrap.is-fill-mode canvas {
    cursor: copy;
}


/* T-35 阶段H-1（问题12）：移动工具的光标反馈 —— 四向箭头暗示「抓住内容整体挪」 */

.canvas-wrap.is-move-mode canvas {
    cursor: move;
}


/* 空状态 */

.empty-state {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 6px;
    padding: 40px 16px;
    text-align: center;
}

.empty-state__icon {
    font-size: 40px;
    line-height: 1;
}

.empty-state__text {
    font-size: 15px;
    color: var(--c-text-sub);
}

.empty-state__sub {
    font-size: 12px;
    color: var(--c-text-weak);
}


/* V2.3 P3（U-5）：新手引导——示例图入口 */

.empty-state__demo {
    margin-top: 10px;
}


/* ==========================================================================
   T-48 / T-49：悬浮竖排工具栏（三态收合）
   --------------------------------------------------------------------------
   现场反馈：「工具栏在画布下方不方便、占空间，参考 Photoshop 悬浮风格」，
   随后追加：「太长太宽、占画布；要用时出来、不用缩回去」。

   结构（见 index.html）：
     #canvas-wrap
       └─ .toolbar-float          ← 本段样式主体（绝对定位浮层）
            ├─ .toolbar-handle    ← T-49 收起态把手（36px，默认态）
            ├─ #toolbar           ← 设计模式 20 个工具（2 列网格 + 6 组分隔）
            ├─ #bead-toolbar      ← 拼豆模式 2 个工具（同位置复用）
            └─ #brush-size-row    ← 笔刷尺寸（仅 pen/eraser 显示）

   三态（plans/T-49 §一）：
     收起（默认）  .toolbar-float.is-collapsed → 只显示 .toolbar-handle
     展开          .toolbar-float 无 is-collapsed → 显示卡片本体
     落笔收起      由 main.js 在 canvas pointerdown 时加回 is-collapsed

   T48-b（打回项）：卡片顶部必须让开 #current-color-badge。
   徽章 top:12px + 高约 50px → 底边约 62px，故卡片 top 取 70px（62 + 8 间距）。
   原先的 top:50% + translateY(-58%) 会让卡片压住徽章，已废弃。

   与 #current-color-badge 同族：圆角 + 阴影 + 半透底 + backdrop-filter。
   ========================================================================== */

.toolbar-float {
    position: absolute;
    /* T48-b：让开当前色徽章行（徽章底 62px + 8px 间距）。
       不再垂直居中 —— 垂直居中必然与左上角徽章同位竞争。 */
    top: 70px;
    left: 10px;
    z-index: 5;
    display: flex;
    flex-direction: column;
    gap: 6px;
    /* 底部留 20px，避免贴到画布卡片下沿 */
    max-height: calc(100% - 90px);
    padding: 6px;
    border: 1px solid var(--c-border);
    border-radius: 14px;
    background: rgba(255, 255, 255, 0.96);
    box-shadow: 0 4px 18px rgba(0, 0, 0, 0.14);
    backdrop-filter: blur(6px);
    /* 卡片本体可点（按钮要能按），卡片外的画布区域不受影响：
       浮层只占自身盒子，absolute 定位不会拦截盒子外的指针事件。 */
    pointer-events: auto;
    /* 工具很多时卡片内部滚动，绝不撑破画布卡片 */
    overflow-y: auto;
    overflow-x: hidden;
    scrollbar-width: thin;
    /* T-49 规格 2：从把手处向右展开 */
    transform-origin: left top;
    transition: opacity 0.15s ease, transform 0.15s ease;
}


/* 隐藏态：设计/拼豆互斥时整卡不占位（避免空卡片留一条阴影） */

.toolbar-float[hidden] {
    display: none;
}


/* --------------------------------------------------------------------------
   T-49 收起态：只留 36px 把手
   --------------------------------------------------------------------------
   收起时卡片本体（#toolbar / #bead-toolbar / #brush-size-row）全部不渲染，
   容器本身也收成把手尺寸 —— 这样「占用宽度」从 T-48 的 94px 降到 36px，
   画布左侧让出约 58px 的可绘制宽度（DoD-2 的量化收益来源）。

   注意：这里用 display:none 而不是 visibility/opacity —— 收起态下卡片
   绝不能拦截指针事件，否则「落笔收起」后用户点画布左侧会点不动。
   -------------------------------------------------------------------------- */

.toolbar-float.is-collapsed {
    /* 收起态容器退化为把手的包裹层：去掉自身所有卡片装饰 */
    padding: 0;
    border: none;
    border-radius: 0;
    background: transparent;
    box-shadow: none;
    backdrop-filter: none;
    overflow: visible;
    max-height: none;
    gap: 0;
    /* 收起态不做展开动画（避免收起瞬间闪一下） */
    transition: none;
}

.toolbar-float.is-collapsed> :not(.toolbar-handle) {
    display: none;
}


/* 把手本体：宽 36px、右侧半圆卡（左侧贴画布边缘，故左侧直角） */

.toolbar-handle {
    display: none;
    /* 默认（展开态）不显示把手 */
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 2px;
    width: 36px;
    padding: 8px 0;
    border: 1px solid var(--c-border);
    border-left: none;
    border-radius: 0 12px 12px 0;
    background: rgba(255, 255, 255, 0.96);
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.12);
    backdrop-filter: blur(4px);
    color: var(--c-text-sub);
    cursor: pointer;
    transition: background 0.15s, color 0.15s;
}

.toolbar-float.is-collapsed .toolbar-handle {
    display: flex;
}

.toolbar-handle:hover {
    background: #fff;
    color: var(--c-primary);
}

.toolbar-handle:focus-visible {
    outline: 2px solid var(--c-primary);
    outline-offset: 1px;
}

.toolbar-handle__icon {
    width: 20px;
    height: 20px;
}

.toolbar-handle__arrow {
    width: 12px;
    height: 12px;
    opacity: 0.55;
}


/* 展开态：把手隐藏（卡片本体已足够表达「工具在这」） */

.toolbar-float:not(.is-collapsed) .toolbar-handle {
    display: none;
}


/* --------------------------------------------------------------------------
   T-49 规格 4：hover 展开仅桌面
   --------------------------------------------------------------------------
   用 `hover: hover` 媒体查询区分「有真实悬停能力的指针设备」。
   触屏设备（hover: none）不匹配，只能点把手展开 —— 否则移动端会出现
   「手指划过就展开、划走就收起」的抖动。
   -------------------------------------------------------------------------- */

@media (hover: hover) and (pointer: fine) {
    .toolbar-float.is-collapsed:hover {
        /* 桌面悬停把手 → 展开（JS 也会在 mouseenter 时移除 is-collapsed，
           这里保留纯 CSS 兜底，避免 JS 未就绪时完全无法展开） */
        padding: 6px;
        border: 1px solid var(--c-border);
        border-radius: 14px;
        background: rgba(255, 255, 255, 0.96);
        box-shadow: 0 4px 18px rgba(0, 0, 0, 0.14);
        backdrop-filter: blur(6px);
        overflow-y: auto;
        overflow-x: hidden;
        max-height: calc(100% - 90px);
        gap: 6px;
    }
    .toolbar-float.is-collapsed:hover> :not(.toolbar-handle) {
        display: revert;
    }
    .toolbar-float.is-collapsed:hover .toolbar-handle {
        display: none;
    }
}


/* T-49 规格 2：prefers-reduced-motion 下关闭展开动画 */

@media (prefers-reduced-motion: reduce) {
    .toolbar-float {
        transition: none;
    }
}


/* 工具条本体：2 列图标网格。
 *
 * T-43 T43-5 的「真分组盒子」在竖排场景下会变成 6 个横向小盒子，
 * 视觉上碎成 6 段。T-48 改为：分组仍是 DOM 语义（data-group 保留），
 * 但视觉上用「组间 1px 分隔线」表达，网格跨组连续排列（2 列）。
 */

.toolbar {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 4px;
    margin: 0;
    padding: 0;
}

.toolbar[hidden] {
    display: none;
}

.tool-group {
    display: contents;
}


/* 组间分隔线：除第一组外，每组之前画一条横贯卡片的 1px 线。
 * 用 grid-column: 1 / -1 让伪元素独占一整行，天然把网格切成上下两段。 */

.tool-group+.tool-group::before {
    content: "";
    grid-column: 1 / -1;
    height: 1px;
    margin: 3px 2px;
    background: var(--c-border);
}


/* T-35 阶段K-1（修【10】【17】【19】）：拼豆模式专属工具条
   #btn-progress 从 #toolbar 移出后独立成条，只在拼豆角色显示
   （由 ROLE_VISIBLE_MAP 控制 hidden）。T-48：复用同一悬浮位。 */

.bead-toolbar {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 4px;
    margin: 0;
    padding: 0;
}

.bead-toolbar[hidden] {
    display: none;
}


/* 工具按钮：图标为主、文字视觉隐藏（保留给屏幕阅读器）。
 *
 * 旧版是「图标 + 11px 文字」竖排、min-width 52px，横排时够用；
 * 竖排 2 列后文字会把卡片撑得很宽，因此 T-48 改为纯图标方块。
 * 名称通过 title 属性 + .tool__label（sr-only）双通道保留。 */

.tool {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 38px;
    height: 38px;
    padding: 0;
    border: 1px solid transparent;
    border-radius: 9px;
    background: transparent;
    color: var(--c-text-sub);
    cursor: pointer;
    transition: background 0.15s, color 0.15s, border-color 0.15s;
}

.tool:hover:not(:disabled) {
    background: rgba(0, 0, 0, 0.05);
    color: var(--c-text);
}

.tool:focus-visible {
    outline: 2px solid var(--c-primary);
    outline-offset: 1px;
}


/* 视觉隐藏但保留可访问性：屏幕阅读器仍能读到工具名 */

.tool__label {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}


/* 选中态：主色 + 浅底 + 加粗描边（T-48 图标规格：选中 = stroke 2.5 + 主色） */

.tool.is-active {
    color: var(--c-primary);
    background: rgba(255, 122, 69, 0.12);
    border-color: rgba(255, 122, 69, 0.35);
}

.tool.is-active .icon {
    stroke-width: 2.5;
}


/* T-34 阶段六A（B-12）：data-toggle 类开关按钮的「已开启」态
   与 data-tool 的 is-active 视觉区分：开关用描边高亮，不填充实心，
   避免用户误以为它与画笔/橡皮一样是互斥工具。 */

.tool[data-toggle][aria-pressed="true"] {
    color: var(--c-primary);
    border-color: var(--c-primary);
    background: rgba(255, 122, 69, 0.1);
}

.tool[data-toggle][aria-pressed="true"] .icon {
    stroke-width: 2.5;
}

.tool:disabled {
    opacity: 0.35;
    cursor: not-allowed;
}

.tool--danger.is-active,
.tool--danger:active:not(:disabled) {
    color: #fff;
    background: var(--c-danger);
    border-color: var(--c-danger);
}


/* 笔刷尺寸行的悬浮卡覆盖规则见「V1.9（W1）：画笔 / 橡皮尺寸」段之后
 * （必须写在基础 .brush-size-row 规则之后，否则同特异性下会被覆盖）。 */


/* 窄屏（375px）：卡片收窄、图标缩到 18px，避免遮挡画布中心。
 * 画布本身是 flex 居中，浮层贴左。
 *
 * T-52 T52-2：按钮热区从 30×30 提到 44×44（Apple HIG 44pt / Material 48dp 下限），
 * 但**图标本身不放大**（仍 18px）—— 视觉密度不变，只是可点区域变大。
 * 展开卡宽度因此从约 82px 涨到约 100px（2 列 × 44 + gap 4 + padding 8），
 * 375px 屏上仍留出约 270px 可视绘制区，不撞画布中线（T-49 验收口径）。 */

@media (max-width: 480px) {
    .toolbar-float {
        left: 6px;
        padding: 4px;
        gap: 4px;
        border-radius: 12px;
    }
    /* T-52 T52-2：热区 44×44（触屏可点下限），图标仍 18px 不放大。
       桌面端保持 38×38 不动 —— 鼠标精度高，不需要 44px 热区。 */
    .tool {
        width: 44px;
        height: 44px;
        min-width: 44px;
        min-height: 44px;
        border-radius: 8px;
    }
    .tool .icon {
        width: 18px;
        height: 18px;
    }
    .toolbar-float .brush-size {
        font-size: 9px;
        padding: 2px 0;
    }
    /* T-49：收起态把手在窄屏同样 36px（规格写死 36px，不随屏缩），
       只把左侧偏移对齐到 6px，与展开卡一致。 */
    .toolbar-float.is-collapsed {
        left: 6px;
        padding: 0;
        border-radius: 0;
    }
    .toolbar-handle {
        width: 36px;
        padding: 6px 0;
    }
    .toolbar-handle__icon {
        width: 18px;
        height: 18px;
    }
}


/* ==========================================================================
   T-34 阶段七B（§12.7）：图层面板
   位置：画布下方、色板上方；默认折叠，避免挤压画布
   ========================================================================== */

.layer-panel {
    margin-top: 12px;
    border: 1px solid var(--c-border);
    border-radius: var(--radius-btn);
    overflow: hidden;
}

.layer-panel__head {
    display: flex;
    align-items: center;
    gap: var(--sp);
    width: 100%;
    min-height: 44px;
    padding: 10px 12px;
    border: none;
    background: #fafafa;
    font-size: 14px;
    font-weight: 500;
    color: var(--c-text);
    text-align: left;
    cursor: pointer;
}

.layer-panel__title {
    flex: 0 0 auto;
}

.layer-panel__count {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 20px;
    height: 18px;
    padding: 0 6px;
    border-radius: 9px;
    background: var(--c-border);
    font-size: 11px;
    font-weight: 600;
    color: var(--c-text-sub);
}

.layer-panel__arrow {
    margin-left: auto;
    font-size: 12px;
    color: var(--c-text-sub);
    transition: transform 0.2s;
}

.layer-panel.is-collapsed .layer-panel__arrow {
    transform: rotate(-90deg);
}

.layer-panel.is-collapsed .layer-panel__body {
    display: none;
}

.layer-panel__body {
    padding: 8px;
    border-top: 1px solid var(--c-border);
}


/* 图层列表：自下而上渲染（顶层在最上），故用 column-reverse 让 DOM 顺序 = 层序 */

.layer-list {
    display: flex;
    flex-direction: column-reverse;
    gap: 4px;
    max-height: 220px;
    margin: 0;
    padding: 0;
    overflow-y: auto;
    list-style: none;
}

.layer-item {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 6px 8px;
    border: 1px solid transparent;
    border-radius: var(--radius-btn);
    background: #fff;
    cursor: pointer;
    user-select: none;
}

.layer-item:hover {
    background: #f7f7f7;
}

.layer-item.is-active {
    border-color: var(--c-primary);
    background: #fff5f0;
}

.layer-item.is-hidden .layer-item__thumb,
.layer-item.is-hidden .layer-item__name {
    opacity: 0.4;
}

.layer-item__eye,
.layer-item__lock {
    flex: 0 0 auto;
    width: 26px;
    height: 26px;
    padding: 0;
    border: 1px solid transparent;
    border-radius: 5px;
    background: transparent;
    font-size: 15px;
    line-height: 1;
    cursor: pointer;
}

.layer-item__eye:hover,
.layer-item__lock:hover {
    background: var(--c-border);
}


/* J-4（修【1】）：锁定状态必须「一眼看出」，不能只在 hover 时才可见。
   现场反馈：「锁定和没锁定看不出来」「按钮很小变色好像没看出效果」。
   做法：锁按钮常驻描边 + 锁定行整行加底色与左侧色条。 */

.layer-item__lock {
    border-color: var(--c-border);
    background: #fff;
}

.layer-item__lock.is-on {
    border-color: var(--c-primary);
    background: rgba(255, 122, 69, 0.16);
    box-shadow: 0 0 0 2px rgba(255, 122, 69, 0.12);
}


/* T-35 阶段K-7（修【1】）：上一轮为什么没修好 ——
   J-4 只给了 #fffaf6（比白底亮 1% 都不到）和 inset 3px 色条，
   在 1080p 屏上肉眼几乎分辨不出，用户仍然反馈「锁定和没锁定看不出来」。
   这一轮改成「三重可辨识信号」：
     1) 底色换成肉眼可辨的暖橙 rgba(255,122,69,0.14)；
     2) 左侧色条加粗到 4px 并改用实色，形成明显的「锁定标记」；
     3) 边框改虚线，即使色弱用户也能靠形状区分。
   同时保留 is-active 的边框优先级（见下方 is-active.is-locked 覆盖）。 */

.layer-item.is-locked {
    background: rgba(255, 122, 69, 0.14);
    border-style: dashed;
    border-color: rgba(255, 122, 69, 0.55);
    box-shadow: inset 4px 0 0 var(--c-primary);
}


/* 锁定 + 选中同时存在时，选中态优先（否则用户看不出当前活动层） */

.layer-item.is-locked.is-active {
    border-style: solid;
    border-color: var(--c-primary);
    background: rgba(255, 122, 69, 0.2);
}

.layer-item.is-locked .layer-item__name {
    color: var(--c-primary);
    font-weight: 600;
}

.layer-item.is-locked .layer-item__thumb {
    opacity: 0.75;
}

.layer-item__thumb {
    flex: 0 0 auto;
    width: 28px;
    height: 28px;
    border: 1px solid var(--c-border);
    border-radius: 4px;
    background: #fff;
    image-rendering: pixelated;
}

.layer-item__name {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    font-size: 13px;
    color: var(--c-text);
    text-overflow: ellipsis;
    white-space: nowrap;
}

.layer-item__name-input {
    flex: 1 1 auto;
    min-width: 0;
    padding: 2px 4px;
    border: 1px solid var(--c-primary);
    border-radius: 4px;
    font-size: 13px;
}

.layer-item__opacity {
    flex: 0 0 64px;
    width: 64px;
    height: 16px;
    accent-color: var(--c-primary);
}


/* T-35 阶段E-1（问题6）：参考图层视觉标记 */

.layer-item.is-ref {
    background: rgba(255, 122, 69, 0.06);
}

.layer-item__badge {
    flex: 0 0 auto;
    padding: 1px 5px;
    border-radius: 4px;
    background: rgba(255, 122, 69, 0.16);
    color: var(--c-primary);
    font-size: 11px;
    line-height: 1.5;
    white-space: nowrap;
}


/* T-35 阶段E-3（问题6）：图层面板内的底图控件 */

.layer-base-image {
    margin-top: 8px;
    padding-top: 8px;
    border-top: 1px dashed var(--c-border);
}

.layer-base-image .switch-row {
    margin-bottom: 6px;
}

.layer-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    margin-top: 8px;
    padding-top: 8px;
    border-top: 1px dashed var(--c-border);
}

.layer-actions .btn {
    flex: 1 1 auto;
    min-width: 52px;
}

.layer-actions .btn:disabled {
    opacity: 0.4;
    cursor: not-allowed;
}


/* 色板面板 */

.palette-panel {
    margin-top: 12px;
    border: 1px solid var(--c-border);
    border-radius: var(--radius-btn);
    overflow: hidden;
}

.palette-panel__head {
    display: flex;
    align-items: center;
    gap: var(--sp);
    width: 100%;
    min-height: 44px;
    padding: 10px 12px;
    border: none;
    background: #fafafa;
    font-size: 14px;
    font-weight: 500;
    color: var(--c-text);
    text-align: left;
}

.palette-panel__current {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    margin-left: auto;
    font-size: 12px;
    color: var(--c-text-sub);
}

.palette-panel__arrow {
    font-size: 12px;
    color: var(--c-text-sub);
    transition: transform 0.2s;
}

.palette-panel.is-collapsed .palette-panel__arrow {
    transform: rotate(-90deg);
}

.palette-panel.is-collapsed .palette-panel__body {
    display: none;
}

.palette-panel__body {
    padding: 12px;
    border-top: 1px solid var(--c-border);
}

.swatch-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(34px, 1fr));
    gap: 6px;
    margin-top: 10px;
    max-height: 240px;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
}

.swatch {
    display: inline-block;
    width: 32px;
    height: 32px;
    border-radius: 6px;
    border: 1px solid rgba(0, 0, 0, 0.08);
    background: #fff;
    padding: 0;
    transition: transform 0.1s, box-shadow 0.1s;
}

.swatch--sm {
    width: 16px;
    height: 16px;
    border-radius: 3px;
    vertical-align: middle;
}

.swatch.is-active {
    border: 2px solid var(--c-primary);
    box-shadow: 0 0 0 2px var(--c-primary-light);
    transform: scale(1.08);
}

.swatch-grid__empty {
    grid-column: 1 / -1;
    padding: 16px;
    text-align: center;
    font-size: 13px;
    color: var(--c-text-weak);
}


/* V1.9（W1）：画笔 / 橡皮尺寸 */

.brush-size-row {
    display: flex;
    align-items: center;
    gap: var(--sp);
    margin-top: 8px;
}

.brush-size-row__label {
    flex: 0 0 auto;
    font-size: 12px;
    color: var(--c-text-sub);
}

.brush-size-row__btns {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
}

.brush-size {
    min-width: 46px;
    min-height: 32px;
    padding: 4px 10px;
    border: 1px solid var(--c-border);
    border-radius: var(--radius-btn);
    background: var(--c-card);
    color: var(--c-text-sub);
    font-size: 12px;
    line-height: 1;
    transition: all 0.15s;
}

.brush-size.is-active {
    color: #fff;
    background: var(--c-primary);
    border-color: var(--c-primary);
}


/* T-48：笔刷尺寸行迁入悬浮卡底部后的紧凑版式。
 *
 * 必须写在上面基础规则之后 —— 同特异性（.toolbar-float .brush-size-row
 * 是 0,2,0，基础 .brush-size-row 是 0,1,0，其实更高，但 .brush-size
 * 与 .toolbar-float .brush-size 的关系需要顺序保证，统一放这里最稳）。
 *
 * 显隐两个维度：
 *   - 工具维度：.is-hidden-by-tool（setActiveTool 控制，仅 pen/eraser 显示）
 *   - 角色维度：[hidden]（ROLE_VISIBLE_MAP 控制，bead 模式隐藏）
 * 两者是 AND 关系，任一命中即隐藏。 */

.toolbar-float .brush-size-row {
    margin: 0;
    padding-top: 6px;
    border-top: 1px solid var(--c-border);
    flex-direction: column;
    align-items: stretch;
    gap: 4px;
}

.toolbar-float .brush-size-row.is-hidden-by-tool,
.toolbar-float .brush-size-row[hidden] {
    display: none;
}

.toolbar-float .brush-size-row__label {
    font-size: 10px;
    color: var(--c-text-muted, #888);
    text-align: center;
}

.toolbar-float .brush-size-row__btns {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 3px;
}

.toolbar-float .brush-size {
    min-width: 0;
    min-height: 0;
    padding: 3px 0;
    font-size: 10px;
    border-radius: 6px;
}


/* V1.9（W2）：色系筛选 */

.hue-filter-row {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    margin-top: 10px;
}

.hue-filter {
    min-height: 30px;
    padding: 4px 10px;
    border: 1px solid var(--c-border);
    border-radius: 999px;
    background: var(--c-card);
    color: var(--c-text-sub);
    font-size: 12px;
    line-height: 1;
    transition: all 0.15s;
}

.hue-filter.is-active {
    color: #fff;
    background: var(--c-primary);
    border-color: var(--c-primary);
}


/* --------------------------------------------------------------------------
   8.5 V1.9（W4）文字转像素字面板
   -------------------------------------------------------------------------- */

.text-pixel-panel {
    margin-top: 10px;
    border: 1px solid var(--c-border);
    border-radius: var(--radius-btn);
    background: var(--c-card);
    overflow: hidden;
}

.text-pixel-panel__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 8px 12px;
    font-size: 13px;
    font-weight: 600;
    color: var(--c-text);
    background: var(--c-bg-soft, #f7f7f7);
    border-bottom: 1px solid var(--c-border);
}

.text-pixel-panel__close {
    width: 24px;
    height: 24px;
    border: none;
    background: transparent;
    color: var(--c-text-sub);
    font-size: 14px;
    line-height: 1;
    cursor: pointer;
}

.text-pixel-panel__body {
    padding: 12px;
}

.text-pixel-preview {
    display: grid;
    gap: 1px;
    justify-content: center;
    margin: 10px 0;
    padding: 8px;
    border: 1px dashed var(--c-border);
    border-radius: var(--radius-btn);
    background: var(--c-bg-soft, #fafafa);
    overflow: auto;
    max-height: 220px;
}

.text-pixel-preview__cell {
    width: 8px;
    height: 8px;
    border-radius: 1px;
    background: transparent;
}

.text-pixel-preview__cell.is-on {
    background: var(--c-primary);
}

.text-pixel-hint {
    margin: 6px 0 10px;
    font-size: 12px;
    line-height: 1.5;
    color: var(--c-text-sub);
}


/* T-35 阶段D-3（问题11 c）：截断时用红字提示 */

.text-pixel-hint.is-warning {
    color: var(--c-danger, #e5484d);
    font-weight: 600;
}


/* T-34 阶段一（K-18）：显式展示当前使用的色号 */


/* T-35 阶段D-5（问题11 d）：改为可点击的独立文字色选择器 */

.text-pixel-color {
    position: relative;
    margin: 8px 0 4px;
}

.text-pixel-color__btn {
    display: flex;
    align-items: center;
    gap: 6px;
    width: 100%;
    padding: 6px 8px;
    border: 1px solid var(--c-border);
    border-radius: var(--radius-btn);
    background: var(--c-bg-soft, #fafafa);
    font-size: 12px;
    color: var(--c-text-sub);
    cursor: pointer;
    text-align: left;
}

.text-pixel-color__btn:hover {
    border-color: var(--c-primary);
}


/* 独立色时用主色描边，一眼区分「跟随画笔」与「独立」 */

.text-pixel-color__btn.is-independent {
    border-color: var(--c-primary);
    background: var(--c-primary-soft, #fff4ef);
}

.text-pixel-color__swatch {
    width: 16px;
    height: 16px;
    flex: 0 0 auto;
    border: 1px solid var(--c-border);
    border-radius: 3px;
    background: #ffffff;
}

.text-pixel-color__code {
    margin-left: auto;
    font-family: var(--font-mono, ui-monospace, monospace);
    font-size: 12px;
    color: var(--c-text);
}

.text-pixel-color__arrow {
    flex: 0 0 auto;
    font-size: 10px;
    color: var(--c-text-sub);
}

.text-pixel-color__picker {
    position: absolute;
    z-index: 20;
    left: 0;
    right: 0;
    top: calc(100% + 4px);
    max-height: 168px;
    overflow-y: auto;
    padding: 6px;
    border: 1px solid var(--c-border);
    border-radius: var(--radius-btn);
    background: var(--c-surface, #ffffff);
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.14);
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(22px, 1fr));
    gap: 4px;
}

.text-pixel-color__picker[hidden] {
    display: none;
}

.text-pixel-color__cell {
    width: 100%;
    aspect-ratio: 1 / 1;
    padding: 0;
    border: 1px solid rgba(0, 0, 0, 0.12);
    border-radius: 3px;
    cursor: pointer;
}

.text-pixel-color__cell.is-selected {
    outline: 2px solid var(--c-primary);
    outline-offset: 1px;
}


/* T-34 阶段一（K-17）：盖章定位模式下的画布光标 */

.canvas-wrap.is-stamping canvas {
    cursor: crosshair;
}


/* --------------------------------------------------------------------------
   9. 统计区
   -------------------------------------------------------------------------- */

.stats-summary {
    font-size: 13px;
    color: var(--c-text-sub);
    margin-bottom: 12px;
}


/* T-34 阶段三（B-27）：按色分组引导条 */

.color-guide {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 8px 12px;
    padding: 10px 12px;
    margin-bottom: 12px;
    background: #fff7f3;
    border: 1px solid #ffd9c7;
    border-radius: var(--radius-btn);
}

.color-guide__main {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 6px;
    min-width: 0;
    font-size: 13px;
}

.color-guide__label {
    color: var(--c-text-sub);
}

.color-guide__swatch {
    display: inline-block;
    width: 16px;
    height: 16px;
    border-radius: 3px;
    border: 1px solid rgba(0, 0, 0, 0.12);
    flex: none;
}

.color-guide__code {
    font-weight: 600;
    color: var(--c-text);
}

.color-guide__name {
    color: var(--c-text-sub);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    max-width: 8em;
}

.color-guide__count {
    color: var(--c-primary, #FF7A45);
    font-weight: 600;
}

.color-guide__actions {
    display: flex;
    gap: 6px;
    flex: none;
}


/* 统计表中「当前按色拼」的行 */

.stats-table tbody tr.is-active-color {
    background: #fff1ea;
    box-shadow: inset 3px 0 0 var(--c-primary, #FF7A45);
}

.stats-table tbody tr.is-active-color td {
    font-weight: 600;
}

.stats-list {
    max-height: 320px;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    border: 1px solid var(--c-border);
    border-radius: var(--radius-btn);
}

.stats-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 14px;
}

.stats-table thead th {
    position: sticky;
    top: 0;
    z-index: 1;
    padding: 10px 12px;
    background: #fafafa;
    border-bottom: 1px solid var(--c-border);
    font-size: 13px;
    font-weight: 600;
    color: var(--c-text-sub);
    text-align: left;
}

.stats-table tbody td {
    padding: 10px 12px;
    border-bottom: 1px solid #f5f5f5;
    vertical-align: middle;
}

.stats-table tbody tr:last-child td {
    border-bottom: none;
}

.stats-table tbody tr {
    cursor: pointer;
    transition: background-color 0.15s;
}

.stats-table tbody tr:active {
    background: var(--c-primary-light);
}

.col-code {
    width: 70px;
}

.col-swatch {
    width: 48px;
}

.col-count {
    width: 80px;
    text-align: right !important;
}

.stats-table .col-count {
    text-align: right;
    font-weight: 600;
    color: var(--c-primary);
    white-space: nowrap;
}

.stats-table .cell-code {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 13px;
    color: var(--c-text-sub);
}

.stats-table .cell-swatch {
    display: inline-block;
    width: 24px;
    height: 24px;
    border-radius: 4px;
    border: 1px solid rgba(0, 0, 0, 0.08);
    vertical-align: middle;
}


/* --------------------------------------------------------------------------
   10. 导出区
   -------------------------------------------------------------------------- */

.export-actions {
    display: flex;
    flex-direction: column;
    gap: 10px;
}


/* T-35 阶段K-9（修【21】【23】）：「导出图纸时在格子内标注色号」勾选框
   从 #export-pro 高级区搬到 #export-free 基础区后，它夹在一堆整宽按钮中间。
   这里给它一个卡片式外观（浅底 + 边框 + 内边距），让它看起来是
   「导出前的一个选项」而不是一个孤零零的复选框，与上下按钮形成层次。 */

.check-row--export {
    padding: 8px 12px;
    border: 1px solid var(--c-border);
    border-radius: var(--radius-sm);
    background: var(--c-bg-sub);
    font-size: 13px;
}

.check-row--export:active {
    background: rgba(255, 122, 69, 0.08);
}


/* V1.2（L2）：高级版导出面板对免费用户可见但置灰 */

.export-actions--pro.is-locked {
    opacity: 0.62;
}

.export-actions--pro.is-locked .btn,
.export-actions--pro.is-locked select,
.export-actions--pro.is-locked input {
    cursor: not-allowed;
}

.pro-lock-hint {
    margin: 0;
    padding: 10px 12px;
    border: 1px dashed var(--c-primary);
    border-radius: var(--radius-sm);
    background: rgba(255, 122, 69, 0.06);
    font-size: 13px;
    line-height: 1.6;
    color: var(--c-text);
}

.pro-lock-hint a {
    color: var(--c-primary);
    font-weight: 600;
}


/* 注：T-32 已删除 .pro-lock-hint__sep ——
   T-30 把 #pro-lock-hint 改为纯说明文字后，js/main.js 不再动态创建
   <span class="pro-lock-hint__sep">，index.html 中亦无该元素，属死代码。 */


/* V1.2（K1）：导出元素勾选面板 */

.export-options {
    border: 1px solid var(--c-border);
    border-radius: var(--radius-sm);
    background: var(--c-bg-sub);
    overflow: hidden;
}

.export-options__head {
    padding: 10px 12px;
    font-size: 13px;
    font-weight: 600;
    color: var(--c-text);
    cursor: pointer;
    user-select: none;
    list-style: none;
}

.export-options__head::-webkit-details-marker {
    display: none;
}

.export-options__head::before {
    content: '▸';
    display: inline-block;
    margin-right: 6px;
    transition: transform 0.15s ease;
}

.export-options[open] .export-options__head::before {
    transform: rotate(90deg);
}

.export-options__body {
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding: 4px 12px 12px;
    border-top: 1px solid var(--c-border);
}


/* T-44 改动2（P-2）：「色号显示方式」下拉从高级区移到免费区后，
   与上方「格子内标注色号」勾选合并成一组，这里给它一点上间距做视觉分组。 */

.field-row--export-mode {
    margin-top: 2px;
}


/* T-44 改动3（P-3）：工程 / 分块并入导出面板底部，默认收起的折叠块。
   视觉上比主按钮弱一档（虚线分隔 + 浅底），避免抢走导出按钮的注意力。 */

.export-fold {
    margin-top: 14px;
    padding-top: 12px;
    border-top: 1px dashed var(--c-border);
}

.export-fold__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    min-height: 44px;
    font-size: 13px;
    font-weight: 600;
    color: var(--c-text-sub);
    cursor: pointer;
    user-select: none;
    list-style: none;
}

.export-fold__head::-webkit-details-marker {
    display: none;
}

.export-fold__title::before {
    content: '▸';
    display: inline-block;
    margin-right: 6px;
    transition: transform 0.15s ease;
}

.export-fold[open] .export-fold__title::before {
    transform: rotate(90deg);
}

.export-fold .hint {
    margin-top: 4px;
}

.export-fold .project-body,
.export-fold .split-body {
    margin-top: 10px;
}

.check-row {
    display: flex;
    align-items: center;
    gap: 8px;
    /* V2.4（T-25）：保证触摸目标 ≥44px */
    min-height: 44px;
    font-size: 13px;
    color: var(--c-text);
    cursor: pointer;
    line-height: 1.5;
}

.check-row input[type='checkbox'] {
    flex: 0 0 auto;
    width: 16px;
    height: 16px;
    accent-color: var(--c-primary);
    cursor: pointer;
}

.check-row>span:first-of-type {
    flex: 1 1 auto;
}

.check-row__tag {
    flex: 0 0 auto;
    padding: 1px 6px;
    border-radius: 4px;
    font-size: 11px;
    line-height: 1.6;
    color: #fff;
    background: var(--c-primary);
}

.check-row__tag--free {
    background: var(--c-success, #2E9E5B);
}

.check-row--disabled {
    opacity: 0.5;
    cursor: not-allowed;
}

.field-row--inline {
    margin-top: 2px;
}


/* --------------------------------------------------------------------------
   11. 页脚
   -------------------------------------------------------------------------- */

.footer {
    max-width: var(--max-width);
    margin: 0 auto;
    padding: 24px 16px 40px;
    text-align: center;
    font-size: 12px;
    color: var(--c-text-sub);
    line-height: 1.8;
}

.footer__sub {
    color: var(--c-text-weak);
}


/* --------------------------------------------------------------------------
   12. Toast
   -------------------------------------------------------------------------- */

.toast-container {
    position: fixed;
    left: 50%;
    bottom: 80px;
    z-index: 1000;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 8px;
    transform: translateX(-50%);
    pointer-events: none;
    width: max-content;
    max-width: 90vw;
}

.toast {
    padding: 10px 18px;
    border-radius: 20px;
    font-size: 14px;
    color: #fff;
    background: rgba(0, 0, 0, 0.78);
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
    animation: toast-in 0.2s ease-out;
    word-break: break-word;
}

.toast--success {
    background: rgba(82, 196, 26, 0.95);
}

.toast--warning {
    background: rgba(250, 173, 20, 0.95);
}

.toast--error {
    background: rgba(255, 77, 79, 0.95);
}

.toast.is-leaving {
    animation: toast-out 0.2s ease-in forwards;
}

@keyframes toast-in {
    from {
        opacity: 0;
        transform: translateY(10px);
    }
    to {
        opacity: 1;
        transform: translateY(0);
    }
}

@keyframes toast-out {
    from {
        opacity: 1;
        transform: translateY(0);
    }
    to {
        opacity: 0;
        transform: translateY(10px);
    }
}


/* --------------------------------------------------------------------------
   13. 模态框
   -------------------------------------------------------------------------- */

.modal-container {
    position: fixed;
    inset: 0;
    z-index: 1100;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 24px;
    background: rgba(0, 0, 0, 0.45);
    animation: fade-in 0.15s ease-out;
}

@keyframes fade-in {
    from {
        opacity: 0;
    }
    to {
        opacity: 1;
    }
}

.modal {
    width: 100%;
    max-width: 340px;
    max-height: 80vh;
    overflow-y: auto;
    padding: 20px;
    border-radius: var(--radius-card);
    background: var(--c-card);
    box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
    animation: modal-in 0.2s ease-out;
}


/* J-5（修【6】）：宽内容弹窗。
   现场反馈：「弹窗列表左右还要拉没显示齐全」「按钮显示不全内容」。
   原因：.modal 固定 max-width:340px，而颜色清单行 grid 的 min-content 约 406px，
   内容被压缩后按钮文字被裁掉。这里给宽弹窗放开到 min(92vw, 560px)。 */

.modal--wide {
    max-width: min(92vw, 560px);
}

@keyframes modal-in {
    from {
        opacity: 0;
        transform: scale(0.94);
    }
    to {
        opacity: 1;
        transform: scale(1);
    }
}

.modal__title {
    font-size: 17px;
    font-weight: 600;
    margin-bottom: 10px;
}

.modal__body {
    font-size: 14px;
    color: var(--c-text-sub);
    line-height: 1.7;
    white-space: pre-wrap;
    word-break: break-word;
}

.modal__body b {
    color: var(--c-text);
}

.modal__input {
    margin-top: 14px;
}

.modal__checkbox {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-top: 14px;
    font-size: 13px;
    color: var(--c-text-sub);
    cursor: pointer;
}

.modal__checkbox input {
    width: 16px;
    height: 16px;
    accent-color: var(--c-primary);
}

.modal__actions {
    display: flex;
    gap: 10px;
    margin-top: 20px;
    /* T-35 阶段K-8（修【6】）：按钮多时允许换行，而不是把每个按钮压窄 */
    flex-wrap: wrap;
    justify-content: flex-end;
}


/* T-35 阶段K-8（修【6】）：上一轮为什么没修好 ——
   J-5 保留了 flex:1，两个按钮各占一半宽度。当按钮文字较长
   （如「清除块数最少的颜色（A01 · 3 颗）」）时，一半宽度装不下，
   文字被压成两行 → 44px 高度装不下 → 用户看到「文字看不到」。
   改为「按内容宽度、不压缩、右对齐」，文字永远完整显示。 */

.modal__actions .btn {
    flex: 0 1 auto;
    min-width: 88px;
    max-width: 100%;
}

.modal__error {
    margin-top: 10px;
    font-size: 13px;
    color: var(--c-danger);
    min-height: 18px;
}


/* V1.2（K4）：导出前成品预览
   T-39 块3（C-17）：从「静态一张图」升级为「左预览 + 右选项」双栏布局。
   左栏是实时预览图，右栏是导出选项；选项变更 200ms 防抖后重绘左栏。 */

.export-preview {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 200px;
    gap: 14px;
    align-items: start;
}


/* 左栏：预览图舞台（img + 「更新中…」浮层） */

.export-preview__stage {
    position: relative;
    min-width: 0;
    display: flex;
    align-items: flex-start;
    justify-content: center;
}

.export-preview__img {
    display: block;
    width: 100%;
    max-height: 52vh;
    object-fit: contain;
    border: 1px solid var(--c-border);
    border-radius: 8px;
    background: #ffffff;
}


/* 重绘期间的「更新中…」提示：浮在预览图上方，不撑开布局 */

.export-preview__busy {
    position: absolute;
    top: 8px;
    right: 8px;
    margin: 0;
    padding: 3px 9px;
    border-radius: 999px;
    font-size: 12px;
    line-height: 1.5;
    color: #ffffff;
    background: rgba(0, 0, 0, 0.55);
    pointer-events: none;
}


/* 右栏：导出选项 */

.export-preview__side {
    display: flex;
    flex-direction: column;
    gap: 8px;
    min-width: 0;
}

.export-preview__side-title {
    margin: 0;
    font-size: 13px;
    font-weight: 600;
    color: var(--c-text);
}

.export-preview__side .check-row {
    font-size: 13px;
}

.export-preview__side .field-row {
    margin: 0;
}

.export-preview__note {
    margin: 2px 0 0;
    font-size: 12px;
    line-height: 1.6;
    color: var(--c-text-sub);
}

.export-preview__tip {
    margin: 0;
    font-size: 13px;
    line-height: 1.6;
    color: var(--c-text-sub);
}


/* 窄屏（<768px）：双栏改为上下排列，预览图在上、选项在下 */

@media (max-width: 767px) {
    .export-preview {
        grid-template-columns: minmax(0, 1fr);
    }
    .export-preview__img {
        max-height: 40vh;
    }
}


/* 注：T-32 已删除 .color-picker-list / .color-picker-item / .is-active ——
   清除颜色弹窗实际使用 .swatch-grid / .swatch（见 js/main.js handleClearColor），
   上述三条规则全站无任何引用，属死代码。 */


/* --------------------------------------------------------------------------
   14. 加载遮罩
   -------------------------------------------------------------------------- */

.loading-overlay {
    position: fixed;
    inset: 0;
    z-index: 1200;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(255, 255, 255, 0.82);
    backdrop-filter: blur(2px);
}

.loading-box {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 14px;
    padding: 24px 32px;
    border-radius: var(--radius-card);
    background: var(--c-card);
    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.12);
}

.spinner {
    width: 34px;
    height: 34px;
    border: 3px solid var(--c-primary-light);
    border-top-color: var(--c-primary);
    border-radius: 50%;
    animation: spin 0.8s linear infinite;
}

@keyframes spin {
    to {
        transform: rotate(360deg);
    }
}

.loading-text {
    font-size: 14px;
    color: var(--c-text-sub);
    white-space: pre-line;
    text-align: center;
}


/* --------------------------------------------------------------------------
   14.5 V1.1 阶段 E：付费方案选择 + 支持者滚屏
   -------------------------------------------------------------------------- */


/* 付费方案列表（弹窗内） */

.plan-list {
    display: flex;
    flex-direction: column;
    gap: 10px;
    margin: 12px 0;
}

.plan-item {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    padding: 12px;
    border: 1.5px solid var(--c-border);
    border-radius: 10px;
    cursor: pointer;
    transition: border-color 0.15s, background 0.15s;
}

.plan-item.is-active {
    border-color: var(--c-primary);
    background: var(--c-primary-light);
}

.plan-item__radio {
    margin-top: 3px;
    accent-color: var(--c-primary);
    flex-shrink: 0;
}

.plan-item__main {
    flex: 1;
    min-width: 0;
}

.plan-item__head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 8px;
}

.plan-item__title {
    font-size: 15px;
    font-weight: 600;
    color: var(--c-text);
}

.plan-item__price {
    font-size: 16px;
    font-weight: 700;
    color: var(--c-primary);
    flex-shrink: 0;
}

.plan-item__desc {
    margin: 4px 0 0;
    font-size: 13px;
    color: var(--c-text-sub);
}

.plan-item__note {
    margin: 4px 0 0;
    font-size: 12px;
    color: var(--c-text-muted, #999);
    line-height: 1.5;
}


/* 弹窗底部提示（V1.8：优惠券抵扣提示） */

.modal__tip {
    margin: 12px 0 0;
    font-size: 12px;
    line-height: 1.6;
    color: var(--c-text-sub);
}


/* 注：T-32 已删除 .modal__tip--coupon —— V2.1 纯公益化后优惠券抵扣提示已下线，
   全站无任何元素使用该修饰类，属死代码。 */


/* 支持者滚屏
   注：T-32 起 .welfare__roll 容器样式统一收敛到 §16.4（纵向列表滚动），
   此处只保留 js/roll.js 实际渲染的 chip 样式，避免同名选择器重复定义。 */

.welfare__chip {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    flex-shrink: 0;
    padding: 5px 10px;
    border-radius: 999px;
    background: var(--c-primary-light);
    font-size: 13px;
    white-space: nowrap;
}

.welfare__chip-heart {
    font-size: 12px;
}

.welfare__chip-name {
    color: var(--c-text);
    max-width: 8em;
    overflow: hidden;
    text-overflow: ellipsis;
}

.welfare__chip-amount {
    color: var(--c-primary);
    font-weight: 600;
}


/* .welfare__empty 样式统一在 §16.4 定义（T-32 去重） */


/* --------------------------------------------------------------------------
   14.6 V1.1 阶段 F-批1：工程管理 + 描边/换色弹窗
   -------------------------------------------------------------------------- */


/* 工程面板主体（折叠区） */

.project-body {
    display: flex;
    flex-direction: column;
    gap: 10px;
    margin-top: 10px;
}


/* 工程列表 */

.project-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 8px;
    max-height: 260px;
    overflow-y: auto;
}

.project-item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    padding: 10px 12px;
    border: 1px solid var(--c-border);
    border-radius: var(--radius-sm);
    background: var(--c-bg-sub);
    transition: border-color 0.15s, background 0.15s;
}

.project-item:hover {
    border-color: var(--c-primary);
    background: var(--c-primary-light);
}

.project-item__info {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
    flex: 1;
}

.project-item__name {
    font-size: 14px;
    font-weight: 600;
    color: var(--c-text);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.project-item__meta {
    font-size: 12px;
    color: var(--c-text-sub);
}

.project-item__actions {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-shrink: 0;
}

.project-empty {
    font-size: 13px;
    color: var(--c-text-sub);
    text-align: center;
    padding: 12px 0;
}


/* 描边 / 换色弹窗表单 */

.outline-form,
.replace-form {
    display: flex;
    flex-direction: column;
    gap: 12px;
}


/* T-35 阶段C-7（问题8）：描边弹窗 —— 示意图 + A/B 双色块 + 实时预览 */

.outline-form__diagram {
    display: grid;
    grid-template-columns: repeat(5, 1fr);
    gap: 2px;
    width: 132px;
    margin: 0 auto;
    padding: 6px;
    border-radius: 10px;
    background: var(--bg-sub, #f5f5f5);
}

.outline-form__diagram-cell {
    display: block;
    aspect-ratio: 1 / 1;
    border-radius: 2px;
}

.outline-form__diagram-cell.is-pattern {
    background: var(--text-2, #666);
}

.outline-form__diagram-cell.is-ring-b {
    background: var(--primary, #ff7a45);
    opacity: 0.55;
}

.outline-form__diagram-cell.is-ring-a {
    background: var(--primary, #ff7a45);
}

.outline-form__tip {
    margin: 0;
    font-size: 12px;
    line-height: 1.5;
    color: var(--text-3, #888);
    text-align: center;
}

.outline-form__flow {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 10px;
    padding: 10px;
    border-radius: 10px;
    background: var(--bg-sub, #f5f5f5);
}

.outline-form__side {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 4px;
    min-width: 84px;
}

.outline-form__tag {
    font-size: 11px;
    font-weight: 700;
    color: var(--text-3, #888);
    letter-spacing: 0.04em;
}

.outline-form__swatch {
    display: block;
    width: 56px;
    height: 56px;
    border-radius: 10px;
    border: 1px solid rgba(0, 0, 0, 0.12);
    background-image: linear-gradient(45deg, #e6e6e6 25%, transparent 25%), linear-gradient(-45deg, #e6e6e6 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #e6e6e6 75%), linear-gradient(-45deg, transparent 75%, #e6e6e6 75%);
    background-size: 10px 10px;
    background-position: 0 0, 0 5px, 5px -5px, -5px 0;
}

.outline-form__code {
    font-size: 14px;
    font-weight: 700;
    color: var(--text-1, #222);
}

.outline-form__name {
    max-width: 96px;
    font-size: 11px;
    color: var(--text-3, #888);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.outline-form__select {
    width: 100%;
    margin-top: 2px;
}

.outline-form__arrow {
    font-size: 22px;
    font-weight: 700;
    color: var(--primary, #ff7a45);
    line-height: 1;
}

.outline-form__preview {
    margin: 0;
    padding: 8px 10px;
    border-radius: 8px;
    font-size: 13px;
    font-weight: 600;
    color: var(--primary, #ff7a45);
    background: var(--primary-weak, rgba(255, 122, 69, 0.1));
    text-align: center;
}

@media (max-width: 420px) {
    .outline-form__swatch {
        width: 44px;
        height: 44px;
    }
    .outline-form__side {
        min-width: 68px;
    }
}


/* T-35 阶段C-5（问题9）：换色弹窗 A → B 双色块 + 箭头 + 影响格数预览 */

.replace-form__flow {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 10px;
    padding: 12px 8px;
    border-radius: var(--radius-md, 10px);
    background: var(--c-bg-sub);
}

.replace-form__side {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 4px;
    min-width: 84px;
}

.replace-form__tag {
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.08em;
    color: var(--c-text-sub);
}

.replace-form__swatch {
    display: block;
    width: 56px;
    height: 56px;
    border-radius: 10px;
    border: 1px solid rgba(0, 0, 0, 0.12);
    box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.5);
    /* 透明色也能看清边界 */
    background-image: linear-gradient(45deg, #e6e6e6 25%, transparent 25%), linear-gradient(-45deg, #e6e6e6 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #e6e6e6 75%), linear-gradient(-45deg, transparent 75%, #e6e6e6 75%);
    background-size: 10px 10px;
    background-position: 0 0, 0 5px, 5px -5px, -5px 0;
}

.replace-form__code {
    font-size: 14px;
    font-weight: 700;
    color: var(--c-text);
}

.replace-form__name {
    font-size: 11px;
    color: var(--c-text-sub);
    max-width: 96px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.replace-form__arrow {
    font-size: 22px;
    font-weight: 700;
    color: var(--c-primary);
    line-height: 1;
}

.replace-form__row {
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.replace-form__label {
    font-size: 12px;
    font-weight: 600;
    color: var(--c-text-sub);
}

.replace-form__preview {
    margin: 0;
    padding: 8px 10px;
    font-size: 12px;
    font-weight: 600;
    color: var(--c-primary);
    border-radius: var(--radius-sm);
    background: var(--c-primary-weak, rgba(255, 122, 69, 0.1));
}

.replace-form__tip {
    font-size: 12px;
    color: var(--c-text-sub);
    line-height: 1.6;
    padding: 8px 10px;
    border-radius: var(--radius-sm);
    background: var(--c-bg-sub);
}

@media (max-width: 420px) {
    .replace-form__swatch {
        width: 44px;
        height: 44px;
    }
    .replace-form__side {
        min-width: 68px;
    }
}


/* 危险操作文字按钮（删除工程等） */

.btn--danger-text {
    background: transparent;
    border: none;
    color: var(--c-danger, #E5484D);
    font-size: 13px;
    padding: 4px 6px;
    cursor: pointer;
    border-radius: var(--radius-sm);
    transition: background 0.15s;
}

.btn--danger-text:hover {
    background: rgba(229, 72, 77, 0.1);
}


/* --------------------------------------------------------------------------
   14.7 V1.1 阶段 F-批2：分块拆分 + 豆子库存
   -------------------------------------------------------------------------- */


/* 分块拆分面板 */

.split-body {
    display: flex;
    flex-direction: column;
    gap: 10px;
    margin-top: 10px;
}

.split-size {
    display: flex;
    align-items: center;
    gap: 6px;
}

.split-size__x {
    color: var(--c-text-sub);
    font-size: 13px;
}

.input--num {
    width: 68px;
    text-align: center;
}

.split-info {
    font-size: 13px;
    color: var(--c-text-sub);
    padding: 8px 10px;
    border-radius: var(--radius-sm);
    background: var(--c-bg-sub);
}

.split-preview {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(64px, 1fr));
    gap: 6px;
    max-height: 240px;
    overflow-y: auto;
    padding: 8px;
    border: 1px solid var(--c-border);
    border-radius: var(--radius-sm);
    background: var(--c-bg-sub);
}

.split-preview__item {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 2px;
    padding: 6px 4px;
    border-radius: var(--radius-sm);
    background: var(--c-bg);
    border: 1px solid var(--c-border);
    font-size: 11px;
    color: var(--c-text-sub);
    text-align: center;
}

.split-preview__item b {
    font-size: 12px;
    color: var(--c-text);
}

.split-preview__item--empty {
    opacity: 0.45;
}


/* 豆子库存面板 */

.stock-body {
    display: flex;
    flex-direction: column;
    gap: 10px;
    margin-top: 10px;
}

.stock-toolbar {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-wrap: wrap;
}

.stock-toolbar .input--search {
    flex: 1;
    min-width: 120px;
}

.stock-shortage {
    font-size: 13px;
    line-height: 1.7;
    color: var(--c-text);
    padding: 10px 12px;
    border-radius: var(--radius-sm);
    background: rgba(229, 72, 77, 0.08);
    border-left: 3px solid var(--c-danger, #E5484D);
    white-space: pre-wrap;
}

.stock-list {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
    gap: 6px;
    max-height: 320px;
    overflow-y: auto;
}

.stock-item {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 6px 8px;
    border: 1px solid var(--c-border);
    border-radius: var(--radius-sm);
    background: var(--c-bg-sub);
}

.stock-item__code {
    font-size: 12px;
    font-weight: 600;
    color: var(--c-text);
    flex-shrink: 0;
}

.stock-item__input {
    flex: 1;
    min-width: 0;
    width: 100%;
    padding: 3px 6px;
    font-size: 12px;
    text-align: center;
    border: 1px solid var(--c-border);
    border-radius: var(--radius-sm);
    background: var(--c-bg);
    color: var(--c-text);
}

.stock-item__input:focus {
    outline: none;
    border-color: var(--c-primary);
}

.stock-empty {
    grid-column: 1 / -1;
    font-size: 13px;
    color: var(--c-text-sub);
    text-align: center;
    padding: 12px 0;
}


/* --------------------------------------------------------------------------
   T-35 阶段C-3 / C-4（问题5）：颜色清单弹窗
   现场反馈：「清色跟撤销有什么区别？」→ 改为列出画布上每种颜色，
   按块数升序，每行可「清除此色」/「合并到…」，顶部有快捷动作。
   -------------------------------------------------------------------------- */

.color-list-modal {
    display: flex;
    flex-direction: column;
    gap: 10px;
    max-height: 60vh;
}

.color-list-modal__quick {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    padding-bottom: 8px;
    border-bottom: 1px solid var(--c-border);
}


/* T-35 阶段K-8（修【6】）：快捷区按钮按内容宽度排布，不被 flex 压缩。
   上一轮 J-5 只给行内按钮加了 flex:0 0 auto，漏了快捷区，
   导致「清除块数最少的颜色（A01 · 3 颗）」被压成两行后文字被裁。 */

.color-list-modal__quick .btn {
    flex: 0 0 auto;
    max-width: 100%;
}

.color-list-modal__tip {
    margin: 0 0 8px;
    font-size: 13px;
    color: var(--c-text-sub);
}

.color-list-modal__list {
    display: flex;
    flex-direction: column;
    overflow-y: auto;
    border: 1px solid var(--c-border);
    border-radius: var(--radius-sm);
}

.color-list-modal__row {
    display: grid;
    /* J-5（修【6】）：列宽改为可收缩的弹性列，避免 min-content 撑破弹窗。
       原 minmax(120px,1.6fr) 64px 72px minmax(150px,1.4fr) 的最小宽度合计约 406px，
       在 340px 弹窗内必然溢出，导致右侧按钮文字被裁、需要左右拖动。 */
    grid-template-columns: minmax(0, 1.6fr) 56px 64px minmax(0, 1.4fr);
    align-items: center;
    gap: 8px;
    padding: 6px 10px;
    font-size: 13px;
    border-bottom: 1px solid var(--c-border);
}

.color-list-modal__row:last-child {
    border-bottom: none;
}

.color-list-modal__row--head {
    position: sticky;
    top: 0;
    z-index: 1;
    font-size: 12px;
    font-weight: 600;
    color: var(--c-text-sub);
    background: var(--c-bg-sub);
}

.color-list-modal__color {
    display: flex;
    align-items: center;
    gap: 6px;
    min-width: 0;
}

.color-list-modal__name {
    font-size: 12px;
    color: var(--c-text-sub);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.color-list-modal__count {
    font-variant-numeric: tabular-nums;
    font-weight: 600;
}

.color-list-modal__stock {
    font-size: 12px;
    color: var(--c-text-sub);
}

.color-list-modal__stock.is-enough {
    color: var(--c-success, #2e9e5b);
}

.color-list-modal__stock.is-short {
    color: var(--c-danger, #d9534f);
}

.color-list-modal__actions {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    justify-content: flex-end;
    min-width: 0;
}


/* J-5（修【6】）：行内按钮统一尺寸与不换行，避免文字被压成竖排或裁掉 */

.color-list-modal__actions .btn {
    flex: 0 0 auto;
    padding: 4px 10px;
    font-size: 12px;
    line-height: 1.4;
    white-space: nowrap;
    /* T-35 阶段K-8（修【6】）：行内按钮给一个最小宽度，
       保证「清除此色」「合并到…」两个按钮宽度一致、不被压扁。 */
    min-width: 64px;
    min-height: 28px;
}


/* T-35 阶段K-8（修【6】）：统一弹窗内按钮配色。
   用户原话：「关闭按钮左边的按钮上面文字看不到，为啥按钮风格不一样？」
   —— 弹窗底部是 btn--primary（实心橙），行内是 btn--ghost（灰底灰字），
   两者视觉权重差异过大，看起来像两套设计。
   这里把弹窗内所有次级按钮统一为 btn--outline（白底 + 主色边框 + 主色字），
   与底部主按钮形成「主/次」同一色系的层次，而不是「橙 vs 灰」的割裂。 */

.modal .btn--ghost {
    color: var(--c-primary);
    background: var(--c-card);
    border-color: rgba(255, 122, 69, 0.45);
}

.modal .btn--ghost:active:not(:disabled) {
    background: var(--c-primary-light, #fff5f0);
}

.color-list-modal__pick {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
    gap: 6px;
    max-height: 50vh;
    overflow-y: auto;
}

.color-list-modal__pick-item {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 6px 8px;
    font-size: 12px;
    text-align: left;
    border: 1px solid var(--c-border);
    border-radius: var(--radius-sm);
    background: var(--c-bg);
    color: var(--c-text);
    cursor: pointer;
}

.color-list-modal__pick-item:hover {
    border-color: var(--c-primary);
    background: var(--c-bg-sub);
}

@media (max-width: 560px) {
    .color-list-modal__row {
        grid-template-columns: minmax(0, 1.4fr) 48px minmax(0, 1.2fr);
    }
    .color-list-modal__row> :nth-child(3) {
        display: none;
    }
}


/* --------------------------------------------------------------------------
   15. 响应式
   -------------------------------------------------------------------------- */


/* 小屏手机 */

@media (max-width: 374px) {
    body {
        font-size: 13px;
    }
    .main {
        padding: 12px;
        gap: 12px;
    }
    .card {
        padding: 12px;
    }
    .topbar {
        padding: 0 12px;
    }
    .topbar__title {
        font-size: 16px;
    }
    /* T-49：原 `.tool { flex: 1 1 calc(25% - 6px); min-width: 46px }` 已删除。
       那是「画布下方横排工具栏」时代的规则（4 列等分 + 最小 46px 触控尺寸），
       T-48 把工具栏改成悬浮竖排 2 列网格后，flex 属性对 grid item 无效，
       但 min-width/min-height 46px 仍会生效，把 32px 图标撑成 46px 方块，
       在 320px 小屏上直接把悬浮卡撑宽到 100px+。 */
}


/* 桌面端 */

@media (min-width: 768px) {
    .main {
        padding: 24px 16px;
    }
    .canvas-wrap {
        min-height: 320px;
    }
    .stats-list {
        max-height: 400px;
    }
}


/* V2.4（T-25）：平板竖屏（768~1023px）
   T-49：原「13 个工具按钮横向滚动」的规则已删除 —— 那是横排工具栏时代的
   补丁。T-48 改成悬浮竖排 2 列网格后，`.toolbar` 是 grid 容器，
   flex-wrap / flex 全部失效，而 `overflow-x: auto` 反而会在网格上
   制造一条无意义的横向滚动条。这里只保留画布最小高度。 */

@media (min-width: 768px) and (max-width: 1023px) {
    .canvas-wrap {
        min-height: 360px;
    }
}


/* V2.4（T-25）：平板横屏 / 小桌面（1024~1365px）
   放宽主内容宽度，给画布更多可视空间 */

@media (min-width: 1024px) and (max-width: 1365px) {
    .main {
        max-width: 900px;
    }
    .canvas-wrap {
        min-height: 420px;
    }
}


/* V2.4（T-25）：横屏（高度受限）
   手机 / 平板横屏时纵向空间紧张，压缩留白优先保证画布可视高度 */

@media (orientation: landscape) and (max-height: 500px) {
    .topbar {
        padding: 0 12px;
    }
    .main {
        padding: 8px;
        gap: 8px;
    }
    .card {
        padding: 10px;
    }
    .canvas-wrap {
        min-height: 180px;
    }
}


/* 减少动画偏好 */

@media (prefers-reduced-motion: reduce) {
    * {
        animation-duration: 0.01ms !important;
        transition-duration: 0.01ms !important;
    }
}


/* --------------------------------------------------------------------------
   16. V1.1 新增：尺寸预设 / 底图控制 / 导出模式 / 公益公示 / 页脚合规
   -------------------------------------------------------------------------- */


/* --- 16.1 尺寸快捷预设按钮组（B2） --- */

.preset-row {
    display: flex;
    flex-direction: column;
    gap: 6px;
    margin-bottom: 12px;
}

.preset-row__label {
    font-size: 13px;
    color: var(--c-text-sub);
}

.preset-row__btns {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
}

.preset-btn {
    flex: 1 1 auto;
    min-width: 56px;
    /* V2.4（T-25）：保证触摸目标 ≥44px */
    min-height: 44px;
    padding: 6px 8px;
    font-size: 12px;
    font-family: inherit;
    color: var(--c-text);
    background: var(--c-bg-sub);
    border: 1px solid var(--c-border);
    border-radius: var(--radius-btn);
    cursor: pointer;
    transition: background 0.15s, border-color 0.15s, color 0.15s;
    -webkit-tap-highlight-color: transparent;
}

.preset-btn:hover {
    border-color: var(--c-primary);
    color: var(--c-primary);
}

.preset-btn:active {
    background: var(--c-primary-light);
}

.preset-btn.is-active {
    background: var(--c-primary-light);
    border-color: var(--c-primary);
    color: var(--c-primary);
    font-weight: 600;
}


/* --- 16.2 底图显隐与透明度（B3） --- */


/* 底图透明度滑块行：复用 .slider-row，仅补充隐藏态 */

#base-image-opacity-row[hidden] {
    display: none;
}


/* --- 16.3 导出模式选择（B7） --- */

.field-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    margin-bottom: 12px;
}

.field-row__label {
    flex: 0 0 auto;
    font-size: 13px;
    color: var(--c-text-sub);
}

.input--select {
    flex: 1 1 auto;
    min-width: 0;
    padding: 8px 10px;
    font-size: 13px;
    font-family: inherit;
    color: var(--c-text);
    background: var(--c-bg-sub);
    border: 1px solid var(--c-border);
    border-radius: var(--radius-btn);
    cursor: pointer;
    appearance: none;
    -webkit-appearance: none;
    background-image: linear-gradient(45deg, transparent 50%, var(--c-text-sub) 50%), linear-gradient(135deg, var(--c-text-sub) 50%, transparent 50%);
    background-position: calc(100% - 16px) 50%, calc(100% - 11px) 50%;
    background-size: 5px 5px, 5px 5px;
    background-repeat: no-repeat;
    padding-right: 30px;
}

.input--select:focus {
    outline: none;
    border-color: var(--c-primary);
    box-shadow: 0 0 0 2px var(--c-primary-light);
}


/* --- 16.3.1 支持项目卡片（T-29 → T-31 已合并） ---
   T-29 曾新建 #support-panel（.card--support）把打赏入口移出导出区；
   T-31 发现它与 #welfare-panel 的合规声明、打赏按钮完全重复，
   已删除该卡片，打赏入口并入 #welfare-panel。
   故 .card--support / .support-actions 样式一并移除，避免死代码。 */


/* --- 16.4 公益公示区（B8） ---
   注：T-31 起本区同时承载「打赏入口 + 合规声明」，是全站唯一的支持入口。 */

.card--welfare {
    border: 1px solid var(--c-primary-light);
    background: linear-gradient(180deg, var(--c-primary-light) 0%, var(--c-bg) 60%);
}

.welfare__desc {
    margin: 0 0 10px;
    font-size: 13px;
    line-height: 1.7;
    color: var(--c-text-sub);
}

.welfare__roll {
    max-height: 168px;
    overflow-y: auto;
    padding: 8px 10px;
    background: var(--c-bg-sub);
    border: 1px solid var(--c-border);
    border-radius: var(--radius-btn);
    font-size: 13px;
    line-height: 1.9;
    color: var(--c-text);
    -webkit-overflow-scrolling: touch;
}


/* 注：T-32 已删除 .welfare__roll-item / -name / -meta ——
   js/roll.js 实际渲染的是 .welfare__chip / -heart / -name / -amount，
   上述三条规则全站无任何引用，属死代码。 */

.welfare__empty {
    padding: 12px 0;
    text-align: center;
    font-size: 13px;
    color: var(--c-text-sub);
}

.welfare__actions {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin-top: 12px;
}

.welfare__actions .btn {
    flex: 1 1 auto;
}


/* --- 16.5 页脚合规链接（B8） --- */

.footer__links {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: 4px 8px;
    margin-bottom: 8px;
}

.footer__links .link {
    font-size: 12px;
    color: var(--c-text-sub);
    text-decoration: none;
    transition: color 0.15s;
}

.footer__links .link:hover {
    color: var(--c-primary);
    text-decoration: underline;
}

.footer__sep {
    font-size: 12px;
    color: var(--c-border);
    user-select: none;
}

.footer__sub {
    margin: 4px 0 0;
    font-size: 12px;
    line-height: 1.7;
    color: var(--c-text-sub);
    text-align: center;
}

.footer__sub .link {
    color: var(--c-text-sub);
    text-decoration: none;
}

.footer__sub .link:hover {
    color: var(--c-primary);
    text-decoration: underline;
}


/* --- 16.6 V1.1 响应式补充 --- */

@media (max-width: 374px) {
    .preset-btn {
        min-width: 48px;
        font-size: 11px;
        padding: 5px 6px;
    }
    .field-row {
        flex-direction: column;
        align-items: stretch;
        gap: 6px;
    }
    .input--select {
        width: 100%;
    }
}


/* --- 17. V1.1（F4）拼豆进度打勾 --- */

.progress-bar {
    margin-top: 12px;
    padding: 12px;
    border-radius: var(--radius-sm);
    background: var(--c-bg-sub);
    border: 1px solid var(--c-border);
}

.progress-bar__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 8px;
}

.progress-bar__label {
    font-size: 13px;
    font-weight: 600;
    color: var(--c-text);
}

.progress-bar__value {
    font-size: 13px;
    font-variant-numeric: tabular-nums;
    color: var(--c-text-sub);
}

.progress-bar__track {
    height: 10px;
    border-radius: 999px;
    background: rgba(0, 0, 0, 0.08);
    overflow: hidden;
}

.progress-bar__fill {
    height: 100%;
    width: 0;
    border-radius: 999px;
    background: linear-gradient(90deg, #2E9E5B, #4CC97A);
    transition: width 0.25s ease;
}

.progress-bar__actions {
    display: flex;
    justify-content: flex-end;
    margin-top: 10px;
}


/* T-35 阶段G-4（问题12/13）：逐色进度清单
   现场反馈：「只知道总进度，不知道现在该拼哪个颜色、这个色还剩几颗」。
   每行 = 色块 + 色号 + 已拼/总数 + 单色进度条 + 定位按钮。 */

.progress-colors {
    margin-top: 12px;
    padding: 12px;
    border-radius: var(--radius-sm);
    background: var(--c-bg-sub);
    border: 1px solid var(--c-border);
}

.progress-colors[hidden] {
    display: none;
}

.progress-colors__head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 8px;
    margin-bottom: 8px;
}

.progress-colors__title {
    font-size: 13px;
    font-weight: 600;
    color: var(--c-text);
}

.progress-colors__hint {
    font-size: 11px;
    color: var(--c-text-muted, #8c8c8c);
}

.progress-colors__list {
    display: flex;
    flex-direction: column;
    gap: 4px;
    margin: 0;
    padding: 0;
    list-style: none;
    max-height: 260px;
    overflow-y: auto;
}

.progress-colors__empty {
    padding: 8px 0;
    font-size: 12px;
    color: var(--c-text-muted, #8c8c8c);
    text-align: center;
}

.progress-color-row {
    display: grid;
    grid-template-columns: 18px 1fr auto auto;
    align-items: center;
    gap: 8px;
    padding: 6px 8px;
    border-radius: 6px;
    background: var(--c-card);
    border: 1px solid transparent;
    cursor: pointer;
    transition: border-color 0.15s ease, background 0.15s ease;
}

.progress-color-row:hover {
    border-color: var(--c-primary, #FF7A45);
}

.progress-color-row.is-active {
    border-color: var(--c-primary, #FF7A45);
    background: rgba(255, 122, 69, 0.08);
}

.progress-color-row.is-done {
    opacity: 0.6;
}

.progress-color-row__swatch {
    width: 18px;
    height: 18px;
    border-radius: 4px;
    border: 1px solid rgba(0, 0, 0, 0.15);
    flex-shrink: 0;
}

.progress-color-row__main {
    display: flex;
    flex-direction: column;
    gap: 3px;
    min-width: 0;
}

.progress-color-row__label {
    display: flex;
    align-items: baseline;
    gap: 6px;
    font-size: 12px;
    line-height: 1.2;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.progress-color-row__code {
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    color: var(--c-text);
}

.progress-color-row__name {
    color: var(--c-text-muted, #8c8c8c);
    overflow: hidden;
    text-overflow: ellipsis;
}

.progress-color-row__track {
    height: 5px;
    border-radius: 999px;
    background: rgba(0, 0, 0, 0.08);
    overflow: hidden;
}

.progress-color-row__fill {
    height: 100%;
    width: 0;
    border-radius: 999px;
    background: linear-gradient(90deg, #2E9E5B, #4CC97A);
    transition: width 0.25s ease;
}

.progress-color-row__count {
    font-size: 12px;
    font-variant-numeric: tabular-nums;
    color: var(--c-text-sub);
    white-space: nowrap;
}

.progress-color-row__locate {
    padding: 3px 8px;
    border: 1px solid var(--c-border);
    border-radius: 999px;
    background: transparent;
    color: var(--c-text-sub);
    font-size: 11px;
    line-height: 1.2;
    cursor: pointer;
    white-space: nowrap;
}

.progress-color-row__locate:hover {
    border-color: var(--c-primary, #FF7A45);
    color: var(--c-primary, #FF7A45);
}


/* 进度模式下画布光标提示「可点选」 */

.canvas-wrap.is-progress-mode #canvas {
    cursor: pointer;
}


/* --------------------------------------------------------------------------
   18. T-34 阶段五A（B-39）：导入已分格图纸
   -------------------------------------------------------------------------- */


/* --- 18.1 入口按钮（位于「上传图片」下方，独立入口） --- */

.btn--import-pattern {
    margin-top: 8px;
}


/* --- 18.2 面板容器 --- */

.import-pattern-panel[hidden] {
    display: none;
}

.import-pattern-panel .card__head {
    align-items: center;
}

.import-pattern-panel .card__title {
    margin-bottom: 0;
}


/* --- 18.3 步骤块 --- */

.import-step {
    margin-top: 14px;
    padding-top: 14px;
    border-top: 1px dashed var(--c-border);
}

.import-step[hidden] {
    display: none;
}

.import-step:first-of-type {
    margin-top: 0;
    padding-top: 0;
    border-top: none;
}

.import-step__head {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-bottom: 10px;
}

.import-step__no {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 20px;
    height: 20px;
    font-size: 12px;
    font-weight: 700;
    color: #fff;
    background: var(--c-primary);
    border-radius: 50%;
}

.import-step__title {
    font-size: 14px;
    font-weight: 600;
    color: var(--c-text);
}


/* --- 18.4 裁剪框（绝对定位覆盖在图片上，四角可拖拽） --- */

.import-crop {
    position: relative;
    width: 100%;
    max-height: 320px;
    margin-bottom: 12px;
    overflow: hidden;
    background: #f2f2f2;
    border: 1px solid var(--c-border);
    border-radius: var(--radius-sm);
    /* 触摸拖拽时不触发页面滚动 */
    touch-action: none;
    user-select: none;
    -webkit-user-select: none;
}

.import-crop__img {
    display: block;
    width: 100%;
    height: auto;
    max-height: 320px;
    object-fit: contain;
    pointer-events: none;
    -webkit-user-drag: none;
}


/* T-47 T47-1b v3：框选引导文案（机器只数格子，位置由用户自己框） */

.import-crop__hint {
    margin: -4px 0 12px;
    font-size: 12px;
    line-height: 1.5;
    color: var(--c-text-sub);
}


/* 裁剪框本体：半透明遮罩 + 高亮边框 */

.import-crop__box {
    position: absolute;
    box-sizing: border-box;
    border: 2px solid var(--c-primary);
    box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.42);
    cursor: move;
    touch-action: none;
}


/* 网格预览线：用 repeating-linear-gradient 画等分线，避免插入大量 DOM */

.import-crop__grid {
    position: absolute;
    inset: 0;
    pointer-events: none;
    background-repeat: repeat;
    background-position: 0 0;
}


/* 四角拖拽手柄
 *
 * T-43 T43-8（修「裁剪框拖不动角」）：
 * 原样式用负偏移（top:-9px / left:-9px）把手柄挂在框**外面**，
 * 而 `.import-crop` 是 `overflow:hidden` —— 裁剪框贴到容器边缘时，
 * 手柄被裁掉一半甚至全部，用户点不到，表现为「角拖不动」。
 *
 * 改为**内嵌定位**：手柄整体落在框内（top:0 / left:0 等），
 * 永远不会被 overflow 裁掉。视觉上仍贴着角，不影响辨识。
 * 同时把命中区放大到 20px（手指友好），并保留 cursor 提示。
 */

.import-crop__handle {
    position: absolute;
    width: 20px;
    height: 20px;
    box-sizing: border-box;
    background: #fff;
    border: 2px solid var(--c-primary);
    border-radius: 3px;
    touch-action: none;
    /* 手柄必须盖在遮罩之上，否则点不到 */
    z-index: 2;
}

.import-crop__handle[data-corner="nw"] {
    top: 0;
    left: 0;
    cursor: nwse-resize;
}

.import-crop__handle[data-corner="ne"] {
    top: 0;
    right: 0;
    cursor: nesw-resize;
}

.import-crop__handle[data-corner="sw"] {
    bottom: 0;
    left: 0;
    cursor: nesw-resize;
}

.import-crop__handle[data-corner="se"] {
    bottom: 0;
    right: 0;
    cursor: nwse-resize;
}


/* --- 18.5 待确认提示（统计区） --- */

.stats-uncertain {
    margin: -4px 0 12px;
    padding: 8px 10px;
    font-size: 12px;
    line-height: 1.5;
    color: #b3491f;
    background: #fff7f3;
    border: 1px dashed #ff7a45;
    border-radius: var(--radius-btn);
}

.stats-uncertain[hidden] {
    display: none;
}


/* --- 18.6 窄屏适配 --- */

@media (max-width: 480px) {
    .import-crop {
        max-height: 240px;
    }
    .import-crop__img {
        max-height: 240px;
    }
    /* T-43 T43-8：移动端手柄放大到 24px（手指友好），
     * 但仍保持**内嵌定位**（top/left/bottom/right 均为 0），
     * 不再用负偏移 —— 负偏移会被 .import-crop 的 overflow:hidden 裁掉。 */
    .import-crop__handle {
        width: 24px;
        height: 24px;
    }
}