/*
Theme Name: Trial & Error 2026
Description: 2026 Trial & Error DIY Music Lab site.
Author: Trial & Error
Version: 1.0
Text Domain: trial-error-2026
*/
/* ============================================================
   TRIAL AND ERROR — DIY MUSIC LAB — 02.10.2026

   Palette rule: exactly two colours site-wide.
     --trial-orange #EF3E25
     --trial-cyan   #AEC5D5
   Every element uses one of these as background and the other as
   foreground (--pane-bg / --accent), and the two swap together when
   the theme toggles (see ".theme-light" on .app).
   ============================================================ */

@font-face{
  font-family: 'PP Right Grotesk Spatial';
  src: url('/wp-content/themes/trial-error-2026/assets/Font/PPRightGrotesk-SpatialBlack.ttf') format('truetype');
  font-weight: 900;
  font-style: normal;
  font-display: swap;
}
@font-face{
  font-family: 'SuisseIntl';
  src: url('/wp-content/themes/trial-error-2026/assets/Font/SuisseIntl-Bold-WebM.woff') format('woff');
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

:root{
  /* design tokens mirrored from the main site's custom-style.css root, so
     both stylesheets share one naming convention — --trial-orange keeps
     its name from the live site, but --trial-purple is renamed to
     --trial-cyan here since this project's second colour is cyan, not
     purple. */
  --trial-orange: #EF3E25;
  --trial-cyan: #AEC5D5;
  --trial-white: #FFFFFF;
  --trial-gray: #C9C9C9;
  --trial-font: 'PP Right Grotesk Spatial', sans-serif;
  --trial-font2: 'SuisseIntl', sans-serif;
  --eyebrow-font-size: clamp(0.75rem, 3.6cqi, 1.2rem);

  --typo-h1: clamp(1.8rem, 3vw, 3rem);
  --typo-h2: clamp(1.4rem, 2vw, 1.5rem);
  --typo-h3: clamp(0.8rem, 1.25vw, 1.25rem);
  --typo-body: clamp(0.875rem, 1.5vw, 1.125rem);
  --typo-small: clamp(0.75rem, 1.2vw, 0.875rem);

  --height-header: 180px;
  --height-footer: calc(100vh / 20);
  --height-main: calc(100vh - var(--height-header) - var(--height-footer) - 1rem);

  /* base grid unit used for every margin/gutter on the page */
  --grid: 20px;

  /* shared sizing for every "chip" row on the page (infobar plates, nav
     buttons, footer links) so they're all the same height, with the same
     gap between them */
  --chip-h: 60px;
  --chip-gap: 13px;

  /* set by script.js to the exact visible height of the right column's
     scroll viewport, so a photo can be "exactly one screen tall" */
  --screen-h: 100vh;

  /* set by script.js to the logo header's rendered height (.left__header),
     so .left__scroll's top padding and .left__bg's bleed line up with it
     exactly. 0px fallback is only ever visible for a frame before JS runs. */
  --header-h: 0px;

  /* set by script.js to .right__footer's actual rendered height, so
     .left__footer (the sponsor-logo row, in a separate column with no
     shared Grid row to align it to) can match it exactly instead of
     assuming both are always --chip-h tall. --chip-h fallback is only
     ever visible for a frame before JS runs. */
  --footer-h: var(--chip-h);
}

*, *::before, *::after{ box-sizing: border-box; }

html, body{
  margin: 0;
  padding: 0;
  height: 100%;
  font-family: var(--trial-font2);
}

body{ overflow: hidden; }

h1,h2,h3{ margin: 0; }
button{ font-family: inherit; }
/* :visited too, not just the default state — without it, any link a
   browser considers "visited" (e.g. .home-link, which always points to
   index.html) falls back to the browser's own default visited-link
   colour instead of the theme's accent/pane-bg, which is what made it
   look wrong/"inverted" specifically on pages other than the one you
   started on. */
a, a:visited{ color: inherit; }
:focus-visible{ outline: 3px solid var(--trial-cyan); outline-offset: 2px; }

.visually-hidden{
  position: absolute; width: 1px; height: 1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;
}

/* ---------- App shell: two EQUAL, independently-scrolling columns ----------
   grid + gap + padding all use the same --grid unit, so the margin around
   the page and the gutter between columns match (20px / 20px). */

.app{
  height: 100vh;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--grid);
  padding: var(--grid);

  /* theme tokens — flipped as a pair by .theme-light */
  --pane-bg: var(--trial-orange);
  --accent:  var(--trial-cyan);
  background: var(--pane-bg);
}

.app.theme-light{
  --pane-bg: var(--trial-cyan);
  --accent:  var(--trial-orange);
}

.pane{ height: 100%; display: flex; flex-direction: column; min-width: 0; min-height: 0; }

.pane__scroll{
  flex: 1 1 auto;
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: thin;
}

/* ---------- logo: real text, same kontur-box + corner-handle treatment
   as the lineup/workshop title chips (.acts), sized up. Two stacked
   rows ("TRIAL AND" / "ERROR") at the same font-size, flush-left, tight
   padding/line-height so the box hugs the letterforms. Their shared
   border is collapsed by one border-width so the seam between the two
   rows reads as a single line, not a double-thick one. ---------- */

.logo{
  display: block;
  width: 100%;
  max-width: 540px;
  margin: 0;
}

.logo-row{
  position: relative;
  display: block;
  width: fit-content;
  max-width: 100%;
  font-family: var(--trial-font);
  text-transform: uppercase;
  background: var(--pane-bg);
  color: var(--accent);
  border: 2.5px solid var(--accent);
  /* em, not px: padding needs to scale with font-size (a clamp(), so it
     changes at every viewport width) or it goes proportionally thinner
     at larger sizes until the border starts crossing into the letters */
  padding: 0 0.1em;
  /* cqi, not vw: a vw-based size was keyed to the full viewport width,
     but this box is only ~half the viewport (two-column layout), so
     "TRIAL AND" could need more width than the box had and it would
     overflow past the border (nowrap can't wrap it onto a 2nd line).
     cqi is relative to the shared .left__top container's width (see
     above), so the text is always sized to what the box can actually
     hold. The coefficient keeps ~8% safety margin for the tightest
     column width (~400px, right at the ~860px breakpoint); the 4.8rem
     cap is reached once that container hits ~540px wide — the same
     width .eyebrow's own cap lines up with. */
  font-size: clamp(2.2rem, 14.2cqi, 4.8rem);
  line-height: 0.85;
  white-space: nowrap;
}

.logo-row + .logo-row{ margin-top: -2.5px; }

/* ================= LEFT PANE ================= */

.left{ background: var(--pane-bg); position: relative; }

/* fixed logo header — sits ABOVE .left__lower below (see z-index there),
   overlapping its top edge, with its own opaque background so whatever
   scrolls up underneath (the infobar, now inside .left__scroll) is hidden
   behind it rather than showing through. position:absolute takes it out
   of .pane.left's flex flow, so .left__lower (flex:1) naturally starts
   from the very top (y=0) and extends the full height behind it — script.js
   measures this header's rendered height into --header-h, which
   .left__scroll's top padding and .left__bg's bleed below both use to
   stay lined up with it.
   left/width match .left__bg's own horizontal bleed (viewport edge on the
   left, start of the 2nd column on the right) — otherwise the header's
   opaque fill stops exactly at the pane's own edges while .left__bg bleeds
   20px further each way, leaving its artwork peeking out at the very top
   corners. padding-left/right of the same amount pushes the *content box*
   back in by exactly that much, so .left__top inside renders at the same
   position as before — only the background extends, nothing moves. */
.left__header{
  position: absolute;
  top: 0;
  left: calc(-1 * var(--grid));
  width: calc(100% + var(--grid) * 2);
  padding: 0 var(--grid);
  z-index: 2;
  background: var(--pane-bg);
}

/* key visual: lives in the lower block only (below the logo header), but
   bleeds sideways past the pane's own edges — all the way to the browser
   edge on the left, and up to the start of the right column — so its
   horizontal reach isn't just "one column wide", it's "one column plus
   the outer margin and the gutter". Also nudged up and scaled up a touch
   so its own top artwork reads as sitting roughly mid-height through the
   date/location plates, not just grazing their bottom edge. */
.left__lower{
  position: relative;
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

.left__bg{
  /* explicit width/height, not left+right/top+bottom with width:auto —
     <img> is a replaced element, so "auto" would fall back to intrinsic
     sizing instead of stretching to fill the offsets. The extra height
     (vs. the box it sits in) combined with object-fit:cover is what
     "upscales" the artwork — no transform:scale, so the left/right edges
     stay exactly on the intended bleed (viewport edge / column start).
     top/height are offset by --header-h: .left__lower now starts at the
     very top of the pane (behind the fixed header), but this artwork
     should still only bleed up starting from right under the *header*,
     not from the true page top — so its top is pushed down by
     --header-h, and height is reduced by the same amount to keep the
     bottom edge exactly where it was (flush with .left__lower's own
     bottom, unaffected by any of this). */
  position: absolute;
  left: calc(-1 * var(--grid));
  top: calc(var(--header-h) - var(--grid) * 1.2);
  width: calc(100% + var(--grid) * 2);
  height: calc(100% - var(--header-h) + var(--grid) * 1.2);
  object-fit: cover;
  object-position: top center;
  opacity: 1;
  pointer-events: none;
  z-index: 0;
}

/* ---------- Privacy/Imprint: plain single-column page ----------
   header.php renders a completely different, flat markup for these two
   pages instead of the two-pane app shell (see $trial_uses_wp_content
   there) - no scroll panes, no JS, so none of the width/z-index/overflow
   fights that structure required. .app still supplies the page's padding
   and colour vars (--pane-bg/--accent, flipped by .theme-light exactly
   like the two-pane pages), .simple-page only overrides the parts that
   made it a two-column grid. Leaf classes (.logo, .infobar, .copy-block,
   .right__nav, .right__footer, .left__footer) are the same ones the
   two-pane layout uses, so the button/chip styling matches without
   duplicating it. */
.app.simple-page{
  display: flex;
  flex-direction: column;
  overflow: hidden;
  gap: var(--grid);
}

/* logo+nav / sponsor-logos+footer-nav each keep the exact two-column
   split .app itself used to give the whole page, so the nav and footer
   stay in the same top-right/bottom-right spot they always were, and
   never move - exactly like the other pages' fixed nav/footer, just not
   split into two columns of content either side of them. */
.simple-page__row{
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--grid);
  align-items: start;
  flex: 0 0 auto;
}

/* the only part of the page that actually scrolls - .infobar/.copy-block
   disappear under .simple-page__row--top's edge exactly like the other
   pages' left column tucks under .left__header, just without needing
   position:absolute + a JS-measured height, since here the header is a
   normal (non-overlapping) flex sibling rather than sitting on top of
   this box. */
.simple-page__scroll{
  flex: 1 1 auto;
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: thin;
}

/* full page width instead of one column's worth */
.simple-page .infobar,
.simple-page .copy-block{
  width: 100%;
}

.simple-page .copy-block{ margin-top: 0; padding-bottom: var(--grid); }

.simple-page .wp-block-heading{ padding-bottom: 1.5rem; }

/* height:auto here broke .left__footer img's height:70% (a percentage
   height needs a definite-height ancestor, which "auto" isn't) - the
   images fell back to their raw uploaded pixel size instead of being
   constrained to the row, unlike the two-pane pages (which get a real
   fixed height from --footer-h). A real height fixes it. */
.simple-page .left__footer{
  height: var(--chip-h);
}

/* query container for both .eyebrow and .logo-row's font-sizes below —
   ties both type sizes to this shared box's own rendered width (instead
   of the viewport), so the eyebrow scales in lockstep with the logo
   underneath it rather than against a different, unrelated reference */
.left__top{
  position: relative;
  z-index: 1;
  padding: 0 var(--grid) 21px;
  container-type: inline-size;
}

/* home links back to index.html: one wrapping just "DIY Music Lab" inside
   .eyebrow, one wrapping just each .logo-row's own text ("TRIAL AND" /
   "ERROR"). Each <a> wraps only the bare text node, not the bordered/
   padded box around it (.eyebrow / .logo-row keep their own border+
   background) and not the .lc-handle corner squares (separate, unwrapped
   siblings) — so the clickable area is exactly the letters, nothing else. */
.home-link{ text-decoration: none; }

.eyebrow{
  position: relative;
  z-index: 0; /* behind the logo, so the logo's own corner tick never gets covered */
  display: inline-block;
  font-family: var(--trial-font);
  font-weight: 900;
  font-style: italic;
  /* 3.6cqi lands on exactly 1.2rem at a 540px-wide container — the same
     width where .logo-row's own clamp below hits its 4.8rem cap — so the
     two type sizes reach their ceilings together instead of drifting
     apart at different widths */
  font-size: clamp(0.75rem, 3.6cqi, 1.2rem);
  letter-spacing: 0.02em;
  /* var(--accent)/var(--pane-bg), not fixed trial-orange/trial-cyan —
     same pairing the nav buttons use, so .eyebrow swaps with the theme
     as the *inverse* of .left's own pane-bg colour on every page: cyan
     bg/orange text on Workshops (pane-bg is orange there, accent cyan),
     orange bg/cyan text on Concert (pane-bg cyan, accent orange) —
     instead of a fixed pair that only matched one theme. */
  background: var(--accent);
  color: var(--pane-bg);
  border: 2px solid var(--accent);
  padding: 1px 7px 3px;
  margin-bottom: -6px;
  margin-left: 10px;
  text-transform: uppercase;
}

.logo{ position: relative; z-index: 1; }

/* now lives inside .left__scroll (see HTML) instead of as its own fixed
   sibling above it, so it scrolls together with the tab content and
   tucks up behind .left__header as you scroll — .left__scroll's own
   padding already provides the left/right/top gutter, so no margin of
   its own here beyond separating it from the content below */
.infobar{
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--chip-gap);
  margin-bottom: var(--grid);
}

.infobar h3{
  /* same type treatment as the right-column nav buttons, so both rows of
     chips read as the same size/weight (margin reset already covered by
     the global h1,h2,h3{ margin:0 } rule) */
  font-family: var(--trial-font);
  font-size: clamp(1rem, 2vw, 1.5rem);
  letter-spacing: 0.03em;
  text-transform: uppercase;
  background: var(--accent);
  color: var(--pane-bg);
  height: var(--chip-h);
  padding: 20px 14px;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  width: 100%;
}

/* "MusikZentrum Hannover" links to Google Maps directions (WP-EDITABLE:
   swap the destination address in the href if the venue ever moves) —
   no decoration at all, in any state, so it reads as plain chip text
   rather than an underlined/coloured link; colour already comes for free
   from the global a{color:inherit}. */
.infobar a,
.infobar a:hover,
.infobar a:visited,
.infobar a:focus{ text-decoration: none; }

/* no background here on purpose — the key visual (.left__bg) sits behind
   this scroll area and shows through behind the list content. Top padding
   is --header-h (not --grid, like the other 3 sides) so the infobar — now
   the first thing inside this scrolling element — starts right below the
   fixed .left__header on load, then scrolls up underneath it as the user
   scrolls, since the scrollable area itself extends all the way behind
   the header (z-index 2, above this element's z-index 1). */
/* container-type:inline-size here (not on #left-content) — .infobar and
   #left-content are both direct children of .left__scroll with no extra
   padding of their own, so cqi units used inside either one resolve
   against this exact same width, guaranteeing they can be sized to
   match each other (see .lc--lead .acts below) instead of just
   coincidentally lining up. */
.left__scroll{ padding: var(--header-h) var(--grid) var(--grid); position: relative; z-index: 1; container-type: inline-size; }

/* height: var(--footer-h) (JS-measured from .right__footer's actual
   rendered height — see script.js), not var(--chip-h) directly:
   .right__footer a only has a min-height of --chip-h and can render
   taller depending on font-size/padding, and since .left__footer lives
   in a separate column with no shared Grid row to align it to, matching
   --chip-h itself isn't enough to keep the two rows' vertical centres in
   sync — matching the *other row's actual height* is. */
.left__footer{
  position: relative;
  z-index: 1;
  flex: 0 0 auto;
  height: var(--footer-h);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--grid);
  /* pane-bg, not accent: the sponsor logos are pre-coloured (like the
     main wordmark) to sit directly on the pane's base colour — an
     accent-coloured strip here would make the accent-ink logos vanish */
  background: var(--pane-bg);
  padding: 1rem var(--grid) 0;
}

/* each logo is now a link (<a href="…"><img></a> — see sponsorFooterHTML()
   in script.js), so the <a> is the flex item here, not the <img> directly.
   height:100% on it gives the <img> below a definite height to compute
   its own percentage against — without it, the <img>'s height:55% would
   resolve against nothing (an auto-height parent) and just collapse. */
.left__footer a{ display: block; height: 100%; position: relative; }

.left__footer img{
  display: block;
  /* percentage, not 100%: fills most of the row's height (now --footer-h,
     matched to .right__footer's actual height) while keeping a little
     breathing room top/bottom, at any height that ends up being. */
  height: 70%;
  width: auto;
  max-width: 100%;
  /* kept in the DOM (not display:none) purely for its layout box - it
     sets the <a>'s intrinsic width/height and its alt text still reaches
     screen readers - opacity:0 just hides the actual pixels, since the
     ::after below repaints the same shape as a flat --accent silhouette. */
  opacity: 0;
}

/* recolours every partner logo to var(--accent) - the theme's own
   "opposite of the current background" token, already flipped by
   .theme-light along with --pane-bg, so this needs no red/cyan logic of
   its own. mask-image (not filter) reads only the logo's alpha shape, so
   it comes out an exact flat colour regardless of the source image's own
   colours - --logo-url is set inline per logo (see header.php/script.js,
   since CSS has no way to reuse an <img>'s own src as a mask). */
.left__footer a::after{
  content: '';
  position: absolute;
  inset: 0;
  background-color: var(--accent);
  -webkit-mask-image: var(--logo-url);
  mask-image: var(--logo-url);
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: center;
  mask-position: center;
  -webkit-mask-size: contain;
  mask-size: contain;
}

#left-content{ position: relative; z-index: 1; }

/* Desktop only. The CSS-only version of this (stretching #left-content
   via nested flex:1 percentage heights) silently failed to actually
   resolve in the browser, so this height instead comes from JS —
   syncLineupHeight() in script.js measures the real remaining pixel
   space in #left-scroll and sets it directly as an inline style on
   .lineup, sidestepping the percentage-height chain entirely. This rule
   just turns .lineup into a flex column so that JS-set height can
   actually spread the 3 cards apart via space-between. Mobile is
   untouched — syncLineupHeight() clears the inline height below 861px,
   so mobile keeps its plain fixed li+li gap (see @media max-width:860px
   below) exactly as it already was. */
@media (min-width: 861px){
  .lineup{ display: flex; flex-direction: column; justify-content: space-between; }
}

/* ================= LINEUP CARDS (Concert tab) =================
   Recreates the two-line "stairstep" wordmark cards from the client's
   artwork: two bordered name chips (blue bg / red text, or the reverse
   in the other theme) offset diagonally, with small corner handles,
   plus a small place tag underneath. */

.lineup{ position: relative; list-style: none; margin: 0; padding: 0; }
/* same li padding mechanism as .workshops li — but NOT the same overlap
   magnitude: workshops' li+li pull-up assumed roughly evenly-sized items,
   while the lineup's own lead card (.lc--lead, bigger font) plus its
   place tag (hanging below the card via the base .lc-place's own
   position:absolute/margin-bottom, unlike workshops' .lc-place override)
   is taller than a plain card — the same -1.5*grid pull collided with
   that hanging tag, spilling it into the next card. 0 here (no overlap)
   keeps each lineup card a clearly separate section instead. */
.lineup li{ padding: var(--grid) 0; }
.lineup li:first-child{ border-top: none; padding-top: 2px; }
/* fixed gap — mobile only (see @media max-width:860px below). Desktop
   relies purely on the min-width:861px block above (justify-content:
   space-between on the flex .lineup) instead of a margin, since a fixed
   margin can't adapt to however much vertical space is actually left in
   a given viewport height the way space-between does. */
.lineup .lc{ margin-bottom: 2px; }

.lc{ position: relative; }

.acts{
  position: relative;
  display: inline-block;
  font-family: var(--trial-font);
  text-transform: uppercase;
  background: var(--pane-bg);
  color: var(--accent);
  border: 2px solid var(--accent);
  padding: 3px 12px;
  font-size: clamp(2.2rem, 4vw, 5rem);
  line-height: 0.9;
  white-space: nowrap;
  max-width: 100%;
  overflow: visible;
}

.acts--2{
  display: table;
  margin-top: -2px;
  margin-left: 16%;
}

/* lead card only (Kat Davids & AVHS): bigger indent than the base 16% */
.lc--lead .acts--2{ margin-left: 45%; }
.lc--lead .acts--1{ margin-left: -10%; }

/* headliner (Kat Davids & AVHS): sized up so it reads as the lead act,
   and specifically sized to fill the same width as .infobar — cqi, not
   vw, so it's tied to .left__scroll's own actual rendered width (see
   container-type there) instead of the full viewport, the same fix the
   logo needed earlier for the same reason: a vw-based size doesn't know
   this box is only as wide as one column, so it can't reliably fill (or
   avoid overflowing) that column's actual width. 15cqi is calibrated so
   "KAT DAVIDS" lands close to .infobar's full width at this container's
   size; I can't verify the exact pixel match without live rendering, so
   nudge this number if it doesn't quite reach (or overshoots) the edge. */
.lineup .lc--lead .acts{ font-size: clamp(1rem, 11cqi, 6.5rem); }

/* horizontally centres the whole lead card within .left__scroll — just
   the .lc container itself (width:fit-content so margin-inline:auto has
   room to centre it), not touching any of .acts/.acts--2's own margins
   or padding, or the <li>'s */
.lc--lead{
  width: fit-content;
  margin-inline: auto;
}

/* Age Eternal & Luc Amaden: shifted right, matching the client's
   reference layout (Kat Davids and Evo Ethel stay flush left). Base
   (non-lineup) rule kept for anything else still relying on it. */
.lc--shift{ margin-left: 18%; }

/* Age Eternal & Luc Amaden: back to the Workshops-style mechanism (each
   line its own block/fit-content/wrap box, individually right-aligned to
   #left-content's right edge via margin-left:auto) — the fit-content
   *card* wrapper (previous approach) couldn't reliably size itself once
   acts--1's own percentage margin pushed it wider than the box's own
   shrink-to-fit width, which is what was both cropping the text and
   leaving the card not flush with .infobar. white-space:normal lets a
   long line wrap instead of overflowing; plain --typo-h1 (no cqi/vw
   override) is the same font-size Workshops uses without ever cropping. */
.lineup .lc--shift{ margin-left: 0; }
.lineup .lc--shift .acts{
  display: block;
  width: fit-content;
  max-width: 100%;
  white-space: normal;
  margin-left: auto;
}
/* stairstep offset (acts--1 sits left of acts--2, matching Kat Davids/
   Evo Ethel's own line1/line2 relationship) as an em value, not a %: an
   em is relative to the line's own font-size only, not to a container
   width — sidesteps the exact drift/overflow issues above, since both
   lines share the same font-size and the gap between them can't stretch
   independently of it. Estimate — nudge if it doesn't match the
   reference layout closely enough. */
.lineup .lc--shift .acts--1{ margin-right: 1.5em; }

/* Evo Ethel & Momo: mirror of the above, but left-aligned to
   #left-content's (and .infobar's) LEFT edge instead — margin-right:auto
   pushes each line flush left rather than margin-left:auto pushing it
   flush right. Overrides the base .acts--2{margin-left:16%} percentage
   stairstep with the same em-based one used above, for the same reason
   (consistent regardless of container width). */
.lineup .lc--third .acts{
  display: block;
  width: fit-content;
  max-width: 100%;
  white-space: normal;
  margin-right: auto;
}
.lineup .lc--third .acts--2{ margin-left: 1em; }

.lc-handle{
  position: absolute;
  z-index: 5; /* always above any overlapping chip/box, even the glued place tag */
  /* 10px total, border still 2px (unchanged) — bigger than the previous
     8px, but only the visible "inner" square grows (8 - 2*2 = 4px of
     inner fill before, 10 - 2*2 = 6px now); the border thickness itself
     is untouched. Even number, same reasoning as before: splits evenly
     across the 2px border, and the -5px corner offset below (half of
     10) centres it exactly on the corner. */
  width: 10px;
  height: 10px;
  background: var(--pane-bg);
  border: 2px solid var(--accent);
  pointer-events: none;
}
.lc-handle--tl{ top: -5px; left: -5px; }
.lc-handle--tr{ top: -5px; right: -5px; }
.lc-handle--bl{ bottom: -5px; left: -5px; }
.lc-handle--br{ bottom: -5px; right: -5px; }

/* edge-midpoint handles (matching the client's own reference design,
   which uses the classic 8-point "selection" look, not just 4 corners)
   — centred on each edge via left/top:50% + a transform, same -5px
   (half the 10px size) offset on the perpendicular axis as the corners. */
.lc-handle--t{ top: -5px; left: 50%; transform: translateX(-50%); }
.lc-handle--b{ bottom: -5px; left: 50%; transform: translateX(-50%); }
.lc-handle--l{ left: -5px; top: 50%; transform: translateY(-50%); }
.lc-handle--r{ right: -5px; top: 50%; transform: translateY(-50%); }

/* glued directly to the bottom edge of the line-2 chip (AVHS / Luc Amaden /
   Momo), overlapping its border — not floating below with a gap */
.lc-place{
  position: absolute;
  left: 15px;
  bottom: 0;
  margin-bottom: -18px;
  transform: translateY(50%);
  z-index: 2;
  display: inline-block;
  white-space: nowrap;
  font-style: italic;
  font-weight: 800;
  /* var(--typo-h3), not a fixed 0.76rem — matches the responsive size
     .workshops .lc-place was upgraded to; this base rule is what
     Concert's lineup-card place tags still use, unscoped, so they'd
     stayed on the old fixed size */
  font-size: var(--typo-h3);
  letter-spacing: 0.04em;
  color: var(--pane-bg);
  background: var(--accent);
  padding: 3px 10px;
  margin-right: -2px;
}

/* concert.html specifically — -12px here instead of the base rule's
   -18px, scoped to .lineup rather than changing the shared base rule.
   white-space:normal + max-width:100% (overriding the base rule's
   nowrap): .lc-place is absolutely positioned, so nowrap text isn't
   constrained by its container's width at all — it just extends off the
   edge instead of shrinking, which is exactly what "HANNOVER / BRISTOL"
   started doing at a narrower viewport once the font-size above went up
   to --typo-h2 (bigger than the old --typo-h3). max-width:100% caps it
   to .acts--2's own width (its positioning container), so it wraps onto
   a second line instead of overflowing past the pane. */
.lineup .lc-place{
  margin-bottom: -12px;
  font-size: var(--typo-h3);
  white-space: normal;
  max-width: 100%;
  margin-bottom: -11px;
}

/* lead card only (Kat Davids & AVHS): .lc-place aligns to the right side
   of .acts--2 instead of the base rule's left:15px — same 15px inset,
   just mirrored to the other side, not a flush/border-aligned 0.
   margin-right:0 cancels the base rule's own margin-right:-2px above,
   which would otherwise stack with this right:15px and pull it in an
   extra 2px past the intended inset. */
.lc--lead .lc-place{
  left: auto;
  right: 15px;
  margin-right: 0;
  margin-bottom: -9px !important;
}

/* 2nd/3rd lineup cards: own classes (not just .lc--shift/.lc--third
   .lc-place descendant selectors) so their "left" inset can be tuned
   independently of each other and of the base rule's left:15px. em, not
   %/px — same reasoning as the acts--1/acts--2 em-based offset above:
   a % here is relative to .acts--2's own (changing, since it wraps)
   width, a different reference than the acts text's em-based offset, so
   the two drift apart as the viewport changes instead of moving
   together. em ties this to the same font-size the surrounding text
   already scales with. Estimate — nudge live if the inset looks off. */
/* shift card: right-aligned instead of left, same inset (0.9em) just
   mirrored — matching how .lc--lead .lc-place flips to the right side
   above. margin-right:0 cancels the base rule's own margin-right:-2px,
   which would otherwise stack with this right offset. */
.lc-place--shift{
  left: auto;
  right: 0.9em;
  margin-right: 0;
}
/* third card: allowed to extend past .acts--2's own width instead of
   wrapping — overrides the .lineup .lc-place white-space:normal/
   max-width:100% above (added for the shift card's overflow, now
   resolved differently by right-aligning it instead), which isn't
   needed here and was forcing "Bristol / Hannover" onto 2 lines. */
.lc-place--third{ left: 0.9em; }
/* .lineup .lc-place--third (not just .lc-place--third) so this matches
   .lineup .lc-place's own specificity (two classes) and actually wins —
   a single-class selector was silently losing to that rule's
   white-space:normal/max-width:100%, which is why it kept wrapping. */
.lineup .lc-place--third{
  white-space: nowrap;
  max-width: none;
}

.workshops{ position: relative; list-style: none; margin: 0 0 var(--grid); padding: 0; }
.workshops li{ padding: var(--grid) 0;}
.workshops li:first-child{ border-top: none; padding-top: 2px; }
/* pulls every item after the first up, overlapping into the previous
   item's bottom padding instead of the two just sitting end-to-end —
   li+li (not a plain li rule) so the very first item isn't pulled up
   against the infobar above it. Increase the magnitude for more overlap,
   decrease (toward 0) for less. */
.workshops li + li{ margin-top: calc(-1.5 * var(--grid)); }

/* column, not row: .time stacks above .tag-note now, not side-by-side.
   align-items is the horizontal (cross-axis) control in column direction —
   flex-start (left) by default, flipped to flex-end (right) by the
   --shift modifier, same true-right-alignment idea as everywhere else on
   this list. (justify-content would be wrong here — in column direction
   that's the *vertical* axis, not horizontal.) */
.workshops .time-row{ display: flex; flex-direction: column; align-items: flex-start; }
.workshops .time-row--shift{ align-items: flex-end; }

/* margin, not padding on the row: a flex item's margin on its "outer"
   side is what actually creates visible space between it and the row's
   edge — the trailing side (right for flex-start, left for flex-end) is
   never visible regardless, so one symmetric rule works for both
   alignments: flex-start rows show the margin-left gap, flex-end rows
   show the margin-right gap, automatically, with no --shift-specific
   override needed. .desc-note gets the same margin here too — but NOT
   .workshops .lc-place: it's position:absolute; right:0, so margin-right
   there doesn't add cosmetic space, it actually shifts it left, off its
   flush-right alignment under .desc-note. */
.workshops .time, .workshops .tag-note, .workshops .desc-note{ margin-left: 1rem; margin-right: 1rem; }

/* same badge styling as .eyebrow (border, font, weight, italic, letter-
   spacing, padding, text-transform) — colours stay theme-relative
   (var(--accent)/var(--pane-bg)) rather than eyebrow's fixed trial-cyan/
   trial-orange, matching how every other chip on the page keeps colours
   theme-relative; .eyebrow and the T&E 2025 archive link are the site's
   only two deliberate fixed-colour exceptions. */
.time, .tag-note{
  display: inline-block;
  font-family: var(--trial-font);
  font-weight: 900;
  font-style: italic;
  font-size: var(--typo-h2);
  letter-spacing: 0.02em;
  background: var(--accent);
  color: var(--pane-bg);
  border: 2px solid var(--accent);
  padding: 1px 7px 3px;
  text-transform: uppercase;
}

/* workshop description line — same .time/.tag-note badge exactly, just at
   --typo-h3 instead. Sits between the title (.acts) and the host tag,
   which now glues to this (see .workshops .lc-place / actsLines() +
   descLines() in script.js) instead of to the title — position:relative
   is what lets .lc-place anchor to it. */
.desc-note{
  position: relative;
  display: inline-block;
  font-family: var(--trial-font);
  font-weight: 900;
  font-style: italic;
  font-size: var(--typo-h3);
  letter-spacing: 0.02em;
  background: var(--accent);
  color: var(--pane-bg);
  border: 2px solid var(--accent);
  padding: 1px 7px 3px;
  text-transform: uppercase;
}

/* workshop title: exactly the same kontur-box + corner-handle chip as the
   Concert tab's lineup cards (.acts) — border, background, padding,
   font-size, corner handles, all inherited as-is, no override. The host
   ("w/ AVHS") is glued to its bottom edge via .lc-place, exactly like the
   place tag on the Concert tab. */
.workshops .lc{ margin-bottom: 2px; }
/* same true right-alignment as .time-row--shift above, scoped to
   .workshops so it doesn't touch .lc--shift on the Concert tab's lineup
   cards — .lc--shift itself resets the shared 18% nudge (see .lc--shift
   globally below), then margin-left:auto pushes the fit-content title
   (.acts, width:fit-content — see below) flush to the true right edge */
.workshops .lc--shift{ margin-left: 0; }
.workshops .lc--shift .acts, .workshops .lc--shift .desc-note{ margin-left: auto; }
/* differences from .acts on the Concert tab: workshop titles run much
   longer than an act name and have to wrap across multiple lines, which
   needs block (not inline-block — margin-left:auto above only works on a
   block box) + fit-content + white-space:normal. font-size is pinned
   explicitly to var(--typo-h1) here rather than just inheriting the base
   .acts rule — the base rule's own font-size is also what Concert's 2nd/
   3rd acts (lc--shift/lc--third, no font-size override of their own on
   desktop) rely on to keep their current sizing, so Workshops needs its
   own explicit value instead of assuming the base stays in sync with it. */
.workshops .acts{
  display: block;
  width: fit-content;
  max-width: 100%;
  white-space: normal;
  font-size: var(--typo-h1);
}
/* a title split across multiple .acts chips (see actsLines() in script.js)
   stacks directly, border touching border — collapse the shared seam by
   one border-width so it reads as one line, not a double-thick one,
   matching how the logo's two rows collapse their own shared border */
.workshops .acts + .acts{ margin-top: -2px; }

/* same block/fit-content/wrap treatment as .acts above, for the same
   reason (right-alignment via margin-left:auto + long text needs to
   wrap). 6px gap below the title box(es); a second .desc-note from the
   same description (split on </br> by descLines() in script.js) instead
   collapses its border against the one above it, same as .acts + .acts. */
.workshops .desc-note{
  display: block;
  width: fit-content;
  max-width: 100%;
  white-space: normal;
}
.workshops .desc-note + .desc-note{ margin-top: -2px; }

/* .lc-place, scoped here since in the workshops context it now glues to
   .desc-note (see script.js) rather than to a lineup card's line-2 (.acts)
   — left:14px there doesn't apply to the right-alignment or the bigger
   .desc-note padding here. right:0 glues it to the right edge instead of
   the left; font-size + border/colours now match .acts exactly (pane-bg
   fill, accent text + border) rather than lc-place's own default
   (accent fill, pane-bg text, no border), overriding just those. */
/* left:0 by default — matches the 2nd/4th (flush-left) workshop items,
   where .desc-note itself sits flush left, so the host tag glues under
   its left edge. .lc--shift (1st/3rd, right-aligned) flips this to the
   right edge instead, right below. */
.workshops .lc-place{
  /* -2px, not 0: an absolutely positioned element's left:0 aligns to its
     containing block's *padding* edge, not its border edge — .desc-note
     has a 2px border, so left:0 would land .lc-place's own left border
     2px inside .desc-note's, instead of flush with it. -2px (its exact
     border-width) cancels that out so the two borders actually align. */
  left: -2px;
  right: auto;
  /* no fixed px/em offset at all, unlike the base rule (bottom:0 +
     margin-bottom, straddling the border with overlap) — top:100% alone
     is a pure percentage of .desc-note's own height, placing .lc-place's
     top edge exactly at its bottom edge: flush underneath, no gap, no
     overlap. Being a percentage (not a fixed number), that relationship
     holds at any viewport width instead of drifting. */
  bottom: auto;
  top: 100%;
  transform: none;
  font-size: var(--typo-h3);
  background: var(--pane-bg);
  color: var(--accent);
  border: 2px solid var(--accent);
}

/* 1st/3rd (right-aligned) workshop items: .desc-note itself sits flush
   right (see .workshops .lc--shift .desc-note's margin-left:auto), so
   the host tag needs to glue under its right edge instead of the left.
   margin-right:0 cancels the base .lc-place's own margin-right:-2px
   (tuned for its original Concert-tab usage) — left in place, it stacks
   with this rule's right:-2px border compensation, compounding to a
   ~4px shift instead of the intended 2px. */
.workshops .lc--shift .lc-place{
  left: auto;
  right: -2px;
  margin-right: 0;
}

/* same kontur-box + corner-handle chip as .note-flag/.acts (border,
   background, padding, corner handles via lcHandles() — added at the end
   of the block in script.js), full width matching .infobar, instead of
   its previous plain/unbordered paragraph styling. */
.copy-block{
  position: relative;
  display: block;
  width: 100%;
  margin-top: 2rem;
  font-family: var(--trial-font);
  text-transform: uppercase;
  background: var(--pane-bg);
  color: var(--accent);
  border: 2px solid var(--accent);
  padding: 3px 12px;
  font-size: var(--typo-h3);
  line-height: 1.22;
  white-space: normal;
  overflow: visible;
}
.copy-block p{ margin: 0 0 var(--grid); font-family: var(--trial-font2); }

/* only real WP content (Privacy/Imprint) renders Gutenberg's own
   .wp-block-paragraph class - scoping to it instead of ".copy-block p"
   generally keeps Tickets/About's own hardcoded copy (still uppercase)
   unaffected. */
.wp-block-paragraph{ text-transform: none; }
.copy-block strong{ font-weight: 800; font-family: var(--trial-font); }

/* same kontur-box + corner-handle chip as .acts (border, background,
   padding, corner handles via lcHandles() in script.js — see .note-flag
   usages there), just at --typo-h3 instead of --typo-h1, and full width
   (matching .infobar) with wrapping turned back on since this holds a
   full sentence, not a short title. */
.note-flag{
  position: relative;
  display: block;
  width: 100%;
  margin-top: 2rem;
  font-family: var(--trial-font);
  text-transform: uppercase;
  background: var(--pane-bg);
  color: var(--accent);
  border: 2px solid var(--accent);
  padding: 3px 12px;
  font-size: var(--typo-h3);
  line-height: 1.22;
  white-space: normal;
  overflow: visible;
}

.ticket-cta{
  display: inline-flex;
  align-items: center;
  gap: 10px;
  margin-top: 4px;
  padding: 13px 20px;
  background: var(--accent);
  color: var(--pane-bg);
  font-weight: 800;
  font-size: 0.9rem;
  letter-spacing: 0.02em;
  text-decoration: none;
  text-transform: uppercase;
  border: 1.5px solid var(--accent);
  transition: opacity 0.15s ease;
}
.ticket-cta:hover{ opacity: 0.8; }

.price-grid{ display: grid; gap: 10px; margin: var(--grid) 0; max-width: 42ch; }
.price-row{
  display: flex; justify-content: space-between; gap: 12px;
  padding: 10px 12px;
  border: 1.5px solid var(--accent);
  color: var(--accent);
  font-weight: 700;
  font-size: 0.84rem;
}
.price-row b{ font-weight: 800; }

/* sponsor logos now live in the persistent .left__footer (see below the
   RIGHT PANE rules) instead of scrolling with the tab content. */

/* ================= RIGHT PANE ================= */

.right{ position: relative; }

.right__nav{ display: grid; grid-template-columns: repeat(4, 1fr); flex: 0 0 auto; gap: 8px; }

/* top nav + bottom footer links share this one rule on purpose — a single
   source of truth so a change to one can't drift out of sync with the
   other again. The archive link's colour override below is the only
   place they intentionally differ. */
.right__nav a, .right__footer a{
  /* default (inactive): background = accent, text = pane-bg — this pairing
     gives blue/orange on the Workshops (red) theme and orange/blue on the
     Concert (light) theme, matching each page's requested button colours */
  background: var(--accent);
  color: var(--pane-bg);
  font-family: var(--trial-font);
  font-size: clamp(0.6rem, 1.25vw, 1.25rem);
  letter-spacing: 0.03em;
  text-transform: uppercase;
  text-decoration: none;
  /* min-width:0 overrides the grid item default (min-width:auto, i.e. its
     text's min-content width) — without it, a label like "Workshops" at
     this bigger size forces its 1fr column wider than its actual share,
     pushing the whole row past the container. min-height (not a fixed
     height) lets the button grow if a label wraps to 2 lines instead of
     clipping it against a fixed height. */
  min-width: 0;
  min-height: var(--chip-h);
  padding: 6px;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  line-height: 1.15;
  cursor: pointer;
  position: relative;
  /* transparent, not no border at all: reserving the same 2px always
     (just invisible at rest) means the hover/current state below only
     has to change its colour, not add it — so nothing shifts size/
     position when the border appears. */
  border: 2px solid transparent;
  transition: opacity 0.15s ease, background-color 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}

/* corner-handle squares (see lcHandles() in script.js, injected into
   every one of these links once on load) — the same kontur-box
   decoration .acts/.archive use elsewhere, hidden at rest and only
   faded in on hover/current below, rather than always visible like
   .archive's own hardcoded ones. */
.right__nav a .lc-handle, .right__footer a .lc-handle{
  opacity: 0;
  transition: opacity 0.15s ease;
}

/* hover AND the current page's own link share this one treatment now —
   border + corner handles + inverted colours (background/text swapped
   from the default above), replacing the old plain opacity-dim hover
   and the old fixed white-background "current page" exception. Set per-
   page as a plain aria-current="page" attribute on that page's own <a>
   (see index.html/concert.html/etc.), not toggled by script — each page
   only ever has one "current" link. */
.right__nav a:hover, .right__footer a:hover,
.right__nav a[aria-current="page"], .right__footer a[aria-current="page"]{
  background: var(--pane-bg);
  color: var(--accent);
  border-color: var(--accent);
}
.right__nav a:hover .lc-handle, .right__footer a:hover .lc-handle,
.right__nav a[aria-current="page"] .lc-handle, .right__footer a[aria-current="page"] .lc-handle{
  opacity: 1;
}

.right__scroll{
  position: relative;
  flex: 1 1 auto;
  overflow-y: auto;
  overscroll-behavior: contain;
  /* no visible scrollbar — it was eating into the width, leaving the photo
     block narrower than the nav/footer rows above and below it */
  scrollbar-width: none;
  -ms-overflow-style: none;
  /* mandatory, not proximity: an actual jump to the next .stack__photo
     (see scroll-snap-align below, and each photo's id="stack-photo-N" in
     script.js) once you scroll past its .stack__text, not just a gentle
     settle if you happen to stop near one. */
  scroll-snap-type: y mandatory;
}
.right__scroll::-webkit-scrollbar{ display: none; }

/* ---------- stacked photo + text sections (workshops / concert) ----------
   Each photo is exactly the visible height of the scroll viewport
   (--screen-h, measured by script.js) — one photo, one screen, same as
   the single full-height photo in the reference PDFs. From item 2
   onward the photo is sticky, so scrolling the caption underneath
   "pins" the next photo at the top instead of just scrolling past it. */

.stack__item{ position: relative; }

.stack__photo{
  position: relative;
  height: var(--screen-h);
  /* no overflow:hidden here on purpose — the bottom-glued meta pill
     (.stack__meta below) is deliberately positioned half outside this
     box and must not be clipped. The abstract art (.stack__art) already
     fills exactly 100%/100% of this box on its own, so nothing relies
     on clipping. */
  display: flex;
  align-items: flex-start;
  background: var(--accent);
  box-sizing: border-box;
  /* pairs with scroll-snap-type on .right__scroll above — each photo is
     a snap point, so scrolling settles on showing one fully instead of
     stopping mid-transition between two. scroll-snap-stop:always (paired
     with the same on .stack__text below) forces even a fast/long scroll
     to actually stop here in turn, rather than skipping over it. */
  scroll-snap-align: start;
  scroll-snap-stop: always;
}

.stack__photo--sticky{ position: sticky; top: 0; z-index: var(--i, 1); }

/* currentColor here resolves to --pane-bg, so the art contrasts against
   the --accent backdrop set on .stack__photo, using only the 2 brand colours */
.stack__art{ position: absolute; inset: 0; width: 100%; height: 100%; color: var(--pane-bg); }

/* real photo (it.photo in script.js), same fill as .stack__art above —
   only one of the two is ever rendered per item, whichever it.photo
   decides */
.stack__photo-img{ position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }

.stack__tag{ position: relative; z-index: 2; padding: var(--grid); max-width: 560px; }

/* workshopStyle only (see stackHTML() in script.js — now used by both
   the Workshops and Concert tabs) — sits bottom-left over the photo
   instead of pinned to the top. position:absolute takes it out of
   .stack__photo's flex flow, so the default top-aligned .stack__tag
   (Tickets/About) is unaffected. */
.stack__tag--workshop{
  position: absolute;
  left: var(--grid);
  bottom: var(--grid);
  max-width: calc(100% - var(--grid) * 2);
  /* transform is set/cleared per scroll tick by syncStackTagOffsets() in
     script.js, translating this upward by however far .stack__text (a
     sibling of .stack__photo, this element's own ancestor) has scrolled
     up into it — no transition, so it tracks the scroll 1:1 with zero lag
     instead of visibly lagging behind it. */
  will-change: transform;
}

/* same badge exactly as .time/.tag-note (border, font, weight, italic,
   letter-spacing, padding, text-transform, colours) */
.stack__kicker{
  display: inline-block;
  font-family: var(--trial-font);
  font-weight: 900;
  font-style: italic;
  font-size: var(--typo-h3);
  letter-spacing: 0.02em;
  background: var(--accent);
  color: var(--pane-bg);
  border: 2px solid var(--accent);
  padding: 3px 7px 4px;
  text-transform: uppercase;
}

/* same kontur-box + corner-handle chip as .acts on the left column — see
   chipLines() in script.js, which splits on </br> into one .stack__title
   span per segment (handles included), same as actsLines() does for
   .acts. Also still works as a single plain <h2 class="stack__title">
   (Tickets/About's simpler one-line titles) — width:fit-content just
   hugs whatever text is inside either way. */
.stack__title{
  position: relative;
  display: block;
  width: fit-content;
  max-width: 100%;
  font-family: var(--trial-font);
  font-size: var(--typo-h1);
  line-height: 1.15;
  text-transform: uppercase;
  color: var(--accent);
  background: var(--pane-bg);
  border: 2px solid var(--accent);
  padding: 2px 9px;
  white-space: normal;
}
.stack__title + .stack__title{ margin-top: -2px; }

/* smaller sibling of .stack__title, same colours, no corner handles —
   same </br>-splitting idea via chipLines() in script.js. workshopStyle
   only; the default .stack__meta below (Tickets/About) is unrelated. */
/* same badge exactly as .desc-note (font-weight, font-size, letter-
   spacing, colours, border, padding) — display:block/width:fit-content/
   white-space:normal stay, though: that's the layout mechanics for
   stacking multiple chipLines() segments, same reason .workshops
   .desc-note carries the same three overrides on the left column. */
.stack__desc{
  position: relative;
  display: block;
  width: fit-content;
  max-width: 100%;
  font-family: var(--trial-font);
  font-weight: 900;
  font-style: italic;
  font-size: var(--typo-h3);
  letter-spacing: 0.02em;
  text-transform: uppercase;
  background: var(--accent);
  color: var(--pane-bg);
  border: 2px solid var(--accent);
  padding: 1px 7px 3px;
  white-space: normal;
}
.stack__desc + .stack__desc{ margin-top: -2px; }

/* same look as .workshops .lc-place specifically (the left column's own
   host tag, glued under .desc-note) — border, font-size:var(--typo-h3),
   and background/colour flipped to pane-bg/accent, not the plain base
   .lc-place's accent/pane-bg. Not its glue positioning (position:
   absolute/bottom/transform/margin-bottom) though, which sticks it to
   the bottom edge of a specific box like .acts/.desc-note — .stack__host
   isn't glued to anything, it just sits in normal flow after
   .stack__desc, so display:inline-block + margin-top stay as they were.
   text-transform:uppercase also stays explicit here: .lc-place normally
   gets that for free by inheriting it from whatever kontur-box it's
   nested in, which doesn't apply to this standalone chip. */
.stack__host{
  display: inline-block;
  background: var(--pane-bg);
  color: var(--accent);
  border: 2px solid var(--accent);
  font-family: var(--trial-font);
  font-weight: 800;
  font-style: italic;
  font-size: var(--typo-h3);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  padding: 3px 10px;
  margin-top: -1px
}

/* same margin as .time/.tag-note (and .desc-note/.lc-place on the left
   column) */
.stack__kicker, .stack__desc, .stack__host{ margin-left: 1rem; margin-right: 1rem; }

/* concert.html only: .stack__host's colours inverted from the default
   (background/text swapped) — scoped via body[data-page] since the
   right-panel stack markup itself is identical across every page that
   uses workshopStyle (Workshops, Concert, About). */
body[data-page="concert"] .stack__host{ background: var(--accent); color: var(--pane-bg); }

/* default (Tickets/About) only — glued to the lower part of the frame's
   contour, like the client's place/city tag sitting on the photo
   rectangle's bottom edge. workshopStyle doesn't use this — see
   .stack__host above instead. */
.stack__meta{
  position: absolute;
  z-index: 3;
  left: 50%;
  bottom: 0;
  transform: translate(-50%, 50%);
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 6px;
  max-width: calc(100% - var(--grid) * 2);
}
.stack__meta span{
  display: inline-block;
  background: var(--pane-bg);
  color: var(--accent);
  font-weight: 800;
  font-style: italic;
  font-size: 0.72rem;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  white-space: nowrap;
  padding: 5px 12px;
}


.stack__text{
  position: relative;
  /* needs to stay ABOVE every item's sticky photo (var(--i,1), 1/2/3…) —
     that's the whole reveal effect: this scrolls up and progressively
     covers the still-stuck photo beneath it. 10 is safely higher than
     any realistic item count. (.stack__tag--workshop staying visible
     while that happens is handled separately, in JS — see script.js
     syncStackTagOffsets(): it can't be done with z-index alone, since
     the tag lives *inside* .stack__photo's own stacking context and can
     never paint above a sibling of it, no matter its own z-index.) */
  z-index: 10;
  background: var(--pane-bg);
  padding: var(--grid);
  /* second snap point per item, after .stack__photo's — "end" so the
     scroll settles once this text's *bottom* edge reaches the container's
     bottom edge (i.e. once it's fully scrolled into view), not its top.
     Without this, .stack__photo (scroll-snap-align:start) was the only
     snap point, so scrolling straight past a still-being-read text
     block to the next photo, instead of pausing on the full text first.
     scroll-snap-stop:always on both this and .stack__photo forces a fast/
     long scroll to actually stop at each one in turn, rather than
     skipping past an intermediate snap point it scrolled clean over. */
  scroll-snap-align: end;
  scroll-snap-stop: always;
}

.stack__text p{
  margin: 0;
  max-width: 100%;
  font-weight: 500;
  line-height: 1.6;
  font-size: clamp(0.8rem, 1.5vw, 1.125rem);
  color: var(--accent);
}

.right__footer{ display: grid; grid-template-columns: repeat(4, 1fr); flex: 0 0 auto; gap: 8px; margin-top: var(--chip-gap); }
/* .right__footer a's own styling lives in the shared ".right__nav a,
   .right__footer a" rule above, next to .right__nav a */

/* T&E 2025 (the archive link) no longer has its own fixed-colour/hover
   overrides — it now looks and behaves exactly like its sibling nav/
   footer links (accent/pane-bg default, border+handles hidden until
   hover/current, same invert-on-hover from the shared ".right__nav a,
   .right__footer a" rule above). It keeps the .archive class and its
   hardcoded handles in the HTML only so script.js's handle-injection
   (.right__footer a:not(.archive)) doesn't double them up. */

/* ---------- transition on tab switch ---------- */

.fade-enter{ animation: fadeSlide 0.32s ease both; }
@keyframes fadeSlide{
  from{ opacity: 0; transform: translateY(8px); }
  to{ opacity: 1; transform: translateY(0); }
}

@media (prefers-reduced-motion: reduce){
  .fade-enter{ animation: none; }
  .stack__photo--sticky{ position: relative; }
}

/* ================= RESPONSIVE ================= */

@media (max-width: 1300px){
  /* less overlap than the default -1.5*grid (-30px) below 1300px —
     assuming the -0.8rem you gave keeps the same negative-margin overlap
     approach, just a smaller magnitude, not a positive (spaced-apart)
     value; say if you meant it the other way. .lineup isn't included —
     it has no overlap at all now (see .lineup li + li above), so there's
     nothing to shrink here for it. */
  .workshops li + li{ margin-top: 0.2rem; }
}

@media (max-width: 860px){
  html, body{ overflow: auto; height: auto; }

  .app{ display: block; height: auto; padding: 0px; --chip-h: 40px; }
  .pane + .pane{ margin-top: var(--grid); }
  .pane{ height: auto; }
  .pane__scroll{ overflow: visible; }

  /* .app.simple-page's own overflow:hidden (needed for its fixed-height
     desktop scroll region) isn't scoped to desktop only, so without this
     it was still clipping content here instead of letting the whole page
     scroll like every other page does at this width. */
  .app.simple-page{ display: block !important; height: auto !important; overflow: visible !important; }
  .simple-page__scroll{ overflow: visible !important; flex: none !important; height: auto !important; }

  /* restore the top padding on mobile only - it was removed from the
     base rule for desktop, but the mobile logo still needs it. */
  .left__top{ padding-top: var(--grid); }

  /* side padding so the bordered headings/chips inside .copy-block don't
     touch the edges of the screen on mobile. */
  .simple-page .copy-block{ margin-left: var(--grid); margin-right: var(--grid); width: auto; }

  /* logo/nav and sponsor-logos/footer-nav stack full-width instead of
     staying a cramped 2-column grid - matching how .left__header goes
     full-width and .right__nav/.right__footer span their own full-width
     pane on the other pages at this breakpoint. */
  .simple-page__row{
    display: block;
  }
  .simple-page__row > *:not(:first-child){
    margin-top: var(--grid);
  }

  .stack__tag{ padding: 0; }

  /* revert the corner-handle squares to their pre-increase size (8px,
     -4px offset — half of 8) below this breakpoint; the bigger 10px/
     -5px version above is desktop-only. */
  .lc-handle{ width: 8px; height: 8px; }
  .lc-handle--tl{ top: -4px; left: -4px; }
  .lc-handle--tr{ top: -4px; right: -4px; }
  .lc-handle--bl{ bottom: -4px; left: -4px; }
  .lc-handle--br{ bottom: -4px; right: -4px; }
  .lc-handle--t{ top: -4px; }
  .lc-handle--b{ bottom: -4px; }
  .lc-handle--l{ left: -4px; }
  .lc-handle--r{ right: -4px; }

  /* mobile only: no fixed-height box to fill (the page just scrolls
     normally), so a plain gap between cards is all that's needed — the
     desktop min-width:861px block uses flex space-between instead. clamp
     with a vw middle term (not a flat calc(var(--grid)*8) = 160px) so the
     gap actually scales with the screen instead of feeling identically
     huge on a narrow phone and a tablet-width 860px window alike —
     estimate, nudge the 10vw/100px if it's still too much/little. */
  .lineup li + li{ margin-top: clamp(24px, 10vw, 100px); }

  .right__nav{ position: sticky; top: 0; z-index: 5; padding-bottom: var(--grid); }

  .stack__photo--sticky{ position: relative; top: auto; }
  /* mobile only: .stack__photo-img + .stack__text together should fill
     exactly one viewport height (not the photo alone, which was the old
     flat 60vh) — .stack__text's own height is content-based (kicker +
     title + desc + host text, no fixed height), so the only way to make
     the pair sum to exactly 100vh is to let the photo take whatever's
     left over: .stack__item becomes a flex column pinned to 100vh, and
     .stack__photo (flex:1, height:auto instead of the desktop
     var(--screen-h)) absorbs all the remaining space after .stack__text
     takes its own natural height. */
  .stack__item{ display: flex; flex-direction: column; height: 100vh; }
  .stack__photo{ height: auto; flex: 1 1 auto; min-height: 0; }

  /* mobile has no internal scrolling (.pane__scroll{overflow:visible} above
     — the whole page scrolls normally instead), so the desktop "fixed
     header overlapping a scroll area" trick doesn't apply here: the header
     goes back to sitting normally in flow, and .left__scroll/.left__bg's
     --header-h compensation is dropped back to the plain --grid spacing
     they'd otherwise have. left/width/padding also reset — those aren't
     position-dependent, so the desktop bleed values would still apply
     (and overflow the viewport) even once position is back to static. */
  .left__header{ position: static; left: 0; width: 100%; padding: 0; }
  .left__scroll{ padding-top: var(--grid); }
  .left__bg{ top: calc(-1 * var(--grid) * 1.2); height: calc(100% + var(--grid) * 1.2); }

  .infobar{ grid-template-columns: 1fr; }

  /* below the breakpoint, .infobar stacks to 1 column and spans the full
     pane width — the logo's desktop cap (540px box / 4.8rem type) is
     dropped here so it keeps scaling with the same container the way
     .infobar does, landing at roughly the same width as .infobar instead
     of looking capped/undersized next to it.
     The floor (left side of max()) is a fixed size, so it can only be
     pushed so high — past a certain point a fixed floor is mathematically
     guaranteed to overflow "TRIAL AND" on a narrow enough phone, since it
     stops shrinking with the container. 2.6rem is the biggest floor that
     still fits at realistic minimum phone widths (~320px viewport); both
     rows share this one rule, so "ERROR" always matches automatically. */
  .logo{ max-width: none; }
  .logo-row{ font-size: max(2.6rem, 15.2cqi); }
  .eyebrow{ font-size: max(0.85rem, 3.6cqi); }

  /* same fix, same reason as the logo/eyebrow above: .left__scroll's cqi
     basis jumps up right at this breakpoint (single-column layout, full
     viewport width instead of half), but the desktop clamp's 6.5rem
     ceiling was capping it right where it should be growing — swapping
     to max() (floor only, no ceiling) lets it actually get bigger here,
     while still naturally shrinking as the viewport keeps narrowing
     past this point since cqi keeps tracking the container. */
  .lineup .lc--shift .acts,
  .lineup .lc--third .acts{     font-size: max(1.9rem, 7cqi); }

  .right__nav a, .right__footer a{ font-size: clamp(0.6rem, 2vw, 1.5rem); padding: 0 4px; }

  /* full edge-to-edge bleed, same formula as the desktop rule above (not
     a smaller mobile-specific one): .app's own padding is var(--grid) on
     BOTH layouts, so "left: -var(--grid)" always lands exactly on the true
     viewport edge and "width: 100% + var(--grid)*2" always lands the
     right edge exactly on the viewport's right edge too — the math is
     self-cancelling regardless of column count, so this can't overflow
     into horizontal scroll. (A smaller, partial overscan here previously
     just left the artwork's own raw edge visible/cropped instead of
     hidden past the viewport edge like on desktop.) */
  .left__bg{ left: calc(-1 * var(--grid)); width: calc(100% + var(--grid) * 2); }

  /* the sponsor logos don't all fit on one line at this width — wrap
     instead of overflowing past the edge */
  /* justify-content:space-between (not center): .left__footer already has
     the exact same var(--grid) left/right padding as .infobar (both
     children of the same full-width .pane.left), so the two boxes are
     already the same width — center was pulling the visible row of logos
     inward from those edges instead of stretching flush to them the way
     .infobar's grid columns do, which is what actually looked mismatched. */
  .left__footer{ height: auto; min-height: var(--chip-h); flex-wrap: wrap; justify-content: space-between; row-gap: 10px; padding: 10px var(--grid); }

  /* .left__footer is height:auto right above, so the logos' desktop
     sizing (a percentage height, resolved against --chip-h) has nothing
     to resolve against here and falls back to each SVG's raw intrinsic
     size instead — this is the one line that actually controls how big
     they render below 860px; lower the px value for smaller logos. */
  .left__footer a{ height: 25px; }
  .left__footer img{ height: 100%; }
}

/* ---------- WordPress admin bar ----------
   Only visible to logged-in users, but WP still pushes the whole page
   down by its height (32px desktop / 46px on narrow viewports, both via
   margin-top on <html>). This layout is a strict 100vh, overflow:hidden
   app shell, so without accounting for that extra margin the bottom of
   the page gets clipped by exactly the admin bar's height. Logged-out
   visitors never see the admin bar, so this only kicks in for admins. */
body.admin-bar .app{ height: calc(100vh - 32px); }

/* below 860px .app already goes to height:auto (see the max-width:860px
   block above) - the whole page scrolls normally there instead of the
   fixed-100vh two-pane trick, so there's nothing to clamp for the admin
   bar. The plain ".app{height:auto}" rule there is only 1 class though,
   so "body.admin-bar .app" below would otherwise still win on specificity
   and silently re-impose a fixed height, clipping the background short
   exactly like the .simple-page bug earlier. */
@media screen and (max-width: 860px){
  body.admin-bar .app{ height: auto; }
}

