/* =========================================================================
   SKP — PAGE TYPE LAYER
   v1 (2026-08-30)   Loaded by skp-page-types.php at priority 35.
   Depends on: skp-theme-tokens (5). Self-sufficient — does NOT require
   skp-design-system (20) or skp-components (30), both of which are gated on
   is_page() and therefore have never applied to the 49 blog posts.

   WHAT THIS OWNS
   Two things, and nothing else:

     1. THE SHELL   — how any page reaches both viewport edges, where its
                      readable column sits, and what happens on mobile.
                      One mechanism, every page, every type.

     2. THE TYPE    — what makes a directory not look like a product page.
                      Expressed ONLY as accent, rhythm, reading measure and
                      heading treatment. Never as a different font, a
                      different palette, or a different button.

   WHY THE DISTINCTION IS DRAWN THERE
   A site reads as one site when the vocabulary is shared and the grammar
   varies. Change the typeface per section and you have a ransom note. Change
   the DENSITY and the COLUMN and you have a magazine that knows the
   difference between a contents page and a feature.

   So: every type gets the same face, the same palette, the same buttons, the
   same focus ring, the same gutter. What changes is how tightly the page is
   packed, how wide the reading column runs, and how a heading announces
   itself. That is enough to tell them apart in under a second and not enough
   to make them look like different websites.

   ---------------------------------------------------------------------
   THE MEASURED PROBLEM THIS REPLACES                        (2026-08-30)

   44 English URLs × 3 viewports:

     on-system      10/44     34 pages had no design-system rule at all
     1920 cap       10/44     34 pages ran uncapped or theme-capped
     mobile-clean   22/44     22 pages overflowed or scrolled horizontally

   Three pages overflowed by exactly 20px at a 390 viewport
   (/products/sunshade-awning-fabric/, /fire-retardant-pvc-mesh/,
   /car-wash-curtains/). Cause, measured on the live DOM:

     div.entry-content   L20  W350      <- Astra .ast-container padding 20px
     nav.skp-lineage     L20  W390      <- width:100vw, margin resolved to 0
                                           => spans 20..410 on a 390 viewport

   .skp-lineage, .skp-product-page and .skp-section all break out with
   `width:100vw; margin-inline: calc(50% - 50vw)`. That formula only lands on
   zero when the PARENT is exactly viewport-wide. Nest it one level deeper
   than expected, or under a padded container, and it misses by exactly the
   padding. It also uses 100vw, which on desktop INCLUDES the scrollbar, so
   the same rule overflows by ~15px on every page that shows one — currently
   masked by `overflow-x: hidden` on <body>, which is not a fix, it is a lid.

   REGION 1 replaces every one of those with a grid whose tracks are bounded
   by the parent. Overflow becomes arithmetically impossible rather than
   suppressed.
   ========================================================================= */


/* =========================================================================
   REGION 0 — TYPE TOKENS
   The only place a type is allowed to differ. If a rule below this region
   names a type, it is because markup differs, not because taste does.
   ========================================================================= */

body.skp-typed {
  /* the readable column for running prose. NOT the same as --skp-inner-cap,
     which caps the BAND. A 1600px band with a 1600px paragraph is unreadable
     at any size — 06 §4 and every typography text ever written. */
  --skp-read-measure: 72ch;
  --skp-read-leading: 1.68;

  /* the head rule: how a section heading announces itself */
  --skp-head-bar-w: var(--skp-accent-bar);
  --skp-head-size: var(--skp-headline-medium);
  --skp-head-case: none;
  --skp-head-track: -0.015em;

  /* vertical rhythm between top-level blocks of legacy prose */
  --skp-flow: var(--skp-space-lg);
  --skp-flow-head: var(--skp-space-2xl);
}

/* ---- ORIENTING PAGES · yellow, loose, wide -------------------------------
   home · directory · hub · company · contact
   These pages exist to tell you where you are and where to go next. They are
   the least dense and the most confident. */

body.skp-type-directory {
  /* A board, not a document. Prose is incidental here — the page is a set of
     routes — so the measure is short and the heading is a LABEL, not a title:
     uppercase and tracked out, the way a wayfinding sign is set. */
  --skp-read-measure: 60ch;
  --skp-head-size: var(--skp-title-large);
  --skp-head-case: uppercase;
  --skp-head-track: 0.14em;
  --skp-flow-head: var(--skp-space-2xl);
}

body.skp-type-hub {
  /* The authoritative parent. Biggest heading on the site after the H1,
     heaviest bar, loosest rhythm. A hub should feel like the plate on the
     machine, not a page about it. */
  --skp-read-measure: 72ch;
  --skp-head-size: var(--skp-headline-large);
  --skp-head-bar-w: 4px;
}

body.skp-type-company {
  --skp-read-measure: 70ch;
  --skp-head-size: var(--skp-headline-medium);
  --skp-head-bar-w: 4px;
}

body.skp-type-contact {
  --skp-read-measure: 62ch;
  --skp-head-bar-w: 4px;
}

/* ---- ANSWERING PAGES · blue, tighter -------------------------------------
   product · resource · article · tool · utility
   These pages exist to answer one question. Denser, narrower, quieter. */

body.skp-type-product {
  --skp-read-measure: 72ch;
  --skp-head-size: var(--skp-headline-medium);
  --skp-head-bar-w: 3px;
}

/* A resource and an article are DOCUMENTS. They are read, not scanned, so
   they get the one thing no other type gets: a genuine reading column with
   longer leading. This is the single clearest signal that you have left the
   catalogue — measured at ~68 characters, which is the middle of the 45–75
   band every legibility study lands on. */
body.skp-type-resource,
body.skp-type-article {
  --skp-read-measure: 68ch;
  --skp-read-leading: 1.72;
  --skp-head-size: var(--skp-headline-small);
  --skp-head-bar-w: 0px;                 /* no bar — see REGION 4 */
  --skp-flow: var(--skp-space-lg);
}

body.skp-type-tool {
  /* the instrument is the content; the chrome gets out of its way */
  --skp-read-measure: 66ch;
  --skp-head-size: var(--skp-headline-small);
  --skp-head-bar-w: 3px;
}

body.skp-type-utility {
  /* legal and transactional. Legible and correct, never marketed.
     No accent, no bar, no eyebrow — 17-PAGE-TYPES §2.10. */
  --skp-read-measure: 68ch;
  --skp-read-leading: 1.72;
  --skp-head-size: var(--skp-headline-small);
  --skp-head-bar-w: 0px;
  --skp-accent: var(--skp-on-surface-variant);
}


/* =========================================================================
   REGION 1 — THE SHELL
   One full-bleed mechanism for the whole site.

   Astra nests the content column like this:
     .site-content > .ast-container(pad 20px, capped) > .content-area
       > main.site-main > article > .entry-content
   so .entry-content is inset and capped before anything of ours runs.

   Step 1 releases the cap so .entry-content is exactly viewport-wide.
   Step 2 rebuilds the gutter and the cap as GRID TRACKS on .entry-content.

   Why a grid and not the negative margin it replaces:
     · tracks are bounded by the parent, so a child cannot exceed the viewport
     · no 100vw, so the desktop scrollbar cannot cause overflow
     · no percentage margin, so nesting depth stops mattering
     · full-bleed and capped children coexist without either knowing the other
       exists — `grid-column: full` vs `content`, and that is the whole API
   ========================================================================= */

/* Step 1 — release Astra's container. Scoped to .site-content so the header
   and footer, which use .ast-container too, are untouched. */
body.skp-typed .site-content > .ast-container {
  max-width: none;
  padding-inline: 0;
}

/* Everything between the container and the content column simply fills. */
body.skp-typed .site-content > .ast-container > .content-area,
body.skp-typed .site-content .content-area > .site-main,
body.skp-typed .site-content .site-main > article,
body.skp-typed .site-content .site-main > .page,
body.skp-typed .site-content .site-main > .post {
  width: 100%;
  max-width: none;
  margin-inline: 0;
  padding-inline: 0;
}

/* Step 2 — the gutter and the cap, as tracks.
   At 1440: gutter 43.2 → content min(1353.6, 1353.6) = 1354
   At 1920: gutter 160  → content min(1600,   1600)   = 1600
   At  390: gutter 18   → content min(366.6,  354)    = 354
   which is exactly the 01-FOUNDATIONS §6.1 contract, with no !important and
   no negative margin anywhere. */
body.skp-typed .entry-content {
  display: grid;
  grid-template-columns:
    [full-start] minmax(var(--skp-gutter), 1fr)
    [content-start] min(var(--skp-inner-cap), 100% - (var(--skp-gutter) * 2)) [content-end]
    minmax(var(--skp-gutter), 1fr) [full-end];
  row-gap: 0;
  width: 100%;
  max-width: none;
  margin-inline: 0;
  padding-inline: 0;
}

body.skp-typed .entry-content > * {
  grid-column: content;
  min-width: 0;          /* grid children default to min-content; without this
                            a wide table forces the track open and the page
                            overflows. This one line is the fix for most of
                            the 22 measured mobile failures. */
}

/* ---- WHERE THE GRID DOES NOT GO -----------------------------------------
   A page whose bands are UAGB root containers sitting DIRECTLY in
   .entry-content already reaches both edges on its own, correctly, and has
   done since 2025. Putting those bands into grid tracks changes the box that
   WordPress's flex column rules resolve `flex-basis: 100%` against, and the
   columns stop stacking.

   Measured on /factories/ at a 390 viewport, with the grid on:
     section.fac-stats        L0    W390   padding 18/18   → content 354
     .wp-block-columns        L18   W354   flex-wrap: wrap, gap 24
     .wp-block-column         L362  W306   flex-basis: 100%
   A 306px column at left 362 inside a 354px wrapping flex container is not a
   width the box model can produce from those inputs — the used main size the
   columns resolved against was ~637px, not the 354px the container reports.
   Bisected by removing one rule at a time: the grid declaration alone
   accounts for all 33 overflowing elements on /factories/, 105 on /about/,
   17 on /certifications/ and 20 on /contact-us/.

   These pages do not need the grid. Their only measured defect was the
   missing 1600 ceiling at 1920, which the direct-child rule below supplies
   without touching their layout at all.

   10-GOVERNANCE, in one line: prefer the constraint that ADDS a limit over
   the rewrite that REPLACES a working mechanism. */
body.skp-typed .entry-content:has(> .wp-block-uagb-container),
body.skp-typed .entry-content:has(> section.wp-block-uagb-container),
body.skp-typed .entry-content:has(> .uagb-is-root-container) {
  display: block;
}

/* The full-bleed set. Anything here spans edge to edge and manages its own
   inner cap — which the component shell already does via .skp-inner. */
body.skp-typed .entry-content > .alignfull,
body.skp-typed .entry-content > main,
body.skp-typed .entry-content > .skp-product-page,
body.skp-typed .entry-content > .skp-resource-page,
body.skp-typed .entry-content > .skp-legacy-product-page,
body.skp-typed .entry-content > .skp-products-directory,
body.skp-typed .entry-content > .skp-section,
body.skp-typed .entry-content > .skp-lineage,
body.skp-typed .entry-content > .skp-applications,
body.skp-typed .entry-content > .wp-block-uagb-container.alignfull,
body.skp-typed .entry-content > .wp-block-group.alignfull,
body.skp-typed .entry-content > .wp-block-cover.alignfull,
/* /factories/ builds its bands as bare <section class="wp-block-uagb-container">
   with no .alignfull, and breaks them out with the same 100vw formula. Left in
   the content track they would compute margin-left: 50% of 1354 − 50vw = −283
   and hang 240px off the left edge. A UAGB root container IS a band. */
body.skp-typed .entry-content > section.wp-block-uagb-container,
body.skp-typed .entry-content > .uagb-is-root-container {
  grid-column: full;
}

/* Neutralise the breakout hacks these tracks replace.
   skp-design-system.css §9 sets width/max-width/margin with !important on
   .skp-product-page and .skp-resource-page, so this must match it. Once those
   pages are all migrated, delete §9 there and this block with it. */
body.skp-typed .entry-content > .skp-product-page,
body.skp-typed .entry-content > .skp-resource-page {
  width: auto !important;
  max-width: none !important;
  margin-inline: 0 !important;
}

/* .skp-section carries the same 100vw + negative-margin pair. Inside a shell
   that is already viewport-wide it is not just redundant, it is the scrollbar
   overflow. A plain block child of a full-bleed parent IS full-bleed. */
body.skp-typed .skp-section,
body.skp-typed .skp-resource-section,
body.skp-typed .skp-lineage,
body.skp-typed .skp-applications {
  width: auto;
  max-width: none;
  margin-inline: 0;
}

/* ---- THE INNER WRAPPER, WHATEVER IT IS CALLED ---------------------------
   The site grew four parallel names for one idea, one per era:

     .skp-inner                          the component shell        (2026-08)
     .skp-resource-inner                 the resource batch         (2026-05)
     .uagb-container-inner-blocks-wrap   the UAGB block pages       (2025)
     — nothing —                         everything older

   Measured on /resource/custom-pvc-tarpaulin-quote-checklist/, all eleven
   .skp-resource-inner wrappers sit at 1354px at a 1440 viewport, which is
   correct, and then run uncapped at 1920 because their max-width is a
   percentage.

   THE CAP APPLIES ONLY DIRECTLY INSIDE A FULL-BLEED BAND, and this is the
   subtle part. --skp-inner-cap is min(94%, 1600px), and 94% is 94% OF THE
   PARENT. Inside a viewport-wide band that is the contract value. Inside an
   already-capped parent it is 94% of an already-correct number — measured on
   /products/, a nested .uagb-container-inner-blocks-wrap took 94% of 1354 and
   rendered at 1272px, shrinking every nested card by 6% for no reason.

   A wrapper cannot know whether its parent is a band. So the default is
   "fill", and the cap is granted by the band, from outside. */
body.skp-typed :is(.skp-inner, .skp-resource-inner, .uagb-container-inner-blocks-wrap) {
  width: 100%;
  max-width: 100%;
  margin-inline: auto;
  box-sizing: border-box;
}

/* Our own bands have zero horizontal padding, so the wrapper owns the gutter
   and takes the full token. */
body.skp-typed :is(.skp-section, .skp-resource-section, .skp-applications) > :is(.skp-inner, .skp-resource-inner, .uagb-container-inner-blocks-wrap) {
  max-width: var(--skp-inner-cap);
}

/* A UAGB band keeps its own padding, so its wrapper takes a CEILING only —
   the same reasoning as the direct-child rule above. Using --skp-inner-cap
   here would take 94% of an already-inset box and lose ~34px at 1440. */
body.skp-typed .wp-block-uagb-container.alignfull > .uagb-container-inner-blocks-wrap,
body.skp-typed :is(main, .entry-content) > .wp-block-uagb-container > .uagb-container-inner-blocks-wrap,
body.skp-typed :is(main, .entry-content) > section > .uagb-container-inner-blocks-wrap {
  max-width: min(1600px, 100%);
}

/* ---- ONE GUTTER, APPLIED ONCE -------------------------------------------
   The gutter is created by the INNER CAP and by nothing else.

   Applying it twice is not a hypothetical: skp-design-system.css §12(b) puts
   `padding-inline: var(--skp-gutter)` on the band, and the inner wrapper then
   caps at 94% of what is left. Measured on /resource/sample-and-order-process/
   at a 390 viewport: band 390 → padding 18+18 → 354 → 94% → 333px, against a
   contract of 354. The page loses 21px of column to a gutter it already had.

   So: bands carry no horizontal padding, and the inner wrapper caps. */
/* Our own bands carry no horizontal padding — the inner wrapper caps.
   .skp-applications is the exception and is NOT in this list: it is the one
   band whose gutter lives in its own padding rather than in an inner cap
   (skp-product-tiers.css:113, `padding: clamp(52px,6vw,88px) var(--skp-gutter)`).
   Zeroing it took the gutter away and left the applications grid running the
   full 1440 with its cards flush to both edges. */
body.skp-typed :is(.skp-section, .skp-resource-section) {
  padding-inline: 0;
}
body.skp-typed .skp-applications {
  padding-inline: var(--skp-gutter);
}

/* ---- LEGACY BANDS: ADD THE CAP, TOUCH NOTHING ELSE ----------------------
   A UAGB band writes its own padding from the editor with !important, and
   that padding is already correct below 1600 — measured, these pages put
   their content at exactly 1354 at a 1440 viewport without any help. The
   ONLY thing they get wrong is that a percentage-based inset never stops
   growing: 1504 at 1920, and 1816 on /about/ before the §12(b) patch.

   So the fix is a ceiling and nothing else. No `width: 100%`, no gutter
   subtraction, no zeroing of the band's padding.

   This is the second attempt. The first zeroed the band padding with
   !important and re-derived the gutter from the child, which is arithmetically
   equivalent and behaviourally not: changing the band's content box from 354
   to 390 changed the containing block that WordPress's own column rules
   resolve against, and four-column blocks stopped stacking on mobile.
   Measured on /factories/ at 390 — a stat column went from
     W354 L18   (stacked, correct)
   to
     W306 L362  (side by side, 360px past the right edge)
   and the layer INTRODUCED a 105-element overflow on /about/, 33 on
   /factories/, 17 on /certifications/ and 20 on /contact-us/. The dry run
   caught all four before they shipped.

   The lesson is in 10-GOVERNANCE terms: a rule that is correct about the
   number it sets can still be wrong about the box it changes. Prefer the
   ceiling that adds a constraint over the rewrite that replaces a mechanism.

   Prose is excluded — a paragraph is not a layout wrapper and takes the
   narrower READING measure from REGION 2. Without the exclusion this rule
   wins on specificity and prose ran at 119ch on /products/. */
body.skp-typed :is(main, .entry-content)
  > :is(.wp-block-uagb-container, .wp-block-group, section)
  > *:not(.uagb-container-inner-blocks-wrap):not(.skp-resource-inner):not(.skp-inner):not(.skp-applications__inner):not(p):not(ul):not(ol):not(dl):not(blockquote),
/* A UAGB ROOT container is a band wherever it sits in the tree, not only as a
   direct child of main. Measured on /products/pvc-tarpaulin/ at 390:
   .uagb-block-furtherread is nested one level deeper, so the rule above missed
   it and its prose ran to 386px on a 390 viewport — no gutter at all. */
body.skp-typed .uagb-is-root-container
  > *:not(.uagb-container-inner-blocks-wrap):not(.skp-resource-inner):not(.skp-inner):not(.skp-applications__inner):not(p):not(ul):not(ol):not(dl):not(blockquote) {
  max-width: min(1600px, 100%);
  margin-inline: auto;
}

/* The applications grid that skp-product-tiers.php appends to every hub.
   Its inner wrapper was never capped, so on /products/industrial-pvc-curtains/
   each card measured the full 1440 and the grid rendered as a single stacked
   column at desktop — a three-up grid that had silently stopped being one. */
body.skp-typed .skp-applications > .skp-applications__inner {
  width: 100%;
  max-width: var(--skp-inner-cap);
  margin-inline: auto;
  box-sizing: border-box;
}

/* /products/industrial-pvc-curtains/ has exactly ONE application child, and a
   one-item grid stretched that card across the full 1354. A lone card is not
   a row — it is a card. Hold it to a third of the grid so a hub with one
   application and a hub with three read as the same component.
   `03 §0` in miniature: the layout should say how many there are. */
body.skp-typed .skp-applications__card:only-child {
  max-width: min(100%, 440px);
}


/* ---- ARTICLES AND ARCHIVES ----------------------------------------------
   The 49 blog posts have never had a single design-system rule applied to
   them: skp-design-system.css and skp-components.css are both gated on
   is_page(). Measured 2026-08-30, every article renders identically wrong:

     band     1019px at a 1440 viewport   (should reach 1440)
     content   933px                      (should be 1354)
     overflow  4 elements, all in the comment form

   Astra's "separate container" wraps the article in a padded white card —
   .ast-article-single carries padding: 5.34em 6.67em, which is where 1440
   becomes 1019 becomes 933. The card is the theme making a magazine-layout
   decision on behalf of a manufacturer's technical library.

   Released, so an article gets the same shell as everything else. The reading
   column is preserved and then some: 68ch at 1.72 leading, from REGION 0.

   Measured cause: `body.ast-right-sidebar` puts the post in a two-column
   layout — .content-area at 1043px with 60px of right padding, beside a
   348px "Recent Posts" widget. That is a magazine decision applied to a
   manufacturer's technical library, and it is why an article is the only
   page type on the site that cannot reach the viewport edge.

   The article goes full width and the widget stacks BELOW it at the same
   cap. Nothing is removed — every link in Recent Posts is still on the page,
   in the same order — it simply stops taking a quarter of the reading width
   on a document whose job is to be read. */
/* The container must stack, not split. `flex: 1 1 100%` on two flex siblings
   inside a nowrap row gives 720 + 720, which measured as an article band of
   719px at a 1440 viewport — narrower than the sidebar layout it replaced.
   Block layout is the whole fix. */
body:is(.skp-type-article, .skp-type-directory) .site-content > .ast-container {
  display: block;
}

/* Not scoped to .ast-separate-container — the posts run
   `ast-plain-container ast-right-sidebar`, and a rule written for the wrong
   container class is a rule that does nothing. */
body:is(.skp-type-article, .skp-type-directory) :is(
  .content-area, #primary, .site-main, .ast-article-single, .ast-article-post
) {
  padding-inline: 0;
  margin-inline: 0;
  max-width: none;
  width: 100%;
  float: none;
}
body:is(.skp-type-article, .skp-type-directory) .ast-article-single {
  padding-block: 0;
}

/* ---- THE FOOT OF AN ARTICLE IS A BAND ----------------------------------
   Measured band coverage — the share of a page's height carried by a
   full-bleed surface that differs from the page:

     product / resource pages    44 – 91 %
     /blog/                       9.3 %
     articles                     0 %

   Zero. An article is a white column on a white page from header to footer,
   which is why it reads as narrow however wide the viewport is. "Full width"
   is not only whether the band spans 1920px — it is whether the eye can SEE
   that it does, and nothing marks an edge that has no colour.

   The prose column stays at 730px, because a 1600px paragraph is unreadable
   and widening it would be the wrong fix. The width comes from BANDING the
   zones either side of the reading column — which is what every other page
   type on the site already does. */
body.skp-type-article :is(.ast-single-related-posts-container, #comments) {
  background: var(--skp-surface-container-low);
  padding-block: var(--skp-section-rhythm);
  border-block-start: 1px solid var(--skp-outline-variant);
}
body.skp-type-article #comments {
  background: var(--skp-surface-container);
}

/* the widget area, stacked and capped like any other content */
body:is(.skp-type-article, .skp-type-directory) :is(#secondary, .widget-area.secondary) {
  width: 100%;
  max-width: var(--skp-inner-cap);
  margin-inline: auto;
  padding-inline: 0;
  float: none;
  flex: 1 1 100%;
}

/* The blog archive is a directory of articles and takes the directory shell.
   Its furniture — hero, filter row, pagination — sits in .content-area, not
   .entry-content, so the grid never reaches it. Measured at 1920: .blog-hero
   uses a flat 60px padding, so its H1 ran to 1800px. Flat padding is exactly
   the unbounded-growth pattern the layout contract forbids (01 §6.1); the cap
   belongs on the child. */
body:is(.skp-type-directory, .skp-type-article) :is(
  .ast-row,
  .site-main > .ast-archive-description,
  .site-main > article,
  .content-area > :is(.blog-hero, .blog-filters, .blog-results-info, .ast-pagination) > *,
  /* Astra's related-posts block, on every article. Same flat-padding pattern:
     42.5px each side, so it measured 1834px at 1920. */
  .ast-single-related-posts-container > *
) {
  width: 100%;
  max-width: var(--skp-inner-cap);
  margin-inline: auto;
}

/* The comment form, on every one of the 49 posts.
   Astra lays it out with .ast-row, a negative-margin grid gutter, inside a
   container that has no matching positive padding. Measured: legend at
   left −20, four elements past the edge. It is the same negative-margin
   failure as the lineage nav, in the theme rather than in our CSS. */
body.skp-typed #comments .ast-row,
body.skp-typed .comment-form .ast-row {
  margin-inline: 0;
  width: 100%;
  max-width: 100%;
}
body.skp-typed #comments {
  width: 100%;
  max-width: var(--skp-inner-cap);
  margin-inline: auto;
  box-sizing: border-box;
}
body.skp-typed #comments :is(input, textarea, select) {
  max-width: 100%;
  box-sizing: border-box;
}
/* Comment furniture lives OUTSIDE .entry-content, so the reading measure in
   REGION 2 never reaches it. Measured on every article at 1920: the
   "Your email address will not be published" note ran to 126ch and the
   comments block to 1834px. Prose is prose wherever it is. */
body.skp-typed #comments :is(p, li, label, .comment-notes, .comment-form-comment) {
  max-width: var(--skp-read-measure);
}
body.skp-typed #comments :is(p, li, label) {
  font-size: var(--skp-body-medium);
  line-height: var(--skp-read-leading);
  color: var(--skp-on-surface-variant);
}


/* =========================================================================
   REGION 2 — TYPED PROSE
   The 34 pages that are not yet built from components still have to look like
   this site TODAY. They are ordinary WordPress markup, so the type layer has
   to reach ordinary WordPress markup.

   Scope is deliberately `:not([class*="skp-"])` — anything carrying a
   component class is owned by skp-components.css and must not be second-
   guessed from here. This styles the raw stuff and only the raw stuff.
   ========================================================================= */

body.skp-typed .entry-content :is(p, li, dd, dt):not([class*="skp-"]) {
  font-size: var(--skp-body-medium);
  line-height: var(--skp-read-leading);
  color: var(--skp-on-surface);
}

/* ---- THE READING MEASURE -------------------------------------------------
   The worst legibility defect on the site, and it is sitewide.

   Measured 2026-08-30 at a 1440 viewport, in real ch units:

     /resource/custom-pvc-tarpaulin-quote-checklist/   176 ch
     /products/                                        176 ch

   Every band caps its inner wrapper at 1354px and then sets running prose to
   the full 1354. 176 characters is roughly two and a half times the top of
   the 45–75 band that every legibility study since Tinker lands on, and it is
   why these pages are exhausting to read even though nothing about them looks
   broken. A capped BAND is not a reading measure; only the paragraph is.

   Applied to the prose element itself, at any depth, and NOT to its container
   — a container measure would also narrow tables, figures and grids, which
   need the whole column.

   Depth rather than a parent list is deliberate. The first version scoped this
   to direct children of the four inner-wrapper names and missed, measured:
     ul.skp-resource-list > li          142 ch   (list carries a class)
     div.skp-resource-note > p          136 ch   (one wrapper div deeper)
   There is no finite list of wrappers on a site with four content eras. A cap
   only ever NARROWS: prose already inside a 400px card is unaffected, because
   max-width does not stretch anything. So the blanket form is both simpler and
   safer than trying to enumerate.

   Excluded: cells, because a table column sets its own width, and the two
   component parts that own a deliberately different measure. */
body.skp-typed .entry-content :is(p, li, blockquote, dt, dd):not(.skp-section-lede):not(.skp-eyebrow) {
  /* The !important is against another !important, not against nothing.
     skp-design-system.css §12(b) ships

       body.page-id-1092 .skp-product-page > *,
       body .skp-legacy-product > *  { max-width: var(--skp-inner-cap) !important }

     to give /products/sound-barrier/ a gutter it lacked. It works, and it also
     applies a 1354px LAYOUT cap to running prose — measured at 126ch on that
     page. A layout cap must never win over a reading measure.

     Delete this !important the day those two page-ID rules are removed. The
     generic direct-child rule in REGION 1 already supplies the gutter they
     were written for, so they are dead weight the moment this layer is live —
     see 17-PAGE-TYPES §9. */
  max-width: var(--skp-read-measure) !important;
}
body.skp-typed .entry-content :is(td, th) :is(p, li, dd, dt),
body.skp-typed .entry-content :is(td, th) {
  max-width: none;
}

/* Furniture outside .entry-content still contains prose. Measured on /blog/:
   the hero lede sits in .content-area, never reached the measure, and ran to
   109ch. A directory and an archive both put real copy above the listing. */
body:is(.skp-type-directory, .skp-type-article)
  .content-area :is(p, li):not([class*="skp-"]):not(.entry-content *) {
  max-width: var(--skp-read-measure);
}

/* Centre-set copy must stay centred once it is narrowed, or a narrowed
   measure silently becomes a left-aligned column under a centred heading —
   which looks like a bug rather than a decision. */
body.skp-typed .entry-content :is(p, ul, ol):is(.has-text-align-center, [style*="text-align:center"], [style*="text-align: center"]) {
  margin-inline: auto;
}
body.skp-typed .entry-content .uagb-container-inner-blocks-wrap > p.has-text-align-center,
body.skp-typed .entry-content .wp-block-uagb-container > p.has-text-align-center {
  margin-inline: auto;
}

/* ---- vertical rhythm -----------------------------------------------------
   Grid items do not margin-collapse. Two stacked blocks that used to share a
   collapsed 22px now sum to 44px, so the flow has to be stated rather than
   inherited. Bottom margins only — one direction, one value, no arithmetic. */
body.skp-typed .entry-content > *          { margin-block-start: 0; }
body.skp-typed .entry-content > * + *      { margin-block-start: var(--skp-flow); }
body.skp-typed .entry-content > * + :is(h2, h3) { margin-block-start: var(--skp-flow-head); }
/* a band owns its own padding and must not also carry flow */
body.skp-typed .entry-content > :is(.skp-section, main, .alignfull, .skp-product-page, .skp-resource-page, .skp-lineage, .skp-applications) + *,
body.skp-typed .entry-content > :is(.skp-section, main, .alignfull, .skp-product-page, .skp-resource-page, .skp-lineage, .skp-applications) {
  margin-block-start: 0;
}

/* ---- headings ------------------------------------------------------------
   One treatment, four variables. The type decides the variables; nothing
   decides the treatment. */
body.skp-typed .entry-content :is(h2, h3):not([class*="skp-"]) {
  font-family: var(--skp-font);
  color: var(--skp-on-surface);
  letter-spacing: var(--skp-head-track);
  text-wrap: balance;
}

body.skp-typed .entry-content h2:not([class*="skp-"]) {
  position: relative;
  font-size: var(--skp-head-size);
  font-weight: 600;
  line-height: 1.12;
  text-transform: var(--skp-head-case);
  max-width: 32ch;
  padding-inline-start: calc(var(--skp-head-bar-w) + var(--skp-space-md));
  margin-block-end: var(--skp-space-md);
}

/* the accent bar — present only where the type gives it a width */
body.skp-typed .entry-content h2:not([class*="skp-"])::before {
  content: "";
  position: absolute;
  inset-block: 0.12em 0.12em;
  inset-inline-start: 0;
  width: var(--skp-head-bar-w);
  border-radius: var(--skp-shape-extra-small);
  background: var(--skp-accent);
}

body.skp-typed .entry-content h3:not([class*="skp-"]) {
  font-size: var(--skp-title-large);
  font-weight: 700;
  line-height: 1.2;
  margin-block-end: var(--skp-space-sm);
  max-width: 40ch;
}

/* ---- links ---------------------------------------------------------------
   Astra ships `a:not(.button):not(...)` rules that outrank a single class
   (07 §3.3) — the documented reason card copy once shipped underlined. The
   !important here is that arms race already lost once; do not remove it
   without re-measuring. */
body.skp-typed .entry-content :is(p, li, dd, td, th) a:not([class*="skp-"]):not(.wp-block-button__link) {
  color: var(--skp-link-ink);
  text-decoration: underline !important;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.18em;
  text-decoration-color: color-mix(in srgb, var(--skp-link-ink) 42%, transparent);
  transition: text-decoration-color var(--skp-motion-fast), color var(--skp-motion-fast);
}
body.skp-typed .entry-content :is(p, li, dd, td, th) a:not([class*="skp-"]):hover {
  text-decoration-color: currentColor;
}
body.skp-typed .entry-content a:focus-visible {
  outline: var(--skp-focus-width) solid var(--skp-focus-ring);
  outline-offset: var(--skp-focus-offset);
  border-radius: var(--skp-shape-extra-small);
}

/* ---- lists ---------------------------------------------------------------
   A marker in the accent is the cheapest way to make an ordinary bulleted
   list belong to a design system. */
body.skp-typed .entry-content > ul:not([class*="skp-"]) > li::marker,
body.skp-typed .entry-content .uagb-container-inner-blocks-wrap > ul > li::marker {
  color: var(--skp-accent);
}
body.skp-typed .entry-content :is(ul, ol):not([class*="skp-"]) > li + li {
  margin-block-start: var(--skp-space-sm);
}

/* ---- images --------------------------------------------------------------
   Never upscale. This is the rule that shipped a 74x13px decorative zigzag at
   1354x846 — 05 §4.2. */
body.skp-typed .entry-content img {
  max-width: 100%;
  height: auto;
}
body.skp-typed .entry-content > figure:not([class*="skp-"]) img {
  border-radius: var(--skp-shape-media);
}
body.skp-typed .entry-content figcaption {
  font-size: var(--skp-body-small);
  color: var(--skp-on-surface-variant);
  margin-block-start: var(--skp-space-sm);
  max-width: var(--skp-read-measure);
}


/* =========================================================================
   REGION 3 — TABLES
   Measured 2026-08-30: a table is the single most common cause of horizontal
   scroll on this site — it appears in the overflow report for 18 of the 22
   failing URLs.

   A spec table is the most valuable content a manufacturer publishes and it
   must not be destroyed to fit 390px. So it scrolls, inside its own bounded
   box, with a visible edge that says so. `min-width: 0` in REGION 1 is what
   makes the box actually bound it.
   ========================================================================= */

body.skp-typed .entry-content table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--skp-body-small);
}
body.skp-typed .entry-content :is(th, td) {
  padding: var(--skp-space-md);
  text-align: start;
  vertical-align: top;
  border-bottom: 1px solid var(--skp-outline-variant);
}
body.skp-typed .entry-content th {
  font-weight: 700;
  color: var(--skp-on-surface);
  background: var(--skp-surface-container);
}
body.skp-typed .entry-content thead th {
  border-bottom: 2px solid var(--skp-accent);
}

/* A <pre> has no wrap point, so a code line is exactly as wide as it is.
   Measured on /how-to-measure-industrial-curtains/: five <pre> blocks running
   to 853px on a 390 viewport. It scrolls in its own box, like a table. */
body.skp-typed .entry-content :is(pre, code) {
  max-width: 100%;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}
body.skp-typed .entry-content pre {
  padding: var(--skp-space-md);
  background: var(--skp-surface-container);
  border-radius: var(--skp-shape-small);
  font-size: var(--skp-body-small);
}

/* A block table ships wrapped in a <figure>, and the figure is the box that
   has to bound it — clipping only the table leaves the figure at the table's
   natural width. Measured on /industrial-curtain-fire-ratings/: figure and
   table both at 785px on a 390 viewport. */
body.skp-typed .entry-content :is(figure.wp-block-table, .wp-block-table) {
  max-width: 100%;
  min-width: 0;
  overflow-x: auto;
}

@media (max-width: 900px) {
  /* the scroll box. `display:block` on a <table> is what makes overflow-x
     work at all — a table box ignores it. */
  body.skp-typed .entry-content table {
    display: block;
    overflow-x: auto;
    max-width: 100%;
    -webkit-overflow-scrolling: touch;
    /* the edge fade IS the affordance. Without it a scrollable table looks
       like a clipped one, and nobody swipes a clipped table. */
    background:
      linear-gradient(to right, var(--skp-surface) 30%, rgba(255, 255, 255, 0)) left center,
      linear-gradient(to right, rgba(255, 255, 255, 0), var(--skp-surface) 70%) right center,
      radial-gradient(farthest-side at 0 50%, rgba(16, 32, 51, 0.14), rgba(16, 32, 51, 0)) left center,
      radial-gradient(farthest-side at 100% 50%, rgba(16, 32, 51, 0.14), rgba(16, 32, 51, 0)) right center;
    background-repeat: no-repeat;
    background-size: 28px 100%, 28px 100%, 13px 100%, 13px 100%;
    background-attachment: local, local, scroll, scroll;
  }
  body.skp-typed .entry-content table :is(thead, tbody) {
    display: table;
    width: max-content;
    min-width: 100%;
  }
  body.skp-typed .entry-content table tr { display: table-row; }
  body.skp-typed .entry-content table :is(th, td) {
    display: table-cell;
    padding: var(--skp-space-sm) var(--skp-space-md);
  }
}


/* =========================================================================
   REGION 4 — THE SIGNATURE OF EACH TYPE
   Everything above is shared. This is the part that differs, and it is
   deliberately small — four devices, one per family of page.
   ========================================================================= */

/* ---- DIRECTORY · the wayfinding board ------------------------------------
   A directory heading is a sign, not a sentence: set as a tracked-out label
   with a hairline running the full width of the column. The rule is the
   device — it turns a list of links into a board with named zones. */
body.skp-type-directory .entry-content h2:not([class*="skp-"]) {
  max-width: none;
  padding-block-end: var(--skp-space-md);
  border-block-end: 1px solid var(--skp-outline-variant);
  color: var(--skp-on-surface-variant);
}
/* the bar becomes a leading tick rather than a full-height rule, because at
   label size a 4px bar spanning a 20px cap-height reads as a typo */
body.skp-type-directory .entry-content h2:not([class*="skp-"])::before {
  inset-block: auto 0.85em;
  height: 3px;
  width: 22px;
  background: var(--skp-accent);
}
body.skp-type-directory .entry-content h2:not([class*="skp-"]) {
  padding-inline-start: 0;
  padding-block-start: var(--skp-space-md);
}

/* ---- RESOURCE + ARTICLE · the technical note -----------------------------
   No bar. A document does not need signage; it needs a margin. The heading
   gets a short rule ABOVE it instead — the way a technical note breaks a
   section — and the whole column sits against a hairline so the page reads
   as a single continuous document rather than a stack of bands. */
body:is(.skp-type-resource, .skp-type-article) .entry-content h2:not([class*="skp-"]) {
  padding-inline-start: 0;
  padding-block-start: var(--skp-space-lg);
  max-width: var(--skp-read-measure);
}
body:is(.skp-type-resource, .skp-type-article) .entry-content h2:not([class*="skp-"])::before {
  inset-block: 0 auto;
  width: 44px;
  height: 3px;
  border-radius: 0;
  background: var(--skp-accent);
}

/* ---- ARTICLE · the related-reading rail ---------------------------------
   Once the sidebar stacks below the article it becomes a full-width column of
   post titles, and a post title set across 1354px runs to 134 characters —
   the same defect the reading measure exists to fix, arriving from the
   theme's widget instead of from the content.

   Set as a rail: a multi-column list of links, each one a proper 48px target,
   separated by hairlines. It reads as "more on this" rather than as a
   sidebar that fell over. */
body:is(.skp-type-article, .skp-type-directory) #secondary .widget {
  padding-block: var(--skp-space-2xl) 0;
  border-block-start: var(--skp-hairline, 1px solid var(--skp-outline-variant));
}
body:is(.skp-type-article, .skp-type-directory) #secondary .widget-title,
body:is(.skp-type-article, .skp-type-directory) #secondary h2 {
  font-size: var(--skp-title-large);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--skp-on-surface-variant);
  margin-block-end: var(--skp-space-lg);
  padding-inline-start: 0;
}
body:is(.skp-type-article, .skp-type-directory) #secondary .widget-title::before { content: none; }
body:is(.skp-type-article, .skp-type-directory) #secondary ul {
  list-style: none;
  margin: 0;
  padding: 0;
  columns: 2;
  column-gap: var(--skp-space-2xl);
}
@media (min-width: 1100px) {
  body:is(.skp-type-article, .skp-type-directory) #secondary ul { columns: 3; }
}
body:is(.skp-type-article, .skp-type-directory) #secondary li {
  break-inside: avoid;
  max-width: none;
  margin: 0;
  border-block-end: 1px solid var(--skp-outline-variant);
}
body:is(.skp-type-article, .skp-type-directory) #secondary li a {
  display: flex;
  align-items: center;
  min-height: var(--skp-target-min);
  padding-block: var(--skp-space-sm);
  font-size: var(--skp-body-small);
  line-height: 1.35;
  color: var(--skp-on-surface);
  text-decoration: none !important;
}
body:is(.skp-type-article, .skp-type-directory) #secondary li a:hover {
  color: var(--skp-link-ink);
}

/* ---- PRODUCT · the pillar hero, reconciled ------------------------------
   Four pages — 500gsm-pvc-tarpaulin, pvc-tarpaulin-rolls, oem-custom-
   fabrication, car-wash-curtains — shipped a hero that was half dark and half
   light, because two global layers disagreed and neither knew about the other.

   skp-product-tiers.css makes every application-tier hero LIGHT:

     .skp-tier-application .skp-hero    { background-color: initial !important }
     .skp-tier-application .skp-hero h1 { color: var(--skp-ink) !important }

   The pillar hero's own inline <style> still dresses it as a DARK hero — one
   built for a background photograph that these pages do not have:

     .uagb-block-pil-hero .skp-inner { background: rgba(15,23,42,.70) }
     .uagb-block-pil-hero p          { color: rgba(255,255,255,.92) }
     .uagb-block-pil-hero::after     { linear-gradient(…rgba(15,23,42,.52)) }
     .skp-kicker                     { color: rgba(255,255,255,.86) }

   The section goes transparent, the scrim survives, and the H1 is forced dark.
   Measured on the live page: H1 #1e293b on the scrim resolves to a background
   of ~#525968 — 2.11:1, against a 4.5 floor and a 3.0 floor for large text.
   The body copy is white on the same panel and effectively invisible.

   This is README rule 2 exactly: A BACKGROUND WITHOUT ITS PAIRED TEXT COLOUR
   IS A DEFECT. Here the background was removed by one layer and its on-colours
   left behind by another, which is the same bug wearing a different coat.

   The tier layer wins the argument — light is what every other application
   hero on the site does, and consistency is the point — so the leftover dark
   dressing goes, and every pair is restated against the light surface.
   ========================================================================= */

/* THE COMPOSITION, which is the part that actually looked broken.

   .skp-inner resolves to `display: grid` with two content-sized columns, so
   four children in the correct DOM order —

     0  p.skp-kicker   "Ready-stock PVC tarpaulin rolls"
     1  h1             "500gsm PVC Tarpaulin"
     2  div            the lede
     3  div.skp-btn-row

   — flow kicker→col1, h1→col2, lede→col1, buttons→col2. On screen that is
   the kicker and the description on the LEFT and the title on the RIGHT: the
   visitor reads the explanation before they are told what the page is.

   That inverts the one rule this whole site is ordered by (CLAUDE.md, the
   information order: what we offer FIRST). The hero's own CSS already assumes
   a single centred column — `text-align: center`, `h1 { margin: 0 auto }`,
   `p { max-width: 74ch; margin-inline: auto }` — so one column is not a new
   design, it is the design the rest of the rules were written for.

   It also un-blockifies the kicker: `display: inline-block` from the pillar
   CSS is forced to `block` on a grid item, which is why the pill stretched to
   508px instead of hugging its text. */
body.skp-typed .uagb-block-pil-hero > .skp-inner {
  display: block;
}

/* the scrim, the border-for-a-photograph, and the deep drop shadow */
body.skp-typed .uagb-block-pil-hero .skp-inner {
  background: transparent;
  border-color: var(--skp-outline-variant);
  box-shadow: none;
}

/* stack rhythm, now that the children are in one column */
body.skp-typed .uagb-block-pil-hero > .skp-inner > * + * {
  margin-block-start: var(--skp-space-lg);
}
body.skp-typed .uagb-block-pil-hero > .skp-inner > .skp-btn-row {
  margin-block-start: var(--skp-space-xl);
}

/* the dark vignette that only made sense over a photograph */
body.skp-typed .uagb-block-pil-hero::after { display: none; }

/* on-colours, restated for a light surface. Measured against the hero's
   ~#eef2f7 gradient: ink 14.63:1 · variant 5.89:1 · link 6.41:1

   Set on .skp-inner so it INHERITS. The lede on these pages is a bare <div>,
   not a <p> — which is why the pillar's own `pil-hero p { color: #fff }` and
   the tier layer's `skp-hero p { color: muted }` both miss it entirely, and
   the copy simply inherits whatever .entry-content happens to be. Colouring
   the container is the only form that reaches an unclassed div. */
body.skp-typed .uagb-block-pil-hero > .skp-inner {
  color: var(--skp-on-surface-variant);
}
body.skp-typed .uagb-block-pil-hero :is(p, li, dd, dt, div) {
  color: inherit;
}
body.skp-typed .uagb-block-pil-hero a:not(.wp-block-button__link):not([class*="btn"]) {
  color: var(--skp-link-ink);
}

/* The H1 keeps a `text-shadow: rgba(0,0,0,.3) 0 8px 24px` from the dark
   photographic hero. Over a light gradient a 24px black blur is not depth,
   it is a smudge under the letterforms. */
body.skp-typed .uagb-block-pil-hero h1 {
  text-shadow: none;
}

/* The kicker was white text on a translucent dark pill. On a light hero that
   is white on light grey; measured, it was inheriting #67768E and reading as
   a grey blob. It becomes what an eyebrow is everywhere else on the site.

   The selector is long on purpose. The kicker is a <p>, so it is caught by
   skp-product-tiers.css's

     body.skp-tier-application main.skp-product-page
       > section.skp-section.skp-hero p   { color: var(--skp-muted) !important }

   which is (0,4,4) with !important. Matching that and relying on source order
   would work today and break the first time either sheet is reordered, so
   this is (0,5,4) — one class higher, stated rather than lucky. */
body.skp-typed main.skp-product-page section.skp-section.uagb-block-pil-hero p.skp-kicker {
  display: inline-block !important;
  color: var(--skp-primary) !important;
  background: var(--skp-surface-container-high) !important;
  border: 1px solid var(--skp-outline-variant) !important;
  max-width: none;
  margin-inline: auto;
}

/* THE BUTTONS. Measured live: primary #060097, secondary #2563eb, and on
   /products/mesh-banner-material/ the same button is #0056A7. Three blues for
   one action across four sibling pages, none of them the palette's.

   The primary rule they inherit — `.skp-btn-primary { background:#fff }` — was
   written for the dark hero, and a white button on a light hero is a button
   nobody can see. */
body.skp-typed .uagb-block-pil-hero :is(.skp-btn-primary, .wp-block-button__link.skp-btn-primary) {
  background: var(--skp-primary) !important;
  border: 1px solid var(--skp-primary) !important;
  color: var(--skp-on-primary) !important;      /* 7.27:1 measured */
}
/* (0,6,4). The secondary button is held by a per-page patch,
     .page-id-2397 .skp-product-page .skp-hero .skp-btn.skp-btn-secondary
       { background: #2563eb !important }
   at (0,5,0) — one of the fifteen page-ID rules 17-PAGE-TYPES exists to
   delete. Until it is deleted it has to be outranked, not argued with. */
body.skp-typed main.skp-product-page section.skp-section.uagb-block-pil-hero a.skp-btn.skp-btn-secondary,
body.skp-typed .uagb-block-pil-hero :is(.skp-btn-secondary, .wp-block-button__link.skp-btn-secondary) {
  background: transparent !important;
  border: 1px solid var(--skp-primary) !important;
  color: var(--skp-primary) !important;         /* 6.41:1 on the hero surface */
}
body.skp-typed main.skp-product-page section.skp-section.uagb-block-pil-hero a.skp-btn.skp-btn-secondary:hover {
  background: var(--skp-accent-wash) !important;
}

/* ---- UTILITY · the document ----------------------------------------------
   No accent, no bar, no signage of any kind. A privacy policy that has been
   art-directed is a privacy policy nobody believes. */
body.skp-type-utility .entry-content h2:not([class*="skp-"])::before { content: none; }
body.skp-type-utility .entry-content h2:not([class*="skp-"]) { padding-inline-start: 0; }

/* ---- HUB + COMPANY · the plate -------------------------------------------
   The heaviest bar on the site, and the only types that get it. Both are
   making an authority claim, and the bar is how the system says so. */
body:is(.skp-type-hub, .skp-type-company) .entry-content h2:not([class*="skp-"]) {
  font-weight: 700;
}

/* ---- H1 · one ramp, every type ------------------------------------------
   Replaces the page-ID patches for 126 and 487 in skp-design-system.css,
   which existed because those two pages rendered their H1 at 48px and 32px
   against the site's ~63px. That is not a per-page problem. */
body.skp-typed .entry-content h1:not([class*="skp-"]),
body.skp-typed .entry-title {
  font-family: var(--skp-font);
  font-size: var(--skp-headline-large);
  font-weight: 700;
  line-height: 1.08;
  letter-spacing: -0.015em;
  text-wrap: balance;
  max-width: 22ch;
}
body:is(.skp-type-resource, .skp-type-article, .skp-type-utility) .entry-content h1:not([class*="skp-"]),
body:is(.skp-type-resource, .skp-type-article, .skp-type-utility) .entry-title {
  max-width: 26ch;
}


/* =========================================================================
   REGION 4b — IMAGE TILES
   02-COMPONENTS §Gallery has said this since v1:

     "Ratio — 3/2, every tile in a row shares one ratio"

   and no stylesheet has ever implemented it for the block markup the product
   pages are actually built from. Measured live, 2026-08-31, at 1440:

     /products/500gsm-pvc-tarpaulin/      195 / 196 / 345 / 196   spread 44%
     /products/pvc-tarpaulin-rolls/       345 / 196 / 345 / 196   spread 43%
     /products/…oem-custom-fabrication/   345 / 173 / 260 / 260   spread 50%
     /products/car-wash-curtains/         375 / 211               spread 44%

   Four images in one row, one of them 76% taller than its neighbours, on
   pages that pass every width and overflow gate. This is the difference
   between a page being the right SIZE and a page being right: the eye
   forgives a tall photo and a short photo on different screens, and never
   forgives them next to each other.

   Cause: each <img> keeps its own intrinsic ratio. `object-fit: cover` is
   already set but has nothing to cover INTO while height is auto — cover only
   does work once the box has a ratio of its own.

   THE THREE GALLERY CONTAINERS, one per content era. Measured ragged rows,
   all with EQUAL-WIDTH tiles, which is what makes them defects rather than
   compositions:

     .skp-card-grid          500gsm · rolls · oem · car-wash      44–50%
     .wp-block-columns       /products/ 4 of 4 rows · /factories/ 25–58%
     .uagb-layout-grid       pvc-tarpaulin · sunland-plastic      18–40%

   /products/ — the main product directory — was the worst on the site: four
   rows of three, every one ragged, spreads to 58%.
   ========================================================================= */

body.skp-typed .skp-card-grid .skp-card :is(figure, .wp-block-image) img,
body.skp-typed .skp-card-grid .skp-card > img,
body.skp-typed .wp-block-columns > .wp-block-column > figure.wp-block-image img,
body.skp-typed .wp-block-columns > .wp-block-column > .wp-block-image img,
/* Any UAGB image block, not only those inside .uagb-layout-grid.
   /factories/sunland-plastic/ places them as bare .wp-block-uagb-image
   children of a plain container, and a grid-scoped selector missed it —
   measured 384x384 / 384x512 / 384x308 in one row, a 40% spread on tiles of
   identical width.

   A DESCENDANT combinator, not a child one. A linked image is
   `figure > a > img`, and `figure > img` silently misses it — measured on
   /products/pvc-tarpaulin/, where one image of three stayed at 0.887 while
   its two neighbours were corrected to 1.5. */
body.skp-typed .wp-block-uagb-image figure img,
body.skp-typed .uagb-layout-grid .wp-block-uagb-image img {
  width: 100%;
  /* !important because the markup carries width/height ATTRIBUTES —
     `width="384" height="512"` — and UAGB pins `height` in CSS to match them.
     Measured on /factories/sunland-plastic/ after the ratio was applied:
       aspect-ratio  3 / 2      (this rule, applied)
       height        512px      (still winning)
     An explicit height beats aspect-ratio every time; the ratio only governs
     the axis that is auto. Both axes have to be released for it to do work. */
  height: auto !important;
  aspect-ratio: 3 / 2;
  object-fit: cover;
  /* centre, so a portrait photograph loses margin rather than its subject */
  object-position: center;
  border-radius: var(--skp-shape-card);
  display: block;
}
/* the figure inherits the pinned height from the image it wraps */
body.skp-typed .wp-block-uagb-image figure.wp-block-uagb-image__figure {
  height: auto !important;
}

/* A uniform ratio KEEPS the feature-plus-thumbnails mosaic working, which is
   why it can be applied to .uagb-layout-grid wholesale. On
   /products/trailer-tarp/ and /factories/shinysky-plastic/ the outer grid is
   a 665px feature beside a 665px inner grid of four 320px thumbs:

     feature   665 / 1.5              = 443
     thumbs    2 rows x (320 / 1.5)   = 426 + 16px gap = 442

   The two columns still align to the pixel. The mosaic was never the problem
   — the mismatched ratios inside it were. */

/* The ratio also removes a lazy-load reflow. WP Rocket swaps src for a data-URI
   SVG placeholder sized from the declared width/height, and where those are
   wrong the PLACEHOLDER's ratio drives layout until the real file arrives —
   measured as a 113×150 placeholder holding open a 260×345 box. A declared
   aspect-ratio makes the box correct before either image exists. */

@media (max-width: 900px) {
  /* A single-column card on a phone gets a slightly taller crop: at 354px wide
     a 3/2 tile is only 236px, which is a stripe rather than a photograph. */
  body.skp-typed .skp-card-grid .skp-card :is(figure, .wp-block-image) img {
    aspect-ratio: 4 / 3;
  }
}


/* =========================================================================
   REGION 4d — THE READING COLUMN HAS ONE EDGE
   Measured on /industrial-curtain-fire-ratings/ at 1440, left edge of each
   top-level block down the article:

     h1      L394  W650      p    L364  W711      p   L358  W729
     h2      L589  W261      ol   L497  W446      p   L450  W538
     figure  L320  W800      p    L665  W130      p   L655  W130

   Fourteen different left edges in one column. Every block is CENTRED at its
   own max-width, so the eye has no edge to follow and the page reads as a
   drift of unrelated fragments rather than a column of text. This is the
   worst alignment on the site and it is on all 49 posts.

   Centring the COLUMN is correct. Centring each BLOCK INDEPENDENTLY is the
   defect: a 26ch heading and a 68ch paragraph, both centred, can never share
   an edge. The fix is one width for every block in the column — the heading
   still looks shorter because its text is shorter, but its BOX starts where
   the paragraph's box starts.

   Two causes, and the second is the interesting one.

   ONE — `width: 100%` and `justify-self: stretch` are load-bearing.
   .entry-content is a grid, and a grid item that is not stretched is sized to
   its CONTENT, so a short paragraph is narrower than a long one and, once
   centred, starts somewhere else. max-width alone cannot fix that: it caps a
   width that was never being filled. Stretch first, then cap.

   TWO — **`ch` IS FONT-RELATIVE, SO IT CANNOT ALIGN.** Measured with a single
   `max-width: 68ch` applied to every block in the column:

     p    16px  →  68ch =  729px  →  starts at 355
     h1   30px  →  26ch =  650px  →  starts at 394
     h2   28px  →  68ch =  997px  →  starts at 221
     h3   22px  →  68ch =  442px  →  starts at 499

   One declaration, four different pixel widths, four different left edges.
   A `ch` measure is the right way to size a LINE of prose and the wrong way
   to size a COLUMN, because a column is shared by type of several sizes.

   So the column is absolute, resolved once, and every block takes it. The
   prose still lands at ~68 characters because the value is derived from the
   body size — it is simply no longer recomputed per element. */
body:is(.skp-type-article, .skp-type-resource, .skp-type-utility) {
  --skp-col: 730px;
}

body:is(.skp-type-article, .skp-type-resource, .skp-type-utility) .entry-content > *,
body:is(.skp-type-article, .skp-type-resource, .skp-type-utility) .entry-content :is(h1, h2, h3, h4, h5) {
  width: 100%;
  justify-self: stretch;
  max-width: var(--skp-col) !important;
  margin-inline: auto;
}

/* The dateline and share row are part of the article and belong on its edge.
   Measured at L0 W1439 — stranded against the left of the viewport while the
   prose sat at 355. */
body.skp-type-article :is(.entry-meta, .ast-post-social-sharing) {
  width: 100%;
  max-width: var(--skp-col);
  margin-inline: auto;
}

/* The header is a full-bleed masthead strip: the band runs edge to edge, its
   contents sit on the column. It is the only surface an article has above the
   fold, and it is what stops a 15,000px white scroll from opening on nothing.
   Contents keep the column edge, so the byline still lines up with the H1. */
body.skp-type-article .entry-header {
  width: 100%;
  max-width: none;
  background: var(--skp-surface-container-low);
  border-block-end: 1px solid var(--skp-outline-variant);
  padding-block: var(--skp-space-xl) var(--skp-space-lg);
  margin-block-end: var(--skp-space-2xl);
}

/* A photograph or a table may run wider than the prose — it is evidence, not
   reading — but it takes ONE wider width, not a width of its own each time. */
body:is(.skp-type-article, .skp-type-resource) .entry-content > :is(figure, table, .wp-block-table, .wp-block-image) {
  max-width: min(100%, calc(var(--skp-read-measure) + 12ch));
}

/* Headings inside the column share the column's edge. The per-element caps in
   REGION 2 and REGION 4 are for BANDED pages, where a heading sits in its own
   section and a narrower measure is a deliberate shape. In a document column
   they are what breaks the edge. */
body:is(.skp-type-article, .skp-type-resource, .skp-type-utility) .entry-content :is(h1, h2, h3, h4) {
  max-width: var(--skp-read-measure);
}


/* =========================================================================
   REGION 4c — ALIGNMENT: ONE CARD INSET
   Measured 2026-08-31 at 1440. 26 of 44 pages start their content at more
   than three distinct x positions, and the dominant pattern is a cluster of
   near-misses a few pixels apart — [43, 68], [43, 70], [43, 76],
   [43, 53, 64, 72, 74, 91] on /products/pvc-tarpaulin/.

   43px is the column. Everything else is card padding, and the site has SIX
   values for it:

     .abt-stat-card             12px
     .uagb-faq-questions        20px
     .skp-quote-box             24px      .abt-timeline-card     24px
     .skp-resource-faq-item     24px      .wp-block-column       24px
     .skp-card                  26px      .skp-resource-card     26px
     .skp-tarp-factor-card      28px      .wp-block-column       28px

   --skp-card-pad exists for exactly this and is referenced by none of them.

   A reader cannot name why a page looks unresolved, but they can see six
   left edges where there should be two. padding-INLINE only: alignment is a
   horizontal problem, and leaving vertical padding alone keeps every card's
   height — and therefore every row's rhythm — exactly where it is.
   ========================================================================= */

body.skp-typed .entry-content :is(
  .skp-card, .skp-quote-box, .skp-block-body, .skp-feature-card,
  .skp-photo-sidecar, .skp-tarp-factor-card,
  .skp-resource-card, .skp-resource-faq-item,
  .abt-stat-card, .abt-timeline-card,
  /* second pass — found by re-measuring after the first */
  .skp-size-frame, .skp-size-frame__field, .skp-note,
  .uagb-faq-questions, .tools-dir-card
) {
  padding-inline: var(--skp-card-pad) !important;
}

/* NOT normalised: bare .wp-block-column. It is a generic layout primitive, not
   a card, and rewriting its padding sitewide is exactly the broad change that
   broke four pages' column stacking earlier in this batch. The two pages that
   use padded columns as cards (/about/, /products/pvc-tarpaulin/) keep their
   own inset until those pages are rebuilt. Documented, not forgotten. */


/* =========================================================================
   REGION 5 — MOBILE AND TOUCH
   The goal is not "it fits". The goal is that a buyer holding a phone on a
   factory floor can hit every control on the first attempt.
   ========================================================================= */

@media (max-width: 900px) {
  /* THE MOBILE GUTTER — one token, and everything downstream follows.
     --skp-inner-cap is min(94%, 1600px), and 94% of a 390px band is 366.6px,
     not the 354px the contract in 01-FOUNDATIONS §6.1 specifies. A percentage
     cap cannot express "18px each side"; it expresses "6% each side", which
     is 11.7px at 390 and 46px at 768. Restating it as a subtraction makes the
     gutter a constant, which is what a gutter is.

     skp-components.css §shell reaches the same value with an !important pair
     on two selectors. This reaches it once, on the token, so every wrapper —
     .skp-inner, .skp-resource-inner, .uagb-container-inner-blocks-wrap and
     the legacy direct-child cap — lands on it without knowing it exists. */
  body.skp-typed { --skp-inner-cap: calc(100% - 36px); }

  body.skp-typed .entry-content {
    grid-template-columns:
      [full-start] 18px
      [content-start] minmax(0, 1fr) [content-end]
      18px [full-end];
  }

  /* 04-INTERACTION §6: max(M3 48, HIG 44). Inline prose links are exempt —
     enlarging a link inside a sentence breaks the line box. */
  body.skp-typed .entry-content :is(.wp-block-button__link, button, input[type="submit"]),
  body.skp-typed .entry-content a[class*="skp-btn"] {
    min-height: var(--skp-target-min);
    display: inline-flex;
    align-items: center;
    justify-content: center;
  }

  /* A button on a phone is full width or it is a mis-tap waiting to happen. */
  body.skp-typed .entry-content .wp-block-buttons > .wp-block-button {
    width: 100%;
  }
  body.skp-typed .entry-content .wp-block-button__link {
    width: 100%;
    text-align: center;
  }

  /* ---- STANDALONE LINKS ARE CONTROLS, NOT PROSE ------------------------
     Measured: 89 sub-44px targets across 16 pages, and almost none of them
     are links inside a sentence — they are lists of routes:

       /contact-us/            "+86 15360926421"      37px
       /                       "PVC Tarpaulin"        24px
       /products/trailer-tarp/ "PVC vs PE Tarpaulin"  22px
       /about/                 "Browse Products →"    17px

     A 17px tap target is a mis-tap. The exemption for inline prose links is
     right — enlarging a link inside a sentence breaks the line box — but a
     link that is the ONLY thing in its <li> or <p> is not prose, it is a
     control, and it takes the 44px floor like every other control. */
  body.skp-typed .entry-content :is(li, p, td) > a:only-child,
  /* A card or related-post title is a heading whose whole job is to be
     tapped. Measured: h4.uagb-post__title > a at 22px on the related-posts
     grid, h3 > a "Request a Quote" at 19px on /contact-us/. */
  body.skp-typed .entry-content :is(h2, h3, h4, h5) > a:only-child,
  body.skp-typed .entry-content a[href^="tel:"],
  body.skp-typed .entry-content a[href^="mailto:"],
  body.skp-typed .entry-content a[href*="wa.me"],
  body.skp-typed .entry-content :is(.skp-card, .skp-panel, .skp-resource-card, .skp-feature-card) > a,
  /* A link that is the whole content of a <strong>, which is itself the whole
     content of its paragraph, is a CTA line — not a bolded word in a sentence.
     Measured at 20px: "Request a Quote for Fire-Rated…", "Sound Barrier Tarp". */
  body.skp-typed .entry-content p > strong:only-child > a:only-child,
  body.skp-typed .entry-content li > strong:only-child > a:only-child,
  body.skp-typed .entry-content a[class*="link-card"] {
    display: flex;
    align-items: center;
    min-height: 44px;
  }

  /* Pagination and form controls. The form inputs measured 43px — one pixel
     under, which is the least interesting kind of failure and the easiest to
     leave in place forever. */
  body.skp-typed .entry-content :is(input, select, textarea, .page-numbers) {
    min-height: 44px;
  }
  body.skp-typed .entry-content a.page-numbers {
    min-width: 44px;
    justify-content: center;
  }
  /* and 8px of clear air between adjacent controls — HIG §touch */
  body.skp-typed .entry-content li + li > a:only-child {
    margin-block-start: var(--skp-space-xs);
  }

  /* ---- A READING FLOOR --------------------------------------------------
     Measured body copy at a 390 viewport: 13px on /, /products/pvc-mesh/ and
     /about/ (10 instances), 14px on /contact-us/ (14 instances). The system's
     own smallest role, --skp-body-small, is 0.92rem = 14.7px, so this text is
     below the floor the design system sets for itself — set by per-page rules
     written before there was a floor.

     15px is the minimum at arm's length on a phone. Captions and labels keep
     their own size; this is running text only.

     The !important is against per-page rules that hardcode 13px — the same
     class of rule 17-PAGE-TYPES exists to delete. An accessibility floor that
     loses a specificity argument is not a floor. */
  body.skp-typed .entry-content :is(p, li, dd, dt):not([class*="caption"]):not([class*="label"]):not([class*="eyebrow"]):not([class*="kicker"]):not([class*="meta"]) {
    font-size: max(15px, var(--skp-body-medium)) !important;
  }
  /* captions and small print keep their role, but not below the system's own
     smallest size — --skp-body-small is 0.92rem = 14.7px */
  body.skp-typed .entry-content :is(figcaption, [class*="caption"]) {
    font-size: max(14px, var(--skp-body-small));
  }

  body.skp-typed .entry-content h2:not([class*="skp-"]) { max-width: none; }
  body.skp-typed .entry-content h1:not([class*="skp-"]) { max-width: none; }
}

/* The lid comes off. `overflow-x: hidden` on <body> hides overflow, it does
   not prevent it — and while it is on, no audit can see a regression. REGION 1
   makes overflow arithmetically impossible, so the suppression is no longer
   load-bearing and is removed so that the next one is visible.
   If this line ever has to come back, something in REGION 1 is broken. */
body.skp-typed { overflow-x: clip; }

@media (prefers-reduced-motion: reduce) {
  body.skp-typed .entry-content * {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}
