@charset "UTF-8";

/* ================================================================
   Prime Urban Services — home + service pages. Everything is scoped to
   .pus-home-doc / body.pus-home, so no other page type is touched.
   ================================================================ */

/* ---- SCROLL RESET — read before touching ----------------------
   assets/pus-site.css was baked out of the old single-page app and still
   carries that app's shell rules:
       html, body { height:100%; overflow:hidden }
       #main-content { flex:1; overflow-y:auto }
       @media (max-width:768px){ body { position:fixed } }
   In that app the window never scrolled — one inner container did, and on
   a phone the body was pinned to stop the rubber-band bounce. These pages
   are ordinary documents, so under those rules a phone had nothing to
   scroll. These lines put normal scrolling back, keyed off the class on
   <html> that only these pages set. The !important has to beat a rule
   inside a media query. */
html.pus-home-doc,
html.pus-home-doc body{
  height:auto !important;max-height:none !important;overflow:visible !important;
  position:static !important;transform:none !important;
}
html.pus-home-doc body{display:block !important;min-height:100vh !important;margin:0}
html.pus-home-doc body .page{
  display:block !important;position:static !important;height:auto !important;
  max-height:none !important;overflow:visible !important;transform:none !important
}
html.pus-home-doc body.ph-locked{overflow:hidden !important}
html.pus-home-doc body #main-content{
  height:auto !important;max-height:none !important;overflow:visible !important;flex:none !important
}
/* assets/pus-site.css breaks the footer out of the old app's 40px page
   padding with
       #app-footer{margin-left:-40px !important;width:calc(100% + 80px) !important}
   inside @media(min-width:1024px). On a service or package page — which
   never had that padding — those 80px have always hung off the right of
   the document: 1320px of page in a 1280px window. html{overflow-x:hidden}
   hid the scrollbar, so it showed up only as a footer whose tinted ground
   ran past the edge. pus-footer.php already paints itself full-bleed with
   a box-shadow, which cannot cause a scrollbar, so the breakout has
   nothing left to do. Returned to a plain full-width block. */
html.pus-home-doc body #app-footer{
  margin-top:0 !important;margin-left:0 !important;margin-right:0 !important;
  width:100% !important;max-width:none !important}

html.pus-home-doc{scroll-behavior:smooth}

body.pus-home{
  --pu:#5B2ED4; --pu-d:#4A22B8; --pu2:#8B5CF6; --pu-soft:#F2EDFF; --pu-line:#E4DBFB;
  --ink:#12131A; --ink2:#4B4C5C; --ink3:#8A8B9B;
  --line:#EAEAF0; --soft:#F6F6FA; --card:#fff;
  --green:#1E8E5A; --green-soft:#E8F6EF;
  --blue:#1C6DD0; --blue-soft:#EAF2FE;
  --amber:#B45309; --amber-soft:#FFF6E6;
  --r:14px; --sh:0 1px 2px rgba(18,19,26,.04),0 6px 18px rgba(18,19,26,.05);
  background:#fff;color:var(--ink);
  font-family:'Inter','Inter Fallback','Open Sans',system-ui,-apple-system,'Segoe UI',Roboto,sans-serif;
  -webkit-font-smoothing:antialiased;
}
body.pus-home *,body.pus-home *::before,body.pus-home *::after{box-sizing:border-box}

/* Clear the app-wide control styling before any of this page's own rules run.
   assets/pus-site.css styles bare <button> elements for the old app shell
   with a minimum size around 44px and generous padding. Every control below
   states its own width and height — but a min-width/min-height clamp beats a
   stated width, so each one silently came out larger than designed. That is
   what turned the banner's dot capsule into a 109x50 blob on the banner, and
   it was quietly padding the burger, the filter chips and the arrows too.
   Only the clamps are cleared here; each component still sets its own size,
   so nothing below has to fight this rule. */
body.pus-home button,body.pus-home [class*="ph-"] button{
  min-width:0;min-height:0;max-width:none;max-height:none;
  -webkit-appearance:none;appearance:none;box-sizing:border-box}

/* Same defence for <i> and <em>. This page uses them as plain text spans —
   a second line under a heading, a separator, a count — but icon-font
   frameworks style <i> as a fixed-size box, and a stray `i{width:1.5em}`
   turns the cart bar's text column into a 20px sliver and wraps the price
   onto three lines. Anything here that really needs a size sets its own
   below, and those rules are more specific than this one. */
body.pus-home i,body.pus-home em{
  width:auto;height:auto;min-width:0;min-height:0;max-width:none;max-height:none;
  font-style:normal;font-size:inherit;vertical-align:baseline}
body.pus-home img{max-width:100%}
/* assets/pus-site.css still carries the old app's home screen:
       #page-home{max-width:1200px !important;padding:32px 40px 64px !important}
   Every section on this page already centres itself inside .ph-wrap
   (max-width 1180, 16px gutters), so that outer box did nothing except
   take 40px off each side — which is why the banner and the service grid
   sat 30px inside the header above them and inside the city pages, whose
   container is not called #page-home and never inherited it. The width
   and the side padding are handed back; the bottom padding stays, because
   the sticky cart bar sits over the end of the page. */
body.pus-home #page-home{
  max-width:none !important;width:auto !important;
  padding-left:0 !important;padding-right:0 !important;padding-top:0 !important;
  padding-bottom:150px !important}
.ph-ic{flex:none}
.pus-sr{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}
.ph-hidden{display:none !important}
.ph-wrap{max-width:1180px;margin:0 auto;padding:0 16px}

/* ================= HEADER =================
   Every rule here is anchored on #app-header.ph-header — the class is on the
   header element itself, not on <body>. That matters: assets/pus-site.css
   still carries the old app-shell rule

     #app-header{position:fixed;display:flex;justify-content:space-between;
                 padding:0 16px;height:var(--nav-h);…}

   which turns the header into a flex row of its own, pins it out of the
   document flow and locks it to a fixed height. Anchoring on the header's
   own class beats it on specificity from inside the element rather than
   from the far end of the document, so the layout holds even if the body
   class is ever dropped or that stylesheet loads after this one. The few
   properties that decide the layout outright are marked !important so
   nothing in that file can win them back.

   The header itself carries no padding — the old rule sets some, and it
   would push the inner bar out of line with the page content. Everything
   lives on .ph-hbar, the same 1180px column the content uses. */
#app-header.ph-header{position:sticky !important;top:0;left:auto;right:auto;z-index:80;background:#fff;
  border-bottom:1px solid var(--line);box-shadow:none;
  padding:0 !important;margin:0;width:100%;max-width:100%;
  display:block !important;
  /* the old rule pins this to a fixed 56px; let it grow to fit the bar
     instead, or the logo and buttons spill past the border */
  height:auto !important;min-height:0;max-height:none;transform:none}
.ph-header .ph-hbar{display:flex;flex-wrap:nowrap;align-items:center;gap:10px;width:100%;max-width:1180px;
  margin:0 auto;padding:8px 16px;min-height:60px;min-width:0}
@media(min-width:700px){.ph-header .ph-hbar{gap:12px;min-height:66px}}
/* Explicit order on every child. Without it a stray `order` in the old
   stylesheet could shuffle the row — which is exactly the kind of bug that
   puts the logo in the middle of the header instead of at the left. */
.ph-header .header-logo{order:1}
/* The city chip has to be given an order too. Without one it defaults to 0,
   which is LOWER than the logo's 1 — so it rendered to the LEFT of the
   logo, which is the one thing about this header that had to stay put. */
.ph-header .ph-hcity{order:2}
.ph-header .ph-hsearch{order:3}
.ph-header .ph-nav{order:4}
.ph-header .header-right{order:5}
#app-header.ph-header .header-logo{display:flex;align-items:center;text-decoration:none;flex:0 1 auto;
  margin-right:auto;min-width:0;overflow:hidden;float:none;position:static}
/* One rule per size band, so the logo never crowds the buttons.
   !important on every one of them: assets/pus-site.css ends with
       .header-logo img{height:48px !important;max-width:170px !important}
   and a desktop copy at 52px, so without it the logo stayed 48px tall on
   a 320px screen — which is the squeeze these three lines exist to stop.
   The city pages, which do not load that stylesheet, have shown the 32/40/
   44px ladder all along; this is what makes the two headers the same. */
#app-header.ph-header .header-logo img{display:block;width:auto;
  height:48px !important;max-width:min(52vw,210px) !important;object-fit:contain}
@media(max-width:400px){#app-header.ph-header .header-logo img{height:40px !important;max-width:46vw !important}}
@media(min-width:700px){#app-header.ph-header .header-logo img{height:52px !important;max-width:230px !important}}
.ph-header .ph-hsearch{display:none;flex:1 1 auto;max-width:460px;align-items:center;gap:9px;margin:0 4px 0 8px;
  padding:9px 14px;border-radius:12px;border:1px solid var(--line);background:var(--soft);position:relative;min-width:0}
.ph-header .ph-hsearch .ph-ic{color:var(--ink3)}
.ph-header .ph-hsearch input{flex:1 1 0;width:100%;min-width:0;border:0;outline:0;background:transparent;
  font:500 13.5px 'Inter','Inter Fallback',system-ui,sans-serif;color:var(--ink)}
@media(min-width:1000px){.ph-header .ph-hsearch{display:flex;margin-right:auto}
  #app-header.ph-header .header-logo{margin-right:0}}
.ph-header .ph-nav{display:none;align-items:center;gap:20px;flex:none}
@media(min-width:1160px){.ph-header .ph-nav{display:flex}}
.ph-header .ph-nav a{font:600 13.5px/1 'Inter','Inter Fallback',system-ui,sans-serif;color:var(--ink2);text-decoration:none;padding:8px 0;position:relative;white-space:nowrap}
.ph-header .ph-nav a:hover,.ph-header .ph-nav a.on{color:var(--pu)}
.ph-header .ph-nav a.on::after{content:"";position:absolute;left:0;right:0;bottom:-12px;height:2.5px;border-radius:3px;background:var(--pu)}
#app-header.ph-header .header-right{display:flex;align-items:center;gap:8px;flex:none;position:static;float:none}
/* min-height stated, not left to the padding: assets/pus-site.css gives
   every bare button a 44px minimum, which the home page picked up and
   the city pages — which do not load that stylesheet — did not, so the
   same Call Us button was 44px on one page and 35px on the other. 44px
   is also the tap target this button should have had anyway. */
.ph-callbtn{display:inline-flex;align-items:center;justify-content:center;gap:7px;
  padding:10px 17px;min-height:44px !important;border-radius:999px;
  background:var(--pu);color:#fff;border:0;font:700 13px/1 'Inter','Inter Fallback',system-ui,sans-serif;text-decoration:none}
.ph-callbtn:hover{background:var(--pu-d)}
/* padding:0 stated, not assumed — the app stylesheet puts 14px 22px on a
   bare button and this one sizes itself by width/height alone */
.ph-iconbtn{display:inline-flex;align-items:center;justify-content:center;width:38px;height:38px;padding:0;border-radius:999px;
  border:1px solid var(--line);background:#fff;color:var(--ink2);text-decoration:none;flex:none}
@media(max-width:640px){.ph-header .ph-callbtn span{display:none}.ph-header .ph-callbtn{padding:10px 12px}}
@media(max-width:560px){#app-header.ph-header .header-right>.ph-iconbtn:not(.ph-burger){display:none}}

/* ---- the city, beside the logo ----
   Service, package and area views only — the home page's is above its
   search bar. From 760px up, because below that the row belongs to the
   logo and the call button, and all three of those views carry a city
   button of their own further down the page. */
.ph-hcity{display:none;align-items:center;gap:6px;flex:none;padding:9px 13px;border-radius:999px;
  border:1px solid var(--line);background:#fff;cursor:pointer;color:var(--ink);
  font-family:inherit;font-size:13px;font-weight:700;min-height:42px}
.ph-hcity .ph-ic{color:var(--pu)}
.ph-hcity:hover{border-color:var(--pu)}
.ph-hcity.pick{border-color:var(--pu);color:var(--pu)}
@media(min-width:760px){.ph-hcity{display:inline-flex}}

/* ---- the cart, on a computer only ---- */
.ph-hcart{position:relative;display:none}
/* 760px, the same breakpoint the bottom bar disappears at, so there is
   exactly one cart on screen at every width and never none. */
@media(min-width:760px){.ph-hcart{display:inline-flex}}
.ph-hcart-n{position:absolute;top:-5px;right:-5px;min-width:18px;height:18px;padding:0 5px;
  border-radius:999px;background:#E0245E;color:#fff;font-style:normal;
  font-size:10.5px;font-weight:800;line-height:18px;text-align:center;box-shadow:0 0 0 2px #fff}
.ph-hcart-n.ph-hidden{display:none}

/* ================= CATEGORY ROW =================
   Icons rather than a text menu, and it scrolls sideways on a phone
   instead of wrapping — three ragged rows of tiny labels is what wrapping
   gives you, and nobody reads the third one. */
/* ---- the rail ----------------------------------------------------
   These are the customer's own category names — "Full Home Deep
   Cleaning", not a marketing word like "Cleaning" — so eight of them do
   not fit across a phone. On a phone the row scrolls sideways with a
   fade at the edge that says "more this way" rather than "cut off". On
   a computer the tiles wrap into a centred block instead, because a
   rail that runs off the right of a 1440px screen with no scrollbar is
   a row nobody knows is scrollable.

   THE FADE IS SWITCHED ON BY JAVASCRIPT, not by a width. At 768px every
   tile already fits and the fade was sitting over empty white, telling
   the visitor there was more when there was not. A few lines at the
   bottom of this page compare the row's scroll width with its visible
   width and add .scrolls only when there really is something past the
   edge — which is also right for a catalogue of three categories on a
   phone, where no width-based rule could have known. */
.ph-cats{background:#fff;border-bottom:1px solid var(--line);position:relative}

/* ---- the line above the row ----
   THE SAME HEADING AS EVERY OTHER SECTION ON THIS PAGE, and that is the
   whole fix. It was 15px at weight 800 while "Best offers for you" and
   "Popular home services" are 20px at 700. Smaller AND heavier than the
   headings around it is what made it read as a squashed label rather
   than the start of a section — at 15px the 800 weight has no room to
   breathe and the letters run into each other.

   The numbers below are .ph-sec-h's, copied deliberately: 20/700/-.4 on
   a phone, 24 from 700px up. One scale for every section heading on the
   page is the difference between a page that looks designed and a page
   that looks assembled. */
.ph-cats-h{margin:0;font-size:20px;line-height:1.2;font-weight:700;letter-spacing:-.4px;
  color:var(--ink);text-align:center;padding:22px 0 15px}
@media(min-width:700px){.ph-cats-h{font-size:24px}}
@media(min-width:1000px){.ph-cats-h{letter-spacing:-.6px;padding:30px 0 20px}}

/* The fade belongs to the rail, not to the whole band. On .ph-cats it
   ran the full height and laid a white wedge over the end of the
   heading as well. */
.ph-cats-rail{position:relative}
.ph-cats-rail::after{content:"";position:absolute;right:-16px;top:0;bottom:0;width:34px;display:none;
  pointer-events:none;
  /* three stops, not two. A straight two-stop fade over 42px was already
     half-white where the fourth tile's name sits, so the name looked
     printed on tracing paper rather than simply running off the edge. */
  background:linear-gradient(90deg,rgba(255,255,255,0) 0%,rgba(255,255,255,.55) 45%,#fff 100%)}
.ph-cats-rail.scrolls::after{display:block}
@media(min-width:1000px){.ph-cats-rail.scrolls::after{display:none}}

.ph-cats-row{display:flex;gap:8px;overflow-x:auto;overflow-y:hidden;
  scrollbar-width:none;-webkit-overflow-scrolling:touch;
  padding:0 16px 16px;margin:0 -16px;scroll-snap-type:x proximity}
.ph-cats-row::-webkit-scrollbar{display:none}
@media(min-width:1000px){
  .ph-cats-row{overflow:visible;flex-wrap:wrap;justify-content:center;
    gap:14px 10px;margin:0;padding:0 0 30px}
}

/* ---- one tile ----
   Picture over name, the same shape on a phone and on a computer. It
   used to turn into a horizontal chip above 1000px — icon on the left,
   name beside it — which made the row a line of pills of eight
   different widths, ending in one sliced off by the edge of the
   screen. One shape everywhere is the whole reason this reads as a set
   rather than as eight things that happen to be next to each other. */
.ph-cat{flex:0 0 auto;scroll-snap-align:start;display:flex;flex-direction:column;
  align-items:center;gap:9px;
  /* A stated width, not a minimum. With min-width the label could grow
     the tile: "Kitchen Deep Cleaning" ran wider than its own icon and
     into the gap beside it. Fixed, every tile in the row is the same
     size and the text wraps inside it. */
  width:86px;padding:6px 2px 4px;border-radius:16px;
  text-decoration:none;color:var(--ink2);
  transition:color .15s,transform .15s}
@media(min-width:1000px){.ph-cat{width:104px;gap:11px;padding:8px 4px 6px}}
@media(min-width:1100px){.ph-cat{width:112px}}

/* ---- the picture ----
   A soft tile rather than a bare picture on white: the tile gives each
   one an edge, and the row a rhythm. Neutral, nearly-white, with one
   hairline — the old solid lilac fought every photograph put inside it
   and made the row look like a set of stickers. */
.ph-cat-ic{position:relative;display:grid;place-items:center;width:62px;height:62px;flex:none;
  color:var(--pu);overflow:hidden;border-radius:19px;
  background:#F8F7FC;border:1px solid #ECEAF4;
  box-shadow:0 1px 2px rgba(18,19,26,.045);
  transition:border-color .16s,box-shadow .16s,transform .16s}
@media(min-width:1000px){.ph-cat-ic{width:70px;height:70px;border-radius:22px}}
@media(min-width:1100px){.ph-cat-ic{width:76px;height:76px;border-radius:24px}}
.ph-cat-ic img{width:100%;height:100%;object-fit:cover;display:block;border-radius:inherit}
/* A drawn icon is one colour and can be inverted on hover; a photograph
   would only be tinted, so a tile holding a picture keeps the picture
   and takes the ring instead. */
.ph-cat:hover .ph-cat-ic:not(.pic){background:var(--pu);border-color:var(--pu);color:#fff}
.ph-cat:hover .ph-cat-ic{border-color:var(--pu-line,#E4DBFA);
  box-shadow:0 6px 16px rgba(74,34,184,.13)}
@media(hover:hover){.ph-cat:hover{transform:translateY(-2px)}}
.ph-cat:active{transform:translateY(0)}
.ph-cat:active .ph-cat-ic{transform:scale(.97)}
.ph-cat:focus-visible{outline:2px solid var(--pu);outline-offset:2px}

/* ---- the name ----
   TWO LINES, RESERVED WHETHER THEY ARE NEEDED OR NOT. This is what was
   wrong with the row: "Pest Control" took one line and "Full Home Deep
   Cleaning" took two, so the tiles were different heights and the row
   of names underneath read as ragged. With the height reserved every
   name starts on the same line and the row sits level.

   Two, not three: at three a single long name made every tile in the
   row taller for the sake of one. Nothing is cut in this catalogue —
   the longest name here is "Commercial Space Cleaning" and it fits.

   11.5px, and it stays there. 12px was tried for legibility and "Full
   Home Deep Cleaning" then needed a third line on every phone width and
   came out clipped. Half a point is what keeps the longest name in this
   catalogue whole, so half a point it is. */
.ph-cat-t{font-size:11.5px;font-weight:600;line-height:1.26;text-align:center;
  color:var(--ink2);max-width:100%;min-height:calc(2 * 1.26em);
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;
  overflow:hidden;overflow-wrap:break-word}
@media(min-width:1000px){.ph-cat-t{font-size:13px;line-height:1.3;min-height:calc(2 * 1.3em)}}
.ph-cat:hover .ph-cat-t{color:var(--pu)}

/* The .ph-promise band's rules were here and are gone with it — the
   markup was removed, so leaving nine rules behind for a class nothing
   uses is how a stylesheet fills up with things nobody dares delete. */

/* ================= SPLIT HERO ================= */

/* ==================================================================
   HERO — the band under the header on the home page
   ==================================================================
   The banner, full width, with the search bar across its bottom edge.
   Nothing else: no headline over the picture, no white wash under the
   words to keep them readable, no slide marks in the corner. Whatever
   is in the artwork is what the visitor sees.

   THE BOX TAKES THE BANNER'S OWN PROPORTIONS. --har is written on the
   tag by PHP from the first slide's real pixel size, so object-fit:cover
   has nothing left to crop — a banner with a price and a Book Now
   designed into it arrives whole, which it could not while a headline
   was being drawn on top of it and the box had a height of its own.

   The two guards are min-height and max-height. A 5:1 banner would be a
   90px sliver on a phone with the search bar lying across most of it,
   and a portrait one would fill the screen before a single service was
   in view. Those two get cropped rather than obeyed; every ordinary
   banner shape falls between them and is untouched.
   ================================================================== */
.ph-hero3{position:relative;isolation:isolate;
  /* overflow:hidden so the corners below actually clip the picture. It
     was visible while the search bar hung out of the bottom of this
     box; the bar sits under it now, so nothing needs to escape. */
  overflow:hidden;
  /* SOFT CORNERS. The band still runs edge to edge — the banner keeps
     every pixel of its width — but its four corners are rounded, so it
     reads as a panel laid on the page rather than a strip that happens
     to end where the screen does. The white that shows in the corners
     is the page itself, above and below. */
  border-radius:16px;
  /* width stated outright. With only aspect-ratio and a min-height, a
     4:1 banner on a 390px phone made the browser satisfy the 150px
     minimum by widening the box to 600px — the whole page then scrolled
     sideways. A definite width leaves height as the only thing the
     ratio can decide, which is the intent. */
  width:100%;
  aspect-ratio:var(--har,16/7);
  /* 96px is a floor against nothing at all, not a shape decision: with
     the search bar out from under the picture there is no reason left to
     make a wide banner taller than it is, and every reason not to —
     whatever was designed into it arrives whole. 80px is one quarter of
     a 320px screen — below that a band is not a banner. */
  min-height:80px;max-height:74vh;
  /* nothing hangs out of it any more, so nothing has to be reserved */
  margin-bottom:0;
  /* what shows with no banner uploaded, and behind a photo with
     transparency in it */
  background:linear-gradient(135deg,#F3EFFF 0%,#EDE6FF 46%,#F8F5FF 100%)}
@media(min-width:760px){.ph-hero3{max-height:66vh;border-radius:22px}}
/* There is no "no banner" version of this band: with nothing uploaded
   the section is not rendered at all, so no rule is needed for it. */

.ph-hero3-bg{position:absolute;inset:0;z-index:0;overflow:hidden}
/* the slider's own boxes have to be told they are full height, every one
   of them — including the <a> when a slide has a link on it, which is
   the link in the chain that broke this once before */
.ph-hero3-bg .ph-banner{height:100%;border:0;border-radius:0;background:none}
.ph-hero3-bg .ph-banner-track,
.ph-hero3-bg .ph-banner-item,
.ph-hero3-bg .ph-banner-a{height:100%}
.ph-hero3-bg .ph-banner-item img{width:100%;height:100%;object-fit:cover;display:block}

/* ---- the search bar ----
   Half in the hero and half out of it. That overlap is what makes it
   read as being in front of the page rather than printed on the
   picture, and it is why the hero carries a bottom margin.

   A QUIET FIELD, NOT A PILL WITH A BUTTON ON IT. It was a fat rounded
   pill with a purple square at the end; it is now a plain rounded box
   with a hairline, a grey magnifier and nothing else — about 50px tall
   instead of 64, which is 14px handed back to the page.

   There is no visible Search button any more. Enter submits, the
   keyboard's own Go key submits, and the suggestions appear under the
   field as you type, so the button was a third way to do what two
   things already did — and it was the tallest thing in the bar. The
   real <button> is still in the markup, moved off screen: a form with
   one text field submits on Enter without it in most browsers, but not
   in all of them, and that is not a thing to leave to chance. */

/* ---- the city selector, above the search ----
   It was a chip in the header, beside the logo. It is here now because
   the price of every single thing on the page depends on it and the
   header is where people look for navigation, not for a setting. Above
   the search bar it is the first thing under the picture and it reads
   as the first step: choose where you are, then say what you need.

   Same card as the search bar under it — white, one hairline, one soft
   shadow — so the two read as a pair of controls rather than a coloured
   tag sitting on a white field. The pin gets a small tinted tile of its
   own; that tile is the only colour in the control and it is what makes
   the pin read as a mark rather than a stray glyph. */
.ph-citysel-row{margin:0 0 10px}
@media(min-width:760px){.ph-citysel-row{max-width:640px;margin:0 auto 11px}}
/* every size stated: assets/pus-site.css gives any bare <button> a
   min-height and padding of its own */
.ph-citysel{-webkit-appearance:none;appearance:none;box-sizing:border-box !important;
  display:inline-flex;align-items:center;gap:10px;max-width:100%;
  min-width:172px !important;max-width:100% !important;max-height:none !important;
  min-height:48px !important;padding:7px 12px 7px 7px !important;margin:0 !important;
  border:1px solid var(--line) !important;border-radius:14px !important;
  background:#fff;color:var(--ink);text-align:left;
  box-shadow:0 6px 18px rgba(18,19,26,.075),0 1px 2px rgba(18,19,26,.04);
  font-family:inherit;cursor:pointer;transition:border-color .15s,box-shadow .15s}
/* the pin in its own tile */
.ph-citysel-ic{flex:none;display:grid;place-items:center;width:32px;height:32px;
  border-radius:9px;background:var(--pu-soft,#F2EDFF);color:var(--pu)}
.ph-citysel b{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
  font-size:14px;font-weight:600;letter-spacing:-.1px}
/* pushed to the right by the name's flex:1 above, so there is air between
   the city and the arrow however short the city's name is */
.ph-citysel > .ph-ic:last-child{flex:none;color:var(--ink3)}
.ph-citysel:hover{border-color:#DCD6EE;box-shadow:0 8px 22px rgba(18,19,26,.1)}
.ph-citysel:active{transform:translateY(1px)}
.ph-citysel:focus-visible{outline:3px solid var(--pu);outline-offset:2px}
/* no city chosen yet — the one thing on this band that should catch the
   eye, because every price below it is a base price until it is set */
.ph-citysel.pick{border-color:#F2D8A8 !important;background:#FFFCF5}
.ph-citysel.pick .ph-citysel-ic{background:#FFF1D6;color:var(--amber,#B26A00)}
.ph-citysel.pick b{color:var(--amber,#B26A00)}

.ph-hero3-search{position:relative;z-index:4;margin:14px 0 0}
@media(min-width:760px){.ph-hero3-search{margin-top:18px}}
.ph-h3s{position:relative;display:flex;align-items:center;gap:12px;
  background:#fff;border:1px solid var(--line);border-radius:14px;padding:0 16px;
  box-shadow:0 10px 26px rgba(18,19,26,.11),0 1px 3px rgba(18,19,26,.05)}
/* centred, now that both headings under it are. A 640px bar pinned to
   the left margin under two centred headings was the one thing on the
   band still lining up with something else. */
@media(min-width:760px){.ph-h3s{max-width:640px;margin:0 auto;border-radius:15px;padding:0 18px}}
.ph-h3s:focus-within{border-color:var(--pu-line,#E4DBFA);
  box-shadow:0 10px 26px rgba(18,19,26,.11),0 0 0 3px rgba(91,46,212,.09)}
.ph-h3s-ic{display:flex;flex:none;color:var(--ink3)}
.ph-h3s-inp{position:relative;flex:1 1 auto;min-width:0;display:flex;align-items:center}
.ph-h3s input{width:100%;min-width:0;box-sizing:border-box;border:0;outline:0;background:transparent;
  font-family:inherit;font-size:15px;font-weight:500;color:var(--ink);padding:15px 0;
  -webkit-appearance:none;appearance:none}
@media(min-width:760px){.ph-h3s input{font-size:15.5px;padding:16px 0}}
.ph-h3s input::placeholder{color:var(--ink3);font-weight:500}
.ph-h3s input::-webkit-search-decoration,
.ph-h3s input::-webkit-calendar-picker-indicator{display:none !important;opacity:0}
/* Off screen, not display:none — a hidden submit is not submitted, and
   the point of keeping it is that Enter always works. Every size is
   stated because assets/pus-site.css gives any bare <button> a
   min-height and padding of its own. */
.ph-h3s-go{position:absolute !important;width:1px !important;height:1px !important;
  min-width:0 !important;min-height:0 !important;max-width:none !important;max-height:none !important;
  padding:0 !important;margin:-1px !important;border:0 !important;
  overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap}
/* the suggestion list belongs to the bar's full width */
.ph-h3s .ph-sugg{top:calc(100% + 8px)}
/* the rotating hint has to be the same size and weight as the text it
   stands in for, or the field visibly changes type the moment someone
   starts typing. Its own base rule is 14px — set for a bar that no
   longer exists — so it is matched to this one here. It is grey the
   whole way across now: a purple word inside a quiet field was the one
   loud thing left in it. */
.ph-h3s .ph-sr-hint{font-size:15px;font-weight:500;color:var(--ink3)}
.ph-h3s .ph-sr-hint em{font-weight:500;color:var(--ink2)}
@media(min-width:760px){.ph-h3s .ph-sr-hint{font-size:15.5px}}



/* ================= HERO =================
   Quiet by design: a thin wash instead of a colour block, one accent line
   in the headline, and the promises as a single row of plain statements.
   No photograph unless uploads/banner/hero.jpg exists. */
/* The hero is now just the two pickers and one heading — the picture
   above it does the shouting. */
.ph-hero{background:#fff;border-bottom:1px solid var(--line);padding:6px 0 20px;position:relative}
.ph-hero-txt{max-width:720px;margin:20px 0 0}
.ph-hero h1{margin:0;font-size:23px;line-height:1.2;font-weight:700;letter-spacing:-.6px;color:#101119}
@media(min-width:700px){.ph-hero h1{font-size:29px;letter-spacing:-.9px}}
.ph-hero-sub{margin:8px 0 0;font-size:13.5px;line-height:1.65;color:var(--ink2);max-width:62ch}
.ph-badge{display:inline-flex;align-items:center;gap:7px;padding:6px 12px;border-radius:999px;background:#fff;
  border:1px solid var(--line);font-size:11.5px;font-weight:600;color:var(--ink2)}
.ph-badge .ph-ic{color:var(--pu)}
/* promises: one line each, evenly spaced, hairline dividers */
.ph-trust4{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:20px 0 0;
  padding:16px 0 2px;border-top:1px solid var(--line)}
@media(min-width:820px){.ph-trust4{grid-template-columns:repeat(4,minmax(0,1fr));gap:0}
  .ph-trust4 div+div{border-left:1px solid var(--line);padding-left:18px}}
.ph-trust4 div{display:flex;align-items:center;gap:9px;min-width:0;padding-right:10px}
.ph-trust4 span{color:var(--pu);flex:none;display:flex}
.ph-trust4 b{font-size:12.5px;font-weight:600;line-height:1.35;color:var(--ink2)}

/* ================= BANNER =================
   The image is never cropped: full width, its own height. A banner with
   words designed into it arrives whole instead of sliced. */
.ph-banner{position:relative;border-radius:16px;overflow:hidden;background:var(--soft);border:1px solid var(--line)}
.ph-banner-track{display:flex;transition:transform .5s cubic-bezier(.4,0,.2,1)}
.ph-banner-item{flex:0 0 100%;min-width:0}
.ph-banner-item img{display:block;width:100%;height:auto}
.ph-banner-a{display:block;text-decoration:none}
.ph-banner-a:focus-visible{outline:3px solid var(--pu);outline-offset:-3px}
/* No max-height and no object-fit on purpose: the banner keeps the exact
   proportions of the file that was uploaded. Capping the height would
   either crop the words off a banner or letterbox it with white bars. To
   change how tall it looks, upload a wider image. */
/* Banner dots.
   They sit inside a dark blurred capsule rather than straight on the image:
   white dots on a pale banner were nearly invisible, and a box-shadow was
   not enough to save them. The active one stretches into a bar that fills
   over the autoplay interval, so the banner tells you it is about to move
   instead of just moving. --bdur MUST match the interval in the JS below. */
/* Every size here is stated outright and marked !important, and nothing is
   left to be worked out from padding or content. assets/pus-site.css styles
   plain <button> elements app-wide — a min-width/min-height around 44px and
   generous padding — and this control is two bare buttons in a div, so it
   inherited all of it: the capsule rendered 109x50 instead of 79x30, which
   on a phone is a big dark blob sitting on the banner. Stating width, height,
   min-*, max-*, padding, margin and border explicitly is what makes that
   impossible, whatever that stylesheet says. */
/* ---- the slider's position marks ----
   Thin lines in a row under the picture, not dots on top of it. The one
   showing is longer and fills left to right as its five seconds run, so
   the row says which slide AND how long is left — the same job the old
   dots did, without covering the artwork to do it.

   The button stays 26px tall and 24px wide while the line inside it is
   3px: a 3px line is not a 3px thing to hit, and the tap target is the
   button, not what you can see. */
.ph-banner-dots{--bdur:5s;display:flex;justify-content:center;align-items:center;gap:4px;
  margin:9px 0 0;padding:0;font-size:0;line-height:0}
.ph-banner-dots button{-webkit-appearance:none;appearance:none;cursor:pointer;flex:none;
  display:flex;align-items:center;justify-content:center;box-sizing:border-box !important;
  width:26px !important;height:22px !important;
  min-width:0 !important;min-height:0 !important;max-width:none !important;max-height:none !important;
  padding:0 !important;margin:0 !important;border:0 !important;border-radius:4px !important;
  background:transparent !important;font-size:0;line-height:0}
.ph-banner-dots button.on{width:44px !important}
.ph-banner-dots button i{display:block;box-sizing:border-box !important;flex:none;
  width:18px !important;height:3px !important;min-width:0 !important;min-height:0 !important;
  border-radius:999px;position:relative;overflow:hidden;font-size:0;line-height:0;
  background:var(--line);transition:width .32s cubic-bezier(.4,0,.2,1),background .32s}
.ph-banner-dots button:hover i{background:#CFCFDC}
.ph-banner-dots button.on i{width:36px !important;background:var(--pu-line)}
.ph-banner-dots button.on i::after{content:"";position:absolute;left:0;top:0;bottom:0;width:0;
  border-radius:999px;background:var(--pu);animation:ph-bfill var(--bdur) linear forwards}
.ph-banner-dots.paused button.on i::after{animation-play-state:paused}
.ph-banner-dots button:focus-visible{outline:2px solid var(--pu);outline-offset:1px}
@keyframes ph-bfill{from{width:0}to{width:100%}}
/* someone who has asked for less motion gets a plain filled bar and no
   sliding: the control still shows which banner is on screen */
@media(prefers-reduced-motion:reduce){
  .ph-banner-track{transition:none}
  .ph-banner-dots button i{transition:none}
  .ph-banner-dots button.on i::after{animation:none;width:100%}
}


/* The rotating hint sits over the empty field rather than in the
   placeholder attribute, because a placeholder cannot be animated. It is
   aria-hidden and the input keeps its real <label>. */
.ph-sr-hint{position:absolute;left:0;right:0;top:50%;transform:translateY(-50%);
  pointer-events:none;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
  font:500 14px/1.3 'Inter','Inter Fallback',system-ui,sans-serif;color:var(--ink3)}
.ph-sr-hint[hidden]{display:none}
.ph-sr-hint em{display:inline-block;font-style:normal;font-weight:600;color:var(--pu);
  transition:transform .26s cubic-bezier(.4,0,.2,1),opacity .26s}
.ph-sr-hint em.out{opacity:0;transform:translateY(-9px)}
.ph-sr-hint em.pre{transition:none;opacity:0;transform:translateY(9px)}

/* someone who has asked for less motion gets one steady name, not a carousel */
@media(prefers-reduced-motion:reduce){.ph-sr-hint em{transition:none}}
.ph-sugg{position:absolute;left:0;right:0;top:calc(100% + 8px);background:#fff;border:1px solid var(--line);
  border-radius:14px;box-shadow:0 18px 40px rgba(18,19,26,.16);padding:6px;z-index:90;display:none;max-height:330px;overflow-y:auto}
.ph-hsearch .ph-sugg{top:calc(100% + 10px)}
.ph-sugg.on{display:block}
.ph-sugg a{display:flex;align-items:center;gap:10px;padding:9px 10px;border-radius:10px;text-decoration:none;
  font:500 13.5px 'Inter','Inter Fallback',system-ui,sans-serif;color:var(--ink2)}
.ph-sugg a:hover{background:var(--pu-soft);color:var(--pu)}
.ph-sugg span{color:var(--pu);display:flex}
.ph-sugg em{font-style:normal;margin-left:auto;font-size:11px;color:var(--ink3)}

/* ================= MENU DRAWER ================= */
.ph-burger{flex-direction:column;gap:4px}
.ph-burger span{display:block;width:16px;height:2px;border-radius:2px;background:var(--ink2)}
/* ==================================================================
   OVERLAYS SIT ABOVE THE HEADER — z-index 400 and up
   ==================================================================
   assets/pus-site.css, left over from the old app shell, contains

       #app-header{ z-index: 300 !important; }

   and !important beats this file whatever it writes, so the header has
   been painting at 300 the whole time while this page believed it was at
   80. Everything that is supposed to cover the page was written below
   that number — the menu drawer at 210, the city picker and the package
   sheet at 200 — so opening any of them left the logo, the search box and
   the buttons drawn on top of it. That is the overlap.

   Rather than fight an !important in a stylesheet this project does not
   own, the overlays move above it. They are full-screen covers; being the
   highest thing on the page is what they are for.
   ================================================================== */
.ph-drawer{position:fixed;top:0;right:0;bottom:0;left:0;inset:0;z-index:420;background:rgba(15,12,35,.45);
  display:flex;justify-content:flex-end;opacity:0;transition:opacity .2s}
.ph-drawer[hidden]{display:none}
.ph-drawer.on{opacity:1}
.ph-drawer-in{width:min(340px,88vw);height:100%;background:#fff;display:flex;flex-direction:column;
  overflow-y:auto;transform:translateX(100%);transition:transform .24s cubic-bezier(.4,0,.2,1);
  box-shadow:-18px 0 44px rgba(18,19,26,.18)}
.ph-drawer.on .ph-drawer-in{transform:none}
.ph-drawer-h{display:flex;align-items:center;padding:15px 16px;border-bottom:1px solid var(--line);position:sticky;top:0;background:#fff;z-index:2}
.ph-drawer-h b{font-size:15px;font-weight:800}
.ph-drawer-h button{margin-left:auto;border:0;background:var(--soft);width:32px;height:32px;border-radius:50%;
  display:grid;place-items:center;color:var(--ink2);cursor:pointer}
.ph-drawer-city{display:flex;align-items:center;gap:10px;width:100%;padding:13px 16px;border:0;border-bottom:1px solid var(--line);
  background:var(--pu-soft);cursor:pointer;text-align:left;font-family:inherit}
.ph-drawer-city>.ph-ic{color:var(--pu)}
.ph-drawer-city i{font-style:normal;display:block;font-size:10.5px;color:var(--ink3)}
.ph-drawer-city b{display:block;font-size:14px;font-weight:700;color:var(--ink)}
.ph-drawer-city em{font-style:normal;margin-left:auto;font-size:12px;font-weight:700;color:var(--pu)}
.ph-drawer-nav{display:flex;flex-direction:column;padding:8px 8px}
.ph-drawer-nav a{display:flex;align-items:center;gap:12px;padding:12px 12px;border-radius:11px;text-decoration:none;
  font:600 14px 'Inter','Inter Fallback',system-ui,sans-serif;color:var(--ink2)}
.ph-drawer-nav a:hover{background:var(--soft);color:var(--pu)}
.ph-drawer-nav a .ph-ic{color:var(--pu)}
.ph-drawer-nav.small a{font-size:13px;font-weight:500;padding:10px 12px}
.ph-drawer-ic{color:var(--pu);display:flex;flex:none}
.ph-drawer-nav em{font-style:normal;margin-left:auto;font-size:11px;color:var(--ink3);font-weight:600}
.ph-drawer-n{margin-left:auto;min-width:20px;height:20px;padding:0 6px;border-radius:999px;background:#EF4444;color:#fff;
  font:800 11px/20px 'Inter','Inter Fallback',system-ui,sans-serif;text-align:center}
.ph-drawer-sec{padding:14px 20px 4px;font-size:10.5px;font-weight:800;letter-spacing:.8px;text-transform:uppercase;color:var(--ink3);
  border-top:1px solid var(--line);margin-top:6px}
.ph-drawer-foot{margin-top:auto;display:grid;grid-template-columns:1fr 1fr;gap:9px;padding:14px 16px 20px;border-top:1px solid var(--line)}
.ph-drawer-btn{display:inline-flex;align-items:center;justify-content:center;gap:7px;padding:12px;border-radius:11px;
  background:var(--pu);color:#fff;font:700 13px 'Inter','Inter Fallback',system-ui,sans-serif;text-decoration:none}
.ph-drawer-btn.wa{background:#1E8E5A}
/* v241 — MENU DRAWER, ALIGNED.
   One left edge for everything: every row (the menu, the service list)
   has its icon in the same 36px tile at the same x, and its text starts
   on the same line. body.pus-home in front of every rule so the page's
   own resets (i/em font-size:inherit, bare-button styles from the old
   site stylesheet) cannot reach in — that is what made "Service in"
   as big as the city name and squeezed the city row. */
body.pus-home .ph-drawer-in{width:min(340px,88vw)}
/* The real cause of the squashed rows: the panel is a flex column that
   scrolls, so when the list is taller than the screen every row was
   allowed to SHRINK to make it fit — the header, the city row and the
   links all lost height. Nothing in it may shrink; the panel scrolls. */
body.pus-home .ph-drawer-in > *{flex-shrink:0}
body.pus-home .ph-drawer-h{height:60px;padding:0 14px 0 20px;border-bottom:1px solid #EFEDF5}
body.pus-home .ph-drawer-h b{font-size:17px;font-weight:800;color:#14151D;letter-spacing:-.2px}
body.pus-home .ph-drawer-h button{width:36px;height:36px;background:#F4F3F8;color:#4B4C5C}
body.pus-home .ph-drawer .ph-drawer-city{display:flex;align-items:center;gap:12px;width:auto;margin:14px 14px 6px;padding:10px 12px;
  min-height:60px;border:1px solid #E4DBFB;border-radius:14px;background:#F6F2FF;text-align:left;box-sizing:border-box;line-height:1.2}
body.pus-home .ph-drawer .ph-drawer-city>.ph-ic{width:36px;height:36px;padding:9px;border-radius:10px;background:#fff;color:var(--pu);
  box-sizing:border-box;box-shadow:0 1px 3px rgba(91,46,212,.12)}
body.pus-home .ph-drawer .ph-drawer-city span{display:flex;flex-direction:column;gap:2px;min-width:0}
body.pus-home .ph-drawer .ph-drawer-city i{display:block;font-size:11.5px;line-height:1.2;font-weight:500;color:#8A8B9B}
body.pus-home .ph-drawer .ph-drawer-city b{display:block;font-size:15px;line-height:1.25;font-weight:700;color:#14151D;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
body.pus-home .ph-drawer .ph-drawer-city em{flex:none;margin-left:auto;padding:7px 12px;border-radius:999px;background:#fff;
  font-size:12.5px;line-height:1;font-weight:700;color:var(--pu);box-shadow:0 1px 3px rgba(91,46,212,.12)}
body.pus-home .ph-drawer .ph-drawer-nav{display:flex;flex-direction:column;gap:2px;padding:6px 10px}
body.pus-home .ph-drawer .ph-drawer-nav a{display:flex;align-items:center;gap:14px;min-height:50px;padding:7px 10px;border-radius:12px;
  font-size:15px;font-weight:600;line-height:1.3;color:#2B2C38;box-sizing:border-box}
body.pus-home .ph-drawer .ph-drawer-ic{flex:none;display:grid;place-items:center;width:36px;height:36px;border-radius:10px;
  background:#F4F0FF;color:var(--pu)}
body.pus-home .ph-drawer .ph-drawer-ic svg{width:18px;height:18px;display:block}
body.pus-home .ph-drawer .ph-drawer-nav a:hover,
body.pus-home .ph-drawer .ph-drawer-nav a.on{background:#F4F0FF;color:var(--pu)}
body.pus-home .ph-drawer .ph-drawer-nav a:hover .ph-drawer-ic,
body.pus-home .ph-drawer .ph-drawer-nav a.on .ph-drawer-ic{background:var(--pu);color:#fff}
body.pus-home .ph-drawer .ph-drawer-nav a .ph-drawer-ic .ph-ic{color:inherit}
body.pus-home .ph-drawer .ph-drawer-nav.small a{min-height:46px;padding:5px 10px;font-size:14px;font-weight:500;color:#4B4C5C}
body.pus-home .ph-drawer .ph-drawer-nav.small .ph-drawer-ic{width:36px;height:34px;background:#F7F5FC}
body.pus-home .ph-drawer .ph-drawer-nav.small .ph-drawer-ic svg{width:17px;height:17px}
body.pus-home .ph-drawer .ph-drawer-n{margin-left:auto}
body.pus-home .ph-drawer .ph-drawer-sec{margin:10px 20px 0;padding:16px 0 6px;border-top:1px solid #EFEDF5;
  font-size:11px;font-weight:700;letter-spacing:.8px;color:#8A8B9B}
body.pus-home .ph-drawer .ph-drawer-foot{position:sticky;bottom:0;background:#fff;gap:10px;padding:12px 14px calc(14px + env(safe-area-inset-bottom));
  border-top:1px solid #EFEDF5}
body.pus-home .ph-drawer .ph-drawer-btn{height:46px;padding:0 12px;border-radius:12px;font-size:14px}

/* ================= SECTION HEADS ================= */
.ph-sec{margin:30px 0}
@media(min-width:900px){.ph-sec{margin:42px 0}}
.ph-sec-h{display:flex;align-items:center;justify-content:space-between;gap:14px;margin:0 0 14px}
.ph-sec-h h2,.ph-sec-h h1{margin:0;font-size:20px;line-height:1.2;font-weight:700;letter-spacing:-.4px}
@media(min-width:700px){.ph-sec-h h2,.ph-sec-h h1{font-size:24px}}
.ph-sec-h p{margin:5px 0 0;font-size:12.5px;color:var(--ink3)}
.ph-sec-h>a,.ph-sec-h>span.ph-note{font-size:12.5px;font-weight:700;color:var(--pu);text-decoration:none;
  white-space:nowrap;display:inline-flex;align-items:center;gap:6px}

/* ---- centred section header (services) ------------------------------
   A small label above a large heading, with the city picked out in the
   brand colour and underscored by a hand-drawn-looking sweep. The sweep
   is a flattened circle showing only its bottom edge — no image, so it
   inherits the colour and scales with the text. */
.ph-sech{text-align:center;margin:0 0 20px}
.ph-eyebrow{display:block;font:600 11px/1 'Inter','Inter Fallback',system-ui,sans-serif;letter-spacing:2.4px;
  text-transform:uppercase;color:var(--ink3)}
/* clamp rather than a fixed size: "Popular home services in Visakhapatnam"
   at 24px ran into both edges of a 360px phone. */
.ph-sech-t{margin:11px 0 0;font:700 clamp(20px,5.4vw,24px)/1.24 'Inter','Inter Fallback',system-ui,sans-serif;
  letter-spacing:-.6px;color:#101119;text-wrap:balance}
.ph-sech-t em{font-style:normal;position:relative;color:var(--pu);white-space:nowrap}
.ph-sech-t em::after{content:"";position:absolute;left:-2%;right:-2%;bottom:-.22em;height:.34em;
  border-bottom:3px solid var(--pu);border-radius:50%;opacity:.65;pointer-events:none}
@media(min-width:700px){
  .ph-sech{margin-bottom:26px}
  .ph-sech-t{font-size:33px;letter-spacing:-1px}
  .ph-eyebrow{font-size:11.5px;letter-spacing:3px}
}
@media(min-width:1080px){.ph-sech-t{font-size:38px}}
/* a very soft wash behind the tiles so the section reads as its own band
   without a hard edge. Sits under the content and ignores the pointer. */
.ph-svcsec{position:relative}
/* min(...,100%) and never a percentage over 100: at 120% this decorative
   wash stuck out past the viewport and gave the whole PAGE a horizontal
   scrollbar on every screen under 1280px. */
.ph-svcsec::before{content:"";position:absolute;left:50%;top:-40px;width:min(1100px,100%);height:340px;
  transform:translateX(-50%);pointer-events:none;z-index:0;
  /* fully transparent well before the box edge, or the wash shows its own
     corners and reads as a grey rectangle behind the heading */
  background:radial-gradient(ellipse 42% 55% at 50% 22%,rgba(139,92,246,.11),rgba(139,92,246,0) 62%)}
.ph-svcsec>*{position:relative;z-index:1}

/* ================= SERVICE CARDS ================= */
/* Three across on a phone. Ten services then take four rows instead of
   five and most of the catalogue is visible without scrolling, which is
   the point of the grid. It also means each tile is about 88px wide at
   320px, so everything inside has to shrink with it — see the phone block
   after the card rules. */
/* Two across on a phone, not three. Three left each tile 114px wide on a
   390px screen — the picture inside it was 114x86, which is a thumbnail,
   not something a service is chosen from. Two makes it 175px. The cost is
   two more rows of scrolling, which is the cheaper of the two. */
.ph-svcgrid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}
@media(min-width:600px){.ph-svcgrid{grid-template-columns:repeat(3,minmax(0,1fr));gap:12px}}
/* One fewer per row than before from 900px up. At five across a card was
   218px wide on a 1280 screen, which is smaller than the picture inside
   it deserves; at four it is 277px. Three stays on a phone. */
@media(min-width:900px){.ph-svcgrid{grid-template-columns:repeat(3,minmax(0,1fr));gap:14px}}
@media(min-width:1180px){.ph-svcgrid{grid-template-columns:repeat(4,minmax(0,1fr));gap:16px}}
/* The photo is inset inside the card rather than bleeding to its edges —
   a white margin all round, the way a printed card mounts a picture. It
   also stops a dark photo from colliding with the card's own border. */
/* The photo is inset inside the card rather than bleeding to its edges —
   a white margin all round, the way a printed card mounts a picture. It
   also stops a dark photo from colliding with the card's own border.
   These are the sizes the tile had before it was briefly compacted: at
   16:10 with tighter padding the whole section read as too small. */
.ph-svc{display:block;background:var(--card);border:1px solid var(--line);border-radius:18px;padding:7px;
  text-decoration:none;box-shadow:0 2px 10px rgba(18,19,26,.045);
  transition:transform .16s,box-shadow .16s,border-color .16s}
.ph-svc:hover{transform:translateY(-3px);border-color:#DCD2F8;box-shadow:0 16px 34px rgba(74,34,184,.13)}
.ph-svc-img{position:relative;aspect-ratio:4/3;border-radius:13px;overflow:hidden;
  background:linear-gradient(135deg,#EDE7FF,#F7F3FF)}
.ph-svc-img img{width:100%;height:100%;object-fit:cover;display:block}
/* a small badge, not a full-width sale banner across the picture.
   ---------------------------------------------------------------
   THE DISCOUNT FAMILY. One idea was being told in three colours: a blue
   "40% OFF" badge, a blue "SAVE ₹1,394" chip and a green "You save ₹1,394"
   line, all on the same card. Two rules now, used everywhere:
     purple  = the deal exists  -> "% OFF"   (.ph-ribbon, .pk-off)
     green   = money you keep   -> "SAVE ₹X" (.ph-price em, .pk-save)
   Blue is gone from pricing entirely, and the amount is stated once. */
.ph-ribbon{position:absolute;right:10px;top:10px;padding:4.5px 10px;border-radius:999px;color:#fff;
  font:800 10.5px/1 'Inter','Inter Fallback',system-ui,sans-serif;letter-spacing:.3px;white-space:nowrap;
  background:linear-gradient(135deg,#7C4DEE,#5B2ED4);
  box-shadow:0 4px 12px rgba(74,34,184,.34),0 0 0 1.5px rgba(255,255,255,.5)}
/* .ph-svc-ico (the category badge over the photo) and .ph-svc-n (the
   rating and "From ₹x" line) are no longer rendered — the card is a photo,
   a name and an arrow. */
/* The card's foot: the name on the left, one round arrow on the right.
   No line clamp — a clamped name is a name the visitor cannot read, and
   "Commercial Space Cleaning" was being cut to "Commercial Space…". The
   name wraps to as many lines as it needs; the grid stretches every card
   in a row to the tallest, so the rows stay level regardless. */
/* ALIGNMENT.
   The foot used to centre its contents, so a one-line name sat lower than
   a two-line name on the card beside it and the row of names read as
   ragged. Everything is top-aligned now and the name reserves two lines
   whether it needs them or not, so every name in a row starts on the same
   line and every card in a row is the same height. A third line is still
   allowed — the reserve is a floor, not a cap, so nothing is ever cut. */
.ph-svc-b{display:flex;align-items:flex-start;gap:10px;padding:11px 11px 12px}
.ph-svc-name{flex:1;min-width:0;font-size:13.5px;font-weight:600;line-height:1.35;color:var(--ink);
  overflow-wrap:break-word;min-height:calc(2 * 1.35em)}
.ph-svc-go{width:30px;height:30px;border-radius:50%;flex:none;display:grid;place-items:center;
  margin-top:-1px;background:var(--pu-soft);color:var(--pu);
  transition:background .16s,color .16s,transform .16s}
.ph-svc:hover .ph-svc-go{background:var(--pu);color:#fff;transform:translateX(2px)}
/* The old 639px tile block lived here — padding, radii, an arrow size
   and a name that was 12.5px at 399px, 11.5px at 400px and 12.5px again
   at 520px. Every one of its rules is replaced by the 759px block
   further down, which wins on source order, so all it did was sit there
   waiting to win one back by accident. Gone; the same clean-up was made
   to city-hub.php. */
/* kept: the offers rail and the package cards still use this chip */
.ph-badge-rate{display:inline-flex;align-items:center;gap:3px;padding:3px 6px;border-radius:6px;flex:none;
  background:var(--green);color:#fff;font-size:11px;font-weight:800;line-height:1}

/* ================= BEST OFFERS =================
   A rail of wide cards that scrolls sideways with snap. The card itself is a
   soft pastel gradient; the photo sits on the RIGHT and is faded into that
   gradient with a mask, so it is only ever partly visible and never a
   full-bleed background. Text stays dark ink on the light tint.
   Arrows live in the section header, so they can never cover a card. */
.ph-offrail{position:relative}
.ph-offers{display:flex;gap:14px;overflow-x:auto;padding:2px 2px 10px;scroll-snap-type:x mandatory;
  scroll-behavior:smooth;-webkit-overflow-scrolling:touch}
.ph-offers::-webkit-scrollbar{height:0}
/* THE CARD: a picture, and the name in plain text under it.
   Nothing is written over the photo except one small badge. Words laid
   across a photograph need it darkened to stay readable, which makes
   every picture worse, and they land on whatever happens to be in that
   corner of the shot. Under the picture the name is always the same
   colour on the same background, and the photo is left alone. */
.ph-off{flex:0 0 44%;max-width:260px;border-radius:0;overflow:visible;
  text-decoration:none;scroll-snap-align:start;background:none;border:0;
  display:block;transition:transform .16s}
@media(min-width:520px){.ph-off{flex:0 0 calc((100% - 14px)/2);max-width:none}}
@media(min-width:760px){.ph-off{flex:0 0 calc((100% - 28px)/3)}}
@media(min-width:1060px){.ph-off{flex:0 0 calc((100% - 42px)/4)}}
@media(min-width:1320px){.ph-off{flex:0 0 calc((100% - 56px)/5)}}
@media(hover:hover){.ph-off:hover{transform:translateY(-2px)}}

/* the picture, in its own rounded box */
.ph-off-pic{position:relative;display:block;aspect-ratio:4/3;border-radius:14px;overflow:hidden;
  background:var(--soft);border:1px solid var(--line);box-shadow:0 2px 10px rgba(18,19,26,.05);
  transition:box-shadow .16s,border-color .16s}
@media(hover:hover){.ph-off:hover .ph-off-pic{border-color:#DCD2F8;box-shadow:0 12px 26px rgba(74,34,184,.13)}}
.ph-off-bg{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;display:block;
  transition:transform .4s cubic-bezier(.4,0,.2,1)}
@media(hover:hover){.ph-off:hover .ph-off-bg{transform:scale(1.05)}}
/* the tints still colour the box when a card has no picture of its own,
   so an empty tile is a soft pastel rather than a grey hole */
.ph-off.t1 .ph-off-pic{background:linear-gradient(120deg,#FFF1EC,#FFF7F4)}
.ph-off.t2 .ph-off-pic{background:linear-gradient(120deg,#F1ECFF,#F8F5FF)}
.ph-off.t3 .ph-off-pic{background:linear-gradient(120deg,#E9F7EF,#F4FBF7)}
.ph-off.t4 .ph-off-pic{background:linear-gradient(120deg,#EAF2FE,#F4F8FF)}

/* NOTHING sits on the photo. No badge, no words — the picture is shown
   as it was uploaded, and everything that has to be read is under it. */

/* the name, under the picture, on the page — not on the photo */
.ph-off-name{display:block;margin:10px 2px 0;font:600 13px/1.4 'Inter','Inter Fallback',system-ui,sans-serif;
  color:var(--ink2);overflow-wrap:break-word}
@media(min-width:560px){.ph-off-name{font-size:14px;margin-top:11px}}
@media(hover:hover){.ph-off:hover .ph-off-name{color:var(--pu)}}
/* arrows, in the header next to the heading */
.ph-offhead{display:flex;align-items:center;gap:12px;flex:none}
.ph-offhead>a{font-size:12.5px;font-weight:700;color:var(--pu);text-decoration:none;
  white-space:nowrap;display:inline-flex;align-items:center;gap:6px}
.ph-offnavs{display:inline-flex;align-items:center;gap:8px}
.ph-offnavs[hidden]{display:none}
.ph-offnav{width:34px;height:34px;border-radius:50%;padding:0;flex:none;cursor:pointer;
  border:1px solid var(--line);background:#fff;color:var(--ink2);
  display:inline-flex;align-items:center;justify-content:center;
  transition:background .15s,color .15s,border-color .15s,opacity .15s}
.ph-offnav:hover:not([disabled]){background:var(--pu);color:#fff;border-color:var(--pu)}
.ph-offnav[disabled]{opacity:.35;cursor:default}
/* on a phone you swipe the rail, so the heading stays clean */
@media(max-width:699px){.ph-offnavs{display:none}}
/* the picture stays the same landscape rectangle on a phone — it was
   square here, which is the one shape a photograph of a room does not
   want to be. Only the corner radius and the name step down. */
@media(max-width:519px){
  .ph-off-pic{border-radius:12px}
  .ph-off-name{font-size:12px;margin-top:8px}
}

/* ================= SERVICE PAGE TOP ================= */
/* The band above the packages.
   It used to cost around 140px of a phone screen for very little: the
   breadcrumb wrapped onto two lines with the chevrons stranded, the back
   button sat beside the H1 and pushed the whole title in by 52px, and the
   three meta pills stacked one per row — so the first package started below
   the fold. Now the breadcrumb is one scrolling line with the back button
   folded into it, the title runs full width beside the service photo, and
   the pills share one scrolling row. */
/* The header had three separate rows of white pills floating on a pale
   wash with nothing holding them together, and — the real fault — the
   city selector was styled exactly like the three claims beside it. One
   of those four changes every price on the page; the other three are
   statements. They looked identical, and they sat directly above the
   real filter chips ("All packages / Offers / Price: low to high"), so
   the page showed two near-identical pill rows, only one of them
   clickable. The three claims are now quiet text with an icon, and the
   city control is the only button up here — the one thing that is meant
   to be pressed is the one thing that looks pressable. */
/* The wash and the glow that used to live here are gone: the picture band
   inside is the header now, and a tint behind a photograph only shows at
   its edges, where it reads as a mistake. What is left is a plain white
   block that holds the band and, on a city page, the standfirst under it. */
.ph-svctop{position:relative;background:#fff;border-bottom:1px solid var(--line)}
.ph-svctop>.ph-wrap{position:relative;z-index:1;padding-top:14px;padding-bottom:16px}
/* One labelled back button instead of a breadcrumb trail. The trail read
   badly on a phone — it needed more width than a phone has, so the chevrons
   dropped onto a second line on their own — and it never earned that space:
   a visitor on a service page wants one way back, not four. The button says
   where it goes, which the bare arrow did not. */
/* The back link was a bordered white pill — the heaviest thing on the
   page, at the top, for the least important job on it. It is a way out,
   not a call to action, so it is plain text now and the arrow slides on
   hover. The tap target is kept honest with padding, not with a box. */
.ph-backrow{padding:14px 0 0}
.ph-backlink{display:inline-flex;align-items:center;gap:6px;max-width:100%;
  margin-left:-4px;padding:6px 4px;border:0;background:none;
  color:var(--ink3);text-decoration:none !important;
  font:700 12.5px 'Inter','Inter Fallback',system-ui,sans-serif;transition:color .15s}
.ph-backlink:hover{color:var(--pu)}
.ph-backlink .ph-ic{flex:none;opacity:.7;transition:transform .18s,opacity .15s}
.ph-backlink:hover .ph-ic{transform:translateX(-3px);opacity:1}
.ph-backlink span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}

/* ---- THE PICTURE BAND ----
   The service photograph is the header. The title sits on it, the back
   link and the city control sit along the top of it, and nothing else
   is in the band at all.

   The picture is absolutely positioned and the band's height comes from
   clamp(), not from the image — so the header occupies exactly the same
   space before the photograph has downloaded as after it. A hero that
   grows when its image lands is the single most expensive layout shift
   a page can have, because it moves everything below it.

   A service with no photograph gets the gradient underneath instead of a
   grey rectangle, so the page never looks half-built. */
.ph-svcband{position:relative;overflow:hidden;display:flex;flex-direction:column;
  min-height:clamp(172px,36vw,236px);
  background:radial-gradient(120% 130% at 86% -12%,rgba(139,92,246,.5),transparent 58%),
             linear-gradient(158deg,#2A1B58 0%,#4A22B8 55%,#6D3BE0 100%)}
.ph-svcband-img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;display:block;border:0}
/* Two gradients, not one. The top one keeps the back link and the city
   control readable over a bright sky; the bottom one carries the title.
   A single flat tint dark enough for both would grey out the photograph
   it is there to show. */
.ph-svcband::after{content:"";position:absolute;inset:0;pointer-events:none;
  background:linear-gradient(180deg,rgba(14,7,38,.52) 0%,rgba(14,7,38,.16) 34%,rgba(14,7,38,.30) 62%,rgba(14,7,38,.76) 100%)}
/* width:100% is doing real work here, not tidying up.
   .ph-wrap centres itself with "margin:0 auto". Inside this band it is a
   flex item in a COLUMN container, and a flex item with auto margins on
   the cross axis does not stretch — so instead of filling the band and
   centring its own 1180px of content, the wrapper shrank to fit whatever
   was inside it and floated in the middle. The back link and the city
   control ended up on a track of their own, a few hundred pixels wide,
   that lined up with nothing: not with the chip bar below, not with the
   package cards, not with the footer. Stating the width restores the
   stretch, and every row on the page starts on the same left edge. */
.ph-svcband>.ph-wrap{position:relative;z-index:2;width:100%;display:flex;flex-direction:column;flex:1;
  padding-top:12px;padding-bottom:16px}
@media(min-width:700px){.ph-svcband>.ph-wrap{padding-bottom:20px}}
/* Back link and city control share the top line. A flex row rather than
   absolute placement, so the <noscript> city form — which is a select and
   a button, not a pill — can wrap instead of hanging off the edge. */
.ph-svcband-top{display:flex;align-items:flex-start;justify-content:space-between;gap:10px}
/* flex:1 so the back link gets every pixel the city control does not want.
   Without it the row sized both items to their content and the link, whose
   label can shrink to an ellipsis, was the one that gave way — "All
   services" came out as "All servi…" on a 1300px screen with half the row
   standing empty beside it. */
.ph-svcband .ph-backrow{flex:1 1 auto;min-width:0;padding:0}
.ph-svcband .ph-backlink{color:rgba(255,255,255,.85)}
.ph-svcband .ph-backlink:hover{color:#fff}
.ph-svcband .ph-backlink .ph-ic{opacity:.9}
.ph-svcband-t{margin-top:auto;padding-top:26px;min-width:0}
.ph-svcband-t h1{margin:0;font-size:23px;line-height:1.16;font-weight:700;letter-spacing:-.6px;
  color:#fff;max-width:20ch;text-shadow:0 1px 14px rgba(10,5,30,.35)}
@media(min-width:700px){.ph-svcband-t h1{font-size:32px;letter-spacing:-1px;max-width:24ch}}
/* rating · packages · bookings, on one line and only where the figure is real */
.ph-svcstats{display:flex;align-items:center;gap:7px;flex-wrap:wrap;margin:8px 0 0;
  font-size:12.5px;color:rgba(255,255,255,.78)}
.ph-svcstats .r{display:inline-flex;align-items:center;gap:4px;padding:2px 8px;border-radius:999px;
  background:rgba(255,255,255,.16);color:#fff;font-weight:700}
.ph-svcstats em{font-style:normal;font-weight:700;color:#fff}
.ph-svcstats i{font-style:normal;opacity:.45}
/* ---- the city control ----
   The only button in the header, and it has to look like one, because
   picking a city is what makes every price on the page real. Two lines:
   a small standing label so it is obvious what the word underneath is,
   and the city itself in the brand purple. Full width on a phone,
   auto-width in the title row from 760px up. */
/* In the picture band it is a compact pill on the top line, not a
   full-width block: the band is only so tall, and the title needs the
   room underneath more than this does. min-width:0 and the ellipsis on
   the city name are what keep "Visakhapatnam" from pushing the back
   link off the left edge of a 320px screen. */
.ph-svcact{flex:0 0 auto;min-width:0;max-width:64%}
/* Below 760px the back link and the city control together want more room
   than the band has: the two of them plus the gap came to within four
   pixels of the content width, and it was the back link — the one whose
   label can ellipsise — that lost, so "All services" arrived as "All
   servi…" on a phone. The standing "SERVING IN" label is what goes, and
   it is the right thing to drop: the pin beside it already says the word
   is a place, while the city name is the part somebody actually reads.
   That frees about 40px, which is ten times the shortfall. From 760px up
   there is room for both and the label comes back. */
@media(max-width:759px){
  .ph-svcband .ph-citypick{padding:9px 11px;gap:7px}
  .ph-svcband .ph-citypick .ph-cplabel{display:none}
  .ph-svcband .ph-citypick-t b{margin-top:0}
}
/* 320px is the narrowest screen still in real use, and there the longest
   back label — a service name, on a city page — is a dozen pixels over
   even with the compact pill. One step down in size clears it, and at
   320px everything else on the page is a step down too. */
@media(max-width:359px){
  .ph-svcband .ph-backlink{font-size:11.5px;gap:5px}
  .ph-svcband .ph-citypick{padding:8px 10px}
}
.ph-citypick{display:flex;align-items:center;gap:9px;width:auto;min-width:0;
  padding:8px 12px;border-radius:13px;cursor:pointer;font-family:inherit;text-align:left;
  border:1.5px solid transparent;background:#fff;
  box-shadow:0 4px 16px rgba(10,5,30,.24);transition:border-color .15s,box-shadow .15s,transform .15s}
@media(min-width:760px){.ph-citypick{min-width:172px}}
.ph-citypick:hover{border-color:var(--pu);box-shadow:0 6px 20px rgba(91,46,212,.19);transform:translateY(-1px)}
.ph-citypick>.ph-ic:first-child{flex:none;color:var(--pu)}
.ph-citypick-t{flex:1 1 0;min-width:0;display:block}
/* A class, not a bare <i>. The defensive rule further up — "body.pus-home
   i, body.pus-home em { font-size: inherit }", which exists to undo the
   site stylesheet turning every <i> into a 1.5em icon box — is more
   specific than ".ph-citypick-t i", so an <i> here came out at the
   inherited 16px and the label shouted over the city name. */
.ph-citypick-t .ph-cplabel{display:block;white-space:nowrap;
  font:700 9.5px/1 'Inter','Inter Fallback',system-ui,sans-serif;letter-spacing:.7px;text-transform:uppercase;
  color:var(--ink3)}
.ph-citypick-t b{display:block;margin-top:3px;font:800 14px/1.15 'Inter','Inter Fallback',system-ui,sans-serif;
  color:var(--pu);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.ph-citypick>.ph-ic:last-child{flex:none;color:var(--ink3)}
/* no city chosen yet — amber, because until one is picked the prices on
   the page are the base ones and may not be what this visitor pays */
.ph-citypick.pick{border-color:#F0D3A0;background:#FFFCF6;box-shadow:0 3px 14px rgba(180,120,20,.13)}
.ph-citypick.pick .ph-citypick-t b{color:var(--amber)}
.ph-citypick.pick>.ph-ic:first-child{color:var(--amber)}
.ph-citypick.pick:hover{border-color:var(--amber);box-shadow:0 6px 20px rgba(180,120,20,.2)}
/* the <noscript> twin: same shell, a real select and a submit inside it */
.ph-citypick.nojs{cursor:default}
.ph-citypick.nojs select{width:100%;margin-top:2px;border:0;background:transparent;color:var(--pu);
  font:800 14px 'Inter','Inter Fallback',system-ui,sans-serif;padding:0}
.ph-citypick.nojs button{flex:none;border:0;cursor:pointer;background:var(--pu);color:#fff;
  border-radius:9px;padding:7px 13px;font:800 12px 'Inter','Inter Fallback',system-ui,sans-serif}

/* The three claims — "Verified professionals · Pay after service · No
   hidden charges" — used to sit under this header and have been taken
   out. They are already said where they are worth something: on the
   package cards, at the checkout step, and in the page's own copy. In
   the header they were three lines of ornament between the title and
   the first price, on the one screen where the visitor is trying to
   get to the prices. Their CSS is gone with them rather than left
   behind to be puzzled over later. */

/* ================= CITY PAGE CONTENT =================
   The written half of a /services/<service>-in-<city>/ page: the standfirst
   under the H1, the prose, the section cards, the localities and the blocks
   of links out to the neighbouring pages. Measured for reading — around 70
   characters a line — rather than stretched to the full grid width. */
.ph-h1sub{margin:0 !important;font-size:13.5px !important;line-height:1.65;color:var(--ink2) !important;max-width:68ch}
.ph-prose{font-size:13.5px;line-height:1.8;color:var(--ink2);max-width:70ch}
.ph-prose p{margin:0 0 13px}
.ph-prose p:last-child{margin-bottom:0}
.ph-seccards{display:grid;gap:12px}
@media(min-width:900px){.ph-seccards{grid-template-columns:repeat(2,minmax(0,1fr))}}
.ph-seccard{background:var(--card);border:1px solid var(--line);border-radius:16px;padding:18px 18px 16px}
.ph-seccard h3{margin:0 0 9px;font-size:15px;line-height:1.35;font-weight:700;letter-spacing:-.2px;color:var(--ink)}
.ph-seccard .ph-prose{max-width:none;font-size:13px}
/* the "why this is worth doing" tiles, on the services whose files list them */
.ph-bens{display:grid;gap:11px;grid-template-columns:minmax(0,1fr)}
@media(min-width:700px){.ph-bens{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media(min-width:1080px){.ph-bens{grid-template-columns:repeat(3,minmax(0,1fr))}}
.ph-ben{background:var(--soft);border:1px solid var(--line);border-radius:14px;padding:15px 16px}
.ph-ben b{display:block;font-size:13.5px;font-weight:700;color:var(--ink);line-height:1.4}
.ph-ben span{display:block;margin:6px 0 0;font-size:12.5px;line-height:1.65;color:var(--ink2)}
/* ---- the four blocks written in the panel --------------------------
   Checklist, numbered steps, reason tiles and a two-column table. They
   reuse the page's existing tokens rather than introducing a second set
   of colours, so a city page still reads as one page and not as four
   widgets stacked on top of each other. */
.ph-checks{list-style:none;margin:0;padding:0;display:grid;gap:9px;grid-template-columns:minmax(0,1fr)}
@media(min-width:700px){.ph-checks{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media(min-width:1080px){.ph-checks{grid-template-columns:repeat(3,minmax(0,1fr))}}
.ph-checks li{display:flex;gap:9px;align-items:flex-start;background:var(--soft);
  border:1px solid var(--line);border-radius:12px;padding:11px 13px;
  font-size:13px;line-height:1.55;color:var(--ink2)}
.ph-checks li svg{flex:none;margin-top:1px;color:var(--pu)}
.ph-checks li span{min-width:0}

/* The number is a counter, not typed text, so adding a step in the middle
   renumbers the ones after it instead of leaving a wrong sequence live. */
.ph-steps{list-style:none;counter-reset:phstep;margin:0;padding:0;display:grid;gap:11px}
@media(min-width:900px){.ph-steps{grid-template-columns:repeat(2,minmax(0,1fr))}}
.ph-steps li{counter-increment:phstep;position:relative;background:var(--card);
  border:1px solid var(--line);border-radius:14px;padding:15px 16px 14px 50px}
.ph-steps li::before{content:counter(phstep);position:absolute;left:14px;top:14px;
  width:25px;height:25px;border-radius:50%;background:var(--pu);color:#fff;
  display:grid;place-items:center;font:800 12px 'Inter','Inter Fallback',system-ui,sans-serif}
.ph-steps li b{display:block;font-size:13.5px;font-weight:700;color:var(--ink);line-height:1.4}
.ph-steps li span{display:block;margin:5px 0 0;font-size:12.5px;line-height:1.65;color:var(--ink2)}

.ph-whys{display:grid;gap:11px;grid-template-columns:minmax(0,1fr)}
@media(min-width:700px){.ph-whys{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media(min-width:1080px){.ph-whys{grid-template-columns:repeat(3,minmax(0,1fr))}}
.ph-why{background:var(--card);border:1px solid var(--line);border-radius:14px;padding:15px 16px}
.ph-why b{display:block;font-size:13.5px;font-weight:700;color:var(--ink);line-height:1.4}
.ph-why span{display:block;margin:6px 0 0;font-size:12.5px;line-height:1.65;color:var(--ink2)}

/* A long value must not widen the page on a phone, so the table scrolls
   inside its own box rather than pushing the body sideways. */
.ph-factwrap{overflow-x:auto;border:1px solid var(--line);border-radius:14px;background:var(--card)}
.ph-facts{width:100%;border-collapse:collapse;font-size:13px}
.ph-facts th,.ph-facts td{text-align:left;padding:12px 15px;line-height:1.6;vertical-align:top}
.ph-facts th{width:38%;font-weight:700;color:var(--ink);background:var(--soft);white-space:normal}
.ph-facts td{color:var(--ink2)}
.ph-facts tr+tr th,.ph-facts tr+tr td{border-top:1px solid var(--line)}

/* ---- the home page banners --------------------------------------
   A picture and nothing else. No border, no caption, no chrome: the
   artwork is the whole message, and anything the page draws around it
   competes with it. The radius matches the cards above and below so the
   strip belongs to the page rather than sitting on it.

   height:auto with the width and height on the tag is what keeps the
   proportions honest AND stops the page jumping when the file lands. */
.ph-promosec{padding-top:2px}
.ph-promo{width:100%}
/* ---- bigger on a phone ------------------------------------------
   A banner keeps its own proportions, so on a 390px screen a 1600x400
   picture is only about 90px tall — noticeably smaller than it looks on
   a desktop, and small enough that words designed into it get hard to
   read. Below 700px the strip therefore runs edge to edge instead of
   sitting inside the page's 16px gutters: it gains the full width of the
   screen, which makes it taller in the same proportion, and the corners
   square off because a rounded corner against the screen edge reads as
   a mistake rather than a card.

   Nothing is cropped and no second picture is needed — it is the same
   file, given the whole width. For a banner that should be taller still
   on a phone, upload a taller picture; the strip will follow it. */
@media(max-width:699px){
  .ph-promosec{margin-left:-16px;margin-right:-16px}
  .ph-promo-a{border-radius:0;box-shadow:0 1px 2px rgba(18,19,26,.06),0 6px 16px rgba(18,19,26,.06)}
}
.ph-promo-a{display:block;border-radius:16px;overflow:hidden;text-decoration:none;
  box-shadow:0 1px 2px rgba(18,19,26,.05),0 8px 22px rgba(18,19,26,.07);
  transition:transform .18s ease,box-shadow .18s ease}
a.ph-promo-a:hover{transform:translateY(-2px);box-shadow:0 3px 8px rgba(18,19,26,.07),0 14px 30px rgba(18,19,26,.12)}
a.ph-promo-a:focus-visible{outline:3px solid var(--pu);outline-offset:3px}
.ph-promo-a img{display:block;width:100%;height:auto}
@media(prefers-reduced-motion:reduce){a.ph-promo-a:hover{transform:none}}

/* localities */
.ph-areas{display:flex;flex-wrap:wrap;gap:8px}
/* The two are drawn the same on purpose. A locality with a page of its
   own is a link and one without is not, but a row where half the pills
   are blue and half are grey reads as half of them being broken. The
   difference shows on hover and on focus, where it belongs. Identical to
   the rule in city-hub.php, because it is the same row of pills. */
/* inline-flex with a stated min-height, not inline-block: a pill is a
   tap target, and assets/pus-site.css gives every <a> a 44px minimum
   on the pages that load it. Without this the same row of pills was
   44px tall on a service page and 31px on a city hub, and the linked
   ones were taller than the plain ones in the same row. Stated here so
   both files agree whatever that stylesheet does. */
.ph-areas span,.ph-areas a{display:inline-flex;align-items:center;min-height:44px;
  padding:7px 13px;border-radius:999px;background:var(--soft);
  border:1px solid var(--line);font-size:12px;font-weight:600;color:var(--ink2)}
.ph-areas a{text-decoration:none;transition:border-color .15s,color .15s,background .15s}
.ph-areas a:hover{border-color:var(--pu-line);background:var(--pu-soft);color:var(--pu)}
.ph-areas a:focus-visible{outline:2px solid var(--pu);outline-offset:2px}
/* the interlink blocks */
.ph-linkgrp{background:var(--card);border:1px solid var(--line);border-radius:16px;padding:16px 18px;margin:0 0 12px}
.ph-linkgrp>b{display:block;font-size:13px;font-weight:800;color:var(--ink);margin:0 0 11px}
.ph-linkwrap{display:flex;flex-wrap:wrap;gap:8px}
.ph-linkwrap a{display:inline-flex;align-items:center;gap:6px;padding:7px 13px;border-radius:999px;
  border:1px solid var(--line);background:#fff;text-decoration:none;
  font:600 12px 'Inter','Inter Fallback',system-ui,sans-serif;color:var(--ink2);transition:border-color .15s,color .15s,background .15s}
.ph-linkwrap a:hover{border-color:var(--pu);color:var(--pu);background:var(--pu-soft)}
.ph-linkwrap a[aria-current]{background:var(--pu);border-color:var(--pu);color:#fff}
.ph-linkwrap.tall a{border-radius:11px}

/* ================= FILTER CHIPS (sticky) ================= */
.ph-chipbar{position:sticky;top:var(--hh,60px);z-index:55;background:#fff;border-bottom:1px solid var(--line);
  margin:16px 0 18px;padding:10px 0}
.ph-chips{display:flex;gap:8px;overflow-x:auto;padding:1px 16px;max-width:1180px;margin:0 auto;-webkit-overflow-scrolling:touch}
.ph-chips::-webkit-scrollbar{height:0}
.ph-chip{display:inline-flex;align-items:center;gap:6px;padding:8px 14px;border-radius:999px;border:1px solid var(--line);
  background:#fff;color:var(--ink2);font:600 12.5px 'Inter','Inter Fallback',system-ui,sans-serif;cursor:pointer;white-space:nowrap;
  flex:none;text-decoration:none}
.ph-chip:hover{border-color:var(--pu-line)}
.ph-chip.on{background:var(--pu);border-color:var(--pu);color:#fff;box-shadow:0 4px 12px rgba(91,46,212,.25)}
/* The "Offers" chip filters the discounted packages, so it wears the same
   green the savings pills do — a blue chip pointing at green savings was
   the last piece of the old three-colour discount scheme. */
.ph-chip.off-chip{color:#12693F;border-color:#CDE9DA;background:var(--green-soft);font-weight:700}
.ph-chip.off-chip.on{background:var(--green);border-color:var(--green);color:#fff;box-shadow:0 4px 12px rgba(30,142,90,.28)}
.ph-chipdiv{flex:none;width:1px;background:var(--line);margin:2px 4px}

/* ================= PACKAGE CARDS ================= */
.ph-pkgs{display:grid;gap:12px}
/* ---- shelf headings ----------------------------------------------
   A heading is a grid item like the cards, so it takes a row of its own
   with grid-column:1/-1 instead of trying to sit outside the grid. The
   first one loses its top margin: it follows the chip bar, which already
   provides the space. */
.ph-grouph{grid-column:1/-1;display:flex;align-items:center;gap:9px;margin:16px 0 -2px;
  font:700 14px/1.3 'Inter','Inter Fallback',system-ui,sans-serif;letter-spacing:-.2px;color:var(--ink)}
.ph-grouph:first-child{margin-top:2px}
.ph-grouph span{font:700 11px/1 'Inter','Inter Fallback',system-ui,sans-serif;color:var(--pu);
  background:var(--pu-soft);border-radius:999px;padding:4px 8px}
.ph-grouph::after{content:"";flex:1;height:1px;background:var(--line)}
.ph-grouph.ph-hidden{display:none}
/* the count on a shelf chip */
.ph-chip.grp-chip em{font-style:normal;font-weight:700;font-size:11px;opacity:.6;margin-left:1px}
.ph-chip.grp-chip.on em{opacity:.85}
@media(min-width:900px){.ph-grouph{font-size:15px;margin:20px 0 0}}
/* Two per row from 900px up.
   -----------------------------------------------------------------
   align-items:start is the important word. A CSS grid stretches every
   cell in a row to the tallest one by default, so a package with two
   bullet points was drawn as tall as the package beside it with five —
   the extra height landing as a pocket of blank card below its own
   text, and the Add button stranded halfway up the right-hand side. It
   looked like something had failed to load. Each card is now exactly as
   tall as what is in it; a row simply has one card shorter than the
   other, which is what a reader expects and never reads as a hole.

   The card also has to stop behaving like the phone layout at this
   width. At 568px per column a 108px picture sat marooned against the
   right edge with a gulf of white between it and the text, so the
   picture and its button get wider, the text is kept to a readable
   measure rather than running the full width, and the padding grows
   with the card. */
/* The desktop rules for this card live at the END of the package-card
   block, after .ph-pkg-side and .ph-add have been declared. Written here
   they lost: a media query carries no extra weight of its own, so a plain
   .ph-pkg-side further down the file overrode it on source order alone
   and the wider picture never appeared. */
.ph-pkg{display:flex;gap:13px;background:var(--card);border:1px solid var(--line);border-radius:var(--r);padding:13px;
  scroll-margin-top:calc(var(--hh,60px) + 70px)}

/* ---- the two inclusions under the link on a package card ----------
   A hairline above them rather than a box around them: the card already
   has a border, and a second one inside it makes a list of ten cards look
   like a form. The marker is a small dot drawn by CSS, so the text in the
   catalogue stays plain text and nothing depends on somebody having typed
   a bullet character into the admin panel.

   Two lines each at most. They sit in a column about 240px wide on a
   phone, so a long inclusion wraps to two lines and stops there — one
   overlong line cannot make its card taller than the ones around it. */
.ph-pkg-pts{margin:9px 0 0;padding:9px 0 0;list-style:none;
  border-top:1px solid var(--line);display:grid;gap:5px}
.ph-pkg-pts li{position:relative;padding-left:14px;font-size:12.5px;line-height:1.5;color:var(--ink2);
  display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;line-clamp:2;overflow:hidden}
.ph-pkg-pts li::before{content:"";position:absolute;left:2px;top:.58em;
  width:5px;height:5px;border-radius:50%;background:var(--pu);opacity:.55}
.ph-pkg-main{flex:1;min-width:0;display:flex;flex-direction:column}
.ph-pkg h3{margin:0;font-size:15px;font-weight:600;line-height:1.35;color:var(--ink)}
/* The package name is a link to its own page. Left unstyled it took the
   browser's default — blue and underlined — which read as a stray link
   dropped into the card rather than the card's own heading. It keeps the
   heading's colour and weight; the arrow is the affordance, and the
   underline only appears on hover, offset so it does not cut through the
   descenders. text-decoration is forced because assets/pus-site.css sets
   its own rule for every anchor. */
.ph-pkg h3 .ph-pkg-link{display:inline;color:inherit;font:inherit;
  text-decoration:none !important;background-image:none}
/* A chevron, not an underline. An underline on a card heading reads as a
   stray hyperlink in a body of text; the arrow says "this opens" without
   competing with the heading. It carries the whole hover state, so no
   text decoration is used at any point. */
.ph-pkg h3 .ph-pkg-link::after{content:"";display:inline-block;width:.36em;height:.36em;
  margin:0 0 .08em .38em;border-right:2.2px solid currentColor;border-bottom:2.2px solid currentColor;
  border-radius:1px;transform:rotate(-45deg);opacity:.3;
  transition:opacity .18s ease,transform .18s ease,margin-left .18s ease;vertical-align:middle}
.ph-pkg h3 .ph-pkg-link:hover,
.ph-pkg h3 .ph-pkg-link:focus-visible{color:var(--pu);text-decoration:none !important}
.ph-pkg h3 .ph-pkg-link:hover::after,
.ph-pkg h3 .ph-pkg-link:focus-visible::after{opacity:1;margin-left:.62em}
.ph-pkg h3 .ph-pkg-link:focus-visible{outline:2px solid var(--pu);outline-offset:3px;border-radius:5px}
@media(hover:none){
  /* On touch there is no hover to reveal anything, so the arrow carries
     the whole signal and is shown at full strength. */
  .ph-pkg h3 .ph-pkg-link::after{opacity:.55}
}
/* The card lifts a little when the pointer is anywhere on it, so the
   heading is not the only hint that there is somewhere to go. */
.ph-pkg{transition:border-color .18s ease,box-shadow .18s ease}
@media(hover:hover){
  .ph-pkg:hover{border-color:#D9CFF6;box-shadow:0 10px 26px rgba(91,46,212,.08)}
}
.ph-meta{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin:7px 0 0;font-size:11.5px;color:var(--ink3)}
.ph-meta .ph-badge-rate{background:var(--green-soft);color:var(--green);padding:3px 7px}
.ph-price{display:flex;align-items:baseline;flex-wrap:wrap;gap:5px 8px;margin:9px 0 0}
.ph-price b{font-size:17px;font-weight:700;letter-spacing:-.3px;font-variant-numeric:tabular-nums}
.ph-price s{font-size:12.5px;color:var(--ink3);text-decoration-thickness:1.5px;
  text-decoration-color:rgba(107,102,122,.55);font-variant-numeric:tabular-nums}
/* The savings tag. It used to be a filled blue block sitting at the same
   visual weight as the price itself, so the eye had two things to read
   first. Now it is a quiet tinted pill: the word SAVE steps back, the
   rupee figure stays bold, and the whole thing reads as a footnote to the
   price rather than a competitor to it. Sizes are stated in full because
   the site stylesheet is a separate upload and restyles bare elements. */
.ph-price em{font-style:normal;display:inline-flex;align-items:center;gap:4px;flex:none;
  font:800 10.5px/1 'Inter','Inter Fallback',system-ui,sans-serif;letter-spacing:-.1px;white-space:nowrap;
  font-variant-numeric:tabular-nums;color:#12693F;background:var(--green-soft);
  border:1px solid #CDE9DA;padding:3.5px 8px 4px;border-radius:999px}
.ph-price em span{font-weight:700;font-size:8.5px;letter-spacing:.6px;opacity:.7;
  text-transform:uppercase;line-height:1}
.ph-dur{display:inline-flex;align-items:center;gap:5px;font-size:11.5px;color:var(--ink3);margin:7px 0 0}

/* ================= ALL PACKAGES SHEET =================
   A bottom sheet the visitor opens, listing every package as one compact
   row. See the markup for why this is not a popup that opens itself. */

/* The trigger sits in the same slot as the cart bar, and steps up out of
   its way when the cart bar appears. :has() is what reads "the cart bar is
   showing" without any JavaScript having to keep the two in sync. */
.ph-allbtn{position:fixed;left:50%;transform:translateX(-50%);
  bottom:calc(66px + env(safe-area-inset-bottom));z-index:69;
  display:none;align-items:center;gap:8px;padding:11px 18px;border-radius:999px;
  border:1px solid var(--pu-line);background:#fff;color:var(--pu);cursor:pointer;
  font:700 13px/1 'Inter','Inter Fallback',system-ui,sans-serif;
  box-shadow:0 10px 30px rgba(37,16,92,.18)}
.ph-allbtn em{font-style:normal;font-weight:800;font-size:11px;background:var(--pu);color:#fff;
  border-radius:999px;padding:3px 7px;line-height:1}
.ph-allbtn:hover{border-color:var(--pu)}
body:has(#ph-cartbar.on) .ph-allbtn{bottom:calc(66px + env(safe-area-inset-bottom) + 68px)}
/* Shown only where scripting can open it. */
html.pus-home-doc.js .ph-allbtn{display:inline-flex}

.ph-sheet{position:fixed;inset:0;z-index:410;display:flex;align-items:flex-end;justify-content:center;
  background:rgba(15,12,35,.55);opacity:0;transition:opacity .2s;box-sizing:border-box;
  padding:calc(var(--hh,60px) + 8px) 0 0}
.ph-sheet[hidden]{display:none}
.ph-sheet.on{opacity:1}
.ph-sheet-in{width:100%;max-width:600px;max-height:100%;min-height:0;display:flex;flex-direction:column;
  background:#fff;border-radius:20px 20px 0 0;transform:translateY(18px);transition:transform .22s;
  box-shadow:0 -20px 60px rgba(15,12,35,.3)}
.ph-sheet.on .ph-sheet-in{transform:none}
@media(min-width:700px){
  .ph-sheet{align-items:center;padding:calc(var(--hh,60px) + 14px) 16px 16px}
  .ph-sheet-in{border-radius:20px}
}

.ph-sheet-h{display:flex;align-items:center;gap:12px;padding:16px 16px 12px;border-bottom:1px solid var(--line);
  position:relative;flex:none}
/* The drag handle is a real button, so the sheet can be dismissed by
   keyboard and by a screen reader, not only by a swipe. */
.ph-sheet-grab{position:absolute;top:7px;left:50%;transform:translateX(-50%);
  width:44px;height:5px;border:0;padding:0;border-radius:999px;background:var(--line);cursor:pointer}
.ph-sheet-h b{display:block;font-size:15px;font-weight:700;letter-spacing:-.2px;color:var(--ink)}
.ph-sheet-h i{display:block;font-style:normal;font-size:11.5px;color:var(--ink3);margin-top:2px}
.ph-sheet-h>div{flex:1;min-width:0}
.ph-sheet-x{flex:none;width:34px;height:34px;border-radius:50%;border:1px solid var(--line);
  background:#fff;color:var(--ink2);cursor:pointer;display:grid;place-items:center}

.ph-sheet-body{flex:1 1 auto;min-height:0;overflow-y:auto;overscroll-behavior:contain;
  -webkit-overflow-scrolling:touch;padding:6px 0}

.ph-srow{display:flex;align-items:center;gap:12px;padding:11px 16px;border-bottom:1px solid var(--soft)}
.ph-srow:last-child{border-bottom:0}
.ph-srow-t{flex:1;min-width:0}
.ph-srow-t b{display:block;font-size:13px;font-weight:600;line-height:1.35;color:var(--ink)}
.ph-srow-t i{display:flex;align-items:center;gap:4px;font-style:normal;font-size:11px;color:var(--ink3);margin-top:3px}
.ph-srow-p{flex:none;text-align:right}
.ph-srow-p b{display:block;font-size:14px;font-weight:800;color:var(--ink);font-variant-numeric:tabular-nums}
.ph-srow-p span{display:flex;align-items:center;justify-content:flex-end;gap:5px;margin-top:2px}
.ph-srow-p s{font-size:11px;color:var(--ink3)}
.ph-srow-p em{font-style:normal;font:800 9.5px/1 'Inter','Inter Fallback',system-ui,sans-serif;letter-spacing:-.1px;
  color:var(--pu);background:var(--pu-soft);border:1px solid var(--pu-line);
  padding:3px 5px;border-radius:999px;white-space:nowrap}
.ph-srow-add{flex:none;min-width:64px;padding:9px 12px;border-radius:10px;border:1px solid var(--pu);
  background:var(--pu);color:#fff;font:700 12px 'Inter','Inter Fallback',system-ui,sans-serif;cursor:pointer}
.ph-srow.in .ph-srow-add{background:var(--green-soft);border-color:#CDE9DA;color:#12693F}

.ph-sheet-f{flex:none;padding:11px 16px calc(11px + env(safe-area-inset-bottom));border-top:1px solid var(--line);
  background:#fff;border-radius:0 0 20px 20px}
.ph-sheet-f a{display:flex;align-items:center;justify-content:center;gap:8px;padding:13px;border-radius:12px;
  background:linear-gradient(135deg,#7C4DEE,#5B2ED4);color:#fff;text-decoration:none;
  font:700 13.5px 'Inter','Inter Fallback',system-ui,sans-serif}

@media(max-width:380px){
  .ph-srow{gap:9px;padding:10px 13px}
  .ph-srow-add{min-width:56px;padding:8px 10px}
}
/* The .ph-pts inclusion list and the .ph-more toggle that went with it
   are gone: the package cards no longer carry the bullet points. The
   package's own page renders its inclusions with its own rules. */
/* "Service details" — the way in to the package's own page. A link, not a
   button: it goes somewhere, so it must be openable in a new tab and
   followable by a search engine. */
/* ---- the package detail popup ----
   It borrows .ph-modal and .ph-modal-in from the city picker, so both sheets
   open the same way and the shell is styled once. Only what differs is here. */
.ph-pd{display:none}
.ph-pdm .ph-modal-in{max-width:660px;position:relative}
.ph-pdm .ph-modal-body{padding:0}

/* The close button floats over the picture, so the sheet can open straight
   on the artwork with no header bar eating the top of a phone screen. */
.ph-pd-x{position:absolute;top:12px;right:12px;z-index:3;width:34px;height:34px;
  border:0;border-radius:50%;background:rgba(255,255,255,.94);color:var(--ink);
  cursor:pointer;display:grid;place-items:center;
  box-shadow:0 2px 10px rgba(18,19,26,.18)}
.ph-pd-x:hover{background:#fff}

/* Hero. A single picture, or the before/after pair side by side — the pair
   read together or not at all, so they never stack. */
.ph-pd-hero{background:var(--soft)}
.ph-pd-hero>img{width:100%;aspect-ratio:16/9;object-fit:cover;display:block}
.ph-pd-hero.pair{display:grid;grid-template-columns:1fr 1fr;gap:2px}
.ph-pd-hero.pair figure{margin:0;position:relative}
.ph-pd-hero.pair img{width:100%;aspect-ratio:4/3;object-fit:cover;display:block}
.ph-pd-hero.pair figcaption{position:absolute;left:8px;bottom:8px;
  padding:4px 9px;border-radius:999px;background:rgba(15,12,35,.72);color:#fff;
  font:700 9.5px/1 'Inter','Inter Fallback',system-ui,sans-serif;letter-spacing:1.2px;text-transform:uppercase}

.ph-pd-head{padding:16px 18px 0}
.ph-pd-head h3{margin:0;font-size:18px;font-weight:700;letter-spacing:-.4px;line-height:1.3;color:var(--ink)}
.ph-pd-facts{display:flex;flex-wrap:wrap;align-items:center;gap:8px 14px;margin:9px 0 0}
.ph-pd-facts span{display:inline-flex;align-items:center;gap:5px;
  font:500 12px/1 'Inter','Inter Fallback',system-ui,sans-serif;color:var(--ink3)}
.ph-pd-facts .r{font-weight:700;color:var(--ink)}
.ph-pd-facts .ph-ic{color:var(--ink3)}

/* The sizes. Two per row on a phone, three from 560px — the same shape the
   reference uses, and wide enough that a price and an Add button sit on one
   line without the name wrapping to three. */
.ph-pd-vars{margin:18px 0 0;padding:16px 18px;background:var(--soft);
  border-top:1px solid var(--line);border-bottom:1px solid var(--line)}
/* ---- the type in this grid ----
   Everything here used to be set heavy: the size at 600, the price at 800,
   the button at 700. Six tiles of that side by side read as six shouts,
   and nothing stands out because everything is trying to. The weights are
   now staggered so the eye is given an order — size first as plain text,
   then the price as the one emphasised thing, then the button. That is
   the pattern the big home-services apps use, and it looks calmer without
   anything actually getting smaller or harder to read. */
.ph-pd-vars-h{margin:0 0 12px;font:600 11px/1 'Inter','Inter Fallback',system-ui,sans-serif;
  letter-spacing:1.1px;text-transform:uppercase;color:var(--ink3)}
.ph-pd-vgrid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}
@media(min-width:560px){.ph-pd-vgrid{grid-template-columns:repeat(3,minmax(0,1fr))}}
.ph-pd-var{background:var(--card);border:1px solid var(--line);border-radius:14px;
  padding:13px 13px 12px;display:flex;flex-direction:column;gap:6px}
/* The size is a label, not a headline — regular weight, and a little
   larger to stay comfortable now that it is no longer bold. */
.ph-pd-var b{font-size:13.5px;font-weight:500;line-height:1.4;color:var(--ink2);letter-spacing:0}
/* The one emphasised thing in the tile. Semibold rather than black: at
   this size 800 does not read stronger, only heavier. */
.ph-pd-vp{font-size:15px;font-weight:600;color:var(--ink);letter-spacing:-.1px;line-height:1.25}
.ph-pd-vp s{margin-left:7px;font-size:12px;font-weight:400;color:var(--ink3);text-decoration-thickness:1px}
.ph-pd-off{display:inline-block;margin-left:6px;font-style:normal;font-size:11px;font-weight:700;color:#137A3D;background:#E8F7EE;border-radius:6px;padding:1px 6px;vertical-align:1px}
.ph-pd-var form{margin:auto 0 0;padding-top:2px}
.ph-pd-var button{width:100%;display:inline-flex;align-items:center;justify-content:center;gap:5px;
  border:1px solid var(--pu);background:#fff;color:var(--pu);border-radius:10px;padding:9px 10px;
  font:600 12.5px/1 'Inter','Inter Fallback',system-ui,sans-serif;cursor:pointer;transition:background .15s,color .15s}
.ph-pd-var button:hover{background:var(--pu);color:#fff}
/* The size the visitor tapped, marked so the grid answers "which one am I
   looking at" without them hunting for it. */
.ph-pd-var.here{border-color:var(--pu);box-shadow:0 0 0 2px var(--pu-soft)}
/* Already in the cart. Green, because on this site green means money kept
   and a done thing — the same rule the savings pill follows. */
.ph-pd-var.in{border-color:#CDE9DA;background:var(--green-soft)}
/* Once a size is in the cart the Add button is replaced by the stepper,
   the same swap the cards make. Two rules, mirroring .ph-addwrap: hide the
   stepper until .in, then hide the Add form instead. Without the first the
   stepper would sit under every Add button in the grid. */
.ph-pd-var .ph-qty{display:none}
.ph-pd-var.in .ph-addform{display:none}
.ph-pd-var.in .ph-qty{display:flex}
/* The stepper's own buttons are square and sit inside a tinted track, so
   they must not inherit the full-width Add styling above. */
.ph-pd-var .ph-qty{width:100%;align-items:center;justify-content:space-between;gap:4px;
  box-sizing:border-box;padding:4px;border-radius:10px;background:#fff;
  border:1px solid #12693F}
.ph-pd-var .ph-qty button{width:30px;height:30px;flex:none;padding:0;border:0;border-radius:8px;
  background:var(--green-soft);color:#12693F;font:500 17px/1 'Inter','Inter Fallback',system-ui,sans-serif}
.ph-pd-var .ph-qty button:hover{background:#12693F;color:#fff}
.ph-pd-var .ph-qty button:disabled{opacity:.4;cursor:default}
.ph-pd-var .ph-qty-n{flex:1 1 0;text-align:center;min-width:0;white-space:nowrap;
  font:600 13.5px/1 'Inter','Inter Fallback',system-ui,sans-serif;color:#12693F}

/* ---- coverage tiles ----
   Pictures of what the visit actually touches. Two per row on a phone,
   three from 560px, which is where a caption stops crowding the picture. */
.ph-pd-cov{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:9px}
@media(min-width:560px){.ph-pd-cov{grid-template-columns:repeat(3,minmax(0,1fr))}}
.ph-pd-cov figure{margin:0;position:relative;border-radius:12px;overflow:hidden;
  background:var(--soft);border:1px solid var(--line)}
.ph-pd-cov img{width:100%;aspect-ratio:4/3;object-fit:cover;display:block}
.ph-pd-cov figcaption{position:absolute;left:0;right:0;top:0;padding:9px 10px;
  font:600 12px/1.2 'Inter','Inter Fallback',system-ui,sans-serif;color:var(--ink);
  background:linear-gradient(180deg,rgba(255,255,255,.94),rgba(255,255,255,0))}
/* No picture yet: the tile is a plain labelled chip rather than an empty box. */
.ph-pd-cov figure.noimg{padding:14px 12px;background:var(--pu-soft);border-color:var(--pu-line)}
.ph-pd-cov figure.noimg figcaption{position:static;padding:0;background:none;color:var(--pu-d)}

/* ---- FAQs ---- */
.ph-pd-faq details{border-bottom:1px solid var(--line)}
.ph-pd-faq details:first-of-type{border-top:1px solid var(--line)}
.ph-pd-faq summary{list-style:none;cursor:pointer;display:flex;align-items:center;gap:12px;
  padding:14px 0;font-size:13px;font-weight:600;line-height:1.45;color:var(--ink)}
.ph-pd-faq summary::-webkit-details-marker{display:none}
.ph-pd-faq summary span{flex:1;min-width:0}
.ph-pd-faq summary .ph-ic{flex:none;color:var(--ink3);transition:transform .2s}
.ph-pd-faq details[open] summary .ph-ic{transform:rotate(180deg)}
.ph-pd-faq p{margin:0;padding:0 26px 15px 0;font-size:12.5px;line-height:1.7;color:var(--ink2)}

/* A section heading with no icon (coverage, FAQs) is plain ink, not a
   green tick or a red cross — those two colours mean included and not. */
.ph-pd-sec h4:not(.in):not(.out){color:var(--ink);font-size:14.5px;letter-spacing:-.2px}

.ph-pd-desc{margin:16px 18px 0;font-size:13px;line-height:1.7;color:var(--ink2)}

.ph-pd-sec{margin:18px 0 0;padding:0 18px}
.ph-pd-sec:last-child{padding-bottom:20px}
.ph-pd-sec h4{display:flex;align-items:center;gap:7px;margin:0 0 9px;
  font:700 12.5px/1 'Inter','Inter Fallback',system-ui,sans-serif;letter-spacing:.2px}
.ph-pd-sec h4.in{color:#12693F}
.ph-pd-sec h4.out{color:#A33A2E}
.ph-pd-sec h4 .ph-ic{flex:none}
.ph-pd-sec ul{list-style:none;margin:0;padding:0;display:grid;gap:7px}
.ph-pd-sec li{position:relative;padding-left:17px;font-size:12.5px;line-height:1.6;color:var(--ink2)}
.ph-pd-sec li::before{content:"";position:absolute;left:0;top:7px;width:7px;height:7px;
  border-radius:50%;background:var(--pu-soft);border:2px solid var(--pu)}
.ph-pd-sec h4.out ~ ul li::before{background:#FBEAE7;border-color:#C9584A}
/* ---- family cards ----
   One card standing for several sizes. "Starts at" is the honest label for a
   price that is the cheapest of five, not the price. */
.ph-price .ph-from{display:block;font-style:normal;font-size:10.5px;font-weight:600;
  letter-spacing:.3px;color:var(--ink3);margin:0 0 1px;text-transform:uppercase}
.ph-fam-add{width:100%}
.ph-fam-n{margin:6px 0 0;text-align:center;font:500 11px/1 'Inter','Inter Fallback',system-ui,sans-serif;color:var(--ink3)}
/* Something from this family is in the cart. Green and bolder, the same
   signal the sizes inside the sheet use, so the card and the sheet agree
   at a glance instead of the card looking untouched. */
.ph-fam.in .ph-fam-n{color:#12693F;font-weight:700}
.ph-fam.in .ph-fam-add{background:#12693F;border-color:#12693F;color:#fff}

.ph-details{display:inline-flex;align-items:center;gap:4px;margin:9px 0 0;
  font:700 12.5px/1 'Inter','Inter Fallback',system-ui,sans-serif;color:var(--pu);text-decoration:none;
  padding:7px 0;transition:gap .15s}
.ph-details:hover{gap:7px;text-decoration:underline}
.ph-details .ph-ic{flex:none}

/* The one-sentence description under the link.
   Two lines and no more: the cards sit in a grid, and a card whose text
   runs to four lines makes every card in its row that tall. The clamp
   hides the overflow rather than deleting it, so the full sentence is
   still in the HTML — the reader sees two lines, Google reads all of it.
   line-clamp is the standard property; the -webkit- pair is what actually
   does the work in every current browser, so both are set. */
.ph-pkg-line{margin:2px 0 0;font-size:12.5px;line-height:1.55;color:var(--ink2);
  display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;line-clamp:2;
  overflow:hidden;max-width:52ch}
.ph-pkg-side{flex:0 0 108px;display:flex;flex-direction:column;gap:9px}
.ph-pkg-img{position:relative;aspect-ratio:1/1;border-radius:12px;overflow:hidden;background:var(--pu-soft)}
.ph-pkg-img img{width:100%;height:100%;object-fit:cover;display:block}
.ph-pkg-img .ph-ribbon{right:6px;top:6px;padding:3px 6px;font-size:9.5px}
.ph-add{width:100%;padding:10px 8px;border-radius:11px;border:1.5px solid var(--pu);background:#fff;color:var(--pu);
  font:800 12.5px 'Inter','Inter Fallback',system-ui,sans-serif;letter-spacing:.4px;text-transform:uppercase;cursor:pointer;
  display:flex;align-items:center;justify-content:center;gap:5px;text-decoration:none;box-shadow:0 2px 8px rgba(91,46,212,.12)}
.ph-add:hover{background:var(--pu);color:#fff}
.ph-add.in{background:var(--green);border-color:var(--green);color:#fff}
/* Once a package is in the cart the ADD turns into a stepper, the way a food
   app does it, so the card itself shows what is in the basket and lets the
   quantity change without leaving the page. Both halves are rendered; the
   wrapper decides which one is on, so the swap needs no reload. Each is a
   real form posting act=add / act=qty, so it all works with JS off too. */
.ph-addwrap{position:relative}
.ph-addwrap .ph-qty{display:none}
.ph-addwrap.in .ph-addform{display:none}
.ph-addwrap.in .ph-qty{display:flex}
/* The stepper replaces the Add button in the same slot, so it has to sit
   at the same weight as that button. A solid green block was the one
   loud colour on an otherwise purple page and pulled the eye away from
   the price. It is now the brand's own outlined control: white, purple
   border, purple digits — the same family as the Add button it replaced,
   just filled in. */
.ph-qty{width:100%;align-items:center;justify-content:space-between;gap:4px;
  padding:4px;border-radius:12px;border:1.5px solid var(--pu);background:var(--card);
  box-shadow:0 2px 10px rgba(91,46,212,.13)}
.ph-qty button{width:30px;height:30px;flex:none;border:0;border-radius:9px;cursor:pointer;
  background:var(--pu-soft);color:var(--pu);font:800 18px/1 'Inter','Inter Fallback',system-ui,sans-serif;
  display:flex;align-items:center;justify-content:center;padding:0;
  transition:background .15s,color .15s}
.ph-qty button:hover{background:var(--pu);color:#fff}
.ph-qty button:disabled{opacity:.4;cursor:default}
.ph-qty button:disabled:hover{background:var(--pu-soft);color:var(--pu)}
.ph-qty-n{flex:1 1 0;text-align:center;min-width:0;color:var(--pu);white-space:nowrap;
  font:800 14.5px/1 'Inter','Inter Fallback',system-ui,sans-serif;letter-spacing:.2px}
/* .ph-save (the duplicate "You save" line under the Add button) is no longer
   rendered — see the note at that spot in the markup. */

/* ---- the package card on a desktop screen ----------------------------
   Last in this block on purpose: these override .ph-pkg-side and .ph-add
   above, and a media query adds no specificity of its own, so source
   order is the only thing making them win.

   Two per row from 900px, and align-items:start is the important word.
   A grid stretches every cell in a row to the tallest one, so a package
   with two bullet points was drawn as tall as the one beside it with
   five — the extra height landing as a pocket of blank card under its
   own text, with the Add button stranded halfway up the side. It read as
   something that had failed to load. Each card is now exactly as tall as
   what is in it.

   The card also has to stop behaving like the phone layout at this
   width. At 568px across, a 108px picture sat marooned against the right
   edge with a band of white between it and the price. The picture takes
   a real share of the card instead, which closes that gap and gives the
   package something to be looked at — the thing a cleaning service is
   actually sold on. */
/* From tablet up the picture stops being a thumbnail. It also stops being
   square: held at 1:1 a 176px-wide picture stands 176px tall on its own
   and drags every card down to match, so a package with one line of text
   becomes a tall box of white. 4:3 keeps the card the height of its
   text. */
@media(min-width:720px){
  .ph-pkg{padding:15px;gap:18px}
  .ph-pkg-side{flex:0 0 158px;gap:10px}
  .ph-pkg-img{aspect-ratio:4/3;border-radius:14px}
  .ph-pkg-img .ph-ribbon{right:8px;top:8px;padding:4px 9px;font-size:10.5px;border-radius:999px}
  .ph-add{padding:12px 8px;font-size:13px;border-radius:12px}
  .ph-qty{padding:5px}
  .ph-qty button{width:34px;height:34px}
}
@media(min-width:900px){
  .ph-pkgs{grid-template-columns:minmax(0,1fr) minmax(0,1fr);align-items:start;gap:14px}
  .ph-pkg h3{font-size:15.5px}
}
@media(min-width:1100px){
  .ph-pkg{padding:16px;gap:20px}
  .ph-pkg-side{flex:0 0 176px;gap:11px}
}

/* The OFFER STRIP's rules — .ph-offer, .ph-offer-t, .ph-offer-code,
   .ph-offer-btn, .ph-offer-gift, .ph-offer-inline and their four
   breakpoints — were here and have gone with the band itself. The
   uploaded banners that replaced it are styled by .ph-promo* above,
   which is a picture and nothing else. */

/* ================= TRUST / STEPS / SAFE ================= */
/* The most-booked rail borrows the offers rail's classes outright
   (.ph-offers / .ph-off / .ph-off-pic / .ph-off-name), so it has no styles
   of its own to keep in step. Its container id and its own nav buttons
   are the only things that differ, and those are wired in the script. */
/* .ph-steps / .ph-step* / .ph-arrow / .ph-safe* / .ph-avs* removed with
   the two sections they styled. .ph-stars below stays — the reviews
   section still uses it. */
.ph-stars{display:flex;gap:2px;color:#F5A623;align-items:center}
.ph-stars em{font-style:normal;margin-left:6px;font-weight:800;font-size:13.5px;color:var(--ink)}

/* ================= CLOSING BLOCK (service page) ================= */
.ph-closing{background:var(--soft);border:1px solid var(--line);border-radius:18px;padding:22px}
@media(min-width:900px){.ph-closing{padding:28px 30px}}
/* 20 and 24, the same as every other section heading on the site. It
   was 19 and 23 — one pixel adrift in both directions, which nobody can
   name but which is why a page of headings can feel slightly off. */
.ph-closing h2{margin:0;font-size:20px;font-weight:700;letter-spacing:-.4px}
@media(min-width:700px){.ph-closing h2{font-size:24px}}
.ph-closing p{margin:9px 0 0;font-size:13.5px;line-height:1.7;color:var(--ink2);max-width:70ch}
.ph-closing .ph-trust4{margin:18px 0 0;border-top-color:#E2E2EA}
.ph-closing-cities{margin:20px 0 0;padding:18px 0 0;border-top:1px solid #E2E2EA}
.ph-closing-h{font-size:11px;font-weight:800;letter-spacing:.8px;text-transform:uppercase;color:var(--ink3);margin:0 0 10px}

/* ================= REVIEWS / FAQ / CITIES ================= */
.ph-rail{display:flex;gap:12px;overflow-x:auto;padding:2px 2px 10px;scroll-snap-type:x mandatory;-webkit-overflow-scrolling:touch}
.ph-rail::-webkit-scrollbar{height:0}
.ph-rev{flex:0 0 272px;scroll-snap-align:start;background:var(--card);border:1px solid var(--line);border-radius:16px;padding:16px}
.ph-rev-top{display:flex;align-items:center;gap:10px}
.ph-av{width:38px;height:38px;border-radius:50%;display:grid;place-items:center;background:linear-gradient(135deg,var(--pu),var(--pu2));color:#fff;font-weight:800;font-size:15px}
.ph-rev-top b{display:block;font-size:13px;font-weight:700}
/* body.pus-home i,em above sets font-size:inherit at specificity 0,2,1;
   a bare .ph-rev-top i is 0,1,1 and lost to it, so the service name
   under a reviewer rendered at 16px instead of 11px. Scoped to match. */
body.pus-home .ph-rev-top i{font-style:normal;font-size:11px;color:var(--ink3)}
.ph-rev .ph-stars{margin:10px 0 7px}
.ph-rev p{margin:0;font-size:12.5px;line-height:1.6;color:var(--ink2)}
.ph-faq details{background:var(--card);border:1px solid var(--line);border-radius:14px;margin:0 0 9px;overflow:hidden}
.ph-faq summary{list-style:none;cursor:pointer;display:flex;align-items:center;gap:10px;padding:14px 16px;font-size:13.5px;font-weight:700}
.ph-faq summary::-webkit-details-marker{display:none}
.ph-faq summary .ph-ic{margin-left:auto;color:var(--ink3);transition:transform .2s}
.ph-faq details[open] summary .ph-ic{transform:rotate(90deg)}
.ph-faq p{margin:0;padding:0 16px 15px;font-size:12.5px;line-height:1.7;color:var(--ink2)}
/* The home page's city row. Deliberately NOT .ph-closing: that block is a
   filled card with a heading and four promises in it, and repeating it at
   the foot of the home page would be a second closing pitch under the one
   the page already makes. This is a plain list with a rule above it. */
.ph-homecities{border-top:1px solid var(--line);padding:20px 0 0}
.ph-citymore{margin:12px 0 0}
.ph-citymore>summary{list-style:none;cursor:pointer;display:inline-flex;align-items:center;gap:7px;
  min-height:44px;font-size:13px;font-weight:600;color:var(--pu)}
.ph-citymore>summary::-webkit-details-marker{display:none}
.ph-citymore>summary svg{transition:transform .2s}
.ph-citymore[open]>summary svg{transform:rotate(90deg)}
.ph-citymore[open]>summary span::after{content:' (hide)'}
/* A grid of plain links, not the pill row used elsewhere on the page.
   Measured on a 390px phone: 12 city pills came out one or two to a line,
   561px tall with most of the width empty, because "Home services in
   Bengaluru" is nearly the width of the screen once it has a border and
   14px of padding on each side. The same twelve as two columns of plain
   text is a little over half that and reads as a directory, which is what
   it is. */
.ph-citygrid{display:grid;gap:2px 20px;grid-template-columns:repeat(2,minmax(0,1fr))}
@media(min-width:700px){.ph-citygrid{grid-template-columns:repeat(3,minmax(0,1fr))}}
@media(min-width:1000px){.ph-citygrid{grid-template-columns:repeat(4,minmax(0,1fr));gap:2px 28px}}
.ph-citygrid a{display:flex;align-items:center;min-height:44px;padding:4px 0;
  color:var(--ink2);text-decoration:none;font-size:12.5px;line-height:1.4;
  transition:color .15s}
.ph-citygrid a:hover{color:var(--pu)}
/* On a narrow phone the column is 169px and "Home services in Hyderabad"
   is 170px, so two of the twelve wrapped to a second line and the rows
   went ragged. A slightly tighter gap and half a point off the type puts
   every city on one line. */
@media(max-width:479px){.ph-citygrid{gap:2px 12px}.ph-citygrid a{font-size:12px}}
.ph-citymore .ph-citygrid{padding:4px 0 2px}
.ph-citylinks{display:flex;flex-wrap:wrap;gap:8px}
.ph-citylinks a{padding:8px 13px;border-radius:999px;background:#fff;border:1px solid var(--line);font-size:12px;
  font-weight:600;color:var(--ink2);text-decoration:none}
.ph-citylinks a:hover{border-color:var(--pu);color:var(--pu)}

/* ================= STICKY CART =================
   The old bar was a flat green block that said "1 item added" — the count,
   but never WHAT. This one names the thing that just went in and shows its
   picture, so the confirmation is about the package rather than a number.
   Dark ink rather than green, matching the footer; green is kept as the
   tick, where it means "done" instead of being the whole surface. */
/* Flat black belonged to no part of this site — it read as a browser
   notification that had landed on the page. This is the brand's own
   deep aubergine with a purple-cast shadow, so it reads as the site
   speaking rather than something pasted on top. */
.ph-cartbar{position:fixed;left:12px;right:12px;bottom:calc(66px + env(safe-area-inset-bottom));z-index:70;
  display:none;align-items:center;gap:10px;padding:8px 8px 8px 10px;border-radius:18px;
  /* One soft top-to-bottom wash instead of the old 135deg three-stop. The
     diagonal put a visible lighter band across the top-right corner, which
     on a phone reads as a rendering fault rather than as depth. */
  background:linear-gradient(180deg,#221142 0%,#170E30 100%);color:#fff;
  box-shadow:0 16px 40px rgba(37,16,92,.42),0 1px 0 rgba(255,255,255,.07) inset;
  max-width:640px;margin:0 auto;border:1px solid rgba(167,139,250,.20)}
.ph-cartbar.on{display:flex}
/* a short pop each time something is added, so a bar that is already on
   screen still registers as having changed */
.ph-cartbar.pop{animation:ph-cartpop .34s cubic-bezier(.34,1.4,.5,1)}
@keyframes ph-cartpop{0%{transform:scale(1)}40%{transform:scale(1.025)}100%{transform:scale(1)}}
/* 38px, not 44. The picture and the tick were the largest, brightest
   things on a bar whose job is to show a total and a way forward; at 44
   the confirmation mark was outweighing the confirmation. */
.ph-cartbar-img{width:38px;height:38px;border-radius:11px;flex:none;object-fit:cover;
  background:rgba(255,255,255,.10);display:block;border:1px solid rgba(255,255,255,.14)}
.ph-cartbar-img[hidden]{display:none}
/* The tick keeps its green — it is the one place a "done" colour earns
   its keep, and at this size against the dark it is a mark, not a block. */
.ph-cartbar-tick{width:38px;height:38px;border-radius:11px;flex:none;display:grid;place-items:center;
  background:rgba(74,222,155,.13);color:#4ADE9B;border:1px solid rgba(74,222,155,.26)}
.ph-cartbar-tick[hidden]{display:none}
.ph-cartbar-t{min-width:0;flex:1 1 0}
/* The name line is the quieter of the two now. It says WHICH thing went
   in, which matters for a second; the total matters until checkout. */
/* Sentence case, not caps. Caps suit a label like "2 items"; they do not
   suit "Balcony Cleaning", which is what this line shows for four seconds
   after every add — a product name in caps reads as shouting. */
.ph-cartbar b{display:block;font-size:12px;font-weight:600;line-height:1.25;letter-spacing:.1px;
  color:rgba(255,255,255,.60);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.ph-cartbar i{font-style:normal;display:block;margin-top:1px;min-width:0;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* The total on its own line, at the size the eye goes to first. It used to
   sit third, behind a count and the word "item" — nobody scans a cart bar
   to find out how many things are in it. */
.ph-cartbar i em#ph-cart-total{font-style:normal;font-weight:800;color:#fff;
  font-size:17px;line-height:1.2;letter-spacing:-.3px}

/* ---- empty the cart ----
   A 38px round target with a 16px glyph: big enough to hit on a phone,
   quiet enough that it never competes with View cart. It turns red only
   on press, which is where a destructive colour belongs — on the action,
   not sitting on the page waiting. */
.ph-cartbar-x{position:relative;flex:none;width:38px;height:38px;border-radius:50%;
  border:1px solid rgba(255,255,255,.14);
  background:rgba(255,255,255,.06);color:rgba(255,255,255,.62);cursor:pointer;
  display:inline-flex;align-items:center;justify-content:center;padding:0;
  transition:background .15s,color .15s,border-color .15s}
/* The drawn circle is 38px because the bar has no room for more; the thing
   a thumb actually hits is 44, which is the size a tap target has to be.
   An invisible overlay does that without changing a pixel of the layout. */
.ph-cartbar-x::after{content:"";position:absolute;top:50%;left:50%;
  width:44px;height:44px;transform:translate(-50%,-50%)}
.ph-cartbar-x:hover{background:rgba(248,113,113,.16);color:#FCA5A5;border-color:rgba(248,113,113,.36)}
.ph-cartbar-x:active{transform:translateY(1px)}
.ph-cartbar-x:focus-visible{outline:2px solid #A78BFA;outline-offset:2px}

/* ---- the confirm, in place of the bar's own row ---- */
.ph-cartbar-ask{display:none;align-items:center;gap:8px;width:100%;min-width:0}
.ph-cartbar.asking .ph-cartbar-ask{display:flex}
.ph-cartbar.asking > :not(.ph-cartbar-ask){display:none}
.ph-cartbar-askt{flex:1 1 auto;min-width:0;font-size:13.5px;font-weight:700;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.ph-cartbar-keep,.ph-cartbar-yes{flex:none;min-height:38px;padding:0 15px;border-radius:12px;cursor:pointer;
  font:800 12.5px 'Inter','Inter Fallback',system-ui,sans-serif;letter-spacing:.2px;border:0}
/* Keep is the safe one, so it is the solid, obvious button; Empty is the
   outline. The destructive choice should never be the easier tap. */
.ph-cartbar-keep{background:#fff;color:#1A1035}
.ph-cartbar-yes{background:rgba(248,113,113,.14);color:#FCA5A5;border:1px solid rgba(248,113,113,.38)}
.ph-cartbar-yes:hover{background:rgba(248,113,113,.24);color:#fff}

.ph-cartbar a{flex:none;padding:12px 17px;border-radius:13px;color:#fff;
  background:linear-gradient(135deg,#7C4DEE,#5B2ED4);
  box-shadow:0 6px 16px rgba(91,46,212,.45);
  font:800 12.5px 'Inter','Inter Fallback',system-ui,sans-serif;text-decoration:none;letter-spacing:.2px;
  display:inline-flex;align-items:center;gap:5px;transition:transform .15s,box-shadow .15s,filter .15s}
.ph-cartbar a:hover{filter:brightness(1.08);box-shadow:0 8px 22px rgba(91,46,212,.55)}
.ph-cartbar a:active{transform:translateY(1px)}
/* 360, not 400. At 400 this caught nearly every phone sold — 390, 393 and
   412 are all inside it — so the compact bar, count removed, was what most
   customers saw. The full bar fits at 390 with room to spare; only the
   genuinely small screens need the cut-down one. */
@media(max-width:360px){
  .ph-cartbar{gap:7px;padding:7px 7px 7px 9px;border-radius:16px}
  .ph-cartbar-img,.ph-cartbar-tick{width:34px;height:34px;border-radius:10px}
  .ph-cartbar-x{width:34px;height:34px}
  .ph-cartbar a{padding:11px 12px;font-size:12px}
  .ph-cartbar i em#ph-cart-total{font-size:16px}
  .ph-cartbar-keep,.ph-cartbar-yes{padding:0 12px;font-size:12px}
}
/* ---- the cart while the package sheet is open ----
   The sheet sits at z-index 200, so until now adding a size from it moved
   a bar the customer could not see: the total changed behind the sheet and
   nothing on screen said so. Rather than build a second total inside the
   sheet — two bars that could disagree — the one real cart bar is lifted
   over it and moved down to the bottom edge, where the bottom nav it
   normally clears is no longer in the way.
   The Done button is the same close as the X; it is there because after
   picking sizes the natural next word is "done", not "close". */
.ph-cartbar-done{display:none}
/* Above the package sheet it belongs to, which is now at 400. */
body.ph-pd-open .ph-cartbar{z-index:440;bottom:calc(12px + env(safe-area-inset-bottom))}
body.ph-pd-open .ph-cartbar a{display:none}
body.ph-pd-open .ph-cartbar-done{flex:none;padding:12px 20px;border:0;border-radius:13px;color:#fff;
  background:linear-gradient(135deg,#7C4DEE,#5B2ED4);
  box-shadow:0 6px 16px rgba(91,46,212,.45);cursor:pointer;
  font:800 12.5px 'Inter','Inter Fallback',system-ui,sans-serif;letter-spacing:.2px;
  display:inline-flex;align-items:center;gap:5px;transition:filter .15s,box-shadow .15s}
body.ph-pd-open .ph-cartbar-done:hover{filter:brightness(1.08);box-shadow:0 8px 22px rgba(91,46,212,.55)}
body.ph-pd-open .ph-cartbar-done:active{transform:translateY(1px)}
@media(max-width:360px){body.ph-pd-open .ph-cartbar-done{padding:11px 15px;font-size:12px}}
/* The sheet's own bottom padding grows so the last row of content can
   still be scrolled clear of the bar sitting over it. */
body.ph-pd-open .ph-pdm .ph-modal-body{padding-bottom:86px}
/* ================= BOTTOM NAV =================
   Everything here is stated rather than inherited. The bar itself lives
   in assets/pus-site.css — a separate upload we cannot see all of, which
   sets .bnav-item to 10px #444 and lets the browser underline every
   label. On a phone this bar is on screen the whole visit, so it is
   worth owning outright. Sizes carry !important because that stylesheet
   loads after this one on some pages. */
body.pus-home #bottom-nav{
  height:auto !important;min-height:62px;
  padding:6px 4px calc(6px + env(safe-area-inset-bottom)) !important;
  background:rgba(255,255,255,.94) !important;
  -webkit-backdrop-filter:saturate(180%) blur(14px);backdrop-filter:saturate(180%) blur(14px);
  border-top:1px solid var(--line);
  box-shadow:0 -6px 22px rgba(26,16,53,.07)}
body.pus-home .bnav-item{
  position:relative;flex:1;min-width:0;display:flex !important;flex-direction:column;
  align-items:center;justify-content:flex-start;gap:3px;padding:6px 2px 4px !important;
  text-decoration:none !important;color:var(--ink3) !important;
  font:600 10.5px/1.2 'Inter','Inter Fallback',system-ui,sans-serif !important;letter-spacing:.1px;
  -webkit-tap-highlight-color:transparent;transition:color .16s ease}
body.pus-home .bnav-item span{
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:100%}
body.pus-home .bnav-icon-wrap{position:relative;display:grid;place-items:center;
  width:34px;height:30px;border-radius:11px;transition:background .18s ease,transform .18s ease}
/* One drawing style for the whole row. Before this, Home was a solid
   silhouette, Cart and Bookings were outlines at a different weight,
   and WhatsApp was a filled blob — four different hands on one bar.
   They are all now stroked on the same 24 grid at the same weight, so
   they read as a set. The stroke is set here rather than on each SVG so
   there is one number to change. */
body.pus-home .bnav-item svg{width:23px !important;height:23px !important;display:block}
body.pus-home .bnav-ic{fill:none;stroke:currentColor;stroke-width:1.75;
  stroke-linecap:round;stroke-linejoin:round;
  transition:stroke-width .18s ease}
/* The tab you are on is drawn a little heavier. Weight is a second
   signal on top of colour, which matters on a cheap screen where two
   greys and a purple can look much the same. */
body.pus-home .bnav-item.active .bnav-ic{stroke-width:2.15}

/* WhatsApp is a brand mark, not a UI icon, so it is filled — but the
   handset is knocked out in white rather than drawn in the same green.
   Painting both in one colour is what turned it into a blob at 23px. */
body.pus-home .bnav-ic-wa{stroke:none}
body.pus-home .bnav-ic-wa .wa-bub{fill:currentColor;stroke:none}
body.pus-home .bnav-ic-wa .wa-tel{fill:#fff;stroke:none}

/* Active: a filled pill behind the icon rather than a colour change
   alone, so which tab you are on is readable at a glance and not only
   to someone who can tell two greys apart. */
body.pus-home .bnav-item.active{color:var(--pu) !important;font-weight:700 !important}
body.pus-home .bnav-item.active .bnav-icon-wrap{background:var(--pu-soft)}
body.pus-home .bnav-item:active .bnav-icon-wrap{transform:scale(.9)}
@media(hover:hover){
  body.pus-home .bnav-item:hover{color:var(--pu) !important}
  body.pus-home .bnav-item:hover .bnav-icon-wrap{background:var(--pu-soft)}
}

body.pus-home .bnav-cart-count{position:absolute;top:-3px;right:-1px;min-width:17px;height:17px;
  padding:0 4px;border-radius:999px;border:2px solid #fff;
  background:#EF4444;color:#fff;font:800 9.5px/14px 'Inter','Inter Fallback',system-ui,sans-serif;
  text-align:center;font-style:normal;box-shadow:0 2px 6px rgba(239,68,68,.4)}

/* ---- WhatsApp ----
   Kept in WhatsApp's own green, because that is what people are looking
   for in a row of icons. The ring is drawn behind the icon and pulses;
   it is transform and opacity only, so it stays on the compositor and
   costs nothing on a phone. */
body.pus-home .bnav-item.bnav-wa{color:#1DA851 !important}
body.pus-home .bnav-item.bnav-wa svg{color:#25D366}
body.pus-home .bnav-wa .bnav-icon-wrap::before{
  content:"";position:absolute;inset:2px;border-radius:50%;
  background:#25D366;opacity:.30;z-index:-1;
  animation:pus-wa-ring 2.4s cubic-bezier(.25,.6,.3,1) infinite}
body.pus-home .bnav-wa .bnav-icon-wrap{isolation:isolate}
body.pus-home .bnav-wa .bnav-icon-wrap svg{position:relative;z-index:1}
@keyframes pus-wa-ring{
  0%   {transform:scale(.62);opacity:.34}
  55%  {transform:scale(1.28);opacity:0}
  100% {transform:scale(1.28);opacity:0}
}
@media(hover:hover){
  body.pus-home .bnav-item.bnav-wa:hover{color:#128C7E !important}
  body.pus-home .bnav-wa:hover .bnav-icon-wrap{background:rgba(37,211,102,.14)}
}
/* A ring that never stops is a nuisance for anyone who has asked the
   system to calm animation down. */
@media(prefers-reduced-motion:reduce){
  body.pus-home .bnav-wa .bnav-icon-wrap::before{animation:none;opacity:.16;transform:scale(1)}
  body.pus-home .bnav-item,body.pus-home .bnav-icon-wrap{transition:none}
}
.ph-toast{position:fixed;left:50%;transform:translateX(-50%) translateY(14px);bottom:calc(140px + env(safe-area-inset-bottom));
  z-index:120;background:#12131A;color:#fff;padding:11px 18px;border-radius:999px;font:700 12.5px 'Inter','Inter Fallback',system-ui,sans-serif;
  box-shadow:0 10px 26px rgba(0,0,0,.3);opacity:0;pointer-events:none;transition:.22s;display:flex;align-items:center;gap:8px}
.ph-toast.on{opacity:1;transform:translateX(-50%) translateY(0)}

/* ================= CITY PICKER ================= */
/* ---- the city picker ------------------------------------------------
   The panel used to be max-height:88vh centred in the whole viewport. On
   a site with 55 cities the list is long enough to hit that cap, and a
   739px panel centred in an 840px window starts at y=50 — seventeen
   pixels UNDER a 67px sticky header. The rounded top corner and the top
   of "You are booking in <city>" disappeared behind the header bar.

   The overlay now carries the header's own height as padding, so the
   panel is centred in the space BELOW the header and can never reach it,
   whatever the list length or window size. --hh is the measured header
   height the page already publishes; 60px is the fallback before the
   script has run. box-sizing matters — without it the padding would be
   added to inset:0 and push the overlay off the bottom of the screen. */
.ph-modal{position:fixed;top:0;right:0;bottom:0;left:0;inset:0;z-index:400;display:flex;align-items:flex-end;
  justify-content:center;background:rgba(15,12,35,.55);opacity:0;transition:opacity .2s;
  box-sizing:border-box;padding:calc(var(--hh,60px) + 8px) 0 0}
.ph-modal[hidden]{display:none}
.ph-modal.on{opacity:1}
@media(min-width:700px){.ph-modal{align-items:center;padding:calc(var(--hh,60px) + 14px) 16px 16px}}
/* 100% of the padded overlay rather than a vh figure: on a phone the
   address bar makes 100vh taller than what is actually on screen, which
   is the other way the bottom of this panel used to be unreachable. */
.ph-modal-in{width:100%;max-width:600px;max-height:100%;min-height:0;display:flex;flex-direction:column;background:#fff;
  border-radius:22px 22px 0 0;overflow:hidden;box-shadow:0 -20px 60px rgba(15,12,35,.28);
  transform:translateY(18px);transition:transform .24s cubic-bezier(.4,0,.2,1)}
.ph-modal.on .ph-modal-in{transform:none}
@media(min-width:700px){.ph-modal-in{border-radius:20px;box-shadow:0 30px 70px rgba(15,12,35,.32)}}

.ph-modal-h{display:flex;align-items:flex-start;gap:12px;padding:20px 18px 16px}
.ph-modal-ic{width:40px;height:40px;border-radius:12px;flex:none;display:grid;place-items:center;
  background:var(--pu-soft);color:var(--pu)}
.ph-modal-h b{display:block;font-size:16.5px;font-weight:800;letter-spacing:-.3px;line-height:1.25}
.ph-modal-h i{font-style:normal;display:block;margin-top:3px;font-size:12.5px;color:var(--ink3);line-height:1.45}
.ph-modal-h button{margin-left:auto;flex:none;border:0;background:var(--soft);width:34px;height:34px;border-radius:50%;
  display:grid;place-items:center;color:var(--ink2);cursor:pointer}
.ph-modal-h button:hover{background:#ECECF3}

.ph-modal-s{position:relative;margin:0 18px 4px}
.ph-modal-s-ic{position:absolute;left:13px;top:50%;transform:translateY(-50%);color:var(--ink3);display:flex}
.ph-modal-s input{width:100%;padding:13px 14px 13px 40px;border-radius:12px;border:1px solid var(--line);
  background:var(--soft);font:500 14px 'Inter','Inter Fallback',system-ui,sans-serif;outline:0;color:var(--ink)}
.ph-modal-s input:focus{border-color:var(--pu);background:#fff;box-shadow:0 0 0 3px rgba(91,46,212,.10)}

.ph-modal-confirm{padding:0 18px 4px}
.ph-modal-yes{display:flex;align-items:center;justify-content:center;gap:8px;width:100%;padding:14px;border:0;
  border-radius:12px;cursor:pointer;background:var(--pu);color:#fff;
  font:700 14px 'Inter','Inter Fallback',system-ui,sans-serif;transition:background .15s}
.ph-modal-yes:hover{background:var(--pu-d)}
/* flex:1 with min-height:0 is what actually makes this scroll: a flex
   item will not shrink below its content without min-height:0, so the
   list grew the panel instead of scrolling inside it. */
.ph-modal-body{flex:1 1 auto;min-height:0;overflow-y:auto;overscroll-behavior:contain;
  padding:14px 18px 18px;-webkit-overflow-scrolling:touch}
.ph-modal-h,.ph-modal-s,.ph-modal-confirm{flex:none}
.ph-modal-lbl{display:flex;align-items:center;gap:8px;font-size:10.5px;font-weight:800;letter-spacing:.9px;
  text-transform:uppercase;color:var(--ink3);margin:6px 0 10px}
.ph-modal-lbl em{font-style:normal;font-size:10.5px;font-weight:700;color:var(--ink3);background:var(--soft);
  border-radius:999px;padding:2px 8px;letter-spacing:0}
.ph-modal-popgrid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:9px;margin:0 0 18px;
  padding:0 0 16px;border-bottom:1px solid var(--line)}
@media(min-width:520px){.ph-modal-popgrid{grid-template-columns:repeat(3,minmax(0,1fr))}}
.ph-poptile{display:flex;align-items:center;gap:8px;padding:13px 12px;border-radius:13px;border:1px solid var(--line);
  background:#fff;text-decoration:none;color:var(--ink);font:700 13.5px 'Inter','Inter Fallback',system-ui,sans-serif;
  transition:border-color .14s,background .14s}
.ph-poptile .ph-ic{color:var(--pu)}
.ph-poptile span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.ph-poptile:hover{border-color:var(--pu);background:var(--pu-soft)}
.ph-poptile.on{border-color:var(--pu);background:var(--pu);color:#fff}
.ph-poptile.on .ph-ic{color:#fff}

.ph-modal-list{display:grid;grid-template-columns:1fr;gap:2px}
@media(min-width:520px){.ph-modal-list{grid-template-columns:1fr 1fr;gap:2px 14px}}
.ph-modal-list a{display:flex;align-items:center;gap:8px;padding:11px 12px;border-radius:10px;text-decoration:none;
  font:500 14px 'Inter','Inter Fallback',system-ui,sans-serif;color:var(--ink2);border:1px solid transparent}
.ph-modal-list a+a{border-top:1px solid var(--line);border-radius:10px}
@media(min-width:520px){.ph-modal-list a+a{border-top:0}}
.ph-modal-list a:hover{background:var(--soft);color:var(--pu)}
.ph-modal-list a .ph-ic{margin-left:auto;color:var(--pu)}
.ph-modal-list a.on{background:var(--pu-soft);color:var(--pu);font-weight:700}
.ph-modal-none{padding:22px 4px;text-align:center;font-size:13px;line-height:1.7;color:var(--ink3)}
.ph-modal-none a{color:var(--pu);font-weight:700;text-decoration:none}

.ph-modal-f{display:flex;align-items:center;gap:8px;padding:13px 18px calc(15px + env(safe-area-inset-bottom));
  border-top:1px solid var(--line);background:var(--soft);font-size:11.5px;color:var(--ink3)}
.ph-modal-f .ph-ic{color:var(--pu)}

/* ---- the name + number sheet, shown on the first Add to cart ----
   Built on .ph-modal so it is the same sheet the city picker is: same
   corner radius, same slide-up on a phone, same centred card on a desktop.
   Only what is inside it is new. */
.ph-lead-body{padding:16px 18px 4px}
.ph-lead-f{display:block;margin-bottom:13px}
.ph-lead-f > span{display:block;font-size:12px;font-weight:700;color:var(--ink2);margin-bottom:6px}
.ph-lead-in{display:flex;align-items:center;gap:9px;background:#fff;border:1.5px solid var(--line);
  border-radius:12px;padding:0 13px;transition:border-color .15s,box-shadow .15s}
.ph-lead-in:focus-within{border-color:var(--pu);box-shadow:0 0 0 3px var(--pu-soft,rgba(91,46,212,.12))}
.ph-lead-in .ph-ic{color:var(--ink3);flex:none}
/* 16px is not a style choice: anything smaller makes iOS Safari zoom the
   whole page in the moment the field is focused, and the sheet jumps. */
.ph-lead-in input{flex:1;min-width:0;border:0;outline:0;background:transparent;font:inherit;
  font-size:16px;padding:13px 0;color:var(--ink);-webkit-appearance:none}
.ph-lead-in input::placeholder{color:var(--ink3);opacity:.8}
.ph-lead-pre{font-size:15px;font-weight:700;color:var(--ink3);flex:none;letter-spacing:.2px;
  padding-right:9px;border-right:1px solid var(--line)}
.ph-lead-f.bad .ph-lead-in{border-color:#d23b3b}
.ph-lead-err{display:none;margin:6px 0 0;font-size:11.5px;font-weight:600;color:#d23b3b}
.ph-lead-f.bad .ph-lead-err{display:block}
.ph-lead-go{display:flex;align-items:center;justify-content:center;gap:8px;width:100%;padding:15px;border:0;
  border-radius:12px;background:var(--pu);color:#fff;font:inherit;font-size:14.5px;font-weight:800;cursor:pointer}
.ph-lead-go:hover{background:var(--pu-d)}
.ph-lead-go[disabled]{opacity:.65;cursor:default}
.ph-lead-note{margin:11px 0 2px;font-size:11.5px;line-height:1.65;color:var(--ink3);text-align:center}

.ph-empty{background:#fff;border:1px dashed var(--pu-line);border-radius:var(--r);padding:26px 18px;text-align:center}
.ph-empty b{display:block;font-size:14.5px;margin-bottom:5px}
.ph-empty p{margin:0;font-size:12.5px;color:var(--ink3)}
.ph-empty a{color:var(--pu);font-weight:700;text-decoration:none}
body.pus-home .ph-flash{outline:2px solid var(--pu);outline-offset:3px;border-radius:var(--r)}

/* ==================================================================
   IMAGE SKELETON — the shimmer a picture sits in while it downloads
   ==================================================================
   THE CLASS GOES ON THE BOX AROUND THE PICTURE, NOT ON THE <img>.

   That is worth writing down, because putting it on the <img> is the
   obvious thing to do and it does not work. Chrome does not paint an
   <img> element AT ALL while its file is still on its way — not the
   picture (there isn't one yet), and not the element's own background
   either. Measured: a background forced on with !important stayed
   invisible for the whole download and appeared only once the photo
   had arrived, by which time it was behind an opaque photo and no use
   to anyone. So the wrapper carries the shimmer and the picture simply
   lands on top of it.

   Every one of these boxes already reserves the right space — either an
   aspect-ratio, a fixed tile size, or width/height on the tag — so the
   skeleton is exactly the shape of the picture that replaces it and
   nothing on the page moves when it does.

   The class comes off on load AND on error AND after ten seconds, so a
   broken or stalled picture never shimmers forever: it falls back to
   the plain box it showed before any of this existed.

   The colour is the site's pale purple, not the usual grey. The
   category tiles are purple circles and a grey shimmer inside one read
   as a mistake.
   ================================================================== */
@keyframes ph-skmove{from{background-position:120% 0}to{background-position:-60% 0}}
.ph-skw{
  /* !important because some of these boxes already have a background of
     their own further down this stylesheet — the services tile has a
     lilac gradient, the category tile a solid purple. The skeleton has
     to beat them for the few hundred milliseconds it is up, and it is
     removed the instant the picture arrives. */
  background-color:#E8E2F8 !important;
  background-image:linear-gradient(100deg,
    rgba(255,255,255,0)   32%,
    rgba(255,255,255,.85) 50%,
    rgba(255,255,255,0)   68%) !important;
  background-repeat:no-repeat !important;
  background-size:190% 100% !important;
  background-position:120% 0;
  animation:ph-skmove 1.15s linear infinite;
}
/* A visitor who has asked their phone to stop moving things gets the
   same box without the sweep. The flat colour still says "a picture is
   coming here", which is the whole point of it. */
@media(prefers-reduced-motion:reduce){
  .ph-skw{animation:none;background-image:none !important}
}

/* ==================================================================
   THE PHONE LAYOUT
   ==================================================================
   Everything from here to the end of this block is under 760px only, and
   it is what turns the desktop arrangement into the one in the design:
   one compact city+search pill, the hero as a card with the picture
   beside the words, and the services as a three-across grid of small
   tiles rather than two big cards.
   ================================================================== */
@media(max-width:759px){


  /* ---- popular services: heading on one line, two across ---- */
  /* Left, not centred, and on its own — the "View All" link that used to
     sit beside it is gone. The services it pointed at are the very next
     thing on the page, so it was a link to scroll two inches. */
  /* Centred here as well as on a computer. Left on a phone and centred
     on a desktop meant the same two headings changed their mind about
     where they belonged halfway up the size range; centred everywhere,
     both of them sit on the middle line the cards and the icon row are
     already built around. */
  .ph-sech{text-align:center;margin-bottom:14px}
  .ph-sech .ph-eyebrow{display:none}
  /* the same 20/-.4 the other section headings use, so the two headings
     a visitor sees one after the other on a phone are the same size */
  .ph-sech-t{margin:0;font-size:20px;letter-spacing:-.4px}
  /* The sweep under the city name needs room below the text; on one line
     with a link beside it there is none, and it clipped. */
  .ph-sech-t em::after{display:none}

  /* THREE ACROSS.
     Two put a 160px photograph on a 390px phone, which is a poster for
     one service rather than a list of ten: two and a bit rows filled the
     screen. Three makes the card 113px and the whole row 164px tall
     instead of 196, so six services are in view where four were.

     The card does NOT simply shrink with the grid. Everything inside it
     is re-set for the new size — the padding, the corner radius, the
     shadow and the type all come down a step. A card scaled down with
     its 6px padding and 16px corners kept looks like a big card that was
     squeezed; the point is for it to look like a small card. */
  .ph-svcgrid{grid-template-columns:repeat(3,minmax(0,1fr));gap:10px}
  /* A REAL CARD, the same shape as the computer one.
     For a while this was a bare picture with a caption under it — no
     card, no border, no shadow. The picture ended, the name floated in
     white, and there was nothing to say where one service stopped and
     the next began. The card puts a white panel under each one, lifts
     it off the page with one soft shadow, and mounts the photo inside
     it with a margin all round, the way a printed card mounts a
     picture. */
  .ph-svc{background:var(--card);border:1px solid var(--line);border-radius:13px;
    padding:5px;box-shadow:0 2px 9px rgba(18,19,26,.055)}
  .ph-svc:active{transform:translateY(1px)}
  /* SQUARE at this size, not the 5:4 it had at two across. 5:4 of 103px
     is an 82px strip — too little of a sofa or a bathroom to tell them
     apart. Square gives the picture back its 21px and the card is still
     164px tall against the 196 it was, because three-across took far
     more off the width than the shape puts back. */
  .ph-svc-img{aspect-ratio:1/1;border-radius:9px}
  /* THE GAP INSIDE THE CARD. 7px under the picture and 8px under the
     name, against a 10px gap between the cards — so the name belongs to
     its own picture and not to the card beside it. With 3px it read as
     one grey block of nine pictures. */
  .ph-svc-b{display:block;padding:7px 3px 8px}
  /* THE NAME.
     12px at 700. 13.5px was right beside a 160px picture and is too
     loud beside a 103px one — at three across it wrapped to three and
     four lines and the names became the loudest thing in the section.

     Left, not centred: "Bathroom Cleaning" is one line and "Full Home
     Deep Cleaning" is two, and centred those two started in different
     places and made the whole column read as ragged. Left, every name
     begins on the same line as the picture edge above it.

     Two lines of height are reserved whether the name needs them or
     not, so a one-line name does not pull its card shorter than the
     two beside it. Nothing is ever cut — the clamp is three lines, the
     reserve is a floor, and the grid stretches a whole row to its
     tallest card so the rows stay level either way. */
  .ph-svc-name{font-size:12px;font-weight:600;line-height:1.25;letter-spacing:-.1px;
    text-align:left;color:var(--ink);min-height:calc(2 * 1.25em);
    display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden}
  .ph-svc-go{display:none}
}
/* At 320px the card is 89px wide and "Deep Cleaning" at 12px is 79px of
   it. Half a point back is what keeps that on one line instead of
   breaking "Cleaning" onto a third. */
@media(max-width:359px){
  .ph-svcgrid{gap:8px}
  .ph-svc-name{font-size:11.5px}
}
/* A tablet upright, or a phone turned sideways. Three cards across 700px
   is a 216px photograph again — back to the poster. Four, and everything
   else about the small card stays exactly as it is. */
@media(min-width:560px) and (max-width:759px){
  .ph-svcgrid{grid-template-columns:repeat(4,minmax(0,1fr));gap:12px}
}

