/* SPDX-License-Identifier: Apache-2.0 */
/*
  OpenMath web shell stylesheet (L0 shell, apps/openmath/web).

  Scope of this file
  ------------------
  Only presentation: three-column workspace, step list, LaTeX source in a monospace face.
  Native CSS only: no framework, no @import, no CDN, no external font/image/stylesheet of any
  kind (the mono stack below is a list of faces that ship with the OS / distro; if none is
  present the generic monospace fallback is used, so the page never needs a network fetch).

  Design tokens
  -------------
  Same names as packages/ui-kit/src/tokens/tokens.json (prefix --fc-), so the OpenMath shell
  stays visually consistent with apps/opencad: raw scales keep raw names (--fc-gray-7),
  semantic roles keep theirs (--fc-bg-canvas, --fc-text-primary, ...).
  Light values live in :root; the dark set overrides the SAME batch of names under
  prefers-color-scheme: dark (one name missing there = that role goes blind in dark mode).

  *** Documented deviations from tokens.json (all tightened, none loosened) ***
  1. --fc-text-brand  #5F5EF0 -> #2F46E5 (= --fc-primary-7, an existing raw value)
     WCAG 1.4.3: #5F5EF0 measured 4.47:1 on --fc-bg-canvas and 3.75:1 on --fc-bg-selected
     (i.e. the active step's rule line failed); #2F46E5 is 6.21:1 / 5.21:1.
  2. --fc-focus-ring  #8F81F4 -> #1B2B95 (= --fc-primary-9, existing raw value)
     WCAG 1.4.11 (non-text, 3:1): #8F81F4 measured 2.94:1 on --fc-bg-canvas, 2.74:1 on
     --fc-bg-sunken; #1B2B95 is 10.6:1 / 9.9:1 and still reads as a brand-coloured ring.
  3. --fc-hit-min     32px -> 44px
     Touch/click target floor. r6 baseline (design-system 口径 44px) + WCAG 2.5.5: the 32px
     token was the measured gap. The literal min-height in the control rule below is kept
     equal to this token on purpose (scripts/check_css.py fails if they drift).
  4. dark --fc-brand-hover  #8F81F4 -> #2F46E5
     WCAG 1.4.3 on the hover state of .action--primary: --fc-text-on-brand (#FFFFFF) on
     #8F81F4 measured 3.17:1 in dark mode; #2F46E5 gives 6.71:1 (hover tone now identical
     in both themes).
  Everything else (raw scales, dark set, other semantics) is byte-identical to tokens.json.
  If the ui-kit ever adopts these three values the deviation disappears; until then this
  header is the single place that documents it.

  Verified by (scripts live outside the repo, see /hf3fs-jd/data/tmp/web-i-c2/REPORT.md):
    check_contrast.py  逐带字元素算 WCAG 对比度（浅/深/窄屏/hover/active/对比度偏好）
    check_css.py       体积预算 / 零外链 / 深色令牌覆盖 / 断点覆盖 / DOM-CSS 覆盖 / 44px
    check_served.py    真起 server.py 量首屏真实字节与 CSP 头

  DOM contract this stylesheet is written against
  -----------------------------------------------
  Peers that write web/index.html + web/js/app.js must use these hooks (any ONE of the
  equivalent selectors per row is enough -- the rules below match all of them):

    nav#infra-svc-shell      .infra-svc-shell   infra 级服务导航条（/web/js/shell.js 运行时注入；
                                                本文件是它唯一的样式来源，含 __home/__entry/__peer/
                                                __sep/__group/__brand 与 data-svc-state 三态）
    header#topbar            .topbar            top bar: #app-title.brand, #toolbar/#op-bar,
                                                #selectors, #actions/#latex-actions
    main#workspace           .workspace         the three-column grid (row 2 of body)
      section#input-pane     [data-pane="input"]   column 1  -> #expr-input / #input-expr
      section#steps-pane     [data-pane="steps"]   column 2  -> ol#steps.steps > li.step
      section#render-pane    #latex-pane / [data-pane="render"]
                                                    column 3  -> .math-display / [data-math-error]
    footer#bottom            footer#status / .bottombar
                                                    status line: #status > .seg > .seg__label/.seg__value
    pane bodies              .pane__body          the scrollable middle of every pane
    pane titles:             .pane__title
    step nodes:              li.step[data-active="1"|"0"] with .step__index .step__rule
                             .step__math .step__latex / pre.tex-source / code.tex
    LaTeX source anywhere:   .tex, pre.tex-source, code.tex, [data-latex-source]
    render errors:           [data-math-error], .math-error
  Everything user-facing still carries data-i18n="<key>" in the HTML (filled by app.js); this
  file adds no text of its own.

  Responsive contract
  -------------------
    >=1920px  extra whitespace, reading measure capped (no endless line length)
    >=1440px  wider columns, body type one step up (15 -> 16px)
    <=1180px  input + steps side by side, render pane moves to a full-width second row
    <= 760px  single column, the page scrolls as a whole
    <= 560px  top bar wraps, op buttons 2x2, status bar wraps instead of truncating
    <= 360px  (incl. 320px) padding-only compaction; font size is NOT reduced
    short viewports (max-height:520px) trim vertical chrome so panes keep their height
  No horizontal overflow at any width: every growable box is min-width:0, long unbreakable
  strings wrap via overflow-wrap:anywhere, and wide math scrolls inside its own pane.
*/

:root {
  /* 官方 UA 控件/滚动条跟随主题（不加这条，暗色下原生下拉与滚动条仍是亮的） */
  color-scheme: light dark;

  /* raw scale: gray 0..13 (tokens.json raw) */
  --fc-gray-0: #FCFCFC;
  --fc-gray-1: #F6F6F6;
  --fc-gray-2: #EEEEEE;
  --fc-gray-3: #E2E2E2;
  --fc-gray-4: #D1D1D1;
  --fc-gray-5: #BBBBBB;
  --fc-gray-6: #A0A0A0;
  --fc-gray-7: #868686;
  --fc-gray-8: #6D6D6D;
  --fc-gray-9: #555555;
  --fc-gray-10: #3E3E3E;
  --fc-gray-11: #303030;
  --fc-gray-12: #222222;
  --fc-gray-13: #181818;
  /* raw scale: primary 0..9 (tokens.json raw) */
  --fc-primary-0: #F7F5FE;
  --fc-primary-3: #CBBFF9;
  --fc-primary-4: #AEA0F7;
  --fc-primary-5: #8F81F4;
  --fc-primary-6: #5F5EF0;
  --fc-primary-7: #2F46E5;
  --fc-primary-9: #1B2B95;

  /* semantic_light (3 documented deviations: text-brand / focus-ring, see header) */
  --fc-bg-canvas: #F6F6F6;
  --fc-bg-surface: #FCFCFC;
  --fc-bg-sunken: #EEEEEE;
  --fc-bg-hover: #EEEEEE;
  --fc-bg-selected: #E5E0F4;
  --fc-line-subtle: #D1D1D1;
  --fc-line-strong: #868686;
  --fc-text-primary: #303030;
  --fc-text-secondary: #555555;
  --fc-text-tertiary: #6D6D6D;
  --fc-text-disabled: #A0A0A0;
  --fc-text-on-brand: #FFFFFF;
  --fc-text-brand: #2F46E5;      /* dev. 1: was #5F5EF0 (4.47:1 / 3.75:1 -> 6.21:1 / 5.21:1) */
  --fc-brand-solid: #5F5EF0;
  --fc-brand-hover: #2F46E5;
  --fc-focus-ring: #1B2B95;      /* dev. 2: was #8F81F4 (2.94:1 -> 10.6:1 vs canvas) */
  --fc-danger: #DA2742;
  --fc-warning: #9A6815;
  --fc-success: #20824A;

  /* space / radius / size / motion (tokens.json) */
  --fc-space-2: 2px;
  --fc-space-4: 4px;
  --fc-space-8: 8px;
  --fc-space-12: 12px;
  --fc-space-16: 16px;
  --fc-space-24: 24px;
  --fc-space-32: 32px;
  --fc-radius-none: 0;
  --fc-radius-sm: 4px;
  --fc-radius-md: 8px;
  --fc-radius-lg: 12px;
  --fc-radius-full: 999px;
  --fc-titlebar-h: 40px;
  --fc-hit-min: 44px;            /* dev. 3: was 32px (44px touch floor, see header) */
  --fc-motion-fast: 120ms;
  --fc-motion-base: 180ms;
  --fc-ease-standard: cubic-bezier(0.2, 0, 0, 1);

  /* typography (tokens.json typography.font-*.size) */
  --fc-font-body: 15px;
  --fc-font-heading: 17px;
  --fc-font-title: 20px;
  /*
    Local-only faces, no @font-face and no download. The list is deliberately long so an
    offline machine still gets a real monospace face (DejaVu Sans Mono / Liberation Mono are
    what a stock Debian/Ubuntu box has); the final "monospace" keyword is the guaranteed
    fallback and is what makes "no external font" true rather than aspirational.
  */
  --fc-font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas,
    "DejaVu Sans Mono", "Liberation Mono", "Courier New", monospace;
  --fc-font-ui: system-ui, -apple-system, "Segoe UI", Roboto, "Noto Sans",
    "DejaVu Sans", Arial, sans-serif;
  --fc-font-math: "Latin Modern Math", "STIX Two Math", "Cambria Math", "DejaVu Serif",
    Georgia, serif;

  /* OpenMath layout metrics (not tokens.json: shell-local, so the three columns stay in step) */
  --om-col-input: minmax(260px, 24%);
  --om-col-steps: minmax(320px, 1fr);
  --om-col-render: minmax(340px, 32%);
  --om-gutter: var(--fc-space-8);
  --om-measure: 72ch;            /* reading measure cap, used from 1920px up */
}

@media (prefers-color-scheme: dark) {
  :root {
    /* semantic_dark —— 与浅色同一批名字，一个不漏 */
    --fc-bg-canvas: #222222;
    --fc-bg-surface: #303030;
    --fc-bg-sunken: #181818;
    --fc-bg-hover: #3E3E3E;
    --fc-bg-selected: #38347E;
    --fc-line-subtle: #3E3E3E;
    --fc-line-strong: #868686;
    --fc-text-primary: #F6F6F6;
    --fc-text-secondary: #E2E2E2;
    --fc-text-tertiary: #A0A0A0;
    --fc-text-disabled: #6D6D6D;
    --fc-text-on-brand: #FFFFFF;
    --fc-text-brand: #CBBFF9;
    --fc-brand-solid: #5F5EF0;
    --fc-brand-hover: #2F46E5;  /* dev. 4: #8F81F4 上白字只有 3.17:1（hover 态 FAIL） */
    --fc-focus-ring: #AEA0F7;   /* 6.95:1 on canvas / 4.67:1 on hover-bg (>= 3:1) */
    --fc-danger: #F88D8B;
    --fc-warning: #E59D25;
    --fc-success: #35C36D;
  }
}

/* 用户显式要求更高对比度：加深分隔线/次要文字（顺序在暗色块之后 => 深色时覆盖生效） */
@media (prefers-contrast: more) {
  :root {
    --fc-line-subtle: #6D6D6D;
    --fc-line-strong: #303030;
    --fc-text-secondary: #303030;
    --fc-text-tertiary: #555555;
  }
}

@media (prefers-contrast: more) and (prefers-color-scheme: dark) {
  :root {
    --fc-line-subtle: #A0A0A0;
    --fc-line-strong: #E2E2E2;
    --fc-text-secondary: #F6F6F6;
    --fc-text-tertiary: #E2E2E2;
  }
}

* { box-sizing: border-box; }

html, body {
  height: 100%;
  margin: 0;
}

body {
  display: grid;
  /* row 1 = top bar, row 2 = the three-column workspace, row 3 = status line */
  grid-template-rows: auto minmax(0, 1fr) auto;
  background: var(--fc-bg-canvas);
  color: var(--fc-text-primary);
  font-family: var(--fc-font-ui);
  font-size: var(--fc-font-body);
  overflow: hidden;
}

/* ---------------------------------------------------------------- top bar */

#topbar,
.topbar {
  display: flex;
  align-items: center;
  flex-wrap: wrap;                /* <=560px 整条换行，不挤压出横向滚动 */
  gap: var(--fc-space-8) var(--fc-space-16);
  min-height: var(--fc-titlebar-h);
  padding: var(--fc-space-8) var(--fc-space-12);
  background: var(--fc-bg-surface);
  border-bottom: 1px solid var(--fc-line-subtle);
}

.brand,
#app-title {
  font-size: var(--fc-font-heading);
  font-weight: 600;
  color: var(--fc-text-primary);
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

#toolbar,
#actions,
.toolbar {
  display: flex;
  align-items: center;
  gap: var(--fc-space-4);
}

#toolbar {
  padding: var(--fc-space-4);
  border: 1px solid var(--fc-line-subtle);
  border-radius: var(--fc-radius-md);
  background: var(--fc-bg-sunken);
}

#selectors,
.selectors {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--fc-space-8) var(--fc-space-12);
  margin-left: auto;
  min-width: 0;
}

.field {
  display: flex;
  align-items: center;
  gap: var(--fc-space-8);
  min-width: 0;
  color: var(--fc-text-secondary);   /* 6.9:1 on surface / 6.4:1 on sunken */
}

.field__label { white-space: nowrap; }

/*
  触达目标：44px 下限（--fc-hit-min 的**字面量**副本，故意保留 —— r6 的 c9 腿按字面量判，
  且 var() 万一解析失败也不会掉回浏览器默认高度）。两处不一致时 check_css.py 判红。
*/
select,
input[type="text"],
input[type="search"],
textarea,
button {
  font: inherit;
  color: var(--fc-text-primary);
  background: var(--fc-bg-surface);
  border: 1px solid var(--fc-line-subtle);
  border-radius: var(--fc-radius-sm);
  min-height: 44px;
}

/* 占位文案默认是 UA 浅灰（Firefox 还会再乘 opacity），显式给一个达标的颜色 */
::placeholder {
  color: var(--fc-text-secondary);
  opacity: 1;
}

a {
  color: var(--fc-text-brand);
  text-underline-offset: 2px;
}

select {
  padding: var(--fc-space-4) var(--fc-space-8);
  min-height: var(--fc-hit-min);
}

button { cursor: pointer; }

/*
  悬停规则一律进 (hover:hover) 守卫：触屏上"点过的控件把 hover 底色粘住"是 x2-u6 F6 的实测欠账。
  就地包住而不是搬到文件末尾 —— 与 .step[data-active="1"] 的先后顺序必须原样保留（同权重后写的赢）。
*/
@media (hover: hover) and (pointer: fine) {
  button:hover { background: var(--fc-bg-hover); }
}

button:disabled,
select:disabled,
textarea:disabled {
  color: var(--fc-text-disabled);
  cursor: not-allowed;
}

/* 键盘焦点环：浅色 #1B2B95 / 深色 #AEA0F7，对三种底色都 >= 3:1（WCAG 1.4.11）。
   宽度/偏移 = 主页口径（docs/external/index.html 的 :focus-visible 3px/2px；统一顶栏 shell.js
   自补的也是 3px/2px）—— 同一次键盘操作里忽粗忽细是 x2-u11 F7 记的欠账，这里并到主页那一档。 */
button:focus-visible,
select:focus-visible,
input:focus-visible,
textarea:focus-visible,
a:focus-visible,
summary:focus-visible,
[tabindex]:focus-visible {
  outline: 3px solid var(--fc-focus-ring);
  outline-offset: 2px;
}

.tool {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--fc-hit-min);
  height: var(--fc-hit-min);
  padding: 0;
  border-color: transparent;
  background: transparent;
  transition: background var(--fc-motion-fast) var(--fc-ease-standard);
}

.tool__glyph {
  width: 20px;
  height: 20px;
}

.tool[aria-pressed="true"] {
  background: var(--fc-bg-selected);
  border-color: var(--fc-brand-solid);
  color: var(--fc-text-brand);     /* 5.21:1 on --fc-bg-selected */
}

.action {
  min-height: var(--fc-hit-min);
  min-width: 7.5rem;               /* 文案到位前后宽度不变（x2-u1 F7：不许把工具栏从窄推宽） */
  padding: var(--fc-space-4) var(--fc-space-12);
}

.action--primary {
  background: var(--fc-brand-solid);
  border-color: var(--fc-brand-solid);
  color: var(--fc-text-on-brand);  /* 4.83:1 on --fc-brand-solid */
}

.action--primary:hover { background: var(--fc-brand-hover); }

/* 跳到主内容（r6 c17；HTML 侧放 <a class="skip" href="#workspace"> 即可生效） */
.skip,
.skip-link {
  position: absolute;
  top: -4rem;
  left: var(--fc-space-8);
  z-index: 50;
  display: inline-flex;
  align-items: center;
  min-height: var(--fc-hit-min);
  padding: 0 var(--fc-space-12);
  background: var(--fc-bg-surface);
  color: var(--fc-text-brand);
  border: 1px solid var(--fc-line-strong);
  border-radius: var(--fc-radius-sm);
  text-decoration: none;
}

.skip:focus,
.skip-link:focus { top: var(--fc-space-8); }

/* ------------------------------------- infra 服务导航条（由 /web/js/shell.js 注入） */

/*
  shell.js 在 body 最前面现造 nav#infra-svc-shell（第 1 行），并把 body 的行模板从三行改成
  四行 auto auto minmax(0,1fr) auto（它只用 CSSOM 改这一条，其余样式**一概不设** —— CSP 是
  style-src 'self'，它不许建 <style>）⇒ 这一段是该导航条唯一的样式来源，删了它整条会变成裸样式。
  三态在 data-svc-state 上（live / self / unset），配色按态分；unset 态没有 href，不给"空链接"。
*/
#infra-svc-shell,
.infra-svc-shell {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--fc-space-2) var(--fc-space-12);
  min-height: var(--fc-titlebar-h);
  padding: var(--fc-space-4) var(--fc-space-12);
  background: var(--fc-bg-sunken);
  border-bottom: 1px solid var(--fc-line-subtle);
  color: var(--fc-text-secondary);      /* 6.4:1 on sunken（浅）/ 12.1:1（深） */
  font-size: 13px;
}

#infra-svc-shell a,
.infra-svc-shell__home,
.infra-svc-shell__entry,
.infra-svc-shell__peer {
  display: inline-flex;
  align-items: center;
  min-height: var(--fc-hit-min);
  color: var(--fc-text-brand);          /* 5.79:1 on sunken（浅）/ 10.4:1（深） */
  text-decoration: none;
}

#infra-svc-shell a:hover { text-decoration: underline; }

.infra-svc-shell__sep,
.infra-svc-shell__group,
.infra-svc-shell__brand {
  color: var(--fc-text-secondary);
  white-space: nowrap;
}

.infra-svc-shell__brand { margin-left: auto; }

.infra-svc-shell__peer[data-svc-state="self"] {
  padding: 0 var(--fc-space-8);
  border: 1px solid var(--fc-brand-solid);
  border-radius: var(--fc-radius-full);
  background: var(--fc-bg-selected);
  color: var(--fc-text-brand);          /* 5.21:1 on --fc-bg-selected（浅） */
}

.infra-svc-shell__peer[data-svc-state="unset"] {
  color: var(--fc-text-secondary);
  border-bottom: 1px dotted var(--fc-line-strong);
}

/* ------------------------------------------- three-column workspace (main) */

/*
  The three columns are input / steps / render. minmax(0, ...) on the middle column is what
  keeps a long LaTeX line inside its pane instead of stretching the grid (a bare 1fr refuses
  to shrink below its content and would push the third column off screen).
*/
#workspace,
.workspace {
  display: grid;
  grid-template-columns: var(--om-col-input) var(--om-col-steps) var(--om-col-render);
  gap: 1px;
  min-height: 0;
  background: var(--fc-line-subtle); /* shows through the 1px gaps as column rules */
}

.pane,
#input-pane,
#steps-pane,
#render-pane,
#latex-pane,
[data-pane] {
  display: flex;
  flex-direction: column;
  min-width: 0;
  min-height: 0;
  background: var(--fc-bg-canvas);
}

#steps-pane,
[data-pane="steps"] { background: var(--fc-bg-surface); }

.pane__title,
.pane > h2,
[data-pane] > h2 {
  margin: 0;
  padding: var(--fc-space-8) var(--fc-space-12);
  font-size: var(--fc-font-body);
  font-weight: 600;
  color: var(--fc-text-secondary);
  background: var(--fc-bg-sunken);
  border-bottom: 1px solid var(--fc-line-subtle);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.pane__body,
.pane__scroll {
  flex: 1 1 auto;
  min-height: 0;
  padding: var(--fc-space-12);
  overflow: auto;
  overflow-wrap: anywhere;         /* 长串（URL/长公式）换行而不是把窗子撑宽 */
}

/* ---- column 1: input ---- */

#expr-input,
#latex-source,
#input-tex,
#input-expr,
.expr-input,
[data-pane="input"] textarea,
[data-pane="render"] textarea {
  flex: 1 1 auto;
  min-height: 120px;
  width: 100%;
  padding: var(--fc-space-8);
  resize: none;
  font-family: var(--fc-font-mono);
  font-size: var(--fc-font-body);
  line-height: 1.5;
  tab-size: 2;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

#op-bar,
#latex-actions,
.input-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--fc-space-8);
  padding: var(--fc-space-8) var(--fc-space-12);
  border-top: 1px solid var(--fc-line-subtle);
  background: var(--fc-bg-surface);
}

.examples,
#examples {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: var(--fc-space-2);
}

.examples li,
#examples li {
  display: flex;
  align-items: center;
  min-height: var(--fc-hit-min);
  padding: var(--fc-space-4) var(--fc-space-8);
  border-radius: var(--fc-radius-sm);
  font-family: var(--fc-font-mono);
  font-size: var(--fc-font-body);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  cursor: pointer;
}

@media (hover: hover) and (pointer: fine) {
  .examples li:hover,
  #examples li:hover { background: var(--fc-bg-hover); }
}

/* ---- column 2: step list ---- */

#steps,
.steps,
ol.steps {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: var(--fc-space-8);
  counter-reset: om-step;
}

/*
  这里 #steps > li（权重 1,0,2）与 li.step 是同一批卡片的两种写法；它会压过任何纯 class
  规则（如 .step:hover 的 0,2,0）。实测证据：不把态规则写成 #steps > li.step:hover（1,2,1），
  悬停底色与「当前步」高亮在真实页面上**根本不生效**（被 #steps > li 的 background 与
  border-left 简写盖掉，check_contrast.py 在 hover-step / active-step 场景里量到的是旧底色）。
  改动这两组选择器时不要退回纯 class。
*/
.step,
#steps > li {
  position: relative;
  padding: var(--fc-space-8) var(--fc-space-12) var(--fc-space-12);
  background: var(--fc-bg-canvas);
  border: 1px solid var(--fc-line-subtle);
  border-left: 3px solid var(--fc-line-strong);
  border-radius: var(--fc-radius-md);
  transition: background var(--fc-motion-fast) var(--fc-ease-standard);
  cursor: pointer;                 /* app.js:436 真绑了 click/keydown */
}

@media (hover: hover) and (pointer: fine) {
  .step:hover,
  #steps > li.step:hover { background: var(--fc-bg-hover); }
}

.step[data-active="1"],
.step--active,
#steps > li.step[data-active="1"],
#steps > li.step--active {
  border-left-color: var(--fc-brand-solid);
  background: var(--fc-bg-selected);
}

.step__head {
  display: flex;
  align-items: baseline;
  gap: var(--fc-space-8);
  margin-bottom: var(--fc-space-4);
}

.step__index {
  counter-increment: om-step;
  min-width: 2em;
  color: var(--fc-text-tertiary);
  font-family: var(--fc-font-mono);
  font-variant-numeric: tabular-nums;
}

/*
  三处「底色变了但文字色没跟着变」的实测欠账（check_contrast.py BEFORE 判定行）：
    .step__index 在 --fc-bg-hover / --fc-bg-selected 上：4.46:1 / 4.02:1（浅）；
                 在 --fc-bg-hover / --fc-bg-selected 上：4.09:1 / 4.10:1（深）
  => 只在「底色被换掉」的两种态里把序号提到 --fc-text-secondary（浅 6.4 / 深 8.1）。
*/
@media (hover: hover) and (pointer: fine) {
  .step:hover .step__index,
  #steps > li.step:hover .step__index { color: var(--fc-text-secondary); }
}

/* 选中态**不跟着**悬停一起被守卫：触屏上没有 hover，而这一条正是那档对比度欠账的解药。 */
.step[data-active="1"] .step__index,
#steps > li.step[data-active="1"] .step__index { color: var(--fc-text-secondary); }

/* When the row index is a render target of its own, keep a visible "n." prefix. */
.step__index:empty::before { content: counter(om-step) "."; }

.step__rule,
.step__title {
  flex: 1 1 auto;
  min-width: 0;
  font-weight: 600;
  color: var(--fc-text-primary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.step__rule {
  font-family: var(--fc-font-mono);
  font-size: var(--fc-font-body);
  color: var(--fc-text-brand);     /* 6.21:1 on canvas / 5.21:1 on selected */
}

.step__source,
.step__badge {
  flex: 0 0 auto;
  padding: 0 var(--fc-space-8);
  border: 1px solid var(--fc-line-subtle);
  border-radius: var(--fc-radius-full);
  font-size: 12px;
  color: var(--fc-text-secondary); /* 原 --fc-text-tertiary 在被悬停的卡上只有 4.46:1 */
  background: var(--fc-bg-surface);
}

.step__body {
  display: flex;
  flex-direction: column;
  gap: var(--fc-space-4);
  min-width: 0;
}

.step__math,
.step__expr {
  font-family: var(--fc-font-math);
  font-size: var(--fc-font-heading);
  line-height: 1.6;
  overflow-x: auto;
  overflow-y: hidden;
}

.step__assumptions {
  margin: var(--fc-space-4) 0 0;
  padding-left: var(--fc-space-16);
  color: var(--fc-text-secondary);
  font-size: 14px;
}

.step__assumptions li { margin: 0; }

.step--error,
.step[data-error="1"] {
  border-left-color: var(--fc-danger);
}

.empty,
.pane__empty {
  padding: var(--fc-space-24) var(--fc-space-12);
  color: var(--fc-text-tertiary);
  text-align: center;
}

/* ---- column 3: rendered output ---- */

.math-display,
.math-block,
[data-pane="render"] .math {
  display: block;
  padding: var(--fc-space-16) var(--fc-space-12);
  overflow-x: auto;
  overflow-y: hidden;
  background: var(--fc-bg-surface);
  border: 1px solid var(--fc-line-subtle);
  border-radius: var(--fc-radius-md);
  font-family: var(--fc-font-math);
  font-size: var(--fc-font-title);
  line-height: 1.7;
  text-align: center;
}

.math,
.math-inline {
  font-family: var(--fc-font-math);
  line-height: 1.6;
}

/* Wrapper the renderer emits around its own (locally vendored) markup. */
.math__html,
[data-math-html] {
  display: block;
  min-width: 0;
  overflow-x: auto;
}

/*
  结果区空态（x2-u1 F8）：三栏里只有它首绘是"无信息的白框"——读者不知道这里以后会出现什么，
  而它正是本产品的核心产出。只给结构（虚线框 + 兜底高度），一个字面文案都不写（文案全走 i18n）。
  :empty 只在真没有子节点/文本时命中，结果一到就自动退出，不会与渲染物抢。
*/
#result:empty {
  min-height: 5rem;
  border-style: dashed;
}

/*
  错误框底色从 --fc-bg-sunken 换到 --fc-bg-surface：
  --fc-danger 在 sunken 上实测 4.16:1（浅色 FAIL），在 surface 上 4.70:1（PASS）；
  深色两种底色都达标（5.77 / 7.76），换底色不产生新的欠账。边框不变，形状靠 --fc-danger 描边。
*/
.math-error,
[data-math-error],
[data-render-error] {
  display: block;
  margin-top: var(--fc-space-8);
  padding: var(--fc-space-8) var(--fc-space-12);
  border: 1px solid var(--fc-danger);
  border-radius: var(--fc-radius-sm);
  background: var(--fc-bg-surface);
  color: var(--fc-danger);
  font-family: var(--fc-font-mono);
  font-size: 14px;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* ------------------------------------------------- LaTeX source (monospace) */

/*
  Every place that shows LaTeX *source* (as opposed to rendered math) must be monospace and
  must not be re-typeset by the math face; ligatures off so -- / -> / <= read literally.
*/
.tex,
pre.tex-source,
code.tex,
[data-latex-source],
.tex__source,
.latex-source {
  display: block;
  margin: 0;
  padding: var(--fc-space-8);
  background: var(--fc-bg-sunken);
  border: 1px solid var(--fc-line-subtle);
  border-radius: var(--fc-radius-sm);
  color: var(--fc-text-secondary);
  font-family: var(--fc-font-mono);
  font-size: 14px;
  line-height: 1.5;
  font-variant-ligatures: none;
  font-feature-settings: "liga" 0, "calt" 0;
  tab-size: 2;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  overflow-x: auto;
}

.step__latex,
.step__tex {
  margin-top: var(--fc-space-4);
}

pre,
code,
kbd,
samp {
  font-family: var(--fc-font-mono);
  font-variant-ligatures: none;
}

pre { overflow-x: auto; }

code {
  padding: 1px var(--fc-space-4);
  background: var(--fc-bg-sunken);
  border-radius: var(--fc-radius-sm);
  font-size: 0.94em;
}

pre code {
  padding: 0;
  background: none;
}

.copy {
  position: absolute;
  top: var(--fc-space-8);
  right: var(--fc-space-8);
  min-height: var(--fc-hit-min);
  padding: 0 var(--fc-space-8);
  font-size: 12px;
  color: var(--fc-text-secondary);
  background: var(--fc-bg-surface);
  border-color: var(--fc-line-subtle);
}

/* ------------------------------------------------------------- status line */

#bottom,
.bottombar,
footer#status {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--fc-space-4) var(--fc-space-12);
  padding: var(--fc-space-8) var(--fc-space-12);
  background: var(--fc-bg-surface);
  border-top: 1px solid var(--fc-line-subtle);
}

#status,
.status {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--fc-space-4) var(--fc-space-16);
  flex: 1 1 auto;
  min-width: 0;
}

.seg {
  display: inline-flex;
  align-items: baseline;
  gap: var(--fc-space-4);
  min-width: 0;
  white-space: nowrap;
}

.seg__label { color: var(--fc-text-tertiary); }   /* 5.0:1 on surface */

.seg__value { color: var(--fc-text-primary); }

.seg--message { flex: 1 1 100%; }

.seg--message .seg__value {
  color: var(--fc-text-secondary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

#status[data-message-kind="error"] .seg--message .seg__value { color: var(--fc-danger); }

#status[data-message-kind="ok"] .seg--message .seg__value { color: var(--fc-success); }

/*
  Locale switch feedback: filled keys are normal, missing keys are visibly flagged.
  app.js:210 缺键时会填**派生文案**（不是空节点），所以这仍是一段要读的文字：
  原 --fc-text-disabled 实测 2.55:1（浅/深都 FAIL）=> 正文色 + 点线标出「这是派生值」。
*/
[data-i18n-missing="1"] {
  color: var(--fc-text-secondary);
  text-decoration: underline dotted var(--fc-warning);
  text-decoration-thickness: 1px;
  text-underline-offset: 2px;
}

/* ------------------------------------------------------------ busy / a11y */

[aria-busy="true"] { cursor: progress; }

[hidden] { display: none !important; }

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
}

/* ------------------------------------------------- 触屏（touch）与首屏骨架 */

/*
  触屏三件套（x2-u6 F6 实测全 0 处）：
  ① touch-action: manipulation —— 手机上"没反应再点一次"（连点化简/求导）不会被吃成双击缩放；
  ② -webkit-tap-highlight-color: transparent —— 去掉盖在自绘控件上的灰色 tap 块（暗色尤其脏）；
  ③ 按压反馈用 filter 而不是改底色：本文件里只有 color+background **成对**出现在同一条规则，
     才会进 check_contrast.py 的判据 ⇒ filter 不产生新的对比度欠账，深浅两套都成立。
  悬停规则不在这里：它们各自就地在原位置被 (hover:hover) 守卫包住（顺序即语义，见那几处注释）。
  MIN_HIT（44px）不许因为"省地方"改小：窄屏要省的是词条数，不是命中区。
*/
button,
select,
textarea,
.action,
.tool,
.examples li,
#examples li,
.step,
#steps > li,
#infra-svc-shell a,
.infra-svc-shell__home,
.infra-svc-shell__entry,
.infra-svc-shell__peer,
a.skip,
.skip-link {
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
}

.action:active,
.tool:active,
.examples li:active,
#examples li:active { filter: brightness(0.94); }

/*
  首屏骨架（x2-u1 F7）：首绘时 14/16 个 data-i18n 节点是空的（三个栏位标题、语言标签、状态段位、
  6 个按钮），读者看到的是"没有字的按钮 + 没有标题的栏位"，文案到位时按钮还会从窄被推到宽。
  这里只做两件事：① 给**空节点**画占位底色 + 呼吸（不写任何字面文案、不引 i18n 键）；② .action
  定最小宽度（见上面 .action 规则）消掉文案到位前后的工具栏抖动。
  data-api 由 app.js 写（booting -> ready/offline/error），本文件只读它；booting 时这些节点本来
  就是空的 ⇒ 不需要 color:transparent（省掉"文字永久不可见"这种失败形态）。
  呼吸动画被 (prefers-reduced-motion: reduce) 的全局 animation:none 收掉。
*/
html[data-api="booting"] .pane__title,
html[data-api="booting"] .field__label,
html[data-api="booting"] .seg__label,
html[data-api="booting"] .action,
html[data-api="booting"] #lang-select {
  background: var(--fc-bg-sunken);
  border-color: var(--fc-line-subtle);
  animation: om-boot-pulse 1.2s var(--fc-ease-standard) infinite alternate;
}

@keyframes om-boot-pulse {
  from { opacity: 0.55; }
  to { opacity: 1; }
}

/* ------------------------------------------------------------ responsive */

/* 宽屏：栏宽加宽 + 正文字号上一档（行高比例不变） */
@media (min-width: 1440px) {
  :root {
    --om-col-input: minmax(280px, 23%);
    --om-col-steps: minmax(360px, 1fr);
    --om-col-render: minmax(400px, 30%);
    --fc-font-body: 16px;
    --fc-font-heading: 18px;
    --fc-font-title: 22px;
  }

  .pane__body,
  .pane__scroll { padding: var(--fc-space-16); }

  .step { padding: var(--fc-space-12) var(--fc-space-16) var(--fc-space-16); }
}

/* 超宽：只加留白并给正文一个阅读宽度上限，行宽不跟着屏幕无限拉长 */
@media (min-width: 1920px) {
  .pane__body,
  .pane__scroll { padding: var(--fc-space-24); }

  .step__body,
  .step__math { max-width: var(--om-measure); }

  .math-display,
  .math-block { max-width: 1600px; margin-inline: auto; }
}

/* Narrow window: keep input + steps side by side, move the render pane under them. */
@media (max-width: 1180px) {
  #workspace,
  .workspace {
    grid-template-columns: minmax(240px, 34%) minmax(0, 1fr);
    grid-template-rows: minmax(0, 1fr) minmax(0, 1fr);
    overflow: auto;
  }

  #render-pane,
  #latex-pane,
  [data-pane="render"] { grid-column: 1 / -1; }
}

/* Single column: pane stack, page scrolls as a whole. */
@media (max-width: 760px) {
  /*
    x2-u6 F3 的根因复位：<=1180px 那档给 #workspace 留了 overflow:auto，而这一档没复位 ⇒
    360/760px 上是**两个滚动盒**（body 定高 100% + #workspace）：window.scrollY 恒 0、
    移动端地址栏不收起、iOS 弹键盘滚的是内层。三条缺一不可，别只改 body 那一条。
  */
  html, body { height: auto; min-height: 100%; }
  body { overflow: visible; }

  #workspace,
  .workspace {
    display: flex;
    flex-direction: column;
    gap: var(--fc-space-8);
    background: var(--fc-bg-canvas);
    overflow: visible;              /* 复位 <=1180px 的 overflow:auto —— F3 的根因就是它 */
  }

  .pane,
  #input-pane,
  #steps-pane,
  #render-pane,
  #latex-pane,
  [data-pane] {
    flex: 0 0 auto;
    border: 1px solid var(--fc-line-subtle);
    border-radius: var(--fc-radius-md);
  }

  .pane__body,
  .pane__scroll { overflow: visible; }

  #expr-input,
  #latex-source,
  #input-tex,
  #input-expr,
  [data-pane="input"] textarea { min-height: 96px; }
}

/* 窄手机：顶栏换行、选择器整行、操作按钮 2 列、状态条换行而不是截断 */
@media (max-width: 560px) {
  .brand,
  #app-title { flex: 1 1 auto; }

  #selectors,
  .selectors {
    margin-left: 0;
    width: 100%;
  }

  .field {
    width: 100%;
    justify-content: space-between;
  }

  .field select {
    flex: 1 1 auto;
    min-width: 0;
  }

  #op-bar,
  #latex-actions,
  .input-actions {
    display: grid;
    grid-template-columns: 1fr 1fr;
  }

  #op-bar .action,
  #latex-actions .action,
  .input-actions .action { width: 100%; }

  .pane__title,
  .pane > h2,
  [data-pane] > h2 {
    white-space: normal;
    overflow-wrap: anywhere;
    text-overflow: clip;
  }

  .step__rule,
  .step__title,
  .step__head {
    flex-wrap: wrap;
    white-space: normal;
    overflow-wrap: anywhere;
  }

  .seg { white-space: normal; }

  .seg--message .seg__value {
    overflow: visible;
    text-overflow: clip;
    white-space: normal;
    overflow-wrap: anywhere;
  }
}

/* 小屏手机（含 320px）：只压留白，不压字号（正文 15px 是下限，不许再小） */
@media (max-width: 360px) {
  :root {
    --fc-titlebar-h: 34px;
    --om-gutter: var(--fc-space-4);
  }

  #topbar,
  .topbar { padding: var(--fc-space-4) var(--fc-space-8); }

  .pane__body,
  .pane__scroll { padding: var(--fc-space-8); }

  .pane__title,
  .pane > h2,
  [data-pane] > h2 { padding: var(--fc-space-4) var(--fc-space-8); }

  #op-bar,
  #latex-actions,
  .input-actions {
    grid-template-columns: 1fr;
    padding: var(--fc-space-8);
  }

  .step {
    padding: var(--fc-space-8);
    border-radius: var(--fc-radius-sm);
  }

  .step__index { min-width: 1.6em; }

  #status,
  .status {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    gap: var(--fc-space-2) var(--fc-space-8);
  }

  .seg { justify-content: space-between; }

  .seg--message { grid-column: 1 / -1; }
}

/* 横屏手机 / 矮窗：削掉竖向 chrome，把高度留给工作区 */
@media (max-height: 520px) {
  #topbar,
  .topbar { padding-block: var(--fc-space-4); }

  .pane__title,
  .pane > h2,
  [data-pane] > h2 { padding-block: var(--fc-space-4); }

  #bottom,
  .bottombar,
  footer#status { padding-block: var(--fc-space-4); }
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    transition: none !important;
    animation: none !important;
  }

  html { scroll-behavior: auto !important; }
}

@media print {
  body {
    display: block;
    overflow: visible;
    background: #FFFFFF;
    color: #000000;
  }

  #topbar,
  #bottom,
  footer#status,
  .toolbar,
  #op-bar,
  #latex-actions,
  .input-actions,
  .copy,
  .skip,
  .skip-link { display: none !important; }

  #workspace,
  .workspace {
    display: block;
    background: none;
  }

  .pane,
  #input-pane,
  #steps-pane,
  #render-pane,
  #latex-pane,
  [data-pane] {
    border: 0;
    background: none;
    break-inside: avoid;
  }

  .step { break-inside: avoid; }

  /* 纸上没有主题：全部落回黑字白底（令牌在打印时不再有意义） */
  .pane__title,
  .step__index,
  .step__rule,
  .step__math,
  .step__assumptions,
  .math-display,
  .tex,
  code,
  .seg__label,
  .seg__value,
  [data-i18n-missing="1"] {
    color: #000000 !important;
    background: none !important;
    text-decoration: none !important;
  }

  .step,
  .math-display,
  .math-error,
  [data-math-error],
  [data-render-error],
  #expr-input,
  #latex-source,
  [data-pane] textarea {
    border-color: #000000 !important;
    background: #FFFFFF !important;
  }

  #expr-input,
  #latex-source,
  [data-pane] textarea {
    overflow: visible;
    white-space: pre-wrap;
  }
}
