:root {
  color-scheme: light;
  --border: #d7d7d7;
  --accent: #2f5fa8;
}

body {
  font-family: -apple-system, "Helvetica Neue", Arial, sans-serif;
  max-width: 800px;
  margin: 2rem auto;
  padding: 0 1rem;
  color: #222;
}

header h1 {
  margin-bottom: 0.25rem;
}

.hint {
  color: #666;
  font-size: 0.9rem;
}

#toolbar {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  align-items: center;
  margin: 1rem 0 0.5rem;
  /* Sticky, not fixed: it scrolls normally until it reaches the top of
     the viewport, then stays there — so on a long note you don't have
     to scroll all the way back up to change formatting. */
  position: sticky;
  top: 0;
  z-index: 20;
  background: #fff;
  padding: 0.5rem 0;
  box-shadow: 0 1px 0 var(--border);
}

#font-select {
  padding: 0.3rem 0.4rem;
  border: 1px solid var(--border);
  border-radius: 4px;
  font-size: 0.85rem;
  background: #fff;
}

#toolbar .group {
  display: flex;
  gap: 0.25rem;
  padding-right: 0.5rem;
  border-right: 1px solid var(--border);
}

#toolbar button {
  padding: 0.35rem 0.6rem;
  border: 1px solid var(--border);
  background: #fff;
  border-radius: 4px;
  cursor: pointer;
  font-size: 0.85rem;
  line-height: 1.2;
}

#toolbar button:hover {
  background: #f0f0f0;
}

#toolbar button.active {
  background: var(--accent);
  color: #fff;
  border-color: var(--accent);
}

#save-btn {
  margin-left: auto;
  background: var(--accent);
  color: #fff;
  border-color: var(--accent);
}

#save-status {
  font-size: 0.85rem;
  color: #666;
}

#dropzone {
  border: 2px dashed var(--border);
  border-radius: 6px;
  padding: 0.75rem 1rem;
  text-align: center;
  color: #888;
  font-size: 0.9rem;
  margin-bottom: 1rem;
  transition: background 0.15s, border-color 0.15s;
}

#dropzone.dragging {
  background: #eef3fb;
  border-color: var(--accent);
  color: var(--accent);
}

#editor {
  border: 1px solid var(--border);
  border-radius: 6px;
  min-height: 400px;
  padding: 1rem;
  position: relative; /* the margin guides below position against this */
}

/* --- The text-column boundary ----------------------------------------
   The editor is already sized to the notice's real text column, but the
   boundary was invisible — so dragging a table column outward gave no
   signal about where the page actually ends, and the table's own right
   border simply scrolled out of view with nothing to explain why.

   These two faint rules mark the left and right text margins. They are
   the line a table should meet when it is meant to run full width, and
   the reference for centring a narrower one. */
#editor::before,
#editor::after {
  content: "";
  position: absolute;
  top: 0.35rem;
  bottom: 0.35rem;
  width: 0;
  border-left: 1px dashed #c9d4e4;
  pointer-events: none; /* never intercept a click meant for the text */
  z-index: 0;
}

#editor::before { left: 1rem; }
#editor::after { right: 1rem; }

/* A table wider than the column scrolls inside its own wrapper. Without
   a little breathing room the right-hand border sits exactly on the clip
   edge and reads as "the table has no border there", which is what made
   the boundary look like it had vanished mid-drag. */
#editor .tableWrapper {
  padding-right: 2px;
}

/* Table alignment. The attribute arrives as a node decoration on
   prosemirror-tables' wrapper div (see table-align.js), so the rules
   reach through the wrapper to the table itself. Without an explicit
   alignment a table keeps its natural width and sits at the left
   margin, which is Word's own default. */
#editor .tableWrapper[data-align-table="center"] > table {
  margin-left: auto;
  margin-right: auto;
}

#editor .tableWrapper[data-align-table="right"] > table {
  margin-left: auto;
  margin-right: 0;
}

#editor .tableWrapper[data-align-table="left"] > table {
  margin-left: 0;
  margin-right: auto;
}

#editor .ProseMirror {
  outline: none;
  min-height: 380px;
}

/* TipTap wraps every table in this div automatically. Without this
   rule, dragging a column wider than the page pushes the WHOLE page
   wider (the bug reported: "table extended outside of the page
   limit") — with it, the table scrolls sideways inside its own box
   instead, and the rest of the page stays put. */
#editor .tableWrapper {
  overflow-x: auto;
  max-width: 100%;
}

#editor table {
  border-collapse: collapse;
  margin: 0.75rem 0;
  table-layout: fixed;
  /* NOT width:100%. That forced every table to the full text column, so
     a table narrower than the page could never be centred — the margins
     had nothing to distribute. `auto` lets the colgroup's real widths
     decide, which is also what Word does with a fixed layout and a
     <w:tblGrid>. `max-width` keeps an over-wide table inside the page
     instead of pushing the whole editor sideways. */
  width: auto;
  max-width: 100%;
}

#editor table td,
#editor table th {
  border: 1px solid var(--border);
  /* Set from the document's own cell margins at import (see
     applyCellPadding in editor.js). These notices use 40/60 twips —
     under 3px — where the old fixed 0.4rem/0.6rem was more than double
     that, which is what made every row look taller on screen than in
     the exported file. */
  padding: var(--cell-pad-y, 3px) var(--cell-pad-x, 4px);
  min-width: 40px;
  position: relative; /* the column-resize handle below positions itself against this */
  vertical-align: middle; /* Word's <w:vAlign w:val="center"/> on these cells */
}

/* A cell should hug its text. Browsers give <p> a 1em margin top and
   bottom by default; Word gives a paragraph none, and puts spacing in
   <w:spacing> instead — which is now carried explicitly (see
   word-attrs.js). Leaving the browser default in place meant a cell of
   four short lines rendered far taller than the same cell in Word, and
   a single blank spacer paragraph looked like two blank lines. */
#editor .ProseMirror p {
  margin: 0;
}

#editor table td > p,
#editor table th > p {
  line-height: 1.3;
}

#editor table th {
  background: #f5f5f5;
}

/* The resizable-table plugin adds this handle at each draggable column
   border, but ships with no visual style at all — so it was there and
   working, just invisible, which is why the drag boundary gave no hint
   you could grab it. This makes it a visible, hoverable blue strip. */
#editor .column-resize-handle {
  position: absolute;
  right: -2px;
  top: 0;
  bottom: -2px;
  width: 4px;
  background-color: var(--accent);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.1s;
}
#editor table td:hover > .column-resize-handle,
#editor table th:hover > .column-resize-handle {
  opacity: 0.5;
}
#editor .ProseMirror.resize-cursor {
  cursor: col-resize;
}

/* Row-height dragging (see the "Row height resize" block in editor.js —
   hand-rolled, since there's no built-in ProseMirror-tables plugin for
   this the way there is for column width). Same visible-on-hover
   treatment as the column handle above, just along a row's bottom
   border instead of a cell's right border. */
#editor.row-resize-cursor {
  cursor: row-resize;
}
#editor table tr.row-resize-target > td,
#editor table tr.row-resize-target > th {
  box-shadow: inset 0 -3px 0 -1px var(--accent);
}

/* The live preview line while dragging a row's bottom border — a plain
   fixed-position element appended to <body>, deliberately OUTSIDE the
   editor's contenteditable area (see the "Row height resize" comment in
   editor.js for why: anything mutated live INSIDE the editor gets
   silently reverted by ProseMirror's own DOM-change watcher). */
.row-resize-guide {
  position: fixed;
  height: 2px;
  background: var(--accent);
  pointer-events: none;
  z-index: 30;
}

#editor img {
  max-width: 100%;
  border-radius: 4px;
}

#editor td img {
  max-width: 100%;
  display: block;
}

/* --- Resizable / floatable images (see frontend/src/resizable-image.js) */

#editor .resizable-image-wrapper {
  position: relative;
  display: inline-block;
  max-width: 100%;
  line-height: 0; /* stop the wrapper adding extra height below the image */
}

#editor .resizable-image-wrapper[data-align="left"] {
  float: left;
  margin: 0 1rem 0.5rem 0;
}
#editor .resizable-image-wrapper[data-align="right"] {
  float: right;
  margin: 0 0 0.5rem 1rem;
}
/* display:table (not display:block) so the wrapper shrinks to the
   image's own width instead of stretching to fill the editor — a
   block-level span with no explicit width defaults to 100% wide, which
   is why "margin: auto" alone did nothing (no extra space to split)
   and the image just sat flush left, only ever looking centered once
   dragged wide enough to fill the row. display:table stays block-level
   (so margin:auto still applies) but sizes to its content like
   inline-block does — this is also what was pushing the resize handle
   (anchored to the wrapper's bottom-right corner) far off to the right
   of a small image, disconnected from it. */
#editor .resizable-image-wrapper:not([data-align="left"]):not([data-align="right"]) {
  display: table;
  margin: 0.5rem auto;
}

#editor .resizable-image-wrapper img {
  display: block;
  max-width: 100%;
  border-radius: 4px;
}

/* Selection outline: TipTap adds ProseMirror-selectednode to whatever
   DOM node a NodeView returns when that node is selected — this is
   the "you clicked it and it IS selected" feedback that was missing. */
#editor .resizable-image-wrapper.ProseMirror-selectednode img {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* Resize handle: a small square at the bottom-right corner, visible
   once the image is selected (otherwise it'd clutter every image on
   the page all the time). */
#editor .resizable-image-handle {
  display: none;
  position: absolute;
  right: -6px;
  bottom: -6px;
  width: 12px;
  height: 12px;
  border-radius: 3px;
  background: var(--accent);
  border: 2px solid #fff;
  cursor: nwse-resize;
}
#editor .resizable-image-wrapper.ProseMirror-selectednode .resizable-image-handle {
  display: block;
}

#editor blockquote {
  border-left: 3px solid var(--border);
  margin: 0.5rem 0;
  padding-left: 0.75rem;
  color: #555;
}

#editor h1, #editor h2, #editor h3 {
  margin: 0.6rem 0 0.3rem;
}

#editor ul, #editor ol {
  padding-left: 1.5rem;
}

/* --- Transient warning banner ---------------------------------------
   Used for a rejected file type and for import notes. It reserves no
   space when empty (max-height 0) so the page doesn't jump as it comes
   and goes, and it animates rather than blinking, so a message that
   replaces another is noticeable instead of looking like it never
   changed. */
#warning {
  max-height: 0;
  overflow: hidden;
  opacity: 0;
  margin: 0 auto;
  max-width: 820px;
  border-radius: 6px;
  background: #fdf3d7;
  color: #6b4e00;
  border: 1px solid #e5c76b;
  font-size: 14px;
  line-height: 1.5;
  padding: 0 14px;
  transition: max-height 0.2s ease, opacity 0.2s ease, padding 0.2s ease,
              margin 0.2s ease;
}

#warning.visible {
  max-height: 200px;
  opacity: 1;
  padding: 11px 14px;
  margin: 0 auto 12px;
}

#export-btn {
  margin-left: 8px;
  font-weight: 600;
}

#dropzone {
  cursor: pointer;
}

#dropzone:focus-visible {
  outline: 2px solid #2b7cd3;
  outline-offset: 2px;
}

/* Images keep the aspect ratio they were stored with. `height: auto`
   would silently override the explicit height that now goes to Word, so
   both dimensions are left to the inline style and only the max-width
   guard stays — an image wider than its cell is clamped at export, where
   the real column width is known. */
#editor img,
#editor td img,
#editor .resizable-image-wrapper img {
  max-width: 100%;
}

/* --- Multi-level list numbering in the editor -------------------------
   Two problems solved together.

   1. A browser numbers a nested <ol> from 1 again, so a sub-item under
      section 7 displayed as "1." while the exported Word file correctly
      read "7.1". CSS counters fix that: `counters(name, ".")` walks the
      whole nesting chain.

   2. Word positions a numbered paragraph absolutely — the number sits at
      (indentLeft - hanging) from the margin and the text at indentLeft,
      whatever the nesting depth. HTML nesting is CUMULATIVE instead, so
      a sub-item inherits its parent's indent and then adds its own,
      drifting further right at every level.

   The real values arrive from the document as CSS custom properties on
   each <li> (--hang and --indent-total, set by word-attrs.js from the
   paragraph's own w:ind). `--indent-total` is what lets a nested list
   cancel its parent's contribution and start from the margin again, so
   the editor reproduces Word's absolute placement instead of HTML's
   compounding one. The em fallbacks cover a list the user typed, which
   has no Word geometry to copy. */
#editor .ProseMirror ol {
  counter-reset: notice-item;
  list-style: none;
  padding-left: 0;
  margin: 0;
}

#editor .ProseMirror ol > li {
  counter-increment: notice-item;
  padding-left: var(--hang, 2.4em);
}

/* Cancel the parent item's indent so this level positions absolutely,
   the way Word does, rather than compounding. */
#editor .ProseMirror ol ol,
#editor .ProseMirror ol ul {
  margin-left: calc(0px - var(--indent-total, 2.4em));
}

#editor .ProseMirror ol > li > p:first-of-type::before {
  content: counters(notice-item, ".") var(--num-suffix, "");
  display: inline-block;
  width: var(--hang, 2.4em);
  margin-left: calc(0px - var(--hang, 2.4em));
  /* Digits in these notices are set in a Latin face, matching the
     school's own convention of Times New Roman for numerals. */
  font-family: "Times New Roman", Times, serif;
}

#editor .ProseMirror ul {
  padding-left: 0;
  margin: 0;
  list-style: none;
}

#editor .ProseMirror ul > li {
  padding-left: var(--hang, 1.6em);
}

#editor .ProseMirror ul > li > p:first-of-type::before {
  content: "\2022";
  display: inline-block;
  width: var(--hang, 1.6em);
  margin-left: calc(0px - var(--hang, 1.6em));
}


/* --- Letter structure ------------------------------------------------
   Every Hong Chi notice has the same skeleton: school name, title,
   敬啟者：, body, 此致／貴家長台鑒, 校長, 石偉強謹啟, date — and then,
   sometimes, an appendix. services/structure.py labels each block, and
   these rules mark where one region ends and the next begins.

   Deliberately faint. This is a document the teacher is proof-reading,
   not a dashboard: the labels sit in the left margin, print-invisible,
   and never displace the text they annotate. The two that earn their
   place are the closing (which must stay together on one page) and the
   appendix (which is what makes a two-page notice suddenly four). */
#editor [data-role-start="appendix"] {
  position: relative;
}

/* Only the appendix is labelled. A "closing block" tag was tried and
   removed: it implied the block was pinned or otherwise special, while
   it simply reflowed with the text like anything else — a label that
   promises behaviour the document doesn't have is worse than none. */
#editor [data-role-start="appendix"]::after {
  position: absolute;
  /* Inside the text column, not out in the page margin. The margin has
     no guaranteed width — the editor is centred and the window can be
     narrow — so a label placed out there gets clipped exactly when the
     screen is small, which is when it is most needed. */
  right: 0;
  top: -0.95rem;
  font-family: -apple-system, "Helvetica Neue", Arial, sans-serif;
  font-size: 10px;
  letter-spacing: 0.04em;
  color: #a8b4c8;
  pointer-events: none;
  white-space: nowrap;
}

#editor [data-role-start="appendix"]::after {
  content: "appendix";
}

/* A hairline where the letter proper ends and the appendix starts. */
#editor [data-role-start="appendix"] {
  border-top: 1px dashed #dde3ec;
  padding-top: 0.75rem;
  margin-top: 1.25rem; /* room for the label to sit above the rule */
}



#keep-closing-label {
  font-size: 12px;
  color: #666;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  margin-left: 8px;
  white-space: nowrap;
}

/* --- The page column -------------------------------------------------
   The editor is sized to the notice's REAL text column so that what
   looks like "the table reaches the margin" on screen is the same
   judgement Word will make. `--text-col` is set from the imported
   document's own page size and margins (see applyPageGeometry in
   editor.js); the 688px fallback is A4 minus this school's 680/900
   twip margins, for a blank start.

   This rule went missing in an earlier edit and the mismatch was
   immediate: the editor fell back to the 800px body width, so a table
   of 10180 twips (98.6% of the real text column — practically at the
   margin) rendered at 679px inside a ~734px box and looked like it had
   room to spare. */
#editor-wrap {
  position: relative;
  max-width: calc(var(--text-col, 688px) + 2rem + 2px);
  margin: 0 auto;
}

/* A table indented by <w:tblInd>. Without this the editor drew it at
   the left margin while Word pushed it in — so a table that looked
   centred on screen sat against the boundary in the export, and a user
   dragging it "to the edge" overshot by exactly the indent. */
/* `:not([data-align-table])` is load-bearing, not tidiness. This rule and
   the alignment rules above have identical specificity (one id, one
   class, one attribute, one element), so whichever comes last in the
   file wins — and this one does. Without the guard it pinned
   `margin-left` for any table carrying an indent, so the one table in
   T222 with `<w:tblInd w:w="880">` ignored left/centre/right entirely
   while every other table obeyed them.

   The semantics are right too: an explicit alignment says where the
   table sits, which supersedes an inherited indent. Word treats them the
   same way — `tblInd` is the left offset for a left-aligned table and is
   meaningless once the table is centred. */
#editor .tableWrapper[data-tbl-indent]:not([data-align-table]) > table {
  margin-left: var(--tbl-indent, 0px);
}

/* A table too wide for the page. Word will let it run past the margin
   rather than shrink it, so the honest thing is to say so here instead
   of letting it look fine and surprising the teacher in the .docx. */
#editor .tableWrapper[data-overflow="1"] > table {
  outline: 2px solid #d9534f;
  outline-offset: 1px;
}

#editor .tableWrapper[data-overflow="1"]::after {
  content: "wider than the page";
  display: block;
  font-family: -apple-system, "Helvetica Neue", Arial, sans-serif;
  font-size: 10px;
  color: #d9534f;
  text-align: right;
  padding-top: 2px;
}

/* --- Selected table cells --------------------------------------------
   prosemirror-tables marks drag-selected cells with `.selectedCell` and
   ships no styling for it, so selecting cells did nothing visible.
   Merge and Split worked the whole time — the user simply had no way to
   see that a selection existed, which is indistinguishable from a
   broken button.

   The tint goes on a ::after overlay rather than the cell's background,
   so it sits above the cell's own fill and below its text, and works
   the same whether the cell is empty, coloured or holds an image. */
#editor .selectedCell {
  position: relative;
}

#editor .selectedCell::after {
  content: "";
  position: absolute;
  inset: 0;
  background: rgba(47, 95, 168, 0.16);
  border: 1px solid rgba(47, 95, 168, 0.45);
  pointer-events: none;
  z-index: 2;
}

/* A toolbar button that can't act on the current selection. Merge is
   the one that matters: it lights up only once two or more cells are
   selected, which is what teaches the interaction. */
#toolbar button:disabled {
  opacity: 0.35;
  cursor: default;
}
