/*
Theme Name: MOFE
Theme URI: https://mofecup.com
Author: MOFE
Description: Classic PHP theme for MOFE — a B2B contract manufacturer's catalogue. The product catalogue, its categories, its specification fields and its enquiry route are the theme's own; there is no shop plugin underneath and no page builder. Design tokens live in theme.json (WordPress prints them as --wp--preset--* custom properties for classic themes too); this file holds the theme header plus the component layer.
Version: 3.3.0
Requires at least: 6.4
Tested up to: 6.8
Requires PHP: 8.0
License: GPL-2.0-or-later
License URI: https://www.gnu.org/licenses/gpl-2.0.html
Text Domain: mofe
Tags: custom-menu, custom-logo, featured-images, translation-ready, accessibility-ready, threaded-comments, left-sidebar
*/

/* ============================================================
   RULE OF THIS FILE
   ------------------------------------------------------------
   Colour, spacing, radius, shadow, motion, font family and font
   size all come from theme.json and are consumed as
       var(--wp--preset--*)   or   var(--wp--custom--*)
   Nothing here writes a literal design value.

   THAT SENTENCE WAS FALSE FOR TYPE UNTIL THIS REVISION

   When it was written the file also contained 36 literal font-size
   declarations — 12px, 12.5px, 13px, 13.5px, 14px, 14.5px, 15px,
   16px, 20px — against 13 references to a token. Nine ad-hoc sizes
   for a scale with seven. The claim and the file disagreed, and the
   claim was the kind that stops a reader looking: "all the design
   values come from theme.json" is exactly what you would assume if
   you had not counted.

   They are now normalised onto the tokens (tools/normalise_type.py,
   which is idempotent and can be run with --check). The only literal
   that survives is `font-size: 0.85em` in the print block, and it is
   relative on purpose — a printed hyperlink suffix has to shrink with
   whatever text it follows.

   The remaining literals in this file are geometry, not design
   values, and they are named where they occur: a 48px control height
   is a touch-target floor rather than a size on the scale, and a
   1px border is a hairline. Those are measurements, and a token set
   that pretended to own them would be lying in the other direction.

   NAME THE CUSTOM PROPERTY EXACTLY AS WORDPRESS EMITS IT
   ------------------------------------------------------------
   For settings.custom, WordPress runs every key through
   strtolower( _wp_to_kebab_case( $key ) ) before prefixing it with
   --wp--custom-- (WP_Theme_JSON::flatten_tree). So a theme.json key
   written as "easeOut" is emitted as --wp--custom--motion--ease-out,
   and a var() here naming --wp--custom--motion--easeOut resolves to
   nothing. A var() with no fallback that names a missing custom
   property does not fail loudly: the declaration becomes invalid at
   computed-value time and is dropped. Every transition in this file
   was dead for that reason until the keys and the references were both
   written in kebab case.

   So: keys in settings.custom are kebab-case, and the string here is
   character-for-character the same string as the key. There is no
   transform to remember. tools/validate.py check_token_references()
   fails the build on a camelCase custom key and on any var() that
   names something theme.json does not define.

   !important appears exactly twice, in the two places where WordPress core
   itself declares !important and an override cannot win without matching it:
   the reduced-motion block near the top, and the left-alignment override for
   constrained sections. Both are scoped to a short selector list and both
   explain themselves in place. If you are tempted to add a third, fix the
   selector instead.

   Why this still works in a classic theme:
   wp-includes/global-styles-and-settings.php builds the stylesheet
   from `variables`, `styles` and `presets` whenever the theme ships
   a theme.json — the block-theme check only decides whether the
   result is printed in the head or the footer. So both the preset
   custom properties and the styles.elements rules (h1–h6, button,
   link, caption) reach the front end here exactly as they did in
   the block theme. See tools/validate.py check_json() and
   check_token_references().
   ============================================================ */

/* ---------- 1. Fonts are declared in theme.json as fontFace ---------- */

/* ---------- 2. Base ---------- */

/* The box model reset. Until 3.3.0 no `box-sizing` was declared anywhere in
   this file, so the CSS default (content-box) applied: `width:100%` on a padded
   element meant 100% PLUS the padding. `.mofe-container` is `width:100%` with
   `padding-inline` of one preset step, so it measured `100% + 48px` and every
   viewport below its 1240px max-width scrolled sideways by exactly that
   (768 → 816, 390 → 438). Above 1240px the max-width masks it — which is why
   this read as a tablet/mobile problem and was mistaken for one for two
   releases. It is not: it is the box model, and desktop merely got lucky.
   Reset globally, first in the base layer, so every measurement below this
   point means what its declaration says. */
*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }

body {
  font-variation-settings: "wdth" 100;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

/* Display type uses the width axis of the same family.
   This is how one font family covers both display and body roles. */
h1, .wp-block-post-title {
  font-stretch: var(--wp--custom--display--width);
  text-wrap: balance;
}
h2, h3 { text-wrap: balance; }

/* ---------- 3. Focus: visible, keyboard-only ---------- */
:focus-visible {
  outline: 2px solid var(--wp--preset--color--brand);
  outline-offset: 2px;
  border-radius: var(--wp--custom--radius--xs);
}
:focus:not(:focus-visible) { outline: none; }

/* ---------- 4. Motion: honour user preference ---------- */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
}

/* ============================================================
   COMPONENTS
   ============================================================ */

/* ---------- Buttons: 48px minimum hit area ----------
   `.mofe-btn` is the PHP-template button (header CTA, empty state, footer).
   It is grouped with the block button rather than given its own rule so the
   two can never drift — a button in the header and a button in an article
   are the same button. */
.wp-block-button__link,
.wp-element-button,
.mofe-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 48px;
  font-weight: 620;
  border-radius: var(--wp--custom--radius--sm);
  transition:
    background-color var(--wp--custom--motion--fast) var(--wp--custom--motion--ease-in-out),
    border-color     var(--wp--custom--motion--fast) var(--wp--custom--motion--ease-in-out),
    color            var(--wp--custom--motion--fast) var(--wp--custom--motion--ease-in-out),
    transform        var(--wp--custom--motion--fast) var(--wp--custom--motion--ease-out);
}
.wp-block-button__link:active,
.wp-element-button:active,
.mofe-btn:active { transform: translateY(1px); }

.wp-block-button.is-style-ghost .wp-block-button__link {
  background: transparent;
  color: var(--wp--preset--color--ink);
  border: 1px solid var(--wp--preset--color--line);
}
.wp-block-button.is-style-ghost .wp-block-button__link:hover {
  border-color: var(--wp--preset--color--steel-400);
  background: var(--wp--preset--color--paper-alt);
  color: var(--wp--preset--color--ink);
}

/* On a brand-coloured band, invert the button */
.is-style-on-brand .wp-block-button__link {
  background: var(--wp--preset--color--card);
  color: var(--wp--preset--color--brand);
}
.is-style-on-brand .wp-block-button__link:hover {
  background: var(--wp--preset--color--brand-soft);
  color: var(--wp--preset--color--brand-deep);
}

/* Mobile: full-width, larger target.
   `.mofe-btn` is excluded: a full-width "Get a quote" in the mobile header
   would push the menu button off the row. It keeps its natural width and the
   52px height, which is what the minimum-tap-target rule actually asks for. */
@media (max-width: 560px) {
  .wp-block-button, .wp-block-button__link { width: 100%; }
  .wp-block-button__link, .mofe-btn { min-height: 52px; }
}

/* ---------- Cards: one elevation language ---------- */
.is-style-card {
  background: var(--wp--preset--color--card);
  border-radius: var(--wp--custom--radius--md);
  box-shadow: var(--wp--preset--shadow--e-1);
  padding: var(--wp--preset--spacing--50);
}
.is-style-card-lift {
  transition: box-shadow var(--wp--custom--motion--base) var(--wp--custom--motion--ease-in-out),
              transform   var(--wp--custom--motion--base) var(--wp--custom--motion--ease-out);
}
.is-style-card-lift:hover {
  box-shadow: var(--wp--preset--shadow--e-3);
  transform: translateY(-2px);
}

/* ---------- Bands ----------
   A band is a surface, so it needs its own horizontal padding once its
   background is visible. The patterns' own constrained layout centres most
   content, but a full-wide child would otherwise sit flush against the edge.
   Radius is not decoration: inside a page container a square-cornered band
   reads as a mistake, and full-bleed bands are not available in a classic
   theme's content column, so the rounded surface is the honest version. */
.is-style-band        { padding-block: var(--wp--preset--spacing--80); }
.is-style-band-lg     { padding-block: calc(var(--wp--preset--spacing--80) * 2); }
.is-style-band-alt    {
  background-color: var(--wp--preset--color--paper-alt);
  border-radius: var(--wp--custom--radius--md);
  padding-inline: var(--wp--preset--spacing--50);
}
.is-style-band-brand  {
  background-color: var(--wp--preset--color--brand);
  color: #FFFFFF;
  border-radius: var(--wp--custom--radius--md);
  padding-inline: var(--wp--preset--spacing--50);
}
.is-style-band-brand a { color: #FFFFFF; }
.is-style-band-brand .is-style-lede,
.is-style-band-brand .is-style-muted { color: rgba(255, 255, 255, .82); }

/* ---------- The night band: the page's one dark pivot ----------
   brand-deep has been in the palette since the first build and nothing
   ever used it as a surface — it appeared in link hover and button
   active and nowhere else, so the darkest colour in the theme was also
   its least seen. A long homepage assembled from four light surfaces
   (paper, card, paper-alt, brand) has no dark moment to turn on: the eye
   slides from the first screen to the last at one brightness, and no
   part of the page is the part where the reader slowed down.

   The band is an existing token, not a new hue. That is the whole trade:
   a new rhythm bought with a colour the palette already owns, so
   theme.json keeps its twenty entries and nothing about the palette
   changes. A site that wants a different dark goes through the
   `mofe_palette` filter (inc/theme-json.php), not through this file.

   The glow is luminance, not colour. It is one static radial gradient of
   white at 7% fading out — the only gradient in the theme — and it is
   white because a brand accent burned into the tokens for one customer
   is exactly the mistake the neutralisation work spent a revision
   undoing. There is no animation, no transition and no JS: the
   reduced-motion block near the top of this file is not touched and does
   not need to be, because there is nothing moving to reduce.

   Every override below names the band in its selector, so a light
   surface elsewhere on the same page keeps its own rules — one
   component, one surface, the same discipline `.is-style-card` and
   `.is-style-band-alt` follow. */
.is-style-band-night {
  background-color: var(--wp--preset--color--brand-deep);
  background-image: radial-gradient(60% 80% at 70% 0%, rgba(255, 255, 255, .07), transparent 70%);
  color: #FFFFFF;
  border-radius: var(--wp--custom--radius--md);
  padding-inline: var(--wp--preset--spacing--50);
}
.is-style-band-night a { color: #FFFFFF; }
.is-style-band-night .is-style-lede,
.is-style-band-night .is-style-muted { color: rgba(255, 255, 255, .82); }
.is-style-band-night .is-style-card {
  background: rgba(255, 255, 255, .06);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .14);
}
.is-style-band-night .wp-block-table thead th { border-bottom-color: rgba(255, 255, 255, .24); }
.is-style-band-night .wp-block-table th,
.is-style-band-night .wp-block-table td { border-bottom-color: rgba(255, 255, 255, .12); }

/* ---------- Specification chips: a parameter, not a status ----------
   The same pill geometry as a badge, and deliberately the opposite
   meaning. A badge's colour is a verdict — recommended, check first,
   excluded — and it is loud because a verdict should be. A chip's
   default is neutral because what it carries is a value: a wavelength, a
   model code, a tolerance. Put the two in one row and they do not
   compete, because one is coloured and one is not.

   Mono is not a style choice here; it is the rule `.is-style-value`
   states above, applied to a pill — the typeface is reserved for real
   values, and `tabular-nums` is what makes a row of chips align on their
   digits. A chip is that same value with a boundary around it, so a line
   of parameters can sit inline in prose without becoming a table.

   The accent variant is the one chip allowed colour, and it is the
   brand-soft/brand pair the filter strip already uses
   (`.mofe-chip.is-current`): it marks the parameter the page is about,
   not a state of the world. */
.is-style-chip {
  display: inline-flex;
  align-items: center;
  height: 26px;
  padding-inline: 10px;
  border: 1px solid var(--wp--preset--color--line);
  border-radius: var(--wp--custom--radius--pill);
  background: var(--wp--preset--color--card);
  color: var(--wp--preset--color--steel-700);
  font-family: var(--wp--preset--font-family--mono);
  font-size: var(--wp--preset--font-size--x-small);
  font-variant-numeric: tabular-nums;
  font-weight: 500;
  line-height: 1;
  white-space: nowrap;
}
.is-style-chip-accent {
  background: var(--wp--preset--color--brand-soft);
  border-color: color-mix(in srgb, var(--wp--preset--color--brand) 24%, transparent);
  color: var(--wp--preset--color--brand);
}

/* ---------- Signal badges: colour carries meaning only ----------
   ONE NAME SET, AND IT IS THE EDITOR'S.

   These rules existed twice: once under `.is-style-badge*`, which is the name
   a block style has, and once under `.mofe-badge*`, which is what the
   mofe/spec-table pattern wrote into its markup. Both worked — the two names
   shared every declaration — but only one of them could ever be reached from
   the editor, and it was not the one the pattern used.

   `is-style-badge` was not registered as a block style in inc/editor.php, so an
   editor had no way to apply a badge: the Styles panel did not offer it, and
   producing one by hand meant the Custom HTML block, which the theme
   deliberately discourages. Meanwhile the pattern's `mofe-badge` worked and was
   invisible — nothing else in the repository mentioned the name, so the only
   record of the real class was one pattern file.

   Now there is one name set, `is-style-badge*`, and it is registered for
   paragraphs in inc/editor.php. An editor ticks "Badge (green)" and gets the
   same pill a pattern writes.

   The colour is the only thing that differs between the four variants, and that
   is the point: on a specification table a colour that does not carry meaning
   is decoration, and decoration on a comparison table is what makes a reader
   stop trusting it. Every variant also sets a border and a label is always
   present, so nothing here depends on colour alone (WCAG 1.4.1). */
.is-style-badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 26px;
  padding-inline: 10px;
  border-radius: var(--wp--custom--radius--pill);
  font-size: var(--wp--preset--font-size--x-small);
  font-weight: 600;
  line-height: 1;
  border: 1px solid transparent;
}
.is-style-badge-pass {
  background: var(--wp--preset--color--pass-soft);
  color: var(--wp--preset--color--pass);
  border-color: color-mix(in srgb, var(--wp--preset--color--pass) 24%, transparent);
}
.is-style-badge-attn {
  background: var(--wp--preset--color--attn-soft);
  color: var(--wp--preset--color--attn);
  border-color: color-mix(in srgb, var(--wp--preset--color--attn) 24%, transparent);
}
.is-style-badge-stop {
  background: var(--wp--preset--color--stop-soft);
  color: var(--wp--preset--color--stop);
  border-color: color-mix(in srgb, var(--wp--preset--color--stop) 24%, transparent);
}
.is-style-badge-neutral {
  background: var(--wp--preset--color--paper-alt);
  color: var(--wp--preset--color--steel-700);
  border-color: var(--wp--preset--color--line);
}

/* ---------- Table: the specification-sheet moment ---------- */
.wp-block-table table { border-collapse: collapse; width: 100%; }
.wp-block-table thead th {
  font-size: var(--wp--preset--font-size--x-small);
  font-weight: 650;
  color: var(--wp--preset--color--steel-500);
  /* A header rule wants to be darker than the row rules below it, so it is not
     `--wp--preset--color--line`. This declaration used to name
     `--wp--preset--color--steel-300`, which no palette defined: a var() with no
     fallback that names a missing custom property makes the declaration invalid
     at computed-value time, so it was dropped and the head row had no rule at
     all. Silent — the only symptom was one missing hairline. `line-strong` is
     the token the design was reaching for; it is defined in theme.json. */
  border-bottom: 1px solid var(--wp--preset--color--line-strong);
  text-align: left;
}
.wp-block-table th,
.wp-block-table td {
  padding: 12px 14px;
  border-bottom: 1px solid var(--wp--preset--color--line);
  text-align: left;
  vertical-align: top;
}
.wp-block-table tbody tr:last-child th,
.wp-block-table tbody tr:last-child td { border-bottom: 0; }

/* A `.wp-block-table .is-style-value` rule sat here, setting the mono face and
   a 13.5px size for values inside a table. It is gone because the unscoped
   `.is-style-value` below already sets both, and the two disagreed by half a
   pixel — a table value and a card value were the same thing declared twice,
   with the table's copy winning on specificity. One rule, one size. */

/* ---------- Mono is reserved for real values, never decorative labels ---------- */
.is-style-value {
  font-family: var(--wp--preset--font-family--mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--wp--preset--font-size--x-small);
  line-height: 1.5;
}

/* ---------- Lede under a heading ---------- */
.is-style-lede {
  font-size: var(--wp--preset--font-size--large);
  line-height: 1.58;
  color: var(--wp--preset--color--steel-700);
  max-width: var(--wp--custom--measure--lede);
}
.is-style-muted {
  color: var(--wp--preset--color--steel-500);
  max-width: var(--wp--custom--measure--prose);
}

/* ---------- Left alignment inside a constrained section ----------------------
   Core's constrained-layout rule does two things to each direct child: caps it
   at `contentSize`, then centres it with `margin-left: auto !important` and
   `margin-right: auto !important`.

   The cap is wanted. The centring is not. This design is left-aligned on
   purpose — the hero is explicitly "asymmetric, no centred stack" — so any
   child that ends up narrower than its section (every lede, every note, every
   value line) would drift into the middle of a 1240px page and read as a
   different design.

   The !important is not a shortcut: core declares those margins with
   !important, so an override without it loses to source order. It is scoped to
   two specificity-lifted utility selectors rather than applied broadly, and it
   is the second and last exception to this file's no-!important rule (the
   first is the reduced-motion block near the top). */
.is-layout-constrained > .is-style-lede,
.is-layout-constrained > .is-style-muted,
.is-layout-constrained > .is-style-value {
  margin-inline: 0 !important;
}

/* ---------- Reading measure for long-form ---------- */
.entry-content > p,
.entry-content > ul,
.entry-content > ol,
.entry-content > blockquote {
  max-width: var(--wp--custom--measure--prose);
}

/* ---------- Forms: 48px targets, honest error styling ----------
   `.entry-content`, NOT `.wp-block-post-content`.

   Every selector in this block used to be written against `.wp-block-post-content`,
   which is the class core puts on the Post Content *block* — a block that
   exists only in a block template and cannot be inserted into a post. This
   theme's content is wrapped by content-page.php and content-single.php, which
   emit `entry-content`. So the ten selectors below matched nothing on any page:
   the theme shipped a contact form whose fields fell back to the browser's
   default 1px grey border and 21px height, and every rule written to prevent
   that was inert.

   Verified against the live site before the fix: `wp-block-post-content`
   appeared 0 times in the rendered HTML of home, about, products and contact;
   `entry-content` appeared once on each. */
.entry-content input[type="text"],
.entry-content input[type="email"],
.entry-content input[type="tel"],
.entry-content input[type="number"],
.entry-content input[type="search"],
.entry-content input[type="url"],
.entry-content select,
.entry-content textarea,
.mofe-field {
  width: 100%;
  min-height: 48px;
  padding: 11px 14px;
  font-family: var(--wp--preset--font-family--sans);
  font-size: var(--wp--preset--font-size--medium);
  color: var(--wp--preset--color--ink);
  background: var(--wp--preset--color--card);
  /*
     steel-400 (#7C8894), not line (#D6DDE3).

     WCAG 1.4.11 asks for 3:1 against the adjacent colour for the boundary of a
     user-interface control, and for a text field that boundary is the *entire*
     affordance — there is no icon, no fill and no label to carry it. Measured
     against the card surface, `--line` is 1.37:1: below the floor, and low
     enough that the field reads as a blank area rather than as something to
     type in. `--line-strong` is 2.13:1 and also fails. steel-400 is 3.62:1 on
     the card and 3.26:1 on the alternate surface, so it holds on both.

     tools/validate.py check_non_text_contrast() computes all three pairs from
     theme.json rather than trusting this comment. */
  border: 1px solid var(--wp--preset--color--steel-400);
  border-radius: var(--wp--custom--radius--sm);
  transition:
    border-color var(--wp--custom--motion--fast) var(--wp--custom--motion--ease-in-out),
    box-shadow   var(--wp--custom--motion--fast) var(--wp--custom--motion--ease-in-out);
}
.mofe-field:focus,
.entry-content input:focus,
.entry-content select:focus,
.entry-content textarea:focus {
  outline: none;
  border-color: var(--wp--preset--color--brand);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--wp--preset--color--brand) 18%, transparent);
}
.mofe-field[aria-invalid="true"] {
  border-color: var(--wp--preset--color--stop);
}
.mofe-field[aria-invalid="true"]:focus {
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--wp--preset--color--stop) 18%, transparent);
}

/* The same two states for a field the editor placed in the content.
   A Contact Form 7 form is a shortcode inside a paragraph, so its inputs are
   core markup inside `.entry-content` and never carry `.mofe-field`. Without
   these, the theme drew a red border on its own invalid fields and left the
   contact form's invalid ones identical to the valid ones — the error message
   was the only signal, and 1.4.1 asks for more than one. */
.entry-content input[aria-invalid="true"],
.entry-content select[aria-invalid="true"],
.entry-content textarea[aria-invalid="true"] {
  border-color: var(--wp--preset--color--stop);
}

/* Contact Form 7's submit control.
   CF7 renders it as <input type="submit" class="wpcf7-submit">, which is not a
   <button>, is not `.mofe-btn`, and matches none of the theme's button rules.
   The result was a contact form whose only control was the browser's stock grey
   box sitting under the theme's navy "Get a quote" button — the same page
   showing two buttons that look like they come from two different sites.
   Both shapes are covered, because a form whose markup someone has hand-edited
   may render a <button> instead. */
.entry-content input[type="submit"],
.entry-content button[type="submit"] {
  /* Not `width: 100%` from the field group above — a submit control is a
     button, and a full-width one on a desktop form reads as a mistake. */
  width: auto;
  min-height: 48px;
  padding: 14px 22px;
  border: 0;
  border-radius: var(--wp--custom--radius--sm);
  background: var(--wp--preset--color--brand);
  color: #FFFFFF;
  font-family: var(--wp--preset--font-family--sans);
  font-size: var(--wp--preset--font-size--small);
  font-weight: 620;
  line-height: 1;
  cursor: pointer;
  transition: background-color var(--wp--custom--motion--fast) var(--wp--custom--motion--ease-in-out);
}
.entry-content input[type="submit"]:hover,
.entry-content button[type="submit"]:hover {
  background: var(--wp--preset--color--brand-deep);
  color: #FFFFFF;
}
/* CF7 puts its validation message under the field and styles it with its own
   default red, which is not this palette's `stop` and fails contrast on the
   card surface. */
.entry-content .wpcf7-not-valid-tip {
  margin-top: 6px;
  font-size: var(--wp--preset--font-size--x-small);
  color: var(--wp--preset--color--stop);
}
.entry-content .wpcf7-response-output {
  margin: var(--wp--preset--spacing--50) 0 0;
  padding: var(--wp--preset--spacing--40);
  border-radius: var(--wp--custom--radius--sm);
  font-size: var(--wp--preset--font-size--small);
}
.mofe-field-label {
  display: block;
  margin-bottom: 6px;
  font-size: var(--wp--preset--font-size--small);
  font-weight: 600;
}
.mofe-field-hint {
  display: block;
  margin-bottom: 6px;
  font-size: var(--wp--preset--font-size--x-small);
  line-height: 1.45;
  color: var(--wp--preset--color--steel-500);
}
.mofe-field-error {
  display: flex;
  gap: 6px;
  align-items: flex-start;
  margin-top: 6px;
  font-size: var(--wp--preset--font-size--x-small);
  line-height: 1.45;
  color: var(--wp--preset--color--stop);
}
.mofe-sr {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* ---------- Template parts ---------- */
.mofe-header { border-bottom: 1px solid var(--wp--preset--color--line); }
.mofe-footer { border-top: 1px solid var(--wp--preset--color--line); }

/*
   TWO UTILITY CLASSES USED TO LIVE HERE, AND BOTH ARE GONE.

       .mofe-hide-mobile { display: block; }
       .mofe-hide-desktop { display: none; }
       @media (max-width: 860px) { .mofe-hide-mobile { display: none; } ... }

   `.mofe-hide-desktop` had no consumer at all: no template and no pattern
   emitted it, and it was not registered as a block style either — so the
   comment above it promising that "a non-technical editor applies these by
   ticking a box in the sidebar" described a feature that did not exist.

   `.mofe-hide-mobile` had two, and neither was doing anything. On the header
   tagline it duplicated `.mofe-brand__tagline { display: none }`, which the
   mobile block already declares. On the header search button it lost on
   specificity and was therefore inert: the button also carries `.mofe-icon-
   button`, and the guard that makes that button work is `.has-js .mofe-icon-
   button`, which is two classes to the utility's one. Reordering the file does
   not help — specificity beats source order, which is exactly the trap that
   made the bug survive review the first time.

   The fix is not to make the utility stronger. It is that the search button
   has three states and a one-class utility cannot express three states: hidden
   with no script, hidden on a phone, shown on a desktop with script. They are
   declared on the component itself, in one place. A blunt override applied
   from 300 lines away is not a utility, it is a coupling.
*/

/* Navigation: sentence case, current page marked by surface not just colour.
   `.mofe-nav__list` is the wp_nav_menu() output in the PHP header; the
   `.wp-block-navigation` selectors apply to a Navigation block an editor
   places inside content. Grouped so both states are identical by construction
   rather than by remembering to update two places. */
.wp-block-navigation .wp-block-navigation-item__content,
.mofe-nav__list a,
.mofe-footer__menu a {
  padding: 10px 12px;
  border-radius: var(--wp--custom--radius--sm);
  font-size: var(--wp--preset--font-size--small);
  font-weight: 500;
  color: var(--wp--preset--color--steel-700);
  text-decoration: none;
  transition:
    color var(--wp--custom--motion--fast) var(--wp--custom--motion--ease-in-out),
    background-color var(--wp--custom--motion--fast) var(--wp--custom--motion--ease-in-out);
}
.wp-block-navigation .wp-block-navigation-item__content:hover,
.mofe-nav__list a:hover,
.mofe-footer__menu a:hover {
  color: var(--wp--preset--color--ink);
  background: var(--wp--preset--color--paper-alt);
}
.wp-block-navigation .current-menu-item > .wp-block-navigation-item__content,
.mofe-nav__list .current-menu-item > a,
.mofe-nav__list .current_page_item > a {
  color: var(--wp--preset--color--brand);
  background: var(--wp--preset--color--brand-soft);
}

/* ---------- Icon system: one grid, one stroke weight ---------- */
.mofe-icon {
  width: 20px;
  height: 20px;
  flex: 0 0 auto;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
  fill: none;
}
.mofe-icon--lg { width: 26px; height: 26px; stroke-width: 1.5; }

/* ---------- Images in dark mode: keep photography wearable ---------- */
@media (prefers-color-scheme: dark) {
  .wp-block-image img,
  .wp-block-post-featured-image img {
    filter: brightness(0.94) contrast(1.02);
  }
}

/* ---------- Dark style variation frame ---------- */
.is-style-on-dark .wp-block-image img,
.is-style-on-dark img {
  filter: brightness(0.94) contrast(1.02);
}

/* ---------- Table of contents (generated by [mofe_toc]) ---------- */
.mofe-toc {
  border: 1px solid var(--wp--preset--color--line);
  border-radius: var(--wp--custom--radius--md);
  padding: var(--wp--preset--spacing--50);
  background: var(--wp--preset--color--card);
}
.mofe-toc__title {
  font-size: var(--wp--preset--font-size--small);
  font-weight: 650;
  letter-spacing: 0.02em;
  margin: 0 0 var(--wp--preset--spacing--40);
}
.mofe-toc__list {
  margin: 0;
  padding: 0;
  list-style: none;
  counter-reset: toc;
}
.mofe-toc__item { margin: 0; }
.mofe-toc__item + .mofe-toc__item { margin-top: var(--wp--preset--spacing--20); }
.mofe-toc__item a {
  color: var(--wp--preset--color--steel-700);
  text-decoration: none;
  font-size: var(--wp--preset--font-size--small);
  line-height: 1.5;
}
.mofe-toc__item a:hover {
  color: var(--wp--preset--color--brand);
  text-decoration: underline;
}
.mofe-toc__item--h3 { padding-left: var(--wp--preset--spacing--50); }

/* ---------- Long-form reading polish ---------- */
.entry-content h2 { margin-top: var(--wp--preset--spacing--70); }
.entry-content h3 { margin-top: var(--wp--preset--spacing--60); }
.entry-content > * + * { margin-top: var(--wp--preset--spacing--50); }
.entry-content ul,
.entry-content ol { padding-left: var(--wp--preset--spacing--50); }
.entry-content li + li { margin-top: var(--wp--preset--spacing--20); }
.entry-content :is(img, figure, table) { margin-block: var(--wp--preset--spacing--60); }

/* The one legitimate case for hanging a first-line indent: none.
   Left-aligned, ragged right, no indent. */

/* ---------- Emphasis inside a headline --------------------------------------
   Borrowed from a reference theme whose landing page underlines the
   load-bearing words in its hero headline ("Have enough courage to start...",
   with the key phrase carried by an accent underline rather than a colour
   change). It is a cheap device that does real work on a B2B page: it points
   at the number or the promise a buyer is scanning for.

   Implemented on <mark> rather than a custom class, because <mark> is what
   the toolbar's Highlighter button produces. Editors reach it without being
   taught a class name, which is the whole reason it is worth having.

   The default yellow marker is removed: a tape highlight fights navy, fails
   against our pale greys, and is invisible in dark mode. An underline in the
   brand colour survives both themes, scales with the type, and does not rely
   on colour alone — the underline is the signal. */
mark {
  background-color: transparent;
  color: inherit;
  text-decoration-line: underline;
  text-decoration-color: var(--wp--preset--color--brand);
  text-decoration-thickness: 0.1em;
  text-underline-offset: 0.16em;
  text-decoration-skip-ink: none;
}
/* In a headline the underline is the only job the mark has, so give it weight. */
:is(h1, h2, h3) mark {
  text-decoration-thickness: 0.14em;
  text-underline-offset: 0.12em;
}
/* On a navy band the brand colour would disappear into the background. */
:is(.is-style-on-dark, .has-brand-background-color) mark {
  text-decoration-color: currentColor;
}

/* ---------- Comments ---------------------------------------------------------
   The comments area covers all 124 posts, so it gets real styling rather than
   whatever core ships with. Kept deliberately quiet: the discussion should not
   compete with the article above it. */
.wp-block-comments { --mofe-avatar: 44px; }

.wp-block-comments-title { margin-bottom: var(--wp--preset--spacing--60); }

.mofe-comment {
  padding-top: var(--wp--preset--spacing--50);
  border-top: 1px solid var(--wp--preset--color--line-soft);
}
.mofe-comment:first-child { border-top: 0; padding-top: 0; }

.mofe-comment-body {
  margin-top: var(--wp--preset--spacing--40);
  max-width: var(--wp--custom--measure--prose);
}
.mofe-comment-body > * + * { margin-top: var(--wp--preset--spacing--40); }

/* Replies. core/comment-template nests them in <ol class="children">. */
.wp-block-comment-template .children {
  list-style: none;
  margin: var(--wp--preset--spacing--60) 0 0;
  padding-left: var(--wp--preset--spacing--50);
  border-left: 1px solid var(--wp--preset--color--line-soft);
}
@media (min-width: 700px) {
  .wp-block-comment-template .children { padding-left: var(--wp--preset--spacing--70); }
}

.wp-block-comments-pagination { margin-top: var(--wp--preset--spacing--70); }
.wp-block-comments-pagination a { text-decoration: none; }

/* The reply form. Its fields are styled by the form rules above; this only
   fixes the heading sitting flush against the last comment. */
.wp-block-post-comments-form { margin-top: var(--wp--preset--spacing--70); }
.wp-block-post-comments-form .comment-reply-title { margin-bottom: var(--wp--preset--spacing--50); }
.wp-block-post-comments-form :is(p, .comment-form-comment) + .form-submit {
  margin-top: var(--wp--preset--spacing--50);
}

/* ---------- Search results --------------------------------------------------- */
.wp-block-query-total { color: var(--wp--preset--color--steel-500); }
.wp-block-query-title em { font-style: normal; color: var(--wp--preset--color--brand); }

/* ============================================================
   CLASSIC THEME LAYOUT LAYER
   ------------------------------------------------------------
   Everything above this line was written for block markup and applies
   unchanged to article content and to the editor canvas. Everything below
   exists only because the active theme is a classic PHP theme: the page
   container, the header and footer, the archive and card grids, and the few
   interactive pieces the templates emit.

   Values below still come from theme.json. Where a literal appears it is one
   of three things, and each says which:
     - a hit-area minimum (a contract with a human finger, not a design token)
     - a measured correction no preset covers
     - a duplicate of a theme.json value that tools/validate.py checks matches
   ============================================================ */

/* ---------- Page container ---------------------------------------------------
   THE single source of page gutters.
   `max-width` mirrors settings.layout.wideSize in theme.json (1240px) — a
   deliberate duplicate, asserted identical by tools/validate.py
   check_container_width(), so the two cannot drift apart silently. */
.mofe-container {
  width: 100%;
  max-width: 1240px;
  margin-inline: auto;
  padding-inline: var(--wp--preset--spacing--50);
}

/* The layout is a column so that a sticky footer and a full-height hero both
   behave without magic numbers. */
#page.wp-site-blocks {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}
.mofe-main { flex: 1 0 auto; }

/* ---------- Skip link ------------------------------------------------------
   Off-screen rather than display:none, because a display:none link is not
   focusable and the whole point is that it is the first focusable thing on
   the page. */
.mofe-skip {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 100;
  padding: 12px 18px;
  background: var(--wp--preset--color--card);
  color: var(--wp--preset--color--ink);
  border-radius: var(--wp--custom--radius--sm);
  box-shadow: var(--wp--preset--shadow--e-2);
  font-weight: 600;
}
.mofe-skip:focus {
  left: var(--wp--preset--spacing--50);
  top: var(--wp--preset--spacing--50);
}

/* ---------- Header --------------------------------------------------------- */
.mofe-header {
  border-bottom: 1px solid var(--wp--preset--color--line);
  background: var(--wp--preset--color--paper);
  position: relative;
  z-index: 10;
}
.mofe-header__inner {
  display: flex;
  align-items: center;
  gap: var(--wp--preset--spacing--40);
  min-height: 72px; /* 72px = brand height + 2 x 12px, measured, not a token */
}

.mofe-brand { margin-inline-end: auto; }
.mofe-brand__link {
  display: flex;
  flex-direction: column;
  gap: 2px;
  text-decoration: none;
  color: inherit;
}
.mofe-brand__name {
  font-size: var(--wp--preset--font-size--x-large);
  font-weight: 700;
  letter-spacing: -0.02em;
  color: var(--wp--preset--color--ink);
}
.mofe-brand__tagline {
  font-size: var(--wp--preset--font-size--x-small);
  line-height: 1.3;
  color: var(--wp--preset--color--steel-500);
}
.custom-logo { max-height: 44px; width: auto; }

.mofe-header__actions {
  display: flex;
  align-items: center;
  gap: var(--wp--preset--spacing--20);
}

/* The quote button is the one filled button outside article content, so it
   carries the brand fill that theme.json gives a block button. */
.mofe-btn--primary,
.mofe-btn--quote {
  background: var(--wp--preset--color--brand);
  color: #FFFFFF;
  padding: 14px 22px;
  font-size: var(--wp--preset--font-size--small);
  line-height: 1;
  text-decoration: none;
}
.mofe-btn--primary:hover,
.mofe-btn--quote:hover {
  background: var(--wp--preset--color--brand-deep);
  color: #FFFFFF;
}

/* The header search toggle has three states, so it declares all three here.

     no script              → hidden. The panel it controls is revealed by
                              script, so without script the button would be a
                              control that does nothing, advertising a field
                              that is permanently absent.
     script, phone          → hidden. The header row on a 360px screen is
                              brand + menu toggle + "Get a quote"; a fourth
                              48px target makes it wrap. The search form stays
                              reachable from the 404 page and the empty state.
     script, wider          → shown.

   The old comment in assets/js/mofe.js claimed a `.has-js` guard already
   existed here. It did not. What existed was a `.mofe-hide-mobile` class that
   lost on specificity and therefore did nothing, on any viewport, with or
   without script — see the note above. */
.mofe-icon-button {
  display: none;
  align-items: center;
  justify-content: center;
  width: 48px;
  height: 48px;
  padding: 0;
  background: transparent;
  border: 1px solid var(--wp--preset--color--line);
  border-radius: var(--wp--custom--radius--sm);
  color: var(--wp--preset--color--steel-700);
  cursor: pointer;
}
.has-js .mofe-icon-button { display: inline-flex; }
.mofe-icon-button:hover {
  background: var(--wp--preset--color--paper-alt);
  color: var(--wp--preset--color--ink);
}
.mofe-icon-button[aria-expanded="true"] {
  background: var(--wp--preset--color--brand-soft);
  border-color: var(--wp--preset--color--brand);
  color: var(--wp--preset--color--brand);
}

/* The nav toggle is display:none by default. It is only ever revealed inside
   `.has-js` at the mobile breakpoint, so with scripting off the menu is a
   plain visible list and nothing is hidden behind a dead button. */
.mofe-nav-toggle {
  display: none;
  align-items: center;
  justify-content: center;
  width: 48px;
  height: 48px;
  padding: 0;
  background: transparent;
  border: 1px solid var(--wp--preset--color--line);
  border-radius: var(--wp--custom--radius--sm);
  color: var(--wp--preset--color--ink);
  cursor: pointer;
}
.mofe-nav-toggle[aria-expanded="true"] {
  background: var(--wp--preset--color--brand-soft);
  border-color: var(--wp--preset--color--brand);
  color: var(--wp--preset--color--brand);
}

.mofe-nav__list {
  display: flex;
  align-items: center;
  gap: 2px;
  margin: 0;
  padding: 0;
  list-style: none;
}
.mofe-nav__list > li { position: relative; }

/* Second-level menus. Opened by :hover and by :focus-within, so a keyboard
   user reaches them by tabbing — no script, and no aria-expanded to keep in
   sync with a state that CSS is already expressing. */
.mofe-nav__list .sub-menu {
  position: absolute;
  inset-inline-start: 0;
  top: 100%;
  z-index: 30;
  display: none;
  flex-direction: column;
  gap: 2px;
  min-width: 220px;
  margin: 0;
  padding: var(--wp--preset--spacing--20);
  list-style: none;
  background: var(--wp--preset--color--card);
  border: 1px solid var(--wp--preset--color--line);
  border-radius: var(--wp--custom--radius--sm);
  box-shadow: var(--wp--preset--shadow--e-3);
}
.mofe-nav__list > li:hover > .sub-menu,
.mofe-nav__list > li:focus-within > .sub-menu { display: flex; }
.mofe-nav__list .sub-menu a { font-size: var(--wp--preset--font-size--small); }

.mofe-header__search {
  border-top: 1px solid var(--wp--preset--color--line-soft);
  padding-block: var(--wp--preset--spacing--40);
  background: var(--wp--preset--color--card);
}
/* Visible by default, collapsed ONLY under `.has-js` — the same direction as
   the navigation, and for the same reason: the state with no script is the
   working state, not a degraded one.

   The panel used to carry a server-rendered `hidden` attribute that only
   JavaScript could remove. With script off, the search field existed in the
   DOM, the toggle button pointed at it with aria-controls, and it could never
   be opened — an element that is present, announced, and unreachable. */
.has-js .mofe-header__search { display: none; }
.has-js .mofe-header__search.is-open { display: block; }

@media (max-width: 860px) {
  .mofe-header__inner { flex-wrap: wrap; padding-block: var(--wp--preset--spacing--30); }

  .has-js .mofe-nav-toggle { display: inline-flex; }

  /* The second of the search toggle's three states. See .mofe-icon-button
     above for why this is not a utility class. */
  .has-js .mofe-icon-button { display: none; }

  /* Order: brand, toggle, actions on the first row; the menu drops below when
     opened. flex-basis:100% is what makes it take its own row. */
  .has-js .mofe-nav {
    display: none;
    flex-basis: 100%;
    width: 100%;
    order: 10;
  }
  .has-js .mofe-nav.is-open { display: block; }

  .mofe-nav__list { flex-direction: column; align-items: stretch; }

  /* 12px of vertical padding plus a 17px line gives a 44px row, which is the
     touch-target floor. Raising it here rather than shrinking the type. */
  .mofe-nav__list a { padding-block: 12px; }

  .mofe-nav__list .sub-menu {
    position: static;
    display: flex;
    border: 0;
    box-shadow: none;
    background: transparent;
    padding-inline-start: var(--wp--preset--spacing--50);
  }

  .mofe-brand__tagline { display: none; }
}

/* ---------- Archive shells -------------------------------------------------- */
.mofe-archive { padding-block: var(--wp--preset--spacing--70); }

.mofe-archive__header { margin-bottom: var(--wp--preset--spacing--70); }

/* Wayfinding, not the page's subject, so it is set one step down from the
   article title it sits above. Same element, different job. */
.mofe-archive__title {
  font-size: var(--wp--preset--font-size--xx-large);
  margin: 0;
}
.mofe-archive__note {
  margin-top: var(--wp--preset--spacing--40);
  max-width: var(--wp--custom--measure--lede);
  color: var(--wp--preset--color--steel-700);
}
.mofe-archive__count {
  margin-top: var(--wp--preset--spacing--30);
  font-size: var(--wp--preset--font-size--small);
}
.mofe-archive__search { margin-bottom: var(--wp--preset--spacing--60); }

/* ---------- Grid + cards ---------------------------------------------------- */
.mofe-grid {
  display: grid;
  gap: var(--wp--preset--spacing--60);
  align-items: stretch;
}
/* auto-fill with a minmax floor means one column on a phone and three on a
   desktop with no breakpoints to maintain. min(100%, 320px) stops the track
   from overflowing a narrow viewport. */
.mofe-grid--3 { grid-template-columns: repeat(auto-fill, minmax(min(100%, 320px), 1fr)); }
/* A `.mofe-grid--products` modifier (260px floor, for narrower product cards)
   was removed in 3.0.0 because the loop that would have selected it never
   served the product branch — so the rule could not match anything.

   It stays removed now that the branch *is* the theme's own. The catalogue
   grid is 320px tracks and yields three columns at the container width, which
   is the width `.mofe-card--product` was designed for; a second floor would be
   a modifier with no problem left to solve. `min(100%, 320px)` is what keeps a
   single track from overflowing a narrow viewport. */

.mofe-card {
  display: flex;
  flex-direction: column;
  margin: 0;
  list-style: none;
}
/* `.is-style-card` supplies the surface, radius and shadow; override only the
   padding, because a card whose image must reach the edge cannot have inner
   padding on the card itself. `overflow: hidden` is what clips the image to
   the radius. */
.mofe-card.is-style-card {
  padding: 0;
  overflow: hidden;
}
.mofe-card__media {
  display: block;
  aspect-ratio: 16 / 10;
  overflow: hidden;
  background: var(--wp--preset--color--paper-alt);
}
/* The shape MOFE photographs products in — see add_image_size() in
   inc/setup.php, which is the same 4:5. */
.mofe-card__media--product { aspect-ratio: 4 / 5; }
.mofe-card__image,
.mofe-card__media img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.mofe-card__body {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  gap: var(--wp--preset--spacing--30);
  padding: var(--wp--preset--spacing--50);
}
.mofe-card__eyebrow {
  margin: 0;
  font-size: var(--wp--preset--font-size--x-small);
  font-weight: 650;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--wp--preset--color--steel-500);
}
.mofe-card__title {
  font-size: var(--wp--preset--font-size--x-large);
  font-weight: 640;
  line-height: 1.24;
  letter-spacing: -0.012em;
  margin: 0;
}
.mofe-card__title a { color: inherit; text-decoration: none; }
.mofe-card__title a:hover { color: var(--wp--preset--color--brand); text-decoration: underline; }
.mofe-card__excerpt {
  margin: 0;
  font-size: var(--wp--preset--font-size--small);
  line-height: 1.6;
  color: var(--wp--preset--color--steel-700);
}
/* margin-block-start:auto pins the last line to the bottom of the card, so
   meta rows and spec lines line up across a row of unequal cards. */
.mofe-card__meta {
  margin: auto 0 0;
  font-size: var(--wp--preset--font-size--x-small);
  color: var(--wp--preset--color--steel-500);
}
.mofe-card__spec {
  margin: auto 0 0;
  color: var(--wp--preset--color--steel-700);
}
.mofe-card__spec--empty {
  font-family: var(--wp--preset--font-family--sans);
  font-size: var(--wp--preset--font-size--small);
  color: var(--wp--preset--color--steel-400);
}

/* ---------- Chips ----------------------------------------------------------
   Used for the product-group filter strip and article categories. A chip is a
   link, so it is a link semantically — not a div with a click handler. */
.mofe-chip {
  display: inline-flex;
  align-items: center;
  min-height: 36px;
  padding: 6px 14px;
  border: 1px solid var(--wp--preset--color--line);
  border-radius: var(--wp--custom--radius--pill);
  background: var(--wp--preset--color--card);
  color: var(--wp--preset--color--steel-700);
  font-size: var(--wp--preset--font-size--small);
  font-weight: 500;
  text-decoration: none;
  transition: background-color var(--wp--custom--motion--fast) var(--wp--custom--motion--ease-in-out);
}
.mofe-chip:hover {
  background: var(--wp--preset--color--paper-alt);
  color: var(--wp--preset--color--ink);
}
.mofe-chip.is-current {
  background: var(--wp--preset--color--brand-soft);
  border-color: var(--wp--preset--color--brand);
  color: var(--wp--preset--color--brand);
  font-weight: 600;
}

.mofe-filters { margin-bottom: var(--wp--preset--spacing--60); }
.mofe-filters__list {
  display: flex;
  flex-wrap: wrap;
  gap: var(--wp--preset--spacing--20);
  margin: 0;
  padding: 0;
  list-style: none;
}

/* ---------- Breadcrumbs ---------------------------------------------------- */
.mofe-crumbs { margin-bottom: var(--wp--preset--spacing--40); }
.mofe-crumbs__list {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--wp--preset--spacing--20);
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: var(--wp--preset--font-size--x-small);
  color: var(--wp--preset--color--steel-500);
}
.mofe-crumbs__item + .mofe-crumbs__item::before {
  content: "/";
  margin-inline-end: var(--wp--preset--spacing--20);
  color: var(--wp--preset--color--steel-400);
}
.mofe-crumbs__item a { color: var(--wp--preset--color--steel-500); text-decoration: none; }
.mofe-crumbs__item a:hover { color: var(--wp--preset--color--brand); text-decoration: underline; }
.mofe-crumbs__item[aria-current="page"] span { color: var(--wp--preset--color--ink); }

/* ---------- Entry (article) ------------------------------------------------- */
.mofe-entry,
.mofe-page { padding-block: var(--wp--preset--spacing--70); }

.mofe-entry__header { margin-bottom: var(--wp--preset--spacing--60); }
.mofe-entry__title { margin: 0 0 var(--wp--preset--spacing--40); }
.mofe-entry__meta {
  margin: 0;
  font-size: var(--wp--preset--font-size--x-small);
  color: var(--wp--preset--color--steel-500);
}
.mofe-entry__eyebrow {
  display: flex;
  flex-wrap: wrap;
  gap: var(--wp--preset--spacing--20);
  margin: var(--wp--preset--spacing--40) 0 0;
}
.mofe-entry__media {
  margin: 0 0 var(--wp--preset--spacing--70);
  border-radius: var(--wp--custom--radius--md);
  overflow: hidden;
}
.mofe-entry__image,
.mofe-entry__media img {
  display: block;
  width: 100%;
  height: auto;
}
.mofe-entry__footer {
  margin-top: var(--wp--preset--spacing--70);
  padding-top: var(--wp--preset--spacing--50);
  border-top: 1px solid var(--wp--preset--color--line);
}
.mofe-entry__tags a {
  display: inline-block;
  margin: 0 var(--wp--preset--spacing--20) var(--wp--preset--spacing--20) 0;
  padding: 4px 12px;
  border-radius: var(--wp--custom--radius--pill);
  background: var(--wp--preset--color--paper-alt);
  color: var(--wp--preset--color--steel-700);
  font-size: var(--wp--preset--font-size--x-small);
  text-decoration: none;
}
.mofe-entry__tags a:hover { background: var(--wp--preset--color--brand-soft); color: var(--wp--preset--color--brand); }

.mofe-page__header { margin-bottom: var(--wp--preset--spacing--60); }
.mofe-page__title { margin: 0; }

.mofe-meta__sep { margin-inline: 6px; color: var(--wp--preset--color--steel-400); }

/* ---------- Related / pagination / empty ------------------------------------ */
.mofe-related { margin-top: var(--wp--preset--spacing--80); }
.mofe-related__title { margin: 0 0 var(--wp--preset--spacing--60); }

.mofe-pagination { margin-top: var(--wp--preset--spacing--70); }
.mofe-pagination .nav-links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--wp--preset--spacing--20);
  justify-content: center;
}
.mofe-pagination .page-numbers {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* 44px, not a token: this is the touch-target floor for a pager. */
  min-width: 44px;
  min-height: 44px;
  padding-inline: 12px;
  border: 1px solid var(--wp--preset--color--line);
  border-radius: var(--wp--custom--radius--sm);
  color: var(--wp--preset--color--steel-700);
  text-decoration: none;
  font-size: var(--wp--preset--font-size--small);
}
.mofe-pagination .page-numbers:hover { background: var(--wp--preset--color--paper-alt); color: var(--wp--preset--color--ink); }
.mofe-pagination .page-numbers.current {
  background: var(--wp--preset--color--brand);
  border-color: var(--wp--preset--color--brand);
  color: #FFFFFF;
}

.mofe-empty { padding-block: var(--wp--preset--spacing--70); }
.mofe-empty__title { margin: 0 0 var(--wp--preset--spacing--40); }
.mofe-empty__search { margin-block: var(--wp--preset--spacing--60); max-width: 560px; }
.mofe-empty__actions { margin-top: var(--wp--preset--spacing--60); }

.mofe-page-links { margin-top: var(--wp--preset--spacing--60); font-size: var(--wp--preset--font-size--small); }
.mofe-page-links__label { font-weight: 600; margin-inline-end: var(--wp--preset--spacing--20); }

/* ---------- Search form ----------------------------------------------------- */
.mofe-search__row {
  display: flex;
  gap: var(--wp--preset--spacing--20);
  align-items: stretch;
}
.mofe-search__field { flex: 1 1 auto; }
.mofe-search__submit { flex: 0 0 auto; white-space: nowrap; }
@media (max-width: 560px) {
  .mofe-search__row { flex-direction: column; }
  .mofe-search__submit { width: 100%; }
}

/* ---------- Footer ---------------------------------------------------------- */
.mofe-footer {
  margin-top: var(--wp--preset--spacing--80);
  border-top: 1px solid var(--wp--preset--color--line);
  background: var(--wp--preset--color--card);
}
.mofe-footer__grid {
  display: grid;
  gap: var(--wp--preset--spacing--70);
  padding-block: var(--wp--preset--spacing--80);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 240px), 1fr));
}
.mofe-footer__col--brand { grid-column: 1 / -1; }
@media (min-width: 860px) {
  .mofe-footer__grid { grid-template-columns: 1.4fr 1fr 1.2fr; }
  .mofe-footer__col--brand { grid-column: auto; }
}
.mofe-footer__name { margin: 0; font-size: var(--wp--preset--font-size--x-large); font-weight: 700; letter-spacing: -0.02em; }
.mofe-footer__tagline {
  margin: var(--wp--preset--spacing--20) 0 0;
  font-size: var(--wp--preset--font-size--small);
  line-height: 1.55;
  color: var(--wp--preset--color--steel-700);
  max-width: 34ch;
}
.mofe-footer__heading {
  margin: 0 0 var(--wp--preset--spacing--40);
  font-size: var(--wp--preset--font-size--x-small);
  font-weight: 650;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--wp--preset--color--steel-500);
}
.mofe-footer__menu {
  margin: 0;
  padding: 0;
  list-style: none;
}
.mofe-footer__menu a { padding-inline: 0; }
.mofe-footer__contact-link { margin: 0 0 var(--wp--preset--spacing--30); font-size: var(--wp--preset--font-size--small); }
.mofe-footer__cta { font-weight: 600; }
.mofe-footer__hint {
  margin-top: var(--wp--preset--spacing--30);
  font-size: var(--wp--preset--font-size--x-small);
  line-height: 1.5;
  color: var(--wp--preset--color--steel-400);
}
.mofe-footer__widget + .mofe-footer__widget { margin-top: var(--wp--preset--spacing--50); }
.mofe-footer__widget-title { font-size: var(--wp--preset--font-size--small); margin: 0 0 var(--wp--preset--spacing--20); }
.mofe-footer__legal {
  padding-block: var(--wp--preset--spacing--50);
  border-top: 1px solid var(--wp--preset--color--line-soft);
  font-size: var(--wp--preset--font-size--x-small);
  color: var(--wp--preset--color--steel-500);
}
.mofe-footer__legal p { margin: 0; }

/* ---------- Table of contents: current-section marker -----------------------
   Set by assets/js/mofe.js with IntersectionObserver. Without scripting the
   list still works — it just does not highlight. The marker is a left rule
   plus weight, so it does not depend on colour alone. */
.mofe-toc__item a.is-active {
  color: var(--wp--preset--color--brand);
  font-weight: 600;
  box-shadow: inset 2px 0 0 var(--wp--preset--color--brand);
  padding-inline-start: var(--wp--preset--spacing--30);
}

/* ---------- Comments --------------------------------------------------------
   comments.php emits list items rather than the comments block, so these are
   the classic-markup names. The block-markup rules further up are unchanged
   for anyone who inserts a Comments block in a page. */
.mofe-comments { margin-top: var(--wp--preset--spacing--80); }
.mofe-comments__list {
  margin: 0;
  padding: 0;
  list-style: none;
}
.mofe-comments__list .children {
  list-style: none;
  margin: var(--wp--preset--spacing--60) 0 0;
  padding-inline-start: var(--wp--preset--spacing--50);
  border-inline-start: 1px solid var(--wp--preset--color--line-soft);
}
.mofe-comment + .mofe-comment { margin-top: var(--wp--preset--spacing--50); }
.mofe-comment__head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--wp--preset--spacing--20);
  font-size: var(--wp--preset--font-size--x-small);
  color: var(--wp--preset--color--steel-500);
}
.mofe-comment__avatar { border-radius: var(--wp--custom--radius--pill); }
.mofe-comment__author { font-weight: 600; color: var(--wp--preset--color--ink); }
.mofe-comment__body { margin-top: var(--wp--preset--spacing--30); }
.mofe-comment__body > * + * { margin-top: var(--wp--preset--spacing--40); }
.mofe-comment__awaiting {
  display: inline-block;
  margin-top: var(--wp--preset--spacing--20);
  font-size: var(--wp--preset--font-size--x-small);
  color: var(--wp--preset--color--attn);
}
.mofe-comment-form { margin-top: var(--wp--preset--spacing--70); }
.mofe-comment-form .comment-reply-title { margin-bottom: var(--wp--preset--spacing--50); }
.mofe-comment-form__note {
  font-size: var(--wp--preset--font-size--x-small);
  line-height: 1.55;
  color: var(--wp--preset--color--steel-500);
}
@media (min-width: 700px) {
  .mofe-comments__list .children { padding-inline-start: var(--wp--preset--spacing--70); }
}

/* ---------- Centred prose page ----------------------------------------------
   A page that is just typed prose has no block layout of its own, so the
   container is narrowed to the reading measure rather than leaving 17px text
   to run 1240px. A page assembled from patterns does not get this class —
   those sections declare their own width. See template-parts/content-page.php. */
.mofe-prose {
  max-width: var(--wp--custom--measure--prose);
  margin-inline: auto;
}

/* ---------- Product page ----------------------------------------------------
   Rendered by single-mofe_product.php. Two columns when there is room for two:
   the photograph carries the decision, so it takes the left and the
   specification sits beside it. Below that the image stacks above the text,
   which is the order in the DOM already.

   "WHEN THERE IS ROOM" IS NOT ONE NUMBER

   The first draft broke at 900px and that was wrong once a rail existed beside
   it. Work the arithmetic with a 24px gutter, a 260px rail and a 48px grid gap:

     899px viewport   rail collapsed   content 851   media track 357px
     901px viewport   rail present     content 545   media track 219px

   Two pixels narrower per track for every pixel of extra viewport. A single
   breakpoint cannot express "two columns" on a branch whose available width
   depends on whether a second column is already there, so the rail case gets
   its own: 1080px is where the media track is back at ~300px, which is the
   floor a 4:5 product photograph needs to be worth looking at.

   `.mofe-product__layout--text-only` is the third case — a product with no
   featured image yet. Without it the info column would sit in the first track
   of a two-track grid with 400px of nothing beside it.
   -------------------------------------------------------------------------- */
.mofe-product { padding-block: var(--wp--preset--spacing--70); }
.mofe-product__layout {
  display: grid;
  gap: var(--wp--preset--spacing--70);
  align-items: start;
}
@media (min-width: 900px) {
  .mofe-product__layout {
    grid-template-columns: minmax(0, 5fr) minmax(0, 6fr);
    gap: var(--wp--preset--spacing--80);
  }
}
@media (min-width: 900px) and (max-width: 1079px) {
  .mofe-layout--sidebar-left .mofe-product__layout {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--wp--preset--spacing--70);
  }
}
.mofe-product__layout--text-only {
  grid-template-columns: minmax(0, 1fr);
}
.mofe-product__media {
  margin: 0;
  border-radius: var(--wp--custom--radius--md);
  overflow: hidden;
  background: var(--wp--preset--color--paper-alt);
}
.mofe-product__media img { display: block; width: 100%; height: auto; }
/* The grid item holding the title, the specification and the enquiry button.
   `min-width: 0` for the same reason `.mofe-column` needs it: a grid item
   defaults to `min-width: auto`, so one long value in the specification table
   — a model string, an unpadded part number — sets a floor for the whole track
   and pushes the layout wider than the container. `overflow-wrap` is the second
   half: the track is now allowed to be narrow, so something has to tell the
   browser it may break a word that has no space in it. */
.mofe-product__info {
  min-width: 0;
  overflow-wrap: break-word;
}
/* The category, above the title, as a link back up. `.mofe-card__eyebrow` is
   the same idea on a catalogue card but it is not a link there, and reusing
   the class would mean one rule serving two different roles. */
.mofe-product__eyebrow {
  margin: 0 0 var(--wp--preset--spacing--30);
  font-size: var(--wp--preset--font-size--x-small);
  font-weight: 650;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--wp--preset--color--steel-500);
}
.mofe-product__eyebrow a { color: inherit; }
.mofe-product__title { margin: 0 0 var(--wp--preset--spacing--40); }
.mofe-product__summary {
  margin: 0 0 var(--wp--preset--spacing--50);
  color: var(--wp--preset--color--steel-700);
}
.mofe-product__specs { margin-block: var(--wp--preset--spacing--60); }
.mofe-product__cta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--wp--preset--spacing--30);
  align-items: center;
  margin-top: var(--wp--preset--spacing--60);
  padding-top: var(--wp--preset--spacing--50);
  border-top: 1px solid var(--wp--preset--color--line-soft);
}
/* A `.mofe-product__reassure` rule sat here, for the small grey line beside
   the enquiry button on the old WooCommerce single-product template. It is gone
   with the line it styled, and the line is gone on purpose rather than by
   oversight.

   The old one read "We reply within 24 hours with a unit price range and a
   sampling lead time." A theme cannot know that. Shipping a response-time
   promise in a distributable stylesheet means every site that installs it
   makes a commitment it never chose, on its own product pages, in its own
   voice — and it is exactly the kind of copy that survives a redesign because
   it reads as part of the design. If a site wants that sentence it belongs in
   the product description or the quote page, where someone decided to say it.

   What is left is the one action: the button. */


/* ---------- 404 ------------------------------------------------------------- */
.mofe-404__code {
  margin: 0 0 var(--wp--preset--spacing--30);
  font-size: var(--wp--preset--font-size--x-large);
  color: var(--wp--preset--color--steel-400);
}
.mofe-404__routes {
  display: flex;
  flex-wrap: wrap;
  gap: var(--wp--preset--spacing--20);
  margin-block: var(--wp--preset--spacing--50);
}

/* ============================================================
   CATALOGUE BRANCH LAYER
   ------------------------------------------------------------
   Everything here belongs to the product branch — the catalogue index, a
   product category and a single product. None of it can appear on an article
   or a page, because the class that switches it on
   (`.mofe-layout--sidebar-left`) is only returned by inc/layout.php when the
   request is on that branch AND has a rail to show.

   3.0.0 wrote this block to sit on top of WooCommerce's stylesheet. That
   dependency is gone: the theme renders the catalogue with its own loop, its
   own cards and its own specification table, so the rules below are no longer
   "adjustments where a plugin's defaults fight the theme's scale" — they are
   the whole of the catalogue's presentation, and there is no second
   stylesheet to defer to.

   Zero `!important` in this section. tools/validate.py holds an allow-list of
   the two that exist in the file, both above this line.
   ============================================================ */

/* ---------- The two-column frame --------------------------------------------
   The rail is 260px wide. That number is a duplicate of mofe_rail_width() in
   inc/layout.php, and the duplication is deliberate: PHP needs it to compute
   $content_width for oEmbeds, CSS needs it to lay out the grid, and neither
   can read the other at render time. tools/validate.py check_rail_width()
   asserts the two are identical, so the duplicate cannot drift.

   Both children are placed explicitly rather than by source order. Every
   catalogue template prints the content column before the rail, so a keyboard
   user reaches the products before the navigation, and grid placement puts the
   rail on the left anyway. Using `order` to achieve the same visual result
   would move the rail earlier in the tab sequence, which is the thing being
   avoided.

   The rail is a sibling of the content column, not a child, which is why it
   can be positioned at all — and why it sits outside <main> rather than inside
   it. See the note in header.php. */
.mofe-layout--sidebar-left {
  display: grid;
  grid-template-columns: 260px minmax(0, 1fr);
  gap: var(--wp--preset--spacing--70);
  align-items: start;
}
/* The content column inside the layout grid.
   `min-width: 0` because every grid item defaults to `min-width: auto`, which
   lets a single wide element inside it — a specification table, an unbroken
   model string — set a floor for the whole 1fr track and push it wider than
   the grid, overflowing the container. The rule belongs to the column rather
   than to the grid, because it is the column that has a width to defend. */
.mofe-column {
  min-width: 0;
}
.mofe-layout--sidebar-left > .mofe-column {
  grid-column: 2;
  grid-row: 1;
}
.mofe-layout--sidebar-left > .mofe-rail {
  grid-column: 1;
  grid-row: 1;
}

/* Below 900px the rail would leave under 400px for a product card, so the grid
   collapses and the rail moves under the products in source order. It is still
   reachable, just not persistent — a persistent 260px column on a phone is not
   a layout, it is a wall. */
@media (max-width: 900px) {
  .mofe-layout--sidebar-left {
    display: block;
  }
  .mofe-layout--sidebar-left > .mofe-rail {
    margin-top: var(--wp--preset--spacing--70);
    padding-top: var(--wp--preset--spacing--50);
    border-top: 1px solid var(--wp--preset--color--line);
  }
}

/* ---------- Catalogue rail --------------------------------------------------
   Sticky on wide screens so the category list stays reachable down a long
   grid. `max-height` plus `overflow` because 14 categories plus their children
   can exceed a short viewport, and a sticky element taller than its viewport
   simply never un-sticks — it scrolls away and looks broken. */
.mofe-rail {
  position: sticky;
  top: var(--wp--preset--spacing--50);
  max-height: calc(100vh - var(--wp--preset--spacing--80));
  overflow-y: auto;
  overscroll-behavior: contain;
}
.mofe-rail__title {
  margin: 0 0 var(--wp--preset--spacing--40);
  font-size: var(--wp--preset--font-size--x-small);
  font-weight: 650;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--wp--preset--color--steel-500);
}
.mofe-rail__list,
.mofe-rail__list .sub-menu {
  list-style: none;
  margin: 0;
  padding: 0;
}
/* Nested levels indent, and the indent stops at one level because
   sidebar-catalog.php caps wp_nav_menu() at depth 2. */
.mofe-rail__list .sub-menu {
  padding-left: var(--wp--preset--spacing--40);
  margin-top: var(--wp--preset--spacing--20);
}
.mofe-rail__list li + li {
  margin-top: 2px;
}
.mofe-rail__list a {
  display: block;
  padding: 9px var(--wp--preset--spacing--40);
  border-radius: var(--wp--custom--radius--sm);
  color: var(--wp--preset--color--ink-2);
  text-decoration: none;
  line-height: 1.35;
  transition: background-color var(--wp--custom--motion--fast) var(--wp--custom--motion--ease-out),
              color var(--wp--custom--motion--fast) var(--wp--custom--motion--ease-out);
}
.mofe-rail__list a:hover,
.mofe-rail__list a:focus-visible {
  background: var(--wp--preset--color--brand-soft);
  color: var(--wp--preset--color--brand);
  text-decoration: none;
}
/* The current category. `is-current` is emitted by the taxonomy fallback in
   inc/hierarchy.php; `current-menu-item` is what WordPress adds when the rail
   is a menu. Both are needed, because the rail is one or the other. */
.mofe-rail__item.is-current > a,
.mofe-rail__list .current-menu-item > a,
.mofe-rail__list .current-menu-ancestor > a {
  background: var(--wp--preset--color--brand);
  color: #fff;
  font-weight: 620;
}
/* A `.mofe-rail__group` label rule lived here, for "By product type" / "By
   capacity" headings inside the rail's fallback list. It is gone with the
   two-axis fallback it served: the taxonomy rail now walks parent terms and
   their children, so there are no group headings to style, and the selector
   matched nothing. The live audit's rule applies — a rule that cannot match
   reads like a live one. */

/* Shown only to someone who can fix it. A visitor never sees this box. */
.mofe-rail__hint {
  margin-top: var(--wp--preset--spacing--50);
  padding: var(--wp--preset--spacing--40);
  border: 1px dashed var(--wp--preset--color--line);
  border-radius: var(--wp--custom--radius--sm);
  background: var(--wp--preset--color--paper-alt);
  font-size: var(--wp--preset--font-size--x-small);
  color: var(--wp--preset--color--steel-500);
}
.mofe-rail__hint p {
  margin: 0 0 var(--wp--preset--spacing--20);
}
.mofe-rail__hint p:last-child {
  margin-bottom: 0;
}

/* ---------- Specification table --------------------------------------------
   Rendered by inc/hierarchy.php. Two columns: a label the buyer scans and a
   value they read. The label column is narrower and muted so the values carry
   the weight — a specification sheet is read by looking down the right edge.

   No zebra striping. The rows are already separated by a hairline, and stripes
   on top of that produce a table that looks like a spreadsheet rather than a
   spec sheet.

   TWO TABLES, ONE DESIGN. `.mofe-spec` is the table the theme renders from
   product meta. `.is-style-spec` is the editor's own core/table block with the
   "Specification table" style picked, and it is used by the [mofe_spec_table]
   shortcode and by two patterns. Until this revision `is-style-spec` was
   registered in inc/editor.php and had no CSS at all — an editor could choose
   it from the Styles panel and absolutely nothing happened, while the shortcode
   and the patterns rendered an unmarked-up core table. Both shapes are served
   by one set of rules here so the two cannot drift into two spec tables that
   look almost alike.

   `is-style-spec` lands on the `<figure class="wp-block-table">`, one level
   above the table, which is why those selectors reach inside. */
.mofe-spec-block {
  margin-block: var(--wp--preset--spacing--60);
}
.mofe-spec-block__title {
  margin: 0 0 var(--wp--preset--spacing--40);
}
.mofe-spec,
.is-style-spec table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--wp--preset--font-size--small);
}
/* The caption repeats which specification language the table is in. It is
   useful to the buyer and it is also the only place that fact is stated, so it
   is styled as a quiet label rather than hidden. */
.mofe-spec__caption {
  text-align: left;
  padding-bottom: var(--wp--preset--spacing--30);
  font-size: var(--wp--preset--font-size--x-small);
  font-weight: 650;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--wp--preset--color--steel-400);
}
/* Core gives every table cell a 1px solid border all the way round. Stated
   here as `border: 0` before the hairline so the reset and the replacement are
   in one declaration — a spec sheet with a box around every cell reads as a
   spreadsheet, which is the opposite of the intent. */
.mofe-spec th,
.mofe-spec td,
.is-style-spec th,
.is-style-spec td {
  padding: 11px 0;
  border: 0;
  border-bottom: 1px solid var(--wp--preset--color--line-soft);
  text-align: left;
  vertical-align: top;
  line-height: 1.5;
}
.mofe-spec tr:last-child th,
.mofe-spec tr:last-child td,
.is-style-spec tr:last-child th,
.is-style-spec tr:last-child td {
  border-bottom: 0;
}
.mofe-spec th,
.is-style-spec th {
  width: 42%;
  padding-right: var(--wp--preset--spacing--50);
  font-weight: 600;
  color: var(--wp--preset--color--steel-500);
}
.mofe-spec td {
  color: var(--wp--preset--color--ink);
}

/* ---------- Specification chips are gone -------------------------------------
   `.mofe-chips` and `.mofe-chips--card` styled a row of two or three facts
   under a product card, in the slot a price used to occupy. The card now ends
   in `.mofe-card__spec`, which prints the same three values from
   mofe_product_card_summary() as one mono line, and nothing ever emitted a
   `mofe-chips` element -- in PHP or in a pattern. Two rules and a wrapper
   class that no template produced.

   `.mofe-single__cta` / `.mofe-single__cta-note` went the same way: they were
   the enquiry block at the foot of WooCommerce's single-product template, and
   single-mofe_product.php renders `.mofe-product__cta` instead. */

/* ============================================================
   PRINT
   ------------------------------------------------------------
   A B2B buyer prints product pages. They print them to compare three models
   side by side and to take a shortlist to a meeting, and they print them on
   paper, in greyscale, without a mouse.

   Three things break on paper that do not break on screen, and all three are
   fixed here rather than left to the browser's defaults:

     1. The rail, the header actions and the enquiry button are navigation. On
        paper there is nowhere to navigate to, and a column of links wastes a
        quarter of the page.
     2. `.is-style-band-brand` is a navy section with white text. Browsers do
        not print background colours by default, so the white text becomes
        white-on-white -- a section that is present on screen and blank on
        paper. If a site has not enabled background graphics, the section has
        to be re-stated as dark ink on white.
     3. Links. On screen a link is blue and clickable; on paper it is neither,
        and the reader cannot tell a link from text. Only links that carry
        information the reader needs are expanded -- an inline link in the
        article, or the product's enquiry address. Expanding every link would
        put `https://example.com/product-category/tumblers/` after each of the
        fourteen rail items that rule 1 just removed.

   Deliberately not done: hiding the skip link with `display: none`. It is
   already off-screen and costs nothing on paper; removing it would be a rule
   that exists to look thorough.
   ============================================================ */
@media print {
  /* 1. Nothing interactive. */
  .mofe-skip,
  .mofe-header__actions,
  .mofe-nav-toggle,
  .mofe-icon-button,
  .mofe-header__search,
  .mofe-rail,
  .mofe-product__cta,
  .mofe-pagination,
  .mofe-related,
  .mofe-toc {
    display: none;
  }

  /* 2. Brand surfaces become outlines. */
  .is-style-band-brand,
  .is-style-band-alt,
  .is-style-card {
    background: transparent;
    border: 1px solid #000;
    box-shadow: none;
  }
  .is-style-band-brand,
  .is-style-band-brand *,
  .is-style-on-brand,
  .is-style-on-brand *,
  .is-style-on-dark,
  .is-style-on-dark * {
    color: #000;
  }

  /* The night band is the same white-on-dark surface as the brand band,
     for the same reason: browsers print no background colour by default,
     so its white text would come out white-on-white. A new rule rather
     than an edit to the two lists above — those carry the 3.1.0
     accessibility surface and this is a new component joining them, not
     a rewrite of what was already fixed. */
  .is-style-band-night,
  .is-style-band-night * {
    color: #000;
  }
  .is-style-band-night {
    background: transparent;
    border: 1px solid #000;
    box-shadow: none;
  }

  /* 3. The one link worth expanding. */
  .entry-content a[href^="http"]::after,
  .mofe-product__eyebrow a[href]::after {
    content: " (" attr(href) ")";
    font-size: 0.85em;
    word-break: break-all;
  }

  /* Gutters and rhythm: the container loses its max-width so the page uses the
     full sheet, and the layout grid collapses -- the rail it was making room
     for is gone. */
  .mofe-container {
    max-width: none;
    padding-inline: 0;
  }
  .mofe-layout--sidebar-left,
  .mofe-product__layout {
    display: block;
  }
  .mofe-product__media,
  .mofe-card__media {
    max-width: 60mm;
  }
  /* A specification row split across a sheet boundary is unreadable, which is
     the whole reason someone printed it. */
  .mofe-card,
  .mofe-spec tr {
    break-inside: avoid;
  }
}
