/* camplane — per-client overrides.
 * Lives HERE (not in static-overrides.css) because static-overrides.css is a
 * verbatim copy of shared/templates/elementor-static-overrides.css and gets
 * byte-refreshed from the template by inject_helpers.refresh_static_overrides().
 * These rules were appended to static-overrides.css during the 2026-08-11 SEO
 * pass and were lost on the 2026-08-17 refresh — restored to the right file. */

/* --- Footer legal links, inside the copyright line (client round 2026-08-19)
 * These began life as `.ym-legal-footer`, a full-width strip UNDER the black
 * socket, which read as a second footer bar stacked on the first. The first
 * pass moved them into the socket as their own flex child — but the socket is
 * `justify-content: space-between`, so that parked them in dead centre in the
 * default face at 14px, matching nothing on either side of them (operator:
 * "not aligned at all or matching other copyright typography").
 *
 * They are now the TAIL OF THE COPYRIGHT SENTENCE, bullet-separated after
 * "All Rights Reserved" — emitted inside that `<p>` by 00-admin/wire-footer.py.
 *
 * That placement is what makes this block short: family, size, weight,
 * letter-spacing, line-height, colour and the uppercase transform all come
 * from the heading they now sit in. Restating any of them here is exactly how
 * they would drift the next time the socket's type is touched, which is the
 * defect being fixed.
 *
 * 2026-08-19, client round: drop the underline — the socket is one uppercase
 * sentence and underlining its tail made the two links read as a separate UI
 * element bolted onto the end of it. They stay discoverable by hover and by
 * being the only two colour-changing words on the line.
 *
 * The hover was the real bug. Nothing here set one, so `.elementor-kit-22
 * a:hover` (0,2,1) supplied it — #171717D9, near-black, on a #000000 socket:
 * 1.02:1, i.e. the link VANISHED under the cursor. `footer a{color:#fff}` was
 * losing that tie on specificity, one class short. The prefix selector below
 * is (0,3,1) and settles it, on the same reasoning as the email rule further
 * down: outrank the kit rather than reach for !important.
 *
 * Red is the site's own #DE1B22 (--e-global-color-14a3eb7), the CTA colour.
 * Measured 4.29:1 on #000000 — under the 4.5:1 AA floor for 18px text. Kept
 * because it is the requested colour and it is the brand's only true red; the
 * palette's lighter #FF7372 measures 7.95:1 if that trade is ever revisited.
 * Focus-visible is included so keyboard users get the same signal. */
.ym-legal-links a {
  color: inherit;
  text-decoration: none;
}
[class*="elementor-kit-"] .ym-legal-links a:hover,
[class*="elementor-kit-"] .ym-legal-links a:focus-visible {
  color: var(--e-global-color-14a3eb7, #DE1B22);
}
/* A real element, not an `::before` on the link: a pseudo-element inside the
 * anchor sits inside the anchor's text-decoration box and would come out
 * underlined along with it. `inline-block` keeps the glyph from being squeezed
 * if the line wraps on a phone. */
.ym-legal-sep {
  display: inline-block;
  margin: 0 0.5em;
  text-decoration: none;
}

/* The same complaint, one row up: the email address in the "Self Storage
 * Location" column inherited the global link colour #59595B, which lands at
 * 1.5:1 on the navy footer — invisible in the client's screenshot and a WCAG
 * failure besides. Every link in the footer is on a dark surface, so white is
 * the whole policy; there is no light-background link down there to exempt.
 *
 * The `[class*=...]` prefix is doing real work, not decoration. Elementor's
 * Global Styles kit sets the site-wide link colour as `.elementor-kit-22 a`
 * (0,1,1), which out-ranks a plain `footer a` (0,0,2) — measured: the email
 * stayed #59595B with the bare selector. Matching the kit class on <body>
 * lifts this to (0,1,2) and wins on specificity rather than with !important,
 * so a later per-element rule can still override it. Matching the class
 * PREFIX rather than `elementor-kit-22` keeps it working if the kit is ever
 * re-exported under a new post id. */
[class*="elementor-kit-"] footer a { color: #fff; }

/* A contact row's link always takes the colour of the row it sits in.
 *
 * The SEO pass wrapped the published phone number in `tel:` everywhere it was
 * still plain text (footer + both reserve-popup contact blocks) so it is
 * tappable on a phone and GA4 `phone_tap` can actually see it. That turns a
 * text node into an anchor, and an anchor picks up the kit's global link
 * colour — which is #59595B, the exact grey the operator called out as hard to
 * read. Measured after wrapping: the popup's phone row went #181818 -> #59595B
 * while the address row directly beneath it stayed #181818. Passing contrast
 * (6.9:1 on white) but a visible regression, and inconsistent with its own
 * neighbour.
 *
 * `inherit` is the right answer rather than a second hard-coded colour: in the
 * footer the row is white so the link is white, in the popup the row is #181818
 * so the link is #181818, and neither can drift from its surface later. That
 * also means this rule TIES with the footer rule above at (0,1,2) — deliberately
 * harmless, because both resolve to white down there. */
[class*="elementor-kit-"] .elementor-icon-list-text a { color: inherit; }

/* An inline link inside a paragraph has to be distinguishable WITHOUT colour.
 *
 * Lighthouse `link-in-text-block`, measured on the deployed home page: the
 * kit's link grey #59595B against #181818 body copy is 2.54:1, under the 3:1
 * WCAG needs when colour is the only cue — and these links carry no underline.
 * So "View our size guide" and the seven size links in the FAQ answer are
 * invisible as links to anyone who does not happen to see that grey.
 *
 * An underline satisfies 1.4.1 on its own, so this restores the browser default
 * the Elementor kit removes, and only where it belongs: paragraphs inside a
 * text-editor widget and inside an FAQ toggle's answer. Deliberately NOT a
 * blanket `a { text-decoration: underline }` — that would also underline the
 * nav, the buttons, the footer contact rows and the size-guide cards, none of
 * which are links in a text block and all of which read as chrome, not prose.
 * `text-underline-offset` matches the socket's legal links so the two agree. */
.elementor-widget-text-editor p a,
.elementor-tab-content p a {
  text-decoration: underline;
  text-underline-offset: 2px;
}

/* The socket sat one percentage point inboard of the whole rest of the site.
 * Operator 2026-08-19: "socket not meeting site gutters". Measured: above
 * 1024px the socket carries `--padding-left/right: 6%` while the footer's
 * column container, the header bar and every other section use 5% — so the
 * copyright started 14px right of "Self Storage Location" directly above it at
 * 1440px, and 19px right at 1920px. Close enough to look like a mistake rather
 * than a decision, which is what it was.
 *
 * Only the desktop value is wrong: Elementor's own `@media(max-width:1024px)`
 * rule already drops the socket to 5%, which is why the two agree on tablet and
 * phone. This scopes to `min-width: 1025px` so it changes nothing below that. */
@media (min-width: 1025px) {
  .elementor-35 .elementor-element.elementor-element-8f678ea {
    --padding-left: 5%;
    --padding-right: 5%;
  }
}

@media (max-width: 767px) {
  /* Stacked socket: centre the copyright line (legal links included, since they
   * are part of that sentence now) rather than leaving it ragged under the
   * centred warehouse glyph. */
  .elementor-element-8f678ea { text-align: center; }
}

/* Horizontal scroll at 320px — pre-existing, reported 2026-08-19, fixed on the
 * operator's go-ahead the same day. `camplaneselfstorage@gmail.com` is a
 * 29-character string with no break opportunity in it, so at the narrowest
 * phone widths the footer contact row pushed the document 6px wider than the
 * viewport and the WHOLE PAGE scrolled sideways.
 *
 * `anywhere` rather than `break-word`: `break-word` does not shrink the
 * element's min-content contribution, so a flex/grid track sized from that
 * content still reserves the full unbroken width and the overflow survives.
 * Scoped to the footer's icon-list text — the one place this site puts
 * unbreakable machine strings — so no body copy anywhere else changes how it
 * wraps. */
footer .elementor-icon-list-text { overflow-wrap: anywhere; }

/* SEO §3 contrast: darken light-gray blog-card date meta (#8e8e8e 3.28:1 -> #6a6a6a 5.0:1 on white) */
#blog-list-grid p.elementor-heading-title { color: #6a6a6a !important; }

/* ===========================================================================
 * CLIENT CHANGE REQUEST 2026-08-17 — surface the specials on mobile.
 *
 * Elementor stacks both two-column layouts at <=1024px, dropping the promos
 * (which live in the SECOND column) far down the page. Markup clones are
 * emitted by 00-admin/wire-mobile-promo.py; this file owns which copy shows
 * at which width. Above 1024px NOTHING here applies, so the desktop the
 * client approved renders exactly as before.
 * ======================================================================== */

/* Every clone is hidden by default; each breakpoint below opts ONE of them in. */
.ym-mobile-promo,
.ym-promo-callout { display: none; }

/* --- The red callout card, shared by BOTH popup clones -------------------
 * These are breakpoint-independent on purpose. The mobile card (left column,
 * above the form) and the desktop card (top of the right column) are the same
 * markup emitted by one function in wire-mobile-promo.py, so they must also be
 * the same CSS — a second copy of these rules inside a media query is how the
 * two would silently drift apart the next time one is adjusted. */
.ym-promo-callout {
  margin: 0 0 22px;
  padding: 14px 16px 16px;
  border: 2px solid #DE1B22;
  border-radius: 6px;
  background: rgba(222, 27, 34, 0.06);
  text-align: left;
}
.ym-promo-callout__title {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0 0 10px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .02em;
  color: #DE1B22;
}
.ym-promo-callout__icon { display: inline-flex; }
.ym-promo-callout__icon svg { width: 1em; height: 1em; fill: #DE1B22; }
/* The source promo copy is centred inside its Elementor text widgets, which
 * reads badly against a hanging bullet — left-align inside the card. */
.ym-promo-callout__body,
.ym-promo-callout__body * { text-align: left; }
.ym-promo-callout__body ul {
  margin: 0 0 10px;
  padding-left: 1.2em;
  list-style: disc;
}
.ym-promo-callout__body ul:last-child { margin-bottom: 0; }
.ym-promo-callout__body li { margin: 0; line-height: 1.4; }
/* `<sub>` defaults to `vertical-align: sub`, which pulls the qualifier down out
 * of flow and lands it on top of the next bullet. Reset it to a plain block. */
.ym-promo-callout__body sub {
  display: block;
  vertical-align: baseline;
  bottom: 0;
  margin-top: 3px;
  font-size: .8em;
  line-height: 1.35;
}

/* Hide the ORIGINAL popup promos at EVERY width — a card now covers both
 * breakpoints, so the source bullet widget and its "Current Promos" star row
 * would only ever be a duplicate.
 *
 * `.ym-popup-promo-row` is stamped on that star row by wire-mobile-promo.py —
 * do NOT swap it for a structural selector such as
 * `:has(.elementor-icon-list-text b)`: every contact row bolds its label, so
 * that matches all five and blanks phone/address/hours too.
 *
 * `!important` is load-bearing: Elementor sets `display:flex` on
 * `.elementor-widget-icon-list .elementor-icon-list-item` (specificity 0,2,0),
 * which out-ranks a bare class. Without it the star row survives and the
 * specials read twice. */
.elementor-element-ebdc12b { display: none !important; }
li.elementor-icon-list-item.ym-popup-promo-row { display: none !important; }

/* --- DESKTOP: the card leads the popup's right column --------------------
 * Operator 2026-08-19: the specials should read as their own box above the
 * contact details, not as a 5th icon-list row below them. */
@media (min-width: 1025px) {
  .ym-promo-callout--desktop { display: block; }
}

@media (max-width: 1024px) {
  /* Below 1024px the right column stacks under the whole form, so the desktop
   * card would land exactly where the original promos were buried. Its
   * left-column twin takes over instead. */
  .ym-promo-callout--desktop { display: none; }

  /* --- HOME: promos directly under the header, above the hero photo -------
   * The hero container is `column-reverse` here, so the LAST item in the
   * order sequence paints at the TOP. Ascending order = bottom-to-top, so
   * text(1) -> photo(2) -> band(3) renders as band, photo, text. */
  .elementor-element-68084794 > .elementor-element-551c8334 { order: 1; }
  .elementor-element-68084794 > .elementor-element-3340f95  { order: 2; }
  .ym-mobile-promo {
    display: block;
    order: 3;
    width: 100%;
    padding: 18px 16px 20px;
    text-align: center;
    /* The promo copy is white, because in its original position it sits inside
     * the text column, which carries this same dark overlay. Lifted above the
     * hero the band would otherwise inherit the hero container's own
     * rgb(185,206,255) — white on pale blue is ~1.7:1, a hard WCAG failure.
     * Re-declare the overlay so the copy keeps the contrast it was designed
     * with and the band still reads as part of the site. */
    background-color: rgba(23, 23, 23, 0.85);
  }
  /* Hide the in-column original so the copy is not stated twice. */
  .elementor-element-68084794 .elementor-element-766daf8 { display: none; }

  /* The cloned "Current Promos" heading carries Elementor's OWN alignment on the
   * widget, which out-ranks the band's inherited `text-align:center` — so the
   * heading sat hard left while the copy under it was centred (operator,
   * 2026-08-19: "center header on tablet/mobile for promos"). Scoped to the band
   * clone: the popup callout cards are deliberately left-aligned and must not
   * pick this up. */
  .ym-mobile-promo .elementor-widget-heading,
  .ym-mobile-promo .elementor-heading-title { text-align: center; }

  /* --- RESERVE POPUP: the left-column card, above the form --------------- */
  .ym-promo-callout:not(.ym-promo-callout--desktop) { display: block; }
}

/* ===========================================================================
 * PHONE ONLY — blue utility band: one line, and it scrolls away.
 * Operator 2026-08-19: "on mobile make blue band content fall onto one line
 * and not sticky" + "drop the text, keep the reserve now button and social
 * icons".
 *
 * 767px, not 1024px: Elementor flips this band from `row` to `column` at
 * exactly its mobile breakpoint (measured — 768px still renders one row, 767px
 * stacks). Tablet already looks right and must not be touched.
 *
 * Stacked it cost 139px of a 390px-wide screen, all of it above the fold and
 * all of it pinned. The headline is the one piece that cannot fit on a single
 * line (234px of the 351px usable width on its own), and it is pure
 * exhortation — the button beside it already says what to do.
 * ======================================================================== */
@media (max-width: 767px) {

  /* The band and the group holding the headline+button both stack here; put
   * both back on one axis so the button and the icons share a line.
   *
   * Set Elementor's CUSTOM PROPERTIES, not `flex-direction` directly. Elementor
   * containers declare `flex-direction: var(--flex-direction)` on `.e-con` at a
   * higher specificity than a single element class, so a plain
   * `flex-direction: row` here loses no matter how late the file loads
   * (measured: the band stayed `column`). Writing the variable feeds its own
   * machinery and wins, and it keeps the mobile/tablet values in the same
   * vocabulary the rest of the build uses. Selector mirrors Elementor's own
   * `.elementor-160 .elementor-element.elementor-element-<id>` (0,3,0). */
  .elementor-160 .elementor-element.elementor-element-77c7ac6,
  .elementor-160 .elementor-element.elementor-element-ec55d8c {
    --flex-direction: row;
    --flex-wrap: nowrap;
    --justify-content: center;
    --align-items: center;
    --gap: 16px 16px;
    --row-gap: 16px;
    --column-gap: 16px;
  }
  /* Fixed vertical padding, replacing Elementor's `3%`. Percentage padding
   * resolves against the container's WIDTH, so the band's height grew with the
   * viewport (50px at 320px wide, 77px at 767px) — and the sticky offset below
   * has to be one exact number. Pinning the padding makes the height constant:
   * 31px button + 2x11px = 53px at every phone width. */
  .elementor-160 .elementor-element.elementor-element-77c7ac6 {
    --padding-top: 11px;
    --padding-bottom: 11px;
  }
  /* Only the INNER group loses its width, never the band — the band is the
   * full-bleed blue bar and `--width:auto` collapsed it to its contents. */
  .elementor-160 .elementor-element.elementor-element-ec55d8c { --width: auto; }
  /* "Reserve A Storage Unit Today!" — dropped on phones only. */
  .elementor-160 .elementor-element.elementor-element-89e5902 { display: none; }
  /* The icon row is a flex CHILD now, not a full-width block; stop it claiming
   * the leftover space and pushing the button off-centre. */
  .elementor-160 .elementor-element.elementor-element-d2de26c {
    --width: auto;
    flex: 0 0 auto;
  }

  /* NOT STICKY — the blue band scrolls off; the logo / hamburger / PAY ONLINE
   * bar below it keeps pinning as before.
   *
   * The header is a SINGLE `position: sticky` element, so there is no way to
   * unstick one of its children directly. Pulling the sticky offset up by the
   * band's own height is the standard answer: the header travels with the page
   * until the band has cleared the top, then the rest pins at y=0.
   *
   * -53px is the band's one-line height, held constant by the fixed vertical
   * padding set above (31px button + 2x11px). That pairing is load-bearing: with
   * Elementor's original `3%` padding the height tracked the viewport width and
   * no single offset was right — too small leaves a sliver of blue pinned, too
   * large clips the top of the logo bar. Change one, re-measure the other. */
  header.elementor-location-header { top: -53px; }
}

/* ===========================================================================
 * FAQ accordions — open/close affordance.
 * Operator 2026-08-19: "faq accordians would benefit from open/close icons on
 * the tabs".
 *
 * The BEHAVIOUR fix is a shared bake (initToggles() in static-overrides.js plus
 * its CSS companion) because a dead accordion is a defect on any Elementor
 * rebuild. The GLYPHS are a camplane decision and live here: the source site
 * ships `.elementor-toggle-icon-closed` / `-opened` as EMPTY spans — Elementor
 * renders the pair whether or not an icon is chosen, and this client chose
 * none. Filling them is a deliberate deviation from source, not a fidelity fix,
 * so it must not ride along on the shared template.
 *
 * Nothing here has to handle the swap: widget-toggle.min.css already hides the
 * closed glyph and shows the opened one off `.elementor-active`, which is the
 * class the shared JS sets. Painting both spans is the whole job.
 *
 * Masked SVG rather than a font glyph or a background-image: it inherits size
 * from the box, needs no webfont request, and takes its colour from one
 * declaration below. Brand red matches every other icon on the site — the
 * footer dot-circles, the contact rows, the promo card. */
.elementor-toggle .elementor-toggle-icon-closed,
.elementor-toggle .elementor-toggle-icon-opened {
  width: 0.62em;
  height: 0.62em;
  /* `.elementor-toggle-icon` is `width:1em; float:right`, so the glyph hangs at
   * the top-right of a title that may wrap to two lines. Push it down to sit on
   * the first line's optical centre and pin it to the right edge. */
  margin: 0.2em 0 0 auto;
  background-color: #DE1B22;
}
.elementor-toggle .elementor-toggle-icon-closed {
  -webkit-mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 448 512'><path d='M416 208H272V64c0-17.7-14.3-32-32-32h-32c-17.7 0-32 14.3-32 32v144H32c-17.7 0-32 14.3-32 32v32c0 17.7 14.3 32 32 32h144v144c0 17.7 14.3 32 32 32h32c17.7 0 32-14.3 32-32V304h144c17.7 0 32-14.3 32-32v-32c0-17.7-14.3-32-32-32z'/></svg>") no-repeat center / contain;
          mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 448 512'><path d='M416 208H272V64c0-17.7-14.3-32-32-32h-32c-17.7 0-32 14.3-32 32v144H32c-17.7 0-32 14.3-32 32v32c0 17.7 14.3 32 32 32h144v144c0 17.7 14.3 32 32 32h32c17.7 0 32-14.3 32-32V304h144c17.7 0 32-14.3 32-32v-32c0-17.7-14.3-32-32-32z'/></svg>") no-repeat center / contain;
}
.elementor-toggle .elementor-toggle-icon-opened {
  -webkit-mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 448 512'><path d='M416 208H32c-17.7 0-32 14.3-32 32v32c0 17.7 14.3 32 32 32h384c17.7 0 32-14.3 32-32v-32c0-17.7-14.3-32-32-32z'/></svg>") no-repeat center / contain;
          mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 448 512'><path d='M416 208H32c-17.7 0-32 14.3-32 32v32c0 17.7 14.3 32 32 32h384c17.7 0 32-14.3 32-32v-32c0-17.7-14.3-32-32-32z'/></svg>") no-repeat center / contain;
}
/* The question text runs full-width under the floated icon once it wraps past
 * the first line, so reserve the gutter for the whole block. */
.elementor-toggle .elementor-toggle-title { display: block; padding-right: 0.4em; }

/* ===========================================================================
 * PHONE ONLY — logo bar on ONE line.
 * Operator 2026-08-19: "can we please put the header on one line in mobile
 * view as well, you can reduce the size of the logo to adjust".
 *
 * The source stacks it the same way we did (measured: logo 300x59 at the same
 * coordinates, burger 50x50, button 140x35 — identical geometry), so this is a
 * deliberate improvement like the blue band above it, not a fidelity repair.
 * Nothing here has a "match live" answer to defer to; the sizing below is a
 * design decision.
 *
 * 767px again, matching the band: Elementor keeps the bar on one row at 768px
 * and wraps at 767px. Tablet already reads correctly and is untouched.
 * ======================================================================== */
@media (max-width: 767px) {

  /* The bar was NOT stacking because of `flex-direction` — it was already
   * `row`. It wrapped, and Elementor drives wrapping at this breakpoint from
   * `--flex-wrap-mobile` (`.e-con` sets `flex-wrap: var(--flex-wrap-mobile)`
   * inside its own mobile query), so overriding `--flex-wrap` alone does
   * nothing here. Both are set: the mobile variable is the one that lands, the
   * other keeps the two in agreement if the breakpoint is ever re-cut.
   *
   * `--justify-content` goes back to the `space-between` the bar carries at
   * every other width — Elementor's own ≤767 rule flips it to `center`, which
   * made sense when the three items were on three rows and does not now.
   * Padding drops from 5% (19.5px a side at 390px) to a flat 14px: at these
   * widths every pixel of it comes straight out of the logo. */
  .elementor-160 .elementor-element.elementor-element-b6b112b {
    --flex-wrap-mobile: nowrap;
    --flex-wrap: nowrap;
    --justify-content: space-between;
    --align-items: center;
    --gap: 10px 10px;
    --row-gap: 10px;
    --column-gap: 10px;
    --padding-left: 14px;
    --padding-right: 14px;
    --padding-top: 8px;
    --padding-bottom: 8px;
    --min-height: 0px;
  }

  /* The logo is what gives. It is pinned to `width: var(--container-widget-width,
   * 300px); max-width: 300px` with NO media query, so on a 390px screen it
   * claimed 300px of ~351px usable and forced everything else onto a second
   * row — that single declaration is the whole reason the header stacked.
   *
   * `flex: 0 1 178px` rather than a hard width: 178px is the cap on a roomy
   * phone, and the logo SHRINKS below it on a narrow one instead of pushing the
   * button off-screen. The image follows at `width:100%; height:auto`, so the
   * lockup scales as one piece and never distorts. */
  .elementor-160 .elementor-element.elementor-element-fa18c05 {
    --container-widget-width: auto;
    width: auto;
    max-width: 178px;
    flex: 0 1 178px;
    min-width: 0;
  }
  .elementor-160 .elementor-element.elementor-element-fa18c05 img {
    width: 100%;
    height: auto;
  }

  /* Burger + PAY ONLINE: natural width, pinned right, never the thing that
   * shrinks. `flex: 0 0 auto` is load-bearing — as a shrinkable item the button
   * label wrapped to two lines before the logo gave up any width at all. */
  .elementor-160 .elementor-element.elementor-element-589c4a2 {
    --width: auto;
    --flex-wrap-mobile: nowrap;
    --flex-wrap: nowrap;
    --justify-content: flex-end;
    --align-items: center;
    --gap: 8px 8px;
    --row-gap: 8px;
    --column-gap: 8px;
    width: auto;
    flex: 0 0 auto;
  }
}

/* ---------------------------------------------------------------------------
 * Hero background: a phone-sized variant.
 *
 * The home hero is the LCP element, and it is a CSS background rather than an
 * <img> — which is why the responsive-image work in the build never reached it.
 * A 2048x1152 file was being downloaded and then painted into a ~412px-wide
 * viewport: 333 KB to fill 900 px of device pixels. Lighthouse mobile measured
 * LCP 10.1 s with 2.2 s of that pure Load Delay.
 *
 * 900px wide covers a 3x phone (412 CSS px) with room to spare and costs 87 KB
 * — a quarter of the payload for a band that is 240px tall on a phone. The
 * selector is copied verbatim from the Elementor rule it overrides (post-74),
 * including the :not(), so it matches at identical specificity and wins on load
 * order alone. Change the source image and this rule goes stale silently, so it
 * names the file it shadows.
 *
 * Shadows: `297058929-...-n.webp` (from elementor-css-post-74).
 * Paired with the two media-scoped <link rel="preload"> in index.html — the
 * variant and its preload must be changed together or the phone downloads both.
 * ------------------------------------------------------------------------- */
@media (max-width: 767px) {
  .elementor-74 .elementor-element.elementor-element-3340f95:not(.elementor-motion-effects-element-type-background),
  .elementor-74 .elementor-element.elementor-element-3340f95 > .elementor-motion-effects-container > .elementor-motion-effects-layer {
    background-image: url("../../assets/images/297058929-186299040432757-3570025339694898214-n-900.webp");
  }
}

/* ---------------------------------------------------------------------------
 * Generated pages (404, thank-you) and the two legal pages — put them on the
 * site's design system. Client round 2026-08-19: "the new pages do not match
 * the look and design of the existing content."
 *
 * They didn't, and the measurements said so rather than the screenshots:
 *
 *                    interior pages          404 / thank-you / legal
 *   page title       Oleo Script 60px        Ysabeau Infant 38–42px
 *   title colour     #09114C navy            #DE1B22 red
 *   page surface     #1818180F tint          #FFFFFF
 *   content          white card, r40px       bare centred column
 *   button           pill, radius 99px       rectangle, radius 6px
 *   divider icon     yes                     none
 *
 * Every value below is a `--e-global-*` token read off the interior pages, not
 * a literal copied out of a screenshot. That is the whole point: when the kit
 * is re-exported these pages move WITH the site instead of drifting away from
 * it again, which is how they got here in the first place.
 *
 * `.ym-util*` is the shared generator's markup (07-seo/build-utility-pages.py)
 * and those two files are regenerated output — never hand-edit them, restyle
 * them from here. `.ym-legal*` is camplane's own hand-authored legal markup.
 * They share this block because they are the same problem.
 * ------------------------------------------------------------------------- */

/* The tinted page surface the interior pages float their cards on. The
 * generator paints `.ym-util-main` white; it no longer does so with
 * !important, so plain load order is enough to win here. */
.ym-util-main {
  background: var(--e-global-color-3e4aff0, #1818180F);
}

/* --- the title band ------------------------------------------------------ */
/* Matches the interior hero container (post-39 `3d011af`): centred column,
 * 15px gap, 2.5%/5% padding, sitting directly on the page tint. */
.ym-util .ym-util-hero,
.ym-legal-hero {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 15px;
  padding: 2.5% 5%;
  text-align: center;
}

/* Oleo Script, navy, at the kit's own responsive size — the kit re-declares
 * --e-global-typography-primary-font-size at 1024px and 767px, so this tracks
 * the breakpoints for free instead of needing its own media queries. */
.ym-util .ym-util-hero h1,
.ym-legal-hero h1 {
  margin: 0;
  font-family: var(--e-global-typography-primary-font-family, "Oleo Script"), Sans-serif;
  font-size: var(--e-global-typography-primary-font-size, 60px);
  font-weight: var(--e-global-typography-primary-font-weight, 400);
  /* The one token deliberately NOT inherited. The kit's primary transform is
   * `capitalize`, which is invisible on every interior page because their
   * titles are already title-case ("Storage Unit Size Guide") — but it renders
   * "Terms of Use" as "Terms Of Use". These four headings are authored text
   * (two by hand, two from the generator's config), so what is written is what
   * should ship; a transform that silently rewrites it is a trap the next
   * heading walks into as well. Nothing about the rendering of the existing
   * pages changes, because none of their titles contain a lowercase word. */
  text-transform: none;
  line-height: var(--e-global-typography-primary-line-height, 1em);
  letter-spacing: var(--e-global-typography-primary-letter-spacing, 0px);
  color: var(--e-global-color-85bbc00, #09114C);
}

/* The interior subtitle style (`a54abed`): Ysabeau Infant 23px, body colour. */
.ym-util .ym-util-hero .ym-util-lead,
.ym-legal-hero .ym-legal-updated {
  margin: 0;
  max-width: 75%;
  font-family: var(--e-global-typography-8912556-font-family, "Ysabeau Infant"), Sans-serif;
  font-size: var(--e-global-typography-8912556-font-size, 23px);
  font-weight: var(--e-global-typography-8912556-font-weight, 400);
  line-height: var(--e-global-typography-8912556-line-height, 26px);
  color: var(--e-global-color-text, #181818);
}

/* The garage-door divider every interior hero closes with. It is an 8 KB inline
 * <svg> on those pages; extracted once to /assets/images/ so these two get it
 * as a cached request instead of 16 KB of duplicated markup — and so the
 * generated pages, whose HTML this stylesheet cannot touch, can have it at all.
 * Height 30px and the -6px tuck match the interior icon widget (`bd432fd`). */
.ym-util .ym-util-hero::after,
.ym-legal-hero::after {
  content: "";
  display: block;
  width: 60px;
  height: 30px;
  margin-bottom: -6px;
  background: url("../../assets/images/ym-hero-divider.svg") center/contain no-repeat;
}

/* --- contain the card's bottom margin ------------------------------------- */
/* `.ym-util-main` is a plain block with no padding or border, so the card's
 * `margin-bottom: 5%` below collapses THROUGH its bottom edge and lands outside
 * the painted box — a 72px band (5% of 1440px) between the gray page surface and
 * the navy footer, painted with whatever <html> has. On /404 and /thank-you that
 * is harmless because the generator pins <html> to the surface colour there; the
 * legal pages predate that rule, so <html> stayed white and the client saw a
 * white stripe above the footer on /privacy-policy/ and /terms/.
 *
 * flow-root establishes a block formatting context, which keeps the margin
 * inside `main` — the 72px of breathing room is unchanged, it just paints in the
 * page surface instead of white. Fixing the SPACING (removing the margin) would
 * have been the wrong repair: the gap is wanted, the colour was the defect.
 * Also baked into shared/scripts/07-seo/build-utility-pages.py so a regenerated
 * page keeps it and no other client rediscovers this. */
.ym-util-main { display: flow-root; }

/* --- the content card ---------------------------------------------------- */
/* The interior white card (`21627206` and its siblings): radius 40px, the same
 * 44px-blur shadow, 3% padding. `--container-max-width` is the kit's own 1660px
 * page width, so the card lines up with the sections above and below it. */
.ym-util .ym-util-body,
.ym-legal {
  display: block;
  box-sizing: border-box;
  width: 90%;
  max-width: var(--container-max-width, 1660px);
  margin: 25px auto 5%;
  padding: 3%;
  background: var(--e-global-color-43e86c6, #FFFFFF);
  border-radius: 40px;
  box-shadow: 0px 4px 44px 0px rgba(0, 0, 0, 0.1);
}

/* `.ym-util` is a flex column with `align-items:center`, so its children are
 * shrink-to-fit. The hero has to claim the full width explicitly or its own
 * `max-width:75%` subtitle resolves against a box sized by that subtitle —
 * circular, and it settled at a width that broke "Your request is on its way."
 * across two lines. The body block escapes this only because it sets a width. */
.ym-util .ym-util-hero { width: 100%; }

/* Both cards keep a reading measure rather than running the kit's full 1660px:
 * legal pages because the copy is long-form, utility pages because a 1660px
 * card holding four short centred lines reads as an empty box. */
.ym-legal { max-width: 900px; text-align: left; line-height: 1.7; }
.ym-util .ym-util-body { max-width: 900px; text-align: center; }
.ym-util .ym-util-sub { max-width: 700px; }
.ym-legal-hero h1 { text-align: center; }

/* Body copy = the interior text-editor style. Young Serif for the section
 * headings, exactly as the services-page cards use it. */
.ym-util .ym-util-body p,
.ym-legal p,
.ym-legal li {
  font-family: var(--e-global-typography-8912556-font-family, "Ysabeau Infant"), Sans-serif;
  font-size: var(--e-global-typography-8912556-font-size, 23px);
  line-height: var(--e-global-typography-8912556-line-height, 26px);
  color: var(--e-global-color-text, #181818);
}
.ym-legal p, .ym-legal li { line-height: 1.55; }
.ym-legal h2 {
  font-family: var(--e-global-typography-secondary-font-family, "Young Serif"), Sans-serif;
  font-size: var(--e-global-typography-secondary-font-size, 40px);
  font-weight: var(--e-global-typography-secondary-font-weight, 500);
  line-height: var(--e-global-typography-secondary-line-height, 45px);
  letter-spacing: var(--e-global-typography-secondary-letter-spacing, -0.5px);
  color: var(--e-global-color-text, #181818);
  margin: 2.2rem 0 .6rem;
}
.ym-legal ul { padding-left: 1.2rem; }
/* Inline links in legal prose: brand red, and underlined — unlike the socket,
 * this is running body copy where colour alone is not a sufficient affordance
 * (WCAG 1.4.1). #DE1B22 on white is 4.75:1, AA for normal text. */
.ym-legal a {
  color: var(--e-global-color-14a3eb7, #DE1B22);
  text-decoration: underline;
  text-underline-offset: 2px;
}

/* --- the call to action -------------------------------------------------- */
/* The site's button is a PILL (radius 99px), Ysabeau Infant at body size, white
 * on brand red — copied off the header's Pay Online button. The generator's
 * default was a 6px-radius rectangle at 700 weight, which is the single most
 * visible thing that read as "not this site" on the thank-you page. */
.ym-util .ym-util-btn {
  font-family: var(--e-global-typography-8912556-font-family, "Ysabeau Infant"), Sans-serif;
  font-size: var(--e-global-typography-8912556-font-size, 23px);
  font-weight: 400;
  padding: .45em 1.6em;
  border-radius: 99px;
  background: var(--e-global-color-14a3eb7, #DE1B22);
  color: #fff;
}
.ym-util .ym-util-links a,
.ym-util .ym-util-contact a {
  color: var(--e-global-color-14a3eb7, #DE1B22);
}
.ym-util .ym-util-sub,
.ym-util .ym-util-contact { color: var(--e-global-color-text, #181818); }
.ym-util .ym-util-sub { margin-left: auto; margin-right: auto; }

/* Phones: the interior pages drop the card's rounding and side margin so the
 * content keeps its full width — match that rather than shrinking to a
 * 90%-wide card with 3% padding on a 390px screen. */
@media (max-width: 767px) {
  .ym-util .ym-util-body,
  .ym-legal {
    width: 100%;
    margin: 15px 0 8%;
    padding: 8% 6%;
    border-radius: 20px;
  }
  .ym-util .ym-util-hero,
  .ym-legal-hero { padding: 8% 6%; }
  .ym-util .ym-util-hero .ym-util-lead,
  .ym-legal-hero .ym-legal-updated { max-width: 100%; }
}

/* ==========================================================================
   BLOG CARDS — equal height, everything on the same baseline   (client, 2026-08-24)
   ==========================================================================
   Client: "make all the cards the same size and always aligned perfectly."

   Why they were not, measured rather than guessed. `#blog-list-grid` is a CSS
   grid with `align-items:stretch`, so the LOOP ITEM already filled its row — but
   the white card inside it (`.e-con-full`, the element that actually paints the
   background, radius and shadow) is sized by its own content. So the grid cell
   was equal and the visible box was not: 621–651px tall depending on how many
   lines the title and excerpt took, which is the ragged bottom edge in the
   client's screenshot.

   Three things have to be true for "aligned perfectly", and each needs its own
   rule — stretching alone fixes none of them:
     1. the painted card fills its stretched cell            -> height:100%
     2. the title occupies the same space regardless of length -> 3lh clamp
     3. the CTA sits on one baseline across the whole row    -> margin-top:auto

   Measured line boxes at 1440px: title 25px/30px running 1–4 lines (30/90/120px),
   excerpt 20px/26px running 3–4 lines (78/104px). Reserving the natural maximum
   (4 lines) would leave a one-line title floating above 90px of dead space, so
   the title is clamped to 3 and given exactly 3 line-boxes.

   Heights are in `lh`, not px: `lh` resolves against each element's OWN computed
   line-height, so the reservation follows the type down through the breakpoints
   instead of stranding a hardcoded 90px against a smaller font. Where `lh` is
   unsupported the declaration is simply dropped and cards fall back to today's
   ragged-but-working behaviour — a safe degradation, not a broken layout.

   Scoped to >=768px on purpose: below that the grid is a single column, every
   card is alone in its row, and forcing a uniform title height there would only
   punch blank space into a phone layout that already reads correctly.

   ⚠ This is a DELIBERATE deviation from the source site, like the FAQ toggle
   icons — Elementor's loop grid ships ragged cards and the client asked for
   even ones. Card height is no longer the source's 651px by construction. */

@media (min-width: 768px) {
  /* 1. the painted card fills the stretched grid cell */
  #blog-list-grid > .e-loop-item { display: flex; }
  #blog-list-grid > .e-loop-item > .e-con { width: 100%; height: 100%; }

  /* 2. the title always occupies exactly three line-boxes */
  #blog-list-grid .elementor-widget-theme-post-title .elementor-heading-title {
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
    height: 3lh;
  }

  /* the excerpt likewise, so the CTA is not the only thing holding the line.
     The widget JS already trims to 22 words (matching wp_trim_words on the
     source); this bounds what that produces to a fixed three lines. */
  #blog-list-grid .elementor-widget-theme-post-excerpt p {
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
    height: 3lh;
    margin-bottom: 14px;
  }

  /* 3. the CTA drops to one baseline.
     The button widget is a CHILD of the excerpt widget, not a sibling — that
     nesting is why the CTA tracked excerpt length in the first place. Make the
     excerpt widget the growing column and the button its auto-margined tail. */
  #blog-list-grid .elementor-widget-theme-post-excerpt {
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
  }
  #blog-list-grid .elementor-widget-theme-post-excerpt > .elementor-widget-button {
    margin-top: auto;
  }
}

/* ── Header icon row: Yelp / BBB / Amarillo Chamber ──────────────────────────
   Added 2026-08-24 (SEO pass). Those three profiles were listed in the home
   page's `sameAs` schema but linked from no page — a trust signal the crawler
   received and the visitor never did.

   Why this block has to exist at all: Elementor styles icon widgets by their
   INDIVIDUAL element id, not by the `.elementor-icon` class they share. There
   is no per-row rule to inherit, so a new widget with an id Elementor never
   generated CSS for falls back to the document defaults — measured 50x50 and
   #181818 against the originals' 30x30 white. The wiring script's first draft
   assumed inheritance and the icon-row probe caught it.

   Matched to the Facebook/phone widgets (30px). The Google-Maps mark is 25px
   because its pin glyph fills more of a 512 viewBox; the BBB seal and Chamber
   building are drawn on the same 512 grid, so they take 25px too and the Yelp
   brand mark takes 30px like the other Font Awesome glyph. That is what makes
   them look the same size, which is the actual goal — not equal numbers. */
.elementor-160 .elementor-element[data-id^="ym-"] > .elementor-widget-container {
  margin: 0 0 -6px 0;
  padding: 0;
}
.elementor-160 .elementor-element[data-id^="ym-"] .elementor-icon-wrapper {
  text-align: center;
}
.elementor-160 .elementor-element[data-id^="ym-"] .elementor-icon {
  font-size: 30px;
  color: #fff;
}
.elementor-160 .elementor-element[data-id^="ym-"] .elementor-icon svg {
  height: 30px;
  width: 30px;
  fill: #fff;
}
/* 512-grid glyphs sit optically larger than the 448/384 Font Awesome marks. */
.elementor-160 .elementor-element[data-id="ym-bbb-org"] .elementor-icon svg,
.elementor-160 .elementor-element[data-id="ym-amarillo-chamber-org"] .elementor-icon svg {
  height: 25px;
  width: 25px;
}
.elementor-160 .elementor-element[data-id^="ym-"] .elementor-icon:hover svg {
  fill: var(--e-global-color-14a3eb7, #f0c419);
}

/* ── Home: customer reviews section ──────────────────────────────────────────
   Added 2026-08-24 (SEO pass, §7 Trust & Legal). Markup is generated by
   00-admin/wire-reviews.py — edit the script, never the emitted HTML.

   Sized so that swapping the placeholder for the real Trustindex/GBP embed does
   not move the page: min-height reserves the slot, which keeps CLS at 0 when a
   third-party widget paints late. That is the whole reason for the min-height —
   this site measured CLS 0.005 and a late-loading widget is exactly what
   destroys that number. */
.ym-reviews {
  /* ⚠ LOAD-BEARING — this is a `display` declaration in disguise.
     Elementor's base rule is `.e-con,.e-con>.e-con-inner{display:var(--display)}`
     and it emits `--display:flex` per ELEMENT ID, e.g.
     `.elementor-element-691ebf9{--display:flex;...}`. `ym-reviews` is an id
     Elementor never generated, so --display is unset, the declaration is invalid
     at computed-value time, and `display` — not being inherited — falls back to
     its INITIAL value, `inline`. The container then fragmented into 3 inline
     boxes and painted stray slivers down the left edge of the page. Invisible
     while the background was white; obvious the moment it was coloured.
     Set the VARIABLE, never `display` directly — the same trap as
     --flex-direction elsewhere in this file. .e-con.e-flex already defaults
     --flex-direction:column, so this alone restores the intended column stack. */
  --display: flex;

  padding: 60px 20px;
  text-align: center;
  /* Brand navy, via the palette variable the footer itself uses
     (--e-global-color-85bbc00 = #09114C) so it tracks any palette change.
     The band above resolves to rgba(24,24,24,.06) over white ≈ #f0f0f0; navy
     against that is unambiguous separation. This replaced white, which had in
     turn replaced #f4f4f4 — that one sat within four points of the neighbour
     and the two bands read as one continuous slab. */
  background: var(--e-global-color-85bbc00, #09114c);
}
/* The heading and lead inherit --e-global-color-primary (#181818) from
   Elementor's generic widget rules — unreadable on navy, so both are re-set
   here rather than left to chance. */
.ym-reviews .elementor-heading-title {
  margin-bottom: 8px;
  color: var(--e-global-color-43e86c6, #fff);
}
.ym-reviews .elementor-widget-text-editor,
.ym-reviews .elementor-widget-text-editor p {
  color: var(--e-global-color-43e86c6, #fff);
}
.ym-reviews .elementor-widget-text-editor p {
  max-width: 62ch;
  margin: 0 auto 28px;
}
.ym-reviews__embed {
  max-width: 1140px;
  margin: 0 auto;
  /* Reserve = the Trustindex widget's MEASURED rendered height, not a guess.
     The first pass reserved 260px against an actual 299px desktop / 394px
     mobile, and that 39px desktop shortfall alone took CLS from the site's
     0.005 baseline to 0.034 — still passing, but 7x worse for no reason.
     Re-measure with probes/camplane-reviews-widget.mjs if the widget's layout
     or review count changes materially. */
  min-height: 300px;
}
.ym-reviews__placeholder {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 260px;
  padding: 24px;
  border: 2px dashed #b4b4b4;
  border-radius: 6px;
  color: #4a4a4a;
  font-size: 15px;
  line-height: 1.6;
  background: #fff;
}
.ym-reviews__placeholder > p {
  margin: 0;
  max-width: 60ch;
}
.ym-reviews__placeholder code {
  background: #ececec;
  padding: 1px 5px;
  border-radius: 3px;
  /* Long tokens must be allowed to break, or the 00-admin/wire-reviews.py path
     forces the flex box wider than a 390px viewport. */
  word-break: break-word;
}
@media (max-width: 767px) {
  .ym-reviews { padding: 40px 16px; }
  .ym-reviews__embed { min-height: 400px; }   /* measured 394px at 390px wide */
  .ym-reviews__placeholder { min-height: 220px; }
}
