/**
 * Restyles a configured gallery page (see JRH_GALLERY_PAGE_SHOWS in
 * jrh-gallery-page.php) from the theme's plain white stock-demo look
 * into a dark, production-specific design. Every color here is a CSS
 * custom property (--jrh-gallery-*), written per-show as inline <style>
 * by the PHP file -- this stylesheet itself never hardcodes a color, so
 * the exact same file works for every future show once its own entry
 * exists in the PHP config. `!important` throughout: the theme's own
 * stylesheet (Photography by ThemeGoods) sets these same properties
 * with its own specificity, and WordPress happens to print it after
 * this plugin's own enqueue, so without `!important` the theme would
 * win outright -- same reasoning as this project's other jrh-* plugins
 * overriding theme/WPForms defaults.
 *
 * Never edits the theme's own template files -- reads the real DOM the
 * theme already renders (page title, credit-list paragraphs, photo
 * grid) and repaints it, the same "own plugin, don't touch the theme"
 * discipline as jrh-curtain-hero/jrh-resume-page/jrh-gallery-filters.
 */

body.jrh-gallery-themed,
body.jrh-gallery-themed #wrapper,
body.jrh-gallery-themed #page_caption {
	background-color: var(--jrh-gallery-bg) !important;
}

/* --- Site header, on this page only ---------------------------------- */
/* The theme's global header (.top_bar) is plain white/black everywhere
   else on the site, which is correct there -- only overridden here,
   scoped to .jrh-gallery-themed, so no other page is affected. The
   logo image itself is swapped to the site's existing white-ink variant
   by gallery-page.js (jhorowwhite.svg, the same asset the homepage's
   own dark header already uses) rather than fighting the black one with
   a CSS filter. */
body.jrh-gallery-themed .top_bar {
	background-color: var(--jrh-gallery-bg) !important;
	border-color: rgba(255, 255, 255, 0.12) !important;
}

body.jrh-gallery-themed .top_bar a,
body.jrh-gallery-themed .top_bar li,
body.jrh-gallery-themed nav a {
	color: var(--jrh-gallery-text) !important;
}

/* Joshua, live: "shift the gallery page up so there is less room
   between the bottom of the menu line and the title." Traced live: the
   real gap wasn't margin/padding on the title itself (all 0) -- it's
   #page_caption's own 80px top padding, the theme's built-in breathing
   room around the whole title area on every gallery page. #wrapper's
   own separate 210px top padding (reserving space for the site's fixed
   header so it never overlaps page content) is left alone -- that one's
   shared by every page on the site, not gallery-specific, so touching
   it would move every other page's content too. */
body.jrh-gallery-themed #page_caption {
	padding-top: 28px !important;
}

/* --- Title ------------------------------------------------------------ */
body.jrh-gallery-themed .page_title_wrapper {
	background: transparent !important;
}

body.jrh-gallery-themed .page_title_inner h1 {
	color: var(--jrh-gallery-text) !important;
	font-family: Georgia, 'Times New Roman', serif !important;
	font-weight: 400 !important;
	/* Was the theme's own default (34px, unset by this plugin until now)
	   -- Joshua, live: "make the title of the opera a little bigger."
	   clamp() so it scales down gracefully on narrow screens instead of
	   just being a flat larger px value, same pattern used elsewhere on
	   the site (e.g. .jrh-directing-headline in curtain.css). */
	font-size: clamp(32px, 3.2vw, 44px) !important;
	letter-spacing: 0.12em !important;
}

/* --- Credit list / company line / photo-credit line -------------------
   All theme-rendered as plain <p> tags inside .gallery_cols_content, in
   document order. Styled uniformly rather than picking apart which
   literal line is which (the theme gives no distinct class to tell them
   apart, and the exact number of credit lines varies show to show) --
   soft, warm, legible against the dark background. The one exception is
   the FIRST paragraph -- see below. */
body.jrh-gallery-themed .gallery_cols_content,
body.jrh-gallery-themed .gallery_cols_content p {
	color: var(--jrh-gallery-warm) !important;
}

/* Joshua, live: "put the cherry blossom under the title... under that
   it should say where the opera was performed." The venue/company line
   has no class of its own (see the comment above) -- getting it to sit
   right under the title is done by moving it to be the FIRST paragraph
   in the gallery's own content (a wp-admin content edit, not a code
   change), which this rule then styles distinctly from the rest of the
   credit list: small, centered, uppercase, letter-spaced, like a
   masthead line rather than a plain credit row. */
body.jrh-gallery-themed .gallery_cols_content > p:first-of-type {
	text-align: center !important;
	text-transform: uppercase !important;
	/* Was 12px -- Joshua, live: "make the text of the companies a little
	   bigger. Just slightly." */
	font-size: 13px !important;
	letter-spacing: 0.08em !important;
	margin-bottom: 3vh !important;
}

/* A paragraph holding an embedded video (see gallery-page.js's own
   comment on the `jrh-gallery-embed-wrap` class it adds, in the credits-
   reorganizing loop) -- the theme's own default markup renders an embed
   full-width and left-aligned with no containment, which read as
   broken/misplaced rather than deliberately positioned. Joshua, live,
   on Journey to Valhalla's own highlight-reel video: "can we find a
   better spot for the youtube video link?" Centered and capped at a
   comfortable width, same general shape as the venue line above it.
   aspect-ratio (not a fixed height) keeps it responsive regardless of
   the embed's own hardcoded width/height attributes. */
.jrh-gallery-embed-wrap {
	max-width: 640px;
	margin: 0 auto 6vh;
}

.jrh-gallery-embed-wrap iframe {
	display: block;
	width: 100%;
	height: auto;
	aspect-ratio: 16 / 9;
}

/* --- Divider sprig ------------------------------------------------------
   Inserted by gallery-page.js as the FIRST child of
   .gallery_cols_content -- i.e. between the title (a separate element
   above this whole block) and whichever paragraph you've put first (the
   venue line, per the rule above) -- NOT after everything like the
   original version. Joshua, live: "put the cherry blossom under the
   title of the opera, and under that it should say where the opera was
   performed." */
.jrh-gallery-page-divider {
	display: block;
	width: 90px;
	/* Joshua, live: "now they're too far apart." Most of the original
	   gap above the divider is the theme's own spacing between its title
	   block and the content area below (not anything this plugin set) --
	   pulled up with a negative top margin rather than chasing that
	   spacing through the theme's own nested wrapper markup. Bottom
	   margin (the gap before the venue line) trimmed too, from 4vh. */
	margin: -40px auto 16px;
	opacity: 0.9;
}

.jrh-gallery-page-divider path {
	stroke: var(--jrh-gallery-warm);
}

/* Each leaf (see buildLeaf in gallery-page.js) is ALSO a <path>, same as
   the stem above -- this overrides the stroke that rule would otherwise
   put on them too (a class-level selector beats the plain-element one
   above regardless of source order). Joshua, live: "can the petals on
   the blossom be more 'petal' shaped and not just circles?"
   transform-origin: 0 0 is what makes the sway animation below pivot
   from the leaf's own base rather than its center or tip -- the <path>
   itself always starts at its own local (0,0) (see buildLeaf's "d"),
   and the wrapping <g> gallery-page.js sets is what places that point
   at the leaf's real spot on the stem, so the two line up automatically
   no matter where on the stem a given leaf sits. */
.jrh-gallery-page-divider .jrh-gallery-page-petal {
	fill: var(--jrh-gallery-motif);
	stroke: none;
	transform-origin: 0 0;
	/* Duration/easing shared by every leaf; animation-delay (set inline,
	   per-leaf, in gallery-page.js) is what keeps them from all bobbing
	   in exact unison -- Joshua, live: "animate the leaves to slowly bob
	   up and down."
	   Was `ease-in-out` with just a 0%/50%/100% keyframe -- technically
	   already looped forever (iteration-count: infinite, confirmed live
	   in devtools), but `ease-in-out` decelerates hard into each
	   extreme, so the leaf sits nearly motionless for a good stretch of
	   every cycle before reversing -- Joshua, live: "they need to
	   smoothly do the swaying, not just once. Like a continuous wind."
	   It reads as "swayed once, then stopped" even though it never
	   actually stopped. `linear`, plus two extra keyframe waypoints
	   between the two extremes (below), removes those resting points --
	   the leaf is always visibly moving, never parked.
	   Was ±5deg -- confirmed live (devtools computed style) that this
	   really was running the whole time, but on a leaf only ~15 units
	   long, 5 degrees of rotation moves the tip by about a pixel --
	   Joshua, live: "they dont do anything at the moment." Not a bug,
	   just genuinely too small to see. Tripled to ±15deg. */
	animation: jrh-gallery-page-leaf-sway 4.5s linear infinite;
}

@keyframes jrh-gallery-page-leaf-sway {
	0% {
		transform: rotate(-15deg);
	}
	25% {
		transform: rotate(6deg);
	}
	50% {
		transform: rotate(15deg);
	}
	75% {
		transform: rotate(-6deg);
	}
	100% {
		transform: rotate(-15deg);
	}
}

/* --- Divider sprig, skyline variant (West Side Story only) -------------
   Built by buildSkylineDivider in gallery-page.js -- see that function's
   own top-of-file comment for the full round-by-round history (moon
   position, window flicker timing, taxi behavior, the spire). This
   stylesheet only ever supplies color/animation, never structure/
   position -- those are all set as attributes in the JS, same split the
   blossom divider above already uses. */
.jrh-gallery-page-divider.jrh-gallery-page-divider--skyline {
	/* Wider than the blossom's 90px -- a skyline strip, not a narrow
	   sprig -- so this needs its own width/margin, not just the shared
	   rule above. Two classes combined on one element outweighs the
	   single-class rule above regardless of source order, so this wins
	   without needing !important.
	   Was -34px -- Joshua, live, on the real page: "have the title...
	   be a little closer to our animation." Most of that extra gap was
	   the theme's own spacing (same source as the blossom's own -40px
	   fix), plus this graphic's own empty sky above the rooflines
	   (headroom for the spire) reads as part of the same gap visually. */
	width: 260px;
	height: auto;
	margin: -68px auto 20px;
	opacity: 1;
}

.jrh-gallery-skyline-building,
.jrh-gallery-skyline-spire {
	/* Was a flat var(--jrh-gallery-accent) -- Joshua, live: "darken the
	   buildings slightly." Mixed toward black rather than picking a new
	   hardcoded color, so it stays tied to this show's own sampled
	   palette instead of an invented shade. */
	fill: color-mix(in srgb, var(--jrh-gallery-accent) 78%, black);
}

.jrh-gallery-skyline-spire {
	/* Same darkened tone as the buildings' own fill above, so the spire
	   reads as part of the same building rather than a lighter accent. */
	stroke: color-mix(in srgb, var(--jrh-gallery-accent) 78%, black);
	stroke-width: 0.5;
}

.jrh-gallery-skyline-line {
	stroke: var(--jrh-gallery-warm);
	stroke-width: 0.8;
}

.jrh-gallery-skyline-moon {
	fill: var(--jrh-gallery-text);
}

/* The moon's own halo gradient (see buildSkylineDivider) -- both of its
   <stop> elements share this class and differ only in their own
   stop-opacity attribute (a plain number, not a color, so it stays
   inline rather than needing a rule here). */
.jrh-gallery-skyline-glow-stop {
	stop-color: var(--jrh-gallery-text);
}

/* Moon dip -- Joshua, live: "Have it slowly dip above and below the
   'horizon' line you created." Moves the whole moon <g> (halo + disc
   together, see the JS) up and down; the viewBox's own bottom edge
   (y=24, exactly the horizon line) is what actually hides it once it
   dips low enough -- no separate clip/mask needed for that part. */
.jrh-gallery-skyline-moon-dip {
	animation: jrh-gallery-skyline-dip 11s ease-in-out infinite alternate;
	transform-box: fill-box;
	transform-origin: center;
}

@keyframes jrh-gallery-skyline-dip {
	0% {
		transform: translateY(-3.5px);
	}
	100% {
		transform: translateY(4.5px);
	}
}

/* Window flicker -- Joshua, live: "have them flicker on and off, but
   randomly and at about 10 seconds between each flicker." Every window
   shares this one keyframe; animation-duration/-delay are set inline
   per-window in the JS (same pattern as the blossom leaves' own sway
   above) so none of them ever line up. */
.jrh-gallery-skyline-window {
	animation-name: jrh-gallery-skyline-flicker;
	animation-timing-function: ease-in-out;
	animation-iteration-count: infinite;
}

@keyframes jrh-gallery-skyline-flicker {
	0%, 46% {
		opacity: 1;
	}
	50%, 96% {
		opacity: 0.12;
	}
	100% {
		opacity: 1;
	}
}

.jrh-gallery-skyline-window-halo,
.jrh-gallery-skyline-taxi-halo {
	fill: var(--jrh-gallery-warm);
	opacity: 0.5;
}

.jrh-gallery-skyline-window-core,
.jrh-gallery-skyline-taxi-core {
	fill: var(--jrh-gallery-warm);
}

/* Taxis -- Joshua, live: "have those zooming like taxis from left to
   right every few seconds randomly," then, after the first version
   eased in/out: "taxis shouldnt slow down as they reach the end of the
   line" (linear, below, fixes that) and "they should only go on the
   gold line, not past it" (the clip-path in the JS handles that part).
   Direction is fixed per taxi in the JS (--ltr/--rtl below) -- a mix of
   both, each on its own uneven duration/delay, is what reads as random
   traffic rather than a strict loop. */
.jrh-gallery-skyline-taxi {
	animation-timing-function: linear;
	animation-iteration-count: infinite;
}

.jrh-gallery-skyline-taxi--ltr {
	animation-name: jrh-gallery-skyline-taxi-ltr;
}

.jrh-gallery-skyline-taxi--rtl {
	animation-name: jrh-gallery-skyline-taxi-rtl;
}

@keyframes jrh-gallery-skyline-taxi-ltr {
	0% {
		transform: translateX(0px);
		opacity: 1;
	}
	28% {
		transform: translateX(100px);
		opacity: 1;
	}
	28.5% {
		transform: translateX(100px);
		opacity: 0;
	}
	100% {
		transform: translateX(100px);
		opacity: 0;
	}
}

@keyframes jrh-gallery-skyline-taxi-rtl {
	0% {
		transform: translateX(100px);
		opacity: 1;
	}
	28% {
		transform: translateX(0px);
		opacity: 1;
	}
	28.5% {
		transform: translateX(0px);
		opacity: 0;
	}
	100% {
		transform: translateX(0px);
		opacity: 0;
	}
}

/* Blue only (post 858) -- the first taxi's light bar, alternating red/
   blue instead of the plain warm taxi glow. See buildSkylineDivider's
   own comment (the copCar parameter) for why this is real, not
   decorative, for this specific opera. Fixed colors, not custom
   properties -- same "real hardware, not a sampled show color"
   exception as the rapiers' own steel blades. steps(1) timing is a hard
   on/off snap rather than a fade, the way an actual strobe reads --
   different from every other flicker/pulse on this page, which all
   ease smoothly. */
.jrh-gallery-skyline-copcar-red,
.jrh-gallery-skyline-copcar-blue {
	animation-timing-function: steps(1);
	animation-iteration-count: infinite;
	animation-duration: 0.5s;
}

.jrh-gallery-skyline-copcar-red {
	fill: #e0302a;
	animation-name: jrh-gallery-skyline-copcar-flash-a;
}

.jrh-gallery-skyline-copcar-blue {
	fill: #2a5ae0;
	animation-name: jrh-gallery-skyline-copcar-flash-b;
}

@keyframes jrh-gallery-skyline-copcar-flash-a {
	0%, 49% {
		opacity: 1;
	}
	50%, 100% {
		opacity: 0.25;
	}
}

@keyframes jrh-gallery-skyline-copcar-flash-b {
	0%, 49% {
		opacity: 0.25;
	}
	50%, 100% {
		opacity: 1;
	}
}

/* --- Divider sprig, camellia variant (La Traviata only) ----------------
   Built by buildCamelliaDivider in gallery-page.js -- see that
   function's own top-of-file comment for the full round-by-round
   history. Same split as the other two dividers: structure/position in
   the JS, color/animation here. */
.jrh-gallery-page-divider.jrh-gallery-page-divider--camellia {
	/* Narrower than the skyline's 260px strip -- a single compact bloom,
	   closer in spirit to the blossom's own 90px sprig but a bit larger
	   since this shape has more going on (two petal rings, leaves,
	   stamens). Two classes combined outweighs the shared single-class
	   rule regardless of source order, same reasoning as the skyline
	   variant's own override. */
	/* Was 130px/-50px -- Joshua, live, on a real screenshot: "the flower
	   here is too big - too much space between the icon and the texts."
	   Smaller AND pulled up further -- the earlier -50px pass (itself a
	   correction from -34px) still wasn't enough, so this jumps further
	   than the previous small increments did. */
	width: 95px;
	height: auto;
	margin: -100px auto 20px;
	opacity: 1;
}

.jrh-gallery-camellia-leaf {
	/* Fixed, not tied to this show's own palette -- see the JS function's
	   own comment for why a camellia leaf stays naturalistic green
	   regardless of any specific production's photography.
	   stroke/stroke-width set explicitly here -- without this, the
	   blossom divider's own shared ".jrh-gallery-page-divider path"
	   rule further up this file (a plain element selector, so it
	   matches ANY <path>, not just blossom petals) was quietly giving
	   these leaves a gold outline at the browser's default 1-unit
	   stroke-width. Joshua, live: "the gold lines around the green
	   leaves a little thinner" -- kept the gold (it's this show's own
	   real var(--jrh-gallery-warm), not invented), just thinner and now
	   a deliberate, documented value instead of an inherited accident. */
	fill: #1c3324;
	stroke: var(--jrh-gallery-warm);
	stroke-width: 0.35;
}

.jrh-gallery-camellia-stamens {
	fill: var(--jrh-gallery-warm);
}

.jrh-gallery-camellia-petal {
	stroke: color-mix(in srgb, var(--jrh-gallery-motif) 55%, black);
	stroke-width: 0.6;
	/* Was 19s each direction (38s full cycle) in the brainstorm -- Joshua,
	   live, after comparing it against a wilt/falling-petals option and
	   picking this one anyway: "just a much slower change from red to
	   white." The two endpoints are this show's own real palette fields
	   (see the JS function's comment), not one-off colors invented just
	   for this animation. */
	animation: jrh-gallery-camellia-color-shift 48s ease-in-out infinite alternate;
}

@keyframes jrh-gallery-camellia-color-shift {
	0% {
		fill: var(--jrh-gallery-text);
	}
	100% {
		fill: var(--jrh-gallery-motif);
	}
}

/* --- Divider sprig, dice variant (Porgy and Bess only) -----------------
   Built by buildDiceDivider in gallery-page.js -- see that function's
   own top-of-file comment for the full round-by-round history (why
   dice specifically, the CSS-transform-in-calc() bug, the bevel
   rendering bug, the direction-reversing spin, and the final "flat and
   dramatic, not tumbling and playful" correction). Same split as the
   other dividers: structure/position in the JS, color/animation here. */
.jrh-gallery-page-divider.jrh-gallery-page-divider--dice {
	/* Compact, like the camellia -- two small dice side by side, not a
	   wide strip. Two classes combined outweighs the shared single-
	   class rule regardless of source order, same reasoning as the
	   other variants' own overrides. Was 170px/-50px -- Joshua, live, on
	   the real page: "the dice can be smaller, and closer to the
	   title." */
	width: 120px;
	height: auto;
	margin: -66px auto 20px;
	opacity: 1;
}

.jrh-gallery-dice-face {
	fill: var(--jrh-gallery-text);
	stroke: var(--jrh-gallery-bg);
	stroke-width: 1.3;
}

.jrh-gallery-dice-pips {
	fill: var(--jrh-gallery-bg);
}

.jrh-gallery-dice-line {
	stroke: var(--jrh-gallery-warm);
	stroke-width: 1;
}

.jrh-gallery-dice-shadow {
	fill: #000;
	transform-origin: center;
	animation: jrh-gallery-dice-shadow 7s ease-in-out infinite;
}

/* Dice sit flat/level (no resting tilt anywhere, in the JS or here) --
   Joshua, live: "the idea of this is more dramatic and not fun and
   silly," after an earlier squash-and-stretch hop read as too playful.
   What's left: long stillness, then one small, slow, heavy lift and
   settle -- no spring, no squash. */
.jrh-gallery-dice-die {
	transform-origin: center;
	animation: jrh-gallery-dice-lift 7s ease-in-out infinite;
}

@keyframes jrh-gallery-dice-lift {
	0%, 74% {
		transform: translateY(0);
	}
	85% {
		transform: translateY(-5px);
	}
	100% {
		transform: translateY(0);
	}
}

@keyframes jrh-gallery-dice-shadow {
	0%, 74% {
		transform: scale(1);
		opacity: 0.32;
	}
	85% {
		transform: scale(0.82);
		opacity: 0.2;
	}
	100% {
		transform: scale(1);
		opacity: 0.32;
	}
}

@media (prefers-reduced-motion: reduce) {
	.jrh-gallery-page-divider .jrh-gallery-page-petal {
		animation: none;
	}
	.jrh-gallery-skyline-moon-dip {
		animation: none;
		transform: translateY(0.5px);
	}
	.jrh-gallery-skyline-window {
		animation: none;
		opacity: 0.7;
	}
	.jrh-gallery-skyline-taxi {
		animation: none;
		opacity: 0;
	}
	.jrh-gallery-skyline-copcar-red,
	.jrh-gallery-skyline-copcar-blue {
		animation: none;
	}
	.jrh-gallery-camellia-petal {
		animation: none;
		fill: var(--jrh-gallery-text);
	}
	.jrh-gallery-dice-die,
	.jrh-gallery-dice-shadow {
		animation: none;
	}
	.jrh-gallery-candles-flame,
	.jrh-gallery-candles-halo {
		animation: none;
	}
	.jrh-gallery-filmstrip-frame-1,
	.jrh-gallery-filmstrip-frame-2,
	.jrh-gallery-filmstrip-frame-3 {
		animation: none;
		opacity: 0;
	}
	.jrh-gallery-filmstrip-frame-1 {
		opacity: 1;
	}
	.jrh-gallery-filmstrip-eye-l,
	.jrh-gallery-filmstrip-eye-r {
		animation: none;
	}
	.jrh-gallery-barberpole-pole {
		animation: none;
		background-position: 0 0;
	}
	.jrh-gallery-wheel-spin {
		animation: none;
	}
	.jrh-gallery-constellation-star,
	.jrh-gallery-constellation-bg-star {
		animation: none;
		opacity: 0.7;
	}
	.jrh-gallery-constellation-lines {
		animation: none;
		opacity: 0.6;
	}
	.jrh-gallery-ring-power {
		animation: none;
	}
	.jrh-gallery-rapiers-blade {
		animation: none;
		opacity: 0.85;
	}
	.jrh-gallery-carmen-ember {
		animation: none;
	}
	.jrh-gallery-carmen-smoke {
		animation: none;
		opacity: 0.35;
	}
	.jrh-gallery-boheme-flame,
	.jrh-gallery-boheme-halo {
		animation: none;
	}
	.jrh-gallery-boheme-smoke {
		animation: none;
		opacity: 0;
	}
	.jrh-gallery-fledermaus-sway {
		animation: none;
		transform: rotate(-8deg);
	}
}

/* --- Divider sprig, filmstrip variant (The Magic Flute only) -----------
   Built by buildFilmstripDivider in gallery-page.js -- see that
   function's own top-of-file comment for the full round-by-round
   history. Same split as every other divider: structure/position in
   the JS, color/animation here. */
.jrh-gallery-page-divider.jrh-gallery-page-divider--filmstrip {
	/* Wider than the compact single-object dividers (camellia/dice/
	   candles) -- a real 3-frame strip needs the room. Two classes
	   combined outweighs the shared single-class rule regardless of
	   source order, same reasoning as every other variant's own
	   override. Margin starts in the already-converged range (see
	   jrh-divider-margin-default in memory) instead of a loose first
	   guess. */
	width: 220px;
	height: auto;
	margin: -60px auto 20px;
	opacity: 1;
}

.jrh-gallery-filmstrip-body {
	fill: var(--jrh-gallery-accent);
}

.jrh-gallery-filmstrip-window {
	fill: var(--jrh-gallery-bg);
}

/* Sprocket holes -- deliberately NOT tied to this show's own palette;
   see this divider's own top-of-file JS comment for why (real film
   hardware, not a sampled color). */
.jrh-gallery-filmstrip-sprockets {
	fill: #c7cbd6;
}

.jrh-gallery-filmstrip-content {
	fill: var(--jrh-gallery-text);
}

.jrh-gallery-filmstrip-content-stroke {
	stroke: var(--jrh-gallery-text);
	stroke-width: 1.6;
	fill: none;
}

.jrh-gallery-filmstrip-pupil {
	fill: var(--jrh-gallery-bg);
}

/* Was an instant on/off snap between frames -- Joshua, live: "the
   sequence should be heart fades up, fades out. Eyes fade up, blink,
   fade out, music note fades up, fades out. then repeats." Rebuilt as
   three separate fade keyframe sets sharing one 9s cycle, each frame's
   own visible window non-overlapping with the others'. */
.jrh-gallery-filmstrip-frame-1 {
	animation: jrh-gallery-filmstrip-heart-fade 9s ease-in-out infinite;
}

@keyframes jrh-gallery-filmstrip-heart-fade {
	0%       { opacity: 0; }
	8%, 25%  { opacity: 1; }
	33%      { opacity: 0; }
	100%     { opacity: 0; }
}

.jrh-gallery-filmstrip-frame-2 {
	animation: jrh-gallery-filmstrip-eyes-fade 9s ease-in-out infinite;
}

@keyframes jrh-gallery-filmstrip-eyes-fade {
	0%, 33%  { opacity: 0; }
	41%, 58% { opacity: 1; }
	66%      { opacity: 0; }
	100%     { opacity: 0; }
}

.jrh-gallery-filmstrip-frame-3 {
	animation: jrh-gallery-filmstrip-note-fade 9s ease-in-out infinite;
}

@keyframes jrh-gallery-filmstrip-note-fade {
	0%, 66%  { opacity: 0; }
	74%, 91% { opacity: 1; }
	100%     { opacity: 0; }
}

/* The blink happens once, timed to land while the eyes are actually
   visible (41%-66% of the same 9s cycle) -- not a separate,
   independently-timed loop the way the earlier owl concept's blink
   was. transform-origin is each eye's own exact (cx, cy) as a literal
   coordinate, not the "center" keyword -- asking the browser to resolve
   "center" via fill-box has been the actual root cause of THREE
   separate silent-animation bugs already on other dividers this
   project (the camellia's breathing, the dice's toss, and this
   pattern's own near-miss); a plain circle's fill-box is simple enough
   that "center" would likely resolve fine here too, but there's no
   reason to re-risk a fourth occurrence when the exact coordinate is
   right there and unambiguous either way. */
.jrh-gallery-filmstrip-eye-l {
	transform-origin: 65px 25px;
	animation: jrh-gallery-filmstrip-blink 9s ease-in-out infinite;
}
.jrh-gallery-filmstrip-eye-r {
	transform-origin: 75px 25px;
	animation: jrh-gallery-filmstrip-blink 9s ease-in-out infinite;
}

@keyframes jrh-gallery-filmstrip-blink {
	0%, 50%, 100% { transform: scaleY(1); }
	53%           { transform: scaleY(0.1); }
	56%           { transform: scaleY(1); }
}

/* --- Divider sprig, candles variant (Fiddler on the Roof only) ---------
   Built by buildCandlesDivider in gallery-page.js -- see that
   function's own top-of-file comment for the full round-by-round
   history. Shared by BOTH of Fiddler's two gallery pages; every color
   below comes from var(--jrh-gallery-*), so each page's own real
   palette differentiates them automatically -- nothing here is
   show-specific. */
.jrh-gallery-page-divider.jrh-gallery-page-divider--candles {
	/* Compact, like the camellia/dice -- two small candles, not a wide
	   strip. Two classes combined outweighs the shared single-class
	   rule regardless of source order, same reasoning as the other
	   variants' own overrides. Margin starts more aggressive than the
	   other dividers' own FIRST guess did -- every one of them ended up
	   needing tightened after Joshua saw it live (skyline -34px->-68px,
	   camellia -34px->-50px->-66px, dice -50px->-66px), so this starts
	   from that same converged range instead of repeating the pattern. */
	width: 130px;
	height: auto;
	margin: -60px auto 20px;
	opacity: 1;
}

.jrh-gallery-candles-wax-stop-light {
	stop-color: var(--jrh-gallery-text);
}

.jrh-gallery-candles-wax-stop-warm {
	stop-color: var(--jrh-gallery-warm);
}

.jrh-gallery-candles-flame-stop-warm {
	stop-color: var(--jrh-gallery-warm);
}

.jrh-gallery-candles-flame-stop-light {
	stop-color: var(--jrh-gallery-text);
}

.jrh-gallery-candles-halo-stop-in {
	stop-color: var(--jrh-gallery-warm);
	stop-opacity: 0.55;
}

.jrh-gallery-candles-halo-stop-out {
	stop-color: var(--jrh-gallery-warm);
	stop-opacity: 0;
}

.jrh-gallery-candles-line {
	stroke: var(--jrh-gallery-warm);
	stroke-width: 0.8;
}

/* Halo brightness loosely tracks its own flame's own flicker (matching
   animation-duration/-delay pair below), so the glow and the flame
   read as one light source instead of two independent things. */
.jrh-gallery-candles-halo {
	animation-name: jrh-gallery-candles-halo-pulse;
	animation-timing-function: ease-in-out;
	animation-iteration-count: infinite;
	/* duration/delay set inline per-halo in the JS, matching its own flame */
}

@keyframes jrh-gallery-candles-halo-pulse {
	0%   { opacity: 0.6; }
	8%   { opacity: 0.55; }
	27%  { opacity: 0.4; }
	34%  { opacity: 0.65; }
	73%  { opacity: 0.4; }
	100% { opacity: 0.6; }
}

/* Flame flicker -- Joshua, live, twice: "much much slower" than the
   first two attempts. Final timing is a slow, deliberately uneven,
   irregular set of stops (not a smooth repeating pulse) on an 18s/22s
   pair, pivoting from each flame's own real base (transform-origin:
   0 0 -- see the JS function's own comment for why that matters here)
   so it can never visibly dip below the candle. */
.jrh-gallery-candles-flame {
	transform-origin: 0 0;
	animation-name: jrh-gallery-candles-flicker;
	animation-timing-function: ease-in-out;
	animation-iteration-count: infinite;
	/* duration/delay set inline per-flame in the JS */
}

@keyframes jrh-gallery-candles-flicker {
	0%   { transform: scaleY(1) translateX(0); }
	8%   { transform: scaleY(0.97) translateX(0.2px); }
	15%  { transform: scaleY(1.04) translateX(-0.2px); }
	27%  { transform: scaleY(0.88) translateX(0.4px); }
	34%  { transform: scaleY(1.05) translateX(-0.3px); }
	50%  { transform: scaleY(0.98) translateX(0.1px); }
	61%  { transform: scaleY(1.02) translateX(-0.2px); }
	73%  { transform: scaleY(0.9) translateX(0.3px); }
	85%  { transform: scaleY(1.03) translateX(-0.15px); }
	100% { transform: scaleY(1) translateX(0); }
}

/* --- Divider sprig, barber pole variant (The Barber of Seville, Associate
   Director credit / post 987, only) -------------------------------------
   Built by buildBarberPoleDivider in gallery-page.js -- see that
   function's own top-of-file comment for the full build history (real
   photo grounding, the 4-option visual-companion choice, and the
   static-transform-vs-CSS-animation split applied proactively here). */
.jrh-gallery-page-divider.jrh-gallery-page-divider--barberpole {
	/* Plain HTML <div>s stacked in a column (see buildBarberPoleDivider's
	   own comment for why this one is HTML, not SVG like every other
	   divider) -- display/width need to be set here since the shared
	   `.jrh-gallery-page-divider` rule above assumes an SVG's own
	   viewBox-driven sizing. Two classes combined outweighs that shared
	   single-class rule regardless of source order, same reasoning as
	   every other variant's own override. Starts at -60px, the already-
	   converged range every other divider's own first guess ended up
	   needing tightened into anyway (see the candles divider's own
	   comment for that history) instead of repeating the pattern from
	   scratch. */
	display: flex;
	flex-direction: column;
	align-items: center;
	width: 22px;
	margin: -60px auto 20px;
	opacity: 1;
}

.jrh-gallery-barberpole-cap {
	width: 22px;
	height: 7px;
	border-radius: 4px;
	/* A soft top-lit gradient rather than a flat fill -- Joshua preferred
	   this version's caps over an SVG flat-fill re-implementation (see
	   buildBarberPoleDivider's own comment). color-mix() for the lighter
	   stop rather than a second custom property, same trick already used
	   for the credits card's own border (.jrh-gallery-credits-col). */
	background: linear-gradient(180deg,
		color-mix(in srgb, var(--jrh-gallery-warm) 55%, white 45%),
		var(--jrh-gallery-warm));
	box-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
}

/* The rotating-stripe illusion: a 45deg repeating-linear-gradient
   (alternating the show's own cream/text and motif colors) animated by
   sliding background-position one full tile per loop -- the classic CSS
   barber-pole trick. background-size/the gradient's own stop widths (18px
   each, 51px total) are deliberately non-round numbers relative to each
   other so the diagonal repeat tiles seamlessly; the keyframe's own
   0->51px travels exactly one tile. inset box-shadow (not a separate
   element) is what gives it a cylinder's own rounded shading. */
.jrh-gallery-barberpole-pole {
	width: 18px;
	height: 78px;
	position: relative;
	overflow: hidden;
	box-shadow:
		inset -6px 0 10px rgba(0, 0, 0, 0.45),
		inset 6px 0 10px rgba(0, 0, 0, 0.25);
	background-image: repeating-linear-gradient(45deg,
		var(--jrh-gallery-text) 0, var(--jrh-gallery-text) 18px,
		var(--jrh-gallery-motif) 18px, var(--jrh-gallery-motif) 36px);
	background-size: 51px 51px;
	/* Was 1.8s -- Joshua, live: "can we slow the pole down a little bit." */
	animation: jrh-gallery-barberpole-spin 2.6s linear infinite;
}

@keyframes jrh-gallery-barberpole-spin {
	from {
		background-position: 0 0;
	}
	to {
		background-position: 0 51px;
	}
}

/* Second Barber of Seville page only (post 561) -- see buildBarberPoleDivider's
   own comment for why this is an opt-in modifier rather than a change to
   the base 2-band rule above. Same trick, a 4th band added: cream/red/
   cream/blue instead of cream/red -- Joshua, live, from a reference photo
   of a real barber pole: "add blue like in this picture," then "make the
   blue thinner ... so the order is red (regular size) white (regular
   size) blue (thin) white (regular size)." Tile is now 42px (12+12+6+12)
   instead of 36px, so background-size/the keyframe's own travel distance
   both change to match (42 * sqrt(2) ~= 59.4) -- same "diagonal tile
   length" math as the base rule, just for a different tile size. Two
   classes combined outweighs the base single-class rule regardless of
   source order, same reasoning as every other variant's own override. */
.jrh-gallery-barberpole-pole.jrh-gallery-barberpole-pole--tri {
	background-image: repeating-linear-gradient(45deg,
		var(--jrh-gallery-motif) 0, var(--jrh-gallery-motif) 12px,
		var(--jrh-gallery-text) 12px, var(--jrh-gallery-text) 24px,
		var(--jrh-gallery-accent) 24px, var(--jrh-gallery-accent) 30px,
		var(--jrh-gallery-text) 30px, var(--jrh-gallery-text) 42px);
	background-size: 59.4px 59.4px;
	animation-name: jrh-gallery-barberpole-spin-tri;
}

@keyframes jrh-gallery-barberpole-spin-tri {
	from {
		background-position: 0 0;
	}
	to {
		background-position: 0 59.4px;
	}
}

/* --- Divider sprig, wheel variant (Rumpelstiltskin and the Unlovable
   Children, post 949, only) -----------------------------------------------
   Built by buildWheelDivider in gallery-page.js -- see that function's
   own top-of-file comment for the full build history (real emblem
   grounding, the 4-option visual-companion choice, and why the wheel's
   rim/spokes/hub are a fixed neutral rather than a sampled color). */
.jrh-gallery-page-divider.jrh-gallery-page-divider--wheel {
	/* Compact, like the camellia/dice/candles -- one small emblem, not a
	   wide strip. Two classes combined outweighs the shared single-class
	   rule regardless of source order, same reasoning as every other
	   variant's own override. Starts at -60px, the already-converged
	   range every other divider's own first guess ended up needing
	   tightened into anyway (see the candles divider's own comment for
	   that history) instead of repeating the pattern from scratch. */
	width: 90px;
	height: auto;
	margin: -60px auto 20px;
	opacity: 1;
}

.jrh-gallery-wheel-shield {
	fill: var(--jrh-gallery-motif);
	stroke: var(--jrh-gallery-warm);
	stroke-width: 2;
}

/* Fixed neutral pewter-grey, not a custom property -- same deliberate
   exception as the filmstrip's sprocket holes (real hardware, not this
   show's own sampled palette), and reusing that exact grey. */
.jrh-gallery-wheel-rim,
.jrh-gallery-wheel-spokes line {
	fill: none;
	stroke: #c7cbd6;
	stroke-width: 3;
}

.jrh-gallery-wheel-rim {
	stroke-width: 4;
}

.jrh-gallery-wheel-hub {
	fill: #c7cbd6;
}

/* The one animated piece. transform-origin is an explicit coordinate
   (this shape's own true center, 50 50 in its 100x100 viewBox), not the
   `center` keyword -- see buildFilmstripDivider's own comment for why
   that's a standing rule on this page. Was 4s, then 8s -- Joshua, live,
   twice: "keep A as an option (But slow it down)," then, after seeing it
   live: "Slow the wheel down a bit." */
.jrh-gallery-wheel-spin {
	transform-origin: 50px 50px;
	animation: jrh-gallery-wheel-spin 12s linear infinite;
}

@keyframes jrh-gallery-wheel-spin {
	from {
		transform: rotate(0deg);
	}
	to {
		transform: rotate(360deg);
	}
}

/* --- Divider sprig, constellation variant (La Calisto, Assistant Director
   / post 939, only) --------------------------------------------------------
   Built by buildConstellationDivider in gallery-page.js -- see that
   function's own top-of-file comment for the full build history (real
   story/costume grounding, the reference-photo-proportions fix, and why
   the connecting lines and the stars animate independently). */
.jrh-gallery-page-divider.jrh-gallery-page-divider--constellation {
	/* Wider than the compact single-object dividers (camellia/dice/
	   candles/wheel) -- the Dipper's own real proportions are a wide,
	   flat shape, not a small icon -- so this needs its own width, not
	   the shared single-class rule's 90px. Two classes combined
	   outweighs that shared rule regardless of source order, same
	   reasoning as every other variant's own override. Starts at -60px,
	   the already-converged range every other divider's own first guess
	   ended up needing tightened into anyway (see the candles divider's
	   own comment for that history) instead of repeating the pattern
	   from scratch. */
	width: 170px;
	height: auto;
	margin: -60px auto 20px;
	opacity: 1;
}

.jrh-gallery-constellation-star {
	fill: var(--jrh-gallery-text);
	animation-name: jrh-gallery-constellation-twinkle;
	animation-timing-function: ease-in-out;
	animation-iteration-count: infinite;
	/* duration/delay/transform-origin set inline per-star in the JS */
}

/* Smaller, dimmer, unconnected stars scattered around the Dipper --
   var(--jrh-gallery-motif) rather than the main stars' own text color,
   a deliberate distinction (not a color mistake) so the Dipper itself
   still reads as the brighter, foreground shape against a dimmer,
   cooler-tinted field of distant stars behind it. */
.jrh-gallery-constellation-bg-star {
	fill: var(--jrh-gallery-motif);
	opacity: 0.6;
}

@keyframes jrh-gallery-constellation-twinkle {
	0%, 100% {
		opacity: 0.15;
		transform: scale(0.8);
	}
	50% {
		opacity: 1;
		transform: scale(1.2);
	}
}

/* The Dipper's own outline -- one shared, slower cycle (not per-element
   randomized like the stars above, since both paths need to move
   together as a single shape): fades in, holds, fades out, so the
   constellation periodically resolves out of the already-twinkling sky
   rather than sitting there statically the whole time. Joshua, live:
   "twinkeling stars, then the lines between them slowly fade in, and
   then out." */
.jrh-gallery-constellation-lines {
	fill: none;
	stroke: var(--jrh-gallery-text);
	stroke-width: 0.7;
	animation: jrh-gallery-constellation-lines-fade 12s ease-in-out infinite;
}

@keyframes jrh-gallery-constellation-lines-fade {
	0%, 15% {
		opacity: 0;
	}
	50% {
		opacity: 0.6;
	}
	85%, 100% {
		opacity: 0;
	}
}

/* --- Divider sprig, ring variant (Journey to Valhalla, post 893, only) --
   Built by buildRingDivider in gallery-page.js -- see that function's own
   top-of-file comment for the full build history (Joshua's own "should
   be a gold ring" steer, several rejected shape/perspective attempts,
   and why the shading is one radial gradient instead of hard-edged
   rings). */
.jrh-gallery-page-divider.jrh-gallery-page-divider--ring {
	/* Compact, like the wheel/candles/dice -- one small emblem, not a
	   wide strip. Two classes combined outweighs the shared single-class
	   rule regardless of source order, same reasoning as every other
	   variant's own override. Starts at -60px, the already-converged
	   range every other divider's own first guess ended up needing
	   tightened into anyway (see the candles divider's own comment for
	   that history) instead of repeating the pattern from scratch. */
	width: 90px;
	height: auto;
	margin: -60px auto 20px;
	opacity: 1;
}

.jrh-gallery-ring-stop-edge {
	stop-color: var(--jrh-gallery-motif);
}

.jrh-gallery-ring-stop-peak {
	stop-color: var(--jrh-gallery-warm);
}

/* Animated via `filter`, not opacity -- Joshua, live: "have it pulse a
   bit in brightness to show its power." brightness() makes the gold
   visibly flare at its peak (the gradient's own highlight blows out
   brighter, same as an overexposed photo of real metal catching more
   light), and the paired drop-shadow grows and fades with it so the
   whole ring seems to radiate rather than just get lighter in place. */
.jrh-gallery-ring-power {
	transform-origin: 50px 50px;
	animation: jrh-gallery-ring-power-pulse 4s ease-in-out infinite;
}

@keyframes jrh-gallery-ring-power-pulse {
	0%, 100% {
		filter: brightness(1) drop-shadow(0 0 0px rgba(228, 180, 60, 0));
	}
	50% {
		filter: brightness(1.55) drop-shadow(0 0 5px rgba(228, 180, 60, 0.85));
	}
}

/* --- Divider sprig, rapiers variant (Romeo and Juliet, Assistant
   Director / post 884, only) ----------------------------------------------
   Built by buildRapiersDivider in gallery-page.js -- see that function's
   own top-of-file comment for the full build history (a dropped rose
   concept, and the tapered-blade-tip fix). */
.jrh-gallery-page-divider.jrh-gallery-page-divider--rapiers {
	/* Compact, like the wheel/candles/dice/ring -- one small emblem, not
	   a wide strip. Two classes combined outweighs the shared single-
	   class rule regardless of source order, same reasoning as every
	   other variant's own override. Starts at -60px, the already-
	   converged range every other divider's own first guess ended up
	   needing tightened into anyway (see the candles divider's own
	   comment for that history) instead of repeating the pattern from
	   scratch. */
	width: 90px;
	height: auto;
	margin: -60px auto 20px;
	opacity: 1;
}

/* Fixed neutral steel greys, not custom properties -- same deliberate
   exception as the filmstrip's sprockets and the wheel's own rim/spokes
   (real hardware, not this show's own sampled palette). The two blades
   use slightly different greys so they read as two distinct swords
   crossing, not one shape mirrored. */
.jrh-gallery-rapiers-blade .jrh-gallery-rapiers-steel {
	fill: #cfd6dc;
	stroke: #cfd6dc;
}

.jrh-gallery-rapiers-blade--2 .jrh-gallery-rapiers-steel {
	fill: #e8ecef;
	stroke: #e8ecef;
}

.jrh-gallery-rapiers-pommel {
	fill: none;
	stroke: var(--jrh-gallery-warm);
	stroke-width: 1.8;
}

/* Each blade glints on its own -- opacity only, staggered by half the
   cycle (delay set via the --2 modifier class below) so the two never
   brighten in unison. */
.jrh-gallery-rapiers-blade {
	animation-name: jrh-gallery-rapiers-glint;
	animation-duration: 4.5s;
	animation-timing-function: ease-in-out;
	animation-iteration-count: infinite;
}

.jrh-gallery-rapiers-blade--2 {
	animation-delay: 2.25s;
}

@keyframes jrh-gallery-rapiers-glint {
	0%, 100% {
		opacity: 0.6;
	}
	50% {
		opacity: 1;
	}
}

/* --- Divider sprig, Carmen variant (post 849 only) ----------------------
   Built by buildCarmenDivider in gallery-page.js -- see that function's
   own top-of-file comment for the full build history (a dropped rose,
   an ace-of-spades card that lost out once asked directly, and two real
   geometry/timing bugs found live). */
.jrh-gallery-page-divider.jrh-gallery-page-divider--carmen {
	/* Wider than the compact single-object dividers -- the angled
	   cigarette plus its own rising smoke needs more horizontal and
	   vertical room than a small icon. Two classes combined outweighs
	   the shared single-class rule regardless of source order, same
	   reasoning as every other variant's own override. Starts at -60px,
	   the already-converged range every other divider's own first guess
	   ended up needing tightened into anyway (see the candles divider's
	   own comment for that history) instead of repeating the pattern
	   from scratch. Was 150px, then 120px -- Joshua, live, twice: "can
	   the cigarette be a little smaller," then "smaller." */
	width: 90px;
	height: auto;
	margin: -60px auto 20px;
	opacity: 1;
}

.jrh-gallery-carmen-paper {
	fill: var(--jrh-gallery-text);
	stroke: #2a1a10;
	stroke-width: 1.3;
}

.jrh-gallery-carmen-filter {
	fill: var(--jrh-gallery-warm);
	stroke: #2a1a10;
	stroke-width: 1.3;
}

/* Fire is still a real color worth sampling per-show, not a fixed
   neutral -- same reasoning as the candles' own flame. Flickers for the
   same "still alight" reason a candle flame does. */
.jrh-gallery-carmen-ember {
	fill: var(--jrh-gallery-motif);
	animation: jrh-gallery-carmen-ember-flicker 3.5s ease-in-out infinite;
}

@keyframes jrh-gallery-carmen-ember-flicker {
	0%, 100% {
		opacity: 0.75;
	}
	30% {
		opacity: 1;
	}
	55% {
		opacity: 0.5;
	}
	80% {
		opacity: 0.9;
	}
}

/* Fixed neutral grey, not a custom property -- smoke is a real material,
   not a sampled show color, same reasoning as the filmstrip's sprockets/
   the wheel's spokes/the rapiers' own steel blades. Joshua, live, on an
   earlier version that just appeared at full opacity: "can the smoke
   fade in, not just appear" -- the 0%-15% ramp below is that fix. Drawn
   OUTSIDE the cigarette's own rotated <g> in the JS (real smoke rises
   straight up regardless of the cigarette's own tilt), starting from
   the ember's real rotated on-screen position (computed with actual
   trigonometry -- see the JS's own comment -- not eyeballed, after two
   earlier attempts at that guess came out wrong: "smoke is coming out
   of the wrong side of the cigarette," twice). */
.jrh-gallery-carmen-smoke {
	fill: none;
	stroke: #c9c2b0;
	stroke-linecap: round;
	animation-name: jrh-gallery-carmen-smoke-rise;
	animation-duration: 7s;
	animation-timing-function: ease-in-out;
	animation-iteration-count: infinite;
	/* animation-delay set inline per-wisp in the JS */
}

@keyframes jrh-gallery-carmen-smoke-rise {
	0% {
		transform: translate(0, 0) scale(1);
		opacity: 0;
	}
	15% {
		transform: translate(1px, -4px) scale(1.02);
		opacity: 0.55;
	}
	70% {
		transform: translate(3px, -24px) scale(1.15);
		opacity: 0.25;
	}
	100% {
		transform: translate(6px, -34px) scale(1.3);
		opacity: 0;
	}
}

/* --- Divider sprig, La Bohème variant (post 836 only) -------------------
   Built by buildBohemeDivider in gallery-page.js -- see that function's
   own top-of-file comment for the full build history (reusing Fiddler's
   own candle shapes/gradients, a new extinguish/relight cycle, and two
   real chamberstick corrections). */
.jrh-gallery-page-divider.jrh-gallery-page-divider--boheme {
	/* Compact, like the wheel/candles/dice/ring -- one small object, not
	   a wide strip. Two classes combined outweighs the shared single-
	   class rule regardless of source order, same reasoning as every
	   other variant's own override. Starts at -60px, the already-
	   converged range every other divider's own first guess ended up
	   needing tightened into anyway (see the candles divider's own
	   comment for that history) instead of repeating the pattern from
	   scratch. */
	width: 90px;
	height: auto;
	margin: -60px auto 20px;
	opacity: 1;
}

/* Wax/flame/halo use ONLY var(--jrh-gallery-warm) and
   var(--jrh-gallery-text) -- identical rule to Fiddler's own candles
   (see that divider's own CSS comment), not invented colors specific to
   this show. */
.jrh-gallery-boheme-wax-stop-light {
	stop-color: var(--jrh-gallery-text);
}

.jrh-gallery-boheme-wax-stop-warm {
	stop-color: var(--jrh-gallery-warm);
}

.jrh-gallery-boheme-flame-stop-warm {
	stop-color: var(--jrh-gallery-warm);
}

.jrh-gallery-boheme-flame-stop-light {
	stop-color: var(--jrh-gallery-text);
}

.jrh-gallery-boheme-halo-stop-in {
	stop-color: var(--jrh-gallery-warm);
	stop-opacity: 0.55;
}

.jrh-gallery-boheme-halo-stop-out {
	stop-color: var(--jrh-gallery-warm);
	stop-opacity: 0;
}

/* The chamberstick's own copper -- both gradient stops come from the
   SAME var(--jrh-gallery-motif), lightened/darkened via color-mix()
   rather than needing two separate custom properties -- same technique
   the barber pole's own caps already use. Was a cool blue-pewter first
   (matching this show's own cold garret lighting); Joshua asked for
   warm copper instead. */
.jrh-gallery-boheme-metal-stop-light {
	stop-color: color-mix(in srgb, var(--jrh-gallery-motif) 55%, white 45%);
}

.jrh-gallery-boheme-metal-stop-dark {
	stop-color: color-mix(in srgb, var(--jrh-gallery-motif) 70%, black 30%);
}

/* transform-origin: 0 0 (not "center") is what keeps the flame's own
   scaleY collapse pivoting from its real base -- see buildCandlesDivider
   and buildFilmstripDivider's own comments for why an explicit
   coordinate is a standing rule on this page rather than the `center`
   keyword. A full extinguish-and-relight cycle, not Fiddler's own steady
   flicker -- this show's story is specifically about the flame going
   OUT, not just burning. */
.jrh-gallery-boheme-flame {
	transform-origin: 0 0;
	animation: jrh-gallery-boheme-flame-out-relight 9s ease-in-out infinite;
}

@keyframes jrh-gallery-boheme-flame-out-relight {
	0%, 30% {
		opacity: 1;
		transform: scaleY(1);
	}
	38% {
		opacity: 0.35;
		transform: scaleY(0.35) translateX(1.5px);
	}
	45%, 70% {
		opacity: 0;
		transform: scaleY(0);
	}
	80% {
		opacity: 0.6;
		transform: scaleY(0.7);
	}
	88%, 100% {
		opacity: 1;
		transform: scaleY(1);
	}
}

.jrh-gallery-boheme-halo {
	animation: jrh-gallery-boheme-halo-out-relight 9s ease-in-out infinite;
}

@keyframes jrh-gallery-boheme-halo-out-relight {
	0%, 30% {
		opacity: 0.55;
	}
	45%, 70% {
		opacity: 0;
	}
	88%, 100% {
		opacity: 0.55;
	}
}

/* A brief wisp right as the flame goes out, gone again before it
   relights -- already fades in as part of its own 42%-48% ramp rather
   than popping in, same lesson as Carmen's own smoke ("can the smoke
   fade in, not just appear"). */
.jrh-gallery-boheme-smoke {
	animation: jrh-gallery-boheme-smoke-rise 9s ease-in-out infinite;
}

@keyframes jrh-gallery-boheme-smoke-rise {
	0%, 42% {
		opacity: 0;
		transform: translateY(0);
	}
	48% {
		opacity: 0.5;
		transform: translateY(-4px);
	}
	58% {
		opacity: 0;
		transform: translateY(-14px);
	}
	100% {
		opacity: 0;
	}
}

/* --- Divider sprig, Die Fledermaus variant (post 823 only) --------------
   Built by buildFledermausDivider in gallery-page.js -- see that
   function's own top-of-file comment for the full build history (a real
   lopsided-outline bug fixed, a dropped cork-pop concept with its own
   real animation bug, and the cork itself cut once the concept changed
   to swaying). */
.jrh-gallery-page-divider.jrh-gallery-page-divider--fledermaus {
	/* Taller than it is wide -- a bottle, not a compact icon -- so this
	   needs its own width, not the shared single-class rule. Two classes
	   combined outweighs that shared rule regardless of source order,
	   same reasoning as every other variant's own override. Starts at
	   -60px, the already-converged range every other divider's own first
	   guess ended up needing tightened into anyway (see the candles
	   divider's own comment for that history) instead of repeating the
	   pattern from scratch. */
	width: 70px;
	height: auto;
	margin: -60px auto 20px;
	opacity: 1;
}

.jrh-gallery-fledermaus-glass {
	fill: var(--jrh-gallery-accent);
}

.jrh-gallery-fledermaus-foil {
	fill: var(--jrh-gallery-warm);
}

.jrh-gallery-fledermaus-cage {
	fill: none;
	stroke: var(--jrh-gallery-warm);
	stroke-width: 1.2;
}

/* Whole bottle sways from its own base, like it's being raised and waved
   around mid-toast -- Joshua, live: "what if the bottle was just
   angled, and swayed around a little bit," after a dropped cork-pop
   concept (see the JS's own comment). */
.jrh-gallery-fledermaus-sway {
	transform-origin: 50px 140px;
	animation: jrh-gallery-fledermaus-sway 4.5s ease-in-out infinite;
}

@keyframes jrh-gallery-fledermaus-sway {
	0%, 100% {
		transform: rotate(-14deg);
	}
	50% {
		transform: rotate(4deg);
	}
}

/* --- Photo grid: replaced by the carousel below ------------------------
   Joshua, live: "i think i want to get rid of the grid in favor of an
   image carousel." The theme's own grid markup is left completely
   intact (gallery-page.js reads its <a class="fancy-gallery"> links to
   build the carousel from the SAME photos) -- just hidden, rather than
   removed, so there's nothing fragile about depending on it still being
   there to read from. */
body.jrh-gallery-themed #portfolio_filter_wrapper {
	display: none !important;
}

/* --- Credits + carousel, side by side -----------------------------------
   Joshua, live: "have the carousel on the right side of the page, and
   the credits next to it." Built by gallery-page.js: the venue line (see
   the :first-of-type rule above) stays put, centered, full width, right
   under the divider -- this row is everything AFTER that (the credit
   list, the photo-credit line) paired with the new carousel. */
/* Was max-width: 1100px, credits/carousel columns roughly 1:2 -- Joshua,
   live, after looking at francescazambello.com and elorenmeeker.com:
   "how do we make the star of the page the production photos?", then
   "keep the carousel, strip it down." Credits are now a narrow, fixed-
   width sidebar (see .jrh-gallery-credits-col below) rather than a
   column that grows with the row, so essentially all of the extra room
   from this wider row goes straight to the photo instead of being split
   between the two. */
.jrh-gallery-row {
	display: flex;
	align-items: center;
	gap: 5vw;
	max-width: 1600px;
	margin: 6vh auto 0;
	flex-wrap: wrap;
}

/* Was a plain stack of theme <p> tags at 260px -- Joshua, live, from a
   reference image: a bordered card with a "PRODUCTION" heading, each
   credit as a small uppercase label over its larger value, a rule, then
   a "PHOTOS" credit in the same shape. gallery-page.js now builds that
   markup (.jrh-gallery-credits-heading/-rule/-credit-row/-label/-value,
   below) instead of moving the theme's own <p> elements in as-is. Bumped
   to 280px (from 260) -- the card's own padding needs a little more room
   than a bare text column did to avoid feeling cramped. */
.jrh-gallery-credits-col {
	flex: 0 0 280px;
	max-width: 280px;
	padding: 32px 26px;
	/* color-mix() rather than a second per-show custom property for the
	   RGB triplet -- var(--jrh-gallery-warm) is only ever set as a hex
	   string (see JRH_GALLERY_PAGE_SHOWS in jrh-gallery-page.php), and
	   color-mix() can fade that straight to a low-opacity border without
	   needing it broken into components. */
	border: 1px solid color-mix(in srgb, var(--jrh-gallery-warm) 40%, transparent);
	border-radius: 4px;
}

.jrh-gallery-credits-heading {
	color: var(--jrh-gallery-text) !important;
	font-family: Georgia, 'Times New Roman', serif;
	font-weight: 400;
	font-size: 15px;
	text-align: center;
	text-transform: uppercase;
	letter-spacing: 0.18em;
	margin: 0 0 18px;
}

.jrh-gallery-credits-rule {
	height: 1px;
	background: color-mix(in srgb, var(--jrh-gallery-warm) 40%, transparent);
	margin: 0 0 20px;
}

/* The second rule, right before the "Photos" row -- sits under the last
   credit row's own bottom margin (below), so it needs less top space of
   its own than the first rule does under the heading. */
.jrh-gallery-credits-rule--photos {
	margin-top: -2px;
}

.jrh-gallery-credit-row {
	margin-bottom: 18px;
}

.jrh-gallery-credit-row:last-child {
	margin-bottom: 0;
}

.jrh-gallery-credit-label {
	color: var(--jrh-gallery-warm) !important;
	text-transform: uppercase;
	font-size: 11px;
	letter-spacing: 0.1em;
	margin-bottom: 4px;
}

.jrh-gallery-credit-value {
	color: var(--jrh-gallery-text) !important;
	font-size: 16px;
	line-height: 1.4;
}

/* The "Photos" row -- smaller and quieter than the credit rows above it,
   same "smallest, most muted line on the page" role the theme's own
   last <p> used to carry before this layout existed. */
.jrh-gallery-credit-row--photos .jrh-gallery-credit-label {
	opacity: 0.85;
}

.jrh-gallery-credit-row--photos .jrh-gallery-credit-value {
	font-size: 12px;
	opacity: 0.65;
	line-height: 1.6;
}

.jrh-gallery-carousel-col {
	flex: 1 1 auto;
	min-width: 0;
}

@media (max-width: 780px) {
	.jrh-gallery-row {
		flex-direction: column;
		align-items: stretch;
	}

	/* Real bug, found live from Joshua's own report ("its missing all
	   the credits," screenshot from his phone): flex-basis applies
	   along whichever axis is the CURRENT main axis -- .jrh-gallery-
	   credits-col's own flex: 0 0 260px (set for the desktop row, where
	   it's a WIDTH) silently became a fixed 260px HEIGHT the moment
	   this same media query switches the row to flex-direction: column,
	   which was never reset here. With flex-shrink: 0 refusing to
	   shrink that box no matter how much taller its real content
	   (5 credit lines + the photo-credit line) actually was, the text
	   ended up rendered into a box far too short for it, overlapping
	   the photo below with only the last line's own tail end ("...
	   Bertrand") poking out from underneath. flex: auto here restores
	   natural, content-driven height instead of carrying over a value
	   that only ever made sense as a width. */
	.jrh-gallery-credits-col {
		flex: auto;
		/* Was max-width: none (full-bleed to the column's own edges) --
		   now that this column is a bordered card rather than plain text,
		   letting it stretch the full row width looked wrong (the border
		   spanning the entire screen); centered instead, capped at a
		   comfortable reading width like the venue line above it. */
		max-width: 420px;
		margin: 0 auto;
		order: 2;
	}

	.jrh-gallery-carousel-col {
		order: 1;
	}
}

/* --- Carousel ------------------------------------------------------------
   A crossfade slideshow built fresh in gallery-page.js from the hidden
   grid's own photos -- see buildCarousel there for the full behavior
   (auto-advance, pause on hover, arrow-key navigation while hovered,
   click-through to the theme's own lightbox). No arrows/counter/
   progress bar on the page itself -- see buildCarousel's own comment
   for why. */
.jrh-gallery-carousel {
	position: relative;
}

.jrh-gallery-carousel-frame {
	position: relative;
	width: 100%;
	aspect-ratio: 16 / 11;
	overflow: hidden;
	/* Was a faint white tint, sized for a `cover`-filled frame where the
	   background rarely showed through -- now that images use `contain`
	   (see .jrh-gallery-carousel-slide img below), any letterbox space
	   is real background, so it's matched to the page's own bg color to
	   blend in rather than read as a leftover gray box. */
	background: var(--jrh-gallery-bg);
	/* Was border-radius: 2px + a drop shadow -- Joshua, live, after
	   looking at francescazambello.com and elorenmeeker.com: "keep the
	   carousel, strip it down." Neither reference frames its photos at
	   all -- no card, no border, no shadow, sharp corners, photo just
	   sits directly on the page. */
}

.jrh-gallery-carousel-slide {
	position: absolute;
	inset: 0;
	display: block;
	opacity: 0;
	/* Was 900ms -- Joshua, live: "just a longer fade between photos I
	   think." */
	transition: opacity 2000ms ease;
	pointer-events: none;
}

.jrh-gallery-carousel-slide.is-active {
	opacity: 1;
	pointer-events: auto;
}

/* Both this and .jrh-gallery-carousel-slide-backdrop below are plain
   <div>s with a CSS background-image (gallery-page.js), not <img>
   elements -- Joshua, live, after TWO straight failed attempts tuning
   an <img>+object-fit version ("no", then "no. Still showing up
   incorrect."): confirmed live in devtools both times that an
   absolutely-positioned <img> using object-fit with percentage width/
   height was ignoring that box entirely and rendering at its own full
   intrinsic size instead (e.g. 962px tall inside a 441px-tall frame) --
   a known cross-browser <img>/object-fit interoperability trap, not
   something worth a third attempt at tuning. background-size on a div
   with no intrinsic size of its own doesn't have this failure mode. */
.jrh-gallery-carousel-slide-photo {
	position: absolute;
	inset: 0;
	z-index: 1;
	background-image: none; /* set inline, per-slide, in gallery-page.js */
	background-repeat: no-repeat;
	background-position: center;
	/* Was object-fit: contain -- Joshua, live: "some photos dont fit
	   right." Your production photos aren't all the same shape, and
	   `cover` (the original choice) crops whatever doesn't match this
	   frame's fixed 16:11 box -- fine for a photo close to that ratio,
	   but it was cutting real content out of ones that aren't. `contain`
	   always shows the whole photo, uncropped -- necessary and correct,
	   but on its own it leaves real empty space beside a portrait photo
	   in this landscape frame. Joshua, live, on that empty space: "the
	   issue is they are portrait while the rest are landscape, what
	   should we do?" -- filled by .jrh-gallery-carousel-slide-backdrop
	   below, a blurred copy of the SAME photo sitting behind this one,
	   rather than trying to solve it here. */
	background-size: contain;
}

/* The same photo as -slide-photo above, stretched to fully cover the
   frame and blurred/darkened, sitting behind it -- fills whatever
   space a portrait photo (or any photo whose own shape doesn't match
   this frame) leaves empty on either side, with more of that same
   photo instead of flat background color. blur() is applied well
   past the visible edge (scale(1.15) below) so the blur's own soft
   fringe never shows a hard boundary at the frame's edge. */
.jrh-gallery-carousel-slide-backdrop {
	position: absolute;
	inset: 0;
	background-image: none; /* set inline, per-slide, in gallery-page.js */
	background-repeat: no-repeat;
	background-position: center;
	background-size: cover;
	filter: blur(28px) brightness(0.45);
	transform: scale(1.15);
}

/* No .jrh-gallery-carousel-arrow/-counter/-progress rules here anymore
   -- gallery-page.js no longer creates any of those elements. Joshua,
   live: "keep the carousel, strip it down." Manual browsing still
   works -- clicking the photo opens the theme's own lightbox, and
   arrow keys step the carousel while it's hovered -- neither one
   needs any CSS of its own here since neither adds a visible element
   to the page. */
