/* Performer profiles have their own immersive layout. Keeping its static CSS
   in a shared asset removes roughly 66 KB from every profile response and lets
   the browser reuse it across performers. */
:root {
  --ground:#120A0C; --ground-2:#1A1013; --ground-3:#221419;
  --ink:#F3E7D0; --ink-soft:#D8C4A6; --muted:#A08C82;
  --wine:#7C1420; --wine-deep:#560D16; --gold:#C9A24B; --gold-bright:#EDD293;
  --accent-ink:#EAD199; --line:rgba(201,162,75,0.24); --line-soft:rgba(201,162,75,0.13);
  --panel-shadow:rgba(0,0,0,0.55); --spot:rgba(201,162,75,0.16); --vignette:rgba(0,0,0,0.62);
  --maxw:1120px; --frame-inset:18px; --topbar-h:4.4rem; --radius:3px;
  --font-display:"Didot","Bodoni 72","Bodoni MT","Big Caslon","Hoefler Text",Georgia,"Times New Roman",serif;
  --font-body:"Optima","Palatino Linotype","Palatino","Gill Sans","Segoe UI",-apple-system,BlinkMacSystemFont,system-ui,sans-serif;
}
@media (prefers-color-scheme: light) {
  :root {
    --ground:#EDE2C7; --ground-2:#E5D7B6; --ground-3:#F5EED8;
    --ink:#241318; --ink-soft:#4A342B; --muted:#6E5748;
    --wine:#7C1420; --wine-deep:#5A0E18; --gold:#B98F34; --gold-bright:#8A6A1E;
    --accent-ink:#7C5A14; --line:rgba(124,20,32,0.26); --line-soft:rgba(124,20,32,0.14);
    --panel-shadow:rgba(90,60,20,0.16); --spot:rgba(185,143,52,0.20); --vignette:rgba(120,90,40,0.10);
  }
}
:root[data-theme="light"] {
  --ground:#EDE2C7; --ground-2:#E5D7B6; --ground-3:#F5EED8; --ink:#241318; --ink-soft:#4A342B; --muted:#6E5748;
  --wine:#7C1420; --wine-deep:#5A0E18; --gold:#B98F34; --gold-bright:#8A6A1E; --accent-ink:#7C5A14;
  --line:rgba(124,20,32,0.26); --line-soft:rgba(124,20,32,0.14); --panel-shadow:rgba(90,60,20,0.16); --spot:rgba(185,143,52,0.20); --vignette:rgba(120,90,40,0.10);
}
:root[data-theme="dark"] {
  --ground:#120A0C; --ground-2:#1A1013; --ground-3:#221419; --ink:#F3E7D0; --ink-soft:#D8C4A6; --muted:#A08C82;
  --wine:#7C1420; --wine-deep:#560D16; --gold:#C9A24B; --gold-bright:#EDD293; --accent-ink:#EAD199;
  --line:rgba(201,162,75,0.24); --line-soft:rgba(201,162,75,0.13); --panel-shadow:rgba(0,0,0,0.55); --spot:rgba(201,162,75,0.16); --vignette:rgba(0,0,0,0.62);
}
* { box-sizing:border-box; }
html { scroll-behavior:smooth; }
@media (prefers-reduced-motion: reduce){ html{ scroll-behavior:auto; } }
body { margin:0; background:var(--ground); color:var(--ink); font-family:var(--font-body); font-size:17px; line-height:1.65; -webkit-font-smoothing:antialiased; text-rendering:optimizeLegibility; overflow-x:hidden; }
::selection { background:var(--wine); color:var(--gold-bright); }
h1,h2,h3 { font-family:var(--font-display); font-weight:400; text-wrap:balance; margin:0; }
a { color:inherit; }
.eyebrow { font-family:var(--font-body); text-transform:uppercase; letter-spacing:0.36em; font-size:0.72rem; font-weight:600; color:var(--accent-ink); }
.suit { font-family:Georgia, serif; }

.frame { position:fixed; inset:var(--frame-inset); border:1px solid var(--line); pointer-events:none; z-index:60; box-shadow:inset 0 0 0 3px rgba(0,0,0,0.12); }
.frame span { position:absolute; width:26px; height:26px; color:var(--gold); font:15px/26px Georgia,serif; text-align:center; background:var(--ground); transition:opacity .4s ease; }
.frame .tl{top:-13px;left:-13px;} .frame .tr{top:-13px;right:-13px;} .frame .bl{bottom:-13px;left:-13px;} .frame .br{bottom:-13px;right:-13px;}
/* No frame on a phone, so nothing to inset the bar to — and 18px of ground
   showing above a fixed bar reads as a rendering fault, not an ornament. */
@media (max-width:640px){ :root{ --frame-inset:0px; } .frame{ display:none; } }
/* The bar and the frame share the same top edge, so while the bar is showing
   it IS that edge: the bar's rule is the rule, and the frame's top border and
   top corner glyphs step aside. Without this the frame's hairline ran
   straight through the performer's name and the two suits sat on the bar's
   ends. The bar covers all three anyway now, but it covers them at 88%
   opacity, and a ghost of a hairline is still a hairline. */
body.nav-pinned .frame { border-top-color:transparent; }
body.nav-pinned .frame .tl, body.nav-pinned .frame .tr { opacity:0; }

/* Reaches the top of the viewport; its RULE stops at the frame's rails.
   A fixed bar promises that nothing gets past it, so insetting the whole bar
   to the frame left an 18px slot above it with the page streaming through —
   which reads as a rendering fault, not as an ornament. The background is
   full-bleed and tall enough to swallow the inset (so it covers the slot, the
   top of both rails, the frame's top border and the two corner suits) while
   the rule below is inset to land exactly rail-to-rail. Content is centred in
   the whole visible band and padded off the rails, so nothing about the
   inset shows except the one place it should. */
.topbar { position:fixed; top:0; left:0; right:0; z-index:61; display:flex; align-items:center; justify-content:space-between; gap:1rem; height:calc(var(--topbar-h) + var(--frame-inset)); padding:0 calc(clamp(1.2rem,4vw,3rem) + var(--frame-inset)); background:color-mix(in srgb, var(--ground) 88%, transparent); backdrop-filter:blur(9px); transform:translateY(-100%); transition:transform .4s ease; }
/* A child, not a border-bottom: a border follows the full bleed and would
   overshoot the frame's rails by the inset at both ends. */
.topbar::after { content:''; position:absolute; left:var(--frame-inset); right:var(--frame-inset); bottom:0; height:1px; background:var(--line); }
/* Not ".show". That is the show-row component a few rules down —
   display:grid, three columns — and it won on source order the moment the
   script added the class, turning the bar into a grid whose middle column
   stretched the button into a full-width wine slab. A state class and a
   component class are not allowed to share a name. */
.topbar.is-pinned { transform:translateY(0); }
/* Belt and braces after the .show collision above: pin the name's width and
   stop the button growing, so the bar cannot be stretched by its contents
   even if some future rule changes its display type again. */
.topbar .name { font-family:var(--font-display); font-size:1.15rem; letter-spacing:.03em; min-width:0; flex:0 1 auto; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.topbar .btn { flex:0 0 auto; }
/* .7rem is the page-wide floor for the smallest type: below ~11px the
   wide-tracked uppercase style stops being legible, it decorates. */
.topbar .name small { font-family:var(--font-body); letter-spacing:.18em; text-transform:uppercase; font-size:.7rem; color:var(--muted); display:block; overflow:hidden; text-overflow:ellipsis; }
.topbar .side { display:flex; align-items:center; gap:1.1rem; flex:0 0 auto; }
/* The route back to the rest of the site, in the corner every site puts it
   in. It sat on the right until a visitor pointed out that the one control
   that behaves like a logo was in the one place nobody looks for one. */
.topbar .bar-left { display:flex; align-items:center; gap:1rem; min-width:0; flex:0 1 auto; }
.topbar .fm-home { color:var(--accent-ink); text-decoration:none; white-space:nowrap; flex:0 0 auto; display:inline-flex; align-items:center; }
/* The link holds a drawing now, so underline-on-hover has nothing to underline. */
.topbar .fm-home:hover .brand-logo, .topbar .fm-home:focus-visible .brand-logo { opacity:.78; }
.topbar .fm-home .back { color:var(--muted); margin-right:.45rem; }
/* 1.25rem is the lockup's floor. Two things give out below about 20px and
   the wordmark's Bodoni is the first: its hairlines thin to nothing before
   the ring's counter closes. This bar is the tightest place the lockup
   appears, so it sits at the floor rather than being scaled to fit the row
   (brand/README.md). */
.topbar .fm-home .brand-logo { height:1.25rem; width:auto; display:block; }
/* Two names side by side need a divider or they read as one phrase —
   "FindMagic Steve Cohen". */
.topbar .bar-sep { flex:0 0 auto; width:1px; height:1.9rem; background:var(--line); }
/* On a phone the left cluster stacks: the site above, the performer below.
   Side by side the bar's three items want about 350px of a 390px screen and
   the name is what loses — ellipsised to "Vi…", which is a poor way to name
   someone on their own page. So the mark used to be hidden here, and a phone
   had no route off a profile short of the footer. Stacked, the mark is whole
   and the name gets 128px of a 390px screen, which fits most of them.
   The line break also does the divider's job of stopping "FindMagic Vitaly
   Beckman" reading as one phrase, so the divider goes. The tagline goes with
   it: at this width it truncates to "MIND-BLOWING CORP…", decoration bought
   with the space the name needs. Body face, not display, because at .72rem
   the display face's thin strokes stop being a wordmark and start being a
   smudge — and --muted clears AA in both themes at that size (5.2 : 1 on
   cream, 6.1 : 1 on ink). */
@media (max-width:520px){
  .topbar .bar-left { flex-direction:column; align-items:flex-start; gap:.1rem; }
  .topbar .fm-home {
    /* Longhands, never the `margin` shorthand: it would wipe out spacing set
       elsewhere on the same element, which is a bug this page has already
       paid for once. Padding buys a tappable strip the lockup's own height
       cannot; the negative margins hand that height back to the bar. */
    padding-block:.45rem; margin-top:-.4rem; margin-bottom:-.35rem; }
  .topbar .bar-sep { display:none; }
  .topbar .name { font-size:1.08rem; max-width:100%; }
  .topbar .name small { display:none; }
}

.btn { display:inline-flex; align-items:center; gap:.55rem; font-family:var(--font-body); font-weight:600; font-size:.82rem; letter-spacing:.12em; text-transform:uppercase; padding:.85rem 1.5rem; border-radius:var(--radius); text-decoration:none; cursor:pointer; border:1px solid transparent; transition:transform .18s ease, background .25s ease, color .25s ease, box-shadow .25s ease; }
/* Wine fill is dark in BOTH themes, so the text is a FIXED bright gold — never the
   theme-flipping --gold-bright (which goes dark in light mode and kills contrast). */
.btn-primary { background:var(--wine); color:#FBEEC8; font-weight:700; border-color:color-mix(in srgb, var(--gold) 72%, transparent); box-shadow:0 8px 26px -10px color-mix(in srgb, var(--wine) 80%, black), inset 0 0 0 1px color-mix(in srgb, var(--gold) 26%, transparent); }
.btn-primary:hover { background:var(--wine-deep); color:#FFF7E4; transform:translateY(-2px); box-shadow:0 14px 34px -12px var(--wine), inset 0 0 0 1px color-mix(in srgb, var(--gold) 40%, transparent); }
.btn-primary .arrow { color:var(--gold-bright); }
.btn-ghost { background:transparent; color:var(--accent-ink); border-color:var(--line); }
.btn-ghost:hover { border-color:var(--gold); background:color-mix(in srgb, var(--gold) 10%, transparent); transform:translateY(-2px); }
.btn-sm { padding:.6rem 1rem; font-size:.72rem; }
.btn:focus-visible, a:focus-visible, input:focus-visible, textarea:focus-visible, select:focus-visible, button:focus-visible { outline:2px solid var(--gold); outline-offset:3px; }

section { position:relative; }
.wrap { max-width:var(--maxw); margin:0 auto; padding:0 clamp(1.2rem,5vw,3rem); }
.section-pad { padding:clamp(4rem,9vw,7rem) 0; }
.entity-glance { padding:1.1rem 0; border-block:1px solid var(--line-soft); background:var(--ground-2); }
.entity-glance h2 { margin:0 0 .8rem; font-size:clamp(1rem,2vw,1.2rem); }
.entity-glance dl { display:grid; grid-template-columns:repeat(auto-fit,minmax(9rem,1fr)); gap:.8rem 1.25rem; margin:0; }
.entity-glance dl div { min-width:0; }
.entity-glance dt { color:var(--muted); font-size:.68rem; font-weight:700; letter-spacing:.1em; text-transform:uppercase; }
.entity-glance dd { margin:.18rem 0 0; line-height:1.35; }
.entity-glance a { color:inherit; text-decoration-color:var(--line); text-underline-offset:3px; }
/* Two untinted sections in a row put their full pads back to back — 14rem of
   empty parchment between the last date and the next eyebrow, which reads as
   the page having ended. A section that carries its own ground or a border
   keeps the full pad, because there the padding is what holds the band off
   its own edge; only a seam between two plain sections is doubled-up air. */
.section-pad + .section-pad:not(.specialties):not(.hire):not(.claim-section):not(.more-magicians) { padding-top:clamp(1rem,2vw,1.5rem); }

/* ---- section styles that used to sit in <style> tags between the sections ----
   A <style> element in the body is a sibling like any other, so one parked
   between two sections silently breaks every `section + section` rule that
   crosses it — which is how the seam rule above matched nothing on its first
   outing. Page-specific section CSS lives up here now; a rule for a section
   this profile does not render simply never matches, which costs nothing. */
/* The remind-box rules moved to showgroup.css when the show page gained the
   same ask — there rather than app.css, which this head does not load. */

.reviews-sec .rev-agg { display:flex; align-items:baseline; gap:.5rem; color:var(--ink-soft); margin:.4rem 0 0; }
.reviews-sec .rev-agg b { font-size:1.4rem; color:var(--gold-bright); }
.reviews-sec .rev-agg-star { color:var(--gold-bright); }
.reviews-grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(17rem,1fr)); gap:1rem; margin-top:1.4rem; }
.rev-card { border:1px solid var(--line-soft); border-radius:6px; padding:1.1rem 1.2rem; background:var(--ground-2); }
.rev-top { display:flex; align-items:center; justify-content:space-between; gap:.6rem; }
.rev-stars { color:var(--gold-bright); letter-spacing:.08em; }
.rev-stars .off { color:var(--line); }
.rev-vbadge { font-size:.68rem; text-transform:uppercase; letter-spacing:.1em; color:var(--gold); border:1px solid var(--line); border-radius:99px; padding:.12rem .5rem; white-space:nowrap; }
.rev-h { margin:.7rem 0 .3rem; font-size:1.05rem; color:var(--ink); }
.rev-b { color:var(--ink-soft); white-space:pre-wrap; margin:.4rem 0 .7rem; }
.rev-by { color:var(--muted); font-size:.85rem; margin:0; }
.rev-resp { margin-top:.9rem; padding-top:.8rem; border-top:1px solid var(--line-soft); }
.rev-resp-l { font-size:.72rem; text-transform:uppercase; letter-spacing:.1em; color:var(--gold); }
.rev-resp p { color:var(--ink-soft); margin:.3rem 0 0; }

.marketplace-sec .marketplace-note { color:var(--ink-soft); margin:.45rem 0 0; max-width:34rem; }
.marketplace-widget { margin-top:1.5rem; padding:1.25rem; border:1px solid var(--line-soft); border-radius:6px; background:var(--ground-2); overflow:auto; }

.booking-sec .booking-note { max-width:46ch; margin:0 auto; text-align:center; color:var(--ink-soft); }
.booking-sec .booking-cta { margin:1.4rem 0 0; text-align:center; }
.section-head { text-align:center; margin-bottom:2.8rem; }
.section-head .eyebrow { display:block; margin-bottom:.9rem; }
.section-head h2 { font-size:clamp(2rem,5vw,3rem); line-height:1.05; letter-spacing:.01em; }

/* Was 100svh, which spent the entire first screen on a name and a scroll cue
   while "5 upcoming shows" — the reason anyone opened the page — sat below
   the fold. Short enough now that the next section is visible without
   scrolling, and still tall enough to feel like a curtain going up. */
.hero { min-height:min(76svh, 680px); display:flex; flex-direction:column; align-items:center; justify-content:center; text-align:center; padding:5rem clamp(1.2rem,5vw,3rem) 3rem; position:relative; overflow:hidden; }
/* The negative margin cancels its own height: a marker that moved the hero
   it marks by half a pixel would be measuring itself. */
.pin-mark { display:block; width:1px; height:1px; margin-bottom:-1px; }
.hero::before { content:""; position:absolute; inset:0; z-index:0; background:radial-gradient(120% 90% at 50% 8%, var(--spot), transparent 55%), radial-gradient(140% 120% at 50% 120%, var(--vignette), transparent 60%); }
.hero > * { position:relative; z-index:1; }
.hero .verified { display:inline-flex; align-items:center; gap:.5rem; font-size:.7rem; letter-spacing:.22em; text-transform:uppercase; color:var(--accent-ink); border:1px solid var(--line); padding:.35rem .8rem; border-radius:100px; margin-bottom:1.6rem; }
.hero .verified .dot { color:var(--gold); }
/* Two badges can be true at once — a retired performer still fooled Penn and
   Teller in 2016 — so the slot is a wrapping row and the gap under it belongs
   to the row rather than to each pill, which would otherwise stack a second
   1.6rem of air between two wrapped lines. */
.hero-badges { display:flex; flex-wrap:wrap; justify-content:center; gap:.5rem; margin-bottom:1.6rem; }
.hero-badges .verified { margin-bottom:0; }
/* The one badge on this page that goes somewhere. Gold-washed rather than
   outlined so it reads as an award and not as another status label, and a
   real link because the claim has a public record behind it: the roster it
   comes from, which lists the episode and the result. */
a.hero-trophy { text-decoration:none; border-color:color-mix(in srgb, var(--gold) 55%, var(--line)); background:color-mix(in srgb, var(--gold) 10%, transparent); transition:border-color .2s ease, background .2s ease; }
a.hero-trophy:hover, a.hero-trophy:focus-visible { border-color:var(--gold); background:color-mix(in srgb, var(--gold) 20%, transparent); }
a.hero-trophy svg { flex:none; fill:var(--gold); }
a.hero-fool-us { text-decoration:none; }
a.hero-fool-us:hover, a.hero-fool-us:focus-visible { border-color:var(--accent-ink); }
.hero-badges em { font-style:italic; }
/* Compound selector on purpose: .placelink below sets `margin` as a shorthand
   at the same specificity and later in this sheet, so a plain .hero-eyebrow
   rule loses its bottom margin entirely the moment the city becomes a link —
   which left the city sitting 7px INTO the name it labels, and only on the
   performers who have a metro. Stated as the visible gap; the anchor variant
   subtracts its own .45rem tap-target padding so a city that links and a city
   that doesn't sit at exactly the same distance from the name. */
.hero .hero-eyebrow { margin-bottom:1.6rem; }
.hero a.hero-eyebrow { margin-bottom:1.15rem; }
/* Reads as the label it already was until you approach it, so linking the
   city changes nothing about the hero's typography. */
/* Padding + negative margin: the visible text stays where it was, the hit
   area grows to the 24px WCAG floor — a bare 17px-tall inline link is a
   miss on a phone. Underline via text-decoration so the padding doesn't
   push a border-bottom into the line below. */
.placelink { color:inherit; text-decoration:none; transition:color .2s ease; padding:.45rem .3rem; margin:-.45rem -.3rem; }
a.placelink:hover, a.placelink:focus-visible { color:var(--accent-ink); text-decoration:underline; text-underline-offset:3px; }
.hero h1 { font-size:clamp(3.4rem,13vw,8.5rem); line-height:0.9; letter-spacing:.01em; margin:0; text-shadow:0 2px 40px color-mix(in srgb, var(--gold) 22%, transparent); }
.hero .subtitle { font-family:var(--font-display); font-style:italic; font-size:clamp(1.2rem,3.6vw,1.9rem); color:var(--ink-soft); margin-top:1.1rem; letter-spacing:.02em; }
.hero-cta { display:flex; flex-wrap:wrap; gap:.9rem; justify-content:center; margin-top:2.2rem; }
.hero-cta .btn { padding:1rem 1.8rem; font-size:.86rem; }
.hero .verified.unclaimed { color:var(--muted); }
.hero .verified.unclaimed .dot { color:var(--muted); }
.hero-meta { display:flex; flex-wrap:wrap; gap:.6rem 2.2rem; justify-content:center; margin-top:2.6rem; color:var(--muted); font-size:.82rem; letter-spacing:.04em; }
.hero-meta b { color:var(--ink); font-weight:600; }
.hero-meta a { padding:.45rem .2rem; margin:-.45rem -.2rem; }
.hero-meta .star { color:var(--gold); }
/* An empty star for the part of the rating that isn't there: five solid
   stars beside "4.6" overstate the number they sit next to. */
.hero-meta .star .off { color:var(--line); }
/* The other half of a duo's career. Deliberately a link with its own line and
   an arrow rather than another hero-meta fact: a fact is something to read,
   and this is somewhere to go — the whole point is that a fan who arrived on
   the wrong one of the two pages can reach the right one. */
.also-billed { margin-top:1.6rem; font-size:.92rem; color:var(--muted); }
.also-billed a { color:var(--ink); text-decoration:none; border-bottom:1px solid var(--line); padding-bottom:.15rem; transition:border-color .2s ease, color .2s ease; }
.also-billed a:hover, .also-billed a:focus-visible { color:var(--accent-ink); border-bottom-color:var(--accent-ink); }
.also-billed .arrow { color:var(--gold); margin-left:.15rem; }
/* In normal flow (not absolute) so it never collides with tall hero content
   e.g. a 2-line name + meta row. Sits below the meta with breathing room. */

.nowplaying { border-top:1px solid var(--line); border-bottom:1px solid var(--line); background:linear-gradient(color-mix(in srgb, var(--wine) 14%, transparent), transparent); }
.nowplaying .wrap { display:flex; flex-wrap:wrap; align-items:center; justify-content:space-between; gap:1.2rem; padding-top:1.4rem; padding-bottom:1.4rem; }
.nowplaying .np-label { display:flex; align-items:center; gap:.7rem; }
.nowplaying .np-pulse { width:9px; height:9px; border-radius:50%; background:var(--wine); animation:ring 2.2s infinite; }
@keyframes ring { 0%{ box-shadow:0 0 0 0 color-mix(in srgb, var(--wine) 70%, transparent);} 70%{ box-shadow:0 0 0 12px transparent;} 100%{ box-shadow:0 0 0 0 transparent;} }
.nowplaying .np-title { font-family:var(--font-display); font-size:clamp(1.1rem,2.6vw,1.5rem); }
.nowplaying .np-title span { color:var(--muted); font-family:var(--font-body); font-size:.8rem; letter-spacing:.04em; }
/* Poster art, licensed from the ticketing feed — the single thing that stops
   every profile looking like every other profile. */
.hero-facts { max-width:60ch; margin:1.2rem auto 0; color:var(--muted); font-size:.95rem; line-height:1.6; }
/* Social row (shared markup: partials/socials.ejs). Styled here because this
   page is self-contained; venue.ejs styles the same classes via app.css. */
.social-links { display:flex; flex-wrap:wrap; gap:.5rem; justify-content:center; margin:1.6rem 0 0; }
.social-links .sl { font-family:var(--font-body); font-size:.78rem; letter-spacing:.1em; text-transform:uppercase;
  padding:.45rem .85rem; border:1px solid var(--line); border-radius:99px; text-decoration:none;
  color:var(--muted); transition:color .2s ease, border-color .2s ease; }
.social-links .sl:hover { color:var(--accent-ink); border-color:var(--accent-ink); }
.social-links .sl-site { color:var(--accent-ink); border-color:color-mix(in srgb, var(--gold) 50%, transparent); }
/* Icon pills: square, glyph inherits the pill colour so the hover recolour above
   applies to the icon itself. */
.social-links .sl-icon { width:2.5rem; height:2.5rem; padding:0; display:inline-flex; align-items:center; justify-content:center; }
.social-links .sl-icon svg { width:1.15rem; height:1.15rem; display:block; fill:currentColor; }

/* HERO PORTRAIT.
   Deliberately after every centred hero rule above, because the two-track
   layout has to beat them — and only when there is a photograph to build it
   from. A page with no image keeps the centred hero exactly as it was, which
   is most of the point: 100+ published profiles have no picture, and a
   two-track grid with one empty track is worse than a column.

   The stacking order on a phone is name, face, buttons — which is why the
   head and the rest of the hero are separate children rather than one block
   with the photo bolted beside it. */
.hero-grid { display:grid; gap:clamp(1.6rem,4vw,2.6rem); width:100%; max-width:var(--maxw); margin-inline:auto; }
/* The grid gap already separates the photograph from the buttons under it;
   the button row's own 2.2rem on top of it left them floating. */
.hero.has-portrait .hero-cta { margin-top:0; }
/* A MATTED PRINT, not a plate with suit pips. The photograph still wants to
   read as billed on this stage rather than pasted onto it, but the ornament
   cannot do that job here: the hero sits under the fixed .frame, whose own
   gold pips land about 90px from where the portrait's did, so a second set of
   suits in the same corner of the same screen is wallpaper rather than a
   motif — the comment on .video-sm below says the same thing about the reel.
   The pips were also offset only half their own width, so each opaque
   page-coloured chip cut a 9px notch out of the corner of the picture, in
   whichever theme the chip did not happen to match. A mat needs no gutter to
   live in, so unlike the ornament it survives on a phone. */
.hero-portrait { position:relative; margin-inline:auto; width:100%; max-width:22rem;
  background:var(--ground-3); border:1px solid var(--line-soft);
  padding:clamp(.7rem,2.2vw,1rem);
  box-shadow:0 20px 44px -26px var(--panel-shadow); }
/* Square inside square. A rounded photograph in a straight-edged mat reads as
   a sticker stuck onto card, which is the opposite of the intent; the mat's
   own edge and the photograph's hairline are the two rules a real mount has. */
.hero-shot .portrait-frame { position:relative; aspect-ratio:4/5; max-height:none;
  border-radius:0; }
/* A measured landscape banner keeps a landscape frame. Vitaly Beckman's is
   1600x480; caged at 4/5 it would show a quarter of one face. */
.hero-portrait.is-wide { max-width:34rem; }
.hero-portrait.is-wide .portrait-frame { aspect-ratio:16/9; }
/* `.portrait.hero-shot`, not `.hero-shot`: the doubled class is load-bearing.
   The base `.portrait img` rule further down this sheet has the same one-class
   specificity and comes LATER, so it was winning the cascade — every hero
   photograph rendered at its own aspect inside the fixed frame, letterboxed,
   with object-fit:cover never applying. Ella Nicholson's 1200x630 banner sat
   167px tall in a 400px box. Losing the extra class silently restores that. */
.portrait.hero-shot img { width:100%; height:100%; aspect-ratio:auto; object-fit:cover;
  /* Faces sit above centre in almost every publicity shot; a centred crop of
     a full-length photo delivers a torso. */
  object-position:center 30%; }
/* Provenance as a credit on the picture, not a caption under it. Under the
   hero photograph "Image via source page" read as a disclaimer about our
   records printed beneath the one asset that sells the act. */
/* Light on a scrim, in both themes: this sits on a photograph, not on the
   page's ground, and the photograph can be a dark stage or a white studio. */
.portrait-credit { position:absolute; left:0; right:0; bottom:0; padding:1.6rem .7rem .45rem;
  font-size:.6rem; letter-spacing:.1em; text-transform:uppercase; line-height:1.3;
  color:#F0E6CE; text-align:right; text-shadow:0 1px 3px rgba(0,0,0,.9);
  background:linear-gradient(transparent, rgba(0,0,0,.7)); }
.portrait-credit a { color:inherit; text-decoration:underline; text-underline-offset:2px; }
@media (min-width:900px){
  .hero.has-portrait { text-align:left; }
  .hero.has-portrait .hero-grid { grid-template-columns:minmax(0,1.25fr) minmax(0,20rem);
    column-gap:clamp(2.4rem,5vw,4.5rem); align-items:center; }
  .hero.has-portrait .hero-head { grid-column:1; grid-row:1; }
  .hero.has-portrait .hero-rest { grid-column:1; grid-row:2; }
  .hero.has-portrait .hero-portrait { grid-column:2; grid-row:1 / span 2; margin-inline:0; max-width:none; }
  /* 13vw was sized for a name centred across the whole wrap. In a 1.25fr
     track it set an eight-word stage name at 166px and wrapped it four ways. */
  .hero.has-portrait h1 { font-size:clamp(2.8rem,6.4vw,5.4rem); }
  .hero.has-portrait .hero-cta,
  .hero.has-portrait .social-links,
  .hero.has-portrait .hero-badges,
  .hero.has-portrait .hero-meta { justify-content:flex-start; }
  .hero.has-portrait .hero-facts { margin-inline:0; }
  .hero.has-portrait .hero-meta { gap:.6rem 1.6rem; }
}
/* A wide photograph wants more of the row than an upright one. */
@media (min-width:900px){
  .hero.has-portrait:has(.hero-portrait.is-wide) .hero-grid { grid-template-columns:minmax(0,1fr) minmax(0,30rem); }
}
/* A magician's own video beats any still we could find. nocookie + lazy so an
   embed does not track a visitor who never presses play. */
.video { margin:0 0 2rem; }
.video-frame { position:relative; width:100%; max-width:820px; aspect-ratio:16/9;
  border:1px solid var(--line); border-radius:var(--radius); overflow:hidden; background:#000; }
.video-frame iframe { position:absolute; inset:0; width:100%; height:100%; border:0; }
.video figcaption { margin-top:.5rem; font-size:.75rem; color:var(--muted); }
/* Watch section: auto-fit so it becomes a 2–3-up reel the moment an act has
   more than one video, with no template change. */
/* min(340px,100%): a hard 340px minimum overflows the wrap on ≤380px
   phones and clips the video's right edge. */
/* The gap has to clear two plates' worth of ornament, not just their rules:
   each corner pip hangs 18px outside its frame, so a 2-up reel spends 36px of
   the gap before any air is left. At the old 1.6rem the ♥ of one plate and the
   ♠ of the next sat 2px apart and read as one cluster of glyphs. 3.5rem leaves
   20px between them — and the same clearance vertically when the reel wraps. */
/* A lead clip, then a quieter row. The old auto-fit grid gave a sizzle reel
   and a phone clip the same width and the same ornament, so a four-video page
   read as four equal claims on the visitor's attention. */
.watch .video-reel { display:grid; gap:2.6rem 1.6rem; margin-top:clamp(2.6rem,5vw,4rem);
  grid-template-columns:repeat(auto-fit, minmax(min(230px, 100%), 1fr)); }
/* The 820px cap belongs on the FIGURE, not only on .video-frame. It used to
   sit only on the frame, which has no auto margins, so above ~911px of
   viewport the frame went flush left in a 1024px track and the clip hung
   102px left of the centred heading over it — justify-items:center could not
   help, because width:100% left nothing to centre. Capping the figure centres
   frame and caption together and keeps the caption's left edge on the video's.
   .poster img below already learned this, with margin-inline. */
.watch .video { margin:0 auto; width:100%; max-width:820px; }
/* PROSCENIUM PLATE. The page frame above (.frame: gold hairline, suit pips at
   the corners) restated at video scale, so a clip reads as billed on this
   stage rather than pasted in from YouTube. No assets — the motif is one
   outline and four glyphs, so it follows both themes for free.
   An outline, not a second border: it draws OUTSIDE .video-frame's
   overflow:hidden, which would clip a pseudo-element, and unlike a spread
   box-shadow it assumes nothing about the colour behind it. */
.video-plate { position:relative; }
.watch .video-frame { outline:1px solid var(--line); outline-offset:8px; }
/* The pips sit ON the rule and stop dead at the frame's edge. Nothing may be
   drawn over that iframe: an overlay there swallows the clicks meant for
   YouTube's own controls, and we cannot style what is inside it. */
.video-plate .vp { position:absolute; width:18px; height:18px; color:var(--gold);
  font:11px/18px Georgia,serif; text-align:center; background:var(--ground); pointer-events:none; }
.video-plate .tl{top:-18px;left:-18px;} .video-plate .tr{top:-18px;right:-18px;}
.video-plate .bl{bottom:-18px;left:-18px;} .video-plate .br{bottom:-18px;right:-18px;}
/* The bottom pips hang 18px below the frame, which is past the caption's
   .5rem. At the default margin the ♣ landed inside the caption's line box and
   read as a bullet before the title. */
.watch .video figcaption { margin-top:1.6rem; }
/* Same rule as the page frame, which hides below 640px: no ornament on a
   phone. There the plate spans the wrap and an 18px pip would have to live in
   a 19.2px gutter. With the pips gone the caption wants its own margin back. */
@media (max-width:640px){ .video-plate .vp{ display:none; } .watch .video figcaption{ margin-top:.5rem; } }
/* Only the lead clip wears the ornament. Four plates' worth of pips in one
   section is not a motif, it is wallpaper — and in the reel the pips would
   need 36px of a 25px gutter between two cards. */
.watch .video-sm { margin:0; width:100%; max-width:none; }
.watch .video-sm .vp { display:none; }
.watch .video-sm .video-frame { outline:none; }
.watch .video-sm figcaption { margin-top:.7rem; }
/* CLICK TO PLAY. At rest this is our poster inside our plate; the player is
   injected on click by the script at the foot of the page. An anchor, not a
   button, so that with no JS it is still a working route to the clip instead
   of a dead frame — and so its accessible name can be the video's title. */
.video-poster { display:block; cursor:pointer; text-decoration:none; background:#0b0608; }
.video-poster img { position:absolute; inset:0; width:100%; height:100%; object-fit:cover; }
/* A wash, so gold type and a gold rule hold their contrast over whatever
   frame YouTube happened to freeze on. */
.video-poster::after { content:""; position:absolute; inset:0;
  background:radial-gradient(62% 62% at 50% 50%, rgba(0,0,0,.12), rgba(0,0,0,.52));
  transition:background .3s ease; }
.video-poster:hover::after, .video-poster:focus-visible::after { background:radial-gradient(62% 62% at 50% 50%, rgba(0,0,0,.05), rgba(0,0,0,.42)); }
.video-poster:focus-visible { outline:2px solid var(--gold); outline-offset:8px; }
.vplay { position:absolute; left:50%; top:50%; z-index:2; width:4.2rem; height:4.2rem;
  border-radius:50%; border:1px solid var(--gold);
  background:color-mix(in srgb, var(--wine) 88%, transparent); color:var(--gold-bright);
  font:1.6rem/4.1rem Georgia,serif; text-align:center;
  box-shadow:0 12px 32px -14px rgba(0,0,0,.85);
  transform:translate(-50%,-50%); transition:transform .25s ease, background .25s ease; }
.video-sm .vplay { width:2.6rem; height:2.6rem; font-size:1rem; line-height:2.5rem; }
.video-poster:hover .vplay, .video-poster:focus-visible .vplay { transform:translate(-50%,-50%) scale(1.07); background:var(--wine); }
/* The clip's title IS the control's name — "Play" alone tells a screen reader
   which button but not which video, and a reel has four of them. */
.vplay-label { position:absolute; width:1px; height:1px; margin:-1px; padding:0;
  overflow:hidden; clip-path:inset(50%); white-space:nowrap; }
.watch-note { text-align:center; margin:2.4rem 0 0; font-size:.78rem; color:var(--muted); letter-spacing:.04em; }
/* A spotlight, not a curtain — the wash the hero already uses, centred on the
   clip. Drapes that never open promise an opening the page cannot deliver. */
.watch::before { content:""; position:absolute; inset:0; z-index:0;
  background:radial-gradient(72% 58% at 50% 62%, var(--spot), transparent 62%); }
.watch > * { position:relative; z-index:1; }
/* .poster now lives in app.css: the show page renders the same art from the
   same column and had to learn the same lessons about it. */
/* Centred as a block, under a centred heading and above centred dates — left
   against the wrap it looked like the start of a column that never arrived.
   The text itself stays ragged-right: 62ch of centred prose is harder to read
   than it is tidy, and this is the paragraph that sells the show. */
.show-blurb { max-width:62ch; margin:0 auto 1.8rem; color:var(--ink-soft); font-size:1.02rem; line-height:1.7; }

/* The Fool Us win, in full. The hero badge is the claim; this is the receipt —
   which episode, what night, and the clip where one exists. Card-per-win
   rather than a table: most acts fooled them once, and a one-row table is a
   table pretending to have rows. */
.fool-us-credit .fu-lead { max-width:62ch; margin:-1.2rem auto 2.2rem; text-align:center; color:var(--ink-soft); font-size:1.02rem; line-height:1.7; }
.fool-us-credit .fu-lead em { color:var(--ink); }
/* Capped and centred rather than 1fr: most acts fooled them exactly once, and
   a lone card stretched across the whole wrap is a four-line paragraph in a
   1,100px box. Bounded columns keep one win and four wins the same shape. */
.fu-wins { list-style:none; display:grid; gap:1rem; margin:0; padding:0; justify-content:center; grid-template-columns:repeat(auto-fit,minmax(min(100%,18rem),22rem)); }
/* The gold wash marks the win. A card for a night they did not fool Penn and
   Teller is the same card without it — present, plainly stated, and not
   dressed up as a trophy. */
.fu-win { padding:1.25rem 1.35rem; border:1px solid var(--line); border-radius:14px; }
.fu-win.is-fooled { background:linear-gradient(140deg, color-mix(in srgb, var(--gold) 7%, transparent), transparent 60%); }
.fu-when { display:inline-block; color:var(--accent-ink); font-size:.72rem; letter-spacing:.08em; text-transform:uppercase; text-decoration:none; border-bottom:1px solid transparent; }
.fu-when:hover, .fu-when:focus-visible { border-bottom-color:var(--accent-ink); }
.fu-win h3 { margin:.4rem 0 0; font-size:1.15rem; line-height:1.3; }
.fu-aired { margin:.3rem 0 0; color:var(--muted); font-size:.84rem; }
/* display:flex, not inline-flex: as an inline box the pill let the watch link
   ride up beside it on the cards with no result note, so one card in a row
   put its result and its link on the same line and its neighbour did not. */
.fu-result { display:flex; align-items:center; width:max-content; margin:.6rem 0 0; padding:.2rem .5rem; border:1px solid var(--line-soft, var(--line)); border-radius:99px; color:var(--muted); font-size:.62rem; letter-spacing:.06em; text-transform:uppercase; }
.fu-result.fooled, .fu-result.fooled-other { border-color:color-mix(in srgb, var(--gold) 62%, var(--line)); color:var(--accent-ink); }
.fu-note { margin:.4rem 0 0; color:var(--muted); font-size:.8rem; font-style:italic; }
.fu-watch { display:inline-flex; align-items:center; gap:.4rem; min-height:2.2rem; margin-top:.7rem; color:var(--accent-ink); font-size:.85rem; text-decoration:none; border-bottom:1px solid var(--line); }
.fu-watch:hover, .fu-watch:focus-visible { border-bottom-color:var(--accent-ink); }
/* A search is not a clip, and the page says so in words rather than leaving
   the difference to a link colour — the same boundary the roster holds. */
.fu-search-note { display:block; margin-top:.35rem; color:var(--muted); font-size:.74rem; }
.fu-more { margin:2rem 0 0; text-align:center; font-size:.92rem; }
.fu-more a { color:var(--accent-ink); text-decoration:none; border-bottom:1px solid var(--line); }
.fu-more a:hover, .fu-more a:focus-visible { border-bottom-color:var(--accent-ink); }
.fu-more .arrow { color:var(--gold); margin-left:.15rem; }

/* No blanket top rule. A venue-grouped list already opens with the venue's
   own 2px header rule, and a hairline sitting a few pixels above that read as
   a stray line nobody could account for. The flat production list below still
   wants a top edge, so it keeps one on its own first row. */
.shows-list { display:flex; flex-direction:column; gap:0; }
.shows-list > .show:first-child { border-top:1px solid var(--line-soft); }
.history-list { list-style:none; margin:0; padding:0; border-top:1px solid var(--line-soft); }
.history-entry { padding:1.25rem .25rem; border-bottom:1px solid var(--line-soft); }
.history-entry h3 { margin:.25rem 0; font-size:1.25rem; }
.history-entry h3 a, .history-place a, .history-cast a { color:inherit; text-underline-offset:3px; }
.history-when { display:block; color:var(--accent-ink); font-size:.72rem; letter-spacing:.08em; text-transform:uppercase; }
.history-place, .history-cast { margin:.25rem 0 0; color:var(--muted); font-size:.88rem; }
.history-cast a { color:var(--accent-ink); }
.history-note { color:var(--muted); font-size:.9rem; margin:-.8rem 0 1.4rem; }
.show { display:grid; grid-template-columns:auto 1fr auto; align-items:center; gap:1.6rem; padding:1.5rem .4rem; border-bottom:1px solid var(--line-soft); transition:background .25s ease; }
/* Tint only — the old padding-left shift jolted the row's text sideways. */
.show:hover { background:color-mix(in srgb, var(--gold) 6%, transparent); }
.show .date { text-align:center; min-width:76px; }
.show .date .mon { display:block; font-family:var(--font-body); text-transform:uppercase; letter-spacing:.18em; font-size:.7rem; color:var(--accent-ink); }
.show .date .day { display:block; font-family:var(--font-display); font-size:2.3rem; line-height:1; }
.show .date .dow { display:block; font-size:.7rem; letter-spacing:.14em; text-transform:uppercase; color:var(--muted); margin-top:.2rem; }
/* A production with no dates yet. Same small-caps accent register the date
   strip uses for its month and count labels, so it reads as a label rather
   than as a date that failed to render. --accent-ink, not --gold: measured
   against this page's cream, gold is 2.32:1 and accent ink is 4.90:1, so
   only one of them clears AA. */
.show .date.is-tour .tour-label { display:block; font-size:.7rem; letter-spacing:.18em;
  text-transform:uppercase; color:var(--accent-ink); }
.show .info h3 { font-size:1.35rem; letter-spacing:.01em; }
.show .info .venue { color:var(--muted); font-size:.9rem; margin-top:.25rem; }
.show .info .venue .city { color:var(--accent-ink); }
.show .act { display:flex; flex-direction:column; align-items:flex-end; gap:.5rem; }
.show .price { font-size:.8rem; color:var(--muted); letter-spacing:.04em; }
.show .tag { font-size:.7rem; letter-spacing:.14em; text-transform:uppercase; color:var(--accent-ink); border:1px solid var(--line); border-radius:100px; padding:.25rem .6rem; }
@media (max-width:620px){ .show{ grid-template-columns:auto 1fr; grid-template-areas:"date info" "act act"; row-gap:1rem; } .show .date{grid-area:date;} .show .info{grid-area:info;} .show .act{grid-area:act; align-items:flex-start; flex-direction:row; justify-content:space-between; width:100%;} }
/* The private-booking aside. Bordered card, not a bare tinted band: the band
   shared the list's edges and read as one more row, which is what put "Gig"
   in a date column in the first place. */
.book-note { display:flex; flex-wrap:wrap; align-items:center; justify-content:space-between;
  gap:1rem 1.8rem; margin:1.8rem 0 0; padding:1.4rem 1.6rem;
  border:1px solid var(--line); border-radius:var(--radius);
  background:color-mix(in srgb, var(--wine) 8%, transparent); }
.book-note .bn-text { flex:1 1 22rem; }
.book-note h3 { font-size:1.3rem; margin:0; }
.book-note p { margin:.4rem 0 0; color:var(--ink-soft); font-size:.95rem; max-width:52ch; }
.book-note .btn { flex:0 0 auto; }
.travel-note { margin-top:1.8rem; text-align:center; color:var(--muted); font-size:.92rem; }
.travel-note a { color:var(--accent-ink); text-underline-offset:3px; }
.empty-note { text-align:center; color:var(--muted); padding:2rem 0; }

/* align-items:start, not center. The two columns are never the same height —
   the bio ran 438px against an 823px side column — and centring split the
   difference into 192px of dead cream above the text and 192px below, so the
   eye fell into a hole between the heading and the first line. The side
   column is capped in rem rather than left as a free 1fr: it holds a photo
   and two count chips, and it does not earn 40% of a 1024px wrap. */
.experience .grid { display:grid; grid-template-columns:minmax(0,1.5fr) minmax(0,17rem); gap:clamp(2rem,5vw,4.5rem); align-items:start; }
@media (max-width:820px){ .experience .grid{ grid-template-columns:1fr; } }
/* No bio yet: the left column isn't rendered, so don't leave its track empty
   and shove the portrait to the right of a gap. */
.experience .grid.no-bio { grid-template-columns:minmax(0,32rem); justify-content:center; }
/* Bio but nothing for the side column (no portrait, counts under the stat
   threshold): a readable single measure, not a 1.3fr track beside a hole. */
.experience .grid.no-side { grid-template-columns:minmax(0,42rem); justify-content:center; }
.about-side { display:grid; gap:1.6rem; align-content:start; }
/* No forced aspect at all, the same call the poster slot already makes.
   image_url is whatever the performer's own site publishes, and plenty of
   those are landscape stage shots: Billy Hsueh's is 1024x682, and caging it
   at 4/5 threw away 47% of the frame width and pushed him to the crop edge.
   There is no ratio that is right for both a posed headshot and a wide shot,
   so we show the photograph as it was taken. .portrait-frame is the cage for
   the pathological case — a very tall image can't run away down the column —
   and it carries the border so the crop happens inside the rounded corner.
   NOT `.frame`: that name is taken by the ornamental fixed border above, and
   a portrait wrapper wearing it inherited position:fixed (the photo covered
   the page) and its `display:none` under 640px (no photo at all on a phone). */
.portrait { margin:0; }
.portrait-frame { display:block; max-height:30rem; overflow:hidden; line-height:0;
  border:1px solid var(--line); border-radius:var(--radius); }
/* `auto 3/2` is a placeholder ratio, not a crop: the keyword means "use the
   image's own ratio once you know it", and the fallback only applies before
   it loads. Dropping the ratio outright reserved no box for a lazy-loaded
   photo and shunted the section down when it arrived. */
.portrait img { width:100%; height:auto; aspect-ratio:auto 3/2; display:block; }
.portrait figcaption { margin-top:.5rem; font-size:.72rem; color:var(--muted); letter-spacing:.04em; }
.experience p { color:var(--ink-soft); margin:0 0 1.1rem; }
.experience .lead-cap::first-letter { font-family:var(--font-display); float:left; font-size:4.2rem; line-height:.8; padding:.1rem .5rem 0 0; color:var(--accent-ink); }
.pullquote { border-left:2px solid var(--gold); padding:.4rem 0 .4rem 1.6rem; font-family:var(--font-display); font-style:italic; font-size:1.5rem; line-height:1.35; color:var(--ink); }
/* Chips, not panels. Stacked at 114px each for a single digit they were what
   made the side column tower over the bio; side by side they read as a
   caption to the photo, which is the weight the fact deserves. */
.stat-stack { display:grid; grid-template-columns:repeat(auto-fit,minmax(8rem,1fr)); gap:.8rem; }
.stat { border:1px solid var(--line-soft); border-radius:var(--radius); padding:1rem 1.2rem; background:var(--ground-2); }
.stat .n { font-family:var(--font-display); font-size:2rem; line-height:1; color:var(--accent-ink); font-variant-numeric:tabular-nums; }
/* --ink-soft, not --muted: at 12.5px uppercase on the tinted chip ground the
   muted grey measured 4.72:1, the thinnest contrast on the page. */
.stat .l { font-size:.78rem; letter-spacing:.12em; text-transform:uppercase; color:var(--ink-soft); margin-top:.35rem; }
/* Where the rating's source link went when it came off the hero meta row.
   Small and quiet, but ON the chip that displays the number it attributes —
   which is the whole of the obligation. */
.stat-credit { margin:.55rem 0 0; font-size:.74rem; letter-spacing:.06em; color:var(--ink-soft); }
.stat-credit a { color:var(--ink-soft); text-underline-offset:3px; }
.stat-credit a:hover, .stat-credit a:focus-visible { color:var(--accent-ink); }

.specialties { background:var(--ground-2); border-top:1px solid var(--line-soft); border-bottom:1px solid var(--line-soft); }
.spec-grid { display:grid; grid-template-columns:repeat(3,1fr); gap:1.2rem; }
/* A lone card in a three-track grid sat left against two empty tracks —
   most profiles have exactly one specialty. Old browsers without :has()
   just keep the left-aligned card; nothing breaks. */
.spec-grid:has(> :only-child) { grid-template-columns:minmax(0, 420px); justify-content:center; }
@media (max-width:780px){ .spec-grid{ grid-template-columns:1fr; } }
/* Odd counts against three tracks leave a short last row hanging left, and it
   reads as a card that failed to load rather than as the end of the list.
   Two cards get two tracks; four get two rows of two; five split a six-track
   grid so the pair centres under the trio. Inside a min-width query because
   these selectors outrank the single-column rule above by specificity and
   would otherwise hold three tracks open on a phone. */
@media (min-width:781px){
  /* Two and four fill even rows of two. Five, seven and eight split a
     six-track grid — every card still two tracks wide, so it is the same
     three-up rhythm — and the short last row is placed to sit centred under
     it. Three, six and nine already divide, and one is centred above. The
     specialty list is a closed set of nine, so this covers all of it. */
  .spec-grid:has(> :nth-child(2):last-child),
  .spec-grid:has(> :nth-child(4):last-child) { grid-template-columns:repeat(2,1fr); }
  .spec-grid:has(> :nth-child(5):last-child),
  .spec-grid:has(> :nth-child(7):last-child),
  .spec-grid:has(> :nth-child(8):last-child) { grid-template-columns:repeat(6,1fr); }
  .spec-grid:has(> :nth-child(5):last-child) > *,
  .spec-grid:has(> :nth-child(7):last-child) > *,
  .spec-grid:has(> :nth-child(8):last-child) > * { grid-column:span 2; }
  .spec-grid:has(> :nth-child(5):last-child) > :nth-child(4) { grid-column:2 / span 2; }
  .spec-grid:has(> :nth-child(5):last-child) > :nth-child(5) { grid-column:4 / span 2; }
  .spec-grid:has(> :nth-child(7):last-child) > :nth-child(7) { grid-column:3 / span 2; }
  .spec-grid:has(> :nth-child(8):last-child) > :nth-child(7) { grid-column:2 / span 2; }
  .spec-grid:has(> :nth-child(8):last-child) > :nth-child(8) { grid-column:4 / span 2; }
}
.spec { border:1px solid var(--line-soft); border-radius:var(--radius); padding:2rem 1.8rem; background:var(--ground-3); transition:transform .25s ease, border-color .25s ease, box-shadow .25s ease; }
.spec:hover { transform:translateY(-4px); border-color:var(--gold); box-shadow:0 20px 40px -24px var(--panel-shadow); }
.spec .glyph { font-family:Georgia,serif; font-size:2rem; color:var(--gold); }
.spec h3 { font-size:1.4rem; margin:1rem 0 .6rem; }
.spec p { color:var(--muted); font-size:.95rem; margin:0; }

.gallery-grid { display:grid; grid-template-columns:repeat(4,1fr); gap:.8rem; }
@media (max-width:900px){ .gallery-grid{ grid-template-columns:repeat(2,1fr); } }
.tile { position:relative; aspect-ratio:3/4; border-radius:var(--radius); overflow:hidden; border:1px solid var(--line-soft); display:flex; align-items:flex-end; padding:1rem; color:#F3E7D0; isolation:isolate; }
.tile::before { content:""; position:absolute; inset:0; z-index:-1; background:radial-gradient(120% 80% at 30% 20%, color-mix(in srgb, var(--gold) 28%, transparent), transparent 60%), linear-gradient(160deg, var(--wine-deep), #0c0709 78%); }
.tile:nth-child(2)::before { background:radial-gradient(120% 80% at 70% 30%, color-mix(in srgb, var(--gold) 22%, transparent), transparent 55%), linear-gradient(200deg, #241014, #0c0709); }
.tile:nth-child(3)::before { background:radial-gradient(130% 90% at 40% 80%, color-mix(in srgb, var(--wine) 55%, transparent), transparent 60%), linear-gradient(160deg, #1a0d12, #060405); }
.tile:nth-child(4)::before { background:radial-gradient(120% 80% at 60% 20%, color-mix(in srgb, var(--gold) 30%, transparent), transparent 55%), linear-gradient(200deg, #2a1410, #080506); }
.tile .big-suit { position:absolute; z-index:-1; right:-10px; bottom:-18px; font:8rem/1 Georgia,serif; color:rgba(243,231,208,0.08); }
.tile .cap { font-family:var(--font-display); font-size:1.15rem; text-shadow:0 2px 10px rgba(0,0,0,.6); }
.tile .cap small { display:block; font-family:var(--font-body); font-size:.7rem; letter-spacing:.16em; text-transform:uppercase; color:var(--gold-bright); margin-bottom:.2rem; }

.praise-grid { display:grid; grid-template-columns:repeat(3,1fr); gap:1.2rem; }
@media (max-width:820px){ .praise-grid{ grid-template-columns:1fr; } }
.quote { border:1px solid var(--line-soft); border-radius:var(--radius); padding:1.8rem; background:var(--ground-2); display:flex; flex-direction:column; }
.quote .stars { color:var(--gold); letter-spacing:.2em; font-size:.8rem; margin-bottom:.9rem; }
.quote blockquote { margin:0; font-family:var(--font-display); font-size:1.16rem; line-height:1.5; color:var(--ink); font-style:italic; }
.quote .by { margin-top:auto; padding-top:1.2rem; font-size:.8rem; color:var(--muted); letter-spacing:.04em; }
.quote .by b { color:var(--accent-ink); font-weight:600; display:block; font-style:normal; letter-spacing:.02em; }

.hire { background:radial-gradient(90% 120% at 15% 0%, color-mix(in srgb, var(--wine) 16%, transparent), transparent 55%), var(--ground-2); border-top:1px solid var(--line); }
.hire .grid { display:grid; grid-template-columns:.9fr 1.1fr; gap:clamp(2rem,5vw,4rem); align-items:start; }
@media (max-width:820px){ .hire .grid{ grid-template-columns:1fr; } }
.hire h2 { font-size:clamp(2rem,5vw,3rem); line-height:1.02; }
.hire .sell { color:var(--ink-soft); margin:1.2rem 0; }
/* Stated plainly and attributed, not badged. A fee is information for a fan
   working out whether to ask, not a price tag on a person. */
.hire .price-line { color:var(--ink-soft); margin:1.2rem 0; font-variant-numeric:tabular-nums; }
.hire .price-line strong { color:var(--ink); }
.hire .checklist { list-style:none; padding:0; margin:1.4rem 0 0; display:grid; gap:.7rem; }
.hire .checklist li { display:flex; gap:.7rem; align-items:baseline; color:var(--ink-soft); font-size:.95rem; }
.hire .checklist li::before { content:"\2726"; color:var(--gold); }
form { display:grid; gap:1rem; background:var(--ground-3); border:1px solid var(--line); border-radius:var(--radius); padding:clamp(1.4rem,3vw,2rem); }
.field { display:grid; gap:.4rem; }
.row2 { display:grid; grid-template-columns:1fr 1fr; gap:1rem; }
@media (max-width:520px){ .row2{ grid-template-columns:1fr; } }
label { font-size:.7rem; letter-spacing:.16em; text-transform:uppercase; color:var(--muted); font-weight:600; }
input, select, textarea { font-family:var(--font-body); font-size:.95rem; color:var(--ink); background:var(--ground); border:1px solid var(--line-soft); border-radius:var(--radius); padding:.8rem .9rem; width:100%; transition:border-color .2s ease, box-shadow .2s ease; }
input::placeholder, textarea::placeholder { color:color-mix(in srgb, var(--muted) 80%, transparent); }
input:focus, select:focus, textarea:focus { border-color:var(--gold); box-shadow:0 0 0 3px color-mix(in srgb, var(--gold) 18%, transparent); outline:none; }
/* This page does not load app.css, so it needs its own copy of the date-input
   fix: a date input will not shrink below the min-content width of the
   browser's native date editor, and the hire form's date sits in a `.row2`
   cell that gets half the form. Without this it spills out of the card on a
   phone. Keep in step with the same rule in app.css. */
input[type="date"] { min-width:0; }
textarea { resize:vertical; min-height:92px; }
.honeypot { position:absolute; left:-9999px; width:1px; height:1px; overflow:hidden; }
.form-foot { display:flex; align-items:center; justify-content:space-between; gap:1rem; flex-wrap:wrap; }
.form-foot .fine { font-size:.74rem; color:var(--muted); max-width:30ch; }
.form-error { color:#e5a3a3; font-size:.82rem; margin:0; }
.form-success { text-align:center; padding:2.5rem 1.5rem; background:var(--ground-3); border:1px solid var(--gold); border-radius:var(--radius); }
.form-success .big-tick { font-size:2.4rem; color:var(--gold); }
.form-success h3 { font-size:1.6rem; margin:.6rem 0 .5rem; }
.form-success p { color:var(--muted); margin:0; font-size:.95rem; }

footer { border-top:1px solid var(--line); background:var(--ground); }
.foot-grid { display:flex; flex-wrap:wrap; gap:2rem; justify-content:space-between; align-items:flex-start; padding:clamp(2.5rem,6vw,4rem) 0 1.5rem; }
footer .name { font-family:var(--font-display); font-size:1.8rem; }
footer .name small { display:block; font-family:var(--font-body); font-size:.7rem; letter-spacing:.2em; text-transform:uppercase; color:var(--muted); margin-top:.3rem; }
.foot-col h4 { font-size:.7rem; letter-spacing:.2em; text-transform:uppercase; color:var(--muted); margin:0 0 .8rem; font-family:var(--font-body); font-weight:600; }
.foot-col a { display:block; color:var(--ink-soft); text-decoration:none; font-size:.92rem; padding:.2rem 0; }
.foot-col a:hover { color:var(--accent-ink); }
.foot-bar { border-top:1px solid var(--line-soft); padding:1.2rem 0 2rem; display:flex; flex-wrap:wrap; gap:1rem; justify-content:space-between; align-items:center; font-size:.78rem; color:var(--muted); }
.powered b { color:var(--accent-ink); font-family:var(--font-display); font-size:1rem; letter-spacing:.02em; }
.foot-bar .claim { color:var(--accent-ink); text-decoration:none; border-bottom:1px solid var(--line); padding-bottom:2px; }
/* A performer's own footer remains the first ending to their page. FindMagic's
   directory navigation sits beneath it as a visibly separate, fixed-dark
   site layer, even when the visitor has selected the light profile theme. */
.site-footer--dark { color:#F3E7D0; background:#080506; border-top-color:rgba(201,162,75,.34); }
.site-footer--dark .name { color:#F3E7D0; }
.site-footer--dark .name small,
.site-footer--dark .foot-col h4,
.site-footer--dark .foot-bar { color:#AA978B; }
.site-footer--dark .foot-col a { color:#D8C4A6; }
.site-footer--dark .foot-col a:hover,
.site-footer--dark .foot-col a:focus-visible,
.site-footer--dark .foot-col .foot-all,
.site-footer--dark .powered b { color:#EAD199; }
.site-footer--dark .foot-bar { border-top-color:rgba(201,162,75,.16); }

/* Claim + report forms */
.claim-form { margin-top:1.6rem; text-align:left; max-width:640px; }
.claim-form .cf-row { display:grid; grid-template-columns:1fr 1fr; gap:1rem; }
@media (max-width:640px){ .claim-form .cf-row{ grid-template-columns:1fr; } }
.claim-form label { display:block; margin-bottom:.9rem; font-size:.82rem; letter-spacing:.06em; text-transform:uppercase; color:var(--muted); }
.claim-form input, .claim-form textarea, .claim-form select {
  display:block; width:100%; margin-top:.4rem; padding:.7rem .8rem; font:inherit; font-size:.95rem;
  text-transform:none; letter-spacing:normal; color:var(--ink);
  background:color-mix(in srgb, var(--ground-3) 70%, transparent);
  border:1px solid var(--line); border-radius:var(--radius); }
.claim-form input:focus, .claim-form textarea:focus, .claim-form select:focus {
  outline:2px solid var(--accent-ink); outline-offset:1px; }
.claim-form .cf-note { font-size:.78rem; color:var(--muted); margin-top:1rem; text-transform:none; letter-spacing:normal; }
.claim-form .cf-error { color:var(--wine); font-size:.85rem; margin:.4rem 0 .8rem; }
.claim-done { margin-top:1.6rem; padding:1.2rem 1.4rem; border:1px solid var(--line); border-radius:var(--radius);
  background:color-mix(in srgb, var(--ground-3) 60%, transparent); text-align:left; }
.claim-done p { margin:.5rem 0 0; color:var(--ink-soft); font-size:.95rem; }
.report-block { margin-top:1.8rem; text-align:left; max-width:640px; }
.report-block summary { cursor:pointer; color:var(--muted); font-size:.9rem; }
.report-block summary:hover { color:var(--accent-ink); }

/* Unclaimed-profile claim funnel */
/* Below "More magicians" and at two thirds the height it used to be. It was
   783px of a 6,111px page — larger than About, larger than the repertoire —
   addressed to exactly one person, and it sat between a fan who had decided
   against this act and the six alternatives we had ready for them. It is
   still a complete pitch; it is no longer the loudest thing left on the page.
   A performer who lands on their own page reads all of it. */
.claim-section { padding:clamp(2.6rem,5vw,3.8rem) 0; background:radial-gradient(90% 120% at 50% 0%, color-mix(in srgb, var(--gold) 12%, transparent), transparent 60%), var(--ground-2); border-top:1px solid var(--line); text-align:center; }
.claim-section .inner { max-width:640px; margin:0 auto; }
.claim-section .eyebrow { display:block; margin-bottom:.8rem; }
.claim-section h2 { font-size:clamp(1.5rem,3.2vw,2rem); line-height:1.15; }
.claim-section p { font-size:.95rem; }
.claim-section .claim-perks { margin:1.2rem 0; }
.claim-section p { color:var(--ink-soft); margin:1.1rem auto 0; }
.claim-section .claim-perks { list-style:none; padding:0; margin:1.8rem 0; display:flex; flex-wrap:wrap; gap:.7rem 1.4rem; justify-content:center; color:var(--muted); font-size:.9rem; }
.claim-section .claim-perks li::before { content:"\2726"; color:var(--gold); margin-right:.5rem; }
.claim-section .claim-actions { display:flex; flex-wrap:wrap; gap:.9rem; justify-content:center; margin-top:1.4rem; }
/* The one concrete thing on offer, so it does not read as another perk. */
.claim-section .claim-lift { color:var(--ink); }
.claim-section .why-line { margin:-.6rem auto 1.8rem; font-size:.9rem; color:var(--muted); }
.claim-section .why-line a { color:var(--accent-ink); text-underline-offset:3px; }

/* Peer links. auto-fill (not auto-fit) so a short list stays card-width
   instead of stretching two cards across the full wrap. */
/* FOLLOW. A band of its own, near the foot, instead of a low-contrast box
   inside Upcoming Shows under the sentence that said there weren't any.
   Centred like every other section head on this page; the form itself stays
   left inside a narrow measure, because a centred text input reads as
   decoration rather than as a thing to type in. */
/* Fragment navigation aligns the section itself with the viewport. Leave the
   fixed profile bar plus a visible beat above it, or #follow hides the title
   that explains the form — exactly the context a badge visitor needs. */
.follow-sec { scroll-margin-top:calc(var(--topbar-h) + var(--frame-inset) + 1rem); background:var(--ground-2); border-top:1px solid var(--line-soft); border-bottom:1px solid var(--line-soft); }
.follow-sec .section-head { margin-bottom:1.6rem; }
.follow-lede { max-width:56ch; margin:0 auto 1.8rem; text-align:center; color:var(--ink-soft); }
.follow-lede a { color:var(--accent-ink); text-underline-offset:3px; }
.follow-sec .remind-box { max-width:34rem; margin:0 auto; padding-top:0; border-top:0; }

/* The prompt that slides in on scroll, ported from the city page — the same
   mechanism, the same manners: it appears once there is scroll behind it,
   stands down while the real band is on screen, closes, and is remembered.
   Remembered per performer: somebody who dismissed this on Kel's page has
   said nothing about Billy's. */
.follow-prompt { position:fixed; z-index:20; right:1rem; bottom:1rem; width:min(26rem, calc(100vw - 2rem));
  padding:1.1rem 1.2rem; background:var(--ground-3); border:1px solid var(--gold); border-radius:7px;
  box-shadow:0 12px 30px var(--panel-shadow); opacity:0; transform:translateY(1rem);
  pointer-events:none; transition:opacity .2s ease, transform .2s ease; }
.follow-prompt.is-visible { opacity:1; transform:translateY(0); pointer-events:auto; }
.follow-prompt .fp-lede { margin:0 0 .8rem; padding-right:1.4rem; font-size:.92rem; color:var(--ink-soft); }
.follow-prompt .fp-lede b { color:var(--ink); font-weight:600; }
.follow-prompt .remind-form label { font-size:.7rem; }
.follow-prompt .remind-fine { font-size:.75rem; margin-top:.6rem; }
.fp-close { position:absolute; top:.35rem; right:.5rem; background:none; border:0; color:var(--muted);
  font-size:1.4rem; line-height:1; padding:.2rem .35rem; cursor:pointer; border-radius:4px; }
.fp-close:hover { color:var(--ink); }
.fp-close:focus-visible { outline:2px solid var(--gold); outline-offset:2px; }
@media (max-width:600px){ .follow-prompt { bottom:.5rem; right:.5rem; width:calc(100vw - 1rem); } }

.more-magicians { border-top:1px solid var(--line-soft); }
.mm-grid { display:grid; gap:1.1rem; grid-template-columns:repeat(auto-fill, minmax(min(260px, 100%), 1fr)); }
.mm-card { display:flex; flex-direction:column; gap:.45rem; padding:1.4rem; text-decoration:none; color:inherit;
  border:1px solid var(--line-soft); border-radius:var(--radius); background:var(--ground-3);
  transition:border-color .2s ease, transform .2s ease; }
/* --ground-3, not --ground-2: in light mode ground-2 is the DARKER parchment,
   which drops the gold "View page" link to 4.42:1 — under AA. Same surface
   .spec already uses. */
.mm-card:hover { border-color:var(--accent-ink); transform:translateY(-2px); }
.mm-card h3 { font-family:var(--font-display); font-size:1.25rem; line-height:1.15; margin:0; }
.mm-where { font-size:.72rem; letter-spacing:.12em; text-transform:uppercase; color:var(--muted); }
.mm-tag { color:var(--ink-soft); font-size:.88rem; line-height:1.5; margin:.2rem 0 0;
  display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden; }
.mm-specs { font-size:.78rem; color:var(--muted); }
.mm-go { margin-top:auto; padding-top:.7rem; font-size:.78rem; letter-spacing:.08em; color:var(--accent-ink); }

.js .reveal { opacity:0; transform:translateY(22px); }
.js .reveal.in { opacity:1; transform:none; transition:opacity .7s ease, transform .7s cubic-bezier(.2,.7,.2,1); }
@media (prefers-reduced-motion: reduce){ .js .reveal{ opacity:1 !important; transform:none !important; } .np-pulse{ animation:none !important; } * { animation-duration:.001ms !important; transition-duration:.05ms !important; } }
/* Print sees the page without ever scrolling it, so unrevealed sections
   would print as blank paper. */
@media print { .js .reveal { opacity:1 !important; transform:none !important; } }
.entity-glance { padding:1.35rem 0; border-block:1px solid var(--line-soft); background:color-mix(in srgb, var(--gold) 5%, transparent); }
.entity-glance h2 { font-family:var(--font-body); color:var(--muted); font-size:.7rem; font-weight:700; letter-spacing:.18em; text-transform:uppercase; margin:0 0 .8rem; }
.entity-glance dl { display:grid; grid-template-columns:repeat(auto-fit,minmax(9rem,1fr)); gap:.8rem 1.4rem; margin:0; }
.entity-glance dl div { min-width:0; }
.entity-glance dt { color:var(--muted); font-size:.68rem; letter-spacing:.1em; text-transform:uppercase; }
.entity-glance dd { margin:.1rem 0 0; color:var(--ink); font-size:.92rem; line-height:1.35; }
.entity-glance dd a { color:var(--accent-ink); text-underline-offset:3px; }
