﻿/* ═════════════════════════════════════════════════════════════════════════════
   Dry Bulk Forecaster — report page styling.

   This lives in wwwroot/css rather than in a .razor.css on purpose: CreatePdf's
   AddHeaderToInnerText inlines every file in wwwroot/css into the PDF document,
   but it does not link Navigator.styles.css. Scoped component CSS therefore never
   reaches the PDF, and the report would print unstyled. Keeping the page styling
   here means screen and PDF render from one stylesheet.

   Screen-only chrome (the grey desk, the page picker, the toolbar) stays scoped in
   RptDryBulkForecaster_010_Main.razor.css — none of it is part of the PDF.

   Every class is dbf- prefixed so nothing leaks into other pages.
   ═════════════════════════════════════════════════════════════════════════════ */

/* ─────────────────────────────────────────────────────────────────────────────
   The sheet of paper. The 900 x 1273px design grid is A4 portrait scaled up by
   1.134 (A4 at 96dpi is 794 x 1123px), so it maps onto a real page without any
   reflow — see the PDF section at the bottom.
   ───────────────────────────────────────────────────────────────────────────── */
.dbf-paper {
    width: 900px;
    min-height: 1273px;
    margin: 0 auto 24px;
    background: #fff;
    box-shadow: 0 8px 40px rgba(0, 0, 0, 0.4);
    display: flex;
    flex-direction: column;
}

/* Body of an inside page. The page components own everything inside this. */
.dbf-page-inner {
    flex: 1;
    padding: 0 36px 30px;
}

/* ── Header band ────────────────────────────────────────────────────────────
   Logo, page title and the SSY shipping-lines wave. */
.dbf-page-header {
    position: relative;
    height: 122px;
    flex: none;
    display: flex;
    align-items: center;
    gap: 20px;
    padding: 0 34px;
    overflow: hidden;
    background: #fff;
}

/* The wave is the same artwork, drawn the same way, as .reportBanner in ssyreports.css —
   the vector in wwwroot rather than a presigned blob image. Two reasons it belongs to the
   stylesheet and not to the artwork table: the SVG stretches (it carries
   preserveAspectRatio="none", which is what lets 100% x 225% distort it into a band rather
   than crop it), and a file the print service fetches from our own origin cannot expire
   underneath a capture the way a signed URL can.

   The path is relative in the same form ssyreports.css uses, which resolves both on screen
   (this file is served from /css/) and in the PDF, where CreatePdf inlines wwwroot/css into
   a <style> under <base href='https://navigator.ssyglobal.com'>.

   Oversized and pulled up so the band shows the part of the artwork that has visible line
   weight in it. The numbers are ssyreports.css's, so this header and a report drawn by
   ReportHeader read as the same masthead. */
.dbf-page-header-wave {
    position: absolute;
    inset: 0;
    background-image: url("../assets/Lines/SSYShippingLines_01_stretchable.svg");
    background-repeat: no-repeat;
    background-size: 100% 225%;
    background-position: right -50px;
}

.dbf-page-header-logo {
    position: relative;
    z-index: 1;
    height: 74px;
    flex: none;
}

.dbf-page-header-text {
    position: relative;
    z-index: 1;
}

.dbf-page-header-title {
    margin: 0;
    font-family: Georgia, Tinos, "Times New Roman", serif;
    font-size: 3.4rem;
    font-weight: 400;
    line-height: 1;
    color: #000061;
    white-space: nowrap;
}

.dbf-page-header-sub {
    margin-top: 1px;
    font-size: 1rem;
    font-weight: 700;
    color: #009DCA;
}

/* ── Footer ───────────────────────────────────────────────────────────────── */
.dbf-page-footer {
    flex: none;
    border-top: 2px solid #000061;
    padding: 8px 36px;
    background: #F4F7F9;
}

.dbf-page-footer-legal {
    font-size: 0.52rem;
    color: #536A75;
    line-height: 1.35;
    margin-bottom: 4px;
}

/* The blurb is a DbfTextBlock, so its blocks are whatever the contenteditable produced
   rather than the one <div> the markup used to hold. Flatten their margins: the footer is
   a fixed band at the foot of a sheet that is 1:1 with the PDF, and a default 1em gap
   between the publisher line and the disclaimer pushes the page number off the paper.
   Scoped to the footer so the report's body copy keeps its own spacing. */
.dbf-page-footer-legal .dbf-text-content > p,
.dbf-page-footer-legal .dbf-text-content > div,
.dbf-page-footer-legal .dbf-text-content > ul,
.dbf-page-footer-legal .dbf-text-content > ol {
    margin: 0;
}

.dbf-page-footer-number {
    text-align: right;
    font-size: 0.7rem;
    color: #536A75;
    font-weight: 700;
}

/* ── Headings ─────────────────────────────────────────────────────────────── */
.dbf-h2 {
    color: #000061;
    font-size: 1.15rem;
    font-weight: 700;
    border-bottom: 3px solid #009DCA;
    padding-bottom: 4px;
    margin: 0 0 10px;
}

.dbf-h3 {
    color: #163E9F;
    font-size: 1rem;
    font-weight: 600;
    margin: 16px 0 8px;
}

/* ── Editable text ────────────────────────────────────────────────────────────
   Text boxes are plain contenteditable markup with no binding behind them: the
   browser owns the text, Blazor never re-renders over it (which would reset the
   caret), and the PDF picks up whatever is on screen because the capture reads
   the live DOM. Nothing is persisted. */
.dbf-edit {
    outline: none;
    border-radius: 2px;
}

    .dbf-edit:hover {
        box-shadow: inset 0 0 0 1px rgba(0, 157, 202, 0.35);
        cursor: text;
    }

    /* focus-within as well as focus: a DbfTextBlock's editable element is nested inside
       the region, so the region itself never takes focus.

       Outline only, no fill — a tinted block behind live copy misrepresents how the page
       will print, which matters on a surface that is 1:1 with the PDF. */
    .dbf-edit:focus,
    .dbf-edit:focus-within {
        box-shadow: inset 0 0 0 1px #009DCA;
    }

.dbf-edit-area {
    position: relative;
}

    /* Pencil hint on hover — never printed, see the PDF section. */
    .dbf-edit-area::after {
        content: '\270E';
        position: absolute;
        top: 2px;
        right: 3px;
        font-size: 0.65rem;
        color: rgba(0, 157, 202, 0.55);
        pointer-events: none;
        opacity: 0;
        transition: opacity 0.15s;
    }

    .dbf-edit-area:hover::after {
        opacity: 1;
    }

/* ── Stored copy (DbfTextBlock) ────────────────────────────────────────────────
   The text on the sheet is now whatever HTML the author's browser produced in the
   contenteditable, so it has to be styled rather than authored. .dbf-text is the
   region, .dbf-text-content the element whose children are the blocks the author
   typed — the same in the editable render and the read-only one, so both read
   identically. A contenteditable separates blocks with <div> where the markup this
   replaced used <p>; both are covered.

   These rules belong here, not in a .razor.css, because the print service inlines
   wwwroot/css and nothing else — see the PDF section at the foot of this file. */
.dbf-text {
    position: relative;
}

.dbf-body-copy .dbf-text-content > p,
.dbf-body-copy .dbf-text-content > div {
    margin: 0 0 6px;
}

    .dbf-body-copy .dbf-text-content > :last-child {
        margin-bottom: 0;
    }

/* Nested list markers, to the three levels the editor allows. */
.dbf-text-content ul { list-style: disc; }
.dbf-text-content ul ul { list-style: circle; }
.dbf-text-content ul ul ul { list-style: square; }
.dbf-text-content ol { list-style: decimal; }
.dbf-text-content ol ol { list-style: lower-alpha; }
.dbf-text-content ol ol ol { list-style: lower-roman; }

.dbf-text-content ul ul,
.dbf-text-content ol ol {
    margin: 2px 0;
    padding-left: 14px;
}

/* Autosave failed — say so, or the author has no way of knowing the text is only on
   screen. Sits clear of the pencil hint. Screen only, see the PDF section. */
.dbf-text-unsaved {
    position: absolute;
    top: 2px;
    right: 16px;
    z-index: 5;
    padding: 0 4px;
    border-radius: 2px;
    background: #EB3871;
    color: #fff;
    font-size: 0.6rem;
    line-height: 1.6;
    font-weight: 600;
}

/* ── Body copy ────────────────────────────────────────────────────────────── */
.dbf-body-copy {
    font-size: 0.72rem;
    line-height: 1.5;
    color: #222;
}

    .dbf-body-copy p {
        margin: 0 0 6px;
    }

        .dbf-body-copy p:last-child {
            margin-bottom: 0;
        }

/* Stand-in copy for pages that have not been built yet. */
.dbf-placeholder {
    font-size: 0.8rem;
    color: #536A75;
    margin: 0;
}

/* ── Charts ───────────────────────────────────────────────────────────────────
   Placeholder panels until the Highcharts configs are wired up. */
.dbf-chart {
    height: 200px;
    background: #F4F7F9;
    border: 1px dashed #B5CBD6;
    border-radius: 3px;
    display: flex;
    align-items: center;
    justify-content: center;
    color: #9FB3BC;
    font-size: 0.78rem;
}

.dbf-chart-title {
    font-size: 0.72rem;
    font-weight: 700;
    color: #000061;
    margin-bottom: 4px;
}

.dbf-chart-title-spaced {
    margin-top: 10px;
}

/* ── Highlight boxes ──────────────────────────────────────────────────────── */
.dbf-hl {
    border-left: 4px solid #000061;
    padding: 5px 9px 5px 11px;
    background: #F4F7F9;
    margin-bottom: 8px;
}

    .dbf-hl h5 {
        margin: 0 0 2px;
        font-size: 0.8rem;
        color: #000061;
    }

    .dbf-hl p,
    .dbf-hl ul {
        margin: 0;
        font-size: 0.7rem;
        line-height: 1.38;
    }

    .dbf-hl ul {
        padding-left: 14px;
    }

/* Accent colour per highlight topic. */
.dbf-hl-coal { border-left-color: #000061; }
.dbf-hl-iron { border-left-color: #009DCA; }
.dbf-hl-agri { border-left-color: #F7A506; }
.dbf-hl-fleet { border-left-color: #163E9F; }
.dbf-hl-supply { border-left-color: #EB3871; }

/* ── Layout helpers ───────────────────────────────────────────────────────── */
.dbf-grid2,
.dbf-grid3 {
    display: grid;
    gap: 14px;
    margin-bottom: 14px;
}

.dbf-grid2 { grid-template-columns: 1fr 1fr; }
.dbf-grid3 { grid-template-columns: 1fr 1fr 1fr; }

    .dbf-grid2 > div,
    .dbf-grid3 > div {
        min-width: 0;
    }

/* Two totals with their combined figure between them. The charts share what the circle
   does not take, so the circle keeps its size whatever the sheet is doing. */
.dbf-coal-totals {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    gap: 14px;
    align-items: center;
    margin-bottom: 14px;
}

    .dbf-coal-totals > div {
        min-width: 0;
    }

/* ─────────────────────────────────────────────────────────────────────────────
   The combined-growth circle.

   One figure lifted out of the table beside it, so the headline number is read
   without hunting along a row for it.
   ───────────────────────────────────────────────────────────────────────────── */
.dbf-growth-badge {
    width: 128px;
    height: 128px;
    border-radius: 50%;
    background: #000061;
    color: #fff;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
    margin: 0 auto;
    padding: 10px;

    /* It sits in an `auto` grid track between the two charts, and a circle that is
       allowed to squash is not a circle. */
    flex: none;
}

    .dbf-growth-badge-label {
        font-size: 0.68rem;
        font-weight: 400;
        line-height: 1.35;
    }

    /* Coloured by direction, same as the tables' dbf-pos/dbf-neg change columns — a decline in
       the circle should read the same way a decline in a row does. Neutral white until a sign is
       known ("—", no figure for the issue). */
    .dbf-growth-badge-value {
        margin-top: 4px;
        font-size: 1.45rem;
        font-weight: 700;
        line-height: 1.1;
        color: #fff;
    }

        /* dbf-pos/dbf-neg are tuned for dark text on the sheet's white tables; against this
           circle's navy they are swapped for Palette 1's own red and olive-green, chosen for
           enough contrast on #000061 while still reading as "growth"/"decline" at a glance. */
        .dbf-growth-badge-value.dbf-pos {
            color: #91A527;
        }

        .dbf-growth-badge-value.dbf-neg {
            color: #DB303A;
        }

/* ─────────────────────────────────────────────────────────────────────────────
   Page 1 — Cover.

   A fixed composition matching the printed cover, so every element is positioned
   absolutely against the 900 x 1273 paper. Shapes are CSS; the wave and the logo
   come from the branding assets in wwwroot.
   ───────────────────────────────────────────────────────────────────────────── */
.dbf-cover {
    position: relative;
    flex: 1;
    min-height: 1273px;
    background: #fff;
    overflow: hidden;
}

/* Gradient block in the top-right corner — navy climbing to brand cyan. */
.dbf-cover-block-corner {
    position: absolute;
    top: 0;
    right: 0;
    width: 315px;
    height: 318px;
    background: linear-gradient(42deg, #000061 55%, #0087CB 85%, #00C4F0 100%);
}

/* Vessel photograph (nav.Asset 68). The URL arrives as an inline background-image;
   the gradient here is the fallback when the asset can't be resolved. */
.dbf-cover-image {
    position: absolute;
    top: 327px;
    left: 0;
    width: 576px;
    height: 406px;
    background-color: #000061;
    background-image: linear-gradient(150deg, #163E9F 0%, #000061 60%, #000050 100%);
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
    display: flex;
    align-items: center;
    justify-content: center;
}

.dbf-cover-image-placeholder {
    color: rgba(255, 255, 255, 0.15);
    font-size: 0.85rem;
    letter-spacing: 2px;
}

/* SSY shipping-lines wave, sweeping out from under the photograph and down towards
   the right edge. Stretched to the band rather than cropped: the artwork's own sweep
   already lands the bright edge in the lower right, as the printed cover has it. The
   image arrives as an inline background-image from the artwork assets. */
.dbf-cover-wave {
    position: absolute;
    left: 0;
    right: 0;
    top: 620px;
    height: 390px;
    background-repeat: no-repeat;
    background-size: 100% 100%;
    background-position: left center;
}

/* The two staggered blocks in the lower half. */
.dbf-cover-block-cyan {
    position: absolute;
    left: 0;
    top: 848px;
    width: 225px;
    height: 197px;
    background: #00E2FD;
}

.dbf-cover-block-blue {
    position: absolute;
    left: 225px;
    bottom: 0;
    width: 210px;
    height: 198px;
    background: #0087CB;
}

.dbf-cover-title {
    position: absolute;
    top: 74px;
    left: 58px;
    z-index: 2;
    font-size: 5.5rem;
    font-weight: 400;
    color: #000061;
    line-height: 1.35;
    font-family: Georgia, Tinos, "Times New Roman", serif;
}

/* Issue date, set beside the cyan block rather than on it. */
.dbf-cover-date {
    position: absolute;
    left: 252px;
    top: 998px;
    z-index: 2;
    margin: 0;
    color: #000061;
    font-size: 2rem;
    font-weight: 700;
}

.dbf-cover-logo {
    position: absolute;
    right: 30px;
    bottom: 100px;
    z-index: 2;
    height: 104px;
}

/* ─────────────────────────────────────────────────────────────────────────────
   Page 2 — Summary.
   ───────────────────────────────────────────────────────────────────────────── */
.dbf-summary-split {
    display: grid;
    grid-template-columns: 1fr 300px;
    gap: 20px;
    margin-bottom: 14px;
}

    .dbf-summary-split > div {
        min-width: 0;
    }

.dbf-summary-highlights {
    display: grid;
    grid-template-columns: 1fr 1fr 1fr;
    gap: 14px;
}

/* ─────────────────────────────────────────────────────────────────────────────
   Data tables. Used from page 3 onwards.
   ───────────────────────────────────────────────────────────────────────────── */
.dbf-scroll-wrap {
    overflow-x: auto;
    margin-bottom: 2px;
    scrollbar-width: thin;
    scrollbar-color: #009DC9 transparent;
}

.dbf-t {
    border-collapse: collapse;
    font-size: 0.72rem;
    white-space: nowrap;
    width: 100%;
}

    /* No border on a heading. A 1px rule between every year and every quarter turned the band
       into a grid of small boxes, and at the fractional zooms the capture renders at those rules
       landed on half pixels and printed unevenly. The band is solid colour instead, and the
       columns are told apart by the figures under them — which carry their own rules. */
    .dbf-t th {
        background: #000061;
        color: #fff;
        padding: 3px 5px;
        text-align: center;
        border: 0;
    }

        .dbf-t th.dbf-t-sub {
            background: #163E9F;
        }

        .dbf-t th.dbf-t-left {
            text-align: left;
        }

    .dbf-t td {
        padding: 2px 5px;
        border: 1px solid #DAE5EB;
        text-align: right;
    }

        .dbf-t td.dbf-t-label {
            text-align: left;
            min-width: 140px;
        }

        /* A name written once across the two rows it covers — a fleet segment over its count
           and its tonnage, a country over its thermal and its coking coal. Sits between the
           pair rather than against the top of it.

           min-width overrides the label's 140px: these names are short, and on a table that
           is already cut for width every pixel the name column does not take is one the
           quarters get. */
        .dbf-t td.dbf-t-segment {
            min-width: 0;
            vertical-align: middle;
        }

        .dbf-t td.dbf-t-sublabel {
            text-align: left;
            padding-left: 16px;
            color: #405864;
        }

        .dbf-t td.dbf-t-unit {
            text-align: center;
            color: #667C87;
            width: 30px;
        }

        /* The grade marker on the coal tables — "Thermal"/"Coking" printed in full. Sized to fit
           the longer word without wrapping, wider than the plain .dbf-t-unit column it otherwise
           shares, and still no wider than it has to be: the quarters get whatever is left.
           Coloured to tell the two grades apart at a glance down the table. */
        .dbf-t td.dbf-t-grade {
            width: 46px;
            min-width: 46px;
            font-size: 0.62rem;
            font-weight: 600;
            white-space: nowrap;
        }

        .dbf-t td.dbf-t-grade-thermal { color: #163E9F; }
        .dbf-t td.dbf-t-grade-coking { color: #009DCA; }

    .dbf-t tr.dbf-t-section td {
        background: #DAE5EB;
        font-weight: 700;
        color: #000061;
        text-align: left;
    }

    .dbf-t tr.dbf-t-total td {
        background: #E7EEF2;
        font-weight: 700;
    }

    .dbf-t tr:nth-child(even):not(.dbf-t-section):not(.dbf-t-total) td {
        background: #F4F7F9;
    }

    /* Boundary between the annual block and the quarterly columns. */
    .dbf-t th.dbf-t-qtr-sep,
    .dbf-t td.dbf-t-qtr-sep {
        border-left: 2px solid #B5CBD6;
    }

/* ─────────────────────────────────────────────────────────────────────────────
   Tables cut where the periods turn quarterly.

   A forecast table runs to twenty-odd columns. Drawn as one table across the sheet
   every figure is squeezed to nothing, so it is cut in two where the periods change:
   the years and the spread on the left, the quarters on the right.

   The annual side never scrolls — being able to read the years whole is the reason for
   cutting the table here. The quarters take what is left of the sheet and scroll.

   Column widths are pinned rather than left to the browser — see the block below
   this one. Sized to the figures, not by dividing the sheet up by the column count,
   which is what made the years unreadable; what the pinning buys is that every table
   on a sheet draws the same grid.

   The two sides line up because the rows are the same height and are drawn in the
   same order on both. A row must never be skipped on one side only.
   ───────────────────────────────────────────────────────────────────────────── */
.dbf-t-split {
    display: grid;
    grid-template-columns: max-content minmax(0, 1fr);
    align-items: start;
    margin-bottom: 2px;
}

    /* min-width: 0 — a grid track otherwise refuses to go below its content's width,
       which is exactly what the quarters need to do before they will scroll. */
    .dbf-t-split-years,
    .dbf-t-split-quarters {
        min-width: 0;
    }

    /* Only the quarters scroll, and the pane is opened at the issue's own year rather than on
       the oldest quarter it holds — dbfReport.js sets that offset, and positioning the pane is
       what puts it on the offsetParent chain the offset is measured along.

       The bar itself is thin and in the report's own crystal blue on nothing, as .ssy-overflow-auto-lw
       has it elsewhere: it sits directly under a table on a white sheet, where the browser's default
       grey slab reads as part of the page rather than as a control. */
    .dbf-t-split-quarters {
        overflow-x: auto;
        position: relative;
        scrollbar-width: thin;
        scrollbar-color: #009DC9 transparent;
    }

    /* Both sides pinned to the same row heights, so a long label on one side can never
       walk the two out of step. */
    .dbf-t-split .dbf-t th,
    .dbf-t-split .dbf-t td {
        height: 20px;
        line-height: 14px;
    }

/* ── One column geometry for every table on a sheet ─────────────────────────
   Left to itself a table sizes its columns to its own figures, so Asia's 1022 makes
   a wider 2023 column than Europe's 167 and the tables stacked down a sheet never
   line up: the year bands step in and out, and each quarter pane starts somewhere
   different. On a page of four tables of the same shape that reads as a fault.

   So the columns are pinned, and every table that opts in draws the same grid — the
   label, a column per year, the spread, a column per quarter. One width serves both a
   year and a quarter, so the two halves either side of the split read as one grid as
   well.

   table-layout: fixed is what makes it a promise rather than a preference: under auto
   layout a specified width is only a floor, and one long figure would walk its table
   back out of step with its siblings. Fixed layout takes its widths from the header
   row alone, which is why the spread and the coal pages' grade column carry a class of
   their own — a width has to be hung on something.

   width: max-content, not the 100% above it, and no floor under it either. Wider than the
   pane is what gives the quarters something to scroll; narrower is what the capture wants,
   where the pane is drawn with fewer quarters and stretching them to fill it would print a
   quarter half again as wide as a year. Every width is given a matching max-width to go with
   it: to max-content sizing a width is only a floor, so a long header would otherwise widen
   its own column and take its table back out of step with the others — which is the whole
   thing this block exists to prevent.

   The figures are whole numbers, so a box is sized for five digits — a tonne-mile
   figure in the tens of thousands — and the horizontal padding is tighter than the
   report's other tables to pay for it. Anything that still will not fit is ellipsised
   rather than left to paint across the column beside it.

   Opt-in, per page, rather than assumed for every split table: the Overview is drawn
   to its own widths and is left as it was. Add .dbf-t-fixed to a page's split wrapper
   to bring it in.
   ──────────────────────────────────────────────────────────────────────────── */
.dbf-t-fixed .dbf-t {
    table-layout: fixed;
    width: max-content;
}

    /* border-box, so the widths below are the widths the columns come out at rather than
       the widths plus whatever padding and rule each cell happens to carry. Without it the
       sum overshoots by 8px a column and the quarters lose two of theirs. */
    .dbf-t-fixed .dbf-t th,
    .dbf-t-fixed .dbf-t td {
        box-sizing: border-box;
        padding-left: 3px;
        padding-right: 3px;
        overflow: hidden;
        text-overflow: ellipsis;
    }

    /* The row's name. Well under the 140px a table sets for itself — every pixel the label
       gives up is one the quarters get — and measured against what actually has to fit rather
       than picked round: on the commodity pages, the longest names the tables carry
       ("Russia/Ukraine", "United States") and the longest header over them ("Country /
       Region", which wants 99px in Soehne at this size). Trimming it further starts
       ellipsising the header.

       A page whose rows are named at greater length than that overrides --dbf-label-w
       rather than reaching in here; see .dbf-labels-long below. */
    .dbf-t-fixed .dbf-t th.dbf-t-left {
        width: var(--dbf-label-w, 100px);
        max-width: var(--dbf-label-w, 100px);
    }

    /* A year on the annual side, a quarter on the other, at one width. */
    .dbf-t-fixed .dbf-t th.dbf-t-sub {
        width: 44px;
        max-width: 44px;
    }

    /* The spread, and the grade marker on the coal tables. Both a shade wider than a
       figure needs: "+153%" and "Thermal" are what they have to hold. */
    .dbf-t-fixed .dbf-t th.dbf-t-spread {
        width: 46px;
        max-width: 46px;
    }

    /* The grade marker on the coal tables. "Thermal" measures 38px in Soehne at its 0.62rem,
       which is exactly what a 46px box leaves it — so 48, for the same few pixels of slack
       every other column here has. */
    .dbf-t-fixed .dbf-t th.dbf-t-gradecol {
        width: 48px;
        max-width: 48px;
    }

    /* The unit or metric column, where a table has one: the Overview's "Mt", "#", "MDwt",
       under a "Unit" or "Metric" header. Sized for "Metric", the widest of them. */
    .dbf-t-fixed .dbf-t th.dbf-t-unitcol {
        width: 42px;
        max-width: 42px;
    }

    /* ── Ceilings on the body cells ───────────────────────────────────────────────
       Fixed layout takes a column's width from the header row, but the table is sized to
       max-content and a body cell wider than its column still counts towards that — enough
       to widen the column on one table and leave its neighbour where it was. "Crude Steel
       Production" is the case: 126px of name reaching into a 100px column. So every cell
       whose width is pinned above is given the same ceiling here, and a name that will not
       fit ellipsises instead of moving the grid.

       The figures come first and least specifically, so the named columns below win the tie.
       Cells with a colspan are left out on purpose: a section band's title is meant to run
       the width of the table. */
    .dbf-t-fixed .dbf-t td:not([colspan]) {
        max-width: 46px;
    }

    /* The label's own 140px floor is set on the body cell, and would hold the table open
       past the sum however the column itself is sized. */
    .dbf-t-fixed .dbf-t td.dbf-t-label,
    .dbf-t-fixed .dbf-t td.dbf-t-sublabel,
    .dbf-t-fixed .dbf-t td.dbf-t-segment {
        min-width: 0;
        max-width: var(--dbf-label-w, 100px);
    }

    .dbf-t-fixed .dbf-t td.dbf-t-unit {
        min-width: 0;
        max-width: 42px;
    }

    .dbf-t-fixed .dbf-t td.dbf-t-grade {
        min-width: 0;
        max-width: 48px;
    }

/* The Overview names its rows at length — "Crude Steel Production", "Cement/Aggregate" —
   where the commodity pages name countries. It buys the room out of the quarter pane, which
   is the trade every width on this sheet is: two tables and no charts, so it has the room to
   spend and the shorter quarter window costs it least. */
.dbf-labels-long {
    --dbf-label-w: 132px;
}

/* ─────────────────────────────────────────────────────────────────────────────
   Tables whose rows come in pairs — Fleet Supply, where a segment is a count and a
   tonnage. The plain even/odd stripe would shade the lower half of every segment and
   split it in two, so the stripe runs in fours instead: one whole segment shaded,
   the next plain. The selectors carry .dbf-t as well so they outweigh the stripe
   above, which is otherwise more specific than they are.
   ───────────────────────────────────────────────────────────────────────────── */
.dbf-t.dbf-t-paired tr:nth-child(even):not(.dbf-t-section):not(.dbf-t-total) td {
    background: transparent;
}

.dbf-t.dbf-t-paired tbody tr:nth-child(4n+3):not(.dbf-t-section):not(.dbf-t-total) td,
.dbf-t.dbf-t-paired tbody tr:nth-child(4n+4):not(.dbf-t-section):not(.dbf-t-total) td {
    background: #F4F7F9;
}

/* ─────────────────────────────────────────────────────────────────────────────
   Dense pages.

   Some sheets carry far more than others. Grain is three export tables, three
   waterfalls, the seaborne total, two flow diagrams and the Imports table; Iron Ore
   & Steel is four tables, two waterfalls and two flow diagrams. The printed report
   carries those pages by setting a tighter row than the ones either side of them,
   and so does this.

   Applied by the page, on .dbf-page-inner, rather than assumed for every table —
   pages 3 and 4 have room to breathe and should keep the roomier row the rest of
   the report reads at. Add it to a page when the page needs it, not before.
   ───────────────────────────────────────────────────────────────────────────── */
.dbf-dense .dbf-t-split .dbf-t th,
.dbf-dense .dbf-t-split .dbf-t td {
    height: 17px;
    line-height: 12px;
}

/* On these pages the tables run one under another with only their titles between
   them, so the heading's own spacing is most of what separates them. */
.dbf-dense .dbf-h3 {
    margin: 10px 0 5px;
}

/* Grain's Imports table: three panels side by side, each opening with the region's
   name, so the label column's cost is paid three times over. The 140px a
   single-panel table can afford would take half the sheet before a figure is
   drawn. */
.dbf-t.dbf-t-imports td.dbf-t-label {
    min-width: 0;
}

/* ─────────────────────────────────────────────────────────────────────────────
   Editable grids (DbfDataGrid, DbfCostChart).

   The same idea as the editable copy above: one object with two renders, and the
   editor adds inputs inside the report's own table rather than replacing it with a
   form. So the cells keep .dbf-t's padding and alignment, and the input is
   invisible until it has focus.

   None of this reaches the PDF — the capture renders with edit rights off, so the
   inputs, the toolbar and the remove-row column are never built. These rules live
   here rather than in a .razor.css only so the read-only render is styled from the
   one stylesheet the print service receives.
   ───────────────────────────────────────────────────────────────────────────── */
.dbf-grid-input {
    width: 100%;
    padding: 0;
    border: 0;
    background: transparent;
    font: inherit;
    color: inherit;

    /* The cell decides — label columns read left, figures read right. */
    text-align: inherit;
}

    .dbf-grid-input:focus {
        outline: 1px solid #009DCA;
        outline-offset: -1px;
    }

.dbf-grid-tools {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-top: 4px;
}

.dbf-grid-btn {
    font-size: 0.62rem;
    padding: 1px 8px;
    border: 1px solid #3C5A9F;
    border-radius: 3px;
    background: #fff;
    color: #000061;
    cursor: pointer;
    white-space: nowrap;
    transition: background 0.15s, color 0.15s;
}

    .dbf-grid-btn:hover {
        background: #163E9F;
        color: #fff;
        border-color: #163E9F;
    }

.dbf-grid-hint {
    font-size: 0.6rem;
    color: #798E98;
}

/* Sits in the flow rather than in the corner, because a grid has a toolbar under
   it for the badge to sit beside. */
.dbf-grid-unsaved {
    position: static;
}

/* The remove-row column. Narrow, quiet, and only ever drawn in the editor. */
.dbf-t td.dbf-t-rowdrop,
.dbf-t th.dbf-t-rowdrop {
    width: 18px;
    padding: 2px 0;
    text-align: center;
    color: #9FB3BC;
    cursor: pointer;
}

    .dbf-t td.dbf-t-rowdrop:hover {
        color: #EB3871;
    }

/* Typed grids are read as a block, not scanned against a wide table, so the label
   column does not need the 140px a forecast table's does. */
.dbf-t.dbf-t-grid td.dbf-t-label {
    min-width: 0;
}

/* A grid's buttons go quiet rather than disappear when there is nothing for them to
   do — Apply sits under the paste box whether or not a block has been recognised. */
.dbf-grid-btn:disabled,
.dbf-grid-btn:disabled:hover {
    background: #fff;
    border-color: #D9E3E8;
    color: #9FB3BC;
    cursor: default;
}

/* ─────────────────────────────────────────────────────────────────────────────
   Paste from Excel (DbfGridPasteZone).

   Editor furniture, drawn only inside the grids' editable branch, so like the
   inputs above it none of this is built for the PDF. It is here rather than in a
   .razor.css for the reason the rest of this section is: these are components the
   print service's stylesheet has to cover, and scoped CSS does not reach them.

   Deliberately quieter than the grid it sits under — dashed, tinted, and a size
   down — so a sheet in edit mode still reads as the report rather than as a form.
   ───────────────────────────────────────────────────────────────────────────── */
.dbf-paste {
    display: flex;
    flex-direction: column;
    gap: 4px;
    margin-top: 8px;
    padding: 6px 8px;
    border: 1px dashed #C7D6DD;
    border-radius: 4px;
    background: #FAFCFD;
}

.dbf-paste-label {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 6px;
    font-size: 0.62rem;
    font-weight: 600;
    color: #536A75;
}

.dbf-paste-box {
    box-sizing: border-box;
    width: 100%;
    padding: 3px 5px;
    border: 1px solid #C7D6DD;
    border-radius: 3px;
    background: #fff;
    color: #2A3B44;

    /* Monospaced, so a block that came out of a spreadsheet still looks like columns
       in the box and a row with a cell too many is visible before it is applied. */
    font-family: ui-monospace, Consolas, "Courier New", monospace;
    font-size: 0.62rem;
    resize: vertical;
}

    .dbf-paste-box:focus {
        outline: 1px solid #009DCA;
        outline-offset: -1px;
        border-color: #009DCA;
    }

.dbf-paste-status {
    padding: 2px 5px;
    border-radius: 3px;
    font-size: 0.6rem;
}

/* Recognised, and refused. Both are stated in words as well as in colour — the
   status line always says the two shapes it is comparing. */
.dbf-paste-ok {
    background: #EFF9F3;
    color: #0A7A0A;
}

.dbf-paste-warn {
    background: #FDF0F3;
    color: #CC0000;
}

/* The assumptions grid sits in a 260px column, so its toolbar has to wrap rather
   than push the band wider. */
.dbf-paste .dbf-grid-tools {
    flex-wrap: wrap;
}

/* ─────────────────────────────────────────────────────────────────────────────
   Page 7 — the levelised cost band: the chart, and the assumptions behind it set
   beside it. The table takes a fixed column because its content is four short
   fields; the chart takes the rest.
   ───────────────────────────────────────────────────────────────────────────── */
.dbf-cost-split {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 260px;
    gap: 14px;
    align-items: start;
    margin-bottom: 14px;
}

    .dbf-cost-split > div,
    .dbf-cost-split > .dbf-cost-chart {
        min-width: 0;
    }

/* The toggle sits above the chart, so the chart does not move when the grid opens
   behind it. */
.dbf-cost-tools {
    margin: 0 0 4px;
}

/* Change figures. */
.dbf-pos {
    color: #0A7A0A;
    font-weight: 600;
}

.dbf-neg {
    color: #CC0000;
    font-weight: 600;
}

/* ─────────────────────────────────────────────────────────────────────────────
   The Fleet Demand measure strip.

   Page 8 draws the same tables in metric tonnes or in tonne miles, so the measure
   is a choice the reader makes on the sheet rather than something the report is
   published in. The strip therefore sits on the paper, but it is screen chrome:
   the page does not render it during a capture, and the document carries the
   caption below instead.

   Light on white and cyan when chosen, rather than the control bar's navy fill —
   these buttons are on the report, not above it, and a row of solid navy blocks
   would read as part of the table.
   ───────────────────────────────────────────────────────────────────────────── */
.dbf-measure-tabs {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
    margin: 0 0 10px;
}

.dbf-measure-tab {
    padding: 3px 10px;
    /* A button does not inherit its font family, and the sheet's face is not the browser's. */
    font-family: inherit;
    font-size: 0.7rem;
    font-weight: 600;
    color: #000061;
    background: #F4F7F9;
    border: 1px solid #B5CBD6;
    border-radius: 99px;
    cursor: pointer;
    white-space: nowrap;
    transition: background 0.15s, color 0.15s, border-color 0.15s;
}

    .dbf-measure-tab:hover {
        background: #E4EEF3;
        border-color: #009DCA;
    }

    .dbf-measure-tab.active {
        background: #009DCA;
        border-color: #009DCA;
        color: #fff;
    }

/* What the document gets in place of the strip: the measure it was drawn in, on the
   page heading's own line so it costs the sheet no height. */
.dbf-measure-caption {
    float: right;
    font-size: 0.8rem;
    font-weight: 600;
    color: #163E9F;
    padding-top: 6px;
}

/* ── Trade flow diagrams ──────────────────────────────────────────────────────
   The Sankeys on pages 4, 5 and 6, each with a strip of year tabs above it and the
   figures' source under it. The block's own wrapper, .dbf-flow, carries no rules —
   it is a hook, so that a later change to how the four parts are spaced has one
   place to go rather than having to reach into the page's grid cell.

   A segmented track rather than the pill buttons of the measure strip on page 8.
   That strip switches what the whole sheet is about, and reads as a control; this
   one moves one chart between years, and reads as part of it — closer to a set of
   sheet tabs than to a row of buttons.
   ───────────────────────────────────────────────────────────────────────────── */
.dbf-flow-tabs {
    display: flex;
    gap: 2px;
    margin: 0 0 5px;
    padding: 2px;
    background: #EDF2F5;
    border-radius: 3px;
}

.dbf-flow-tab {
    flex: 1 1 0;
    min-width: 0;
    padding: 2px 0;
    /* A button does not inherit its font family, and the sheet's face is not the browser's. */
    font-family: inherit;
    font-size: 0.66rem;
    font-weight: 600;
    color: #536A75;
    background: transparent;
    border: 1px solid transparent;
    border-radius: 2px;
    cursor: pointer;
    white-space: nowrap;
    transition: background 0.15s, color 0.15s, box-shadow 0.15s;
}

    .dbf-flow-tab:hover {
        color: #000061;
        background: #E4EEF3;
    }

    /* Raised out of the track rather than filled with the accent: at this size a solid
       block of cyan five times over is the loudest thing on the sheet. */
    .dbf-flow-tab.active {
        color: #000061;
        background: #FFFFFF;
        border-color: #D7E3EA;
        box-shadow: 0 1px 1px rgba(0, 0, 97, 0.08);
    }

/* What the document gets in place of the strip, and what a single-year chart gets on
   screen: the year it was drawn for, on the title's own line so it costs no height. */
.dbf-flow-year {
    float: right;
    font-weight: 600;
    color: #163E9F;
}

/* Where the figures came from. Under the chart, quiet, and right-aligned as the
   printed report has it. */
.dbf-flow-source {
    margin-top: 2px;
    font-size: 0.6rem;
    font-style: italic;
    color: #7B8E99;
    text-align: right;
}

/* A footnote about the figures themselves, at the foot of the sheet above the shell's
   footer — not a caption on any one table. */
.dbf-note {
    font-size: 0.65rem;
    color: #536A75;
    margin: 0;
}

/* ═════════════════════════════════════════════════════════════════════════════
   PDF rendering.

   Every page is stacked into one document and each sheet carries .dbf-paper-pdf
   plus portrait-page — CreatePdf.AddHeaderToInnerText looks for that class and
   emits @page { size: 210mm 297mm; margin: 0 }.

   zoom scales each sheet by 794/900 so the px-based layout lands on the paper at
   full bleed. It is set a hair under that ratio (0.8817 rather than 0.8822) and the
   height pinned to 1272px, because at the exact figure Chrome's rounding pushed
   every sheet a fraction over the page and emitted a blank one after each. Verified
   at 3 sheets in, 3 pages out; retune if the PDF service renders at another dpi.
   ═════════════════════════════════════════════════════════════════════════════ */
.dbf-paper-pdf {
    /* Fixed to exactly one sheet. On screen .dbf-paper grows when a page is
       overfull so the author can see it; here it is clipped, because a report page
       spilling onto a second sheet would break the page numbering. */
    zoom: 0.8817;
    min-height: 0;
    height: 1272px;
    overflow: hidden;
    margin: 0;
    box-shadow: none;
    break-after: page;
    page-break-after: always;
}

    .dbf-paper-pdf:last-child {
        break-after: auto;
        page-break-after: auto;
    }

/* Editing affordances are screen furniture — keep them out of the document. */
.dbf-paper-pdf .dbf-edit:hover,
.dbf-paper-pdf .dbf-edit:focus,
.dbf-paper-pdf .dbf-edit:focus-within {
    box-shadow: none;
    background: transparent;
}

.dbf-paper-pdf .dbf-edit-area::after {
    content: none;
}

.dbf-paper-pdf .dbf-text-unsaved {
    display: none;
}

/* The quarters scroll on screen. Paper cannot be scrolled: whatever sits past the edge
   of the pane is not clipped politely, it is simply absent from the report. So the
   document is drawn with fewer quarters rather than the same ones cut off — those from
   the issue's year onwards, which is the window the reader is opened on and narrow
   enough to print whole (DbfTableReader.QuartersDrawn). Leaving the pane visible is
   what makes that a promise: with nothing to scroll and nothing clipped, a quarter that
   reached the sheet edge would show as an overhang rather than vanish quietly. */
.dbf-paper-pdf .dbf-t-split-quarters {
    overflow-x: visible;
}
