/**
 * Dollarisho — global shimmer / skeleton loading system.
 * OWNER: shimmer module (inc/shimmer.php). Loaded on every template.
 *
 * Two independent jobs:
 *   1. Image shimmer  — a wrapper paints an animated placeholder until its
 *      <img> fires load/error. Driven by assets/js/shimmer.js (adds .is-ready).
 *   2. Hydration skeletons — regions whose content only exists after Alpine
 *      boots show a skeleton until <html> gets .ds-hydrated.
 *
 * The .ds-shimmer / dsShimmer keyframes here are the canonical copy. blog.css
 * defines the same names for the blog grid skeleton; the declarations are
 * identical so load order does not matter.
 */

@keyframes dsShimmer { 100% { transform: translateX(100%); } }

/* --------------------------------------------------------------------------
   1. Generic shimmer surface
   -------------------------------------------------------------------------- */
.ds-shimmer { position: relative; background: #EBE9F5; overflow: hidden; }
.ds-shimmer::after {
	content: ""; position: absolute; inset: 0; transform: translateX(-100%);
	background: linear-gradient(90deg, transparent, rgba(255,255,255,.65), transparent);
	animation: dsShimmer 1.4s infinite;
}

/* --------------------------------------------------------------------------
   2. Image shimmer
   The overlay sits on the wrapper, never on the <img>, so existing inline
   image styles in the template parts stay untouched.
   -------------------------------------------------------------------------- */
.ds-imgshim { position: relative; overflow: hidden; background: #EBE9F5; }
.ds-imgshim::after {
	content: ""; position: absolute; inset: 0; z-index: 2; pointer-events: none;
	transform: translateX(-100%);
	background: linear-gradient(90deg, transparent, rgba(255,255,255,.6), transparent);
	animation: dsShimmer 1.4s infinite;
}
/* The hide rule is gated on html.ds-shim, which shimmer.js sets on ITSELF as its
   first statement. If that file 404s, is blocked, or throws early, the class is
   never added and images just stay visible — a loader that cannot resolve can
   never hide content. (ds-js, set inline in wp_head, is not safe for this: it is
   true even when shimmer.js never arrives.) */
html.ds-shim .ds-imgshim > img,
html.ds-shim .ds-imgshim > picture > img { opacity: 0; transition: opacity .38s ease; }

/* Settled state — set by shimmer.js on load or error.
   NOTE: the reveal selectors MUST stay scoped under the same html.ds-shim root as
   the hide rule above, or they weigh (0,2,1) against its (0,2,2), lose, and leave
   every wrapped image permanently invisible. Regression-tested in tests/shimmer.sh. */
.ds-imgshim.is-ready::after { content: none; animation: none; }
.ds-imgshim.is-ready { background: none; }
html.ds-shim .ds-imgshim.is-ready > img,
html.ds-shim .ds-imgshim.is-ready > picture > img { opacity: 1; }

/* Variant for dark panels (revolut showcase, wise hero, footer). */
.ds-imgshim--dark { background: rgba(255,255,255,.07); }
.ds-imgshim--dark::after {
	background: linear-gradient(90deg, transparent, rgba(255,255,255,.16), transparent);
}
/* Variant for small logos/wordmarks sitting in tinted pills. */
.ds-imgshim--chip { background: rgba(25,20,75,.09); border-radius: 6px; }

/* Variant for boxes that already paint their own placeholder gradient
   (.ds-cover, .ds-featured__fig, .ds-post-hero in blog.css). Only the sweep is
   added; the element keeps its own background at every stage. */
.ds-imgshim--keepbg,
.ds-imgshim--keepbg.is-ready { background: revert; }

/* --------------------------------------------------------------------------
   3. Hydration skeletons
   Shown only while shimmer.js is running AND Alpine has not initialised yet.
   shimmer.js adds .ds-hydrated to <html> on alpine:initialized, with a timeout
   fallback so a blocked Alpine CDN cannot leave a skeleton on screen forever.

   Both gates below are written to FAIL OPEN: the resting state is "skeleton
   hidden, real content visible", and the swap only happens inside
   html.ds-shim:not(.ds-hydrated) — a window that can only exist while the
   script is alive. If shimmer.js never loads or throws, .ds-shim is absent and
   visitors get the plain server-rendered page rather than content that is
   hidden behind a loader nothing will ever resolve.

   !important is required on both sides: several call sites carry an inline
   display (display:grid on a skeleton wrapper), and an inline declaration would
   otherwise beat the resting hide rule and pin the skeleton on screen forever.
   Because of that, a skeleton needing a display mode other than block must say
   so with a --grid/--flex class, NOT with inline style.
   -------------------------------------------------------------------------- */
.ds-hyd { display: none !important; }
html.ds-shim:not(.ds-hydrated) .ds-hyd { display: block !important; }
html.ds-shim:not(.ds-hydrated) .ds-hyd.ds-hyd--grid { display: grid !important; }
html.ds-shim:not(.ds-hydrated) .ds-hyd.ds-hyd--flex,
html.ds-shim:not(.ds-hydrated) .ds-hyd.ds-hyd__row { display: flex !important; }

/* Real content that Alpine fills in. Visible by default; hidden only for the
   brief pre-hydration window. The --flex/--grid variants restate the display
   mode these nodes need, since some rely on the class rather than inline style. */
.ds-hyd-real--flex { display: flex; }
.ds-hyd-real--grid { display: grid; }
html.ds-shim:not(.ds-hydrated) .ds-hyd-real { display: none !important; }

/* Skeleton primitives. */
.ds-hyd__line {
	position: relative; overflow: hidden; background: #EBE9F5;
	border-radius: 7px; height: 13px;
}
.ds-hyd__line::after {
	content: ""; position: absolute; inset: 0; transform: translateX(-100%);
	background: linear-gradient(90deg, transparent, rgba(255,255,255,.65), transparent);
	animation: dsShimmer 1.4s infinite;
}
.ds-hyd__stack { display: flex; flex-direction: column; gap: 10px; }
.ds-hyd__row { display: flex; flex-wrap: wrap; gap: 8px; }
.ds-hyd__chip { height: 32px; width: 104px; border-radius: 999px; }
.ds-hyd__chip--sm { height: 28px; width: 84px; }

/* On dark surfaces (compare picker recommendation card, revolut panels). */
.ds-hyd--dark .ds-hyd__line { background: rgba(255,255,255,.1); }
.ds-hyd--dark .ds-hyd__line::after {
	background: linear-gradient(90deg, transparent, rgba(255,255,255,.18), transparent);
}

/* --------------------------------------------------------------------------
   4. Accessibility
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
	.ds-shimmer::after,
	.ds-imgshim::after,
	.ds-hyd__line::after { animation: none; }
	.ds-imgshim > img,
	.ds-imgshim > picture > img { transition: none; }
}

/* No-JS. The visibility gates above are already keyed on html.ds-shim, which
   only shimmer.js can add, so without JS images are visible and skeletons are
   hidden with no help needed here. The one thing left to do is stop the sweep:
   .is-ready is applied by script, so the overlay would otherwise animate
   forever on top of an image that has long since finished loading. */
html.no-js .ds-imgshim::after { content: none; animation: none; }
html.no-js .ds-imgshim { background: none; }
