/* floors.css: owner rulings a skin cannot go under. Loaded after skin.css on purpose. */
/* Photographs are rounded, 6px floor (2026-08-27). A photo that bleeds to the viewport edge is marked cx-bleed and stays square. */
main img:not(.cx-bleed):not(.r-brand-logo):not([class*="logo"]):not([class*="icon"]) { border-radius: max(var(--cx-radius-media, 6px), 6px); }
.cx-bleed img, .cx-bleed { border-radius: 0; }
/* The hamburger's position is NOT here, on purpose. A first version pinned it with margin-left:auto
   and order:2; on a skin that had already placed the button correctly that moved it from the corner
   to 99px away on every page. Position is layout, and layout cannot be floored by a rule that does
   not know the skin's header. The mobile gate names the selector and the gap; that repair stays a
   targeted model edit. */
/* The phone action bar is chassis furniture: hidden above the chassis breakpoint whatever a skin says.
   A visibility rule on an element the skin does not own cannot move skin content. */
@media (min-width: 901px) { .cx-bar { display: none; } }
/* The sticky header and the strip above it are chassis furniture, and their HEIGHT is the chassis's
   business, not a band: every in-page anchor on the site is offset by --cx-head-h, and whatever they
   take comes off the first screen on every page at once. Their inner wraps set the height (.r-hdr-in
   has min-height: var(--cx-head-h), .r-util-in 42px), so a skin that wants a taller header still says
   so there; what is ruled out is vertical padding on the outer element, which is where a section band
   lands when it leaks. The normalizer's theme-is-a-palette correction removes the usual cause; this is
   the same ruling for a selector it does not read. Same shape as the .cx-bar rulings above: a rule on
   furniture the skin does not own cannot move skin content. */
.r-hdr.r-hdr, .r-hdr2.r-hdr2, .r-mast.r-mast, .r-util.r-util, .r-topbar.r-topbar { padding-block: 0; }
/* Caption children stay in the caption's flow. A skin once wrote .r-tiles [class*="-title"] { position: absolute; bottom: 0 } beside the same rule for -cap, so every tile's title sat on top of its line. Position the caption box, never its children. Two attribute selectors, so this ties the skin's .r-tiles [class*="-title"] on specificity and wins by coming later. */
[class*="-cap"] > [class*="-title"], [class*="-cap"] > [class*="-line"], [class*="-cap"] > [class*="-name"], [class*="-cap"] > [class*="-text"] { position: static; inset: auto; padding: 0; background: none; }
/* Text over a photograph stays readable whatever the skin did to the caption's gradient: a shadow travels with the letters, so a caption that lands on a bright sky still reads. A skin may make it stronger, never absent. */
[class*="-cap"], .r-tile2-cap, .r-tile-cap { text-shadow: 0 1px 2px rgba(0,0,0,.7), 0 0 14px rgba(0,0,0,.55); }
/* A card the renderer draws as a BOX stays a box. A skin may restyle the border, thicken it, colour it
   or take it off every edge; it may not take off ONE edge and leave three. A last-child rule that
   zeroes the right border is a single-row idiom - in one row that edge is the section's own edge, so the
   strip closes itself - and the moment the row count is anything else the last card is a three-sided
   box with its right side open. The owner saw it in a second on a build that had passed 145 checks.
   Restored from the renderer's own token, which is what draws the other three sides. */
.r-step:last-child, .r-metro-card:last-child, .r-svc-card:last-child, .r-tile2:last-child {
  border-right: 1px solid var(--r-line);
}
/* Tap targets: 44px on everything a thumb lands on. */
.r-btn, .r-chip, .r-ledger-link, .r-textlink, .cx-nav a, .cx-bar a, a[href^="tel:"], .cx-form button, .cx-navbtn { min-height: 44px; }
/* The phone bar's labels keep room: padding inside each link and a gap between the two. A skin restyled the bar with neither and the labels touched on every page. */
.cx-bar { gap: 8px; } .cx-bar a { padding-inline: 12px; }
/* The bar's type is the chassis's too. Two labels share 390px; at 15px bold with .1em tracking "GET A FREE ESTIMATE" is wider than its half and runs off the screen, and two repair runs restyled the bar's type in one round and re-broke it in the next. The same specificity as a skin's .cx-bar a, .cx-bar .r-btn rule, loaded after it. */
.cx-bar a, .cx-bar .r-btn { font-size: 14px; letter-spacing: .03em; line-height: 1.15; white-space: normal; text-transform: uppercase; }
.r-chip, .r-ledger-link, .r-textlink { display: inline-flex; align-items: center; }
/* A live mount is empty until the platform fills it. The snippet stamps data-skyhq-state on it and
   the markup carries an honest line beside it; the line goes the moment real content lands. Without
   this the reviews section rendered a heading over 300px of nothing, which the eye is told to ignore
   because it cannot tell an empty mount from a broken one. */
[data-skyhq-state="ready"] + .r-mount-empty { display: none; }
/* The split card is a panel laid over the photograph, meant as the OPPOSITE of its section. It took
   that from the skin's inverse tokens, and every one of three generated skins resolved them so the
   card's fill and its words were both dark inside a dark section: "Priced before we dig" at 1.02:1.
   The section's own text and background are the one pair every skin must keep legible (the page's
   body copy is judged on them), so the card is that pair, swapped - the same construction as a
   solid button. Borders and radius stay the skin's; only fill and ink are ruled, at 0-3-0. */
.r-split .r-split-card.r-split-card { background: var(--r-fg); color: var(--r-bg); }
.r-split .r-split-card.r-split-card strong { color: inherit; }
.r-split .r-split-card.r-split-card span { color: color-mix(in srgb, var(--r-bg) 82%, var(--r-fg)); }
/* NO floor on the service card's scrim or words, deliberately. One was added (076fb9c) for skins
   that darken the scrim and forget the words, and the sixth Cloud Run build showed why it was wrong:
   that skin had redesigned the card with the photograph BESIDE the text, the floor laid a dark
   scrim under text meant for a light card, and three repair rounds fought it (a colour, a darker
   colour, then !important) while the page stayed failed and no re-look could run. A floor that
   rules half of something a skin has deliberately redesigned is the button mistake again. The
   over-photograph check names the element either way, and the repair loop can read the skin's
   intent; a floor cannot. */
/* Colour the caption box; its children INHERIT. The sibling ruling above says position the box and
   never its children, and colour turns out to have the same shape. A skin repainted a tile caption
   with a black gradient and cream words, which is right, and the section's own rule for the TITLE
   inside it still said var(--r-inverse-fg): that skin reads inverse as a cream panel with near-black
   type, so every service name on the page came out near-black on black at 1.02:1 and simply was not
   there. A child that re-declares its colour defeats whatever correction the box just made, so the
   children stop re-declaring it. The accent line keeps its tint by mixing toward the colour it
   inherits, which is the one colour known to work against that backdrop. */
.r-tile2-cap > *, .r-tile-cap > *, .r-carousel-cap > * { color: inherit; }
.r-tile2-cap .r-tile2-line { color: color-mix(in srgb, var(--r-inverse-accent-text, currentColor) 45%, currentColor); }
/* A photo placeholder is the one piece of copy on the page addressed to the OWNER: it says which
   shot is still missing, and it is the reason a site can ship honestly before every photograph
   exists. A skin set it in 12px uppercase at .18em, and at 75 characters that is an instruction
   nobody reads, so the gap it is meant to advertise stays invisible. It is read, not scanned, so it
   takes the reading floor. */
.r-placeholder, .r-placeholder span { font-size: max(14px, var(--cx-body-s, 14px)); letter-spacing: normal; text-transform: none; }
/* The placeholder paints its OWN background and carries its own words, both renderer furniture, so
   both are ruled together and cannot disagree: stripes from the section's own background tones, the
   words in the section's own foreground. The sixth Cloud Run build's skin striped it near-black and
   left the words the chassis's near-black, 1.03:1, on the instruction that tells the owner which
   photograph to take. Ruling the pair, not half of it, is what keeps this from being the button
   mistake: nothing a skin does can leave the two unmatched. */
.r-placeholder.r-placeholder { background: repeating-linear-gradient(135deg, var(--r-surface, var(--r-bg)) 0 12px, var(--r-bg) 12px 24px); color: var(--r-fg); }
.r-placeholder.r-placeholder span { color: inherit; }
/* The reviews note is a SENTENCE ("We do not screen, edit or reorder them, and nobody is paid for
   one...", 117 characters and up), and its class name reads like a label: two of three generated
   skins set it in 12px tracked capitals, which fails the reading floor and the capitals-prose rule
   and says the one honest thing on the reviews page in the least readable type on the site. */
.r-reviews-note { font-size: max(14px, var(--cx-body-s, 14px)); letter-spacing: normal; text-transform: none; line-height: 1.55; }
/* A caption sits ON the photograph, so the photograph has to be big enough to survive it. At two
   tiles to a 390px row each picture is about 131px tall and a two-line caption covers 63% of it,
   which the third acceptance build shipped at 89%: a gallery that had stopped showing the work. The
   section stylesheet says one column on a phone and a skin outranked it by loading later with no
   media query at all. Only when the tiles CARRY captions: an uncaptioned grid has nothing to cover
   and keeps whatever columns the skin chose. */
@media (max-width: 640px) {
  .r-gallery-grid:has(.r-tile-cap), .r-gallery-grid:has([class*="-cap"]) { grid-template-columns: 1fr; }
}
/* A marquee's track is wider than the screen on purpose: the second copy of its words scrolls in
   behind the first. A skin that caps it (max-width on the track or its spans) drops that copy onto a
   second line and the ticker prints itself twice, which is what shipped. */
.r-marquee-track { width: max-content; flex-wrap: nowrap; }
.r-marquee-track > * { max-width: none; }
/* A section is a full-width band. Its gutter is its wrap's width and its measure is inside it; a skin that
   narrows the section element itself (a .r-prose max-width meant for the text: third acceptance build,
   every desktop service page 685px wide and flush left) narrows the band, and the normalizer records the
   removal. This floor is the same ruling for a selector the normalizer does not read, at equal specificity. */
main > section, .r-section { width: auto; max-width: none; }
/* The phone bar's clearance under a first screen sized to the viewport, once. Only the anchored hero between
   641 and 900px is viewport-sized with the bar showing; under 640px it is content-sized, and padding below
   content that already runs past the first screen is a dark band (164px on the third acceptance build, when
   the base sheet reserved the bar on the section AND its wrap). After the skin, so a skin's hero padding
   cannot remove it; the [class] attribute lifts it over a two-class skin selector. */
@media (min-width: 641px) and (max-width: 900px) { main > section.r-hero--anchored:first-child[class] { padding-bottom: calc(var(--cx-bar-h) + var(--cx-gap)); } }
