/*
Theme Name: FocusX Cleaning
Theme URI: https://focusxcleaning.com.au/
Author: FocusX (white-label build)
Author URI: https://focusxcleaning.com.au/
Description: Bespoke commercial and residential cleaning theme for FocusX Cleaning Solutions, Melbourne. Designed homepage, service-page templates (slug-based), a dark footer, and a built-in lead/contact/newsletter form handler. Converted from the FocusX static build.
Version: 1.3.0
Requires at least: 6.1
Tested up to: 6.5
Requires PHP: 7.4
License: GNU General Public License v2 or later
License URI: http://www.gnu.org/licenses/gpl-2.0.html
Text Domain: focusx
*/

:root {
  /* Brand blues — deeper, more saturated for contrast */
  --blue-900: #06345a;
  --blue-800: #064e86;
  --blue-700: #0a63b4;
  --blue-600: #0f7ad4;
  --blue-500: #2a93e6;
  --blue-300: #7cc2f0;
  --blue-100: #dcefff;
  --blue-075: #e6f2fc;
  --blue-050: #f2f9ff;

  --yellow: #ffc400;
  --yellow-600: #f2b200;
  --yellow-ink: #0b2a44;

  --green: #17a34a;
  --green-700: #128038;

  --ink: #0d2338;
  --text: #21384b;
  --muted: #55697c;
  --white: #ffffff;
  --line: #d5e5f2;

  /* Derived tints & washes. These are recalculated from the Primary and Accent
     pickers (FocusX → Colours), so changing the brand colour also moves the
     hero wash, the section bands, the contact panel and the footer — they used
     to be hardcoded blue and stayed blue whatever you picked. */
  --primary-rgb: 15, 122, 212;
  --accent-rgb: 255, 196, 0;
  --tint-a: #cfe8ff;
  --tint-b: #e1f1ff;
  --tint-c: #f0f8ff;
  --tint-page: #eef7ff;
  --contact-a: #d7ebfd;
  --contact-b: #e9f3fc;
  --footer-a: #0b2038;
  --footer-b: #081627;
  --star: #ffb400;
  --callbanner-bg: #55b600;
  --callbanner-rgb: 51, 122, 0;
  --green-rgb: 18, 128, 60;

  --shadow: 0 20px 55px rgba(8, 40, 70, .16);
  --shadow-soft: 0 14px 34px rgba(10, 62, 105, .12);
  --radius: 18px;
  --radius-sm: 12px;
  --container: 1180px;
}

* { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
  margin: 0;
  font-family: Inter, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  color: var(--text);
  background: #fff;
  line-height: 1.55;
}
img { display: block; max-width: 100%; }
a { color: inherit; text-decoration: none; }
button, input, select, textarea { font: inherit; }
.ico { width: 1.05em; height: 1.05em; display: inline-block; vertical-align: -0.16em; flex: 0 0 auto; }
.skip-link { position: absolute; left: -999px; top: 1rem; z-index: 999; background: #fff; padding: .75rem 1rem; border-radius: 8px; }
.skip-link:focus { left: 1rem; }
.container { width: min(calc(100% - 40px), var(--container)); margin-inline: auto; }
.section { padding: 64px 0; }
.section-soft { background: var(--blue-075); }

/* ---------- Nav ---------- */
.navbar { position: sticky; top: 0; z-index: 40; background: rgba(255,255,255,.96); backdrop-filter: blur(12px); border-bottom: 1px solid rgba(10,64,110,.12); box-shadow: 0 2px 18px rgba(10,62,105,.06); }
.navbar__inner { min-height: 82px; display: flex; align-items: center; justify-content: space-between; gap: 1.5rem; }
.brand { display: inline-flex; align-items: center; }
.brand img { height: 78px; width: auto; object-fit: contain; }
.nav-links { display: flex; align-items: center; gap: 1.7rem; font-weight: 700; font-size: .92rem; margin-left: auto; }
.nav-links a { color: #1e3a55; position: relative; }
.nav-links a:hover, .nav-links .active { color: var(--blue-700); }
/* .active covers the hardcoded fallback nav; the current-menu-* classes are
   what WordPress adds once a real menu is assigned in Appearance → Menus. */
.nav-links .current-menu-item > a, .nav-links .current-menu-ancestor > a, .nav-links .current_page_item > a { color: var(--blue-700); }
.nav-links a.active::after,
.nav-links .current-menu-item > a::after,
.nav-links .current-menu-ancestor > a::after,
.nav-links .current_page_item > a::after { content: ""; position: absolute; left: 0; right: 0; bottom: -6px; height: 3px; border-radius: 3px; background: var(--yellow); }
.nav-actions { display: flex; align-items: center; gap: .7rem; }
.menu-toggle { display: none; background: none; border: 0; padding: .4rem; }
.menu-toggle span { display: block; width: 26px; height: 3px; margin: 5px 0; border-radius: 999px; background: var(--ink); }

/* ---------- Nav menus & dropdowns ---------- */
/* Once a menu is assigned in Appearance → Menus the links arrive wrapped in a
   <ul class="nav-menu">, so the flex layout that used to sit on .nav-links
   (whose children were bare anchors) has to move onto the list itself. */
.nav-links { align-self: stretch; }
.nav-links .nav-menu { display: flex; align-items: center; gap: 1.7rem; height: 100%; list-style: none; margin: 0; padding: 0; }
.nav-links .nav-menu > li { position: relative; display: flex; align-items: center; height: 100%; }
.nav-links .nav-menu > li > a { display: inline-flex; align-items: center; }

.nav-links .sub-menu { position: absolute; top: 100%; left: -.9rem; min-width: 248px; margin: 0; padding: .45rem 0; list-style: none; background: #fff; border: 1px solid var(--line); border-radius: 10px; box-shadow: 0 14px 30px rgba(10,44,73,.14); opacity: 0; visibility: hidden; transform: translateY(6px); transition: opacity .18s ease, transform .18s ease, visibility .18s; z-index: 50; }
.nav-links .sub-menu li { display: block; }
.nav-links .sub-menu a { display: block; padding: .62rem 1.1rem; white-space: nowrap; font-weight: 700; }
.nav-links .sub-menu a:hover { background: var(--blue-050); color: var(--blue-700); }
.nav-links .nav-menu > li:hover > .sub-menu,
.nav-links .nav-menu > li:focus-within > .sub-menu,
.nav-links .nav-menu > li.is-expanded > .sub-menu { opacity: 1; visibility: visible; transform: translateY(0); }
/* Underline marks the item, not the whole dropdown row. */
.nav-links .sub-menu a::after { display: none; }

/* Visible at every width, not just mobile: a hover-only dropdown is
   unreachable on a touch tablet, which is wide enough to miss the mobile
   breakpoint but has no hover. */
.sub-toggle { display: inline-flex; align-items: center; background: none; border: 0; padding: .2rem; margin-left: .1rem; color: inherit; cursor: pointer; line-height: 0; }
.sub-toggle .ico { width: 15px; height: 15px; transition: transform .2s ease; }
.nav-links .nav-menu > li.is-expanded > .sub-toggle .ico { transform: rotate(180deg); }

/* Footer columns reuse the same markup but must stay a plain indented list —
   never a floating hover panel. */
.footer .nav-menu { list-style: none; margin: 0; padding: 0; }
.footer .nav-menu .sub-menu { position: static; opacity: 1; visibility: visible; transform: none; display: block; min-width: 0; margin: 0 0 .35rem; padding: 0 0 0 .9rem; background: none; border: 0; border-left: 2px solid rgba(255,255,255,.18); border-radius: 0; box-shadow: none; }
.footer .nav-menu .sub-menu a { padding: 0; white-space: normal; font-weight: inherit; }
.footer .nav-menu .sub-menu a:hover { background: none; }
.footer .sub-toggle { display: none; }

/* ---------- Buttons ---------- */
.btn { display: inline-flex; align-items: center; justify-content: center; gap: .55rem; border-radius: 10px; border: 0; padding: .95rem 1.5rem; font-weight: 800; text-transform: uppercase; letter-spacing: .015em; cursor: pointer; transition: transform .2s ease, box-shadow .2s ease, background .2s ease, color .2s ease; }
.btn:hover { transform: translateY(-2px); }
.btn--accent { background: var(--yellow); color: var(--yellow-ink); box-shadow: 0 12px 26px rgba(242, 178, 0, .40); }
.btn--accent:hover { background: var(--yellow-600); }
.btn--primary { background: linear-gradient(135deg, var(--blue-700), var(--blue-900)); color: #fff; box-shadow: 0 12px 28px rgba(8, 91, 151, .30); }
.btn--phone { background: linear-gradient(135deg, var(--blue-700), var(--blue-900)); color: #fff; box-shadow: 0 10px 22px rgba(8, 91, 151, .28); }
.btn--phone:hover { filter: brightness(1.06); }
.btn--outline { border: 2px solid var(--blue-700); color: var(--blue-700); background: #fff; padding: calc(.95rem - 2px) calc(1.5rem - 2px); }
.btn--outline:hover { background: var(--blue-050); }
.btn--full { width: 100%; }
.btn--sm { padding: .68rem 1.05rem; font-size: .8rem; border-radius: 9px; }

@keyframes ctaPulse {
  0%   { box-shadow: 0 12px 26px rgba(242,178,0,.40), 0 0 0 0 rgba(var(--accent-rgb),.55); }
  70%  { box-shadow: 0 12px 26px rgba(242,178,0,.40), 0 0 0 16px rgba(var(--accent-rgb),0); }
  100% { box-shadow: 0 12px 26px rgba(242,178,0,.40), 0 0 0 0 rgba(var(--accent-rgb),0); }
}
.btn--pulse { animation: ctaPulse 2.1s cubic-bezier(.66,0,0,1) infinite; }
@media (prefers-reduced-motion: reduce) { .btn--pulse { animation: none; } }

/* ---------- Hero (light but bluer) ---------- */
.hero { position: relative; overflow: hidden; background: linear-gradient(180deg, var(--tint-a) 0%, var(--tint-b) 52%, var(--tint-c) 100%); }
.hero::before { content: ""; position: absolute; width: 680px; height: 680px; right: -200px; top: -240px; background: radial-gradient(circle, rgba(var(--primary-rgb), .30), rgba(var(--primary-rgb), 0) 68%); pointer-events: none; }
.hero::after { content: ""; position: absolute; width: 520px; height: 520px; left: -180px; bottom: -220px; background: radial-gradient(circle, rgba(var(--accent-rgb), .22), rgba(var(--accent-rgb), 0) 70%); pointer-events: none; }
.hero__grid { position: relative; z-index: 1; display: grid; grid-template-columns: minmax(0, 1.1fr) 440px; gap: 56px; align-items: center; padding: 72px 0 84px; }
.hero__copy--media { align-self: start; }
.hero__copy--media + .quote-wrap { align-self: start; margin-top: 0; }
.hero-video { position: relative; width: 100%; max-width: 650px; aspect-ratio: 16 / 9; margin: 0 0 1.35rem; overflow: hidden; border: 8px solid #fff; border-radius: 16px; background: #fff; box-shadow: 0 22px 52px rgba(8, 40, 70, .20); }
.hero-video::after { content: ""; position: absolute; inset: 0; border: 1px solid rgba(255,255,255,.55); border-radius: 8px; pointer-events: none; }
.hero-video video { width: 100%; height: 100%; display: block; object-fit: cover; }
.hero-video--sm { max-width: 380px; margin-bottom: 1rem; }

/* Suppress the native start-playback overlay.
   Safari paints this whenever it refuses autoplay (Low Power Mode, the
   per-site "Auto-Play: Never" setting, Reduce Motion) — that is the play
   button reported on macOS. Each selector MUST stay in its own rule: an
   engine that does not recognise one selector discards the entire
   comma-separated list, which would silently disable the fix. */
.hero-video video::-webkit-media-controls-start-playback-button { display: none !important; -webkit-appearance: none; }
.hero-video video::-webkit-media-controls-overlay-play-button { display: none !important; -webkit-appearance: none; }
.hero-video video::-webkit-media-controls-play-button { display: none !important; -webkit-appearance: none; }
.hero-video video::-webkit-media-controls { display: none !important; }
.hero-video video::-webkit-media-controls-panel { display: none !important; }
.hero-video video::-webkit-media-controls-overlay-enclosure { display: none !important; }

/* Give-up / reduced-motion state: paint the poster still and drop the video
   entirely, so there is never a frozen frame with dead chrome over it. */
.hero-video.is-static { background-image: var(--hero-poster); background-size: cover; background-position: center; background-repeat: no-repeat; }
.hero-video.is-static video { visibility: hidden; }
/* Our own play control for the static state — shown only when the browser
   refused autoplay or the visitor prefers reduced motion, so they still have a
   way to start the video. */
.hero-video__play { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); z-index: 2; display: grid; place-items: center; width: 68px; height: 68px; padding: 0; border: 0; border-radius: 999px; background: rgba(255,255,255,.92); color: var(--blue-700); cursor: pointer; box-shadow: 0 10px 30px rgba(8,40,70,.28); transition: transform .18s ease, background .18s ease; }
.hero-video__play:hover { transform: translate(-50%, -50%) scale(1.06); background: #fff; }
.hero-video__play svg { width: 30px; height: 30px; margin-left: 3px; }
.eyebrow { display: inline-flex; align-items: center; gap: .45rem; color: var(--blue-700); font-weight: 900; text-transform: uppercase; letter-spacing: .07em; font-size: .76rem; margin-bottom: .7rem; background: #fff; border: 1px solid rgba(var(--primary-rgb),.28); padding: .5rem .9rem; border-radius: 999px; box-shadow: 0 6px 16px rgba(10,62,105,.10); }
.eyebrow .ico { color: var(--yellow); }
.hero h1 { margin: .5rem 0 1rem; font-size: clamp(2.4rem, 4.6vw, 4.4rem); line-height: 1.02; letter-spacing: -.03em; color: var(--ink); max-width: 15ch; }
.hero h1 .fixed-line { display: block; }
h1 span.hl, h2 span { color: var(--blue-600); }

.rotator { display: inline-block; color: var(--blue-600); position: relative; }
.hero h1 .rotator { display: block; }
.rotator__word { display: inline-block; transition: transform .38s ease, opacity .38s ease; }
.rotator.is-out .rotator__word { opacity: 0; transform: translateY(-14px); }
.rotator::after { content: ""; display: inline-block; width: 3px; height: 1em; margin-left: 4px; background: var(--yellow); vertical-align: -0.12em; animation: caret 1s steps(1) infinite; }
@keyframes caret { 50% { opacity: 0; } }
@media (prefers-reduced-motion: reduce) { .rotator__word { transition: none; } .rotator::after { animation: none; } }

.hero p { max-width: 620px; color: #35506a; font-size: 1.04rem; }
.hero__actions { margin-top: 1.9rem; display: flex; flex-wrap: wrap; gap: 1rem; align-items: center; }
.trust-pills { margin-top: 1.5rem; display: flex; flex-wrap: wrap; gap: .9rem 1.4rem; color: #33495d; font-weight: 700; font-size: .9rem; }
.trust-pills span { display: inline-flex; gap: .45rem; align-items: center; }
.trust-pills .tick { color: var(--green); }

/* ---------- Trust strip (deep blue contrast band) ---------- */
.trust-strip { background: linear-gradient(90deg, var(--blue-900), var(--blue-700)); color: #fff; }
.trust-strip__inner { display: flex; flex-wrap: wrap; justify-content: center; gap: 1rem 2.3rem; padding: 18px 0; font-weight: 800; font-size: .9rem; }
.trust-strip span { display: inline-flex; align-items: center; gap: .5rem; }
.trust-strip .b-ico { color: var(--yellow); }

/* ---------- Quote box (reference match) ---------- */
.quote-wrap { position: relative; width: 100%; padding-top: 16px; }
.quote-offer { position: absolute; top: 0; left: 50%; transform: translateX(-50%); z-index: 3; display: inline-flex; align-items: center; gap: .4rem; background: var(--green); color: #fff; font-weight: 800; text-transform: uppercase; letter-spacing: .03em; font-size: .72rem; padding: .5rem .95rem; border-radius: 999px; box-shadow: 0 10px 22px rgba(var(--green-rgb), .34); white-space: nowrap; }
.quote-offer .ico { color: #fff; }
.quote-card { background: #fff; border-radius: var(--radius); box-shadow: var(--shadow); border: 1px solid rgba(10,62,105,.10); overflow: hidden; }
.quote-card__head { background: linear-gradient(135deg, var(--blue-700), var(--blue-900)); color: #fff; text-align: center; padding: 40px 28px 30px; }
.quote-card__head h2 { margin: 0; font-size: 1.46rem; letter-spacing: -.01em; }
.quote-card__head p { margin: .7rem 0 0; color: rgba(255,255,255,.88); font-size: .86rem; font-weight: 600; }
.quote-card__body { padding: 30px 28px 28px; }
.form-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1.25rem 1rem; }
label { color: #37506a; font-size: .78rem; font-weight: 800; display: block; }
input, select, textarea { width: 100%; min-width: 0; margin-top: .5rem; border: 1px solid #cbdcec; border-radius: 10px; padding: .95rem 1rem; background: #f9fcff; color: var(--text); outline: 0; transition: border .2s ease, box-shadow .2s ease; }
select { padding-right: 2.25rem; text-overflow: ellipsis; }
input:focus, select:focus, textarea:focus { border-color: var(--blue-600); box-shadow: 0 0 0 4px rgba(var(--primary-rgb),.14); }
textarea { resize: vertical; }
.span-2 { grid-column: span 2; }
.quote-card__body .btn { margin-top: 1.75rem; }
.quote-trust { margin-top: 1.5rem; padding-top: 1.35rem; border-top: 1px solid rgba(10,62,105,.10); display: flex; flex-wrap: wrap; justify-content: center; gap: .55rem 1.2rem; color: var(--muted); font-size: .8rem; font-weight: 700; }
.quote-trust span { display: inline-flex; align-items: center; gap: .35rem; }
.quote-trust .ico { color: var(--green); }

/* ---------- Message card ---------- */
.message-card { background: #fff; color: var(--text); border-radius: var(--radius); padding: 28px; box-shadow: var(--shadow); border: 1px solid rgba(10,62,105,.10); }
.message-card h2 { margin: 0 0 1.25rem; color: var(--blue-900); text-align: center; text-transform: uppercase; letter-spacing: .05em; font-size: 1.28rem; }
.message-card .btn { margin-top: 1rem; }

/* ---------- About ---------- */
.split-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 80px; align-items: center; }
.section-copy h2, .section-heading h2, .testimonial-head h2, .contact-copy h2 { margin: 0 0 1rem; font-size: clamp(2rem, 3.1vw, 3.1rem); line-height: 1.06; letter-spacing: -.035em; color: var(--blue-900); }
.check-list { list-style: none; padding: 0; margin: 1.5rem 0 2rem; display: grid; gap: .7rem; }
.check-list li { position: relative; padding-left: 2rem; font-weight: 700; color: #35485c; }
.check-list li::before { content: ""; position: absolute; left: 0; top: 1px; width: 22px; height: 22px; border-radius: 999px; background: var(--blue-700) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23ffffff' stroke-width='3.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E") center/13px no-repeat; }
.about-media { position: relative; min-height: 460px; }
.about-media__main { width: 82%; height: 430px; object-fit: cover; border-radius: var(--radius); box-shadow: var(--shadow); margin-left: auto; }
.about-media__inset { position: absolute; left: 0; bottom: 20px; width: 250px; height: 250px; object-fit: cover; border-radius: 50%; border: 10px solid #fff; box-shadow: var(--shadow); }
.experience-badge { position: absolute; right: -8px; top: 54px; display: flex; align-items: center; gap: .8rem; background: var(--yellow); color: var(--yellow-ink); border-radius: var(--radius); padding: 1.2rem 1.35rem; box-shadow: 0 16px 34px rgba(242,178,0,.36); }
.experience-badge strong { font-size: 3rem; line-height: .9; }
.experience-badge span { font-weight: 900; }

/* ---------- CTA band ---------- */
.cta-band { padding: 0 0 72px; background: #fff; }
.cta-band__inner { border-radius: var(--radius); background: linear-gradient(135deg, var(--blue-900), var(--blue-600)); color: #fff; padding: 34px 44px; display: flex; align-items: center; justify-content: space-between; gap: 1.2rem; box-shadow: var(--shadow); }
.cta-band h2 { margin: 0; font-size: clamp(1.7rem, 3vw, 2.6rem); }
.cta-band p { margin: .25rem 0 0; color: rgba(255,255,255,.9); font-weight: 700; }

.call-banner { background: var(--blue-075); padding: 56px 0 56px; }
.call-banner__inner { background: var(--callbanner-bg); color: #fff; text-align: center; padding: 56px 28px 60px; box-shadow: 0 18px 48px rgba(var(--callbanner-rgb), .16); }
.call-banner h2 { margin: 0 0 .45rem; color: #fff; font-size: clamp(1.65rem, 3vw, 2.35rem); line-height: 1.08; text-transform: uppercase; letter-spacing: .01em; }
.call-banner p { margin: 0 auto 1.15rem; max-width: 660px; color: rgba(255,255,255,.94); font-weight: 800; }
.call-banner strong { display: block; margin: .45rem 0 1.25rem; color: #fff; font-size: clamp(1.15rem, 2.3vw, 1.55rem); letter-spacing: .08em; text-transform: uppercase; }
.call-banner .btn { background: #fff; color: var(--green-700); border-radius: 999px; box-shadow: 0 10px 24px rgba(0,0,0,.12); }
.call-banner .btn:hover { background: var(--blue-050); }

/* ---------- Sections / headings ---------- */
.section-heading { max-width: 720px; margin-bottom: 28px; }
.section-heading--row, .testimonial-head { max-width: none; display: flex; align-items: flex-end; justify-content: space-between; gap: 1.5rem; }
.testimonial-head { margin-bottom: 30px; }

/* ---------- Services ---------- */
.services__grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 24px; }
.service-card { position: relative; display: flex; flex-direction: column; background: #fff; border-radius: 10px; overflow: hidden; box-shadow: 0 16px 38px rgba(8, 40, 70, .11); border: 1px solid rgba(10,62,105,.14); transition: transform .2s ease, box-shadow .2s ease, border-color .2s ease; }
.service-card::before { content: ""; position: absolute; left: 0; right: 0; top: 0; height: 4px; z-index: 2; background: linear-gradient(90deg, var(--yellow), var(--blue-600)); }
.service-card:hover { transform: translateY(-5px); border-color: rgba(var(--primary-rgb),.32); box-shadow: 0 24px 60px rgba(8, 40, 70, .18); }
.service-card img { height: 176px; width: 100%; object-fit: cover; filter: saturate(1.06); }
.service-card__body { padding: 22px 20px 20px; text-align: left; display: flex; flex-direction: column; flex: 1; }
.service-card h3 { margin: 0 0 .5rem; color: var(--blue-900); line-height: 1.18; min-height: 42px; font-size: 1.04rem; }
.service-card p { margin: 0 0 1.15rem; color: var(--muted); font-size: .9rem; flex: 1; }
.service-card .btn { align-self: stretch; min-height: 42px; }
.mini-card h3, .step h3 { margin: 0 0 .35rem; color: var(--blue-900); line-height: 1.2; }
.mini-card p, .step p { margin: 0; color: var(--muted); font-size: .92rem; }
.icon { display: grid; place-items: center; width: 56px; height: 56px; border-radius: 16px; background: linear-gradient(135deg, var(--blue-600), var(--blue-800)); color: #fff; }
.icon .ico { width: 26px; height: 26px; }

/* ---------- Why choose ---------- */
.why__grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 22px; }
.mini-card { background: #fff; border: 1px solid rgba(10,62,105,.12); border-radius: var(--radius-sm); box-shadow: var(--shadow-soft); padding: 26px 22px; }
.mini-card .icon { margin-bottom: 1rem; }

/* ---------- Work ---------- */
.work__grid { display: grid; grid-template-columns: repeat(5, 1fr); gap: 20px; }
.project-card { margin: 0; background: #fff; border: 1px solid var(--line); border-radius: var(--radius-sm); overflow: hidden; box-shadow: var(--shadow-soft); }
.project-card img { height: 130px; width: 100%; object-fit: cover; }
.project-card figcaption { padding: .75rem .9rem; font-weight: 900; color: var(--blue-900); font-size: .86rem; }

/* ---------- Testimonials ---------- */
.rating { display: flex; align-items: center; gap: .55rem; background: #fff; border-radius: 999px; padding: .8rem 1rem; box-shadow: var(--shadow-soft); border: 1px solid var(--line); white-space: nowrap; }
.rating strong { color: var(--blue-900); font-size: 1.5rem; }
.rating .stars, .stars { color: var(--star); display: inline-flex; gap: 1px; }
.stars__empty { display: inline-flex; gap: 1px; color: var(--line); }
.rating .stars .ico, .stars .ico { width: 15px; height: 15px; }
.rating small { color: var(--muted); font-weight: 700; }
.testimonials__grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 24px; }
.testimonial { position: relative; background: #fff; border: 1px solid rgba(10,62,105,.14); border-radius: 10px; box-shadow: 0 16px 38px rgba(8, 40, 70, .11); padding: 32px; overflow: hidden; }
.testimonial::before { content: "\201C"; position: absolute; right: 25px; bottom: 0; font-size: 6rem; color: #dcefff; font-weight: 900; line-height: .8; }
.testimonial p { color: #3c4f60; margin: 0 0 1rem; position: relative; z-index: 1; }
.testimonial .stars { margin-bottom: .4rem; }
.testimonial h3 { margin: .3rem 0 0; color: var(--blue-900); }
.testimonial span { color: var(--muted); font-size: .9rem; font-weight: 700; }

/* ---------- Process ---------- */
.process__grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 25px; }
.step { background: #fff; border: 1px solid rgba(10,62,105,.12); border-radius: var(--radius-sm); box-shadow: var(--shadow-soft); padding: 32px 26px; text-align: center; position: relative; }
.step > .step-num { position: absolute; top: 22px; right: 22px; width: 42px; height: 42px; border-radius: 999px; background: var(--yellow); color: var(--yellow-ink); display: grid; place-items: center; font-weight: 900; }
.step .icon { margin: 0 auto 1rem; }

/* ---------- Contact (bluer, more filled) ---------- */
.contact { position: relative; overflow: hidden; background: linear-gradient(180deg, var(--contact-a), var(--contact-b)); }
.contact::before { content: ""; position: absolute; width: 560px; height: 560px; left: -180px; top: -170px; background: radial-gradient(circle, rgba(var(--primary-rgb),.20), rgba(var(--primary-rgb),0) 70%); }
.contact::after { content: ""; position: absolute; width: 420px; height: 420px; right: -160px; bottom: -180px; background: radial-gradient(circle, rgba(var(--accent-rgb),.16), rgba(var(--accent-rgb),0) 72%); }
.contact__grid { position: relative; z-index: 1; display: grid; grid-template-columns: .9fr 1.2fr; gap: 60px; align-items: center; padding: 84px 0; }
.contact-copy h2 { color: var(--blue-900); }
.contact-copy p { color: #35506a; max-width: 510px; }
.contact-list { padding: 0; margin: 1.5rem 0 0; list-style: none; display: grid; gap: .85rem; font-weight: 800; color: var(--ink); }
.contact-list li { display: flex; align-items: center; gap: .65rem; }
.contact-list .ico { color: var(--blue-700); width: 1.2em; height: 1.2em; }
.contact-list a:hover { color: var(--blue-700); }

/* ---------- Footer ---------- */
.footer { background: linear-gradient(180deg, var(--footer-a), var(--footer-b)); color: rgba(255,255,255,.78); }
.footer__grid { padding: 54px 0 42px; display: grid; grid-template-columns: 1.25fr .8fr 1fr 1.2fr; gap: 40px; }
.footer__brand { display: inline-block; margin-bottom: 1.15rem; }
.footer__brand img { width: auto; height: 88px; object-fit: contain; }
.footer h3 { margin: 0 0 1rem; color: #fff; }
.footer a, .footer .f-line { display: flex; align-items: center; gap: .5rem; margin-bottom: .55rem; color: rgba(255,255,255,.78); }
.footer .col-links a { display: block; }
.footer a:hover { color: #fff; }
.footer .ico { color: var(--blue-300); }
.footer__social { display: flex; gap: .5rem; margin-top: 1rem; }
.footer__social a { display: grid; place-items: center; width: 34px; height: 34px; border-radius: 999px; background: rgba(255,255,255,.16); color: #fff; margin: 0; transition: background .2s ease, transform .2s ease; }
.footer__social a:hover { background: var(--yellow); color: var(--yellow-ink); transform: translateY(-2px); }
.footer__social .ico { color: currentColor; width: 17px; height: 17px; }
.newsletter { margin-top: 1rem; }
.newsletter label { color: #fff; font-size: 1rem; display: block; margin-bottom: .5rem; }
.newsletter div { display: flex; gap: .35rem; }
.newsletter input { margin: 0; min-width: 0; background: #fff; }
.newsletter button { border: 0; border-radius: 8px; padding: 0 .9rem; background: var(--yellow); color: var(--yellow-ink); font-weight: 900; text-transform: uppercase; font-size: .75rem; cursor: pointer; }
.footer__bottom { text-align: center; padding: 18px; border-top: 1px solid rgba(255,255,255,.08); color: rgba(255,255,255,.55); font-size: .85rem; }

/* ---------- Floating mobile call banner ---------- */
.mobile-callbar { display: none; position: fixed; left: 16px; right: 16px; bottom: 16px; z-index: 60; }
.mobile-callbar .mc-call { display: flex; align-items: center; justify-content: center; gap: .55rem; width: 100%; padding: 15px; border-radius: 999px; background: var(--yellow); color: var(--yellow-ink); font-weight: 900; text-transform: uppercase; letter-spacing: .02em; font-size: .95rem; box-shadow: 0 14px 32px rgba(8,26,44,.30); animation: ctaPulse 2.1s cubic-bezier(.66,0,0,1) infinite; }
.mobile-callbar .mc-call .ico { width: 1.2em; height: 1.2em; }
@media (prefers-reduced-motion: reduce) { .mobile-callbar .mc-call { animation: none; } }

/* ---------- Blog ---------- */
.page-hero { position: relative; overflow: hidden; background: linear-gradient(180deg, var(--tint-a), var(--tint-page)); padding: 66px 0 54px; text-align: center; }
.page-hero::before { content: ""; position: absolute; width: 560px; height: 560px; right: -180px; top: -220px; background: radial-gradient(circle, rgba(var(--primary-rgb),.22), rgba(var(--primary-rgb),0) 70%); }
.page-hero .container { position: relative; z-index: 1; }
.page-hero h1 { margin: .4rem 0 .8rem; font-size: clamp(2.2rem, 4vw, 3.4rem); letter-spacing: -.03em; color: var(--ink); }
.page-hero p { max-width: 620px; margin: 0 auto; color: #35506a; font-size: 1.05rem; }
.social-cta { display: flex; flex-wrap: wrap; justify-content: center; gap: 1rem; margin-top: 1.8rem; }
.social-btn { display: inline-flex; align-items: center; gap: .55rem; padding: .8rem 1.3rem; border-radius: 999px; font-weight: 800; color: #fff; box-shadow: var(--shadow-soft); transition: transform .2s ease; }
.social-btn:hover { transform: translateY(-2px); }
.social-btn .ico { width: 18px; height: 18px; }
/* Official platform colours — deliberately not themeable. */
.social-btn.fb, .social-btn.facebook { background: #1877f2; }
.social-btn.ig, .social-btn.instagram { background: linear-gradient(135deg, #f9ce34, #ee2a7b, #6228d7); }
.social-btn.li, .social-btn.linkedin { background: #0a66c2; }
.footer__legal { margin-top: .5rem; font-size: .85rem; opacity: .8; }
.footer__legal a { text-decoration: underline; }
.social-btn.youtube { background: #ff0000; }
.social-btn.tiktok { background: #010101; }
.social-btn.x { background: #000000; }
.post-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 26px; }
.post-card { display: flex; flex-direction: column; background: #fff; border: 1px solid var(--line); border-radius: var(--radius-sm); overflow: hidden; box-shadow: var(--shadow-soft); transition: transform .2s ease, box-shadow .2s ease; }
.post-card:hover { transform: translateY(-5px); box-shadow: 0 24px 60px rgba(8,40,70,.20); }
.post-card img { height: 190px; width: 100%; object-fit: cover; }
.post-card__body { padding: 22px; display: flex; flex-direction: column; flex: 1; }
.post-tag { align-self: flex-start; background: var(--blue-100); color: var(--blue-700); font-weight: 800; font-size: .72rem; text-transform: uppercase; letter-spacing: .04em; padding: .3rem .7rem; border-radius: 999px; margin-bottom: .8rem; }
.post-card h3 { margin: 0 0 .5rem; color: var(--blue-900); line-height: 1.25; }
.post-card p { margin: 0 0 1rem; color: var(--muted); font-size: .92rem; flex: 1; }
.post-meta { color: var(--muted); font-size: .8rem; font-weight: 700; }
.post-link { color: var(--blue-700); font-weight: 800; font-size: .88rem; margin-top: .8rem; display: inline-flex; gap: .35rem; }
.blog-note { text-align: center; color: var(--muted); font-weight: 700; margin-top: 2.5rem; }

/* ---------- Service pages ---------- */
.service-info { background: #fff; padding: 48px 0 42px; }
.service-info__inner { max-width: 980px; margin: 0 auto; }
.service-info h2 { margin: 0 0 1rem; color: var(--blue-900); font-size: clamp(2rem, 3.2vw, 2.85rem); line-height: 1.05; letter-spacing: -.03em; }
.service-info h2 span { color: var(--blue-600); }
.service-info p { margin: 0 0 .95rem; color: #354a5e; font-size: 1.02rem; line-height: 1.68; }
.service-info h3 { margin: 1.3rem 0 .75rem; color: var(--ink); font-size: 1.05rem; }
.service-audience { list-style: none; padding: 0; margin: 0; display: grid; gap: .7rem; max-width: 520px; }
.service-audience li { position: relative; padding-left: 2rem; font-weight: 800; color: #35485c; }
.service-audience li::before { content: ""; position: absolute; left: 0; top: 2px; width: 22px; height: 22px; border-radius: 999px; background: var(--blue-700) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23ffffff' stroke-width='3.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E") center/13px no-repeat; }

/* Hero image band under the page hero */
.service-hero-media { position: relative; margin-top: -28px; z-index: 2; }
.service-hero-media .container { position: relative; }
.service-hero-media img { width: 100%; height: clamp(220px, 34vw, 380px); object-fit: cover; border-radius: var(--radius); box-shadow: var(--shadow); }

/* Two-column intro: copy + lead card */
.service-intro { display: grid; grid-template-columns: 1.25fr .9fr; gap: 56px; align-items: start; }
.service-intro .lead p { color: #354a5e; font-size: 1.04rem; margin: 0 0 1rem; }
.service-intro .lead p:last-child { margin-bottom: 0; }
.service-lead-card { background: #fff; border-radius: var(--radius); box-shadow: var(--shadow-soft); border: 1px solid rgba(10,62,105,.12); border-top: 4px solid var(--blue-600); padding: 26px; }
.service-lead-card h3 { margin: 0 0 .8rem; color: var(--blue-900); font-size: 1.15rem; }
.service-lead-card .check-list { margin: 0; }
.service-lead-card .btn { margin-top: 1.1rem; }

/* Feature grid (what's included) */
.service-features { display: grid; grid-template-columns: repeat(3, 1fr); gap: 22px; }
.service-features .mini-card { text-align: left; }

/* Quote form band reuse: centre the .quote-card on a soft bg */
.service-quote { background: var(--blue-075); }
.service-quote .quote-wrap { max-width: 560px; margin: 0 auto; }

/* Related services cross-links */
.related-services__grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 18px; }
.related-card { display: flex; flex-direction: column; background: #fff; border: 1px solid rgba(10,62,105,.12); border-radius: var(--radius-sm); overflow: hidden; box-shadow: var(--shadow-soft); transition: transform .2s ease, box-shadow .2s ease; }
.related-card:hover { transform: translateY(-4px); box-shadow: 0 20px 48px rgba(8,40,70,.18); }
.related-card img { height: 120px; width: 100%; object-fit: cover; }
.related-card__body { padding: 16px; display: flex; flex-direction: column; flex: 1; }
.related-card h3 { margin: 0 0 .6rem; color: var(--blue-900); font-size: .98rem; line-height: 1.25; }
.related-card .arrow { margin-top: auto; color: var(--blue-700); font-weight: 800; font-size: .85rem; display: inline-flex; gap: .3rem; }

@media (max-width: 1040px) {
  .service-intro { grid-template-columns: 1fr; gap: 36px; }
  .service-features { grid-template-columns: repeat(2, 1fr); }
  .related-services__grid { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 760px) {
  .service-features, .related-services__grid { grid-template-columns: 1fr; }
}

/* ---------- Service page: animated stats ---------- */
.stats { display: grid; grid-template-columns: repeat(4, 1fr); gap: 22px; }
.stat { background: linear-gradient(135deg, var(--blue-700), var(--blue-900)); color: #fff; border-radius: var(--radius-sm); padding: 30px 22px; text-align: center; box-shadow: var(--shadow-soft); position: relative; overflow: hidden; }
.stat::after { content: ""; position: absolute; width: 160px; height: 160px; right: -60px; top: -60px; background: radial-gradient(circle, rgba(var(--accent-rgb),.22), rgba(var(--accent-rgb),0) 70%); }
.stat__num { font-size: clamp(2.2rem, 4vw, 3rem); font-weight: 900; line-height: 1; color: #fff; letter-spacing: -.02em; }
.stat__num .suffix { color: var(--yellow); }
.stat__label { margin-top: .5rem; font-weight: 800; text-transform: uppercase; letter-spacing: .04em; font-size: .82rem; color: rgba(255,255,255,.88); position: relative; z-index: 1; }
.stat__num { position: relative; z-index: 1; }
@media (max-width: 760px) { .stats { grid-template-columns: repeat(2, 1fr); } }

/* ---------- Service page: tabs ---------- */
.tabs__list { display: flex; flex-wrap: wrap; gap: .5rem; justify-content: center; margin-bottom: 2rem; }
.tab { background: #fff; border: 2px solid var(--line); color: var(--blue-900); font-weight: 800; padding: .7rem 1.4rem; border-radius: 999px; cursor: pointer; transition: background .2s ease, color .2s ease, border-color .2s ease; font-size: .9rem; }
.tab:hover { border-color: var(--blue-500); }
.tab[aria-selected="true"] { background: linear-gradient(135deg, var(--blue-700), var(--blue-900)); color: #fff; border-color: transparent; box-shadow: 0 10px 22px rgba(8,91,151,.26); }
.tab-panel { display: none; animation: tabFade .35s ease; }
.tab-panel.is-active { display: block; }
@keyframes tabFade { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }

/* ---------- Service page: FAQ accordion ---------- */
.faq { max-width: 820px; margin: 0 auto; display: grid; gap: 14px; }
.faq-item { background: #fff; border: 1px solid rgba(10,62,105,.14); border-radius: var(--radius-sm); box-shadow: var(--shadow-soft); overflow: hidden; }
.faq-q { width: 100%; display: flex; align-items: center; justify-content: space-between; gap: 1rem; background: none; border: 0; padding: 20px 24px; text-align: left; cursor: pointer; font-weight: 800; color: var(--blue-900); font-size: 1.02rem; }
.faq-q .faq-icon { flex: 0 0 auto; width: 26px; height: 26px; display: grid; place-items: center; border-radius: 999px; background: var(--blue-075); color: var(--blue-700); transition: transform .3s ease, background .2s ease; }
.faq-item.is-open .faq-q { color: var(--blue-700); }
.faq-item.is-open .faq-icon { transform: rotate(45deg); background: var(--yellow); color: var(--yellow-ink); }
.faq-a { max-height: 0; overflow: hidden; transition: max-height .35s ease; }
.faq-a__inner { padding: 0 24px 22px; color: var(--muted); font-size: .98rem; line-height: 1.6; }

/* ---------- Service page: testimonial carousel ---------- */
.carousel { position: relative; max-width: 860px; margin: 0 auto; }
.carousel__viewport { overflow: hidden; border-radius: 10px; padding: 0 4px 4px; }
.carousel__track { display: flex; transition: transform .5s cubic-bezier(.65,0,.35,1); }
.carousel__slide { min-width: 100%; padding: 4px; }
.carousel__slide .testimonial { height: 100%; }
.carousel__nav { display: flex; align-items: center; justify-content: center; gap: 1rem; margin-top: 1.6rem; }
.carousel__btn { display: grid; place-items: center; width: 44px; height: 44px; border-radius: 999px; border: 2px solid var(--line); background: #fff; color: var(--blue-700); cursor: pointer; transition: background .2s ease, border-color .2s ease, transform .2s ease; }
.carousel__btn:hover { background: var(--blue-050); border-color: var(--blue-500); transform: scale(1.06); }
.carousel__dots { display: flex; gap: .5rem; }
.carousel__dot { width: 11px; height: 11px; border-radius: 999px; border: 0; background: var(--blue-300); cursor: pointer; padding: 0; transition: background .2s ease, transform .2s ease; }
.carousel__dot[aria-selected="true"] { background: var(--blue-700); transform: scale(1.2); }

/* ---------- Service page: sub-service feature list ---------- */
/* Image-based feature list variant */
.feature-list { display: grid; gap: 20px; }
.feature-list--cards { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 18px; max-width: 880px; margin-inline: auto; }
.feature-list--cards .feature-list__item { display: flex; align-items: flex-start; gap: 14px; min-width: 0; background: #fff; border: 1px solid rgba(10,62,105,.14); border-radius: 8px; box-shadow: 0 12px 28px rgba(8,40,70,.10); padding: 16px 18px; }
.feature-list--cards .feature-list__icon { flex: 0 0 auto; display: grid; place-items: center; width: 36px; height: 36px; border-radius: 8px; background: linear-gradient(135deg, var(--blue-600), var(--blue-800)); color: #fff; }
.feature-list--cards .feature-list__icon .ico { width: 19px; height: 19px; }
.feature-list--cards .feature-list__text h3 { margin: 0 0 .25rem; color: var(--blue-900); font-size: .98rem; line-height: 1.22; }
.feature-list--cards .feature-list__text p { margin: 0; color: var(--muted); font-size: .84rem; line-height: 1.48; }
.feature-list--images { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 24px; }
.feature-list--images .feature-list__item { position: relative; min-width: 0; height: clamp(230px, 24vw, 280px); padding: 0; overflow: hidden; border: 0; border-radius: 10px; box-shadow: 0 16px 38px rgba(8,40,70,.13); isolation: isolate; }
.feature-list--images .feature-list__item img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; transition: transform .5s ease; }
.feature-list--images .feature-list__item:hover img { transform: scale(1.06); }
.feature-list--images .feature-list__item::after { content: ""; position: absolute; inset: 0; background: linear-gradient(180deg, rgba(6,52,90,.04) 15%, rgba(6,52,90,.55) 62%, rgba(6,52,90,.96) 100%); pointer-events: none; }
.feature-list--images .feature-list__text { position: absolute; left: 0; right: 0; bottom: 0; padding: 20px 20px 18px; color: #fff; z-index: 1; }
.feature-list--images .feature-list__text h3 { margin: 0 0 .3rem; color: #fff; font-size: 1.1rem; line-height: 1.18; }
.feature-list--images .feature-list__text p { margin: 0; color: rgba(255,255,255,.88); font-size: .86rem; line-height: 1.4; }
.feature-list--images .feature-list__num { position: absolute; top: 14px; left: 14px; z-index: 1; display: grid; place-items: center; width: 34px; height: 34px; border-radius: 999px; background: rgba(255,255,255,.92); color: var(--blue-800); font-weight: 900; font-size: .82rem; backdrop-filter: blur(4px); }
.feature-list--images .feature-list__icon { display: none; }
@media (max-width: 760px) { .feature-list--images { grid-template-columns: 1fr 1fr; gap: 18px; } .feature-list--images .feature-list__item { height: 220px; } }
@media (max-width: 520px) { .feature-list--images { grid-template-columns: 1fr; } .feature-list--images .feature-list__item { height: 240px; } }
.feature-list__icon { flex: 0 0 auto; display: grid; place-items: center; width: 50px; height: 50px; border-radius: 14px; background: linear-gradient(135deg, var(--blue-600), var(--blue-800)); color: #fff; }
.feature-list__icon .ico { width: 24px; height: 24px; }
.feature-list__text h3 { margin: 0 0 .3rem; color: var(--blue-900); font-size: 1.04rem; line-height: 1.2; }
.feature-list__text p { margin: 0; color: var(--muted); font-size: .9rem; line-height: 1.5; }

@media (max-width: 760px) { .feature-list, .feature-list--cards { grid-template-columns: 1fr; } }

/* ---------- Service page: trust icon boxes (3-up) ---------- */
.trust-boxes { display: grid; grid-template-columns: repeat(3, 1fr); gap: 22px; }
.trust-box { text-align: center; background: #fff; border: 1px solid rgba(10,62,105,.12); border-radius: var(--radius); box-shadow: var(--shadow-soft); padding: 34px 26px; transition: transform .2s ease, box-shadow .2s ease; }
.trust-box:hover { transform: translateY(-4px); box-shadow: 0 22px 50px rgba(8,40,70,.18); }
.trust-box .icon { margin: 0 auto 1rem; }
.trust-box h3 { margin: 0 0 .4rem; color: var(--blue-900); font-size: 1.12rem; }
.trust-box p { margin: 0; color: var(--muted); font-size: .92rem; }
@media (max-width: 760px) { .trust-boxes { grid-template-columns: 1fr; } }

/* ---------- Reveal ---------- */
.reveal { opacity: 0; transform: translateY(18px); transition: opacity .65s ease, transform .65s ease; }
.reveal.is-visible { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) { .reveal { opacity: 1; transform: none; transition: none; } }

/* ---------- Responsive ---------- */
@media (max-width: 1040px) {
  .hero__grid { grid-template-columns: minmax(0, 1fr) minmax(360px, 440px); gap: 36px; }
  .split-grid, .contact__grid { grid-template-columns: 1fr; }
  .quote-wrap { max-width: 560px; }
  .services__grid { grid-template-columns: repeat(2, 1fr); }
  .why__grid, .work__grid { grid-template-columns: repeat(2, 1fr); }
  .testimonials__grid, .process__grid { grid-template-columns: 1fr; }
  .footer__grid { grid-template-columns: repeat(2, 1fr); }
  .navbar__cta, .navbar__call { display: none; }
  .post-grid { grid-template-columns: repeat(2, 1fr); }
}

@media (max-width: 900px) {
  .hero__grid { grid-template-columns: 1fr; }
}

@media (max-width: 760px) {
  .container { width: min(calc(100% - 28px), var(--container)); }
  .section { padding: 60px 0; }
  .navbar__inner { min-height: 70px; }
  .brand img { height: 62px; width: auto; }
  .menu-toggle { display: block; margin-left: auto; }
  .nav-actions { display: none; }
  .nav-links { position: absolute; left: 0; right: 0; top: 70px; background: #fff; padding: 1rem 20px 1.2rem; display: none; flex-direction: column; align-items: flex-start; gap: 1rem; border-bottom: 1px solid var(--line); box-shadow: 0 14px 30px rgba(10,44,73,.14); }
  .nav-links.is-open { display: flex; }
  .nav-links a.active::after { display: none; }
  .nav-links .current-menu-item > a::after,
  .nav-links .current-menu-ancestor > a::after,
  .nav-links .current_page_item > a::after { display: none; }

  /* Dropdowns become a tap-to-open accordion inside the drawer. */
  .nav-links { align-self: auto; }
  .nav-links .nav-menu { flex-direction: column; align-items: flex-start; gap: 1rem; width: 100%; height: auto; }
  .nav-links .nav-menu > li { width: 100%; flex-wrap: wrap; height: auto; }
  .nav-links .sub-menu { position: static; display: none; opacity: 1; visibility: visible; transform: none; width: 100%; min-width: 0; margin: .55rem 0 0; padding: 0 0 0 .9rem; border: 0; border-left: 2px solid var(--line); border-radius: 0; box-shadow: none; }
  .nav-links .nav-menu > li:hover > .sub-menu,
  .nav-links .nav-menu > li:focus-within > .sub-menu { display: none; }
  .nav-links .nav-menu > li.is-expanded > .sub-menu { display: block; }
  .nav-links .sub-menu a { padding: .5rem 0; white-space: normal; }
  .sub-toggle { display: inline-flex; margin-left: auto; padding: .45rem .5rem; }
  .hero__grid { padding: 42px 0 54px; gap: 34px; }
  .hero-video { border-width: 6px; border-radius: 12px; margin-bottom: 1rem; }
  .hero h1 { font-size: 2.5rem; max-width: none; }
  .hero__actions { gap: .75rem; }
  .hero__actions .btn { width: 100%; }
  .services__grid, .why__grid, .work__grid, .footer__grid, .post-grid { grid-template-columns: 1fr; }
  .cta-band__inner, .section-heading--row, .testimonial-head { align-items: flex-start; flex-direction: column; }
  .cta-band__inner .btn { width: 100%; }
  .about-media { min-height: 390px; }
  .about-media__main { width: 100%; height: 330px; }
  .about-media__inset { width: 160px; height: 160px; border-width: 7px; }
  .experience-badge { right: 8px; top: auto; bottom: 18px; }
  .experience-badge strong { font-size: 2.2rem; }
  .newsletter div { flex-direction: column; }
  .newsletter button { padding: .85rem .9rem; }
}

@media (max-width: 520px) {
  .form-grid { grid-template-columns: 1fr; }
  .span-2 { grid-column: auto; }
  .hero__actions .btn--phone { display: none; }
  .mobile-callbar { display: block; }
  body { padding-bottom: 86px; }
}

/* ---------- Homepage hero — client feedback pass ---------- */
/* (video restored to full size via HTML; eyebrow + trust pills removed via HTML) */

/* Smaller hero heading — specificity .hero.hero--home holds across all breakpoints */
.hero.hero--home h1 { font-size: clamp(2.05rem, 3.6vw, 3.15rem); line-height: 1.07; }

/* Larger, more prominent quote form — desktop only (grid collapses normally below 900px) */
@media (min-width: 901px) {
  .hero--home .hero__grid { grid-template-columns: minmax(0, 1fr) 480px; gap: 52px; }
  .hero--home .quote-card__head { padding: 46px 34px 34px; }
  .hero--home .quote-card__head h2 { font-size: 1.62rem; }
  .hero--home .quote-card__body { padding: 34px 34px 30px; }
  .hero--home .quote-card__body input,
  .hero--home .quote-card__body select { padding: 1.05rem 1.05rem; }
  .hero--home .quote-card__body .btn { font-size: 1.03rem; padding-block: 1.05rem; }
}

/* Mobile: keep hero content aligned to the left ("on side"), never centred */
@media (max-width: 900px) {
  .hero--home .hero__copy { text-align: left; }
  .hero--home .hero__copy .hero__actions { justify-content: flex-start; }
}


/* ---------- Form submission toast (WordPress form handler) ---------- */
.form-toast { position: fixed; top: 18px; left: 50%; transform: translateX(-50%); z-index: 1000; display: flex; align-items: center; gap: .9rem; max-width: min(92vw, 560px); padding: .85rem 1.1rem; border-radius: 12px; box-shadow: 0 18px 46px rgba(8,40,70,.24); font-weight: 700; animation: toastIn .35s ease; }
.form-toast.is-ok { background: #eafaf0; color: #12652f; border: 1px solid #9bdcb2; }
.form-toast.is-error { background: #fdecec; color: #a3221c; border: 1px solid #f0b3ad; }
.form-toast__close { background: none; border: 0; font-size: 1.35rem; line-height: 1; cursor: pointer; color: inherit; opacity: .7; padding: 0 .1rem; }
.form-toast__close:hover { opacity: 1; }
@keyframes toastIn { from { opacity: 0; transform: translate(-50%, -12px); } to { opacity: 1; transform: translate(-50%, 0); } }
@media (prefers-reduced-motion: reduce) { .form-toast { animation: none; } }

/* ---------- Generic Page content (page.php) ---------- */
.page-content { max-width: 820px; }
.page-content > h2 { margin: 2rem 0 .8rem; }
.page-content > h3 { margin: 1.5rem 0 .6rem; }
.page-content > p { margin: 0 0 1.1rem; line-height: 1.7; }
.page-content > ul, .page-content > ol { margin: 0 0 1.2rem; padding-left: 1.3rem; line-height: 1.7; }
.page-content > ul > li, .page-content > ol > li { margin-bottom: .4rem; }
.page-content img { max-width: 100%; height: auto; border-radius: 14px; }
.page-links { margin: 1.5rem 0; font-weight: 700; }


/* ==========================================================================
   BLOCK EDITOR BRIDGE
   Core blocks emit their own wrappers (.wp-block-group, .wp-block-image,
   .wp-block-buttons…) around the theme's class names. These rules make the
   block markup render identically to the hand-written PHP markup above.
   They only ever match inside a block wrapper, so pages still using the
   designed panels are unaffected.
   ========================================================================== */

/* ============================================================
   Block bridge — Hero (.hero--home)
   Append to style.css AFTER the existing hero rules (i.e. after
   the "Homepage hero — client feedback pass" block, ~line 618).
   Four rules only; everything else in the section inherits
   through className without help.
   ============================================================ */

/* 1. Intro paragraph.
      style.css has no global margin reset (line 67 is only box-sizing), so the
      hand-written hero relies on the UA <p> margin of 1em. core/group's flow
      layout emits
        :where(.is-layout-flow) > * { margin-block-start: var(--wp--style--block-gap);
                                      margin-block-end: 0 }
      which is 0-specificity but still beats the UA sheet, replacing 1em/1em
      with 24px/0. Restore it. 1em of .hero p's 1.04rem (style.css:209) is the
      exact value being replaced.

      Nothing ELSE in .hero__copy--media needs touching, and a blanket
      `.hero__copy--media > * { margin: 0 }` must NOT be used: .hero-video
      (style.css:167 and :573) is also (0,1,0), so an appended blanket rule
      wins the tie and silently deletes its 1.35rem bottom margin. .hero h1
      (0,1,1) and .hero__actions (0,1,0, unchallenged) are already safe. */
.hero__copy--media > p { margin-block: 1.04em; }

/* 2. Action buttons — the only place className inheritance genuinely fails.
      core/button puts its className on the WRAPPER <div class="wp-block-button">,
      never on the <a>. Putting .btn/.btn--accent/.btn--phone there would still
      MATCH style.css:140-146 — it would just paint a second inline-flex,
      .95rem/1.5rem-padded, yellow, shadowed box AROUND the real button, which
      theme.json's styles.elements.button has already styled yellow. So the
      pattern uses collision-free marker classes and this block re-points
      style.css lines 140-158 at .wp-block-button__link.

      Specificity (0,3,0) beats both core's .wp-block-button__link (0,1,0) and
      theme.json's generated `.wp-element-button, .wp-block-button__link` (0,1,0),
      so it holds regardless of stylesheet print order. */
.hero__actions .wp-block-button > .wp-block-button__link {
  display: inline-flex; align-items: center; justify-content: center; gap: .55rem;
  border-radius: 10px; border: 0; padding: .95rem 1.5rem;
  font-weight: 800; text-transform: uppercase; letter-spacing: .015em;
  cursor: pointer;
  transition: transform .2s ease, box-shadow .2s ease, background .2s ease, color .2s ease;
}
.hero__actions .wp-block-button > .wp-block-button__link:hover { transform: translateY(-2px); }

/* Re-uses @keyframes ctaPulse, already defined at style.css:152. */
.hero__actions .is-hero-cta > .wp-block-button__link {
  background: var(--yellow); color: var(--yellow-ink);
  box-shadow: 0 12px 26px rgba(242, 178, 0, .40);
  animation: ctaPulse 2.1s cubic-bezier(.66,0,0,1) infinite;
}
.hero__actions .is-hero-cta > .wp-block-button__link:hover { background: var(--yellow-600); }

.hero__actions .is-hero-call > .wp-block-button__link {
  background: linear-gradient(135deg, var(--blue-700), var(--blue-900)); color: #fff;
  box-shadow: 0 10px 22px rgba(8, 91, 151, .28);
}
.hero__actions .is-hero-call > .wp-block-button__link:hover { filter: brightness(1.06); }

@media (prefers-reduced-motion: reduce) {
  .hero__actions .is-hero-cta > .wp-block-button__link { animation: none; }
}

/* 3. Responsive parity with style.css:576 and style.css:592, which target
      `.hero__actions .btn` and no longer match. */
@media (max-width: 760px) {
  .hero__actions .wp-block-button,
  .hero__actions .wp-block-button > .wp-block-button__link { width: 100%; }
}
@media (max-width: 520px) {
  .hero__actions .is-hero-call { display: none; }
}

/* ------------------------------------------------------------
   DELIBERATELY NOT INCLUDED — verified redundant or harmful:

   .hero.wp-block-group, .hero__grid.wp-block-group { margin:0; padding:0 }
       HARMFUL. (0,2,0) beats .hero__grid's `padding: 72px 0 84px`
       (style.css:164) and would flatten the hero. Core adds no spacing to
       .wp-block-group at all.

   .hero__grid > * { margin-block: 0 }
       Redundant. The only child at risk is .quote-wrap, and
       `.hero__copy--media + .quote-wrap { margin-top: 0 }` (style.css:166,
       specificity 0,2,0) already beats the :where() flow rule. The copy
       column is :first-child, which the flow rule zeroes itself.

   .hero__actions.wp-block-buttons { margin-top: 1.9rem }
       Redundant. `.hero__actions` (style.css:210) applies directly to the
       div.wp-block-buttons and nothing outranks it — the flow-layout margin
       rule is :where(), specificity 0.

   .hero__actions .wp-block-button { margin: 0 }
       Redundant. .wp-block-buttons is a FLEX layout, so the
       :where(.is-layout-flow) margin rules never reach its children.

   .hero__actions .wp-block-button__link .ico { flex: 0 0 auto }
       Redundant. style.css:79 already sets `flex: 0 0 auto` on .ico globally.

   Gap overrides for .hero__actions / .hero__grid
       Redundant. Core emits gaps via :where(...) at specificity 0;
       .hero__actions{gap:1rem} and .hero__grid{gap:56px} both win.
   ------------------------------------------------------------ */

/* OPTIONAL, editor-only (assets/editor.css). editor.css:74 already labels
   .wp-block-shortcode, and editor.css:86 already spaces .wp-block-buttons.
   Adding the button skin to the editor canvas is cosmetic only:
   .editor-styles-wrapper .hero__actions .wp-block-button > .wp-block-button__link
     { display: inline-flex; gap: .55rem; }  */

/* ---------- Block bridge: trust strip ---------- */

/* Each badge was a hand-written bare <span> (front-page.php:89); it is now a
   core/paragraph. style.css:218 `.trust-strip span { display:inline-flex;
   align-items:center; gap:.5rem; }` therefore no longer matches. Restate it on
   .trust-badge.

   `margin:0` is REQUIRED, for two independent reasons:
     1. style.css has no `p` reset (the only reset is `* { box-sizing }` at
        line 67 and `body { margin:0 }` at line 70), so the UA default
        `margin: 1em 0` would apply.
     2. layout:{"type":"default"} makes core print flow-layout CSS
        (`:where(.is-layout-flow) > * { margin-block-start: … }`) server-side.
        These children are flex items of .trust-strip__inner, so a block-start
        margin would push badges 2-5 down out of alignment.
   Both are beaten by this selector: :where() is 0 specificity, the UA rule is
   lower still, and this is (0,2,0). */
.trust-strip .trust-badge {
	margin: 0;
	display: inline-flex;
	align-items: center;
	gap: .5rem;
}

/* [focusx_icon] returns focusx_icon()'s output verbatim (inc/blocks.php:149-154)
   — `<svg class="ico">` with no wrapper — so the `<span class="b-ico">` the PHP
   template added is gone and style.css:219 `.trust-strip .b-ico { color:
   var(--yellow) }` has nothing to hit. Colour the svg directly. Sizing still
   comes from the global `.ico` rule (style.css:79), which matches unchanged. */
.trust-strip .trust-badge .ico { color: var(--yellow); }

/* That is the whole front-end bridge. Everything else inherits as-is:
     .trust-strip            (style.css:216) gradient + #fff  -> matches <section>
     .trust-strip__inner     (style.css:217) flex/gap/padding -> matches inner group
     .container              (style.css:82)  width clamp      -> matches inner group
     .ico                    (style.css:79)  1.05em box       -> matches shortcode svg
   `.wp-block-group` and `.wp-block-paragraph` contribute nothing; core's block
   library CSS for group is only `box-sizing:border-box`, which `* { box-sizing:
   border-box }` (style.css:67) already applied. */

/* --- NOT needed on the homepage; see notes. Only if this pattern is reused on
   a page rendered by page.php, which wraps the_content() in
   `<section class="section"><div class="container page-content">` and would
   clamp the band to 1180px:
.page-content > .trust-strip {
	width: 100vw;
	margin-inline: calc(50% - 50vw);
}
*/

/* --- Optional editor parity. assets/editor.css (added via add_editor_style,
   inc/blocks.php:28-29) has no .trust-strip rules, so the band renders white in
   the canvas. To preview it, add the same declarations there scoped to
   .editor-styles-wrapper, matching the file's existing convention (e.g. its
   `.editor-styles-wrapper .trust-box` rule at line 56):
.editor-styles-wrapper .trust-strip { background: linear-gradient(90deg, var(--blue-900), var(--blue-700)); color: #fff; }
.editor-styles-wrapper .trust-strip__inner { display:flex; flex-wrap:wrap; justify-content:center; gap:1rem 2.3rem; padding:18px 0; font-weight:800; font-size:.9rem; }
.editor-styles-wrapper .trust-badge { margin:0; display:inline-flex; align-items:center; gap:.5rem; }
*/

/* ===========================================================================
 * FocusX — bridge CSS for the About pattern (core blocks -> existing design).
 *
 * Append to the END of style.css, or enqueue a separate sheet with
 * array( 'focusx-style' ) as its dependency. Order matters: it must come after
 * wp-block-library AND after the global-styles (theme.json) inline stylesheet.
 * Verified: style.css currently contains ZERO .wp-block-* selectors (grep over
 * all 638 lines), so nothing below collides with an existing declaration.
 * ========================================================================= */

/* 1 — Flow-layout child margins.
 *     Every core/group renders with .is-layout-flow, and WP's global stylesheet
 *     emits, at ZERO specificity:
 *       :where(body .is-layout-flow) > *          { margin-block-start: 24px }
 *       :where(body .is-layout-flow) > :first-child { margin-block-start: 0 }
 *     Any author class beats that, but .section-copy, .about-media and
 *     .experience-badge carry no margins of their own — so the 24px lands.
 *     It is not cosmetic in two places:
 *       - .split-grid is display:grid + align-items:center; a 24px top margin on
 *         the second grid item (.about-media) visibly de-centres the row.
 *       - .experience-badge (top:54px) and .about-media__inset (bottom:20px) are
 *         absolutely positioned with one offset set, so margin still moves them.
 *     Physical properties are used deliberately: they also override core's
 *     `.wp-block-image { margin: 0 0 1em }` (same 0,1,0 specificity, decided on
 *     load order — this sheet is later). */
.split-grid  > * { margin-top: 0; }
.about-media > * { margin-top: 0; margin-bottom: 0; }

/* 2 — Eyebrow. Was <span class="eyebrow">, now <p class="eyebrow">.
 *     .eyebrow (style.css:195) sets margin-bottom but never margin-top, so the
 *     UA rule `p { margin: 1em 0 }` (0,0,1 — it beats the :where() flow rule)
 *     adds a top margin the span never had. display:inline-flex, the pill
 *     background and margin-bottom:.7rem all still apply unchanged. */
.section-copy > p.eyebrow { margin-top: 0; }

/* 3 — Button.
 *     core/button puts `className` on the WRAPPER <div class="wp-block-button">,
 *     never on the <a>. Putting "btn btn--accent" there would give the div the
 *     padding, radius, yellow fill and shadow while the inner <a> ALSO picked up
 *     theme.json's elements.button styles — visibly doubled. So the pattern uses
 *     two hooks that appear nowhere else in the theme (.focusx-btn /
 *     .focusx-btn--accent) and style.css:140-143 is re-applied to the inner link
 *     here. Specificity is 0,3,0 (.wp-block-button + .focusx-btn +
 *     .wp-block-button__link), comfortably past theme.json's
 *     `:root :where(.wp-block-button__link)` at 0,1,0.
 *
 *     Re-declaring the COLOURS is NOT redundant, even though theme.json's
 *     elements.button already resolves to the same shipped hex values:
 *     inc/customizer.php:664-671 rewrites --yellow / --yellow-600 / --yellow-ink
 *     at runtime from the Accent picker, while theme.json's `accent` preset stays
 *     frozen at #ffc400. Drop these two rules and the About button becomes the
 *     only accent button on the site that ignores a brand-colour change.
 *
 *     (Tidier alternative: append
 *      `.wp-block-button.focusx-btn > .wp-block-button__link` to the existing
 *      .btn selector list and `.wp-block-button.focusx-btn--accent > ...` to the
 *      .btn--accent list in style.css, then delete this section.) */
.section-copy > .wp-block-buttons { margin-top: 0; margin-bottom: 0; }

.wp-block-button.focusx-btn > .wp-block-button__link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: .55rem;
  border-radius: 10px;
  border: 0;
  padding: .95rem 1.5rem;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: .015em;
  cursor: pointer;
  text-decoration: none;
  transition: transform .2s ease, box-shadow .2s ease, background .2s ease, color .2s ease;
}
.wp-block-button.focusx-btn > .wp-block-button__link:hover { transform: translateY(-2px); }

.wp-block-button.focusx-btn--accent > .wp-block-button__link {
  background: var(--yellow);
  color: var(--yellow-ink);
  box-shadow: 0 12px 26px rgba(242, 178, 0, .40);
}
.wp-block-button.focusx-btn--accent > .wp-block-button__link:hover { background: var(--yellow-600); }

/* 4 — Photos.
 *     focusx_img() (inc/helpers.php:230) printed a bare <img class="...">;
 *     core/image wraps it: <figure class="wp-block-image size-full ...">.
 *     Rather than rewriting the design, the theme classes are allowed to land on
 *     the FIGURE — every one of their declarations is a box property (width,
 *     height, position, border-radius, border, box-shadow, margin-left:auto) that
 *     works identically on a figure, so the <=900px overrides at style.css:580-582
 *     keep working verbatim. The <img> then only has to fill the figure.
 *     overflow:hidden is required so .about-media__inset's border-radius:50%
 *     actually clips; it does not affect box-shadow (outer shadows are painted
 *     outside the border box and are never clipped by overflow).
 *     height:100% must beat core's `.wp-block-image img { height: auto }` (0,1,1)
 *     — the selector below is 0,2,1. */
.about-media > .wp-block-image { overflow: hidden; }
.about-media > .wp-block-image.about-media__main { margin-left: auto; margin-right: 0; }
.about-media > .wp-block-image > img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: inherit;
}

/* 5 — Experience badge.
 *     <strong>25+</strong> and <span>Years of<br>Experience</span> were bare
 *     inline flex children; core blocks cannot emit bare inline siblings, so they
 *     become two core/paragraphs. .experience-badge is display:flex, so two
 *     block-level paragraphs behave exactly like the two anonymous flex items
 *     they replace.
 *     The <strong> is kept INSIDE the first paragraph so `.experience-badge
 *     strong` (style.css:257) and its 2.2rem override (style.css:584) keep
 *     matching untouched. line-height:.9 on the paragraph collapses its strut so
 *     the flex item's height is exactly the <strong>'s line box, as before.
 *     The label's <span> is replaced by a class: `.experience-badge span` only
 *     set font-weight and has no responsive override to preserve, and keeping a
 *     bare <span> in owner-editable text invites it being retyped away. */
.experience-badge > p { margin: 0; }
.experience-badge__num   { line-height: .9; }
.experience-badge__label { font-weight: 900; }

/* ---------------------------------------------------------------------------
 * 6 — Editor canvas. Add to assets/editor.css, NOT to this file.
 *     editor.css already covers .eyebrow (l.36), .section (l.32), h2 span (l.28)
 *     and .wp-block-buttons (l.87); it has nothing for the About media column.
 *     style.css is deliberately not loaded into the canvas, so .reveal's
 *     opacity:0 is not currently a hazard — the guard below is cheap insurance.
 *
 *   .editor-styles-wrapper .split-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 48px; align-items: center; }
 *   @media (max-width: 1040px) { .editor-styles-wrapper .split-grid { grid-template-columns: 1fr; } }
 *   .editor-styles-wrapper .check-list { list-style: none; padding: 0; margin: 1.5rem 0 2rem; display: grid; gap: .7rem; }
 *   .editor-styles-wrapper .check-list li { font-weight: 700; color: #35485c; }
 *   .editor-styles-wrapper .about-media { position: relative; }
 *   .editor-styles-wrapper .about-media .wp-block-image { margin: 0; overflow: hidden; border-radius: 18px; }
 *   .editor-styles-wrapper .experience-badge { background: #ffc400; color: #0b2a44; display: flex; align-items: center; gap: .8rem; border-radius: 18px; padding: 1.2rem 1.35rem; }
 *   .editor-styles-wrapper .experience-badge p { margin: 0; font-weight: 900; }
 *   .editor-styles-wrapper .experience-badge strong { font-size: 3rem; line-height: .9; }
 *   .editor-styles-wrapper .reveal { opacity: 1; transform: none; }
 * ------------------------------------------------------------------------- */

/* REMOVED from the original proposal as redundant:
 *   `.section-copy > ul.check-list { list-style: none; padding-left: 0 }` —
 *   .check-list (style.css:250) already sets list-style:none and padding:0 at
 *   0,1,0. The only competing rules are UA defaults (0,0,1), which an author
 *   class always beats; there is no .wp-block-list padding rule in
 *   wp-block-library to insure against. Load order is irrelevant here. */

/* =========================================================================
   Bridge CSS — CTA band pattern (focusx/cta-band). Append to style.css.
   Deliberately short: core/group emits no styles of its own, so the legacy
   bare <div> and the block's <div class="wp-block-group"> behave identically.
   Only three real deltas exist.
   ========================================================================= */

/* 1. REQUIRED — block-gap. wp_render_layout_support_flag() injects
      `is-layout-flow wp-block-group-is-layout-flow` at RENDER time (not in
      saved markup), and core's flow-layout stylesheet applies
        :where(.is-layout-flow) > * { margin-block-start: <fallback gap>; }
      theme.json declares settings.spacing.blockGap:true but sets no
      styles.spacing.blockGap value, so core's fallback applies. That puts a
      top margin on the second child — the call button — shifting it off the
      align-items:center baseline of .cta-band__inner (style.css:262).
      The layout rules are inside :where() so specificity 0; this wins. */
.cta-band__inner > * { margin-block-start: 0; }

/* 2. REQUIRED for the designed look (pre-existing bug, fixed here).
      theme.json styles.elements.h2 compiles to
        :root :where(h2){color:var(--wp--preset--color--primary-dark)}   /* #06345a */
      specificity (0,1,0). style.css:263 `.cta-band h2` sets only margin and
      font-size — no colour — so the inherited #fff from
      .cta-band__inner{color:#fff} loses to that direct declaration and the
      heading renders deep blue on the deep-blue-to-bright-blue gradient.
      This is ALREADY happening in the PHP markup at front-page.php:122;
      the selector fixes both renderings. */
.cta-band h2 { color: #fff; }

/* 3. REQUIRED once sections are reorderable. style.css:261 is
      `.cta-band { padding: 0 0 72px }` — the zero top padding is only correct
      because in front-page.php the band always follows `.about.section`,
      which supplies the gap. In block mode the neighbour is arbitrary.
      Scoped to `.wp-block-group.cta-band` so the PHP-rendered service pages
      and blog page are untouched. */
.wp-block-group.cta-band { padding-top: 72px; }
.wp-block-group.section + .wp-block-group.cta-band { padding-top: 0; }

/* NOT NEEDED — verified, do not add:
   • `.wp-block-group{margin-block:0}` — core adds no margin to .wp-block-group,
     and classic-theme post content has no root layout container.
   • `padding-inline:0` on .cta-band — the shorthand at style.css:261 already
     sets it.
   • flex/min-width on the inner copy group — the legacy element is a bare
     <div>; .wp-block-group adds nothing, so flex behaviour is unchanged.
   • Any .wp-block-button / .wp-block-buttons rules — the button is a shortcode
     emitting the legacy <a class="btn btn--accent btn--pulse">, so
     style.css:140-158 and the mobile rule .cta-band__inner .btn{width:100%}
     (style.css:579) keep working verbatim.
   • Anything for core/paragraph — it saves a bare <p>, so .cta-band p
     (style.css:264) already matches.
   • A `.btn--accent` colour pin against theme.json elements.link. The tie is
     genuine but pre-existing and identical in the current PHP output; the
     conversion does not change it. Leave it alone.

   ---- assets/editor.css (OPTIONAL, canvas fidelity only) ----
   Correction to a claim in the proposal: editor.css ALREADY contains
   `.editor-styles-wrapper .cta-band h2, .editor-styles-wrapper .cta-band p
   { color:#fff }` immediately after the gradient rule, so the heading is NOT
   unreadable in the editor. The only editor delta is that editor.css paints
   the gradient on `.cta-band` while the front end paints it on
   `.cta-band__inner`, and editor.css has no `.cta-band__inner` rule, so the
   canvas stacks the copy above the button instead of flexing them. Harmless.
   To match more closely, add:
     .editor-styles-wrapper .cta-band__inner {
       display: flex; align-items: center; justify-content: space-between;
       gap: 1.2rem;
     }
   No other editor change is needed. */

/* ============================================================
 * BRIDGE CSS — Services grid pattern (core blocks -> focusx design)
 * Append to style.css. Editor-canvas additions listed at the bottom
 * belong in assets/editor.css.
 * ========================================================== */

/* 1. Flow-layout block gap.
 *    Core emits, at specificity 0,0,0:
 *      :where(.is-layout-flow) > :first-child { margin-block-start: 0 }
 *      :where(.is-layout-flow) > :last-child  { margin-block-end: 0 }
 *      :where(.is-layout-flow) > *            { margin-block-start: var(--wp--style--block-gap);
 *                                               margin-block-end: 0 }
 *    Only margin-block-start needs neutralising, and only on the four
 *    non-first children where no theme rule already sets a top margin.
 *
 *    Do NOT blanket-zero margin-block-end on all children: the designed bottom
 *    margins (.eyebrow .7rem style.css:195, .section-heading h2 1rem :249,
 *    .service-card h3 .5rem :286, .service-card p 1.15rem :287) are single-class
 *    rules that already beat core's 0,0,0 — zeroing them out and adding them
 *    back is circular and breaks the moment one of those rules is edited.
 *
 *    The .services__grid case is the load-bearing one: a margin on a CSS grid
 *    item ADDS to `gap: 24px` (style.css:280), so rows would sit 48px apart. */
.container > .services__grid,
.services__grid > .service-card,
.service-card > .service-card__body,
.service-card__body > .wp-block-buttons { margin-block-start: 0; }

/* 2. The eyebrow is now a core/paragraph, not the hand-written <span>
 *    (front-page.php:132). `.eyebrow { display: inline-flex }` (style.css:195)
 *    already makes a <p> behave correctly and supplies margin-bottom: .7rem;
 *    only the UA top margin needs clearing. Core's :first-child rule covers
 *    this today — this is the explicit, order-independent version. */
.section-heading > p.eyebrow { margin-top: 0; }

/* 3. core/image wraps the <img> in <figure class="wp-block-image">, and that
 *    figure — not the img — becomes the flex item of .service-card.
 *
 *    THE IMPORTANT ONE: core block-library ships `.wp-block-image img { height: auto }`
 *    at specificity 0,1,1 — an exact tie with `.service-card img { height: 176px;
 *    width: 100%; object-fit: cover }` (style.css:284). Which wins depends purely
 *    on stylesheet enqueue order, which the theme does not control, so the card
 *    images would be 176px or full natural height depending on load order.
 *    Restating the sizing at 0,2,2 removes the ambiguity.
 *
 *    Core's `.wp-block-image { margin: 0 0 1em }` is also collapsed here, or a
 *    gap opens between the image and .service-card__body. */
.service-card > figure.wp-block-image { margin: 0; line-height: 0; }
.service-card > figure.wp-block-image img {
	display: block;
	width: 100%;
	height: 176px;
	object-fit: cover;
	filter: saturate(1.06);
}

/* 4. Buttons. core/button puts `className` on the WRAPPER
 *    <div class="wp-block-button">, never on the <a> — so the designed recipe
 *    `btn btn--accent btn--pulse btn--sm` (front-page.php:146) can never land on
 *    the link, and putting it on the wrapper would paint a second yellow box
 *    around the anchor. The pattern therefore marks the core/buttons wrapper
 *    `service-card__cta` and this block re-applies the recipe to the link.
 *
 *    `.service-card__cta .wp-block-button__link` (0,2,0) beats both core's
 *    `.wp-block-button__link` (0,1,0) and theme.json's generated
 *    `:root :where(.wp-block-button__link)` global styles (0,1,0).
 *
 *    @keyframes ctaPulse (style.css:152) and --accent-rgb (style.css:46) already
 *    exist and are reused as-is. */
.service-card__cta { display: block; gap: 0; }
.service-card__cta .wp-block-button { display: block; }
.service-card__cta .wp-block-button__link {
	display: flex;
	align-items: center;
	justify-content: center;
	gap: .55rem;
	width: 100%;
	min-height: 42px;              /* .service-card .btn (style.css:288) */
	border: 0;                     /* .btn (style.css:140) */
	border-radius: 9px;            /* .btn--sm (style.css:150) */
	padding: .68rem 1.05rem;       /* .btn--sm */
	font-size: .8rem;              /* .btn--sm */
	font-weight: 800;
	text-transform: uppercase;
	letter-spacing: .015em;
	text-decoration: none;
	background: var(--yellow);     /* .btn--accent (style.css:142) */
	color: var(--yellow-ink);
	box-shadow: 0 12px 26px rgba(242, 178, 0, .40);
	transition: transform .2s ease, box-shadow .2s ease, background .2s ease, color .2s ease;
	animation: ctaPulse 2.1s cubic-bezier(.66, 0, 0, 1) infinite; /* .btn--pulse (style.css:157) */
}
.service-card__cta .wp-block-button__link:hover {
	transform: translateY(-2px);
	background: var(--yellow-600);
	color: var(--yellow-ink);
}
@media (prefers-reduced-motion: reduce) {
	.service-card__cta .wp-block-button__link { animation: none; }
}

/* 5. Nothing further is required.
 *    - core/group adds only .wp-block-group (box-sizing); .services, .container,
 *      .section-heading, .services__grid, .service-card and .service-card__body
 *      keep their existing rules verbatim.
 *    - The accent word needs NO rule: core's text-colour format saves
 *      style="background-color:rgba(0, 0, 0, 0)" inline, which already overrides
 *      the UA `mark { background: yellow }`. A `background: none` bridge rule
 *      would be dead code.
 *    - .service-card::before (the 4px gradient bar, style.css:282) and
 *      .service-card:hover still match — the article IS the .service-card.
 *    - .service-card p { flex: 1 } (style.css:287) still pushes the button to the
 *      bottom: the <p> remains a flex item of .service-card__body.
 *    - The pattern uses flow layout ("layout":{"type":"default"}), which adds no
 *      max-width. Omitting the layout attribute would inherit theme.json's
 *      constrained default (contentSize 1180px, theme.json:7) and fight
 *      .container (style.css:82) — hence the explicit "default". */


/* ---------- assets/editor.css additions ----------
 * editor.css already carries `.editor-styles-wrapper .service-card .wp-block-image
 * { margin: 0 }` (line 84) and `.editor-styles-wrapper .wp-block-buttons
 * { margin-top: 1rem }` (line 86). These complete it:
 *
 * .editor-styles-wrapper .services__grid {
 *   display: grid;
 *   grid-template-columns: repeat(2, minmax(0, 1fr));
 *   gap: 24px;
 * }
 * .editor-styles-wrapper .services__grid > * { margin-block-start: 0; }
 * .editor-styles-wrapper .service-card { padding: 0; overflow: hidden; }
 * .editor-styles-wrapper .service-card__body { padding: 22px 20px 20px; }
 * .editor-styles-wrapper .service-card img { height: 176px; width: 100%; object-fit: cover; }
 *
 * Note .editor-styles-wrapper .service-card (editor.css:51) sets padding: 22px,
 * which the front end does not — the reset above is what stops the image being
 * inset while editing.
 */

/* ============================================================
   Bridge CSS — "Why choose" pattern. Append to style.css.
   Front-end markup the pattern produces (layout classes are added
   server-side by the layout support, they are not in post_content):

   <section class="wp-block-group why section section-soft is-layout-flow …">
     <div class="wp-block-group container is-layout-flow …">
       <div class="wp-block-group section-heading reveal is-layout-flow …">
         <p class="eyebrow">…</p>
         <h2 class="wp-block-heading">…</h2>
       </div>
       <div class="wp-block-group why__grid is-layout-flow …">
         <article class="wp-block-group mini-card reveal is-layout-flow …">
           <div class="icon"><svg class="ico">…</svg></div>
           <h3 class="wp-block-heading">…</h3>
           <p>…</p>
         </article> × n
       </div>
     </div>
   </section>

   Existing rules that already match unchanged — NO bridge needed:
     .container (l.82), .section (l.83), .section-soft (l.84),
     .eyebrow (l.195), .section-heading h2 (l.249), .section-heading (l.275),
     .mini-card h3 (l.289), .mini-card p (l.290), .icon (l.291),
     .icon .ico (l.292), .why__grid (l.295), .mini-card (l.296),
     .mini-card .icon (l.297), .reveal (l.527-529),
     .why__grid breakpoints (l.537 two-up, l.577 one-up).
   `.why` itself has no rule in style.css — it is kept for readability
   and as the scoping hook these four rules use.
   ============================================================ */

/* ONLY problem the block output creates: the global flow block-gap.
   theme.json enables settings.spacing.blockGap (l.73) with no styles value,
   so WP prints core's default
     :root :where(.is-layout-flow)>*{margin-block-start:24px;margin-block-end:0}
   at specificity 0,1,0. Every group here is a flow container, so the cards
   inside the CSS grid gain 24px of top margin and .section-heading's
   28px bottom margin is zeroed.
   NOTE the fix must be at least 0,2,0 — a bare `.why__grid > *` is 0,1,0,
   which only TIES with the core rule and would be decided by stylesheet
   order (global styles are printed inline in <head>, style.css is enqueued,
   so the tie is fragile). Scoping under `.why` gives 0,2,0. */
.why .container > *,
.why .why__grid > * { margin-block-start: 0; }

/* Restore the one design margin the same core rule zeroes out
   (.section-heading is 0,1,0 and therefore also only ties). */
.why .container > .section-heading { margin-bottom: 28px; }

/* The eyebrow was a <span>; as core/paragraph it is a <p>. .eyebrow's
   display:inline-flex still applies, so the pill hugs its text and the <h2>
   still falls on the next line — only the block-level margins differ.
   margin-bottom is restated because .eyebrow (0,1,0) ties with the core
   margin-block-end:0. margin-top covers the UA 1em if blockGap is ever
   switched off in theme.json. */
.why .section-heading > p.eyebrow { margin-top: 0; margin-bottom: .7rem; }

/* Nothing else is required. In particular:
   - .mini-card h3 (0,1,1) and .mini-card p (0,1,1) already beat the core
     0,1,0 gap rule, and .icon is a :first-child, so the card interior needs
     no reset. Do not add one.
   - .mini-card .icon (0,2,0) already beats it, so its 1rem gap survives.
   - core/html emits its content verbatim with no wrapper, so
     <div class="icon"> reaches the page exactly as focusx_icon() printed it.
   - add_theme_support('wp-block-styles') is already on (inc/blocks.php l.27);
     wp-block-library-theme.css touches quote/table/separator/caption, not
     group/heading/paragraph, so it adds nothing to fix here.
   - useRootPaddingAwareAlignments (theme.json l.10) only affects constrained
     layouts and .has-global-padding; these groups are flow, and theme.json
     defines no styles.spacing.padding, so it is inert. */


/* ---- OPTIONAL, assets/editor.css only ----
   editor.css deliberately does NOT load style.css, so .reveal{opacity:0}
   never applies in the editor and the section is visible while editing —
   no editor override for .reveal is needed (contrary to a common assumption).
   editor.css already styles .mini-card (l.52-63). Two things read badly:
   the grid is not defined there, and the [focusx_icon] shortcode shows as
   literal text inside the 56px gradient tile. */
.editor-styles-wrapper .why__grid {
	display: grid;
	grid-template-columns: repeat(2, 1fr);
	gap: 22px;
}
.editor-styles-wrapper .mini-card .icon {
	width: auto;
	height: auto;
	min-height: 0;
	display: inline-block;
	padding: .35rem .6rem;
	border-radius: 8px;
	background: #f2f9ff;
	border: 1px dashed #7cc2f0;
	color: #06345a;
	font: 700 11px/1.3 ui-monospace, Menlo, Consolas, monospace;
	margin-bottom: 1rem;
}

/* ============================================================
   BRIDGE CSS — Recent Projects (#work) pattern
   PART A: front end. Append to style.css, or put in
   assets/blocks-bridge.css and enqueue it in focusx_assets()
   with array( 'focusx-style' ) as its dependency.
   ============================================================ */

/* --- A1. Flow-layout block gap -------------------------------
   Core emits :where(.is-layout-flow) > * + * { margin-block-start:
   var(--wp--style--block-gap) } (24px, from core's default theme.json —
   this theme's theme.json sets settings.spacing.blockGap:true but no
   styles.spacing.blockGap, so the core default applies).
   Only two nodes actually lose that fight: the grid (would add 24px on
   top of .section-heading's 28px margin-bottom) and the CTA wrapper
   (would get a 24px top margin as a flex item and drop below baseline).
   The figures do NOT need a rule — .project-card{margin:0} (0,1,0)
   already beats the zero-specificity :where() rule, as does
   .section-heading h2{margin:0 0 1rem} and .eyebrow{margin-bottom:.7rem}. */
.wp-block-group.work__grid,
.section-heading--row > .wp-block-buttons { margin-block-start: 0; }

/* --- A2. core/image -> .project-card -------------------------
   core/image save() emits exactly <figure><img><figcaption>, which is
   structurally identical to the hand-written .project-card. The only
   problem is that every collision is at EQUAL specificity
   (.wp-block-image img 0,1,1 vs .project-card img 0,1,1), so the winner
   depends on stylesheet print order. Win them explicitly.
   Note this theme calls add_theme_support('wp-block-styles')
   (inc/blocks.php:27), which loads wp-block-library-theme — that is what
   centres captions at 13px/#555. */
.wp-block-image.project-card { margin: 0; }
.wp-block-image.project-card img {
  display: block;
  width: 100%;
  height: 130px;
  max-width: 100%;
  object-fit: cover;
}
.wp-block-image.project-card figcaption {
  margin: 0;
  padding: .75rem .9rem;
  text-align: left;
  font-size: .86rem;
  color: var(--blue-900);
}

/* --- A3. core/button -> the outline CTA ----------------------
   className lands on the WRAPPER <div class="wp-block-button">, never on
   the <a class="wp-block-button__link wp-element-button">. So .btn /
   .btn--outline from style.css:140-148 cannot be reused here at all —
   putting them on the wrapper only creates a second padded, bordered box
   that then has to be neutralised. A dedicated bridge class is strictly
   less CSS. These declarations must also out-specify theme.json's
   generated `:root :where(.wp-element-button, .wp-block-button__link)`
   (specificity 0,1,0) which otherwise paints this button accent-yellow
   with 10px radius and .95rem/1.5rem padding. 0,3,0 clears it. */
.wp-block-button.focusx-btn > .wp-block-button__link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: .55rem;
  border: 0;
  border-radius: 10px;
  padding: .95rem 1.5rem;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: .015em;
  text-decoration: none;
  cursor: pointer;
  background: none;
  transition: transform .2s ease, box-shadow .2s ease, background .2s ease, color .2s ease;
}
.wp-block-button.focusx-btn > .wp-block-button__link:hover { transform: translateY(-2px); }

.wp-block-button.focusx-btn--outline > .wp-block-button__link {
  border: 2px solid var(--blue-700);
  color: var(--blue-700);
  background: #fff;
  padding: calc(.95rem - 2px) calc(1.5rem - 2px);
}
.wp-block-button.focusx-btn--outline > .wp-block-button__link:hover { background: var(--blue-050); }

/* Nothing else is needed on the front end.
   .container, .section, .work__grid, .section-heading--row, .eyebrow,
   .reveal and the 900px/760px media queries (style.css:537, 577-578) all
   match plain divs and apply unchanged. */


/* ============================================================
   PART B: block editor. Append to assets/editor.css, which is
   already wired up via add_editor_style() in inc/blocks.php:29.
   (No new add_editor_style call is required — the proposal's claim
   that the theme is missing one is incorrect.)
   ============================================================ */

/* editor.css has no .work__grid rule, so the figures would stack. */
.editor-styles-wrapper .work__grid {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 20px;
}
@media (max-width: 960px) {
  .editor-styles-wrapper .work__grid { grid-template-columns: repeat(2, 1fr); }
}

/* editor.css:52-64 styles .project-card as a padded card, and editor.css:84
   targets `.project-card .wp-block-image` (a NESTED image). Neither is right
   now that the figure IS the image block. */
.editor-styles-wrapper .wp-block-image.project-card { margin: 0; padding: 0; overflow: hidden; }
.editor-styles-wrapper .wp-block-image.project-card img {
  display: block; width: 100%; height: 130px; object-fit: cover;
}
.editor-styles-wrapper .wp-block-image.project-card figcaption {
  margin: 0; padding: .75rem .9rem; text-align: left;
  font-size: .86rem; font-weight: 900; color: #06345a;
}

/* editor.css has no .section-heading--row rule, and editor.css:85 gives every
   .wp-block-buttons a 1rem top margin, which breaks the heading row. */
.editor-styles-wrapper .section-heading--row {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: 1.5rem; margin-bottom: 28px;
}
.editor-styles-wrapper .section-heading--row .wp-block-buttons { margin-top: 0; }

/* DELIBERATELY ABSENT: a `.editor-styles-wrapper .reveal { opacity: 1 }` rule.
   style.css is intentionally NOT loaded in the editor (see the header comment
   in assets/editor.css), so `.reveal { opacity: 0 }` never applies there and
   the pattern previews normally. Only add such a rule if style.css is ever
   passed to add_editor_style(). */

/* =========================================================================
   Block bridge — Testimonials pattern (focusx/testimonials)
   Append to style.css after the "Testimonials" block (line ~318).
   ========================================================================= */

/* [SHARED — put this once, not per pattern]
   The pattern's outer group is alignfull, but the theme has no .alignfull rule
   anywhere, so the class currently does nothing. On front-page.php it does not
   need to: the_content() is echoed into <main class="focusx-blocks"> with no
   wrapper (front-page.php:17), so the section is already full width. On
   page.php the_content() runs inside .container.page-content (page.php:33) and
   .section-soft's tint would be inset instead of a full-bleed band. Scoped to
   .page-content so it can never misfire on the homepage.
   Note: 100vw includes the scrollbar; if a page ever scrolls horizontally,
   switch to `width: 100%` plus a negative inline margin on the container. */
.page-content > .alignfull {
  width: 100vw;
  max-width: 100vw;
  margin-inline: calc(50% - 50vw);
}

/* 1. Neutralise core's flow-layout block-gap inside this section.
      Core emits `body .is-layout-flow > * { margin-block-start: 0.5em;
      margin-block-end: 0 }` (theme.json enables settings.spacing.blockGap but
      sets no styles.spacing.blockGap, so the gap falls back to core's 0.5em).
      That selector is (0,1,1). It TIES with .testimonial p / .testimonial h3
      and the theme wins on source order (global-styles is enqueued before
      focusx-style), but it BEATS the two rules that matter at (0,1,0):
        .testimonial-head { margin-bottom: 30px }   (style.css:277)
        .eyebrow          { margin-bottom: .7rem }  (style.css:195)
      Zeroing at (0,2,0) and restoring explicitly is deterministic; the layout
      selector and default gap have both changed between WP 6.0 and 6.4.
      Only margin-block is touched, so .container's margin-inline:auto is safe. */
.testimonials.wp-block-group > *,
.testimonials .wp-block-group > * { margin-block-start: 0; margin-block-end: 0; }

/* ---- Head ------------------------------------------------------------ */

/* Restores .testimonial-head { margin-bottom: 30px } (style.css:277). */
.testimonials .container > .testimonial-head { margin-block-end: 30px; }

/* REQUIRED, not insurance. The eyebrow was <span class="eyebrow">; core makes
   it <p class="eyebrow">, and style.css has no `p { margin: 0 }` reset (the
   only reset is `* { box-sizing }` + `body { margin: 0 }`, lines 67-75), so the
   UA's `p { margin: 1em 0 }` applies. .eyebrow only sets margin-bottom, so
   ~16px of unwanted space would appear above the pill. .eyebrow is
   display:inline-flex — an atomic inline-level box, which honours vertical
   margins — so once margins are normalised the box is pixel-identical. */
.testimonials p.eyebrow { margin: 0 0 .7rem; }

/* Restores .testimonial-head h2 { margin: 0 0 1rem } (style.css:249). */
.testimonials .testimonial-head h2 { margin: 0 0 1rem; }

/* ---- Cards ----------------------------------------------------------- */

/* Restores .testimonial p { margin: 0 0 1rem } (style.css:315). */
.testimonials .testimonial > p { margin: 0 0 1rem; }

/* Restores .testimonial .stars { margin-bottom: .4rem } (style.css:316). */
.testimonials .testimonial > .stars { margin-block-end: .4rem; }

/* Restores .testimonial h3 { margin: .3rem 0 0 } (style.css:317). */
.testimonials .testimonial > h3 { margin: .3rem 0 0; }

/* The only real cascade change in the section. The location line was a bare
   <span> styled by `.testimonial span` (style.css:318); no core block emits a
   bare block-level span, so it becomes <p class="testimonial__meta">. That
   makes `.testimonial span` stop matching and `.testimonial p` (the quote's
   colour and 1rem bottom margin) wrongly start matching. Re-declare exactly
   the three span declarations — no line-height, because the original <span>
   inherited body's 1.55. */
.testimonials .testimonial > p.testimonial__meta {
  margin: 0;
  color: var(--muted);
  font-size: .9rem;
  font-weight: 700;
}

/* NOT NEEDED, deliberately omitted:
   - box-sizing on .wp-block-group — core's wp-block-library already ships
     `.wp-block-group { box-sizing: border-box }`, and style.css:67 sets it on *.
   - a margin reset on .testimonials__grid's children — rule 1 already covers
     them (.testimonials__grid is itself a .wp-block-group inside .testimonials).
   - .container margin handling — it is the section group's only child, so
     core's `> :first-child` / `> :last-child` rules already zero it.
   - .wp-block-heading — carries no margin of its own in core CSS.
   - .testimonial::before, .section, .section-soft, .testimonials__grid,
     .rating, .stars, .stars__empty — all match unchanged on the block output. */


/* =========================================================================
   assets/editor.css additions (editor canvas parity).
   editor.css already covers .section, .section-soft, .eyebrow and .testimonial
   (lines 31-62) but not the two layout containers or the rating pill.
   ========================================================================= */

.editor-styles-wrapper .testimonials .wp-block-group > *
  { margin-block-start: 0; margin-block-end: 0; }
.editor-styles-wrapper .testimonial-head {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: 1.5rem; margin-bottom: 30px;
}
.editor-styles-wrapper .testimonials__grid {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 24px;
}
.editor-styles-wrapper .rating {
  display: flex; align-items: center; gap: .55rem; background: #fff;
  border-radius: 999px; padding: .8rem 1rem; border: 1px solid #d5e5f2;
  white-space: nowrap;
}
.editor-styles-wrapper .rating strong { color: #06345a; font-size: 1.5rem; }
.editor-styles-wrapper .rating small { color: #55697c; font-weight: 700; }
.editor-styles-wrapper .eyebrow { margin: 0 0 .7rem; }
.editor-styles-wrapper .testimonial__meta
  { margin: 0; color: #55697c; font-size: .9rem; font-weight: 700; }

/* =========================================================================
 * Working Process — core-block bridge  (append to style.css)
 *
 * CORRECTED from the proposal: the sweeping
 *   .process .wp-block-group > * { margin-block-start:0; margin-block-end:0 }
 * reset plus its five restatement rules has been REMOVED. It rested on a false
 * premise. Core's flow-layout rules are emitted by WP_Theme_JSON with :where()
 * wrappers — `:where(.is-layout-flow) > *`, `> :first-child`, `> :last-child`,
 * `> * + *` — so they carry specificity ZERO and already lose to every theme
 * rule in style.css, all of which use a class (>= 0,1,0). Nothing needs
 * restating; a 0,2,0 reset only breaks .section-heading (:275), .eyebrow
 * (:195), .section-heading h2 (:249), .step .icon (:324) and .step h3 (:289)
 * and then has to put them back.
 * ========================================================================= */

/* 1. The ONE genuine blockGap collision. `:where(.is-layout-flow) > * + *`
 *    gives every non-first child `margin-block-start: var(--wp--style--block-gap)`
 *    (24px, from core's default theme.json — this theme.json enables
 *    settings.spacing.blockGap but sets no styles.spacing.blockGap value).
 *    On .process__grid (style.css:321, `display:grid; gap:25px`) those margins
 *    ADD to the gap, so steps 2 and 3 sit 24px low and the row heights split.
 *    Everywhere else in this section the theme already declares the margin and
 *    wins, so zeroing the start margin is a no-op there.
 *    Margins an editor sets via the block toolbar are inline (1,0,0) and still
 *    override this. */
.process .wp-block-group > * + * { margin-block-start: 0; }

/* 2. MISSED IN THE PROPOSAL — real visual regression.
 *    style.css:290 is `.mini-card p, .step p { margin:0; color:var(--muted);
 *    font-size:.92rem; }`. The legacy step number was a <span> (front-page.php
 *    :205) so this never matched it; core/paragraph makes it a <p>, so the
 *    number now renders at .92rem. `color` is rescued — .step > .step-num
 *    (:323, 0,2,0) beats .step p (0,1,1) — but that rule declares no
 *    font-size, so nothing overrides it. Legacy inherited 1rem from
 *    theme.json styles.typography.fontSize (preset "medium"). Restate it. */
.process .step > .step-num { font-size: 1rem; }

/* 3. Guard: .process__grid must stay on layout type "default" (flow). If an
 *    editor switches the group to "Constrained", core applies
 *    `max-width:1180px; margin-inline:auto` to every .step and the 3-up grid
 *    collapses. One line makes that harmless. */
.process__grid.is-layout-constrained > .step { max-width: none; margin-inline: 0; }

/* 4. SHARED — add ONCE for all FocusX patterns, not once per pattern.
 *    front-page.php:17 prints block content into a bare <main class="focusx-blocks">
 *    with no container (and style.css defines no .focusx-blocks rules), so the
 *    pattern correctly carries its own .section + .container. page.php:35-37
 *    already wraps the_content in `.section > .container.page-content`, so on a
 *    generic page the pattern's wrapper doubles up AND .page-content's
 *    `max-width:820px` (style.css:631) squeezes the 3-up grid. */
.page-content .section { padding: 0; }
.page-content .section > .container { width: 100%; }
.page-content:has(.section) { max-width: none; }

/* =========================================================================
 * Editor (append to assets/editor.css)
 *
 * assets/editor.css already supplies .section padding (:31), .eyebrow (:35)
 * and the .step card shell — background/border/radius/shadow/padding (:52-62).
 * Only the process-specific geometry is missing.
 * ========================================================================= */
.editor-styles-wrapper .process__grid {
	display: grid;
	grid-template-columns: repeat(3, 1fr);
	gap: 25px;
}
.editor-styles-wrapper .process .wp-block-group > * + * { margin-block-start: 0; }
.editor-styles-wrapper .step { text-align: center; position: relative; }
.editor-styles-wrapper .step > .step-num {
	position: absolute; top: 22px; right: 22px;
	width: 42px; height: 42px; border-radius: 999px;
	background: #ffc400; color: #0b2a44;
	display: grid; place-items: center;
	font-weight: 900; font-size: 1rem; margin: 0;
}
.editor-styles-wrapper .step h3 { margin: 0 0 .35rem; }
.editor-styles-wrapper .step p  { margin: 0; color: #55697c; font-size: .92rem; }

/* NOT included, deliberately:
 *  - .step .icon / .ico rules. The icon lives in a core/html block, which the
 *    editor renders inside a SANDBOXED IFRAME. add_editor_style() CSS does not
 *    reach into that iframe, so any .icon rule here would be dead code. The
 *    proposal shipped these; they have been dropped.
 *  - .reveal. assets/editor.css does not load style.css (see its header
 *    comment), so `.reveal { opacity:0 }` (style.css:527) never applies in the
 *    canvas and the section stays visible while editing. Do not copy it in. */

/* ---------- Contact section: core-block bridge (front end) ---------- */
/* Three rules. The proposal's rules 4, 5 and 6 are redundant — see notes. */

/* 1. .contact__grid is now a flow-layout group, so WP's zero-specificity
      :where(.is-layout-flow) > * { margin-block-start: var(--wp--style--block-gap, 24px);
      margin-block-end: 0 } lands on both grid items. Nothing in style.css sets a
      margin on .contact-copy or .message-card, so it wins by default, and with
      .contact__grid { align-items: center } (style.css:330) it visibly offsets
      the form against the copy column. */
.contact__grid > * { margin-block: 0; }

/* 2. The eyebrow is <p class="eyebrow"> instead of <span class="eyebrow">, so
      .contact-copy p (style.css:332, specificity 0,1,1) now outranks .eyebrow
      (style.css:195, 0,1,0) and overwrites its colour and clamps its width.
      This is the only genuine visual regression in the section.
      Margins need no fix: .eyebrow's own margin-bottom:.7rem still beats the
      :where() rule, and :first-child already gets margin-block-start:0. */
.contact-copy p.eyebrow { color: var(--blue-700); max-width: none; }

/* 3. The intro <p> had no theme margin rule and relied on the UA 1em block
      margins. Flow layout replaces those with 24px top / 0 bottom. Restoring
      1em keeps the h2→p gap at 16px instead of 24px. Purely cosmetic; drop this
      rule if you do not care about the 8px. */
.contact-copy > p:not(.eyebrow) { margin-block: 1em; }


/* ---------- Optional: assets/editor.css additions (editor canvas only) -------
   Not required for the front end. editor.css already styles .wp-block-shortcode
   (line 74) and .eyebrow (line 34), but has no .contact__grid rule, so the
   section edits as a single stacked column. Add if you want the two-up preview:

   .editor-styles-wrapper .contact { background: linear-gradient(180deg,#d7ebfd,#e9f3fc); }
   .editor-styles-wrapper .contact__grid { display: grid; grid-template-columns: .9fr 1.2fr; gap: 60px; align-items: center; }
   .editor-styles-wrapper .contact-list { list-style: none; padding: 0; display: grid; gap: .85rem; font-weight: 800; }
------------------------------------------------------------------------- */


/* ---------- Block bridge: service pages & blog ---------- */

/* ============================================================
   Block bridge — Service hero, layout A (.hero#quote)
   Append to style.css AFTER the existing "Block bridge — Hero
   (.hero--home)" block (which ends at the 520px media query,
   style.css:719-721).

   FIVE rules. Everything else inherits through className.
   NO button CSS here: the pattern reuses the marker classes
   .is-hero-cta / .is-hero-call, and the shipped homepage bridge
   at style.css:687-721 is scoped to `.hero__actions …`, not to
   `.hero--home`. VERIFIED: it already covers this section
   verbatim, including the 760px width:100% rule (parity with
   style.css:576) and the 520px hide-the-call-button rule
   (parity with style.css:592). Do not duplicate those selectors.
   ============================================================ */

/* 1. EYEBROW — TWO problems, not one.

      (a) render-service.php:117 wrote <span class="eyebrow">; it is now
          <p class="eyebrow">. That means `.hero p` (style.css:209,
          specificity 0,1,1) MATCHES IT FOR THE FIRST TIME. `.eyebrow`
          (style.css:195) is only (0,1,0), so .hero p WINS on both
          properties they share:
              font-size : .76rem  ->  1.04rem   (pill text ~37% larger)
              color     : var(--blue-700) -> #35506a  (blue -> slate)
          and additionally imposes max-width:620px, which the <span>
          never had. This is a VISIBLE break, not a nicety, and it is
          the layout-A analogue of rule 3 below — the homepage bridge
          did not need it because the .hero--home pattern has no
          eyebrow and no trust pills at all (see style.css:614, "eyebrow
          + trust pills removed via HTML"). Restating the three
          properties at (0,2,1) fixes it.

      (b) Top margin. style.css has no `p { margin: 0 }` reset (line 67
          is `* { box-sizing }`, line 70 is `body { margin: 0 }`).
          Core's `:where(.is-layout-flow) > :first-child
          { margin-block-start: 0 }` happens to cover this today, but
          this is the explicit, order-independent version. `.eyebrow`'s
          own margin-bottom:.7rem, display:inline-flex, pill background,
          .45rem gap and the yellow `.eyebrow .ico` (style.css:196) all
          keep applying to the [focusx_icon name="sparkle"] output
          unchanged. */
.hero__copy > p.eyebrow {
	margin-top: 0;
	font-size: .76rem;
	color: var(--blue-700);
	max-width: none;
}

/* 2. SUB-HEADING PARAGRAPH. This one genuinely wants `.hero p`, so only the
      margin needs restoring: the hand-written <p> relied on the UA 1em
      margin, and core's flow layout replaces it with
      `margin-block-start: var(--wp--style--block-gap); margin-block-end: 0`.
      1em of `.hero p`'s 1.04rem (style.css:209) is the exact value being
      replaced. `:not(.eyebrow)` keeps rule 1 intact.

      NOTE this selector is (0,2,1) and therefore also matches the HOMEPAGE
      hero's intro paragraph, since that element carries BOTH .hero__copy and
      .hero__copy--media. Harmless: the declaration is byte-identical to the
      shipped `.hero__copy--media > p { margin-block: 1.04em }` at
      style.css:673. Do not "tidy" one into the other — style.css:673 must
      survive for the homepage pattern, this one for layout A.

      A blanket `.hero__copy > * { margin: 0 }` must NOT be used: it is (0,1,0)
      and, appended later, would tie with and beat both
      `.hero__actions { margin-top: 1.9rem }` (style.css:210) and
      `.trust-pills { margin-top: 1.5rem }` (style.css:211), silently deleting
      3.4rem of hero spacing. Keep the selector narrow. */
.hero__copy > p:not(.eyebrow) { margin-block: 1.04em; }

/* 3. THE OTHER ONE THE HOMEPAGE DID NOT NEED. .hero__grid is `display: grid`
      with `align-items: center` (style.css:164), and the quote form is its
      second grid item. Grid items honour margins, so core's
      `:where(.is-layout-flow) > * { margin-block-start: <block-gap> }` on the
      container group pushes the whole form down — and with align-items:center
      it is the MARGIN BOX that gets centred, so the card lands ~half a gap
      below the copy column.

      The homepage is immune only by accident: its copy column carries
      .hero__copy--media, and `.hero__copy--media + .quote-wrap
      { margin-top: 0 }` (style.css:166, specificity 0,2,0) already
      neutralises it. Layout A uses plain .hero__copy, so that adjacent-sibling
      rule never fires. REQUIRED, not insurance.

      VERIFIED: [focusx_quote_form] renders focusx_quote_wrap() output directly
      (inc/blocks.php:151-157), and core/shortcode adds no front-end wrapper, so
      `<div class="quote-wrap reveal">` (inc/partials.php:214) really is a direct
      child of .hero__grid, exactly as at render-service.php:130. */
.hero__grid > .quote-wrap { margin-block-start: 0; }

/* 4. TRUST PILLS — again TWO problems.

      (a) Each pill was a bare <span> containing `<span class="tick">` + svg
          (render-service.php:126); it is now a core/paragraph, so
          `.trust-pills span` (style.css:212) no longer matches. Restate the
          inline-flex layout on .trust-pill.

      (b) SAME `.hero p` COLLISION AS RULE 1, and worse here, because
          .trust-pills sets colour and size on the PARENT (style.css:211) and
          relies on INHERITANCE. A directly-matching rule always beats an
          inherited value, so `.hero p` (0,1,1) hands every pill
          font-size:1.04rem and color:#35506a in place of the inherited
          .9rem/#33495d. Restated below at (0,2,0), which beats (0,1,1).
          `font-weight: 700` needs no help — nothing on the site targets a
          bare <p> for weight, so it still inherits from .trust-pills.

      `margin: 0` is required for two independent reasons, identical to the
      shipped trust-strip bridge (style.css:778):
        1. no `p` reset in the theme, so the UA `margin: 1em 0` applies;
        2. layout:{"type":"default"} makes core print the flow-layout rules
           server-side, and these paragraphs are flex items of .trust-pills
           (style.css:211 display:flex), so a block-start margin would knock
           pills 2..n out of the row. */
.trust-pills .trust-pill {
	margin: 0;
	display: inline-flex;
	align-items: center;
	gap: .45rem;
	font-size: .9rem;
	color: #33495d;
	max-width: none;
}

/* 5. TICK COLOUR. [focusx_icon name="check"] returns focusx_icon()'s output
      verbatim (inc/blocks.php:186-192) — `<svg class="ico">` with no wrapper —
      so the `<span class="tick">` the template added is gone and
      `.trust-pills .tick { color: var(--green) }` (style.css:213) has nothing
      to hit. Colour the svg directly. Sizing still comes from the global `.ico`
      rule (style.css:79), which matches unchanged. */
.trust-pills .trust-pill .ico { color: var(--green); }

/* ------------------------------------------------------------
   DELIBERATELY NOT INCLUDED — verified redundant or harmful:

   .hero.wp-block-group, .hero__grid.wp-block-group { margin:0; padding:0 }
       HARMFUL, same as on the homepage. (0,2,0) beats
       `.hero__grid { padding: 72px 0 84px }` (style.css:164) and flattens the
       hero. Core adds no spacing to .wp-block-group at all.

   Anything under .hero__actions
       Already shipped at style.css:687-721 and scoped to .hero__actions, which
       this pattern reuses. The recipes line up exactly:
         .btn btn--accent btn--pulse (render-service.php:121) == .is-hero-cta
         .btn btn--phone             (render-service.php:122) == .is-hero-call
       and the two responsive parity rules (760px width:100%, 520px display:none)
       are already there. Zero new button CSS.

   .hero__copy > .wp-block-group.trust-pills { margin-top: 1.5rem }
       Redundant. `.trust-pills` (style.css:211) applies directly to the group
       div and the competing flow rule is :where(), specificity 0.

   .hero h1 fixes
       Redundant. `.hero h1` (style.css:197) is (0,1,1) and beats theme.json's
       `:root :where(h1)` (0,1,0) for font-size, line-height, letter-spacing and
       colour — in block mode exactly as in PHP mode. font-weight:900 comes from
       theme.json in BOTH modes, so it is unchanged. core/heading's
       `wp-block-heading` class carries no margin in wp-block-library.

   .hero__grid gap / .hero__actions gap overrides
       Redundant. Core emits gaps via :where(...) at specificity 0; the theme's
       single-class rules win. The 1040px / 900px / 760px .hero__grid rules
       (style.css:533, :545, :572) all target .hero__grid directly and are
       unaffected by the conversion.
   ------------------------------------------------------------ */

/* OPTIONAL, editor-only (assets/editor.css — NOT this file). editor.css has a
   .eyebrow rule at line 34 and nothing else relevant: no .hero, .hero__grid,
   .trust-pills or .reveal rules, so the canvas shows a stacked, ungradiented
   hero. Cosmetic only. To improve it:
     .editor-styles-wrapper .hero__grid { display:grid; grid-template-columns:minmax(0,1.1fr) 440px; gap:56px; align-items:center; }
     @media (max-width: 1040px) { .editor-styles-wrapper .hero__grid { grid-template-columns:minmax(0,1fr) minmax(360px,440px); gap:36px; } }
     @media (max-width: 900px)  { .editor-styles-wrapper .hero__grid { grid-template-columns:1fr; } }
     .editor-styles-wrapper .trust-pills { display:flex; flex-wrap:wrap; gap:.9rem 1.4rem; font-weight:700; font-size:.9rem; color:#33495d; }
     .editor-styles-wrapper .trust-pill  { margin:0; display:inline-flex; align-items:center; gap:.45rem; }
     .editor-styles-wrapper .reveal      { opacity:1; transform:none; }
   style.css is not loaded into the canvas, so neither .reveal's opacity:0 nor
   the `.hero p` collision is a live hazard there — the last line is cheap
   insurance only. */

/* ===========================================================================
 * FocusX — bridge CSS for the Service Intro pattern (.service-info).
 *
 * Append to the END of style.css. The BLOCK EDITOR BRIDGE section opens at
 * style.css:641-648 (the boxed comment) and runs to EOF at :1646; this belongs
 * after the Contact bridge, which is currently the last block (:1618-1635).
 *
 * ONE rule is genuinely required.
 * ========================================================================= */

/* 1 — REQUIRED. The eyebrow was a hand-written <span class="eyebrow">
 *     (inc/render-service.php:136) and therefore never matched
 *     `.service-info p` (style.css:401, specificity 0,1,1). As a core/paragraph
 *     it now does, and 0,1,1 OUTRANKS `.eyebrow` (style.css:195, 0,1,0) on
 *     every property the two share — the pill would lose its --blue-700 colour
 *     to #354a5e, its .76rem size to 1.02rem and its .7rem bottom margin to
 *     .95rem. Restated at 0,2,1 so it wins outright, independent of stylesheet
 *     order.
 *
 *     line-height is restated because .eyebrow never set one: the original
 *     <span> inherited body's 1.55 (style.css:69-75), whereas
 *     `.service-info p` imposes 1.68 — a visible ~3px change in pill height.
 *     margin-top: 0 matches the original: `.service-info p`'s `margin: 0 0 …`
 *     already zeroed it at 0,1,1, so this is belt-and-braces, not a fix.
 *
 *     All four declarations are load-bearing; there is no fifth. `.service-info p`
 *     sets nothing else, so display:inline-flex, the white pill background,
 *     border, padding, border-radius, box-shadow, font-weight, text-transform
 *     and letter-spacing all survive from .eyebrow untouched.
 *
 *     PRECEDENT — this is the third instance of exactly this problem and the
 *     selector shape matches the two already shipped:
 *       .testimonials p.eyebrow   { margin: 0 0 .7rem }            style.css:1457
 *       .contact-copy p.eyebrow   { color: var(--blue-700); … }    style.css:1629
 *     Neither can match inside .service-info, so there is no collision. */
.service-info p.eyebrow {
	margin: 0 0 .7rem;
	color: var(--blue-700);
	font-size: .76rem;
	line-height: 1.55;
}

/* ------------------------------------------------------------
   DELIBERATELY NOT INCLUDED — verified redundant:

   .service-info__inner > * { margin-block-start: 0 }
       Redundant. core/group's flow layout emits
         :where(body .is-layout-flow) > * { margin-block-start: 24px;
                                            margin-block-end: 0 }
       at ZERO specificity. Every child of this group is already claimed by a
       higher-specificity theme rule:
         p.eyebrow         (0,2,1) margin: 0 0 .7rem       rule 1 above
         .service-info h2  (0,1,1) margin: 0 0 1rem        style.css:399
         .service-info p   (0,1,1) margin: 0 0 .95rem      style.css:401
         .service-info h3  (0,1,1) margin: 1.3rem 0 .75rem style.css:402
         .service-audience (0,1,0) margin: 0               style.css:403
       All beat :where(). Spacing is byte-for-byte the legacy spacing. This
       holds for the core/html fallback path too: core/html has no wrapper, so
       its <p> elements land as direct flow children and `.service-info p`
       still claims them.

       Cross-checked against every `> *` reset already in the bridge —
       :846-847, :986, :1063-1066, :1223, :1443, :1621 — all are scoped to
       homepage containers (.split-grid, .about-media, .cta-band__inner,
       .container > .services__grid, .why__grid, .testimonials, .contact__grid).
       None reaches .service-info__inner. No collision, no duplication.

   .service-audience.wp-block-list { list-style: none; padding: 0 }
       Redundant. .service-audience (style.css:403) already sets both at 0,1,0
       and the only competitor is the UA `ul` default (0,0,1 in the USER-AGENT
       origin), which any author class beats unconditionally. wp-block-library
       ships no .wp-block-list padding or list-style rule.

   Anything for .service-info / .service-info__inner / .container
       Redundant. .wp-block-group contributes only box-sizing:border-box, which
       `* { box-sizing: border-box }` (style.css:67) already applied. The
       classNames land on the real <section>/<div> and every rule at
       style.css:397-405 and :82 matches unchanged. Note style.css:400
       (.service-info h2 span) and :199 (h2 span) both still match the <span>
       inside the core/heading — the accent-word highlight is safe.

   .reveal un-hiding for the editor
       Not needed. assets/editor.css deliberately does not load style.css (see
       its header comment, editor.css:1-8), so `.reveal { opacity: 0 }`
       (style.css:527) never reaches the canvas.
   ------------------------------------------------------------ */

/* OPTIONAL, editor-only (assets/editor.css). The canvas already covers
   .eyebrow (editor.css:34-47) and h2/h3 + `h2 span` (editor.css:16-28), so the
   section reads correctly as-is. The only thing missing is the audience list's
   tick discs; add if a closer preview is wanted, matching the file's
   .editor-styles-wrapper convention:

.editor-styles-wrapper .service-info__inner { max-width: 980px; margin: 0 auto; }
.editor-styles-wrapper .service-audience { list-style: none; padding: 0; margin: 0; display: grid; gap: .7rem; max-width: 520px; }
.editor-styles-wrapper .service-audience li { font-weight: 800; color: #35485c; }
*/

/* ============================================================
   Block bridge — Layout A call banner (.call-banner)
   Append to style.css inside the BLOCK EDITOR BRIDGE section
   (opened at style.css:642). Two rules only.
   Legacy source: inc/render-service.php:148-155
   Theme rules relied on: style.css:266-272
   ============================================================ */

/* 1. The emphasised line.
      Legacy markup is a bare <strong> that is a DIRECT child of
      .call-banner__inner (render-service.php:152). core/paragraph cannot save a
      bare <strong>, so the block form is <p class="call-banner__strong"><strong>.
      .call-banner strong (style.css:270) still matches as a descendant, so
      display:block, the margins, colour, font-size, letter-spacing and
      text-transform all survive untouched. Only the new wrapper <p> is a delta,
      and it is matched by `.call-banner p` (style.css:269), which introduces
      three of them. All three are cancelled here; specificity (0,2,1) beats
      (0,1,1):

      a) font-weight. The UA sheet sets `strong { font-weight: bolder }`, which is
         RELATIVE. Legacy inherits 400 (body, style.css:69-74, declares no
         font-weight) so bolder resolves to 700. Inside the wrapper <p> it would
         inherit 800 from style.css:269 and resolve to 900. Resetting the wrapper
         to 400 restores 700.
      b) max-width. style.css:269 clamps the paragraph to 660px; the legacy
         <strong> is full width of the padded .call-banner__inner. Invisible for a
         short slogan, visible the moment the line is longer.
      c) margins. `margin: 0 auto 1.15rem` on the wrapper is redundant with the
         <strong>'s own .45rem/1.25rem and the `auto` centring is already handled
         by .call-banner__inner's text-align:center (style.css:267). Zeroing it
         makes the spacing deterministic rather than collapse-dependent.

      .call-banner__strong appears nowhere in the hand-written markup, so this
      rule matches block output only — the invariant the BLOCK EDITOR BRIDGE
      header (style.css:642-648) states for the whole section. */
.call-banner p.call-banner__strong { margin: 0; max-width: none; font-weight: 400; }

/* 2. The call button.
      layout:{"type":"default"} makes core add `is-layout-flow` at RENDER time
      (wp_render_layout_support_flag), which brings the zero-specificity global
        :where(body .is-layout-flow) > *           { margin-block-start: 24px }
        :where(body .is-layout-flow) > :first-child { margin-block-start: 0 }
      — the same pair already documented in the shipped About bridge in this file.
      The shortcode expands to a bare <a class="btn"> that IS an element child of
      the inner group and is NOT :first-child, so it picks up 24px. The legacy
      anchor has no margin; the gap comes entirely from the <strong>'s 1.25rem
      bottom margin. Vertical margins do apply to an inline-flex box, so this is a
      real 24px shift. The other three children are safe: .call-banner h2 (0,1,1),
      .call-banner p (0,1,1) and rule 1 above all outrank :where(). Only the
      anchor has nothing defending it.

      .wp-block-group is included so the rule cannot reach the legacy anchor,
      which is also a direct child of .call-banner__inner. Harmless there either
      way, but it keeps this section's stated block-only contract. (0,3,0). */
.call-banner__inner.wp-block-group > .btn { margin-block-start: 0; }

/* ------------------------------------------------------------
   DELIBERATELY NOT INCLUDED — verified redundant or harmful:

   .call-banner > .call-banner__inner { margin-block: 0 }
       Redundant. The inner group is the outer section's only child, therefore
       :first-child, which core's own flow rule zeroes — the same reasoning the
       shipped hero bridge applies to .hero__copy--media.

   .call-banner h2 { margin-block-start: 0 }
   .call-banner p  { margin-block-start: 0 }
       Redundant. Both selectors are (0,1,1) and already declare the full margin
       shorthand at style.css:268 and 269; the flow rule is :where(), zero
       specificity, and never reaches them.

   .call-banner.wp-block-group { padding: 0 }
   .call-banner__inner.wp-block-group { padding: 0 }
       HARMFUL. (0,2,0) beats style.css:266 (`padding: 56px 0 56px`) and 267
       (`padding: 56px 28px 60px`) and would flatten the band. core/group's
       save() adds no spacing of its own.

   .call-banner strong { font-weight: 700 }
       An alternative to the font-weight half of rule 1, not an addition. One or
       the other; resetting the wrapper is the more faithful of the two, because
       in the legacy markup any text sitting beside the <strong> would also have
       inherited 400.

   align:"full" on the outer group
       Omitted, matching patterns/cta-band.php. style.css defines no .alignfull
       rule, and inc/render-service.php:89-94 echoes the_content() bare inside
       <main id="main" class="focusx-blocks"> with no .container and no
       .focusx-blocks rule anywhere in style.css, so the section is already
       full-bleed by document flow.
   ------------------------------------------------------------ */


/* ---- assets/editor.css (SEPARATE FILE) ----
   editor.css has no .call-banner rule — only .cta-band (editor.css:64-71) —
   so the band renders as unstyled dark-on-white text in the canvas. Hex values
   are hardcoded because the customizer's :root override (inc/customizer.php:680,
   --callbanner-bg / --callbanner-rgb) is not loaded into the editor iframe;
   #55b600 is the style.css:56 default and #e6f2fc the style.css:25 --blue-075.
   Same approach the shipped .cta-band editor rule takes with #0a63b4.
   The h2 rule is required: editor.css:23 sets .editor-styles-wrapper h2 to
   #06345a at (0,1,1); the selector below is (0,2,1) and wins. */
.editor-styles-wrapper .call-banner { background: #e6f2fc; padding: 28px 0; }
.editor-styles-wrapper .call-banner__inner {
	background: #55b600;
	color: #fff;
	text-align: center;
	padding: 28px;
}
.editor-styles-wrapper .call-banner__inner h2,
.editor-styles-wrapper .call-banner__inner p,
.editor-styles-wrapper .call-banner__inner strong { color: #fff; }

/* ============================================================
   Bridge CSS — Service "What's Included" (focusx/service-inclusions).
   Append to style.css AFTER the existing bridge sections (the last one
   currently in the file is the Recent Projects PART B block, ~l.1400).

   Front-end markup the pattern produces (is-layout-flow /
   wp-block-group-is-layout-flow are injected server-side by the layout
   support and are correctly absent from post_content):

   <section class="wp-block-group section service-inclusions is-layout-flow …">
     <div class="wp-block-group container is-layout-flow …">
       <div class="wp-block-group section-heading section-heading--svc reveal …">
         <p class="eyebrow">…</p>
         <h2 class="wp-block-heading">…</h2>
       </div>
       <div class="wp-block-group feature-list feature-list--cards reveal …">
         <div class="wp-block-group feature-list__item …">
           <div class="feature-list__icon"><svg class="ico">…</svg></div>
           <div class="wp-block-group feature-list__text …">
             <h3 class="wp-block-heading">…</h3>
             <p>…</p>
           </div>
         </div> × n
       </div>
     </div>
   </section>

   VERIFIED as already matching unchanged — NO bridge needed:
     .container (l.82), .section (l.83), .eyebrow (l.195),
     .section-copy h2/.section-heading h2/… (l.249 — one shared rule),
     .section-heading (l.275),
     .feature-list (l.491), .feature-list--cards (l.492),
     .feature-list--cards .feature-list__item (l.493),
     .feature-list--cards .feature-list__icon (l.494),
     .feature-list--cards .feature-list__icon .ico (l.495),
     .feature-list--cards .feature-list__text h3 (l.496, 0,2,1),
     .feature-list--cards .feature-list__text p (l.497, 0,2,1),
     the 760px one-up breakpoint (l.515), .ico (l.79), .reveal (l.527-529).
   `.service-inclusions` itself has no rule in style.css — it is kept because
   the renderer emits it (render-service.php:157) and it is the scoping hook
   rules 1-3 use.
   ============================================================ */

/* 1 — Flow-layout block gap. theme.json enables settings.spacing.blockGap
   with no styles value, so WP prints core's default
     :root :where(.is-layout-flow)>*{margin-block-start:24px;margin-block-end:0}
   at specificity 0,1,0. Three places lose that fight:
     • .feature-list--cards is a CSS GRID — a 24px top margin on grid items
       ADDS to `gap: 18px` (l.492), so rows would sit 42px apart AND row 1
       would be misaligned (item 1 is :first-child and exempt, item 2 is not).
     • .feature-list__item is display:flex (l.493) and .feature-list__text
       carries no margin of its own, so the text column drops 24px below the
       icon and the `align-items: flex-start` alignment breaks.
     • the .feature-list group is the second child of .container, so it would
       gain 24px on top of .section-heading's 28px margin-bottom.
   The fix must be at least 0,2,0 — a bare `.feature-list > *` is 0,1,0 and
   would only TIE with the core rule, leaving the winner to stylesheet order
   (global styles print inline in <head>, style.css is enqueued). Scoping
   under .service-inclusions gives 0,2,0. Same shape as the shipped `.why`
   bridge (style.css ~l.1222). */
.service-inclusions .container > *,
.service-inclusions .feature-list > *,
.service-inclusions .feature-list__item > * { margin-block-start: 0; }

/* 2 — Restore the one design margin the same core rule (margin-block-end:0)
   zeroes out. .section-heading (l.275) is 0,1,0 and therefore also only ties. */
.service-inclusions .container > .section-heading { margin-bottom: 28px; }

/* 3 — The eyebrow was a hand-written <span> (render-service.php:159); as a
   core/paragraph it is a <p>. .eyebrow's display:inline-flex still applies so
   the pill hugs its text and the <h2> still falls on the next line — only the
   block-level margins differ. margin-bottom is restated because .eyebrow
   (0,1,0) ties with core's margin-block-end:0; margin-top covers the UA
   `p{margin:1em 0}` if blockGap is ever switched off in theme.json. */
.service-inclusions .section-heading > p.eyebrow { margin-top: 0; margin-bottom: .7rem; }

/* 4 — REQUIRED, and the only genuinely NEW rule here.
   render-service.php:160 sets an INLINE style on this h2:
     margin:0 0 1rem; font-size:clamp(1.8rem,3vw,2.6rem);
     line-height:1.08; letter-spacing:-.03em; color:var(--blue-900)
   i.e. the service pages deliberately run a SMALLER h2 than the shared rule
   at style.css:249 (`.section-copy h2, .section-heading h2, .testimonial-head
   h2, .contact-copy h2` → clamp(2rem,3.1vw,3.1rem)/1.06/-.035em).
   The pattern marks the WRAPPER `section-heading--svc` and only the THREE
   properties that actually differ are restated.

   DELIBERATELY OMITTED (verified against style.css:249, which is 0,2,0 and
   already applies to this h2):
     • `margin: 0 0 1rem`   — l.249 already declares exactly that.
     • `color: var(--blue-900)` — l.249 already declares exactly that, and
       0,2,0 already beats theme.json's `:root :where(h2)` at 0,1,0.
   Including margin here is not merely redundant, it is actively WRONG for the
   sibling sections that will share this class (see the note below).

   0,2,1 beats l.249's 0,2,0, so it holds regardless of print order. */
.section-heading.section-heading--svc h2 {
	font-size: clamp(1.8rem, 3vw, 2.6rem);
	line-height: 1.08;
	letter-spacing: -.03em;
}

/* NOTE FOR THE OTHER LAYOUT-A SECTIONS — read before reusing this class.
   The same inline font-size/line-height/letter-spacing appears on the h2 of
   `why` (render-service.php:183), `testimonials` (:199) and `faq` (:244), so
   declare rule 4 ONCE and have those patterns reuse `section-heading--svc`.
   But their MARGIN differs: inclusions uses `margin:0 0 1rem` (identical to
   l.249, hence nothing to restate), while why/testimonials/faq use `margin:0`.
   That is exactly why rule 4 carries no margin — a shared rule declaring
   `margin:0 0 1rem` would add 1rem below three headings that are supposed to
   have none. Those three patterns each add their own
     .<section-hook> .section-heading--svc h2 { margin: 0; }
   The testimonials head uses `.testimonial-head`, not `.section-heading`, so
   its selector must be `.testimonial-head.section-heading--svc h2` (or the
   class goes on a different element); and the faq head additionally carries
   inline `text-align:center;max-width:680px;margin-inline:auto`, which needs
   its own hook. Both are out of scope here — flagging so the merge is clean.

   NOT NEEDED — verified, do not add:
   • Anything for the card interior. `.feature-list--cards .feature-list__text h3`
     and `… p` (l.496-497) are 0,2,1 and already beat the 0,1,0 gap rule; the
     h3 is also :first-child, which core zeroes itself.
   • `min-width: 0` on .feature-list__text. The legacy element is a bare <div>
     and .wp-block-group adds nothing but box-sizing (already global, l.67), so
     flex behaviour is byte-for-byte unchanged. `min-width:0` is on the ITEM
     (l.493) and still matches.
   • Any .wp-block-heading rule. core/heading's only class contributes no
     styles from wp-block-library.
   • Any wrapper reset for core/html. It emits its content verbatim with no
     wrapper, so <div class="feature-list__icon"> reaches the page exactly as
     the PHP template printed it and l.494/495 match untouched.
   • `.wp-block-group{margin-block:0}` — core adds no margin to .wp-block-group,
     and focusx_maybe_render_blocks() (render-service.php:85-96) emits
     `<main id="main" class="focusx-blocks">` bare, so there is no wrapping
     .section/.container to nest inside. The page.php double-wrap hazard
     (page.php wraps the_content() in .section > .container.page-content) does
     not apply to service pages.
   • useRootPaddingAwareAlignments (theme.json) only affects constrained
     layouts and .has-global-padding; every group here is flow. Inert. */


/* ---- OPTIONAL, assets/editor.css ----
   editor.css deliberately does not load style.css, so .reveal{opacity:0} never
   applies in the canvas and no .reveal override is needed (verified: grep for
   "reveal" in assets/editor.css returns nothing). It also has NO .feature-list
   and NO .section-heading rules at all, so the cards stack full-width and the
   [focusx_icon] shortcode shows as literal text.

   HOUSEKEEPING NOTE: the shipped file is inconsistent about this — the
   `.editor-styles-wrapper` rules for the why and work patterns were pasted
   LIVE into style.css (~l.1258-1275 and ~l.1360-1400) rather than moved to
   editor.css. They are harmless there (the selector never matches on the front
   end) but the block below belongs in assets/editor.css; put it there and
   consider relocating the earlier two while you are in the file.

   .editor-styles-wrapper .feature-list--cards {
     display: grid; grid-template-columns: repeat(2, minmax(0,1fr));
     gap: 18px; max-width: 880px; margin-inline: auto;
   }
   .editor-styles-wrapper .feature-list--cards > * { margin-block-start: 0; }
   .editor-styles-wrapper .feature-list__item {
     display: flex; align-items: flex-start; gap: 14px;
     background: #fff; border: 1px solid rgba(10,62,105,.14);
     border-radius: 8px; padding: 16px 18px;
   }
   .editor-styles-wrapper .feature-list__item > * { margin-block-start: 0; }
   .editor-styles-wrapper .feature-list__icon {
     flex: 0 0 auto; padding: .35rem .6rem; border-radius: 8px;
     background: #f2f9ff; border: 1px dashed #7cc2f0; color: #06345a;
     font: 700 11px/1.3 ui-monospace, Menlo, Consolas, monospace;
   }
   .editor-styles-wrapper .feature-list__text h3 { margin: 0 0 .25rem; font-size: .98rem; }
   .editor-styles-wrapper .feature-list__text p  { margin: 0; font-size: .84rem; }
   @media (max-width: 760px) {
     .editor-styles-wrapper .feature-list--cards { grid-template-columns: 1fr; }
   }
   (The .feature-list__icon rule mirrors the placeholder treatment already used
   for .mini-card .icon in the why-choose-us bridge, style.css ~l.1263.)
*/

/* ============================================================
   BRIDGE CSS — Service Layout A, "Why choose us" (.trust-boxes)
   Legacy source: inc/render-service.php l.171-187.
   Append to the END of style.css, after the existing BLOCK EDITOR
   BRIDGE section (which currently ends around l.1403). It MUST
   come after wp-block-library and after the theme.json
   global-styles inline sheet.

   Block output this targets:
     <section class="wp-block-group service-why section section-soft is-layout-flow …">
       <div class="wp-block-group container is-layout-flow …">
         <div class="wp-block-group section-heading reveal is-layout-flow …">
           <p class="eyebrow">…</p>
           <h2 class="wp-block-heading">…</h2>
         </div>
         <div class="wp-block-group trust-boxes reveal is-layout-flow …">
           <div class="wp-block-group trust-box is-layout-flow …">
             <div class="icon"><svg class="ico">…</svg></div>
             <h3 class="wp-block-heading">…</h3>
             <p>…</p>
           </div> × n
         </div>
       </div>
     </section>

   Already matches unchanged — NO bridge needed:
     .container (l.82), .section (l.83), .section-soft (l.84),
     .eyebrow (l.195), .section-heading (l.275),
     .icon (l.291), .icon .ico (l.292),
     .trust-boxes (l.518), .trust-box (l.519),
     .trust-box:hover (l.520), .trust-box .icon (l.521),
     .trust-box h3 (l.522), .trust-box p (l.523),
     .trust-boxes 1-up breakpoint (l.524), .reveal (l.527-529),
     and the shipped unscoped .section-heading > p.eyebrow
     { margin-top: 0 } at l.1073.

   HOOK NAME: `.service-why`. It has no rule in style.css and
   exists only as a scoping hook. The name follows the theme's
   OWN service-page convention — `.service-info` (l.397-402) and
   `.service-inclusions` (set at render-service.php l.157, no CSS
   rule, naming only). It must NOT be `.why`: the homepage pattern
   already owns that hook and its heading typography differs
   (see rule 1).
   ============================================================ */

/* 1. THE HEADING — the one thing className inheritance cannot carry.
      The legacy <h2> carried an inline style attribute (l.175):
        margin:0; font-size:clamp(1.8rem,3vw,2.6rem); line-height:1.08;
        letter-spacing:-.03em; color:var(--blue-900)
      core/heading's save() emits no arbitrary style attribute, and adding
      one by hand to the saved HTML without a matching block attribute is
      exactly what produces "unexpected or invalid content". So the inline
      style is dropped from the markup and restated here.
      Without this rule the grouped selector at style.css:249
      (.section-copy h2, .section-heading h2, .testimonial-head h2,
      .contact-copy h2) takes over and the service heading silently grows
      to the HOMEPAGE size — clamp(2rem,3.1vw,3.1rem) — and gains a 1rem
      bottom margin the design does not have.
      SPECIFICITY: `.section-heading h2` is 0,1,1 (one class + one type),
      NOT 0,2,0. `.service-why .section-heading h2` is 0,2,1 and beats it
      outright, so it holds regardless of stylesheet order. */
.service-why .section-heading h2 {
  margin: 0;
  font-size: clamp(1.8rem, 3vw, 2.6rem);
  line-height: 1.08;
  letter-spacing: -.03em;
  color: var(--blue-900);
}

/* 2. FLOW BLOCK-GAP. theme.json enables settings.spacing.blockGap (l.73)
      with no styles value, so WP prints core's default
        :root :where(.is-layout-flow)>*{margin-block-start:24px;margin-block-end:0}
      at 0,1,0. Every group here is a flow container, so each .trust-box in
      the CSS grid gains 24px of top margin.
      The fix must be at least 0,2,0 — a bare `.trust-boxes > *` is 0,1,0
      and only TIES with the core rule, and that tie is decided by print
      order (global styles are inline in <head>, style.css is enqueued).
      Scoping under .service-why gives 0,2,0. Mirrors the shipped
      `.why .container > *, .why .why__grid > *` rule at style.css:1222. */
.service-why .container > *,
.service-why .trust-boxes > * { margin-block-start: 0; }

/* 3. Restore the design margin the same core rule zeroes out.
      .section-heading{margin-bottom:28px} (l.275) is 0,1,0 and therefore
      also only ties with margin-block-end:0. Mirrors style.css:1227. */
.service-why .container > .section-heading { margin-bottom: 28px; }

/* 4. The eyebrow was <span class="eyebrow">; as core/paragraph it is a <p>.
      .eyebrow's display:inline-flex still applies so the pill hugs its text
      and the h2 still falls on the next line — only block margins differ.
      margin-bottom is restated because .eyebrow (0,1,0) ties with the core
      margin-block-end:0.
      margin-top is NOT restated here: style.css:1073 already ships the
      unscoped `.section-heading > p.eyebrow { margin-top: 0 }` at 0,2,1,
      which beats core's 0,1,0 and covers this section. (The shipped
      .why bridge at l.1235 does restate it — that is redundant there, and
      copying the redundancy would not make this rule any safer.) */
.service-why .section-heading > p.eyebrow { margin-bottom: .7rem; }

/* ------------------------------------------------------------
   DELIBERATELY NOT INCLUDED — verified redundant or harmful:

   .trust-box > * { margin-block-start: 0 }
       Redundant. .trust-box h3 (l.522) and .trust-box p (l.523) are both
       0,1,1 and already beat the 0,1,0 core rule; .trust-box .icon
       (l.521) is 0,2,0 AND is :first-child, which core zeroes itself.
       Adding this rule would also flatten .trust-box .icon's
       `margin: 0 auto 1rem` shorthand (l.521) — the shorthand's
       margin-top:0 currently wins on specificity; a competing
       margin-block-start at equal-or-higher specificity would not.

   .service-why.wp-block-group { padding: 0 }
       HARMFUL. 0,1,1 beats .section{padding:64px 0} (l.83) and would
       collapse the section. Core adds no spacing to .wp-block-group.

   Grid / gap overrides for .trust-boxes
       Redundant. Core emits gaps via :where(...) at specificity 0;
       .trust-boxes{display:grid;grid-template-columns:repeat(3,1fr);
       gap:22px} (l.518) wins, and the 760px 1-up breakpoint (l.524)
       still matches because the class is unchanged.

   Any .reveal override for the editor
       Not needed. assets/editor.css deliberately does not load style.css,
       so .reveal{opacity:0} never reaches the editor canvas.
   ------------------------------------------------------------ */

/* ---- OPTIONAL, assets/editor.css only ----
   editor.css already styles .editor-styles-wrapper .trust-box (it is one
   of the six selectors in the shared card rule at l.50-62; .trust-box is
   the selector on l.56) but defines no .trust-boxes grid. The icon is a
   core/html block, so editor.css's .wp-block-shortcode placeholder rule
   (l.74-81) does NOT apply to it and the [focusx_icon] shortcode shows as
   literal text inside the 56px gradient tile. Mirrors the treatment the
   homepage why-choose-us bridge added for .why__grid / .mini-card .icon
   (style.css:1259-1275):

.editor-styles-wrapper .trust-boxes {
	display: grid;
	grid-template-columns: repeat(3, 1fr);
	gap: 22px;
}
.editor-styles-wrapper .trust-box .icon {
	width: auto;
	height: auto;
	min-height: 0;
	display: inline-block;
	padding: .35rem .6rem;
	border-radius: 8px;
	background: #f2f9ff;
	border: 1px dashed #7cc2f0;
	color: #06345a;
	font: 700 11px/1.3 ui-monospace, Menlo, Consolas, monospace;
	margin: 0 auto 1rem;
}
*/

/* =========================================================================
 * Block bridge — Service Layout A testimonials (focusx/service-a-testimonials)
 *
 * APPEND LOCATION — CORRECTED. The BLOCK EDITOR BRIDGE section opens at
 * style.css:642, not in the 1400s, and the file currently ends at line 1646.
 * This block goes at the END OF FILE, after the Contact bridge (which closes
 * at :1646). The earlier proposal said "after line 1523", which would splice
 * it into the middle of the homepage testimonials bridge and ahead of the
 * Working Process and Contact bridges. Do not do that.
 *
 * SCOPING. The legacy section is <section class="section section-soft"
 * id="testimonials"> (inc/render-service.php:189) — it carries NO section-level
 * class, so there is nothing to hang bridge rules on. #testimonials is not
 * usable either: patterns/testimonials.php gives the HOMEPAGE section the same
 * anchor, and the two sections need DIFFERENT h2 typography (see rule 2), so an
 * id-scoped rule would regress the homepage. The pattern therefore adds one
 * scoping class, `svc-testimonials`. Verified: grep for "svc-" across style.css
 * returns nothing, so the token is collision-free. This follows the precedent
 * set by patterns/testimonials.php, which invented `.testimonials` and
 * `.testimonial__meta` for exactly this reason.
 *
 * TWO RULES (the proposal had three; see the note under "NOT NEEDED").
 * Core's flow-layout rules are emitted by WP_Theme_JSON wrapped in :where() —
 * `:where(.is-layout-flow) > *`, `> :first-child`, `> :last-child`,
 * `> * + *` — so they carry specificity ZERO and lose to every rule in
 * style.css, all of which use a class. Per the correction recorded at
 * style.css:1525-1538, no blanket zero-then-restore reset is used here.
 * ========================================================================= */

/* 1. The ONE genuine blockGap collision in this section.
 *    .testimonial-head (style.css:276) is `display:flex; align-items:flex-end`.
 *    It is now a core/group with layout type "default", so it emits
 *    is-layout-flow and core's `:where(.is-layout-flow) > * + *` puts
 *    `margin-block-start: var(--wp--style--block-gap)` on the second child, the
 *    .rating pill. Verified in theme.json: settings.spacing.blockGap is true and
 *    styles.spacing is absent entirely, so the value falls back to core's
 *    default (24px) — nonzero either way. style.css:306 gives .rating padding
 *    and border but no margin, so nothing outranks the zero-specificity rule.
 *    On a flex line the pill's outer cross size grows by 24px, which inflates
 *    the head's height and drops the pill away from the heading baseline.
 *    Zeroed at (0,2,0); toolbar margins are inline (1,0,0) and still win.
 *    Direct precedent: `.contact__grid > * { margin-block: 0 }` (style.css:1621). */
.svc-testimonials .testimonial-head > * { margin-block: 0; }

/* 2. REQUIRED — the only real visual regression, and it is service-page
 *    specific. render-service.php:194 hard-codes an inline style on the h2:
 *      margin:0; font-size:clamp(1.8rem,3vw,2.6rem); line-height:1.08;
 *      letter-spacing:-.03em; color:var(--blue-900)
 *    core/heading cannot carry that without a style attribute whose emitted
 *    class list (has-text-color etc.) is version-sensitive and risks the
 *    "unexpected or invalid content" notice, so the pattern ships a plain
 *    <!-- wp:heading --> and the value moves here.
 *
 *    WHAT IS ACTUALLY BEING FOUGHT (corrected). theme.json declares
 *    styles.elements.h2 with lineHeight 1.08 and letterSpacing -0.03em —
 *    byte-identical to the legacy inline style, clearly authored to match it —
 *    emitted at `:root :where(h2)` = (0,1,0). That is NOT the problem. The
 *    problem is style.css:249, `.section-copy h2, .section-heading h2,
 *    .testimonial-head h2, .contact-copy h2` at (0,1,1), which already outranks
 *    theme.json and imposes clamp(2rem,3.1vw,3.1rem), line-height 1.06,
 *    letter-spacing -.035em and `margin: 0 0 1rem`. Without this rule the
 *    heading grows up to 0.5rem at desktop and gains a 1rem bottom margin —
 *    clearly visible.
 *    `color` is NOT restated: the inline style, style.css:249 and theme.json all
 *    resolve to var(--blue-900) / primary-dark, so it is already correct.
 *    Specificity (0,2,1) beats .testimonial-head h2 (0,1,1) and :root :where(h2)
 *    (0,1,0). Note rule 1 does not reach the h2 — it is a grandchild of
 *    .testimonial-head — which is why `margin: 0` is restated here. */
.svc-testimonials .testimonial-head h2 {
  margin: 0;
  font-size: clamp(1.8rem, 3vw, 2.6rem);
  line-height: 1.08;
  letter-spacing: -.03em;
}

/* NOT NEEDED, deliberately omitted:
 *  - REMOVED FROM THE PROPOSAL: `.svc-testimonials p.eyebrow { margin: 0 0 .7rem }`.
 *    The proposal shipped this while simultaneously arguing (correctly) that
 *    core's rules are :where()-wrapped and specificity ZERO. Under that premise
 *    the rule is dead weight, and it is inconsistent to ship it. The eyebrow is
 *    :first-child of .testimonial-head__copy, so core's `> :first-child
 *    { margin-block-start: 0 }` (author-level, therefore beating the UA's
 *    `p { margin: 1em 0 }` regardless of specificity) already zeroes the top;
 *    and `.eyebrow { margin-bottom: .7rem }` (style.css:195, 0,1,0) beats the
 *    zero-specificity `margin-block-end: 0`. Computed margins are identical to
 *    the legacy <span>. The exact precedent is the Contact bridge at
 *    style.css:1631-1634, which converts the same <span class="eyebrow"> to
 *    <p class="eyebrow"> and states: "Margins need no fix". The homepage
 *    testimonials bridge (style.css:1460) does ship such a rule, but that
 *    predates the premise correction at :1525-1538 and should not be copied.
 *  - anything at all for .carousel and its descendants. The whole widget is a
 *    single core/html block, so its DOM is byte-identical to
 *    render-service.php:198-211 — including the <span> location line, which is
 *    why this section needs NO `.testimonial__meta` rule (the homepage bridge
 *    at style.css:1475 exists only because that pattern used core/paragraph
 *    for the card body). .carousel, .carousel__viewport, .carousel__track,
 *    .carousel__slide, .carousel__nav, .carousel__btn, .carousel__dots,
 *    .carousel__dot (style.css:477-488) and .testimonial (313-318) all match
 *    unchanged. Core layout never sees inside a core/html block.
 *  - a margin reset on .container's children. .testimonial-head is :first-child
 *    and its own margin-bottom:30px (style.css:277, 0,1,0) beats the
 *    zero-specificity end margin; .carousel declares `margin: 0 auto`
 *    (style.css:477, 0,1,0) which beats the `> * + *` start margin.
 *  - .section / .section-soft / .container / .rating / .stars / .stars__empty /
 *    .rating .stars .ico — all match unchanged through className.
 *  - a `.is-layout-constrained` guard. Unlike .process__grid, nothing here is a
 *    CSS grid, and .container (style.css:82) already caps width at
 *    var(--container), so a constrained layout would be a no-op.
 *  - .alignfull. Shipped once already at style.css:1424 (.page-content >
 *    .alignfull) and inert on the service-page path — focusx_maybe_render_blocks
 *    (render-service.php:85-96) prints the_content() into a bare
 *    <main id="main" class="focusx-blocks"> with no container. align:"full" is
 *    kept only so the section still breaks out if the pattern is inserted on a
 *    generic Page routed through page.php. Verified theme.json sets
 *    useRootPaddingAwareAlignments:true but declares no styles.spacing.padding,
 *    so no --wp--style--root--padding-* vars are emitted and alignfull gains no
 *    unwanted inset. */


/* =========================================================================
 * assets/editor.css additions (editor canvas only) — ONE rule.
 *
 * editor.css already covers .section (:31), .section-soft (:32), .eyebrow
 * (:34), the .testimonial card shell (:54-62) and .wp-block-shortcode (:74);
 * the homepage testimonials bridge already added .testimonial-head, .rating,
 * .eyebrow and .testimonial__meta UNSCOPED (style.css:1503-1523), so they
 * already reach this section in the canvas. The only thing missing is the
 * service-specific heading size, and it MUST be scoped or it will shrink the
 * homepage heading in the canvas too.
 * ========================================================================= */
.editor-styles-wrapper .svc-testimonials .testimonial-head h2 {
  margin: 0;
  font-size: clamp(1.8rem, 3vw, 2.6rem);
  line-height: 1.08;
  letter-spacing: -.03em;
}

/* NOT included, deliberately:
 *  - any .carousel__* rule. The carousel is a core/html block, which the editor
 *    renders either as a raw-HTML textarea or in a SANDBOXED IFRAME.
 *    add_editor_style() CSS does not reach into that iframe, so every such rule
 *    would be dead code — the same finding recorded for the process pattern at
 *    style.css:1605-1610. The carousel simply previews unstyled; that is the
 *    accepted cost of keeping the widget in one block.
 *  - .reveal. assets/editor.css does not load style.css (see its header
 *    comment), so `.reveal { opacity: 0 }` (style.css:527) never applies in the
 *    canvas. */

/* ============================================================
   Block bridge — Service Layout A · FAQ (.service-faq)
   Legacy markup: inc/render-service.php l.215-227.
   Front-end markup the pattern produces (is-layout-flow /
   wp-block-group-is-layout-flow are injected server-side by the
   layout support and are not in post_content):

   <section class="wp-block-group service-faq section is-layout-flow …">
     <div class="wp-block-group container is-layout-flow …">
       <div class="wp-block-group section-heading reveal is-layout-flow …">
         <p class="eyebrow">…</p>
         <h2 class="wp-block-heading">…</h2>
       </div>
       <div class="wp-block-group faq reveal is-layout-flow …">
         <div class="faq-item">…</div> × n     (core/html, verbatim, no wrapper)
       </div>
     </div>
   </section>

   Existing rules that already match unchanged — NO bridge needed:
     .container (l.82), .section (l.83), .eyebrow (l.195),
     .section-heading (l.275), .reveal (l.527-529),
     .faq (l.467), .faq-item (l.468), .faq-q (l.469),
     .faq-q .faq-icon (l.470), .faq-item.is-open .faq-q (l.471),
     .faq-item.is-open .faq-icon (l.472), .faq-a (l.473),
     .faq-a__inner (l.474).
   `.service-faq` has no rule in style.css — verified absent. It is added
   purely as the scoping hook these rules need, because the legacy section
   wrapper is the bare `<section class="section">` and would otherwise be
   unaddressable.

   ---- WHAT CORE ACTUALLY EMITS (corrected) --------------------
   theme.json enables settings.spacing.blockGap (l.73) with no styles value,
   so WP prints core's DEFAULT flow-layout block gap. wp_get_layout_style()
   emits FOUR rules for a flow container with block-gap support, not one:

     :root :where(.is-layout-flow) > :first-child { margin-block-start: 0 }
     :root :where(.is-layout-flow) > :last-child  { margin-block-end: 0 }
     :root :where(.is-layout-flow) > *            { margin-block-start: 0;
                                                    margin-block-end: 0 }
     :root :where(.is-layout-flow) > * + *        { margin-block-start:
                                                    var(--wp--style--block-gap);
                                                    margin-block-end: 0 }

   All four are specificity 0,1,0 (`:root` is the only scoring component;
   `:where()` scores 0). Two practical consequences for this section:

   a) The 24px gap lands on `* + *`, i.e. items 2..n only. Inside .faq
      (display:grid, gap:14px — style.css l.467) that margin ADDS to the
      grid gap, so the first pair of accordion rows sits at 14px and every
      later pair at 38px — an UNEVEN stack, which is more visible than the
      uniform 38px a `> *` rule would have produced.
   b) The `> *` rule's `margin-block-end: 0` is what silently kills
      .section-heading's 28px bottom margin (l.275, also 0,1,0 — a tie,
      resolved by print order: global styles print inline in <head>,
      style.css is enqueued).
   ============================================================ */

/* 1. Neutralise the injected flow gap on both flow containers.
      Scoping under .service-faq gives 0,2,0, which cleanly beats the 0,1,0
      core rules — no reliance on stylesheet order. This is the same form
      the shipped .why bridge uses (style.css l.1221-1222). A BARE
      `.faq > *` would be 0,1,0 and merely tie; the scoped `> *` does not. */
.service-faq .container > *,
.service-faq .faq > * { margin-block-start: 0; }

/* 2. The .section-heading rule — ONE rule, covering both the margin core
      zeroed out and the two inline styles the legacy template carried
      (render-service.php l.217:
        style="text-align:center;max-width:680px;margin-inline:auto;").
      This FAQ heading is centred and narrower than the 720px left-aligned
      .section-heading of l.275. text-align:center is what centres the
      inline-flex .eyebrow pill, exactly as in the legacy markup.
      margin-bottom is restated because .section-heading (0,1,0) only ties
      with core's margin-block-end:0. */
.service-faq .container > .section-heading {
  margin-bottom: 28px;
  text-align: center;
  max-width: 680px;
  margin-inline: auto;
}

/* 3. The eyebrow was a <span>, it is a <p> as core/paragraph. .eyebrow's
      display:inline-flex (l.195) still applies so the pill hugs its text and
      the <h2> still falls on the next line — only block margins differ.
      margin-bottom is restated because .eyebrow (0,1,0) ties with core's
      margin-block-end:0; margin-top covers the UA 1em should blockGap ever
      be switched off in theme.json. Mirrors the .why bridge (l.1233). */
.service-faq .section-heading > p.eyebrow { margin-top: 0; margin-bottom: .7rem; }

/* 4. The h2's inline style, render-service.php l.219:
        style="margin:0;font-size:clamp(1.8rem,3vw,2.6rem);line-height:1.08;
               letter-spacing:-.03em;color:var(--blue-900);"
      These are NOT the same numbers as `.section-copy h2, .section-heading h2,
      …` (l.249, which is clamp(2rem,3.1vw,3.1rem)/1.06/-.035em and a 1rem
      bottom margin) — the service pages deliberately run a smaller heading.
      Without this rule the block version silently inherits the larger
      homepage size.
      SPECIFICITY (corrected): this selector is (0,2,1) — two classes, one
      type — NOT 0,3,0. It still beats .section-heading h2 (0,1,1) and
      theme.json's `:root :where(h2)` (0,1,0), so it holds regardless of
      print order. */
.service-faq .section-heading h2 {
  margin: 0;
  font-size: clamp(1.8rem, 3vw, 2.6rem);
  line-height: 1.08;
  letter-spacing: -.03em;
  color: var(--blue-900);
}

/* ------------------------------------------------------------
   DELIBERATELY NOT INCLUDED — verified redundant or harmful:

   .service-faq.wp-block-group, .faq.wp-block-group { margin:0; padding:0 }
       HARMFUL. 0,2,0 would beat .section{padding:64px 0} (l.83) and flatten
       the section. Core adds no spacing to .wp-block-group itself.

   .faq { gap: 14px } restated
       Redundant. Core expresses block-gap as a margin on the CHILDREN, not
       as `gap` on the container, so l.467's grid gap is never challenged —
       only the children's margins are, and rule 1 handles that.

   A rule for .faq's own `margin: 0 auto` (l.467)
       Not needed. Core's `> *` sets margin-block-start/end to 0 and .faq
       sets its block margins to 0 as well — the values agree, and
       margin-inline:auto is untouched by any logical block-axis property.

   Anything targeting .faq-q / .faq-a / .faq-a__inner / .faq-icon
       Redundant. core/html emits its content verbatim with no wrapper, so
       the whole accordion item reaches the page byte-identical to the legacy
       template's output and l.468-474 match unchanged.
   ------------------------------------------------------------ */

/* OPTIONAL, editor-only (assets/editor.css — it deliberately does NOT load
   style.css, per its own header comment l.1-8).

   STATUS TODAY (corrected): editor.css contains no `.faq*` and no `.reveal`
   rules — verified by grep. So `.faq-a{max-height:0}` never reaches the
   canvas and the answers are currently UNSTYLED BUT VISIBLE, not hidden.
   Nothing is required right now.

   The trap is for whoever adds FAQ styling to editor.css later: the moment
   `.faq-a` is copied across, every answer disappears in the editor, because
   no JS runs there to set the inline max-height. Ship the two rules together:

     .editor-styles-wrapper .faq-item { border:1px solid rgba(10,62,105,.14);
       border-radius:10px; padding:14px 18px; }
     .editor-styles-wrapper .faq-a { max-height: none; }

   A third rule makes the [focusx_icon] shortcode read as a placeholder
   rather than stray text inside the 26px pill, mirroring the treatment
   editor.css already gets for .mini-card .icon (style.css l.1257-1269). */

/* Block bridge — Related services grid (service_a, order 8).

   NONE REQUIRED. Verified against the files, not assumed:

   core/shortcode's save() returns <RawHTML>{attributes.text}</RawHTML> — no
   wrapper element at all. So on the front end the converted page emits
   byte-identical output to inc/render-service.php:229
   (`focusx_related_services( $slug, 7 )`). The
   <section class="section related-services">, the <div class="container">,
   .section-heading.reveal, .related-services__grid and every
   .related-card.reveal are produced inside focusx_related_services()
   (inc/partials.php:432-484). No core block wrapper is interposed anywhere,
   so nothing in style.css:82 (.container), :83 (.section), :275
   (.section-heading), :430-436 (.related-services__grid, .related-card,
   .related-card img, .related-card__body, .related-card h3, .arrow), :441 /
   :444 (the 2-col and 1-col breakpoints) or :527-528 (.reveal) changes
   selector-match status.

   Editor canvas is already handled: assets/editor.css:74-81 styles
   .editor-styles-wrapper .wp-block-shortcode as a dashed labelled box. This
   is the same treatment the shipped patterns/cta-band.php:134-136
   ([focusx_call_button]) already relies on.

   ALREADY SHIPPED — DO NOT DUPLICATE OR CONTRADICT:

   style.css:1575-1577 already carries
       .page-content .section          { padding: 0; }
       .page-content .section > .container { width: 100%; }
       .page-content:has(.section)     { max-width: none; }
   That block exists precisely for the case where a pattern emitting its own
   `.section > .container` is inserted on a generic page rendered by page.php
   (which wraps the_content in `<section class="section"><div class="container
   page-content">`, page.php:32-33). The shortcode's server-rendered
   `<section class="section related-services"><div class="container">` matches
   those three selectors, so the double-64px / double-20px / 820px-cap problem
   is ALREADY solved for the insert-only case. Adding another rule here would
   be redundant at best and a specificity fight at worst.

   DELIBERATELY NOT ADDED:

   .related-services { … }
       grep confirms there is no `.related-services` rule anywhere in
       style.css — only `.related-services__grid` at line 430. The class is a
       bare hook; all vertical padding comes from `.section` (style.css:83).
       Anything added here would be a design change, not a bridge.

   Any .alignfull breakout rule
       The pattern emits no group and no align attribute, so there is nothing
       to break out of. Service pages route through focusx_maybe_render_blocks()
       (inc/render-service.php:85-96), which echoes the_content() into a bare
       <main id="main" class="focusx-blocks"> — no .container, no .section, and
       style.css defines no .focusx-blocks rule, so the band is already full
       width. (The existing `.page-content > .alignfull` rule at style.css:1424
       is for the homepage patterns and is irrelevant here.)

   Any `.wp-block-group.section + …` adjacency rule
       style.css:1006 has `.wp-block-group.section + .wp-block-group.cta-band
       { padding-top: 0 }`. Our section is a plain PHP-emitted
       `<section class="section">`, NOT a .wp-block-group, so it neither
       matches nor breaks that rule. The FAQ section (order 7) that precedes it
       will be a .wp-block-group.section; `.section` + `.section` has no
       collapse rule in this theme and none is wanted — the panel-rendered page
       has exactly the same two adjacent 64px-padded sections.
*/

/* ============================================================
 * BRIDGE CSS — Service hero, Layout B (focusx/service-b-hero)
 * Append to style.css after the existing bridge sections.
 * Source markup: inc/render-service.php:249-265.
 * Only TWO deltas exist; everything else inherits through className.
 * ========================================================== */

/* 1. THE LOAD-BEARING ONE — the eyebrow.
 *    render-service.php:251 emitted <span class="eyebrow">; core/paragraph
 *    emits <p class="eyebrow">. That tag change is NOT free here, because this
 *    section has a descendant <p> rule:
 *
 *      style.css:370  .page-hero p { max-width:620px; margin:0 auto;
 *                                    color:#35506a; font-size:1.05rem; }
 *
 *    (0,1,1) — it out-specifies .eyebrow (style.css:195, 0,1,0) and would
 *    silently repaint the pill: 1.05rem instead of .76rem, #35506a instead of
 *    var(--blue-700), and margin:0 which deletes the .7rem gap above the <h1>.
 *    The <span> never matched that selector, so this is a pure conversion
 *    regression, not a pre-existing bug. Restated at (0,2,1).
 *
 *    This is the SAME collision the shipped bridge already handles twice:
 *      style.css:1629  .contact-copy p.eyebrow  (vs .contact-copy p, :332)
 *      style.css:1457  .testimonials p.eyebrow  (vs the UA p margin)
 *    Both precedents are consistent with this rule; neither overlaps it, so
 *    there is no duplication or contradiction. font-size is restated here and
 *    not at :1629 only because .contact-copy p does not set font-size while
 *    .page-hero p does.
 *
 *    Everything else on .eyebrow survives: display:inline-flex, the white pill
 *    background, border, padding, border-radius and box-shadow are all set by
 *    a single-class rule with nothing competing. Because it stays inline-level,
 *    .page-hero{text-align:center} (style.css:366) still centres it, so no
 *    horizontal correction is needed. */
.page-hero p.eyebrow {
	margin: 0 0 .7rem;
	max-width: none;
	color: var(--blue-700);
	font-size: .76rem;
}

/* 2. The hero image band.
 *    focusx_img() (inc/helpers.php:230-235) printed a bare <img> straight into
 *    .container; core/image wraps it in <figure class="wp-block-image
 *    size-large">. Two independent problems:
 *
 *    (a) `margin: 0` on the figure. The UA sheet gives <figure> `margin:1em
 *        40px` — a 40px inline indent that would visibly narrow the band. Core
 *        block-library's `.wp-block-image { margin: 0 0 1em }` normally covers
 *        the inline part, but that is (0,1,0) and only present when
 *        wp-block-library is enqueued. Setting it here at (0,2,1) makes the
 *        result independent of both the UA sheet and stylesheet load order,
 *        and also collapses the 1em bottom margin that would open a gap above
 *        the following .section. Same remedy as style.css:1087.
 *
 *    (b) THE AMBIGUOUS ONE. Core ships `.wp-block-image img { height: auto }`
 *        at (0,1,1) — an exact tie with `.service-hero-media img { width:100%;
 *        height:clamp(220px,34vw,380px); object-fit:cover; border-radius;
 *        box-shadow }` (style.css:410, also 0,1,1). Which wins is decided
 *        purely by enqueue order, which the theme does not control, so the band
 *        would be either the designed clamp height or the photo's full natural
 *        height depending on load order. Restating at (0,2,2) removes the
 *        ambiguity. Identical hazard and remedy to the services-grid bridge
 *        rule 3 (style.css:1078-1094) and the work-projects one (:1303-1315).
 *
 *    `display:block` is NOT a change and NOT a fix — style.css:76 already sets
 *    `img { display: block; max-width: 100% }` globally, so the legacy bare
 *    <img> was block-level too and never carried a baseline descender gap.
 *    It is restated only so this rule is self-contained at (0,2,2); the
 *    conversion is pixel-identical here. */
.service-hero-media figure.wp-block-image { margin: 0; }
.service-hero-media figure.wp-block-image img {
	display: block;
	width: 100%;
	height: clamp(220px, 34vw, 380px);
	object-fit: cover;
	border-radius: var(--radius);
	box-shadow: var(--shadow);
}

/* NOT NEEDED — verified, do not add:
 *
 * • Anything for the <h1>. `.page-hero h1` (style.css:369, 0,1,1) sets margin,
 *   font-size, letter-spacing and colour and beats both the :where() flow-layout
 *   margin rule (specificity 0) and theme.json's `:root :where(h1)` (0,1,0).
 *   theme.json ALSO contributes font-weight:900 and line-height:1.07, which
 *   .page-hero h1 does not set — but global-styles CSS is enqueued site-wide
 *   and applied to the hand-written <h1> too, so there is no delta.
 *   core/heading adds only .wp-block-heading, which carries no declarations.
 *
 * • Anything for the sub-paragraph. core/paragraph saves a bare <p>, so
 *   `.page-hero p` (style.css:370) matches it exactly as before, and its
 *   `margin:0 auto` (0,1,1) already beats the flow-layout margin.
 *
 * • Anything for .page-hero .container (style.css:368, position/z-index) or
 *   .service-hero-media .container (style.css:409) — core/group with
 *   className="container" reproduces the selector exactly.
 *
 * • Flow-layout resets on the two .container groups. Each is the only child of
 *   its section group, so core's own `:where(.is-layout-flow) > :first-child`
 *   and `> :last-child` rules zero its block margins. `.container`
 *   (style.css:82) supplies width and margin-inline:auto untouched.
 *
 * • `.wp-block-group { margin: 0; padding: 0 }` on either top-level group.
 *   HARMFUL — same trap documented for .hero__grid at style.css:727. Core adds
 *   no spacing to .wp-block-group, and a (0,2,0) rule here would flatten
 *   .page-hero's `padding: 66px 0 54px` (style.css:366).
 *
 * • Full-bleed / alignfull handling. focusx_maybe_render_blocks()
 *   (inc/render-service.php:83-96) echoes `<main id="main" class="focusx-blocks">`
 *   and calls the_content() with NO .container or .page-content wrapper, and
 *   style.css defines no .focusx-blocks rules (verified by grep). So the
 *   .page-hero gradient and its ::before blob stay full-bleed, and there is no
 *   root flow container to put a margin between the two top-level groups —
 *   .service-hero-media's margin-top:-28px survives intact. Do not add a
 *   wrapper to that template.
 *
 * • `.page-content img { max-width:100%; height:auto; border-radius:14px }`
 *   (style.css:637) — checked, and it does NOT apply: focusx_maybe_render_blocks()
 *   emits no .page-content wrapper. If a future template change adds one it
 *   would hit the band at (0,1,1); this bridge rule at (0,2,2) already wins.
 *
 * • Any responsive override. `.page-hero` and `.service-hero-media` have no
 *   media-query variants anywhere in style.css (grep for both class names
 *   returns exactly lines 366-370 and 408-410 — the complete set).
 *
 * ---- assets/editor.css (OPTIONAL, canvas fidelity only) ----
 * editor.css:34 already styles .eyebrow and the file has no .page-hero rule,
 * so the (0,1,1) collision above does not exist in the canvas — rule 1 is
 * front-end only. editor.css:84-85 scopes its .wp-block-image margin reset to
 * .service-card / .project-card, so it does not reach this band. To preview
 * the band shape:
 *   .editor-styles-wrapper .page-hero { text-align: center; padding: 40px 0; }
 *   .editor-styles-wrapper .service-hero-media figure.wp-block-image { margin: 0; }
 *   .editor-styles-wrapper .service-hero-media img { height: 260px; object-fit: cover; border-radius: 18px; }
 */

/* ============================================================
   Bridge CSS — Service (B) intro pattern (focusx/service-b-intro).
   Append to style.css, AFTER the existing BLOCK EDITOR BRIDGE sections.
   Editor-canvas additions at the bottom belong in assets/editor.css.

   COLLISION CHECK (requirement d) — done against the shipped bridge:
     • .focusx-btn / .focusx-btn--accent are DECLARED, not redeclared, here.
       They already exist at style.css:880-902 (About bridge). Note the theme
       ALSO carries a second, near-duplicate copy of
       `.wp-block-button.focusx-btn > .wp-block-button__link` at style.css:1334
       (with .focusx-btn--outline at :1352). That duplication is pre-existing
       and harmless — both copies are identical for the base hook — but it is
       one more reason NOT to add a third. This file only adds the two things
       neither copy covers: width and pulse.
     • .service-card__cta (style.css:1109-1138) and .service-lead-card__cta are
       different class names; no selector below matches services-grid markup.
     • `.section-copy > p.eyebrow` (:854), `.section-heading > p.eyebrow`
       (:1073), `.why .section-heading > p.eyebrow` (:1235) and
       `.testimonials p.eyebrow` (:1457) are all scoped to other ancestors and
       cannot match `.service-intro .lead > p.eyebrow`.
     • No rule below touches .split-grid, .about-media or .experience-badge.
   ========================================================== */

/* 1. Block gap on the two-column grid — REQUIRED.
      wp_render_layout_support_flag() adds `is-layout-flow` at render time and
      core applies `:where(.is-layout-flow) > * { margin-block-start: <gap> }`
      at specificity 0,0,0. theme.json:73 sets settings.spacing.blockGap:true
      with no styles.spacing.blockGap value, so core's fallback gap applies.
      .service-intro is a CSS grid with `gap: 56px` (style.css:413) — a margin on
      a grid item ADDS to the gap, so the .service-lead-card column would drop
      below the top of the lead column. .container's rule is order-independence
      insurance: today .service-intro is the container's only child so core's
      :first-child rule already zeroes it, but an owner dragging any block above
      it in the editor would re-expose the gap.
      Deliberately NOT a blanket `.service-lead-card > *` reset: h3
      (style.css:417) and .check-list (418) already set margin shorthands that
      beat 0,0,0, and the buttons wrapper gets its margin set in rule 4. */
.container > .service-intro,
.service-intro > * { margin-block-start: 0; }

/* 2. The lead heading — REQUIRED, and it is the important one.
      render-service.php:272 puts the whole look in an inline style because
      style.css:249 (`.section-copy h2, .section-heading h2, .testimonial-head h2,
      .contact-copy h2`) does not include `.lead h2`. core/heading has no way to
      emit an inline style, so the declarations move here verbatim.
      Specificity 0,2,1 — beats theme.json's `:root :where(h2)` (0,1,0).
      `color: var(--blue-900)` is NOT redundant against theme.json's identical
      shipped #06345a (theme.json:20 primary-dark, referenced at :114):
      inc/customizer.php:634 rewrites --blue-900 at runtime from the Primary
      picker while the preset stays frozen. Drop it and this becomes the only
      heading on the site that ignores a brand-colour change.
      font-size/line-height/letter-spacing duplicate theme.json's x-large h2
      exactly (theme.json:66, :109-112); they are restated so the section does
      not silently change if theme.json's type scale is ever retuned.
      `h1 span.hl, h2 span` (style.css:199) still matches, so the highlighted
      words in intro_h2 keep their --blue-600 colour untouched. */
.service-intro .lead h2 {
  margin: 0 0 1rem;
  font-size: clamp(1.8rem, 3vw, 2.6rem);
  line-height: 1.08;
  letter-spacing: -.03em;
  color: var(--blue-900);
}

/* 3. Eyebrow — REQUIRED, and not the usual case.
      Was <span class="eyebrow">, now <p class="eyebrow">. On the homepage the
      only delta was the UA top margin; here `.service-intro .lead p
      { margin: 0 0 1rem }` (style.css:414, 0,2,1) now matches the eyebrow and
      OVERRIDES `.eyebrow { margin-bottom: .7rem }` (style.css:195, 0,1,0),
      adding .3rem below the pill. This selector is 0,3,1 and, being appended
      later, also wins the tie with `.service-intro .lead p:last-child` (0,3,1) —
      inconsequential, since the eyebrow is never the last child (the h2 always
      follows it).
      display:inline-flex, the white pill background, border and shadow all
      continue to apply, so p.eyebrow shrink-wraps exactly as the span did. */
.service-intro .lead > p.eyebrow { margin-top: 0; margin-bottom: .7rem; }

/* 4. The aside CTA — REQUIRED.
      Original: <a class="btn btn--accent btn--pulse btn--full" href="#quote">.
      core/button puts `className` on the WRAPPER <div class="wp-block-button">,
      never on the <a>, so the recipe cannot land on the link and putting it on
      the wrapper would paint a second yellow box around the anchor.
      The BASE and ACCENT halves are already solved: the About bridge shipped
      `.wp-block-button.focusx-btn > .wp-block-button__link` (style.css:880) and
      `.wp-block-button.focusx-btn--accent > ...` (:897), which reproduce
      style.css:140-143 including the customizer-aware --yellow / --yellow-600 /
      --yellow-ink. This pattern reuses both hooks rather than re-declaring them.
      Only .btn--full and .btn--pulse are missing, plus the margin: with the <a>
      gone, `.service-lead-card .btn { margin-top: 1.1rem }` (style.css:419)
      matches nothing, so the 1.1rem moves to the buttons wrapper — which also
      neutralises the flow block-gap on it (see rule 1).
      .wp-block-buttons is flex (core's `body .is-layout-flex{display:flex}`,
      0,1,0) and .wp-block-button is a content-sized flex item, so both must go
      display:block before width:100% on the link means anything. The selector
      below is 0,3,0 and wins outright rather than on load order — this is
      STRONGER than the shipped `.service-card__cta` (0,1,0), which only beats
      core on source order.
      CORRECTION vs. the proposal: `gap: 0` added, mirroring style.css:1109
      exactly. It is inert while display:block holds, but it is one declaration
      of insurance against a future editor.css or theme.json rule restoring flex.
      .btn--pulse's animation beats .focusx-btn--accent's static box-shadow
      regardless of specificity — animation declarations sit above normal author
      declarations in the cascade — so the pulse is not lost to the 0,3,0 hook.
      @keyframes ctaPulse (style.css:152) and --accent-rgb (style.css:46) are
      reused as-is, including the reduced-motion opt-out at style.css:158.
      (Alternative considered and REJECTED for consistency: core/button's
      `width:100` attribute emits `<div class="wp-block-button has-custom-width
      wp-block-button__width-100">` and core supplies the width, removing one
      declaration. focusx_b_button() has no width parameter and the shipped
      .service-card__cta hand-rolls it, so hand-rolling stays.) */
.service-lead-card > .wp-block-buttons.service-lead-card__cta {
  display: block;
  gap: 0;
  margin-top: 1.1rem;
  margin-bottom: 0;
}
.service-lead-card__cta > .wp-block-button { display: block; }
.service-lead-card__cta .wp-block-button__link {
  width: 100%;                                                   /* .btn--full  (style.css:149) */
  animation: ctaPulse 2.1s cubic-bezier(.66, 0, 0, 1) infinite;  /* .btn--pulse (style.css:157) */
}
@media (prefers-reduced-motion: reduce) {
  .service-lead-card__cta .wp-block-button__link { animation: none; }
}

/* 5. NOT NEEDED — verified, do not add:
      • Anything for .service-intro's grid or its 1040px single-column override
        (style.css:413, 439) — the className lands on the group, both still match.
      • Anything for .service-lead-card (416) — every declaration is a box
        property (background, var(--radius) 18px, var(--shadow-soft), border,
        border-top 4px --blue-600, padding 26px) that behaves identically on
        <aside class="wp-block-group service-lead-card reveal">.
      • `.service-lead-card h3` (417) — 0,1,1, still beats theme.json's
        `:root :where(h3)` (0,1,0), so the heading keeps --blue-900 and its
        `margin: 0 0 .8rem`.
      • `.service-lead-card .check-list { margin: 0 }` (418) — 0,2,0, unchanged.
        (.check-list's own margin at :250 is `1.5rem 0 2rem`; :418 is what zeroes
        it inside the card, and it still matches.)
      • `ul.check-list { list-style: none; padding: 0 }` insurance — .check-list
        (250) already sets both at 0,1,0; the only competitor is the UA `ul`
        default at 0,0,1, and wp-block-library ships no .wp-block-list padding
        rule. Load order is irrelevant. (Same conclusion the About bridge
        reached and documented at style.css:964-965.)
      • Anything for core/paragraph — it saves a bare <p> (className:false in its
        block.json), so `.service-intro .lead p` and `p:last-child` (414-415)
        already match, including `p:last-child { margin-bottom: 0 }` on the final
        intro_body paragraph.
      • Anything for .reveal — the class is carried on both columns by the
        pattern, and script.js:143-152 queries `.reveal` flat with no structural
        assumptions.
      • A colour pin for <span> inside intro_h2 — `h1 span.hl, h2 span`
        (style.css:199) is 0,0,2 for the h2 arm and there is nothing competing.

   ---- assets/editor.css (OPTIONAL, canvas fidelity only) ----
   VERIFIED line refs: editor.css already covers .section (l.31), .eyebrow
   (l.34), h2 span (l.28), h3 colour (l.24) and .wp-block-buttons (l.86); it has
   nothing for the two-column intro and nothing for .service-lead-card (its card
   block at l.51-56 lists .service-card/.mini-card/.project-card/.testimonial/
   .step/.trust-box only). style.css is not loaded into the canvas, so .reveal's
   opacity:0 is not a hazard today — the last rule is cheap insurance.
   The .service-lead-card h3 rule below is 0,2,0 and correctly overrides
   editor.css:24's `.editor-styles-wrapper h3 { color: #0d2338 }`.
   Known, accepted fidelity gap (same as the shipped bridge at style.css:953-954):
   the .check-list li::before tick disc is not reproduced in the canvas.

   .editor-styles-wrapper .service-intro { display: grid; grid-template-columns: 1.25fr .9fr; gap: 56px; align-items: start; }
   @media (max-width: 1040px) { .editor-styles-wrapper .service-intro { grid-template-columns: 1fr; gap: 36px; } }
   .editor-styles-wrapper .service-intro > * { margin-block-start: 0; }
   .editor-styles-wrapper .service-intro .lead h2 { margin: 0 0 1rem; font-size: clamp(1.8rem, 3vw, 2.6rem); line-height: 1.08; letter-spacing: -.03em; color: #06345a; }
   .editor-styles-wrapper .service-intro .lead p { color: #354a5e; font-size: 1.04rem; margin: 0 0 1rem; }
   .editor-styles-wrapper .service-lead-card { background: #fff; border-radius: 18px; border: 1px solid rgba(10,62,105,.12); border-top: 4px solid #0f7ad4; padding: 26px; }
   .editor-styles-wrapper .service-lead-card h3 { margin: 0 0 .8rem; color: #06345a; font-size: 1.15rem; }
   .editor-styles-wrapper .check-list { list-style: none; padding: 0; margin: 0; display: grid; gap: .7rem; }
   .editor-styles-wrapper .check-list li { font-weight: 700; color: #35485c; }
   .editor-styles-wrapper .service-lead-card .wp-block-buttons { margin-top: 1.1rem; }
   .editor-styles-wrapper .reveal { opacity: 1; transform: none; }
*/

/* ============================================================
   BRIDGE CSS — "Service B · What's included" pattern
   (focusx/service-b-included). Append to style.css AFTER the
   "Why choose" bridge block, which currently ends at l.1276.

   Front-end markup the pattern produces (is-layout-flow /
   wp-block-group-is-layout-flow are injected server-side by
   wp_render_layout_support_flag(); they are NOT in post_content):

   <section class="wp-block-group service-included section section-soft …">
     <div class="wp-block-group container …">
       <div class="wp-block-group section-heading reveal …">
         <p class="eyebrow">…</p>
         <h2 class="wp-block-heading">…</h2>
       </div>
       <div class="wp-block-group service-features …">
         <article class="wp-block-group mini-card reveal …">
           <div class="icon"><svg class="ico">…</svg></div>
           <h3 class="wp-block-heading">…</h3>
           <p>…</p>
         </article> × n
       </div>
     </div>
   </section>

   Existing rules that already match unchanged — NO bridge needed:
     .container (l.82), .section (l.83), .section-soft (l.84),
     .eyebrow (l.195), .section-copy h2/.section-heading h2 (l.249),
     .section-heading (l.275), .mini-card h3 (l.289),
     .mini-card p (l.290), .icon (l.291), .icon .ico (l.292),
     .mini-card (l.296), .mini-card .icon (l.297),
     .service-features (l.422), .service-features .mini-card (l.423),
     .reveal (l.527-529), and both .service-features breakpoints —
     two-up at l.440, one-up at l.444.
   `.service-included` has no rule of its own in style.css. Like `.why`,
   it exists purely as the 0,2,x scoping hook these four rules need.
   ============================================================ */

/* 1. The global flow block-gap. theme.json (l.73) sets settings.spacing.blockGap
      true with no styles value, so core prints
        :root :where(.is-layout-flow)>*{margin-block-start:0;margin-block-end:0}
        :root :where(.is-layout-flow)>*+*{margin-block-start:24px}
      at specificity 0,1,0 (:where contributes nothing). Every group here is a
      flow container, so .service-features gains 24px of top margin and the
      .mini-card articles inside the CSS grid gain it too — and on a grid item
      that margin ADDS to `gap: 22px` (l.422), so rows would sit 46px apart.
      The fix must be at least 0,2,0 — a bare `.service-features > *` is 0,1,0
      and would only TIE with core, leaving the winner to stylesheet order
      (global styles print inline in <head>, style.css is enqueued). Scoping
      under .service-included gives 0,2,0. Exactly mirrors l.1222-1223. */
.service-included .container > *,
.service-included .service-features > * { margin-block-start: 0; }

/* 2. Restore the design margin the same core rule zeroes out. `.section-heading`
      (l.275) is 0,1,0 and therefore only ties with core's margin-block-end:0.
      Mirrors l.1227. */
.service-included .container > .section-heading { margin-bottom: 28px; }

/* 3. The eyebrow was a <span> (render-service.php:291); as core/paragraph it is
      a <p>. `.eyebrow{display:inline-flex}` (l.195) still applies so the pill
      hugs its text and the <h2> still falls on the next line — only the
      block-level margins differ. margin-bottom is the load-bearing half: it is
      restated because .eyebrow (0,1,0) ties with core's margin-block-end:0.

      NOTE — the `margin-top: 0` half is ALREADY supplied, unscoped, by the
      shipped Services-grid bridge at style.css:1073
        .section-heading > p.eyebrow { margin-top: 0; }
      which is 0,2,1 and matches this markup as-is. It is repeated here only for
      parity with the shipped `.why` rule at l.1235, which repeats it for the
      same reason. If the service-page patterns are integrated as a set,
      collapse l.1073 / l.1235 / this rule into one selector list rather than
      shipping a third near-identical copy. */
.service-included .section-heading > p.eyebrow { margin-top: 0; margin-bottom: .7rem; }

/* 4. render-service.php:292 carries a per-heading inline style on this h2 that
      makes the Layout-B heading SMALLER than the homepage one:
        margin:0 0 1rem; font-size:clamp(1.8rem,3vw,2.6rem);
        line-height:1.08; letter-spacing:-.03em; color:var(--blue-900)
      `.section-heading h2` (l.249) already supplies margin:0 0 1rem and
      color:var(--blue-900) identically, so only the three genuinely different
      declarations are restated here. 0,2,1 beats l.249's 0,1,1.
      Do NOT try to reproduce this with core/heading's style attribute —
      core normalises typography attrs and clamp() round-trips badly, which is
      exactly how a block ends up flagged as invalid content. */
.service-included .section-heading h2 {
	font-size: clamp(1.8rem, 3vw, 2.6rem);
	line-height: 1.08;
	letter-spacing: -.03em;
}

/* Nothing else is required. In particular:
   - The card interior needs no reset. `.mini-card h3` (0,1,1) sets the
     shorthand margin:0 0 .35rem and `.mini-card p` (0,1,1) sets margin:0 —
     both beat core's 0,1,0. `.icon` is the first child (margin-block-start
     already 0) and `.mini-card .icon` (0,2,0) keeps its 1rem bottom gap.
     Do not add `.mini-card > *`.
   - core/html emits its content verbatim with no wrapper, so <div class="icon">
     reaches the page exactly as focusx_icon() printed it and .icon / .icon .ico
     match unchanged.
   - .service-features (l.422) is repeat(3,1fr) gap 22px, NOT the 4-up
     .why__grid (l.295). Keeping it a plain core/group preserves l.440 (2-up at
     900px) and l.444 (1-up at 760px) verbatim, and lets the owner add or delete
     an inclusion without touching a column count. core/columns would have to
     re-implement all three steps in bridge CSS.
   - .service-features .mini-card{text-align:left} (l.423) needs no help. There
     is NO centred .mini-card anywhere in style.css — l.296 sets no text-align
     at all (the centred card in this theme is `.step`, l.322). l.423 is a
     defensive left-align with nothing to beat; it applies here because the
     pattern keeps both classes, and it would apply to the legacy markup too.
   - useRootPaddingAwareAlignments (theme.json l.10) only affects constrained
     layouts and .has-global-padding; these groups are flow and theme.json
     defines no styles.spacing.padding, so it is inert. */


/* ---- OPTIONAL, assets/editor.css only ----
   editor.css deliberately does NOT load style.css, so .reveal{opacity:0} never
   reaches the canvas and the section is visible while editing — no .reveal
   override needed. editor.css:50-62 already styles .mini-card, and the
   `.editor-styles-wrapper .mini-card .icon` placeholder treatment added by the
   "Why choose" bridge (now style.css:1264-1276) already covers the literal
   [focusx_icon …] string here. The only thing missing is the grid itself.
   No collision with the shipped `.editor-styles-wrapper .why__grid` (l.1259). */
.editor-styles-wrapper .service-features {
	display: grid;
	grid-template-columns: repeat(2, 1fr);
	gap: 22px;
}

/* ---------- Service Layout B — quote section: core-block bridge ----------
   TWO rules only. Both exist for the same reason: inc/render-service.php:308
   and :310 style this section with INLINE style attributes that have no
   class-based equivalent anywhere in style.css, and core/group + core/heading
   cannot carry arbitrary inline CSS.

   VERIFIED NOT NEEDED (do not add these):
   - Flow blockGap on .container's two children. Core emits, at specificity 0,0,0,
       :where(.is-layout-flow) > :first-child { margin-block-start: 0 }
       :where(.is-layout-flow) > *            { margin-block-start: 24px;
                                                margin-block-end: 0 }
     The .section-heading group is :first-child, so its top margin is already
     zeroed; and the shortcode's own <div class="quote-wrap reveal"> is caught by
     `.service-quote .quote-wrap { max-width: 560px; margin: 0 auto }`
     (style.css:427, specificity 0,2,0) whose `margin` shorthand sets
     margin-top: 0 and beats the 0,0,0 rule. VERIFIED: core/shortcode's save()
     emits the bare shortcode text with no wrapper, so after do_shortcode (the_content
     priority 11) .quote-wrap really is a direct child of .container and :427 matches.
   - The h2's top margin inside .section-heading. `.section-copy h2, .section-heading h2,
     .testimonial-head h2, .contact-copy h2` (style.css:249, 0,1,1) declares
     `margin: 0 0 1rem` — a shorthand, so it sets margin-top:0 explicitly and
     outranks the :where() flow rule. Rule 2 below deliberately does NOT restate
     margin or color, so :249 keeps supplying both (its color: var(--blue-900)
     is byte-identical to the inline style at :310).
   - The eyebrow's top margin. `.section-heading > p.eyebrow { margin-top: 0 }`
     is ALREADY in this bridge at style.css:1073 (shipped with the services-grid
     pattern) and matches here unchanged. Do not duplicate it.
   - The <span class="eyebrow"> -> <p class="eyebrow"> swap. Unlike .contact-copy
     (style.css:332) there is no `.service-quote p` or `.section-heading p` rule
     anywhere in style.css — grep for "section-heading" returns only :249, :275,
     :276 plus bridge rules — so nothing outranks `.eyebrow` (style.css:195) and
     its display:inline-flex still centres the pill under rule 1 below.
   - The page.php `.page-content` double-container guard. Block mode for service
     pages goes through focusx_maybe_render_blocks() (inc/render-service.php:89),
     which prints `<main id="main" class="focusx-blocks">` with no container —
     the same path front-page.php:16-17 uses. The shared guard at style.css:1575-1577
     is already there for the generic-page case; nothing new is required.
   - Anything for .service-quote's own background: `.service-quote { background:
     var(--blue-075) }` (style.css:426) keys off the className the pattern emits. */

/* 1. inc/render-service.php:308 centres the heading block with
      style="text-align:center;max-width:680px;margin-inline:auto". `.section-heading`
      (style.css:275) is `max-width: 720px; margin-bottom: 28px` — left-aligned,
      40px wider, no auto margin — so without this the heading goes flush-left
      while the form below it stays centred at 560px. 0,2,0 beats :275's 0,1,0.
      Uses margin-inline (not the margin shorthand) so :275's margin-bottom: 28px
      survives, exactly as it does under the inline style today. Scoped to
      .service-quote because the homepage .section-heading must stay left-aligned. */
.service-quote .section-heading { text-align: center; max-width: 680px; margin-inline: auto; }

/* 2. inc/render-service.php:310 also inlines a SMALLER h2 than the homepage's.
      style.css:249 correctly supplies margin: 0 0 1rem and color: var(--blue-900),
      but its font-size clamp(2rem, 3.1vw, 3.1rem), line-height 1.06 and
      letter-spacing -.035em are the homepage values. Restate only the three
      service-page values; 0,2,1 beats :249's 0,1,1.

      SHARED: every service-page section heading carries this identical inline
      style — Layout A and Layout B, intro (:272), included (:292), quote (:310).
      Ship it ONCE as a grouped selector list rather than repeating the
      declarations in each service pattern's bridge block. */
.service-quote .section-heading h2 {
	font-size: clamp(1.8rem, 3vw, 2.6rem);
	line-height: 1.08;
	letter-spacing: -.03em;
}

/* ---------- Optional: assets/editor.css (editor canvas only) ----------
   editor.css already labels the shortcode placeholder (:74-81), styles
   .section (:31) and .eyebrow (:34-47), and style.css:1521 already ships
   `.editor-styles-wrapper .eyebrow { margin: 0 0 .7rem }`. Only the two things
   the canvas cannot inherit are missing. Literal hex because editor.css does not
   load style.css and therefore has no --blue-075 (style.css:25 = #e6f2fc) —
   the same value editor.css:32 already hardcodes for .section-soft. */
.editor-styles-wrapper .service-quote { background: #e6f2fc; }
.editor-styles-wrapper .service-quote .section-heading {
	text-align: center;
	max-width: 680px;
	margin-inline: auto;
}
.editor-styles-wrapper .service-quote .section-heading h2 {
	font-size: clamp(1.8rem, 3vw, 2.6rem);
	line-height: 1.08;
	letter-spacing: -.03em;
}

/* =========================================================================
   Bridge CSS — service CTA band (focusx/cta-band-service)

   NOTHING NEW IS REQUIRED. Verified by reading style.css:965-1040: the three
   rules already shipped in the BLOCK EDITOR BRIDGE section for the homepage
   CTA band are written against .cta-band / .cta-band__inner with no
   front-page-only scoping, so they cover this pattern verbatim. Reproduced
   here only so the reviewer can confirm coverage — DO NOT append a second
   copy to style.css.

     .cta-band__inner > * { margin-block-start: 0; }        (style.css:986)
     .cta-band h2 { color: #fff; }                          (style.css:997)
     .wp-block-group.cta-band { padding-top: 72px; }        (style.css:1005)
     .wp-block-group.section + .wp-block-group.cta-band
       { padding-top: 0; }                                  (style.css:1006)

   ONE CONTEXT-SPECIFIC CHECK on that last pair. style.css:261 is
   `.cta-band { padding: 0 0 72px }` — zero top padding, correct in
   inc/render-service.php only because the band at :316 always follows
   `<section class="section service-quote" id="quote">` (:306), which supplies
   the gap. The sibling rule restores that in block mode ONLY IF the quote
   section immediately before it is itself a core/group carrying `section` in
   its className (i.e. className "section service-quote", tagName section,
   anchor "quote"). Section chunks are joined with "\n\n" by
   inc/blocks-migrate.php:110 and land as true DOM siblings inside
   <main class="focusx-blocks">, so `+` matches; whitespace text nodes are
   ignored by the adjacent-sibling combinator. Coordinate with the service_b
   quote-section author.

   *** CORRECTED FALLBACK ***
   The reviewed proposal offered this fallback:

     .wp-block-shortcode + .wp-block-group.cta-band,
     .wp-block-html + .wp-block-group.cta-band { padding-top: 0; }

   That is DEAD CSS and must not be used. Neither class exists in front-end
   output. core/shortcode's save() emits the raw shortcode text with no
   wrapper, and core/html's save() emits the raw HTML with no wrapper —
   the proposal states this itself in its own button rationale, so the
   fallback contradicts it. `.wp-block-shortcode` exists only in the editor
   canvas, where assets/editor.css paints it as a dashed placeholder.

   The correct fallback keys off the class the quote section's own markup
   carries, whatever block wraps it:

     .service-quote + .wp-block-group.cta-band { padding-top: 0; }

   This matches a core/group with className "section service-quote", a
   core/html containing `<section class="section service-quote">`, and the
   PHP renderer alike, so it is safe to add unconditionally. Add it only if
   the quote section cannot be a plain core/group; if it can, style.css:1006
   already covers the case and nothing should be appended.

   VERIFIED NOT NEEDED (confirmed against style.css:1008-1040):
   • no .wp-block-button rules — the button is [focusx_call_button], which
     emits the legacy <a class="btn btn--accent btn--pulse">, so style.css
     :140-158 and the mobile `.cta-band__inner .btn{width:100%}` (:579) apply
     unchanged.
   • nothing for core/paragraph — it saves a bare <p>, matched by
     `.cta-band p` (:264).
   • nothing for .cta-band__copy — grep confirms style.css contains no
     `.cta-band__copy` rule; it is an inert marker so the pattern and the
     builder serialize identically.
   • the mobile stack `.cta-band__inner {flex-direction:column}` (:578) and
     `.cta-band h2` (:263) / `.container` (:82) / `.reveal` (:527-529) all
     match the block output unchanged.
   • nothing for <main class="focusx-blocks"> — grep confirms style.css
     defines no .focusx-blocks rule (it appears only in comments at :1417,
     :1569-1570), so the wrapper contributes no layout of its own.

   EDITOR (assets/editor.css) — optional, canvas fidelity only, and identical
   to the note already recorded at style.css:1026-1039. editor.css already
   declares `.editor-styles-wrapper .cta-band h2, .editor-styles-wrapper
   .cta-band p { color:#fff }`, so the heading is NOT unreadable in the
   canvas. The only delta is that editor.css paints the gradient on
   .cta-band while the front end paints it on .cta-band__inner and has no
   .cta-band__inner rule, so the canvas stacks copy above button. Add the
   same optional flex rule if desired; nothing service-specific. */

/* ============================================================
   BRIDGE CSS — Blog hero (.page-hero) pattern
   Append to style.css after the existing BLOCK EDITOR BRIDGE section
   (which begins at style.css:642 and currently ends with the Contact
   section rules at style.css:1613-1646).

   Front-end markup the pattern produces (is-layout-flow /
   wp-block-group-is-layout-flow are added server-side by
   wp_render_layout_support_flag, never by save()):

   <section class="wp-block-group page-hero is-layout-flow …">
     <div class="wp-block-group container is-layout-flow …">
       <p class="eyebrow">…</p>
       <h1 class="wp-block-heading">…</h1>
       <p>…</p>
       <div class="social-cta">                    <- from [focusx_social_buttons]
         <a class="social-btn facebook" target="_blank" rel="noopener">
           <svg class="ico">…</svg> Follow on Facebook</a> × n
       </div>
     </div>
   </section>

   Existing rules that already match unchanged — NO bridge needed:
     .container (style.css:82), .eyebrow (:195), .eyebrow .ico (:196),
     .page-hero (:366), .page-hero::before (:367), .page-hero .container
     (:368), .page-hero h1 (:369), .page-hero p (:370), .social-cta (:371),
     .social-btn (:372), .social-btn:hover (:373), .social-btn .ico (:374),
     .social-btn.facebook/.instagram/.linkedin (:376-378),
     .social-btn.youtube/.tiktok/.x (:381-383).
   grep confirms no media query anywhere in style.css targets .page-hero,
   .social-cta or .social-btn, so responsive behaviour is unaffected.

   No collision with the shipped bridge: .page-hero appears nowhere in
   style.css between :642 and EOF, and the only existing eyebrow bridge
   rules are scoped to other sections (.section-copy > p.eyebrow :854,
   .section-heading > p.eyebrow :1073, .why .section-heading > p.eyebrow
   :1235, .testimonials p.eyebrow :1457, .contact-copy p.eyebrow :1629).
   ============================================================ */

/* THE ONLY REQUIRED RULE. In the legacy template the eyebrow is a
   <span class="eyebrow"> (page-blog.php:27); as core/paragraph it becomes a
   <p class="eyebrow">, which now also matches
     .page-hero p { max-width:620px; margin:0 auto; color:#35506a;
                    font-size:1.05rem }        (style.css:370, 0,1,1)
   That out-specifies .eyebrow (style.css:195, 0,1,0) on FOUR declarations,
   so without this rule the pill renders at 1.05rem in body-grey with its
   .7rem bottom gap collapsed to 0 by the `margin:0 auto` shorthand. Same
   class of regression already patched for the contact section at
   style.css:1629 (.contact-copy p.eyebrow) — two properties wider here,
   because .page-hero p also sets margin and font-size, which
   .contact-copy p (style.css:332) does not.
   Specificity 0,2,1 — a clean win over 0,1,1, not a stylesheet-order tie. */
.page-hero p.eyebrow {
	margin: 0 0 .7rem;
	color: var(--blue-700);
	font-size: .76rem;
	max-width: none;
}

/* DELIBERATELY NOT INCLUDED — each verified redundant:
   - A flow block-gap reset. theme.json enables settings.spacing.blockGap
     (theme.json:73) with no styles.spacing.blockGap value, so core prints
       :root :where(.is-layout-flow)>*{margin-block-start:24px;margin-block-end:0}
     Every child of .container already out-specifies it:
     .page-hero h1 (0,1,1), .page-hero p (0,1,1), the eyebrow via the rule
     above (0,2,1), and .social-cta{margin-top:1.8rem} (0,1,0 vs 0,0,0).
     Adding `.page-hero .container > * { margin-block-start:0 }` would DELETE
     .social-cta's 1.8rem top gap. Do not add it. This is why the section
     needs no analogue of `.contact__grid > * { margin-block:0 }`
     (style.css:1621) — there the children carried no theme margin at all.
   - The `.page-hero .container.is-layout-constrained > *` guard the proposal
     shipped. REMOVED: it cited "`.process__grid.is-layout-constrained`
     (style.css ~l.1565)" as precedent, and no such rule exists —
     `is-layout-constrained` appears nowhere in style.css. None of the ten
     shipped patterns carries a constrained-layout guard; do not start here.
   - Any margin/padding reset on .wp-block-group. Core's block library adds
     none, and .page-hero's `padding:66px 0 54px` (:366) is unchallenged.
   - Anything for .social-cta / .social-btn. core/shortcode's save() emits raw
     text with no wrapper, and shortcode_unautop strips the <p> wpautop puts
     round a lone shortcode, so after do_shortcode the <div class="social-cta">
     is a direct child of .container — byte-identical to page-blog.php:41-52.
   - Any anchor-colour pin. theme.json styles.elements.link (theme.json:95-97)
     ties with .social-btn{color:#fff} (0,1,0 each) — but that tie is
     PRE-EXISTING on the legacy template too, unchanged by this conversion.
     Out of scope; do not "fix" it here.
   - .reveal. The legacy blog hero carries NO .reveal class (page-blog.php:25-26,
     unlike .cta-band__inner at page-blog.php:84). Do not add one; it would
     make the hero start at opacity:0 (style.css:527). */


/* ---- assets/editor.css additions (editor canvas only, optional) ----
   Follows the convention of the shipped bridge (e.g. style.css:1499-1523,
   1583+): these live in style.css as documentation and must be COPIED INTO
   assets/editor.css to take effect — style.css itself is not loaded in the
   canvas (see assets/editor.css:1-8).

   CORRECTION to the proposal: it claimed editor.css "already covers .eyebrow
   (l.34) including the margin reset added at style.css:1521". Half true.
   assets/editor.css:34-47 has .eyebrow but sets NO margin; the margin reset
   at style.css:1521 is itself only a documented editor.css addition and may
   not have been copied across. The eyebrow margin is therefore restated below
   so this block is self-sufficient.
   editor.css:22 (h1 colour) and :74 (.wp-block-shortcode dashed placeholder)
   are real and do apply. There is no .page-hero rule in editor.css at all,
   so the section currently edits as plain left-aligned text on white. */
.editor-styles-wrapper .page-hero {
	background: linear-gradient(180deg, #e8f3fd, #f4f9fe);
	padding: 48px 0;
	text-align: center;
}
.editor-styles-wrapper .page-hero h1 { margin: .4rem 0 .8rem; }
.editor-styles-wrapper .page-hero p  { color: #35506a; margin: 0 auto; max-width: 620px; }
.editor-styles-wrapper .page-hero p.eyebrow {
	margin: 0 0 .7rem;
	color: #0a63b4;
	font-size: .76rem;
	max-width: none;
}
/* No .social-cta / .social-btn editor rules: the buttons only exist after
   do_shortcode runs, and the editor shows the shortcode placeholder instead. */

/* ===========================================================================
 * Block bridge — Blog featured cards (.post-grid / .post-card / .blog-note)
 * Append to the END of style.css, inside the existing "BLOCK EDITOR BRIDGE"
 * region (opens at style.css:640-648; current last rule is the Contact bridge
 * ending at style.css:1635). Must load after wp-block-library and
 * wp-block-library-theme (add_theme_support('wp-block-styles'),
 * inc/blocks.php:27) and after the theme.json global-styles inline sheet.
 *
 * Source rules being defended: style.css:384 .post-grid, :385 .post-card,
 * :387 .post-card img, :388 .post-card__body, :389 .post-tag, :390 .post-card
 * h3, :391 .post-card p, :392 .post-meta, :393 .post-link, :394 .blog-note;
 * breakpoints :541 (2-up <=1040px) and :577 (1-up <=760px).
 *
 * NO COLLISION with any shipped bridge rule: every existing bridge selector is
 * scoped to .hero__*, .about-media, .cta-band*, .service-card*, .services__grid,
 * .why*, .work__grid, .project-card, .testimonials*, .process*, .contact*.
 * None of them can match a .post-* node.
 * ========================================================================= */

/* 1 — Flow-layout block gap on the two nodes that lose the fight.
 *
 *     Every core/group renders with .is-layout-flow, and core emits at
 *     specificity 0,0,0:
 *       :where(.is-layout-flow) > *            { margin-block-start: 24px;
 *                                                margin-block-end: 0 }
 *       :where(.is-layout-flow) > :first-child { margin-block-start: 0 }
 *
 *     a) .post-card (style.css:385) declares NO margin of its own — unlike
 *        .project-card, which is exactly why patterns/work-projects.php could
 *        drop its equivalent rule as redundant. That reasoning does NOT
 *        transfer here. A margin on a CSS-grid item ADDS to `gap: 26px`
 *        (style.css:384), so cards 2..N would sit 24px lower than card 1 in
 *        every row.
 *     b) MISSING FROM THE PROPOSAL, and the more visible of the two:
 *        .post-card__body (style.css:388) also declares no margin, and it is
 *        the SECOND child of .post-card (the core/image figure is first). It
 *        therefore picks up margin-block-start: 24px and a 24px white band
 *        opens between every card image and its text. This is the direct
 *        analogue of the shipped `.service-card > .service-card__body`
 *        (style.css:1065) — same defect, same fix.
 *
 *     Selector form and property (margin-block-start, not margin-block) match
 *     the shipped precedent at style.css:1063-1066.
 *
 *     NOT needed: `.container > .post-grid`. Unlike .services__grid — which is
 *     preceded by .section-heading and so needed style.css:1063 — .post-grid is
 *     the FIRST child of .container, and core's :first-child rule already
 *     zeroes it. */
.post-grid > .post-card,
.post-card > .post-card__body { margin-block-start: 0; }

/* 2 — The card image is now wrapped in core/image's <figure>.
 *
 *     a) wp-block-library-theme ships `.wp-block-image { margin: 0 0 1em }` and
 *        the UA adds `figure { margin: 1em 40px }`. Both must go — the image is
 *        full-bleed inside `.post-card { overflow: hidden }` (style.css:385).
 *        `line-height: 0` mirrors the shipped `.service-card > figure`
 *        (style.css:1087) and closes the figure's line box.
 *
 *     b) HEIGHT IS ORDER-DEPENDENT WITHOUT THIS. Core ships
 *        `.wp-block-image img { box-sizing:border-box; height:auto;
 *        max-width:100%; vertical-align:bottom }` at (0,1,1) — an EXACT TIE
 *        with `.post-card img { height:190px; width:100%; object-fit:cover }`
 *        (style.css:387). On a tie the later stylesheet wins and the theme does
 *        not control enqueue order, so cards would render at 190px or at full
 *        natural height depending on load. This is the same defect the shipped
 *        services bridge documents at style.css:1078-1085 for .service-card img
 *        and the work bridge documents at style.css:1300-1307 for .project-card
 *        img. (0,2,2) settles it. `display: block` is restated because core's
 *        `vertical-align: bottom` supersedes reliance on `img { display: block }`
 *        (style.css:76). */
.post-card > figure.wp-block-image { margin: 0; line-height: 0; }
.post-card > figure.wp-block-image img {
	display: block;
	width: 100%;
	height: 190px;
	object-fit: cover;
}

/* 3 — Tag / meta / link were bare <span>/<a> (page-blog.php:69, :72, :73) and
 *     are now core/paragraphs, so `.post-card p` (style.css:391, specificity
 *     0,1,1) suddenly matches all three and out-ranks .post-tag, .post-meta and
 *     .post-link (style.css:389, :392, :393 — all 0,1,0). It imposes four wrong
 *     values on each: `margin: 0 0 1rem`, `color: var(--muted)`,
 *     `font-size: .92rem` and — worst — `flex: 1`, whose 0% flex-basis inside
 *     the column flex `.post-card__body` (style.css:388) collapses the element
 *     and then grows it to fill the card. Only the excerpt paragraph is meant
 *     to be the flexible one, which is why it carries no className.
 *
 *     Each selector below is (0,2,1) and restores ONLY what `.post-card p` took.
 *     Everything else still reaches the element through its own class
 *     unopposed and is deliberately not repeated: background, padding,
 *     border-radius, text-transform, letter-spacing, align-self on .post-tag;
 *     font-weight:700 on .post-meta; font-weight:800, display:inline-flex and
 *     gap on .post-link — `.post-card p` sets none of those.
 *
 *     `color` is restored on .post-tag and .post-link only. .post-meta's own
 *     colour IS var(--muted) (style.css:392), identical to what .post-card p
 *     imposes, so restating it would be dead weight.
 *
 *     The explicit margins also absorb the UA <p> margin and the flow-layout
 *     margin-block-start, so no separate reset is needed for these three.
 *     `flex: 0 1 auto` is the CSS initial value — i.e. exactly how the original
 *     <span>/<a> flex items behaved. */
.post-card__body > p.post-tag {
	color: var(--blue-700);
	font-size: .72rem;
	margin: 0 0 .8rem;
	flex: 0 1 auto;
}
.post-card__body > p.post-meta {
	font-size: .8rem;
	margin: 0;
	flex: 0 1 auto;
}
.post-card__body > p.post-link {
	color: var(--blue-700);
	font-size: .88rem;
	margin: .8rem 0 0;
	flex: 0 1 auto;
}

/* 4 — REQUIRED for exact parity (the proposal marked this optional; it is not,
 *     given inc/blocks-build.php:5-7's "converting leaves the page looking
 *     exactly as it did" contract).
 *
 *     `.blog-note { margin-top: 2.5rem }` (style.css:394, 0,1,0) beats the
 *     :where() margin-block-start unaided, so the TOP margin is safe. But it
 *     sets no bottom margin, and today the <p> keeps the UA's 1em there
 *     (page-blog.php:79 — it sits inside `.section { padding: 64px 0 }`
 *     (style.css:83), so the margin does not collapse away). The flow rule's
 *     `margin-block-end: 0` deletes it and the note lands 16px lower. */
.container > .blog-note { margin-bottom: 1em; }

/* ------------------------------------------------------------
 * DELIBERATELY NOT INCLUDED — verified redundant or harmful:
 *
 *   .post-card.wp-block-group, .post-grid.wp-block-group { margin:0; padding:0 }
 *       Pointless. Core's group CSS is only `box-sizing: border-box`, which
 *       style.css:67 (`* { box-sizing: border-box }`) already applied globally.
 *       Same conclusion the shipped bridge reached at style.css:1488-1489.
 *
 *   .post-card__body > * { margin-block: 0 }
 *       Redundant AND destructive. `.post-card h3 { margin: 0 0 .5rem }`
 *       (style.css:390) and `.post-card p { margin: 0 0 1rem }` (style.css:391)
 *       are (0,1,1) shorthands that already zero the top margin and beat the
 *       :where() rule; a blanket rule would delete their designed bottom
 *       margins and then need them re-added. Explicitly rejected for the same
 *       reason at style.css:1055 and style.css:1052-1056.
 *
 *   Any `gap` override on .post-grid
 *       Redundant. Flow layout emits no gap, and .post-grid { gap: 26px }
 *       (style.css:384) is unopposed.
 *
 *   Re-declaring the .post-grid breakpoints
 *       Redundant. style.css:541 and style.css:577 target `.post-grid`
 *       directly and match the core/group unchanged.
 *
 *   .post-card__body > p.post-link a { color: inherit }
 *       Redundant. style.css:77 is already `a { color: inherit }`.
 *
 *   A .reveal override
 *       Not needed on the front end (style.css:527-529 apply unchanged) and not
 *       needed in the editor either — assets/editor.css deliberately does not
 *       load style.css, so `.reveal { opacity: 0 }` never applies in the canvas.
 *       Same finding as style.css:1253-1255 and :1403-1405.
 * ------------------------------------------------------------ */

/* OPTIONAL, editor-only — add to assets/editor.css, matching that file's
   conventions. Its card rule (editor.css:51-62) lists .service-card,
   .mini-card, .project-card, .testimonial, .step, .trust-box but NOT
   .post-card, and its image reset (editor.css:84-85) covers only .service-card
   and .project-card, so blog cards read as unstyled stacks in the canvas.
   Note .post-card__body needs its own padding line because editor.css:61 puts
   `padding: 22px` on the card itself, whereas the front end puts it on the body
   (style.css:388):

.editor-styles-wrapper .post-card {
	background: #fff; border: 1px solid rgba(10,62,105,.14);
	border-radius: 12px; box-shadow: 0 12px 28px rgba(8,40,70,.10);
	overflow: hidden;
}
.editor-styles-wrapper .post-card .wp-block-image { margin: 0; }
.editor-styles-wrapper .post-card__body { padding: 22px; }
.editor-styles-wrapper .post-tag {
	display: inline-block; background: #d7ebfb; color: #0a63b4;
	font-weight: 800; font-size: .72rem; text-transform: uppercase;
	letter-spacing: .04em; padding: .3rem .7rem; border-radius: 999px;
}
.editor-styles-wrapper .post-grid > * { margin-block-start: 0; }

   editor.css:86 `.wp-block-buttons { margin-top: 1rem }` is irrelevant here —
   this section contains no core/buttons block. */

/* =========================================================================
   Bridge CSS — Blog note + quote CTA band (focusx/blog-note-cta).
   Append to style.css inside the BLOCK EDITOR BRIDGE section (starts :642).

   PREREQUISITE — three rules already shipped for focusx/cta-band are REUSED
   VERBATIM here. Do not duplicate or restate them:
     style.css:986   .cta-band__inner > * { margin-block-start: 0 }
     style.css:997   .cta-band h2 { color: #fff }
     style.css:1005  .wp-block-group.cta-band { padding-top: 72px }
     style.css:1006  .wp-block-group.section + .wp-block-group.cta-band { padding-top: 0 }
   :986 covers the new .cta-band__copy group AND the .wp-block-buttons wrapper.
   :997 still matches — .cta-band h2 is a descendant selector, so wrapping the
   heading in .cta-band__copy does not break it.
   :1005/:1006 are why the note band deliberately carries `section`: it makes
   :1006 fire and reproduces page-blog.php:83, where .cta-band's
   `padding: 0 0 72px` (style.css:261) relies on the preceding .section for its
   top gap.
   ========================================================================= */

/* --- 1. The note band ---------------------------------------------------
   page-blog.php:79 puts .blog-note as the LAST child of the SAME
   <section class="section"><div class="container"> that holds .post-grid
   (page-blog.php:57-81). Split across pattern boundaries (grid = order 2,
   note = order 3) it becomes a second .section, so the grid section's 64px
   bottom padding now stacks on the note's own margin-top: 2.5rem
   (style.css:394): 64 + 40 = 104px where the design has 40px. */
.wp-block-group.section + .wp-block-group.blog-note-band { padding-top: 0; }

/* Reconcile the doubled gap back to the designed 2.5rem. Scoped to the
   "follows a padded .section" case only, so a reordered or standalone note
   band falls back to the plain 2.5rem from style.css:394. `.section` is 64px
   at desktop (style.css:83) and 60px at <=760px (style.css:550) — hence two
   calcs. Specificity 0,6,0, so it beats `.blog-note` (0,1,0). */
.wp-block-group.section + .wp-block-group.blog-note-band > .container > .blog-note {
  margin-top: calc(2.5rem - 64px);
}
@media (max-width: 760px) {
  .wp-block-group.section + .wp-block-group.blog-note-band > .container > .blog-note {
    margin-top: calc(2.5rem - 60px);
  }
}

/* Restore the note's bottom margin. `.blog-note` (style.css:394) sets only
   margin-top, so the legacy <p> keeps the UA 1em bottom margin — which
   escapes .container by margin-collapsing and stops at .section's padding.
   Inside the .container GROUP, core's flow layout emits
   `:where(.is-layout-flow) > * { margin-block-end: 0 }` at specificity 0,
   which still beats the UA sheet and deletes it. Declaring 1em explicitly is
   a no-op for the PHP-rendered blog page and exact for blocks.
   (margin-TOP needs no help: `.blog-note` is 0,1,0 and beats core's
   `:where(.is-layout-flow) > :first-child { margin-block-start: 0 }`.) */
.blog-note { margin-bottom: 1em; }

/* --- 2. The quote button ------------------------------------------------
   Legacy anchor, page-blog.php:89:
     <a class="btn btn--accent btn--pulse" href="{quote url}">Get a Free Quote →</a>
   Unlike the homepage CTA band (front-page.php) and the service CTA band
   (inc/render-service.php:322), which are CALL buttons carrying an inline
   phone SVG and therefore need [focusx_call_button], this is a plain quote
   link with no icon — so core/button expresses it, exactly as the shipped
   patterns/hero-home.php does for the identical "Get a Free Quote →" button.

   core/button puts className on the WRAPPER <div class="wp-block-button">,
   never on the <a>, so .btn/.btn--accent/.btn--pulse cannot be inherited
   through className. The pattern therefore uses the collision-free marker
   class `is-quote-cta` (same technique as hero-home's `is-hero-cta`,
   style.css:695) and these rules re-point style.css:140-157 at
   .wp-block-button__link.

   Specificity (0,3,0) beats core block-library's `.wp-block-button__link`
   (0,1,0) AND theme.json's generated `:root :where(.wp-block-button__link)`
   (0,1,0), so it holds regardless of stylesheet print order. That order
   independence is the whole point: core ships its own border-radius and
   padding on .wp-block-button__link which TIE with theme.json's
   elements.button (theme.json:120-128) — this is why hero-home's bridge
   restates radius/padding/weight/transform/background rather than trusting
   theme.json, and why this block does the same. (theme.json's accent
   #ffc400 / accent-ink #0b2a44 do equal --yellow / --yellow-ink,
   style.css:28 and :30 — the values agree; only the cascade is unreliable.)

   theme.json styles.elements.link (theme.json:95-97) does NOT interfere:
   WP emits it as `a:where(:not(.wp-element-button))`. */
.cta-band__inner .wp-block-button > .wp-block-button__link {
  display: inline-flex; align-items: center; justify-content: center; gap: .55rem;
  border-radius: 10px; border: 0; padding: .95rem 1.5rem;
  font-weight: 800; text-transform: uppercase; letter-spacing: .015em;
  cursor: pointer;
  transition: transform .2s ease, box-shadow .2s ease, background .2s ease, color .2s ease;
}
.cta-band__inner .wp-block-button > .wp-block-button__link:hover { transform: translateY(-2px); }

/* .btn--accent (style.css:142-143) + .btn--pulse (style.css:157).
   Re-uses @keyframes ctaPulse, already defined at style.css:152. */
.cta-band__inner .is-quote-cta > .wp-block-button__link {
  background: var(--yellow); color: var(--yellow-ink);
  box-shadow: 0 12px 26px rgba(242, 178, 0, .40);
  animation: ctaPulse 2.1s cubic-bezier(.66,0,0,1) infinite;
}
.cta-band__inner .is-quote-cta > .wp-block-button__link:hover { background: var(--yellow-600); }

/* Parity with style.css:158. */
@media (prefers-reduced-motion: reduce) {
  .cta-band__inner .is-quote-cta > .wp-block-button__link { animation: none; }
}

/* Responsive parity with style.css:579 `.cta-band__inner .btn { width: 100% }`,
   which no longer matches. Three levels need it because .cta-band__inner is a
   flex COLUMN with align-items: flex-start at this breakpoint (style.css:578)
   and the .wp-block-buttons wrapper is now the flex item. */
@media (max-width: 760px) {
  .cta-band__inner .wp-block-buttons,
  .cta-band__inner .wp-block-button,
  .cta-band__inner .wp-block-button > .wp-block-button__link { width: 100%; }
}

/* NOT NEEDED — verified, do not add:
   • `.cta-band__inner > * { margin-block-start: 0 }` — style.css:986.
   • `.cta-band h2 { color: #fff }` — style.css:997.
   • `.wp-block-group.cta-band { padding-top: … }` — style.css:1005-1006; the
     note band's `section` class makes :1006 fire.
   • Any `.cta-band__copy` rule — style.css has no such selector. It is a hook
     class only (focusx_build_cta already emits it, inc/blocks-build.php:350),
     and `.cta-band h2` / `.cta-band p` (style.css:263-264) are descendant
     selectors that still match through it.
   • Anything for core/paragraph — it saves a bare <p>, so `.cta-band p`
     (style.css:264) already matches.
   • `.wp-block-buttons` gap/justification — core's flex-layout defaults are
     emitted at specificity 0 and are invisible with a single button anyway.
   • `.wp-block-button { margin: 0 }` — .wp-block-buttons is a FLEX layout, so
     `:where(.is-layout-flow)` margin rules never reach its children.

   ---- assets/editor.css (OPTIONAL, canvas fidelity only) ----
   Same single known delta already recorded for the shipped focusx/cta-band
   bridge (style.css:1028-1038): editor.css paints the gradient on `.cta-band`
   and has no `.cta-band__inner` rule, so the canvas stacks copy above button
   instead of flexing them. Harmless. The note band needs no editor rule. No
   `.editor-styles-wrapper .reveal` override is needed — editor.css does not
   load style.css, so `.reveal { opacity: 0 }` never applies in the canvas
   (style.css:1403-1406, :1608-1610). */
