/* ============================================================
   theme.css —— 前台与编辑器共用的"设计令牌"层
   对应 WordPress Gutenberg 的 theme.json（单一数据源，一份定义、两处消费）
   画布三层结构：.studio-canvas（editor-viewport，可滚动）
              > .editor-page（editor-page，--page-width + --canvas-scale 缩放）
              > .studio-canvas-sheet（editor-content，--content-width 居中）
   所有画布布局尺寸只能引用这里的全局变量，禁止硬编码。

   数值出处（Gutenberg 源码/官方文档，非自造）：
   - --content-width  = theme.json settings.layout.contentSize。
     WP 核心无默认值（不定义即不限宽）；840px 取官方文档示例值，2026-08-29 定稿
   - --wide-width     = theme.json settings.layout.wideSize（官方文档示例 1100px）
   - --block-gap      = theme.json styles.spacing.blockGap，官方文档默认值 2em
   - --spacing-10~80  = WP 默认 spacingScale（×1.5、7 档、mediumStep 1.5rem）生成的
     --wp--preset--spacing--NN；WP 默认只收录 20~80，10 是 ×1.5 链再往下的半档（补齐命名）
   - --page-width     = 设备预览画布页宽。WP 机制：页宽 = 视口断点 − 1px
     （trunk DEVICE_PREVIEW_WIDTH_OFFSET = 1，防浏览器缩放舍入越过断点；
     默认断点 mobile 480px / tablet 782px，见 global-styles-engine/src/utils/viewport.ts）。
     我们用自己主题的档位断点：平板 = tier-1 断点 1024 − 1 = 1023px，
     手机 = tier-3 断点 480 − 1 = 479px（数值在 canvas.css 的设备规则里覆盖）
   - --canvas-scale   = 画布整页缩放（transform: scale，禁用 CSS zoom——zoom 会引发
     内容重排与命中坐标失真，WP zoom-out 也用 transform：--wp-block-editor-iframe-
     zoom-out-scale 同职能，见 PR #61424/#65998）。默认 1；zoom-out 模式实现时由 JS 计算
   ============================================================ */

:root {
    /* ---- 画布页宽 / 缩放（editor-page 层）---- */
    --page-width: 100%;        /* 桌面 = 100% 铺满（WP 同）；设备预览在 canvas.css 覆盖 */
    --canvas-scale: 1;         /* 整页缩放系数（zoom-out 预留位，默认不缩放） */

    /* ---- 内容列 / 宽幅档（editor-content 层，= theme.json layout 设置）---- */
    --content-width: 840px;
    --wide-width: 1100px;      /* wide 对齐档宽（§6.3 约束布局消费；工具栏显示
                                  「对齐：宽幅 1100px」，新建模组默认此档） */

    /* ---- 间距令牌（= theme.json spacing 默认值与默认阶梯）---- */
    --block-gap: 2em;          /* WP 根级默认块间距；本项目模组间距由间距层（style.spacing）
                                  负责（theme.css 约束布局规则消费），画布根层不消费此令牌 */
    --spacing-10: 0.3rem;      /* WP 阶梯补齐的半档（×1.5 链向下推一档） */
    --spacing-20: 0.44rem;
    --spacing-30: 0.67rem;
    --spacing-40: 1rem;
    --spacing-50: 1.5rem;      /* = 24px：画布 24px 内边距引用它（WP $grid-unit-30 同值） */
    --spacing-60: 2.25rem;
    --spacing-70: 3.38rem;
    --spacing-80: 5.06rem;

    /* ---- 画布背景（theme.json styles.color.background）---- */
    --wp-canvas-background: #fff;   /* 想要经典主题灰底改这里（如 #f0f0f1） */

    /* ---- 阴影预设档（§13 style.border.shadow 的消费端，v5 新增）----
       20260918D 对齐 WP 7.1 ShadowPanel：--shadow-natural/deep/sharp/
       outlined/crisp = wp-includes/theme.json shadow.presets 原值逐字节照抄
       （检查器弹层 swatch 图标与块渲染共用同一令牌，WP preset 串同语义）。
       --shadow-sm/md/lg 为历史三档（检查器已无入口），存量数据仍渲染 */
    --shadow-sm: 0 1px 3px rgba(0, 0, 0, .08);
    --shadow-md: 0 4px 14px rgba(0, 0, 0, .10);
    --shadow-lg: 0 12px 32px rgba(0, 0, 0, .14);
    --shadow-natural: 6px 6px 9px rgba(0, 0, 0, 0.2);
    --shadow-deep: 12px 12px 50px rgba(0, 0, 0, 0.4);
    --shadow-sharp: 6px 6px 0px rgba(0, 0, 0, 0.2);
    --shadow-outlined: 6px 6px 0px -3px rgb(255, 255, 255), 6px 6px rgb(0, 0, 0);
    --shadow-crisp: 6px 6px 0px rgb(0, 0, 0);
}

/* ============================================================
   通用间距层 v2（规范书 §5/§6 flex+gap 公式体系，2026-08-29 用户拍板）
   双层渲染（§6.1 层级归属表，服务端 render_module 产出同构 DOM）：

     .work-module-wrap                     ← BlockWrapper：margin 四向在此
                                             （块间空气；data-align 也在此层）
         └ 模组根元素                       ← BlockContent：padding 四向在此
                                             （块内部留白，背景/边框之内）

   编辑器的 .block-wrap 仍是 display:contents 逻辑层（不产生盒子），
   DOM 与前台同构；选中框/悬浮框画在模组根元素 outline 上（border-box），
   天然不含 margin 区 —— §6.1"选中框不包含 margin 区域"由结构保证。

   垂直间距公式（§6.2，flex 下原生叠加不塌陷，禁止 JS 补偿计算）：
     垂直间距(A,B) = 根容器 gap(--block-gap) + A.margin.bottom + B.margin.top
     首块上方     = A.margin.top；末块下方 = B.margin.bottom（与容器 padding 叠加）
   ============================================================ */

/* ---- 根容器 = §6.2 公式的 C（canvas 根 / 前台内容列）----
   flex column：子项（Wrapper 层）margin 不塌陷、原生叠加；
   gap = 0（用户 2026-08-29 定稿：相邻模组之间无基础间距，
   间距全权由各块的 margin 层负责 —— §6.2 公式退化为 A.mb + B.mt） */
.studio-canvas-sheet,
.work-page-content {
    display: flex;
    flex-direction: column;
    gap: 0;
}

/* ---- block-wrap：容器子模组的逻辑包装层（2026-08-29 起前台也有）----
   服务端渲染容器子模组时同样包这层（modules.render_children_wrapped），
   与编辑器画布挂载结构同构——所有容器子项规则（选择器链含 .block-wrap，
   如 §16 constrained 宽度公式、封面九宫格对齐）在前台与编辑器命中同一元素。
   display:contents = 不产生盒子、布局零影响；编辑器侧 canvas.css 有同款
   规则（历史遗留，保留无害） */
.block-wrap {
    display: contents;
}

/* ---- BlockWrapper：margin 层 + 对齐钩子 + 宽度公式（§6.3）----
   宽度显式计算 = 内容列宽 − 左右缩进，配合 align-self:flex-start
   不依赖 stretch —— 各浏览器对"stretch 被 max-width 截断后是否保持
   margin 偏移"的行为存在差异（2026-08-29 验收发现画布整体偏右），
   显式公式无歧义。max-width:100%−缩进 兜底页窄于内容列的情形
   （此时 margin 公式的 max 也回落为纯用户缩进，移动端语义）。
   左右 margin 接 --gutter-home（前台/设备预览的页面统一边距语义，
   编辑器桌面未定义 → 回退用户原值） */
.studio-canvas-sheet > .block-wrap > .work-module-wrap,
.work-page-content > .work-module-wrap {
    align-self: flex-start;
    width: calc(var(--content-width) - var(--m-le) - var(--m-re));
    max-width: calc(100% - var(--m-le) - var(--m-re));
    margin-top: calc(var(--m-t, 0px) * var(--rsp-t, 1));
    margin-bottom: calc(var(--m-b, 0px) * var(--rsp-t, 1));
    --m-le: min(var(--m-l, 0px), var(--gutter-home, var(--m-l, 0px)));
    --m-re: min(var(--m-r, 0px), var(--gutter-home, var(--m-r, 0px)));
    margin-left: max(calc((100% - var(--content-width)) / 2 + var(--m-le)), var(--m-le));
    margin-right: max(calc((100% - var(--content-width)) / 2 + var(--m-re)), var(--m-re));
}
/* wide 对齐：突破内容列到宽幅档（WP .alignwide）。
   2026-09-02 废除 §8 互斥细则（WP trunk 取源证实 spacing 与 align 零联动）：
   与 none 档同一套显式几何，参照系从 --content-width 换成 --wide-width——
   居中缩进 + 用户水平边距叠加，max() 防负偏移，width 扣减水平边距
   （margin+width 恰好占行）；页窄于宽幅档时 max() 回落纯用户边距（移动端语义） */
.studio-canvas-sheet > .block-wrap > .work-module-wrap[data-align="wide"],
.work-page-content > .work-module-wrap[data-align="wide"] {
    width: calc(var(--wide-width) - var(--m-le) - var(--m-re));
    max-width: calc(100% - var(--m-le) - var(--m-re));
    margin-left: max(calc((100% - var(--wide-width)) / 2 + var(--m-le)), var(--m-le));
    margin-right: max(calc((100% - var(--wide-width)) / 2 + var(--m-re)), var(--m-re));
}
/* full 对齐：满页宽（WP .alignfull）。2026-09-15 修正：本机 WP7.1 layout.php:607
   取源实锤——constrained 下 .alignfull 仅 max-width:none、从无显式 width，
   宽度由 margin 推导（设边距=对应侧内缩，两侧各生效、永不溢出；负边距由
   auto 宽吸收外扩）。旧注「正边距右溢出由用户自担」是当年取源误判，作废。
   Studio 因 flex item 的 auto 宽会收缩到内容宽，改用显式公式模拟同一结果：
   width 扣减水平边距（与 none/wide 档同一范式，margin+width 恰好占行）；
   max-width 保持 none 让负边距外扩不被钳死。
   --m-le/--m-re 在前台被 --gutter-home 钳制，正边距不超页面统一边距 */
.studio-canvas-sheet > .block-wrap > .work-module-wrap[data-align="full"],
.work-page-content > .work-module-wrap[data-align="full"] {
    width: calc(100% - var(--m-le) - var(--m-re));
    max-width: none;
    margin-left: var(--m-le);
    margin-right: var(--m-re);
}

/* ---- 水平对齐档（2026-09-02 A06 / 同日二修）：left / center / right ----
   为什么不用 float：内容根容器是 flex column，CSS 规范规定 flex item 的
   float 被忽略，WP 那种「图片浮动 + 后续段落环绕」只在块级流式容器下成立。
   本项目 §6.2 依赖 flex 的「相邻 margin 不塌陷」公式，改根容器为 block 的
   代价远大于环绕收益 —— 用户 2026-09-02 拍板取限宽对齐、不做环绕。
   二修（同日，用户实测发现 center 贴左）：首版只改 align-self，但 Wrapper
   的显式宽度公式（none 档的 calc(840−边距)）仍然生效——全宽盒子上
   align-self 的 center/flex-end 没有视觉位移，收缩只发生在图片 figure 层
   （left 档"正常"纯属 flex-start 恰好是默认位置的巧合）。现改为：水平档下
   Wrapper 整体改收缩盒——width: fit-content + align-self 靠边；margin 直取
   用户值，原公式的居中偏移项 (100%−content-width)/2 是给整列宽盒子定位
   用的，收缩盒的定位已交给 align-self，保留偏移会把 center 档推歪。
   fit-content 分型（intrinsic = min(max-content, max(min-content, 可用宽))）：
   - 标题：文字链无百分比宽度子元素 → 短标题收缩到文字排版宽，长标题钳到
     列宽正常换行（title.css 无需任何限宽规则，机制天然安全）；
   - image 已退出本体系（2026-09-10 第三轮 WP 1:1 复刻）：三态改 figure
     display:table 收缩（image.css 消费 data-align），见 §6.3 区新注释；
   - button（2026-09-03 接入）：根元素 inline-flex、宽度随文字收缩、无
     百分比宽度子元素；超长文字被 max-width 钳到列宽后由
     overflow-wrap:break-word 换行（button.css 已有）；
   - video（2026-09-03 §29 接入）：nat_w/nat_h 给 video 元素确定固有宽 →
     fit-content 收缩，超列宽被 max-width 钳满；空态定宽 320px 同样安全。
   为什么用 is-halign-* 类而非直接吃 data-align：逐类型放行（title/button/
   video），类名即「该类型 intrinsic 收缩链已确认安全」的标记；新模组
   开放前先推演其 intrinsic 链，段落/容器类不放行 */
/* 三档公共收缩几何：特异性 (0,4,0) 压过 none 档基础公式 (0,3,0)；
   margin-top/bottom 不在此覆盖（垂直间距仍归基础规则的响应式公式）；
   --m-le/--m-re 由基础规则在同一元素上定义，此处直接消费 */
.studio-canvas-sheet > .block-wrap > .work-module-wrap.is-halign-left,
.studio-canvas-sheet > .block-wrap > .work-module-wrap.is-halign-center,
.studio-canvas-sheet > .block-wrap > .work-module-wrap.is-halign-right,
.work-page-content > .work-module-wrap.is-halign-left,
.work-page-content > .work-module-wrap.is-halign-center,
.work-page-content > .work-module-wrap.is-halign-right {
    width: fit-content;
    max-width: calc(100% - var(--m-le) - var(--m-re));
    margin-left: var(--m-le);
    margin-right: var(--m-re);
}
.studio-canvas-sheet > .block-wrap > .work-module-wrap.is-halign-left,
.work-page-content > .work-module-wrap.is-halign-left {
    align-self: flex-start;
}
.studio-canvas-sheet > .block-wrap > .work-module-wrap.is-halign-center,
.work-page-content > .work-module-wrap.is-halign-center {
    align-self: center;
}
.studio-canvas-sheet > .block-wrap > .work-module-wrap.is-halign-right,
.work-page-content > .work-module-wrap.is-halign-right {
    align-self: flex-end;
}

/* ---- image 退出收缩盒体系（2026-09-10 第三轮 WP 1:1 复刻）----
   原 is-halign 收缩盒（image 分型）与 is-fitw（none 档设宽收缩）随
   trunk 复刻退役：image 的水平三态改由 figure 自身 display:table 收缩
   （WP .wp-block-image.align* 同款，消费端 image.css 吃 data-align），
   none 档 Wrapper 恒占行——img 承载固定宽缩在 figure 左缘、命中区含
   空白是 WP 原生行为（用户拍板回归）。is-halign 通用段保留：仅服务
   title / glass_button / video（render.py 放行元组已移除 image） */

/* ---- BlockContent：padding 层（模组根元素）----
   box-sizing:border-box（§6.3）：padding 向内挤占内容区，不撑破 Wrapper 宽度。
   margin:0 —— 模组根元素多是排版标签（title 的 h1~h6 / paragraph 的 p /
   image 的 figure），浏览器 UA 默认 margin（h2 上下 0.83em、p 上下 1em、
   figure 上下 1em + 左右 40px）在 flex 不塌陷语境下全部计入块高，
   表现为"模组之间凭空多出间隙"。§6.1 层级契约规定 margin 全权属于
   Wrapper 层，模组根元素必须清零（title.css/paragraph.css/image.css
   注释中"UA 重置由 theme.css 承担"即指本条——此前缺失，2026-08-29 补上）。
   只作用于根元素（> * 直接子元素），不影响模组内部的嵌套内容。
   v5：选择器扩展到容器内层（:is(.work-group-inner, .work-cover-inner, .work-pattern-inner, .st-inner, .vh-reveal-inner) 的
   直接子模组）——容器回归后，嵌套模组的根元素同样需要 UA margin 清零与
   通用 padding 层（此前只有根级模组命中，嵌套后会漏）。
   padding 是块内部留白（§6.1），不参与页面边距体系 —— 全额生效、
   不接 --gutter-home（页面边距语义只属于 Wrapper 层的水平 margin）；
   上下随 --rsp-t 档位缩放（前台媒体查询 / 编辑器设备预览设值） */
.studio-canvas-sheet > .block-wrap > .work-module-wrap > *,
.work-page-content > .work-module-wrap > *,
:is(.work-group-inner, .work-cover-inner, .work-pattern-inner, .st-inner, .vh-reveal-inner) > .block-wrap > .work-module-wrap > *,
:is(.work-group-inner, .work-cover-inner, .work-pattern-inner, .st-inner, .vh-reveal-inner) > .work-module-wrap > *,
/* §71 列表容器内层（2026-09-25）：.work-list-inner 里的 list_item 根（li）
   与 .work-list-item-inner 里的嵌套 list 根（ol/ul）同样需要 UA margin
   清零与通用 padding 层——list 是 ol/ul 语义容器，子项排布由列表接管，
   故只登记本处（flex/grid/constrained 名单不登记，那些布局变量对列表
   是死代码） */
:is(.work-list-inner, .work-list-item-inner) > .block-wrap > .work-module-wrap > *,
:is(.work-list-inner, .work-list-item-inner) > .work-module-wrap > * {
    box-sizing: border-box;
    margin: 0;
    padding-top: calc(var(--p-t, 0px) * var(--rsp-t, 1));
    padding-right: var(--p-r, 0px);
    padding-bottom: calc(var(--p-b, 0px) * var(--rsp-t, 1));
    padding-left: var(--p-l, 0px);
}

/* ---- 容器边框变量作用域封口（§13，2026-09-03）----
   容器根元素由 _container_style_vars 输出的 --bd-*（圆角/宽度/线型/
   颜色/阴影）只应作用于容器自己。但 CSS 自定义属性天然沿 DOM 继承，
   不封口就会漏进子模组：子模组根元素只在自己设置了边框时才输出
   --bd-*（_block_style_vars 条件输出），没设置时继承到的就是父容器
   的值——表现为"群组加了边框/圆角/阴影，里面的标题/图片跟着长"。
   这里在五个容器的子模组挂载点上把 --bd-* 重置为 initial
   （guaranteed-invalid）：下游所有 var(--bd-*, fallback) 统一落回
   各自 CSS 的默认值（0px 直角 / 无边框 / 无阴影；button 的胶囊
   60px fallback 同样保住）。子模组要边框/圆角/阴影，在自己的外观
   面板单独设置——自己的 inline style 直接定义优先于任何继承，
   不受封口影响。群组文字色 --c-text 20260918K 已随功能退役
   （群组不控制子项文字颜色，用户需求），封口名单无需增补。 */
.work-group-inner,
.work-cover-inner,
.work-pattern-inner,
.st-inner {
    --bd-radius-tl: initial;
    --bd-radius-tr: initial;
    --bd-radius-br: initial;
    --bd-radius-bl: initial;
    /* 20260915R 四边化重置：width/color/style 逐边掐断继承（单值变量
       --bd-width/--bd-style/--bd-color 随四边化退役，消费端已全部切逐边） */
    --bd-width-top: initial;
    --bd-width-right: initial;
    --bd-width-bottom: initial;
    --bd-width-left: initial;
    --bd-style-top: initial;
    --bd-style-right: initial;
    --bd-style-bottom: initial;
    --bd-style-left: initial;
    --bd-color-top: initial;
    --bd-color-right: initial;
    --bd-color-bottom: initial;
    --bd-color-left: initial;
    --bd-shadow: initial;
}

/* ---- 响应式档位系数（本主题自己的"viewport 断点"体系）----
   --rsp-t    字号/圆角/绝对边距的缩放系数。前台由下方媒体查询按视口设置；
              编辑器设备预览由 canvas.css 的 .is-device-* 规则按画布设备设置（数值逐档一致）
   --rsp-gate 最小值门槛开关：0 = 不生效（桌面保持用户原值），1 = 生效。
              模组规则形如 max(calc(14px * var(--rsp-gate,0)), calc(size * var(--rsp-t,1)))，
              保证"最小可读字号"只在缩小档位出现 */
/* ---- 前台响应式档位（WP：视口驱动的媒体查询）----
   ≤1024 平板 ×0.85 → ≤720 大屏手机 ×0.7 → ≤480 小屏手机 ×0.55（苹果式分档）。
   编辑器画布没有 .work-page 祖先，这些规则不命中——编辑器桌面模式
   恒为档位 1（所见即所得），设备预览档位由 canvas.css 单独设置 */
@media (max-width: 1024px) {
    .work-page { --rsp-t: 0.85; --rsp-gate: 1; }
}
@media (max-width: 720px) {
    .work-page { --rsp-t: 0.7; --rsp-gate: 1; }
}
@media (max-width: 480px) {
    .work-page { --rsp-t: 0.55; --rsp-gate: 1; }
}

/* ============================================================
   嵌套容器布局引擎 v6（§16，2026-09-12 §43 严格 WP 四型收敛）
   作用域：容器模组（群组 .work-module-group / 封面 .work-module-cover /
   同步图案引用 .work-module-pattern）
   的内层挂载点。布局形态由模组根元素的 is-layout-* 类决定：
   constrained / flow / flex / grid（WP group 四 variation 同构；
   旧 row/stack 已并入 flex——横排 is-layout-flex、竖排
   is-layout-flex.is-vert，模板层输出）；
   变量来自 §13 通用 style 层（modules._container_style_vars 只对容器输出，
   模组 css 之后加载、可按级联覆盖引擎默认值）：
     --lay-content / --lay-wide   子项内容宽 / 宽幅（0 值已在服务端解析成令牌）
     --lay-gap-row / --lay-gap-col  子项间距双向（§39：垂直=换行行距 / 水平=列间，
                                    WP blockGap axial 同语义 top=row-gap、left=column-gap；
                                    --lay-gap 旧单值变量已退役）
     --lay-dir                    flex 方向（column = 竖排；横排不输出，row 为默认）
     --lay-justify / --lay-valign flex 主轴 / 交叉轴对齐（render.py 按方向分派
                                    好轴角色，WP justifyContent/verticalAlignment
                                    双属性方向对调同构；竖排时 --lay-valign 恒输出）
     --lay-wrap                   flex 换行开关
     --lay-jf / --lay-jr          constrained 子项居中偏移因子（0.5=居中缺省，
                                    0/1=贴左/贴右，WP constrained justification）
     --lay-cols / --lay-rows      grid 列模板 / 行模板（三分支公式 + 行数；WP
                                    grid.jsx getLayoutStyle 同构）

   垂直间距公式（§6.2，容器内同样成立，flex 原生不塌陷，禁止 JS 补偿）：
     子项(A,B) 垂直间距 = gap + A.margin.bottom + B.margin.top
   水平边距说明：--gutter-home（页面统一边距）只属于根层——嵌套容器已在
   页面内容列之内，再做 gutter 收敛会双重缩进，故引擎内不接。
   ============================================================ */

/* ---- 内层挂载点基础形态：纵向 flex（constrained / flow 的共同底座）----
   ⚠ DOM 差异（2026-08-29 实测踩坑）：编辑器画布的子模组结构是
   inner > .block-wrap > .work-module-wrap（block-wrap 是 canvas.js 挂载层），
   前台 render_content 的结构是 inner > .work-module-wrap（没有 block-wrap）。
   因此本文件所有容器内层选择器都必须写两条路径（见各规则的后半条）——
   只写 .block-wrap 路径的规则在前台静默失效（嵌套 cover 背景图事故根因）。 */
.work-group-inner,
.work-cover-inner,
.work-pattern-inner,
.st-inner {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    /* §39 gap 双向：第一值 = row-gap（垂直/换行行距），第二值 = column-gap
       （水平/子项列间）——CSS gap 简写语义，WP blockGap axial 直出同构 */
    gap: var(--lay-gap-row, 0px) var(--lay-gap-col, 0px);
    /* min-width 不设（§24 对齐 WP）：WP flex/grid 子项零输出，保持浏览器默认
       min-width:auto = min-content 兜底——此前此处的 min-width:0 会把空容器
       （如 row 群组里的空 cover）的宽度下限也拆掉，配合 fit 缺省直接塌成 0 宽 */
}

/* ---- 子项 Wrapper：通用 margin 层（上下随档位缩放，四向对所有布局生效）---- */
/* ★vh-stage（vision_hero hero 槽）与 .vh-reveal-inner（揭示段，2026-09-06 接入）
   都在本名单：hero 槽只有 margin 层——满幅舞台不参与 constrained 宽度体系
   （子块默认 stretch 撑满）；揭示段全体系参与（见下方 constrained 组） */
:is(.work-group-inner, .work-cover-inner, .work-pattern-inner, .st-inner, .vh-reveal-inner, .vh-stage) > .block-wrap > .work-module-wrap,
:is(.work-group-inner, .work-cover-inner, .work-pattern-inner, .st-inner, .vh-reveal-inner, .vh-stage) > .work-module-wrap {
    margin-top: calc(var(--m-t, 0px) * var(--rsp-t, 1));
    margin-bottom: calc(var(--m-b, 0px) * var(--rsp-t, 1));
    margin-left: var(--m-l, 0px);
    margin-right: var(--m-r, 0px);
}

/* ---- constrained（WP Group 默认，§16）：子项宽度 = 内容宽 − 水平缩进 ----
   显式公式 + align-self:flex-start（与根层同一套 §6.3 几何，参照系从
   --content-width 换成容器自己的 --lay-content；对称边距自然居中、
   单侧缩进贴对侧，页窄于内容列时 max 兜底回落纯用户边距）。
   §44 justification（WP constrained.jsx 三档）：居中偏移因子 --lay-jf/
   --lay-jr——0.5/0.5 = 居中（缺省，WP margin auto/auto）、0/1 = 贴左
   （WP margin 0/auto）、1/0 = 贴右（auto/0）。因子写法与原 /2 在缺省档
   数学等价，存量零回归 */
.is-layout-constrained > :is(.work-group-inner, .work-cover-inner, .work-pattern-inner, .st-inner, .vh-reveal-inner) > .block-wrap > .work-module-wrap,
.is-layout-constrained > :is(.work-group-inner, .work-cover-inner, .work-pattern-inner, .st-inner, .vh-reveal-inner) > .work-module-wrap {
    align-self: flex-start;
    width: calc(var(--lay-content, var(--content-width, 840px)) - var(--m-l, 0px) - var(--m-r, 0px));
    max-width: calc(100% - var(--m-l, 0px) - var(--m-r, 0px));
    margin-left: max(calc((100% - var(--lay-content, var(--content-width, 840px))) * var(--lay-jf, 0.5) + var(--m-l, 0px)), var(--m-l, 0px));
    margin-right: max(calc((100% - var(--lay-content, var(--content-width, 840px))) * var(--lay-jr, 0.5) + var(--m-r, 0px)), var(--m-r, 0px));
}
/* 容器内 wide/full 突破（WP constrained layout 同语义；根层规则见上方 §6.3 区）。
   2026-09-02 废除 §8 互斥细则：wide 与 constrained 档同一套显式几何
   （参照系 --lay-wide；子项水平边距用 --m-l/--m-r 原值，无 gutter 钳制层），
   用户水平边距叠加生效 */
.is-layout-constrained > :is(.work-group-inner, .work-cover-inner, .work-pattern-inner, .st-inner, .vh-reveal-inner) > .block-wrap > .work-module-wrap[data-align="wide"],
.is-layout-constrained > :is(.work-group-inner, .work-cover-inner, .work-pattern-inner, .st-inner, .vh-reveal-inner) > .work-module-wrap[data-align="wide"] {
    width: calc(var(--lay-wide, var(--wide-width, 1100px)) - var(--m-l, 0px) - var(--m-r, 0px));
    max-width: calc(100% - var(--m-l, 0px) - var(--m-r, 0px));
    margin-left: max(calc((100% - var(--lay-wide, var(--wide-width, 1100px))) / 2 + var(--m-l, 0px)), var(--m-l, 0px));
    margin-right: max(calc((100% - var(--lay-wide, var(--wide-width, 1100px))) / 2 + var(--m-r, 0px)), var(--m-r, 0px));
}
/* full 档 2026-09-15 修正：width 扣减水平边距（WP .alignfull 无显式宽、
   宽度由 margin 推导——本机 WP7.1 layout.php:607 取源，见上方 full 对齐
   注释；原先 width:100% 时 over-constrained 下 margin-right 被忽略、
   左边距把块推出容器）。参照系即容器自身 100%，负边距由 calc 吸收外扩 */
.is-layout-constrained > :is(.work-group-inner, .work-cover-inner, .work-pattern-inner, .st-inner, .vh-reveal-inner) > .block-wrap > .work-module-wrap[data-align="full"],
.is-layout-constrained > :is(.work-group-inner, .work-cover-inner, .work-pattern-inner, .st-inner, .vh-reveal-inner) > .work-module-wrap[data-align="full"] {
    width: calc(100% - var(--m-l, 0px) - var(--m-r, 0px));
    max-width: none;
    margin-left: var(--m-l, 0px);
    margin-right: var(--m-r, 0px);
}

/* ---- 容器内水平对齐档（2026-09-03 扩展）：纵向语义布局生效 ----
   根层 is-halign（§6.3 区）选择器只挂根级前缀，容器内子项此前无消费方。
   只扩展 constrained / flow / 竖排 flex（.is-vert，旧 stack 收敛进来的
   形态）——三者 inner 都是 flex column，交叉轴即水平轴，align-self 三档
   语义正确；横排 flex（旧 row，主轴水平，align-self 管垂直、水平归容器
   justify-content）与 grid（align-self 管块轴）不扩展——is-halign 类照挂
   但无消费方，行为与扩展前一致（WP 的 flex row / grid 布局同样没有子项
   主轴自定位语义）。
   与根层规则的两处差异：
   - margin 消费 --m-l/--m-r（容器子项通用层的原值直取；根层用的
     --m-le/--m-re 是根级 Wrapper 就地定义的 gutter 钳制计算变量，
     容器子项身上不存在，直接复用根层选择器会取空值）；
   - 特异性 (0,5,0) 压过 constrained 子项公式 (0,4,0)——fit-content
     与原值 margin 覆盖 constrained 的显式宽度与居中偏移（源顺序
     也在其后，双保险） */
:is(.is-layout-constrained, .is-layout-flow, .is-layout-flex.is-vert) > :is(.work-group-inner, .work-cover-inner, .work-pattern-inner, .st-inner, .vh-reveal-inner) > .block-wrap > .work-module-wrap.is-halign-left,
:is(.is-layout-constrained, .is-layout-flow, .is-layout-flex.is-vert) > :is(.work-group-inner, .work-cover-inner, .work-pattern-inner, .st-inner, .vh-reveal-inner) > .work-module-wrap.is-halign-left,
:is(.is-layout-constrained, .is-layout-flow, .is-layout-flex.is-vert) > :is(.work-group-inner, .work-cover-inner, .work-pattern-inner, .st-inner, .vh-reveal-inner) > .block-wrap > .work-module-wrap.is-halign-center,
:is(.is-layout-constrained, .is-layout-flow, .is-layout-flex.is-vert) > :is(.work-group-inner, .work-cover-inner, .work-pattern-inner, .st-inner, .vh-reveal-inner) > .work-module-wrap.is-halign-center,
:is(.is-layout-constrained, .is-layout-flow, .is-layout-flex.is-vert) > :is(.work-group-inner, .work-cover-inner, .work-pattern-inner, .st-inner, .vh-reveal-inner) > .block-wrap > .work-module-wrap.is-halign-right,
:is(.is-layout-constrained, .is-layout-flow, .is-layout-flex.is-vert) > :is(.work-group-inner, .work-cover-inner, .work-pattern-inner, .st-inner, .vh-reveal-inner) > .work-module-wrap.is-halign-right {
    width: fit-content;
    max-width: calc(100% - var(--m-l, 0px) - var(--m-r, 0px));
    margin-left: var(--m-l, 0px);
    margin-right: var(--m-r, 0px);
}
:is(.is-layout-constrained, .is-layout-flow, .is-layout-flex.is-vert) > :is(.work-group-inner, .work-cover-inner, .work-pattern-inner, .st-inner, .vh-reveal-inner) > .block-wrap > .work-module-wrap.is-halign-left,
:is(.is-layout-constrained, .is-layout-flow, .is-layout-flex.is-vert) > :is(.work-group-inner, .work-cover-inner, .work-pattern-inner, .st-inner, .vh-reveal-inner) > .work-module-wrap.is-halign-left {
    align-self: flex-start;
}
:is(.is-layout-constrained, .is-layout-flow, .is-layout-flex.is-vert) > :is(.work-group-inner, .work-cover-inner, .work-pattern-inner, .st-inner, .vh-reveal-inner) > .block-wrap > .work-module-wrap.is-halign-center,
:is(.is-layout-constrained, .is-layout-flow, .is-layout-flex.is-vert) > :is(.work-group-inner, .work-cover-inner, .work-pattern-inner, .st-inner, .vh-reveal-inner) > .work-module-wrap.is-halign-center {
    align-self: center;
}
:is(.is-layout-constrained, .is-layout-flow, .is-layout-flex.is-vert) > :is(.work-group-inner, .work-cover-inner, .work-pattern-inner, .st-inner, .vh-reveal-inner) > .block-wrap > .work-module-wrap.is-halign-right,
:is(.is-layout-constrained, .is-layout-flow, .is-layout-flex.is-vert) > :is(.work-group-inner, .work-cover-inner, .work-pattern-inner, .st-inner, .vh-reveal-inner) > .work-module-wrap.is-halign-right {
    align-self: flex-end;
}

/* ---- 容器内 image 退出收缩盒（2026-09-10 第三轮）----
   原 is-fitw 容器段随 trunk 复刻退役（机制与根级同因）：image 三态/
   设宽的占位语义已统一移交 figure 层（display:table 收缩 + img 内联
   固定宽，见 image.css），容器内 Wrapper 恒按各布局基础公式占位——
   flex/grid 子项的 stretch、constrained 的显式宽度公式对 image 照常
   生效，与 WP 容器布局对待图片块的方式一致 */

/* ---- flex：横排（旧 row）与竖排（旧 stack，.is-vert）合一（§43）----
   方向由 --lay-dir 承载（竖排 render.py 输出 column，横排不输出 = row
   默认，WP「row 是 flex 默认、基础布局从不声明」同构）；两轴对齐变量由
   render.py 按方向分派好轴角色（WP justifyContent/verticalAlignment
   双属性方向对调同构）：
   - 横排：justify-content ← --lay-justify（主轴水平，未设置 flex-start）、
     align-items ← --lay-valign（交叉轴垂直，未设置 center = WP baseStyles
     拍板延续——2026-09-10 用户拍板方案 A 严格 WP）
   - 竖排：justify-content ← --lay-justify（主轴垂直，未设置 flex-start
     顶对齐）、align-items ← --lay-valign（交叉轴水平，render.py 恒输出
     ——竖排缺省 flex-start 压掉横排 center 缺省，WP alignItemsMap.left
     同构）
   子项不设 min-width（§24 对齐 WP：flex 子项零输出，浏览器默认
   min-width:auto = min-content 兜底）；此前"长内容撑破行"的防护交还
   wrap 换行 + 各模组自身 max-width（cover 九宫格双上限等），WP 同一套
   机制。stretch（竖排第 4 档 / 横排 valign 第 4 档）= 等宽/等高拉伸
   （WP 「Stretch to fill」同语义） */
.is-layout-flex > :is(.work-group-inner, .work-cover-inner, .work-pattern-inner, .st-inner, .vh-reveal-inner) {
    flex-direction: var(--lay-dir, row);
    justify-content: var(--lay-justify, flex-start);
    flex-wrap: var(--lay-wrap, wrap);
    align-items: var(--lay-valign, center);
}

/* ---- §42 flex 横排窄屏变竖排（stack_narrow，方案 A 轻量档）----
   开关容器由 render.py 输出三变量（--lay-narrow-dir/justify/align），未开
   开关的容器三变量均不输出 → 下方回落链取基础值，窄屏下横排原样零回归；
   开关后轴映射翻转对齐 WP flex.jsx 竖排分支（justify-content ← 原交叉轴
   valign 映射、align-items ← 原主轴 justify 的 _NARROW_ALIGN，未显式档
   回落 flex-start）。双通道对齐项目响应式范式：
   - 前台：@media ≤720（大屏手机档）+ .work-page 作用域——编辑器画布无
     .work-page 祖先恒横排（所见即所得原则，与 --rsp-t 档位同构）；
   - 编辑器：设备预览 .is-device-mobile 类驱动（无媒体查询，canvas.css
     同款机制），手机预览下所见即前台窄屏效果 */
   ⚠️ 为什么这里是 720 而前台 CSS 多用 768（2026-10-08 P3 卫生债·断点语义统一）：
     720 是**前台 .work-page 页面**的大屏手机档，与 --rsp-t 分档（1024/720/480）
     同一条刻度线——它决定的是"页面内容按什么比例缩排"。编辑器桌面模式画布
     没有 .work-page 祖先，这些规则压根不命中，编辑器窄屏由设备预览的
     .is-device-* 类驱动；前台 768 则服务于导航折叠、表格横滚等**面向公众
     页面**的组件，两者各管一摊、刻意不同档，不是不一致而是分工。
     ⚠️ 本轮只补注释不改断点值：改值要全量回归，不值。 */
@media (max-width: 720px) {
    .work-page .is-layout-flex > :is(.work-group-inner, .work-cover-inner, .work-pattern-inner, .st-inner, .vh-reveal-inner) {
        flex-direction: var(--lay-narrow-dir, var(--lay-dir, row));
        justify-content: var(--lay-narrow-justify, var(--lay-justify, flex-start));
        align-items: var(--lay-narrow-align, var(--lay-valign, center));
    }
}
.studio-canvas.is-device-mobile .is-layout-flex > :is(.work-group-inner, .work-cover-inner, .work-pattern-inner, .st-inner, .vh-reveal-inner) {
    flex-direction: var(--lay-narrow-dir, var(--lay-dir, row));
    justify-content: var(--lay-narrow-justify, var(--lay-justify, flex-start));
    align-items: var(--lay-narrow-align, var(--lay-valign, center));
}

/* ---- grid：网格铺排，列模板由 --lay-cols 决定（固定列数 / 最小列宽自动），
   行模板 --lay-rows 仅固定列数+行数同设时输出（WP shouldOutputGridRows
   同条件；缺省 none = 行高随内容）。
   position:relative：§46 灰框可视化层的定位上下文——可视化层 absolute
   挂在 inner 内部（WP GridVisualizer 同款挂法），absolutely positioned
   子项不参与 grid 轨道布局（CSS 规范），不会挤占格子 */
.is-layout-grid > :is(.work-group-inner, .work-cover-inner, .work-pattern-inner, .st-inner, .vh-reveal-inner) {
    position: relative;
    display: grid;
    grid-template-columns: var(--lay-cols, repeat(auto-fill, minmax(240px, 1fr)));
    grid-template-rows: var(--lay-rows, none);
    align-items: stretch;
}

/* ---- 子项占位申报（§19，WP child layout 同构）----
   数据源：子模组 style.layout 的 selfStretch/flexSize/columnSpan，服务端
   翻译成 --ch-* 变量挂在 .work-module-wrap 上（modules._child_layout_vars）。
   三条消费规则各取所需、互不越界：
   - row 子项：grow（fill 申报）/ basis（fixed 申报）；fit 缺省 = grow:0 + basis:auto
     即内容自适应——WP Row 原味：容器从不替子项分配尺寸。
   - grid 子项：只消费 span（grid-column: span N，WP columnSpan 同语义）
     与 rowspan（§40 grid-row: span N，WP rowSpan 同语义）；
     flex-* 属性对 grid item 天然无效，row 里输出的 span 变量也无人读取，
     交叉携带无害（变量与父容器类型解耦，服务端无需知道父布局）。
   - constrained / stack / flow 子项不消费占位变量（WP 同：这几类布局的
     子项没有 selfStretch 语义，constrained 宽度由 §6.3 公式全权负责）。
   注意 basis 用 minmax 兜底不必要——flex-basis 的 % 参照 flex 容器
   content-box，配合 --lay-wrap: wrap，窄屏放不下自动换行（响应式无断点）。*/
.is-layout-flex:not(.is-vert) > :is(.work-group-inner, .work-cover-inner, .work-pattern-inner, .st-inner, .vh-reveal-inner) > .block-wrap > .work-module-wrap,
.is-layout-flex:not(.is-vert) > :is(.work-group-inner, .work-cover-inner, .work-pattern-inner, .st-inner, .vh-reveal-inner) > .work-module-wrap {
    flex-grow: var(--ch-grow, 0);
    flex-basis: var(--ch-basis, auto);
}
/* ---- flex/grid 子项宽度兜底（2026-09-05 手机溢出修复，同日二轮）----
   根级 §6.3 与 constrained 子项（§16）都有 max-width: 100%−边距 的"页窄
   回落"钳制，flex/grid 子项此前缺失——flex/grid 子项默认 min-width:auto
   （下限 = min-content），子块的最小内容宽一旦被顶高（cover 的
   aspect-ratio×min-height 传导宽 ≈853px 最典型），页面再窄也缩不下去、
   直接溢出画面。CSS Flexbox 规范：definite max-width 会钳住 automatic
   minimum size（min = min(min-content, max-width)），布局期容器宽度
   确定、百分比可解析，故本条在布局期必然生效——与根级/constrained 档
   同一套兜底，补齐缺失处。（intrinsic 测量期百分比仍失效，但那只影响
   换行判定；cover 传导宽 853 本就放不进窄行，换行是正确行为）
   §43：旧 row/stack 两条收敛为 .is-layout-flex 一条（横竖排都要兜底） */
.is-layout-flex > :is(.work-group-inner, .work-cover-inner, .work-pattern-inner, .st-inner, .vh-reveal-inner) > .block-wrap > .work-module-wrap,
.is-layout-flex > :is(.work-group-inner, .work-cover-inner, .work-pattern-inner, .st-inner, .vh-reveal-inner) > .work-module-wrap {
    max-width: calc(100% - var(--m-l, 0px) - var(--m-r, 0px));
}
.is-layout-grid > :is(.work-group-inner, .work-cover-inner, .work-pattern-inner, .st-inner, .vh-reveal-inner) > .block-wrap > .work-module-wrap,
.is-layout-grid > :is(.work-group-inner, .work-cover-inner, .work-pattern-inner, .st-inner, .vh-reveal-inner) > .work-module-wrap {
    grid-column: span var(--ch-span, 1);
    /* §40 跨行数（WP rowSpan 同语义）：与 columnSpan 完全同构——变量缺省 1
       = 不申报，grid-row: span 1 是 CSS 默认行为，旧数据视觉零差异 */
    grid-row: span var(--ch-rowspan, 1);
}
