1<!doctype html> 2<html lang="en-GB"> 3<head> 4<meta charset="utf-8"> 5<meta name="viewport" content="width=device-width,initial-scale=1"> 6<meta name="color-scheme" content="light"> 7<title>Simply Red â Summer Funk â27 Tour</title> 8<meta name="description" content="Simply Red â the Summer Funk â27 tour across the UK and Europe. Sign up for first access to the artist pre-sale."> 9<meta name="theme-color" content="#F0F0EF"> 10<meta name="format-detection" content="telephone=no"> 11<meta name="author" content="Simply Red"> 12<link rel="canonical" href="https://www.simplyred.com/"> 13<meta name="robots" content="index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1"> 14<link rel="icon" href="https://www.simplyred.com/favicon.ico"> 15<link rel="apple-touch-icon" href="https://www.simplyred.com/stage/wp-content/uploads/favicon-320x320.png"> 16 17<meta property="og:type" content="website"> 18<meta property="og:site_name" content="SIMPLYRED.COM"> 19<meta property="og:locale" content="en_GB"> 20<meta property="og:title" content="Simply Red â Summer Funk â27 Tour"> 21<meta property="og:description" content="The Summer Funk â27 tour across the UK and Europe. Sign up for first access to the artist pre-sale."> 22<meta property="og:url" content="https://www.simplyred.com/"> 23<!-- 1200Ã630 composed from the hero assets (logo, script, key visual on the 24 ground colour). Swap for Dan's export if one comes; same filename. --> 25<meta property="og:image" content="https://www.simplyred.com/announcement-assets/og.jpg"> 26<meta property="og:image:type" content="image/jpeg"> 27<meta property="og:image:width" content="1200"> 28<meta property="og:image:height" content="630"> 29<meta property="og:image:alt" content="Simply Red â Summer Funk â27"> 30<meta name="twitter:card" content="summary_large_image"> 31<meta name="twitter:title" content="Simply Red â Summer Funk â27 Tour"> 32<meta name="twitter:description" content="The Summer Funk â27 tour across the UK and Europe. Sign up for first access to the artist pre-sale."> 33<meta name="twitter:image" content="https://www.simplyred.com/announcement-assets/og.jpg"> 34
35<script type="application/ld+json"> 36{ 37 "@context": "https://schema.org", 38 "@type": "MusicGroup", 39 "name": "Simply Red", 40 "url": "https://www.simplyred.com/", 41 "sameAs": [ 42 "https://www.instagram.com/simplyredofficial/", 43 "https://www.facebook.com/simplyred/", 44 "https://www.tiktok.com/@simplyredofficial", 45 "https://www.youtube.com/channel/UCwtvFWbWkLr8ra51W-pLstQ", 46 "https://open.spotify.com/artist/1fa0cOhromAZdq2xRA4vv8", 47 "https://music.apple.com/gb/artist/simply-red/163002", 48 "https://www.deezer.com/en/artist/1516" 49 ] 50} 51</script>
vendor: 1 bytes, line 51
51
52<script type="application/ld+json"> 53{ 54 "@context": "https://schema.org", 55 "@type": "ConcertSeries", 56 "name": "Simply Red â Summer Funk â27 Tour", 57 "description": "Simply Redâs Summer Funk â27 tour across the UK and Europe.", 58 "performer": { "@type": "MusicGroup", "name": "Simply Red" }, 59 "url": "https://www.simplyred.com/" 60} 61</script>
61 62
63<script> 64 document.documentElement.classList.add("js"); 65 /* GTM â both containers the current WP site loads, only after cookie 66 consent (see the cookie banner script at the foot of the page). 67 GA4 property G-Q3MK41W88D is fired BY GTM-5QVH3P6J (verified in the 68 container JS 2026-09-15), alongside Meta, Google Ads and Clarity tags. 69 Do NOT add a direct gtag() for G-Q3MK41W88D here â it would double-count 70 page views and enhanced-measurement events. Custom events go through 71 SR_track() â dataLayer, named sr_*; GTM needs a Custom Event trigger per 72 name to forward them to GA4 (list in docs/landing-page-plan.md). */ 73 window.SR_GTM_IDS = ["GTM-TNHFM53", "GTM-5QVH3P6J"]; 74 window.SR_loadGTM = function () { 75 if (window.SR_gtmLoaded) return; 76 window.SR_gtmLoaded = true; 77 window.dataLayer = window.dataLayer || []; 78 window.dataLayer.push({ "gtm.start": new Date().getTime(), event: "gtm.js" }); 79 window.SR_GTM_IDS.forEach(function (id) { 80 var s = document.createElement("script"); 81 s.async = true; 82 s.src = "https://www.googletagmanager.com/gtm.js?id=" + id; 83 document.head.appendChild(s); 84 }); 85 }; 86 window.SR_track = function (name, params) { 87 window.dataLayer = window.dataLayer || []; 88 var p = { event: name }; for (var k in (params || {})) p[k] = params[k]; 89 window.dataLayer.push(p); 90 }; 91 try { 92 if (localStorage.getItem("sr_cookie_consent") === "1") window.SR_loadGTM(); 93 } catch (e) {} 94</script>
94 95 96<!-- Fonts. Two Adobe kits: 97 fjk5ujc â the current simplyred.com theme's kit: forma-djr-text 98 (300â800) + owners-wide (500/700) 99 dkt5nqg â Dharma Gothic C/E/M. M ships Regular / Bold / ExBold / 100 Heavy (400/700/800/900) â the weights the Figma uses. 101 Saira Extra Condensed / Archivo remain as network fallbacks only. --> 102<link rel="preconnect" href="https://use.typekit.net" crossorigin> 103<link rel="stylesheet" href="https://use.typekit.net/fjk5ujc.css"> 104<link rel="stylesheet" href="https://use.typekit.net/dkt5nqg.css"> 105<link rel="preconnect" href="https://fonts.googleapis.com" crossorigin> 106<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> 107<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Archivo:wght@400;500;700&family=Saira+Extra+Condensed:wght@400;700;800&display=swap"> 108 109<style> 110 /* ============================================================ 111 Tokens â from Dan's hero prototype + the Figma landing frame 112 (RjNy0ZlZGRbV74d5Zusjvs, node 31:414). Light ground, red accent. 113 ============================================================ */ 114 :root { 115 --ground: #F0F0EF; 116 --ink: #1D1D1B; 117 --white: #FFFFFF; 118 --accent: #D03131; 119 --accent-dark: #B82B2B; 120 /* Small red text on the light ground. #D03131 is 4.42:1 there (AA needs 121 4.5); this is 4.61:1 and indistinguishable. Display type + buttons 122 keep the true brand red (large text 3:1 / white-on-red 5:1 both pass). */ 123 --accent-text: #CC2E2E; 124 --text-2: #404042; 125 --text-3: #666668; /* was #6E6E70 â 4.46:1 on the ground, now 5.0:1 */ 126 --icon-muted: #9A9A9C; 127 --hairline: #DCDBD9; 128 --footer: #10100F; 129 --panel: #1D1D1B; 130 131 --sans: "forma-djr-text", "Forma DJR Text", Archivo, system-ui, -apple-system, "Segoe UI", sans-serif; 132 --display: "dharma-gothic-m", "Dharma Gothic M", "Saira Extra Condensed", Impact, sans-serif; 133 --wide: "owners-wide", "Owners Wide", Archivo, system-ui, sans-serif; 134 135 /* Large-desktop scale. Type, buttons, row heights and containers all 136 multiply by --k so a 1920+ display doesn't feel like a 1440 layout 137 floating in space. The hero has its own stage scale. */ 138 --k: 1; 139 --kd: calc(1 + (var(--k) - 1) * 0.5); /* date rows: half the boost, they get loud fast */ 140 --container: min(calc(960px * var(--k)), calc(100vw - 32px)); 141 --wide-container: min(calc(1200px * var(--k)), calc(100vw - 32px)); 142 143 /* Hero stage â --s and --stageBase set by JS. The sticky hero is at 144 least one viewport tall so the first view is the hero alone; the 145 1440Ã852 stage sits vertically centred inside that. */ 146 --s: 1; 147 --navS: 1; /* nav pill scale â tracks --s but never above 1 */ 148 --stageBase: 852px; 149 --stageH: max(var(--stageBase), 100svh); 150 } 151 152 @media (min-width: 1680px) { :root { --k: 1.2; } } 153 @media (min-width: 2200px) { :root { --k: 1.4; } } 154 155 *, *::before, *::after { box-sizing: border-box; } 156 /* overflow-x: clip, not hidden â hidden would make body a scroll container 157 and break the sticky hero. clip just stops sideways scroll. */ 158 html, body { margin: 0; padding: 0; overflow-x: clip; } 159 html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; } 160 body { 161 background: var(--ground); 162 color: var(--ink); 163 font-family: var(--sans); 164 font-size: calc(16px * var(--k)); 165 line-height: 1.4; 166 letter-spacing: 0.02em; 167 -webkit-font-smoothing: antialiased; 168 -moz-osx-font-smoothing: grayscale; 169 min-height: 100vh; 170 } 171 body.no-scroll { overflow: hidden; } 172 a { color: inherit; text-decoration: none; } 173 /* Every link and button eases between states. Components with richer 174 hover behaviour (buttons, rows, tiles) set their own transition lists. */ 175 a:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 2px; } 176 a, button { 177 transition: color 180ms ease, background-color 180ms ease, border-color 180ms ease, 178 opacity 180ms ease, transform 180ms ease, box-shadow 180ms ease; 179 } 180 img, svg { display: block; max-width: 100%; } 181 [hidden] { display: none !important; } 182 p { margin: 0; } 183 184 .vh { 185 position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; 186 clip: rect(0 0 0 0); white-space: nowrap; border: 0; 187 } 188 .skip-link { 189 position: absolute; left: -9999px; top: 0; z-index: 200; 190 background: var(--ink); color: var(--white); padding: 8px 12px; font-size: calc(14px * var(--k)); 191 } 192 .skip-link:focus { left: 8px; top: 8px; } 193 194 /* Display type â Dharma Gothic M. ExBold (800) for headings and dates, 195 Heavy (900) for city + region labels, Regular (400) for venue names. */ 196 .display, h1, h2, h3 { 197 font-family: var(--display); 198 font-weight: 800; 199 line-height: 1.1; 200 letter-spacing: 0.04em; 201 text-transform: uppercase; 202 margin: 0; 203 } 204 .display em, h2 em { font-style: normal;
204 color: var(--accent); } 205 206 /* State-driven copy â every element carrying data-copy-* attributes gets 207 its text swapped by JS according to the page state (announce â presale 208 â onsale). The HTML holds the announce copy as the no-JS default. */ 209 210 /* ============================================================ 211 Buttons â SM · Button component: red fill, 2px radius, 16/500. 212 ============================================================ */ 213 .btn { 214 appearance: none; 215 position: relative; 216 display: inline-flex; 217 align-items: center; 218 justify-content: center; 219 min-height: calc(54px * var(--k)); 220 padding: calc(16px * var(--k)) calc(24px * var(--k)); 221 border: 0; 222 border-radius: 2px; 223 background: var(--accent); 224 color: var(--white); 225 font-family: var(--sans); 226 font-weight: 500; 227 font-size: calc(16px * var(--k)); 228 line-height: 1.4; 229 letter-spacing: 0.02em; 230 cursor: pointer; 231 text-align: center; 232 text-decoration: none; 233 white-space: nowrap; 234 transition: background-color 160ms ease, color 160ms ease, opacity 160ms ease; 235 } 236 .btn:hover, .btn:focus-visible { background: var(--accent-dark); } 237 .btn:focus-visible { outline: 2px solid var(--ink); outline-offset: 3px; } 238 .btn--outline { 239 background: transparent; 240 color: var(--accent); 241 box-shadow: inset 0 0 0 1px var(--accent); 242 } 243 .btn--outline:hover, .btn--outline:focus-visible { background: var(--accent); color: var(--white); } 244 .btn--block { width: 100%; } 245 246 .text-link { 247 font-weight: 500; 248 color: var(--accent-text); 249 transition: color 160ms ease; 250 } 251 .text-link:hover, .text-link:focus-visible { color: var(--accent-dark); } 252 253 .eyebrow-pill { 254 display: inline-flex; 255 align-items: center; 256 padding: 6px 14px; 257 border: 1px solid var(--accent-text); 258 border-radius: 999px; 259 font-family: var(--sans); 260 font-weight: 500; 261 font-size: calc(11px * var(--k)); 262 letter-spacing: 0.02em; 263 text-transform: uppercase; 264 color: var(--accent-text); 265 margin: 0 0 21px; 266 } 267 268 /* ============================================================ 269 Section shells 270 ============================================================ */ 271 section { position: relative; scroll-margin-top: 96px; } 272 .section-inner { 273 width: var(--container); 274 margin: 0 auto; 275 padding: clamp(56px, 8vw, 112px) 0; 276 text-align: center; 277 } 278 .section-heading { 279 font-size: clamp(32px, 4vw, calc(40px * var(--k))); 280 margin: 0 0 8px; 281 } 282 .section-cta { margin: 24px 0 0; } 283 .section-lede { 284 font-size: calc(16px * var(--k)); 285 line-height: 1.5; 286 color: var(--text-2); 287 max-width: 60ch; 288 margin: 0 auto; 289 } 290 291 /* ============================================================ 292 NAV â frosted pill, fixed to the viewport. Lifted out of the 293 scaled hero stage so it sticks; keeps the stage scale + 41px 294 design offset via --s. Tour / Music / simplyred.com â 295 ============================================================ */ 296 .navbar { 297 position: fixed; top: 0; left: 0; right: 0; z-index: 60; 298 display: flex; justify-content: center; 299 padding-top: calc(41px * var(--navS) + env(safe-area-inset-top)); 300 pointer-events: none; 301 } 302 .nav { 303 height: 68px; pointer-events: auto; 304 transform: scale(var(--navS)); transform-origin: top center; 305 display: flex; align-items: center; gap: 4px; padding: 8px; 306 border-radius: 999px; 307 background: var(--white); 308 } 309 .nav a { 310 display: flex; align-items: center; gap: 8px; height: 52px; padding: 0 28px; 311 border-radius: 999px; text-decoration: none; 312 font-size: 16px; line-height: 20px; letter-spacing: 0; font-weight: 500; 313 color: var(--ink); white-space: nowrap; 314 transition: background-color 180ms ease, color 180ms ease; 315 } 316 .nav a:hover { background: var(--ground); } 317 .nav a.is-active { background: var(--ground); font-weight: 700; } 318 .nav a.is-out { color: var(--accent-text); } 319 .nav a.is-out span { display: inline-block; transition: transform 180ms ease; } 320 .nav a.is-out:hover span { transform: translateX(3px); } 321 .nav a:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; } 322 /* Small screens: the pill no longer tracks the stage scale, it gets its 323 own compact size so it stays tappable. */ 324 @media (max-width: 719px) { 325 .navbar { padding-top: calc(12px + env(safe-area-inset-top)); } 326 .nav { transform: none; height: auto; padding: 4px; gap: 0; } 327 .nav a { height: 40px; padding: 0 14px; font-size: 14px; } 328 } 329 330 /* ============================================================ 331 HERO â Dan's prototype, verbatim where possible. A 1440Ã852 332 stage scaled to fill the viewport. The page scrolls normally 333 from the top; the bands straighten over the first ~45% of a 334 screen of scroll while the hero is still in view (Dan's 335 prototype pinned the hero for this â dropped on review, it 336 felt like the page was stuck). 337 Geometry from Figma nodes 47:2239 / 47:2253. 338 ============================================================ */ 339 .hero-scroll { position: relative; height: var(--stageH); } 340 .hero-sticky { position: relative; height: var(--stageH); overflow: hidden; } 341 .stage { 342 position: absolute; left: 50%; margin-left: -720px; 343 top: max(0px, calc((var(--stageH) - var(--stageBase)) / 2)); 344 width: 1440px; height: 852px; 345 transform: scale(var(--s)); transform-origin: 50% 0; 346 } 347 .logo { position: absolute; left: 416px; top: 162px; width: 608.618px; height: 66.44px; } 348 .script { position: absolute; left: 502px; top: 249px; width: 436.825px; height: 82.21px; } 349 .logo img, .script img { display: block; width: 100%; height: 100%; } 350 351 /* left-to-right reveal with a soft edge */ 352 .wipe { 353 -webkit-mask-image: linear-gradient(90deg, #000 45%, transparent 55%); 354 mask-image: linear-gradient(90deg, #000 45%, transparent 55%); 355 -webkit-mask-size: 220% 100%; mask-size: 220% 100%; 356 -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat; 357 -webkit-mask-position: 100% 0; mask-position: 100% 0; 358 } 359 360 .kv { position: absolute; left: 467px; top: 367px; width: 502px; height: 452px; } 361 .band { position: absolute; will-change: transform, opacity; } 362 .band-inner { width: 100%; height: 100%; will-change: transform; } 363 .band img { display: block; width: 100%; height: 100%; } 364
365 .b-orange { left: 0; top: 14px; width: 83.6px; height: 377.7px; } 366 .b-blue { left: 93px; top: 25px; width: 73px; height: 375.9px; } 367 .b-green { left: 173px; top: 0; width: 72.5px; height: 380.1px; } 368 .b-red { left: 251px; top: 46px; width: 82.7px; height: 343.7px; } 369 .b-yellow { left: 328px; top: 0; width: 83.5px; height: 377.7px; } 370 .b-purple { left: 417px; top: 23.3px; width: 85.2px; height: 378px; } 371 .b-mick { left: 69px; top: 115px; width: 328px; height: 336.8px; } 372 373 @keyframes riseIn { from { opacity: 0; transform: translateY(var(--rise, 20px)); } to { opacity: 1; transform: none; } } 374 @keyframes wipeIn { to { -webkit-mask-position: 0% 0; mask-position: 0% 0; } } 375 @keyframes cueIn { from { opacity: 0; } to { opacity: 1; } } 376 377 .intro .navbar, 378 .intro .band { opacity: 0; animation: riseIn .9s cubic-bezier(.22, 1, .36, 1) forwards; } 379 .intro .navbar { --rise: -10px; animation-duration: .7s; animation-delay: .15s; } 380 .intro .b-green { animation-delay: .60s; } 381 .intro .b-red { animation-delay: .66s; } 382 .intro .b-blue { animation-delay: .72s; } 383 .intro .b-yellow { animation-delay: .78s; } 384 .intro .b-orange { animation-delay: .84s; } 385 .intro .b-purple { animation-delay: .90s; } 386 .intro .b-mick { animation-delay: 1.30s; --rise: 26px; } 387 .intro .wipe { animation: wipeIn 1.05s cubic-bezier(.42, 0, .16, 1) .35s forwards; } 388 .intro .script .wipe { animation-duration: 1.35s; animation-delay: 1.15s; } 389 .intro .cue { opacity: 0; animation: cueIn .8s ease 2.3s forwards; } 390 /* Once the fade-in has run, hand opacity back to the scroll script â a 391 forwards-filled animation would otherwise pin it at 1. */ 392 .intro .cue.is-live { animation: none; opacity: 1; } 393 394 /* Scroll mark (Figma 47:1876), centred over the figure. */ 395 .cue { 396 position: absolute; left: 692px; top: 738px; width: 52px; 397 display: flex; flex-direction: column; align-items: center; gap: 6px; 398 text-decoration: none; 399 } 400 .cue span { font-size: 16px; line-height: 22px; letter-spacing: .02em; font-weight: 500; color: #fff; } 401 .cue svg { display: block; color: var(--accent); } 402 /* Mobile composition (< 720px): the 1440 stage is not scaled as a whole. 403 Logo + script sit under the nav, sized to the width; the key visual is 404 scaled on its own (--kvS, set by JS) and anchored to the bottom of the 405 screen so Mick is big and the strips fill the height. JS sets --logoTop, 406 --logoW, --scriptTop, --scriptW. */ 407 @media (max-width: 719px) { 408 /* Height is fixed at load by JS (--stageBase) â no viewport units, so 409 the collapsing browser chrome can't re-flow the hero mid-scroll. */ 410 :root { --stageH: var(--stageBase); } 411 .stage { width: 100%; left: 0; margin-left: 0; height: var(--stageH); transform: none; } 412 .logo, .script { left: 50%; transform: translateX(-50%); height: auto; } 413 .logo { top: var(--logoTop, 96px); width: var(--logoW, 86vw); } 414 .script { top: var(--scriptTop, 150px); width: var(--scriptW, 62vw); } 415 .logo img, .script img { height: auto; } 416 .kv { left: 50%; top: auto; bottom: -28px; transform-origin: 50% 100%; } 417 /* Outer strips tuck behind their neighbours (see MOBILE_REST in the JS). */ 418 .b-orange, .b-purple { z-index: 1; } 419 .b-blue, .b-yellow { z-index: 2; } 420 .b-green, .b-red { z-index: 3; } 421 .b-mick { z-index: 4; } 422 .cue { left: 50%; top: auto; bottom: calc(24px + env(safe-area-inset-bottom)); transform: translateX(-50%); } 423 } 424 .cue:hover span { opacity: 0.8; } 425 .cue:hover svg { transform: translateY(2px); } 426 .cue span, .cue svg { transition: opacity 180ms ease, transform 180ms ease; } 427 .cue:focus-visible { outline: 2px solid #fff; outline-offset: 6px; border-radius: 4px; } 428 429 /* ============================================================ 430 SIGN-UP â "First to know". Heading + lede from Figma 31:581, 431 then the OpenStage embed in place of the drawn form (OpenStage 432 renders its own fields, consent + reCAPTCHA inside the iframe; 433 it cannot be styled from here â see docs). Then the share row. 434 ============================================================ */ 435 .signup { background: var(--ground); } 436 .signup-inner { 437 width: min(calc(492px * var(--k)), calc(100vw - 32px)); 438 margin: 0 auto; 439 padding: clamp(40px, 6vw, 72px) 0 clamp(56px, 8vw, 96px); 440 text-align: center; 441 } 442 .signup h2 { 443 font-size: clamp(48px, 7vw, calc(64px * var(--k))); 444 line-height: 1.1; 445 min-height: 1.1em; 446 } 447 .signup .lede { 448 margin: 12px 0 0; 449 font-size: calc(18px * var(--k)); line-height: 1.5; color: var(--text-2); 450 } 451 .os-embed { 452 position: relative; 453 margin: 24px auto 0; 454 width: 100%; 455 max-width: calc(500px * var(--k)); 456 /* OpenStage's embed says 410px, but its tallest step (consent) ends 457 around 300px; 340 removes the dead space under the form. Re-measure 458 if they add fields. */ 459 min-height: 340px; 460 background: var(--white); 461 border-radius: 2px; 462 overflow: hidden; 463 } 464 .os-embed iframe { 465 display: block; 466 width: 100%; 467 height: 340px; 468 border: 0; 469 max-width: 100%; 470 } 471 .os-embed noscript p { padding: 24px; font-size: calc(14px * var(--k)); color: var(--text-3); } 472 .micro { margin: 18px 0 0; font-size: calc(12px * var(--k)); line-height: calc(18px * var(--k)); color: var(--text-3); } 473 .share-label { margin: 32px 0 0; font-size: calc(12px * var(--k)); line-height: calc(18px * var(--k)); color: var(--text-3); } 474 .share { margin-top: 16px; display: flex; flex-wrap: wrap; justify-content: center; gap: 12px; } 475 .share a { 476 display: inline-flex; align-items: center; height: 36px; padding: 8px 14px; 477 border: 1px solid var(--accent-text); border-radius: 999px; text-decoration: none; 478 font-size: calc(16px * var(--k)); line-height: calc(20px * var(--k)); font-weight: 500; color: var(--accent-text); 479 transition: background .18s ease, color .18s ease; 480 } 481 .share a:hover, .share a:focus-visible { background: var(--accent); color: #fff; } 482 .share a:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; } 483 484 /* Fade-up per component. Hidden state only applied once JS arms it. */ 485 .armed .r { 486 opacity: 0; transform: translateY(18px); 487 transition: opacity .7s cubic-bezier(.22, 1, .36, 1) var(--d, 0ms), 488 transform .7s cubic-bezier(.22, 1, .36, 1) var(--d, 0ms); 489 } 490 .armed.in .r { opacity: 1; transform: none; } 491 492 /* Whole-section reveals for everything below the sign-up. */ 493 .js .reveal { 494 opacity: 0; 495 transform: translateY(30px); 496 transition: opacity 800ms ease-out, transform 900ms cubic-bezier(0.22, 1, 0.36, 1); 497 will-change: opacity, transform; 498 } 499 .js .reveal.is-visible { opacity: 1; transform: none; } 500 501 /* ============================================================ 502 TOUR â heading, state-driven lede, full-bleed trailer, then 503 the dates grouped by region. 504 ============================================================ */ 505 .tour .section-inner { padding-top: 0; } 506 /* Locked 16:9. Full-bleed, but never taller than the viewport â on very 507 wide screens it narrows and centres rather than cropping the frame. */ 508 .tour-video { 509 --tv: min(100vw, calc(100vh * 16 / 9)); 510 position: relative; 511 width: var(--tv); 512 margin: 24px 0 56px calc(50% - var(--tv) / 2); 513 aspect-ratio: 16 / 9; 514 background: var(--panel); 515 overflow: hidden; 516 } 517 .tour-video video, 518 .tour-video .tour-video-poster { 519 position: absolute; inset: 0; 520 width: 100%; height: 100%; 521 object-fit: cover; 522 display: block; 523 } 524 /* Logo + script sit over the poster until the trailer starts. */ 525 .tour-video-overlay { 526 position: absolute; inset: 0; 527 display: flex; flex-direction: column; align-items: center; justify-content: center; 528 gap: clamp(8px, 1.5vw, 18px); 529 pointer-events: none; 530 transition: opacity 500ms ease; 531 } 532 .tour-video-overlay::before { /* slight dim so the button reads */ 533 content: ""; position: absolute; inset: 0; z-index: -1; 534 background: rgba(0, 0, 0, 0.18); 535 } 536 .tour-video-overlay img { width: min(501px, 55vw); height: auto; } 537 .tour-video-overlay img + img { width: min(360px, 40vw); } 538 .tour-video-overlay .btn { pointer-events: auto; margin-top: clamp(12px, 3vw, 32px); } 539 .tour-video.is-playing .tour-video-overlay { opacity: 0; } 540 .tour-video.is-playing .tour-video-overlay .btn { pointer-events: none; } 541 .tour-video.is-playing.is-paused .tour-video-overlay { opacity: 1; } 542 .tour-video.is-playing.is-paused .tour-video-overlay .btn { pointer-events: auto; } 543 .video-mute { 544 position: absolute; 545 right: clamp(12px, 2.5vw, 28px); 546 bottom: clamp(12px, 2.5vw, 28px); 547 z-index: 2; 548 display: inline-flex; align-items: center; justify-content: center; 549 width: 47px; height: 47px; padding: 0; border: 0; border-radius: 50%; 550 background: rgba(29, 29, 27, 0.75); 551 color: var(--white); 552 cursor: pointer; 553 transition: background-color 160ms ease, transform 160ms ease; 554 } 555 .video-mute:hover, .video-mute:focus-visible { background: rgba(29, 29, 27, 0.95); transform: scale(1.06); } 556 .video-mute:focus-visible { outline: 2px solid var(--white); outline-offset: 3px; }
557 .video-mute svg { width: 20px; height: 20px; display: block; fill: currentColor; } 558 .video-mute[aria-pressed="false"] .icon-unmuted, 559 .video-mute[aria-pressed="true"] .icon-muted { display: none; } 560 561 .dates-list { max-width: calc(960px * var(--k)); margin: 0 auto; text-align: left; } 562 .region-divider { 563 font-family: var(--display); 564 font-weight: 900; 565 font-size: clamp(32px, 4vw, calc(40px * var(--kd))); 566 line-height: calc(32px * var(--kd)); 567 letter-spacing: 0.04em; 568 text-transform: uppercase; 569 color: var(--ink); 570 padding: 0 0 22px; 571 margin: 0; 572 border-bottom: 1px solid var(--text-2); 573 } 574 .region-divider + .region-divider, 575 .date-row + .region-divider, 576 .dates-empty + .region-divider { margin-top: 56px; } 577 .date-row { 578 display: grid; 579 grid-template-columns: calc(138px * var(--kd)) minmax(0, 1fr) auto; 580 gap: 24px; 581 align-items: center; 582 padding: calc(16px * var(--kd)) 0; 583 border-bottom: 1px solid var(--text-2); 584 color: inherit; 585 text-decoration: none; 586 transition: background-color 180ms ease; 587 } 588 .date-row[data-state="onsale"], 589 .date-row[data-state="presale"] { cursor: pointer; } 590 .date-row[data-state="onsale"]:hover, 591 .date-row[data-state="onsale"]:focus-visible, 592 .date-row[data-state="presale"]:hover, 593 .date-row[data-state="presale"]:focus-visible { 594 background: rgba(208, 49, 49, 0.06); 595 box-shadow: -12px 0 0 rgba(208, 49, 49, 0.06), 12px 0 0 rgba(208, 49, 49, 0.06); 596 outline: none; 597 } 598 .date-row:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; } 599 .date-when { 600 font-family: var(--display); 601 font-weight: 800; 602 font-size: calc(32px * var(--kd)); 603 line-height: calc(32px * var(--kd)); 604 letter-spacing: 0.04em; 605 text-transform: uppercase; 606 color: var(--accent); 607 white-space: nowrap; 608 } 609 .date-where { 610 display: flex; flex-wrap: wrap; align-items: baseline; gap: 0 10px; 611 font-family: var(--display); 612 font-size: calc(32px * var(--kd)); 613 line-height: calc(32px * var(--kd)); 614 letter-spacing: 0.04em; 615 text-transform: uppercase; 616 min-width: 0; 617 } 618 .date-city { font-weight: 900; color: var(--ink); } 619 .date-venue { font-weight: 400; color: var(--text-2); } 620 .date-support { 621 flex-basis: 100%; 622 font-family: var(--sans); 623 font-size: calc(14px * var(--k)); line-height: calc(20px * var(--k)); 624 letter-spacing: 0.02em; text-transform: none; 625 color: var(--text-3); 626 margin-top: 4px; 627 } 628 .date-cta { 629 display: inline-flex; align-items: center; justify-content: center; 630 min-height: calc(54px * var(--kd)); padding: calc(16px * var(--kd)) calc(24px * var(--kd)); 631 border-radius: 2px; 632 background: var(--accent); color: var(--white); 633 font-family: var(--sans); font-weight: 500; font-size: calc(16px * var(--kd)); line-height: 1.4; letter-spacing: 0.02em; 634 white-space: nowrap; 635 transition: background-color 160ms ease; 636 } 637 .date-row:hover .date-cta { background: var(--accent-dark); } 638 /* Inert states â sold out + not-yet-on-sale read as grey. */ 639 .date-row[data-state="soldout"] .date-cta, 640 .date-row[data-state="coming"] .date-cta { 641 background: var(--hairline); color: var(--text-3); 642 } 643 .date-row[data-state="soldout"] { cursor: not-allowed; } 644 .date-row[data-state="soldout"] .date-when { color: var(--text-3); } 645 .date-row[data-state="soldout"] .date-city { color: var(--text-2); } 646 .date-row[data-state="coming"] { cursor: default; } 647 648 .dates-empty { 649 padding: 32px 0; 650 font-size: calc(18px * var(--k)); line-height: 1.4; color: var(--text-2); 651 border-bottom: 1px solid var(--text-2); 652 } 653 654 /* "See all dates" pushdown â rows past the first five collapse. */ 655 .dates-list .is-hidden { 656 max-height: 0; padding-block: 0; border-bottom: 0; 657 opacity: 0; transform: translateY(-6px); overflow: hidden; pointer-events: none; 658 transition: max-height 320ms ease, padding 320ms ease, opacity 260ms ease-out, 659 transform 320ms cubic-bezier(0.22, 1, 0.36, 1), margin 320ms ease; 660 } 661 .dates-list .region-divider.is-hidden { margin-top: 0; }
662 .dates-list.is-expanded .is-hidden { 663 max-height: 240px; padding-block: 16px; border-bottom: 1px solid var(--text-2); 664 opacity: 1; transform: none; pointer-events: auto; 665 transition-delay: calc(var(--i, 0) * 45ms); 666 } 667 .dates-list.is-expanded .region-divider.is-hidden { padding-block: 0 22px; margin-top: 56px; } 668 .dates-toggle { margin: 56px auto 0; min-width: 266px; } 669 .dates-toggle[hidden] { display: none; } 670 671 @media (max-width: 700px) { 672 .date-row { grid-template-columns: minmax(0, 1fr) auto; gap: 4px 16px; } 673 .date-when { grid-column: 1; font-size: calc(24px * var(--k)); line-height: calc(26px * var(--k)); } 674 .date-where { grid-column: 1; font-size: calc(24px * var(--k)); line-height: calc(26px * var(--k)); gap: 0 8px; } 675 .date-cta { grid-column: 2; grid-row: 1 / 3; align-self: center; min-height: 48px; padding: 12px 18px; font-size: calc(15px * var(--k)); } 676 .date-support { font-size: calc(13px * var(--k)); line-height: calc(18px * var(--k)); } 677 .region-divider { font-size: calc(28px * var(--k)); padding-bottom: 14px; } 678 .dates-list.is-expanded .is-hidden { max-height: 320px; } 679 } 680 681 /* ============================================================ 682 PULL QUOTES â Dharma quote marks 140px, red opening, ink 683 closing (rotated), lead phrase in red. Figma 31:556 / 31:562. 684 ============================================================ */ 685 .pull-quote .section-inner { width: min(1080px, calc(100vw - 32px)); max-width: none; padding-top: clamp(56px, 8vw, 128px); padding-bottom: clamp(56px, 8vw, 128px); } 686 /* The quote marks live inside the blockquote's side padding: their size is 687 derived from it and they're offset by exactly it, so they stay on screen 688 at every width (they were hanging off the edge below ~1500px). */ 689 .pull-quote blockquote { --qpad: clamp(28px, 7vw, 110px); position: relative; margin: 0; padding: 0 var(--qpad); } 690 /* Zero-width inline anchors at the start of the first line and the end of 691 the last line; the marks hang off those, so they hug the text wherever 692 it wraps and can't leave the container (the gutter is their headroom). 693 Needs the split JS below â the no-JS fallback keeps the <em> lead. */ 694 .pull-quote .qa { position: relative; display: inline-block; width: 0; height: 1em; vertical-align: top; } 695 /* No-JS fallback: the static marks sit at the paragraph's top-left and 696 bottom-right rather than hanging off the (JS-made) anchors. */ 697 .pull-quote blockquote:not(.is-split) .quote-mark--open { right: auto; left: calc(-1 * var(--qpad)); } 698 .pull-quote blockquote:not(.is-split) .quote-mark--close { left: auto; right: calc(-1 * var(--qpad)); top: auto; bottom: -0.5em; } 699 .pull-quote blockquote p { 700 position: relative; 701 font-family: var(--display); 702 font-weight: 800; 703 /* Tracks the viewport so the quote fills the window with intent: 704 ~36px at 1440, ~48px at 1920, capped at 60px. (Dharma is condensed â 705 the column is held at 1080px so it sets on ~3 lines, per the Figma.) */ 706 font-size: clamp(24px, 2.5vw, 60px); 707 line-height: 1.1; 708 letter-spacing: 0.04em; 709 color: var(--ink); 710 text-wrap: balance; 711 } 712 .pull-quote blockquote p em { font-style: normal; color: var(--accent); } 713 /* Scroll-locked fill. JS splits the text into letters (--i) and sets --p 714 (0â1) on the blockquote as it travels up the viewport; each letter mixes 715 ink â red once the sweep reaches its index. Before JS runs the <em> 716 lead phrase is red and the rest ink, as in the Figma. */ 717 .pull-quote blockquote.is-split p { color: var(--ink); } 718 .pull-quote blockquote.is-split p em { color: inherit; } 719 .pull-quote .w { white-space: nowrap; } 720 .pull-quote .ch { 721 --f: clamp(0, calc(var(--p, 0) * var(--n, 1) - var(--i, 0)), 1); 722 color: color-mix(in srgb, var(--accent) calc(var(--f) * 100%), var(--ink)); 723 } 724 .quote-mark { 725 position: absolute; 726 font-family: var(--display); 727 font-weight: 800; 728 font-size: min(4.375em, calc(var(--qpad) * 1.6)); /* 140/32 of the quote, capped by the gutter */ 729 line-height: 1; 730 letter-spacing: 0.04em; 731 color: var(--accent); 732 user-select: none; 733 } 734 .quote-mark--open { right: 0.14em; top: -0.26em; } 735 /* Rotated 180°, so the ink sits at the bottom of its box: pull the box up 736 so the ink hangs just under the last line, like the Figma. */ 737 .quote-mark--close { left: 0.18em; top: -0.44em; color: var(--ink); transform: rotate(180deg); } 738 .pull-quote cite { 739 display: block; 740 margin-top: 28px; 741 font-family: var(--sans); font-style: normal; font-weight: 400; 742 font-size: 16px; line-height: 1.4; color: var(--ink); 743 } 744 @media (max-width: 700px) { 745 .quote-mark--open { top: -0.3em; right: 0.1em; } 746 .quote-mark--close { left: 0.12em; } 747 } 748 749 /* ============================================================ 750 SINGLE â artwork + copy, then a 16:9 clip. (Section is a partial.) 751 ============================================================ */ 752 .single .section-inner { width: var(--wide-container); } 753 .single .section-lede { max-width: 800px; } 754 .video-player { 755 position: relative; 756 aspect-ratio: 16 / 9; 757 max-width: calc(1080px * var(--k)); 758 margin: 32px auto 0; 759 background: var(--panel); 760 overflow: hidden; 761 } 762 .video-player > img { width: 100%; height: 100%; object-fit: cover; } 763 .video-player iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; } 764 .video-play { 765 position: absolute; top: 50%; left: 50%; 766 transform: translate(-50%, -50%); 767 width: 64px; height: 64px; padding: 0; border: 0; 768 background: transparent; cursor: pointer; 769 transition: transform 200ms ease; 770 } 771 .video-play img { width: 100%; height: 100%; } 772 .video-play:hover, .video-play:focus-visible { transform: translate(-50%, -50%) scale(1.08); } 773 .video-play:focus-visible { outline: 2px solid var(--white); outline-offset: 6px; border-radius: 50%; } 774 775 /* ============================================================ 776 PROMO CARD â single artwork + copy + two CTAs. Figma 31:495. 777 ============================================================ */ 778 .promo-grid { 779 display: grid; 780 gap: clamp(28px, 4vw, 50px); 781 align-items: center; 782 max-width: calc(960px * var(--k)); 783 margin: 0 auto; 784 text-align: left; 785 } 786 @media (min-width: 780px) { .promo-grid { grid-template-columns: calc(468px * var(--k)) minmax(0, 1fr); } } 787 .promo-art { 788 position: relative; display: block; 789 aspect-ratio: 1 / 1; width: 100%; 790 border: 2px solid var(--text-2); 791 overflow: hidden; background: var(--white); 792 } 793 .promo-art img { width: 100%; height: 100%; object-fit: cover; transition: transform 400ms ease; } 794 .promo-art:hover img, .promo-art:focus-visible img { transform: scale(1.02); } 795 .promo-art:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; } 796 .promo-art:hover, .promo-art:focus-visible { box-shadow: 0 12px 30px rgba(0, 0, 0, 0.12); } 797 .single-clip { margin-top: clamp(48px, 6vw, 80px); } 798 .single-signup { margin-top: 32px; } 799 .promo-copy .section-heading { text-align: left; } 800 .promo-copy .section-lede { margin: 0 0 32px; text-align: left; } 801 .promo-ctas { display: flex; flex-wrap: wrap; gap: 10px; } 802 803 /* ============================================================ 804 STORE â three product tiles on a dark gradient. 805 ============================================================ */ 806 .store-grid { 807 display: grid; 808 grid-template-columns: 1fr; 809 gap: 40px; 810 max-width: calc(960px * var(--k)); 811 margin: 48px auto 0; 812 } 813 @media (min-width: 640px) { .store-grid { grid-template-columns: repeat(3, 1fr); gap: 30px; } } 814 .product { 815 display: flex; flex-direction: column; gap: 16px; 816 padding: 0 0 24px; 817 text-align: left; color: inherit; text-decoration: none; 818 transition: transform 200ms ease; 819 } 820 a.product:hover, a.product:focus-visible { transform: translateY(-2px); outline: none; } 821 a.product:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; } 822 .product-img { 823 aspect-ratio: 1 / 1; width: 100%; 824 background: linear-gradient(224.58deg, #423823 3.17%, #241F13 95%); 825 display: flex; align-items: center; justify-content: center; 826 overflow: hidden; 827 } 828 .product-img img { width: 100%; height: 100%; object-fit: cover; } 829 .product-meta { font-weight: 500; font-size: calc(11px * var(--k)); line-height: 1.4; letter-spacing: 0.02em; text-transform: uppercase; color: var(--text-2); } 830 .product-name { 831 font-family: var(--display); font-weight: 800; font-size: calc(24px * var(--k)); line-height: 1.1; letter-spacing: 0.04em; 832 color: var(--ink); 833 } 834 .product-price { font-weight: 500; font-size: calc(16px * var(--k)); color: var(--ink); } 835 .product .btn { width: 100%; margin-top: auto; } 836 .shop-all-link { display: inline-block; margin: 48px 0 0; font-size: calc(16px * var(--k)); } 837 838 /* ============================================================ 839 NEWSLETTER â dark, Mick photo left, copy + CTA right. 840 ============================================================ */ 841 .newsletter { 842 position: relative; 843 background: var(--panel); 844 color: var(--white);
845 min-height: min(935px, 90vh); 846 display: flex; align-items: center; 847 isolation: isolate; 848 overflow: hidden; 849 } 850 /* Parallax layer â taller than the section, shifted by JS as the section 851 travels through the viewport. */ 852 .newsletter-bg { 853 position: absolute; left: 0; right: 0; top: -15%; height: 130%; z-index: -2; 854 background: url("/announcement-assets/newsletter-bg.webp") center / cover no-repeat; 855 will-change: transform; 856 } 857 .newsletter::before { 858 content: ""; position: absolute; inset: 0; z-index: -1; 859 background: linear-gradient(90deg, rgba(16, 16, 15, 0.1) 0%, rgba(16, 16, 15, 0.55) 55%, rgba(16, 16, 15, 0.75) 100%); 860 } 861 .newsletter-inner { 862 width: var(--wide-container); 863 margin: 0 auto; 864 padding: clamp(64px, 10vw, 140px) 0; 865 display: flex; justify-content: flex-end; 866 } 867 .newsletter-panel { width: min(calc(492px * var(--k)), 100%); text-align: left; } 868 .newsletter-panel h2 { font-size: clamp(32px, 4vw, calc(40px * var(--k))); margin-bottom: 12px; } 869 .newsletter-panel .lede { font-size: calc(18px * var(--k)); line-height: 1.5; margin: 0 0 24px; } 870 .newsletter-panel .micro { text-align: center; color: var(--icon-muted); } 871 @media (max-width: 780px) { 872 .newsletter-bg { background-image: url("/announcement-assets/newsletter-bg-960.webp"); background-position: 30% center; } 873 .newsletter::before { background: linear-gradient(180deg, rgba(16, 16, 15, 0.35) 0%, rgba(16, 16, 15, 0.8) 60%); } 874 .newsletter-inner { justify-content: center; } 875 .newsletter-panel { text-align: center; } 876 } 877 878 /* ============================================================ 879 FOOTER â Figma 31:635. 880 ============================================================ */ 881 .site-footer { 882 background: var(--footer); 883 color: var(--white); 884 padding: clamp(48px, 6vw, 80px) 0 calc(clamp(40px, 6vw, 80px) + env(safe-area-inset-bottom)); 885 } 886 .footer-inner { width: var(--wide-container); margin: 0 auto; display: flex; flex-direction: column; gap: 56px; } 887 .footer-primary { display: flex; justify-content: space-between; align-items: flex-start; gap: 32px; flex-wrap: wrap; } 888 .footer-heading { 889 font-family: var(--wide); font-weight: 700; font-size: calc(24px * var(--k)); line-height: calc(32px * var(--k)); 890 letter-spacing: 0; text-transform: uppercase; color: var(--white); margin: 0 0 22px; 891 } 892 .footer-socials { display: flex; gap: 18px; align-items: center; } 893 .footer-socials a { display: inline-flex; width: 22px; height: 22px; position: relative; } 894 .footer-socials a:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; } 895 .footer-socials img { width: 22px; height: 22px; transition: opacity 160ms ease; } 896 .footer-socials a:hover img { opacity: 0.7; } 897 .footer-socials a::after { 898 content: attr(aria-label); 899 position: absolute; bottom: calc(100% + 10px); left: 50%; 900 transform: translateX(-50%) translateY(4px); 901 background: var(--white); color: var(--ink); 902 padding: 5px 9px; font-size: calc(11px * var(--k)); font-weight: 500; line-height: 1; white-space: nowrap; border-radius: 2px; 903 opacity: 0; pointer-events: none; transition: opacity 180ms ease, transform 180ms ease; 904 } 905 .footer-socials a:hover::after, .footer-socials a:focus-visible::after { opacity: 1; transform: translateX(-50%) translateY(0); } 906 .footer-stay { text-align: right; } 907 .footer-stay a { font-weight: 500; font-size: calc(16px * var(--k)); line-height: calc(20px * var(--k)); letter-spacing: 0; text-transform: uppercase; } 908 .footer-stay a:hover, .footer-stay a:focus-visible { color: var(--accent); } 909 .footer-secondary a { text-decoration: underline; text-decoration-color: transparent; text-underline-offset: 4px; transition: color 180ms ease, text-decoration-color 180ms ease; } 910 .footer-secondary { 911 display: flex; justify-content: space-between; gap: 16px; flex-wrap: wrap; 912 font-size: calc(14px * var(--k)); line-height: calc(20px * var(--k)); color: var(--icon-muted); 913 } 914 .footer-secondary a:hover, .footer-secondary a:focus-visible { color: var(--white); text-decoration-color: currentColor; } 915 .footer-legal { font-size: calc(12px * var(--k)); line-height: calc(18px * var(--k)); letter-spacing: 0; color: var(--icon-muted); text-align: center; } 916 @media (max-width: 640px) { 917 .footer-primary { flex-direction: column; } 918 .footer-stay { text-align: left; } 919 } 920 921 /* ============================================================ 922 SIGN-UP SHEET â native <dialog>
922, bottom sheet. Holds a second 923 OpenStage iframe, created on first open. Opened by date rows 924 in the pre-sale state, the newsletter CTA and the footer link. 925 ============================================================ */ 926 .signup-sheet { 927 padding: 0; border: 0; background: transparent; 928 max-width: 100vw; width: 100vw; margin: 0; 929 position: fixed; inset: auto 0 0 0; top: auto; 930 color: var(--ink); 931 overflow: visible; 932 transform: translateY(100%); 933 transition: transform 340ms cubic-bezier(0.22, 1, 0.36, 1), 934 overlay 340ms ease allow-discrete, display 340ms ease allow-discrete; 935 } 936 .signup-sheet[open] { transform: translateY(0); } 937 @starting-style { .signup-sheet[open] { transform: translateY(100%); } } 938 /* Closing: JS adds .is-closing, waits for the transition, then close(). 939 Without that, close() drops the dialog out of the top layer instantly 940 and the backdrop just vanishes. */ 941 .signup-sheet[open].is-closing { transform: translateY(100%); } 942 .signup-sheet::backdrop { 943 background: rgba(29, 29, 27, 0); 944 transition: background-color 300ms ease, overlay 340ms ease allow-discrete, display 340ms ease allow-discrete; 945 } 946 .signup-sheet[open]::backdrop { background: rgba(29, 29, 27, 0.45); } 947 .signup-sheet[open].is-closing::backdrop { background: rgba(29, 29, 27, 0); } 948 @starting-style { .signup-sheet[open]::backdrop { background: rgba(29, 29, 27, 0); } } 949 .sheet-panel { 950 background: var(--ground); 951 border-radius: 16px 16px 0 0; 952 padding: 28px clamp(16px, 4vw, 40px) calc(24px + env(safe-area-inset-bottom)); 953 display: flex; flex-direction: column; gap: 12px; 954 max-width: calc(580px * var(--k)); margin: 0 auto; 955 max-height: calc(100dvh - 24px); overflow-y: auto; 956 box-shadow: 0 -20px 60px rgba(0, 0, 0, 0.35); 957 text-align: center; 958 } 959 .sheet-panel h2 { font-size: clamp(32px, 5vw, calc(40px * var(--k))); } 960 .sheet-panel .lede { font-size: calc(16px * var(--k)); line-height: 1.5; color: var(--text-2); } 961 .sheet-panel .os-embed { margin-top: 12px; } 962 .sheet-close { 963 appearance: none; display: block; margin: 4px auto 0; 964 background: transparent; border: 0; color: var(--text-3); 965 font-family: var(--sans); font-size: calc(15px * var(--k)); padding: 8px 16px; cursor: pointer; 966 text-decoration: underline; text-underline-offset: 4px; 967 } 968 .sheet-close:hover, .sheet-close:focus-visible { color: var(--ink); outline: none; } 969 .sheet-x { 970 position: absolute; top: 12px; right: 12px; 971 width: 40px; height: 40px; padding: 0; border: 0; border-radius: 50%; 972 background: transparent; color: var(--text-3); font-size: calc(28px * var(--k)); line-height: 1; cursor: pointer; 973 display: flex; align-items: center; justify-content: center; 974 } 975 .sheet-x:hover, .sheet-x:focus-visible { color: var(--ink); background: rgba(29, 29, 27, 0.06); outline: none; } 976 .sheet-panel { position: relative; } 977 978 /* ============================================================ 979 VENDOR PICKER â a show with more than one ticket outlet in 980 Bandsintown (V4) opens this instead of linking straight 981 through. Ported from simpleminds.com, re-dressed in SR tokens. 982 ============================================================ */ 983 .vendor-modal { 984 padding: 0; border: 0; background: transparent; color: var(--ink); 985 width: min(calc(460px * var(--k)), calc(100vw - 32px)); max-width: calc(100vw - 32px); 986 margin: auto; height: fit-content; max-height: calc(100dvh - 32px); 987 opacity: 0; scale: 0.96; 988 transition: opacity 220ms ease, scale 260ms cubic-bezier(0.22, 1, 0.36, 1), 989 overlay 260ms ease allow-discrete, display 260ms ease allow-discrete; 990 } 991 .vendor-modal[open] { opacity: 1; scale: 1; } 992 @starting-style { .vendor-modal[open] { opacity: 0; scale: 0.96; } } 993 .vendor-modal::backdrop { 994 background: rgba(29, 29, 27, 0); 995 transition: background-color 260ms ease, overlay 260ms ease allow-discrete, display 260ms ease allow-discrete; 996 } 997 .vendor-modal[open]::backdrop { background: rgba(29, 29, 27, 0.45); } 998 @starting-style { .vendor-modal[open]::backdrop { background: rgba(29, 29, 27, 0); } } 999 .vendor-panel { 1000 position: relative; background: var(--ground); border-radius: 12px; 1001 padding: 28px clamp(20px, 4vw, 32px) 20px; 1002 max-height: calc(100dvh - 32px); overflow-y: auto; 1003 box-shadow: 0 24px 80px rgba(0, 0, 0, 0.3); 1004 }
1005 .vendor-panel h2 { font-size: clamp(30px, 4.5vw, calc(36px * var(--k))); margin: 0 48px 6px 0; } 1006 .vendor-show { margin: 0 0 18px; font-size: calc(15px * var(--k)); line-height: 1.4; color: var(--text-2); } 1007 .vendor-list { list-style: none; margin: 0; padding: 0; border-top: 1px solid rgba(29, 29, 27, 0.14); } 1008 .vendor-link { 1009 display: flex; align-items: center; justify-content: space-between; gap: 16px; 1010 padding: 16px 4px; border-bottom: 1px solid rgba(29, 29, 27, 0.14); 1011 color: var(--ink); text-decoration: none; 1012 } 1013 .vendor-link:hover, .vendor-link:focus-visible { background: rgba(29, 29, 27, 0.04); outline: none; border-radius: 0; } 1014 .vendor-link:focus-visible .vendor-name { text-decoration: underline; text-underline-offset: 4px; } 1015 .vendor-text { display: flex; flex-direction: column; gap: 3px; min-width: 0; } 1016 .vendor-name { font-weight: 500; font-size: calc(17px * var(--k)); line-height: 1.25; } 1017 .vendor-meta { font-size: calc(13px * var(--k)); line-height: 1.3; color: var(--text-3); } 1018 .vendor-arrow { flex: 0 0 auto; width: 18px; height: 18px; color: var(--accent); } 1019 .vendor-modal .sheet-close { margin-top: 12px; } 1020 1021 /* ============================================================ 1022 COOKIE banner â same pattern as simpleminds.com. GTM only 1023 loads after "Got it". 1024 ============================================================ */ 1025 .cookie-banner { 1026 position: fixed; left: 50%; bottom: clamp(16px, 2vw, 32px); 1027 transform: translateX(-50%) translateY(20px); 1028 width: min(440px, calc(100vw - 32px)); 1029 background: var(--panel); color: var(--white); 1030 padding: clamp(24px, 3vw, 36px); 1031 box-shadow: 0 20px 60px rgba(0, 0, 0, 0.35), 0 4px 16px rgba(0, 0, 0, 0.2); 1032 opacity: 0; pointer-events: none; 1033 transition: opacity 500ms ease, transform 500ms ease; 1034 z-index: 90; 1035 display: flex; flex-direction: column; gap: 24px; 1036 } 1037 .cookie-banner.is-visible { opacity: 1; transform: translateX(-50%) translateY(0); pointer-events: auto; } 1038 @media (min-width: 900px) { 1039 .cookie-banner { left: auto; right: clamp(16px, 2vw, 32px); transform: translateY(20px); } 1040 .cookie-banner.is-visible { transform: translateY(0); } 1041 } 1042 .cookie-copy { font-size: calc(16px * var(--k)); line-height: 1.4; } 1043 .cookie-copy a { display: inline-block; margin-top: 12px; text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 4px; opacity: 0.85; } 1044 .cookie-copy a:hover, .cookie-copy a:focus-visible { opacity: 1; color: var(--accent); } 1045 .cookie-accept { 1046 appearance: none; border: 0; width: 100%; min-height: 54px; padding: 16px 24px; 1047 background: var(--accent); color: var(--white); 1048 font-family: var(--sans); font-size: calc(16px * var(--k)); font-weight: 500; line-height: 1.4; cursor: pointer; border-radius: 2px; 1049 transition: background-color 160ms ease; 1050 } 1051 .cookie-accept:hover { background: var(--accent-dark); } 1052 .cookie-accept:focus-visible { outline: 2px solid var(--white); outline-offset: 3px; } 1053 1054 @media (prefers-reduced-motion: reduce) { 1055 html { scroll-behavior: auto; } 1056 .intro .navbar, .intro .band, .intro .cue { opacity: 1; animation: none; } 1057 .intro .wipe { -webkit-mask-position: 0% 0; mask-position: 0% 0; animation: none; } 1058 .armed .r { opacity: 1; transform: none; transition: none; } 1059 .js .reveal { opacity: 1; transform: none; transition: none; } 1060 .cue { display: none; } 1061 .signup-sheet, .signup-sheet::backdrop, .cookie-banner { transition: none; } 1062 } 1063</style> 1064</head> 1065<body> 1066<div id="page" class="intro"> 1067 1068<a class="skip-link" href="#signup">Skip to sign-up</a> 1069 1070<!-- Fixed nav pill. "simplyred.com â" goes to the real WP homepage via the 1071 ?full=1 bypass (see the mu-plugin). Plain "/" would loop back here. --> 1072<div class="navbar"> 1073 <nav class="nav" aria-label="Page sections"> 1074 <a class="is-active" href="#tour" data-nav="tour" aria-current="true">Tour</a> 1075 <a href="#store" data-nav="music">Music</a> 1076 <a class="is-out" href="/?full=1" data-continue>simplyred.com <span aria-hidden="true">→</span></a> 1077 </nav> 1078</div> 1079 1080<!-- Crawler-visible nav â existing WP URLs --> 1081<nav class="vh" aria-label="Simply Red site sections"> 1082 <ul> 1083 <li><a href="/?full=1">Home</a></li> 1084 <li><a href="/tour-dates/">Tour dates</a></li> 1085 <li><a href="/live-in-santiago/">Live in Santiago</a></li> 1086 <li><a href="/recollections/">Recollections</a></li> 1087 <li><a href="/archive/">Archive</a></li> 1088 <li><a href="https://store.simplyred.com/">Shop</a></li> 1089 <li><a href="https://www.simplyred.com/privacy-policy/">Privacy policy</a></li> 1090 <li><a href="https://www.simplyred.com/terms-conditions/">Terms and conditions</a></li> 1091 </ul> 1092</nav> 1093 1094<main> 1095 1096<!-- ==============================================================
1097 HERO â Dan's prototype. Sticky stage; bands scatter in on load 1098 and straighten on scroll. Then the page carries on. 1099 ============================================================== --> 1100<header class="hero-scroll" id="heroScroll" aria-label="Simply Red â Summer Funk â27"> 1101 <div class="hero-sticky"> 1102 <div class="stage" id="stage"> 1103 <h1 class="vh">Simply Red â Summer Funk â27 Tour</h1> 1104 1105 <div class="logo"><img class="wipe" src="/announcement-assets/logo.svg" alt="" width="608" height="66" fetchpriority="high"></div> 1106 <div class="script"><img class="wipe" src="/announcement-assets/script.svg" alt="" width="437" height="82"></div> 1107 1108 <div class="kv" id="kv" aria-hidden="true"> 1109 <div class="band b-purple"><div class="band-inner"><img src="/announcement-assets/band-purple.webp" alt="" width="170" height="756"></div></div> 1110 <div class="band b-yellow"><div class="band-inner"><img src="/announcement-assets/band-yellow.webp" alt="" width="168" height="756"></div></div> 1111 <div class="band b-red"><div class="band-inner"><img src="/announcement-assets/band-red.webp" alt="" width="166" height="688"></div></div> 1112 <div class="band b-green"><div class="band-inner"><img src="/announcement-assets/band-green.webp" alt="" width="146" height="760"></div></div> 1113 <div class="band b-blue"><div class="band-inner"><img src="/announcement-assets/band-blue.webp" alt="" width="146" height="752"></div></div> 1114 <div class="band b-orange"><div class="band-inner"><img src="/announcement-assets/band-orange.webp" alt="" width="168" height="756"></div></div> 1115 <div class="band b-mick"><div class="band-inner"><img src="/announcement-assets/mick.webp" alt="" width="984" height="1011" fetchpriority="high"></div></div> 1116 </div> 1117 1118 <a class="cue" id="cue" href="#signup"> 1119 <span>Scroll</span> 1120 <svg width="15" height="30" viewBox="0 0 15.0604 30.2737" fill="none" aria-hidden="true"> 1121 <path d="M14.4764 22.3531C14.2631 22.1399 13.9158 22.1399 13.7025 22.3531L7.52679 28.5289L1.35104 22.3531C1.13639 22.1467 0.794611 22.1494 0.584051 22.36C0.373505 22.5705 0.370771 22.9123 0.577215 23.127L7.13972 29.6895C7.353 29.9028 7.70028 29.9028 7.91353 29.6895L14.476 23.127C14.6893 22.9137 14.6896 22.5664 14.4764 22.3531Z" fill="currentColor" stroke="currentColor" stroke-width="0.848485" stroke-linejoin="bevel"/> 1122 <path d="M14.476 7.04064L7.91353 0.478144C7.70025 0.264861 7.35297 0.264861 7.13972 0.478144L0.577215 7.04064C0.370771 7.25529 0.373505 7.59707 0.584051 7.80763C0.794597 8.01818 1.13639 8.02091 1.35104 7.81447L7.52679 1.63872L13.7025 7.81447C13.9172 8.02091 14.259 8.01818 14.4695 7.80763C14.6801 7.59709 14.6825 7.2553 14.476 7.04064Z" fill="currentColor" stroke="currentColor" stroke-width="0.636364"/> 1123 <path d="M11.3557 15.0837C11.3557 13.536 10.4233 12.1401 8.99319 11.5469C7.56176 10.9549 5.91564 11.2817 4.82049 12.3768C3.72534 13.4719 3.39862 15.118 3.99061 16.5495C4.58396 17.9796 5.97987 18.912 7.52736 18.912C9.64104 18.9093 11.3529 17.1975 11.3557 15.0837ZM4.79319 15.0837C4.79319 13.9776 5.459 12.9809 6.48166 12.5571C7.50296 12.1346 8.67872 12.3684 9.46076 13.1505C10.2428 13.9325 10.4766 15.1083 10.0541 16.1295C9.6303 17.1522 8.6336 17.818 7.52753 17.818C6.01816 17.8167 4.79456 16.5931 4.79319 15.0837Z" fill="currentColor" stroke="currentColor" stroke-width="0.848485" stroke-linejoin="bevel"/> 1124 </svg> 1125 </a> 1126 </div> 1127 </div> 1128</header> 1129 1130<!-- ============================================================== 1131 SIGN-UP â "First to know". The OpenStage form does the capture. 1132 ============================================================== --> 1133<section class="signup" id="signup" aria-labelledby="signupHeading"> 1134 <div class="signup-inner"> 1135 <h2 class="r" id="signupHeading" style="--d:0ms">First <em>to know</em></h2> 1136 <p class="lede r" style="--d:90ms" 1137 data-copy-announce="Sign up for first access to the artist pre-sale, plus tour news, extra dates and ticket releases."
1138 data-copy-presale="The artist pre-sale is open now. Sign up for access, plus tour news, extra dates and ticket releases." 1139 data-copy-onsale-uk="The EU artist pre-sale is open now. Sign up for access, plus tour news, extra dates and ticket releases." 1140 data-copy-onsale="Sign up for tour news, extra dates and ticket releases."> 1141 Sign up for first access to the artist pre-sale, plus tour news, extra dates and ticket releases. 1142 </p> 1143 1144 <div class="os-embed r" style="--d:180ms"> 1145 <!-- OpenStage embed, as supplied. loading="lazy" keeps it out of the 1146 hero's critical path. --> 1147 <iframe 1148 width="500" height="340" 1149 src="https://simplyred.os.fan/website-sign-up-form" 1150 title="Openstage Fan Signup" 1151 loading="lazy" 1152 frameborder="0" scrolling="no" 1153 style="max-width: 100%; color-scheme: none;" 1154 allow="geolocation; microphone; camera; fullscreen; payment" 1155 sandbox="allow-same-origin allow-scripts allow-forms allow-popups"></iframe> 1156 <noscript><p>Sign up at <a href="https://simplyred.os.fan/website-sign-up-form">simplyred.os.fan</a>.</p></noscript> 1157 </div> 1158 1159 <p class="micro r" style="--d:500ms">No spam · Unsubscribe any time</p> 1160 1161 <p class="share-label r" style="--d:570ms">Send this to whoever you’re going with</p> 1162 <div class="share" data-share> 1163 <a class="r" style="--d:640ms" data-share-email href="#" rel="noopener">Email</a> 1164 <a class="r" style="--d:690ms" data-share-whatsapp href="#" rel="noopener" target="_blank">WhatsApp</a> 1165 <a class="r" style="--d:740ms" data-share-x href="#" rel="noopener" target="_blank">X</a> 1166 <a class="r" style="--d:790ms" data-share-facebook href="#" rel="noopener" target="_blank">Facebook</a> 1167 </div> 1168 </div> 1169</section> 1170 1171<!-- ============================================================== 1172 TOUR â state-driven lede; trailer; dates by region. 1173 Rows hydrate from Bandsintown. Until the tour is published 1174 there, the list shows a "dates to be announced" line rather 1175 than fake rows. ?mock=1 renders a review dataset instead. 1176 ============================================================== --> 1177<section class="tour reveal" id="tour" aria-labelledby="tourHeading"> 1178 <div class="section-inner"> 1179 <h2 class="section-heading" id="tourHeading">The Summer Funk ‘27 <em>Tour</em></h2> 1180 <p class="section-lede" 1181 data-copy-announce="UK and Europe, Summer 2027. Sign up for first access to the artist pre-sale from 22 September. General sale opens 24 September for the UK shows, and 25 September for the European shows." 1182 data-copy-presale="UK and Europe, Summer 2027. The artist pre-sale is open now â sign up for access. General sale opens 24 September for the UK shows, and 25 September for the European shows." 1183 data-copy-onsale-uk="UK and Europe, Summer 2027. UK tickets are available now. The artist pre-sale for EU is open now â sign up for access. EU General sale opens 25 September for the European shows." 1184 data-copy-onsale="UK and Europe, Summer 2027. Tickets on sale now."> 1185 UK and Europe, Summer 2027. Sign up for first access to the artist pre-sale from 22 September. General sale opens 24 September for the UK shows, and 25 September for the European shows. 1186 </p> 1187 1188 <!-- Tour trailer â 16:9, full-bleed, self-hosted. Click to play, with 1189 sound, from the button centred on the poster (no autoplay â client, 1190 15 Sep). Pauses if scrolled away. The mp4s are placeholders. --> 1191 <figure class="tour-video" data-tour-video> 1192 <img class="tour-video-poster" src="/announcement-assets/tour-trailer-poster-v2.webp" alt="" width="1600" height="1067" loading="lazy"> 1193 <video 1194 data-trailer-video 1195 data-src-large="https://sr-summer-funk-media.eleven-eleven-studio.workers.dev/trailer-1080.mp4" 1196 data-src-small="https://sr-summer-funk-media.eleven-eleven-studio.workers.dev/trailer-720.mp4" 1197 muted loop playsinline preload="none" 1198 poster="/announcement-assets/tour-trailer-poster-v2.webp" 1199 aria-label="Simply Red â Summer Funk â27 tour trailer" 1200 hidden> 1201 <source data-trailer-source type="video/mp4"> 1202 </video> 1203 <div class="tour-video-overlay"> 1204 <img src="/announcement-assets/logo.svg" alt="" width="501" height="55" aria-hidden="true"> 1205 <img src="/announcement-assets/script-white.svg" alt="" width="360" height="68" aria-hidden="true"> 1206 <button type="button" class="btn" data-trailer-play>Watch the tour trailer</button> 1207 </div> 1208 <button type="button" class="video-mute" data-trailer-mute aria-pressed="false" aria-label="Unmute trailer" hidden>
1209 <svg class="icon-muted" viewBox="0 0 24 24" aria-hidden="true"><path d="M3 9v6h4l5 5V4L7 9H3zm13.59 3L14 9.41 15.41 8 18 10.59 20.59 8 22 9.41 19.41 12 22 14.59 20.59 16 18 13.41 15.41 16 14 14.59 16.59 12z"/></svg> 1210 <svg class="icon-unmuted" viewBox="0 0 24 24" aria-hidden="true"><path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z"/></svg> 1211 </button> 1212 <figcaption class="vh">Simply Red â Summer Funk â27 tour trailer</figcaption> 1213 </figure> 1214 1215 <div class="dates-list" data-dates-list id="tour-dates" aria-live="polite"> 1216 <!-- Rendered by JS from Bandsintown. ?mock=1 renders a review dataset 1217 through the same pipeline; nothing here is real until BIT is. --> 1218 <p class="dates-empty">Dates to be announced shortly.</p> 1219 </div> 1220 <button type="button" class="btn dates-toggle" data-dates-toggle aria-expanded="false" aria-controls="tour-dates" hidden> 1221 <span data-dates-toggle-label>See all dates ›</span> 1222 </button> 1223 </div> 1224</section> 1225 1226<!-- ============================================================== 1227 QUOTE â Mick on the tour. 1228 ============================================================== --> 1229<section class="pull-quote" aria-label="A word from Mick Hucknall"> 1230 <div class="section-inner"> 1231 <blockquote> 1232 <p><span class="quote-mark quote-mark--open" aria-hidden="true">“</span><em>We can’t wait to get back out on the road again in Summer 2027</em> to perform at some incredible and historic venues across the UK and Europe. You can expect a set full of Simply Red upbeat hits to get you on your feet having fun in the sun with friends and family.<span class="quote-mark quote-mark--close" aria-hidden="true">“</span></p> 1233 <cite>Mick Hucknall</cite> 1234 </blockquote> 1235 </div> 1236</section> 1237 1238<!-- ============================================================== 1239 REACH OUT IâLL BE THERE â the single. Order per client (15 Sep): 1240 artwork + Coming soon + Pre-save â the clip â sign-up line â Mick. 1241 Release day: at SR.SINGLE_OUT_AT the copy flips to Out now + Listen now 1242 (same smart link). ?single=soon|out forces either for review. 1243 The clip plays via YouTube once SR.REACH_OUT_YOUTUBE_ID is set; until 1244 then the play badge is hidden. 1245 ============================================================== --> 1246<section class="single reveal" id="single" aria-labelledby="promoHeading"> 1247 <div class="section-inner"> 1248 <div class="promo-grid"> 1249 <a class="promo-art" href="https://simplyred.lnk.to/ReachOut" rel="noopener" target="_blank" aria-label="Pre-save Reach Out Iâll Be There" data-single-art> 1250 <img src="/announcement-assets/reachout-artwork.webp" alt="Reach Out Iâll Be There â Simply Red single artwork" width="1200" height="1200" loading="lazy"> 1251 </a> 1252 <div class="promo-copy"> 1253 <h2 class="section-heading" id="promoHeading">Coming <em>soon</em></h2> 1254 <p class="section-lede" data-single-lede>Simply Red’s live take on the Four Tops classic, released on 24 September.</p> 1255 <div class="promo-ctas"> 1256 <a class="btn" href="https://simplyred.lnk.to/ReachOut" data-presave rel="noopener" target="_blank">Pre-save</a> 1257 </div> 1258 </div> 1259 </div> 1260 1261 <!-- Clip + its sign-up line: hidden until SR.CLIP_AT (?clip=1 previews). --> 1262 <div class="single-clip-wrap" data-single-clip hidden> 1263 <div class="video-player single-clip" data-video-player> 1264 <img src="/announcement-assets/reachout-still-v3.webp" alt="Mick Hucknall performing Reach Out Iâll Be There live on stage" width="1280" height="720" loading="lazy"> 1265 <button type="button" class="video-play" data-video-play aria-label="Play the Reach Out Iâll Be There clip"> 1266 <img src="/announcement-assets/play-btn.svg" alt="" width="64" height="64"> 1267 </button> 1268 </div> 1269 1270 <p class="section-lede single-signup">Sign up to be the first to watch the full live performance of ‘Reach Out I’ll Be There’, filmed on the 40th anniversary tour, 2025.</p> 1271 <p class="section-cta"><button type="button" class="btn" data-open-sheet data-sheet-context="single">Sign up</button></p> 1272 </div> 1273 </div> 1274</section> 1275 1276<!-- ============================================================== 1277 QUOTE â Mick on the single. 1278 ============================================================== --> 1279<section class="pull-quote" aria-label="Mick Hucknall on Reach Out Iâll Be There"> 1280 <div class="section-inner"> 1281 <blockquote> 1282 <p><span class="quote-mark quote-mark--open" aria-hidden="true">“</span><em>We’re really pleased to bring you our live cover of ‘Reach Out I’ll Be There’.</em> We had a great time performing it on our 40th anniversary tour last year, and it will help keep you going until you can see Simply Red live again next Summer.<span class="quote-mark quote-mark--close" aria-hidden="true">“</span></p> 1283 <cite>Mick Hucknall</cite> 1284 </blockquote> 1285 </div> 1286</section> 1287
1288<script> 1289 /* Reach Out â config + clip player. Lives in the partial so nothing about 1290 the single is in the page until the partial is inlined. The partial sits 1291 above the page's CONFIG script, so wait for it: SR isn't defined yet. */ 1292 document.addEventListener("DOMContentLoaded", function () { 1293 SR.REACH_OUT_YOUTUBE_ID = "xVHUZCqs_WI"; // "Reach Out (Live Version) Trailer", unlisted 1294 SR.CLIP_AT = "2026-09-24T09:45:00+01:00"; // clip + sign-up line appear, BST (brought forward 24 Sep) 1295 var clipWrap = document.querySelector("[data-single-clip]"); 1296 if (clipWrap && (/[?&]clip=1/.test(location.search) || Date.now() >= Date.parse(SR.CLIP_AT))) clipWrap.hidden = false; 1297 SR.PRESAVE_URL = "https://simplyred.lnk.to/ReachOut"; 1298 SR.SINGLE_OUT_AT = "2026-09-24T00:00:00+01:00"; // release, BST 1299 var forced = /[?&]single=(soon|out)/i.exec(location.search); 1300 var out = forced ? forced[1].toLowerCase() === "out" : Date.now() >= Date.parse(SR.SINGLE_OUT_AT); 1301 if (out) { 1302 var h = document.getElementById("promoHeading"); 1303 var lede = document.querySelector("[data-single-lede]"); 1304 var art = document.querySelector("[data-single-art]"); 1305 var btn = document.querySelector("[data-presave]"); 1306 if (h) h.innerHTML = "Out <em>now</em>"; 1307 if (lede) lede.innerHTML = "Simply Red’s live take on the Four Tops classic, out now."; 1308 if (art) art.setAttribute("aria-label", "Listen to Reach Out Iâll Be There"); 1309 if (btn) btn.textContent = "Listen now"; 1310 } 1311 SR.SHEET_COPY = SR.SHEET_COPY || {}; 1312 SR.SHEET_COPY.single = { 1313 h: "Be the first <em>to watch</em>", 1314 p: "Sign up for free and weâll send you the full live performance of âReach Out Iâll Be Thereâ as soon as itâs released, plus tour news and ticket releases." 1315 }; 1316 var player = document.querySelector("[data-video-player]"); 1317 var play = document.querySelector("[data-video-play]"); 1318 var presave = document.querySelector("[data-presave]"); 1319 if (presave && SR.PRESAVE_URL) presave.setAttribute("href", SR.PRESAVE_URL); 1320 1321 // No clip URL yet â the play button is hidden. Never route this to the 1322 // sign-up sheet. 1323 if (!SR.REACH_OUT_YOUTUBE_ID && play) play.hidden = true; 1324 1325 function playClip() { 1326 if (!SR.REACH_OUT_YOUTUBE_ID) return; 1327 if (!player || player.querySelector("iframe")) return; 1328 var f = document.createElement("iframe"); 1329 f.setAttribute("src", "https://www.youtube-nocookie.com/embed/" + SR.REACH_OUT_YOUTUBE_ID + "?autoplay=1&rel=0&modestbranding=1"); 1330 f.setAttribute("title", "Simply Red â Reach Out Iâll Be There"); 1331 f.setAttribute("allow", "autoplay; encrypted-media; picture-in-picture; fullscreen"); 1332 f.setAttribute("allowfullscreen", ""); 1333 player.appendChild(f); 1334 if (play) play.hidden = true; 1335 window.SR_track("sr_clip_played", { clip: "single" }); 1336 } 1337 if (play) play.addEventListener("click", playClip); 1338 }); 1339 1340</script>
1340 1341 1342<!-- ============================================================== 1343 STORE â three tiles. Titles, prices, URLs and "limited edition" 1344 flags checked against the live store pages 15 Sep 2026 (packshots 1345 are the store's own). "Buy now", not "Add to cart": no cart here. 1346 ============================================================== --> 1347<section class="store reveal" id="store" aria-labelledby="storeHeading"> 1348 <div class="section-inner"> 1349 <h2 class="section-heading" id="storeHeading">The official <em>store</em></h2> 1350 <p class="section-lede">Signed editions, coloured vinyl and bundles you will not find anywhere else.</p> 1351 1352 <div class="store-grid"> 1353 <a class="product" href="https://store.simplyred.com/product/holding-back-the-years-artbook" rel="noopener" target="_blank"> 1354 <div class="product-img"><img src="/announcement-assets/product-santiago-artbook.webp" alt="Holding Back The Years: 40 Years of Simply Red, Live in Santiago â Collectors Edition 4-disc artbook" width="800" height="800" loading="lazy"></div> 1355 <p class="product-meta">Limited edition</p> 1356 <p class="product-name">Holding Back The Years: 40 Years of Simply Red, Live in Santiago Collectors Edition 4Disc Artbook</p> 1357 <p class="product-price">£70</p> 1358 <span class="btn">Buy now</span> 1359 </a> 1360 <a class="product" href="https://store.simplyred.com/product/holding-back-the-years-marble-vinyl-lp-triple-heavyweight-lp" rel="noopener" target="_blank"> 1361 <div class="product-img"><img src="/announcement-assets/product-santiago-vinyl.webp" alt="Holding Back The Years: 40 Years of Simply Red, Live in Santiago â Special Edition marble vinyl 3LP" width="800" height="800" loading="lazy"></div> 1362 <p class="product-meta">Limited edition</p> 1363 <p class="product-name">Holding Back The Years: 40 Years of Simply Red, Live in Santiago Special Edition Marble Vinyl 3LP</p> 1364 <p class="product-price">£55</p> 1365 <span class="btn">Buy now</span> 1366 </a> 1367 <a class="product" href="https://simplyred.tmstor.es/product/recollections" rel="noopener" target="_blank"> 1368 <div class="product-img"><img src="/announcement-assets/product-recollections-3cd.webp" alt="Recollections 3CD" width="800" height="800" loading="lazy"></div> 1369 <p class="product-meta">3CD</p> 1370 <p class="product-name">Recollections 3CD</p> 1371 <p class="product-price">£15</p> 1372 <span class="btn">Buy now</span> 1373 </a> 1374 </div> 1375 1376 <a class="text-link shop-all-link" href="https://store.simplyred.com/" rel="noopener" target="_blank">Shop the full Simply Red store ›</a> 1377 </div> 1378</section> 1379 1380</main> 1381 1382<!-- ============================================================== 1383 NEWSLETTER â dark. One CTA into the sign-up sheet rather than a 1384 second drawn form (OpenStage owns the fields). 1385 ============================================================== --> 1386<section class="newsletter reveal" id="newsletter" aria-labelledby="newsletterHeading"> 1387 <div class="newsletter-bg" data-parallax aria-hidden="true"></div> 1388 <div class="newsletter-inner"> 1389 <div class="newsletter-panel"> 1390 <h2 class="section-heading" id="newsletterHeading">Never miss a <em>Simply Red moment</em></h2> 1391 <p class="lede">Tour news, new music and store drops, straight to your inbox.</p> 1392 <button type="button" class="btn btn--block" data-open-sheet data-sheet-context="newsletter">Sign up now</button> 1393 <p class="micro">No spam · Unsubscribe any time</p> 1394 </div> 1395 </div> 1396</section> 1397 1398<!-- ============================================================== 1399 FOOTER 1400 ============================================================== --> 1401<footer class="site-footer"> 1402 <div class="footer-inner"> 1403 <div class="footer-primary"> 1404 <div> 1405 <p class="footer-heading">Follow Simply Red</p> 1406 <nav class="footer-socials" aria-label="Simply Red on social platforms"> 1407 <a href="https://www.facebook.com/simplyred/" rel="noopener" target="_blank" aria-label="Facebook"><img src="/announcement-assets/social/facebook.svg" alt="" width="22" height="22"></a> 1408 <a href="https://www.tiktok.com/@simplyredofficial" rel="noopener" target="_blank" aria-label="TikTok"><img src="/announcement-assets/social/tiktok.svg" alt="" width="22" height="22"></a> 1409 <a href="https://www.instagram.com/simplyredofficial/" rel="noopener" target="_blank" aria-label="Instagram"><img src="/announcement-assets/social/instagram.svg" alt="" width="22" height="22"></a> 1410 <a href="https://www.youtube.com/channel/UCwtvFWbWkLr8ra51W-pLstQ" rel="noopener" target="_blank" aria-label="YouTube"><img src="/announcement-assets/social/youtube.svg" alt="" width="22" height="22"></a> 1411 </nav> 1412 </div> 1413 <div class="footer-stay"> 1414 <p class="footer-heading">Stay in touch</p> 1415 <a href="#signup" data-open-sheet data-sheet-context="footer">Sign up for the mailing list</a> 1416 </div> 1417 </div> 1418 <div class="footer-secondary"> 1419 <p>© 2026 Simply Red. All rights reserved. <a href="https://www.simplyred.com/terms-conditions/">Terms and Conditions</a> | <a href="https://www.simplyred.com/privacy-policy/">Privacy Policy</a></p> 1420 <p><a href="https://11-11.studio" rel="noopener" target="_blank">Website by 11:11 Studio</a></p> 1421 </div> 1422 <div class="footer-legal"> 1423 <p>This site and all the material contained therein © copyright simplyred.com ltd.</p> 1424 <p>simplyred.com ltd (reg no 4260793) is incorporated in England & Wales and is registered</p> 1425 <p>c/o OJK Limited 3rd Floor Colwyn Chambers, York Street, Manchester, M2 3BA. Vat Reg No 785 4256 94</p> 1426 </div> 1427 </div> 1428</footer> 1429 1430</div><!-- /#page --> 1431 1432<!-- ============================================================== 1433 SIGN-UP SHEET â the OpenStage form in a bottom sheet. iframe is 1434 created on first open so the page only ever loads one copy of 1435 the form up front. 1436 ============================================================== -->
1437<dialog class="signup-sheet" data-signup-sheet aria-labelledby="sheetHeading"> 1438 <div class="sheet-panel"> 1439 <button type="button" class="sheet-x" data-close-sheet aria-label="Close">×</button> 1440 <h2 id="sheetHeading" class="display" 1441 data-copy-announce="Pre-sale <em>registration</em>" 1442 data-copy-presale="Pre-sale <em>access</em>" 1443 data-copy-onsale-uk="Pre-sale <em>access</em>" 1444 data-copy-onsale="Stay <em>in touch</em>">Pre-sale <em>registration</em></h2> 1445 <p class="lede" 1446 data-copy-announce="Sign up for free and weâll send your artist pre-sale access before tickets go on general sale." 1447 data-copy-presale="Sign up for free and weâll send your artist pre-sale access. General sale opens 24 September for the UK shows, and 25 September for the European shows." 1448 data-copy-onsale-uk="Sign up for free and weâll send your artist pre-sale access for the European shows. General sale opens 25 September." 1449 data-copy-onsale="Tour news, new music and store drops, straight to your inbox."> 1450 Sign up for free and weâll send your artist pre-sale access before tickets go on general sale. 1451 </p> 1452 <div class="os-embed" data-sheet-embed> 1453 <iframe 1454 width="500" height="340" 1455 data-src="https://simplyred.os.fan/website-sign-up-form" 1456 title="Openstage Fan Signup" 1457 frameborder="0" scrolling="no" 1458 style="max-width: 100%; color-scheme: none;" 1459 allow="geolocation; microphone; camera; fullscreen; payment" 1460 sandbox="allow-same-origin allow-scripts allow-forms allow-popups"></iframe> 1461 </div> 1462 <button type="button" class="sheet-close" data-close-sheet>Close</button> 1463 </div> 1464</dialog> 1465 1466<!-- Vendor picker â filled by JS from the Bandsintown V4 offers for the 1467 clicked show. Only opens when a show lists more than one outlet. --> 1468<dialog class="vendor-modal" data-vendor-modal aria-labelledby="vendorHeading"> 1469 <div class="vendor-panel"> 1470 <button type="button" class="sheet-x" data-close-vendor aria-label="Close">×</button> 1471 <h2 id="vendorHeading" class="display">Buy <em>tickets</em></h2> 1472 <p class="vendor-show" data-vendor-show></p> 1473 <ul class="vendor-list" data-vendor-list></ul> 1474 <button type="button" class="sheet-close" data-close-vendor>Cancel</button> 1475 </div> 1476</dialog> 1477 1478<!-- Cookie consent --> 1479<div class="cookie-banner" role="region" aria-label="Cookie consent" aria-live="polite" hidden> 1480 <p class="cookie-copy"> 1481 This website uses cookies to ensure you get the best experience on our website. 1482 <a href="https://www.simplyred.com/privacy-policy/" rel="noopener">Learn more</a> 1483 </p> 1484 <button type="button" class="cookie-accept" data-cookie-accept>Got it</button> 1485</div> 1486
1487<script> 1488 /* ========================================================= 1489 CONFIG â the things that change between now and general 1490 sale live here. Everything else derives from them. 1491 ========================================================= */ 1492 var SR = { 1493 // Campaign timeline (BST). Times are placeholders until the promoter 1494 // confirms them â see docs/landing-page-plan.md â Open items. 1495 PRESALE_AT: "2026-09-22T10:00:00+01:00", 1496 ONSALE_AT_UK: "2026-09-24T10:00:00+01:00", // UK shows 1497 ONSALE_AT: "2026-09-25T09:00:00+01:00", // European shows: 10:00 CEST = 09:00 BST (per-show times in EU_SALES) 1498 // trailer-1080.mp4 / trailer-720.mp4 are PLACEHOLDERS (a 12 s push-in 1499 // on the poster, silent audio track) so the player + sound control are 1500 // reviewable. Replace both files with the real trailer, same names. 1501 TRAILER_READY: true, 1502 // Bandsintown 1503 BIT_APP_ID: "js_www.simplyred.com", 1504 BIT_ARTIST_ID: "6644", 1505 BIT_ARTIST: "Simply Red", 1506 SHARE_URL: "https://www.simplyred.com/", 1507 SHARE_TEXT: "Simply Red â Summer Funk â27 tour. Sign up for pre-sale access." 1508 }; 1509 1510 /* ========================================================= 1511 PAGE STATE â announce â presale â onsale-uk â onsale, by clock. 1512 ?state=announce|presale|onsale-uk|onsale forces one for review. 1513 ========================================================= */ 1514 function pageState() { 1515 var m = /[?&]state=(announce|presale|onsale-uk|onsale)/i.exec(location.search); 1516 if (m) return m[1].toLowerCase(); 1517 var now = Date.now(); 1518 if (now >= Date.parse(SR.ONSALE_AT)) return "onsale"; 1519 if (now >= Date.parse(SR.ONSALE_AT_UK)) return "onsale-uk"; 1520 if (now >= Date.parse(SR.PRESALE_AT)) return "presale"; 1521 return "announce"; 1522 } 1523 var STATE = pageState(); 1524 document.documentElement.setAttribute("data-state", STATE); 1525 1526 // Swap state copy on every element that carries data-copy-<state>. 1527 document.querySelectorAll("[data-copy-" + STATE + "]").forEach(function (el) { 1528 var v = el.getAttribute("data-copy-" + STATE); 1529 if (el.tagName === "H2") el.innerHTML = v; else el.textContent = v; 1530 }); 1531 1532 /* ========================================================= 1533 HERO â Dan's prototype motion. Band start offsets in KV 1534 space from Figma 47:2253. They resolve over the first ~45% of a screen of scroll. 1535 ========================================================= */ 1536 (function () { 1537 var reduce = window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches; 1538 var page = document.getElementById("page"); 1539 var stage = document.getElementById("stage"); 1540 var scroller = document.getElementById("heroScroll"); 1541 var sticky = scroller.querySelector(".hero-sticky"); 1542 var kv = document.getElementById("kv"); 1543 var cue = document.getElementById("cue"); 1544 var signup = document.getElementById("signup"); 1545 1546 var BANDS = [ 1547 { sel: ".b-orange", dx: -159.99, rot: -6.31, end: 1.00 }, 1548 { sel: ".b-blue", dx: -102.02, rot: 3.09, end: 0.82 }, 1549 { sel: ".b-green", dx: -35.05, rot: -2.68, end: 0.68 }, 1550 { sel: ".b-red", dx: 53.00, rot: -2.92, end: 0.72 }, 1551 { sel: ".b-yellow", dx: 136.03, rot: -3.06, end: 0.86 }, 1552 { sel: ".b-purple", dx: 179.96, rot: 8.75, end: 1.00 }, 1553 { sel: ".b-mick", dx: 0, dy: 70, rot: 0, end: 0.90 } 1554 ]; 1555 BANDS.forEach(function (b) { b.el = stage.querySelector(b.sel + " .band-inner"); }); 1556 1557 function easeOutQuart(t) { return 1 - Math.pow(1 - t, 4); } 1558 function clamp01(v) { return v < 0 ? 0 : v > 1 ? 1 : v; } 1559 1560 var MOBILE = false, KV_S = 1; 1561 // Phone rest positions: the outer strips slide inward so all six stay 1562 // on screen at a scale where Mick fills the width. KV-space px. 1563 var MOBILE_REST = { ".b-orange": 72, ".b-blue": 30, ".b-yellow": -30, ".b-purple": -72 }; 1564 function paint(p) { 1565 for (var i = 0; i < BANDS.length; i++) { 1566 var b = BANDS[i]; 1567 var e = easeOutQuart(clamp01(p / b.end)); 1568 // Phone: strips rest tucked inward and only scatter ~35% as far, 1569 // so the outer ones stay on screen before the user scrolls. 1570 var rest = MOBILE ? (MOBILE_REST[b.sel] || 0) : 0; 1571 var amp = MOBILE ? 0.35 : 1; 1572 b.el.style.transform = 1573 "translate3d(" + (rest + b.dx * amp * (1 - e)).toFixed(2) + "px," + 1574 ((b.dy || 0) * (1 - e)).toFixed(2) + "px,0) rotate(" + 1575 (b.rot * (1 - e)).toFixed(3) + "deg)"; 1576 } 1577 var breathe = 1 + 0.016 * (1 - easeOutQuart(p)); 1578 kv.style.transform = MOBILE 1579 ? "translateX(-50%) scale(" + (KV_S * breathe).toFixed(4) + ")" 1580 : "scale(" + breathe.toFixed(4) + ")"; 1581 if (cue && cue.classList.contains("is-live")) cue.style.opacity = String(clamp01(1 - p / 0.25)); 1582 } 1583 1584 /* Fit the 1440Ã852 stage to the viewport. Desktop: scale so the whole 1585 composition fills the window â up on a big display, down on a small 1586 one (both axes considered, so nothing is cropped). Narrow screens:
1587 scale so the key visual (plus the flying-in bands) fills the width â 1588 the stage is centred, so the logo, script and Mick stay in frame and 1589 the outer band edges crop. Artwork is exported at 3Ã so it holds up 1590 to ~2.5Ã scale. */ 1591 var fittedW = 0; 1592 function fit(force) { 1593 var w = window.innerWidth, h = window.innerHeight; 1594 // Phones fire resize as the browser chrome collapses on scroll; the 1595 // height changes but the width doesn't. Rebuilding the hero to the 1596 // new height mid-scroll is the "jump" â so on phones only refit when 1597 // the width changes (rotation), and let the layout stand otherwise. 1598 if (!force && w < 720 && w === fittedW) return; 1599 fittedW = w; 1600 var root = document.documentElement.style; 1601 MOBILE = w < 720; 1602 if (MOBILE) { 1603 // Own composition: fill the phone screen. Logo under the nav, key 1604 // visual scaled to the space left and pinned to the bottom. Mick 1605 // (328 wide in KV space) is kept on screen; the outer strips may 1606 // crop on narrow phones, which is fine â they're the frame. 1607 var navEl = document.querySelector(".nav"); 1608 var navB = navEl ? navEl.getBoundingClientRect().bottom : 60; 1609 var logoW = Math.min(w * 0.86, 520), logoH = logoW * 66.44 / 608.618; 1610 var scriptW = logoW * 0.72, scriptH = scriptW * 82.21 / 436.825; 1611 var logoTop = navB + 64, scriptTop = logoTop + logoH + 14; 1612 var availH = h - (scriptTop + scriptH + 12); 1613 KV_S = Math.max(0.6, Math.min(availH / 452, (w / 328) * 0.96)) * 0.9; 1614 root.setProperty("--s", 1); 1615 root.setProperty("--navS", 1); 1616 root.setProperty("--stageBase", h + "px"); 1617 root.setProperty("--logoTop", logoTop + "px"); 1618 root.setProperty("--logoW", logoW + "px"); 1619 root.setProperty("--scriptTop", scriptTop + "px"); 1620 root.setProperty("--scriptW", scriptW + "px"); 1621 paint(current); 1622 return; 1623 } 1624 KV_S = 1; 1625 // The composition (logo + bands at their flung-out start positions) 1626 // spans ~860px of the 1440 stage. Fit that, with a little air, rather 1627 // than the full stage width â otherwise a tablet or small desktop 1628 // window shows the artwork small in the middle of empty ground. 1629 var s = Math.min((w - 32) / 1000, h / 852); 1630 root.setProperty("--s", s); 1631 root.setProperty("--navS", Math.min(1, s)); 1632 root.setProperty("--stageBase", (852 * s) + "px"); 1633 } 1634 1635 function armReveal() { 1636 if (reduce || !("IntersectionObserver" in window)) return; 1637 signup.classList.add("armed"); 1638 new IntersectionObserver(function (entries) { 1639 entries.forEach(function (e) { 1640 if (e.isIntersecting) e.target.classList.add("in"); 1641 else if (e.boundingClientRect.top > 0) e.target.classList.remove("in"); 1642 }); 1643 }, { threshold: 0, rootMargin: "0px 0px -15% 0px" }).observe(signup); 1644 } 1645 1646 var target = 0, current = 0, raf = null; 1647 1648 if (reduce) { 1649 current = 1; fit(true); paint(1); 1650 window.addEventListener("resize", function () { fit(); }); 1651 return; 1652 } 1653 function readScroll() { 1654 // Resolve over the first 45% of the hero's height of scroll. 1655 var len = sticky.offsetHeight * 0.45; 1656 target = clamp01(len > 0 ? (window.scrollY || window.pageYOffset || 0) / len : 1); 1657 if (!raf) raf = requestAnimationFrame(tick); 1658 } 1659 function tick() { 1660 current += (target - current) * 0.12; 1661 if (Math.abs(target - current) < 0.0004) current = target; 1662 paint(current); 1663 raf = (current === target) ? null : requestAnimationFrame(tick); 1664 } 1665 1666 if (cue) cue.addEventListener("animationend", function () { cue.classList.add("is-live"); readScroll(); }); 1667 fit(true); 1668 paint(0); 1669 armReveal(); 1670 readScroll(); 1671 window.addEventListener("scroll", readScroll, { passive: true }); 1672 window.addEventListener("resize", function () { fit(); readScroll(); }); 1673 })(); 1674 1675 /* ========================================================= 1676 Section reveals + nav active state. 1677 ========================================================= */ 1678 (function () { 1679 var els = document.querySelectorAll(".reveal"); 1680 if (!("IntersectionObserver" in window)) {
1681 els.forEach(function (el) { el.classList.add("is-visible"); }); 1682 return; 1683 } 1684 var io = new IntersectionObserver(function (entries) { 1685 entries.forEach(function (e) { 1686 if (e.isIntersecting) { e.target.classList.add("is-visible"); io.unobserve(e.target); } 1687 }); 1688 }, { threshold: 0.08, rootMargin: "0px 0px -60px 0px" }); 1689 els.forEach(function (el) { io.observe(el); }); 1690 1691 // Nav pill: "Tour" is active from the top down to the Music section 1692 // (the single's section when it's in the page, otherwise the store); "Music" from there on. 1693 var navLinks = document.querySelectorAll("[data-nav]"); 1694 var music = document.getElementById("single") || document.getElementById("store"); 1695 var musicLink = document.querySelector('[data-nav="music"]'); 1696 if (musicLink && music) musicLink.setAttribute("href", "#" + music.id); 1697 function setActive(key) { 1698 navLinks.forEach(function (a) { 1699 var on = a.getAttribute("data-nav") === key; 1700 a.classList.toggle("is-active", on); 1701 if (on) a.setAttribute("aria-current", "true"); else a.removeAttribute("aria-current"); 1702 }); 1703 } 1704 if (music) { 1705 new IntersectionObserver(function (entries) { 1706 entries.forEach(function (e) {
1707 setActive(e.isIntersecting || e.boundingClientRect.top < 0 ? "music" : "tour"); 1708 }); 1709 }, { rootMargin: "-40% 0px -55% 0px" }).observe(music); 1710 // Anything past the store is "Music" territory too. 1711 window.addEventListener("scroll", function () { 1712 if (music.getBoundingClientRect().top < window.innerHeight * 0.4) setActive("music"); else setActive("tour"); 1713 }, { passive: true }); 1714 } 1715 })(); 1716 1717 /* ========================================================= 1718 Share row â populated from SR.SHARE_URL / SHARE_TEXT. 1719 ========================================================= */ 1720 (function () { 1721 var u = encodeURIComponent(SR.SHARE_URL), t = encodeURIComponent(SR.SHARE_TEXT); 1722 var map = { 1723 "[data-share-email]": "mailto:?subject=" + encodeURIComponent("Simply Red â Summer Funk â27") + "&body=" + t + "%0A%0A" + u, 1724 "[data-share-whatsapp]": "https://wa.me/?text=" + t + "%20" + u, 1725 "[data-share-x]": "https://twitter.com/intent/tweet?text=" + t + "&url=" + u, 1726 "[data-share-facebook]": "https://www.facebook.com/sharer/sharer.php?u=" + u 1727 }; 1728 Object.keys(map).forEach(function (sel) { 1729 var a = document.querySelector(sel); 1730 if (a) a.setAttribute("href", map[sel]); 1731 }); 1732 var wrap = document.querySelector("[data-share]"); 1733 if (wrap) wrap.addEventListener("click", function (e) { 1734 var a = e.target.closest("a"); if (!a) return; 1735 window.SR_track("sr_share_clicked", { share_channel: a.textContent.trim().toLowerCase() }); 1736 }); 1737 })(); 1738 1739 /* ========================================================= 1740 Sign-up sheet â native <dialog>. iframe src set on first open. 1741 ========================================================= */ 1742 var sheet = document.querySelector("[data-signup-sheet]"); 1743 // Sheet heading + lede follow the page state, unless the trigger's context 1744 // has its own copy in SR.SHEET_COPY (set by a partial, e.g. the single). 1745 var sheetHead = sheet && sheet.querySelector("#sheetHeading"); 1746 var sheetLede = sheet && sheet.querySelector(".lede"); 1747 var sheetDefault = sheet && { h: sheetHead.innerHTML, p: sheetLede.textContent }; 1748 function openSheet(context) { 1749 if (!sheet) return; 1750 var copy = (SR.SHEET_COPY && SR.SHEET_COPY[context]) || sheetDefault; 1751 sheetHead.innerHTML = copy.h; sheetLede.textContent = copy.p; 1752 var frame = sheet.querySelector("iframe[data-src]"); 1753 if (frame) { frame.setAttribute("src", frame.getAttribute("data-src")); frame.removeAttribute("data-src"); } 1754 if (typeof sheet.showModal === "function") sheet.showModal(); else sheet.setAttribute("open", ""); 1755 document.body.classList.add("no-scroll"); 1756 var isTouch = window.matchMedia && window.matchMedia("(pointer: coarse)").matches; 1757 if (isTouch && document.activeElement && document.activeElement.blur) document.activeElement.blur(); 1758 window.SR_track("sr_signup_sheet_opened", { sheet_context: context || "unknown", page_state: STATE }); 1759 } 1760 var sheetClosing = null; 1761 function closeSheet() { 1762 if (!sheet || !sheet.open || sheetClosing) return; 1763 var reduce = window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches; 1764 function finish() { 1765 sheetClosing = null; 1766 sheet.classList.remove("is-closing"); 1767 if (typeof sheet.close === "function") sheet.close(); else sheet.removeAttribute("open"); 1768 document.body.classList.remove("no-scroll"); 1769 } 1770 if (reduce) { finish(); return; } 1771 sheet.classList.add("is-closing"); 1772 sheetClosing = setTimeout(finish, 360); 1773 } 1774 document.addEventListener("click", function (e) { 1775 var open = e.target.closest("[data-open-sheet]"); 1776 if (open) { e.preventDefault(); openSheet(open.getAttribute("data-sheet-context")); return; } 1777 if (e.target.closest("[data-close-sheet]")) closeSheet(); 1778 }); 1779 if (sheet) { 1780 sheet.addEventListener("click", function (e) { if (e.target === sheet) closeSheet(); }); 1781 sheet.addEventListener("cancel", function (e) { e.preventDefault(); closeSheet(); }); 1782 sheet.addEventListener("close", function () { document.body.classList.remove("no-scroll"); }); 1783 } 1784 1785 /* ========================================================= 1786 Tour dates â Bandsintown hydration + row behaviour. 1787 Row state: 1788 "presale" â CTA "Pre-sale", opens the sign-up sheet 1789 (every UK + EU row before general sale) 1790 "onsale" â CTA "Buy tickets", links to the BIT offer URL 1791 "soldout" â inert 1792 "coming" â "Coming soon", inert (on sale, but no offer yet) 1793 ========================================================= */ 1794 var MONTH_SHORT = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]; 1795 var DAY_SHORT = ["Sun","Mon","Tue","Wed","Thu","Fri","Sat"]; 1796 var COUNTRY_NAME_TO_ISO = { 1797 "United Kingdom":"GB", "United States":"US", "Ireland":"IE", "Germany":"DE", "France":"FR", 1798 "Italy":"IT", "Spain":"ES", "Portugal":"PT", "Netherlands":"NL", "Belgium":"BE", 1799 "Switzerland":"CH", "Austria":"AT", "Denmark":"DK", "Sweden":"SE", "Norway":"NO", 1800 "Finland":"FI", "Poland":"PL", "Czech Republic":"CZ", "Czechia":"CZ", "Hungary":"HU", 1801 "Greece":"GR", "Luxembourg":"LU", "Croatia":"HR", "Slovenia":"SI", "Slovakia":"SK", 1802 "Romania":"RO", "Bulgaria":"BG", "Serbia":"RS", "Turkey":"TR", "Türkiye":"TR", "Monaco":"MC", 1803 "Australia":"AU", "New Zealand":"NZ", "Canada":"CA", "Mexico":"MX", "Japan":"JP", 1804 "South Africa":"ZA", "United Arab Emirates":"AE" 1805 }; 1806 var EU = ["IE","DE","FR","IT","ES","PT","NL","BE","CH","AT","DK","SE","NO","FI","PL","CZ","HU","GR","LU","HR","SI","SK","RO","BG","RS","TR","MC"]; 1807 function regionFor(ev) { 1808 var iso = COUNTRY_NAME_TO_ISO[(ev.venue || {}).country || ""] || ""; 1809 if (iso === "GB") return "UK";
1810 if (EU.indexOf(iso) !== -1) return "Europe"; 1811 return "Rest of world"; 1812 } 1813 var REGION_ORDER = ["UK", "Europe", "Rest of world"]; 1814 1815 /* EU general sale, per show (client sheet "Simply Red 2027 Europe Tour", 1816 ANNOUNCE tab, 24 Sep). Keyed by BIT event id. "10AM local" is 10:00 1817 CEST = 09:00 BST for everything except Lisbon (WEST = BST). `at` gates 1818 the row; `url` is the promoter's general-sale link, used when BIT has no 1819 offer (API lag, 24 Sep). BIT's own offer wins when present. at: null â 1820 not on sale yet (TBD). Times carry their offset â never parsed as local. */ 1821 SR.EU_SALES = { 1822 "108902517": { at: "2026-09-25T10:00:00+01:00", url: "https://simplyred.bol.pt/" }, // Lisbon â 10:00 local = 10:00 BST 1823 "108902527": { at: "2026-09-25T10:00:00+02:00", url: "https://www.ticketmaster.nl/artist/simply-red-tickets/2165" }, // Amsterdam 19 Jun 1824 "108907912": { at: "2026-09-25T10:00:00+02:00", url: "https://www.ticketmaster.nl/artist/simply-red-tickets/2165" }, // Amsterdam 20 Jun 1825 "108907918": { at: "2026-09-25T10:00:00+02:00", url: "https://www.ticketmaster.be/event/613645963?language=en-be&brand=afasdome" }, // Antwerp â client link 24 Sep 1826 "108907927": { at: "2026-09-25T10:00:00+02:00", url: "https://dalessandroegalli.com/events/34552/simply-red/" }, // Venice 1827 "108907952": { at: "2026-09-25T10:00:00+02:00", url: "https://www.ticketmaster.de/artist/2165?venueId=31503&subchannel_id=11911&brand=de_livenation" }, // Munich 1828 "108907957": { at: "2026-09-25T10:00:00+02:00", url: "https://www.ticketmaster.de/artist/2165?venueId=35959&subchannel_id=11911&brand=de_livenation" }, // Nuremberg 1829 "108907960": { at: "2026-09-25T10:00:00+02:00", url: "https://www.ticketmaster.de/artist/2165?venueId=27373&subchannel_id=11911&brand=de_livenation" }, // Fulda 1830 "108907969": { at: "2026-09-25T10:00:00+02:00", url: "https://www.ticketmaster.de/artist/2165?venueId=39685&subchannel_id=11911&brand=de_livenation" }, // Aurich 1831 "108907986": { at: "2026-09-25T10:00:00+02:00", url: "https://www.ticketmaster.de/artist/2165?venueId=38557&subchannel_id=11911&brand=de_livenation" }, // Hemer 1832 "108907996": { at: "2026-09-25T10:00:00+02:00", url: "https://www.ticketmaster.de/artist/2165?venueId=39687&subchannel_id=11911&brand=de_livenation" }, // Hannover 1833 "108908002": { at: "2026-09-25T10:00:00+02:00", url: "https://www.ticketmaster.de/artist/2165?venueId=19391&subchannel_id=11911&brand=de_livenation" }, // Mönchengladbach 1834 "108908114": { at: "2026-09-25T10:00:00+02:00", url: "https://www.ticketmaster.de/artist/2165?venueId=27731&subchannel_id=11911&brand=de_livenation" }, // Bonn 1835 "108908143": { at: "2026-09-25T10:00:00+02:00", url: "https://www.ticketmaster.de/artist/2165?venueId=39197&subchannel_id=11911&brand=de_livenation" }, // Halle (Westf.) 1836 "108908256": { at: "2026-10-09T11:00:00+02:00", url: "https://www.jazzopen-stuttgart.com/" }, // Stuttgart â 9 Oct, 11:00 local 1837 "108908271": { at: "2026-09-25T10:00:00+02:00", url: "https://www.ticketmaster.de/artist/2165?venueId=33199&subchannel_id=11911&brand=de_livenation" }, // Wiesbaden 1838 "108908321": { at: "2026-09-25T10:00:00+02:00", url: "https://www.ticketmaster.de/artist/2165?venueId=31397&subchannel_id=11911&brand=de_livenation" }, // Regensburg 1839 "108908412": { at: "2026-09-25T10:00:00+02:00", url: "https://www.ticketmaster.de/artist/2165?venueId=22571&subchannel_id=11911&brand=de_livenation" }, // Leipzig 1840 "108908455": { at: "2026-09-25T10:00:00+02:00", url: "https://www.oeticket.com/event/simply-red-summer-funk-27-clam-burgarena-22118537/?affiliate=eoe" }, // Klam 1841 "108914691": { at: "2026-09-28T12:00:00+02:00", url: null }, // Locarno â Mon 28 Sep, 12:00 local; no general link yet 1842 "108908466": { at: "2026-09-25T10:00:00+02:00", url: "https://www.ticketmaster.de/artist/2165?venueId=27669&subchannel_id=11911&brand=de_livenation" }, // Füssen 1843 "108914765": { at: "2026-09-25T10:00:00+02:00", url: "https://www.oeticket.com/noapp/event/simply-red-summer-funk-27-viereck-am-fusse-der-burg-hochosterwitz-22137348/?affiliate=H95" }, // Launsdorf (Carinthia) â client link 24 Sep 1844 "108908542": { at: "2026-09-25T10:00:00+02:00", url: "https://www.eventim.hr/artist/simply-red/simply-red-4242148/" }, // Pula
1845 "108914835": { at: null, url: "https://dalessandroegalli.com/events/34552/simply-red/" }, // Rome â on-sale TBD â stays Coming soon until a time is set 1846 "108908549": { at: "2026-09-25T10:00:00+02:00", url: "https://dalessandroegalli.com/events/34552/simply-red/" }, // Pompei 1847 "108909526": { at: "2026-09-25T10:00:00+02:00", url: "https://dalessandroegalli.com/events/34552/simply-red/" }, // Bari 1848 "108908561": { at: "2026-09-25T10:00:00+02:00", url: "https://dalessandroegalli.com/events/34552/simply-red/" } // Lucca 1849 }; 1850 1851 // General sale opens for UK rows on the 24th, everything else on the 25th. 1852 function generalSaleOpen(region) { 1853 if (STATE === "onsale") return true; 1854 if (STATE === "onsale-uk") return region === "UK"; 1855 return false; 1856 } 1857 1858 function bitStateFor(ev) { 1859 var offer = (ev.offers || []).filter(function (o) { return o.type === "Tickets"; })[0]; 1860 if (offer && offer.status === "sold out") return { state: "soldout", url: null }; 1861 // EU shows: the client's per-show on-sale time decides, not the page 1862 // state or BIT's zone-less on_sale_datetime. 1863 var sale = SR.EU_SALES[String(ev.id)]; 1864 if (sale) { 1865 var at = sale.at ? Date.parse(sale.at) : NaN; 1866 if (isNaN(at) || Date.now() < at) return { state: STATE === "onsale" ? "coming" : "presale", url: null }; 1867 if (offer && offer.status === "available") return { state: "onsale", url: offer.url, vendors: ev._vendors || [] }; 1868 if (sale.url) return { state: "onsale", url: sale.url, vendors: ev._vendors || [] }; 1869 return { state: "coming", url: null }; 1870 } 1871 // Before general sale every row registers for the pre-sale, whatever BIT 1872 // says â BIT publishes pre-sale links as status "available", which read 1873 // as general sale (learned the hard way on simpleminds.com, 2026-09-09). 1874 if (!generalSaleOpen(regionFor(ev))) return { state: "presale", url: null }; 1875 var onSale = ev.on_sale_datetime ? new Date(ev.on_sale_datetime) : null; 1876 if (onSale && onSale > new Date()) return { state: "coming", url: null }; 1877 // The public API can lag the BIT site by a while at on-sale (24 Sep: no 1878 // offers for ~an hour while the site had them). If the on-sale time has 1879 // passed and there's no offer yet, BIT's ticket redirect (/t/<event id>) 1880 // already resolves to the ticket page, so use it. Only for events that 1881 // carry an on_sale_datetime â never guessed for the rest. 1882 if (!offer && onSale && ev.id) return { state: "onsale", url: "https://www.bandsintown.com/t/" + encodeURIComponent(ev.id) + "?app_id=" + encodeURIComponent(SR.BIT_APP_ID || "js_www.simplyred.com"), vendors: ev._vendors || [] }; 1883 if (!offer) return { state: "coming", url: null }; 1884 if (offer.status === "available") return { state: "onsale", url: offer.url, vendors: ev._vendors || [] }; 1885 return { state: "coming", url: null }; 1886 } 1887 function ctaTextFor(state) { 1888 if (state === "soldout") return "Sold out"; 1889 if (state === "onsale") return "Buy tickets"; 1890 if (state === "presale") return "Pre-sale"; 1891 return "Coming soon"; 1892 } 1893 function esc(s) { return String(s).replace(/&/g, "&").replace(/</g, "<").replace(/"/g, """); } 1894 1895 function rowHtml(ev) { 1896 // BIT datetime is venue-local with no zone â read the calendar date 1897 // straight off the string so UTC conversion can't shift it a day. 1898 var p = /^(\d{4})-(\d{2})-(\d{2})/.exec(ev.datetime || ""); 1899 var d = p ? new Date(+p[1], +p[2] - 1, +p[3]) : new Date(ev.datetime); 1900 var when = DAY_SHORT[d.getDay()] + " " + MONTH_SHORT[d.getMonth()] + " " + d.getDate(); 1901 var v = ev.venue || {}; 1902 var lineup = (ev.lineup || []).filter(function (a) { return a && a !== SR.BIT_ARTIST; }); 1903 // Country after the venue for non-UK rows; UK rows stay city + venue. 1904 var country = v.country === "United Kingdom" ? "" : (v.country || ""); 1905 var venueText = (v.name || "Venue TBA") + (country ? ", " + country : ""); 1906 var st = bitStateFor(ev); 1907 var attrs = 'data-state="' + st.state + '" data-date="' + esc((ev.datetime || "").slice(0, 10)) + '" data-city="' + esc(v.city || "") + '"'; 1908 if (st.url) attrs += ' data-ticket-url="' + esc(st.url) + '"'; 1909 if (st.vendors && st.vendors.length > 1) attrs += " data-offers='" + JSON.stringify(st.vendors).replace(/&/g, "&").replace(/'/g, "'") + "'"; 1910 return ( 1911 '<div class="date-row" data-date-row ' + attrs + '>' + 1912 '<span class="date-when">' + esc(when) + '</span>' + 1913 '<span class="date-where"><span class="date-city">' + esc(v.city || "") + '</span>' + 1914 '<span class="date-venue">' + esc(venueText) + '</span>' + 1915 (lineup.length ? '<span class="date-support">with ' + esc(lineup.join(", ")) + '</span>' : '') + 1916 '</span>' + 1917 '<span class="date-cta">' + esc(ctaTextFor(st.state)) + '</span>' + 1918 '</div>' 1919 ); 1920 } 1921 1922 /* Skip festival slots: BIT lists every appearance as an event. */ 1923 function isTourDate(ev) { 1924 var title = (ev.title || "").trim().toLowerCase(); 1925 return !title || title.indexOf(SR.BIT_ARTIST.toLowerCase()) !== -1; 1926 } 1927 1928 function hydrateDateRows() { 1929 document.querySelectorAll("[data-date-row]").forEach(function (row) { 1930 var state = row.getAttribute("data-state") || "presale"; 1931 row.setAttribute("data-state", state); 1932 var cta = row.querySelector(".date-cta"); 1933 if (cta) cta.textContent = ctaTextFor(state); 1934 var where = row.querySelector(".date-where"); 1935 var venueText = where ? where.textContent.replace(/\s+/g, " ").trim() : ""; 1936 var action = { soldout: "Sold out:", onsale: "Buy tickets for", presale: "Register for the pre-sale for", coming: "Tickets coming soon for" }[state]; 1937 row.setAttribute("aria-label", action + " " + venueText); 1938 if (state === "onsale" || state === "presale") { 1939 row.setAttribute("role", state === "onsale" ? "link" : "button"); 1940 row.setAttribute("tabindex", "0"); 1941 row.removeAttribute("aria-disabled"); 1942 } else { 1943 row.setAttribute("aria-disabled", "true"); 1944 row.setAttribute("role", "group"); 1945 row.removeAttribute("tabindex"); 1946 } 1947 }); 1948 } 1949 document.addEventListener("click", function (e) { 1950 var row = e.target.closest("[data-date-row]"); 1951 if (!row) return; 1952 var state = row.getAttribute("data-state"); 1953 if (state === "presale") { openSheet("date-row"); return; } 1954 if (state !== "onsale") return; 1955 var vendors = parseVendors(row); 1956 if (vendors.length > 1) { openVendorModal(row, vendors); return; } 1957 var url = row.getAttribute("data-ticket-url"); 1958 if (!url) return; 1959 window.SR_track("sr_ticket_clicked", { ticket_city: row.getAttribute("data-city"), ticket_date: row.getAttribute("data-date") }); 1960 window.open(url, "_blank", "noopener"); 1961 }); 1962 document.addEventListener("keydown", function (e) { 1963 if (e.key !== "Enter" && e.key !== " ") return; 1964 var row = e.target.closest("[data-date-row]"); 1965 if (!row) return; 1966 e.preventDefault(); 1967 row.click(); 1968 }); 1969 1970 /* "See all dates" â first five rows visible, rest pushed down. */ 1971 var datesList = document.querySelector("[data-dates-list]"); 1972 var datesToggle = document.querySelector("[data-dates-toggle]"); 1973 var datesToggleLabel = document.querySelector("[data-dates-toggle-label]"); 1974 var COLLAPSED_ROWS = Infinity; // client (16 Sep): list every show, no "See all dates" 1975 function applyRowVisibility() { 1976 if (!datesList) return; 1977 var kids = Array.prototype.slice.call(datesList.children); 1978 var rowCount = 0, hidden = []; 1979 kids.forEach(function (el) { 1980 el.classList.remove("is-hidden"); 1981 if (el.classList.contains("date-row")) rowCount++; 1982 if (rowCount > COLLAPSED_ROWS || (rowCount === COLLAPSED_ROWS && el.classList.contains("region-divider"))) { 1983 el.classList.add("is-hidden"); hidden.push(el); 1984 } 1985 });
1986 hidden.forEach(function (el, i) { el.style.setProperty("--i", i); }); 1987 if (datesToggle) datesToggle.hidden = hidden.length === 0; 1988 datesList.classList.remove("is-expanded"); 1989 if (datesToggle) datesToggle.setAttribute("aria-expanded", "false"); 1990 if (datesToggleLabel) datesToggleLabel.innerHTML = "See all dates âº"; 1991 } 1992 if (datesToggle) datesToggle.addEventListener("click", function () { 1993 var expanded = !datesList.classList.contains("is-expanded"); 1994 datesList.classList.toggle("is-expanded", expanded); 1995 datesToggle.setAttribute("aria-expanded", expanded ? "true" : "false"); 1996 datesToggleLabel.innerHTML = expanded ? "Show fewer" : "See all dates âº"; 1997 var hidden = datesList.querySelectorAll(".is-hidden"); 1998 hidden.forEach(function (el, i) { el.style.setProperty("--i", expanded ? i : hidden.length - 1 - i); }); 1999 if (!expanded) datesList.scrollIntoView({ behavior: "smooth", block: "start" }); 2000 window.SR_track("sr_dates_toggled", { dates_expanded: expanded }); 2001 }); 2002 2003 function renderEmpty(msg) { 2004 datesList.innerHTML = '<p class="dates-empty">' + esc(msg) + '</p>'; 2005 if (datesToggle) datesToggle.hidden = true; 2006 } 2007 2008 /* Vendor list for one V4 event: available ticket offers, one per vendor 2009 (BIT sometimes lists Ticketmaster twice â keep the official one), 2010 official first. */ 2011 function vendorsFor(ev) { 2012 var seen = {}, out = []; 2013 (ev.offers || []).forEach(function (o) { 2014 if (!o || o.type !== "Tickets" || o.status !== "available" || !o.url) return; 2015 var key = String(o.vendor_name || "").trim().toLowerCase() || o.url; 2016 var v = { name: o.vendor_name || "", url: o.url, official: !!o.official, 2017 min_price: typeof o.min_price === "number" && o.min_price > 0 ? o.min_price : null, currency: o.currency || null }; 2018 if (key in seen) { if (v.official && !out[seen[key]].official) out[seen[key]] = v; return; } 2019 seen[key] = out.length; out.push(v); 2020 }); 2021 return out.sort(function (a, b) { return (b.official ? 1 : 0) - (a.official ? 1 : 0); }); 2022 } 2023 function parseVendors(row) { 2024 try { var l = JSON.parse(row.getAttribute("data-offers") || "[]"); return Array.isArray(l) ? l.filter(function (o) { return o && o.url; }) : []; } 2025 catch (e) { return []; } 2026 } 2027 // BIT names some outlets by domain: a venue box office ("www.theo2.co.uk") 2028 // or a short link ("bit.ly") for the promoter's own page. 2029 function vendorLabel(o) { 2030 var name = String(o.name || "").trim(); 2031 if (!name || /^(bit\.ly|tinyurl\.com|lnk\.to)$/i.test(name)) return { name: "Official tickets", meta: "" }; 2032 if (/^(www\.)?[a-z0-9-]+(\.[a-z0-9-]+)+$/i.test(name)) return { name: "Venue box office", meta: name.replace(/^www\./i, "") }; 2033 return { name: name, meta: "" }; 2034 } 2035 function fromPrice(o) { 2036 if (o.min_price === null || !o.currency) return ""; 2037 try { return "From " + new Intl.NumberFormat("en-GB", { style: "currency", currency: o.currency }).format(o.min_price).replace(/\.00$/, ""); } 2038 catch (e) { return ""; } 2039 } 2040 var vendorModal = document.querySelector("[data-vendor-modal]"); 2041 var vendorList = vendorModal && vendorModal.querySelector("[data-vendor-list]"); 2042 var vendorShow = vendorModal && vendorModal.querySelector("[data-vendor-show]"); 2043 function openVendorModal(row, vendors) { 2044 if (!vendorModal) { window.open(vendors[0].url, "_blank", "noopener"); return; } 2045 var city = row.querySelector(".date-city"), venue = row.querySelector(".date-venue"), when = row.querySelector(".date-when"); 2046 vendorShow.textContent = [city && city.textContent, venue && venue.textContent, when && when.textContent].filter(
2046Boolean).join(" · "); 2047 vendorList.innerHTML = vendors.map(function (o) { 2048 var l = vendorLabel(o); 2049 var meta = [o.official ? "Official" : "", l.meta, fromPrice(o)].filter(Boolean).join(" · "); 2050 return '<li><a class="vendor-link" href="' + esc(o.url) + '" target="_blank" rel="noopener" data-vendor="' + esc(l.name) + '">' + 2051 '<span class="vendor-text"><span class="vendor-name">' + esc(l.name) + '</span>' + 2052 (meta ? '<span class="vendor-meta">' + esc(meta) + '</span>' : '') + '</span>' + 2053 '<svg class="vendor-arrow" viewBox="0 0 18 18" aria-hidden="true" focusable="false"><path d="M4 9h9M9 4l5 5-5 5" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"/></svg>' + 2054 '</a></li>'; 2055 }).join(""); 2056 vendorModal.dataset.city = row.getAttribute("data-city") || ""; 2057 vendorModal.dataset.date = row.getAttribute("data-date") || ""; 2058 if (typeof vendorModal.showModal === "function") vendorModal.showModal(); else vendorModal.setAttribute("open", ""); 2059 document.body.classList.add("no-scroll"); 2060 var isTouch = window.matchMedia && window.matchMedia("(pointer: coarse)").matches; 2061 var first = vendorList.querySelector("a"); 2062 if (first && !isTouch) first.focus({ preventScroll: true }); 2063 else if (document.activeElement && document.activeElement.blur) document.activeElement.blur(); 2064 window.SR_track("sr_vendor_picker_opened", { ticket_city: vendorModal.dataset.city, ticket_date: vendorModal.dataset.date }); 2065 } 2066 if (vendorModal) { 2067 var closeVendor = function () { if (typeof vendorModal.close === "function") vendorModal.close(); else vendorModal.removeAttribute("open"); }; 2068 vendorModal.addEventListener("click", function (e) { 2069 if (e.target === vendorModal || e.target.closest("[data-close-vendor]")) { closeVendor(); return; } 2070 var link = e.target.closest("a.vendor-link"); 2071 if (!link) return; 2072 window.SR_track("sr_ticket_clicked", { ticket_city: vendorModal.dataset.city, ticket_date: vendorModal.dataset.date, ticket_vendor: link.getAttribute("data-vendor") }); 2073 setTimeout(closeVendor, 150); 2074 }); 2075 vendorModal.addEventListener("close", function () { document.body.classList.remove("no-scroll"); }); 2076 } 2077 2078 function hydrateBandsintown() { 2079 if (!datesList) return; 2080 var url = "https://rest.bandsintown.com/artists/" + encodeURIComponent(SR.BIT_ARTIST) + 2081 "/events?app_id=" + encodeURIComponent(SR.BIT_APP_ID) + "&date=upcoming"; 2082 // date=upcoming is the API default, but spelling it out matters: on 2083 // 24 Sep the bare query was served from a server-side cache that still 2084 // had no offers 10+ minutes after on-sale, while every other variant 2085 // (date=upcoming, Simply+Red, artist id) was current. 2086 // V4 lists every ticket outlet per show (the endpoint above only gives 2087 // BIT's priority one) â used for the vendor picker only. It has fewer 2088 // offers than the main feed for some EU shows, so it never decides a 2089 // row's state. Optional: failure or >3 s just means no picker. 2090 var v4 = "https://rest.bandsintown.com/V4/artists/id_" + encodeURIComponent(SR.BIT_ARTIST_ID) + 2091 "/events?app_id=" + encodeURIComponent(SR.BIT_APP_ID) + "&date=upcoming"; 2092 function getJson(u) { return fetch(u).then(function (r) { return r.ok ? r.json() : null; }); } 2093 var vendorsP = Promise.race([ 2094 getJson(v4).catch(function () { return null; }), 2095 new Promise(function (res) { setTimeout(function () { res(null); }, 3000); }) 2096 ]); 2097 Promise.all([getJson(url), vendorsP]) 2098 .then(function (res) { 2099 var events = res[0], v4events = res[1]; 2100 if (!Array.isArray(events)) { renderEmpty("Dates to be announced shortly."); return; } 2101 if (Array.isArray(v4events)) { 2102 var byId = {}; 2103 v4events.forEach(function (e) { if (e && e.id) byId[String(e.id)] = vendorsFor(e); }); 2104 events.forEach(function (e) { e._vendors = byId[String(e.id)] || []; }); 2105 } 2106 renderEvents(events, true); 2107 }) 2108 .catch(function () { renderEmpty("Dates to be announced shortly."); }); 2109 } 2110 2111 /* Review dataset â Bandsintown-shaped, so it runs through the exact same 2112 grouping / state / render path as the live feed. Venues are plausible 2113 summer-run venues (the Figma's five, plus the UK leg from the working 2114 mobile frame). NOT real dates. ?mock=1 (or ?nolive=1) renders it. */ 2115 var SR_MOCK_EVENTS = (function () { 2116 function ev(date, city, country, venue, opts) { 2117 opts = opts || {}; 2118 var offers = opts.nooffer ? [] : [{ type: "Tickets", status: opts.soldout ? "sold out" : "available", url: "https://www.bandsintown.com/a/6644" }]; 2119 return { datetime: date + "T19:30:00", title: "", venue: { name: venue, city: city, country: country }, offers: offers, lineup: ["Simply Red"].concat(opts.support || []) }; 2120 } 2121 return [ 2122 ev("2027-06-16", "Lisbon", "Portugal", "MEO Arena"), 2123 ev("2027-06-19", "Amsterdam", "Netherlands", "Ziggo Dome"), 2124 ev("2027-06-20", "Amsterdam", "Netherlands", "Ziggo Dome", { soldout: true }), 2125 ev("2027-06-22", "Antwerp", "Belgium", "AFAS Dome"), 2126 ev("2027-06-25", "Venice", "Italy", "Piazza San Marco"), 2127 ev("2027-06-27", "Berlin", "Germany", "Waldbühne"), 2128 ev("2027-06-29", "Hamburg", "Germany", "Stadtpark"), 2129 ev("2027-07-02", "Paris", "France", "Accor Arena", { nooffer: true }), 2130 ev("2027-07-24", "Chester", "United Kingdom", "Chester Racecourse"), 2131 ev("2027-07-26", "Halifax", "United Kingdom", "The Piece Hall"), 2132 ev("2027-07-27", "Halifax", "United Kingdom", "The Piece Hall"), 2133 ev("2027-07-30", "Scarborough", "United Kingdom", "Open Air Theatre"), 2134 ev("2027-08-01", "Cardiff", "United Kingdom", "Cardiff Castle"), 2135 ev("2027-08-06", "Edinburgh", "United Kingdom", "Edinburgh Castle", { soldout: true }), 2136 ev("2027-08-13", "London", "United Kingdom", "Gunnersbury Park"), 2137 ev("2027-08-14", "Margate", "United Kingdom", "Dreamland"), 2138 // A festival slot, to prove the title filter drops it: 2139 { datetime: "2027-07-10T20:00:00", title: "Isle of Wight Festival 2027", venue: { name: "Seaclose Park", city: "Newport", country: "United Kingdom" }, offers: [], lineup: ["Simply Red"] } 2140 ]; 2141 })(); 2142 2143 function injectEventSchema(events) { 2144 if (!events.length || document.getElementById("sr-event-schema")) return;
2145 var items = events.slice(0, 30).map(function (ev) { 2146 var v = ev.venue || {}; 2147 var offer = (ev.offers || []).filter(function (o) { return o.type === "Tickets"; })[0]; 2148 var item = { 2149 "@context": "https://schema.org", "@type": "MusicEvent", 2150 "name": "Simply Red â Summer Funk â27: " + (v.city || ""), 2151 "startDate": ev.datetime, 2152 "eventStatus": "https://schema.org/EventScheduled", 2153 "eventAttendanceMode": "https://schema.org/OfflineEventAttendanceMode", 2154 "location": { "@type": "MusicVenue", "name": v.name || "", "address": { "@type": "PostalAddress", "addressLocality": v.city || "", "addressCountry": v.country || "" } }, 2155 "performer": { "@type": "MusicGroup", "name": "Simply Red" }, 2156 "url": "https://www.simplyred.com/" 2157 }; 2158 if (offer && offer.url) item.offers = { "@type": "Offer", "url": offer.url, "availability": offer.status === "sold out" ? "https://schema.org/SoldOut" : "https://schema.org/InStock" }; 2159 return item; 2160 }); 2161 var sc = document.createElement("script"); 2162 sc.type = "application/ld+json"; sc.id = "sr-event-schema"; 2163 sc.textContent = JSON.stringify(items); 2164 document.head.appendChild(sc); 2165 } 2166 2167 function renderEvents(events, isLive) { 2168 events = events.filter(isTourDate); 2169 if (!events.length) { renderEmpty("Dates to be announced shortly."); return; } 2170 events.sort(function (a, b) { return String(a.datetime).localeCompare(String(b.datetime)); }); 2171 var byRegion = {}; 2172 events.forEach(function (ev) { var r = regionFor(ev); (byRegion[r] = byRegion[r] || []).push(ev); }); 2173 var html = ""; 2174 REGION_ORDER.forEach(function (region) { 2175 var rows = byRegion[region]; 2176 if (!rows || !rows.length) return; 2177 html += '<h3 class="region-divider">' + esc(region) + '</h3>'; 2178 rows.forEach(function (ev) { html += rowHtml(ev); }); 2179 }); 2180 datesList.innerHTML = html; 2181 hydrateDateRows(); 2182 applyRowVisibility(); 2183 if (isLive) injectEventSchema(events); 2184 } 2185 2186 // Live by default. ?mock=1 renders the review dataset instead. 2187 if (/[?&](mock|nolive)=1/.test(location.search)) renderEvents(SR_MOCK_EVENTS); 2188 else hydrateBandsintown(); 2189 2190 /* ========================================================= 2191 Tour trailer â only wired once the files exist. 2192 720p to small screens / Save-Data, 1080p otherwise. Plays 2193 muted when scrolled into view, pauses when out. 2194 ========================================================= */ 2195 (function () { 2196 var fig = document.querySelector("[data-tour-video]"); 2197 var video = document.querySelector("[data-trailer-video]"); 2198 var source = document.querySelector("[data-trailer-source]"); 2199 var btn = document.querySelector("[data-trailer-mute]"); 2200 if (!SR.TRAILER_READY || !fig || !video || !source) { 2201 var pb = document.querySelector("[data-trailer-play]"); if (pb) pb.hidden = true; 2202 return; 2203 } 2204 2205 var small = window.matchMedia && window.matchMedia("(max-width: 768px)").matches; 2206 var saveData = navigator.connection && navigator.connection.saveData; 2207 source.setAttribute("src", (small || saveData) ? video.getAttribute("data-src-small") : video.getAttribute("data-src-large")); 2208 video.hidden = false; 2209 // load() registers the source we just set; with preload="none" it 2210 // fetches nothing â bytes only move when the button is pressed. 2211 video.load(); 2212 video.addEventListener("playing", function () { fig.classList.add("is-playing"); if (btn) btn.hidden = false; }); 2213 video.addEventListener("error", function () { video.hidden = true; if (btn) btn.hidden = true; fig.classList.remove("is-playing"); }, true); 2214 2215 function safePlay() { 2216 var p = video.play(); 2217 if (p && typeof p.catch === "function") p.catch(function () { video.muted = true; video.play().catch(function () {}); }); 2218 } 2219 // No autoplay. The centred button starts it with sound; after that, 2220 // scrolling away pauses it and scrolling back resumes it. Tapping the 2221 // playing video pauses / resumes. 2222 var started = false, pausedByScroll = false;
2223 var playBtn = document.querySelector("[data-trailer-play]"); 2224 if (playBtn) playBtn.addEventListener("click", function () { 2225 started = true; 2226 video.muted = false; 2227 safePlay(); 2228 window.SR_track("sr_trailer_played", { trailer_source: "button" }); 2229 }); 2230 if ("IntersectionObserver" in window) { 2231 new IntersectionObserver(function (entries) { 2232 entries.forEach(function (entry) { 2233 if (!started) return; 2234 if (entry.isIntersecting) { if (pausedByScroll) { pausedByScroll = false; safePlay(); } } 2235 else if (!video.paused) { pausedByScroll = true; video.pause(); } 2236 }); 2237 }, { threshold: 0.2 }).observe(video); 2238 } 2239 video.addEventListener("click", function () { if (!started) return; if (video.paused) safePlay(); else video.pause(); }); 2240 video.addEventListener("pause", function () { if (started && !pausedByScroll) fig.classList.add("is-paused"); }); 2241 video.addEventListener("play", function () { fig.classList.remove("is-paused"); }); 2242 2243 if (btn) { 2244 function sync() { 2245 var unmuted = !video.muted; 2246 btn.setAttribute("aria-pressed", unmuted ? "true" : "false"); 2247 btn.setAttribute("aria-label", unmuted ? "Mute trailer" : "Unmute trailer"); 2248 } 2249 btn.addEventListener("click", function () { 2250 video.muted = !video.muted; 2251 if (!video.muted && video.paused) safePlay(); 2252 sync(); 2253 window.SR_track("sr_trailer_unmuted", { trailer_unmuted: !video.muted }); 2254 }); 2255 video.addEventListener("volumechange", sync); 2256 sync(); 2257 } 2258 })(); 2259 2260 /* ========================================================= 2261 Pull quotes â letter-by-letter red fill locked to scroll. 2262 p = 0 when the quote's top reaches 85% down the viewport, 2263 p = 1 when its bottom reaches 40%. Letters are wrapped in 2264 spans (words kept unbreakable); the CSS does the colour. 2265 ========================================================= */ 2266 (function () { 2267 if (!(window.CSS && CSS.supports && CSS.supports("color", "color-mix(in srgb, red 50%, blue)"))) return; 2268 var quotes = Array.prototype.slice.call(document.querySelectorAll(".pull-quote blockquote")); 2269 if (!quotes.length) return; 2270 quotes.forEach(function (bq) { 2271 var p = bq.querySelector("p"); 2272 var marks = Array.prototype.slice.call(p.querySelectorAll(".quote-mark")); 2273 marks.forEach(function (m) { m.remove(); }); 2274 var text = p.textContent.replace(/\s+/g, " ").trim(); 2275 var i = 0, html = ""; 2276 text.split(" ").forEach(function (word, wi) { 2277 if (wi) html += " "; 2278 html += '<span class="w">' + Array.prototype.map.call(word, function (ch) { 2279 return '<span class="ch" style="--i:' + (i++) + '">' + ch.replace(/&/g, "&").replace(/</g, "<") + '</span>'; 2280 }).join("") + '</span>'; 2281 }); 2282 p.innerHTML = 2283 '<span class="vh">\u201C' + text.replace(/&/g, "&").replace(/</g, "<") + '\u201D</span>' + 2284 '<span aria-hidden="true">' + 2285 '<span class="qa"><span class="quote-mark quote-mark--open">\u201C</span></span>' + html + 2286 '<span class="qa"><span class="quote-mark quote-mark--close">\u201C</span></span>' + 2287 '</span>'; 2288 bq.style.setProperty("--n", i); 2289 bq.classList.add("is-split"); 2290 }); 2291 var raf = null; 2292 function update() { 2293 raf = null; 2294 var vh = window.innerHeight; 2295 quotes.forEach(function (bq) { 2296 var r = bq.getBoundingClientRect(); 2297 var start = vh * 0.85, end = vh * 0.40; 2298 var p = (start - r.top) / (start - end + r.height); 2299 bq.style.setProperty("--p", Math.max(0, Math.min(1, p)).toFixed(4)); 2300 }); 2301 } 2302 function onScroll() { if (!raf) raf = requestAnimationFrame(update); } 2303 window.addEventListener("scroll", onScroll, { passive: true }); 2304 window.addEventListener("resize", onScroll); 2305 update(); 2306 })(); 2307 2308 /* ========================================================= 2309 Newsletter parallax â the background layer drifts at ~15% 2310 of scroll speed while the section crosses the viewport. 2311 ========================================================= */ 2312 (function () { 2313 var layer = document.querySelector("[data-parallax]"); 2314 if (!layer) return; 2315 if (window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches) return; 2316 var section = layer.parentElement, raf = null;
2317 function update() { 2318 raf = null; 2319 var r = section.getBoundingClientRect(); 2320 var vh = window.innerHeight; 2321 if (r.bottom < 0 || r.top > vh) return; 2322 // -1 when the section has just entered from below, +1 as it leaves at the top. 2323 var p = ((r.top + r.height / 2) - vh / 2) / (vh / 2 + r.height / 2); 2324 var range = r.height * 0.15; 2325 layer.style.transform = "translate3d(0," + (-p * range).toFixed(1) + "px,0)"; 2326 } 2327 function onScroll() { if (!raf) raf = requestAnimationFrame(update); } 2328 window.addEventListener("scroll", onScroll, { passive: true }); 2329 window.addEventListener("resize", onScroll); 2330 update(); 2331 })(); 2332 2333 /* ========================================================= 2334 Continue link + cookie banner 2335 ========================================================= */ 2336 document.querySelectorAll("[data-continue]").forEach(function (a) { 2337 a.addEventListener("click", function () { window.SR_track("sr_continue_clicked", { page_state: STATE }); }); 2338 }); 2339 (function () { 2340 var banner = document.querySelector(".cookie-banner"); 2341 if (!banner) return; 2342 var accepted = false; 2343 try { accepted = localStorage.getItem("sr_cookie_consent") === "1"; } catch (e) {} 2344 if (accepted) return; 2345 banner.hidden = false; 2346 setTimeout(function () { banner.classList.add("is-visible"); }, 2600); 2347 var accept = banner.querySelector("[data-cookie-accept]"); 2348 if (accept) accept.addEventListener("click", function () { 2349 try { localStorage.setItem("sr_cookie_consent", "1"); } catch (e) {} 2350 banner.classList.remove("is-visible"); 2351 setTimeout(function () { banner.hidden = true; }, 500); 2352 window.SR_loadGTM(); 2353 }); 2354 })(); 2355</script>
2355 2356</body> 2357</html>
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.