/* 摆弄页。样式是**全局**的 —— 这一页没有 shadow root 挡着,所以每条选择器
 * 都带 `.play` / `.play-` 前缀。之前是 `playground-app::part(x)`,拆掉自定义
 * 组件后那层前缀没了,不加就等于给整站文档写规则(button / textarea / pre 全中)。
 *
 * 配色一律走站点已有的 CSS 变量(见 lib/css/),不另起一套:深浅色主题
 * 是站点切一次就全站生效的,这里硬编码颜色会在深色下瞎掉。 */

.play {
    /* 撑满 `main` 剩下的高度:body 是 `grid-template-rows: auto 1fr`,`main`
     * 作为第 2 行的格子会被拉伸到一屏余下的高度,这里再 flex 一次把这份
     * 高度传给 `.play-layout`。以前是给 `.play-layout` 写死
     * `calc(100dvh - 19rem)`,19rem 是照「页头 + 两段说明」拍出来的;
     * 说明文字一删,减出来的高度就小于实际剩余空间,底下空出一大块。 */
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-height: 0;
    margin: 1rem 0 2rem;
    /* 不给整页挂等宽字体:按钮和下拉是按 UI 字体设计的,强制等宽会
     * 让它们跟旁边的正文对不上。只给真正是代码的两块(log)用等宽。 */
    font-size: 14px;
}

/* `main` 默认是 block。摆弄页要把「一屏余下的高度」一路传到 `.play`,
 * 所以这一页的 main 改成纵向 flex。只在这个页面生效 —— 别的页面没有
 * `.play`,不该被顺手改成 flex(那会改掉正文里块级元素的布局)。 */
body:has(main > .play) main {
    display: flex;
    flex-direction: column;
    min-height: 0;
}

/* ---- 主体:表头行 | 内容行,列是 左 | 拖动条 | 右 ----
 *
 * **五格平铺,左右栏各自不套 wrapper。** 工具条和右边的选项卡条是第 1 行的
 * 两格,编辑框和右边的内容区是第 2 行的两格 —— 同一个 grid 的同一个 `auto`
 * 行,高度自动取两边表头里更高的那个,对齐是白送的。之前套了 `.play-left` /
 * `.play-right` 再让它们 `grid-template-rows: subgrid` 认领父级的行,绕了
 * 一圈还多背一条 `subgrid` 的浏览器要求(Chrome 117+ / Safari 16+ /
 * Firefox 71+),更要命的是间隙得两边各写各的 —— subgrid 只继承父级的**轨道
 * 尺寸**,间隙取自己的,父级的 `row-gap` 一点都透不进来,漏一边就成了「右边
 * 有间隙、左边贴着底线」。平铺之后一条 `row-gap` 两栏同时吃到。
 *
 * **两行而不是一行**:表头和它下面的内容必须共用行轨道,否则两栏的表头各按各
 * 的高度垂直居中,按钮对不齐 —— 而且上沿不齐会一路带到下面,编辑器顶边和
 * 面板顶边也跟着错开十几像素。
 *
 * 三列:中间那列是拖动条独占的一格,**表头行的中间格也归它**(`grid-row: 1 / 3`,
 * 跨过 row-gap,所以线是连的)—— 于是两栏的底线在分隔条处断开,左右读起来是
 * 两个面板,而不是一条线穿过去。左列宽度由 script.js 拖动时写进 `--play-left`
 * (px);没拖过是 `1fr`,两栏等分。
 *
 * `--play-gutter` 是 22px 而不是原来的 8px:8px 时示例框、拖动线、编辑框
 * 的右边框全挤在一起,看着像糊成一块。22px 加上两侧表头各自的 padding,
 * 线两边才有喘气的地方。
 *
 * 高度不自己算:`.play` 是 `main` 里的 flex 项,拿到了 body grid 第 2 行
 * 拉伸出来的一屏余量,这里 `flex: 1` 接着吃。以前写死
 * `calc(100dvh - 19rem)`,那个 19rem 是照页头 + 说明文字拍的 —— 页面上
 * 的文字一变,底下就会空出或者溢出一截。 */
.play-layout {
    --play-left: 1fr;
    --play-gutter: 22px;
    /* 表头里所有控件的统一高度。两个下拉是原生 `<select>`,直接吃
     * `--play-ctl-h`;`<sl-button>` 的高度来自 Shoelace 的公开 token
     * `--sl-input-height-medium`,在这里把它指同一个值 —— 自定义属性会穿过
     * shadow 边界继承下去,所以一处定义两边对齐。
     * 宽度不统一:下拉框按内容长度给,收窄会把样例名截成「aaa_hello-」。 */
    --play-ctl-h: 40px;
    --sl-input-height-medium: var(--play-ctl-h);
    display: grid;
    grid-template-columns: var(--play-left) var(--play-gutter) minmax(0, 1fr);
    grid-template-rows: auto minmax(0, 1fr);
    /* 表头和内容之间的 6px。`column-gap` 留 0 —— 列宽是 `--play-gutter` 算好的,
     * 再加一份间距只会把左右两栏推开。分隔条靠 `grid-row: 1 / 3` 跨过来。 */
    row-gap: 6px;
    flex: 1 1 auto;
    min-height: 26rem;
}

/* 五个格子各自的落位。`min-width: 0` 不能省:grid item 默认 `min-width: auto`,
 * 会被整行的 min-content(约 630px 的工具条)撑破,栏被拖窄时「编译」就直接压
 * 过分隔线跑到右栏去了。 */
.play-left-head,
.play-editor,
.play-tabs-bar,
.play-panels {
    min-width: 0;
}
.play-left-head {
    grid-column: 1;
    grid-row: 1;
}
.play-editor {
    grid-column: 1;
    grid-row: 2;
}
.play-tabs-bar {
    grid-column: 3;
    grid-row: 1;
}
.play-panels {
    grid-column: 3;
    grid-row: 2;
}

.play-left-head {
    display: flex;
    align-items: center;
    gap: 10px;
    /* 永远允许折行,不是只在窄屏。工具条是 grid item,`min-width: auto`
     * 会把它的最小宽度锁死在整行的 min-content(约 630px);栏被拖到比这
     * 窄时,「编译」就直接压过分隔线跑到右栏去了。折行之后 min-content
     * 降到最宽的那一个控件,栏才能真的收窄。 */
    flex-wrap: wrap;
    /* 工具条高度按里面最高的控件定;给个下限,`<sl-button>` 还没 upgrade 时
     * 不至于塌成一条线再跳一下。 */
    min-height: 36px;
    /* 补一条底线,和右边选项卡条的底线接成同一行(中间被分隔条断开)。以前
     * 左边没有底线,两栏的表头读起来不像一层。 */
    border-bottom: 1px solid var(--border-soft);
    /* 控件 40px 高,原来底线就贴在他们下沿;补一点下方内边距,让横线和
     * 控件之间透口气。 */
    padding-bottom: 6px;
    /* 「编译」贴着右边,离分隔条太近;留出内边距让它和拖动线分开。 */
    padding-right: 14px;
}

/* 「示例 + 选 + 载入」这一组用间距抱在一起,不画框 —— 边框会和「目标」
 * 那组并排成两块独立面板的样子。组与组之间由 `<sl-divider vertical>` 的
 * 一条短竖线分隔。
 *
 * 组里的控件都是 `<sl-*>` —— 样式在 shadow root 里,外面只能通过它们
 * 暴露的 `--sl-*` 变量和 `::part()` 调。 */
.play-picker {
    display: flex;
    align-items: center;
    gap: 6px;
    white-space: nowrap;
}
/* 竖版 divider 高度交给组件(吃满工具栏的高度)。左右留白交给工具栏自己
 * 的 `gap`,这里不再加 `--spacing`。 */
.play-divider {
    --spacing: 0;
}
/* 「示例：」三个字:显式告诉人这个框是干嘛的。`.play-label` 不带前缀会被
 * 站点全局的 label 规则抓到。 */
.play-picker .play-label {
    font-size: 13px;
    opacity: 0.6;
    user-select: none;
}
/* 「取消 / 编译」推到最右。取消在编译左边:它是「撤掉正在跑的那次」。 */
.play-run-group {
    margin-left: auto;
    display: flex;
    align-items: center;
    gap: 6px;
    flex: none;
}
/* 按钮的高度不在这里写。Shoelace 把尺寸做成公开的 token,`.play-layout`
 * 已经把 `--sl-input-height-medium` 指到 `--play-ctl-h`,从那里继承进
 * shadow root。给 `<sl-button>` 宿主直接写 `height` 只会把内部已经排好的
 * 盒模型裁掉,不是真改 —— 这也是当初从 MDUI 换过来的原因:那套没有尺寸
 * token。 */
/* 左栏的编辑框:CodeMirror 6 的挂载点。CodeMirror 自己管滚动、光标、IME,
 * 这里只负责把高度传下去 —— 格子高度已经是第 2 行的 `minmax(0, 1fr)`,
 * `.cm-editor` 在这个 flex 列里再吃一次。 */
.play-editor {
    min-height: 0;
    display: flex;
    flex-direction: column;
}

/* CodeMirror 6 主题:跟着站点的深浅色走 —— 不写死颜色,全用
 * `--code-*` token,变深色时 `theme.css` 改一处就全局生效。 */
.play-editor .cm-editor {
    flex: 1 1 0;
    min-height: 0;
    /* 焦点环是绝对定位的伪元素,需要它做包含块。 */
    position: relative;
    border: 1px solid var(--border);
    border-radius: 6px;
    background-color: var(--code-bg);
    color: var(--fg);
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 12px;
}
.play-editor .cm-editor.cm-focused {
    /* 焦点环不能用 `outline` + 负 offset:Chrome 在圆角处会沿内接矩形
     * 裁一刀,左上/左下角露出一个斜切缺口;而且它画在当前行背景之下,
     * 高亮行会把右侧那一段盖成灰色。改用覆盖层,边框跟着 border-radius
     * 走,z-index 保证压在行高亮之上。
     *
     * 颜色仍用 `--accent`,别让默认的系统 focus 色在深色下变成看不见的
     * 纯白 —— 这是这个页面的设计语言。 */
    outline: none;
}
.play-editor .cm-editor.cm-focused::after {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 2;
    box-sizing: border-box;
    border: 2px solid var(--accent);
    border-radius: inherit;
    pointer-events: none;
}
.play-editor .cm-scroller {
    line-height: 1.5;
    font-family: inherit;
}
/* 当前行高亮:与 `--hover` 同色 —— 同样的「鼠标移过」语义,只不过这次
 * 是「光标所在」。`--active` 太重,会盖住诊断波浪线的视觉。
 *
 * 它不能直接画在 `.cm-line` 的背景上:行属于内容层,会盖住 z 为负的选区
 * 自绘层 —— 光标所在那一行的选区整段消失。改成行内一个负 z 的伪元素,
 * 让它参与 `.cm-scroller`(它建了层叠上下文)的排序,落到
 * `.cm-selectionLayer` 下面。z 取 -10 而不是贴着 -2,免得 CM 再加图层
 * 把顺序挤乱。 */
.play-editor .cm-activeLine {
    position: relative;
    /* CM 基础主题的 `.cm-activeLine` 自带一层 #cceeff44,它同样在内容层,
     * 会把选区和伪元素一起盖掉。底色全部交给下面的伪元素。 */
    background-color: transparent;
}
.play-editor .cm-activeLine::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: -10;
    background-color: var(--hover);
    pointer-events: none;
}
.play-editor .cm-gutters {
    background-color: var(--code-bg);
    color: var(--muted);
    border-right: none;
}
/* 行号跟着当前行走:与正文同一层 `--hover`。gutter 在自己的 z=200 层里,
 * 不受选区层影响。 */
.play-editor .cm-activeLineGutter {
    background-color: var(--hover);
}
/* 多带一层 `.cm-editor`:CM 注入的基础主题也是两层权重(主题类 +
 * `.cm-content`),同权重但注入得更晚,会盖掉只写两层的规则。 */
.play-editor .cm-editor .cm-content {
    padding: 6px 0;
}
/* 选区:颜色取 `--accent` 与代码底色 40/60 的混色。
 *
 * 用不透明色而不是半透明:矩形已经被脚本对齐到行盒(见 `script.js` 的
 * `snapSelectionToLine`),相邻两行在亚像素上仍可能压住半像素;半透明
 * 叠起来会在行距处露出一道更深的接缝。
 *
 * 装了 `drawSelection()` 后原生选区被 CM 强制透明,能看到的只有它自绘的
 * `.cm-selectionBackground`;而 CM 针对聚焦态的默认规则是四层权重
 * (`cm-focused > .cm-scroller > .cm-selectionLayer ...`),这里必须写到
 * 五层才压得住。 */
.play-editor .cm-editor > .cm-scroller > .cm-selectionLayer .cm-selectionBackground {
    background-color: color-mix(in srgb, var(--accent) 40%, var(--code-bg));
}
.play-editor .cm-cursor,
.play-editor .cm-dropCursor {
    border-left-color: var(--fg);
}
/* 不可编辑时(setBusy 时只读):光标淡掉,但还允许选中复制。 */
.play-editor .cm-editor[contenteditable="false"] .cm-cursor {
    border-left-color: transparent;
}

/* ---- LSP semanticTokens 高亮 ----
 *
 * 颜色复用 `theme.css` 里那一组 `--code-syn-*` —— 那 8 个角色是为 Typst
 * 高亮挑的,但语义大体对得上 LSP 的 23 个类型:
 *   - 关键字 / 修饰符 / 装饰器 / typeParameter → attribute(深色读得清)
 *     —— 关键字例外:它单用红色 `--code-syn-keyword`,见下面那条。
 *   - 函数 / 方法 / 类型 / 接口 / struct       → symbol(蓝)
 *   - 字符串 / 注释 / 数字                    → string / comment / literal
 *   - 命名空间 / 宏                          → markup / macro
 *   - 类 / 枚举 / 枚举成员 / 属性             → member(紫)
 *   - 变量 / 参数 / 属性 / value             → plain(偏灰,不抢)
 *   - 操作符                                → 正文色(和括号等标点一致)
 *
 * 不在映射里的类型(method / event / regexp ...)就近复用:
 * method 与 function 同色,event 偏红,regexp 沿 literal。
 * 复用而不是另开一组 token,深色覆盖就一处管。 */
.play-editor .cm-tok-namespace      { color: var(--code-syn-markup); }
.play-editor .cm-tok-type           { color: var(--code-syn-symbol); }
.play-editor .cm-tok-class          { color: var(--code-syn-member); }
.play-editor .cm-tok-enum           { color: var(--code-syn-member); }
.play-editor .cm-tok-interface      { color: var(--code-syn-symbol); }
.play-editor .cm-tok-struct         { color: var(--code-syn-symbol); }
.play-editor .cm-tok-typeParameter  { color: var(--code-syn-attribute); font-style: italic; }
.play-editor .cm-tok-parameter      { color: var(--code-syn-plain); }
.play-editor .cm-tok-variable       { color: var(--code-syn-plain); }
.play-editor .cm-tok-property       { color: var(--code-syn-member); }
.play-editor .cm-tok-enumMember     { color: var(--code-syn-member); }
.play-editor .cm-tok-event          { color: var(--code-syn-attribute); }
.play-editor .cm-tok-function       { color: var(--code-syn-symbol); }
.play-editor .cm-tok-method         { color: var(--code-syn-symbol); }
.play-editor .cm-tok-macro          { color: var(--code-syn-macro); }
/* 关键字单开一色:红。不用 `--code-syn-attribute` —— 浅色主题下那是
 * #301414,近乎纯黑,关键字成了一片黑块,读起来像代码坏了。红色既不抢
 * 注释(comment 灰)、也不抢字符串(绿),在一屏彩色 token 里站得住。
 * 顺带去掉加粗:粗体让红色显得更重,不如靠色相区分。
 *
 * 色值在 theme.css 的 `--code-syn-keyword` 里:浅色 #d73948,深色换成
 * 同色相提亮的 #ff8080(深色底上原色只有 3.3:1,读不出来)。 */
.play-editor .cm-tok-keyword        { color: var(--code-syn-keyword); }
.play-editor .cm-tok-modifier       { color: var(--code-syn-attribute); }
.play-editor .cm-tok-comment        { color: var(--code-syn-comment); font-style: italic; }
.play-editor .cm-tok-string         { color: var(--code-syn-string); }
.play-editor .cm-tok-number         { color: var(--code-syn-literal); }
.play-editor .cm-tok-regexp         { color: var(--code-syn-literal); }
/* 操作符跟正文同色。语义 token 里它和变量一样归到 plain,但操作符夹在
 * 括号、逗号这些没被 token 化的标点中间 —— 单独灰一档,读起来像被稀释了。 */
.play-editor .cm-tok-operator       { color: var(--fg); }
.play-editor .cm-tok-decorator      { color: var(--code-syn-attribute); }

/* ---- 诊断波浪线 ----
 *
 * RA 给的 severity:1=Error / 2=Warning / 3=Information / 4=Hint,
 * 0 视为 Information(协议注释里说服务端允许这么发)。
 *
 * wavy 是 CSS 标准 `text-decoration-style` 的值;Chrome 87+ / Firefox 70+
 * / Safari 12.1+ 都支持,早于这个的就当普通下划线 —— 不会因此报错,
 * 只是少一个波浪。 */
.play-editor .cm-diag-error {
    text-decoration: underline wavy var(--err);
    text-decoration-skip-ink: none;
}
.play-editor .cm-diag-warning {
    text-decoration: underline wavy var(--warn);
    text-decoration-skip-ink: none;
}
.play-editor .cm-diag-info {
    text-decoration: underline var(--link);
    text-decoration-skip-ink: none;
}
.play-editor .cm-diag-hint {
    text-decoration: underline dotted var(--muted);
    text-decoration-skip-ink: none;
}

/* ---- 诊断的悬停提示 ----
 *
 * 由 CM 的 hoverTooltip 生成(`lsp-client.js` 的 `diagnosticTooltip`),
 * 挂在 `.cm-tooltip` 里。CM 的基础主题会给 tooltip 一套浅色边框和背景,
 * 两层的权重一样 —— 多带一层 `.cm-editor` 压过去,和编辑器的深色主题对齐。 */
.play-editor .cm-editor .cm-tooltip.cm-tooltip-hover {
    border: 1px solid var(--border);
    border-radius: 6px;
    background-color: var(--code-bg);
    color: var(--fg);
    box-shadow: 0 6px 20px rgba(0, 0, 0, .18);
    max-width: 26em;
    padding: 7px 10px;
    font-family: inherit;
    font-size: 13px;
    line-height: 1.5;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}
.play-editor .cm-diag-tip.is-warning { color: var(--warn); }
.play-editor .cm-diag-tip.is-info    { color: var(--muted); }

/* ---- 下拉(`<sl-select>`) ----
 *
 * 配色、箭头、焦点环、胶囊圆角全由 Shoelace 自己画(主题在
 * `lib/css/shoelace-theme.css`);宿主上只留宽度 —— 高度已经由
 * `--sl-input-height-medium` 和按钮对好了。
 *
 * `width` 而不是 `min-width`:`.play-picker` 是 flex 行,select 的默认宽度
 * 会被内部 `display: block` 撑成整行,把「载入」挤出去。
 *
 * 字号跟着站点工具条走(13px),不是 Shoelace 默认的 1rem —— 16px 下
 * 「aaa_hello-world」在 10rem 里放不下,显示输入框会截断。两个 token 都要
 * 给:`--sl-input-font-size-medium` 管显示框,`sl-option` 用的是通用的
 * `--sl-font-size-medium`。自定义属性会继承进 shadow root,打开的弹层也
 * 吃同一个值。 */
.play-select {
    width: 7rem;
    --sl-input-font-size-medium: 13px;
    --sl-font-size-medium: 13px;
}
/* 样例名长(「aaa_hello-world」),比「目标」的选项宽;窄了会被截掉。 */
.play-sample-select {
    width: 12rem;
}
/* ---- 补全弹窗(autocomplete) ----
 *
 * CM 自动完成默认是浅色硬编码背景,与站点的深色主题撞色 —— 深色下弹窗
 * 还是白底黑字,文字读了还行但 hover 高亮 (灰色) 与正文分不清。覆写
 * 关键几条让弹窗跟主题走。 */
/* 选择器里多带一层 `.cm-editor`:CM 注入的基础主题是
 * `.ͼ1 .cm-tooltip.cm-tooltip-autocomplete{...}` / `... > ul{...}`,
 * 三个类 + 注入得更晚。少一个类就整条丢给 CM 的浅色硬编码 ——
 * 深色主题下弹窗白底、列表字体也退回泛型 `monospace`。 */
.play-editor .cm-editor .cm-tooltip.cm-tooltip-autocomplete {
    background-color: var(--surface);
    color: var(--fg);
    border: 1px solid var(--border);
    border-radius: 6px;
    box-shadow: var(--shadow-rest);
}
.play-editor .cm-editor .cm-tooltip.cm-tooltip-autocomplete > ul {
    /* 显式给编辑框同一套等宽字体。`font-family: inherit` 继承下来的是
     * CM 默认的泛型 `monospace`,Windows 上落到 Courier New —— 小字号下
     * 笔画又细又毛,和编辑框里的字明显不是一套。 */
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 12px;
}
.play-editor .cm-editor .cm-tooltip.cm-tooltip-autocomplete > ul > li[aria-selected] {
    background-color: var(--hover);
    color: var(--fg);
}
/* 每行都是 flex:名字在左,常量值 / 参数 / trait 注释紧随其后,
 * 类型 / 返回类型推到最右。
 * 选择器带满 `.cm-editor` 和两个 tooltip 类,压过 CM 注入的基础主题。 */
.play-editor .cm-editor .cm-tooltip.cm-tooltip-autocomplete > ul > li {
    display: flex;
    align-items: center;
}
/* 只有名字(会被插入的文本)用正文色;其余一律淡色。 */
.play-editor .cm-editor .cm-tooltip.cm-tooltip-autocomplete .cm-completionValue {
    margin-left: 0.35em;
    color: var(--muted);
    white-space: nowrap;
}
.play-editor .cm-editor .cm-tooltip.cm-tooltip-autocomplete .cm-completionParams {
    color: var(--muted);
    white-space: nowrap;
}
/* `of Trait` / `alias ...`:紧跟在参数列表后面。 */
.play-editor .cm-editor .cm-tooltip.cm-tooltip-autocomplete .cm-completionNote {
    margin-left: 0.6em;
    color: var(--muted);
    white-space: nowrap;
}
/* 类型 / 返回类型贴在行尾。 */
.play-editor .cm-editor .cm-tooltip.cm-tooltip-autocomplete .cm-completionMeta {
    margin-left: auto;
    padding-left: 1em;
    color: var(--muted);
    white-space: nowrap;
    flex-shrink: 0;
}
/* 匹配到的前缀默认加粗,在等宽字体里会把整行挤得忽宽忽窄 —— 只保留
 * 颜色强调。 */
.play-editor .cm-tooltip-autocomplete .cm-completionMatchedText {
    font-weight: inherit;
    color: var(--accent);
    text-decoration: none;
}

/* ---- 搜索面板 ----
 *
 * `@codemirror/search` 的默认外观同样是浅色硬编码(`&light` 分支),页面
 * 没开 `EditorView.darkTheme`,所以落到白底黑字。这里整体改回主题色。
 *
 * `.cm-panels` 的基础规则是 `&light .cm-panels{background:#f5f5f5}`,
 * 权重是主题类 + 类名;多带一层 `.cm-editor` 压过它。 */
.play-editor .cm-editor .cm-panels {
    background-color: var(--surface);
    color: var(--fg);
}
.play-editor .cm-editor .cm-panels.cm-panels-top {
    border-bottom: 1px solid var(--border);
}
.play-editor .cm-editor .cm-panels.cm-panels-bottom {
    border-top: 1px solid var(--border);
}
/* 面板里的控件:输入框、按钮、复选框文字。字色不跟主题走的话,深色下
 * 是黑字贴深底,基本看不清。 */
.play-editor .cm-editor .cm-panel.cm-search input,
.play-editor .cm-editor .cm-panel.cm-search button {
    background-color: var(--bg);
    color: var(--fg);
    border: 1px solid var(--border);
    border-radius: 4px;
    font: inherit;
}
.play-editor .cm-editor .cm-panel.cm-search button {
    background-color: var(--hover);
    /* CM 的 `.cm-button` 是 `background: linear-gradient(...)` —— 一条
     * `background-image`,只盖 `background-color` 压不住它,按钮照旧是白
     * 底浅字。显式清掉渐变。 */
    background-image: none;
    padding: 0 8px;
    cursor: pointer;
}
.play-editor .cm-editor .cm-panel.cm-search button:hover {
    background-color: var(--border-soft);
}
.play-editor .cm-editor .cm-panel.cm-search label {
    color: var(--muted);
}
/* 关闭按钮:基础主题里给了它的定位和 `background-color: inherit`,别把
 * 上面那套按钮底色套上去 —— 否则面板右上角会多出一个方块。 */
.play-editor .cm-editor .cm-panel.cm-search button[name="close"] {
    background-color: transparent;
    border: none;
    color: var(--muted);
    font-size: 16px;
    line-height: 1;
}
.play-editor .cm-editor .cm-panel.cm-search button[name="close"]:hover {
    color: var(--fg);
}
/* 命中高亮。CM 的默认色是亮黄 / 青色(`&light` / `&dark` 两套都没被选到,
 * 这里直接按 `--accent` 混色,和选区色同一路数,深浅主题共用一个式子。 */
.play-editor .cm-editor .cm-searchMatch {
    background-color: color-mix(in srgb, var(--accent) 28%, transparent);
    border-radius: 2px;
}
.play-editor .cm-editor .cm-searchMatch.cm-searchMatch-selected {
    background-color: color-mix(in srgb, var(--accent) 55%, transparent);
}
/* 「选中下一个相同词」和 `highlightSelectionMatches` 用的是同一套
 * `.cm-selectionMatch` 标记,比搜索命中更淡。 */
.play-editor .cm-editor .cm-selectionMatch {
    background-color: color-mix(in srgb, var(--accent) 16%, transparent);
}
.play-editor .cm-editor .cm-selectionMatch.cm-selectionMatch-main {
    background-color: transparent;
}

/* ---- 中间的拖动条 ----
 *
 * 独占中间那一列,而且跨两行(`grid-row: 1 / 3`)—— 表头那一段也有它,上下
 * 连成一条。宽度是 `--play-gutter`(22px),可点区域就是整条;可见的那道线用
 * ::after 画在正中的 2px —— 直接给整条上色会太粗。
 * `touch-action: none` 让触屏拖动不被浏览器的滚动手势抢走。 */
.play-splitter {
    grid-column: 2;
    grid-row: 1 / 3;
    position: relative;
    cursor: col-resize;
    touch-action: none;
    user-select: none;
}
.play-splitter::after {
    content: "";
    position: absolute;
    /* 原来的 `inset: 0 3px` 是按 8px 宽写的;列宽变成 22px 之后那会画出
     * 一条 16px 的粗线。改成定宽 2px 再水平居中。 */
    inset: 0 auto 0 50%;
    margin-left: -1px;
    width: 2px;
    border-radius: 1px;
    /* `--border-soft` 在深色下几乎和背景同色,拖动条等于不存在;用 `--border`
     * 就够看出这条线是可以抓的,又不至于抢镜。 */
    background: var(--border);
    transition: background 0.15s;
}
.play-splitter:hover::after,
.play-splitter[data-dragging]::after {
    background: var(--accent);
}

/* 选项卡条。底边那条线一路铺满,选项卡自己的下边框压在它上面 —— 选中的
 * 那个就「接上」了下面的内容。
 *
 * `align-items: stretch`(不是默认的 center)是为了让选项卡拉满整行:行高由
 * 左边更高的工具条定,居中的话选项卡的下边框会浮在半空里,接不住下面那
 * 条底线。按钮本身内容垂直居中,文字位置不受影响。 */
.play-tabs-bar {
    display: flex;
    align-items: stretch;
    gap: 10px;
    padding: 0 0 0 12px;
    border-bottom: 1px solid var(--border-soft);
    min-height: 34px;
}

.play-tabs {
    display: flex;
    gap: 2px;
    flex: none;
}

/* 原生 button,样式得自己抹平(appearance / 边框 / 字体)。没选中的压在
 * 下面一档透明度,选中的 1.0 加一条主色底边。 */
.play-tab {
    appearance: none;
    background: none;
    border: 0;
    border-bottom: 2px solid transparent;
    /* 和选项卡条那条分隔线对齐:把选项卡自己往下推 1px,选中时的底边
     * 正好盖在上面的线上,看着是连通的。 */
    margin-bottom: -1px;
    padding: 6px 10px 5px;
    font: inherit;
    font-size: 13px;
    color: var(--fg);
    opacity: 0.55;
    cursor: pointer;
    border-radius: 6px 6px 0 0;
}
.play-tab:hover {
    background: var(--hover);
    opacity: 0.85;
}
.play-tab[data-active] {
    opacity: 1;
    border-bottom-color: var(--accent);
}
/* 键盘 Tab 到选项卡上时要看得见焦点,`:focus-visible` 不会在鼠标点击后亮。 */
.play-tab:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: -2px;
}

/* 状态行占掉选项卡和下载按钮之间的空白;太长就省略号,不能把下载按钮挤走。
 * 选项卡条是 `align-items: stretch`(为了让选项卡贴住底线),状态文字不能
 * 跟着拉满整行 —— 那会让文字停在行首。单独收回来自适应高度再居中。 */
.play-status {
    flex: 1 1 auto;
    align-self: center;
    min-width: 0;
    font-size: 13px;
    text-align: right;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.play[data-status="ok"] .play-status { color: var(--ok); }
.play[data-status="err"] .play-status { color: var(--err); }
.play[data-status="warn"] .play-status { color: var(--warn); }
.play[data-status="pending"] .play-status { opacity: 0.6; }

.play-actions {
    display: flex;
    gap: 6px;
    flex: none;
}
/* 「取消」「下载」在没东西可取消 / 可下载的时候要藏起来。`<sl-button>` 是
 * 自定义元素,组件的 `:host` 自己设了 `display`,`[hidden]` 的 UA 默认样式
 * 不一定压得住,所以显式补一条 —— 两个按钮分属左栏和右栏,**两处都要包到**,
 * 漏一个它就一直亮着。 */
.play-run-group [hidden],
.play-actions [hidden] {
    display: none;
}

/* ---- 选项卡内容 ----
 *
 * 两个面板叠在同一个格子里,靠 `hidden` 切换 —— 这样切换时不会因为「另一个
 * 面板此刻多高」把布局顶得跳一下。`.play-panel[hidden]` 必须显式写:下面
 * 那条 `display: flex` 会把 `hidden` 的默认 `display: none` 盖掉。 */
.play-panels {
    display: flex;
    flex-direction: column;
    min-height: 0;
}
.play-panel {
    display: flex;
    flex-direction: column;
    flex: 1 1 0;
    min-height: 0;
}
.play-panel[hidden] {
    display: none;
}

.play-log {
    /* 必须显式写 `display: block`。code_block.css:111 有一条全局的
     * `pre { display: inline }` —— 那是 Typst 的约定,代码块的 `<pre>` 是包着
     * 内联内容的,要跟着段落流动,改不得。日志面板是真的块级 `<pre>`,被它盖成
     * inline 之后宽度按内容收缩、高度直接被忽略(实测 min-height 352px 而
     * 渲染出来 208px、列宽 716px 而实际 372px)。
     * 拆掉 shadow root 之前这些站点全局规则进不来,所以没这个坑。 */
    display: block;
    flex: 1 1 0;
    min-height: 0;
    margin: 0;
    padding: 10px 12px;
    overflow: auto;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 12px;
    line-height: 1.5;
    color: var(--fg);
    background-color: var(--code-bg);
    border: 1px solid var(--border);
    border-radius: 6px;
}

/* 编译输出里可点的源码位置(`--> lib/src/lib.rs:32:5`,见 script.js 的
 * `appendDiagText`)。做成链接的样子而不是按钮的样子 —— 它在日志里就是
 * 一段可点的文本,按 `line-height` 参与排版,给按钮的边框和内边距会破坏
 * 日志的行距。 */
.play-log-link {
    font: inherit;
    color: var(--accent);
    background: none;
    border: 0;
    padding: 0;
    cursor: pointer;
    text-decoration: underline dotted;
    text-underline-offset: 2px;
}
.play-log-link:hover {
    text-decoration-style: solid;
}
.play-log-link:focus-visible {
    outline: 1px solid var(--accent);
    outline-offset: 1px;
}

/* ---- 预览：状态与骨架 ----
 *
 * 状态是 `.play-preview` 上的四个互斥 class。非就绪时 `.preview-shell`
 * 整体 display:none —— 交互结构不参与布局，只剩一句居中的状态文字。
 */
.play-preview {
    display: flex;
    flex: 1 1 0;
    min-height: 0;
    overflow: hidden;
    padding: 0;
    color: var(--fg);
    background-color: var(--code-bg);
    border: 1px solid var(--border);
    border-radius: 6px;
}
.play-preview > .preview-message {
    margin: auto;
    padding: 10px 12px;
    text-align: center;
    color: var(--muted);
}
/* 就绪后骨架接管，状态文字必须整块让位 —— 光清空文字它仍占 padding */
.play-preview.is-ready > .preview-message {
    display: none;
}
.play-preview.is-empty > .preview-message {
    font-size: 2em;
}
.play-preview.is-error > .preview-message {
    color: var(--err);
}

.play-preview > .preview-shell {
    display: none;
}
.play-preview.is-ready > .preview-shell {
    display: flex;
    flex: 1 1 0;
    min-width: 0;
    min-height: 0;
}

/* 外层：只有图标的图 / 结构体切换 */
.preview-rail {
    display: flex;
    flex: none;
    flex-direction: column;
    gap: 4px;
    padding: 6px;
    border-right: 1px solid var(--border);
}
.preview-rail-button {
    display: grid;
    place-items: center;
    width: 32px;
    height: 32px;
    padding: 0;
    border: 1px solid transparent;
    border-radius: 6px;
    background: none;
    color: var(--muted);
    cursor: pointer;
}
.preview-rail-button .material-icons {
    font-size: 20px;
}
.preview-rail-button:hover {
    color: var(--fg);
    background-color: var(--border-soft);
}
.preview-rail-button[data-active] {
    color: var(--accent);
    background-color: var(--border-soft);
}
.preview-rail-button:focus-visible {
    outline: 1px solid var(--accent);
    outline-offset: 1px;
}

.preview-panes {
    display: flex;
    flex: 1 1 0;
    min-width: 0;
    min-height: 0;
}
.preview-graph-view {
    display: flex;
    flex: 1 1 0;
    min-width: 0;
    min-height: 0;
}
/* 两处都显式写了 display，必须自己压掉 hidden —— 和 .play-panel[hidden] 同理 */
.preview-graph-view[hidden],
.preview-struct-view[hidden] {
    display: none;
}

/* 内层：所有图的名称 */
.preview-graph-list {
    display: flex;
    flex: none;
    flex-direction: column;
    width: 180px;
    min-height: 0;
    overflow-y: auto;
    border-right: 1px solid var(--border);
    background-color: var(--graph-canvas);
}
.preview-file {
    flex: none;
    padding: 8px 10px;
    overflow: hidden;
    color: var(--muted);
    font-size: 12px;
    font-variant-numeric: tabular-nums;
    text-overflow: ellipsis;
    white-space: nowrap;
    border-bottom: 1px solid var(--border-soft);
}
.preview-graph-item {
    display: flex;
    align-items: baseline;
    gap: 6px;
    width: 100%;
    padding: 6px 10px;
    border: 0;
    background: none;
    color: var(--fg);
    font: inherit;
    font-size: 13px;
    text-align: left;
    cursor: pointer;
}
.preview-graph-item:hover,
.preview-graph-item[data-active] {
    background-color: var(--border-soft);
}
.preview-graph-item[data-active] {
    color: var(--accent);
}
.preview-graph-item.is-empty-graph {
    color: var(--muted);
}
.preview-graph-name {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.preview-graph-count {
    margin-left: auto;
    color: var(--muted);
    font-size: 11px;
    font-variant-numeric: tabular-nums;
}

/* 画布：ui-graph 铺满；不写 data-height，高度交给 CSS */
.preview-graph-canvas {
    position: relative;
    flex: 1 1 0;
    min-width: 0;
    min-height: 0;
    overflow: hidden;
}
.preview-graph-canvas > ui-graph {
    position: absolute;
    inset: 0;
    height: 100%;
    margin: 0;
    border: 0;
    border-radius: 0;
}
.preview-canvas-note {
    display: grid;
    place-items: center;
    height: 100%;
    color: var(--muted);
}

.preview-struct-view {
    display: grid;
    flex: 1 1 0;
    min-height: 0;
    place-items: center;
    color: var(--muted);
}

/* 手机上「示例 + 下拉 + 载入」本身就占了不少宽度,把下拉框的宽度收一收,
 * 好让折行后的第二行不至于只有「编译」孤零零一个。
 * (折行本身已经无条件打开了,见 `.play-left-head` 里的说明。) */
@media (max-width: 560px) {
    .play-sample-select {
        /* 13px 下最长的样例名也要放得下(名字 + 左右内边距 + 箭头)。 */
        width: 9rem;
    }
}

/* 窄屏上下摞起来。拖动条在竖排里没有意义(而且它的 `col-resize` 光标
 * 会误导),直接藏掉,两栏各占一半高度。
 *
 * 宽屏那五条显式的 `grid-column` / `grid-row` 在这里必须清掉 —— 不然四个
 * 格子全去抢第 1 列的前两行、直接叠在一起。清成 `auto` 之后它们按 DOM 顺序
 * 各自占一行,而 DOM 顺序本来就是「工具条、编辑框、| 选项卡条、内容」,
 * 正好是竖排要的上下两组。`display: none` 的拖动条不占格子,所以正好四行。 */
@media (max-width: 900px) {
    .play-layout {
        grid-template-columns: minmax(0, 1fr);
        /* 四行 = 表头、编辑框、选项卡条、内容。表头按内容,两块内容区平分
         * 剩下的高度 —— 表头夹在中间是因为两栏是上下摞的,不是并排。 */
        grid-template-rows: auto minmax(0, 1fr) auto minmax(0, 1fr);
    }
    .play-layout > * {
        grid-column: auto;
        grid-row: auto;
    }
    .play-splitter {
        display: none;
    }
}
