/* ---------------------------------------------------------------------------
 * DELIBERATE DIVERGENCE FROM THE SOURCE SITE.
 *
 * Everything else in this rebuild is a faithful port. This file is hand-written
 * and must never be generated — note the name: scripts/build-css.ps1 clears
 * site-*.css in this directory before rebuilding, so anything called
 * site-something.css here gets silently deleted on the next CSS build.
 *
 * The WhatsApp (Join.chat) button never appears on cleaningteam.ie. Confirmed
 * broken on the live WordPress site, in the Simply Static export, and — before
 * this file — in the rebuild. It is not a migration regression.
 *
 * Why it breaks. The plugin reveals the button with an animation rather than
 * with the cascade:
 *
 *     .joinchat { animation: joinchat_show .5s ... both;
 *                 transform: scale3d(0,0,0) }
 *     @keyframes joinchat_show { 0%  { transform: scale3d(0,0,0) }
 *                                to  { transform: scaleX(1) } }
 *
 * `fill-mode: both` means the animation's value wins over every normal
 * declaration, including the `.joinchat--show { transform: scaleX(1) }` that
 * the plugin's JS is meant to trigger. So if that animation never progresses,
 * the element stays pinned at its 0% frame — scaled to nothing — even though
 * the JS has correctly removed `hidden` and added `.joinchat--show`.
 * That is exactly what is observed: a computed transform of
 * matrix3d(0,0,0, 0,0,0, 0,0,0, 0,0,0, 0,0,0,1) and a 0x0 button.
 *
 * The fix. Drop the animation and set the end state directly, but only once
 * the plugin has decided the button should be shown. Keying off
 * `.joinchat--show` means the plugin still controls *when* it appears, so the
 * configured button_delay is respected — this does not make the button show up
 * any earlier than intended. !important is load-bearing: it is what outranks
 * the animation, and it is why link order relative to the bundles does not
 * matter.
 *
 * Cost: the half-second scale-in is lost. The button simply appears. That also
 * happens to be the better behaviour for anyone browsing with reduced motion.
 *
 * If the plugin is ever fixed or updated upstream, delete this file and remove
 * the <link> in app/layout.tsx.
 * ------------------------------------------------------------------------- */

.joinchat.joinchat--show {
  animation: none !important;
  transform: scaleX(1) !important;
}

/* The tooltip rides the same pattern — animated in with fill-mode both, so it
   inherits the same failure. */
.joinchat--tooltip .joinchat__tooltip {
  animation: none !important;
  opacity: 1 !important;
  transform: scaleX(1) !important;
}

/* ---------------------------------------------------------------------------
 * The homepage's "Here's why we're the #1..." comparison table (5 columns:
 * Cleaning Team, Typical franchise, Small local company, Independent maids)
 * ships with its own stylesheet, stored in Kadence's custom-CSS field. That
 * stylesheet is corrupt at the source — every \r\n lost its backslashes,
 * leaving 178 literal "rn" sequences and zero real newlines, so all 75
 * declarations in it were invalid and dropped by every browser, including on
 * live WordPress. See scripts/extract-content.ps1 for where it is un-mangled
 * and shipped as this page's normal inline CSS — that recovers the author's
 * own overflow-x:auto, min-width and sticky-first-column rules, which is most
 * of the fix on its own.
 *
 * A "swipe to compare" hint used to be added here for the horizontal-scroll
 * version, but scrolling a 5-column table sideways on a phone turned out not
 * to work even once you knew to do it: only the row label and one competitor
 * are visible at a time, so comparing "does the franchise have this, does the
 * maid service" meant swiping back and forth per row. Below, the table
 * becomes a stack of cards instead — one per feature, all four competitors
 * shown together as label/value pairs, no scrolling. Pure CSS, no markup
 * change: the per-competitor labels come from `nth-child` since the source
 * table has no `data-label` attributes to key off. Column order is fixed by
 * the table's own thead (feature | Cleaning Team | Typical franchise | Small
 * local company | Independent maids), so nth-child is safe here.
 *
 * `!important` is used on the handful of properties the page's own inline
 * <style> also sets (sticky positioning, fixed widths, min-width) — that
 * stylesheet renders inside <body>, after this file's <head> <link>, so at
 * equal specificity it would win the tie on source order otherwise.
 * ------------------------------------------------------------------------- */
@media screen and (max-width: 768px) {
  .table-container {
    overflow-x: visible;
    background: none;
    border: none;
    padding: 0;
  }

  .comparison-table {
    display: block;
    width: 100%;
    min-width: 0 !important;
    border: none;
  }

  .comparison-table thead {
    display: none;
  }

  .comparison-table tbody,
  .comparison-table tr {
    display: block;
  }

  .comparison-table tr {
    margin-bottom: 12px;
    border: 1px solid #e0e0e0;
    border-radius: 8px;
    overflow: hidden;
  }

  .comparison-table tr:last-child {
    margin-bottom: 0;
  }

  .comparison-table td {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    text-align: left;
    border: none;
    border-top: 1px solid #eee;
  }

  .comparison-table td:first-child {
    display: block;
    position: static !important;
    left: auto !important;
    z-index: auto !important;
    max-width: none !important;
    width: auto !important;
    background: #f8f8f8;
    border-top: none;
    font-size: 15px;
  }

  .comparison-table td:nth-child(2)::before { content: "Cleaning Team"; }
  .comparison-table td:nth-child(3)::before { content: "Typical franchise"; }
  .comparison-table td:nth-child(4)::before { content: "Small local company"; }
  .comparison-table td:nth-child(5)::before { content: "Independent maids"; }

  .comparison-table td:not(:first-child)::before {
    font-weight: 500;
    color: #6b7280;
    font-size: 13px;
  }

  /* Cells with no checkmark are genuinely empty in the source markup
     (`<td></td>`) — :empty is safe to key off here. Spelling out the miss
     makes each row scannable without hunting for blank space. */
  .comparison-table td:not(:first-child):empty::after {
    content: "✗";
    color: #d1d5db;
    font-size: 16px;
  }

  .checkmark {
    font-size: 18px;
  }
}

/* ---------------------------------------------------------------------------
 * Fluid heading sizes on small screens.
 *
 * The theme sets one fixed size per heading level with no responsive rule at
 * all — h1 44px, h2 32px, h3 24px, all at line-height 1.5, on every viewport.
 * On a 375px phone that is far too big for the service pages, whose h1s run
 * 39–64 characters ("Commercial Cleaning Dublin | Daily, Weekly & One-Off
 * Service"), so they wrap to four or five very airy lines before any content
 * is visible. The source site has the same problem — this is not a migration
 * regression, it is a fix.
 *
 * h2 and h3 have to come along: dropping h1 alone to ~30px would put it *below*
 * the theme's 32px h2 and invert the hierarchy on exactly the pages being
 * fixed.
 *
 * Each size interpolates from its mobile value at 375px to the theme's own
 * value at 768px, and is capped there — so tablets and desktops render exactly
 * as before, byte for byte. Only the phone range changes.
 *
 * Plain element selectors are deliberate. This file is linked last, so these
 * win on source order against the theme's equally-specific `h1 { }` — while any
 * Kadence per-block override (.wp-block-kadence-advancedheading.kt-adv-heading-x,
 * specificity 0,2,0) still beats them, so a heading deliberately sized in the
 * editor keeps its size.
 * ------------------------------------------------------------------------- */
h1 { font-size: clamp(30px, 16.6px + 3.56vw, 44px); }
h2 { font-size: clamp(25px, 18.3px + 1.78vw, 32px); }

/* h3 is deliberately left alone. An equivalent clamp on it was tried and
   dropped: Kadence's info-box titles (h3.kt-blocks-info-box-title, which is
   every h3 on the service pages) rendered 21px against the source's 24px at
   desktop widths, so it changed desktop rather than only the phone range. It is
   also unnecessary — the theme's 24px already sits below the new 25px h2, so
   the hierarchy holds on a phone without touching h3 at all. */

/* 1.5 is a body-text line-height; on a heading that now wraps to two or three
   lines it reads as a gap rather than a heading. Phone range only. */
@media screen and (max-width: 767px) {
  h1, h2 { line-height: 1.25; }
}

/* ---------------------------------------------------------------------------
 * Info-box padding on phones.
 *
 * Several info boxes are configured in the Kadence editor with
 * `padding: var(--global-kb-spacing-xxl, 5rem)` — 80px on every side. That is a
 * desktop value with no mobile variant, so on a 375px screen it eats 160px of a
 * 279px card and leaves 119px for the text. "Moving out can be stressful, but
 * our end of tenancy cleaning services…" then wraps at two or three words a
 * line down a tall narrow ribbon. The source site does exactly the same — this
 * is a fix, not a regression.
 *
 * Only the horizontal padding is touched; the vertical padding is fine and still
 * separates the cards. The value is exactly 1rem — the same
 * `--global-kb-spacing-xs` default the un-customised info boxes already use, as
 * seen on /commercial-cleaning/ — so boxes that were never over-padded are left
 * bit-identical, and only the 5rem outliers actually move.
 *
 * The selector needs all three parts. The per-block rule is
 * `.kt-info-box<id> .kt-blocks-info-box-link-wrap` — specificity 0,2,0 — and
 * merely matching that is not enough here: the per-block CSS ships as an inline
 * <style> inside <body> (see components/PageShell.tsx), which comes *after*
 * this file's <link> in the head, so an equal-specificity rule loses the tie on
 * source order. `.wp-block-kadence-infobox` and `.kt-info-box<id>` sit on the
 * same element, so combining them gives 0,3,0 and wins outright — without
 * !important, which would also steamroll any block that legitimately wants
 * different padding later.
 * ------------------------------------------------------------------------- */
@media screen and (max-width: 767px) {
  .wp-block-kadence-infobox[class*="kt-info-box"] .kt-blocks-info-box-link-wrap {
    padding-left: var(--global-kb-spacing-xs, 1rem);
    padding-right: var(--global-kb-spacing-xs, 1rem);
  }
}

/* ---------------------------------------------------------------------------
 * Kadence's large spacing tokens, given mobile values.
 *
 * Blocks are spaced with `padding: var(--global-kb-spacing-<step>, <default>)`.
 * The site never defines any of these variables, so every one of them falls
 * back to its desktop literal on every screen size:
 *
 *     xxs .5rem   xs 1rem    sm 1.5rem   md 2rem    lg 3rem   xl 4rem
 *     xxl 5rem (80px)   3xl 6.5rem (104px)   4xl 8rem (128px)   5xl 10rem (160px)
 *
 * As horizontal padding on a 375px phone the top of that scale is unusable.
 * /after-builders-cleaning/ has a column padded with 5xl, so 320px of a 351px
 * container went to padding and the "Our Post-Construction Cleaning Process"
 * heading was left 31px wide - one character per line, straight down the page.
 * The source site does the same; this is a fix, not a regression.
 *
 * Because the variables are only ever *referenced*, defining them here is
 * surgical: it changes exactly the blocks that opted into these steps and
 * nothing else, and it preserves the author's intent - a block asking for "the
 * biggest gap" still gets the biggest gap, just one that fits a phone. The
 * order of the scale is kept.
 *
 * Only the four largest steps are touched. xs through xl are already sensible
 * at this width and are left alone, so the vast majority of blocks - 285 pages
 * use xxs alone - are completely unaffected.
 * ------------------------------------------------------------------------- */
@media screen and (max-width: 767px) {
  :root {
    --global-kb-spacing-xxl: 1.5rem;  /* 80px  -> 24px */
    --global-kb-spacing-3xl: 1.75rem; /* 104px -> 28px */
    --global-kb-spacing-4xl: 2rem;    /* 128px -> 32px */
    --global-kb-spacing-5xl: 2rem;    /* 160px -> 32px */
  }
}

/* ---------------------------------------------------------------------------
 * Stop nested Kadence containers padding the same axis twice.
 *
 * Rows are nested: a column's .kt-inside-inner-col wraps a .kb-row-layout-wrap
 * whose own .kt-row-column-wrap pads again, and on these pages both levels are
 * set to the same large spacing step. The insets compound, so on
 * /after-builders-cleaning/ the "Site Inspection" card ended up 175px wide in a
 * 375px viewport — 88px of horizontal padding a side, less than half the screen
 * for the actual content. Shrinking the tokens alone cannot fix this: whatever
 * value is chosen, it is applied twice.
 *
 * So the outer level gives up its horizontal padding when it is only there to
 * wrap a row that will pad anyway. `:has(> .kb-row-layout-wrap)` matches exactly
 * that case and nothing else, and the vertical padding is left alone because it
 * does not compound in a way that hurts.
 * ------------------------------------------------------------------------- */
@media screen and (max-width: 767px) {
  .wp-block-kadence-column[class*="kadence-column"] > .kt-inside-inner-col:has(> .kb-row-layout-wrap) {
    padding-left: 0;
    padding-right: 0;
  }

  /* Same idea one level up, and it also straightens out a slip in the source:
     the single-column row that wraps this section is padded
     `104px 0px 24px 24px` — 24px on the left, nothing on the right — so the
     content sat visibly off-centre. It exists only to hold the row inside it,
     which supplies its own horizontal padding, so zeroing both sides both
     removes the double inset and makes the gutters even. */
  .kt-row-column-wrap:has(.kb-row-layout-wrap) {
    padding-left: 0;
    padding-right: 0;
  }
}

/* ---------------------------------------------------------------------------
 * Headings/paragraphs touching the screen edge on phones — three distinct
 * causes, found by a full-site crawl (scripts/mobile-design-audit.mjs) after
 * the homepage hero turned out not to be a one-off.
 *
 * Case A: the theme's `content-style-unboxed` body class — used by every
 * location page (house-cleaning/*, after-builders-cleaning/*, end-of-tenancy-
 * cleaning/*, apartment-cleaning/*, …) — sets `.entry-content-wrap{padding:0}`
 * outright (see kadence's content.min.css). That is correct *when* every
 * top-level block supplies its own inset via `.kt-row-has-bg`/`.alignfull`,
 * which is how the rest of each page's content is built. But the
 * "Satisfaction 100% Guaranteed / Quick Same-Day Booking / …" trust-badge
 * block repeated near the bottom of essentially every one of these pages is a
 * plain `alignnone` row with no background of its own, so it inherits nothing
 * and sits flush against both edges. This is not something the earlier
 * per-block CSS ever set correctly and then lost — it is how the theme has
 * always rendered a bare row on an unboxed page; the rule restores the same
 * inset boxed pages get automatically, scoped to exactly the rows that don't
 * already supply their own.
 *
 * Case B: the rule just above THIS one (`.kt-row-column-wrap:has(.kb-row-
 * layout-wrap)`) zeros a row's padding on the assumption that a nested row
 * will supply its own and the two would otherwise add up. That is sometimes
 * true and sometimes not depending on what padding (if any) the *nested* row
 * happens to carry — not something a selector can see, since it depends on
 * that row's own computed value, not its class list. What *is* selectable:
 * every case actually found where zeroing was wrong shared one thing the one
 * confirmed-correct case (the "Site Inspection" card grid on
 * /after-builders-cleaning/) does not — the outer row itself is
 * `.kt-row-has-bg`. A `kt-row-has-bg` row is a deliberate, self-contained
 * background section; it should never lose its own edge inset just because
 * something is nested inside it. Restoring it there tested fine even in the
 * one case with a nested row that *does* carry real padding of its own (the
 * card grid: still 263px per card instead of 311px, comfortably readable) —
 * so the exception is unconditional, not content-style-unboxed-scoped, since
 * the same `:has()` rule can zero a `kt-row-has-bg` row on any page.
 *
 * Case C: on /pay-online/ and /reviews/, the heading isn't wrapped in a row
 * at all — a bare block directly in `.entry-content`. Neither A nor B can
 * reach it (there's no row to select), so padding goes on the heading block
 * itself. Text-align on both is `center`, so an inset on the block reads the
 * same as one on the text: `kt-adv-heading129_93ee31-07` (pay-online) and
 * `kt-adv-heading116_7aadc2-16` (reviews) are each just the one heading
 * block, so this cannot leak onto anything else on the page.
 *
 * `!important` on Case B: /cleaning-services-dublin/'s row is one of the
 * pages this fixes, and it carries its own author-set
 * `padding-left:0;padding-right:0` for this breakpoint (visible in its
 * per-page stylesheet) at equal specificity to the rule it's meant to beat,
 * later in source order — same tie this whole file exists to break.
 * ------------------------------------------------------------------------- */
@media screen and (max-width: 767px) {
  /* Case A — any top-level row on an unboxed page that doesn't supply its
     own background/full-bleed padding gets the standard inset. */
  body.content-style-unboxed .entry-content > .kb-row-layout-wrap:not(.kt-row-has-bg):not(.alignfull) > .kt-row-column-wrap {
    padding-left: var(--global-content-edge-padding, 15px);
    padding-right: var(--global-content-edge-padding, 15px);
  }

  /* Case A follow-on — do not let the row *inside* a Case A row pad again.

     Case A gives a top-level unboxed row the standard inset. Where that row is
     only a wrapper, the row nested inside it pads too and the two compound: on
     /house-cleaning/, /cleaning-services-dublin/, /deep-cleaning/,
     /commercial-cleaning/ and /end-of-tenancy-cleaning/ body text sat at x=48
     in a 294px measure, against live WordPress x=24 / 342px — a quarter of a
     390px screen spent on padding, and the rebuild diverging from the site it
     ports.

     The discriminator is depth, not class: the trust-badge block that Case A
     exists for nests one level deeper, so its inner row is already zeroed by
     the :has(.kb-row-layout-wrap) rule further up and never doubled. The row
     that doubles is always the innermost one — the only one that rule cannot
     reach, because it contains no further row. Selecting exactly that, and only
     inside a row Case A has already padded, leaves the trust badge untouched:
     verified by scripts/mobile-design-audit.mjs, edge-flush-text unchanged at 3.

     An earlier attempt excluded wrapper rows from Case A instead. That read
     correctly but stripped the inset from the trust-badge block on ~250
     location pages — edge-flush-text went 3 to 257. The nested row's own
     padding is what matters, and only its depth exposes it.

     Narrowed again to .kt-has-2-columns after the first depth-only version
     also caught the five region hubs (/northside-dublin/, /southside-dublin/,
     /meath/, /kildare-area/, /wicklow-area/), which live renders at 48/294 and
     which the rule widened to 24/342. Their inner row is a single column; every
     doubling case is a multi-column row. That is a structural proxy rather than
     the underlying cause — the cause is the inner row's own computed padding,
     which no selector can read — so it is held in place by measurement:
     scripts/mobile-design-audit.mjs stays at its 3 baseline edge-flush hits, and
     the pages below are compared against live WordPress directly.

     The .kt-row-has-bg exclusion was dropped after the same doubling turned up
     inside the Contact Details and contact-form sections, whose top-level row
     is a background section. There Case B forces the outer inset back on with
     !important, while live leaves it at 0 and lets the nested two-column row
     supply it — so the section rendered 48px narrower than live on all six
     service pages (contact heading 72/246 against live 48/294, the form 80/230
     against 56/278). Case B is about the outer row keeping its inset; this rule
     is about the inner row not adding a second one, so the two do not conflict
     and Case B is left exactly as it was.

     The child combinator after .entry-content is a descendant combinator for
     the same reason: on /steam-cleaning/ the contact row is nested one level
     further in, inside a .kt-inside-inner-col rather than sitting directly in
     .entry-content, so a direct-child selector missed it while the identical
     section on the other five pages matched. */
  body.content-style-unboxed .entry-content .kb-row-layout-wrap:not(.alignfull) > .kt-row-column-wrap .kt-row-column-wrap.kt-has-2-columns:not(:has(.kb-row-layout-wrap)) {
    padding-left: 0;
    padding-right: 0;
  }

  /* Case B — a `kt-row-has-bg` row never loses its own edge inset just
     because something is nested inside it. */
  .kt-row-has-bg > .kt-row-column-wrap:has(.kb-row-layout-wrap) {
    padding-left: var(--global-content-edge-padding, 15px) !important;
    padding-right: var(--global-content-edge-padding, 15px) !important;
  }

  /* Case C — one-off: bare headings with no row to hang Case A/B off. */
  .kt-adv-heading129_93ee31-07,
  .kt-adv-heading116_7aadc2-16 {
    padding-left: var(--global-content-edge-padding, 15px);
    padding-right: var(--global-content-edge-padding, 15px);
  }
}

/* ---------------------------------------------------------------------------
 * Vertical tabs become stacked tabs on phones.
 *
 * The tabs block on /sofa-steam-cleaning/ is set to Kadence's "vtabs" layout —
 * a titles column floated alongside the panel — and its mobile layout option is
 * left on "inherit", so a phone gets the desktop arrangement. The titles column
 * is a fixed proportion of the width, which at 375px is 98px, so titles like
 * "Benefits of Upholstery Cleaning" broke to two or three characters a line
 * down a narrow ribbon, and the panel was left 229px. The source site does the
 * same; this is a fix, not a regression.
 *
 * Both halves are floated, so both floats have to be dropped, not just the
 * widths — otherwise the panel keeps sitting alongside. The titles then run
 * above the panel, each on its own full-width line.
 *
 * Full width rather than letting them share a row. `flex: 1 1 auto` was tried
 * first and adapts badly to both cases at once: the long titles here each take
 * a line anyway, while the short "Testimonial 1/2/3" labels on the location
 * pages land two-up with a lonely full-width third. Sizing by content cannot
 * serve both, and any fixed basis narrow enough to fit three across re-creates
 * the squeeze this is fixing. One tab per line is predictable, never squeezes,
 * and reads the same on all 66 pages that use this block.
 * ------------------------------------------------------------------------- */
@media screen and (max-width: 767px) {
  .kt-tabs-wrap.kt-tabs-layout-vtabs > .kt-tabs-title-list,
  .kt-tabs-wrap.kt-tabs-layout-vtabs > .kt-tabs-content-wrap {
    float: none;
    width: 100%;
  }

  .kt-tabs-wrap.kt-tabs-layout-vtabs > .kt-tabs-title-list {
    flex-direction: row;
    flex-wrap: wrap;
    gap: 4px;
    margin-bottom: 12px;
  }

  .kt-tabs-wrap.kt-tabs-layout-vtabs > .kt-tabs-title-list > .kt-title-item {
    flex: 1 1 100%;
  }

  /* Centre the label now that each tab spans the width, rather than leaving it
     hard against the left edge of a full-width block. */
  .kt-tabs-wrap.kt-tabs-layout-vtabs > .kt-tabs-title-list > .kt-title-item > a {
    justify-content: center;
    text-align: center;
  }
}

/* ---------------------------------------------------------------------------
 * Neighbourhood-directory table forced into horizontal scroll on phones.
 *
 * /northside-dublin/, /southside-dublin/, /wicklow-area/, /meath/ and
 * /kildare-area/ each carry a plain 4-column table of place names ("Artane,
 * Ashtown, Balbriggan, Baldoyle, …") built with Kadence's table block. Its
 * container already sets `overflow-x: auto` — deliberately, per the source —
 * but there is no scroll hint of any kind, so on the three widest of the five
 * (northside, southside, wicklow-area: ~500px of table in a 375px viewport)
 * the third and fourth columns are just gone unless a visitor happens to
 * swipe blind. There is nothing tabular about the content — no row/column
 * relationship a reader needs preserved — so unlike the homepage comparison
 * table (which keeps real columns as label/value pairs), this one just
 * reflows into a plain two-column list of links. `.kb-table` is generic
 * across all five pages' tables; kildare-area and meath already fit at this
 * width, so this only changes cell arrangement there, not visible layout.
 * ------------------------------------------------------------------------- */
@media screen and (max-width: 599px) {
  .kb-table-container {
    overflow-x: visible;
  }

  .kb-table-container .kb-table {
    display: block;
    width: 100%;
  }

  .kb-table-container .kb-table tbody {
    display: block;
  }

  .kb-table-container .kb-table tr {
    display: flex;
    flex-wrap: wrap;
  }

  .kb-table-container .kb-table tr td,
  .kb-table-container .kb-table tr th {
    box-sizing: border-box;
    flex: 0 0 50%;
    width: 50%;
  }
}


/* ===========================================================================
 * Blocks added by the SEO work: link lists, the service-area directory, the
 * further-reading panel and the price card.
 *
 * These started as bare <ul>s inheriting whatever the theme happened to give
 * them, which on a site that alternates teal bands with white cards meant they
 * read as unstyled markup dropped between two designed sections. This gives
 * them one shared vocabulary, taken from the site rather than invented: every
 * colour below is a Kadence palette value already in use, and the radius is the
 * 3px the theme's own buttons use.
 *
 * The one substantive departure is link colour. The theme's link amber
 * (#ffa903) sits at 1.92:1 against white and 1.71:1 against the light surface —
 * WCAG AA wants 4.5:1 for body text, so it fails by a wide margin. The primary
 * teal is 7.30:1 on white and 6.48:1 on the surface. These blocks use teal.
 * That is a legibility decision rather than a styling preference, and it is
 * scoped to the blocks added here; recolouring the site's own links is a
 * separate call for the client to make.
 * ------------------------------------------------------------------------- */

.ct-area-directory,
.ct-sibling-services,
.ct-hub,
.ct-pricing,
.ct-service-links {
  /* Kadence palette 1, 3, 4, 6, 7, 8 — no new hues introduced. */
  --ct-teal: #155d7a;
  --ct-teal-deep: #0f4557;
  --ct-ink: #1a202c;
  --ct-body: #2d3748;
  --ct-muted: #718096;
  --ct-surface-soft: #f7fafc;
  --ct-line: rgba(21, 93, 122, 0.22);
  --ct-radius: 3px;
}

/* --- Shared container ---------------------------------------------------- *
 * The service and location pages lay content out in full-width Kadence rows,
 * each supplying its own container, so .entry-content is edge-to-edge and a
 * block appended to it has none of its own. These reproduce the theme's
 * container so the text lines up with the content above at every width. */
.ct-area-directory,
.ct-sibling-services,
.ct-hub {
  box-sizing: border-box;
  max-width: var(--global-content-width, 1290px);
  margin-inline: auto;
  padding-inline: var(--global-content-edge-padding, 1.5rem);
}

.ct-sibling-services,
.ct-hub {
  margin-top: 2.5rem;
  padding-top: 1.5rem;
  border-top: 1px solid var(--ct-line);
}

.ct-area-directory {
  margin-top: 2.5rem;
}

.ct-area-directory h3 {
  margin: 1.75rem 0 0.75rem;
  font-size: 1.05rem;
}

/* --- Chips --------------------------------------------------------------- *
 * Destination lists render as a wrapped grid of chips rather than bullets. The
 * page these are densest on, /cleaning-service-area/, already opens with a row
 * of teal pill buttons for the five regions; chips are that same control at a
 * weight that survives sixty of them, where sixty full buttons would not.
 *
 * A wrapped grid also beats CSS columns here: columns order items down each
 * column, so a reader scanning left to right meets them out of alphabetical
 * order. */
.ct-area-directory ul,
.ct-sibling-services ul,
.ct-service-links {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  list-style: none;
  margin: 0;
  padding: 0;
}

.ct-area-directory li,
.ct-sibling-services li,
.ct-service-links li {
  margin: 0;
}

.ct-area-directory ul a,
.ct-sibling-services ul a,
.ct-service-links a {
  display: inline-block;
  padding: 0.4rem 0.8rem;
  background: var(--ct-surface-soft);
  border: 1px solid var(--ct-line);
  border-radius: var(--ct-radius);
  color: var(--ct-teal);
  font-size: 0.95rem;
  font-weight: 600;
  line-height: 1.35;
  text-decoration: none;
  transition: background-color 120ms ease, color 120ms ease, border-color 120ms ease;
}

.ct-area-directory ul a:hover,
.ct-sibling-services ul a:hover,
.ct-service-links a:hover,
.ct-area-directory ul a:focus-visible,
.ct-sibling-services ul a:focus-visible,
.ct-service-links a:focus-visible {
  background: var(--ct-teal);
  border-color: var(--ct-teal);
  color: #fff;
}

.ct-area-directory a:focus-visible,
.ct-sibling-services a:focus-visible,
.ct-service-links a:focus-visible,
.ct-hub a:focus-visible {
  outline: 2px solid var(--ct-teal-deep);
  outline-offset: 2px;
}

/* The three service cards sit on a teal band and centre their text, so the
   chips centre with them. */
.ct-service-links {
  justify-content: center;
  margin-top: 0.9rem;
}

/* --- Further reading ----------------------------------------------------- *
 * /commercial-cleaning/ links guides and a case study rather than services.
 * Those are a different kind of destination — something to read, not something
 * to book — so they are deliberately not chips. Keeping them as a quiet list
 * lets the chip vocabulary keep meaning "a page that sells something". */
.ct-hub ul {
  display: grid;
  gap: 0.15rem;
  list-style: none;
  margin: 0 0 0.5rem;
  max-width: 46rem;
  padding: 0;
}

.ct-hub li {
  margin: 0;
}

.ct-hub li a {
  border-bottom: 1px solid transparent;
  color: var(--ct-teal);
  display: inline-block;
  font-weight: 600;
  padding: 0.35rem 0;
  text-decoration: none;
}

.ct-hub li a:hover,
.ct-hub li a:focus-visible {
  border-bottom-color: currentColor;
}

.ct-hub > p {
  font-size: 0.95rem;
  margin-top: 0.75rem;
}

.ct-hub > p a {
  color: var(--ct-teal);
  font-weight: 600;
}

/* --- Price card ---------------------------------------------------------- *
 * This sits inside the page's own "Pricing – Deep Cleaning" section, which has
 * a teal background and colours its heading and paragraph white through
 * per-instance Kadence classes rather than through the container. Anything
 * injected there inherits the theme's dark body colour and lands dark-on-dark,
 * so the card carries its own surface.
 *
 * Flat, with a hairline and a shallow shadow: the theme's buttons use a 3px
 * radius and no elevation, and a soft 10px-radius floating card read as
 * belonging to a different site. */
.ct-pricing {
  background: #fff;
  border: 1px solid var(--ct-line);
  border-radius: var(--ct-radius);
  box-shadow: 0 1px 3px rgba(26, 32, 44, 0.08);
  box-sizing: border-box;
  color: var(--ct-body);
  margin: 1.75rem auto 0;
  max-width: 34rem;
  padding: 1.5rem 1.75rem;
  text-align: left;
}

.ct-pricing > p:first-child {
  color: var(--ct-body);
  margin-top: 0;
}

.ct-pricing strong {
  color: var(--ct-ink);
}

.ct-pricing a {
  color: var(--ct-teal);
  font-weight: 600;
}

.ct-pricing-table {
  border-collapse: collapse;
  margin: 1.15rem 0 0;
  width: 100%;
}

/* The caption says what the figures include, which the rows cannot say on
   their own — it is information, so it is set as a label rather than hidden. */
.ct-pricing-table caption {
  color: var(--ct-muted);
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.09em;
  margin-bottom: 0.6rem;
  padding: 0;
  text-align: left;
  text-transform: uppercase;
}

.ct-pricing-table th,
.ct-pricing-table td {
  border-bottom: 1px solid var(--ct-line);
  padding: 0.6rem 0;
  text-align: left;
}

.ct-pricing-table tr:last-child th,
.ct-pricing-table tr:last-child td {
  border-bottom: none;
}

.ct-pricing-table th {
  color: var(--ct-ink);
  font-weight: 600;
}

.ct-pricing-table td {
  color: var(--ct-teal);
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  text-align: right;
  white-space: nowrap;
}

.ct-pricing-note {
  border-top: 1px solid var(--ct-line);
  color: var(--ct-muted);
  font-size: 0.88rem;
  margin: 1.15rem 0 0;
  padding-top: 0.9rem;
}

@media (prefers-reduced-motion: reduce) {
  .ct-area-directory ul a,
  .ct-sibling-services ul a,
  .ct-service-links a {
    transition: none;
  }
}

/* The region heading is a link to that region's hub, and must not be mistaken
   for one of the chips beneath it. It keeps heading weight and takes the
   theme's underline on hover rather than the chip's fill. */
.ct-area-directory h3 a {
  color: var(--ct-ink);
  text-decoration: none;
  border-bottom: 2px solid var(--ct-line);
  padding-bottom: 2px;
}

.ct-area-directory h3 a:hover,
.ct-area-directory h3 a:focus-visible {
  color: var(--ct-teal);
  border-bottom-color: currentColor;
}

/* These blocks are the last thing in .entry-content, and the footer starts
   immediately after it — without this the final chip sits flush against the
   dark footer band with no breathing room at all. */
.ct-area-directory,
.ct-sibling-services,
.ct-hub {
  padding-bottom: 2.5rem;
}

/* ---------------------------------------------------------------------------
 * Service-card links: specificity fix, and full-width rows inside the cards.
 *
 * `.ct-service-links` is a bare class (0,1,0) while its siblings in the rule
 * above are class-plus-element (0,1,1), so a theme rule for `ul` inside the
 * content outranked this one alone: it kept a 34px padding-left the others shed.
 * That pushed every chip 17px right of the card's centre — so the list read as
 * mis-centred — and squeezed the row to 228px inside a 294px card, which is
 * what wrapped "End of tenancy cleaning Dublin" onto a second line.
 *
 * `ul.ct-service-links` restores the tie and the reset lands.
 *
 * With the width recovered, the chips still centred raggedly: four pills of
 * four different widths stacked in a narrow column, each one a different
 * distance from both edges. Inside a card this narrow the wrapped-grid idea
 * stops paying for itself, so here — and only here — the chips become
 * full-width rows. They align to the card's own edges, every label gets the
 * whole width before it can wrap, and each becomes a full-width tap target.
 *
 * The directory and the sibling lists keep the wrapped grid: they sit at full
 * content width, where the grid is what makes sixty items scannable.
 * ------------------------------------------------------------------------- */
ul.ct-service-links {
  display: grid;
  gap: 0.4rem;
  margin: 1rem 0 0;
  padding: 0;
}

ul.ct-service-links li {
  margin: 0;
}

ul.ct-service-links a {
  display: block;
  width: 100%;
  padding: 0.5rem 0.75rem;
  text-align: center;
}

/* ---------------------------------------------------------------------------
 * Mobile drawer — brought onto the site's own palette, plus two calls to action.
 *
 * The drawer shipped near-black (#090c10) with an amber current-item bar, which
 * belonged to no other surface on the site: the header directly above it is
 * teal, and opening the menu swapped one for the other mid-gesture. It is the
 * header's own panel, so it now uses the header's own colour.
 *
 * Every value is a Kadence palette entry already in use — palette 1 for the
 * ground, white for type, and the theme's link amber reserved for the single
 * primary action. Amber is used only there: at 1.92:1 on white it is unfit for
 * body text, but as a filled button behind near-black type it runs to 11:1 and
 * is the strongest thing on the panel, which is what a primary action should be.
 * ------------------------------------------------------------------------- */
#mobile-drawer .drawer-inner {
  background: #155d7a;
  color: #fff;
}

#mobile-drawer .drawer-header {
  border-bottom: 1px solid rgba(255, 255, 255, 0.16);
}

/* The close control read as a bordered box floating in the corner. A plain
   glyph on the panel is enough, and it stops competing with the menu. */
#mobile-drawer .menu-toggle-close {
  border-color: transparent;
  color: #fff;
}

#mobile-drawer .menu-toggle-close:hover,
#mobile-drawer .menu-toggle-close:focus-visible {
  border-color: rgba(255, 255, 255, 0.5);
}

#mobile-menu li {
  border-bottom: 1px solid rgba(255, 255, 255, 0.14);
}

#mobile-menu li:last-child {
  border-bottom: none;
}

#mobile-menu a {
  color: #fff;
  font-size: 1rem;
  padding-block: 0.95rem;
}

#mobile-menu a:hover,
#mobile-menu a:focus-visible {
  color: #ffa903;
}

/* The current page is marked with a rule and weight rather than a full amber
   bar. The bar was the loudest thing in the panel while saying the least — the
   reader already knows where they are; what they need is where to go next. */
#mobile-menu .current-menu-item > a,
#mobile-menu .current_page_item > a,
#mobile-menu .current-menu-item > .drawer-nav-drop-wrap > a,
#mobile-menu .current_page_item > .drawer-nav-drop-wrap > a {
  background: transparent;
  box-shadow: inset 3px 0 0 #ffa903;
  color: #fff;
  font-weight: 700;
  padding-left: 0.85rem;
}

#mobile-drawer .drawer-sub-toggle {
  color: #fff;
}

#mobile-menu .sub-menu {
  background: rgba(0, 0, 0, 0.14);
}

#mobile-menu .sub-menu a {
  color: rgba(255, 255, 255, 0.92);
  font-size: 0.95rem;
  padding-left: 1.5rem;
}

#mobile-drawer a:focus-visible,
#mobile-drawer button:focus-visible {
  outline: 2px solid #ffa903;
  outline-offset: 2px;
}

/* --- Calls to action ------------------------------------------------------ *
 * The menu ended at "Contact" with nothing to act on, so anyone who opened it
 * on a phone had to close it again to reach the header's buttons — on a site
 * where mobile is three quarters of all clicks. See lib/drawer.ts; both targets
 * are the ones the header already uses. */
.ct-drawer-cta {
  display: grid;
  gap: 0.6rem;
  padding: 1.4rem 0.5rem 0.5rem;
}

.ct-drawer-cta a {
  border-radius: 3px;
  display: block;
  font-size: 1rem;
  font-weight: 700;
  padding: 0.85rem 1rem;
  text-align: center;
  text-decoration: none;
  transition: background-color 120ms ease, color 120ms ease;
}

.ct-drawer-cta-primary {
  background: #fff;
  color: #155d7a;
}

.ct-drawer-cta-primary:hover,
.ct-drawer-cta-primary:focus-visible {
  background: #eef4f6;
  color: #0f4557;
}

/* Secondary reads as an outline so the two are not competing. Calling is the
   fallback for someone who does not want a form, not the headline action. */
.ct-drawer-cta-secondary {
  background: transparent;
  border: 1px solid rgba(255, 255, 255, 0.55);
  color: #fff;
}

.ct-drawer-cta-secondary:hover,
.ct-drawer-cta-secondary:focus-visible {
  background: rgba(255, 255, 255, 0.12);
  border-color: #fff;
  color: #fff;
}

@media (prefers-reduced-motion: reduce) {
  .ct-drawer-cta a {
    transition: none;
  }
}

/* The amber current-page bar sits on the list item and its drop-wrap, not on
   the link, so zeroing it on the anchor alone left the bar in place. It is
   replaced by a rule and bolder type: with the drawer now teal, a full amber
   block was the loudest thing in the panel while saying the least — the reader
   knows what page they are on, and amber is wanted for the one primary action
   at the foot of the menu, which it was competing with. */
#mobile-menu li.current-menu-item,
#mobile-menu li.current_page_item,
#mobile-menu li.current-menu-ancestor,
#mobile-menu li.current-menu-item > .drawer-nav-drop-wrap,
#mobile-menu li.current_page_item > .drawer-nav-drop-wrap,
#mobile-menu li.current-menu-ancestor > .drawer-nav-drop-wrap {
  background: rgba(255, 255, 255, 0.08);
  box-shadow: inset 3px 0 0 #ffa903;
}

/* ---------------------------------------------------------------------------
 * The mobile header's call button.
 *
 * It is already a button — .button.mobile-header-button, 185x45 with a large
 * tap target — but its background computed to #155d7a, the exact teal of the
 * header bar behind it. A teal button on a teal bar reads as a line of text, so
 * the one action in the mobile header looked like a label.
 *
 * Amber is the site's accent and the only colour with real separation from the
 * header; near-black on it runs about 11:1. It also keeps one meaning across
 * the site — amber is the thing to tap. The header's call button and the
 * drawer's Book Now are each the primary action in their own context and are
 * never on screen together, so they do not compete.
 *
 * The desktop header is untouched: this class is only rendered on mobile.
 * ------------------------------------------------------------------------- */
.mobile-header-button,
a.button.mobile-header-button {
  background: #fff;
  border: 1px solid #fff;
  color: #155d7a;
}

.mobile-header-button:hover,
a.button.mobile-header-button:hover,
.mobile-header-button:focus-visible,
a.button.mobile-header-button:focus-visible {
  background: #eef4f6;
  border-color: #eef4f6;
  color: #0f4557;
}

.mobile-header-button:focus-visible,
a.button.mobile-header-button:focus-visible {
  outline: 2px solid #fff;
  outline-offset: 2px;
}

/* The glyph is a text character in the label, so it takes the colour above —
   this only guards against a theme rule setting it separately. */
.mobile-header-button svg,
.mobile-header-button .kadence-svg-icon {
  color: inherit;
  fill: currentColor;
}

/* The menu toggle sits beside it at 43x37 with no background at all, under the
   44px minimum tap size and reading as a bare glyph next to a filled button.
   An outline gives it an edge without making it compete with the call action. */
#mobile-toggle.menu-toggle-open {
  border: 1px solid rgba(255, 255, 255, 0.55);
  border-radius: 10px;
  min-height: 45px;
  min-width: 45px;
}

#mobile-toggle.menu-toggle-open:hover,
#mobile-toggle.menu-toggle-open:focus-visible {
  background: rgba(255, 255, 255, 0.12);
  border-color: #fff;
}

/* ---------------------------------------------------------------------------
 * The form submit button.
 *
 * It shipped as amber with white text — 1.92:1, well under the 4.5:1 WCAG asks
 * for, and the only button on the site not using one of its two established
 * treatments (teal fill with white text, or white fill with teal text). It sits
 * on a white card, so white fill is not available to it; teal is the site's
 * dominant body-button style and takes it to 7.30:1. It already carried a 3px
 * teal border, so filling it teal is where the button was heading anyway.
 *
 * Set through WPForms' own custom properties rather than a selector override:
 * the rule that wins is
 * div.wpforms-container-full button[type="submit"]:not(:hover):not(:active),
 * which reads --wpforms-button-background-color-alt. Feeding the plugin its own
 * variables beats out-specifying a selector already carrying two pseudo-classes.
 * ------------------------------------------------------------------------- */
div.wpforms-container-full {
  --wpforms-button-background-color: #155d7a;
  --wpforms-button-background-color-alt: #155d7a;
  --wpforms-button-text-color: #fff;
  --wpforms-button-text-color-alt: #fff;
  --wpforms-button-border-color: #155d7a;
  --wpforms-button-border-color-alt: #155d7a;
}
