diff --git a/src/components/DraftNotice.js b/src/components/DraftNotice.js
new file mode 100644
index 000000000..547a8af56
--- /dev/null
+++ b/src/components/DraftNotice.js
@@ -0,0 +1,113 @@
+import { useLocation } from '@docusaurus/router';
+import latestVersions from '@site/static/versions/latestVersions.json';
+import { draftDocs } from '../draftDocs';
+
+/**
+ * Draft status marking for documents flagged `draft: true` in docConfig.js.
+ *
+ * Two surfaces, one message:
+ * - DraftStatusStrip — a slim strip at the top of the content column that sticks
+ * below the site header, so the status stays visible regardless of scroll
+ * position or deep-link entry point. It is deliberately bounded by the content
+ * column rather than the viewport: a full-bleed bar reads as site-wide chrome,
+ * while a column-width bar reads as status for this document.
+ * - DraftPrintFrame — wraps the document body in a table whose thead/tfoot the
+ * browser repeats on every printed sheet, marking each physical page.
+ *
+ * The strip is hidden in print and the frame is inert on screen (display: contents),
+ * so each surface only renders in the medium it is meant for.
+ */
+
+export const DRAFT_STATUS_TEXT = 'This document is pending final approval by the RMC Director.';
+
+function getDocInfo(pathname) {
+ const stripped = pathname
+ .replace(/^\/RMC-Software-Documentation\/docs\//, '')
+ .replace(/^\/docs\//, '')
+ .replace(/^docs\//, '');
+ const match = stripped.match(/^(.+?)\/(v\d+\.\d+(?:\.\d+)?)(?:\/|$)/);
+ if (!match) return null;
+ return { docBasePath: match[1], version: match[2] };
+}
+
+function isDraftPath(pathname) {
+ const info = getDocInfo(pathname);
+ if (!info) return false;
+ const isFlagged = draftDocs.some((base) => info.docBasePath === base || info.docBasePath.startsWith(base + '/'));
+ if (!isFlagged) return false;
+ const latest = latestVersions[info.docBasePath];
+ if (!latest) return true;
+ return info.version === latest;
+}
+
+/** True when the current route is the latest version of a doc flagged `draft: true`. */
+export function useIsDraftDoc() {
+ const location = useLocation();
+ return isDraftPath(location.pathname);
+}
+
+/**
+ * Slim status strip at the top of the content column.
+ *
+ * Renders nothing unless the current route is a draft doc, so it can be dropped
+ * into the layout unconditionally.
+ *
+ * The outer anchor is the sticky element and carries an opaque page-coloured
+ * band, so the gap between the header and the strip is preserved while scrolling
+ * without document text showing through it.
+ */
+export const DraftStatusStrip = () => {
+ const isDraft = useIsDraftDoc();
+ if (!isDraft) return null;
+
+ return (
+
+
+ {DRAFT_STATUS_TEXT}
+
+
+ );
+};
+
+/**
+ * Repeats the draft band at the top and bottom of every printed page.
+ *
+ * Browsers repeat `thead` and `tfoot` across page breaks, which is the only
+ * cross-browser way to get a running header/footer — `@page` margin boxes are
+ * unsupported in Chrome and Firefox. On screen the whole table collapses via
+ * `display: contents`, so it generates no boxes and leaves layout untouched.
+ */
+export const DraftPrintFrame = ({ children }) => {
+ const isDraft = useIsDraftDoc();
+ if (!isDraft) return <>{children}>;
+
+ return (
+
+
+
+ |
+
+ {DRAFT_STATUS_TEXT}
+
+ |
+
+
+
+
+ | {children} |
+
+
+
+
+ |
+
+ {DRAFT_STATUS_TEXT}
+
+ |
+
+
+
+ );
+};
+
+export default DraftStatusStrip;
diff --git a/src/css/custom.css b/src/css/custom.css
index 7268425f0..fd58fa49d 100644
--- a/src/css/custom.css
+++ b/src/css/custom.css
@@ -5,6 +5,7 @@
@import '../../node_modules/@usace/groundwork/dist/style.css';
@import './schema.css';
@import './print.css';
+@import './draft-notice.css';
@import './responsive.css';
@font-face {
diff --git a/src/css/draft-notice.css b/src/css/draft-notice.css
new file mode 100644
index 000000000..a08374c6f
--- /dev/null
+++ b/src/css/draft-notice.css
@@ -0,0 +1,141 @@
+/* ===== Draft Status Marking ===== */
+/* Screen: a slim strip at the top of the content column that sticks below the
+ site header as the document scrolls.
+ Print: a band repeated at the top and bottom of every printed sheet.
+ See src/components/DraftNotice.js. */
+
+/* ── Screen: document status strip ── */
+/* Bounded by the content column, not the viewport: a full-bleed bar reads as
+ site-wide chrome, a column-width bar reads as status for this document. */
+/* The anchor is the sticky element. It carries an opaque, page-coloured band so
+ the gap above the strip is preserved as the document scrolls without text
+ showing through it — the site header itself has a transparent background. */
+.draft-strip-anchor {
+ position: sticky;
+ top: var(--ifm-navbar-height, 0px);
+ z-index: 5;
+
+ /* Holds the gap open as the document scrolls under it. Uses an explicit token
+ rather than --ifm-background-color, which resolves to #0000 in light mode. */
+ padding-top: 1rem;
+ padding-bottom: 0.35rem;
+ /* Cancels the container's top spacing so the strip sits at its stuck position
+ from first paint — it locks in place instead of shifting up on first scroll. */
+ margin-top: -1rem;
+ margin-bottom: 0.7rem;
+ background-color: var(--draft-strip-band);
+}
+
+.draft-strip {
+ display: flex;
+ align-items: baseline;
+ justify-content: center;
+ flex-wrap: wrap;
+ gap: 0.45rem;
+
+ padding: 0.4rem 0.9rem;
+ border: 1px solid var(--draft-strip-border);
+ border-radius: var(--ifm-global-radius);
+ background-color: var(--draft-strip-bg);
+
+ color: var(--draft-strip-fg);
+ font-size: 0.8125rem;
+ line-height: 1.3;
+ text-align: center;
+}
+
+/* Light mode — lighter red */
+:root {
+ --draft-strip-bg: #fdf3e0;
+ --draft-strip-border: #eed9ab;
+ --draft-strip-fg: #6f4a08;
+ --draft-strip-band: #f7f8fa;
+}
+
+/* Dark mode — darker red */
+html[data-theme='dark'] {
+ --draft-strip-bg: #33280f;
+ --draft-strip-border: #6a5423;
+ --draft-strip-fg: #f2e0be;
+ --draft-strip-band: #1b1b1b;
+}
+
+@media (max-width: 480px) {
+ .draft-strip {
+ font-size: 0.75rem;
+ padding: 0.35rem 0.7rem;
+ }
+}
+
+/* ── Print frame: inert on screen ── */
+/* display: contents makes the wrapper generate no boxes, so the document lays
+ out exactly as it would without the table. */
+.draft-print-frame,
+.draft-print-frame > thead,
+.draft-print-frame > tbody,
+.draft-print-frame > tfoot,
+.draft-print-frame > * > tr,
+.draft-print-frame > * > tr > td {
+ display: contents;
+}
+
+.draft-print-frame__band {
+ display: none;
+}
+
+/* ── Print ── */
+@media print {
+ /* The viewport strip is a screen affordance; the repeating band replaces it. */
+ .draft-strip-anchor {
+ display: none !important;
+ }
+
+ .draft-print-frame {
+ display: table !important;
+ width: 100% !important;
+ border-collapse: collapse !important;
+ }
+
+ /* thead/tfoot repeat across page breaks — this is what marks every sheet. */
+ .draft-print-frame > thead {
+ display: table-header-group !important;
+ }
+
+ .draft-print-frame > tfoot {
+ display: table-footer-group !important;
+ }
+
+ .draft-print-frame > tbody {
+ display: table-row-group !important;
+ }
+
+ .draft-print-frame > * > tr {
+ display: table-row !important;
+ }
+
+ .draft-print-frame > * > tr > td {
+ display: table-cell !important;
+ border: 0 !important;
+ padding: 0 !important;
+ }
+
+ /* Class specificity beats the `* { color: #000 !important }` reset in print.css. */
+ .draft-print-frame__band {
+ display: block !important;
+ margin: 0 0 8pt !important;
+ padding: 3pt 0 !important;
+ border-top: 1pt solid #8a5a00 !important;
+ border-bottom: 1pt solid #8a5a00 !important;
+ color: #8a5a00 !important;
+ font-size: 8.5pt !important;
+ font-weight: 700 !important;
+ letter-spacing: 0.06em;
+ text-align: center;
+ -webkit-print-color-adjust: exact;
+ print-color-adjust: exact;
+ }
+
+ .draft-print-frame__band--foot {
+ margin: 8pt 0 0 !important;
+ }
+}
diff --git a/src/docConfig.js b/src/docConfig.js
index e5d3ace83..b159ee860 100644
--- a/src/docConfig.js
+++ b/src/docConfig.js
@@ -78,6 +78,7 @@ const docs = [
{
category: 'desktop-applications',
software: 'rmc-totalrisk',
+ doc_location: 'desktop-applications/rmc-totalrisk/verification-report',
doc_name: 'RMC TotalRisk Verification Report',
active: true,
draft: true,
diff --git a/src/theme/DocItem/Layout/index.js b/src/theme/DocItem/Layout/index.js
index 37e38c8f6..31a9170b0 100644
--- a/src/theme/DocItem/Layout/index.js
+++ b/src/theme/DocItem/Layout/index.js
@@ -7,6 +7,9 @@
* - Added FloatingNav FAB + bottom-sheet drawer so users can reach
* the same navigation without scrolling to the top when the
* viewport is narrow (< 1080 px).
+ * - Added DraftStatusStrip at the top of the content column and wrapped the
+ * content in DraftPrintFrame, so draft documents carry a sticky status
+ * strip on screen and a repeating DRAFT band on every printed page.
*/
import React from 'react';
import clsx from 'clsx';
@@ -21,6 +24,7 @@ import DocItemContent from '@theme/DocItem/Content';
import DocBreadcrumbs from '@theme/DocBreadcrumbs';
import ContentVisibility from '@theme/ContentVisibility';
import FloatingNav, { InlineNav } from '@site/src/components/FloatingNav';
+import { DraftPrintFrame, DraftStatusStrip } from '@site/src/components/DraftNotice';
import styles from './styles.module.css';
function useDocTOC() {
@@ -41,11 +45,14 @@ export default function DocItemLayout({ children }) {
+
- {children}
+
+ {children}
+
diff --git a/src/theme/DocItem/index.js b/src/theme/DocItem/index.js
deleted file mode 100644
index 162334e68..000000000
--- a/src/theme/DocItem/index.js
+++ /dev/null
@@ -1,42 +0,0 @@
-import React from "react";
-import DocItem from "@theme-original/DocItem";
-import { useLocation } from "@docusaurus/router";
-import { draftDocs } from "../../draftDocs";
-import latestVersions from "@site/static/versions/latestVersions.json";
-
-function getDocInfo(pathname) {
- const stripped = pathname
- .replace(/^\/RMC-Software-Documentation\/docs\//, "")
- .replace(/^\/docs\//, "")
- .replace(/^docs\//, "");
- const match = stripped.match(/^(.+?)\/(v\d+\.\d+(?:\.\d+)?)(?:\/|$)/);
- if (!match) return null;
- return { docBasePath: match[1], version: match[2] };
-}
-
-function isDraftDoc(pathname) {
- const info = getDocInfo(pathname);
- if (!info) return false;
- const isFlagged = draftDocs.some(
- (base) => info.docBasePath === base || info.docBasePath.startsWith(base + "/")
- );
- if (!isFlagged) return false;
- const latest = latestVersions[info.docBasePath];
- if (!latest) return true;
- return info.version === latest;
-}
-
-export default function DocItemWrapper(props) {
- const location = useLocation();
- const showWatermark = isDraftDoc(location.pathname);
- return (
- <>
- {showWatermark && (
-
- DRAFT
-
- )}
-
- >
- );
-}