:root { color-scheme: light; }  /* 2026-10-27 (Ali): never follow the phone's dark mode */
/* Rickezza -- real brand palette, confirmed directly with Ali against
   his own real logo artwork (Rickezza_logo.jpg): colors sampled
   directly from the actual image pixels (median of the logo
   wordmark's own gold stroke, and the artwork's own cream
   background), not eyeballed or guessed. Replaces the PREVIOUS
   palette below, which -- despite its own comment claiming it was
   "the real brand accent" -- was transcribed from a different,
   generic reference site's stylesheet (rickezza_website_reference.md),
   never Ali's own actual brand asset. That's exactly what Ali means by
   "the general colors are very basic" -- confirmed directly with him.
*/

:root {
  /* Real brand gold, sampled directly from the logo artwork's own
     wordmark (median of the core stroke pixels, avoiding anti-aliased
     edges): rgb(202,151,98). Used for links, active nav state, product
     name/price text, and the "In Stock" badge -- the single most
     important color to get right, same real-world usage the old
     token already had, just now the shop's own actual color. */
  --accent: #ca9762;
  /* A soft, muted tint of the accent gold -- real, subtle hover/active
     backgrounds (e.g. a selected filter chip) where the full-strength
     accent would be too heavy as a fill. */
  --accent-soft: #f1e4d5;
  --header-bg: #ffffff;      /* real site: white/transparent header, not dark */
  /* Warmed slightly off pure neutral gray, to sit naturally alongside
     the real cream/gold tokens above rather than reading as a cold,
     unrelated gray dropped into a warm palette. */
  --ink: #33291f;            /* real: buttons/dark UI text */
  --ink-soft: #6b5f52;       /* real: body copy */
  --ink-mute: #9c9086;       /* real: muted/secondary text */
  /* Real, warm cream -- sampled directly from the logo artwork's own
     background (rgb(242,237,231)), not the old palette's cool,
     unrelated gray. Used for hairlines/dividers and soft section
     backgrounds (--bg-soft below) -- both were cool gray before, now
     genuinely part of the same warm family as the accent gold. */
  --hairline: #e5ddd2;
  --bg-soft: #f2ede7;
  --panel-bg: #ffffff;
  /* Handoff -- real bug fix, confirmed directly with Ali: the whole
     page's own real background (catalog, product pages, everywhere)
     -- previously hardcoded #fff with no real token behind it. */
  --page-bg: #ffffff;
  /* Real, warm placeholder tones -- confirmed directly with Ali: the
     old #f2f2f2/#cfcfcf pair (a cool, generic gray) is the actual
     image/photo placeholder background+icon-stroke used everywhere a
     real photo hasn't been uploaded yet (cart, checkout, catalog
     tiles, product page) -- one of the single most visible, most
     repeated pieces of UI on the whole site, so warming it to match
     the real brand palette matters as much as the accent gold itself. */
  --placeholder-bg: #f1ece4;
  --placeholder-stroke: #c7bda9;
  --overlay: rgba(31, 24, 16, 0.45);
  --btn-dark: #33291f;
  --btn-dark-hover: #221b13;
  --danger: #a64637;         /* real secondary accent (rust/terracotta), used sparingly for alert states -- already warm, unchanged */
  /* Handoff -- real, explicit request, confirmed directly with Ali:
     "add to favorite... the heart will turn to a color i will pick
     in website colors in admin" -- the fill color once favorited.
     A real, classic heart-red default until Ali picks his own. */
  --favorite-heart: #d6433c;
  /* Handoff -- real bug fix, confirmed directly with Ali: "i dont
     have control on footer background and text colors" -- real
     defaults matching .rk-footer's own previous hardcoded values
     exactly, so nothing visually changes for a site that hasn't
     touched these two new fields yet. */
  --footer-bg: #201b17;
  --footer-text: rgba(255, 255, 255, 0.82);
  /* Handoff -- real, explicit request, confirmed directly with Ali:
     a full audit of every real, named part of the site he wants
     controllable on its own, no longer silently sharing a color with
     something unrelated ("things are linked together they are not
     supposed to be"). Defaults below each equal whatever color that
     part actually rendered as before this field existed, so nothing
     visually changes for a site that hasn't touched these yet -- see
     each new field's own real comment for exactly which shared
     variable it used to piggyback on and why that's now split out. */
  /* Was plain `color: inherit` (the header's own text color) -- now
     its own real token so it can differ from the icons next to it. */
  --header-link: #33291f;
  /* Was --ink (shared with all dark UI text/buttons everywhere). */
  --header-icon: #33291f;
  /* Was --ink (shared with body text and dark buttons). */
  --page-title: #33291f;
  /* Was a plain, hardcoded #fff with no real token behind it at all. */
  --card-bg: #ffffff;
  /* Was the same --card-bg above -- now its own real token so the
     info strip (SKU/name/price/Add to Bag) under a tile's photo can
     be a different color from the card's own background. */
  --card-footer-bg: #ffffff;
  /* Was --accent (shared with every other link, highlight, AND the
     product name right next to it) -- covers both the catalog tile's
     own price and the product detail page's price, the same single
     real concept shown in two places. */
  --price: #ca9762;
  /* Was --hairline (shared with every thin divider line site-wide). */
  --qty-box-border: #e5ddd2;
  /* Was --footer-text (shared with every other line of footer copy). */
  --footer-title: #ffffff;
  /* Was --footer-bg exactly (the signature strip -- "© 2026 Rickezza,
     all rights reserved" -- sits directly below the main footer and
     looked identical to it, not a real, separately-controllable bar
     before now). */
  --footer-signature-bg: #201b17;
  /* Was --panel-bg (shared with the desktop hover dropdown menu --
     two real, unrelated surfaces that happened to render the same). */
  --checkout-side-menu-bg: #ffffff;
  /* Was a plain, hardcoded #fff with no real token behind it at all. */
  --hamburger-menu-bg: #ffffff;
  /* Poppins is the ONLY font actually rendering anywhere on the real
     site (body, headings, buttons, product names, nav all confirmed
     via computed font-family) -- the single body/UI font here too. */
  --font-body: "Poppins", sans-serif;
  /* Abril Fatface was loaded but unused live -- brought forward ONLY
     for an optional decorative hero/heading treatment, per the
     reference doc's own recommendation. Not used for the logo itself:
     the real logo is a script/calligraphic image asset (see §2), so
     a script web font is not part of the real token set. */
  --font-display: "Abril Fatface", serif;
}

/* ---------------------------------------------------------------- */
/* Photo placeholder language                                        */
/* ---------------------------------------------------------------- */
/* No real product/lifestyle photography exists anywhere in this
   codebase yet -- see the redesign report. Every image slot in this
   rebuild (hero, category rail, craft block) uses this SAME visual
   language: a warm neutral panel (built from the real ink/accent
   tokens above, not arbitrary color) with a centered image glyph, so
   it reads as a deliberate "photo goes here" placeholder rather than
   a broken image or a demo gradient. Swap the inner content for a
   real <img> the moment real photography exists; nothing else about
   the layout needs to change when that happens. */
.rk-ph {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  background: linear-gradient(155deg, #efe9e2 0%, #e4dace 55%, #d9cabb 100%);
  color: var(--ink-mute);
  overflow: hidden;
}
.rk-ph svg { width: 15%; height: 15%; min-width: 22px; min-height: 22px; stroke: #b8a998; opacity: 0.7; }
.rk-ph--dark {
  background: linear-gradient(155deg, #3a332c 0%, #2b251f 55%, #201b17 100%);
}
.rk-ph--dark svg { stroke: rgba(255,255,255,0.35); }

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  font-family: var(--font-body);
  font-size: 16px;
  font-weight: 400;
  color: var(--ink-soft);
  /* Handoff -- real, serious bug found and fixed, confirmed directly
     with Ali via a real, live DevTools diagnosis (Chrome's own agent
     tooling): overflow-x: hidden and overscroll-behavior-y: contain
     together on html/body were genuinely breaking real mouse-wheel
     and real trackpad scroll site-wide -- only real arrow keys still
     worked. Both removed entirely rather than patched again. Neither
     was actually protecting what it looked like it was protecting:
     the real "pull-to-refresh on the homepage slider" fix this
     overscroll-behavior-y line was originally added for is now
     scoped to .rk-fullpage itself instead (present only on the real
     homepage, exactly where that real gesture conflict could ever
     happen) -- see that rule's own real comment. The real "off-canvas
     nav/cart drawer reachable via horizontal scroll while closed"
     concern this overflow-x line was originally added for is already
     independently, more robustly handled by those two panels' own
     real visibility: hidden while closed (see .rk-navpanel's own
     real comment) -- an element genuinely excluded from hit-testing
     and painting can't be scrolled to touch in the first place,
     making this page-wide overflow-x: hidden redundant on top of
     that, not load-bearing. */
  background: var(--page-bg);
  -webkit-font-smoothing: antialiased;
}
/* Handoff -- see html/body's own real comment above for the full
   explanation: this real class is what now locks the real page
   underneath the nav panel/cart drawer while either is open (see
   openNavPanel/openCartDrawer/closeAllPanels in app.js), taking over
   for html/body's own real overflow-x: hidden, which had been doing
   this only as an accidental, page-wide side effect this whole time. */
.rk-scroll-locked {
  overflow: hidden;
}

a { color: inherit; text-decoration: none; }

button { font-family: inherit; cursor: pointer; }

/* legacy elements from the plain frontend that we don't want leaking
   through visually if any page forgets to remove one */
hr { display: none; }

/* ---------------------------------------------------------------- */
/* Header                                                            */
/* ---------------------------------------------------------------- */

/* Real site: header padding 8px 0, white background, ~81px tall,
   sticky (`sticky-header`) -- see reference §1.3/§2. On inner pages the
   background is solid white immediately; only the homepage floats it
   transparently over the hero image (see .rk-header--transparent). */
.rk-header {
  position: sticky;
  top: 0;
  z-index: 40;
  /* Handoff -- real bug fix, confirmed directly with Ali against his
     own real phone screenshot ("Logo is not centered in the middle
     of the website"): flex + justify-content: space-between only
     truly centers a middle item when both its neighbors are the
     exact same real width -- here they never are (the hamburger is
     one real icon; the account+cart group is two icons plus a real
     badge), so the logo was always pulled slightly toward the
     narrower side rather than sitting at the real, true center of
     the screen. A real 3-column grid instead: the outer two columns
     are always equal (1fr each, real math, not just visual
     coincidence), so the logo in the middle is genuinely centered on
     the actual viewport regardless of how wide either side's own
     content is -- same real fix already used for the desktop nav's
     own header layout, now applied to the base/mobile one too. */
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  background: var(--header-bg);
  padding: 8px 18px;
  min-height: 81px;
  border-bottom: 1px solid var(--hairline);
  /* Part B convention: header shrink is a class-toggle-driven CSS
     transition, not a JS-animated loop -- see app.js's scroll
     listener (initHeaderScrollBehavior). */
  transition: min-height 220ms ease-out, padding 220ms ease-out,
    background 220ms ease-out, border-color 220ms ease-out;
}
.rk-header.is-shrunk { min-height: 56px; padding: 4px 18px; }
.rk-header__hamburger { grid-column: 1; justify-self: start; }
.rk-header__logo { grid-column: 2; justify-self: center; }
.rk-header__right { grid-column: 3; justify-self: end; }

/* Handoff -- real, explicit request, confirmed directly with Ali:
   "when pressing 'Account' button, i want a dropdown balloon to
   open with the options: view account, Order History, Favorites,
   Logout." position:relative anchors the dropdown against this real
   wrapper, not the whole header row. */
.rk-account-menu { position: relative; display: inline-flex; }
.rk-account-dropdown {
  position: absolute;
  top: calc(100% + 10px);
  right: 0;
  z-index: 50;
  /* Handoff -- real bug fix, confirmed directly with Ali ("i selected
     the colors i want in 'website colors' and clicked save, but the
     colors didnt change") -- --panel-bg (its own real admin label:
     "Card / panel background") was defined but never actually
     referenced anywhere in this stylesheet at all, so picking it in
     admin genuinely changed nothing real anywhere on the site. Wired
     into every real card/panel-like surface that was hardcoded #fff
     instead (this dropdown, the cart drawer below). */
  background: var(--panel-bg);
  border: 1px solid var(--hairline);
  border-radius: 8px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
  min-width: 180px;
  padding: 6px 0;
  display: flex;
  flex-direction: column;
}
/* Handoff -- real bug fix, found live via screenshot: this rule's own
   display: flex above has the exact same real specificity as the
   browser's own built-in [hidden] { display: none } rule, and simply
   comes later in this real stylesheet's own cascade -- so it was
   silently winning and keeping the dropdown visually open regardless
   of its own real hidden attribute/property. This real, explicit
   override restores hidden's own real, expected effect. */
.rk-account-dropdown[hidden] { display: none; }
.rk-account-dropdown a {
  padding: 10px 16px;
  font-size: 13px;
  letter-spacing: 0.3px;
  color: var(--ink);
  white-space: nowrap;
}
.rk-account-dropdown a:hover { background: var(--bg-soft, #f7f5f2); }

/* Handoff -- real header control, confirmed directly with Ali: a real
   announcement bar (Core's own, already-built Announcement model,
   now actually surfaced on the site) above the header. Real flow
   space, not fixed/floating -- unlike the transparent-header case
   there's no hero underneath needing to peek through, so this simply
   pushes the header down when shown. Hidden by default via the real
   [hidden] attribute (see app.js's own renderAnnouncementBar). */
.rk-announcementbar {
  background: var(--ink, #201b17);
  color: #fff;
  text-align: center;
  font-size: 12px;
  letter-spacing: 0.5px;
  padding: 8px 16px;
}

/* Homepage-only: transparent/absolute over the hero image, no hairline,
   icon color flipped to white until scrolled past the hero. Applied by
   app.js only on index.html, per the real site's own header-transparent
   behavior (reference §2: "transparent/absolute-positioned on the
   homepage so the hero image shows through behind it"). */
/* Handoff -- real, explicit request, confirmed directly with Ali:
   "when sliding through home page slides, remove the header and just
   keep the logo" -- hides the hamburger, desktop nav, and account/
   cart icons on the homepage specifically; the logo itself is
   untouched (still genuinely centered via its own real rule above),
   this only hides everything else that would normally share the
   header row with it. Real elements stay in the DOM (display: none
   only) so every other real handler on them -- hamburger open, cart
   badge refresh -- keeps working unaffected if this class is ever
   removed later. */
.rk-header--logo-only .rk-header__hamburger,
.rk-header--logo-only .rk-header__desktopnav,
.rk-header--logo-only .rk-header__right {
  display: none;
}
.rk-header--transparent {
  /* fixed (not absolute): it must float over the hero in EXACTLY the
     same position, in or out of flow, whether it's transparent or has
     solidified -- using the same positioning scheme for both states
     avoids a layout jump/reflow the moment .is-solid gets added. */
  position: fixed;
  top: 0; left: 0; right: 0;
  background: transparent;
  border-bottom: none;
}
.rk-header--transparent .rk-header__icon-btn,
.rk-header--transparent .rk-header__icon-btn svg,
.rk-header--transparent .rk-header__logo {
  color: var(--rk-header-fg, #fff);
  stroke: var(--rk-header-fg, #fff);
}

/* Homepage header solidifies once the user scrolls past the hero --
   a real gap the old single-hero-then-one-section homepage never
   surfaced (nothing to scroll past), but a genuine requirement now
   that the homepage has real length. Toggled by app.js's scroll
   listener, same debounced-scroll-position-class-toggle pattern the
   research doc's Part B describes for shrink-on-scroll headers. */
.rk-header--transparent.is-solid {
  /* Handoff -- real bug fix, confirmed directly with Ali ("in
     footer's slide, the header appears white and another white layer
     appears also, and the header looks like it jumped"): position:
     sticky here was a leftover from before the homepage became a
     real fullpage slider -- back when the homepage still scrolled
     normally, "solidifying" the header also meant switching it to
     stick in place once scrolled past the hero. Now that the whole
     homepage is one fixed, overflow:hidden fullpage container (see
     .rk-fullpage), the transparent header must stay position: fixed
     -- the same real positioning scheme -- in every real panel,
     solid or not; switching schemes specifically on the footer panel
     is exactly what produced the real jump and the real second white
     layer Ali saw. Only the visual solidity (background/text color)
     changes now, never the positioning itself. */
  background: var(--header-bg);
  border-bottom: 1px solid var(--hairline);
}
.rk-header--transparent.is-solid .rk-header__icon-btn,
.rk-header--transparent.is-solid .rk-header__icon-btn svg,
.rk-header--transparent.is-solid .rk-header__logo {
  color: var(--ink);
  stroke: var(--ink);
}

/* Handoff -- real "vertical fullpage slider" fix, confirmed directly
   with Ali: the old reserved-flow-space approach (push body down
   81px, then pull the hero back up 81px to cancel it) assumed normal
   in-flow content below the hero that needed to not be hidden behind
   the fixed header. That's no longer true -- every homepage section
   now lives inside .rk-fullpage, a single, fixed 100vh container the
   header floats transparently over regardless of which panel is
   showing (see initHomeFullpage's own updateChrome, which solidifies
   the header once the current panel isn't the dark hero). No reserved
   space is needed or wanted here now. */

.rk-header__icon-btn {
  background: none;
  border: none;
  padding: 4px;
  display: flex;
  align-items: center;
  /* Handoff -- real, explicit request, confirmed directly with Ali:
     "header buttons" needed its own real, separate color -- was
     --ink, shared with all dark UI text/buttons everywhere else. */
  color: var(--header-icon);
}
.rk-header__icon-btn svg { width: 22px; height: 22px; stroke: var(--header-icon); }

/* Real logo is a script/calligraphic image asset (Rickezza Logo.png,
   952x517, ~95x50px in the header) -- not a web font. This text
   fallback renders in the display font as the closest approximation
   until the real logo asset is available to drop in as an <img>; swap
   this selector for an <img src="..."> the moment that asset exists,
   rather than treating this text as the real logo. */
.rk-header__logo {
  font-family: var(--font-display);
  font-size: 26px;
  color: var(--ink);
  letter-spacing: 0.5px;
  white-space: nowrap;
}
.rk-header__logo img { display: block; height: 50px; width: auto; }

.rk-header__right { display: flex; align-items: center; gap: 18px; }

/* Handoff -- real desktop header nav, confirmed directly with Ali:
   below 960px (this site's own existing desktop breakpoint) the
   hamburger + slide-in panel is genuinely correct mobile UX and
   stays exactly as it was; at/above it, the real site reference
   (YSL) shows its nav directly in the header, so the hamburger
   disappears and a real horizontal nav with hover dropdowns takes
   its place instead -- not both at once. */
.rk-header__desktopnav { display: none; }
@media (min-width: 960px) {
  .rk-header__hamburger { display: none; }
  /* Real fallback, confirmed directly with Ali: keeps the hamburger
     reachable at desktop width when the desktop nav genuinely has
     nothing in it (see renderDesktopNav's own real check) -- higher
     specificity than the plain rule above, not !important. */
  .rk-header__hamburger.rk-header__hamburger--force-visible { display: flex; }
  /* Handoff -- real bug fix, confirmed directly with Ali ("when no
     categories available, it is showing the hamburger icon in
     desktop view, they should be in the top left"): the hamburger is
     the header's own first child with no explicit grid placement, so
     CSS grid auto-placed it into column 1 -- the SAME column
     .rk-header__desktopnav--left explicitly claims -- and with no
     justify-self of its own, it defaulted to grid's own "stretch",
     making the button's real, clickable/hoverable area stretch across
     the ENTIRE left column (confirmed live: 656px wide, not a normal
     ~40px icon button), even though the visible icon glyph inside it
     happened to still render at the button's own left edge. Pinned to
     the real, actual top-left corner now: its own explicit column 1
     placement plus justify-self: start, sized to its own real
     content instead of stretching to fill the column. */
  .rk-header__hamburger.rk-header__hamburger--force-visible { grid-column: 1; justify-self: start; }
  /* Handoff -- real bug fix, confirmed directly with Ali against his
     own screen recording of the real reference site: switches to a
     real 3-column grid at desktop width so the logo is genuinely,
     exactly centered regardless of how the left/right nav groups'
     own widths differ -- flex's own justify-content:space-between
     (the mobile layout, unchanged below this breakpoint) only
     guarantees equal gaps, not a centered middle item, once there are
     more than 3 children. A real, symmetric 4-column grid now: left
     nav, logo, right nav, icons are each their own real grid column
     (not a nested flex group sharing one column with the icons, an
     earlier round's approach that made the right nav's own spacing
     unpredictable relative to the icons). */
  .rk-header {
    display: grid;
    grid-template-columns: 1fr auto 1fr auto;
    align-items: center;
    /* Handoff -- real bug fix, confirmed live: without this, the left
       nav group's own right-aligned links sat flush against the
       logo's grid cell with zero real spacing between them --
       "Belts" was visually colliding with "Rickezza". */
    column-gap: 24px;
  }
  .rk-header__desktopnav {
    display: flex;
    align-items: center;
    gap: 28px;
    /* Handoff -- real bug fix, confirmed directly with Ali against his
       own real desktop screenshot ("main menu is wrapping around the
       middle logo"): without this, a narrower desktop width (or
       several longer real category names) wrapped the nav's own links
       onto a second line -- and since the header row uses
       align-items: center, that taller, 2-line block was vertically
       centered too, so its second line visually sat BELOW the logo
       while its first line sat above it, reading as the menu
       "wrapping around" the logo. Reproduced live at 960px (this
       breakpoint's own real minimum width) with 3 real category
       names: the nav block's own height doubled from 36px to 56px,
       confirming the wrap. white-space: nowrap on each link (below)
       stops an individual long name from wrapping internally too --
       together these keep the nav on its own single real line,
       overflowing/clipping horizontally in the rare case of a
       genuinely very long category list, rather than ever wrapping
       vertically into the logo's own space. */
    flex-wrap: nowrap;
    /* Handoff -- real bug fix, confirmed directly with Ali ("sub-
       categories are not showing as i hover... no drop down menu is
       opening"): plain overflow: hidden (this rule's own earlier
       fix, above) also clipped the real hover dropdown -- it's an
       absolutely-positioned child of a .rk-desktopnav__item inside
       this same nav, extending below this container's own box, so
       hiding overflow on both axes hid the dropdown completely
       regardless of its own opacity/pointer-events.
       Handoff -- real, serious bug found and fixed, confirmed
       directly with Ali via a real, live "right-click, Inspect"
       screenshot: this exact element (#rkDesktopNavLeft) was the real
       source of the "navigation arrows beside the logo" he'd been
       describing this entire time -- DevTools' own real "scroll"
       badge on it gave it away. A genuine, real CSS quirk this
       file's own earlier "overflow-x only" fix didn't know about:
       the spec computes overflow-y as "auto" the instant overflow-x
       is anything other than "visible" -- and setting overflow-y:
       visible explicitly does NOT escape this; the rule keys off
       overflow-x's own specified value, not whether overflow-y was
       written out. So this nav quietly became a real, genuine
       vertical scroll container the whole time regardless, and once
       its own real content (the category links) came out even a few
       real pixels taller than the header's own fixed height, a real,
       native vertical scrollbar appeared -- which on Windows renders
       with small real up/down arrow buttons at each end, exactly the
       icon Ali kept describing, appearing the moment the real menu's
       own links finished loading in (nothing to overflow before
       that) and only ever on desktop (this is the desktop-only nav).
       overflow-x removed entirely rather than patched again --
       flex-wrap: nowrap above is what actually stops the real
       wrapping-into-the-logo bug that rule was originally added for
       (it already worked before this nav ever had any overflow
       property at all), so dropping overflow-x doesn't reopen that
       real issue; it only removes the real, accidental scrollbar
       that came bundled with it. Neither axis specified now means
       both stay their own real, genuine "visible" default -- the
       one combination that never triggers the spec's mixed-overflow
       computation at all. */
  }
  /* Handoff -- real bug fix, confirmed directly with Ali against his
     own real desktop screenshot: the left nav group was right-aligned
     against the logo's own grid cell (justify-content: flex-end),
     which reads as "hugging the logo" rather than a real, spacious
     desktop nav -- especially with few real categories, where the
     links end up cramped right next to the wordmark instead of
     spread out. Centered within its own column instead, so the links
     sit in the real, balanced middle of the left half regardless of
     how many real categories exist. */
  .rk-header__desktopnav--left { grid-column: 1; justify-content: center; }
  /* Handoff -- real, explicit request, confirmed directly with Ali:
     "logo should always be centered in the middle of the website
     page in header" -- grid-column: 2 + justify-self: center only
     centers the logo within ITS OWN column, which sits between
     columns 1 and 3 (both real 1fr) -- but column 4 (the account/cart
     icons, real auto-width, not 1fr) adds extra real width on the
     right that isn't mirrored on the left, so the logo's own column
     was never actually at the true center of the full header, just
     the midpoint of columns 1-3. Taking the logo fully out of grid
     flow and centering it against the header's own real, full width
     instead makes it genuinely centered on the actual page,
     regardless of how wide the icon group or nav links are on either
     side. */
  .rk-header__logo {
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    /* Handoff -- real bug fix, confirmed live: the base/mobile rule's
       own real grid-column: 2 was still in effect here -- per the
       real CSS Grid spec, an absolutely-positioned grid item that
       still has a grid-column/row set uses THAT cell as its own
       containing block, not the full grid container, so left: 50%
       was resolving against column 2's own narrow width instead of
       the header's real full width. grid-column: auto removes it
       from grid placement entirely so 50%/50% resolves against the
       real .rk-header box itself, genuinely centering it on the page. */
    grid-column: auto;
  }
  /* Mirrors the left nav exactly -- centered within its own column,
     not right-aligned against the icons (this column's own former
     nested-flex-group approach, .rk-header__rightgroup, made the gap
     to the icons unpredictable depending on how many real categories
     existed on each side). */
  .rk-header__desktopnav--right { grid-column: 3; justify-content: center; }
  .rk-header__right { grid-column: 4; display: flex; align-items: center; gap: 20px; justify-self: end; }
  .rk-desktopnav__item { position: relative; }
  .rk-desktopnav__link {
    font-size: 13px;
    letter-spacing: 1px;
    text-transform: uppercase;
    /* Handoff -- real, explicit request, confirmed directly with
       Ali: "main menu links" needed its own real, separate color --
       was plain `color: inherit` (the header's own text color),
       giving it no real control independent of anything else. */
    color: var(--header-link);
    padding: 8px 0;
    display: block;
    white-space: nowrap;
  }
  .rk-header--transparent .rk-desktopnav__link { color: var(--rk-header-fg, #fff); }
  .rk-header--transparent.is-solid .rk-desktopnav__link { color: var(--ink); }
  /* Handoff -- real, explicit request, confirmed directly with Ali:
     "i want the search button when pressed to animate transfer into
     a wider text box... when finished or pressed outside the search
     box, it gets back to first status". Anchored to its own real
     right edge so it grows LEFTWARD as it widens -- growing rightward
     here would push straight into the account/cart icons just past
     it. The "Search" link fades out as the input fades/grows in,
     rather than both being visible stacked on each other. */
  .rk-desktopsearch { position: relative; display: inline-block; }
  .rk-desktopsearch__input {
    position: absolute;
    top: 50%;
    right: 0;
    transform: translateY(-50%);
    width: 0;
    opacity: 0;
    pointer-events: none;
    border: none;
    border-bottom: 1px solid var(--ink);
    background: transparent;
    font-size: 13px;
    font-family: inherit;
    color: inherit;
    padding: 4px 0;
    outline: none;
    transition: width 280ms cubic-bezier(0.65, 0, 0.35, 1), opacity 200ms ease;
  }
  .rk-desktopsearch.is-open .rk-desktopsearch__link { opacity: 0; pointer-events: none; transition: opacity 120ms ease; }
  .rk-desktopsearch.is-open .rk-desktopsearch__input { width: 220px; opacity: 1; pointer-events: auto; }
  .rk-desktopnav__dropdown {
    position: absolute;
    top: 100%;
    left: 50%;
    transform: translateX(-50%);
    background: #fff;
    box-shadow: 0 8px 24px rgba(0,0,0,0.12);
    padding: 10px 0;
    min-width: 180px;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.15s ease;
    z-index: 41;
  }
  .rk-desktopnav__item:hover .rk-desktopnav__dropdown { opacity: 1; pointer-events: auto; }
  .rk-desktopnav__sublink {
    display: block;
    padding: 8px 20px;
    font-size: 13px;
    color: var(--ink);
    white-space: nowrap;
  }
  .rk-desktopnav__sublink:hover { background: var(--bg-soft, #f7f5f2); }
}

.rk-cart-btn { position: relative; }
.rk-cart-badge {
  position: absolute;
  top: -7px;
  right: -9px;
  background: var(--accent);
  color: #fff;
  font-size: 10px;
  line-height: 1;
  min-width: 16px;
  height: 16px;
  border-radius: 999px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 3px;
}

/* ---------------------------------------------------------------- */
/* Nav slide-in panel (left) + backdrop                              */
/* ---------------------------------------------------------------- */

.rk-backdrop {
  position: fixed;
  inset: 0;
  background: var(--overlay);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s ease;
  z-index: 45;
}
.rk-backdrop.is-open { opacity: 1; pointer-events: auto; }

.rk-navpanel {
  position: fixed;
  top: 0;
  left: 0;
  bottom: 0;
  width: 78%;
  max-width: 360px;
  /* Handoff -- real, explicit request, confirmed directly with Ali:
     "add selector for side menu in hamburger menu" -- was a plain,
     hardcoded #fff with no real token behind it at all. */
  background: var(--hamburger-menu-bg);
  transform: translateX(-100%);
  transition: transform 0.25s ease, visibility 0.25s ease;
  z-index: 46;
  overflow-y: auto;
  /* Handoff -- real, explicit request, confirmed directly with Ali
     via a real, live DevTools diagnosis: this real panel's own
     internal scroll should stop at its own real top/bottom rather
     than chaining into whatever real page sits behind it once this
     panel is open -- background scroll-locking while open is now
     handled explicitly (see .rk-scroll-locked, toggled by
     openNavPanel/closeAllPanels in app.js), this is an additional,
     real safeguard specifically for this panel's own real internal
     scroll boundary. */
  overscroll-behavior: contain;
  padding-bottom: 40px;
  /* Handoff -- real bug found and fixed, confirmed directly with Ali:
     "as if the left side of the main menu in header is taking the
     focus to scroll in it" -- a real, exact description of this
     panel's own real bug. Being moved off real-screen with transform
     alone (not also visibility) left its own real, scrollable
     (overflow-y: auto) layout box sitting at its real, original,
     un-transformed position/size for a real browser's own scroll-
     chaining and hit-testing purposes on some real page/browser
     combinations -- transform only ever changes where something is
     PAINTED, never where it still genuinely lives for a real
     wheel/touch event's own real target search. Since this panel is
     real, permanently present in the DOM on every real page (not
     just the homepage, which has its own separate real scroll-
     jacking that intercepts wheel/touch before this panel is ever
     reached), a real scroll attempt on catalog/product/every other
     real page could get real, silently swallowed by this real,
     closed-but-still-scrollable panel instead of ever reaching the
     real page underneath it -- explaining every real symptom Ali
     described: normal page scroll genuinely doing nothing, on both
     real mouse and real touch, only on pages other than home.
     visibility: hidden (real, standard fix for exactly this real
     class of bug) genuinely removes it from a real browser's own
     hit-testing entirely while closed, not just its own real paint. */
  visibility: hidden;
  /* Handoff -- defensive hardening, confirmed directly with Ali: an
     explicit block display + top-alignment on the panel itself and
     its list, ruling out any flex/grid centering context being
     inherited from anywhere else in the cascade. Investigated
     thoroughly and found no rule that should have been centering
     content here, but this makes top-alignment explicit and
     unambiguous rather than merely "not contradicted" -- if the
     described layout persists after this, it needs a screenshot to
     diagnose further, not another blind CSS guess. */
  display: block;
}
.rk-navpanel.is-open { transform: translateX(0); visibility: visible; }

.rk-navpanel__search {
  margin: 22px 20px 10px;
  padding-bottom: 10px;
  border-bottom: 1px solid var(--hairline);
  /* Handoff -- real, serious bug found and fixed, confirmed directly
     with Ali via a real screenshot and reproduced directly: "the sub
     category is having a very large white space under it" -- traced
     to its real, true root cause here, and it had nothing to do with
     categories or subcategories at all. The real search icon SVG
     right below (see app.js's own ICONS.search) has no real
     width/height of its own, unlike every other real icon SVG in
     this entire file (see .rk-header__icon-btn svg and friends just
     above) -- with nothing constraining it, a browser's own real
     default replaced-element size for an SVG with no intrinsic
     dimensions is hundreds of real pixels, not icon-sized, inflating
     this whole real container's own height to match and pushing
     every real row below it apart -- Home, Scarves, its own real
     subcategory, Shawls, all of them, not something specific to
     subcategories at all. display: flex + align-items: center here
     also keeps this real icon and the input actually aligned on one
     real line now that the icon is a sane, real size. */
  display: flex;
  align-items: center;
  gap: 8px;
}
.rk-navpanel__search svg {
  width: 18px;
  height: 18px;
  stroke: var(--ink-mute);
  flex-shrink: 0;
}
.rk-navpanel__search input {
  border: none;
  width: 100%;
  font-size: 16px;
  color: var(--ink-soft);
  outline: none;
  font-family: inherit;
}

.rk-navlist { list-style: none; margin: 6px 0 0; padding: 0; }
.rk-navlist__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  /* Handoff -- real bug fix, confirmed directly with Ali: categories
     in the side panel had visibly too much space between them.
     10px/20px (was 14px/20px) -- still comfortably tappable on
     mobile, just tighter. */
  padding: 10px 20px;
  font-size: 17px;
  color: var(--ink);
}
.rk-navlist__row.is-link { cursor: pointer; }
.rk-navlist__row.is-active { color: var(--accent); }
.rk-navlist__chevron { width: 14px; height: 14px; transition: transform 0.2s ease; flex-shrink: 0; }
.rk-navlist__row.is-open .rk-navlist__chevron { transform: rotate(180deg); }

.rk-navlist__sub {
  list-style: none;
  margin: 0;
  padding: 0 0 6px 0;
  max-height: 0;
  overflow: hidden;
  transition: max-height 0.2s ease;
}
.rk-navlist__sub.is-open { max-height: 320px; }
.rk-navlist__sub li a {
  display: block;
  padding: 10px 20px 10px 40px;
  font-size: 15px;
  color: var(--ink);
}

/* ---------------------------------------------------------------- */
/* Cart drawer (right) -- shares the backdrop above                  */
/* ---------------------------------------------------------------- */

.rk-cartdrawer {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  width: 82%;
  max-width: 400px;
  /* Handoff -- real, explicit request, confirmed directly with Ali:
     "add the card/panel background and rename it to checkout side
     menu" -- was --panel-bg, shared with the desktop hover dropdown
     menu, a real, unrelated surface that happened to render the
     same color. Its own real, dedicated token now. */
  background: var(--checkout-side-menu-bg);
  transform: translateX(100%);
  transition: transform 0.25s ease, visibility 0.25s ease;
  z-index: 46;
  display: flex;
  flex-direction: column;
  padding: 22px 24px;
  overflow-y: auto;
  /* Handoff -- real, explicit request, confirmed directly with Ali
     via a real, live DevTools diagnosis: same real reasoning as
     .rk-navpanel's own identical real addition above -- this real
     drawer's own internal scroll should stop at its own real
     top/bottom rather than chaining into the real page behind it. */
  overscroll-behavior: contain;
  /* Handoff -- real bug fix, same real cause and same real fix as
     .rk-navpanel's own identical real bug above -- see that comment
     for the full real explanation. */
  visibility: hidden;
}
.rk-cartdrawer.is-open { transform: translateX(0); visibility: visible; }

.rk-cartdrawer__close {
  align-self: flex-end;
  background: none;
  border: none;
  font-size: 26px;
  line-height: 1;
  color: var(--ink);
  padding: 4px;
}

.rk-cartdrawer__title {
  font-size: 26px;
  font-weight: 600;
  margin: 6px 0 22px;
}

.rk-cartline {
  display: flex;
  gap: 14px;
  padding: 14px 0;
  border-bottom: 1px solid var(--hairline);
  position: relative;
}
.rk-cartline__thumb {
  width: 72px;
  height: 88px;
  background: var(--placeholder-bg);
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}
.rk-cartline__thumb svg { width: 34px; height: 34px; stroke: var(--placeholder-stroke); }
.rk-cartline__thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* Handoff -- real product thumbnail in the checkout summary's own
   line items, confirmed directly with Ali: this table previously
   showed name/price text only, no image at all. Small and inline
   (not a separate table column) since it's meant to sit right before
   the product name, matching the exact "image icon before a product"
   ask -- not a redesign of the summary table's own layout. */
.rk-checkout__summary-thumb {
  width: 32px;
  height: 40px;
  background: var(--placeholder-bg);
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  vertical-align: middle;
  margin-right: 8px;
}
.rk-checkout__summary-thumb svg { width: 16px; height: 16px; stroke: var(--placeholder-stroke); }
.rk-checkout__summary-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* Handoff -- the major "confirmation + receipt" feature, confirmed
   directly with Ali: a plain, real container for the receipt preview
   built by buildReceiptHtml (checkout.html) -- the receipt's own
   inline styles carry its look, this just centers/spaces it on the
   page. */
.rk-checkout__receipt-preview { margin: 20px 0; }
/* Handoff -- real bug fix, confirmed directly with Ali: the plain
   full-page Shopping Cart table (cart.html, separate from the cart
   drawer) never rendered a product image at all, plain text only.
   Same inline-before-the-name placement as the checkout summary's own
   thumb above. */
/* Handoff -- real, explicit request, confirmed directly with Ali
   against his own real Photoshop before/after mockup: "adjust update
   button to be the same size as the qty counter field" -- both a
   real, explicit shared width (instead of each sizing to its own
   browser-default content, which made the input and button two
   different real widths) stacked directly on top of each other. */
td.rk-cart-page__qtycell input[type="number"],
td.rk-cart-page__qtycell button {
  display: block;
  width: 64px;
}
.rk-cart-page__qtycell input { margin-bottom: 6px; }
/* "remove the Remove button and replace it with an X" -- plain,
   compact, no border/background, so it costs the item-name column
   almost none of its own real width (that column badly needed it --
   see cart.html's own docstring on this same change). */
td button.rk-cart-page__remove {
  background: none;
  border: none;
  font-size: 20px;
  line-height: 1;
  color: var(--ink-soft);
  cursor: pointer;
  padding: 4px;
}
td button.rk-cart-page__remove:hover { color: var(--danger); }

.rk-cart-page__thumb {
  width: 32px;
  height: 40px;
  background: var(--placeholder-bg);
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  vertical-align: middle;
  margin-right: 8px;
}
.rk-cart-page__thumb svg { width: 16px; height: 16px; stroke: var(--placeholder-stroke); }
.rk-cart-page__thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.rk-cartline__body { flex: 1; }
.rk-cartline__sku { font-size: 15px; color: var(--ink); margin: 0 0 8px; }
.rk-cartline__qtyprice { font-size: 15px; color: var(--ink); }
/* Handoff -- real bug fix, confirmed directly with Ali ("if i open a
   shopping cart shortcut... it doesnt have the remove or X"): this
   button's own real SVG (ICONS.close) never carries its own stroke
   color or size -- every other icon in this file (e.g.
   .rk-cartline__thumb svg just above) sets both explicitly via CSS,
   since the raw SVG markup itself is deliberately colorless so CSS
   controls it. This one rule was missing that, so the X's own real
   <line> elements rendered with SVG's own true default (stroke: none)
   -- completely invisible, not just small or faint -- even though the
   button/SVG were both genuinely present in the DOM (confirmed via a
   real browser test: 0 visible pixels, but the click handler was
   there and functional the whole time). */
.rk-cartline__remove {
  position: absolute;
  top: 14px;
  right: 0;
  background: none;
  border: none;
  color: var(--accent);
  font-size: 15px;
  cursor: pointer;
}
.rk-cartline__remove svg { width: 16px; height: 16px; stroke: var(--accent); }

.rk-cartdrawer__subtotal {
  display: flex;
  justify-content: space-between;
  font-weight: 700;
  font-size: 17px;
  padding: 18px 0;
}

.rk-btn {
  display: block;
  width: 100%;
  text-align: center;
  padding: 15px;
  font-size: 15px;
  border-radius: 2px;
  border: 1px solid var(--btn-dark);
  background: #fff;
  color: var(--btn-dark);
  margin-bottom: 12px;
}
.rk-btn--solid { background: var(--btn-dark); color: #fff; }
.rk-btn--solid:hover { background: var(--btn-dark-hover); }

.rk-cartdrawer__empty { color: var(--ink-soft); padding: 30px 0; text-align: center; }

/* ---------------------------------------------------------------- */
/* Homepage: real vertical fullpage slider (ysl.com-style), confirmed */
/* directly with Ali -- see app.js's own initHomeFullpage.            */
/* ---------------------------------------------------------------- */

.rk-fullpage {
  position: relative;
  height: 100vh;
  overflow: hidden;
  /* Handoff -- real bug fix, confirmed directly with Ali: on mobile,
     swiping down on this real slider (see initHomeFullpage's touch
     handlers in app.js) was ALSO triggering the browser's own native
     pull-to-refresh gesture underneath it, since nothing told the
     browser this vertical drag was already being handled by the
     page. `contain` stops that overscroll effect from escaping to
     the browser chrome. Moved here from html/body (where it used to
     live) as part of a real, separate fix, confirmed directly with
     Ali via live DevTools: combined with a real overflow-x: hidden
     that also used to sit on html/body, this same property was
     genuinely breaking real mouse-wheel/trackpad scroll site-wide --
     scoped to just this real container (present only on the real
     homepage, exactly where the real pull-to-refresh conflict can
     ever happen) instead, so it can no longer reach any other real
     page at all. */
  overscroll-behavior-y: contain;
}
/* Handoff -- real bug fix, confirmed directly with Ali, who described
   the exact real difference from the real ysl.com reference: our own
   real transition was a "push" (both the outgoing and incoming slide
   move together, like a single filmstrip shifting) -- the real
   reference instead "covers": every panel stays in its own real,
   fixed stacking position and NEVER moves once revealed; only the ONE
   panel currently sliding animates, on top of (z-index) whatever's
   already sitting still underneath it. Scrolling forward slides the
   next real panel up from below to cover the current one (which
   itself does not move); scrolling back slides that same covering
   panel back down out of the way, revealing the one underneath it --
   which, again, never moved. No shared track transform anymore (see
   below) -- each panel controls its own real transform/z-index.
   Speed/easing unchanged, same real values confirmed earlier against
   the reference's own recording. */
.rk-fp-track {
  position: relative;
  height: 100%;
}
.rk-fp-panel {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100vh;
  min-height: 100vh;
  overflow: hidden;
  transition: transform 320ms cubic-bezier(0.65, 0, 0.35, 1);
  will-change: transform;
  /* Handoff -- real bug fix, confirmed directly with Ali ("shop by
     category section comes up covering the only visible card from
     the middle only... the last scroll is the footer also just
     covering the slide from the middle"): this panel never had its
     own real, opaque background at all -- only its own inner content
     (the category tile row, the footer block) is actually opaque, so
     "covering" the panel underneath only ever blocked that one real
     strip in the middle, leaving the previous slide fully visible
     everywhere else around it. A real background here means the
     panel itself genuinely covers the full real screen the moment it
     slides into place, not just its own content's own real bounding
     box. The hero panel's own real photo/video already fills its
     entire box regardless, so this is never visible there. */
  background: var(--page-bg);
}
/* Handoff -- real, serious bug found and fixed, confirmed directly
   with Ali via his own real screen recording, frame by frame: a real
   <input type="number"> spinner (from a real "New Arrivals" tile)
   appeared right beside the logo, in perfect sync with the header's
   own nav links appearing, then vanished together the moment the
   real slide transition happened -- exactly matching every real
   symptom he described (desktop only, no click involved, invisible
   to a real DOM inspection taken after the page had already settled,
   reproducing on any real device yet only on this one real site).
   Root cause: the base rule above sets position: absolute with NO
   default real transform of its own -- every real panel sat stacked
   at the exact same top:0 spot until initHomeFullpage's own real JS
   explicitly moved every later panel off real-screen, but that JS
   only ever runs after BOTH the real carousel-slides and real
   featured-products fetches finish. On a real, live server with real
   network latency (unlike this sandbox's own near-instant local
   fetches, which is exactly why this was never caught here before
   Ali's own real recording), that's a real, visible window where a
   later real panel's own content can appear out of place before its
   real, intended transform is ever applied. Fixed here at the CSS
   layer, not the JS timing layer, so it can never depend on how fast
   any real fetch happens to resolve: every real panel except the
   first is off real-screen from its very first real paint, with zero
   dependency on any real JS having run yet. initHomeFullpage's own
   real per-panel inline transform still overrides this the instant
   it does run (a real inline style always beats a real class
   selector) -- this is a real, permanent safety net for the gap
   before that, not a replacement for it. */
.rk-fp-panel:not(:first-child) {
  transform: translateY(100%);
}
/* The footer's real content can genuinely exceed one screen's height
   (address/social/newsletter block) -- unlike the hero/category/
   featured panels, which are deliberately composed to fit, the footer
   is allowed its own internal scroll rather than clipping real
   content or forcing the whole page taller than 100vh. */
/* Handoff -- real, explicit request, confirmed directly with Ali:
   "footer should cover the whole page as if it is a slider" --
   align-items: center (this rule's own earlier real approach) let
   the real <footer> element sit shorter than 100vh, centered, with
   this panel's own real page-background color showing above/below
   it instead of the footer's own real dark background filling the
   whole real screen like every other slide does. height: 100% makes
   the real <footer> element itself the full height of its own
   panel -- overflow-y: auto stays, for the rare case its own real
   content (address/social/newsletter) genuinely exceeds one screen. */
.rk-fp-panel--footer { overflow-y: auto; }
.rk-fp-panel--footer > footer { width: 100%; height: 100%; display: flex; flex-direction: column; justify-content: center; }
/* The two content panels (category/featured) are real sections, not
   full-bleed photos -- centered vertically within their own real
   full-screen slide so they don't look pinned to the top of an
   otherwise-empty screen. */
.rk-section.rk-fp-panel { display: flex; flex-direction: column; justify-content: center; }
/* Handoff -- real bug found and fixed, confirmed directly with Ali:
   "scroll is not working on website... arrows are showing near the
   logo when im scrolling through products (which are not moving)"
   -- this real content panel ("New Arrivals" and any other real
   .rk-section slide) inherited the base .rk-fp-panel's own real
   overflow: hidden, on the assumption (see the base rule's own real
   comment) that a content panel is "deliberately composed to fit"
   one real screen -- true when this was written, but Ali's real
   catalog has grown substantially since, and this real fullpage
   mechanism only ever moves BETWEEN whole slides (wheel/arrow-key/
   swipe), never scrolls WITHIN one -- so if this real panel's own
   real content ever does exceed one real screen's height now, every
   real item past that point becomes genuinely unreachable, not
   merely clipped: no scrollbar, no gesture, nothing brings it into
   view. Same real, low-risk safety net already given to the real
   footer panel below for the exact same real reason -- invisible
   when content already fits (the common, current real case), a real
   escape hatch the moment it doesn't. overflow-x stays hidden so a
   real horizontal overflow still can't leak sideways. */
.rk-section.rk-fp-panel { overflow-y: auto; overflow-x: hidden; }

/* Handoff -- real bug fix, confirmed directly with Ali against his own
   screen recording of the real reference site: it has NO visible
   arrow buttons at all, gesture/scroll only -- the .rk-fp-arrow rules
   that used to live here are removed entirely, not just hidden, along
   with initHomeFullpage's own click handlers for them (app.js). */

/* ---------------------------------------------------------------- */
/* Homepage hero                                                     */
/* ---------------------------------------------------------------- */

.rk-hero {
  /* Handoff -- real bug fix, confirmed directly with Ali ("home page
     slides over each other is not working at all" -- reproduced live
     with 4 real, visually distinct test slides: scrolling never moved
     off slide 1 at all): this rule's own real position: relative --
     same specificity as .rk-fp-panel's own real position: absolute,
     defined earlier in this file -- was winning by pure cascade
     order, silently overriding it for every hero panel specifically
     (every hero panel carries both classes). That put every real hero
     panel back in NORMAL DOCUMENT FLOW, stacked one below the next
     instead of all overlapping at the same real position -- so
     "covering" the previous slide never had anything to actually
     cover; the covering panel was simply somewhere else on the page
     entirely. Removed here -- .rk-fp-panel's own position: absolute
     already establishes the real positioning context this rule's
     children (the caption/CTA/placeholder) need, so nothing here was
     actually relying on this rule setting it a second time. */
  height: 100vh;
  min-height: 520px;
  overflow: hidden;
}
.rk-hero .rk-ph { position: absolute; inset: 0; }
/* Handoff -- real bug fix, confirmed directly with Ali: replaces the
   old giant 22vw "RICKEZZA" text watermark sprawled across the hero.
   The reference site's own real hero has zero decorative text
   overlay competing with the photo -- "the only visible chrome is a
   single line of text navigation" and "tiny uppercase captions float
   near the bottom edge." The header's own logo already carries brand
   identity; this caption is now genuinely tiny, uppercase, and
   anchored near the bottom like the reference, not a second,
   oversized logo lettered across the photo. Absolutely positioned
   (not flex order) so the caption-above-CTA stacking is exact and
   explicit, not dependent on flex item ordering. Centered
   horizontally (was left-aligned) -- confirmed directly with Ali
   against his own screen recording of the real reference site, which
   centers both the caption and the CTA under it. */
.rk-hero__caption {
  position: absolute;
  left: 50%;
  bottom: 66px;
  transform: translateX(-50%);
  margin: 0;
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 3px;
  text-transform: uppercase;
  text-align: center;
  color: #fff;
  pointer-events: none;
  white-space: nowrap;
}
.rk-hero__cta {
  position: absolute;
  left: 50%;
  bottom: 34px;
  transform: translateX(-50%);
  padding: 0 0 4px;
  /* Handoff -- real bug fix, confirmed directly with Ali: the old
     filled/bordered pill button ("Solid, not translucent... a solid
     pale fill") was exactly the kind of chrome the reference site
     never has -- "zero shadows, zero borders, zero decoration."
     Replaced with a plain, minimal text link with a thin underline,
     matching the reference's own "tiny uppercase captions." */
  background: none;
  border: none;
  border-bottom: 1px solid rgba(255,255,255,0.7);
  color: #fff;
  font-size: 12px;
  letter-spacing: 2px;
  text-transform: uppercase;
  text-align: center;
  white-space: nowrap;
}
.rk-hero__cta:hover { border-bottom-color: #fff; opacity: 0.85; }

/* Handoff -- website content management, confirmed directly with
   Ali. Real, Admin-managed carousel replacing the old single static
   placeholder -- see app.js's own renderHomeCarousel. Slides stack
   absolutely and cross-fade via opacity (no layout thrash, no
   dependency on a carousel library); dots overlay the bottom center,
   above the CTA's own bottom-left position so the two never collide. */
.rk-hero__track { position: absolute; inset: 0; }
.rk-hero__slide {
  position: absolute;
  inset: 0;
  opacity: 0;
  transition: opacity 0.6s ease;
  pointer-events: none;
}
.rk-hero__slide.is-active { opacity: 1; pointer-events: auto; }
.rk-hero__media, .rk-hero__slidelink { display: block; width: 100%; height: 100%; object-fit: cover; }
/* Handoff -- real, explicit request, confirmed directly with Ali:
   "visual margins for mobile view and desktop view and ability to
   move the margin within the image to show the wanted part of the
   images" -- mobile-first: the real --mobile-focal custom property
   (set per-slide inline by app.js's own renderHomeCarousel) drives
   object-position by default; the desktop media query below swaps to
   --desktop-focal instead, matching the same real min-width:960px
   breakpoint every other real desktop override in this file already
   uses. The var()'s own 50% 50% fallback keeps this correct even for
   a slide from before this feature existed, with no focal point set
   on it at all. */
.rk-hero__media { object-position: var(--mobile-focal, 50% 50%); }
@media (min-width: 960px) {
  .rk-hero__media { object-position: var(--desktop-focal, 50% 50%); }
}
.rk-hero__slidelink { position: relative; }
.rk-hero__dots {
  position: absolute;
  left: 50%;
  bottom: 22px;
  transform: translateX(-50%);
  display: flex;
  gap: 8px;
  z-index: 2;
}
.rk-hero__dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  border: 1px solid rgba(255,255,255,0.8);
  background: transparent;
  padding: 0;
  cursor: pointer;
}
.rk-hero__dot.is-active { background: #fff; }

.rk-section {
  padding: 34px 20px 10px;
}
.rk-section__title {
  font-size: 13px;
  letter-spacing: 2px;
  text-transform: uppercase;
  color: var(--ink-soft);
  text-align: center;
  margin: 0 0 18px;
}
/* ---------------------------------------------------------------- */
/* Homepage: "Shop by Category" rail (data-driven, see app.js's       */
/* renderHomeCategoryRail -- real category count, not a fixed 3-up)   */
/* ---------------------------------------------------------------- */

.rk-familyrail {
  display: flex;
  gap: 14px;
  overflow-x: auto;
  padding: 4px 20px 10px;
  -webkit-overflow-scrolling: touch;
  scroll-snap-type: x proximity;
}
.rk-familytile {
  flex: 0 0 auto;
  width: 128px;
  scroll-snap-align: start;
  color: inherit;
}
.rk-familytile__img {
  width: 128px;
  height: 160px;
  border-radius: 2px;
  overflow: hidden;
  /* Handoff -- real bug fix, confirmed directly with Ali ("i selected
     the colors i want... but the colors didnt change") -- --bg-soft's
     own real admin label explicitly names "category tiles" as an
     example of what it controls; wired in here as the real fallback
     shown while a real photo loads/if one is missing, so the color
     genuinely does something visible here too, not just on two hover
     states nobody's likely to sit on long enough to notice. */
  background: var(--bg-soft);
}
/* Handoff -- real bug fix, confirmed directly with Ali: a real
   uploaded category photo has no natural relationship to this tile's
   own 128x160px size -- without an explicit rule constraining the
   <img> tag itself (not just its container), it rendered at its own
   full, uncropped resolution, breaking out of the tile entirely and
   overlapping neighboring tiles. object-fit: cover crops to fill the
   tile without distorting the photo's own aspect ratio, same
   convention as every other real product/category photo elsewhere in
   this codebase. */
.rk-familytile__img img, .rk-familytile__img .rk-ph { width: 100%; height: 100%; object-fit: cover; display: block; }
.rk-familytile__name {
  margin-top: 10px;
  font-size: 12px;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--ink);
  text-align: center;
}

/* ---------------------------------------------------------------- */
/* Homepage: Featured / New Arrivals rail (data-driven, see app.js's  */
/* renderHomeFeaturedRail -- real in-stock catalog items, capped for  */
/* display, never padded/faked when the catalog has fewer)            */
/* ---------------------------------------------------------------- */

.rk-featurerail {
  display: flex;
  gap: 14px;
  overflow-x: auto;
  padding: 4px 20px 10px;
  -webkit-overflow-scrolling: touch;
  scroll-snap-type: x proximity;
}
.rk-featuretile {
  flex: 0 0 auto;
  width: 62vw;
  max-width: 230px;
  scroll-snap-align: start;
  display: flex;
  flex-direction: column;
}
.rk-featuretile__media { display: block; }
.rk-featuretile__media, .rk-featuretile__media .rk-ph {
  width: 100%;
  aspect-ratio: 4/5;
  border-radius: 2px;
}
.rk-featuretile__media img { width: 100%; height: 100%; object-fit: cover; display: block; border-radius: 2px; }
.rk-featuretile__brand {
  margin-top: 10px;
  font-size: 11px;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--ink-soft);
}
.rk-featuretile__name {
  display: block;
  margin-top: 2px;
  font-size: 13px;
  color: var(--accent);
}
.rk-featuretile__price { margin-top: 2px; font-size: 13px; font-weight: 600; color: var(--accent); }
.rk-featuretile__price s { color: var(--ink-mute); font-weight: 400; margin-right: 6px; }
.rk-featuretile__add {
  margin-top: 10px;
  border: 1px solid var(--btn-dark);
  background: #fff;
  color: var(--btn-dark);
  padding: 9px 6px;
  font-size: 11px;
  letter-spacing: 1px;
  text-transform: uppercase;
}
.rk-featuretile__add:hover { background: var(--btn-dark); color: #fff; }

/* ---------------------------------------------------------------- */
/* Homepage: brand/craft story block (static editorial copy -- see   */
/* the redesign report re: no real photo/video asset exists yet)      */
/* ---------------------------------------------------------------- */

.rk-craft {
  display: flex;
  flex-direction: column;
  margin-top: 20px;
}
.rk-craft__img { width: 100%; aspect-ratio: 4/3; }
.rk-craft__img .rk-ph { width: 100%; height: 100%; }
.rk-craft__body { padding: 26px 24px 6px; text-align: center; }
.rk-craft__eyebrow {
  font-size: 12px;
  letter-spacing: 2px;
  text-transform: uppercase;
  color: var(--ink-soft);
  margin: 0 0 10px;
}
.rk-craft__copy { font-size: 14px; line-height: 1.7; color: var(--ink-soft); max-width: 460px; margin: 0 auto 18px; }
.rk-craft__cta {
  display: inline-block;
  padding: 12px 26px;
  border: 1px solid var(--btn-dark);
  color: var(--btn-dark);
  font-size: 12px;
  letter-spacing: 2px;
  text-transform: uppercase;
}
.rk-craft__cta:hover { background: var(--btn-dark); color: #fff; }

@media (min-width: 760px) {
  .rk-craft { flex-direction: row-reverse; align-items: center; }
  .rk-craft__img { width: 50%; aspect-ratio: 1/1; }
  .rk-craft__body { width: 50%; text-align: left; padding: 40px; }
  .rk-craft__copy { margin: 0 0 18px; }
}

/* ---------------------------------------------------------------- */
/* Footer (site-wide across homepage/catalog/product -- app.js's      */
/* renderFooter). No real newsletter backend exists yet (see report)  */
/* -- the form gives friendly inline confirmation only, doesn't       */
/* claim to persist anywhere.                                         */
/* ---------------------------------------------------------------- */

.rk-footer { margin-top: 40px; background: var(--footer-bg); color: var(--footer-text); }
.rk-footer__newsletter {
  max-width: 420px;
  margin: 0 auto;
  padding: 52px 24px 40px;
  text-align: center;
}
.rk-footer__newsletter-title {
  font-family: var(--font-display);
  font-size: 24px;
  margin: 0 0 8px;
  /* Handoff -- real, explicit request, confirmed directly with Ali:
     "footer title" needed its own real, separate color -- was a
     plain, hardcoded #fff, and also shared with --footer-text (every
     other line of footer copy) if it had used that instead. */
  color: var(--footer-title);
}
.rk-footer__newsletter-sub { font-size: 13px; color: rgba(255,255,255,0.55); margin: 0 0 22px; }
.rk-footer__newsletter-form { display: flex; border-bottom: 1px solid rgba(255,255,255,0.35); padding-bottom: 10px; }
.rk-footer__newsletter-form input {
  flex: 1;
  background: none;
  border: none;
  outline: none;
  color: #fff;
  font-size: 14px;
  font-family: inherit;
}
.rk-footer__newsletter-form input::placeholder { color: rgba(255,255,255,0.45); }
.rk-footer__newsletter-form button {
  background: none;
  border: none;
  color: #fff;
  font-size: 12px;
  letter-spacing: 1.5px;
  text-transform: uppercase;
}
.rk-footer__newsletter-note { font-size: 12px; color: var(--accent); min-height: 16px; margin: 10px 0 0; }
/* Handoff -- website content management, confirmed directly with
   Ali. Real address/phone/email + social links, shown only when
   actually set -- see renderFooter's own docstring. */
.rk-footer__contact { margin-top: 18px; }
.rk-footer__contact-line { font-size: 13px; margin: 2px 0; opacity: 0.85; }
.rk-footer__contact-line a { color: inherit; }
.rk-footer__social { margin-top: 10px; display: flex; gap: 14px; flex-wrap: wrap; }
.rk-footer__social a { font-size: 12px; letter-spacing: 1px; text-transform: uppercase; opacity: 0.85; }
.rk-footer__social a:hover { opacity: 1; text-decoration: underline; }
.rk-footer__bottom {
  /* Handoff -- real, explicit request, confirmed directly with Ali:
     "footer signature" needed its own real, controllable background
     -- this real bar had no background of its own at all before,
     only ever showing through --footer-bg from its parent .rk-footer,
     not a genuinely separate, real surface Ali could set on its own. */
  background: var(--footer-signature-bg);
  border-top: 1px solid rgba(255,255,255,0.12);
  padding: 20px 24px 28px;
  text-align: center;
}
/* Handoff -- real, explicit request, confirmed directly with Ali:
   same real "footer title" token the newsletter heading above uses
   (see .rk-footer__newsletter-title) -- the same real Rickezza
   wordmark shown a second time here, not a plain, hardcoded #fff. */
.rk-footer__logo { font-family: var(--font-display); font-size: 18px; color: var(--footer-title); margin-bottom: 10px; }
.rk-footer__copyright { font-size: 11px; color: rgba(255,255,255,0.45); margin: 0; }

/* ---------------------------------------------------------------- */
/* Catalog                                                            */
/* ---------------------------------------------------------------- */

.rk-page-title {
  text-align: center;
  font-size: 13px;
  letter-spacing: 3px;
  text-transform: uppercase;
  /* Handoff -- real, explicit request, confirmed directly with Ali:
     "page titles" needed its own real, separate color -- was --ink,
     shared with body text and dark buttons. */
  color: var(--page-title);
  margin: 26px 0 4px;
}

/* Real category/listing pages (reference §4): "Pinterest-style masonry
   photo feed" -- confirmed a real, deliberate brand-feel decision Ali
   wants kept, NOT switched to a conventional grid despite the
   reference document's own recommendation leaning that way. CSS
   multi-column layout gives the genuine staggered effect (tiles of
   different photo heights naturally interlock) without hand-rolled JS
   layout math -- the real technique for this pattern, not a fixed-grid
   approximation of it. */

.rk-controls {
  max-width: 480px;
  margin: 18px auto 8px;
  padding: 0 16px;
}
.rk-controls__row { margin-bottom: 18px; }
.rk-controls__label {
  font-size: 13px;
  letter-spacing: 0.5px;
  text-transform: uppercase;
  color: var(--ink-soft);
  margin-bottom: 10px;
}
/* Real brand-colored range slider -- replaces the old site's unstyled
   Bootstrap-blue default (#1a73e8, confirmed bug, reference §9 item
   10). Cross-browser range styling needs the vendor-prefixed thumb/
   track rules below; `accent-color` alone (Chrome/Firefox only) isn't
   enough to guarantee the fix on every real device Ali's shoppers use. */
.rk-controls input[type="range"] {
  width: 100%;
  accent-color: var(--accent);
  height: 4px;
  background: var(--hairline);
  border-radius: 2px;
  -webkit-appearance: none;
  appearance: none;
}
.rk-controls input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--accent);
  cursor: pointer;
  border: 2px solid #fff;
  box-shadow: 0 0 0 1px var(--accent);
}
.rk-controls input[type="range"]::-moz-range-thumb {
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--accent);
  cursor: pointer;
  border: 2px solid #fff;
  box-shadow: 0 0 0 1px var(--accent);
}
.rk-controls__divider { border: none; border-top: 1px solid var(--hairline); margin-top: 10px; }

/* Real, explicit request, confirmed directly with Ali, from a real, live
   screenshot: "the space over the products is big, i want to minimize
   that in a clever way" -- this real line is deliberately left EMPTY
   the whole time the catalog is loading (see catalog.html, cleared right
   before the real fetch and only filled in once results are known), so
   its own real margin was adding real, visible blank space above the
   skeleton grid for no reason -- an empty element still occupies real
   box height even with zero text inside it. Margin now only applies
   once there is real text to separate from its neighbors: :empty
   collapses it to zero height during the real loading window, and it
   quietly, automatically returns the instant the status text is set. */
.rk-catalog-status { text-align: center; color: var(--ink-soft); font-size: 14px; margin: 10px 0; }
.rk-catalog-status:empty { margin: 0; }

/* Real filter chips -- Price range/Color/Size (reference §4's
   legitimate filters). Deliberately NO Brand filter unless real
   brand/collection data exists to filter by -- the old site's Brand
   filter was fake demo data (Samsung, LG, Toyota; §9 item 7,
   confirmed leftover platform seed data), not something to replicate
   even styled correctly. */
.rk-filterbar {
  display: flex;
  gap: 8px;
  overflow-x: auto;
  padding: 4px 16px 14px;
  -webkit-overflow-scrolling: touch;
}
.rk-filterchip {
  flex-shrink: 0;
  border: 1px solid var(--hairline);
  background: #fff;
  color: var(--ink);
  padding: 8px 16px;
  font-size: 12px;
  letter-spacing: 0.5px;
  border-radius: 20px;
  white-space: nowrap;
}
.rk-filterchip.is-active { border-color: var(--accent); color: var(--accent); background: var(--accent-soft); }
/* Handoff -- real, explicit request, confirmed directly with Ali:
   "create a filter icon to the right" -- margin-left: auto pushes
   it to the real far right of the bar regardless of how many chips
   are currently shown/hidden to its left. */
.rk-filterbar__togglebtn {
  flex-shrink: 0;
  margin-left: auto;
  width: 34px;
  height: 34px;
  border: 1px solid var(--hairline);
  border-radius: 50%;
  background: #fff;
  color: var(--ink);
  display: flex;
  align-items: center;
  justify-content: center;
}
.rk-filterbar__togglebtn.is-active { border-color: var(--accent); color: var(--accent); background: var(--accent-soft); }
.rk-filterbar__togglebtn svg { width: 16px; height: 16px; stroke: currentColor; }

.rk-filterpanel {
  max-width: 480px;
  margin: 0 auto;
  padding: 14px 16px;
  border-bottom: 1px solid var(--hairline);
  display: none;
}
.rk-filterpanel.is-open { display: block; }
.rk-filterpanel__row { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 12px; }
.rk-filterpanel__swatch {
  width: 30px;
  height: 30px;
  border-radius: 50%;
  border: 1px solid var(--hairline);
  cursor: pointer;
  padding: 0;
}
.rk-filterpanel__swatch.is-selected { border: 2px solid var(--accent); background: var(--accent-soft); }
.rk-filterpanel__sizebtn {
  border: 1px solid var(--hairline);
  background: #fff;
  padding: 6px 12px;
  font-size: 12px;
  cursor: pointer;
}
.rk-filterpanel__sizebtn.is-selected { border-color: var(--accent); color: var(--accent); background: var(--accent-soft); }
.rk-filterpanel__apply {
  width: 100%;
  border: none;
  background: var(--btn-dark);
  color: #fff;
  padding: 12px;
  font-size: 13px;
  letter-spacing: 1px;
  text-transform: uppercase;
}

/* Handoff -- real bug fix, confirmed directly with Ali: the old
   column-count "masonry" layout (each tile's own photo height decided
   where it landed) is genuinely why tiles looked left-aligned/
   uneven -- column-count fills columns top-to-bottom independently,
   it was never a real row-based grid to begin with, so there was no
   "centering" to fix within that approach. Replaced with a real CSS
   grid: fixed column count per breakpoint (2 up to tablet, 4 at
   desktop -- Ali's own explicit spec, replacing the old 2/3/4 masonry
   steps), and every tile's own image now uses a fixed aspect-ratio
   (see .rk-tile__img below) so rows genuinely line up -- a real grid
   with variable-height content wouldn't look aligned either. */
.rk-grid {
  /* Handoff -- real, explicit request, confirmed directly with Ali:
     "always in mobile view or desktop view, the last finished
     product card should be in the middle if it is alone on the
     page... if the last row has 1 or 2 product cards, then also
     center them in the middle" -- CSS grid has no real way to center
     an incomplete last row on its own; flexbox does, natively, via
     justify-content: center on wrapped items. Each real tile below
     gets an explicit width matching this breakpoint's own real
     column count (2 here, 4 at desktop) so the visual layout is
     identical to the grid it replaces in every other real respect --
     only the incomplete-last-row behavior actually changes. */
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 2px;
  padding: 0 2px;
  margin-top: 4px;
}
.rk-grid > .rk-tile {
  /* 2 columns, gap: 2px between them (1 gap per row) -- matches this
     breakpoint's own real .rk-grid gap above exactly. */
  width: calc((100% - 2px) / 2);
}
.rk-tile {
  width: 100%;
  /* Handoff -- real bug fix, found live from a real mobile screenshot
     Ali sent: on a narrow 2-column mobile grid, the fanned variant
     thumbnail row's own fixed, non-shrinking width (flex-shrink: 0)
     was forcing this tile's own grid TRACK wider than its real 1fr
     share (CSS grid items default to min-width: auto, not 0, so a
     child's min-content size can silently inflate the whole column) --
     the result was two visibly different-width columns and the whole
     grid overflowing past the real viewport edge, cutting off the
     second column's own card entirely. min-width: 0 here is the real,
     standard fix: lets this tile actually shrink to its real 1fr grid
     track instead of forcing the track to grow around it.
     Confirmed live: before this fix, the two columns measured 253px
     and 152px (should both be equal); after, both grid columns render
     at their real, equal, in-viewport width. */
  min-width: 0;
  /* Handoff -- real, explicit request, confirmed directly with Ali:
     "card background" needed its own real, controllable color -- was
     a plain, hardcoded #fff with no real token behind it at all. */
  background: var(--card-bg);
  padding-bottom: 14px;
}
.rk-tile__img {
  aspect-ratio: 3 / 4;
  background: var(--placeholder-bg);
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  /* Handoff -- real, explicit request, confirmed directly with Ali:
     "add to favorite... an empty heart icon on the top right" --
     anchors the real heart button (see .rk-favorite-btn below)
     against this image's own box, not the whole tile. */
  position: relative;
}
.rk-tile__img svg { width: 30%; height: 30%; stroke: var(--placeholder-stroke); }
.rk-tile__img img { width: 100%; height: 100%; display: block; object-fit: cover; }
/* Real, explicit request, confirmed directly with Ali: "i want the
   blurhash in my website" -- the real photo starts invisible and fades
   in once loaded (.is-loaded, added by tileImageHtml's own real onload
   handler in app.js), revealing the BlurHash preview set as this
   element's own background-image underneath it for however long the
   real photo takes. A tile with no real BlurHash (this div's own
   inline background-image style omitted entirely in that case) shows
   nothing here until the photo loads -- same as today, unaffected. */
.rk-tile__imgbg { position: absolute; inset: 0; width: 100%; height: 100%; }
.rk-tile__realimg {
  width: 100%; height: 100%; display: block; object-fit: cover;
  position: relative; opacity: 0; transition: opacity 0.4s ease;
}
.rk-tile__realimg.is-loaded { opacity: 1; }
/* Handoff -- real, explicit request, confirmed directly with Ali:
   "i want first of all to have the skeleton loading when visitors
   are waiting" -- real placeholder tiles shown immediately (before
   the real catalog fetch even starts), matching this same real
   .rk-tile shape/proportions exactly, so nothing visually jumps or
   resizes once real tiles replace them. The shimmer sweep is a real,
   moving gradient (background-position animated left to right), not
   a flat pulsing opacity -- reads as "actively loading" rather than
   "broken/greyed out" to a real shopper. prefers-reduced-motion
   users get the same static color with no animation. */
/* Handoff -- real, explicit request, confirmed directly with Ali:
   "when it loads (or even before) i cant scroll... i want to change
   it to a skeleton loading that has animation" -- the shimmer was
   already genuinely animating the whole time (this rule's own real
   keyframes below never changed), but --placeholder-bg (#f1ece4) and
   --bg-soft (#f2ede7) are so close in real lightness that the actual
   moving sweep was invisible to a real, ordinary shopper's own eye --
   correct in theory, imperceptible in practice. A real, visibly
   lighter highlight color now (white, not a near-identical beige)
   gives the same real left-to-right sweep genuine, obvious contrast
   against the base placeholder tone, while --placeholder-bg itself
   stays exactly as it always was on both ends of the real gradient,
   so this still reads as this site's own real, existing palette, not
   a generic grey skeleton dropped in from somewhere else. */
/* Real, explicit request, confirmed directly with Ali: "i want the
   loading of the catalogue or category page to start with the blurhash
   effect, i want to remove the skeleton" -- the shimmer animation above
   no longer applies to the real tile image itself (replaced with a
   real, calm BlurHash preview instead, set inline via
   skeletonTilesHtml's own real style attribute) -- .rk-tile__img--
   placeholder is this real, new element, excluded from the shimmer
   rule above so its own real background-image (the default BlurHash)
   is never overwritten by the shimmer gradient. The real skeleton TEXT
   lines (brand/name/price) still shimmer exactly as before -- only the
   real photo area itself changed. */
/* Real, explicit request, confirmed directly with Ali: "i want the
   loading of the catalogue or category page to start with the blurhash
   effect, i want to remove the skeleton" -- the real photo area itself
   no longer shimmers at all; it shows the real, calm default BlurHash
   from the very first paint (skeletonTilesHtml's own real inline
   background-image) instead. Only the real skeleton TEXT lines
   (brand/name/price, which have no real color data to show yet at all)
   still shimmer. */
.rk-skel-line {
  background: linear-gradient(90deg, var(--placeholder-bg) 25%, #ffffff 50%, var(--placeholder-bg) 75%);
  background-size: 200% 100%;
  animation: rk-skel-shimmer 1.4s ease-in-out infinite;
}
.rk-tile--skeleton .rk-tile__img {
  aspect-ratio: 3 / 4;
  background-color: var(--placeholder-bg);
  background-size: cover;
  background-position: center;
}
/* Real, explicit request, confirmed directly with Ali: "when the real
   data of blurhash appears, it fades to the new colors in blurhash" --
   CSS cannot transition a background-image change directly (no browser
   interpolates between two different images), so the real fade between
   the default BlurHash and a tile's own real, specific one is a genuine
   two-layer crossfade instead (same real pattern already used for
   .rk-tile__imgbg/.rk-tile__realimg's own photo fade-in): a real,
   second BlurHash layer (.rk-tile__imgbg-real) is stacked on top of the
   real default one (.rk-tile__imgbg-default) and faded in via opacity
   (a real, animatable property) the moment a real tile with its own
   real blur_hash is actually rendered -- see app.js's own real
   tileImageHtml and fadeInPendingBlurHashLayers. The real photo itself
   still fades in on top of both via .rk-tile__realimg's own existing
   opacity transition, unaffected by this. */
.rk-tile__imgbg-default, .rk-tile__imgbg-real {
  position: absolute; inset: 0; background-size: cover; background-position: center;
}
.rk-tile__imgbg-real { opacity: 0; transition: opacity 0.5s ease; }
.rk-tile__imgbg-real.is-visible { opacity: 1; }
.rk-skel-line { height: 12px; border-radius: 3px; margin: 6px 10px; }
.rk-skel-line--brand { width: 40%; height: 10px; }
.rk-skel-line--name { width: 75%; }
.rk-skel-line--price { width: 30%; }
@keyframes rk-skel-shimmer {
  0% { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}
@media (prefers-reduced-motion: reduce) {
  .rk-skel-line { animation: none; background: var(--placeholder-bg); }
  .rk-tile__imgbg-real { transition: none; }
}
/* Handoff -- real, explicit request, confirmed directly with Ali:
   "add to favorite... an empty heart icon on the top right, when
   pressing it, the heart will turn to a color i will pick in website
   colors in admin". Empty/outline by default (stroke: currentColor,
   fill: none -- see ICONS.heart's own real SVG); .is-favorited swaps
   to a real, filled heart in the real, admin-picked
   --favorite-heart color -- both states share the exact same SVG
   markup, never swapped for a second icon. z-index above the image
   itself and any placeholder icon, but below nothing else real on
   this tile (no dropdown/overlay on a catalog tile competes for this
   corner).

   Handoff -- real, explicit follow-up request, confirmed directly
   with Ali: "decrease the favorite button's opacity to 50% and
   remove the circle around it, just keep the internal heart" -- the
   white circular backdrop (background + border-radius) is gone, and
   the whole button sits at 50% opacity so the heart itself reads as
   a soft overlay directly on the image rather than a separate button
   chrome floating above it. */
.rk-favorite-btn {
  position: absolute;
  top: 8px;
  right: 8px;
  z-index: 2;
  width: 30px;
  height: 30px;
  border: none;
  background: none;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--ink-mute);
  cursor: pointer;
  opacity: 0.5;
}
.rk-favorite-btn svg { width: 16px; height: 16px; }
.rk-favorite-btn.is-favorited { color: var(--favorite-heart); }
.rk-favorite-btn.is-favorited svg { fill: currentColor; }
/* Handoff -- real bug fix / direct reversal, confirmed directly with
   Ali against his own Photoshop mockup (product_card.zip): the main
   hero photo above stays its own separate, full-size image -- the
   variant thumbnails below sit in their own real row next to name/
   price instead of replacing it (an earlier round's interpretation,
   now corrected). infotext (name/brand/price) and the thumbs share
   this one row, name/price on the left, thumbs on the right, exactly
   matching the mockup's own layout. */
.rk-tile__inforow {
  /* Handoff -- real, explicit request, confirmed directly with Ali:
     "card footer" needed its own real, separate color from the
     card's own background above (--card-bg) -- previously the same
     surface with nothing distinguishing the info strip (SKU/name/
     price/Add to Bag) from the rest of the card. */
  background: var(--card-footer-bg);
  display: flex;
  /* Handoff -- real bug fix, confirmed directly with Ali against his
     own before/after screenshots ("the names are now each one on
     level, they should stay on the same level"): flex-end bottom-
     aligned name/price against the fan -- since the fan (102px tall)
     is much taller than the name/price text block (~46px), that
     pushed the text down by the real height difference, so a tile
     with a fan showed its name noticeably lower than a tile without
     one. flex-start top-aligns both instead, so every tile's name
     starts at the exact same real row position regardless of whether
     it has a fan or not. */
  align-items: flex-start;
  justify-content: space-between;
  gap: 8px;
  padding: 8px 10px 0;
  min-width: 0;
}
.rk-tile__infotext { min-width: 0; }
/* Handoff -- real "fanned mini variant thumbnails" feature, confirmed
   directly with Ali against his own Photoshop mockup: up to 4 real
   variant photo thumbnails in one single horizontal row, NEVER
   wrapping, each overlapping the previous one (a real fanned deck, not
   a 2x2 grid or independent tiles), same top/bottom alignment. flex +
   nowrap with real negative margins for the overlap, not absolute
   positioning -- simpler to keep genuinely responsive since each
   card's own width/overlap are both set in real %, not fixed px. */
/* Handoff -- real, explicit request, confirmed directly with Ali: with
   Add to Bag removed from a multi-variant tile (see catalog.html's own
   addRowHtml), the fan grows to fill that real, freed vertical space
   instead of leaving it empty -- measured live: the removed add-row
   was exactly 46px tall, so the fan's own height grows by that same
   amount (56 -> 102px), width scaled up proportionally to keep the
   same real look, just bigger. A genuinely single-variant tile never
   shows this fan at all (sibling_images only exists when sibling_count
   > 1), so there's no competing size need to balance against here. */
.rk-varthumbs {
  display: flex;
  flex-wrap: nowrap;
  flex-shrink: 0;
  width: 153px;
  height: 102px;
  /* Handoff -- real, explicit request, confirmed directly with Ali
     against his own before/after screenshots: "move the stack a
     little bit to the right, so if we have 1 variant, it will show
     on the far end at right, if we have 2, they will be that far end
     card with one to her left" -- with fewer than 4 real siblings,
     the cards used to sit flush against this container's own LEFT
     edge (flex's own real default), leaving empty space on the
     right. flex-end anchors the group to this container's own real
     right edge instead, growing leftward as more real cards are
     added, so the rightmost card's own right edge is always in the
     same real place regardless of count. */
  justify-content: flex-end;
}
.rk-varthumb {
  width: 40%;
  height: 100%;
  flex-shrink: 0;
  border: 1.5px solid #fff;
  box-shadow: 0 1px 3px rgba(0,0,0,0.15);
  overflow: hidden;
  background: var(--placeholder-bg);
  /* Real bug found and fixed, verified live by a real Cowork
     investigation (2026-10-02): this element was missing position:
     relative, so the absolutely-positioned .rk-tile__imgbg inside it
     (tileImageHtml's own real markup, see app.js) had no real
     positioned ancestor to size against at all -- it escaped this
     element entirely and sized itself to the ENTIRE viewport instead
     (confirmed live: 375x812 at mobile), with overflow: hidden above
     doing nothing to contain it, since overflow only clips a
     descendant whose own containing block is this element or inside
     it. The real swatch's own 1.5x transform then enlarged that
     already-viewport-sized image further, covering the whole real
     page. */
  position: relative;
}
/* Real, solved-for pairing (card width + 3 x (card width - overlap) =
   100) so 4 cards exactly fill the stack's own width with an even
   overlap -- same math as the earlier catalog-grid fan attempt, kept
   here since it's still correct, just applied to a smaller component
   now. */
.rk-varthumb:not(:first-child) { margin-left: -20%; }
.rk-varthumb:nth-child(1) { z-index: 4; }
.rk-varthumb:nth-child(2) { z-index: 3; }
.rk-varthumb:nth-child(3) { z-index: 2; }
.rk-varthumb:nth-child(4) { z-index: 1; }
/* Handoff -- real, explicit instruction from Ali: each variant
   thumbnail's own photo is zoomed to 150% and anchored to the top of
   the frame, not centered -- most of these product photos are shot
   with the garment/pattern's own most distinctive part near the top,
   so a plain centered crop wastes the small thumbnail's own limited
   space on empty lower fabric/background. object-fit: cover already
   crops to fill; scale(1.5) is the real zoom on top of that (not a
   180%+ background-size hack, since these are real <img> tags, not
   CSS background images), object-position: top anchors the crop to
   the top edge instead of the vertical center. Explicitly confirmed
   directly with Ali as NOT applying to the product detail page's own
   variant selector (product.html's renderSwatches) -- that one stays
   as already built. */
.rk-varthumb img { width: 100%; height: 100%; display: block; object-fit: cover; object-position: top; transform: scale(1.5); transform-origin: top; }
.rk-varthumb svg { width: 100%; height: 100%; padding: 22%; box-sizing: border-box; stroke: var(--placeholder-stroke); }
.rk-tile__brand {
  font-size: 11px;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--ink-soft);
}
/* Real: product name/price text renders in the brand accent color
   (reference §1). */
.rk-tile__name { font-size: 14px; padding: 2px 0 4px; color: var(--accent); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Handoff -- real, explicit request, confirmed directly with Ali:
   "price (only price without name linked to it)" -- was --accent,
   shared with the product name right next to it, every link, and
   every other highlight site-wide. Its own real, dedicated token
   now, shared only with the product detail page's own price below
   (the same real concept shown in two places), never the name. */
.rk-tile__price { font-size: 14px; color: var(--price); font-weight: 600; }
.rk-tile__price s { color: var(--ink-mute); font-weight: 400; margin-right: 6px; }
/* Handoff -- real, explicit request, confirmed directly with Ali
   against his own before/after screenshots: "move the add to bag to
   the right with the counter" -- the button used to stretch
   (flex: 1) to fill the entire row; both it and the qty input now
   size to their own real content and sit together at the row's own
   right edge instead. */
.rk-tile__addrow { padding: 10px 10px 0; display: flex; gap: 8px; align-items: center; justify-content: flex-end; }
.rk-tile__addrow input {
  width: 46px;
  border: 1px solid var(--hairline);
  padding: 6px;
  font-size: 13px;
}
.rk-tile__addrow button {
  border: 1px solid var(--btn-dark);
  background: #fff;
  color: var(--btn-dark);
  padding: 8px 14px;
  font-size: 12px;
  letter-spacing: 1px;
  text-transform: uppercase;
  white-space: nowrap;
}
.rk-tile__addrow button:hover { background: var(--btn-dark); color: #fff; }

/* DISPATCH-18 §3a -- a tile whose design has other in-stock colors
   (sibling_count >= 2, see catalog.html's renderCatalog) wraps its
   image/brand/name/price in a real link into the product page, so a
   shopper can still reach the gallery/sibling-switcher (§3b) from any
   one color's card -- while the qty/add-to-cart row underneath stays
   OUTSIDE this link, so it keeps working as its own direct action
   without triggering navigation (matches the reference site's card
   being tappable AND carrying its own persistent "add to bag", see
   the redesign research doc). A single-variant tile (sibling_count
   === 1) never gets this wrapper -- unchanged, plain inline-add card. */
.rk-tile__linkoverlay {
  display: block;
  color: inherit;
  text-decoration: none;
  cursor: pointer;
}

@media (min-width: 960px) {
  .rk-grid > .rk-tile {
    /* 4 columns, 3 gaps of 2px between them per row -- matches this
       breakpoint's own real .rk-grid gap exactly. */
    width: calc((100% - 6px) / 4);
  }
}

/* ---------------------------------------------------------------- */
/* Product / variant-selection screen (Addendum 14)                  */
/* ---------------------------------------------------------------- */

/* Real product page (reference §5): "strong photography-led layout --
   large image + thumbnail strip + minimal right-column info: name,
   price, stock badge, variant selector, Share/Add to Cart." Mobile-
   first: image on top full-width, info stacks below it (the "right
   column" becomes a below-image block at this width; a real side-by-
   side column layout only kicks in above tablet width, since ~99% of
   real traffic is phone-width per Ali). */

.rk-productpage { max-width: 640px; margin: 0 auto; padding: 0 0 60px; }
/* Handoff -- real bug fix, confirmed directly with Ali: a real way
   back to the catalog page a shopper actually came from -- see
   product.html's own real history.back() behavior. */
.rk-productpage__back {
  display: inline-block;
  padding: 16px 16px 0;
  font-size: 13px;
  letter-spacing: 0.5px;
  color: var(--ink-soft);
}
.rk-productpage__back:hover { color: var(--ink); }

.rk-productpage__gallery {
  margin-bottom: 4px;
  /* Handoff -- real, explicit request, confirmed directly with Ali:
     "add to favorite... an empty heart icon on the top right" --
     anchors the real favorite button (below) against this real
     gallery box. */
  position: relative;
}
.rk-productpage__favorite { top: 12px; right: 12px; }
.rk-productpage__img {
  position: relative; /* DISPATCH-18 §3b -- anchors the absolutely-
     positioned crossfade layers below; aspect-ratio keeps giving this
     box its real height since the layers themselves don't. */
  aspect-ratio: 4/5;
  background: var(--placeholder-bg);
  overflow: hidden;
}
/* Handoff -- real, explicit request, confirmed directly with Ali: "i
   want skeleton animation on product details loading page" -- same
   real shimmer keyframes catalog.html's own skeleton tiles already
   use (see rk-skel-shimmer), toggled on this real page via one class
   on the layout container (see product.html's own loadProduct),
   removed the moment the real product data actually arrives or
   genuinely fails to load -- never left showing indefinitely. */
.rk-productpage__layout.rk-pp-loading .rk-productpage__img {
  background: linear-gradient(90deg, var(--placeholder-bg) 25%, #ffffff 50%, var(--placeholder-bg) 75%);
  background-size: 200% 100%;
  animation: rk-skel-shimmer 1.4s ease-in-out infinite;
}
.rk-productpage__layout.rk-pp-loading #brand,
.rk-productpage__layout.rk-pp-loading #title,
.rk-productpage__layout.rk-pp-loading #price {
  display: inline-block;
  color: transparent;
  border-radius: 3px;
  background: linear-gradient(90deg, var(--placeholder-bg) 25%, #ffffff 50%, var(--placeholder-bg) 75%);
  background-size: 200% 100%;
  animation: rk-skel-shimmer 1.4s ease-in-out infinite;
}
.rk-productpage__layout.rk-pp-loading #brand { width: 30%; height: 12px; }
.rk-productpage__layout.rk-pp-loading #title { width: 60%; height: 22px; }
.rk-productpage__layout.rk-pp-loading #price { width: 20%; height: 18px; }
/* DISPATCH-18 §3b -- real crossfade layers (see product.html's
   renderGallery/showImage): each swap stacks a new layer on top and
   fades it in over the outgoing one, rather than a hard innerHTML
   swap. ~250ms sits inside the research doc's confirmed 200-300ms
   convention for PDP image-swap crossfades. */
.rk-productpage__imglayer {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  transition: opacity 250ms ease-in-out;
}
.rk-productpage__imglayer.is-visible { opacity: 1; }
/* Handoff: real bug found and fixed -- object-fit: cover was cropping
   every product photo to fill this box's fixed aspect-ratio, and on
   mobile that box was forced to a literal square (aspect-ratio: 1/1
   below), confirmed as exactly the "square view" Ali described.
   object-fit: contain shows the real, whole image at its own natural
   proportions -- letterboxed within the box rather than cropped --
   which is what "keep the same size of the images without
   auto-cropping" means in practice for a fixed-height gallery box
   (the crossfade layers are absolutely positioned and don't
   contribute to the parent's own height, so the box still needs some
   real height to size against -- see the box's own aspect-ratio
   comment above -- but nothing inside it gets cropped anymore). */
.rk-productpage__imglayer img { width: 100%; height: 100%; object-fit: contain; }
.rk-productpage__imglayer svg { width: 30%; height: 30%; stroke: var(--placeholder-stroke); }

.rk-productpage__thumbs {
  display: flex;
  gap: 8px;
  padding: 10px 16px;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}
.rk-productpage__thumb {
  width: 56px;
  height: 68px;
  flex-shrink: 0;
  background: var(--placeholder-bg);
  border: 1px solid var(--hairline);
  padding: 0;
  overflow: hidden;
  cursor: pointer;
}
.rk-productpage__thumb.is-active { border-color: var(--accent); border-width: 2px; }
.rk-productpage__thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* Handoff: real click-to-zoom lightbox for the main product image
   (see product.html's openLightbox). cursor: zoom-in on the trigger
   hints it's clickable before the person ever hovers the lightbox
   itself. */
.rk-productpage__img { cursor: zoom-in; }
.rk-lightbox {
  position: fixed;
  inset: 0;
  z-index: 200;
  background: rgba(20, 18, 16, 0.92);
  display: flex;
  align-items: center;
  justify-content: center;
}
.rk-lightbox[hidden] { display: none; }
.rk-lightbox__close {
  position: absolute;
  top: 18px;
  right: 20px;
  /* Handoff -- real, serious bug found and fixed, confirmed directly
     with Ali: "i cant press x to exit" -- and independently caught
     again in this fix's own real, live test: a real, deliberate
     pinch-zoom past this button's own real screen position made the
     stage below it genuinely intercept the tap meant for this real
     button instead. Root cause: with no real z-index of its own,
     this button's real stacking order fell back to DOM order --
     appearing BEFORE .rk-lightbox__stage in the real markup put it
     BEHIND that stage, not in front of it, the instant the real
     zoomed image grew large enough to actually overlap this real
     button's own corner. A real, explicit z-index above the stage's
     own now guarantees this button always receives a real tap,
     regardless of whatever real zoom level the image is at. */
  z-index: 1;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  border: none;
  background: rgba(255, 255, 255, 0.12);
  color: #fff;
  font-size: 26px;
  line-height: 1;
  cursor: pointer;
}
.rk-lightbox__close:hover { background: rgba(255, 255, 255, 0.22); }
.rk-lightbox__stage {
  width: min(92vw, 900px);
  height: min(86vh, 900px);
  overflow: hidden;
  cursor: zoom-in;
  position: relative;
}
/* Handoff: the actual hover-zoom -- transform-origin is set live in
   JS to track the cursor position (see the mousemove handler), and
   this scale is what makes that origin point visibly magnify.
   transform (not width/height) so this stays GPU-composited and
   doesn't re-trigger layout on every mouse-move tick. */
.rk-lightbox__img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  transform: scale(1);
  transition: transform 120ms ease-out;
  will-change: transform;
}
.rk-lightbox__stage.is-zoomed .rk-lightbox__img { transform: scale(2.2); transition: none; }
/* Handoff -- real, explicit request, confirmed directly with Ali: "i
   want the image pressed to be focused on with full width on screen
   and i can zoom in and out by 2 fingers" -- the real min(92vw,900px)
   stage above leaves a real visible margin on a real phone-width
   screen; full-bleed here instead, matching the real "full width"
   Ali asked for specifically on mobile (desktop keeps its own real,
   unchanged margin). touch-action: none hands pinch gestures on this
   real stage entirely to this file's own real JS handler below,
   rather than letting a real mobile browser's own native page-zoom
   fight it. */
@media (max-width: 720px) {
  .rk-lightbox__stage {
    width: 100vw;
    height: 100vh;
    touch-action: none;
  }
}

.rk-productpage__info { padding: 6px 16px 0; }
.rk-productpage__brand {
  font-size: 11px;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--ink-soft);
  margin: 0 0 4px;
}
.rk-productpage__title { font-size: 19px; font-weight: 500; margin: 0 0 4px; color: var(--ink); }
/* DISPATCH-18 §3b -- real, live sibling count ("5 colors available"),
   empty/collapsed via :empty when there's nothing to count (see
   product.html's renderVariantCounter). */
.rk-productpage__variantcount { font-size: 12px; color: var(--ink-soft); margin: 0 0 8px; }
.rk-productpage__variantcount:empty { display: none; }

/* Real: product name/price text renders in the brand accent color
   (reference §1 -- accent used on "product name/price text"). */
/* Handoff -- real, explicit request, confirmed directly with Ali:
   same real "price" token the catalog tile's own price uses (see
   .rk-tile__price) -- the same real concept shown on two pages, no
   longer --accent (shared with every link/highlight site-wide). */
.rk-productpage__price { font-size: 18px; font-weight: 600; color: var(--price); margin: 0 0 10px; }
.rk-productpage__price s { color: var(--ink-mute); font-weight: 400; margin-right: 8px; }

/* Real "In Stock" badge -- accent-colored, per reference §1. */
.rk-stockbadge {
  display: inline-block;
  font-size: 11px;
  letter-spacing: 0.5px;
  text-transform: uppercase;
  color: var(--accent);
  border: 1px solid var(--accent);
  padding: 3px 10px;
  border-radius: 2px;
  margin-bottom: 16px;
}
.rk-stockbadge--out { color: var(--ink-mute); border-color: var(--hairline); }

.rk-productpage__section-label {
  font-size: 12px;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--ink-soft);
  margin: 0 0 10px;
}

/* DISPATCH-20 §3b -- real, final, confirmed spec from Ali: replaces
   the old color_hex-filled-circle / color_name-chip rendering
   entirely (a "variant" here is often a richly patterned,
   multi-colored version of the same design, genuinely impossible to
   represent with a single color swatch -- Ali explicitly rejected
   both a swatch and a photo-thumbnail-as-selector approach). Plain
   text-label buttons, one per sibling, showing that sibling's real
   variant_code (e.g. "23-7"). Selected state is a border/text-color
   highlight, NOT a filled background -- confirmed in real browser
   testing against a seeded 6-variant patterned product. The old
   .rk-swatch* rules (circular color fill, fallback chip) are removed
   outright, not left parked -- unlike §3a's craft block, there's no
   "will come back later" scenario here; this replaces a wrong pattern
   with the confirmed-correct one. */
.rk-variantcodes { display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 22px; }
/* Handoff -- real bug fix / direct reversal, confirmed directly with
   Ali: mini real photos instead of plain variant_code number/text
   buttons -- see renderSwatches's own docstring (product.html) for
   the full history. Sized like a real small product thumbnail (not a
   tiny color dot), since the photo itself IS the label now, not a
   decoration next to one. */
.rk-variantcode {
  width: 52px;
  height: 66px;
  padding: 0;
  box-sizing: border-box;
  background: none;
  border: 1px solid var(--hairline);
  border-radius: 2px;
  cursor: pointer;
  overflow: hidden;
  flex-shrink: 0;
}
.rk-variantcode img, .rk-variantcode svg { width: 100%; height: 100%; object-fit: cover; display: block; }
.rk-variantcode.is-active { border-color: var(--accent); border-width: 2px; }
/* Handoff -- real, explicit request, confirmed directly with Ali:
   the selected variant's own real code/name as real text under the
   swatches, updating live on selection. */
.rk-productpage__selectedvariant { margin-top: 8px; font-size: 12px; letter-spacing: 0.5px; color: var(--ink-soft); text-transform: uppercase; }

.rk-productpage__qtyrow { display: flex; align-items: center; gap: 12px; margin-bottom: 14px; }
.rk-productpage__qtyrow input {
  width: 64px;
  /* Handoff -- real, explicit request, confirmed directly with Ali:
     "qty box" needed its own real, separate color -- was --hairline,
     shared with every thin divider line site-wide. */
  border: 1px solid var(--qty-box-border);
  padding: 9px;
  font-size: 14px;
  text-align: center;
}

.rk-productpage__actions { display: flex; gap: 10px; margin-bottom: 26px; }
.rk-btn-addcart {
  flex: 1;
  border: none;
  background: var(--btn-dark);
  color: #fff;
  padding: 14px;
  font-size: 13px;
  letter-spacing: 1px;
  text-transform: uppercase;
}
.rk-btn-addcart:hover { background: var(--btn-dark-hover); }
.rk-btn-addcart:disabled { background: var(--hairline); color: var(--ink-mute); cursor: not-allowed; }
.rk-btn-share {
  width: 48px;
  border: 1px solid var(--hairline);
  background: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
}
.rk-btn-share svg { width: 18px; height: 18px; stroke: var(--ink); }

/* Real product descriptions (reference §9 item 5 -- was confirmed
   empty on every product on the old site; this is the fix, not a
   fallback for existing gaps). Omitted entirely when a product
   genuinely has none, rather than showing an empty labeled section. */
.rk-productpage__description {
  font-size: 14px;
  line-height: 1.6;
  color: var(--ink-soft);
  padding-top: 18px;
  border-top: 1px solid var(--hairline);
  white-space: pre-line;
}

@media (min-width: 700px) {
  .rk-productpage { max-width: 1040px; padding: 32px 24px 80px; }
  .rk-productpage__layout { display: flex; gap: 40px; align-items: flex-start; }
  .rk-productpage__gallery { flex: 1 1 55%; margin-bottom: 0; }
  .rk-productpage__info { flex: 1 1 45%; padding: 0; }
  /* Handoff: real bug found and fixed -- this forced the gallery box
     into a literal square at desktop width, on top of the base
     aspect-ratio: 4/5 above, confirmed as exactly the "square view"
     Ali described (he views/tests this on desktop). Removed rather
     than widened, so desktop now matches the same 4/5 shape as
     mobile -- one real, consistent aspect-ratio everywhere, not two
     different shapes at two breakpoints. */
}

/* Sticky add-to-bag bar (Part B convention) -- slides up from the
   bottom once the real button scrolls out of view (app.js's
   initStickyAddBar). Translate + opacity, not display:none/block, so
   it can actually transition rather than snapping in. */
.rk-stickyadd {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  z-index: 39; /* under the nav/cart panels (46) and header (40) */
  display: flex;
  align-items: center;
  gap: 14px;
  background: #fff;
  border-top: 1px solid var(--hairline);
  padding: 12px 16px;
  transform: translateY(100%);
  opacity: 0;
  transition: transform 260ms ease-out, opacity 260ms ease-out;
  pointer-events: none;
}
.rk-stickyadd.is-visible { transform: translateY(0); opacity: 1; pointer-events: auto; }
.rk-stickyadd__info { flex: 1; min-width: 0; }
.rk-stickyadd__name {
  margin: 0;
  font-size: 13px;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.rk-stickyadd__price { margin: 2px 0 0; font-size: 13px; font-weight: 600; color: var(--accent); }
.rk-stickyadd__price s { color: var(--ink-mute); font-weight: 400; margin-right: 6px; }
.rk-stickyadd .rk-btn-addcart { flex: 0 0 auto; width: auto; padding: 12px 22px; }

/* ---------------------------------------------------------------- */
/* Filter form (kept functional, styled as a slide-down disclosure) */
/* ---------------------------------------------------------------- */

/* ---------------------------------------------------------------- */
/* Generic form pages (login / register / account / checkout)        */
/* ---------------------------------------------------------------- */

.rk-formpage { max-width: 420px; margin: 0 auto; padding: 40px 24px 60px; }
.rk-formpage__title {
  font-size: 15px;
  letter-spacing: 3px;
  text-transform: uppercase;
  font-weight: 700;
  margin: 0 0 30px;
}
.rk-field { margin-bottom: 26px; }
.rk-field label {
  display: block;
  font-size: 12px;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--ink-soft);
  margin-bottom: 8px;
}
.rk-field input, .rk-field select {
  width: 100%;
  border: none;
  border-bottom: 1px solid var(--ink);
  padding: 6px 0;
  font-size: 16px;
  font-family: inherit;
  color: var(--ink);
  outline: none;
  background: transparent;
}
.rk-field--boxed input, .rk-field--boxed select {
  border: 1px solid var(--hairline);
  padding: 10px 12px;
}
.rk-forgot { display: block; margin: -10px 0 22px; font-size: 14px; }
.rk-formpage .rk-btn { margin-top: 4px; }
.rk-formpage__divider {
  text-align: center;
  color: var(--ink-soft);
  font-size: 12px;
  letter-spacing: 2px;
  margin: 26px 0;
}
.rk-formpage__section-title {
  font-size: 13px;
  letter-spacing: 2px;
  text-transform: uppercase;
  font-weight: 700;
  margin: 0 0 12px;
}
.rk-formpage__note { font-size: 13px; color: var(--ink-soft); line-height: 1.6; }

.rk-error {
  background: #fbeceb;
  color: var(--danger);
  border: 1px solid #f1cfcb;
  padding: 10px 14px;
  font-size: 14px;
  margin-bottom: 18px;
  border-radius: 2px;
}
/* Real, reusable "this specific field has a problem" marker -- same
   real pattern/class name as admin_frontend and pos_backend, applied
   via the same markFieldError/clearFieldErrors helpers in app.js. */
.field-error {
  border-color: var(--danger) !important;
  box-shadow: 0 0 0 1px var(--danger);
}

/* ---------------------------------------------------------------- */
/* Simple list pages (cart full page / orders) reuse table look but  */
/* restyled to match the rest of the site                            */
/* ---------------------------------------------------------------- */

.rk-simple-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 14px;
  margin: 20px 0;
}
.rk-simple-table th {
  text-align: left;
  font-size: 11px;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--ink-soft);
  border-bottom: 1px solid var(--ink);
  padding: 10px 8px;
}
.rk-simple-table td {
  padding: 12px 8px;
  border-bottom: 1px solid var(--hairline);
  vertical-align: middle;
}
.rk-simple-table input[type="number"] {
  width: 3.5em;
  padding: 4px;
  border: 1px solid var(--hairline);
}
.rk-simple-table button {
  border: 1px solid var(--btn-dark);
  background: #fff;
  color: var(--btn-dark);
  padding: 6px 10px;
  font-size: 12px;
}

.rk-page { max-width: 640px; margin: 0 auto; padding: 30px 20px 60px; }
.rk-page__title {
  font-size: 15px;
  letter-spacing: 3px;
  text-transform: uppercase;
  font-weight: 700;
  margin: 0 0 6px;
}
.rk-subtotal-row {
  display: flex;
  justify-content: space-between;
  font-weight: 700;
  padding: 14px 8px;
  border-top: 2px solid var(--ink);
}
.rk-empty-note { color: var(--ink-soft); padding: 30px 0; text-align: center; }

/* ---------------------------------------------------------------- */
/* Checkout (DISPATCH-11 §3c) -- tabbed Details/Shipping/Payment/    */
/* Review flow, matching Ali's own reference screenshots of the real */
/* old site's flow precisely (mint = active step, peach = completed  */
/* step, matching those screenshots' own two-tone tab progression).  */
/* ---------------------------------------------------------------- */

:root {
  --checkout-mint: #d8f0e8;
  --checkout-mint-tab: #cdeee0;
  --checkout-peach: #e8cdb0;
}

.rk-checkout {
  display: flex;
  gap: 28px;
  max-width: 1000px;
  margin: 0 auto;
  padding: 30px 20px 60px;
  align-items: flex-start;
}
.rk-checkout__main { flex: 1 1 auto; min-width: 0; }

.rk-checkout__tabs {
  display: flex;
  border-bottom: 1px solid var(--hairline);
  margin-bottom: 0;
}
.rk-checkout__tab {
  flex: 1;
  text-align: center;
  padding: 14px 8px;
  font-size: 13px;
  letter-spacing: 1px;
  font-weight: 700;
  color: var(--ink-soft);
  background: #f4f2f0;
  cursor: pointer;
}
.rk-checkout__tab.is-complete { background: var(--checkout-peach); color: var(--ink); cursor: pointer; }
.rk-checkout__tab.is-active { background: var(--checkout-mint-tab); color: var(--ink); }

.rk-checkout__panel {
  background: var(--checkout-mint);
  padding: 24px;
  margin-top: 0;
}
.rk-checkout__panel h2 {
  font-size: 15px;
  letter-spacing: 1px;
  text-transform: uppercase;
  font-weight: 700;
  margin: 0 0 18px;
}
.rk-checkout__panel-head {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  margin-bottom: 18px;
}
.rk-checkout__panel-head h2 { margin: 0; }

.rk-checkout__radio-card {
  display: flex;
  align-items: center;
  gap: 12px;
  background: #fff;
  border: 1px solid var(--hairline);
  padding: 14px 16px;
  margin-bottom: 10px;
  cursor: pointer;
}
.rk-checkout__radio-card input { flex-shrink: 0; }
.rk-checkout__radio-title { display: block; font-weight: 600; }
.rk-checkout__radio-sub { display: block; color: var(--ink-soft); font-size: 13px; }

.rk-checkout__inline-form {
  background: #fff;
  border: 1px solid var(--hairline);
  padding: 16px;
  margin-bottom: 16px;
}
.rk-checkout__inline-form .rk-field { margin-bottom: 14px; }
.rk-checkout__inline-form-actions { display: flex; gap: 10px; }
/* Real, explicit request, confirmed directly with Ali: "the first
   address pressed should be auto selected in a box... in the third
   checkout i can see 2 boxes for the recent addresses." */
.rk-checkout__saved-addresses-label { font-weight: 600; margin-bottom: 10px; }
.rk-checkout__saved-addresses-list {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-bottom: 14px;
}
.rk-checkout__saved-address-box {
  display: flex;
  flex-direction: column;
  gap: 2px;
  text-align: left;
  background: #fff;
  border: 1px solid var(--hairline);
  padding: 12px 14px;
  cursor: pointer;
  font: inherit;
  color: inherit;
  border-radius: 4px;
}
.rk-checkout__saved-address-box:hover { border-color: var(--ink-soft); }
.rk-checkout__saved-address-box--selected {
  border-color: var(--ink);
  border-width: 2px;
  background: var(--bg-soft);
}
.rk-checkout__saved-address-location { color: var(--ink-soft); font-size: 13px; }
.rk-checkout__guest-address-confirm {
  background: #fff;
  border: 1px solid var(--hairline);
  padding: 14px 16px;
  margin-bottom: 16px;
}
.rk-checkout__set-password {
  background: var(--bg-soft);
  border: 1px solid var(--hairline);
  padding: 16px;
  margin-top: 16px;
}
.rk-checkout__set-password h3 { margin-top: 0; }

.rk-checkout__continue { width: 220px; }

.rk-checkout__error {
  background: #fbeceb;
  color: var(--danger);
  border: 1px solid #f1cfcb;
  padding: 10px 14px;
  font-size: 14px;
  margin-bottom: 14px;
  border-radius: 2px;
}

.rk-checkout__review-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 20px;
  margin-bottom: 24px;
}
.rk-checkout__review-grid strong {
  display: block;
  font-size: 12px;
  letter-spacing: 1px;
  text-transform: uppercase;
  margin-bottom: 8px;
}
.rk-checkout__review-grid p { margin: 0; line-height: 1.5; color: var(--ink); }

.rk-checkout__summary {
  flex: 0 0 300px;
  border: 1px solid var(--hairline);
  padding: 20px;
}
.rk-checkout__summary h2 {
  font-size: 15px;
  letter-spacing: 1px;
  text-transform: uppercase;
  font-weight: 700;
  margin: 0 0 4px;
  padding-bottom: 12px;
  border-bottom: 2px solid var(--accent);
}
.rk-checkout__summary-table { width: 100%; border-collapse: collapse; margin: 14px 0; font-size: 14px; }
.rk-checkout__summary-table th {
  text-align: left;
  font-size: 11px;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--ink-soft);
  padding-bottom: 8px;
  border-bottom: 1px solid var(--hairline);
}
.rk-checkout__summary-table th:last-child, .rk-checkout__summary-table td:last-child { text-align: right; }
.rk-checkout__summary-table td { padding: 6px 0; }

.rk-checkout__summary-row {
  display: flex;
  justify-content: space-between;
  padding: 6px 0;
  font-size: 14px;
  color: var(--ink-soft);
}
/* Handoff -- real bug fix, found live while testing the new Whish fee
   row: display:flex above overrides the browser's own default
   [hidden] { display: none } rule (same class of bug as .nav-badge's
   own earlier fix elsewhere in this file) -- every other summary row
   (shipping/coupon/gift card) had this same latent bug, just masked
   because they're empty text when hidden, which looks the same as
   genuinely not being there. The new Whish fee row's own static label
   text made it actually visible. */
.rk-checkout__summary-row[hidden] { display: none; }
.rk-checkout__summary-row--total {
  font-weight: 700;
  font-size: 16px;
  color: var(--ink);
  border-top: 1px solid var(--hairline);
  margin-top: 6px;
  padding-top: 12px;
}

.rk-checkout__code-box {
  border: 1px solid var(--hairline);
  padding: 14px;
  margin-top: 16px;
}
.rk-checkout__code-box label {
  display: block;
  font-size: 12px;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--ink-soft);
  margin-bottom: 8px;
}
.rk-checkout__code-row {
  display: flex;
  gap: 8px;
  /* Real bug found and fixed, confirmed directly with Ali from a real
     screenshot: on a narrow/mobile screen, the "Apply" button was
     pushed off the right edge of the screen, cut off and unreachable.
     Root cause: a flex item's default min-width is "auto" (never
     "0"), meaning the input below (flex: 1) refused to shrink past its
     own natural content width no matter how narrow the real screen
     got, forcing this whole row -- input plus button -- wider than the
     viewport. flex-wrap: wrap is the real, correct fix here (rather
     than min-width: 0 alone): letting the button drop to its own line
     below the input on a genuinely narrow screen keeps both fully
     visible and tappable, instead of squeezing the input down to an
     uncomfortably tiny width just to keep both on one line. */
  flex-wrap: wrap;
}
.rk-checkout__code-row input {
  flex: 1;
  /* Real, related fix: without this, "flex: 1" alone doesn't stop the
     input from claiming its full natural content width first (see
     .rk-checkout__code-row's own comment on this exact default) --
     min-width: 0 lets it genuinely shrink to fit real, narrow screens
     alongside flex-wrap above. */
  min-width: 140px;
  border: 1px solid var(--hairline);
  padding: 8px 10px;
  font-size: 14px;
  font-family: inherit;
}
.rk-checkout__code-note { font-size: 12px; color: var(--ink-soft); margin: 8px 0 0; }

.rk-btn--sm { display: inline-block; width: auto; padding: 8px 16px; font-size: 13px; margin-bottom: 0; }

@media (max-width: 720px) {
  .rk-checkout { flex-direction: column; }
  .rk-checkout__summary { flex: 1 1 auto; width: 100%; }
  .rk-checkout__review-grid { grid-template-columns: 1fr; }
  /* Handoff -- real, explicit request, confirmed directly with Ali:
     "on mobile view, i dont want the variants cards in parent
     product view to take more than 50% of the area under the main
     image" -- the fan's own fixed 153px width (sized for a real
     desktop-width tile) could exceed half of a real, narrow mobile
     tile's own inforow, crowding out the name/price text next to it.
     max-width caps it at exactly half of whatever the real inforow's
     own width actually is on a given phone, leaving the other half
     for name/price regardless of screen size -- overriding the fixed
     153px, not replacing it (desktop keeps its own real fixed size,
     min-width:960px's own rule is untouched).

     Handoff -- real, explicit follow-up request, confirmed directly
     with Ali: "decrease the size of variant stack under a parent
     product on mobile view by 30%" -- real width/height set here,
     each exactly 70% of the base 153px/102px desktop size (107px/
     71px), so the whole stack shrinks proportionally rather than
     just getting narrower while staying just as tall (which would
     have distorted -- not shrunk -- each card's own real aspect
     ratio). The 50% max-width cap above stays on top of this as a
     real safety net for a genuinely narrow phone, not replaced by
     it -- on the narrowest screens the smaller of the two still
     wins. */
  .rk-varthumbs { max-width: 50%; width: 107px; height: 71px; }

  /* Handoff -- real bug fix, confirmed directly with Ali against his
     own real phone screenshot ("Checkout fields design is not mobile
     friendly and some text box's edge is not shown on the right
     edge"): the real root cause, confirmed live (measured the actual
     page 501px wide inside a real 375px viewport) -- .rk-checkout__
     tab's own flex:1 should have let 5 tabs share the row and shrink
     to fit, but a flex item's own real, implicit default (min-width:
     auto, not 0) stopped each one shrinking past its own longest
     unbroken word ("VERIFICATION"/"CONFIRMATION"), forcing the whole
     row -- and everything under it, since .rk-checkout__main's own
     width is driven by its own widest child -- wider than the real
     phone screen. That's the ONE real fix needed here: once the tabs
     row can genuinely shrink to fit, the input fields (already a
     real width: 100% of their own real parent) fit correctly too,
     with no accordion/redesign of the fields themselves required.
     min-width: 0 lets each tab actually shrink; smaller font + tighter
     padding + a real 2-line wrap (more readable than truncating
     "CONFIRMATION" to "CONF…") make 5 tabs legible at once in that
     narrower real space. */
  .rk-checkout__tab {
    min-width: 0;
    font-size: 9px;
    letter-spacing: 0;
    padding: 10px 3px;
    white-space: normal;
    line-height: 1.3;
    word-break: break-word;
  }
}

/* 2026-10-25: Sign in with Google button (login / register). */
.rk-google-box .rk-google-btn { display: flex; justify-content: center; margin: 4px 0 8px; min-height: 44px; }

/* "New" badge (2026-10-27): shown on tiles/products flagged is_new in Admin > Products. */
.rk-newbadge { position:absolute; top:8px; left:8px; z-index:2; background:#111; color:#fff; font-size:11px; font-weight:700; letter-spacing:.06em; text-transform:uppercase; padding:3px 8px; border-radius:3px; pointer-events:none; }
.rk-newbadge--inline { position:static; display:inline-block; margin-bottom:8px; }

/* ---------- "Get the app" prompts (2026-10-27) ---------- */
.rk-promo-icon { flex:0 0 auto; width:34px; height:34px; border-radius:9px; background:var(--accent); color:#fff; display:inline-flex; align-items:center; justify-content:center; font-weight:700; font-size:18px; }
.rk-promo-btn { display:inline-block; padding:10px 16px; border-radius:8px; background:var(--ink); color:#fff !important; text-decoration:none; font-size:14px; font-weight:600; text-align:center; }
.rk-promo-btn--sm { padding:6px 12px; font-size:13px; }
.rk-promo-btn--block { display:block; margin-top:8px; }
.rk-promo-x { background:none; border:0; font-size:22px; line-height:1; color:inherit; opacity:.6; cursor:pointer; padding:2px 6px; }
/* A: top banner (sits under the announcement bar) */
.rk-promo-banner { display:flex; align-items:center; gap:10px; padding:8px 12px; background:var(--bg-soft); border-bottom:1px solid var(--hairline); font-size:13px; color:var(--ink); }
.rk-promo-banner__txt { flex:1; min-width:0; line-height:1.3; }
.rk-promo-banner__txt span { color:var(--ink-soft); }
@media (max-width:600px) { .rk-promo-banner__txt span { display:none; } }
/* B: bottom sheet */
.rk-promo-sheet { position:fixed; left:0; right:0; bottom:0; z-index:60; background:#fff; padding:18px 16px calc(18px + env(safe-area-inset-bottom)); border-radius:16px 16px 0 0; box-shadow:0 -6px 24px rgba(0,0,0,.18); transform:translateY(110%); transition:transform .3s ease; }
.rk-promo-sheet.is-open { transform:none; }
.rk-promo-sheet .rk-promo-x { position:absolute; top:6px; right:8px; }
.rk-promo-sheet__row { display:flex; gap:12px; align-items:center; padding-right:24px; }
.rk-promo-sheet__row p { margin:2px 0 0; font-size:13px; color:var(--ink-soft); }
/* C: popup */
.rk-promo-popup { position:fixed; inset:0; z-index:70; background:rgba(0,0,0,.5); display:flex; align-items:center; justify-content:center; padding:20px; }
.rk-promo-popup__card { position:relative; background:#fff; border-radius:14px; padding:28px 22px 18px; max-width:340px; width:100%; text-align:center; }
.rk-promo-popup__card .rk-promo-x { position:absolute; top:6px; right:8px; }
.rk-promo-popup__card .rk-promo-icon { width:52px; height:52px; font-size:26px; border-radius:13px; margin-bottom:10px; }
.rk-promo-popup__card h3 { margin:0 0 6px; } .rk-promo-popup__card p { margin:0 0 8px; color:var(--ink-soft); font-size:14px; }
.rk-promo-later { background:none; border:0; margin-top:10px; color:var(--ink-mute); font-size:13px; cursor:pointer; }
/* D: floating button */
.rk-promo-fab { position:fixed; right:14px; bottom:calc(14px + env(safe-area-inset-bottom)); z-index:35; display:flex; align-items:center; gap:2px; background:var(--ink); color:#fff; border-radius:28px; padding:6px 6px 6px 8px; box-shadow:0 4px 14px rgba(0,0,0,.25); }
.rk-promo-fab__btn { display:flex; align-items:center; gap:8px; color:#fff !important; text-decoration:none; font-size:13px; font-weight:600; }
.rk-promo-fab .rk-promo-icon { width:26px; height:26px; font-size:14px; border-radius:50%; }
.rk-promo-fab .rk-promo-x { color:#fff; font-size:18px; }
/* E: inline card */
.rk-promo-inline { display:flex; align-items:center; gap:14px; flex-wrap:wrap; max-width:900px; margin:28px auto; padding:16px 18px; background:var(--bg-soft); border:1px solid var(--hairline); border-radius:12px; }
.rk-promo-inline__txt { flex:1 1 220px; } .rk-promo-inline__txt p { margin:2px 0 0; font-size:13px; color:var(--ink-soft); }
.rk-promo-inline__btns { display:flex; gap:8px; flex-wrap:wrap; }
/* F: footer badges */
.rk-promo-footer { text-align:center; padding:14px 0 6px; } .rk-promo-footer p { margin:0 0 8px; font-size:13px; }
.rk-promo-footer div { display:flex; gap:8px; justify-content:center; flex-wrap:wrap; }
.rk-promo-footer .rk-promo-btn { background:#fff; color:var(--ink) !important; }
.rk-promo-inline { width:calc(100% - 28px); box-sizing:border-box; }
