/* app.css -- component styles for the Pinyin Editor.
 *
 * Every rule is scoped under `.toz-app` and nothing here touches `html`,
 * `body`, `dialog` or bare element selectors outside the root. That is what
 * makes the same stylesheet safe to drop inside a WordPress theme: the theme
 * keeps its own page background, header, footer, base font and button styling,
 * and two editors can coexist on one page.
 *
 * Colours are consumed as `var(--toz-*, fallback)` but NOT defined here --
 * each host decides them:
 *   standalone  -> css/standalone.css  (concrete light/dark palette + shell)
 *   WordPress   -> assets/css/theme.css (theme presets, currentColor, inherit)
 */

.toz-app {
  /* Fallbacks only. Hosts override every one of these. */
  --toz-line: rgba(0, 0, 0, .16);
  --toz-line-soft: rgba(0, 0, 0, .09);
  --toz-muted: rgba(0, 0, 0, .58);
  --toz-accent: #1f6f5c;
  --toz-accent-ink: #fff;
  --toz-accent-soft: rgba(31, 111, 92, .14);
  --toz-changed: #f9ee21;
  --toz-locked: #2f5fa8;
  --toz-gap: 12px;
  --toz-pad: 12px;
  --toz-radius: 10px;
  /* Main-area height. Standalone pins this to the viewport; in a theme it stays
     `auto` so the editor grows with its content instead of setting a page-height
     block inside someone's article. */
  --toz-h: auto;
  --toz-font: "ToneOZ-Pinyin-Kai-Simplified-GB2312", serif;
  --toz-ui: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC",
           "Hiragino Sans GB", "Microsoft YaHei", sans-serif;

  display: flex;
  flex-direction: column;
  min-height: 0;
  color: inherit;              /* never impose a text colour on the theme */
  font: 14px/1.5 var(--toz-ui);
  text-align: left;
  opacity: 0;
  transition: opacity .15s;
  container-type: inline-size; /* so the grid can react to column width, not
                                  viewport width -- a 700px theme column inside
                                  a 1600px window must still collapse */
}
.toz-app.is-ready { opacity: 1; }
.toz-app.is-failed { opacity: 1; }
/* `fitHeight()` in app.js measures the room between the host's header and its
   footer and writes it to --toz-h, which the `min-height` above picks up. That is
   the entire fill mechanism, and it is deliberately grow-only.

   A hard `height: var(--toz-h)` was tried and it is wrong in both directions.
   `min-height` only ever grows the editor into a gap, which is the whole point; a
   hard height also SHRINKS it, and shrinks hardest where the fill is least wanted
   -- at container widths too narrow for three columns, where the panes wrap onto
   two grid rows and `align-content` stretches `auto` rows to a fixed-height grid.
   The panes collapsed from 699px to 568px, i.e. the "fill" made the editor 131px
   shorter than it already was. And when the caps on the scroll areas were dropped
   at the same time, the live editor grew to 1110px inside a 700px window, pushing
   the footer to 1190px down the page. A page that already reaches its footer has
   no gap to close and must be left exactly as it is. */
/* `display:flex` above outranks the UA rule for [hidden], so restate it. */
.toz-app[hidden] { display: none; }

.toz-app *, .toz-app *::before, .toz-app *::after { box-sizing: border-box; }

/* Themes style `button`/`input` globally; claim them back inside the root. */
.toz-app button, .toz-app input, .toz-app textarea, .toz-app output {
  font: inherit; color: inherit; margin: 0; text-transform: none;
  letter-spacing: normal; line-height: normal;
}
/* Buttons are naked <button>s inside a page whose theme styles `button` hard, so
   the reset has to win -- but it must not win against the editor's OWN button
   styles. `.toz-app button` is (0,1,1); a bare `.toz-primary` is only (0,1,0), so
   the reset beat every component class and every button in the editor rendered
   as plain body text: no border, no fill, no font-size, and a tab underline of
   width 0. Hence every button rule below is scoped under `.toz-app` too, which
   makes them (0,2,0) and puts them on the right side of that line. */
.toz-app button { background: none; border: 0; cursor: pointer; }
.toz-app :focus-visible { outline: 2px solid var(--toz-accent); outline-offset: 2px; }

/* ------------------------------------------------------------------ chrome */

/* A grid, not `flex` + `space-between`: the export actions have to sit in the
   MIDDLE of the bar -- the same row as the badge, centred above the preview
   column -- and flex can only centre by luck, because it centres the leftover
   space, not the group. With `1fr auto 1fr` the buttons are exactly centred no
   matter how lopsided the title on the left and the badge on the right are. */
.toz-bar {
  display: grid; align-items: center; gap: 10px;
  grid-template-columns: 1fr auto 1fr;
  padding: 10px 16px; border-bottom: 1px solid var(--toz-line);
}
.toz-bar > :first-child { justify-self: start; min-width: 0; }
.toz-bar h1 { font-size: 15px; font-weight: 650; margin: 0; letter-spacing: .02em; }
.toz-sub { font-weight: 400; color: var(--toz-muted); font-size: 12px; }
.toz-export {
  justify-self: center; display: flex; gap: 8px; align-items: center;
  flex-wrap: wrap;
}
.toz-bar-right { justify-self: end; display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.toz-badge {
  font-size: 11px; color: var(--toz-muted); font-variant-numeric: tabular-nums;
  padding: 3px 8px; border: 1px solid var(--toz-line); border-radius: 99px;
}

/* ------------------------------------------------------------------ layout */

.toz-main {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  gap: var(--toz-gap);
  padding: var(--toz-pad);
  flex: 1; min-height: 0;
}
@container (min-width: 900px) {
  .toz-main {
    grid-template-columns: minmax(240px, 1fr) minmax(320px, 1.5fr) minmax(280px, 1fr);
  }
}

.toz-pane {
  display: flex; flex-direction: column; min-width: 0;
  border: 1px solid var(--toz-line); border-radius: var(--toz-radius);
  background: transparent;          /* let the theme page background through */
  overflow: hidden;
}
.toz-pane-head {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 9px 12px; border-bottom: 1px solid var(--toz-line); flex-wrap: wrap;
}
.toz-pane-head label { font-weight: 600; font-size: 12px; }
.toz-tools { display: flex; gap: 14px; align-items: center; flex-wrap: wrap; }
.toz-tool { font-size: 12px; color: var(--toz-muted); display: flex; gap: 6px; align-items: center; }
.toz-tool input[type=range] { width: 96px; accent-color: var(--toz-accent); }
.toz-pane-foot {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 7px 12px; border-top: 1px solid var(--toz-line); font-size: 12px;
}
.toz-spacer { flex: 1; }

/* ------------------------------------------------------------------- input */

.toz-src {
  flex: 1; min-height: 150px; width: 100%;
  border: 0; resize: vertical; padding: 12px;
  font: 15px/1.9 var(--toz-ui);
  background: transparent; outline: none;
}
.toz-warn { color: var(--toz-changed); }
.toz-muted { color: var(--toz-muted); }
.toz-small, .is-small { font-size: 12px; }

/* ----------------------------------------------------------------- preview */

/* Two views of one text, as tabs on the preview pane rather than a strip bolted
   underneath it: a second box below the first splits attention and, once the
   long annotated line wraps, pushes the strip off the pane entirely. */
.toz-viewtabs {
  display: flex; gap: 4px; padding: 0 8px;
  border-bottom: 1px solid var(--toz-line); flex-wrap: wrap;
}
/* Scoped under `.toz-app` so it outranks the `button` reset above. */
.toz-app .toz-viewtab {
  padding: 8px 12px 7px; background: transparent; cursor: pointer;
  color: var(--toz-muted); font-size: 12px; font-weight: 600;
  border-bottom: 2px solid transparent; margin-bottom: -1px;
}
.toz-app .toz-viewtab:hover { color: inherit; }
.toz-app .toz-viewtab.is-active { color: inherit; border-bottom-color: var(--toz-accent); }

.toz-preview {
  flex: 1; overflow: auto; padding: 16px 12px; min-height: 180px;
  /* The fallback must agree with `state.size` in app.js and with the range
     input's `value` in both markups -- three other places carry this default,
     and they are not derived from each other, so all four have to be edited
     together or the preview and its own size readout disagree on first paint. */
  font-family: var(--toz-font); font-size: var(--fs, 28px);
  line-height: 1.55; word-break: break-all;
}
/* Until the webfont arrives, the annotated pane is not merely unstyled -- it is
 * WRONG. A character whose reading differs from the default is emitted as a
 * Private Use Area codepoint (see the help text), and no system font has those
 * glyphs, so every changed reading renders as a tofu box while the default ones
 * render fine. The result looks like a broken tool rather than a pending one,
 * and for a 纠音 page that is the whole product.
 *
 * So dim it and label it, rather than showing boxes and saying nothing. The text
 * stays visible -- it IS correct -- and only the annotation is missing, which is
 * exactly what the label claims.
 *
 * `display:none` on the content would also work and is worse: the reader loses
 * the sense that anything is being computed. Nothing here is decorative; it is
 * the one thing on the page that is wrong, and it says so. */
.toz-preview.is-font-pending { opacity: .3; }
.toz-preview-pane { position: relative; flex: 1; display: flex; min-height: 0; }
.toz-fontnote {
  position: absolute; inset: 0; display: none;
  align-items: center; justify-content: center; text-align: center;
  font-family: var(--toz-ui); font-size: 13px; line-height: 1.6;
  color: var(--toz-muted); padding: 16px; pointer-events: none;
}
.toz-preview-pane.is-font-pending .toz-fontnote { display: flex; }
.toz-raw { color: inherit; }
.toz-raw.is-orphan { color: var(--toz-changed); }
.toz-unknown {
  color: var(--toz-muted); text-decoration: line-through wavy var(--toz-changed);
  text-decoration-thickness: 1px;
}
.toz-char { cursor: pointer; border-radius: 4px; transition: background .08s; }
.toz-char:hover { background: var(--toz-accent-soft); }
.toz-char.is-changed { color: var(--toz-changed); }
.toz-char.is-locked {
  text-decoration: underline; text-decoration-color: var(--toz-locked);
  text-underline-offset: .14em; text-decoration-thickness: 2px;
}
.toz-char.is-sel { background: var(--toz-accent-soft); box-shadow: 0 0 0 2px var(--toz-accent); }

/* The 纯拼音 tab: readings with no glyphs. Deliberately the UI font, not the
   pinyin font -- this is text to be READ, and at 26px the UI face is far more
   legible than a kai face cut for 1-em hanzi. Each syllable stays its own click
   target, so changing a reading costs the same here as in the annotated view. */
.toz-plain {
  flex: 1; overflow: auto; padding: 16px 12px; min-height: 180px;
  font: 26px/2 var(--toz-ui); word-break: break-all;
}
.toz-pd { cursor: pointer; border-radius: 4px; transition: background .08s; }
.toz-pd:hover { background: var(--toz-accent-soft); }
.toz-pd.is-changed { color: var(--toz-changed); }
.toz-pd.is-locked {
  text-decoration: underline; text-decoration-color: var(--toz-locked);
  text-underline-offset: .18em; text-decoration-thickness: 2px;
}
.toz-pd.is-sel { background: var(--toz-accent-soft); box-shadow: 0 0 0 2px var(--toz-accent); }
.toz-plainraw { color: inherit; }

/* ------------------------------------------------------------ char readout */

.toz-readbody { padding: 12px; overflow: auto; flex: 1; min-height: 0; }
.toz-empty { color: var(--toz-muted); font-size: 12px; padding: 8px 0; }

.toz-charhead { display: flex; gap: 12px; align-items: center; margin-bottom: 12px; }
/* 52px, not 62px: at 62px the kai glyph's ink overran the 1px border of this box
   on all four sides, so the frame read as broken rather than as a highlight. */
.toz-bigchar {
  font-family: var(--toz-font); font-size: 52px; line-height: 1.2;
  padding: 6px 12px; border: 1px solid var(--toz-line-soft); border-radius: 8px;
}
.toz-meta { min-width: 0; }
.toz-codepoint { font: 11px ui-monospace, Menlo, monospace; color: var(--toz-muted); }
.toz-origin { font-size: 12px; margin-top: 3px; }

.toz-readings { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 12px; }
/* A <button>, so scoped under `.toz-app` like every other button rule. */
.toz-app .toz-reading {
  cursor: pointer; padding: 7px 11px; border-radius: 7px;
  border: 1px solid var(--toz-line); background: transparent; color: inherit;
  font: 13px ui-monospace, Menlo, monospace;
}
.toz-app .toz-reading:hover { border-color: var(--toz-accent); }
.toz-app .toz-reading.is-sel {
  background: var(--toz-accent); border-color: var(--toz-accent); color: var(--toz-accent-ink);
}
.toz-tag {
  font-size: 9px; vertical-align: super; margin-left: 4px; opacity: .65; letter-spacing: .04em;
}

/* ----------------------------------------------------------------- buttons */

/* Scoped under `.toz-app` so they outrank the `button` reset, which is (0,1,1)
   and would otherwise strip the border and fill off every one of them. */
.toz-app .toz-primary {
  padding: 8px 14px; border-radius: 7px; border: 1px solid var(--toz-accent);
  background: var(--toz-accent); color: var(--toz-accent-ink);
  cursor: pointer; font-size: 13px; font-weight: 600;
}
.toz-app .toz-ghost {
  padding: 6px 11px; border-radius: 7px; border: 1px solid var(--toz-line);
  background: transparent; color: var(--toz-muted); cursor: pointer; font-size: 12px;
}
.toz-app .toz-ghost:hover { color: inherit; border-color: var(--toz-accent); }
.toz-app .toz-ghost.is-small { margin-top: 8px; padding: 5px 9px; }

/* The two export actions are the controls a reader actually reaches for, so both
   get a surface of their own rather than reading as bare text. `color-mix`
   against `currentColor` (not a fixed grey) is what keeps this legible on a white
   page, a dark page and any WordPress theme with no media query and no palette.
   The metrics match .toz-primary so the pair reads as one group. */
.toz-app .toz-export .toz-ghost {
  padding: 8px 14px; font-size: 13px; font-weight: 600; color: inherit;
  border-color: color-mix(in srgb, currentColor 30%, transparent);
  background: color-mix(in srgb, currentColor 8%, transparent);
}
.toz-app .toz-export .toz-ghost:hover { background: color-mix(in srgb, currentColor 14%, transparent); }

.toz-app button:disabled { opacity: .55; cursor: default; }
.toz-app button:disabled:hover { border-color: var(--toz-line); color: var(--toz-muted); }

/* -------------------------------------------------------------------- help */

.toz-helpdlg {
  max-width: 640px; width: calc(100vw - 32px);
  border: 1px solid var(--toz-line); border-radius: 12px;
  background: Canvas; color: CanvasText;
  padding: 22px 24px; font: 14px/1.5 var(--toz-ui); text-align: left;
}
.toz-helpdlg::backdrop { background: rgba(0, 0, 0, .45); }
.toz-helpdlg h2 { font-size: 14px; margin: 16px 0 8px; }
.toz-helpdlg h2:first-child { margin-top: 0; }
.toz-helpdlg ol { margin: 0; padding-left: 20px; font-size: 13px; line-height: 1.8; }
.toz-helpdlg code { font-size: 12px; }
.toz-helpdlg .toz-primary { margin-top: 18px; }

/* The licence / privacy / trial dialog. Same palette discipline as everything
 * else here: colours are CONSUMED as var(--toz-*, fallback), never defined, so
 * the standalone page and a WordPress theme both render it correctly with no
 * media query and no light/dark fork.
 *
 * It is taller than the help dialog on purpose -- three sections of legal text,
 * and a dialog that scrolls its own body rather than growing past the viewport
 * is what makes it readable on a laptop. The body gets the scroll, the heading
 * and the button stay put, so "I have read it" is always reachable without
 * scrolling back up. */
.toz-licensedlg {
  max-width: 680px; width: calc(100vw - 32px);
  /* vh FIRST, dvh after: `dvh` is the right unit where the URL bar moves and
   * is a progressive enhancement over `vh` here, so an engine that does not
   * recognise it still gets a working bound.
   *
   * The pixel clamp in app.js (clampLicDlg) is the real bound. Do not mistake
   * `overflow-y: auto` on this element for a safety net: it cannot be one.
   * `overflow` only scrolls content that exceeds the BOX, and when the box is
   * height:auto it grows to fit its content -- measured, a dialog forced to
   * 2334px in a 786px window with overflow-y:auto reported scrollHeight ==
   * clientHeight and stayed un-scrollable, button off-screen. A bound has to
   * bound the box; that means max-height, and it means something that does not
   * depend on a viewport unit resolving. */
  max-height: calc(100vh - 48px);
  max-height: calc(100dvh - 48px);
  border: 1px solid var(--toz-line); border-radius: 12px;
  background: Canvas; color: CanvasText;
  padding: 22px 24px; font: 14px/1.6 var(--toz-ui); text-align: left;
}
/* `display: flex` belongs on [open], and ONLY there.
 *
 * The UA stylesheet hides a closed dialog with `dialog:not([open]) { display:
 * none }`. A `display` in an author stylesheet beats a `display` in the UA
 * stylesheet whatever their specificity, so putting `display: flex` on
 * `.toz-licensedlg` unconditionally overrode that and left the CLOSED dialog
 * laid out in normal flow, 498px tall with a live-looking button in it.
 *
 * That is the whole "the dialog will not close" report, and it was four
 * reports long. Reproduced here: with the dialog closed and the loading
 * overlay gone, offsetHeight 498, display flex, button 36px.
 *
 * It looked like a second dialog appearing after the progress bar, because
 * `.toz-loading` is `position:fixed; inset:0; z-index:1000` and had been
 * covering it the entire time; the overlay leaving is what revealed it. It was
 * never a modal, so it never dimmed the page, never made the rest inert, and
 * `close()` on it did nothing because it was already closed -- which is why
 * every probe of "can this be dismissed" passed while the thing on screen was
 * unreachable.
 *
 * The same trap is already handled for [hidden] above; this is the second
 * instance of the identical mistake, so the pattern is worth naming: an author
 * `display` silently cancels a UA rule that was doing structural work.
 */
.toz-licensedlg[open] { display: flex; flex-direction: column; }
.toz-licensedlg::backdrop { background: rgba(0, 0, 0, .55); }
.toz-licensedlg h2 { font-size: 15px; margin: 0 0 12px; }
.toz-licscroll {
  overflow-y: auto; overscroll-behavior: contain;
  /* min-height:0 is load-bearing in a flex column: without it the scroll
   * container refuses to shrink below its content and the dialog's own
   * max-height does nothing, so the button ends up off-screen. */
  min-height: 0; flex: 1 1 auto;
  padding-right: 6px; margin-right: -6px;
  font-size: 13px;
}
.toz-licensedlg h3 {
  font-size: 13px; margin: 18px 0 6px;
  padding-bottom: 4px;
  border-bottom: 1px solid color-mix(in srgb, currentColor 14%, transparent);
}
.toz-licensedlg h3:first-child { margin-top: 0; }
.toz-licensedlg p { margin: 0 0 8px; }
.toz-licensedlg ul { margin: 0 0 8px; padding-left: 20px; }
.toz-licensedlg li { margin-bottom: 4px; }
.toz-licensedlg code {
  font-size: 12px;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
.toz-licensedlg b { font-weight: 600; }
.toz-licnote {
  border-inline-start: 3px solid var(--toz-accent, #1f6f5c);
  padding-inline-start: 10px;
  color: var(--toz-muted, #6a6a66);
}
/* flex: none, so the confirm button cannot be squeezed out of view by a long
 * body on a short viewport -- which is the one failure that would make the
 * dialog un-dismissible in practice rather than by design. */
.toz-licensedlg .toz-licenseok { flex: none; margin-top: 16px; }

/* ------------------------------------------------------------- narrow host */
/* A block editor / shortcode widget can be far narrower than the viewport. */

@container (max-width: 460px) {
  /* The bar's 3-column grid cannot survive a column this narrow: the badge is
     ~300px of unbreakable text, so `auto` + `1fr` overflows the container. */
  .toz-bar { grid-template-columns: 1fr; justify-items: center; }
  .toz-bar > :first-child, .toz-export, .toz-bar-right { justify-self: center; }
  .toz-badge { text-align: center; }
  .toz-tool input[type=range] { width: 72px; }
}
.toz-loading {
  position: fixed;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--toz-bg, #f6f6f4);
  color: var(--toz-ink, #1c1c1a);
  z-index: 1000;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
}
.toz-loading-box {
  text-align: center;
  padding: 24px;
  background: var(--toz-panel, #fff);
  border-radius: 12px;
  box-shadow: 0 6px 20px rgba(0,0,0,.08);
}
.toz-loading-bar {
  width: 240px;
  height: 8px;
  background: var(--toz-line-soft, #ecece8);
  border-radius: 4px;
  overflow: hidden;
  margin: 12px 0 8px;
}
.toz-loading-bar-inner {
  width: 0%;
  height: 100%;
  background: var(--toz-accent, #1f6f5c);
  transition: width .5s ease;
}
.toz-loading-percent {
  font-size: 13px;
  color: var(--toz-muted, #7d7d78);
}
.toz-download-wrap {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--toz-pad);
  width: 100%;
}

.toz-download {
  padding: 10px 16px;
  font-size: 14px;
  border-radius: var(--toz-radius);
}
@media (min-width: 769px) {
  .toz-download-wrap--phone {
    display: none;
  }
}
