diff --git a/app/logic/document_outputs.py b/app/logic/document_outputs.py
index 28a33a6..5cc26b6 100644
--- a/app/logic/document_outputs.py
+++ b/app/logic/document_outputs.py
@@ -67,6 +67,7 @@ from reportlab.lib.utils import ImageReader
from reportlab.pdfbase.pdfmetrics import stringWidth
from reportlab.pdfgen import canvas
from reportlab.pdfbase import pdfmetrics
+from reportlab.pdfbase.ttfonts import TTFont
from sqlalchemy import func
from sqlalchemy.orm import Session
@@ -368,7 +369,32 @@ def _fit_font_size_for_bbox_text(text: str, box_width: float, box_height: float)
def _safe_pdf_font_name(font_name: str | None) -> str:
- candidate = (font_name or "Helvetica").strip()
+ raw = (font_name or "Helvetica").strip()
+ key = raw.lower()
+
+ # ReportLab core PDF fonts only unless TTFs are registered.
+ # Map common UI font names to visible built-in PDF fonts.
+ aliases = {
+ "arial": "Helvetica",
+ "calibri": "Helvetica",
+ "verdana": "Helvetica",
+ "trebuchet ms": "Helvetica",
+ "helvetica": "Helvetica",
+
+ "times new roman": "Times-Roman",
+ "times": "Times-Roman",
+ "georgia": "Times-Roman",
+ "cambria": "Times-Roman",
+ "liberation serif": "Times-Roman",
+ "dejavu serif": "Times-Roman",
+
+ "courier new": "Courier",
+ "courier": "Courier",
+ "liberation mono": "Courier",
+ "dejavu sans mono": "Courier",
+ }
+
+ candidate = aliases.get(key, raw)
try:
pdfmetrics.getFont(candidate)
return candidate
@@ -380,8 +406,9 @@ def _font_size_for_box(text: str, font_name: str, box_width: float, box_height:
fitted = _fit_font_size_for_bbox_text(text, box_width, box_height)
if saved_size and saved_size > 0:
- # Saved UI/editor font size is allowed, but geometry wins for replica output.
- return max(1.0, min(float(saved_size), float(fitted)))
+ # Layout Review is the source of truth after manual editing.
+ # Do not silently shrink manual font edits back to the fitted estimate.
+ return max(1.0, float(saved_size))
return max(1.0, float(fitted))
@@ -993,6 +1020,12 @@ def build_replica_layout(document: Document, mode: str = "shared") -> dict:
current_file, raw_ocr, reviewed, source_layout, layout_source = _get_replica_source_context(document)
reader = PdfReader(str(current_file))
+ source_layout_meta = source_layout if isinstance(source_layout, dict) else {}
+ prefer_word_entries = (
+ source_layout_meta.get("layout_sync_source") == "layout_review"
+ or source_layout_meta.get("layout_sync_status") == "synced"
+ )
+
pages = []
page_layouts = {page["page"]: page for page in (source_layout.get("pages", []) if isinstance(source_layout, dict) else [])}
@@ -1066,6 +1099,7 @@ def build_replica_layout(document: Document, mode: str = "shared") -> dict:
"page_height": page_h,
"image_width": src_w,
"image_height": src_h,
+ "prefer_word_entries": prefer_word_entries,
"lines": line_entries,
"words": page_layout.get("words", []) or [],
}
@@ -1156,8 +1190,35 @@ def _render_replica_pdf_from_layout(
page_layout = pages.get(page_num, {"lines": []})
+ edited_words = [
+ w for w in (page_layout.get("words") or [])
+ if (isinstance(w.get("manual_flags"), dict) and w.get("manual_flags", {}).get("style_edited"))
+ or str(w.get("text_color_guess") or "#000000").lower() != "#000000"
+ ]
+ if edited_words:
+ print(
+ "[replica-render-debug]",
+ "page=", page_num,
+ "prefer_word_entries=", page_layout.get("prefer_word_entries"),
+ "edited_words=",
+ [
+ (
+ w.get("id"),
+ w.get("text"),
+ w.get("font_size_guess"),
+ w.get("font_family_guess"),
+ w.get("text_color_guess"),
+ w.get("manual_flags"),
+ )
+ for w in edited_words[:20]
+ ],
+ flush=True,
+ )
+
render_entries = []
- if page_layout.get("lines"):
+ if page_layout.get("prefer_word_entries") and page_layout.get("words"):
+ render_entries = _build_word_entries_for_page(page_layout, page_h)
+ if not render_entries and page_layout.get("lines"):
render_entries = _build_line_entries_for_page(page_layout, page_h)
if not render_entries and page_layout.get("words"):
render_entries = _build_word_entries_for_page(page_layout, page_h)
@@ -1189,8 +1250,18 @@ def _render_replica_pdf_from_layout(
c.setStrokeColorRGB(1, 0, 0)
c.setFillColorRGB(1, 0, 0)
else:
- c.setStrokeColorRGB(0, 0, 0)
- c.setFillColorRGB(0, 0, 0)
+ color = str(line.get("text_color_guess") or "#000000").lstrip("#")
+ try:
+ if len(color) == 6:
+ r = int(color[0:2], 16) / 255.0
+ g = int(color[2:4], 16) / 255.0
+ b = int(color[4:6], 16) / 255.0
+ else:
+ r = g = b = 0
+ except Exception:
+ r = g = b = 0
+ c.setStrokeColorRGB(r, g, b)
+ c.setFillColorRGB(r, g, b)
text_obj.textLine(text_line)
c.drawText(text_obj)
diff --git a/app/models/__init__.py b/app/models/__init__.py
index ab7e0db..d11d447 100644
--- a/app/models/__init__.py
+++ b/app/models/__init__.py
@@ -1,4 +1,5 @@
from app.models.document import Document
+from app.models.document_review_state import DocumentReviewState
from app.models.document_version import DocumentVersion
from app.models.text_version import TextVersion
from app.models.extracted_field import ExtractedField
@@ -8,6 +9,8 @@ from app.models.document_additional_field import DocumentAdditionalField
from app.models.document_preset import DocumentPreset
__all__ = [
+ "DocumentReplicaReviewState",
+ "DocumentReviewState",
"DocumentAnalysisVersion",
"Document",
"DocumentVersion",
diff --git a/app/routes/documents.py b/app/routes/documents.py
index 39bb27b..bc567aa 100644
--- a/app/routes/documents.py
+++ b/app/routes/documents.py
@@ -19,7 +19,7 @@ from io import BytesIO
from fastapi import APIRouter, Depends, Form, Query, Request
from fastapi.responses import FileResponse, HTMLResponse, RedirectResponse, Response
from fastapi.templating import Jinja2Templates
-from sqlalchemy import distinct
+from sqlalchemy import distinct, text
from sqlalchemy import func
from sqlalchemy.orm import Session, selectinload
from pypdf import PdfReader
@@ -2023,12 +2023,30 @@ async def save_layout_review(document_id: str, request: Request, db: Session = D
x_right = max(x1, x2)
y_top = min(y1, y2)
y_bottom = max(y1, y2)
-
if abs(x_right - x_left) < 1.0 or abs(y_bottom - y_top) < 1.0:
continue
- font_size_guess = float(word.get("font_size_guess") or max(6.0, (y_bottom - y_top) * 0.75))
- font_family_guess = (word.get("font_family_guess") or "Helvetica")
+ manual_flags = word.get("manual_flags") if isinstance(word.get("manual_flags"), dict) else {}
+ style_edited = manual_flags.get("style_edited") is True
+ override_style = word.get("override_style") if style_edited and isinstance(word.get("override_style"), dict) else {}
+ resolved_style = word.get("resolved_style") if style_edited and isinstance(word.get("resolved_style"), dict) else {}
+
+ font_size_guess = float(word.get("font_size_guess") or override_style.get("font_size") or max(6.0, (y_bottom - y_top) * 0.75))
+ font_family_guess = word.get("font_family_guess") or override_style.get("font_family") or "Helvetica"
+ font_weight_guess = int(word.get("font_weight_guess") or resolved_style.get("font_weight") or 400)
+ font_style_guess = word.get("font_style_guess") or resolved_style.get("font_style") or "normal"
+ letter_spacing_guess = float(word.get("letter_spacing_guess") or resolved_style.get("letter_spacing") or 0)
+ text_color_guess = word.get("text_color_guess") or override_style.get("text_color") or "#000000"
+
+ if style_edited:
+ override_style = dict(override_style)
+ override_style.update({"font_family": font_family_guess, "font_size": font_size_guess, "text_color": text_color_guess})
+ resolved_style = dict(resolved_style)
+ resolved_style.update(override_style)
+ else:
+ override_style = {}
+ resolved_style = {}
+ manual_flags["style_edited"] = False
words.append({
"id": int(word.get("id") or word_idx),
@@ -2037,6 +2055,13 @@ async def save_layout_review(document_id: str, request: Request, db: Session = D
"confidence": None,
"font_size_guess": font_size_guess,
"font_family_guess": font_family_guess,
+ "font_weight_guess": font_weight_guess,
+ "font_style_guess": font_style_guess,
+ "letter_spacing_guess": letter_spacing_guess,
+ "text_color_guess": text_color_guess,
+ "override_style": override_style,
+ "resolved_style": resolved_style,
+ "manual_flags": manual_flags,
})
words.sort(key=lambda w: (w["bbox"][1], w["bbox"][0]))
@@ -2186,12 +2211,27 @@ def document_detail(document_id: str, request: Request, queue: str | None = None
bbox = word.get("bbox") or [0, 0, 0, 0]
if not isinstance(bbox, (list, tuple)) or len(bbox) != 4:
continue
+ resolved_style = word.get("resolved_style") if isinstance(word.get("resolved_style"), dict) else {}
+ override_style = word.get("override_style") if isinstance(word.get("override_style"), dict) else {}
+ inferred_style = word.get("inferred_style") if isinstance(word.get("inferred_style"), dict) else {}
+ manual_flags = word.get("manual_flags") if isinstance(word.get("manual_flags"), dict) else {}
+ font_size_value = word.get("font_size_guess") or override_style.get("font_size") or resolved_style.get("font_size") or max(6.0, (float(bbox[3]) - float(bbox[1])) * 0.75)
+ font_family_value = word.get("font_family_guess") or override_style.get("font_family") or resolved_style.get("font_family") or "Helvetica"
+ text_color_value = word.get("text_color_guess") or override_style.get("text_color") or resolved_style.get("text_color") or "#000000"
word_row = {
"id": idx,
"text": (word.get("text") or "").strip(),
"bbox": [float(bbox[0]), float(bbox[1]), float(bbox[2]), float(bbox[3])],
- "font_size_guess": float(word.get("font_size_guess") or max(6.0, (float(bbox[3]) - float(bbox[1])) * 0.75)),
- "font_family_guess": (word.get("font_family_guess") or "Helvetica"),
+ "font_size_guess": float(font_size_value),
+ "font_family_guess": font_family_value,
+ "font_weight_guess": int(word.get("font_weight_guess") or resolved_style.get("font_weight") or 400),
+ "font_style_guess": word.get("font_style_guess") or resolved_style.get("font_style") or "normal",
+ "letter_spacing_guess": float(word.get("letter_spacing_guess") or resolved_style.get("letter_spacing") or 0),
+ "text_color_guess": text_color_value,
+ "inferred_style": inferred_style,
+ "override_style": override_style,
+ "resolved_style": resolved_style,
+ "manual_flags": manual_flags,
}
words.append(word_row)
@@ -3293,78 +3333,151 @@ async def run_diagnostic_candidates(document_id: str, db: Session = Depends(get_
)
-@router.get("/{document_id}/diagnostic-output/{output_id}/download")
-async def download_diagnostic_output(document_id: str, output_id: int, db: Session = Depends(get_db)):
- document = db.query(Document).filter(Document.document_id == document_id).first()
- if document is None:
- return HTMLResponse(content="Document not found", status_code=404)
- row = db.execute(
- text("""
- SELECT file_path, engine, output_type, version_number
- FROM document_diagnostic_outputs
- WHERE id = :id AND document_id = :document_id
- """),
- {"id": output_id, "document_id": document.id},
- ).mappings().first()
+@router.get("/{document_id}/diagnostic-output/{output_id}/download")
+async def download_diagnostic_output(document_id: str, output_id: int):
+ with engine.connect() as conn:
+ row = conn.execute(
+ text("""
+ SELECT ddo.file_path, ddo.engine, ddo.output_type, ddo.version_number
+ FROM document_diagnostic_outputs ddo
+ JOIN documents d ON d.id = ddo.document_id
+ WHERE ddo.id = :id AND d.document_id = :document_id
+ """),
+ {"id": output_id, "document_id": document_id},
+ ).mappings().first()
if not row or not row["file_path"]:
return HTMLResponse(content="Diagnostic output not found", status_code=404)
path = Path(row["file_path"])
if not path.exists():
- return HTMLResponse(content="Diagnostic output file missing", status_code=404)
+ return HTMLResponse(content=f"Diagnostic output file missing: {path}", status_code=404)
return FileResponse(path=str(path), filename=path.name)
@router.post("/{document_id}/diagnostic-output/{output_id}/select")
-async def select_diagnostic_output(document_id: str, output_id: int, db: Session = Depends(get_db)):
- document = db.query(Document).filter(Document.document_id == document_id).first()
- if document is None:
- return HTMLResponse(content="Document not found", status_code=404)
+async def select_diagnostic_output(document_id: str, output_id: int):
+ with engine.begin() as conn:
+ row = conn.execute(
+ text("""
+ SELECT ddo.id, ddo.document_id, ddo.engine, ddo.output_type
+ FROM document_diagnostic_outputs ddo
+ JOIN documents d ON d.id = ddo.document_id
+ WHERE ddo.id = :id AND d.document_id = :document_id
+ """),
+ {"id": output_id, "document_id": document_id},
+ ).mappings().first()
- row = db.execute(
- text("""
- SELECT engine, output_type
- FROM document_diagnostic_outputs
- WHERE id = :id AND document_id = :document_id
- """),
- {"id": output_id, "document_id": document.id},
- ).mappings().first()
+ if not row:
+ return HTMLResponse(content="Diagnostic output not found", status_code=404)
- if not row:
- return HTMLResponse(content="Diagnostic output not found", status_code=404)
+ conn.execute(
+ text("""
+ UPDATE document_diagnostic_outputs
+ SET is_selected = false
+ WHERE document_id = :document_pk
+ AND engine = :engine
+ AND output_type = :output_type
+ """),
+ {
+ "document_pk": row["document_id"],
+ "engine": row["engine"],
+ "output_type": row["output_type"],
+ },
+ )
- db.execute(
- text("""
- UPDATE document_diagnostic_outputs
- SET is_selected = false
- WHERE document_id = :document_id
- AND engine = :engine
- AND output_type = :output_type
- """),
- {
- "document_id": document.id,
- "engine": row["engine"],
- "output_type": row["output_type"],
- },
- )
-
- db.execute(
- text("""
- UPDATE document_diagnostic_outputs
- SET is_selected = true, updated_at = NOW()
- WHERE id = :id AND document_id = :document_id
- """),
- {"id": output_id, "document_id": document.id},
- )
-
- db.commit()
+ conn.execute(
+ text("""
+ UPDATE document_diagnostic_outputs
+ SET is_selected = true, updated_at = NOW()
+ WHERE id = :id
+ """),
+ {"id": output_id},
+ )
return RedirectResponse(
url=f"/documents/{document_id}?tab=ocr-review&success=diagnostic_candidate_selected",
status_code=303,
)
+
+
+
+@router.post("/{document_id}/diagnostic-output/select")
+async def select_diagnostic_output_from_form(document_id: str, diagnostic_output_id: int = Form(...)):
+ return await select_diagnostic_output(document_id, diagnostic_output_id)
+
+
+@router.get("/{document_id}/diagnostic-output/{output_id}/view")
+async def view_diagnostic_output(document_id: str, output_id: int):
+ with engine.connect() as conn:
+ row = conn.execute(
+ text("""
+ SELECT ddo.file_path, ddo.engine, ddo.output_type, ddo.version_number
+ FROM document_diagnostic_outputs ddo
+ JOIN documents d ON d.id = ddo.document_id
+ WHERE ddo.id = :id AND d.document_id = :document_id
+ """),
+ {"id": output_id, "document_id": document_id},
+ ).mappings().first()
+
+ if not row or not row["file_path"]:
+ return HTMLResponse(content="Diagnostic output not found", status_code=404)
+
+ path = Path(row["file_path"])
+ if not path.exists():
+ return HTMLResponse(content=f"Diagnostic output file missing: {path}", status_code=404)
+
+ suffix = path.suffix.lower()
+
+ if suffix == ".pdf":
+ return FileResponse(path=str(path), filename=path.name, media_type="application/pdf")
+
+ if suffix == ".docx":
+ with open(path, "rb") as f:
+ result = mammoth.convert_to_html(f)
+
+ body = result.value or ""
+ return HTMLResponse(content=f"""
+
+
+
+
+
+
+
+
+
+ {body}
+
+
+
+""")
+
+ return FileResponse(path=str(path), filename=path.name)
+
+
# --- diagnostic candidate routes end ---
diff --git a/app/templates/documents/detail.html b/app/templates/documents/detail.html
index a3be104..a8b25c6 100644
--- a/app/templates/documents/detail.html
+++ b/app/templates/documents/detail.html
@@ -1,3 +1,12 @@
+
+
+
{% extends "base.html" %}
{% block title %}Document Detail
@@ -263,56 +316,6 @@ document.addEventListener("DOMContentLoaded", () => {
-
-
-
Diagnostic candidates
-
-
-
- {% if diagnostic_outputs %}
-
- {% for out in diagnostic_outputs %}
-
-
-
-
- {% if out.is_selected %}✓ {% endif %}{{ out.engine }} · {{ out.output_type }}
-
-
- v{{ out.version_number }} · {{ out.status }}
-
-
-
- {{ out.created_at }}
-
-
-
- {% if out.error_message %}
-
- {{ out.error_message }}
-
- {% endif %}
-
-
- {% if out.file_path %}
-
Download
-
- {% else %}
-
No file output
- {% endif %}
-
-
- {% endfor %}
-
- {% else %}
-
No diagnostic candidates saved yet.
- {% endif %}
-
-
@@ -351,7 +354,63 @@ document.addEventListener("DOMContentLoaded", () => {
Words
- {% endif %}
+
+
+ Diagnostics:
+
+
+ {% set selected_diag = diagnostic_outputs | selectattr("is_selected") | list | first %}
+ {% set default_diag = selected_diag or (diagnostic_outputs | selectattr("file_path") | list | first) %}
+
+ {% if diagnostic_outputs %}
+
+
+
+
View
+
Download
+
+
+ {% else %}
+
none
+ {% endif %}
+
+
+{% endif %}
@@ -614,24 +673,1324 @@ document.addEventListener("DOMContentLoaded", () => {
gap:0.35rem;
align-items:center;
}
-
-
-
-
-
-
-
-
-
-
-
- 100%
-
- Ready
-
-
+
+ /* Word-like floating mini toolbar for selected word */
+ #layout-word-popover {
+ width: min(92vw, 30rem) !important;
+ padding: 0.55rem !important;
+ border: 1px solid #b8c6d8 !important;
+ border-radius: 0.65rem !important;
+ background: linear-gradient(#f8fbff, #eef4fc) !important;
+ box-shadow: 0 0.7rem 1.8rem rgba(15,23,42,0.18) !important;
+ }
+
+ #layout-word-popover > div:first-child {
+ margin-bottom: 0.3rem !important;
+ font-size: 0.72rem !important;
+ color: #475569 !important;
+ text-transform: uppercase !important;
+ letter-spacing: 0.03em !important;
+ }
+
+ #layout-word-popover input[type="text"],
+ #layout-word-popover input[type="number"] {
+ min-height: 1.75rem !important;
+ padding: 0.2rem 0.45rem !important;
+ border: 1px solid #cbd5e1 !important;
+ border-radius: 0.35rem !important;
+ font-size: 0.8rem !important;
+ background: #ffffff !important;
+ }
+
+ #layout-word-popover input[type="color"] {
+ width: 1.55rem !important;
+ height: 1.55rem !important;
+ padding: 0.04rem !important;
+ border: 1px solid #94a3b8 !important;
+ border-radius: 0.25rem !important;
+ }
+
+ #layout-word-popover label {
+ font-size: 0.72rem !important;
+ color: #475569 !important;
+ white-space: nowrap !important;
+ }
+
+ #layout-word-popover button {
+ min-height: 1.75rem !important;
+ padding: 0.22rem 0.55rem !important;
+ border: 1px solid #b8c6d8 !important;
+ border-radius: 0.35rem !important;
+ background: linear-gradient(#ffffff, #e9eef7) !important;
+ color: #1f2937 !important;
+ font-size: 0.76rem !important;
+ }
+
+ #layout-popover-apply {
+ background: linear-gradient(#dbeafe, #bfdbfe) !important;
+ border-color: #60a5fa !important;
+ color: #1e3a8a !important;
+ }
+
+ #layout-popover-delete {
+ background: linear-gradient(#fff1f2, #ffe4e6) !important;
+ border-color: #fda4af !important;
+ color: #9f1239 !important;
+ }
+
+ #layout-word-popover .layout-mini-grid {
+ grid-template-columns: 4rem auto 4.8rem auto auto auto !important;
+ gap: 0.28rem !important;
+ align-items: center !important;
+ }
+
+
+ /* Floating selected-word mini toolbar: Word-like font group */
+ #layout-word-popover {
+ width: min(94vw, 34rem) !important;
+ padding: 0.55rem 0.65rem !important;
+ border: 1px solid #b8c6d8 !important;
+ border-radius: 0.65rem !important;
+ background: linear-gradient(#f8fbff, #eef4fc) !important;
+ box-shadow: 0 0.7rem 1.8rem rgba(15,23,42,0.18) !important;
+ }
+
+ #layout-word-popover > div:first-child {
+ font-size: 0.72rem !important;
+ font-weight: 700 !important;
+ text-transform: uppercase !important;
+ letter-spacing: 0.04em !important;
+ color: #475569 !important;
+ margin-bottom: 0.35rem !important;
+ }
+
+ #layout-word-popover > div:nth-child(2) {
+ display: grid !important;
+ grid-template-columns: 1.7fr 1fr auto auto auto auto auto !important;
+ gap: 0.3rem !important;
+ align-items: center !important;
+ }
+
+ #layout-word-popover input[type="text"],
+ #layout-word-popover input[type="number"] {
+ min-height: 2rem !important;
+ padding: 0.25rem 0.45rem !important;
+ border: 1px solid #cbd5e1 !important;
+ border-radius: 0.32rem !important;
+ font-size: 0.86rem !important;
+ background: #ffffff !important;
+ }
+
+ #layout-word-popover label {
+ font-size: 0.72rem !important;
+ color: #475569 !important;
+ white-space: nowrap !important;
+ }
+
+ #layout-word-popover input[type="color"] {
+ width: 2rem !important;
+ height: 2rem !important;
+ padding: 0.04rem !important;
+ border: 1px solid #94a3b8 !important;
+ border-radius: 0.25rem !important;
+ }
+
+ #layout-word-popover button {
+ min-height: 2rem !important;
+ padding: 0.25rem 0.55rem !important;
+ border: 1px solid #b8c6d8 !important;
+ border-radius: 0.32rem !important;
+ background: linear-gradient(#ffffff, #e9eef7) !important;
+ color: #1f2937 !important;
+ font-size: 0.82rem !important;
+ font-weight: 600 !important;
+ }
+
+ #layout-word-popover .layout-mini-grid {
+ display: contents !important;
+ }
+
+ #layout-popover-apply {
+ background: linear-gradient(#dbeafe, #bfdbfe) !important;
+ border-color: #60a5fa !important;
+ color: #1e3a8a !important;
+ }
+
+ #layout-popover-delete {
+ background: linear-gradient(#fff1f2, #ffe4e6) !important;
+ border-color: #fda4af !important;
+ color: #9f1239 !important;
+ }
+
+ @media (max-width: 760px) {
+ #layout-review-toolbar {
+ grid-template-columns: repeat(4, auto) !important;
+ }
+
+ #layout-word-popover > div:nth-child(2) {
+ grid-template-columns: 1fr 4.5rem auto auto !important;
+ }
+ }
+
+
+ /* Word-like selected-word mini toolbar, compact not huge */
+ #layout-word-popover {
+ width: min(92vw, 28rem) !important;
+ padding: 0.55rem !important;
+ border-radius: 0.65rem !important;
+ background: #f8fbff !important;
+ border: 1px solid #cbd5e1 !important;
+ box-shadow: 0 0.65rem 1.4rem rgba(15,23,42,0.18) !important;
+ }
+
+ #layout-word-popover .layout-mini-grid {
+ grid-template-columns: 4.5rem auto auto auto auto auto !important;
+ gap: 0.25rem !important;
+ align-items: center !important;
+ }
+
+ #layout-word-popover input[type="text"],
+ #layout-word-popover input[type="number"] {
+ min-height: 1.8rem !important;
+ font-size: 0.82rem !important;
+ padding: 0.22rem 0.4rem !important;
+ }
+
+ #layout-word-popover button {
+ min-height: 1.8rem !important;
+ padding: 0.22rem 0.48rem !important;
+ font-size: 0.78rem !important;
+ }
+
+ #layout-word-popover input[type="color"] {
+ width: 1.65rem !important;
+ height: 1.65rem !important;
+ }
+
+ @media (max-width: 760px) {
+ #layout-review-toolbar {
+ gap: 0.22rem !important;
+ padding: 0.35rem !important;
+ }
+
+ #layout-review-toolbar .layout-tool-btn,
+ #layout-review-toolbar button {
+ font-size: 0.76rem !important;
+ padding: 0.25rem 0.42rem !important;
+ }
+
+ #layout-review-toolbar label {
+ font-size: 0.72rem !important;
+ padding: 0.15rem 0.28rem !important;
+ }
+ }
+
+
+ /* Rebuilt Word-style ribbon toolbar */
+ #layout-review-toolbar.word-ribbon-toolbar {
+ display: flex !important;
+ flex-wrap: wrap !important;
+ align-items: stretch !important;
+ gap: 0 !important;
+ padding: 0.25rem 0.35rem !important;
+ border: 1px solid #c7d3e2 !important;
+ border-radius: 0.55rem !important;
+ background: #f7f9fc !important;
+ box-shadow: inset 0 -1px 0 #d8e1ec !important;
+ }
+
+ .word-ribbon-group {
+ display: flex !important;
+ flex-direction: column !important;
+ justify-content: space-between !important;
+ gap: 0.18rem !important;
+ padding: 0.25rem 0.55rem 0.12rem !important;
+ border-right: 1px solid #cbd5e1 !important;
+ min-height: 4.1rem !important;
+ }
+
+ .word-ribbon-row {
+ display: flex !important;
+ align-items: center !important;
+ gap: 0.25rem !important;
+ min-height: 1.55rem !important;
+ }
+
+ .word-ribbon-label {
+ text-align: center !important;
+ font-size: 0.64rem !important;
+ color: #64748b !important;
+ line-height: 1 !important;
+ }
+
+ #layout-review-toolbar .layout-tool-btn,
+ #layout-review-toolbar button {
+ min-height: 1.55rem !important;
+ padding: 0.18rem 0.45rem !important;
+ border: 1px solid #b8c6d8 !important;
+ border-radius: 0.22rem !important;
+ background: linear-gradient(#ffffff, #eef3fa) !important;
+ color: #1f2937 !important;
+ font-size: 0.76rem !important;
+ font-weight: 600 !important;
+ line-height: 1 !important;
+ }
+
+ #layout-review-toolbar .layout-tool-btn.active,
+ #layout-review-toolbar .layout-tool-btn.primary {
+ background: #dbeafe !important;
+ border-color: #60a5fa !important;
+ color: #1e3a8a !important;
+ }
+
+ #layout-review-toolbar .layout-tool-btn.danger {
+ background: #ffe4e6 !important;
+ border-color: #fda4af !important;
+ color: #9f1239 !important;
+ }
+
+ #layout-review-toolbar label {
+ display: inline-flex !important;
+ align-items: center !important;
+ gap: 0.22rem !important;
+ font-size: 0.74rem !important;
+ color: #334155 !important;
+ white-space: nowrap !important;
+ }
+
+ #layout-review-toolbar input[type="checkbox"] {
+ width: 0.95rem !important;
+ height: 0.95rem !important;
+ margin: 0 !important;
+ accent-color: #2563eb !important;
+ }
+
+ #layout-review-toolbar input[type="color"] {
+ width: 1.45rem !important;
+ height: 1.45rem !important;
+ padding: 0 !important;
+ border: 1px solid #94a3b8 !important;
+ border-radius: 0.2rem !important;
+ background: #ffffff !important;
+ }
+
+ .word-ribbon-select,
+ .word-ribbon-font-size {
+ height: 1.55rem !important;
+ display: inline-flex !important;
+ align-items: center !important;
+ padding: 0 0.4rem !important;
+ border: 1px solid #b8c6d8 !important;
+ border-radius: 0.2rem !important;
+ background: white !important;
+ font-size: 0.74rem !important;
+ color: #334155 !important;
+ }
+
+ .word-ribbon-select {
+ min-width: 5.3rem !important;
+ }
+
+ .word-ribbon-blue-a {
+ font-size: 1.05rem !important;
+ font-weight: 700 !important;
+ color: #2563eb !important;
+ }
+
+ #layout-zoom-label,
+ #layout-review-status {
+ font-size: 0.8rem !important;
+ color: #334155 !important;
+ padding: 0 0.25rem !important;
+ white-space: nowrap !important;
+ }
+
+
+ /* Force Word-like single horizontal ribbon */
+ #layout-review-toolbar.word-ribbon-toolbar {
+ display: flex !important;
+ flex-wrap: nowrap !important;
+ align-items: stretch !important;
+ gap: 0 !important;
+ padding: 0.25rem 0.35rem !important;
+ min-height: 4.85rem !important;
+ max-height: 5.25rem !important;
+ overflow-x: auto !important;
+ overflow-y: hidden !important;
+ border: 1px solid #c7d3e2 !important;
+ border-radius: 0.45rem !important;
+ background: #f6f8fb !important;
+ }
+
+ #layout-review-toolbar .word-ribbon-group {
+ flex: 0 0 auto !important;
+ min-height: 4.3rem !important;
+ height: 4.3rem !important;
+ padding: 0.25rem 0.55rem 0.1rem !important;
+ border-right: 1px solid #cbd5e1 !important;
+ display: flex !important;
+ flex-direction: column !important;
+ justify-content: space-between !important;
+ }
+
+ #layout-review-toolbar .word-ribbon-row {
+ display: flex !important;
+ align-items: center !important;
+ gap: 0.22rem !important;
+ min-height: 1.45rem !important;
+ white-space: nowrap !important;
+ }
+
+ #layout-review-toolbar .word-ribbon-label {
+ height: 0.75rem !important;
+ font-size: 0.58rem !important;
+ color: #64748b !important;
+ text-align: center !important;
+ line-height: 0.75rem !important;
+ }
+
+ #layout-review-toolbar button,
+ #layout-review-toolbar .layout-tool-btn {
+ min-height: 1.45rem !important;
+ height: 1.45rem !important;
+ padding: 0.12rem 0.38rem !important;
+ font-size: 0.68rem !important;
+ border-radius: 0.18rem !important;
+ line-height: 1 !important;
+ }
+
+ #layout-review-toolbar label {
+ min-height: 1.45rem !important;
+ font-size: 0.68rem !important;
+ gap: 0.18rem !important;
+ padding: 0 !important;
+ }
+
+ #layout-review-toolbar input[type="checkbox"] {
+ width: 0.82rem !important;
+ height: 0.82rem !important;
+ }
+
+ #layout-review-toolbar input[type="color"] {
+ width: 1.25rem !important;
+ height: 1.25rem !important;
+ padding: 0 !important;
+ }
+
+ #layout-review-toolbar .word-ribbon-select {
+ min-width: 4.6rem !important;
+ height: 1.45rem !important;
+ font-size: 0.68rem !important;
+ padding: 0 0.3rem !important;
+ }
+
+ #layout-review-toolbar .word-ribbon-font-size {
+ height: 1.45rem !important;
+ font-size: 0.68rem !important;
+ padding: 0 0.32rem !important;
+ }
+
+ #layout-review-toolbar .word-ribbon-blue-a {
+ font-size: 0.95rem !important;
+ }
+
+ #layout-zoom-label,
+ #layout-review-status {
+ font-size: 0.72rem !important;
+ height: 1.45rem !important;
+ display: inline-flex !important;
+ align-items: center !important;
+ }
+
+
+ /* Refine Word-like ribbon: denser, usable horizontal scroll */
+ #layout-review-toolbar.word-ribbon-toolbar {
+ min-height: 4.35rem !important;
+ max-height: 4.6rem !important;
+ padding: 0.18rem 0.25rem !important;
+ scrollbar-width: thin !important;
+ background: linear-gradient(#f8fafc, #eef3f8) !important;
+ }
+
+ #layout-review-toolbar.word-ribbon-toolbar::-webkit-scrollbar {
+ height: 0.35rem !important;
+ }
+
+ #layout-review-toolbar.word-ribbon-toolbar::-webkit-scrollbar-thumb {
+ background: #cbd5e1 !important;
+ border-radius: 999px !important;
+ }
+
+ #layout-review-toolbar .word-ribbon-group {
+ height: 3.85rem !important;
+ min-height: 3.85rem !important;
+ padding: 0.2rem 0.42rem 0.08rem !important;
+ }
+
+ #layout-review-toolbar .word-ribbon-row {
+ min-height: 1.35rem !important;
+ }
+
+ #layout-review-toolbar button,
+ #layout-review-toolbar .layout-tool-btn {
+ height: 1.35rem !important;
+ min-height: 1.35rem !important;
+ padding: 0.08rem 0.34rem !important;
+ font-size: 0.66rem !important;
+ }
+
+ #layout-review-toolbar label,
+ #layout-review-toolbar .word-ribbon-select,
+ #layout-review-toolbar .word-ribbon-font-size,
+ #layout-zoom-label,
+ #layout-review-status {
+ height: 1.35rem !important;
+ min-height: 1.35rem !important;
+ font-size: 0.66rem !important;
+ }
+
+ #layout-review-toolbar input[type="color"] {
+ width: 1.18rem !important;
+ height: 1.18rem !important;
+ }
+
+ #layout-review-toolbar input[type="checkbox"] {
+ width: 0.78rem !important;
+ height: 0.78rem !important;
+ }
+
+ #layout-review-toolbar .word-ribbon-label {
+ height: 0.65rem !important;
+ line-height: 0.65rem !important;
+ font-size: 0.55rem !important;
+ }
+
+ #layout-review-toolbar .word-ribbon-select {
+ min-width: 4.25rem !important;
+ }
+
+
+ /* arrow group row */
+ #layout-word-popover > div:nth-child(2) > div:nth-of-type(2) {
+ grid-column: 1 / 2 !important;
+ display: flex !important;
+ align-items: center !important;
+ gap: 0.28rem !important;
+ flex-wrap: wrap !important;
+ }
+
+ #layout-word-popover > div:nth-child(2) > div:nth-of-type(2) > div {
+ display: contents !important;
+ }
+
+ /* apply/delete row */
+ #layout-word-popover > div:nth-child(2) > div:nth-of-type(3) {
+ grid-column: 2 / 3 !important;
+ display: flex !important;
+ align-items: center !important;
+ justify-content: flex-end !important;
+ gap: 0.28rem !important;
+ flex-wrap: wrap !important;
+ }
+
+ #layout-popover-apply {
+ background: linear-gradient(#dbeafe, #c9defc) !important;
+ border-color: #8cb4f4 !important;
+ color: #1e3a8a !important;
+ }
+
+ #layout-popover-delete {
+ background: linear-gradient(#fff1f2, #ffe4e6) !important;
+ border-color: #f3b0b8 !important;
+ color: #9f1239 !important;
+ }
+
+ @media (max-width: 760px) {
+ #layout-word-popover {
+ width: min(96vw, 30rem) !important;
+ }
+
+ #layout-word-popover .layout-mini-grid {
+ grid-template-columns: minmax(7rem, 1fr) 3.2rem auto auto auto 1.8rem !important;
+ }
+
+ #layout-popover-text,
+ #layout-popover-font-family,
+ #layout-popover-font-size {
+ font-size: 0.78rem !important;
+ }
+
+ #layout-popover-font-down,
+ #layout-popover-font-up,
+ #layout-popover-font-all,
+ #layout-popover-up,
+ #layout-popover-left,
+ #layout-popover-down,
+ #layout-popover-right,
+ #layout-popover-apply,
+ #layout-popover-delete {
+ font-size: 0.76rem !important;
+ padding: 0.12rem 0.34rem !important;
+ }
+ }
+
+
+ /* Word-like selected-word popup */
+ #layout-word-popover {
+ width: min(94vw, 38rem) !important;
+ padding: 0.32rem !important;
+ border: 1px solid #bfc8d4 !important;
+ border-radius: 0.12rem !important;
+ background: #ffffff !important;
+ box-shadow: 0 0.35rem 1.05rem rgba(15, 23, 42, 0.22) !important;
+ }
+
+ #layout-word-popover > div:first-child {
+ display: none !important;
+ }
+
+ #layout-word-popover > div:nth-child(2) {
+ display: grid !important;
+ grid-template-columns: auto auto auto auto auto auto auto auto auto !important;
+ grid-auto-rows: 1.9rem !important;
+ gap: 0.18rem !important;
+ align-items: center !important;
+ }
+
+ #layout-popover-text {
+ grid-column: 1 / 4 !important;
+ grid-row: 1 !important;
+ width: 11.5rem !important;
+ min-height: 1.85rem !important;
+ padding: 0.16rem 0.36rem !important;
+ border: 1px solid #bfc8d4 !important;
+ border-radius: 0 !important;
+ font-size: 0.86rem !important;
+ background: #ffffff !important;
+ }
+
+ #layout-word-popover .layout-mini-grid {
+ display: contents !important;
+ }
+
+ #layout-popover-font-family {
+ grid-column: 1 / 3 !important;
+ grid-row: 2 !important;
+ width: 9.5rem !important;
+ }
+
+ #layout-popover-font-size {
+ grid-column: 3 / 4 !important;
+ grid-row: 2 !important;
+ width: 3.6rem !important;
+ text-align: center !important;
+ }
+
+ #layout-popover-font-family,
+ #layout-popover-font-size {
+ height: 1.85rem !important;
+ padding: 0.12rem 0.34rem !important;
+ border: 1px solid #bfc8d4 !important;
+ border-radius: 0 !important;
+ background: #ffffff !important;
+ font-size: 0.84rem !important;
+ }
+
+ #layout-word-popover label[for="layout-popover-font-family"],
+ #layout-word-popover label[for="layout-popover-text-color"] {
+ display: none !important;
+ }
+
+ #layout-popover-font-down,
+ #layout-popover-font-up,
+ #layout-popover-font-all,
+ #layout-popover-up,
+ #layout-popover-left,
+ #layout-popover-down,
+ #layout-popover-right,
+ #layout-popover-apply,
+ #layout-popover-delete {
+ height: 1.85rem !important;
+ min-height: 1.85rem !important;
+ padding: 0.12rem 0.42rem !important;
+ border: 1px solid #bfc8d4 !important;
+ border-radius: 0 !important;
+ background: linear-gradient(#ffffff, #edf2f8) !important;
+ color: #111827 !important;
+ font-size: 0.82rem !important;
+ font-weight: 600 !important;
+ line-height: 1 !important;
+ }
+
+ #layout-popover-font-down {
+ grid-column: 4 !important;
+ grid-row: 1 !important;
+ }
+
+ #layout-popover-font-up {
+ grid-column: 5 !important;
+ grid-row: 1 !important;
+ }
+
+ #layout-popover-font-all {
+ grid-column: 6 !important;
+ grid-row: 1 !important;
+ }
+
+ #layout-popover-text-color {
+ grid-column: 7 !important;
+ grid-row: 1 !important;
+ width: 1.85rem !important;
+ height: 1.85rem !important;
+ padding: 0 !important;
+ border: 1px solid #94a3b8 !important;
+ border-radius: 0 !important;
+ background: #ffffff !important;
+ }
+
+ #layout-word-popover > div:nth-child(2) > div:nth-of-type(2) {
+ display: contents !important;
+ }
+
+ #layout-word-popover > div:nth-child(2) > div:nth-of-type(2) > div {
+ display: contents !important;
+ }
+
+ #layout-popover-up {
+ grid-column: 8 !important;
+ grid-row: 1 !important;
+ }
+
+ #layout-popover-left {
+ grid-column: 8 !important;
+ grid-row: 2 !important;
+ }
+
+ #layout-popover-down {
+ grid-column: 9 !important;
+ grid-row: 2 !important;
+ }
+
+ #layout-popover-right {
+ grid-column: 10 !important;
+ grid-row: 2 !important;
+ }
+
+ #layout-word-popover > div:nth-child(2) > div:nth-of-type(3) {
+ display: contents !important;
+ }
+
+ #layout-popover-apply {
+ grid-column: 4 / 6 !important;
+ grid-row: 2 !important;
+ background: linear-gradient(#ffffff, #edf2f8) !important;
+ }
+
+ #layout-popover-delete {
+ grid-column: 6 / 8 !important;
+ grid-row: 2 !important;
+ background: linear-gradient(#fff7f8, #ffe4e6) !important;
+ color: #9f1239 !important;
+ }
+
+ @media (max-width: 760px) {
+ #layout-word-popover {
+ width: min(96vw, 34rem) !important;
+ overflow-x: auto !important;
+ }
+
+ #layout-word-popover > div:nth-child(2) {
+ min-width: 32rem !important;
+ }
+ }
+
+
+
+ /* Lock-size control in selected-word popup */
+ #layout-word-popover .layout-popover-lock-size-label {
+ height: 1.85rem !important;
+ padding: 0 0.35rem !important;
+ border: 1px solid #bfc8d4 !important;
+ background: #ffffff !important;
+ font-size: 0.78rem !important;
+ color: #334155 !important;
+ box-sizing: border-box !important;
+ }
+
+ #layout-popover-lock-size {
+ width: 0.9rem !important;
+ height: 0.9rem !important;
+ margin: 0 !important;
+ }
+
+
+ /* Move selected-word controls into ribbon */
+ #layout-ribbon-selection-group {
+ min-width: 42rem !important;
+ }
+
+ #layout-ribbon-selection-group .word-ribbon-row {
+ gap: 0.25rem !important;
+ }
+
+ #layout-ribbon-selection-group .ribbon-inline-field {
+ display: inline-flex !important;
+ align-items: center !important;
+ gap: 0.2rem !important;
+ margin: 0 !important;
+ padding: 0 !important;
+ border: none !important;
+ min-height: 1.35rem !important;
+ }
+
+ #layout-ribbon-selection-group .ribbon-inline-field span {
+ font-size: 0.62rem !important;
+ color: #64748b !important;
+ }
+
+ #layout-ribbon-selection-group input,
+ #layout-ribbon-selection-group select {
+ height: 1.35rem !important;
+ min-height: 1.35rem !important;
+ padding: 0.08rem 0.3rem !important;
+ font-size: 0.66rem !important;
+ border: 1px solid #b8c6d8 !important;
+ border-radius: 0.18rem !important;
+ background: #fff !important;
+ box-sizing: border-box !important;
+ }
+
+ #layout-ribbon-selection-group .ribbon-text-field input {
+ width: 7.5rem !important;
+ }
+
+ #layout-ribbon-selection-group .ribbon-font-field input {
+ width: 6.5rem !important;
+ }
+
+ #layout-ribbon-selection-group .ribbon-size-field input {
+ width: 3.2rem !important;
+ text-align: center !important;
+ }
+
+ #layout-ribbon-selection-group .ribbon-color-field input[type="color"] {
+ width: 1.35rem !important;
+ padding: 0 !important;
+ }
+
+ #layout-ribbon-selection-group .ribbon-small-select select {
+ width: 4.8rem !important;
+ }
+
+ #layout-ribbon-selection-group .ribbon-small-field input,
+ #layout-ribbon-selection-group .ribbon-xy-field input {
+ width: 3.6rem !important;
+ }
+
+ #layout-ribbon-selection-group button {
+ height: 1.35rem !important;
+ min-height: 1.35rem !important;
+ padding: 0.08rem 0.32rem !important;
+ font-size: 0.64rem !important;
+ }
+
+ #layout-props-card {
+ display: none !important;
+ }
+
+
+ /* Better fit selected-word ribbon controls */
+ #layout-review-toolbar.word-ribbon-toolbar {
+ overflow-x: auto !important;
+ overflow-y: hidden !important;
+ max-width: 100% !important;
+ }
+
+ #layout-ribbon-selection-group {
+ min-width: 36rem !important;
+ max-width: none !important;
+ flex: 0 0 auto !important;
+ padding-left: 0.4rem !important;
+ padding-right: 0.4rem !important;
+ }
+
+ #layout-ribbon-selection-group .word-ribbon-row {
+ display: flex !important;
+ flex-wrap: nowrap !important;
+ align-items: center !important;
+ gap: 0.18rem !important;
+ overflow: hidden !important;
+ }
+
+ #layout-ribbon-selection-group .ribbon-inline-field {
+ display: inline-flex !important;
+ align-items: center !important;
+ gap: 0.12rem !important;
+ min-width: 0 !important;
+ white-space: nowrap !important;
+ }
+
+ #layout-ribbon-selection-group .ribbon-inline-field span {
+ font-size: 0.58rem !important;
+ max-width: 3.4rem !important;
+ overflow: hidden !important;
+ text-overflow: ellipsis !important;
+ }
+
+ #layout-ribbon-selection-group input,
+ #layout-ribbon-selection-group select,
+ #layout-ribbon-selection-group button {
+ height: 1.25rem !important;
+ min-height: 1.25rem !important;
+ font-size: 0.6rem !important;
+ padding: 0.05rem 0.22rem !important;
+ }
+
+ #layout-ribbon-selection-group .ribbon-text-field input {
+ width: 7rem !important;
+ }
+
+ #layout-ribbon-selection-group .ribbon-font-field input {
+ width: 5.8rem !important;
+ }
+
+ #layout-ribbon-selection-group .ribbon-size-field input {
+ width: 2.6rem !important;
+ text-align: center !important;
+ }
+
+ #layout-ribbon-selection-group .ribbon-color-field input[type="color"] {
+ width: 1.25rem !important;
+ min-width: 1.25rem !important;
+ padding: 0 !important;
+ }
+
+ #layout-ribbon-selection-group .ribbon-small-select select {
+ width: 4.2rem !important;
+ }
+
+ #layout-ribbon-selection-group .ribbon-small-field input {
+ width: 3rem !important;
+ }
+
+ #layout-ribbon-selection-group .ribbon-xy-field {
+ display: none !important;
+ }
+
+ #layout-ribbon-selection-group #layout-apply-style-line,
+ #layout-ribbon-selection-group #layout-apply-style-page,
+ #layout-ribbon-selection-group #layout-reset-style-word {
+ display: none !important;
+ }
+
+ #layout-ribbon-selection-group #layout-delete-word-inline {
+ color: #9f1239 !important;
+ background: #ffe4e6 !important;
+ border-color: #fda4af !important;
+ }
+
+ @media (max-width: 760px) {
+ #layout-ribbon-selection-group {
+ min-width: 31rem !important;
+ }
+
+ #layout-ribbon-selection-group .ribbon-text-field input {
+ width: 6rem !important;
+ }
+
+ #layout-ribbon-selection-group .ribbon-font-field input {
+ width: 5rem !important;
+ }
+
+ #layout-ribbon-selection-group .ribbon-inline-field span {
+ display: none !important;
+ }
+ }
+
+
+ /* Selection ribbon polish with compact position controls */
+ #layout-ribbon-selection-group {
+ min-width: 38rem !important;
+ padding-left: 0.25rem !important;
+ padding-right: 0.25rem !important;
+ }
+
+ #layout-ribbon-selection-group .word-ribbon-row {
+ gap: 0.12rem !important;
+ }
+
+ #layout-ribbon-selection-group .ribbon-inline-field {
+ gap: 0.08rem !important;
+ }
+
+ #layout-ribbon-selection-group .ribbon-inline-field span {
+ display: inline-flex !important;
+ font-size: 0.54rem !important;
+ max-width: 2.4rem !important;
+ color: #64748b !important;
+ }
+
+ #layout-ribbon-selection-group input,
+ #layout-ribbon-selection-group select,
+ #layout-ribbon-selection-group button {
+ height: 1.18rem !important;
+ min-height: 1.18rem !important;
+ font-size: 0.58rem !important;
+ padding: 0.03rem 0.18rem !important;
+ }
+
+ #layout-ribbon-selection-group .ribbon-text-field input {
+ width: 6.2rem !important;
+ }
+
+ #layout-ribbon-selection-group .ribbon-font-field input {
+ width: 4.8rem !important;
+ }
+
+ #layout-ribbon-selection-group .ribbon-size-field input {
+ width: 2.25rem !important;
+ text-align: center !important;
+ }
+
+ #layout-ribbon-selection-group .ribbon-color-field input[type="color"] {
+ width: 1.12rem !important;
+ min-width: 1.12rem !important;
+ padding: 0 !important;
+ }
+
+ #layout-ribbon-selection-group .ribbon-small-select select {
+ width: 3.55rem !important;
+ }
+
+ #layout-ribbon-selection-group .ribbon-small-field input {
+ width: 2.55rem !important;
+ }
+
+ /* Restore compact x/y position controls */
+ #layout-ribbon-selection-group .ribbon-xy-field {
+ display: inline-flex !important;
+ }
+
+ #layout-ribbon-selection-group .ribbon-xy-field span {
+ display: inline-flex !important;
+ max-width: 1rem !important;
+ font-size: 0.52rem !important;
+ }
+
+ #layout-ribbon-selection-group .ribbon-xy-field input {
+ width: 2.45rem !important;
+ text-align: right !important;
+ }
+
+ #layout-ribbon-selection-group #layout-apply-style-word {
+ min-width: 3.2rem !important;
+ }
+
+ #layout-ribbon-selection-group #layout-apply-word {
+ min-width: 2.5rem !important;
+ }
+
+ #layout-ribbon-selection-group #layout-delete-word-inline {
+ min-width: 4.3rem !important;
+ color: #9f1239 !important;
+ background: #ffe4e6 !important;
+ border-color: #fda4af !important;
+ }
+
+ #layout-ribbon-selection-group .word-ribbon-label {
+ margin-top: 0.02rem !important;
+ }
+
+ @media (max-width: 760px) {
+ #layout-ribbon-selection-group {
+ min-width: 37rem !important;
+ }
+
+ #layout-ribbon-selection-group .ribbon-inline-field span {
+ display: inline-flex !important;
+ }
+
+ #layout-ribbon-selection-group .ribbon-text-field input {
+ width: 5.6rem !important;
+ }
+
+ #layout-ribbon-selection-group .ribbon-font-field input {
+ width: 4.4rem !important;
+ }
+
+ #layout-ribbon-selection-group .ribbon-xy-field input {
+ width: 2.25rem !important;
+ }
+ }
+
+
+
+ /* Real paired font select */
+ .layout-font-family-select {
+ height: 1.18rem !important;
+ min-height: 1.18rem !important;
+ width: 1.35rem !important;
+ padding: 0 !important;
+ margin-left: 0.08rem !important;
+ font-size: 0.58rem !important;
+ border: 1px solid #b8c6d8 !important;
+ border-radius: 0.18rem !important;
+ background: #ffffff !important;
+ box-sizing: border-box !important;
+ }
+
+ #layout-popover-font-family-select {
+ width: 1.8rem !important;
+ height: 1.9rem !important;
+ min-height: 1.9rem !important;
+ grid-column: 1 / 2 !important;
+ grid-row: 2 !important;
+ justify-self: end !important;
+ z-index: 2 !important;
+ }
+
+ #layout-popover-font-family {
+ padding-right: 2rem !important;
+ }
+
+ #layout-ribbon-selection-group .ribbon-font-field {
+ min-width: 6.5rem !important;
+ }
+
+ #layout-ribbon-selection-group .ribbon-font-field input {
+ width: 4.6rem !important;
+ }
+
+
+
+ /* Popup: anchored normally on desktop, bottom bar only on mobile */
+ #layout-word-popover {
+ box-sizing: border-box !important;
+ z-index: 9998 !important;
+ }
+
+ @media (max-width: 760px) {
+ #layout-word-popover {
+ position: fixed !important;
+ left: 4.35rem !important;
+ right: 0.35rem !important;
+ bottom: 0.45rem !important;
+ top: auto !important;
+ width: auto !important;
+ max-width: none !important;
+ transform: none !important;
+ overflow: visible !important;
+ }
+ }
+
+ /* Keep ribbon from loading half-scrolled / visually clipped */
+ #layout-review-toolbar.word-ribbon-toolbar {
+ scroll-behavior: auto !important;
+ }
+
+ #layout-ribbon-selection-group {
+ max-width: none !important;
+ }
+
+
+ /* Mobile popup final cleanup */
+ #layout-review-debug {
+ display: none !important;
+ }
+
+ @media (max-width: 760px) {
+ #layout-word-popover {
+ position: fixed !important;
+ left: 4.25rem !important;
+ right: 0.35rem !important;
+ bottom: calc(env(safe-area-inset-bottom, 0px) + 0.6rem) !important;
+ top: auto !important;
+ width: auto !important;
+ max-width: none !important;
+ max-height: 9.5rem !important;
+ overflow: hidden !important;
+ padding: 0.35rem !important;
+ box-sizing: border-box !important;
+ z-index: 9998 !important;
+ }
+
+ #layout-word-popover > div:nth-child(2) {
+ display: grid !important;
+ grid-template-columns: minmax(0, 1fr) 3.4rem 3.4rem 3.4rem !important;
+ grid-auto-rows: 1.9rem !important;
+ gap: 0.25rem !important;
+ min-width: 0 !important;
+ max-width: 100% !important;
+ align-items: center !important;
+ }
+
+ #layout-popover-text {
+ grid-column: 1 / 5 !important;
+ grid-row: 1 !important;
+ width: 100% !important;
+ }
+
+ #layout-popover-font-family {
+ grid-column: 1 / 2 !important;
+ grid-row: 2 !important;
+ width: 100% !important;
+ min-width: 0 !important;
+ padding-right: 0.35rem !important;
+ }
+
+ #layout-popover-font-family-select {
+ grid-column: 2 / 3 !important;
+ grid-row: 2 !important;
+ width: 100% !important;
+ height: 1.9rem !important;
+ min-height: 1.9rem !important;
+ position: static !important;
+ justify-self: stretch !important;
+ }
+
+ #layout-popover-font-size {
+ grid-column: 3 / 4 !important;
+ grid-row: 2 !important;
+ width: 100% !important;
+ text-align: center !important;
+ }
+
+ #layout-popover-text-color {
+ grid-column: 4 / 5 !important;
+ grid-row: 2 !important;
+ width: 100% !important;
+ height: 1.9rem !important;
+ position: static !important;
+ justify-self: stretch !important;
+ }
+
+ #layout-popover-font-down {
+ grid-column: 1 / 2 !important;
+ grid-row: 3 !important;
+ }
+
+ #layout-popover-font-up {
+ grid-column: 2 / 3 !important;
+ grid-row: 3 !important;
+ }
+
+ #layout-popover-font-all {
+ grid-column: 3 / 4 !important;
+ grid-row: 3 !important;
+ }
+
+ #layout-popover-apply {
+ grid-column: 1 / 3 !important;
+ grid-row: 4 !important;
+ }
+
+ #layout-popover-delete {
+ grid-column: 3 / 5 !important;
+ grid-row: 4 !important;
+ }
+
+ .layout-popover-lock-size-label {
+ grid-column: 1 / 5 !important;
+ grid-row: 5 !important;
+ height: 1.5rem !important;
+ min-height: 1.5rem !important;
+ padding: 0.15rem 0.3rem !important;
+ }
+
+ #layout-popover-up,
+ #layout-popover-left,
+ #layout-popover-down,
+ #layout-popover-right {
+ display: none !important;
+ }
+
+ #layout-word-popover input,
+ #layout-word-popover select,
+ #layout-word-popover button {
+ min-width: 0 !important;
+ box-sizing: border-box !important;
+ }
+ }
+
+
+
+
+