/* AdNeed public portfolio ("Order" theme) — Step 9 restyle.
   This stylesheet is intentionally standalone and never shares tokens with
   /css/adneed.css: a business's public catalog must never visually compete
   with, or be mistaken for, AdNeed's own product/brand surfaces. Typography
   stays deliberately different from AdNeed's own Sora/Manrope for the same
   reason, even though the accent colors below lean warm like the AdNeed
   mark.

   --p-font-heading / --p-font-body default to the original Fraunces/Inter
   pairing (Basic theme) so a business with no portfolioFont saved yet (or
   simply on a page where font-utils.js hasn't run) renders identically to
   before the Portfolio Font feature existed. Standard/Premium businesses
   get these two variables overridden inline by font-utils.js's
   applyFontVariables() (see portfolio.js renderBusiness()) once their
   chosen Google Fonts pair has been requested. */
:root{
  --p-font-heading:'AdNeedRupee','Fraunces', serif;
  --p-font-body:'AdNeedRupee','Inter', sans-serif;
  --p-ink:#1A1A1A;
  --p-ink-muted:#6B6B6B;
  --p-bg:#FBFAF8;
  --p-surface:#FFFFFF;
  --p-border:#EAE7E1;
  --p-navy:#101820;
  --p-cover-start:#F5A623;
  --p-cover-end:#D6401C;
  --p-price:#D6401C;
  --p-cyan:#1CA8DD;
  --p-cyan-dark:#1478A8;
  --p-success:#1F9D55;
  --p-shadow: 0 1px 2px rgba(16,24,32,.05), 0 10px 28px rgba(16,24,32,.08);
  --p-radius-lg:20px;
  --p-radius-md:14px;
  --p-radius-sm:10px;
}
*{box-sizing:border-box;}
html,body{margin:0; padding:0; overflow-x:hidden; max-width:100%;}
body{
  background:var(--p-bg); color:var(--p-ink);
  font-family:var(--p-font-body),-apple-system,sans-serif;
  -webkit-font-smoothing:antialiased;
}
h1,h2,h3{font-family:var(--p-font-heading); margin:0;}
img{max-width:100%; display:block;}
.hidden{display:none !important;}
button{font-family:inherit;}

/* ---------------------------------------------------------- STATE SCREEN */
.state-screen{
  min-height:100vh; display:flex; align-items:center; justify-content:center;
  padding:24px; text-align:center; background:var(--p-bg);
}
.state-box{max-width:340px;}
.state-spinner{
  width:34px; height:34px; margin:0 auto 18px; border-radius:50%;
  border:3px solid var(--p-border); border-top-color:var(--p-cover-end);
  animation:p-spin .8s linear infinite;
}
/* Used when showState() is given an opts.icon — swaps the endless
   loading circle for a static illustration, for terminal states (expired,
   not found, etc.) where a spinner would misleadingly imply something is
   still in progress. */
.state-spinner.state-spinner--icon{
  width:auto; height:auto; margin:0 auto 18px; border:none; animation:none;
  border-radius:0; display:flex; justify-content:center;
}
@keyframes p-spin{to{transform:rotate(360deg);}}
.state-box h1{font-size:19px; font-weight:600; margin-bottom:8px;}
.state-box p{color:var(--p-ink-muted); font-size:14px; line-height:1.5; margin:0 0 20px;}
.state-back{color:var(--p-cover-end); font-weight:600; font-size:13px; text-decoration:none;}

/* ------------------------------------------------------- GALLERY GATE
   Private-gallery password screen (public/gallery-shell/). Reuses the
   same --p-* tokens as the rest of this file rather than pulling in
   /css/adneed.css's separate auth-card look — a private gallery link is
   still a business's own public-facing surface, so it should read as
   "this portfolio, password-locked", not as an AdNeed account screen. */
.gate-screen{
  min-height:100vh; display:flex; align-items:center; justify-content:center;
  padding:24px; background:var(--p-bg);
}
.gate-box{
  max-width:360px; width:100%; background:var(--p-surface); border:1px solid var(--p-border);
  border-radius:var(--p-radius-lg); padding:32px 28px; box-shadow:var(--p-shadow); text-align:center;
}
.gate-icon{
  width:42px; height:42px; margin:0 auto 16px; border-radius:50%;
  background:linear-gradient(135deg, var(--p-cover-start), var(--p-cover-end));
  display:flex; align-items:center; justify-content:center; color:#fff;
}
.gate-icon svg{width:20px; height:20px;}
.gate-box h1{font-size:19px; font-weight:600; margin-bottom:6px;}
.gate-sub{color:var(--p-ink-muted); font-size:14px; line-height:1.5; margin:0 0 20px;}
.gate-box #gateAlert .alert{border-radius:12px; padding:11px 14px; font-size:13px; margin-bottom:16px; text-align:left;}
.gate-box #gateAlert .alert-error{background:#FDECEC; color:#C0392B; border:1px solid #F5C6C6;}
.gate-field{margin-bottom:18px; text-align:left;}
.gate-field label{display:block; font-size:12px; font-weight:600; color:var(--p-ink-muted); margin-bottom:6px;}
.gate-field input{
  width:100%; padding:11px 14px; border:1px solid var(--p-border); border-radius:var(--p-radius-sm);
  font-size:15px; font-family:inherit; color:var(--p-ink); background:var(--p-bg);
}
.gate-field input:focus{outline:none; border-color:var(--p-cover-end);}
.gate-submit{
  width:100%; padding:12px; border:none; border-radius:var(--p-radius-sm); background:var(--p-navy);
  color:#fff; font-size:15px; font-weight:600; cursor:pointer; display:flex; align-items:center;
  justify-content:center; gap:8px;
}
.gate-submit:hover:not(:disabled){opacity:.92;}
.gate-submit:disabled{opacity:.6; cursor:not-allowed;}
.gate-submit .spinner{
  width:15px; height:15px; border:2px solid rgba(255,255,255,.4); border-top-color:#fff;
  border-radius:50%; animation:p-spin .7s linear infinite; display:inline-block;
}

/* ------------------------------------------------------ GALLERY HEADER
   Simple centered name banner shown above the unlocked product grid —
   deliberately lighter than the full p-header (no cover/logo/tagline/
   contact row): a private gallery is a curated subset view, not the
   business's whole portfolio identity. */
.gallery-header{max-width:720px; margin:0 auto; padding:28px 20px 4px; text-align:center;}
.gallery-header h1{font-size:22px; font-weight:600;}

/* ---------------------------------------------------------------- COVER */
.p-cover{
  height:180px; width:100%;
  background:linear-gradient(135deg, var(--p-cover-start), var(--p-cover-end));
  position:relative;
}
.portfolio.no-cover .p-cover{height:140px;}
.p-cover-overlay{
  position:absolute; top:0; right:0; bottom:0; left:0;
  background:linear-gradient(180deg, rgba(0,0,0,0) 40%, rgba(0,0,0,.18) 100%);
}

/* -------------------------------------------------------------- HEADER */
.p-header{
  max-width:720px; margin:0 auto; padding:0 20px 20px; text-align:center;
  position:relative;
}
.p-logo-badge{
  width:84px; height:84px; border-radius:20px;
  margin:-42px auto 14px; display:flex; align-items:center; justify-content:center;
  font-family:var(--p-font-heading); font-weight:600; font-size:26px; color:var(--p-navy);
  overflow:hidden;
}
/* Initials fallback (no logo uploaded): solid box + border. */
.p-logo-badge:not(.has-logo){ background:var(--p-surface); border:2px solid var(--p-border); }
.p-logo-badge img{width:100%; height:100%;}
/* A logo with real transparency (a round badge, a die-cut icon, plain
   text with no background — detected pixel-by-pixel via
   detectSquareLogoShape() in color-utils.js) gets no box border at all:
   the outline is traced directly onto its own non-transparent pixels
   via stacked drop-shadow filters, so it hugs the actual artwork
   instead of boxing an irregular shape and leaving empty corners. 4px
   padding reserved so that outline always has room to render before
   hitting the overflow:hidden edge. */
.p-logo-badge.has-logo:not(.logo-square){ background:transparent; }
.p-logo-badge.has-logo:not(.logo-square) img{
  object-fit:contain; padding:4px;
  filter:
    drop-shadow(1px 0 0 var(--p-border))
    drop-shadow(-1px 0 0 var(--p-border))
    drop-shadow(0 1px 0 var(--p-border))
    drop-shadow(0 -1px 0 var(--p-border));
}
/* A logo that already fills its own bounding rectangle — any JPEG, or a
   PNG whose corners come back opaque from that same pixel check —
   outline lives on the box itself instead: a pixel-traced outline
   breaks down right at this badge's rounded corners, a real border
   doesn't. */
.p-logo-badge.has-logo.logo-square{ background:transparent; border:2px solid var(--p-border); }
.p-logo-badge.has-logo.logo-square img{ object-fit:cover; }
.p-name{font-size:26px; font-weight:700; letter-spacing:-.3px;}
.p-tagline{font-size:15px; font-weight:600; color:var(--p-ink); margin-top:4px;}
.p-description{font-size:14px; color:var(--p-ink-muted); line-height:1.6; margin-top:18px; padding-top:16px; border-top:1px solid var(--p-border); max-width:480px; margin-left:auto; margin-right:auto;}

/* All four pills must fit on one line without scrolling — sized to fit
   real phone widths (down to ~320px) via the narrow-screen rule below,
   rather than letting the row scroll. overflow-x:auto here previously let
   a touch-drag on the row scroll the WHOLE PAGE sideways on some devices
   instead of just this row, which is why it's gone now — see the
   html/body overflow-x:hidden safety net further up this file too. */
.p-contact-row{
  display:flex; flex-wrap:nowrap; gap:6px; justify-content:center; margin-top:16px; max-width:100%;
}
.p-contact-pill{
  display:inline-flex; align-items:center; gap:5px; padding:8px 12px; flex-shrink:0;
  border-radius:999px; border:1.5px solid var(--p-border); background:var(--p-surface);
  color:var(--p-ink); font-size:12.5px; font-weight:600; text-decoration:none; white-space:nowrap;
  transition:transform .12s ease, border-color .12s ease;
}
.p-contact-pill:hover{transform:translateY(-1px); border-color:var(--p-ink-muted);}
.p-contact-pill.whatsapp{background:#25D366; border-color:#25D366; color:#fff;}
.p-contact-pill svg, .p-contact-pill img{width:15px; height:15px; flex-shrink:0;}

/* Narrow phones (~360px and under, e.g. older/smaller Android devices):
   trims header padding and pill sizing further so the same four pills
   still fit on one row instead of needing to shrink below legible size
   or wrap. */
@media (max-width:380px){
  .p-header{padding-left:12px; padding-right:12px;}
  .p-contact-row{gap:4px;}
  .p-contact-pill{padding:7px 9px; font-size:11px; gap:4px;}
  .p-contact-pill svg, .p-contact-pill img{width:13px; height:13px;}
}

/* ------------------------------------------------------------- SOCIAL LINKS
   Medium solid brand-colour circles for the owner's Instagram/Facebook/
   YouTube links (Profile page → Social Links), shown as their own row in
   the page footer, above the "Powered by AdNeed" badge. A business with
   none configured renders no children here — the :empty rule below
   collapses the row so it takes no vertical space instead of leaving a gap. */
.p-social-row{
  display:flex; flex-wrap:wrap; gap:12px; justify-content:center;
  max-width:720px; margin:28px auto 8px; padding:0 20px;
}
.p-social-row:empty{ display:none; margin:0; }
.p-social-link{
  display:inline-flex; align-items:center; justify-content:center;
  width:44px; height:44px; border-radius:50%;
  border:none; color:#fff;
  transition:transform .12s ease, filter .12s ease;
}
.p-social-link:hover{ transform:translateY(-2px); filter:brightness(1.08); }
.p-social-link svg{ width:20px; height:20px; flex-shrink:0; }
/* Solid brand-colour fill at rest — this is the deliberate "link-in-bio"
   treatment (Linktree/Instagram-bio style): bigger, filled, high-contrast
   icons meant to be noticed and tapped, rather than the understated tinted
   outline other in-page icon rows (like the contact pills) use. */
.p-social-link.instagram{ background:#E1306C; }
.p-social-link.facebook{ background:#1877F2; }
.p-social-link.youtube{ background:#FF0000; }
.p-social-link.twitter{ background:#000000; }
.p-social-link.linkedin{ background:#0A66C2; }
.p-social-link.pinterest{ background:#E60023; }
.p-social-link.tiktok{ background:#FE2C55; }

@media (max-width:380px){
  .p-social-link{ width:38px; height:38px; }
  .p-social-link svg{ width:17px; height:17px; }
}

/* --------------------------------------------------- STICKY IDENTITY BAR
   Fixed at the very top, slid up out of view by default and only slid
   down once the main header scrolls past (portfolio.js toggles the
   .scrolled class on .portfolio via IntersectionObserver). --p-sticky-h
   is the single source of truth for its height — the search bar's own
   sticky offset (below) reads the same variable so the two never overlap
   regardless of future height tweaks here. */
:root{ --p-sticky-h:52px; }
.p-sticky-header{
  position:fixed; top:0; left:0; right:0; z-index:30; height:var(--p-sticky-h);
  background:var(--p-surface); border-bottom:1px solid var(--p-border);
  box-shadow:0 2px 10px rgba(16,24,32,.06);
  transform:translateY(-100%); transition:transform .18s ease;
}
.portfolio.scrolled .p-sticky-header{ transform:translateY(0); }
.p-sticky-inner{
  max-width:720px; height:100%; margin:0 auto; padding:0 16px;
  display:flex; align-items:center; gap:10px;
}
.p-sticky-logo{
  width:30px; height:30px; border-radius:8px; flex-shrink:0; overflow:hidden;
  display:flex; align-items:center; justify-content:center;
  font-family:var(--p-font-heading); font-weight:600; font-size:13px; color:var(--p-navy);
}
/* Initials fallback (no logo uploaded): solid box + border. */
.p-sticky-logo:not(.has-logo){ background:var(--p-bg); border:1px solid var(--p-border); }
.p-sticky-logo img{width:100%; height:100%;}
/* Same detectSquareLogoShape() split as .p-logo-badge above, scaled
   down for this 30px badge — 2px is enough room here for the traced
   outline to clear the overflow:hidden edge without shrinking an
   already-small logo too much. */
.p-sticky-logo.has-logo:not(.logo-square){ background:transparent; }
.p-sticky-logo.has-logo:not(.logo-square) img{
  object-fit:contain; padding:2px;
  filter:
    drop-shadow(1px 0 0 var(--p-border))
    drop-shadow(-1px 0 0 var(--p-border))
    drop-shadow(0 1px 0 var(--p-border))
    drop-shadow(0 -1px 0 var(--p-border));
}
.p-sticky-logo.has-logo.logo-square{ background:transparent; border:1px solid var(--p-border); }
.p-sticky-logo.has-logo.logo-square img{ object-fit:cover; }
.p-sticky-name{
  flex:1; min-width:0; font-size:15px; font-weight:700; letter-spacing:-.2px;
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
}
/* Standard/Premium multi-page catalog nav, compact version for the sticky
   bar — stays entirely invisible (no gap, no border) while empty (a
   business with only its one default catalog), per the comment in
   index.html. Scrolls horizontally rather than wraps, so the identity
   bar's height never changes regardless of how many catalogs exist. */
.p-sticky-pages{ flex-shrink:0; display:flex; gap:6px; overflow-x:auto; scrollbar-width:none; }
.p-sticky-pages::-webkit-scrollbar{ display:none; }
.p-sticky-pages:empty{ display:none; }
.p-sticky-pages .p-catalog-pill{ padding:5px 10px; font-size:11.5px; }

/* -------------------------------------------------------- CATALOG NAV
   The always-visible version, shown in the main header under the contact
   pills. Same underlying data/links as .p-sticky-pages above (see
   renderCatalogNav() in portfolio.js) — this is just the larger, non-
   condensed presentation for when the page hasn't scrolled yet. Hidden
   entirely for a business with only its one default catalog. */
.p-catalog-nav{
  display:flex; flex-wrap:wrap; gap:6px; justify-content:center; margin-top:10px; max-width:100%;
}
.p-catalog-pill{
  display:inline-flex; align-items:center; padding:7px 14px; flex-shrink:0;
  border-radius:999px; border:1.5px solid var(--p-border); background:var(--p-surface);
  color:var(--p-ink-muted); font-size:12.5px; font-weight:700; text-decoration:none; white-space:nowrap;
  transition:transform .12s ease, border-color .12s ease, color .12s ease, background .12s ease;
}
.p-catalog-pill:hover{transform:translateY(-1px); border-color:var(--p-ink-muted);}
.p-catalog-pill.active{background:var(--p-navy); border-color:var(--p-navy); color:#fff;}

/* -------------------------------------------------------------- CONTROLS */
.p-controls{
  max-width:720px; margin:0 auto; padding:0 20px 16px;
  position:sticky; top:0; z-index:5; background:var(--p-bg);
  transition:top .18s ease;
}
@media(min-width:900px){ .p-controls{max-width:1080px;} }
@media(min-width:1200px){ .p-controls{max-width:1400px;} }
@media(min-width:1600px){ .p-controls{max-width:1680px;} }
.portfolio.scrolled .p-controls{ top:var(--p-sticky-h); }
.p-search-wrap{position:relative;}
.p-search-icon{
  position:absolute; left:14px; top:50%; transform:translateY(-50%);
  width:18px; height:18px; color:var(--p-ink-muted); pointer-events:none;
}
.p-search{
  width:100%; padding:13px 14px 13px 42px; border-radius:14px;
  border:1.5px solid var(--p-border); background:var(--p-surface);
  font-family:var(--p-font-body); font-size:14px; color:var(--p-ink);
}
.p-search:focus{outline:none; border-color:var(--p-cyan);}
.p-chips-row{display:flex; align-items:center; gap:6px; margin-top:12px;}
.p-chips{display:flex; gap:8px; overflow-x:auto; padding-bottom:2px; -webkit-overflow-scrolling:touch; scroll-behavior:smooth; cursor:grab; user-select:none; flex:1; min-width:0;}
.p-chips:active{cursor:grabbing;}
.p-chips::-webkit-scrollbar{display:none;}
.p-chip-arrow{
  flex-shrink:0; width:30px; height:30px; border-radius:50%; padding:0;
  border:1.5px solid var(--p-border); background:var(--p-surface); color:var(--p-ink-muted);
  cursor:pointer; display:flex; align-items:center; justify-content:center;
  transition:border-color .12s ease, color .12s ease;
}
.p-chip-arrow svg{width:16px; height:16px;}
.p-chip-arrow:hover{border-color:var(--p-ink-muted); color:var(--p-ink);}
.p-chip-arrow:disabled{opacity:.35; cursor:default; pointer-events:none;}
.p-chip-arrow.hidden{display:none;}
.p-chip{
  flex-shrink:0; padding:8px 16px; border-radius:999px; font-size:13px; font-weight:600;
  border:1.5px solid var(--p-border); background:var(--p-surface); color:var(--p-ink-muted);
  cursor:pointer; white-space:nowrap;
  transition:background .12s ease, color .12s ease, border-color .12s ease, transform .15s cubic-bezier(.34,1.56,.64,1);
}
.p-chip:active{transform:scale(.92);}
.p-chip.active{background:var(--p-navy); border-color:var(--p-navy); color:#fff; transform:scale(1.05);}

/* ------------------------------------------------------------------ GRID */
.p-main{max-width:720px; margin:0 auto; padding:4px 20px 100px;}
/* Column count and gap live in custom properties so the Masonry layout
   (portfolio.js, .p-grid.masonry below) can read the SAME number of columns
   from CSS instead of duplicating these breakpoints in JS. Values are
   unchanged from before: 2 columns on phones, then 3 / 4 / 5 / 6. */
.p-grid{
  --p-cols:2; --p-gap:14px;
  display:grid; grid-template-columns:repeat(var(--p-cols),1fr); gap:var(--p-gap);
}
@media(min-width:520px){ .p-grid{--p-cols:3;} }
@media(min-width:900px){ .p-main{max-width:1080px;} .p-grid{--p-cols:4;} }
@media(min-width:1200px){ .p-main{max-width:1400px;} .p-grid{--p-cols:5; --p-gap:18px;} }
@media(min-width:1600px){ .p-main{max-width:1680px;} .p-grid{--p-cols:6;} }

/* ---- Owner-chosen catalog look (Profile -> Catalog Layout; Standard+) ----
   portfolio.js adds these classes to .p-grid. With none of them the grid is
   exactly the classic look: square boxes, photos cropped to fill. */

/* Photo shape (the box every photo sits in). Grid layout only. */
.p-grid.shape-portrait  .p-card-thumb{aspect-ratio:4/5;}
.p-grid.shape-landscape .p-card-thumb{aspect-ratio:4/3;}

/* Photo fit (how the photo sits in that box). Grid layout only.
   fill (default) = crop to fill; contain = whole photo visible with bars;
   width = photo as wide as the box (extra height is cropped, or bars if it's
   shorter); height = photo as tall as the box (extra width is cropped, or
   bars if it's narrower). The thumb is already a centring flex box, so
   width/height modes centre and crop evenly without any transforms (which
   would fight the hover-zoom transform on the img). background-COLOR only,
   so the loading shimmer (a background-image) still shows. */
.p-grid.fit-contain .p-card-thumb{background-color:var(--p-surface);}
.p-grid.fit-contain .p-card-thumb-img{object-fit:contain;}
.p-grid.fit-width  .p-card-thumb{background-color:var(--p-surface);}
.p-grid.fit-width  .p-card-thumb-img{width:100%; height:auto; flex:none;}
.p-grid.fit-height .p-card-thumb{background-color:var(--p-surface);}
.p-grid.fit-height .p-card-thumb-img{height:100%; width:auto; max-width:none; flex:none;}

/* Masonry: photos keep their own proportions, so cards have different
   heights. portfolio.js deals the cards left-to-right into N columns
   (N = --p-cols) so the owner's order still reads across the page; the
   .p-card-thumb box starts at a portrait placeholder ratio and JS swaps in
   the photo's real ratio once it loads. Shape/fit don't apply here (the box
   IS the photo's shape), so JS doesn't add those classes in this mode. */
.p-grid.masonry{display:flex; align-items:flex-start;}
.p-masonry-col{flex:1 1 0; min-width:0; display:flex; flex-direction:column; gap:var(--p-gap);}
.p-grid.masonry .p-card-thumb{aspect-ratio:4/5;}

.p-card{
  background:var(--p-surface); border:1px solid var(--p-border); border-radius:var(--p-radius-md);
  overflow:hidden; cursor:pointer; box-shadow:var(--p-shadow);
  /* Scroll-reveal entrance (see setupGridReveal() in portfolio.js): cards
     start faded/dropped and animate in with a per-card stagger as they
     enter the viewport, replaying on every filter/search/category change
     since the grid is rebuilt each time — a small bit of motion that
     rewards scrolling instead of dumping the whole catalog in at once. */
  opacity:0; transform:translateY(16px) scale(.97);
  transition:opacity .5s cubic-bezier(.16,1,.3,1), transform .5s cubic-bezier(.16,1,.3,1), box-shadow .25s ease;
}
.p-card.in{opacity:1; transform:none;}
.p-card:hover{transform:translateY(-4px); box-shadow:0 14px 30px rgba(16,24,32,.16);}
.p-card-thumb{
  position:relative; aspect-ratio:1/1; background:var(--p-bg);
  display:flex; align-items:center; justify-content:center;
  color:var(--p-ink-muted); font-size:11px; overflow:hidden;
}
.p-card-thumb img{width:100%; height:100%; object-fit:cover; transition:transform .5s cubic-bezier(.2,.8,.2,1);}
.p-card:hover .p-card-thumb-img{transform:scale(1.07);}
/* Shimmer while a card's photo is still loading (toggled by JS on the
   img's load/error events) instead of a blank square. */
.p-card-thumb.loading{
  background:linear-gradient(100deg, var(--p-bg) 30%, var(--p-border) 45%, var(--p-bg) 60%);
  background-size:250% 100%; animation:p-shimmer 1.3s ease-in-out infinite;
}
@keyframes p-shimmer{ 0%{background-position:120% 0;} 100%{background-position:-20% 0;} }

/* ---- Blur-up: the photo resolves from soft to sharp instead of popping
   in the instant it finishes downloading. Applies to every product photo,
   old and new — it's a CSS transition off the SAME request, so it costs
   nothing extra and never delays the photo itself.
   For products uploaded from now on, portfolio.js also drops in a tiny
   (few-KB) preview photo (image.thumbUrl, captured at upload time —
   image-upload.js) behind the full photo as soon as the card is built; it
   typically arrives well before the full photo and gives a real blurry
   preview during the wait, rather than just the plain shimmer. Older
   photos, uploaded before this existed, have no such preview stored, so
   they keep the shimmer until they resolve straight from blurred-and-
   invisible to sharp — everything below still applies to them, just
   without that extra head start. */
.p-card-thumb-placeholder{
  position:absolute; top:0; right:0; bottom:0; left:0; z-index:0;
  filter:blur(20px); transform:scale(1.15); /* scaled up so the blurred edge doesn't show a rim of the box behind it */
  opacity:1; transition:opacity .5s ease;
}
.p-card-thumb:not(.loading) .p-card-thumb-placeholder{opacity:0;}
.p-card-thumb-img{
  position:relative; z-index:1;
  opacity:0; filter:blur(14px);
  transition:opacity .45s ease, filter .45s ease, transform .5s cubic-bezier(.2,.8,.2,1);
}
.p-card-thumb:not(.loading) .p-card-thumb-img{opacity:1; filter:blur(0);}

/* A photo that failed to load even after retries (image-load.js) — hides
   the browser's own tiny broken-image icon and shows this in its place, so
   it reads as a clear, deliberate "this one's actually broken" instead of
   looking identical to one that's simply mid-load. */
.p-card-thumb:not(.loading) .p-card-thumb-img.p-img-broken{opacity:0; visibility:hidden;}
.p-card-thumb-fallback{
  position:absolute; top:0; right:0; bottom:0; left:0; z-index:2;
  display:flex; flex-direction:column; align-items:center; justify-content:center; gap:6px;
  background:var(--p-bg); color:var(--p-ink-muted); text-align:center; padding:10px;
}
.p-card-thumb-fallback svg{width:26px; height:26px; opacity:.6;}
.p-card-thumb-fallback span{font-size:11px; font-weight:600;}
.p-card-quickadd{
  /* Above .p-card-thumb-fallback (z-index:2) — that fallback covers the
     whole thumb box, and without this the button becomes unclickable on a
     product whose only photo is broken. */
  position:absolute; z-index:3; top:8px; right:8px; width:30px; height:30px; border-radius:50%;
  background:rgba(16,24,32,.72); color:#fff; border:none; font-size:18px; line-height:1;
  cursor:pointer; display:flex; align-items:center; justify-content:center;
  backdrop-filter:blur(2px); transition:transform .15s cubic-bezier(.34,1.56,.64,1), background .15s ease;
}
.p-card-quickadd:hover{transform:scale(1.12); background:rgba(16,24,32,.85);}
.p-card-quickadd:active{transform:scale(.88);}
.p-card-body{padding:12px;}
.p-card-video-badge{
  position:absolute; bottom:8px; left:8px; width:22px; height:22px; border-radius:50%;
  background:rgba(16,24,32,.72); color:#fff; font-size:10px; line-height:1;
  display:flex; align-items:center; justify-content:center; padding-left:1px;
}
.p-card-cat{
  display:block; font-size:10px; text-transform:uppercase; letter-spacing:.6px;
  color:var(--p-ink-muted); font-weight:700; margin-bottom:4px;
}
.p-card-name{
  font-family:var(--p-font-body); font-size:13.5px; font-weight:600; line-height:1.35;
  margin:0 0 6px; display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden;
}
.p-card-price{font-family:var(--p-font-heading); font-weight:600; font-size:15px; color:var(--p-price); margin-bottom:10px;}
.p-card-order{
  position:relative; overflow:visible;
  width:100%; display:flex; align-items:center; justify-content:center; gap:6px;
  padding:9px 10px; border-radius:10px; border:none; cursor:pointer;
  background:var(--p-cyan); color:#fff; font-size:13px; font-weight:700;
  transition:background .15s ease, transform .12s ease;
}
.p-card-order svg{width:15px; height:15px;}
.p-card-order:hover{background:var(--p-cyan-dark);}
.p-card-order:active{transform:scale(.97);}
.p-card-order.added{background:var(--p-success); animation:p-order-pulse .4s ease;}
@keyframes p-order-pulse{ 0%{transform:scale(1);} 45%{transform:scale(1.05);} 100%{transform:scale(1);} }

/* Tiny confetti burst on "Order"/quick-add — appended/removed by JS
   (burstConfetti() in portfolio.js), never left in the DOM. Kept small and
   quick (6 dots, ~650ms) so it reads as a satisfying tick, not a gimmick. */
.p-burst{position:absolute; top:0; right:0; bottom:0; left:0; pointer-events:none; z-index:2;}
.p-burst-dot{
  position:absolute; top:50%; left:50%; width:6px; height:6px; border-radius:50%;
  background:var(--p-success); transform:translate(-50%,-50%);
  animation:p-burst-fly .6s cubic-bezier(.2,.8,.2,1) forwards;
}
@keyframes p-burst-fly{
  to{ transform:translate(calc(-50% + var(--dx)), calc(-50% + var(--dy))) scale(.4); opacity:0; }
}

@media(min-width:1200px){
  .p-card-body{padding:10px;}
  .p-card-name{font-size:12.5px;}
  .p-card-price{font-size:14px; margin-bottom:8px;}
  .p-card-order{padding:8px 10px; font-size:12.5px;}
}

.p-empty{
  text-align:center; padding:70px 20px; color:var(--p-ink-muted); grid-column:1/-1;
}
.p-empty svg{width:44px; height:44px; margin:0 auto 14px; opacity:.5;}
.p-empty p{font-size:14px; margin:0;}

/* ---------------------------------------------------------------- BADGE */
.p-badge{
  display:flex; align-items:center; justify-content:center; gap:5px;
  max-width:720px; margin:0 auto 32px; padding:0 20px;
  font-size:12px; color:var(--p-ink-muted); text-decoration:none;
}
.p-legal-note{max-width:720px; margin:-18px auto 28px; padding:0 20px; text-align:center; font-size:11.5px; line-height:1.5; color:var(--p-ink-muted);}
.p-legal-note a{color:inherit; text-decoration:underline;}
.p-badge strong{color:var(--p-ink); font-family:var(--p-font-heading);}
/* Logo art already fills its square frame edge-to-edge (see badge_preview
   check), so a plain border-radius circle-crops it cleanly with no extra
   cropped asset needed. object-fit:cover guards against any future logo
   file that isn't perfectly square. */
.p-badge-logo{width:16px; height:16px; border-radius:50%; object-fit:cover; flex-shrink:0;}

/* ---------------------------------------------------------------- MODAL */
.p-modal-overlay{
  position:fixed; top:0; right:0; bottom:0; left:0; background:rgba(16,24,32,.55); z-index:50;
  display:flex; align-items:flex-end; justify-content:center;
}
@media(min-width:640px){ .p-modal-overlay{align-items:center; padding:24px;} }
.p-modal{
  background:var(--p-surface); width:100%; max-width:480px; max-height:92vh;
  border-radius:22px 22px 0 0; overflow-y:auto; position:relative;
}
@media(min-width:640px){ .p-modal{border-radius:var(--p-radius-lg); max-height:88vh;} }
.p-modal-close{
  position:absolute; top:12px; right:12px; z-index:2; width:34px; height:34px;
  border-radius:50%; background:rgba(16,24,32,.6); color:#fff; border:none; cursor:pointer;
  display:flex; align-items:center; justify-content:center;
}
.p-modal-close svg{width:18px; height:18px;}
.p-modal-imagewrap{
  position:relative; background:var(--p-bg); display:flex; align-items:center; justify-content:center;
  min-height:200px; max-height:38vh; overflow:hidden;
}
.p-modal-imagewrap img{width:100%; height:100%; max-height:38vh; object-fit:contain;}
.p-modal-nav{
  position:absolute; top:50%; transform:translateY(-50%); width:34px; height:34px; border-radius:50%;
  background:rgba(255,255,255,.9); border:none; cursor:pointer;
  display:flex; align-items:center; justify-content:center; box-shadow:0 2px 8px rgba(16,24,32,.15);
}
.p-modal-nav svg{width:18px; height:18px; color:var(--p-ink);}
.p-modal-prev{left:10px;}
.p-modal-next{right:10px;}
.p-modal-thumbs{display:flex; gap:8px; padding:12px 16px 0; overflow-x:auto;}
.p-modal-thumbs img{
  width:52px; height:52px; object-fit:cover; border-radius:8px; cursor:pointer;
  border:2px solid transparent; opacity:.6;
}
.p-modal-thumbs img.active{border-color:var(--p-cyan); opacity:1;}
/* Product Video (Premium) — see pModalVideoWrap in index.html */
.p-modal-video-wrap{padding:12px 16px 0;}
.p-modal-video{width:100%; max-height:52vh; border-radius:10px; background:#000; display:block;}
.p-modal-body{padding:18px 20px 24px;}
.p-modal-category{
  display:inline-block; font-size:10px; text-transform:uppercase; letter-spacing:.6px;
  color:var(--p-ink-muted); font-weight:700; margin-bottom:6px;
}
.p-modal-name{font-size:19px; font-weight:600; line-height:1.3;}
.p-modal-price{font-family:var(--p-font-heading); font-weight:600; font-size:18px; color:var(--p-price); margin:8px 0;}
.p-modal-description{font-size:13.5px; color:var(--p-ink-muted); line-height:1.6; margin:0 0 12px;}
.p-modal-tags{display:flex; flex-wrap:wrap; gap:6px; margin-bottom:16px;}
.p-modal-tags span{
  font-size:11px; padding:4px 10px; border-radius:999px; background:var(--p-bg);
  border:1px solid var(--p-border); color:var(--p-ink-muted);
}
.p-modal-qtyrow{display:flex; align-items:center; justify-content:space-between; margin-bottom:14px;}
.p-modal-qtylabel{font-size:13px; font-weight:600; color:var(--p-ink-muted);}
.p-qty-stepper{
  display:flex; align-items:center; gap:0; border:1.5px solid var(--p-border); border-radius:10px; overflow:hidden;
}
.p-qty-stepper button{
  width:36px; height:36px; border:none; background:var(--p-bg); font-size:17px; cursor:pointer; color:var(--p-ink);
}
.p-qty-stepper span{width:36px; text-align:center; font-weight:600; font-size:14px;}
.p-modal-addbtn{
  width:100%; padding:14px; border-radius:12px; border:none; cursor:pointer;
  background:var(--p-cyan); color:#fff; font-weight:700; font-size:14.5px; margin-bottom:10px;
  transition:background .15s ease;
  /* always visible while the shopper scrolls options; the shadow masks content behind it */
  position:sticky; bottom:0; z-index:2; box-shadow:0 0 0 10px var(--p-surface);
}
.p-modal-addbtn.added{background:var(--p-success);}
.p-modal-addbtn:hover{background:var(--p-cyan-dark);}
.p-modal-whatsapp{
  display:block; text-align:center; font-size:13px; color:var(--p-ink-muted); text-decoration:underline;
}

/* ------------------------------------------------------- GALLERY MODE
   Applied to .portfolio when the active catalog's mode is "gallery" (see
   portfolio.js's loadPortfolio()). A "mixed" catalog hides these same
   elements per-product instead (via .p-card-gallery / the modal's own
   hidden-class toggles), so it never needs this page-wide class. Cart
   items added earlier from a different (shop/mixed) catalog aren't lost
   — they're just not reachable from this particular gallery page. */
.catalog-gallery .cart-fab, .catalog-gallery .cart-overlay{ display:none !important; }

/* --------------------------------------------------------------- CART FAB */
.cart-fab{
  position:fixed; bottom:22px; right:22px; z-index:40; width:58px; height:58px; border-radius:50%;
  background:var(--p-navy); color:#fff; border:none; cursor:pointer;
  display:flex; align-items:center; justify-content:center; box-shadow:0 8px 24px rgba(16,24,32,.3);
  transition:transform .15s ease;
}
.cart-fab:active{transform:scale(.94);}
/* Triggered from JS (renderCartFab()) only when the cart count just went
   UP — never on every render — so it reads as "something was added", not
   as random ambient motion. Class is removed on animationend so it can
   retrigger on the next add even in quick succession. */
.cart-fab.bump{animation:p-fab-bump .45s cubic-bezier(.34,1.56,.64,1);}
@keyframes p-fab-bump{
  0%{transform:scale(1) rotate(0);}
  35%{transform:scale(1.18) rotate(-6deg);}
  65%{transform:scale(.95) rotate(3deg);}
  100%{transform:scale(1) rotate(0);}
}
.cart-fab svg{width:24px; height:24px;}
.cart-fab-badge{
  position:absolute; top:-4px; right:-4px; background:var(--p-price); color:#fff;
  font-size:11px; font-weight:700; min-width:20px; height:20px; border-radius:10px;
  display:flex; align-items:center; justify-content:center; padding:0 5px; border:2px solid var(--p-bg);
}
.cart-fab-badge.pop{animation:p-badge-pop .35s cubic-bezier(.34,1.56,.64,1);}
@keyframes p-badge-pop{ 0%{transform:scale(1);} 55%{transform:scale(1.45);} 100%{transform:scale(1);} }

/* ------------------------------------------------------------- CART DRAWER */
.cart-overlay{
  position:fixed; top:0; right:0; bottom:0; left:0; background:rgba(16,24,32,.55); z-index:50;
  display:flex; align-items:flex-end; justify-content:center;
}
@media(min-width:640px){ .cart-overlay{align-items:center; padding:24px;} }
.cart-drawer{
  background:var(--p-surface); width:100%; max-width:440px; max-height:88vh;
  border-radius:22px 22px 0 0; display:flex; flex-direction:column; overflow:hidden;
}
@media(min-width:640px){ .cart-drawer{border-radius:var(--p-radius-lg);} }
.cart-drawer-head{
  display:flex; align-items:center; justify-content:space-between;
  padding:18px 20px; border-bottom:1px solid var(--p-border); flex-shrink:0;
}
.cart-drawer-head h2{font-size:17px; font-weight:600;}
.cart-close{
  width:32px; height:32px; border-radius:50%; border:none; background:var(--p-bg); cursor:pointer;
  display:flex; align-items:center; justify-content:center;
}
.cart-close svg{width:16px; height:16px;}
.cart-items{overflow-y:auto; padding:8px 20px; flex:1;}
.cart-empty{padding:40px 20px; text-align:center; color:var(--p-ink-muted); font-size:13.5px;}
.cart-item{
  display:grid; grid-template-columns:52px 1fr auto; gap:12px; align-items:center;
  padding:12px 0; border-bottom:1px solid var(--p-border);
}
.cart-item:last-child{border-bottom:none;}
.cart-item-thumb{
  width:52px; height:52px; border-radius:10px; background:var(--p-bg); overflow:hidden;
  display:flex; align-items:center; justify-content:center; font-size:9px; color:var(--p-ink-muted); text-align:center;
}
.cart-item-thumb img{width:100%; height:100%; object-fit:cover;}
.cart-item-info{min-width:0;}
.cart-item-name{font-size:13px; font-weight:600; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;}
.cart-item-price{font-size:12px; color:var(--p-price); font-weight:600; margin-top:2px;}
.cart-item-stepper{
  display:flex; align-items:center; border:1.5px solid var(--p-border); border-radius:8px; overflow:hidden; grid-row:1; grid-column:3;
}
.cart-item-stepper button{width:26px; height:26px; border:none; background:var(--p-bg); font-size:14px; cursor:pointer;}
.cart-item-stepper span{width:22px; text-align:center; font-size:12px; font-weight:600;}
.cart-item-remove{
  grid-column:3; grid-row:2; justify-self:end; border:none; background:none; color:var(--p-ink-muted);
  font-size:11px; text-decoration:underline; cursor:pointer; padding:2px 0 0;
}
.cart-drawer-footer{padding:14px 20px 20px; border-top:1px solid var(--p-border); flex-shrink:0;}
.cart-subtotal{
  display:flex; align-items:center; justify-content:space-between; font-weight:700; font-size:15px; margin-bottom:12px;
}
.cart-subtotal small{display:block; font-weight:400; font-size:11px; color:var(--p-ink-muted); text-align:right; margin-top:2px;}
.cart-whatsapp-btn{
  display:flex; align-items:center; justify-content:center; gap:8px; width:100%;
  padding:14px; border-radius:12px; background:#25D366; color:#fff; font-weight:700; font-size:14.5px;
  text-decoration:none; margin-bottom:8px;
}
.cart-whatsapp-btn svg, .cart-whatsapp-btn img{width:18px; height:18px;}
.cart-no-whatsapp{font-size:12.5px; color:var(--p-ink-muted); text-align:center; margin:0 0 8px;}
.cart-clear{
  display:block; width:100%; text-align:center; border:none; background:none;
  color:var(--p-ink-muted); font-size:12.5px; text-decoration:underline; cursor:pointer; padding:4px 0;
}

/* -------------------------------------------------- REDUCED MOTION ------ */
/* Everyone still gets the entrance/hover/press *states* (opacity, colors,
   scale end-points) — this only strips the animated transition getting
   there, per the standard prefers-reduced-motion contract. Cards also skip
   straight to visible instead of staying hidden waiting for a transition
   that will never visibly run. */
@media (prefers-reduced-motion: reduce) {
  .p-card{opacity:1; transform:none; transition:none;}
  .p-card-thumb-img,
  .p-card-thumb-placeholder,
  .p-card-quickadd,
  .p-card-order,
  .p-chip,
  .cart-fab,
  .cart-fab-badge{transition:none;}
  .p-card-thumb.loading{animation:none;}
  .p-card-thumb-img{opacity:1; filter:none;}
  .p-card-thumb-placeholder{display:none;}
  .p-card-order.added,
  .cart-fab.bump,
  .cart-fab-badge.pop{animation:none;}
  .p-burst{display:none;}
}

/* ------------------------------------------------------ COMPACT (PHONES)
   Phones only (under 520px, i.e. while the grid is still 2 columns) — the
   default spacing was sized for larger screens and pushed the products
   almost off the first screen on a phone: cover + logo + name + tagline +
   description + buttons + search + categories added up to ~480px before
   the first product photo. Everything below just tightens vertical gaps
   (cover height, logo size, margins/padding between header blocks, and
   the space around the search + category bar); nothing changes on tablets
   or desktop. Kept last in the file so it overrides the base rules above. */
@media(max-width:519px){
  .p-cover{height:130px;}
  .portfolio.no-cover .p-cover{height:100px;}
  .p-header{padding-bottom:12px;}
  .p-logo-badge{width:72px; height:72px; border-radius:18px; margin:-36px auto 8px; font-size:22px;}
  .p-name{font-size:24px;}
  .p-tagline{font-size:14px; margin-top:2px;}
  .p-description{font-size:13.5px; line-height:1.5; margin-top:10px; padding-top:10px;}
  .p-contact-row{margin-top:12px;}
  .p-catalog-nav{margin-top:8px;}
  .p-controls{padding-bottom:10px;}
  .p-search{padding-top:11px; padding-bottom:11px;}
  .p-chips-row{margin-top:8px;}
  .p-main{padding-top:2px;}
  .p-grid{--p-gap:10px;}
  .p-social-row{margin-top:20px;}
}

/* ------------------------------------------------ product options (size / colour) */
.p-modal-options{margin-bottom:14px;}
.p-opt-group{margin-bottom:10px;}
.p-opt-label{font-size:13px; font-weight:600; color:var(--p-ink-muted); margin-bottom:6px;}
.p-opt-pills{display:flex; flex-wrap:wrap; gap:8px;}
.p-opt-pill{
  min-width:44px; padding:8px 14px; border:1.5px solid var(--p-border); border-radius:999px;
  background:var(--p-surface); color:var(--p-ink); font:inherit; font-size:14px; font-weight:600; cursor:pointer;
}
.p-opt-pill.is-selected{border-color:var(--p-ink); background:var(--p-ink); color:#fff;}
.p-opt-group.needs-pick .p-opt-pill{border-color:var(--p-price);}
.p-opt-group.needs-pick .p-opt-label::after{content:' \2014  please choose one'; color:var(--p-price); font-weight:600;}
.cart-item-variant{font-size:12px; color:var(--p-ink-muted); margin-top:2px;}
