/*!
 * site-chrome.css - the site header, footer, drawer and loading skeletons.
 *
 * Loaded LAST, after design-system.css, so it is the final word on the shell.
 *
 * WHY ITS OWN SHEET
 * This project has no Tailwind pipeline: tailwindcss.css is a static v3 dump and
 * design-system.css back-fills the utilities the dump is missing. Rebuilding the
 * header and footer out of utilities meant adding another ~80 single-purpose
 * classes, each of which silently does nothing if it is forgotten. The shell is
 * three components, so it is spelled out as real CSS here instead, with the
 * measurements taken from the source project's own layout (its inline <style>
 * block is already copied verbatim into layout.css - .nav-container, .nav-link,
 * .profile-btn, .wallet-btn, .btn-auth, .footer-custom, .contact-card,
 * .sticky-footer-* and the chat widget all come from there and are NOT repeated).
 *
 * Everything colour-related resolves through the custom properties
 * layouts/default.vue writes from the admin's settings, so changing the theme in
 * the admin restyles the whole shell.
 */

:root {
  /* Measured, not guessed: the bar is its vertical padding plus the tallest
     control in the row, which is the 2.5rem avatar (.profile-btn, layout.css).
     Anything that has to clear the header (scroll anchors, the drawer's top
     offset, the dropdown's max-height) reads these.

     The source's own padding was `py-3 md:py-5`, which put the desktop bar at
     88px - a fifth of a 450px-tall laptop viewport spent on a logo and an
     avatar, and it pushed the first thing on every page below the fold. Tighter
     now, from the padding and the logo's ceiling only: no control loses size, so
     the avatar and the wallet pill are still 40px of touch target. */
  --header-h: 56px;
  /* The bar is 65px (layout.css) plus whatever the device reserves for its own
     home indicator, so the last menu label is not underneath it on an iPhone.
     `env()` is 0px on everything that has no inset. */
  --sticky-footer-h: calc(65px + env(safe-area-inset-bottom, 0px));
  /* The one measure the header row, every page's `.container` and the footer
     panel all share. */
  --shell-max: 1180px;
}

@media (min-width: 768px) {
  :root {
    --header-h: 68px;
  }
}

/* ---------------------------------------------------------------------------
   1. OVERLAP GUARDS

   The header is `position: sticky`, which means it stays in normal flow and
   reserves its own height - it is structurally incapable of covering page
   content. These rules remove the three things that used to break that.
   --------------------------------------------------------------------------- */

/*
 * styles.css (copied from the source) carries
 * `@media (max-width:767px){ body { padding-top: 76px } }` to clear its old
 * `.navbar`, which was `position: fixed`. That navbar is not part of this port -
 * no component renders it - so on a phone the rule was pure dead space above the
 * header.
 */
body {
  padding-top: 0 !important;
}

/*
 * The same sheet ends with `@media (max-width:767px){ body { margin-bottom: 70px } }`,
 * its own clearance for the fixed mobile menu. The menu already reserves that
 * space with `.sticky-footer-spacer` (section 3b), which is exactly the bar's
 * height, so the 70px was a second allowance stacked on the first: on a phone the
 * copyright line ended, then ~135px of empty page ran down to the menu. One
 * allowance, next to the bar, where it can be kept in step with it.
 */
body {
  margin-bottom: 0 !important;
}

/*
 * THE LAYOUT WIDTH
 *
 * `.container` is the one shell class the header row, every page and the footer
 * share, and it is NOT set here. There was a `.container { width: 100%;
 * max-width: var(--shell-max) }` rule at this spot and it never did anything:
 * assets/css/global.scss declares the same properties with `!important` and is
 * injected after every <link>, so it wins outright. Two rules claiming to be
 * "the final word on the shell" is how the shop listing ended up 40px wider
 * than the footer panel while both looked correct in their own file.
 *
 * The measure itself is --shell-max above, and assets/css/global.scss is the one
 * place that applies it to `.container`. Change the variable to move the header,
 * every page and the footer together.
 */

/*
 * Nothing may make the page wider than the device. A single overflowing element
 * - a long unbroken order id, a wide table, an image without a max-width - gives
 * the whole document a horizontal scrollbar and shifts the sticky header and the
 * fixed mobile bar with it, which is what "the layout is broken on my phone"
 * usually turns out to be.
 *
 * On <html> only, never on <body>: giving <body> a non-visible overflow makes it
 * a scroll container, and the sticky header would then be sticky inside an
 * element that does not scroll - i.e. it would stop sticking.
 */
html {
  max-width: 100%;
  overflow-x: hidden;
}

img,
video,
canvas,
iframe {
  max-width: 100%;
}

/*
 * In-page anchors. The home page's "All Products" link is `/#topup`, and a
 * sticky header covers the top of whatever a hash lands on unless the scroll
 * port is told to stop short of it.
 */
html {
  scroll-padding-top: calc(var(--header-h) + 12px);
  scroll-behavior: smooth;
}

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }
}

/*
 * `position: sticky` is cancelled by any ancestor with a clipping overflow, and
 * Vuetify's wrap is the one ancestor between <body> and the header. Without
 * this the bar scrolls away on some routes and behaves inconsistently on
 * others, which is exactly the kind of "sometimes it works" report that gets
 * blamed on the header itself.
 */
.v-application--wrap {
  overflow: visible !important;
}

/* Short pages must not leave the footer floating in the middle of the viewport.
   The source solves this on `.body-bg`; here the flex column has to be declared
   on Vuetify's wrap as well, because that is the element that actually holds the
   header / <v-main> / footer sequence. */
.body-bg,
.body-bg > .v-application--wrap {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}

.body-bg > .v-application--wrap > .v-main {
  flex: 1 0 auto;
}

.body-bg footer.footer-custom {
  margin-top: auto;
}

/*
 * VUETIFY'S DETACHED OVERLAYS
 *
 * A sticky bar cannot cover page content, but it can cover anything painted in
 * its own layer - and Vuetify does not read this scale. Every overlay it mounts
 * (dialog, select menu, tooltip) takes its z-index from JS at open time: it
 * walks up from the component's own element until it finds a computed z-index,
 * then paints two above that. Nothing on the way up has one, so a dialog lands
 * on 202 and a menu on 8, i.e. under the header (1200) and under the mobile
 * sticky footer bar (9999).
 *
 * `.v-dialog__container` is that hook. It is `display: none` (Vuetify's own
 * rule), so a z-index on it is invisible - it changes no layout and creates no
 * stacking context - but it is the element the walk stops at, so the whole
 * dialog stack shifts up together and keeps its internal order: scrim 10001,
 * dialog 10002, and a menu opened inside the dialog 10004. Above the bar, still
 * far below the account drawer (100000/100001).
 *
 * Deliberately not `.v-overlay`: product/ShopGrid.vue dims sold-out cards with
 * `<v-overlay absolute>`, which is a panel inside a card, not a modal. Lifting
 * those would float a grey rectangle over the whole page. The two menus/tooltips
 * that are not inside a dialog carry `z-index="10000"` at their call site
 * instead, one number below the scrim, so an open dialog still wins.
 */
.v-dialog__container {
  z-index: 10000;
}

/* ---------------------------------------------------------------------------
   2. HEADER

   The source's header is a plain block in flow. Making it sticky is the only
   deliberate deviation from the design - the bar looks identical, it just stays
   put - so everything here is positioning and the state it needs while stuck.

   `.header`'s colours come from custom-styles.css
   (`background-color: var(--navigation-background-color) !important`), which is
   the sheet the source project loads too, so the bar is the admin's colour on
   both sites. Nothing here touches it.
   --------------------------------------------------------------------------- */
.site-header {
  position: -webkit-sticky;
  position: sticky;
  top: 0;
  /* Above page content and the chat widget (1000), below the sticky footer bar
     (9999) and far below the account drawer (100000/100001). The old value was
     99999, which is what let the bar paint over the drawer. */
  z-index: 1200;
  width: 100%;
  /* No backdrop-filter here. On a sticky ancestor it creates a containing block
     for descendants, which breaks the dropdown's `position: absolute`, and it
     forces a repaint of the whole bar on every scroll frame. The glass effect
     the design shows belongs to .nav-container (layout.css), which is a static
     child - it works there and costs nothing. */
  transition: box-shadow 0.28s ease;
}

/* Flat while the page is at the top, exactly as the source renders it; a hairline
   and a soft lift appear only once content has scrolled under the bar, so the
   header reads as a layer instead of a seam. */
.site-header::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 1px;
  background: rgba(15, 23, 42, 0.08);
  opacity: 0;
  transition: opacity 0.28s ease;
  pointer-events: none;
}

.site-header.is-stuck {
  box-shadow: 0 10px 30px -22px rgba(15, 23, 42, 0.55);
}

.site-header.is-stuck::after {
  opacity: 1;
}

@media (prefers-reduced-motion: reduce) {
  .site-header,
  .site-header::after {
    transition: none;
  }
}

/* Header row. The source's markup is `.container m-auto p-1 py-3 md:py-5 md:px-0`
   around `nav.nav-container`; the padding is restated here because `.container`
   is redefined three times across the copied sheets and the utility that used to
   supply `py-5` is not in the dump for every breakpoint.

   The vertical figures are deliberately below the source's `py-3 md:py-5` - see
   --header-h above for why. Horizontal padding is unchanged. */
.site-header .header-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  padding: 0.5rem 0.75rem;
}

@media (min-width: 768px) {
  .site-header .header-row {
    padding: 0.875rem 0.5rem;
  }
}

@media (min-width: 1024px) {
  .site-header .header-row {
    padding: 0.875rem 1rem;
  }
}

.site-header .logo-link {
  display: inline-flex;
  align-items: center;
  flex-shrink: 0;
  min-width: 0;
  text-decoration: none;
}

/* The source's logo is `w-28 md:w-48`, height auto, so a wordmark of any aspect
   ratio keeps its shape. `max-height` is the one addition: a near-square logo at
   12rem wide would otherwise make the bar 200px tall. */
.site-header .logo {
  display: block;
  width: 7rem;
  height: auto;
  max-height: 2.25rem;
  object-fit: contain;
  object-position: left center;
}

@media (min-width: 768px) {
  .site-header .logo {
    width: 12rem;
    max-height: 2.5rem;
  }
}

/* The right-hand control cluster: nav links, cart, wallet, avatar / auth buttons.
   `position: relative` is the anchor the dropdown is positioned against. */
.site-header .header-actions {
  position: relative;
  display: flex;
  align-items: center;
  flex-shrink: 0;
}

.site-header .header-cluster {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

/* The desktop link row. `.nav-link` itself (colour, weight, size, padding, hover)
   comes from layout.css; this is only the row that holds it, hidden below md
   exactly as the source hides it. */
.site-header .header-nav {
  display: none;
}

@media (min-width: 768px) {
  .site-header .header-nav {
    display: flex;
    align-items: center;
    gap: 0.125rem;
    margin-right: 0.25rem;
  }
}

/* THE COLOUR HAS TO BE RESTATED HERE
   layout.css already says `.nav-link { color: var(--navigation-font-color) }`,
   and it was losing: Vuetify's own sheet is injected after every <link> in this
   document and carries `.v-application a { color: var(--v-anchor-base) }`, which
   is (0,1,1) against layout.css's (0,1,0) and therefore wins on specificity no
   matter what order the sheets load in. `--v-anchor-base` is the Vuetify light
   theme's `primary`, which nuxt.config.js sets to #de2d59 - so every header link
   that was not the active one rendered crimson while the hamburger beside them,
   being a <button> and not an <a>, stayed the settings colour. Two classes here
   (0,2,0) clear that anchor rule, and the value stays the admin's setting rather
   than a literal, so the navigation colour in the panel still drives the bar. */
.site-header .nav-link {
  display: inline-block;
  white-space: nowrap;
  color: var(--navigation-font-color, #1f2937);
  text-decoration: none;
}

/* The current section, so the bar tells you where you are - the source has no
   active state at all, which on a five-item nav is a genuine gap rather than a
   style choice. Kept to the same hover tint plus the theme colour, so it reads
   as part of the design and not as a new component. */
.site-header .nav-link.is-active {
  color: var(--theme-color);
  background: rgba(0, 0, 0, 0.04);
}

/* Menu trigger. The source has no mobile nav button - a signed-out visitor on a
   phone gets only LOGIN, because its register button is hidden under 768px and
   its link row is hidden too, leaving the sticky footer bar as the only way
   around. This button is that missing affordance; it is styled as a quiet
   companion to .profile-btn so the cluster still looks like one row of controls. */
.site-header .menu-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.25rem;
  height: 2.25rem;
  flex-shrink: 0;
  padding: 0;
  border: 0;
  border-radius: 9999px;
  background: rgba(15, 23, 42, 0.05);
  color: var(--navigation-font-color, #1f2937);
  cursor: pointer;
  transition: background-color 0.2s ease, transform 0.2s ease;
}

.site-header .menu-btn:hover {
  background: rgba(15, 23, 42, 0.1);
  transform: scale(1.05);
}

.site-header .menu-btn:focus-visible {
  outline: 2px solid var(--theme-color);
  outline-offset: 2px;
}

@media (min-width: 768px) {
  .site-header .menu-btn {
    display: none;
  }
}

/* The same Vuetify anchor problem as .nav-link above, one control along.
   shop.css builds this button at `.hdr-cart-btn` (0,1,0) and asks for
   `color: var(--theme-color)`, and because the markup is an <a>, Vuetify's injected
   `.v-application a { color: var(--v-anchor-base) }` (0,1,1) took it - so the cart
   glyph rendered #de2d59 next to a bar that had just been fixed to the settings
   colour. Restated at (0,2,0) with the same value shop.css intended; the button's
   size, radius and tinted background all still come from shop.css. */
.site-header .hdr-cart-btn {
  color: var(--theme-color, #216287);
}

/* ---------------------------------------------------------------------------
   Wallet pill

   The source builds this out of six utilities plus four inline `style`
   attributes. Spelled out here so the markup carries no inline styles and the
   pill can shrink on a narrow phone instead of pushing the avatar off-screen.
   layout.css already owns its hover (`transform: scale(1.05)`).
   --------------------------------------------------------------------------- */
.site-header .wallet-btn {
  display: inline-flex;
  align-items: center;
  flex-shrink: 1;
  min-width: 0;
  padding: 2px;
  border: 1px solid #e5e7eb;
  border-radius: 9999px;
  background: #fff;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
  text-decoration: none;
}

.site-header .wallet-btn .wallet-icon,
.site-header .wallet-btn .wallet-add {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  flex-shrink: 0;
  border-radius: 9999px;
}

.site-header .wallet-btn .wallet-icon {
  background-color: #fef3c7;
  color: #d97706;
}

.site-header .wallet-btn .wallet-add {
  background-color: var(--theme-color);
  color: #fff;
}

.site-header .wallet-btn .wallet-amount {
  margin: 0 6px;
  overflow: hidden;
  font-size: 0.875rem;
  font-weight: 700;
  line-height: 1;
  color: #1f2937;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.site-header .wallet-btn .wallet-currency {
  font-size: 0.9em;
  opacity: 0.8;
}

/*
 * Narrow phones. A 320px screen has to hold the logo, the cart, a wallet pill
 * and the avatar, and at full size that comes to more than the viewport - the
 * avatar ends up pushed off the right edge, which is the one control that must
 * always be reachable. Two things give way: the cart button drops to 34px and
 * the balance truncates. The balance is the safe one to lose characters from -
 * /add-money and the drawer both show it in full.
 */
@media (max-width: 400px) {
  .site-header .wallet-btn .wallet-amount {
    max-width: 4.5rem;
    margin: 0 4px;
  }

  .site-header .hdr-cart {
    margin-right: 4px;
  }

  .site-header .hdr-cart-btn {
    width: 34px;
    height: 34px;
  }

  .site-header .profile-btn {
    width: 2.25rem;
    height: 2.25rem;
  }
}

/* The avatar is the drawer/dropdown trigger, so it needs a visible focus ring
   for keyboard users; layout.css gives it its size, radius and hover border.

   WHY THIS IS A GRID AND WHY THE CHILDREN ARE 100%
   layout.css sizes the button 2.5rem square and gives it `border: 2px solid
   transparent` so the hover border costs no layout shift - which means the
   content box inside it is 36px, not 40px. Both children were sized against the
   outer figure instead: `.profile-btn img` is `width/height: 100%` (36px) but was
   laid out as an inline image, so it sat on a text baseline and the line box was
   taller than the content box - the picture rode up against the top of the frame
   with its bottom edge sliced off by `overflow: hidden` - and `.hdr-initial` was
   a literal 2.5rem, 4px larger than the box holding it, so its rounded corners
   were cut square on all four sides. Either way the avatar did not sit inside its
   circle. `display: block` on the image kills the baseline, `place-items: center`
   centres whichever child is present, and `overflow` is restated here so the clip
   cannot be lost if layout.css is ever re-extracted from the source. */
.site-header .profile-btn {
  display: grid;
  place-items: center;
  flex-shrink: 0;
  padding: 0;
  overflow: hidden;
  background: none;
}

.site-header .profile-btn img {
  display: block;
  width: 100%;
  height: 100%;
  border-radius: 9999px;
  object-fit: cover;
}

.site-header .profile-btn:focus-visible {
  outline: 2px solid var(--theme-color);
  outline-offset: 2px;
}

/* ---------------------------------------------------------------------------
   Account dropdown (desktop)

   The source's `#userMenu`, toggled by inline JS. layout.css already carries its
   surface (background, border colour, 16rem, radius, shadow) and the `.menu-item`
   hover states, including the red wash on `.logout`; what is missing is the
   positioning, because the original relied on Tailwind's `absolute right-0 mt-2
   w-64 z-50 border` - and the dump has no `w-64` or `z-50`.
   --------------------------------------------------------------------------- */
.site-header #userMenu {
  position: absolute;
  right: 0;
  top: calc(100% + 0.5rem);
  z-index: 30;
  width: 16rem;
  border-width: 1px;
  border-style: solid;
  overflow: hidden;
  /* A short laptop window plus a long menu would otherwise run off the bottom of
     the screen with no way to reach Logout. */
  max-height: calc(100vh - var(--header-h) - 1.5rem);
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}

/* Identity block at the top of the menu. */
.site-header .menu-head {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  padding: 0.875rem 1rem;
  border-bottom: 1px solid var(--color-bg-main, #e5e7eb);
  background: rgba(15, 23, 42, 0.02);
}

.site-header .menu-head img {
  width: 2.5rem;
  height: 2.5rem;
  flex-shrink: 0;
  border-radius: 9999px;
  object-fit: cover;
}

/*
 * THE LETTER THAT STANDS IN FOR A PICTURE
 *
 * An account with no photo - nobody who signed in with a password has one, and a
 * Google profile need not - shows the first letter of its name. The source asked
 * ui-avatars.com to draw that letter as a remote PNG; drawing it here costs no
 * request, cannot 404 behind a captive portal, and does not hand the account
 * holder's name to a third party on every page that renders a header.
 *
 * Two selectors because the same span appears in the round trigger button, which
 * is already 2.5rem and clipped by `.profile-btn`, and in the menu's identity row,
 * which sizes its own avatar. The theme colour is the one the source passed to
 * ui-avatars as `background=f29f2c`.
 */
.site-header .profile-btn .hdr-initial,
.site-header .menu-head .hdr-initial {
  display: grid;
  place-items: center;
  width: 2.5rem;
  height: 2.5rem;
  flex-shrink: 0;
  border-radius: 9999px;
  background: var(--theme-color, #f29f2c);
  color: #fff;
  font-size: 1.0625rem;
  font-weight: 800;
  line-height: 1;
  text-transform: uppercase;
  user-select: none;
}

/* In the trigger it fills the button's content box rather than restating the
   outer 2.5rem - see .site-header .profile-btn above for why those are not the
   same number. The menu's identity row keeps the literal size: nothing clips it
   there, and it has to match the 2.5rem picture it stands in for. */
.site-header .profile-btn .hdr-initial {
  width: 100%;
  height: 100%;
}

.site-header .menu-head .menu-id {
  min-width: 0;
}

.site-header .menu-head .menu-name {
  overflow: hidden;
  font-size: 0.875rem;
  font-weight: 700;
  color: #111827;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.site-header .menu-head .menu-mail {
  overflow: hidden;
  font-size: 0.75rem;
  color: #6b7280;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Menu rows. The source's are `px-4 py-2.5 text-sm` with a 20px icon; the colour
   and hover come from layout.css's `#userMenu .menu-item`. */
.site-header #userMenu .menu-item {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  width: 100%;
  padding: 0.625rem 1rem;
  border: 0;
  background: none;
  font-size: 0.875rem;
  font-weight: 500;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
}

.site-header #userMenu .menu-item svg {
  width: 20px;
  height: 20px;
  flex-shrink: 0;
  opacity: 0.75;
}

.site-header #userMenu .menu-item.logout {
  color: #ef4444;
}

.site-header #userMenu .menu-sep {
  height: 1px;
  margin: 0.25rem 0;
  border: 0;
  background: var(--color-bg-main, #e5e7eb);
}

/* Open/close. `data-[state=open]` animations are a tailwindcss-animate feature
   this build has no plugin for, so the dropdown uses a Vue <transition> and these
   two class pairs instead. */
.hdr-menu-enter-active,
.hdr-menu-leave-active {
  transition: opacity 0.18s ease, transform 0.18s ease;
}

.hdr-menu-enter,
.hdr-menu-leave-to {
  opacity: 0;
  transform: translateY(-6px) scale(0.97);
}

@media (prefers-reduced-motion: reduce) {
  .hdr-menu-enter-active,
  .hdr-menu-leave-active {
    transition: opacity 0.18s ease;
  }

  .hdr-menu-enter,
  .hdr-menu-leave-to {
    transform: none;
  }
}

/* LOGIN / REGISTER. layout.css owns their fill, border, radius and hover, plus
   the `display: none !important` that hides REGISTER under 768px.

   The size reset below is the part the source wrote as an inline `style`
   attribute on both anchors (`padding: 6px 18px; font-size: 13px; ...`). It has
   to be restated here because tailwindcss.css - which loads after layout.css -
   carries the old theme's login-page button under the same class name:

       .btn-login,.btn-pro-circle{ ...; font-size:20px }
       .btn-login{ padding:10px 30px; width:100%!important }

   so without this the header's LOGIN came out at 20px type in a 10px/30px pill
   while REGISTER stayed at 13px. `width` needs `!important` to be answered, and
   the gradient plus blue glow from that same rule are cleared as well, so the
   two buttons are one pair rather than two different shapes. Scoped to
   `.site-header`, so the login page's own `.btn-login` is untouched. */
.site-header .btn-auth {
  white-space: nowrap;
}

.site-header .btn-auth,
.site-header .btn-login,
.site-header .btn-register {
  width: auto !important;
  padding: 6px 18px;
  font-size: 13px;
  font-weight: 600;
  line-height: 1.35;
  background-image: none;
  box-shadow: none;
}

/* ---------------------------------------------------------------------------
   3. FOOTER

   layout.css already carries `.footer-custom` itself and its `.social-grid`,
   `.social-btn` and `.contact-card` children. Everything below is the part the
   source wrote as inline `style` attributes on the elements - a 1050px column,
   the type scale, the card contents - which cannot be copied into a stylesheet
   automatically and had to be transcribed.
   --------------------------------------------------------------------------- */
.footer-custom .ftr-section {
  max-width: var(--shell-max);
  margin: 0 auto;
  padding: 1.5rem 1rem;
}

/*
 * The panel reads as a deliberate end to the page rather than as the last row of
 * it: a hairline of the theme colour across the top, and a very slight vertical
 * wash so the white contact cards and channel buttons have something to sit on
 * whatever `--footer-color` the admin picked. Both are additive - the base
 * `.footer-custom` (border, `--footer-color`, 2rem top margin) is layout.css's,
 * copied from the source, and is untouched.
 */
.footer-custom {
  position: relative;
  background-image: linear-gradient(180deg, rgba(15, 23, 42, 0.025), rgba(15, 23, 42, 0));
}

.footer-custom::before {
  content: '';
  position: absolute;
  top: -1px;
  left: 0;
  right: 0;
  height: 2px;
  background: linear-gradient(
    90deg,
    transparent,
    var(--theme-color, #216287) 20%,
    var(--theme-color, #216287) 80%,
    transparent
  );
  opacity: 0.65;
}

.footer-custom .ftr-grid {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: space-between;
}

@media (min-width: 768px) {
  .footer-custom .ftr-grid {
    flex-direction: row;
    gap: 1.5rem;
  }
}

.footer-custom .ftr-brand {
  width: 100%;
}

.footer-custom .ftr-contact {
  width: 100%;
  margin-top: 1.75rem;
}

@media (min-width: 768px) {
  .footer-custom .ftr-brand {
    width: 50%;
  }

  .footer-custom .ftr-contact {
    width: 45%;
    margin-top: 0;
  }
}

.footer-custom .ftr-brand-link {
  display: inline-block;
  max-width: 100%;
  text-decoration: none;
}

.footer-custom .ftr-logo {
  display: block;
  height: 60px;
  max-width: 100%;
  margin-bottom: 20px;
  object-fit: contain;
  object-position: left center;
}

.footer-custom .ftr-name {
  margin: 0 0 4px;
  font-size: 15px;
  font-weight: 600;
  color: #2c3e50;
}

.footer-custom .ftr-title {
  margin: 0 0 12px;
  font-size: 15px;
  font-weight: 800;
  color: #2c3e50;
}

.footer-custom .ftr-note {
  margin: 0 0 12px;
  padding-right: 15px;
  font-size: 14px;
  line-height: 1.6;
  color: #6b7280;
}

.footer-custom .ftr-note strong {
  color: #374151;
}

.footer-custom .ftr-note p {
  margin: 0 0 0.5rem;
}

.footer-custom .ftr-note p:last-child {
  margin-bottom: 0;
}

/* The mobile-only channel buttons. `.social-grid` (2 columns, 12px gap, 30px top
   margin, hidden from md up) and `.social-btn` (white, 8px radius, 14px 0,
   soft shadow, -2px hover lift) are layout.css's. */
.footer-custom .social-btn i,
.footer-custom .social-btn svg {
  width: 18px;
  height: 18px;
  margin-right: 8px;
  font-size: 18px;
  line-height: 1;
}

.footer-custom .social-btn span {
  font-size: 14px;
  font-weight: 500;
  color: #1f2937;
}

.footer-custom .ftr-heading {
  margin: 0 0 20px;
  font-size: 18px;
  font-weight: 800;
  color: #111827;
}

.footer-custom .ftr-cards {
  display: flex;
  flex-direction: column;
  gap: 14px;
  width: 100%;
}

@media (min-width: 768px) {
  .footer-custom .ftr-cards {
    max-width: 350px;
  }
}

/* Inside `.contact-card` (layout.css: white, 12px radius, 14px 16px, 2px #f1f5f9
   border, -2px hover lift) and beside its 30px `.icon`. */
.footer-custom .contact-card .ftr-card-body {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

/* The lift is layout.css's; the border picking up the theme colour on hover is
   not, and is what makes the card read as a control rather than a panel. */
.footer-custom .contact-card:hover {
  border-color: var(--theme-color, #216287);
}

.footer-custom .contact-card .ftr-card-title {
  display: flex;
  align-items: center;
  font-size: 14px;
  font-weight: 700;
  color: #111827;
}

.footer-custom .contact-card .ftr-card-title .ftr-verified {
  margin-left: 6px;
  font-size: 14px;
  color: #1877f2;
}

.footer-custom .contact-card .ftr-card-sub {
  margin-top: 2px;
  font-size: 12.5px;
  color: #6b7280;
}

/* The copyright bar: the site's small print, and the band the CMS page links now
   live in.

   PERMANENTLY WHITE, deliberately. The panel above it takes the admin's footer
   colour (`--footer-color`, applied by layout.css's `.footer-custom`) and that
   colour can be anything including near-black, so the bar opts out of it rather
   than inheriting a tint that some settings would make the small print unreadable
   against. Everything in it that should follow the shop's colour does so through
   `var(--theme-color)` on the links below. */
.footer-custom .ftr-bar {
  border-top: 1px solid #e2e8f0;
  padding: 1rem;
  background: #fff;
  font-size: 0.8125rem;
  font-weight: 500;
  line-height: 1.6;
  color: #4b5563;
  text-align: center;
}

/* The admin's CMS pages (/page/:slug). Not in the source's footer - it has no
   equivalent feature - and it used to be a row of its own on the panel above,
   where it competed with the brand and contact columns. Centred at the top of the
   bar instead: the pages are terms, privacy, refund and the like, which is what
   the rest of this bar is for. */
.footer-custom .ftr-bar .ftr-pages {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 0.375rem 1rem;
  max-width: var(--shell-max);
  margin: 0 auto;
}

.footer-custom .ftr-bar .ftr-pages a {
  font-size: 13px;
  font-weight: 600;
  color: #64748b;
  text-decoration: none;
  transition: color 0.2s ease;
}

.footer-custom .ftr-bar .ftr-pages a:hover {
  color: var(--theme-color);
}

.footer-custom .ftr-bar .ftr-copy {
  max-width: var(--shell-max);
  margin: 0 auto;
}

/* Only when there is a row above it to be separated from - a shop with no CMS
   pages renders no nav at all, and the copyright line is then the whole bar. */
.footer-custom .ftr-bar .ftr-pages + .ftr-copy {
  margin-top: 0.5rem;
}

.footer-custom .ftr-bar .ftr-strong {
  font-weight: 600;
}

.footer-custom .ftr-bar .ftr-dot {
  display: none;
  margin: 0 0.25rem;
}

@media (min-width: 640px) {
  .footer-custom .ftr-bar .ftr-dot {
    display: inline;
  }
}

/* The developer credit. The shop's own colour rather than the sky blue this was:
   it is the one link on the bar meant to be noticed. */
.footer-custom .ftr-bar .ftr-copy a {
  font-weight: 700;
  color: var(--theme-color);
  text-decoration: none;
  transition: opacity 0.2s ease;
}

.footer-custom .ftr-bar .ftr-copy a:hover {
  opacity: 0.8;
}

/* ---------------------------------------------------------------------------
   3b. THE FIXED MOBILE MENU

   layout.css owns its look (65px, blurred white, top hairline) and styles.css
   carries an older 767px-down copy of the same block. Only the fit is set here,
   and only where the device asks for it: the bar grows by the home-indicator
   inset so the labels clear it, and `.sticky-footer-spacer` below reserves the
   same `--sticky-footer-h`, so the two can never drift apart.
   --------------------------------------------------------------------------- */
@media (max-width: 767px) {
  .sticky-footer-container {
    height: var(--sticky-footer-h);
    padding-bottom: env(safe-area-inset-bottom, 0px);
  }
}

/* Clearance for the bar above, rendered by the same component so it only exists
   when the bar does. Exactly the bar's height and no more: this is the page's ONLY
   allowance for it now that styles.css's `body { margin-bottom: 70px }` is
   neutralised above, so the copyright line meets the top of the menu instead of
   sitting a screen-inch clear of it. The source hard-codes 55px against a 65px bar
   and lets the last row show through; the variable keeps the two exact.

   It used to be `.footer-custom .ftr-spacer` as well, i.e. two spacers in a row -
   ~120px of blank page under the copyright bar on every phone. */
.sticky-footer-spacer {
  display: block;
  width: 100%;
  height: var(--sticky-footer-h);
  background: transparent;
}

@media (min-width: 768px) {
  .sticky-footer-spacer {
    display: none;
  }
}

/* ---------------------------------------------------------------------------
   4. TEXT SELECTION

   layout.css ends with the source's copy-protection block:
   `body { user-select: none }` plus `-webkit-touch-callout: none` on everything.
   It is carried over because layout.css is a verbatim extract, but it is not part
   of the design and on this storefront it is actively harmful: the whole point of
   /codes, /transactions and an order's detail page is that the visitor copies a
   voucher code, a transaction ID or a player ID out of it, and none of that is
   possible while the page refuses to select. It also blocks long-press-to-copy on
   every phone.

   Selection is restored here rather than by editing layout.css, so that file
   stays a faithful copy of its source.
   --------------------------------------------------------------------------- */
body,
* {
  -webkit-touch-callout: default;
}

body {
  -webkit-user-select: text;
  -moz-user-select: text;
  -ms-user-select: text;
  user-select: text;
}

/* Controls are the exception: a double-tap on a nav item or a quantity stepper
   should not paint the label blue. */
.site-header .nav-link,
.site-header .btn-auth,
.site-header .menu-btn,
.site-header .wallet-btn,
.site-header #userMenu .menu-item,
.footer-custom .social-btn,
.footer-custom .contact-card,
.sticky-footer-item a,
.chat-widget-container,
button {
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
}

/* ---------------------------------------------------------------------------
   5. SKELETONS

   One shimmer primitive, `.sk`, plus shape modifiers. Every placeholder in the
   app is built from these, so a section that is waiting for data looks like the
   section it is about to become rather than like a spinner in a box.

   The shine is a real gradient sweeping across the block (an ::after that
   translates), not a background-position trick: because the highlight is a child
   it can be delayed per element, which is what makes the light travel across a
   grid of cards instead of every card flashing in unison. Components set
   `--sk-d` on each item to stagger it.
   --------------------------------------------------------------------------- */
@keyframes sk-sweep {
  0% {
    transform: translateX(-100%);
  }
  100% {
    transform: translateX(100%);
  }
}

.sk {
  position: relative;
  overflow: hidden;
  border-radius: 0.5rem;
  background-color: #e8edf4;
  /* So a bare <div class="sk"> is never zero-height by accident. */
  min-height: 0.75rem;
}

.sk::after {
  content: '';
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 100%;
  background-image: linear-gradient(
    90deg,
    rgba(255, 255, 255, 0) 0%,
    rgba(255, 255, 255, 0.72) 50%,
    rgba(255, 255, 255, 0) 100%
  );
  animation: sk-sweep 1.6s ease-in-out infinite;
  animation-delay: var(--sk-d, 0s);
}

/* Motion is the whole point of this component, so it is not simply switched off:
   the block keeps a gentle opacity pulse, which still reads as "loading" without
   anything sliding across the screen. */
@media (prefers-reduced-motion: reduce) {
  .sk::after {
    animation: none;
    background-image: none;
    background-color: rgba(255, 255, 255, 0.35);
  }
}

/* Shapes. `sk-text` sizes match the type they stand in for, so nothing shifts
   when the real content arrives. */
.sk-text {
  height: 0.75rem;
  border-radius: 9999px;
}

.sk-text-sm {
  height: 0.625rem;
  border-radius: 9999px;
}

.sk-text-lg {
  height: 1.125rem;
  border-radius: 9999px;
}

.sk-title {
  height: 1.375rem;
  border-radius: 0.375rem;
}

.sk-circle {
  border-radius: 9999px;
}

.sk-thumb {
  width: 100%;
  aspect-ratio: 1 / 1;
  border-radius: 0.75rem;
}

.sk-banner {
  width: 100%;
  aspect-ratio: 16 / 9;
  border-radius: 0.75rem;
}

.sk-chip {
  height: 1.75rem;
  border-radius: 9999px;
}

.sk-btn {
  height: 2.5rem;
  border-radius: 0.5rem;
}

/* Fractional widths, so a stack of placeholder lines has the ragged right edge
   real text has instead of looking like a bar chart. */
.sk-w-25 { width: 25%; }
.sk-w-33 { width: 33%; }
.sk-w-40 { width: 40%; }
.sk-w-50 { width: 50%; }
.sk-w-60 { width: 60%; }
.sk-w-70 { width: 70%; }
.sk-w-80 { width: 80%; }
.sk-w-full { width: 100%; }

/* Placeholder containers. These mirror the real grids' breakpoints so the
   skeleton occupies the same footprint as the content it precedes. */
.sk-grid {
  display: grid;
  gap: 0.75rem;
}

.sk-grid-2 {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

.sk-grid-3 {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

.sk-grid-4 {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

.sk-grid-7 {
  grid-template-columns: repeat(4, minmax(0, 1fr));
}

@media (min-width: 640px) {
  .sk-grid-4 {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

@media (min-width: 768px) {
  .sk-grid {
    gap: 1rem;
  }

  .sk-grid-3 {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }

  .sk-grid-4 {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }

  .sk-grid-7 {
    grid-template-columns: repeat(7, minmax(0, 1fr));
  }
}

.sk-stack {
  display: flex;
  flex-direction: column;
  gap: 0.625rem;
}

.sk-inline {
  display: flex;
  align-items: center;
  gap: 0.75rem;
}

/* A placeholder that stands in for one of the design's white panels, so the page
   keeps its card rhythm while it waits. */
.sk-card {
  padding: 0.625rem;
  border-radius: 0.75rem;
  background: #fff;
  box-shadow: 0 1px 2px rgba(15, 23, 42, 0.05), 0 0 0 1px rgba(226, 232, 240, 0.7);
}

@media (min-width: 768px) {
  .sk-card {
    padding: 0.75rem;
  }
}

.sk-card-body {
  display: flex;
  flex-direction: column;
  gap: 0.4375rem;
  margin-top: 0.625rem;
}

/*
 * The second half of the loading story: content that is already on screen from
 * cache while a fresh copy is fetched. There is nothing to replace with a
 * skeleton - the data is right there and usable - so the section keeps its real
 * content and a single highlight passes over it to say "checking for updates".
 *
 * `pointer-events: none` on the sweep is what keeps the section fully
 * interactive while it runs.
 */
.sk-refresh {
  position: relative;
}

.sk-refresh::after {
  content: '';
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 40%;
  background-image: linear-gradient(
    90deg,
    rgba(255, 255, 255, 0) 0%,
    rgba(255, 255, 255, 0.55) 50%,
    rgba(255, 255, 255, 0) 100%
  );
  /* Its own keyframe, not sk-sweep: a translateX percentage resolves against the
     element's own width, so a 40%-wide highlight animated -100% to 100% would
     only cross the middle 80% of the section and stop. */
  animation: sk-glide 1.4s ease-in-out infinite;
  pointer-events: none;
  z-index: 2;
}

@keyframes sk-glide {
  0% {
    transform: translateX(-100%);
  }
  100% {
    transform: translateX(350%);
  }
}

@media (prefers-reduced-motion: reduce) {
  .sk-refresh::after {
    display: none;
  }
}

/* ---------------------------------------------------------------------------
   6. ROUTE CHANGES

   With every page bundled into the first load and every payload warmed in the
   background, switching routes is usually instant and none of this is seen. It
   exists for the first visit and for a cold link someone opens directly.
   --------------------------------------------------------------------------- */

/* The bar Nuxt's `loading` component renders. Above the header and the drawer,
   because it has to remain visible whatever is open. */
.route-bar {
  position: fixed;
  top: 0;
  left: 0;
  height: 3px;
  z-index: 100010;
  background-image: linear-gradient(
    90deg,
    rgba(255, 255, 255, 0) 0%,
    var(--theme-color, #216287) 45%,
    rgba(255, 255, 255, 0.9) 100%
  );
  box-shadow: 0 0 10px rgba(15, 23, 42, 0.25);
  transition: width 0.2s ease, opacity 0.3s ease;
  pointer-events: none;
}

/* A navigation that threw. The bar still runs to 100% - Nuxt is rendering the
   error page and a bar stuck mid-way would suggest work is still happening - but
   in the error colour rather than the theme's. */
.route-bar.is-failed {
  background-image: linear-gradient(
    90deg,
    rgba(255, 255, 255, 0) 0%,
    #dc2626 45%,
    rgba(255, 255, 255, 0.9) 100%
  );
}

/* Page transition. Short and downward-biased: long enough to feel deliberate,
   short enough that it never reads as a wait. */
.page-enter-active,
.page-leave-active {
  transition: opacity 0.18s ease, transform 0.18s ease;
}

.page-enter,
.page-leave-to {
  opacity: 0;
  transform: translateY(6px);
}

@media (prefers-reduced-motion: reduce) {
  .page-enter-active,
  .page-leave-active {
    transition: opacity 0.18s ease;
  }

  .page-enter,
  .page-leave-to {
    transform: none;
  }
}

