/* ─────────────────────────────────────────────────────────────────────────
   Shared slide-content styles — pairs with js/slide-render.js. ONE stylesheet
   for the shared block markup, loaded by BOTH proposal-builder.html and
   proposal-view.html (the client + PDF), so a block's CONTENT renders
   identically on every surface. Loaded AFTER css/builder.css in the builder so
   these definitions win where they overlap.

   Colours are HARDCODED to the builder's LIGHT-mode values (not --blue-dark /
   --blue / --gray-600, which shift with the viewer's dark mode via app.css) so
   the delivered document looks the same for everyone (CLAUDE.md goal #2) and
   matches the builder. The client used to hardcode a slightly different blue
   (#0C447C/#185FA5); this unifies both on the builder's #0E4F8A/#1A6DB8.

   Ported so far: richtext (single body), about-us (heading + body + stat cards),
   2-col / 3-col column layouts. Other block types still render on each surface's
   own path until ported.
   ───────────────────────────────────────────────────────────────────────── */

/* ── Content-hug ──
   A richtext (layout-full) section is exactly as tall as its content — no
   min-height floor — so there is no dead gap before the next section (the
   layout-spec target: "box height follows content, section ends where the copy
   ends"). Overrides css/builder.css's stale 562 floors by higher specificity.
   Cover / full-bleed-image sections keep a fixed shared size — that is their own
   port (freeform/cover phase), not here. */
:is(.bld-slide, .preview-slide):is(.layout-full, .layout-2col, .layout-3col),
:is(.bld-slide, .preview-slide):is(.layout-full, .layout-2col, .layout-3col) > .slide-body.slide-content-body,
.pv-slide > .slide-body.slide-content-body { min-height: 0; }

/* ── Image panel (simple photo + caption) ──
   Content-hug: heading + photo + caption, box ends at the caption — no floor, no
   stranded gap under the caption (the spec's "panel + caption only" target). BOTH
   the block body AND the slide card carry a stale floor (builder .image-block 562 +
   the .bld-slide card 562; client .pv-image-block via .pv-body 300 + no card
   floor), so zero both. The FULL-BLEED variant (.has-image-area) is intentionally
   tall (a hero image area) and is excluded. The image slide carries no layout class
   (it has no .slide-body), so the card is matched with :has() — supported on every
   render surface (Chromium PDF + modern client browsers). */
.bld-slide .image-block:not(.has-image-area),
.preview-slide .image-block:not(.has-image-area),
.pv-slide .pv-image-block:not(.has-image-area) { min-height: 0; }
.bld-slide:has(> .image-block:not(.has-image-area)),
.preview-slide:has(> .image-block:not(.has-image-area)),
.pv-slide:has(> .pv-image-block:not(.has-image-area)) { min-height: 0; }

/* ── Pricing slide ── content-hug (the spec's highest-risk block: a stray gap
   sits right above the total the client accepts). The pricing body is .slide-body
   (builder) / .pv-body (client) — NOT .slide-content-body — so target it by its
   unique Option-B rows wrapper (.qpr-rows / .pv-optionb-rows) rather than zeroing
   every .slide-body. Zero the body AND the slide card (pricing slides carry no
   layout class, so match the card with :has()). RENDER-ONLY and layout-only: the
   money math (tax rate, currency symbol) is deliberately untouched here — the
   builder is region-aware while the client hardcodes AU/10%/$, a separate PRODUCT
   decision flagged to the owner, never guessed at as a side effect of this. */
.bld-slide > .slide-body:has(> .qpr-rows),
.preview-slide > .slide-body:has(> .qpr-rows),
.pv-slide > .pv-body:has(> .pv-optionb-rows) { min-height: 0; }
.bld-slide:has(> .slide-body > .qpr-rows),
.preview-slide:has(> .slide-body > .qpr-rows),
.pv-slide:has(> .pv-body > .pv-optionb-rows) { min-height: 0; }

/* ── Fill (cover) background = full-bleed hero (owner decision) ──
   A slide whose background is Fill/cover takes the FULL design-box height on EVERY surface
   instead of content-hugging, so the photo covers the same area in the builder, client, preview
   and PDF (a short content-hug slide would otherwise crop the photo to a thin strip on the client
   while the PDF shows the full page). Both engines add .slc-fill-bg to the slide card when a Fill
   background is applied (js/builder.js _applySettingsToElement + proposal-view.html applySettings).
   MUST sit after the content-hug rules above so it wins the card's min-height. min-height (not a
   fixed height) so a content-heavy hero still grows and is never clipped. Fit (contain, letterboxed)
   is NOT full-bleed and keeps content-hug — the marker is added only in each engine's Fill branch. */
.pv-slide.slc-fill-bg,
.bld-slide.slc-fill-bg,
.preview-slide.slc-fill-bg,
.ppv-page.slc-fill-bg { min-height: 794px; }

/* ── Fixed slide height + auto-fit (opt-in: slide.settings.fixedHeight) — phase 1: richtext ──
   PowerPoint/Canva model: the slide renders at ONE fixed canvas size (the design box
   CANVAS_W×CANVAS_H = 1123×794) instead of content-hugging, and the text auto-fits the box via a
   per-slide scale (settings.fitScale, computed once in the builder at save and stored). --fit-scale
   multiplies the text font sizes so short copy grows (capped) and dense copy shrinks (to a floor) to
   fill the fixed height. The fit is computed at the 1123 DESIGN WIDTH.
   OPT-IN + additive: a slide WITHOUT settings.fixedHeight gets no .slc-fixed-height class and no
   --fit-scale (which defaults to 1), so it renders exactly as before — content-hug, byte-identical.
   Overrides the content-hug min-height above by equal specificity + later position.

   TWO surface families, because they do NOT all render at the same width:
   • FIXED-GEOMETRY (all lay out at the 1123 design width): the builder canvas (#slide-canvas,
     transform-scaled to fit), the PDF (.bld-slide on an A4-landscape page ≈ 1123) and the
     standalone preview (.ppv-page, 1123 since #233). The fit computed at 1123 holds EXACTLY here,
     so pin to the design height and clip — this is the page the client downloads. (A slide whose
     copy cannot fit even at the font floor is flagged to the author in the builder, PowerPoint-
     style; that is the only case anything reaches the clip here.)
   • FLUID-WIDTH (narrow with the viewport, so text wraps TALLER than at 1123): the on-screen client
     web view (.pv-slide) — which also backs the proposal Preview popup iframe — and the quote
     preview modal (.preview-slide). Here the 1123 fit can run taller than 794 on a small screen, so
     we fill the design height as a MINIMUM but NEVER hard-clip: on a screen too narrow to hold the
     fitted copy the slide grows instead of losing content — exactly today's content-hug reflow. The
     downloaded PDF is still the exact fitted 794; only the live web preview reflows on small screens. */
.bld-slide.slc-fixed-height,
.ppv-page.slc-fixed-height { height: 794px; min-height: 794px; overflow: hidden; }
.bld-slide.slc-fixed-height > .slide-body.slide-content-body,
.ppv-page.slc-fixed-height > .slide-body.slide-content-body { height: 100%; }
.pv-slide.slc-fixed-height,
.preview-slide.slc-fixed-height { min-height: 794px; }

/* ── Image-panel image — single canonical sizing, loaded on every surface ──
   The picture is capped to fit inside the slide and never grow over the caption: object-fit:contain
   scales an oversized image into the cap without cropping; a smaller one is untouched. Both engines
   emit an image element (.image-block-img on the builder engine, .pv-image-block img on the client),
   sized here so the cap / fit / radius come from ONE place. Per-image width, a custom aspect and
   object-fit:fill (edge-stretch) are still applied inline by each renderer and win over these
   defaults. The full-bleed .has-image-area variant caps to the area instead. */
.image-block-img,
.pv-image-block img { width: 100%; max-height: 460px; object-fit: contain; border-radius: 8px; }
.image-block.has-image-area .image-block-img,
.pv-image-block.has-image-area img { max-height: 100%; }

/* ── Body wrapper ──
   line-height 1.5 = the builder's value (its app.css cascades 1.5 onto the slide;
   the client has no app.css, so without this its heading is `normal` and its body
   inherits 1.75 → both render taller than the builder). Set on the container so the
   heading and body BOTH inherit it. "Builder wins." */
.slide-body.slide-content-body { padding: 48px 56px; line-height: 1.5; }

/* ── Heading ── builder light-mode blues, hardcoded for document invariance.
   font-size folds in --fit-scale (default 1 → 32px, byte-identical for non-fixed-height slides)
   so the auto-fit scale reaches the heading too. The body scales via its stamped calc() size. */
.slide-heading {
  font-size: calc(32px * var(--fit-scale, 1)); font-weight: 700; margin: 0 0 20px;
  color: #0E4F8A; outline: none;
  border-bottom: 3px solid #1A6DB8; padding-bottom: 10px;
  display: inline-block;
}
/* An EMPTY heading reserves the same line on the builder-engine DELIVERED surfaces (the staff
   Download PDF, the slide thumbnails and the standalone preview's PDF) that the editing canvas
   already shows. Where a title is typed into the body as an <h2>, the block's heading field is
   left empty: on the canvas that div carries contenteditable, which keeps a line box even with no
   content (61px = 48 line + 10 padding + 3 border), while every non-editable render collapses it
   to 13px and the block jumps up 42px. The zero-width space recreates exactly that line box.
   `:not([contenteditable])` means this can NEVER apply to the editing canvas — the Builder is the
   reference here and is deliberately left untouched, including its caret behaviour. Being a
   pseudo-element it is invisible to innerHTML, so it can never be captured into stored content.
   `body.is-proposal` is set by js/builder.js, by printPreview()'s PDF window and by
   proposal-preview.html — so QUOTES, which share this stylesheet, are untouched. The client
   engine is a separate copy and carries the twin of this rule in proposal-view.html. */
body.is-proposal .slide-heading:not([contenteditable]):empty::before { content: '\200B'; }

/* ── Body text ──
   No font-size here on purpose: the size is the theme's (both engines stamp the
   container's font-size from settings — 16px default; small/med/large = 14/16/18),
   and .slide-text inherits it. A hardcoded size here would override the theme on
   the client (which inherits) while the builder stamps it inline → size mismatch.
   colour is likewise theme-driven via .slc-textcolor; #4B5563 is only a fallback. */
.slide-text {
  /* line-height inherits 1.5 from .slide-body (above) — matches the builder; an
     explicit value here would override that inheritance on the client. */
  color: #4B5563;
  outline: none; min-height: 40px;
}
.slide-text:empty:before {
  content: attr(data-placeholder); color: #9CA3AF; pointer-events: none;
}
/* Block spacing — one definition for both surfaces. rt-block/figure wrappers the
   normalizer adds carry no margin (so wrapped loose text renders like bare text);
   authored blocks get consistent spacing. p.rt-block outranks the bare-p rule. */
.slide-text p { margin: 0 0 12px; }
.slide-text p.rt-block, .slide-text figure { margin: 0; }
.slide-text ul, .slide-text ol { margin: 12px 0; padding-left: 24px; }
.slide-text li { margin-bottom: 6px; }
.slide-text img { max-width: 100%; }
/* Neutralise a stored transform:translate on an inline body image (baked into stored
   content by the #35 opposite-edge resize anchoring) so it renders IN PLACE instead of
   riding off-position / out of view. Display-only — never written back. Both the builder
   (css/builder.css) and the old client (.pv-text) carried this; the shared port to
   .slide-text/.slide-col dropped it, and the CLIENT does not load builder.css, so it must
   live here or a transformed inline image goes missing on the client + PDF. Floated images
   anchor by float and are excluded. */
.slide-text img:not([style*="float"]),
.slide-col  img:not([style*="float"]) { transform: none !important; }
/* Hide pasted background-colour residue on inline text (display-only; never
   touches stored HTML). */
.slide-text :is(span, b, i, u, em, strong, font) { background-color: transparent !important; }

/* ── Slide-level Text colour override (settings.textColor) ──
   When the slide sets a text colour, its container carries .slc-textcolor +
   --slc-textcolor; block elements follow it (span/font keep per-selection
   colours). Display-only. */
.slc-textcolor .slide-text :where(p,div,ul,ol,li,h1,h2,h3,h4,h5,h6,blockquote,pre),
.slc-textcolor .slide-col :where(p,div,ul,ol,li,h1,h2,h3,h4,h5,h6,blockquote,pre) {
  color: var(--slc-textcolor) !important;
}

/* ── About-Us stat cards ──
   Shared markup for the About-Us block's stat row (heading + body reuse the
   .slide-heading / .slide-text rules above). Values are the builder's LIGHT-mode
   vars, hardcoded for document invariance (builder.css: --blue-light #EAF3FC,
   --blue #1A6DB8, --gray-600 #4B5563, --radius-md 8px). These are only the base;
   each engine's settings pass then stamps the card surface (cardColor, default
   navy #1A2D42), the value colour (accentColor) and the on-dark contrast text —
   both engines now target these .about-stat* classes, so the themed result is
   identical on the builder, client and PDF. */
.about-stats {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; margin-top: 28px;
}
.about-stat {
  background: #EAF3FC; border-radius: 8px;
  padding: 22px 16px; text-align: center;
}
.about-stat-value { font-size: 30px; font-weight: 700; color: #1A6DB8; outline: none; }
.about-stat-label { font-size: 12px; color: #4B5563; margin-top: 4px; outline: none; }

/* ── Multi-column richtext (2-col / 3-col) ──
   Shared markup for the column layouts. The heading (.slide-heading, above)
   spans full width; the body splits into per-column .slide-col slots. Values are
   the builder's (builder.css): base 3-track grid, gap 24px; --2 modifier for two
   tracks; .slide-col is font-size 14px / colour --gray-600 (#4B5563). NOTE the
   builder deliberately does NOT stamp the document font-size onto .slide-col (its
   settings pass excludes it), so columns stay 14px on every surface — hardcoding
   14px here keeps the client identical.

   No line-height here on purpose (same reasoning as .slide-text): the builder
   stamps the document line-height (default 1.5) INLINE on .slide-col, which beats
   builder.css's own .slide-col{1.75}. The client doesn't load builder.css and gets
   no inline stamp for the default, so letting .slide-col INHERIT the container's
   1.5 makes the client match the builder (an explicit 1.75 here would render the
   client's columns looser than the builder). An explicit theme line-height is
   stamped inline on .slide-col by each engine's settings pass; colour is likewise
   theme-driven (#4B5563 is only the fallback). */
.slide-columns { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 24px; }
.slide-columns--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.slide-col { font-size: 14px; color: #4B5563; outline: none; min-height: 40px; }
.slide-col:empty:before { content: attr(data-placeholder); color: #9CA3AF; pointer-events: none; }
.slide-col p { margin: 0 0 12px; }
.slide-col p.rt-block, .slide-col figure { margin: 0; }
.slide-col ul, .slide-col ol { margin: 12px 0; padding-left: 24px; }
.slide-col li { margin-bottom: 6px; }
.slide-col img { max-width: 100%; }
.slide-col :is(span, b, i, u, em, strong, font) { background-color: transparent !important; }
/* Stack columns on real phones (CLIENT web only — scoped to .pv-slide so the
   builder canvas and the print/PDF surface keep the columns side by side, exactly
   as the client's old .pv-columns rule did). */
@media screen and (max-width: 640px) { .pv-slide .slide-columns { grid-template-columns: 1fr; } }
