/* ==========================================================================
   WordPress / Elementor bridge.
   The static build styled its own .site-header markup. Elementor and Xpro
   output different structure, so these rules map the design system onto it.
   Kept separate from andy.css so the design system stays portable.
   ========================================================================== */

/* --- Astra resets -------------------------------------------------------- */
body, .site, #page { background: var(--ink); }
.ast-container, .site-content .ast-container { max-width: none; padding: 0; }
.entry-header.ast-no-thumbnail { display: none; }   /* Elementor pages supply their own headings */
.site-content { padding: 0; }
.ast-separate-container .ast-article-single { padding: 0; background: transparent; }

/* Astra colours links WordPress blue by default. Inherit instead, so contrast
   stays correct on both the ink and parchment grounds, and let the underline
   mark them as links rather than colour alone. */
.entry-content a:not(.elementor-button):not(.btn) { color: inherit; text-decoration: underline; }
.entry-content a:not(.elementor-button):not(.btn):hover { text-decoration-thickness: 2px; }

/* --- Header -------------------------------------------------------------- */
.xpro-theme-builder-header { background: var(--ink); }
.andy-header { padding-left: 1.5rem; padding-right: 1.5rem; }

.andy-brand-text {
  display: flex;
  flex-direction: column;
  line-height: 1.1;
  text-decoration: none;
}
.andy-brand-text .brand-name {
  font-family: var(--font-head);
  font-weight: 700;
  font-size: 1.16rem;
  letter-spacing: -0.01em;
  color: var(--on-dark-head);
}
.andy-brand-text .brand-tag {
  font-size: .64rem;
  font-weight: 700;
  letter-spacing: .19em;
  text-transform: uppercase;
  color: var(--gold);
}

/* Elementor writes width:100% on every image widget, so a fixed size has to
   say so with !important or the logo stretches to the column. */
.andy-logo img { width: 42px !important; height: auto; }

/* --- Header button ------------------------------------------------------- */
.andy-header-cta .elementor-button {
  background-color: var(--gold);
  color: var(--ink);
  border-radius: 999px;
  font-family: var(--font-body);
  font-weight: 700;
  font-size: .88rem;
  letter-spacing: .02em;
  padding: .68rem 1.35rem;
  border: 2px solid var(--gold);
  transition: background-color .18s ease, border-color .18s ease;
}
.andy-header-cta .elementor-button:hover,
.andy-header-cta .elementor-button:focus {
  background-color: var(--gold-soft);
  border-color: var(--gold-soft);
  color: var(--ink);
}

/* --- Nav ----------------------------------------------------------------- */
/* Xpro paints dropdown rows with Elementor's global PRIMARY colour, which on
   this kit is the brand gold - every submenu rendered as a solid gold block.
   There is no dropdown-background control in the free version, and Xpro ships
   its own declaration as important, so this needs !important to win. */
.xpro-elementor-dropdown-menu,
.xpro-elementor-dropdown-menu > li > a {
  background-color: var(--ink-2) !important;
  color: var(--on-dark) !important;
}
.xpro-elementor-dropdown-menu > li > a:hover {
  background-color: rgba(215,174,65,.14) !important;
  color: var(--gold-soft) !important;
}
.andy-nav .xpro-menu-item > a { font-family: var(--font-body); }

/* --- Footer -------------------------------------------------------------- */
.xpro-theme-builder-footer { background: #07070B; }
.andy-footer a { color: var(--on-dark); text-decoration: none; }
.andy-footer a:hover { color: var(--gold-soft); text-decoration: underline; }
.andy-footer h3, .andy-footer .elementor-heading-title {
  font-family: var(--font-body);
  font-size: .76rem;
  font-weight: 700;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--gold);
}

/* --- WooCommerce --------------------------------------------------------- */
.woocommerce ul.products li.product .price,
.woocommerce div.product p.price { color: var(--gold); font-family: var(--font-head); font-weight: 700; }
.woocommerce ul.products li.product .woocommerce-loop-product__title { font-family: var(--font-head); }
.woocommerce #respond input#submit,
.woocommerce a.button,
.woocommerce button.button,
.woocommerce input.button {
  background: var(--gold);
  color: var(--ink);
  border-radius: 999px;
  font-weight: 700;
}
.woocommerce #respond input#submit:hover,
.woocommerce a.button:hover,
.woocommerce button.button:hover,
.woocommerce input.button:hover { background: var(--gold-soft); color: var(--ink); }

/* --- Layout corrections -------------------------------------------------- */
/* Column percentages must leave room for the flex gap. A row of N columns with
   gap G needs at least G x (N-1) / N subtracted from the total, or the last
   column wraps onto its own line - which is exactly what happened here first
   time round. Widths are set in the template; nowrap is the second guard. */
.andy-header-row, .andy-footer-row { flex-wrap: nowrap; }
@media (max-width: 980px) {
  .andy-header-row, .andy-footer-row { flex-wrap: wrap; }
}

/* Nav: keep every item on one line and tighten spacing so eight fit */
.andy-nav .xpro-elementor-menu > li > a,
.andy-nav .xpro-menu-item > a {
  white-space: nowrap;
  padding-left: .58rem;
  padding-right: .58rem;
  font-size: 14px;
}
.andy-nav ul { flex-wrap: nowrap; }

/* Header: vertical rhythm */
.andy-header { padding-top: 12px; padding-bottom: 12px; }
.andy-cta-col { align-items: flex-end; }

/* --- Footer -------------------------------------------------------------- */
.andy-footer-brand .elementor-widget-image { text-align: left; }
.andy-footer-brand img { margin-left: 0 !important; }
.andy-footer-brand p { font-size: .93rem; margin-top: 1rem; }

.andy-footer ul { list-style: none; margin: 0; padding: 0; }
.andy-footer li { margin-bottom: .55rem; font-size: .93rem; }
.andy-footer h3 { margin: 0 0 1rem; }

/* The copyright and email ran straight into each other with no separation. */
.andy-footer-bottom {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
  justify-content: space-between;
  align-items: baseline;
  margin-top: 3rem;
  padding-top: 1.7rem;
  border-top: 1px solid var(--ink-line);
  font-size: .85rem;
  opacity: .85;
}
@media (max-width: 700px) {
  .andy-footer-bottom { flex-direction: column; gap: .5rem; }
}

/* Xpro's logged-in front-end editing panel - hide it from visitors */
body:not(.logged-in) .xpro-themer-frontend-panel,
body:not(.logged-in) #xpro-themer-panel { display: none !important; }

/* --- Astra override ------------------------------------------------------- */
/* Astra's main.min.css sets body{background:#fff} and is enqueued after this
   stylesheet, so it wins on load order. Specificity plus !important is the
   only reliable way to hold the ink ground. */
html body, body.andy, .site, #page, #content { background: var(--ink) !important; }
body.andy { color: var(--on-dark); }

/* --- Header height ------------------------------------------------------- */
/* Elementor puts 10px padding on every child container. Stacked with the
   header padding that pushed the bar to 147px; it should be about 80px. */
.andy-header-row > .e-con, .andy-header-row > .elementor-element { padding: 0 !important; }
.andy-header .elementor-widget-container { padding: 0; }
.andy-nav .xpro-elementor-menu > li > a, .andy-nav .xpro-menu-item > a { padding-top: .45rem; padding-bottom: .45rem; }
.andy-nav .elementor-widget-container > * { margin-bottom: 0; }
.andy-header { padding-top: 10px !important; padding-bottom: 10px !important; }

/* Footer columns should not inherit the 10px child padding either */
.andy-footer-row > .e-con { padding-left: 0 !important; padding-right: 0 !important; }

/* --- Header height, measured ---------------------------------------------- */
/* 123px came from four stacked paddings: header 10, Elementor's .e-con-inner
   14, the row 10, and 20px on each nav link. The earlier nav selectors did
   not match Xpro's real markup, so target the anchor directly. */
.andy-header > .e-con-inner { padding-top: 0 !important; padding-bottom: 0 !important; }
.andy-header-row { padding: 0 !important; }
.andy-nav a { padding-top: 10px !important; padding-bottom: 10px !important; line-height: 1.3 !important; }
.andy-header { padding-top: 14px !important; padding-bottom: 14px !important; }

/* --- Mobile header --------------------------------------------------------- */
/* Below the desktop breakpoint the three header columns each went full width
   and stacked, giving a 202px bar. Keep the brand and the burger on one row
   and drop the CTA - it is the first item inside the drawer anyway. */
@media (max-width: 1024px) {
  .andy-header-row { flex-wrap: nowrap !important; align-items: center; }
  .andy-brand { width: auto !important; flex: 1 1 auto !important; }
  .andy-nav-col { width: auto !important; flex: 0 0 auto !important; }
  .andy-cta-col { display: none !important; }
  .andy-header { padding-top: 12px !important; padding-bottom: 12px !important; }
}

/* --- Blockquote on dark --------------------------------------------------- */
/* The design system styles .prose blockquote for the parchment ground, and
   Astra styles blockquote too. On an ink section that produced near-black
   text on near-black - 1.9:1. Set the colour per ground explicitly. */
.sec-ink blockquote, .sec-raised blockquote, .hero blockquote, .cta-band blockquote { color: var(--on-dark) !important; }
.sec-ink blockquote strong, .sec-raised blockquote strong { color: var(--on-dark-head) !important; }
.sec-parchment blockquote, .sec-parchment-2 blockquote { color: var(--on-light-head); }
blockquote.notice { border-left-color: var(--gold); }
.sec-parchment blockquote.notice, .sec-parchment-2 blockquote.notice { border-left-color: var(--teal); }

/* --- WooCommerce on the ink ground ---------------------------------------- */
/* Astra styles the shop for a light theme. Measured failures before this:
   .ast-woo-product-category 1.77:1, the sort dropdown 2.59:1, breadcrumb
   links 4.38:1. All three are set here against the ink ground. */
.woocommerce, .woocommerce-page { color: var(--on-dark); }
.woocommerce .site-content, .woocommerce-page .site-content { background: var(--ink); }

/* product cards */
ul.products li.product { background: var(--ink-2); border: 1px solid var(--ink-3); border-radius: var(--radius); overflow: hidden; padding-bottom: 1.2rem; }
ul.products li.product .astra-shop-summary-wrap { padding: 0 1.1rem; }
ul.products li.product img { border-radius: 0; margin-bottom: 1rem; }
ul.products li.product .woocommerce-loop-product__title { font-family: var(--font-head); color: var(--on-dark-head); font-size: 1.05rem; line-height: 1.25; }
ul.products li.product .price, ul.products li.product .price .amount, ul.products li.product .woocommerce-Price-currencySymbol { color: var(--gold) !important; font-family: var(--font-head); font-weight: 700; }
.ast-woo-product-category { color: var(--on-dark) !important; opacity: .75; font-size: .78rem; letter-spacing: .08em; text-transform: uppercase; }

/* sorting + result count */
.woocommerce .woocommerce-ordering select, select.orderby {
  background: var(--ink-2) !important; color: var(--on-dark-head) !important;
  border: 1px solid var(--ink-line) !important; border-radius: 8px; padding: .5rem .8rem;
}
.woocommerce .woocommerce-ordering select option, select.orderby option { background: var(--ink-2); color: var(--on-dark-head); }
.woocommerce .woocommerce-result-count { color: var(--on-dark); opacity: .85; }

/* breadcrumb */
.woocommerce-breadcrumb, .woocommerce-breadcrumb a, .ast-woocommerce-container nav { color: var(--on-dark) !important; }
.woocommerce-breadcrumb a { text-decoration: underline; }
.woocommerce-breadcrumb a:hover { color: var(--gold-soft) !important; }

/* Astra on-card buttons */
.ast-on-card-button { background: var(--gold) !important; color: var(--ink) !important; }
.ast-on-card-button .ahfb-svg-iconset, .ast-on-card-button .ast-icon { color: var(--ink) !important; }

/* single product */
.woocommerce div.product .product_title { font-family: var(--font-head); color: var(--on-dark-head); }
.woocommerce div.product p.price, .woocommerce div.product span.price, .woocommerce div.product .woocommerce-Price-currencySymbol { color: var(--gold) !important; font-family: var(--font-head); font-weight: 700; }
.woocommerce div.product .woocommerce-tabs ul.tabs li { background: var(--ink-2); border-color: var(--ink-3); }
.woocommerce div.product .woocommerce-tabs ul.tabs li a { color: var(--on-dark) !important; }
.woocommerce div.product .woocommerce-tabs ul.tabs li.active a { color: var(--gold) !important; }
.woocommerce div.product form.cart .quantity input { background: var(--ink-2); color: var(--on-dark-head); border: 1px solid var(--ink-line); }
.woocommerce .quantity input::-webkit-outer-spin-button { opacity: 1; }

/* notices, cart, checkout */
.woocommerce-message, .woocommerce-info, .woocommerce-error { background: var(--ink-2); color: var(--on-dark); border-top-color: var(--gold); }
.woocommerce-message a, .woocommerce-info a { color: var(--gold-soft) !important; }
.woocommerce table.shop_table, .woocommerce table.shop_table th, .woocommerce table.shop_table td { color: var(--on-dark); border-color: var(--ink-3); }
.woocommerce table.shop_table thead th { color: var(--on-dark-head); }
.woocommerce form .form-row input.input-text, .woocommerce form .form-row textarea, .woocommerce form .form-row select {
  background: var(--ink-2); color: var(--on-dark-head); border: 1px solid var(--ink-line); border-radius: 8px;
}
.woocommerce form .form-row label { color: var(--on-dark-head); }

/* out of stock, sale badge */
.woocommerce span.onsale { background: var(--gold); color: var(--ink); }
.woocommerce div.product .stock { color: var(--gold-soft); }
.woocommerce div.product .out-of-stock { color: #E8A0A0; }

/* --- Imported product descriptions ---------------------------------------- */
/* The 303 descriptions came across from the live site with their own classes
   and inline colours baked in (dark text meant for a white page), which
   measured 1.25:1 here. Force them to inherit the ground they sit on.
   Scoped to the description panels so it cannot touch prices or buttons. */
.woocommerce div.product .woocommerce-Tabs-panel,
.woocommerce div.product .woocommerce-Tabs-panel *:not(a):not(.price):not(.amount):not(.woocommerce-Price-currencySymbol),
.woocommerce div.product .woocommerce-product-details__short-description,
.woocommerce div.product .woocommerce-product-details__short-description *:not(a) {
  color: var(--on-dark) !important;
  background-color: transparent !important;
}
.woocommerce div.product .woocommerce-Tabs-panel h1,
.woocommerce div.product .woocommerce-Tabs-panel h2,
.woocommerce div.product .woocommerce-Tabs-panel h3,
.woocommerce div.product .woocommerce-Tabs-panel h4,
.woocommerce div.product .woocommerce-Tabs-panel strong {
  color: var(--on-dark-head) !important;
  font-family: var(--font-head);
}
.woocommerce div.product .woocommerce-Tabs-panel a { color: var(--gold-soft) !important; }

/* Visually hidden text should never be colour-checked or seen */
.screen-reader-text { position: absolute !important; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }

/* --- Cart, checkout, account --------------------------------------------- */
/* The empty-cart button and the account headings kept Astra light-theme
   colours: "Return to shop" measured 1.19:1 and "Login" 1.34:1. */
.woocommerce .return-to-shop .button, .woocommerce a.button.wc-backward, .woocommerce .wc-backward {
  background: var(--gold) !important; color: var(--ink) !important; border-radius: 999px; font-weight: 700;
}
.woocommerce .return-to-shop .button:hover, .woocommerce a.button.wc-backward:hover {
  background: var(--gold-soft) !important; color: var(--ink) !important;
}
.woocommerce h1, .woocommerce h2, .woocommerce h3,
.woocommerce-account h1, .woocommerce-account h2, .woocommerce-account h3,
.woocommerce-cart h2, .woocommerce-checkout h2 {
  color: var(--on-dark-head) !important; font-family: var(--font-head);
}
.woocommerce-cart-form, .woocommerce-checkout, .woocommerce-account { color: var(--on-dark); }
.woocommerce .cart-empty, .woocommerce-info { color: var(--on-dark) !important; }
.woocommerce-form-login, .woocommerce-form-register { background: var(--ink-2); border-color: var(--ink-3); }

/* --- WPForms on the ink card --------------------------------------------- */
/* WPForms ships light-ground defaults: near-black labels and a blue submit.
   On .form-card (ink-2) the labels measured 1.15:1. Everything below maps the
   plugin's markup onto the design system's .field / .btn rules. */
.form-card .wpforms-container { margin: 0; }
.form-card .wpforms-container .wpforms-field { padding: 0 0 1.15rem; }
.form-card .wpforms-container .wpforms-field-label {
  display: block;
  font-family: var(--font-body);
  font-size: .88rem;
  font-weight: 700;
  margin-bottom: .42rem;
  color: var(--on-dark-head) !important;
}
.form-card .wpforms-container .wpforms-required-label { color: var(--gold) !important; }
.form-card .wpforms-container .wpforms-field-description,
.form-card .wpforms-container .wpforms-field-sublabel {
  font-family: var(--font-body);
  font-size: .84rem;
  color: var(--on-dark) !important;
  margin-top: .35rem;
}
.form-card .wpforms-container noscript,
.form-card .wpforms-container .wpforms-error-noscript { color: var(--on-dark) !important; }

.form-card .wpforms-container input[type=text],
.form-card .wpforms-container input[type=email],
.form-card .wpforms-container select,
.form-card .wpforms-container textarea {
  width: 100% !important;
  max-width: 100% !important;
  padding: .82rem .95rem !important;
  font-family: var(--font-body) !important;
  font-size: 1rem !important;
  color: var(--on-dark-head) !important;
  background: var(--ink) !important;
  border: 1px solid var(--ink-line) !important;
  border-radius: 8px !important;
  box-shadow: none !important;
}
.form-card .wpforms-container textarea { min-height: 145px !important; resize: vertical; }
.form-card .wpforms-container select option { background: var(--ink); color: var(--on-dark-head); }
.form-card .wpforms-container input:focus,
.form-card .wpforms-container select:focus,
.form-card .wpforms-container textarea:focus {
  outline: 2px solid var(--gold) !important;
  outline-offset: 1px;
  border-color: var(--gold) !important;
}
.form-card .wpforms-container input::placeholder,
.form-card .wpforms-container textarea::placeholder { color: var(--on-dark); opacity: .6; }

.form-card .wpforms-container button[type=submit],
.form-card .wpforms-container .wpforms-submit {
  display: block;
  width: 100%;
  background: var(--gold) !important;
  color: var(--ink) !important;
  border: 0 !important;
  border-radius: 999px !important;
  font-family: var(--font-body) !important;
  font-weight: 700 !important;
  font-size: 1rem !important;
  padding: .95rem 1.6rem !important;
  cursor: pointer;
  transition: background .18s ease, transform .18s ease;
}
.form-card .wpforms-container button[type=submit]:hover,
.form-card .wpforms-container .wpforms-submit:hover {
  background: var(--gold-soft) !important;
  transform: translateY(-1px);
}
.form-card .wpforms-container button[type=submit]:focus-visible,
.form-card .wpforms-container .wpforms-submit:focus-visible {
  outline: 2px solid var(--on-dark-head) !important;
  outline-offset: 2px;
}

/* Validation: a red that still clears 4.5:1 on ink-2 rather than the stock #d63638. */
.form-card .wpforms-container label.wpforms-error,
.form-card .wpforms-container em.wpforms-error {
  color: #ffb3a7 !important;
  font-size: .84rem;
  margin-top: .35rem;
  display: block;
}
.form-card .wpforms-container input.wpforms-error,
.form-card .wpforms-container textarea.wpforms-error,
.form-card .wpforms-container select.wpforms-error { border-color: #ffb3a7 !important; }

.wpforms-confirmation-container-full,
div[submit-success] > .wpforms-confirmation-container-full:not(.wpforms-redirection-message) {
  background: var(--ink-3) !important;
  border: 1px solid var(--gold) !important;
  border-radius: var(--radius);
  padding: 1.2rem 1.4rem;
}
.wpforms-confirmation-container-full p {
  color: var(--on-dark-head) !important;
  margin: 0;
}

.form-card h2 { margin-top: 0; }

/* --- Mobile drawer ------------------------------------------------------- */
/* Xpro's off-canvas drawer works: the toggler adds .active to
   .xpro-elementor-horizontal-navbar-wrapper and xpro-responsive.css slides it
   from translateX(100%) to 0. Verified by hand - all 8 links land at left 0.

   Note for anyone testing this in a headless or hidden browser pane: CSS
   transitions outrank !important in the cascade, and a pane that is not
   compositing freezes a transition at its start value. The drawer then looks
   permanently stuck at translateX(375px) no matter what you override. Set
   transition:none before measuring, or the reading is meaningless.

   What is actually ours below is the appearance: the stock panel is near-white
   and the links inherit Astra's colours. */
@media screen and (max-width: 1024px) {
  /* The stock drawer is near-white. Put it on the ink ground so it does not
     flash light over a dark site. */
  .xpro-elementor-horizontal-menu-responsive-tablet {
    background-color: var(--ink-2) !important;
    border-left: 1px solid var(--ink-line);
    padding-top: 4.5rem;
    text-align: left !important;
    overflow-y: auto;
  }
  .xpro-elementor-horizontal-menu-responsive-tablet .xpro-elementor-horizontal-navbar-nav > li > a {
    color: var(--on-dark-head) !important;
    font-family: var(--font-body);
    font-weight: 600;
    padding: 16px 24px !important;
    border-bottom-color: var(--ink-line) !important;
  }
  .xpro-elementor-horizontal-menu-responsive-tablet .xpro-elementor-horizontal-navbar-nav > li > a:hover,
  .xpro-elementor-horizontal-menu-responsive-tablet .xpro-elementor-horizontal-navbar-nav > li > a:focus {
    color: var(--gold) !important;
    background: var(--ink-3) !important;
  }
  .xpro-elementor-horizontal-menu-responsive-tablet .xpro-elementor-dropdown-menu > li > a {
    color: var(--on-dark) !important;
    padding-left: 40px !important;
  }
  .xpro-elementor-horizontal-menu-responsive-tablet .xpro-elementor-horizontal-menu-close {
    color: var(--on-dark-head) !important;
    /* Xpro ships #E8E8E8 as the background here and never exposes it as a control.
       Near white icon on light grey: the X was invisible, so the only visible way
       out of the mobile menu was the browser back button. */
    background: transparent !important;
    position: absolute;
    top: 14px;
    right: 18px;
    font-size: 1.5rem;
    min-width: 44px;
    min-height: 44px;
    align-items: center;
    justify-content: center;
  }
  .xpro-elementor-horizontal-menu-responsive-tablet .xpro-elementor-horizontal-menu-close:hover { color: var(--gold) !important; }

  /* The burger itself - it inherits Astra's link colour otherwise. */
  .xpro-elementor-horizontal-menu-toggler {
    color: var(--on-dark-head) !important;
    background: transparent !important;
    border: 0 !important;
    min-width: 44px;
    min-height: 44px;
    font-size: 1.25rem;
    cursor: pointer;
  }
  .xpro-elementor-horizontal-menu-toggler:focus-visible {
    outline: 2px solid var(--gold) !important;
    outline-offset: 2px;
  }
}

/* --- Row and split maths -------------------------------------------------- */
/* Rows were wrapping one item early everywhere: 6 symptom cards came out 5 + 1,
   4 stats came out 3 + 1, 3 step cards came out 2 + 1, and each row sat in the
   left portion of its section instead of filling it.

   Two separate converter faults:

   1. It wrote plain percentage widths (14.4%, 31.5%, 23%) alongside a FIXED
      PIXEL gap. The design system sizes children with
      flex-basis: calc(33.333% - 1.47rem) precisely so the gap is inside the
      calc. 3 x 31.5% + 2 x 32px overflows a 1140px container by about a pixel,
      and flex-wrap does the rest.
   2. It divided the width by the NUMBER OF CHILDREN rather than the column
      count in the class name. `.col-3` means three per row whatever the child
      count, so six cards belong on two rows of three - not squeezed to 14.4%
      each on one row.

   Rather than recompute percentages (which would break again at every
   breakpoint), restore the design system's own rules and let them win over
   Elementor's inline widths. Breakpoints below are copied from style.css so the
   two stay in step. */
.row { gap: 2.2rem !important; }
.row-tight { gap: 1.2rem !important; }
.split { gap: 3.4rem !important; }

.row > *, .split > * {
  width: auto !important;
  flex-grow: 0 !important;
  flex-shrink: 1 !important;
  flex-basis: 100% !important;
  min-width: 0;
}

.row.col-2 > * { flex-basis: calc(50% - 1.1rem) !important; }
.row.col-3 > * { flex-basis: calc(33.333% - 1.47rem) !important; }
.row.col-4 > * { flex-basis: calc(25% - 1.65rem) !important; }
.row.row-tight.col-3 > * { flex-basis: calc(33.333% - .8rem) !important; }
.row.row-tight.col-4 > * { flex-basis: calc(25% - .9rem) !important; }

.split.split-60 > *:first-child { flex-basis: 56% !important; }
.split.split-60 > *:last-child  { flex-basis: calc(44% - 3.4rem) !important; }
.split.split-40 > *:first-child { flex-basis: calc(44% - 3.4rem) !important; }
.split.split-40 > *:last-child  { flex-basis: 56% !important; }
.split.split-even > *            { flex-basis: calc(50% - 1.7rem) !important; }

@media (max-width: 980px) {
  .row.col-3 > *, .row.col-4 > * { flex-basis: calc(50% - 1.1rem) !important; }
  .row.row-tight.col-3 > * { flex-basis: calc(50% - .6rem) !important; }
  .row.row-tight.col-4 > * { flex-basis: calc(33.333% - .8rem) !important; }
  .split { gap: 2.4rem !important; }
  /* Match the base rules' specificity - .split.split-60 > *:first-child is
     (0,3,0) and beats a plain (0,2,0) selector even from a later block, so
     split-60 refused to stack. Same selector list the design system uses. */
  .split.split-60 > *:first-child, .split.split-60 > *:last-child,
  .split.split-40 > *:first-child, .split.split-40 > *:last-child,
  .split.split-even > * { flex-basis: 100% !important; }
  /* On a narrow screen the image in a split-40 should follow the words. */
  .split.split-40 > *:first-child { order: 2; }
  .split.split-40 > *:last-child  { order: 1; }
}

@media (max-width: 760px) {
  .row.row-tight.col-4 > * { flex-basis: calc(50% - .6rem) !important; }
}

@media (max-width: 700px) {
  .row.col-2 > *, .row.col-3 > *, .row.col-4 > * { flex-basis: 100% !important; }
  .row.row-tight.col-3 > * { flex-basis: 100% !important; }
}

@media (max-width: 430px) {
  .row.row-tight.col-4 > * { flex-basis: 100% !important; }
}

/* The hero photo layer. .hero already carries position:relative and
   overflow:hidden, so the absolutely-positioned .hero-bg inside an Elementor
   HTML widget still anchors to the section. */
.hero-bg { pointer-events: none; }

/* --- Hero photo layer ----------------------------------------------------- */
/* The static hero paints a photo behind the words at 30% with a gradient scrim
   over it (.hero-bg + .hero-bg::after). The converter carried the markup across
   but Elementor wraps an HTML widget in its own relatively-positioned box, and
   that box has zero height - so the layer's inset:0 collapsed to nothing and the
   hero rendered as flat ink with a bare right-hand side.

   Fix: make the boxed inner wrapper static so the layer anchors to the section
   itself (.hero carries position:relative and overflow:hidden), pin the tagged
   widget over the whole section, and lift the real content above it. */
.hero > .e-con-inner { position: static !important; }

.hero .hero-bg-layer {
  position: absolute !important;
  inset: 0 !important;
  z-index: 0;
  pointer-events: none;
  margin: 0 !important;
}
.hero .hero-bg-layer > .elementor-widget-container { position: absolute; inset: 0; }

.hero > .e-con-inner > .elementor-element:not(.hero-bg-layer) {
  position: relative;
  z-index: 2;
}

/* --- Desktop nav hover ---------------------------------------------------- */
/* Elementor bakes the menu widget's colour into post-468.css as

     .elementor-468 .elementor-element.elementor-element-261b9dd
       .xpro-elementor-horizontal-navbar-nav > li > a { color:#C6C3BC }

   which is (0,4,1) and has NO desktop hover variant - Xpro only emits hover
   colours for the tablet and mobile drawer. A base rule at that specificity
   applies in every state, so the design system's `a:hover { color: var(--gold) }`
   at (0,1,1) could never show through and the nav simply stopped reacting.

   Note this file regenerates whenever Elementor's cache is cleared, so the
   override has to live here rather than being fixed in the widget settings.

   Values are the design system's own: .nav a:hover is gold-soft on a faint gold
   wash, and the current page sits at solid gold. */
.andy-nav .xpro-elementor-horizontal-navbar-nav > li > a {
  border-radius: 6px;
  transition: color .18s ease-in-out, background-color .18s ease-in-out;
}
.andy-nav .xpro-elementor-horizontal-navbar-nav > li > a:hover,
.andy-nav .xpro-elementor-horizontal-navbar-nav > li > a:focus-visible {
  color: var(--gold-soft) !important;
  background-color: rgba(215, 174, 65, .09) !important;
}
.andy-nav .xpro-elementor-horizontal-navbar-nav > li.current-menu-item > a,
.andy-nav .xpro-elementor-horizontal-navbar-nav > li.current-menu-ancestor > a,
.andy-nav .xpro-elementor-horizontal-navbar-nav > li > a[aria-current="page"] {
  color: var(--gold) !important;
}
.andy-nav .xpro-elementor-horizontal-navbar-nav > li > a:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 2px;
}

/* Dropdown children get the same treatment. */
.andy-nav .xpro-elementor-dropdown-menu > li > a:hover,
.andy-nav .xpro-elementor-dropdown-menu > li > a:focus-visible {
  color: var(--gold-soft) !important;
  background-color: var(--ink-3) !important;
}

/* =========================================================================
   TEAL: the third logo colour, given a job.
   DEPLOYABLE: this block is written to be appended verbatim to
   wp-content/themes/andy-porter/assets/andy-wp.css (then bump ANDY_VERSION).
   It contains no artifact-only rules.

   MEASURED, and the reason for every rule below:
     #2e788d on parchment #F5F2EA .... 4.5:1  PASS for text
     #2e788d on ink #0B0B10 ......... 3.9:1  FAIL for text
     #ffffff on #2e788d ............. 5.0:1  PASS anywhere
     #245F70 on parchment-2 #EBE6DA .. 5.7:1  PASS for text

   So on dark grounds teal is ONLY ever a filled ground carrying white text,
   or a non-text rule. There is no teal text on ink anywhere in this file.

   SEMANTIC: teal marks PROCESS and INFORMATION: the stages of a session,
   the rule under a process label, the information callout, the secondary
   light-ground label. Gold stays the accent on dark, purple stays the accent
   on light. Teal is a third voice, not a replacement for either.
   ========================================================================= */

:root {
  --teal-deep: #245F70;               /* text weight for light grounds */
  --teal-line: rgba(46,120,141,.45);  /* hairlines and rules on ink */
  --teal-wash: rgba(46,120,141,.07);  /* barely-there fill behind info blocks */
}

/* --- The session stages -------------------------------------------------
   These were gold discs with ink numerals. Teal discs with white numerals
   measure 5.0:1, so one component is now safe on ink AND on parchment and
   needs no per-ground variant. This is the page's main teal moment. */
.step-num {
  background: var(--teal);
  color: #fff;
}
.sec-parchment .step-num,
.sec-parchment-2 .step-num {
  background: var(--teal);
  color: #fff;
}

/* --- Process label ------------------------------------------------------
   A short teal rule under the eyebrow, used ONLY where the section really
   describes a sequence. It encodes something true about the content rather
   than decorating every heading on the page. */
.eyebrow-process { display: inline-block; margin-bottom: 1rem; }
.eyebrow-process::after {
  content: "";
  display: block;
  width: 30px;
  height: 2px;
  margin: .55rem auto 0;
  background: var(--teal);
}

/* --- Structural rules ---------------------------------------------------
   Non-text, so the 3.9:1 text failure does not apply. */
.sec-topline { border-top-color: var(--teal-line); }

/* --- Information callout ------------------------------------------------ */
.notice { border-left-color: var(--teal); background: var(--teal-wash); }
.sec-parchment .notice,
.sec-parchment-2 .notice { border-left-color: var(--teal); background: var(--teal-wash); }

/* --- Secondary label on light grounds -----------------------------------
   Purple remains the primary accent on parchment. The alternating
   parchment-2 band takes teal instead, so the colour has a presence in the
   light half of the page and not only on the dark. Text-safe at 5.7:1. */
.sec-parchment-2 .eyebrow { color: var(--teal-deep); }
.sec-parchment .figcap,
.sec-parchment-2 .figcap { color: var(--teal-deep); }

/* =========================================================================
   SIGNATURE QUOTE BAND, and the expanded call to action band.
   Appended after the teal layer, so equal specificity rules here win.

   MEASURED:
     #e6cd8a on ink-2 #14141C ... 11.8:1  PASS
     #C6C3BC on ink-2 #14141C ... 10.4:1  PASS
   ========================================================================= */

/* === Andy's line, given its own band ====================================
   Spectral italic at display size. The band deliberately carries no button
   and no link: it is a pause between sections, not another step to take. */
.pull-quote {
  font-family: var(--font-head);
  font-style: italic;
  font-weight: 400;
  font-size: clamp(1.45rem, 3.2vw, 2.35rem);
  line-height: 1.4;
  color: var(--gold-soft);
  margin: 0 0 1.5rem;
  padding: 0;
  border: 0;
  text-wrap: balance;
}
.pull-quote-cite {
  display: block;
  font-family: var(--font-body);
  font-style: normal;
  font-size: .8rem;
  font-weight: 700;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--on-dark);
}

/* === Call to action band with several paragraphs ========================
   Four paragraphs set centred would be unreadable, so the heading stays
   centred and the body drops to a left aligned reading measure. */
.cta-body {
  max-width: var(--wrap-narrow);
  margin: 0 auto 2rem;
  text-align: left;
}
.cta-body p { margin-bottom: 1rem; }
.cta-body p:last-child { margin-bottom: 0; }

/* =========================================================================
   CHANNELS: the social and listening links.
   Tiles sit in the design system's own .row.col-4 grid, so the tablet and
   mobile reflow is inherited rather than reinvented.

   MEASURED, tile sits on ink-2 #14141C:
     #F2EFE8 label ....... 15.4:1  PASS
     #C6C3BC descriptor .. 10.4:1  PASS
     #e6cd8a on hover .... 11.8:1  PASS
   ========================================================================= */

.channel {
  display: flex;
  align-items: center;
  gap: .85rem;
  height: 100%;
  padding: 1rem 1.1rem;
  background: var(--ink-2);
  border: 1px solid var(--ink-3);
  border-radius: var(--radius);
  color: var(--on-dark-head);
  text-decoration: none;
}
.channel:hover,
.channel:focus-visible {
  border-color: var(--gold);
  color: var(--gold-soft);
}
.channel .ico {
  width: 40px;
  height: 40px;
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--gold);
  border-radius: 50%;
  color: var(--gold);
}
.channel .ico svg { width: 19px; height: 19px; }
/* Scope to the descriptor only. A bare `.channel span` also matches the
   .channel-text wrapper, which drops the bold label to the dim colour and
   flattens the hierarchy between platform name and descriptor. */
.channel .channel-text { min-width: 0; }
.channel b {
  display: block;
  font-weight: 700;
  font-size: .95rem;
  line-height: 1.3;
  color: var(--on-dark-head);
}
.channel .channel-text > span {
  display: block;
  font-size: .82rem;
  color: var(--on-dark);
  line-height: 1.35;
}
.channel:hover b,
.channel:focus-visible b,
.channel:hover .channel-text > span,
.channel:focus-visible .channel-text > span { color: inherit; }

/* === Notice border, specificity fix =====================================
   andy-wp.css already carried `blockquote.notice { border-left-color:
   var(--gold) }` from the original build. That selector is (0,1,1) and beats
   the plain `.notice` teal rule at (0,1,0) however far down the file it sits,
   so every blockquote notice on a dark ground stayed gold while the teal rule
   looked applied. Match the specificity to actually win. */
blockquote.notice { border-left-color: var(--teal); }

/* === Pull quote colour, specificity and importance fix ==================
   andy-wp.css carries `.sec-ink blockquote, .sec-raised blockquote, .hero
   blockquote, .cta-band blockquote { color: var(--on-dark) !important }` to
   tame Elementor blockquote styling. That is (0,1,1) AND !important, so the
   plain `.pull-quote` rule at (0,1,0) lost twice over and the signature quote
   rendered in body grey instead of gold. Match specificity and importance. */
blockquote.pull-quote { color: var(--gold-soft) !important; }

/* === Emergency appointment emphasis, and dollar pricing =================
   The emergency card moves to the top of the list and takes a teal border
   plus a filled teal flag. Teal FAILS as text on ink at 3.9:1, so the flag is
   a filled ground carrying white text, which measures 5.0:1 and is safe.
   There is no teal text anywhere in this block.

   Gold still marks the recommended standard appointment, so the two emphases
   stay distinguishable: gold means start here, teal means fastest route. */
.booking-item.urgent {
  border-color: var(--teal);
  border-width: 2px;
}
.urgent-flag {
  display: inline-block;
  background: var(--teal);
  color: #fff;
  font-family: var(--font-body);
  font-size: .74rem;
  font-weight: 700;
  letter-spacing: .14em;
  text-transform: uppercase;
  padding: .34rem .72rem;
  border-radius: 100px;
  margin: 0 0 1.1rem;
}

/* Indicative dollar price under each sterling price. */
.booking-item .price-usd {
  font-family: var(--font-body);
  font-size: .85rem;
  font-weight: 500;
  color: var(--on-dark);
  line-height: 1.3;
  margin: 0 0 .35rem;
}
.sec-parchment .booking-item .price-usd { color: var(--on-light); }
.price-note {
  font-size: .84rem;
  color: var(--on-dark);
  margin-top: .9rem;
}

/* === Appointment kind subtitle ==========================================
   The person appointments are all spiritual health checks, so each one says
   so directly under its heading. This ties the booking page to the Spiritual
   Health Check page, which previously had no price of its own and routed
   here without the wording ever connecting.
   #F2EFE8 on ink-2 measures 15.4:1. Deliberately NOT gold uppercase, so it
   does not compete with the .same-day line directly below it. */
.booking-item .appt-kind {
  font-family: var(--font-body);
  font-size: .95rem;
  color: var(--on-dark-head);
  margin: -.15rem 0 .75rem;
}
.sec-parchment .booking-item .appt-kind { color: var(--on-light-head); }

/* === Emergency chip in the jump nav =====================================
   Every other chip is an outline on ink. The emergency one is a filled teal
   ground, so it reads as the standout rather than just another pill.

   It must be filled, not teal text: #2e788d on ink is 3.9:1 and fails. White
   on teal is 5.0:1, and white on teal-deep for hover is 7.1:1.
   The nested price also has to be forced to white. It inherits gold from
   `.quick-nav .qn-price`, and gold on teal measures 2.4:1, a bad failure.
   Specificity: .quick-nav a.qn-urgent is (0,2,1) and beats .quick-nav a at
   (0,1,1); the price rule is (0,3,1) and beats .quick-nav .qn-price at (0,2,0). */
.quick-nav a.qn-urgent {
  background: var(--teal);
  border-color: var(--teal);
  color: #fff;
}
.quick-nav a.qn-urgent .qn-price { color: #fff; }
.quick-nav a.qn-urgent:hover,
.quick-nav a.qn-urgent:focus-visible {
  background: var(--teal-deep);
  border-color: var(--teal-deep);
  color: #fff;
}
.quick-nav a.qn-urgent:hover .qn-price,
.quick-nav a.qn-urgent:focus-visible .qn-price { color: #fff; }

/* === Bright decision callout ============================================
   The "not sure which one you need" block is a decision helper sitting at the
   end of the price list, so it earns more weight than an ordinary .notice.
   Stronger teal wash, a heavier left rule, a teal hairline all round, and
   body text lifted to heading white.
   #F2EFE8 over the teal wash composited on ink measures 15.1:1, and the gold
   link inside measures 8.2:1. This is a div, not a blockquote, so the
   .sec-ink blockquote !important colour rule does not reach it. */
.notice.notice-bright {
  border: 1px solid rgba(46,120,141,.5);
  border-left: 5px solid var(--teal);
  background: rgba(46,120,141,.16);
  color: var(--on-dark-head);
  font-size: 1.02rem;
}
.notice.notice-bright strong { color: #fff; }

/* --- Light sections inside WooCommerce ------------------------------------ */
/* The WooCommerce block above sets headings to --on-dark-head with !important,
   which was right when every Woo page sat on the ink ground. The shop landing
   put parchment sections on a WooCommerce page, so those headings rendered
   near-white on near-white: measured 1.03:1 and 1.08:1. Give the light grounds
   their colours back. */
.woocommerce .sec-parchment h1,   .woocommerce .sec-parchment h2,
.woocommerce .sec-parchment h3,   .woocommerce .sec-parchment h4,
.woocommerce .sec-parchment-2 h1, .woocommerce .sec-parchment-2 h2,
.woocommerce .sec-parchment-2 h3, .woocommerce .sec-parchment-2 h4 {
  color: var(--on-light-head) !important;
}
.woocommerce .sec-parchment,   .woocommerce .sec-parchment p,   .woocommerce .sec-parchment li,
.woocommerce .sec-parchment-2, .woocommerce .sec-parchment-2 p, .woocommerce .sec-parchment-2 li {
  color: var(--on-light) !important;
}
.woocommerce .sec-parchment .eyebrow, .woocommerce .sec-parchment-2 .eyebrow { color: var(--purple) !important; }
.woocommerce .sec-parchment summary, .woocommerce .sec-parchment-2 summary { color: var(--on-light-head) !important; }

/* Keep the solid button readable: a bare `.sec-parchment a` rule outranks .btn
   and once turned these bronze-on-gold at 2.99:1. Same trap, guarded again. */
.woocommerce .sec-parchment a:not(.btn), .woocommerce .sec-parchment-2 a:not(.btn) { color: var(--purple); }
.woocommerce .sec-parchment a.btn:not(.btn-ghost),
.woocommerce .sec-parchment-2 a.btn:not(.btn-ghost) { color: var(--ink) !important; }

/* === Symptom frequency chart ============================================
   Teal marks information, and a page of aggregate data is exactly that.
   The bars are non-text, so the 3.9:1 teal on ink text failure does not
   apply to them. Nothing here puts teal on ink as type.
   MEASURED: label #F2EFE8 on ink 15.4:1, percentage #d7ae41 on ink 9.3:1.
   The percentage is present as text, so the bar itself is aria-hidden and
   adds no duplicate announcement for screen readers. */
.symptom-list { display: flex; flex-direction: column; gap: 1.2rem; }
.symptom-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 1rem;
  margin-bottom: .45rem;
}
.symptom-name { color: var(--on-dark-head); font-size: 1rem; line-height: 1.35; }
.symptom-pct {
  flex: 0 0 auto;
  font-family: var(--font-head);
  font-weight: 700;
  font-size: 1.05rem;
  color: var(--gold);
  font-variant-numeric: tabular-nums;
}
.symptom-track {
  height: 9px;
  background: var(--ink-3);
  border-radius: 999px;
  overflow: hidden;
}
.symptom-fill { display: block; height: 100%; background: var(--teal); border-radius: 999px; }

/* light grounds, should this block ever be reused there */
.sec-parchment .symptom-name, .sec-parchment-2 .symptom-name { color: var(--on-light-head); }
.sec-parchment .symptom-pct, .sec-parchment-2 .symptom-pct { color: var(--purple); }
.sec-parchment .symptom-track, .sec-parchment-2 .symptom-track { background: var(--paper-line); }
.sec-parchment .symptom-fill, .sec-parchment-2 .symptom-fill { background: var(--teal); }

/* --- Research page: data tables and distribution bars --------------------- */
.data-wrap { overflow-x: auto; margin: 0 0 1.1rem; }
.data-table { width: 100%; border-collapse: collapse; font-size: .93rem; min-width: 33rem; }
.data-table caption { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
.data-table th, .data-table td { padding: .62rem .7rem; text-align: left; vertical-align: baseline; border: 0; border-bottom: 1px solid var(--ink-3); }
.data-table thead th { font-family: var(--font-head); font-weight: 700; font-size: .78rem; letter-spacing: .07em; text-transform: uppercase; color: var(--gold); }
.data-table th.num, .data-table td.num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.data-table tbody tr:last-child td { border-bottom: none; }
.pair-and { opacity: .55; padding: 0 .1rem; }
.sec-parchment .data-table th, .sec-parchment .data-table td,
.sec-parchment-2 .data-table th, .sec-parchment-2 .data-table td { border-bottom-color: rgba(11,11,16,.14); }
.sec-parchment .data-table thead th,
.sec-parchment-2 .data-table thead th { color: var(--purple); }
.fig-note { font-size: .86rem; opacity: .78; line-height: 1.65; margin: .1rem 0 1.7rem; max-width: 60rem; }
.dist { display: flex; flex-direction: column; gap: .55rem; }
.dist-row { display: grid; grid-template-columns: 11rem 1fr 3rem; align-items: center; gap: .8rem; font-size: .9rem; }
.dist-label { color: var(--on-dark); }
.dist-track { height: 7px; background: var(--ink-3); border-radius: 999px; overflow: hidden; }
.dist-fill { display: block; height: 100%; background: var(--teal); border-radius: 999px; }
.dist-n { text-align: right; font-variant-numeric: tabular-nums; font-family: var(--font-head); font-weight: 700; color: var(--gold); }
@media (max-width: 640px) {
  .dist-row { grid-template-columns: 7.4rem 1fr 2.4rem; gap: .6rem; font-size: .84rem; }
  .data-table th, .data-table td { padding: .55rem .5rem; }
}

/* --- Header nav: room for the dropdown carets ----------------------------- */
/* Giving Health Check, Symptoms and Learn submenus put an 8px caret plus a 10px
   margin on three items, 54px in all, and the header had nothing to give: at the
   1140px content width the three columns (brand 273, nav 614, cta 205, plus two
   24px gaps) already add up to exactly 1140. The links were therefore already
   overflowing their centred column before the carets existed, and the extra
   54px pushed the last item under the Book a Session button by 26px.

   The space comes back from the per link margin, which sat at 10px a side on top
   of 10px of padding, so 40px of chrome per item. Dropping the margin to 4px
   returns 96px and the caret margin returns another 18px, which is more than the
   carets took. Type size and padding are untouched, so nothing gets harder to
   read or harder to tap.

   Desktop only. The hamburger takes over below 1025px and has its own spacing. */
@media (min-width: 1025px) {
  .andy-nav .xpro-elementor-horizontal-navbar-nav > li > a {
    margin-left: 4px !important;
    margin-right: 4px !important;
  }
  .andy-nav .xpro-dropdown-menu-toggle { margin-left: 4px !important; }
}


/* ============================================================
   BLOG, ARCHIVES AND ASTRA'S OWN SURFACES          theme 1.4.7
   ------------------------------------------------------------
   Astra's ast-separate-container layout paints its content cards
   opaque white (--ast-global-color-4). Every colour this theme
   sets on top is an on-ink colour, so the blog rendered near-white
   text on white. Measured before this block:

     post h1        #F2EFE8 on #FFFFFF ....  1.15:1
     post body      #C6C3BC on #FFFFFF ....  1.76:1
     archive titles #1E293B on #FFFFFF ....  Astra slate, unusable
     29 of 72 text nodes failed on a single post
     70 of 148 failed on /blog/

   The reset at line 13 already tried this and lost on specificity:
   Astra's selector carries :not(.ast-related-post), and a :not()
   argument counts, making it 0-3-0 against line 13's 0-2-0. Astra
   also prints these rules INLINE in the head, so source order is
   no help either.

   Every selector below is prefixed with `body`, which is where
   .ast-separate-container lives. That buys one element of
   specificity over Astra's inline CSS, with no !important anywhere.

   .ast-related-post is excluded on purpose: it is the one card
   that is meant to carry a ground of its own.
   ============================================================ */

/* 1. Single posts, comments, search headers and the 404 read as a
      column on the page ground, so they take no card at all. */
body.ast-separate-container .ast-article-single:not(.ast-related-post),
body.ast-separate-container .ast-archive-description,
body.ast-separate-container .comments-area,
body.ast-separate-container .comments-count-wrapper,
body.ast-separate-container .related-posts-title-wrapper,
body.ast-separate-container .error-404,
body.ast-separate-container .no-results,
body.single.ast-separate-container .site-main .ast-author-meta {
  background-color: transparent;
}

/* 2. The archive is a three column grid, so its cards DO need a
      ground. Without one the twelve posts run together with nothing
      between them. --ink-2 is the raised panel token and --ink-3 the
      documented card border. Body copy on --ink-2 measures 10.3:1,
      headings 15.8:1, gold links 11.7:1. Astra's own 25.5px padding
      is left alone. */
body.ast-separate-container .ast-article-inner {
  background-color: var(--ink-2);
  border: 1px solid var(--ink-3);
  border-radius: var(--radius);
}

/* 3. Post titles. Astra sets .entry-title a to its slate
      --ast-global-color-2 (#1E293B), unreadable on ink. */
body.ast-separate-container .entry-title a { color: var(--on-dark-head); }
body.ast-separate-container .entry-title a:hover,
body.ast-separate-container .entry-title a:focus { color: var(--gold-soft); }

/* 4. Byline, date and categories. Astra's link blue #046BD2
      measures 3.78:1 on ink and fails. --gold-soft is the
      documented on-ink link colour at 12.6:1. */
body.ast-separate-container .entry-meta,
body.ast-separate-container .entry-meta * { color: var(--on-dark); }
body.ast-separate-container .entry-meta a { color: var(--gold-soft); }
body.ast-separate-container .entry-meta a:hover,
body.ast-separate-container .entry-meta a:focus { color: var(--gold); }

/* 5. Previous / next. Astra's slate #334155 measured 1.90:1. The
      small PREVIOUS and NEXT labels stay quieter than the titles. */
body.single.ast-separate-container .post-navigation a { color: var(--gold-soft); }
body.single.ast-separate-container .post-navigation a:hover,
body.single.ast-separate-container .post-navigation a:focus { color: var(--gold); }
body.single.ast-separate-container .post-navigation .ast-post-nav { color: var(--on-dark); }

/* 6. Pagination. The current page sat on Astra's blue #046BD2.
      Gold ground with ink text is the brand equivalent and is the
      safe direction for gold: 9.4:1. */
body.ast-separate-container .ast-pagination .page-numbers { color: var(--gold-soft); }
body.ast-separate-container .ast-pagination .page-numbers:hover { color: var(--gold); }
body.ast-separate-container .ast-pagination .page-numbers.current {
  background-color: var(--gold);
  color: var(--ink);
}

/* 7. The archive, search and 404 headline.
      Caught by re-auditing after rules 1 to 6 went live: h1.page-title
      was Astra's slate #1E293B, which was legible while the card behind
      it was white and became 1.34:1 the moment that card went
      transparent. Astra sets it from .ast-404-layout-1 h1.page-title at
      0-2-1, and elsewhere from a bare h1 rule that wins on source order
      because Astra prints inline. Both are covered below. */
body h1.page-title,
body .ast-404-layout-1 h1.page-title {
  color: var(--on-dark-head);
}

/* 8. Read more on the archive cards.
      The link itself is switched on in functions.php and carries the
      site's own .btn .btn-ghost classes, so it needs no colours of its
      own. All that is set here is scale: the full size .btn is a page
      level call to action at .95rem/1.9rem, which is too heavy repeated
      twelve times down a card grid, plus the alignment rules below, which
      sit the button at the foot of the card. */
/* Card, then content column, so the button can be pushed to the foot of
   the card. Excerpt heights vary by about 60px across a row, which left
   the buttons visibly stepped. .entry-content is empty on this layout, so
   nothing sits below the button once it is pushed down. padding-top keeps
   a minimum gap for the cards where the excerpt already fills the space. */
body.ast-separate-container .ast-article-inner {
  display: flex;
  flex-direction: column;
}
body.ast-separate-container .ast-article-inner > .post-content {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
}
body.ast-separate-container .ast-read-more-container {
  margin: 0;
  margin-top: auto;
  padding-top: 1.35rem;
}
body.ast-separate-container .ast-read-more-container .btn {
  padding: .58rem 1.2rem;
  font-size: .84rem;
  border-width: 1px;
}

/* 9. Archive grid overflow.
      Pre-existing, found while verifying the read more work, and NOT
      caused by the blog rules above: disabling every rule in this block
      leaves the overflow unchanged at 1442 against a 1425 viewport.

      Astra's .ast-row carries margin: 0 -17px, which is meant to be
      absorbed by 17px of padding on the container so the outer columns
      line up with the container edge. main.site-main has padding 0 here,
      the same zeroed horizontal padding described in the sitewide gutter
      note, so the row hangs 17px past the viewport on each side and the
      blog scrolls sideways.

      Fixed by giving the container the padding Astra expects rather
      than by fighting the margin. A first attempt zeroed the row margin
      instead and lost: Astra sets it from
      .ast-separate-container.ast-desktop .ast-blog-layout-4-grid .ast-row
      at 0-4-0, inline. Nothing sets padding on .site-main, so this rule
      wins outright at 0-2-1 with no escalation.

      Scoped to blog, archive and search body classes, so single posts
      keep their 750px reading column untouched. On mobile it also gives
      these templates the horizontal gutter the rest of the site is still
      missing. The sitewide gutter fix remains a separate job across all
      45 Elementor pages. */
body.blog .site-main,
body.archive .site-main,
body.search .site-main {
  padding-left: 1em;
  padding-right: 1em;
}

/* 10. Imported post content.
       The 13 posts brought over from andy-porter.co.uk on 28 Aug 2026
       carry that site's Elementor markup, which was authored against a
       light ground. Their per-post Elementor CSS does not exist here, so
       the widgets fall back to Astra's global heading and text colours
       and land dark-on-ink:

         .elementor-heading-title      #1E293B on ink ....  1.34:1
         .elementor-blockquote__*      #334155 on ink ....  1.90:1

       Everything sits inside .entry-content, so scoping there is enough
       and nothing outside the imported posts is touched. No inline style
       attributes are involved, so no !important is needed. */
body.single .entry-content h1,
body.single .entry-content h2,
body.single .entry-content h3,
body.single .entry-content h4,
body.single .entry-content h5,
body.single .entry-content h6 {
  color: var(--on-dark-head);
}
/* Set on the blockquote itself, not just __content: these quotes also
   contain bare <p> children with no class of their own, which inherit. */
body.single .entry-content .elementor-blockquote,
body.single .entry-content .elementor-blockquote__content {
  color: var(--on-dark);
}
body.single .entry-content .elementor-blockquote__author,
body.single .entry-content .elementor-blockquote__tweet-label {
  color: var(--gold-soft);
}

/* 11. Guided meditations page (/audio-downloads-meditations/).
       15 meditations, each a .card on the ink ground so the existing card
       and badge components carry the styling. Only the audio specific
       parts are new.

       The badge holds the real duration read from the file's own metadata
       at import, not the figure in the title: several titles round oddly
       (a "5 Minute" file runs 4:37, a "36Min" file runs 35:58).

       color-scheme: dark is what makes the browser draw its native audio
       controls in their dark variant. Without it Chrome paints a light
       grey pill that sits badly on --ink-2. This is the supported way to
       do it; the controls are shadow DOM and cannot be styled directly. */
.med-list {
  display: grid;
  gap: 1.5rem;
  max-width: var(--wrap-narrow);
  margin: 0 auto;
}
.med { scroll-margin-top: 90px; }
.med h3 {
  font-size: clamp(1.15rem, 2.2vw, 1.45rem);
  margin-bottom: .35rem;
}
.med-sub {
  color: var(--gold-soft);
  font-size: .95rem;
  font-weight: 600;
  margin: 0 0 1.1rem;
}
.med p { font-size: .97rem; }
.med-player { margin-top: 1.4rem; }
.med-player + .med-player { margin-top: 1rem; }
.med-part {
  font-family: var(--font-body);
  font-size: .72rem;
  font-weight: 700;
  letter-spacing: .13em;
  text-transform: uppercase;
  color: var(--on-dark);
  margin: 0 0 .5rem;
}
.med audio {
  display: block;
  width: 100%;
  height: 42px;
  color-scheme: dark;
  border-radius: 999px;
}
.med-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .85rem;
  margin-top: 1.4rem;
}
.med-actions .btn {
  padding: .58rem 1.2rem;
  font-size: .84rem;
  border-width: 1px;
}
.med-size {
  font-size: .8rem;
  color: var(--on-dark);
  font-variant-numeric: tabular-nums;
}

/* Several meditations carry bullet lists of what the session covers.
   Astra's default put them 51px in on top of the card's own 35px padding,
   87px from the card edge, which reads as a stray indent rather than a
   list. 1.35rem keeps the bullets close to the text they belong to. */
.med ul,
.med ol {
  margin: 0 0 1.5rem;
  padding-left: 1.35rem;
}
.med li {
  font-size: .97rem;
  margin-bottom: .4rem;
}
.med li:last-child { margin-bottom: 0; }

/* 12. Mobile gutter, sitewide. THE REAL FIX, replacing the page-scoped
       stopgap that previously sat here for pages 417, 678, 680 and 686.

       Root cause, andy.css:100: .section { padding: var(--pad-y) 0 }. In the
       original static build the gutter came from an inner wrapper,
       andy.css:102: .container { max-width: var(--wrap); padding: 0 1.5rem }.
       Porting the design into Elementor replaced that wrapper with .e-con-inner,
       which has no horizontal padding, so the '0' in the shorthand became the
       final word and every section lost its gutter. Same story for .hero
       (andy.css:550), .page-head (580), .section-sm (101) and .cta-band (1045).
       Below roughly 1180px text ran flush to both screen edges on 50 of the
       51 Elementor pages.

       Setting per-container --padding-left in Elementor does not help: the
       shorthand above beats the custom property, which is why the container
       settings on the newer pages read 18px and rendered as 0.

       Longhands only, so andy.css keeps ownership of the vertical rhythm
       including its own media queries. Same specificity, later file, so these
       two properties win and nothing else moves.

       Costs nothing on desktop: the inner is boxed and centred at max 1180px,
       so outer padding has no visual effect until the viewport is narrow
       enough to need it.

       Checked before shipping. No section container anywhere is set to full
       width, so there is no full-bleed band to inset. '.container' appears
       nowhere in the Elementor data, so no gutter doubles. The header matches
       none of these selectors and keeps its own 1.5rem. The one footer hit,
       template 469 container 97640aa '.section .cta-band .geo', has no
       horizontal padding of its own, so it had the same bug and this fixes
       it rather than doubling it. */
.section,
.section-sm,
.hero,
.page-head,
.cta-band {
  padding-left: clamp(18px, 5vw, 24px);
  padding-right: clamp(18px, 5vw, 24px);
}

/* 13. Page head paragraphs.
       .page-head .lede is held to a 660px measure but a plain <p> beside it
       had none, so it ran the full 1180px container. Free resources now
       carries three paragraphs in its head rather than one, which made the
       mismatch obvious. Matching the lede keeps the block reading as one. */
.page-head p { max-width: 660px; }


/* 14. split-even mobile order.
       These split layouts put the media column first in the DOM, and
       .split-40 already flips it below 980px so the words come before the
       picture on a phone. .split-even had no such rule, so switching the
       home page 'Twenty years' section from split-40 to split-even to widen
       the photo would have silently moved the image above the heading on
       mobile.

       First written as a blanket .split-even rule, which was wrong: the home
       page already had a second split-even section (28a1cad, the free
       resources block) whose text comes FIRST and image second, and the
       blanket rule would have flipped that one the wrong way. Which column
       leads is a property of the section, not of the ratio, so it is a
       modifier class applied only where the media column is first. */
@media (max-width: 980px) {
  .split.split-media-first > *:first-child { order: 2; }
  .split.split-media-first > *:last-child  { order: 1; }
}


/* 15. Do not upscale a source image past its own pixels.
       The Andy and Terry photograph is 830x346. Inside the 1180px container
       .media img { width: 100% } was rendering it at 1140px, a 37% upscale
       that shows as softness. Capped at native width and centred. If a larger
       original turns up, swap the file and drop this class from the page. */
.media-cap-830 {
  max-width: 830px;
  margin-left: auto;
  margin-right: auto;
}


/* 16. Footer search.
       Reuses the existing .field input styling, which is already built for a
       dark ground (ink background, ink-line border, gold focus ring), and the
       existing .btn. All that is added here is the side by side layout and
       zeroing .field's own bottom margin, which exists for stacked form rows
       and is wrong inside a one line search. */
.footer-search {
  display: flex;
  align-items: stretch;
  gap: .6rem;
  margin-top: 1.5rem;
  max-width: 24rem;
}
.footer-search .field {
  flex: 1 1 auto;
  min-width: 0;
  margin-bottom: 0;
}
.footer-search .btn {
  flex: 0 0 auto;
  padding: .7rem 1.25rem;
  font-size: .88rem;
  border-width: 1px;
}
@media (max-width: 600px) {
  .footer-search { max-width: none; }
}


/* 17. Negative entities page.

       .media-native is the general answer to the upscaling problem that
       .media-cap-830 solved for one file: .media img sets width:100%, which
       stretches a small source to fill its container. Several images on this
       page are small historical scans (224x257, 228x221, 460x276) and would
       have been blown up well past their own pixels. width:auto renders them
       at native size, max-width keeps them inside the column. Prefer this
       over adding another per width cap class. */
.media-native img {
  width: auto;
  max-width: 100%;
  height: auto;
}

/* Entity type cards. The three sources are very different shapes, so a
   fixed ratio with object-fit keeps the row even. */
.ent-shot {
  margin: -0.35rem 0 1.1rem;
  border-radius: 8px;
  overflow: hidden;
}
.ent-shot img {
  display: block;
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
}

/* --- Gutenberg blocks on the ink ground ----------------------------------- */
/* The 42 imported blog posts are Gutenberg content and the block library's own
   defaults assume a light page: .wp-block-quote renders slate-700, which
   measured 1.90:1 against the ink ground. There are no inline colours in any of
   the post content - this is entirely stylesheet defaults, so it belongs here
   rather than in the posts themselves. */
.entry-content .wp-block-quote,
.entry-content .wp-block-quote p,
.entry-content .wp-block-quote cite,
.entry-content .wp-block-quote > div {
  color: var(--on-dark) !important;
}
.entry-content .wp-block-quote {
  border-left: 3px solid var(--gold);
  padding-left: 1.4rem;
  font-style: italic;
}
.entry-content .wp-block-quote cite,
.entry-content .wp-block-quote > div:last-child {
  color: var(--gold-soft) !important;
  font-style: normal;
  font-size: .9rem;
}

.entry-content .wp-block-separator { border-color: var(--ink-line); opacity: 1; }
.entry-content .wp-block-image figcaption,
.entry-content figcaption { color: var(--on-dark) !important; opacity: .78; font-size: .88rem; }

.entry-content .wp-block-button__link {
  background: var(--gold) !important;
  color: var(--ink) !important;
  border-radius: 999px;
  font-weight: 700;
}
.entry-content .wp-block-button__link:hover { background: var(--gold-soft) !important; }

.entry-content .wp-block-table,
.entry-content .wp-block-table td,
.entry-content .wp-block-table th {
  color: var(--on-dark) !important;
  border-color: var(--ink-line) !important;
}
.entry-content pre, .entry-content code {
  color: var(--on-dark-head);
  background: var(--ink-2);
}

/* Post furniture: dates, categories, read-more and the archive listing. */
.entry-content, .ast-article-single, .ast-article-post { color: var(--on-dark); }
.entry-meta, .entry-meta *, .post-meta, .ast-blog-meta-container,
.ast-blog-meta-container * { color: var(--on-dark) !important; }
.entry-meta a, .ast-blog-meta-container a { color: var(--gold-soft) !important; }
.entry-title a { color: var(--on-dark-head); }
.entry-title a:hover { color: var(--gold) !important; }
.ast-blog-featured-section .post-thumb-img-content img { border-radius: var(--radius); }


/* 18. What every session includes, inside the booking cards.
       The full twelve item list already exists once further down the page, but a
       reader comparing appointments cannot see it without scrolling past all six
       cards. Repeating it as visible text would roughly triple each card and push
       the payment buttons down six times over, so it goes in a closed accordion:
       no height cost until someone asks for it.

       .accordion is a page level component at 1.1rem with 1.35rem padding, which
       is too heavy inside a card, so this is a compact variant. It sits between
       the card's own differentiators and the buttons, which is where the question
       actually arises. */
.booking-detail .covered {
  border-top: 1px solid var(--ink-line);
  border-bottom: 1px solid var(--ink-line);
  margin: 0 0 1.4rem;
}
.booking-detail .covered summary {
  list-style: none;
  cursor: pointer;
  position: relative;
  padding: .85rem 2rem .85rem 0;
  font-family: var(--font-body);
  font-size: .92rem;
  font-weight: 700;
  letter-spacing: .01em;
  color: var(--gold-soft);
}
.booking-detail .covered summary::-webkit-details-marker { display: none; }
.booking-detail .covered summary::after {
  content: "";
  position: absolute;
  right: .35rem;
  top: 50%;
  width: 8px;
  height: 8px;
  border-right: 2px solid var(--gold);
  border-bottom: 2px solid var(--gold);
  transform: translateY(-70%) rotate(45deg);
  transition: transform .2s ease;
}
.booking-detail .covered[open] summary::after { transform: translateY(-30%) rotate(225deg); }
.booking-detail .covered summary:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
.booking-detail .covered .covered-body {
  padding: 0 0 1.1rem;
  columns: 2;
  column-gap: 2rem;
}
.booking-detail .covered .check { margin: 0; }
.booking-detail .covered .check li {
  font-size: .93rem;
  margin-bottom: .5rem;
  break-inside: avoid;
}
@media (max-width: 700px) {
  .booking-detail .covered .covered-body { columns: 1; }
}


/* 19. Delivery promise chip on the booking cards.
       .same-day carries the speed promise: 'Report sent same day', and on the
       emergency card 'Normally available next day'. It was 0.8rem gold-soft text
       sitting under the price, reading as a caption rather than the selling point
       it actually is.

       Teal, not gold. Gold is already doing three jobs in this card, the price,
       the tick marks and both buttons, so a fourth gold element would compete with
       the thing we most want clicked. Teal is the documented colour for process
       and information, which is exactly what a delivery promise is, and RULE 3 in
       andy.css allows it on a dark ground only as a filled ground carrying white
       text. White on --teal measures 5.1:1, so this is the safe direction for it.

       The emergency card also has a teal .urgent-flag at the top. They sit in
       different places and say different things, one a category, one a promise,
       so the repeat reads as a family rather than a mistake. */
.booking-item .same-day {
  display: inline-block;
  background: var(--teal);
  color: #fff;
  font-family: var(--font-body);
  font-size: .82rem;
  font-weight: 700;
  letter-spacing: .09em;
  text-transform: uppercase;
  line-height: 1.25;
  padding: .5rem .9rem;
  border-radius: 999px;
  margin: .55rem 0 0;
}
/* the light-ground variant kept the old purple text rule, which would now be
   invisible white-on-nothing, so it is restated as a filled chip too. */
.sec-parchment .booking-item .same-day,
.sec-parchment-2 .booking-item .same-day {
  background: var(--teal);
  color: #fff;
}


/* 20. Emergency card, gold premium marker.
       The delivery promise chip in rule 19 made every card teal, which left the
       emergency card carrying three teal elements: its 2px border, the
       'Priority booking' flag and the new chip. Teal stopped meaning anything
       there.

       So the premium marker moves to gold and the promise stays teal. The
       emergency card is now the only gold-outlined card on the page, which is
       what marks it as the priority option, and teal is left doing exactly one
       job across all six cards: when your report arrives.

       Gold as a filled ground with ink text measures 9.4:1, which is the safe
       direction for gold per RULE 1 in andy.css. The border is non-text. */
.booking-item.urgent {
  border-color: var(--gold);
}
.booking-item.urgent .urgent-flag {
  background: var(--gold);
  color: var(--ink);
}


/* 21. Newsletter promo band, top of the home page.
       A lead magnet bar for the Mailchimp signup: heading left, button right,
       stacking on narrow screens.

       Purple ground with white text is the one use of --purple that RULE 2 in
       andy.css permits on a dark page: never as text, only as a filled ground.
       Measured 7.7:1.

       The reference image used a teal button on the purple. Not copied: --teal on
       --purple measures 1.54:1, so the button would barely separate from its own
       background. Gold gives 3.7:1 against the purple and is already the CTA
       colour everywhere else on the site, so the band matches rather than
       introduces a fourth button style. */
.promo-band {
  background: var(--purple);
  color: #fff;
}
.promo-band .promo-inner {
  max-width: var(--wrap);
  margin: 0 auto;
  padding: 1.1rem 24px;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 1rem 2rem;
}
.promo-band .promo-title {
  font-family: var(--font-head);
  font-size: clamp(1.15rem, 2.2vw, 1.5rem);
  font-weight: 700;
  line-height: 1.25;
  color: #fff;
  margin: 0;
}
.promo-band .btn {
  padding: .72rem 1.4rem;
  font-size: .92rem;
  border-color: var(--gold);
  background: var(--gold);
  color: var(--ink);
  flex: 0 0 auto;
}
.promo-band .btn:hover {
  background: var(--gold-soft);
  border-color: var(--gold-soft);
  color: var(--ink);
}
.promo-band .btn:focus-visible { outline: 3px solid #fff; outline-offset: 2px; }
@media (max-width: 700px) {
  .promo-band .promo-inner { flex-direction: column; text-align: center; padding: 1.2rem 18px; gap: .9rem; }
  .promo-band .btn { width: 100%; justify-content: center; }
}


/* 22. Responsive video embed.
       aspect-ratio holds the 16:9 box before the iframe loads, so the page does
       not jump when it arrives. loading=lazy keeps the third party player off
       the critical path, which matters on a home page already carrying 30
       stylesheets and 27 scripts.

       dnt=1 on the player URL is Vimeo's do not track flag: it stops the player
       setting tracking cookies, which keeps the embed consistent with what the
       privacy policy says about embedded video. */
.video-embed {
  max-width: 760px;
  margin: 2.2rem auto 0;
  aspect-ratio: 16 / 9;
  border-radius: var(--radius);
  overflow: hidden;
  border: 1px solid var(--ink-3);
  background: #000;
}
.video-embed iframe {
  display: block;
  width: 100%;
  height: 100%;
  border: 0;
}
.video-intro {
  max-width: 760px;
  margin: 2.6rem auto 0;
  text-align: center;
}
.video-intro h3 { margin-bottom: .5rem; }
.video-intro p { margin-bottom: 0; }


/* 23. Ask Chen live band.
       The weekly stream, on the home page and on /chen/. The times line is the
       operationally useful part, so it is set apart from the prose rather than
       buried in it: this is an international audience and 'Sunday evening' means
       nothing without a timezone.

       Gold on the times, matching the price treatment on the booking page, since
       both answer 'when and how much'. The caveat underneath is deliberately
       quieter: it is a qualification, not a headline. */
.stream-times {
  font-family: var(--font-head);
  font-size: clamp(1.05rem, 2vw, 1.3rem);
  font-weight: 700;
  color: var(--gold);
  line-height: 1.4;
  margin: 1.4rem 0 .5rem;
}
.sec-parchment .stream-times,
.sec-parchment-2 .stream-times { color: var(--purple); }
.stream-note {
  font-size: .92rem;
  opacity: .85;
  max-width: 46rem;
  margin: 0 auto 0;
}
.center .stream-note { margin-left: auto; margin-right: auto; }


/* 25. Scroll to top button.
       Astra ships this as #ast-scroll-top with
       background-color: var(--ast-global-color-0), which is still the stock
       Astra blue #046BD2. A saturated blue square against the gold, purple
       and ink palette was the one piece of unbranded chrome left on every
       page.

       Colour choice is constrained: the button is position:fixed, so it
       floats over dark and light sections alike, and neither brand colour
       survives both. Gold on ink is 9.3:1 but only 1.9:1 on parchment, so a
       gold disc all but vanishes over the light sections. Purple is the
       mirror image, 6.9:1 on parchment but 2.6:1 on ink, under the 3:1 that
       non-text UI boundaries need.

       So: purple fill for the body, which carries the light grounds, and a
       gold-soft hairline ring to define the edge against the dark ones
       (12.6:1 on ink). White arrow reads on purple at 7.7:1, which is rule 2
       used the way rule 2 allows, purple as a filled ground rather than as
       text. Purple also keeps the button clear of gold, which is the CTA
       colour everywhere else, so a utility control does not sit there
       impersonating a call to action.

       Size goes from Astra's 2.1em (31.5px) to 44px, the minimum touch
       target, and the 2px radius becomes a circle to match the pill buttons.

       body prefix takes this to 1-0-1 against Astra's 1-0-0, so no
       !important is needed. */
body #ast-scroll-top {
  width: 44px;
  height: 44px;
  line-height: 44px;
  border-radius: 999px;
  background-color: var(--purple);
  border: 1px solid var(--gold-soft);
  color: #fff;
  box-shadow: 0 4px 18px rgba(11, 11, 16, .35);
  transition: background-color .2s ease, border-color .2s ease, transform .2s ease;
}
body #ast-scroll-top:hover,
body #ast-scroll-top:focus-visible {
  background-color: var(--purple-deep);
  border-color: var(--gold);
  transform: translateY(-2px);
}
body #ast-scroll-top:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 3px;
}
/* The chevron is an inline SVG using fill:currentColor, so the colour above
   carries it, but Astra sizes it off the old 2.1em box. */
body #ast-scroll-top .ast-arrow-svg {
  width: 16px;
  height: 16px;
  vertical-align: middle;
  fill: currentColor;
}


/* 26. Video poster link.
       A still that links out to Vimeo, used instead of an iframe for the Ask
       Chen stream. Two reasons. The video is domain restricted, so the
       player returns 403 on the staging hostname and would show a dead grey
       box until the site moves to andy-porter.co.uk. And the home page
       already carries one Vimeo iframe for the mission film a section above,
       so a second embed would mean two players and two lots of Vimeo
       JavaScript on one page.

       A poster costs one image, works on every hostname, and still gets the
       viewer to the full video in one click.

       The play badge is decorative and marked aria-hidden. The link takes
       its accessible name from the image alt plus the visually hidden 'opens
       on Vimeo', so it does not depend on the badge at all. */
.video-poster {
  position: relative;
  display: block;
  max-width: 760px;
  margin: 0 auto;
  border-radius: var(--radius);
  overflow: hidden;
  border: 1px solid var(--ink-line);
  line-height: 0;
  isolation: isolate;
}
.sec-parchment .video-poster,
.sec-parchment-2 .video-poster { border-color: var(--paper-line); }
.video-poster img {
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  display: block;
  transition: transform .4s ease, filter .3s ease;
}
/* Slight darkening so the gold badge holds its contrast whatever the frame
   happens to be. Vimeo stills are photographic and cannot be relied on. */
.video-poster::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  background: linear-gradient(to bottom, rgba(11,11,16,.10), rgba(11,11,16,.34));
  transition: background .3s ease;
}
.video-poster-play {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  z-index: 2;
  width: 72px;
  height: 72px;
  border-radius: 999px;
  background: var(--gold);
  box-shadow: 0 6px 26px rgba(11,11,16,.45);
  transition: transform .25s ease, background .25s ease;
}
.video-poster-play::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 54%;
  transform: translate(-50%, -50%);
  border-style: solid;
  border-width: 13px 0 13px 21px;
  border-color: transparent transparent transparent var(--ink);
}
.video-poster:hover img,
.video-poster:focus-visible img { transform: scale(1.03); }
.video-poster:hover::before,
.video-poster:focus-visible::before { background: linear-gradient(to bottom, rgba(11,11,16,.04), rgba(11,11,16,.24)); }
.video-poster:hover .video-poster-play,
.video-poster:focus-visible .video-poster-play {
  transform: translate(-50%, -50%) scale(1.08);
  background: var(--gold-soft);
}
.video-poster:focus-visible { outline: 2px solid var(--gold); outline-offset: 3px; }
@media (prefers-reduced-motion: reduce) {
  .video-poster img, .video-poster-play, .video-poster::before { transition: none; }
  .video-poster:hover img, .video-poster:focus-visible img { transform: none; }
}
@media (max-width: 600px) {
  .video-poster-play { width: 56px; height: 56px; }
  .video-poster-play::after { border-width: 10px 0 10px 16px; }
}

/* Modifier for a still that is a capture of a player, so the frame already
   has its own play button, timebar and logo baked into the pixels. Adding the
   gold badge on top would put two play buttons on one image. The scrim is
   lightened to match, since there is no badge left to protect. */
.video-poster.poster-plain .video-poster-play { display: none; }
.video-poster.poster-plain::before {
  background: linear-gradient(to bottom, rgba(11,11,16,0), rgba(11,11,16,.10));
}
.video-poster.poster-plain:hover::before,
.video-poster.poster-plain:focus-visible::before { background: rgba(11,11,16,0); }

/* Portrait variant. The mission film is shot vertically, so a 16:9 frame
   would be two thirds black bar. Held to 380px so a 9:16 still does not run
   away down the page: at that width it stands 675px tall, about the same
   height as the 760px landscape poster plus its heading. */
.video-poster.poster-portrait {
  max-width: 380px;
}
.video-poster.poster-portrait img {
  aspect-ratio: 9 / 16;
}
@media (max-width: 480px) {
  .video-poster.poster-portrait { max-width: 280px; }
}


/* Footnote to .media-native, 30 Aug 2026.
   width:auto is necessary but not sufficient. When an img carries a srcset
   with w descriptors the browser takes its intrinsic width from the srcset
   and sizes, not from the file, so width:auto can still resolve to a
   candidate that is not the one you want. Terry's conference photo is a
   460x360 original: it rendered at 737px, a 1.6x upscale, and then at 302px
   once sizes changed, neither of which is its own size.

   For a small original there is nothing useful in a srcset anyway, since
   every generated candidate is smaller than the file you want shown. Build
   the tag by hand with width, height and no srcset, and intrinsic sizing
   comes from the file as this class assumes:
     <img src="..." width="460" height="360" alt="..." loading="lazy">
   wp_get_attachment_image() always adds a srcset, so it is the wrong helper
   for .media-native images. */

/* Centring a .media-native image.
   .media img is display:block, so text-align:center on the figure does
   nothing to it: a block box ignores the parent's text alignment. At native
   width inside a wider column that leaves the image flush left with all the
   slack on one side, which reads as a mistake rather than a choice. Auto
   margins are what actually centre a block. The caption is inline text, so
   .center already handles that. */
.media-native.center img {
  margin-left: auto;
  margin-right: auto;
}


/* --- Header call to action ------------------------------------------------ */
/* This button was hidden on desktop for a while, and the note that used to sit
   here explained why: a 'Book appointment' item had been added to the nav on
   30 Aug, so the header carried the same call to action twice, ~150px apart,
   and the nav overflowed its 54% column and sat on top of the button.

   That duplicate nav item has now been removed, so the reason is gone. The nav
   is back to 8 top-level items and the gold Book Now button is the single call
   to action, sitting at the right-hand end of the header at every width. */
.andy-cta-col { align-items: flex-end; justify-content: center; }
.andy-header-cta { text-align: right; }
/* And the narrow band just above the drawer. With the duplicate button gone
   the nav fits everywhere except right at 1025, where it was still 4px over
   and put a horizontal scrollbar on the page. Nine links at 10px a side is
   180px of padding; 7px returns 54px, which clears 4px without the spacing
   being noticeably tighter. Only in this band, so the nav keeps its full
   spacing at the widths most desktops actually use. */
@media (min-width: 1025px) and (max-width: 1080px) {
  body .andy-header .xpro-elementor-horizontal-navbar-nav > li > a,
  body .xpro-elementor-horizontal-navbar-nav > li > a {
    padding-left: 7px;
    padding-right: 7px;
  }
}

/* 28. Statutory company disclosure in the footer.
       A UK limited company has to show its registered name, number and
       registered office on its website. This is the required text, not
       marketing, so it is set quieter than the copyright line above it but
       still has to clear AA. */
.andy-footer-legal {
  font-size: .78rem;
  line-height: 1.6;
  opacity: .72;
  margin: .75rem 0 0;
  max-width: 70ch;
}

/* --- WPForms selects: clipped text and the missing arrow ------------------ */
/* Reported on /contact/: the "What is this about?" dropdown showed its
   selected option sliced off halfway down the glyphs, and nothing marked it
   as a dropdown at all.

   Two separate causes, both from the block above meeting WPForms' modern
   render stylesheet.

   1. Height. WPForms pins every control to a fixed
        height: var(--wpforms-field-size-input-height)
      which computes to 43px, and then zeroes the select's own vertical padding with padding-block: 0.
      The design system re-applies padding: .82rem .95rem !important, which
      beats padding-block, but never touches height. So the select carried
      about 26px of padding and a 19px line inside a 43px border-box and
      overflowed itself. Freeing the height lets padding plus line-height do
      the sizing, which also makes the select match the text inputs instead
      of sitting 5px shorter than them. The inputs are included for the same
      reason, since they were being squeezed by the same fixed height.

   2. The arrow. WPForms sets appearance: none and paints the chevron as a
      background-image. The block above sets `background: var(--ink)
      !important`, and the shorthand resets background-image to none, so the
      arrow disappeared with it. Same specificity here, later in the file, so
      background-image alone wins and the background colour above survives.
      The chevron is inlined as a data URI because nothing else in this theme
      loads an icon font on the front end. */
.form-card .wpforms-container input[type="text"],
.form-card .wpforms-container input[type="email"],
.form-card .wpforms-container input[type="tel"],
.form-card .wpforms-container input[type="url"],
.form-card .wpforms-container input[type="number"],
.form-card .wpforms-container select {
  height: auto !important;
  min-height: 0 !important;
  line-height: 1.45 !important;
}

.form-card .wpforms-container select {
  padding-right: 2.6rem !important;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1l5 5 5-5' fill='none' stroke='%23C6C3BC' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") !important;
  background-repeat: no-repeat !important;
  background-position: right .95rem center !important;
  background-size: 12px 8px !important;
  text-overflow: ellipsis;
}

/* The option list is drawn by the operating system, not the page, so it needs
   its colours set explicitly or Windows renders black on black. */
.form-card .wpforms-container select option {
  background: var(--ink-2);
  color: var(--on-dark-head);
}
/* --- Equal height media cards in a .row -------------------------------- */
/* Reported on /free-resources/: the three cards under "Meditations, podcast
   and daily card" all finished at different heights.

   Two independent causes, so two fixes.

   1. The images. .card-media img only set width: 100%, so each picture kept
      its own aspect ratio and set its own height. The meditations art is
      940x788 and the oracle 1293x1096, both about 1.19, but the podcast cover
      is square. That one alone rendered roughly 70px taller and pushed its
      whole card down. Pinning one aspect ratio makes the three image areas
      identical whatever is dropped in later.

      object-fit is contain rather than cover on purpose. Cover would crop
      about 17% off the square podcast artwork, which would cut the
      "insight, healing, transformation" line off the bottom of the logo.
      Contain letterboxes it instead, and because both the podcast and oracle
      images are already black on black the side bars are invisible.

   2. The cards. .row already carries align-items: stretch, so the Elementor
      containers were full height, but Elementor's own widget div sits between
      that container and .card and was not passing the height down. The card
      therefore stopped at its content. Two rules carry the height through
      the wrapper to the card itself. */
.card-media img {
  aspect-ratio: 6 / 5;
  object-fit: contain;
  background: var(--ink);
  display: block;
}

.row > * > .elementor-widget-html {
  height: 100%;
}

.row > * > .elementor-widget-html > .card {
  height: 100%;
}

/* --- Header layout: let the columns size themselves ----------------------- */
/* The overlap that caused the Book button to be hidden came from fixed column
   percentages: brand 24 / nav 54 / cta 18 of a 1180px row gives the nav ~611px
   for content that needed ~700px, so it ran under the button.
   Sizing brand and button to their content and giving the nav whatever is left
   removes the failure mode rather than tuning around it - the nav now gets
   roughly 820px for ~560px of links, and the button always sits hard right. */
@media (min-width: 1025px) {
  .andy-header-row { flex-wrap: nowrap !important; align-items: center !important; }
  .andy-brand   { width: auto !important; flex: 0 0 auto !important; }
  .andy-nav-col { width: auto !important; flex: 1 1 auto !important; min-width: 0; }
  .andy-cta-col {
    width: auto !important;
    flex: 0 0 auto !important;
    display: flex !important;
    align-items: center;
    justify-content: flex-end;
  }
  .andy-nav .xpro-elementor-horizontal-navbar-nav { justify-content: flex-end; flex-wrap: nowrap; }

  /* The drawer-only booking link is redundant here - the button says it. */
  .andy-nav .andy-mobile-only { display: none !important; }
}

/* Below the desktop breakpoint the Xpro drawer takes over and the CTA column is
   hidden to keep brand and burger on one row, so the booking link lives inside
   the drawer instead. One call to action at every width, never two. */
@media (max-width: 1024px) {
  .andy-nav .andy-mobile-only > a { color: var(--gold) !important; font-weight: 700; }
}

/* --- Team cards ----------------------------------------------------------- */
/* Portraits are mixed crops and orientations straight off the live site, so the
   frame does the work: a fixed square with object-fit cover, centred on the
   upper part of the photo. Centring on the middle decapitates portrait shots -
   the same trap that bit the contact page. */
.team-card { text-align: center; }
.team-card .team-photo {
  width: 132px;
  height: 132px;
  margin: 0 auto 1.1rem;
  border-radius: 50%;
  overflow: hidden;
  border: 2px solid var(--ink-line);
}
.team-card .team-photo img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 28%;
  display: block;
}
.team-card h3 { margin-bottom: .2rem; }
.team-card .eyebrow { color: var(--gold-soft); }

/* Header basket. The site previously had no link to the cart anywhere, so a
   shopper who added something had no way to reach the checkout. The count
   badge is filled by WooCommerce cart fragments after an AJAX add. The
   .added_to_cart rules below style the View cart link WooCommerce appends to
   the Add to cart button, which was rendering in an unreadable colour on the
   dark product cards. */
.andy-header-cart { display: flex; align-items: center; }
.andy-cart-wrap { display: flex; align-items: center; }
.andy-cart {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 42px;
  height: 42px;
  margin-right: .6rem;
  border: 1px solid var(--ink-line);
  border-radius: 999px;
  color: var(--on-dark-head);
  background: transparent;
  transition: border-color .15s ease, color .15s ease;
}
.andy-cart:hover, .andy-cart:focus-visible { color: var(--gold); border-color: var(--gold); }
.andy-cart.has-items { border-color: var(--gold); color: var(--gold); }
.andy-cart-icon { display: block; }
.andy-cart-count {
  position: absolute;
  top: -4px;
  right: -4px;
  min-width: 19px;
  height: 19px;
  padding: 0 5px;
  border-radius: 999px;
  background: var(--gold);
  color: var(--ink);
  font-size: .72rem;
  font-weight: 700;
  line-height: 19px;
  text-align: center;
}
.woocommerce a.added_to_cart {
  display: inline-block;
  margin-top: .6rem;
  color: var(--gold);
  font-weight: 600;
  text-decoration: underline;
  text-underline-offset: 3px;
}
.woocommerce a.added_to_cart:hover { color: var(--on-dark-head); }
@media (max-width: 921px) { .andy-cart { width: 38px; height: 38px; } }


/* andy-cart layout.

   Two things had to be undone for the basket to sit where it should.

   The CTA column is a column in Elementor with items pushed to the end, which
   would have stacked the basket above the Book Appointment button. It is set to
   a row here rather than changed in Elementor, so the header stays editable in
   the usual way and this override is visible to whoever reads the stylesheet.

   Below 1024px the whole CTA column is hidden, because the Book Appointment
   button is the first item inside the mobile drawer and a second copy in the bar
   was redundant. The basket is not redundant there, and a phone shopper with no
   route to the checkout is the whole problem being fixed, so the column comes
   back at that width carrying the basket alone. It is ordered ahead of the
   burger so the burger stays where people reach for it, at the right edge. */
.andy-header .andy-cta-col {
  flex-direction: row !important;
  align-items: center !important;
  justify-content: flex-end !important;
}
.andy-header-cart { display: flex !important; align-items: center; width: auto !important; flex: 0 0 auto !important; }

@media (max-width: 1024px) {
  .andy-header .andy-cta-col {
    display: flex !important;
    width: auto !important;
    flex: 0 0 auto !important;
    order: 2;
  }
  .andy-header .andy-nav-col { order: 3; }
  .andy-header .andy-header-cta { display: none !important; }
  .andy-cart { margin-right: .35rem; }
}

