:root {
	/* colors for offcanvas filter toggles and filter badges */
	--filter-badge-color: white; /* foreground color */
	--filter-toggle-color-checked: white; /* foreground color */
	--filter-color-state: #00008B; /* dark blue */
	--filter-color-keyword: #5D3FD3; /* iris */
	--filter-color-etag: #7393B3; /* blue grey */
	--filter-color-qtag: #40B5AD; /* Verdigris */
	--filter-color-tag: #3F00FF; /* indigo */
	--filter-color-city: #4682B4; /* steel blue */
	--filter-color-cat:  #2E8B57; /* sea green */
	--filter-color-ecat: #A0522D; /* sienna */
	--filter-color-company: #8B008B; /* dark magenta */
	--filter-color-visitor: #40B5AD; /* Verdigris -- matches --filter-color-qtag, posts-search offcanvas */
	--filter-color-activity: #3F00FF; /* indigo -- matches --filter-color-tag, posts-search offcanvas */
	--filter-color-region: #4682B4; /* steel blue -- matches --filter-color-city, posts-search offcanvas */

	/* EDITOR-ONLY MARKER. One colour, one meaning: nothing wearing this is visible to the public.
	   Applied to CONTAINERS, never to an interactive control -- a tinted button would have to fight
	   Bootstrap's own hover and active states for .btn-light, and the company toggles carry a
	   per-criterion colour pairing that must not be broken. Tint the wrapper, leave the control.

	   PINKY-PURPLE RATHER THAN THE GOLDENROD THE EDITOR STRIP USED TO CARRY, and the reason is
	   collision: amber is already a PUBLIC content colour (--vocab-caution-bg, behind caution
	   callouts inside articles), so an amber "Edit this Post" block would sit directly above
	   article content using the same amber to mean something else entirely. This hue appears
	   nowhere on a public surface, so it means exactly one thing site-wide.

	   Ray's reason for wanting it, 2026-08-21, and it is the one to design against: Gwenn asking
	   "why are these extra lines displayed?". The answer should be visible without explanation. */
	--editor-only-bg: #F0E4F7; /* pale orchid */
	--editor-only-rule: #D8B4E2; /* mid orchid */
	--editor-only-text: #6A1B9A; /* deep purple */

	/* post content vocabulary -- see the POST CONTENT VOCABULARY section at the end of this file */
	--vocab-icon: #6C757D; /* slate grey -- every details icon, deliberately uniform */
	--vocab-caution-rule: #C77700; /* dark amber */
	--vocab-caution-bg: #FCEFD8; /* pale amber */
	--vocab-note-rule: #4682B4; /* steel blue -- same value as --filter-color-city */
	--vocab-note-bg: #E7F0F8; /* pale steel blue */
	--vocab-fineprint-rule: #ADB5BD; /* grey */
	--vocab-fineprint-bg: #E4E4E4; /* grey */
	/* These three sit on a plain white body. Tuned live against real posts over two passes: the
	   first attempt (#FDF4E5/#EEF4F9/#F6F6F6) left only the amber visible, and the second still had
	   the fine print reading as untinted. The lesson is that a NEUTRAL grey needs a much larger
	   lightness delta than a hued tint to register at all, because it has no hue doing any of the
	   work -- so equal deltas do not give equal visibility. Loudness order is still amber, blue,
	   grey, but it now comes from hue rather than from lightness. */
}

body {
	padding-top: 70px; /* to allow space for navbar */
}

nav.navbar, footer {
	background-color: white;
}

.editor-strip {
	position: static;
	display: flex;
	align-items: center;
	gap: 1rem;
	overflow-x: auto;
	white-space: nowrap;
	border-bottom: 1px solid var(--editor-only-rule);
	padding: 0.35rem 1rem;
	font-size: 0.8rem;
}

/* THE EDITOR-ONLY MARKER ITSELF. Colours only -- every site that wears it keeps its own geometry,
   because the strip wants a bottom rule, the post block wants padding it already has, and the card
   blocks want to be an inline-block inside a <p>. See the token comment at the top of this file. */
.editor-only {
	background-color: var(--editor-only-bg);
	color: var(--editor-only-text);
}

/* inside a card these sit within <p class="card-text">, where a block element is illegal and the
   parser would close the paragraph early -- so an inline-block span, which also shrink-wraps the
   tint to the text rather than banding the whole card */
span.editor-only {
	display: inline-block;
	padding: 0.15rem 0.4rem;
	border: 1px solid var(--editor-only-rule);
	border-radius: 0.25rem;
}

/* the offcanvas company section: the WRAPPER is tinted and the toggles inside are untouched, so
   the section reads as editor-only without breaking the per-criterion toggle colour pairing */
#companies_btn_wrapper.editor-only {
	padding: 0.5rem;
	border: 1px solid var(--editor-only-rule);
	border-radius: 0.25rem;
}

.editor-strip-label {
	display: inline-flex;
	align-items: center;
	gap: 0.25rem;
	color: var(--editor-only-text);
	font-weight: 600;
	flex-shrink: 0;
}

.editor-strip a.editor-strip-link {
	display: inline-flex;
	align-items: center;
	gap: 0.25rem;
	color: var(--editor-only-text);
	text-decoration: none;
	flex-shrink: 0;
}

.editor-strip a.editor-strip-link:hover {
	text-decoration: underline;
}

@media (min-width: 768px) {
	.editor-strip {
		position: sticky;
		top: 60px; /* actual rendered navbar height, confirmed in devtools -- differs from body's 70px padding-top */
		margin-top: calc(60px - 70px); /* closes the gap between body's 70px padding-top and the 60px sticky offset, so the strip sits at its sticky position from page load instead of visibly sliding into place */
		z-index: 1020; /* below navbar's fixed-top (1030), above page content */
		white-space: normal;
		overflow-x: visible;
		flex-wrap: wrap;
	}
}

.req {
	color: red;
}

form label {
	font-weight: 700;
}

.help-block {
	font-size: 0.8em;
	font-style: italic;
}

span.size-note {
	color: orange;
	font-size: 1em;
}

.card-text a {
	text-decoration: none;
}

div.card-img-overlay span.badge {
	color: #403f3f; /* a shade of dark grey */
}

div.card-img-overlay span.badge-tod {
	background-color: #defade; /* green */
}

div.card-img-overlay span.badge-tom {
	background-color: #FAFAD2; /* yellow */
}

div.card-img-overlay span.badge-nex {
	background-color: #fed5a4; /* orange */
}

div.card-img-overlay span.badge-pickup {
	background-color: #99ff99; /* green yellow */
}

div.card-img-overlay span.badge-kids {
	background-color: #FFC107; /* shool bus yellow */
}

div.card-img-overlay span.badge-lgbt {
	background-color: #dda0dd; /* plum */
}

div.card-img-overlay span.badge-accessible {
	background-color: #96CCEE; /* handicap blue */
}

div.card-img-overlay span.badge-age {
	background-color: #edda74; /* goldenrod */
}

div.card-img-overlay span.badge-rating {
	background-color: #7fffd4; /* aquamarine */
}

div.card-img-overlay span.badge-private {
	background-color: #ff3333; /* red-orange */
	color: white;
}

/* hover for toggle buttons */
.btn-check + .btn.toggle-checkbox:hover {
	color: var(--bs-btn-hover-color); /* Uses Bootstrap's hover text color variable */
	background-color: var(--bs-btn-hover-bg); /* Uses Bootstrap's hover background color variable */
	border-color: var(--bs-btn-hover-border-color); /* Uses Bootstrap's hover border color variable */
}

/* this class controls the "checkbox toggles" used in off-canvas
** set the border-radius to 1rem to make it a "rounded-pill"
** otherwise, this gives a different shape
*/
.toggle-checkbox {
	border-top-left-radius: 1rem;
	border-top-right-radius: 0.3rem;
	border-bottom-left-radius: 1rem;
	border-bottom-right-radius: 0.3rem;
}

/* for logo in navbar - based on BRAND_SLUG */
a.navbar-brand > img.pricoday {
	height: 1.5rem;
}

body.page-faq div.faq-content p {
	padding-left: 1rem !important;
}

/* these classes are for dealing with "favorite items" buttons */
div.card-img-overlay button {
	padding: 3px 5px 0 5px;
	margin: 0.05em 0.05em auto auto; /* positions to top right corner */
}

/* post cards use Bootstrap's stretched-link (on the title) to make the whole card clickable --
   stretched-link's ::after pseudo-element is position:absolute, inset:0, z-index:1, covering the
   entire card, so anything else on the card that needs its own click target (fav button, editor's
   edit-post link) must be explicitly positioned above it. Tour cards don't use stretched-link at
   all (several genuinely different destinations per card -- city/tag/company links, book-now --
   so no whole-card click target), which is why this was never needed there; harmless no-op for
   them since there's no stretched-link to escape */
div.card-img-overlay button,
a.card-interactive-link {
	position: relative;
	z-index: 2;
}

button i.bi-heart {
	display: inline-block;
}

button i.bi-heart-fill {
	color: red;
	display: none;
}

button.fav i.bi-heart {
	display: none;
}

button.fav i.bi-heart-fill {
	display: inline-block;
}

/* FOR FILTER BADGES and OUTLINE BUTTONS IN OFFCANVAS */

/* STATE and KEYWORD */
.filter-badge-state {
	color: var(--filter-badge-color);
	background-color: var(--filter-color-state);
}

.filter-badge-keyword {
	color: var(--filter-badge-color);
	background-color: var(--filter-color-keyword);
}


/* ETAG */

.filter-badge-etag {
	color: var(--filter-badge-color);
	background-color: var(--filter-color-etag);
}

.btn-outline-etag {
  color: var(--filter-color-etag);
  border-color: var(--filter-color-etag);
}

.btn-outline-etag:hover,
.btn-outline-etag:focus,
.btn-outline-etag:active,
.btn-outline-etag.active,
.btn-check:checked+.btn-outline-etag {
  color: var(--filter-toggle-color-checked) !important; /* Text color on hover */
  background-color: var(--filter-color-etag) !important; /* Background color on hover */
  border-color: var(--filter-color-etag) !important;
}

/* QTAG */

.filter-badge-qtag {
	color: var(--filter-badge-color);
	background-color: var(--filter-color-qtag);
}

.btn-outline-qtag {
  color: var(--filter-color-qtag);
  border-color: var(--filter-color-qtag);
}

.btn-outline-qtag:hover,
.btn-outline-qtag:focus,
.btn-outline-qtag:active,
.btn-outline-qtag.active,
.btn-check:checked+.btn-outline-qtag {
  color: var(--filter-toggle-color-checked) !important; /* Text color on hover */
  background-color: var(--filter-color-qtag) !important; /* Background color on hover */
  border-color: var(--filter-color-qtag) !important;
}

/* TAG */

.filter-badge-tag {
	color: var(--filter-badge-color);
	background-color: var(--filter-color-tag);
}

.btn-outline-tag {
  color: var(--filter-color-tag);
  border-color: var(--filter-color-tag);
}

.btn-outline-tag:hover,
.btn-outline-tag:focus,
.btn-outline-tag:active,
.btn-outline-tag.active,
.btn-check:checked+.btn-outline-tag {
  color: var(--filter-toggle-color-checked) !important; /* Text color on hover */
  background-color: var(--filter-color-tag) !important; /* Background color on hover */
  border-color: var(--filter-color-tag) !important;
}

/* CITY */

.filter-badge-city {
	color: var(--filter-badge-color);
	background-color: var(--filter-color-city);
}

.btn-outline-city {
  color: var(--filter-color-city);
  border-color: var(--filter-color-city);
}

.btn-outline-city:hover,
.btn-outline-city:focus,
.btn-outline-city:active,
.btn-outline-city.active,
.btn-check:checked+.btn-outline-city {
  color: var(--filter-toggle-color-checked) !important; /* Text color on hover */
  background-color: var(--filter-color-city) !important; /* Background color on hover */
  border-color: var(--filter-color-city) !important;
}

/* COMPANY */

.filter-badge-company {
	color: var(--filter-badge-color);
	background-color: var(--filter-color-company);
}

.btn-outline-company {
  color: var(--filter-color-company);
  border-color: var(--filter-color-company);
}

.btn-outline-company:hover,
.btn-outline-company:focus,
.btn-outline-company:active,
.btn-outline-company.active,
.btn-check:checked+.btn-outline-company {
  color: var(--filter-toggle-color-checked) !important; /* Text color on hover */
  background-color: var(--filter-color-company) !important; /* Background color on hover */
  border-color: var(--filter-color-company) !important;
}

/* BLOG */

body.page-content div.card h5.card-title a {
	text-decoration: none;
	color: inherit;
}

/* CATEGORIES */

.filter-badge-cat {
    color: var(--filter-badge-color);
    background-color: var(--filter-color-cat);
}
.btn-outline-cat {
    color: var(--filter-color-cat);
    border-color: var(--filter-color-cat);
}
.btn-outline-cat:hover,
.btn-outline-cat:focus,
.btn-outline-cat:active,
.btn-outline-cat.active,
.btn-check:checked+.btn-outline-cat {
    color: var(--filter-toggle-color-checked) !important;
    background-color: var(--filter-color-cat) !important;
    border-color: var(--filter-color-cat) !important;
}

/* VISITOR-TYPE CATEGORIES (posts-search offcanvas) */

.filter-badge-visitor {
    color: var(--filter-badge-color);
    background-color: var(--filter-color-visitor);
}
.btn-outline-visitor {
    color: var(--filter-color-visitor);
    border-color: var(--filter-color-visitor);
}
.btn-outline-visitor:hover,
.btn-outline-visitor:focus,
.btn-outline-visitor:active,
.btn-outline-visitor.active,
.btn-check:checked+.btn-outline-visitor {
    color: var(--filter-toggle-color-checked) !important;
    background-color: var(--filter-color-visitor) !important;
    border-color: var(--filter-color-visitor) !important;
}

/* ACTIVITY CATEGORIES (posts-search offcanvas) */

.filter-badge-activity {
    color: var(--filter-badge-color);
    background-color: var(--filter-color-activity);
}
.btn-outline-activity {
    color: var(--filter-color-activity);
    border-color: var(--filter-color-activity);
}
.btn-outline-activity:hover,
.btn-outline-activity:focus,
.btn-outline-activity:active,
.btn-outline-activity.active,
.btn-check:checked+.btn-outline-activity {
    color: var(--filter-toggle-color-checked) !important;
    background-color: var(--filter-color-activity) !important;
    border-color: var(--filter-color-activity) !important;
}

/* REGION CATEGORIES (posts-search offcanvas) */

.filter-badge-region {
    color: var(--filter-badge-color);
    background-color: var(--filter-color-region);
}
.btn-outline-region {
    color: var(--filter-color-region);
    border-color: var(--filter-color-region);
}
.btn-outline-region:hover,
.btn-outline-region:focus,
.btn-outline-region:active,
.btn-outline-region.active,
.btn-check:checked+.btn-outline-region {
    color: var(--filter-toggle-color-checked) !important;
    background-color: var(--filter-color-region) !important;
    border-color: var(--filter-color-region) !important;
}

/* ECATEGORIES */

.filter-badge-ecat {
    color: var(--filter-badge-color);
    background-color: var(--filter-color-ecat);
}
.btn-outline-ecat {
    color: var(--filter-color-ecat);
    border-color: var(--filter-color-ecat);
}
.btn-outline-ecat:hover,
.btn-outline-ecat:focus,
.btn-outline-ecat:active,
.btn-outline-ecat.active,
.btn-check:checked+.btn-outline-ecat {
    color: var(--filter-toggle-color-checked) !important;
    background-color: var(--filter-color-ecat) !important;
    border-color: var(--filter-color-ecat) !important;
}


/* CONTENT-MAINT POST LISTING (2026-08-20)

   Replaced five status accordions and their client-side title filter. One row per post: title and
   slug share the line and truncate together, everything else is pinned right at a fixed width so
   the badge/date/icon column lines up down the page and stays readable at 1000+ rows. */

/* The listing is capped rather than filling the column. On a wide monitor the row was ~2000px
   across, which put the title at one end and the badges/date/icons at the other and made a row
   genuinely hard to track. Cap first, hover second -- the hover is the fix for the rows that are
   still wide, not a substitute for not being that wide */
body.page-content-maint .post-list {
	max-width: 88rem;
}

body.page-content-maint .post-row {
	display: flex;
	align-items: baseline;
	gap: 0.5rem;
	padding: 0.2rem 0;
	border-bottom: 1px solid #eee;
}

body.page-content-maint .post-row:hover {
	background-color: #ced4da; /* bootstrap gray-400. Two steps darker than the first attempt at #e9ecef, which did not read across a wide row. Dark text stays about 9:1 against this */
}

body.page-content-maint .post-row-main {
	flex: 1 1 auto;
	min-width: 0; /* without this a flex item refuses to shrink below its content and never ellipsises */
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

body.page-content-maint .post-row-slug {
	color: #6c757d; /* text-body-secondary */
	font-size: 0.8rem;
}

body.page-content-maint .post-row-meta {
	flex: 0 0 auto;
	white-space: nowrap;
}

body.page-content-maint .post-row-date {
	color: #6c757d;
	font-size: 0.8rem;
	display: inline-block;
	min-width: 8.5rem;
	text-align: right;
}

body.page-content-maint .post-row-meta .badge {
	font-weight: normal;
	font-size: 0.7rem;
}

/* status colours: draft and pending are in-progress, published is done, private and scheduled are
   set aside. Deliberately muted -- this is a list to scan, not a dashboard */
body.page-content-maint .post-status-draft     { color: #664d03; background-color: #fff3cd; }
body.page-content-maint .post-status-pending   { color: #664d03; background-color: #ffe69c; }
body.page-content-maint .post-status-scheduled { color: #084298; background-color: #cfe2ff; }
body.page-content-maint .post-status-published { color: #0f5132; background-color: #d1e7dd; }
body.page-content-maint .post-status-private   { color: #41464b; background-color: #e2e3e5; }
body.page-content-maint .post-status-unknown   { color: #842029; background-color: #f8d7da; }

/* type badges carry their own colours for the same reason the status ones do: on the recently-
   modified strip and in search results every row can be a different type, and a colour is quicker
   to scan than a word. Each is deliberately far from the others AND from the status palette
   sitting beside it on the same row, which is a crowded constraint -- the statuses already spend
   amber, blue, green, grey and red, so the types get purple, orange, teal, pink and cyan. 'error'
   is the http-error row (post_type h); 'other' is any type with no singular label yet.

   ADD A COLOUR HERE WHENEVER post_type_labels_singular() GAINS AN ENTRY. A type with a label but no
   rule renders a badge with no background at all, which reads as a missing badge rather than as a
   missing style -- 'notice' was added 2026-08-27 and would have shipped exactly that way. */
body.page-content-maint .post-type-post   { color: #432874; background-color: #e0cffc; }
body.page-content-maint .post-type-page   { color: #8a4b00; background-color: #ffe5d0; }
body.page-content-maint .post-type-notice { color: #0a4f4a; background-color: #cdeceb; }
body.page-content-maint .post-type-error  { color: #6f1d51; background-color: #f7d6e6; }
body.page-content-maint .post-type-other  { color: #055160; background-color: #cff4fc; }
body.page-content-maint .post-source-badge { color: #6c757d; background-color: transparent; border: 1px solid #dee2e6; }

/* SHORTCODE REFERENCE PANE (2026-08-21). An offcanvas listing every registered shortcode with a
   copyable template. The copy button is full width with left-aligned text because the thing being
   read is the template, not the button -- a centred [book-now co="" tour=""] next to a centred
   [option name=""] gives two ragged edges and nothing to scan down. */
body.page-content-maint .shortcode-ref-item {
	margin-bottom: 0.75rem;
}

body.page-content-maint .shortcode-ref-copy {
	display: block;
	width: 100%;
	text-align: left;
}

/* the template must stay legible against the button's hover and focus states, where Bootstrap
   inverts the foreground -- inherit rather than letting <code> keep its own pink */
body.page-content-maint .shortcode-ref-copy code {
	color: inherit;
}

body.page-content-maint .shortcode-ref-note {
	font-size: 0.8rem;
	color: #6c757d;
	padding: 0.25rem 0 0 0.5rem;
}

/* NOTICE REFERENCE PANE (2026-08-28). The type 'n' counterpart to the shortcode pane, shown in its
   place rather than beside it. Wider than that one because what it holds is different: a shortcode
   template is a dozen characters, while a phrase is a full sentence carrying <em> and <a> markup,
   and at the 400px default every one of them wrapped three or four times. --bs-offcanvas-width is
   Bootstrap 5.3's own variable for this, so there is no specificity fight and no !important. */
body.page-content-maint .notice-ref-pane {
	--bs-offcanvas-width: min(680px, 90vw);
}

body.page-content-maint .notice-ref-section {
	margin-top: 1.5rem;
	padding-bottom: 0.25rem;
	border-bottom: 1px solid #dee2e6;
}

/* the first heading in the body has the offcanvas header directly above it already */
body.page-content-maint .notice-ref-section:first-child {
	margin-top: 0;
}

/* the skeleton is multi-line, so the <code> keeps its newlines. A button collapses whitespace by
   default and the whole thing would arrive as one unreadable line */
body.page-content-maint .notice-ref-skeleton {
	display: block;
	width: 100%;
	text-align: left;
}

body.page-content-maint .notice-ref-skeleton code {
	color: inherit;
	white-space: pre;
	display: block;
	margin-top: 0.25rem;
}

/* CLOSED BY DEFAULT. The skeleton is sixteen lines and these notes another ten, which together put
   the colours and the phrases below the fold every time the pane is opened -- and phrases is the
   section wanted most often, since every storm update is new wording. They stay next to the
   skeleton they annotate, one click away, rather than moving somewhere else in the pane. */
body.page-content-maint .notice-ref-keywords > summary {
	font-size: 0.8rem;
	color: #6c757d;
	cursor: pointer;
	margin-top: 0.5rem;
}

body.page-content-maint .notice-ref-note {
	font-size: 0.8rem;
	color: #6c757d;
	padding: 0.2rem 0 0 0.5rem;
}

body.page-content-maint .notice-ref-color-row,
body.page-content-maint .notice-ref-emoji-row {
	display: flex;
	flex-wrap: wrap;
	gap: 0.25rem;
}

/* the swatch carries its own inline background and foreground -- the pair the name resolves to --
   so the only job here is a border, since a pale subtle background on a white pane has no edge */
body.page-content-maint .notice-ref-color {
	border: 1px solid #dee2e6;
}

body.page-content-maint .notice-ref-emoji {
	border: 1px solid #dee2e6;
	font-size: 1.1rem;
	line-height: 1.1;
}

/* full width and left-aligned for the same reason the shortcode templates are: the thing being
   read is the sentence, and centred sentences of different lengths give nothing to scan down */
body.page-content-maint .notice-ref-phrase {
	display: block;
	width: 100%;
	text-align: left;
	font-size: 0.85rem;
	margin-bottom: 0.25rem;
	white-space: normal; /* a phrase is a sentence and must wrap, unlike the skeleton */
}

/* one blank line in the phrase-bank row, which is the corpus's only grouping */
body.page-content-maint .notice-ref-gap {
	height: 0.75rem;
}

body.page-content-maint .post-status-pills {
	display: flex;
	flex-wrap: wrap;
	gap: 0.25rem;
}


/* ARTICLE READING COLUMN AND POST CONTENT (2026-08-12) */

/* Replaces the col-12 col-md-8 col-lg-6 mx-auto px-2 that post-default.php used to carry.
   Percentage columns are the wrong tool for a text column: tying the measure to window width
   made it GROW without limit on big monitors (about 117 characters per line at 1920px) and,
   worse, SHRINK by ~160px the moment the window passed 992px, where the fraction dropped from
   66.7% to 50% while the window barely changed.

   800px was chosen empirically, not from the typographic rule of thumb. Measured on a real post
   it gives ~106 characters per line, against the ~115 Ray was reading comfortably at the time,
   so it sits inside demonstrated comfort rather than inside a citation. Classic typography wants
   65-75 characters (nearer 600px), but that guidance is softer than it is usually stated -- the
   research finds longer lines are read slightly FASTER and merely liked less -- and it assumes
   continuous prose. These articles are scanned as much as read: someone is hunting for the hours
   or the cost, and return-sweep errors barely matter to an eye that is jumping around anyway.

   It also leaves ~62 characters beside a floated 300px photo, which matters because 74% of
   pricoday's posts carry one.

   The known cost: a 1000px image displays at 768 here, smaller than the 936 it got on a 1920
   screen under the old percentage column. Going to 1000px would show those at native size but
   pushes prose to ~133 characters. No single number gives both. If the images turn out to matter
   more than the line length, the fix is narrow text with images allowed to exceed it, NOT a
   wider column -- and that depends on whether inline images sit inside <p> tags, which is
   unverified.

   Tune the one number if it reads wrong; nothing else references it.

   Also used by page-default.php, hence "content" rather than "post" -- Ray's naming, since
   content is what comes out of the DB.

   .content-body below was called .post-content until 2026-08-13 and was renamed for the same
   reason, once page-default.php made it clear that pages want every rule scoped to it: the image
   sizing, the alignright/alignleft floats, the heading clears. All of that is about DB markup, not
   about posts. The pair reads as a family now -- the column owns the width, the body owns what is
   inside it. */
.content-column {
	max-width: 800px;
	margin-left: auto;
	margin-right: auto;
	padding-left: 1rem;
	padding-right: 1rem;
}

/* The article/page title, emitted by all four templates INSIDE .content-column and OUTSIDE
   .content-body -- which is why the heading scale further down does not reach it, and why this
   rule is scoped to the column instead.

   Added 2026-08-14, immediately after the heading scale shipped. Ray: "too screamin' big now."
   That is the scale doing its job and exposing the odd one out: the h1 had always been Bootstrap's
   2.5rem, but it sat above an h2 that was also 2rem, so the step was invisible. Dropping content
   headings to 1.5rem left the title towering over everything by 1rem.

   2rem gives a 1.33 ratio against the 1.5rem h2 -- a clear step without shouting. Deliberately a
   fixed value rather than a clamp() or vw expression: the column is capped at 800px and does not
   grow, so scaling the title with the viewport would size it against a width the text never uses.
   Nothing else in this stylesheet uses viewport units either.

   One known asymmetry: Bootstrap's default h1 is responsive below 1200px (calc(1.375rem + 1.5vw)),
   so on a ~375px phone it was rendering near 1.72rem. A flat 2rem is therefore slightly LARGER on
   a phone than before, while being much smaller on the desktop where the complaint came from. If
   that reads wrong on mobile the fix is a clamp() here, not a change to the desktop value. */
.content-column h1 {
	font-size: 2rem;
}

/* ---------- THE HURRICANE FEED MARKER ----------

   The marker NhcFeedUtility puts beside a system whose watches and warnings name Puerto Rico.
   A CLASS RATHER THAN THE INLINE style="color:red;font-weight:bold" the WordPress original used,
   so the one visual decision on that block lives with every other visual decision.

   IT WRAPS bi-exclamation-circle-fill, replacing an &lowast; that read as a stray bullet at text
   size (2026-08-23). The icon is not a free choice: AlertManager gives bi-exclamation-circle to
   alert-danger and to nothing else, so that glyph is already this site's word for "this is the bad
   one". The fill variant is the concession to size -- ours sits inline beside a storm name where
   the outline goes thin.

   NO font-weight. It had one while the marker was a text character; Bootstrap Icons is a webfont
   with one weight, so asking for bold now only invites a renderer to synthesise a smeared one.

   It is NOT the only signal that a storm matters -- the whole alert box turns alert-danger when
   any system threatens, and a legend below the list explains the mark. That redundancy is
   deliberate: colour alone would carry the entire meaning for a reader who cannot distinguish it,
   on the one block on this site where the stakes are physical. Screen readers get a third route,
   the visually-hidden text inside the marker itself -- see NhcFeedUtility::buildStormItem(). */
.nhc-threat {
	color: var(--bs-danger);
}

/* NOAA'S OWN WORDS, quoted under a storm that names Puerto Rico -- added 2026-08-27, see
   NhcFeedUtility::assessPuertoRico(). currentColor rather than a fixed hue so the rule takes the
   alert's own text colour: this block renders inside alert-warning and alert-danger both, and a
   hardcoded border would clash with one of them. */
.nhc-quote {
	border-left: 3px solid currentColor;
	padding-left: 0.75rem;
	margin: 0.5rem 0;
	font-size: 0.95em;
}

/* The five-day forecast cone. NHC serves these around 900px wide, so max-width is doing real work
   on a phone; height:auto keeps the aspect ratio, since the markup deliberately states no
   dimensions (cone sizes vary by storm). block+margin stops it sitting on the headline's baseline. */
.nhc-cone {
	display: block;
	max-width: 100%;
	height: auto;
	margin: 0.5rem 0;
}

/* ---------- THE AD DECK BESIDE A POST ----------

   post-default.php emits .content-with-ads ONLY when the brand actually has ads. A brand without
   them renders the bare .content-column exactly as it did before this existed, so nothing here
   can push an article off-centre on a site with an empty gutter.

   A GRID RATHER THAN A PROPORTIONAL SPLIT, and that is the whole point of the layout. WordPress
   put its ad deck in a Bootstrap column pair, so the article shrank as the sidebar took its share
   -- the live defect fixed on 2026-08-12 when .content-column replaced the grid. Fixed tracks give
   the sidebar its 268px and leave the reading measure at exactly 800px, or drop the sidebar to its
   own row when there is not room for both. The article width never moves.

   Below the breakpoint the ads are a rotating carousel (assets/ad-column.js mounts splide there
   and destroys it above), so they occupy about one ad of height rather than a grid of them. */
.content-with-ads {
	display: grid;
	grid-template-columns: minmax(0, 1fr);
	justify-content: center;
	gap: 1.5rem;
}

/* 1200px is not arbitrary: 800 article + 268 ads + the gap needs ~1140, so this is the nearest
   Bootstrap breakpoint that clears it with room to spare. A 1024px tablet in landscape therefore
   gets the carousel, not a cramped two-column squeeze. */
@media (min-width: 1200px) {

	.content-with-ads {
		/* FIXED TRACKS, NOT minmax(0, 800px). A minmax track is content-sized up to its maximum, so
		   the article column collapsed to the natural width of its text and the pair sat left of
		   centre with the reading measure well under 800px -- the exact defect this layout exists to
		   avoid. Caught in the preview, not by reading the rule. Overflow is not a risk: 800 + 268 +
		   the gap is 1092, and this only applies from 1200 up. */
		grid-template-columns: 800px 268px;
		align-items: start; /* without this the ad column stretches to article height and sticky never engages */
	}

	/* Splide is destroyed at this width, but its stylesheet still declares .splide__list as flex,
	   so the slides would sit in a row inside a 268px column. This is what turns them back into a
	   stack. Two class selectors to beat splide's own single-class rule. */
	.ad-block .splide__list {
		display: block;
	}

	/* THE SEA-GREEN THEME PUTS 3em OF PADDING ON .splide, which is arrow clearance and is exactly
	   right on a full-width rail. In a 268px column it is 96px of the 268, and it silently rendered
	   every 268x125 ad at 172px wide -- the downscaling this layout exists to avoid, reintroduced by
	   a stylesheet nobody was looking at. Measured, not guessed. Only zeroed at this width, where
	   the carousel is destroyed and there are no arrows to clear; the narrow rail still needs it. */
	.ad-block .splide {
		padding: 0;

		/* SPLIDE HIDES .splide UNTIL IT MOUNTS. Its stylesheet declares visibility: hidden and
		   reveals the element only when the library adds is-initialized, which is its guard against
		   a flash of unstyled slides. At this width we deliberately never mount, so that class
		   never arrives and the ads stayed invisible -- heading and CTA present, because both sit
		   OUTSIDE .splide, and a column of nothing between them. Shrinking the window mounted the
		   carousel and revealed it, and widening again left the class behind, so the fault
		   disappeared as soon as anyone resized to investigate it.

		   Worth knowing for the next time: a visibility: hidden element still reports its full
		   box, so every measurement of width, height and position came back correct while nothing
		   was on screen. Geometry cannot answer "is it visible". */
		visibility: visible;
	}

	.ad-block .splide__slide {
		margin-bottom: 1rem;
	}

	/* STICKY ON ITS BOTTOM EDGE, NOT ITS TOP, AND THE DIFFERENCE IS THE WHOLE BEHAVIOUR.
	   Measured with the real inventory: 12 ads make a 1719px deck, against a 950px window and a
	   3005px article. The deck is TALLER THAN THE SCREEN, and a sticky element taller than the
	   screen can only ever show one slice of itself while pinned.

	   With top, it pinned almost immediately and froze on ads 1-6. Ads 7-12 were then unreachable
	   until the reader hit the end of the article, so half the inventory was invisible to anyone
	   who did not finish. It also produced the scroll-stop-scroll motion Ray reported: move, freeze
	   for most of the article, then move again as the grid cell ran out.

	   THE FIX IS A COMPUTED top OFFSET, NOT bottom. `bottom: 1rem` was shipped here first, on the
	   belief that it would let the deck scroll past and then hold. It does not: sticky offsets are
	   not symmetric. `top` holds a box rising out of the top of the screen, which is what happens
	   when a reader scrolls DOWN; `bottom` holds a box sinking out of the bottom, which only
	   happens scrolling UP. Reading an article top to bottom, `bottom` did nothing whatsoever and
	   the column just scrolled away.

	   assets/ad-column.js therefore sets top to (viewport - deck height) whenever the deck is taller than
	   the screen, so the deck pins only once its TAIL reaches the bottom: every ad passes the window
	   first, then the last few and the button hold for the rest of the read. It runs out of ads and
	   stops, which reads as an ending rather than an interruption.

	   The 1rem below is the no-JS fallback AND the correct value whenever the deck fits the screen;
	   the script keeps it in that case rather than special-casing it.

	   Ray's point, and it is the part that makes the shuffle load-bearing rather than merely fair:
	   the two positions are DIFFERENT KINDS of good. Landing near the top buys an immediate view;
	   landing near the bottom buys a long hold on screen. Shuffling per request hands every
	   advertiser both over time, so neither slot needs to be sold as the better one.

	   NO INNER SCROLLBAR. This once carried max-height: calc(100vh - 2rem) with overflow-y: auto to
	   guard the too-tall case. The cure was worse: a column of ads scrolling independently of the
	   article. Removed 2026-08-21.

	   If the deck ever grows past what this handles well, the lever is FEWER ADS IN THE COLUMN with
	   the shuffle choosing which -- not a scrollbar, and not a different sticky edge. */
	.ad-block {
		position: sticky;
		top: 1rem;
	}
}

/* Centred in its slide rather than left-aligned. No effect in the 268px column, where the slide is
   exactly the width of the ad; it is the narrow rail that would otherwise hang every ad off the
   left edge of a wider slide. */
.ad-block .splide__slide img {
	display: block;
	margin-inline: auto;
}

/* WHERE THE ADS LAND WHEN THE GRID COLLAPSES.

   Source order puts them after the article, which is where WordPress had them and is the safe
   default: it is the placement least likely to read as advertising competing with the piece
   somebody came to read.

   TO TRY THEM ABOVE THE ARTICLE INSTEAD, uncomment this block. That is the entire change -- one
   property, no PHP, no template edit, and no risk of the two brand copies of post-default.php
   drifting apart. Worth knowing before you look: above the article puts an ad rail directly under
   the navbar and above the headline on a phone.

@media (max-width: 1199.98px) {
	.ad-block {
		order: -1;
	}
}
*/

/* PostViewHandler::displayThePost() emits this wrapper. Was border-top mt-3 pt-3 on a Bootstrap
   row until 2026-08-12; the border went (a full-width rule above an 800px column reads as
   detached) and the breathing room stayed */
.post {
	margin-top: 1.5rem;
}

/* This one rule is what makes image alignment size-dependent without any content edits, which
   is otherwise impossible in CSS: a 300px photo floats at its natural size with text beside it,
   while a 1000px photo is clamped to the column, leaving the float no room, so text falls below
   it. Old posts keep their magazine layout and recent ones go full width, decided per image.
   It also makes the img-responsive class in 451 imported posts unnecessary rather than needing
   a definition -- this covers all 1068, including the ones that never got the class */
/* display: flow-root (2026-08-14) -- offered three times and declined twice on the grounds that a
   full layout review happens at cutover; taken on the third with Ray's "let's get it right and then
   fix things I did as early workarounds".

   It makes .content-body establish a block formatting context, which means the element CONTAINS its
   floats. Without it, a post whose last image is an alignright/alignleft float and which has no
   heading after it lets that image hang out the bottom of .content-body, overlapping whatever the
   template puts below. The old fix for that is a clearfix pseudo-element or a trailing <div
   style="clear:both">; flow-root is the same thing as a real declaration, one line, no extra markup.

   It is not a replacement for the clear: both on headings further down. That handles floats running
   INTO a following section; this handles floats running OUT of the container entirely.

   One behavioural consequence worth knowing, since it is the usual surprise: a BFC also stops
   margins collapsing THROUGH the container, so the first child's top margin no longer escapes. That
   is exactly why .content-body > :first-child gets margin-top: 0 below. */
.content-body {
	display: flow-root;
}

.content-body img {
	max-width: 100%;
	height: auto;

	/* Imported markup almost never wraps an image in anything, so without this the following <p>
	   butts straight up against the photo. Ray, 2026-08-18.

	   Safe as a blanket rule because of what comes after it. .centered sets only margin-left/right,
	   so it inherits this; alignright/alignleft set all four sides in one shorthand and therefore
	   override it with their own 1rem bottom, which is the same value. An inline image inside a
	   sentence is unaffected either way, since bottom margin on an inline box does not move the
	   line.

	   THE CASE THIS LIST MISSED, found 2026-08-22: an image that is a TABLE'S OWN CHROME rather than
	   content. A replaced inline element's vertical margins DO count toward its line box, so the
	   OpenWeather logo in the weather widget's credit row pushed the timestamp under it down by
	   16px. table#weather-widget img resets it. Anything else that generates a table into content
	   will want the same, and that reset belongs with that table's own rules rather than as another
	   exception here. */
	margin-bottom: 1rem;
}

.content-body img.centered {
	display: block;
	margin-left: auto;
	margin-right: auto;
}

/* alignright/alignleft are WordPress editor classes, present in 795 and 406 imported posts.
   Float only above md -- below it there is not enough width for text to sit beside a photo.

   THE position/z-index PAIR IS THE SAME FIX .sc-action-button CARRIES, and for the same reason:
   a positioned block paints above a float, so an adjacent p.gps/p.map/p.directions -- all
   position: relative, because each is the containing block for its own ::before icon -- lands
   invisibly on top of the image. For a plain photo that costs nothing, but a floated image wrapped
   in a link becomes unclickable while looking perfectly normal, which is how this was found on the
   shortcode side.

   Scoped inside the media query on purpose: below 768px nothing floats, so nothing can overlap,
   and a z-index that only exists when it has work to do is one less thing to reason about later.

   Verified with elementFromPoint() at the image's centre: P.map before, the image after. */
@media (min-width: 768px) {
	.content-body img.alignright {
		float: right;
		margin: 0 0 1rem 1rem;
		position: relative;
		z-index: 1;
	}
	.content-body img.alignleft {
		float: left;
		margin: 0 1rem 1rem 0;
		position: relative;
		z-index: 1;
	}
}

/* HEADING SCALE FOR DB CONTENT (2026-08-14)

   Until now nothing here set a heading size, so everything in .content-body sat on Bootstrap's
   defaults -- h2 2rem, h3 1.75rem, h4 1.5rem, h5 1.25rem -- which nobody chose. Ray's own account
   is that he picked <h4> and <h5> in privacy.php and <h3> in the WP posts "for the way they are
   rendered, nothing else". So a scale was already being expressed; it was recorded by choosing tag
   names rather than by writing it down, which is what made the markup structurally wrong (a page
   title at h1 followed immediately by h4, and an FAQ whose every question was an h5).

   Defining the scale here inverts that: the author writes the level the document actually has, and
   the CSS decides how big it looks. 1.5rem and 1.25rem are not new values -- they are exactly what
   h4 and h5 were already giving him, so the pages he approved on 2026-08-14 render identically
   while becoming correct markup.

   THIS ALSO MOVES THE 572 PUBLISHED PRICODAY POSTS, deliberately and with Ray's agreement. They
   lean on <h3> (the "Details" block is under one in ~99% of them), which drops 1.75rem -> 1.25rem.
   That is the point rather than a side effect: 1.75rem inside an 800px column is nearly the size of
   the page title, and it was an inherited default, not a decision. Same job the vocabulary CSS did.

   h5 and h6 are pinned to 1rem for a mechanical reason, not an aesthetic one: without them h5 would
   stay at Bootstrap's 1.25rem and render LARGER than the h4 above it. Content should not need them
   in a column this narrow, but imported markup may carry them. */
.content-body h2 {
	font-size: 1.5rem;
}

.content-body h3 {
	font-size: 1.25rem;
}

.content-body h4 {
	font-size: 1.1rem;
}

.content-body h5,
.content-body h6 {
	font-size: 1rem;
}

/* clear: both -- without this a floated photo near the end of a section overlaps the next heading.
   Carried over from the WP theme, which had it on h3 only ("add some space for old IMG classes"),
   then h2-h4 here, now the full range. It stopped at h4 while content was still using h5 headings,
   which is a float trap for anyone writing the next page by copying an existing one.

   margin-top -- Bootstrap's Reboot sets margin-top: 0 on every heading, so a section heading sat
   only the previous paragraph's 1rem below it, with nothing to signal a new section. */
.content-body h2,
.content-body h3,
.content-body h4,
.content-body h5,
.content-body h6 {
	clear: both;
	margin-top: 1.5rem;
}

/* THE OPT-OUT, added 2026-08-22, and the reason is that the rule above is BROADER THAN THE ONE IT
   WAS PORTED FROM. WordPress scoped it `body.page-single div.post h3` -- articles only, h3 only.
   Ours applies to h2-h6 on every post AND page, so it fires in places WP never did.

   It surfaced on the weather page. That page floats a ~250px, ~590px-tall generated table right, and
   expects the article to run beside it for several sections. Every heading after the table cleared
   it instead, dropping to the bottom of the float and leaving a column of white space beside it.
   Nothing was wrong with the page; the rule simply does not suit a float that tall.

   PER-HEADING RATHER THAN PER-PAGE, deliberately. The blunter fix is to reproduce WP's scoping and
   let pages never clear, which needs the two templates to diverge and takes the choice away from
   whoever is writing the content. On a page like this only the first heading or two want to sit
   beside the float; a heading further down, past the end of it, still wants the protection. So this
   is a class the author applies where they mean it, alongside centered/alignright/alignleft in the
   same content vocabulary.

   It does NOT need to fight specificity: same two-class weight as the rule above and it sits after
   it, so source order decides. */
.content-body h2.no-clear,
.content-body h3.no-clear,
.content-body h4.no-clear,
.content-body h5.no-clear,
.content-body h6.no-clear {
	clear: none;
}

/* the margin-top above must not apply to the first thing in the body, or every page opens with a
   gap. Needed specifically BECAUSE of the flow-root below: a block formatting context stops the
   first child's top margin collapsing out through the container, so without this the two changes
   would fight and the gap would be visible rather than theoretical */
.content-body > :first-child {
	margin-top: 0;
}

/* POST CONTENT VOCABULARY: THE DETAILS BLOCK AND CALLOUTS (2026-08-13) */

/* These 19 classes are semantic markup applied consistently across ~780 pricoday articles since
   2007 -- structured data hiding in prose, and expensive to recreate. Until this section existed
   they had NO definitions at all, so they rendered as plain paragraphs. The vocabulary carries
   forward; the 2007 implementation (BS3 wells, glyphicons) does not.

   Two families, and the split is structural rather than semantic, which is why it is reliable:
   the DETAILS classes are a contiguous stack at the foot of the article, under an <h3>Details</h3>
   in ~99% of posts. The CALLOUTS interrupt prose. That came out of Ray's own WP authoring
   boilerplate (rjb_default_post_content() in prdt-025/functions.php), which lists them in exactly
   those two groups.

   'important' is a DETAIL, not a caution, despite the name -- Ray's gloss is "other things that are
   important to note" and it sits mid-stack between hours and duration. Filing it as a caution would
   box 231 published posts.

   THE ICONS ARE NOT DECORATION. <p class="cost">$10 per adult</p> carries no label -- the class is
   the label, and the class is invisible. Without the icon the details block is an unmarked stack of
   short paragraphs and a reader cannot tell the cost from the phone number.

   The whole icon system is pure CSS because the <span class="glyphicon"> was never in the database.
   WP injected it at render by walking the DOM with simple_html_dom. Stored content is just
   <p class="warning">text</p>, so no PHP pass, no DOM parse, and no simple_html_dom port is needed.

   Every rule is scoped to .content-body so the vocabulary cannot leak into the rest of the site --
   'warning', 'important', 'information' and 'map' are generic enough to collide eventually.

   Codepoints are Bootstrap Icons 1.13.1, loaded site-wide in header-footer.php. They must be
   codepoints rather than bi-* class names because the markup comes out of the database and cannot
   be given classes. If the icon font ever changes, this table is what needs re-deriving.

   Known limit: a <p> carrying two vocabulary classes gets ONE ::before, whichever content rule sits
   later in this file, chosen by accident rather than by intent. WP gave such a paragraph no icon at
   all, so this is not a regression. Unmeasured, and believed rare.

   Accessibility note: an icon-font ::before can be read out as a private-use character by some
   screen readers. Accepted here because the paragraph text carries the meaning and the icon is a
   scanning aid, not information. Inline SVG would avoid it at the cost of 19 data URIs. */

/* --- the details block --- */

/* The hanging indent is what makes this a list you can scan rather than a wall of short paragraphs:
   the icons line up in a gutter and the eye runs straight down them looking for the clock.

   position: relative is the load-bearing declaration and the non-obvious one. It makes the
   paragraph the containing block for its own ::before, so the icon is positioned against the
   paragraph instead of against the viewport. It is also what lets a two-line 'directions' wrap
   flush at the gutter instead of running back underneath its own icon. An inline ::before cannot
   do that at any padding value. */
.content-body p.cost,
.content-body p.hours,
.content-body p.duration,
.content-body p.travel,
.content-body p.phone,
.content-body p.website,
.content-body p.information,
.content-body p.important,
.content-body p.directions,
.content-body p.gps,
.content-body p.map,
.content-body p.photo {
	position: relative;
	padding-left: 2rem;
	margin-bottom: 0.4rem; /* tighter than a prose paragraph -- these stack, and should read as one block */
}

/* Uniform slate grey, deliberately. The WP theme coloured each icon separately (green cost, blue
   hours, red important) and a dozen colours in one stack is noise -- the icon SHAPE already carries
   which class it is, so colour has no work left to do here. Colour is reserved for the callouts,
   where it means severity. */
.content-body p.cost::before,
.content-body p.hours::before,
.content-body p.duration::before,
.content-body p.travel::before,
.content-body p.phone::before,
.content-body p.website::before,
.content-body p.information::before,
.content-body p.important::before,
.content-body p.directions::before,
.content-body p.gps::before,
.content-body p.map::before,
.content-body p.photo::before {
	position: absolute;
	left: 0;
	top: 0;
	font-family: bootstrap-icons;
	font-style: normal;
	font-weight: normal;
	font-size: 1.05rem;
	line-height: inherit; /* matches the paragraph's line box, so the icon sits on the first line */
	color: var(--vocab-icon);
}

/* --- the callouts --- */

/* Left accent bar plus a soft tint. The 2007 rule was a hard 1px border all round on flat #f5f5f5,
   and what dated it was not the box -- these genuinely are outside the normal flow and should look
   it -- but the full border, the square corners, and one neutral grey applied regardless of what
   the paragraph said. That last part is the real fix: the old rule gave "we got this trip for free"
   and "people have drowned here" identical weight.

   No border-radius, deliberately: a rounded corner against a single-sided border reads as a
   mistake. */
.content-body p.warning,
.content-body p.own-risk,
.content-body p.new-info,
.content-body p.decision,
.content-body p.tldr,
.content-body p.disclosure,
.content-body p.disclaimer {
	position: relative;
	padding: 0.85em 1em 0.85em 2.75em;
	margin: 1.25rem 0;
	border-left: 3px solid;
	border-radius: 0;
}

.content-body p.warning::before,
.content-body p.own-risk::before,
.content-body p.new-info::before,
.content-body p.decision::before,
.content-body p.tldr::before,
.content-body p.disclosure::before,
.content-body p.disclaimer::before {
	position: absolute;
	left: 1em;
	top: 0.85em; /* matches padding-top, so the icon aligns with the first line of text */
	font-family: bootstrap-icons;
	font-style: normal;
	font-weight: normal;
	font-size: 1.05rem;
	line-height: inherit;
}

/* Three levels, and colour carries importance. Blue deliberately carries the most volume
   (new-info is 192 published posts against warning's 40) -- the calm level should be the common one
   so that amber stays rare enough to still mean something. */

/* caution -- stop and read this */
.content-body p.warning,
.content-body p.own-risk {
	background-color: var(--vocab-caution-bg);
	border-left-color: var(--vocab-caution-rule);
}

.content-body p.warning::before,
.content-body p.own-risk::before {
	color: var(--vocab-caution-rule);
}

/* note -- useful, not urgent. new-info is usually a banner at the top of the article announcing
   changed hours or regulations, sometimes lower in the body. Some of it is eight years old and no
   longer new, which is an editorial matter, but it is also why this level is calm: an amber banner
   shouting about a change that predates the reader's interest would undermine the article. */
.content-body p.new-info,
.content-body p.decision,
.content-body p.tldr {
	background-color: var(--vocab-note-bg);
	border-left-color: var(--vocab-note-rule);
}

.content-body p.new-info::before,
.content-body p.decision::before,
.content-body p.tldr::before {
	color: var(--vocab-note-rule);
}

/* fine print -- skippable boilerplate. The disclosure is largely the same paragraph repeated across
   25 published posts, and a reader who has met it twenty times should be able to slide past it.
   Making this the quietest thing on the page is the point, not an oversight. */
.content-body p.disclosure,
.content-body p.disclaimer {
	background-color: var(--vocab-fineprint-bg);
	border-left-color: var(--vocab-fineprint-rule);
}

.content-body p.disclosure::before,
.content-body p.disclaimer::before {
	color: var(--vocab-fineprint-rule);
}

/* --- the icon table --- */

/* Ported from fix_image_tags() in prdt-025/single.php, which had made most of these choices in
   2007. Four collisions in the original are fixed here: travel and duration both had the hourglass,
   directions and gps both had map-marker, website and map both had the globe, and new-info,
   disclosure and disclaimer all had the flag. Icons that collide inside a stack where the classes
   routinely appear together defeat the point of having icons.

   The three time-shaped concepts are the set most worth getting right, since they often appear
   within a few lines of each other:
     hours    = the days and times it is open   -> a clock face asks "when"
     duration = how much time to allow for it   -> an hourglass is elapsed time
     travel   = how long it takes to drive there -> a car, because what distinguishes it is the
                journey, not the time. A third timepiece here would be unscannable.

   SECOND KIND OF COLLISION, found on a real post 2026-08-13 and worth remembering: it is not enough
   for icons to mean different things, they have to LOOK different at 1.05rem. The first version of
   this table used bi-exclamation-circle, bi-info-circle, bi-clock and bi-globe in the same stack --
   four circles. Whatever sits inside a circle is about six pixels across at this size and reads as
   texture, not as a glyph, so all four rendered as "small grey circle". Exactly one circle survives
   (the clock, since a clock is inherently round and hours is the most-used class at 310 posts) and
   the rest were given distinct outlines.

   This bit three times in one sitting -- the four circles, then exclamation-triangle against
   shield-exclamation on warning/own-risk, which are both pointed shapes with a mark inside and
   share the amber level so colour could not separate them either. WHEN ADDING A CLASS HERE, CHECK
   ITS SILHOUETTE AGAINST ITS NEIGHBOURS AT ACTUAL SIZE, not just its meaning. Two icons that mean
   different things are still one icon if they draw the same shape. Colour only helps across levels,
   never within one. */
.content-body p.cost::before        { content: "\f636"; } /* bi-currency-dollar */
.content-body p.hours::before       { content: "\f293"; } /* bi-clock */
.content-body p.duration::before    { content: "\f41f"; } /* bi-hourglass-split */
.content-body p.travel::before      { content: "\f7e1"; } /* bi-car-front */
.content-body p.phone::before       { content: "\f5c1"; } /* bi-telephone */
.content-body p.website::before     { content: "\f470"; } /* bi-link-45deg -- a chain link is closer to what it means than a globe, and breaks up the run of circles */
.content-body p.information::before { content: "\f228"; } /* bi-card-text */
.content-body p.directions::before  { content: "\f549"; } /* bi-signpost-split -- a route, not a point */
.content-body p.gps::before         { content: "\f3e8"; } /* bi-geo-alt -- coordinates genuinely are a point */
.content-body p.map::before         { content: "\f47f"; } /* bi-map */
.content-body p.photo::before       { content: "\f220"; } /* bi-camera */

/* 'important' means prerequisites, not "note this" -- Ray's examples are closed-toe shoes required,
   a minimum height requirement, the driver must hold a valid licence. Conditions you have to meet or
   you get turned away at the gate. That is why a clipboard reads better here than an exclamation
   mark: the reader's job is to check something before going, not to be alarmed. It is also why the
   class belongs in the details block rather than the callouts -- requirements sit naturally beside
   cost and hours as part of planning the trip. */
.content-body p.important::before   { content: "\f28b"; } /* bi-clipboard-check */

.content-body p.warning::before     { content: "\f33b"; } /* bi-exclamation-triangle */
/* An octagon is a stop sign, so it reads as one step more severe than the warning triangle, which
   is the actual relationship between these two. They share the caution level and therefore the same
   amber, so the icon is the only thing separating them -- and the first try, bi-shield-exclamation,
   was another pointed shape with a mark inside and was indistinguishable from the triangle at this
   size. bi-cone-striped was the other candidate and was rejected on meaning rather than looks: a
   traffic cone reads as construction, which is a hazard to your schedule, not to you. */
.content-body p.own-risk::before    { content: "\f337"; } /* bi-exclamation-octagon */
.content-body p.new-info::before    { content: "\f484"; } /* bi-megaphone */
.content-body p.decision::before    { content: "\f507"; } /* bi-question-diamond */
.content-body p.tldr::before        { content: "\f7f4"; } /* bi-fast-forward */
.content-body p.disclosure::before  { content: "\f341"; } /* bi-eye */
.content-body p.disclaimer::before  { content: "\f38b"; } /* bi-file-earmark-text */


/* SHORTCODE OUTPUT (2026-08-17)

   Everything the shortcodes in includes/shortcodes.php emit is styled here rather than in PHP.
   The WordPress originals hardcoded style='background-color:#f0ad4e;font-weight:bold;padding-
   right:5px;padding-left:5px' and style='max-width:35%;padding:4px;margin:0 0 2px 2px;display:
   inline;float:right' into the functions themselves, which meant a colour change was a code
   change, and meant the two shortcodes that render the same thing drifted apart.

   The colours below are the WP theme's own -- #f0ad4e is Bootstrap 3's warning orange and green
   was btn-success -- carried over so the migration does not also restyle 56 articles. They are
   meant to be tuned; that is now a one-line edit here.

   .sc-action is the shared shape, .sc-action-link / -button the form, and .sc-book-now /
   .sc-find-tours the per-shortcode variant. Nothing keys off an element name, so changing the
   markup does not silently drop the styling. */
.sc-action-link {
	background-color: #f0ad4e;

	/* NO font-weight, and its absence is deliberate -- do not "restore" it as a fidelity fix. The
	   WordPress original hardcoded font-weight:bold inline and it was carried over with the colours,
	   but the orange background already marks the link out, so the bold was a second solution to a
	   solved problem and made the label heavier than the sentence carrying it. Ray, 2026-08-18:
	   "there is no need for the bold - it looks clunky". If it ever needs a nudge back, 600 rather
	   than 700 is the thing to try. */
	padding-left: 0.35rem;
	padding-right: 0.35rem;
	border-radius: 0.2rem;

	/* The label is a unit, so it must not break across lines mid-phrase. "Book This Tour Now"
	   wrapping after "This" leaves an orange fragment at the end of one line and another at the
	   start of the next, which reads as two things. Ray, 2026-08-18.

	   Doing it here rather than with &nbsp; in the content means it holds for every label including
	   the defaults, and does not have to be remembered when someone writes a new one.

	   LINK FORM ONLY, deliberately. .sc-action-button is left alone: its labels are the ones that
	   carry explicit <br> (mata-la-gata, dive-desecheo-island) and it floats, so it is free to be as
	   wide as it needs. A <br> would still break under nowrap anyway, but there is no reason to
	   constrain the form that never had the problem.

	   The cost is that a pathologically long label would overflow a narrow column instead of
	   wrapping. The longest in the corpus is "Book Free-Style Tour Now", which is nowhere near it. */
	white-space: nowrap;
}

.sc-action-link a,
.sc-action-link a:hover,
.sc-action-link a:focus {
	color: #fff;
	text-decoration: none;
}

/* float:right is on the BUTTON only, never the link. That asymmetry is deliberate and it is what
   makes SHORTCODE_DISPLAY_LINK the safe default: a link flows inside the sentence that introduced
   it, while a button rearranges the paragraph it landed in. */
.sc-action-button {
	float: right;
	margin: 0 0 0.5rem 0.5rem;
	color: #fff;

	/* Deliberately bigger than Bootstrap's .btn default of 0.375rem/0.75rem, which we were simply
	   inheriting and which read as barely more than inline text beside a serif article. Ray's call
	   2026-08-18 after comparing four sizes side by side.

	   THE VERTICAL PADDING IS THE HALF THAT MATTERS. Horizontal alone gives a wide, short pill that
	   still does not read as a button; height is what separates it from a run of text.

	   rem rather than px so the button grows with the reader's browser text size. A px button stays
	   put while the prose around it grows, which is exactly backwards for the one element on the
	   page you want people to find.

	   No font-weight here, considered and left out: the size bump alone does the standing-out, and
	   .sc-action-link keeps 700 because an inline link has no other way to be noticed.

	   This wins over .btn on equal specificity only because styles.css is linked after Bootstrap in
	   header-footer.php. Worth knowing before anyone reorders those two link tags. */
	padding: 0.65rem 1.55rem;
	font-size: 1.05rem;

	/* position/z-index ARE NOT COSMETIC -- without them this button is not clickable, 2026-08-18.

	   A block box overlaps a float rather than avoiding it; only its LINE boxes shorten. So a
	   paragraph following this button still has its box underneath it, which is normally harmless
	   because floats paint above in-flow block backgrounds. But the article vocabulary paragraphs
	   (p.gps, p.map, p.directions, p.cost and the rest) are position: relative, because each needs
	   to be the containing block for its own ::before icon -- and a positioned element paints ABOVE
	   a float. The paragraph therefore sits invisibly on top of the button and swallows the click.

	   The symptom is the confusing part: the button renders perfectly, in the right place, and does
	   nothing. No hover cursor either, since the pointer never reaches the anchor. And it is
	   post-dependent, so an identical button on an article with no vocabulary paragraph beside it
	   works fine, which makes it look like the shortcode rather than the stylesheet.

	   Fixed on this side rather than by stopping those paragraphs from overlapping: giving them a
	   block formatting context would push a short p.gps below the float and leave a gap, where the
	   text wrapping beside the button is exactly what should happen. z-index 1 is far below the
	   navbar's 1020, and .content-body opens no stacking context, so this cannot cover chrome.

	   Verified with elementFromPoint() at the button's centre: P.gps before, the anchor after. */
	position: relative;
	z-index: 1;
}

.sc-action-button:hover,
.sc-action-button:focus {
	color: #fff;
}

.sc-book-now.sc-action-button {
	background-color: #ec971f;
}

.sc-find-tours.sc-action-button {
	background-color: #449d44;
}

/* The random-photo image already carries .alignright, which owns the float and the margins (see
   the image alignment rule above). This only caps it, since the source images are 300x200 and a
   narrow column should not be handed a third of its width. */
.sc-random-photo {
	max-width: 45%;
	height: auto;
}


/* BIOBAY MOON CALENDAR (ported 2026-08-17 from prdt-025/css/biobay-moon.css)

   Styles the nightly cron-generated calendar that [include-file] drops into the biobay-and-moon
   article. Ported rather than linked because that stylesheet lived in the WordPress theme, which
   dies at cutover, and it is 100 lines with no dependencies.

   THE COLOURS CARRY THE MEANING HERE, unlike everywhere else in this file: green is a good night
   for the bio bay, yellow is fair, red struck through is bad. That is the entire content of the
   table, so these are not decoration and must not be "tidied" into the site palette.

   The two gradient states (moon-gf, moon-fx) used background images in the theme
   (moon-background-good-fair.png, moon-background-fair-bad.png) to show a night that changes
   partway through. They are linear-gradients here instead: two colours are exactly what a
   gradient expresses, it scales to any cell size, and it removes two image files from the
   cutover inventory. */
div.moon-month { /* holds a table for 1 month */
	display: block;
	margin-left: auto;
	margin-right: auto;
	text-align: center;
}

table.moon-month {
	width: 100%;
	border: 1px solid #efefff;
	padding: 0;
	border-spacing: 1px;
	text-align: left;
	border-collapse: separate;
}

table.moon-month th { /* cell holding name of month */
	text-align: center;
}

table.moon-month tr.moon-weekdays td, /* cells with S M T W T F S */
table.moon-month tr.moon-dates td {   /* cells with dates */
	text-align: right;
}

/* new, best, good */
td.moon-n, td.moon-b, td.moon-g, td.moon-bg,
span.moon-n, span.moon-b, span.moon-g, span.moon-bg {
	background-color: #99ff99;
	border: 2px solid #99ff99;
}

td.moon-n, span.moon-n { border: 2px dashed green; } /* new */
td.moon-b, span.moon-b { border: 2px solid green; }  /* best */

/* fair */
td.moon-f, td.moon-gf, span.moon-f, span.moon-gf {
	background-color: #ffff99;
	border: 2px solid #ffff99;
}

/* bad, full */
td.moon-x, td.moon-o, td.moon-fx,
span.moon-x, span.moon-o, span.moon-fx {
	background-color: #ff9c94;
	text-decoration: line-through;
	border: 2px solid #ff9c94;
}

td.moon-o, span.moon-o { border: 2px dotted #fff; } /* full */

/* good early, fair later */
td.moon-gf, span.moon-gf {
	background: linear-gradient(to bottom, #99ff99 50%, #ffff99 50%);
	border-top-color: #99ff99;
	border-left-color: #99ff99;
}

/* fair early, bad later */
td.moon-fx, span.moon-fx {
	background: linear-gradient(to bottom, #ffff99 50%, #ff9c94 50%);
	border-top-color: #ffff99;
	border-left-color: #ffff99;
}

/* best early, good later */
td.moon-bg, span.moon-bg {
	border-top-color: green;
	border-left-color: green;
}

td.moon-FILLER { /* filler for blank row */
	border: 2px solid #fff;
}


/* WEATHER WIDGET (ported 2026-08-22 from prdt-025/css/weather.css)

   Styles the hourly cron-generated forecast table that [include-file] drops into the weather page.
   Same shape of thing as the biobay calendar above, and here for the same reason: that stylesheet
   lives in the WordPress theme, which dies at cutover.

   IT IS HERE RATHER THAN IN A PAGE-SPECIFIC STYLESHEET, and the reason is not tidiness. An
   [include-file] is portable by design -- the same generated file can be dropped into any post or
   page with one shortcode -- so CSS bound to one page would fail silently the first time it moved,
   rendering an unstyled table with nothing to say why. The CSS has to follow the content, and the
   content is not bound to a page. There is also no good hook for the alternative: the only thing
   that knows the widget is present is the shortcode, at render time, inside <body>, which is too
   late for a <head> link.

   WHAT CHANGED ON THE WAY IN, all deliberate:

   - The float is now behind the 768px breakpoint, matching .content-body img.alignright above.
     Unguarded it floated a ~300px table right on a 375px phone and left a sliver of text beside it.
     Nothing else was needed for floats: .content-body is display: flow-root so it contains them,
     and the heading rules already clear: both.
   - th.source ADDED. The original targeted `tr td.source` and the generator emits
     <th class='source'>, so that rule has never matched anything in 18 years. It looked right only
     because the generic th styling is nearly identical. Both are accepted now rather than betting
     on which one the generator emits next.
   - Sizes are rem. The original was 14px/10px/9px fixed; 9px is very small on a modern display and
     none of it scaled with the rest of our type.
   - The `tr` was dropped from descendant selectors (`tr th` -> `th`). Still ID-scoped, so nothing
     can reach these accidentally.
   - DROPPED: `* img { border-width: 0 }`, which is IE-era and dead -- our .content-body img rules
     are what govern that logo now; an empty td.curr-descrip rule; and div#onecaribweather, which
     styles a video block that is not part of this widget.

   THE ICONS ARE NOT STYLED BY FONT CLASS. The generator emits `wi wi-day-cloudy` and friends, which
   need Weather Icons 2.0 -- a 26KB stylesheet and a ~45KB webfont that this engine does not load, so
   every icon cell would be blank. Only nine icons are ever emitted and all nine exist in Bootstrap
   Icons, which every page here already loads. The rule below is scoped to the <i> element rather
   than to .wi or .bi so it works whichever the generated file ends up carrying. */
table#weather-widget {
	border: 3px solid #808080;
	font-size: 0.875rem;
	line-height: 1.2;
	margin: 0 auto 1rem auto; /* narrow screens: a centred block, not a float */
}

@media (min-width: 768px) {
	table#weather-widget {
		float: right;
		margin: 0 0 1rem 1rem;
	}
}

table#weather-widget th {
	text-align: center;
	background-color: #808080;
	color: #fff;
	line-height: 1.2;
	padding: 3px;
}

table#weather-widget td {
	padding: 3px;
	line-height: 1.2;
}

table#weather-widget span.time,
table#weather-widget span.hi-lo {
	font-size: 0.6875rem;
}

/* the credit strip at the foot of the table -- see th.source in the notes above */
table#weather-widget th.source,
table#weather-widget td.source {
	font-size: 0.6875rem;
	background-color: #808080;
	color: #fff;
	padding: 0;
	line-height: 1.2;
	text-align: center;
}

/* The OpenWeather logo is the table's own chrome, not content, so it must not take the 1rem bottom
   margin .content-body img gives every image. That margin exists to keep a following paragraph off
   a photo; here it just pushed the credit line 16px down inside its own cell. An ID selector, so it
   wins on specificity wherever this sits in the file. */
table#weather-widget img {
	margin-bottom: 0;
}

table#weather-widget tr.weather-current,
table#weather-widget tr.weather-forecast {
	background-color: #efefff;
}

table#weather-widget td.curr-cond {
	text-align: center;
}

table#weather-widget td.curr-cond span.temp_f {
	font-weight: bold;
	font-size: 0.875rem;
}

table#weather-widget td.curr-cond span.temp_c {
	font-weight: bold;
	font-size: 0.6875rem;
}

table#weather-widget td.curr-descrip span.location {
	font-weight: bold;
}

table#weather-widget td.fore-date,
table#weather-widget td.fore-cond,
table#weather-widget td.fore-descrip {
	border-bottom: 1px solid #808080;
}

table#weather-widget td.fore-date,
table#weather-widget td.fore-cond {
	text-align: center;
}

table#weather-widget td.fore-date span.weekday {
	font-weight: bold;
}

table#weather-widget i {
	font-size: 1.875rem;
	color: dodgerblue;
	line-height: 1.5;
}


/* SITE NOTICES (2026-08-27)

   The site-wide header notice: a collapsed accordion above the page, one line of heading that
   expands to detail. Rendered by NoticeUtility from a POST_TYPE_NOTICE row -- see that file for the
   bracket markup, and context-weather-page.md for what this replaced on the WordPress side.

   NO PRINT RULE IS NEEDED. The @media print block below hides `body > *` and puts .post back, and
   the notice sits in the chrome as a direct child of body, exactly like the alert row. Ray's call
   was that notices never print (his editorial commentary is not what a reader should carry to a
   shelter), and that comes for free rather than needing its own line.

   The background and text colours are NOT here. They come from the row's [bgcolor] and [color] as
   inline style, because the author picks them per notice -- these rules are geometry only. */
.site-notice {
	margin: 0.75rem 0;
}

/* A banner strip rather than a card. Bootstrap's accordion draws a rounded, shadowed box meant to
   sit inside content; this one spans the top of every page and should read as part of the chrome. */
.site-notice.accordion,
.site-notice .accordion-item {
	--bs-accordion-border-radius: 0.25rem;
	--bs-accordion-inner-border-radius: 0.25rem;
	border: 0;
}

/* THE INLINE COLOURS CARRY !important AND THIS IS WHY. Bootstrap sets the button's background and
   colour through --bs-accordion-btn-bg and again, differently, through
   .accordion-button:not(.collapsed), which scores higher than a plain inline declaration can
   settle. Without !important a notice would be tinted while collapsed and revert to Bootstrap's
   pale blue the moment it was opened. See NoticeUtility::styleAttr(). */
.site-notice .accordion-button {
	font-weight: 600;
	padding: 0.6rem 1rem;
}

/* the focus ring is Bootstrap's default blue and fights every notice colour. The button is a full
   -width bar, so it is never ambiguous which control has focus */
.site-notice .accordion-button:focus {
	box-shadow: none;
}

.site-notice .accordion-body {
	padding: 0.75rem;
}

/* The auto-generated "updated" line, above the first card. Quiet on purpose: it is provenance for
   the content below it, not content itself, so it should be readable when looked for and invisible
   when not. Replaces a line that was hand-typed into the body of every WordPress notice file. */
.site-notice-updated {
	font-size: 0.85rem;
	opacity: 0.75;
	margin-bottom: 0.5rem;
}

/* One card per [cardN]. Its background is inline, from the card's own [bgcolor] or inherited from
   [noticehead] -- the WordPress original used Bootstrap 3's .well for this, which has no BS5 twin. */
.site-notice-card {
	padding: 0.75rem 1rem;
	border-radius: 0.25rem;
	margin-bottom: 0.75rem;
}

.site-notice-card:last-child {
	margin-bottom: 0;
}

/* UNIFORM BY DECISION, and there is no keyword to colour it per card. In the WordPress files this
   line was hand-written as <strong>, sometimes with an inline colour, so it looked slightly
   different in every notice. [heading] inside a card makes it structure instead, which is also what
   leaves the door open to promoting cards to their own accordion items later with no content
   rewrite. [color] in [noticehead] is unrelated -- that one still sets the collapsed strip's text. */
.site-notice-card-heading {
	font-weight: 700;
	margin-bottom: 0.35rem;
}

/* PRINT (2026-08-15)

   Our engine had NO print handling at all before this. What is replaced is the WP theme's
   print.css, four rules loaded with media="print" (which is why its rules carry no media query).
   This is a carry-over decision rather than preservation -- Ray wants print kept, and the reason is
   concrete: his wife printed a travel site's pages and wasted ink on images that added nothing to
   the printed information.

   WHY THE IMAGE RULES LOOK THE WAY THEY DO. The WP boilerplate inserted into every new post says
   images do not print except those "vital to the content of the pages, like maps", tagged
   centered-print. That intent was never implemented -- WP's print.css has no centered-print rule,
   so a map tagged for print has never printed in the life of that theme. Measured against the 572
   published pricoday posts on 2026-08-15:

     12   posts carry an <img> tagged centered-print
     12   of those 12 also have "map" in the image filename -- ALL of them
     19   posts have a map-filename image in total
      7   of those 19 have no tagged image at all (they used centered, alignright or hero)
      0   map images are tagged centered-noprint, so the filename rule contradicts nobody

   So centered-print adds NOTHING to coverage that the filename does not already give, and misses
   7 posts the filename catches.

   PRINT IS DECOUPLED FROM LAYOUT, AND centered-print IS NOT HONOURED HERE. Ray's call, 2026-08-15,
   reversing his own historical method. The measurement above is the argument: nobody was ever
   reasoning about print when they chose these classes. The 7 untagged map posts used centered,
   alignright and hero; the 12 tagged ones are all maps because whoever wanted a centered map
   reached for the class that centered. A class that does two things gets chosen for the one you
   can SEE, and the invisible half rides along by accident -- which happened here in both
   directions, with the documented intent sitting in the new-post boilerplate the whole time.

   The marker is `print-always`, and it does nothing but print. Layout stays with `centered`,
   `alignright` and `alignleft`.

   Deliberately NOT Bootstrap's d-print-block, even though it does this exact job. Stored content
   outlives Bootstrap major versions and their utility names churn across them -- there is a live
   post in this corpus carrying `hidden-print`, a BS3 class that has done nothing since the BS5
   upgrade. A class we define here is one we can keep working.

   There is no `print-never`. Under "nothing prints unless tagged" it would be a no-op by
   construction.

   The filename bridge that stood here, img[src*="map"], was removed 2026-08-16 once A1a's retag
   reached our database. Verified first: zero published posts carry a map-named image without
   print-always, so nothing that printed before stops printing.

   An earlier count of 371 was wrong and is recorded here so nobody rediscovers it: a plain LIKE on
   'centered-print' matches the boilerplate COMMENT TEXT, so it counted posts that still carry the
   template rather than posts with a tagged image. All 359 of the difference are unpublished.

   NOT INCLUDED, DELIBERATELY: the WP theme's print-with-photos / print-without-photos toggle. Once
   maps print, the default printout is already the good one, and "with photos" means overriding an
   editorial judgement the reader has no basis to second-guess. It also needs a post meta row to put
   the links in, which does not exist yet and is entangled with the still-open byline question. It
   stays cheap to add later: one class plus three lines of JS. Judge it against real printouts.

   The known counter-argument, since it is a closer call than it looks: only 19 of 572 published
   articles contain anything that prints, so the default is text-only for 97% of the site. */

/* The print branding is emitted into every post and page by PostViewHandler and must never be
   visible on screen. THIS RULE IS OUTSIDE THE MEDIA QUERY ON PURPOSE -- a display:none that only
   applied when printing would be exactly backwards, and these elements would render on every
   article. The @media print block below turns them back on. */
.print-banner,
.print-footer {
	display: none;
}

@media print {

	@page {
		margin: 2cm;
	}

	/* CHROME. Hide every top-level element, then put the article back. The alternative is naming
	   the nav, the offcanvas panels, the alert row and the legal footer one at a time, which is
	   both longer and wrong in the other direction: anything added to the page chrome later would
	   print by default and nobody would notice until they printed something. This way new chrome
	   is silently excluded and only a deliberate addition inside .post can appear on paper.

	   .post is a direct child of body (PostViewHandler::displayThePost emits it) and wraps BOTH
	   posts and pages, so this covers every template.

	   !important IS LOAD-BEARING HERE AND MUST NOT BE REMOVED. `body > *` scores (0,0,1) -- the
	   universal selector contributes NOTHING to specificity, so this is very nearly the weakest
	   rule that can be written. Any chrome element carrying its own display declaration beats it
	   on a class alone. That is not hypothetical: the first version of this block shipped without
	   !important and the yellow .editor-strip printed on both sites, because
	   `.editor-strip { display: flex }` scores (0,1,0). Bootstrap's own `.offcanvas` has the same
	   shape and is worse, since those panels are visibility:hidden rather than display:none and
	   can occupy space on the page -- blank sheets rather than visible junk.

	   Matching specificity the way the image rules below do is not available here, because the
	   whole point of `body > *` is that it names nothing. The rule is meant to be absolute: only
	   the article prints. !important says exactly that. */
	body > * {
		display: none !important;
	}

	/* NOTHING THE PUBLIC CANNOT SEE EVER PRINTS. One rule for the whole marker, which is the
	   payoff for having made it a semantic class rather than a colour: every editor-only element
	   is covered, including ones added later, and the ones INSIDE .post are the only ones that
	   need it -- everything else is already gone with the chrome above.
	   This replaced a d-print-none on the post edit block (2026-08-21). !important for the same
	   reason the rule above needs it: a class alone loses to anything carrying its own display. */
	.editor-only {
		display: none !important;
	}

	body > .post {
		display: block !important;
	}

	/* screen breathing room above the article, pointless once the chrome above it is gone */
	.post {
		margin-top: 0;
	}

	/* rem is relative to the ROOT font size, so this one declaration rescales the entire heading
	   scale defined above without restating any of it -- h1 stays 2rem, h2 1.5rem and so on, all
	   now measured against 11pt instead of the browser's 16px. Restating the scale in pt here
	   would mean two scales to keep in agreement, and they would drift. 16px is 12pt at the 96dpi
	   browsers assume for print, so this is a deliberate one-point tightening, not a conversion. */
	html {
		font-size: 11pt;
	}

	/* 800px was chosen for reading on a screen and means nothing on paper -- worse, it exceeds the
	   ~6.5in printable width inside the margins above, so leaving it would clip. @page owns the
	   measure now. */
	.content-column {
		max-width: none;
		padding-left: 0;
		padding-right: 0;
	}

	/* IMAGES.

	   THE REPEATED ANCESTOR IN THESE SELECTORS IS SPECIFICITY, NOT DECORATION. Do not "simplify"
	   .content-column .content-body img to .content-body img. The screen rule
	   `.content-body img.centered { display: block }` scores (0,2,1); a hide rule written as
	   `.content-body img` scores (0,1,1) and LOSES to it, so every centered image would still
	   print and the whole block would appear to do nothing. Matching (0,2,1) and sitting later in
	   the file is what makes the hide actually win. The re-enable rules below then score (0,3,1)
	   so they beat the hide in turn.

	   The hero image is a direct child of .content-column (not inside .content-body) and carries
	   only img-fluid, so it needs its own selector but no specificity padding.

	   THE HERO NEVER PRINTS, not even when its filename contains "map". It was briefly exempted
	   like body images and that was wrong for two reasons. It is a presentation element -- the
	   page's lead photo, chosen for the article's appearance on screen and reused as the og:image
	   and as the QR filename key -- rather than something the author placed in the flow of the
	   text to be read. And on exactly the articles where the exemption fired, the same map is
	   usually also in the body, so it printed twice. If a map is worth ink it is in the content,
	   which is where the rule below finds it. */
	.content-column .content-body img,
	.content-column > img {
		display: none;
	}

	.content-column .content-body img.print-always {
		display: block;
	}

	/* PRINT BRANDING. Emitted by PostViewHandler inside .post, never rendered on screen.

	   The masthead and the citation foot do different jobs and that is why they are in different
	   places rather than one block. The logo at the top answers "where did this come from" at the
	   moment someone picks the sheet up. The QR at the foot answers "take me back to this", which
	   is a finishing action -- and a QR in a banner reads as a coupon, which is the wrong register
	   for a travel guide.

	   The foot is also what makes suppressing link URLs safe: a[href]::after is killed above, so the
	   inline addresses are gone, and this gives one scannable way back instead of dozens of unusable
	   ones. */
	.print-banner {
		display: block;
		text-align: center;
		border-bottom: 1px solid #000;
		padding-bottom: 0.5rem;
		margin-bottom: 1.5rem;
	}

	.print-banner img {
		display: block;
		max-width: 200px;
		margin-left: auto;
		margin-right: auto;
	}

	.print-footer {
		display: block;
		border-top: 1px solid #000;
		margin-top: 2rem;
		padding-top: 0.5rem;
		font-size: 0.8rem;
		text-align: center;
		break-inside: avoid;
	}

	/* the QR is the one image on the page that is neither content nor hero, so it needs its own
	   permission -- it sits outside .content-column entirely and is therefore untouched by the
	   image rules above, but it is stated explicitly so nobody has to work that out */
	.print-footer img.print-qr {
		display: block;
		width: 100px;
		height: auto;
		margin: 0 auto 0.5rem auto;
	}

	/* a QR code is recognisable but its DESTINATION never is, and in print this one is also the
	   only route to anything the article links to, since a[href]::after is suppressed above */
	.print-qr-label {
		margin: 0 0 0.35rem 0;
		font-style: italic;
	}

	.print-footer-url {
		margin: 0;
		word-break: break-all;    /* a long slug must wrap rather than run off the page */
	}

	/* the printed date is the reader's only clue to how stale the hours and prices might be, which
	   is the whole reason it is here -- so it gets weight rather than being tucked into the
	   copyright line */
	.print-footer-printed {
		margin: 0.35rem 0 0 0;
		font-weight: 600;
	}

	.print-footer-copy {
		margin: 0.25rem 0 0 0;
	}

	/* Defensive. Bootstrap 4 expanded every link to "text (https://...)" on print, which is the
	   single biggest contributor to a printout reading as a web page dump rather than a page.
	   Bootstrap 5 appears to have dropped that rule, but this stylesheet does not control which
	   Bootstrap the CDN serves, and the cost of the override if the rule is already gone is zero.
	   The QR code planned for the print footer is what makes suppressing URLs safe: kill the inline
	   addresses, give one scannable way back to the article. */
	a[href]::after {
		content: none !important;
	}

	/* SHORTCODE OUTPUT ON PAPER (2026-08-17)

	   The rule above is why these exist. [phone] and [email] normally show the number or address
	   as their own link text, so they print correctly for free. But both accept a label, and
	   label="Call the office" prints as "Call the office" with the number nowhere on the sheet --
	   the one piece of information the reader is holding the paper to get.

	   PHP adds data-sc-print ONLY when a label was used, so the attribute selector is what makes
	   this do nothing in the common case rather than printing every number twice. */
	.sc-phone[data-sc-print]::after,
	.sc-email[data-sc-print]::after {
		content: " (" attr(data-sc-print) ")" !important;
		font-style: italic;
	}

	/* Booking and search links do not print. They are calls to action that need a live browser to
	   do anything at all, and the button form floats right and would push body text around on
	   paper for no gain. The citation foot's QR code is the printed article's one route to
	   anything interactive, which is exactly the argument that made suppressing link URLs safe. */
	.sc-action {
		display: none !important;
	}

	/* PAGE BREAKS. An image split across a page boundary is unreadable, and a heading stranded at
	   the foot of a page with its section overleaf is the classic tell of unstyled print. */
	.content-column img {
		break-inside: avoid;
	}

	.content-body h2,
	.content-body h3,
	.content-body h4,
	.content-body h5,
	.content-body h6 {
		break-after: avoid;
		break-inside: avoid;
	}

	/* the details block (cost, hours, duration...) is a run of one-line paragraphs that belong
	   together -- breaking between them separates a value from the rest of the planning info */
	.content-body p.cost,
	.content-body p.hours,
	.content-body p.duration,
	.content-body p.travel,
	.content-body p.phone,
	.content-body p.website,
	.content-body p.information,
	.content-body p.directions,
	.content-body p.gps,
	.content-body p.map,
	.content-body p.photo,
	.content-body p.important {
		break-inside: avoid;
	}

	.content-body p {
		orphans: 3;
		widows: 3;
	}
}