Custom CSS and HTML in page content
Content pages on altapac.com often carry their own <style> block inside a description field, for things like a styled call-out, a card grid or a jump-link bar. Stock Maropost leaves that CSS unscoped: a rule you write for one heading applies to every matching heading on the page. This theme has a convention for keeping page CSS contained, and a safety script that only half does the job.
Use this page any time you add or edit a <style> block, a <script>, or layout HTML (rows, columns, cards) in Description 1, Description 2 or Description 3.
The rule
Every selector in a page’s <style> block must start with the wrapper the theme puts around the field you are writing in. On most templates that is #_Custom-page-content, which wraps Description 2. Description 1 sits in a different wrapper on almost every template (see the reference table).
A correctly prefixed rule only touches your own content. An unprefixed rule such as h2 or .btn reaches every heading or button in the page body: product cards, related-article cards, forms and the templates’ own sections.
Steps
- Find the page’s template. Open the content page in the cPanel and read the Body template field. If it is blank, the store falls back to a default for that content type. In that case open the live page, view the source (Ctrl+U) and search for
_Custom-page-contentto see whether the wrapper is there. - Pick the field. Look the template up in the reference table. Put styled content in the field whose wrapper has an id you can prefix, usually Description 2. On the
hometemplate it is Description 1. - Switch the editor to source view before pasting HTML or CSS, so the WYSIWYG doesn’t treat it as text.
- Write one
<style>block at the top of the field. Start every selector with the wrapper, including each selector in a comma list and every rule inside@media. - Use the theme’s Bootstrap 4 classes for layout (see Classes you can use). Bootstrap 5 class names do nothing on this site.
- Save, then reopen the page in the cPanel and look at the source again. The editor rewrites HTML on save (see What the editor changes), so check it left your markup intact.
- Check the live page after it has fully loaded, and check the header, footer and any product or article cards on the page as well as your own content. Add
?nc=and a random number to the URL to skip the page cache.
Copy-paste example: correctly scoped
This goes at the top of Description 2 on any template where Description 2 renders inside #_Custom-page-content.
<style>#_Custom-page-content .pg-callout { background: #0D162D; color: #fff; padding: 1.5rem; border-radius: 1rem;}#_Custom-page-content .pg-callout h2,#_Custom-page-content .pg-callout p { color: #fff;}@media (max-width: 767.98px) { #_Custom-page-content .pg-callout { padding: 1rem; }}</style><div class="pg-callout"><h2>Book a fitting</h2><p>All four workshops fit roof racks, towbars and canopies.</p></div>Why it is safe: every selector starts with #_Custom-page-content, including the second selector in the comma list and the rule inside @media. The class pg-callout is new, so it can’t clash with a theme class. If you need a colour variable, define it on the wrapper (#_Custom-page-content { --pg-navy: #0D162D; }), not on :root.
Bad example: what not to paste
<style>:root { --brand: #00A6FB; }h2 { font-size: 55px; }.btn { background: var(--brand); }#_Custom-page-content h3, h4 { color: #00A6FB; }</style>What goes wrong:
h2and.btnrestyle every heading and button in the page body, not just yours: Add to Cart buttons on product cards, card titles, form buttons.#_Custom-page-content h3, h4only scopes the first selector. Theh4part is unscoped and hits everyh4in the page body.- The
:rootvariable is thrown away once the page finishes loading (see below), sovar(--brand)ends up empty and the buttons lose their background.
What the safety script does and doesn’t do
The theme loads a script on every page that rewrites <style> blocks once the page has loaded. It only touches <style> blocks inside one large container that holds the whole page body, from below the breadcrumbs down to just above the footer. The header, main menu, breadcrumbs and footer sit outside that container. The script puts the container’s id in front of every selector it finds, so h2 becomes “an h2 anywhere in the page body”.
So, once the page has loaded:
- Unprefixed rules stop reaching the header and footer, but they still reach everything else in the page body. The script does not limit your CSS to your own field.
- Rules for
body,htmlor:rootstop working entirely, because the rewritten selector looks for those elements inside the page body, where they never are. CSS variables you define on:rootdisappear. - Nested CSS (rules written inside other rules with
&) is dropped. Write every rule flat. - Commas inside
:is(),:where()or:not()get split as if they were separate selectors, which rewrites the selector into something different. Avoid commas inside them. - Every rewritten rule gains an id, which makes it stronger. Your rules can override the theme’s stylesheet after loading even where they didn’t before it.
Where each description field renders
Wrapper each field is printed inside, by Body template. Only rows with an id (#...) give you a unique prefix. A class (._...) wrapper works as a prefix but may be shared with another field on the same template.
| Body template | Description 1 | Description 2 | Description 3 |
|---|---|---|---|
home | #_Custom-page-content | Not shown | Not shown |
content | ._content-main-description (first page only, and only if filled) | #_Custom-page-content (also has class ._content-main-description) | Not shown |
default | ._content-main-description | #_Custom-page-content (see caution below) | Same wrapper as Description 2, straight after it |
brands | ._content-main-description | #_Custom-page-content (see caution below) | Same wrapper as Description 2, straight after it |
promotions-list | Short description area, no unique wrapper | #_Custom-page-content (see caution below) | Same wrapper as Description 2, straight after it |
promotions | ._content-main-description | #_Custom-page-content (also has class ._content-main-description) | ._content-main-description, no id |
category | ._services-content-main-description | #_Custom-page-content, only if filled. The wrapper is a Bootstrap row | Not shown |
services, service-parent, learn-parent | ._services-content-main-description | #_Custom-page-content, only if filled | Not shown |
serviceNew | ._services-content-main-description | #_Custom-page-content, only if filled | A second #_Custom-page-content, only if filled |
parent-category | .cat-description (long text is split, see below) | No id. The wrapper is a Bootstrap row | Not shown |
products, store_finder | All three fields share one section with no id | Same section | Same section |
contact | Inside the page’s sub-heading | Not shown | Not shown |
modal | Printed with no wrapper | Not shown | Not shown |
Product category pages with a blank Body template render the category layout on the live site (checked on /cargo-management/, 21 October 2026).
Classes you can use
- Bootstrap 4.5.2 is the framework, with its JavaScript (and jQuery 3.5.1) loaded at the bottom of every page. Use Bootstrap 4 names:
row,col-md-6,order-md-2,ml-3,mr-3,text-right,font-weight-bold,card,card-deck,badge,btn btn-primary,stretched-link,embed-responsive,d-flex,row-cols-*. - Bootstrap 5 names do nothing here:
ms-*,me-*,fw-bold,text-end,float-end,g-*gutters,gap-*,ratio,rounded-3. Collapses and modals usedata-toggle, notdata-bs-toggle. - Breakpoints: sm 576px, md 768px, lg 992px, xl 1200px, plus an extra xxl at 1600px (
col-xxl-*) that stock Bootstrap 4 doesn’t have. - Theme colour variables you can read in page CSS:
var(--altapac-blue)andvar(--theme-highlight). - The content kits are not in the theme.
giant-text,btn-custom, theap-*classes and mostaltapac-*classes only work because each page that uses them carries its own copy of the CSS. Copying the HTML of a section to a new page without its<style>block gives you unstyled markup. Which kit is the standard is still an owner decision; match the kit the page already uses. One exception: theserviceNewtemplate styles its own.ap-mod“Why Choose Altapac?” section, soap-*markup inside an.ap-modblock on aserviceNewpage is styled without page CSS (not live yet, AUD-86).
What the editor changes
The cPanel WYSIWYG (CKEditor 4) rewrites HTML when you save, by rules Maropost doesn’t document. This is what has been observed, mostly from a 34-case round-trip test on /testing on 21 October 2026, where every rewrite left the markup equivalent:
- Survives:
<style>blocks (all 114 in the 20 October pull carrytype="text/css"),<script>blocks, comments, inlinestyleattributes, classes, curly quotes, dashes and accented letters. The Typeform scripts in Description 2 on the eight location service pages came through cPanel saves on 20 October 2026 unchanged. - Reformatted, same result: attributes sorted alphabetically, each block tag on its own line (nested
<div>s too), a blank line between sibling blocks, single quotes on attributes changed to double, void tags self-closed (<br />), boolean attributes expanded (checked="checked"), runs of spaces collapsed. - Changed in ways you might notice: an empty paragraph becomes
<p> </p>(a visible blank line), loose text not inside a tag gets wrapped in<p>, named and numeric entities such as’become the plain character (&,<,>and stay), and a bare&,<or>in text gets encoded.
The full list is in Push CMS content through the API.
Scripts in content
- Avoid them if you can. Existing pages use them for Typeform embeds and the conversion tracking on form submit; leave those blocks intact when editing.
- jQuery and Bootstrap’s JavaScript load after your content. A script in a description field runs as the browser reaches it, before
$exists. Anything that uses jQuery or Bootstrap must wait: wrap it indocument.addEventListener('DOMContentLoaded', function () { ... });. - The safety script doesn’t touch scripts, and it doesn’t touch
<style>blocks your script adds after the page has loaded. CSS injected by a script is never rewritten, so prefix it yourself. - Typeform forms on service pages: on
servicesandserviceNew, filling Label 3 with the form’s embed ID (thedata-tf-livevalue from Typeform’s embed code) makes the template add the form and its embed script at the top of Description 2. You don’t need to paste the embed script yourself on those templates.
What breaks: common mistakes
- Prefixing Description 1 with
#_Custom-page-content. On every template excepthome, Description 1 is outside that wrapper, so the rules match nothing. Use the Description 1 wrapper from the table, or move the content to Description 2. - Unprefixed selectors. They don’t reach the header and footer once the page has loaded, but they restyle everything else in the page body. See the bad example.
- Only the first selector in a comma list prefixed. Each selector after a comma needs its own prefix.
- Rules on
body,htmlor:root. They stop working once the page loads. Put variables and base styles on the wrapper instead. - Nested CSS. Rules nested inside other rules are dropped. Write flat rules.
- Bootstrap 5 classes. They silently do nothing.
- Paragraphs side by side on
categoryandparent-category. Description 2 on those templates sits directly inside a Bootstrap row, which lays its children out as a flexible row. Wrap the whole field in one<div class="col-12">so it stacks normally. - Long Description 1 on
parent-category. If it runs over 100 words, the theme keeps everything up to the first closing paragraph tag visible and rebuilds the rest inside a “read more” collapse, cutting at each closing paragraph tag. Divs or tables that wrap several paragraphs get broken apart. Keep that field to plain paragraphs, and start it with a paragraph. - Block HTML in Description 1 on
contact. It prints inside the page’s sub-heading. Keep it to a short line of plain text. - Copying a section from another page without its CSS. The kit classes live in each page’s own
<style>block, not the theme. - Duplicated style blocks. Several pages already carry the same rules two or three times. Add to the existing block instead of pasting another copy.
- Saving from a stale cPanel tab. If the content was changed through the API since you opened the edit page, saving writes your old copy back. Reload the edit page before you save.
For developers
Based on ~/src/altapac-org/Altapac-ReDev, branch development, read 21 October 2026, and ~/src/altapac-org/Altapac-Content.
src/js/cssScopeEnforcer.js: runs once onDOMContentLoaded(73-83), rewrites<style>elements inside#algolia-shadowonly, prefixing each comma-split selector (1-10).@keyframesis left verbatim,CSSGroupingRuleis recursed (35-57). Nested rules are lost because onlyrule.style.cssTextis kept (37). Behaviour checked in headless Chrome:bodyand:rootrules go dead,& spannesting is dropped,:is(h3, h4)becomes:is(h3, #algolia-shadow h4), header and footer are untouched. Same file onmain; the live site loadscssScopeEnforcer.min.js.src/templates/headers/template.html:265:#algolia-shadowopens after the header and breadcrumbs. Closed atsrc/templates/footers/template.html:6-8, before the footer.src/templates/footers/includes/scripts.template.html:1(Neto skeletalvendor.js: Bootstrap 4.5.2 JS, jQuery 3.5.1),:24(enforcer).- Wrappers:
cms/home.template.html:113,cms/content.template.html:142,153,cms/default.template.html:79,149-152,cms/brands.template.html:75,112-115,cms/promotions-list.template.html:95-97,157-160(the Description 1 column block at 99-108 is commented out),cms/promotions.template.html:60,70,164,cms/category.template.html:112,448-451,cms/services.template.html:129,230-238,cms/serviceNew.template.html:192,295-306,407-410,cms/service-parent.template.html:178,188-192,cms/learn-parent.template.html:151,187-191,cms/parent-category.template.html:17-19,44-86,344-348,cms/products.template.html:28-34,cms/store_finder.template.html:29-33,cms/contact.template.html:254-256,cms/modal.template.html:1-3. - Trailing-space id: on
main,default,brandsandpromotions-listprintid="_Custom-page-content ", confirmed on live/learn/altapac-news-events/and/brands/. Fixed ondevelopmentinf901c2f(AUD-88). src/css/app.css: Bootstrap v4.5.2 header,--breakpoint-xxl: 1600px(76).src/css/style.css:406-409:--theme-highlight,--altapac-blueon:root.giant-text,btn-custom,ap-*appear in no theme stylesheet.- Altapac-Content
CLAUDE.md“Pushing content”: CKEditor 4 round-trip rules, 4-byte truncation on API push, stale-tab overwrite. Script survival: Altapac-Content commits dfdb79c, 6061335, a908243 and 83d9efc (cPanel saves of the Learn and location pages, no script changed). Emoji check: no character above U+FFFF in any pulled page.docs/style-guide.mdsection 5: the three content CSS kits.