Help
RSS
API
Feed
Maltego
Contact
Domain > b2nny.com
×
More information on this domain is in
AlienVault OTX
Is this malicious?
Yes
No
DNS Resolutions
Date
IP Address
2025-10-27
104.21.20.136
(
ClassC
)
2026-03-05
172.67.192.239
(
ClassC
)
Port 443
HTTP/1.1 200 OKDate: Thu, 05 Mar 2026 17:13:34 GMTContent-Type: text/html; charsetutf-8Transfer-Encoding: chunkedConnection: keep-aliveServer: cloudflarelast-modified: Tue, 10 Feb 2026 16:25:21 GMTaccess-control-allow-origin: *expires: Thu, 05 Mar 2026 17:23:34 GMTCache-Control: max-age600Report-To: {group:cf-nel,max_age:604800,endpoints:{url:https://a.nel.cloudflare.com/report/v4?sV1LIXDq%2Bs31sJWZ1g6s9b7St%2FyxqpLQ0kSS8VRQhQCKsuH6pJKejBsLSQxY1IwX7sqSMvCYiHUkjTPoDAu58hZ0%2FLCnx%2BOk%3D}}x-proxy-cache: MISSx-github-request-id: 48C8:38DB44:31E1A4:32E9C2:69A9B9BENel: {report_to:cf-nel,success_fraction:0.0,max_age:604800}Age: 0via: 1.1 varnishx-served-by: cache-sjc1000144-SJCx-cache: MISSx-cache-hits: 0x-timer: S1772730815.745066,VS0,VE111vary: Accept-Encodingx-fastly-request-id: 3c055fcd662047c3f2b337f07b13abc334a60c9ecf-cache-status: DYNAMICCF-RAY: 9d7ac087dfe6ff09-PDXalt-svc: h3:443; ma86400 !DOCTYPE html>html langen>head> meta charsetutf-8 /> meta nameviewport contentwidthdevice-width, initial-scale1 /> title>b2nny | links/title> meta namedescription contentOfficial links for b2nny — stream on Spotify, Apple Music, and SoundCloud, plus YouTube + socials. Book a session right here. /> meta nametheme-color content#faf8f5 /> meta namerobots contentindex,follow /> link relcanonical hrefhttps://b2nny.com/ /> meta propertyog:type contentwebsite /> meta propertyog:site_name contentb2nny /> meta propertyog:url contenthttps://b2nny.com/ /> meta propertyog:title contentb2nny | links /> meta propertyog:description contentOfficial links for b2nny — stream on Spotify, Apple Music, and SoundCloud, plus YouTube + socials. Book a session right here. /> meta propertyog:image contenthttps://b2nny.com/Screenshot%202025-10-23%20180721.png /> meta propertyog:image:alt contentb2nny — official links /> meta nametwitter:card contentsummary_large_image /> meta nametwitter:title contentb2nny | links /> meta nametwitter:description contentOfficial links for b2nny — stream on Spotify, Apple Music, and SoundCloud, plus YouTube + socials. Book a session right here. /> meta nametwitter:image contenthttps://b2nny.com/Screenshot%202025-10-23%20180721.png /> link relpreconnect hrefhttps://fonts.googleapis.com /> link relpreconnect hrefhttps://fonts.gstatic.com crossorigin /> link hrefhttps://fonts.googleapis.com/css2?familyDM+Sans:ital,opsz,wght@0,9..40,400;0,9..40,500;0,9..40,700;1,9..40,400&familySyne:wght@700;800&displayswap relstylesheet /> link relicon hrefdata:image/svg+xml,%3Csvg xmlnshttp://www.w3.org/2000/svg width64 height64%3E%3Cdefs%3E%3ClinearGradient idg x10%25 y10%25 x2100%25 y2100%25%3E%3Cstop offset0%25 stop-color%23ff7f7f/%3E%3Cstop offset100%25 stop-color%23b496ff/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect width64 height64 rx16 fillurl(%23g)/%3E%3Cpath dM18 40c9 8 19 8 28 0 stroke%23fff stroke-width4 fillnone stroke-linecapround/%3E%3Ccircle cx26 cy26 r4 fill%23fff/%3E%3Ccircle cx38 cy26 r4 fill%23fff/%3E%3C/svg%3E /> style> :root{ --maxw: 780px; --fg: #1a1625; --fg2: #3d3654; --muted: rgba(26,22,37,0.65); /* iOS 26 + 2016 retro palette */ --bg0: #faf8f5; --bg1: #f0ebe4; /* Vaporwave-inspired pastels */ --coral: rgba(255, 127, 127, 0.35); --peach: rgba(255, 180, 128, 0.30); --lavender: rgba(180, 150, 255, 0.28); --mint: rgba(130, 230, 200, 0.30); --sky: rgba(130, 200, 255, 0.32); /* Glass effect colors */ --glass: rgba(255,255,255,0.72); --glass2: rgba(255,255,255,0.55); --glassBorder: rgba(255,255,255,0.85); --radius: 20px; --radius2: 28px; --shadowA: 0 1px 2px rgba(26,22,37,0.06); --shadowB: 0 12px 40px rgba(26,22,37,0.12); --shadowC: 0 4px 20px rgba(26,22,37,0.08); --focusRing: rgba(180, 150, 255, 0.50); /* Accent for buttons */ --accent1: #ff7f7f; --accent2: #b496ff; --accent3: #82c8ff; } *{ box-sizing:border-box; margin:0; padding:0; } html, body{ height:100%; } html{ scroll-behavior: smooth; } body{ font-family: DM Sans, system-ui, -apple-system, BlinkMacSystemFont, sans-serif; color: var(--fg); background: var(--bg0); overflow-x:hidden; display:flex; justify-content:center; align-items:flex-start; } html{ -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-rendering: optimizeLegibility; } /* Retro geometric background */ .baseFX{ position: fixed; inset: 0; z-index: -5; pointer-events: none; background: radial-gradient(ellipse 900px 700px at 15% 10%, var(--coral), transparent 60%), radial-gradient(ellipse 800px 600px at 85% 20%, var(--lavender), transparent 55%), radial-gradient(ellipse 700px 500px at 50% 90%, var(--mint), transparent 50%), radial-gradient(ellipse 600px 500px at 20% 70%, var(--sky), transparent 55%), linear-gradient(180deg, var(--bg0), var(--bg1)); } /* Subtle grid pattern - 2016 aesthetic */ body::before{ content:; position:fixed; inset:0; z-index:-4; pointer-events:none; opacity: 0.35; background: repeating-linear-gradient(0deg, transparent 0px, transparent 39px, rgba(180,150,255,0.08) 39px, rgba(180,150,255,0.08) 40px ), repeating-linear-gradient(90deg, transparent 0px, transparent 39px, rgba(180,150,255,0.08) 39px, rgba(180,150,255,0.08) 40px ); } /* Floating orbs - iOS liquid glass feel */ .colorFX{ position:fixed; inset:-10%; z-index:-3; pointer-events:none; background: radial-gradient(300px 280px at 20% 25%, rgba(255,127,127,0.20), transparent 70%), radial-gradient(350px 320px at 75% 15%, rgba(180,150,255,0.18), transparent 65%), radial-gradient(280px 260px at 80% 75%, rgba(130,230,200,0.15), transparent 70%), radial-gradient(320px 300px at 25% 80%, rgba(130,200,255,0.16), transparent 65%); filter: blur(60px); animation: floatOrbs 20s ease-in-out infinite alternate; } @keyframes floatOrbs{ 0%{ transform: translate3d(0,0,0) scale(1); } 50%{ transform: translate3d(30px,-20px,0) scale(1.05); } 100%{ transform: translate3d(-20px,15px,0) scale(0.98); } } /* Diagonal stripes - retro accent */ .bgFX{ position:fixed; inset:0; z-index:-2; pointer-events:none; opacity: 0.04; background: repeating-linear-gradient( -45deg, var(--fg) 0px, var(--fg) 1px, transparent 1px, transparent 12px ); } .scanFX{ display: none; /* Removing scan lines for cleaner iOS look */ } @keyframes shimmer{ 0%{ background-position: -200% 0; } 100%{ background-position: 200% 0; } } .wrap{ width:100%; max-width: var(--maxw); padding: 28px 20px 60px; } main.wrap{ position:relative; } header{ text-align:center; margin-bottom: 20px; } .pfp{ display:flex; justify-content:center; margin-bottom: 16px; position:relative; } .pfpRing{ position:relative; padding: 4px; border-radius: 999px; background: linear-gradient(135deg, var(--accent1), var(--accent2), var(--accent3)); box-shadow: 0 0 0 1px rgba(255,255,255,0.90), 0 8px 32px rgba(180,150,255,0.25), 0 2px 8px rgba(255,127,127,0.20); } .pfpRing::after{ content:; position:absolute; inset:-12px; border-radius: 999px; background: radial-gradient(closest-side, rgba(180,150,255,0.15), transparent 70%); filter: blur(8px); z-index:-1; } .pfp img{ width: 115px; height: 115px; border-radius: 999px; object-fit: cover; border: 3px solid #fff; box-shadow: 0 4px 20px rgba(26,22,37,0.15); background: #fff; display:block; } .topRow{ display:grid; gap: 12px; justify-content:center; margin: 10px 0 14px; } .topBtn{ justify-content:space-between; gap: 14px; width: min(100%, 480px); } .topLabel{ text-transform: uppercase; letter-spacing: 0.12em; font-weight: 700; font-size: 11px; color: var(--fg2); } .topSub{ font-weight: 700; font-size: 13px; color: var(--fg); } h1{ font-family: Syne, sans-serif; font-size: 32px; margin: 10px 0 8px; letter-spacing: -0.01em; font-weight: 800; color: var(--fg); } .subHead{ font-size: 12px; color: var(--fg2); letter-spacing: 0.08em; text-transform: uppercase; margin-top: 4px; font-weight: 500; } .links{ display:grid; gap: 12px; margin-top: 18px; } .link{ display:flex; align-items:center; gap: 14px; padding: 14px 18px; border-radius: var(--radius2); text-decoration:none; color: var(--fg); border: 1px solid var(--glassBorder); background: var(--glass); box-shadow: var(--shadowC); position:relative; overflow:hidden; transition: transform 200ms ease, box-shadow 200ms ease; } @supports ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))){ .link, .card, .modal, .nav, .iconLink{ -webkit-backdrop-filter: blur(20px) saturate(180%); backdrop-filter: blur(20px) saturate(180%); } } .link::before{ content:; position:absolute; inset:0; background: linear-gradient(135deg, rgba(255,255,255,0.5) 0%, transparent 50%); pointer-events:none; } .link:hover{ transform: translateY(-2px) scale(1.01); box-shadow: var(--shadowB); } .link:focus-visible{ outline:none; box-shadow: var(--shadowC), 0 0 0 3px var(--focusRing); } .link img{ width: 32px; height: 32px; border-radius: 10px; object-fit: cover; border: 1px solid rgba(255,255,255,0.80); box-shadow: 0 2px 8px rgba(26,22,37,0.10); background: #fff; flex: 0 0 auto; } .link span{ font-weight: 600; letter-spacing: 0.01em; } footer{ font-size: 12px; color: var(--fg2); margin-top: 30px; text-align:center; } /* Navigation - iOS pill style */ .nav{ position: sticky; top: 16px; z-index: 9; display:flex; align-items:center; justify-content:space-between; gap: 12px; padding: 8px 12px; border-radius: 999px; border: 1px solid var(--glassBorder); background: var(--glass); box-shadow: var(--shadowC); isolation: isolate; } .nav::before{ display:none; } .nav::after{ display:none; } .nav a{ color: var(--fg); text-decoration:none; font-weight: 600; letter-spacing: 0.01em; padding: 8px 14px; border-radius: 999px; border: 1px solid transparent; font-size: 13px; transition: background 150ms ease, border-color 150ms ease; } .nav a:hover{ background: rgba(180,150,255,0.12); border-color: rgba(180,150,255,0.20); } .nav a:focus-visible{ outline:none; box-shadow: 0 0 0 3px var(--focusRing); } .navBrand{ display:flex; align-items:center; gap: 10px; white-space:nowrap; padding-left: 12px; padding-right: 12px; background: linear-gradient(135deg, rgba(255,127,127,0.15), rgba(180,150,255,0.15)); border-color: rgba(255,255,255,0.60) !important; font-weight: 700 !important; } .navToggle{ display:inline-flex; align-items:center; justify-content:center; gap: 8px; border: 1px solid rgba(180,150,255,0.30); background: linear-gradient(135deg, rgba(180,150,255,0.15), rgba(130,200,255,0.15)); color: var(--fg); padding: 8px 14px; border-radius: 999px; cursor:pointer; font: inherit; font-weight: 600; letter-spacing: 0.02em; box-shadow: var(--shadowA); user-select:none; text-decoration:none; transition: transform 150ms ease, box-shadow 150ms ease; } .navToggle:hover{ transform: translateY(-1px); box-shadow: var(--shadowC); } .navToggle:focus-visible{ outline:none; box-shadow: var(--shadowA), 0 0 0 3px var(--focusRing); } .hero{ margin-top: 20px; margin-bottom: 20px; display:grid; grid-template-columns: 1fr; gap: 16px; text-align:center; } .heroBlurb{ font-size: 15px; color: var(--fg2); max-width: 42ch; margin: 0 auto; line-height: 1.55; } .heroCtas{ display:flex; gap: 12px; justify-content:center; flex-wrap: wrap; margin-top: 10px; } .pageSection{ margin-top: 28px; } /* Section dropdown styles */ .secDrop{ border-radius: 20px; border: 1px solid var(--glassBorder); background: var(--glass); box-shadow: var(--shadowC); overflow: hidden; } .secDrop summary{ list-style: none; cursor: pointer; user-select: none; -webkit-tap-highlight-color: transparent; padding: 16px 20px; display: flex; align-items: center; justify-content: space-between; gap: 12px; transition: background 150ms ease; } .secDrop summary::-webkit-details-marker{ display: none; } .secDrop summary::marker{ content: ; } .secDrop summary:hover{ background: rgba(180,150,255,0.08); } .secDrop summary:focus-visible{ outline: none; box-shadow: inset 0 0 0 3px var(--focusRing); } .secDrop .secTitle{ margin: 0; pointer-events: none; } .secDrop .secTitle::after{ display: none; } .secDrop .secChevron{ width: 20px; height: 20px; display: flex; align-items: center; justify-content: center; border-radius: 6px; background: linear-gradient(135deg, rgba(180,150,255,0.15), rgba(130,200,255,0.15)); color: var(--fg2); font-size: 12px; transition: transform 200ms ease, background 150ms ease; } .secDropopen .secChevron{ transform: rotate(180deg); background: linear-gradient(135deg, rgba(255,127,127,0.20), rgba(180,150,255,0.20)); } .secDropBody{ padding: 0 20px 20px; } .secDropBody > .card + .card{ margin-top: 14px; } .secTitle{ font-family: Syne, sans-serif; font-size: 14px; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; color: var(--fg2); margin: 0 0 14px; position:relative; display: flex; align-items: center; gap: 12px; } .secTitle::after{ content:; flex: 1; height: 2px; border-radius: 999px; background: linear-gradient(90deg, var(--accent1), var(--accent2), var(--accent3), transparent); opacity: 0.40; } .cardGrid{ display:grid; grid-template-columns: 1fr 1fr; gap: 14px; } @media (max-width: 640px){ .cardGrid{ grid-template-columns: 1fr; } } .releaseCard{ padding: 18px; text-align:left; } .releaseTop{ display:flex; align-items:center; justify-content:space-between; gap: 12px; } .releaseTitle{ font-family: Syne, sans-serif; font-size: 18px; font-weight: 700; letter-spacing: 0.01em; color: var(--fg); } .releaseMeta{ margin-top: 6px; font-size: 13px; color: var(--fg2); line-height: 1.4; } .releaseActions{ margin-top: 14px; display:flex; gap: 10px; flex-wrap: wrap; align-items:center; } .socialRow{ display:flex; flex-wrap: wrap; gap: 12px; align-items:center; justify-content:center; } .iconLink{ display:inline-flex; align-items:center; gap: 10px; padding: 12px 16px; border-radius: 18px; border: 1px solid var(--glassBorder); background: var(--glass); color: var(--fg); text-decoration:none; box-shadow: var(--shadowA); transition: transform 180ms ease, box-shadow 180ms ease; } .iconLink:hover{ transform: translateY(-2px) scale(1.02); box-shadow: var(--shadowC); } .iconLink:focus-visible{ outline:none; box-shadow: var(--shadowA), 0 0 0 3px var(--focusRing); } .iconLink img{ width: 28px; height: 28px; border-radius: 8px; object-fit: cover; border: 1px solid rgba(255,255,255,0.70); background: #fff; } .iconLink strong{ font-weight: 600; font-size: 14px; } .card{ border-radius: 24px; padding: 20px; border: 1px solid var(--glassBorder); background: var(--glass); box-shadow: var(--shadowB); position:relative; overflow:hidden; } .card::before{ content:; position:absolute; inset:0; background: linear-gradient(160deg, rgba(255,255,255,0.6) 0%, transparent 40%); pointer-events:none; } .card::after{ display: none; } .title2{ font-family: Syne, sans-serif; font-size: 22px; font-weight: 700; letter-spacing: 0.01em; color: var(--fg); } .sub2{ font-size: 14px; color: var(--fg2); margin-top: 6px; margin-bottom: 12px; } .tiny{ font-size: 12px; color: var(--fg2); line-height: 1.45; } .priceRow{ margin-top: 12px; display:inline-flex; align-items:center; gap: 10px; padding: 10px 14px; border-radius: 14px; border: 1px solid rgba(180,150,255,0.25); background: linear-gradient(135deg, rgba(180,150,255,0.10), rgba(130,200,255,0.10)); } .priceRow strong{ font-family: Syne, sans-serif; font-size: 18px; color: var(--accent2); } .grid2{ display:grid; grid-template-columns: 1fr 1fr; gap: 14px; } .grid3{ display:grid; grid-template-columns: 1fr 1fr 1fr; gap: 14px; } @media (max-width: 640px){ .grid2, .grid3{ grid-template-columns: 1fr; } } label{ font-size: 12px; color: var(--fg2); display:block; margin: 2px 0 6px; letter-spacing: 0.02em; font-weight: 500; } input, select, textarea{ width:100%; padding: 14px 16px; border-radius: 16px; border: 1px solid rgba(180,150,255,0.25); background: rgba(255,255,255,0.80); color: var(--fg); font: inherit; font-size: 15px; outline:none; box-shadow: inset 0 1px 2px rgba(26,22,37,0.04); transition: border-color 150ms ease, box-shadow 150ms ease; } input:focus, select:focus, textarea:focus{ border-color: var(--accent2); box-shadow: inset 0 1px 2px rgba(26,22,37,0.04), 0 0 0 3px var(--focusRing); } input::placeholder, textarea::placeholder{ color: var(--fg2); opacity: 0.6; } textarea{ resize: vertical; } .isInvalid{ border-color: var(--accent1) !important; box-shadow: inset 0 1px 2px rgba(26,22,37,0.04), 0 0 0 3px rgba(255,127,127,0.25) !important; } inputtypecheckbox.isInvalid{ outline: 3px solid rgba(255,127,127,0.25) !important; outline-offset: 4px; box-shadow: none !important; } .calWrap{ margin-top: 14px; padding: 16px; border-radius: 20px; border: 1px solid rgba(180,150,255,0.20); background: rgba(255,255,255,0.50); } .calTop{ display:flex; align-items:center; justify-content:space-between; gap: 10px; margin-bottom: 12px; } .calMonth{ font-family: Syne, sans-serif; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase; font-size: 13px; color: var(--fg); } .calBtns{ display:flex; gap: 8px; align-items:center; } .calGrid{ display:grid; grid-template-columns: repeat(7, 1fr); gap: 8px; } .calDay{ width:100%; aspect-ratio: 1 / 1; border-radius: 14px; border: 1px solid rgba(180,150,255,0.15); background: rgba(255,255,255,0.70); color: var(--fg); font-weight: 600; font-size: 13px; cursor:pointer; box-shadow: var(--shadowA); transition: transform 150ms ease, border-color 150ms ease, box-shadow 150ms ease; } .calDay:hover{ transform: translateY(-1px) scale(1.03); box-shadow: var(--shadowC); } .calDay:disabled{ opacity: 0.35; cursor:not-allowed; transform:none; } .calDay:focus-visible{ outline:none; box-shadow: var(--shadowA), 0 0 0 3px var(--focusRing); } .calDay--loading{ opacity: 0.50; } .calDay--avail{ border-color: rgba(130,230,200,0.50); background: linear-gradient(135deg, rgba(130,230,200,0.20), rgba(255,255,255,0.70)); } .calDay--busy{ border-color: rgba(255,127,127,0.40); background: linear-gradient(135deg, rgba(255,127,127,0.15), rgba(255,255,255,0.70)); } .calDay--sel{ border-color: var(--accent2); background: linear-gradient(135deg, rgba(180,150,255,0.25), rgba(255,255,255,0.70)); box-shadow: 0 0 0 2px rgba(180,150,255,0.30); } .rules{ margin-top: 14px; padding-top: 14px; border-top: 1px solid rgba(180,150,255,0.15); } .rules ul{ margin: 10px 0 0; padding-left: 20px; color: var(--fg2); line-height: 1.55; font-size: 13px; } .checkRow{ margin-top: 12px; display:flex; gap: 12px; align-items:flex-start; padding: 12px 14px; border-radius: 14px; border: 1px solid rgba(180,150,255,0.15); background: rgba(255,255,255,0.50); } .checkRow input{ margin-top: 3px; accent-color: var(--accent2); } .checkRow span{ font-size: 13px; color: var(--fg2); line-height: 1.45; } .gear{ margin-top: 16px; padding-top: 14px; border-top: 1px solid rgba(180,150,255,0.15); } .gear h3{ font-family: Syne, sans-serif; font-size: 13px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: var(--fg2); margin-bottom: 12px; } .gearDrop{ border-radius: 14px; } .gearDrop summary{ list-style: none; cursor: pointer; user-select: none; font-family: Syne, sans-serif; font-size: 13px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: var(--fg2); margin-bottom: 12px; display:flex; align-items:center; justify-content:space-between; gap: 10px; } .gearDrop summary::-webkit-details-marker{ display:none; } .gearDrop summary::marker{ content:; } .gearDrop summary::after{ content:▾; opacity: 0.60; transition: transform 150ms ease; } .gearDropopen summary::after{ transform: rotate(-180deg); } .gearDrop summary:focus-visible{ outline:none; box-shadow: 0 0 0 3px var(--focusRing); border-radius: 12px; padding: 6px 10px; margin-left: -10px; margin-right: -10px; } .gearDrop .gearList{ margin-top: 12px; } .gearList{ display:grid; gap: 10px; margin: 0; padding: 0; list-style: none; } .gearItem{ display:flex; align-items:flex-start; justify-content:space-between; gap: 12px; padding: 12px 14px; border-radius: 14px; border: 1px solid rgba(180,150,255,0.15); background: rgba(255,255,255,0.60); } .gearItem strong{ font-weight: 600; color: var(--fg); } .gearItem small{ display:block; font-size: 11px; color: var(--fg2); margin-top: 2px; } .cta{ margin-top: 16px; display:flex; gap: 12px; align-items:center; flex-wrap: wrap; } /* Buttons - iOS style with retro gradient */ .btn{ display:inline-flex; align-items:center; justify-content:center; gap: 8px; border: none; background: linear-gradient(135deg, var(--accent1), var(--accent2)); color: #fff; padding: 14px 20px; border-radius: 16px; cursor:pointer; font: inherit; font-weight: 600; font-size: 14px; letter-spacing: 0.01em; box-shadow: 0 4px 16px rgba(180,150,255,0.30), 0 2px 4px rgba(255,127,127,0.20); transition: transform 180ms ease, box-shadow 180ms ease, filter 180ms ease; text-decoration:none; user-select:none; } .btn:hover{ transform: translateY(-2px) scale(1.02); box-shadow: 0 8px 24px rgba(180,150,255,0.35), 0 4px 8px rgba(255,127,127,0.25); filter: brightness(1.05); } .btn:active{ transform: translateY(1px) scale(0.98); } .btn:focus-visible{ outline:none; box-shadow: 0 4px 16px rgba(180,150,255,0.30), 0 0 0 3px var(--focusRing); } .btn:disabled{ opacity: 0.45; cursor:not-allowed; transform:none; filter: grayscale(30%); } .btn--pill{ border-radius: 999px; padding: 14px 24px; } .btn--hot{ background: linear-gradient(135deg, #ff6b6b, #ff8e53); box-shadow: 0 4px 20px rgba(255,107,107,0.35), 0 2px 6px rgba(255,142,83,0.25); } .btn--hot:hover{ box-shadow: 0 8px 28px rgba(255,107,107,0.40), 0 4px 10px rgba(255,142,83,0.30); } .btn--ghost{ border: 1px solid rgba(180,150,255,0.30); background: var(--glass); color: var(--fg); box-shadow: var(--shadowA); } .btn--ghost:hover{ background: rgba(180,150,255,0.10); border-color: rgba(180,150,255,0.45); box-shadow: var(--shadowC); } .msg{ font-size: 13px; color: var(--fg2); white-space: pre-wrap; } .timePick{ margin-top: 14px; } .timeLegend{ display:flex; align-items:center; gap: 14px; flex-wrap: wrap; font-size: 12px; color: var(--fg2); margin-top: 8px; } .timeLegendItem{ display:inline-flex; align-items:center; gap: 6px; } .timeSwatch{ width: 14px; height: 14px; border-radius: 6px; border: 1px solid rgba(26,22,37,0.10); } .timeSwatch--avail{ background: linear-gradient(135deg, rgba(130,230,200,0.50), rgba(130,230,200,0.30)); } .timeSwatch--busy{ background: linear-gradient(135deg, rgba(255,127,127,0.50), rgba(255,127,127,0.30)); } .timeSwatch--sel{ background: linear-gradient(135deg, rgba(180,150,255,0.60), rgba(180,150,255,0.40)); } .timeGrid{ margin-top: 12px; display:grid; grid-template-columns: repeat(auto-fit, minmax(100px, 1fr)); gap: 10px; } .timeTile{ width:100%; border-radius: 14px; border: 1px solid rgba(180,150,255,0.20); background: rgba(255,255,255,0.70); color: var(--fg); padding: 12px; text-align:left; cursor:pointer; user-select:none; transition: transform 150ms ease, border-color 150ms ease, box-shadow 150ms ease; box-shadow: var(--shadowA); } .timeTile:hover{ transform: translateY(-1px); box-shadow: var(--shadowC); } .timeTile:active{ transform: translateY(1px) scale(0.98); } .timeTile:focus-visible{ outline:none; box-shadow: var(--shadowA), 0 0 0 3px var(--focusRing); } .timeTile--avail{ border-color: rgba(130,230,200,0.45); background: linear-gradient(135deg, rgba(130,230,200,0.15), rgba(255,255,255,0.80)); } .timeTile--busy{ border-color: rgba(255,127,127,0.35); background: linear-gradient(135deg, rgba(255,127,127,0.12), rgba(255,255,255,0.70)); opacity: 0.55; cursor:not-allowed; } .timeTile--sel{ border-color: var(--accent2); background: linear-gradient(135deg, rgba(180,150,255,0.20), rgba(255,255,255,0.80)); box-shadow: 0 0 0 2px rgba(180,150,255,0.25); } .timeTile .tMain{ font-weight: 600; font-size: 14px; color: var(--fg); } .timeTile .tSub{ margin-top: 4px; font-size: 11px; color: var(--fg2); } .timeSummary{ margin-top: 12px; font-size: 13px; color: var(--fg2); } .srOnly{ position:absolute !important; width:1px !important; height:1px !important; padding:0 !important; margin:-1px !important; overflow:hidden !important; clip: rect(0, 0, 0, 0) !important; white-space:nowrap !important; border:0 !important; } .modalBackdrop{ position:fixed; inset:0; background: rgba(26,22,37,0.50); -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px); display:none; align-items:center; justify-content:center; z-index: 10; padding: 20px; } .modalBackdrop.open{ display:flex; } .modal{ width: min(440px, 100%); border-radius: 28px; border: 1px solid var(--glassBorder); background: var(--glass); box-shadow: var(--shadowB), 0 0 0 1px rgba(255,255,255,0.50); padding: 24px; } .modalHeader{ display:flex; align-items:flex-start; justify-content:space-between; gap: 14px; margin-bottom: 12px; } .modalTitle{ font-family: Syne, sans-serif; font-size: 20px; font-weight: 700; color: var(--fg); } .modalSub{ font-size: 13px; color: var(--fg2); margin-top: 4px; } .modalClose{ border: 1px solid rgba(180,150,255,0.20); background: rgba(255,255,255,0.70); color: var(--fg2); cursor:pointer; font-size: 18px; line-height: 1; padding: 8px 12px; border-radius: 999px; transition: transform 150ms ease, background 150ms ease; } .modalClose:hover{ background: rgba(180,150,255,0.15); transform: scale(1.05); } .modalClose:focus-visible{ outline:none; box-shadow: 0 0 0 3px var(--focusRing); } .modalLinks{ display:grid; gap: 12px; margin-top: 12px; } .modalLink{ justify-content:space-between; gap: 12px; width: 100%; border-radius: 18px; } .modalLink strong{ font-weight: 600; color: var(--fg); } .modalLink small{ font-size: 12px; color: var(--fg2); display:block; margin-top:2px; } .pill{ font-size: 11px; text-transform: uppercase; letter-spacing: 0.08em; font-weight: 600; padding: 6px 12px; border-radius: 999px; border: 1px solid rgba(180,150,255,0.30); background: linear-gradient(135deg, rgba(180,150,255,0.15), rgba(130,200,255,0.15)); color: var(--fg2); white-space:nowrap; } body.modalOpen{ overflow: hidden; } @media (prefers-reduced-motion: reduce){ .colorFX{ animation: none !important; } *{ scroll-behavior: auto !important; transition: none !important; } } /* Footer */ .siteFooter{ margin-top: 32px; padding-top: 20px; border-top: 1px solid rgba(180,150,255,0.15); text-align: center; } .footerLinks{ display: flex; align-items: center; justify-content: center; gap: 12px; flex-wrap: wrap; } .footerLinks a, .footerBtn{ color: var(--fg2); text-decoration: none; font-size: 13px; font-weight: 500; padding: 6px 12px; border-radius: 999px; border: 1px solid transparent; background: none; cursor: pointer; font-family: inherit; transition: color 150ms ease, border-color 150ms ease, background 150ms ease; } .footerLinks a:hover, .footerBtn:hover{ color: var(--fg); border-color: rgba(180,150,255,0.25); background: rgba(180,150,255,0.08); } .footerDot{ color: var(--fg2); opacity: 0.4; font-size: 10px; } @media (max-width: 480px){ .pfp img{ width: 100px; height: 100px; } h1{ font-size: 28px; } .nav{ top: 12px; padding: 6px 10px; } .wrap{ padding: 24px 16px 50px; } } /style>/head>body> div classbaseFX>/div> div classcolorFX>/div> div classbgFX>/div> div classscanFX>/div> main classwrap idtop> nav classnav aria-labelPrimary idsiteNav> a classnavBrand href#top>b2nny/a> a classnavToggle href#book>book/a> /nav> header classhero> div classpfp> div classpfpRing> img srcapple music.jpg altb2nny profile width120 height120 decodingasync fetchpriorityhigh> /div> /div> div> h1>@b2nny/h1> div classsubHead>artist • releases • booking/div> p classheroBlurb>Stream the latest drops, hit the socials, or request a studio session./p> div classheroCtas> a classbtn btn--pill btn--hot hrefhttps://open.spotify.com/track/4UNT9lBqiSI9KbiatolxPY?sieb400639ba2c4df5 target_blank relnoopener>stream stressed/a> a classbtn btn--pill href#book>book a session/a> /div> /div> /header> section classpageSection idlinks aria-labelOfficial links> details classsecDrop> summary> div classsecTitle>links/div> span classsecChevron>▾/span> /summary> div classsecDropBody> div classsocialRow> a classiconLink hrefhttps://open.spotify.com/artist/2tbEf5RXMJ4XXwssBVlJ2B target_blank relnoopener> img srcSpotify.png altSpotify width22 height22 loadinglazy decodingasync> strong>Spotify/strong> /a> a classiconLink hrefhttps://music.apple.com/us/artist/b2nny/1541506202 target_blank relnoopener> img srcApple.png altApple Music width22 height22 loadinglazy decodingasync> strong>Apple Music/strong> /a> a classiconLink hrefhttps://soundcloud.com/b2nny target_blank relnoopener> img srcSoundcloud.png altSoundCloud width22 height22 loadinglazy decodingasync> strong>SoundCloud/strong> /a> a classiconLink hrefhttps://www.youtube.com/channel/UCfdQotfDZTGMygO-wCEGX5g target_blank relnoopener> img srcYoutube.jpg altYouTube width22 height22 loadinglazy decodingasync> strong>YouTube/strong> /a> a classiconLink hrefhttps://instagram.com/b2nnyy target_blank relnoopener> img srcInstagram.png altInstagram width22 height22 loadinglazy decodingasync> strong>Instagram/strong> /a> a classiconLink hrefhttps://x.com/b2nnny target_blank relnoopener> img srcTwitter.jpg altX width22 height22 loadinglazy decodingasync> strong>X/strong> /a> /div> /div> /details> /section> section classpageSection idbook aria-labelBooking> details classsecDrop> summary> div classsecTitle>booking/div> span classsecChevron>▾/span> /summary> div classsecDropBody> div classcard idbookFormCard> div classtitle2>request booking/div> div classsub2>pick a date then pick a start and end time/div> div classtiny>no payment here. I will contact you to confirm./div> div classpriceRow> strong>$60/strong> span>per hour/span> /div> div classgear aria-labelStudio gear> details classgearDrop> summary>gear/summary> ul classgearList> li classgearItem> span>strong>TLM 103/strong>small>microphone/small>/span> span classpill>Mic/span> /li> li classgearItem> span>strong>WA73/strong>small>mic preamp/small>/span> span classpill>Pre/span> /li> li classgearItem> span>strong>dbx 160A/strong>small>compressor/small>/span> span classpill>Comp/span> /li> li classgearItem> span>strong>HS5/strong>small>monitors/small>/span> span classpill>Monitors/span> /li> /ul> /details> /div> form idbkForm novalidate> div styleheight:14px;>/div> div classcalWrap aria-labelAvailability calendar> div classcalTop> div classcalMonth idbkCalMonth>loading…/div> div classcalBtns> button classbtn btn--ghost btn--pill idbkCalPrev typebutton>prev/button> button classbtn btn--ghost btn--pill idbkCalNext typebutton>next/button> /div> /div> div classtimeLegend idbkCalLegend aria-hiddentrue>/div> div styleheight:10px;>/div> div classcalGrid idbkCal>/div> div classtiny idbkCalMsg stylemargin-top:10px;>/div> /div> div styleheight:12px;>/div> div classgrid2> div> label forbkFirst>first name/label> input idbkFirst typetext autocompletegiven-name required /> /div> div> label forbkLast>last name/label> input idbkLast typetext autocompletefamily-name required /> /div> /div> div styleheight:12px;>/div> div classgrid3> div> label forbkEmail>email/label> input idbkEmail typeemail autocompleteemail required /> /div> div> label forbkPhone>phone/label> input idbkPhone typetel autocompletetel inputmodetel required /> /div> div> label forbkIg>instagram/label> input idbkIg typetext placeholder@handle autocompleteusername required /> /div> /div> div styleheight:12px;>/div> div> label forbkDate>date/label> input idbkDate typedate required /> /div> div classtimePick> div classtimeLegend aria-hiddentrue> span classtimeLegendItem>span classtimeSwatch timeSwatch--avail>/span>available/span> span classtimeLegendItem>span classtimeSwatch timeSwatch--busy>/span>unavailable/span> span classtimeLegendItem>span classtimeSwatch timeSwatch--sel>/span>selected/span> /div> div classtimeGrid idbkTiles>/div> div classtimeSummary idbkTimeSummary>/div> div classsrOnly> label forbkStart>start time/label> select idbkStart required>/select> label forbkEnd>end time/label> select idbkEnd required>/select> /div> /div> div styleheight:12px;>/div> div classpriceRow idbkQuote styledisplay:none;> strong>quote/strong> span idbkQuoteLine>/span> /div> div classrules aria-labelStudio rules and minimums> div classtiny>strong>minimum:/strong> span idbkMinHoursLabel>2 hours/span>/div> ul> li>arrive on time (late time still counts)/li> li>no refunds on same-day cancellations/li> li>be ready (references, beats, lyrics)/li> /ul> label classcheckRow stylemargin-top:10px;> input idbkAgree typecheckbox /> span>i understand the rules + minimum and want to request this session/span> /label> /div> div classcalWrap idbkDepositWrap styledisplay:none; margin-top:12px; aria-labelOptional deposit> div classtiny>strong>optional:/strong> pay a strong>$25/strong> deposit to lock your time faster (applied to total)./div> div classcta stylemargin-top:10px;> a classbtn btn--pill idbkDepositStripe target_blank relnoopener>pay deposit (stripe)/a> a classbtn btn--pill btn--ghost idbkDepositPaypal target_blank relnoopener>pay deposit (paypal)/a> /div> /div> div> label forbkNotes>about/label> textarea idbkNotes rows3 placeholderwhat you want to work on, references, any details>/textarea> /div> div classcta> button classbtn idbkSubmit typesubmit>request booking/button> div classmsg idbkMsg rolestatus aria-livepolite>/div> /div> div classtiny stylemargin-top:10px;> times shown in your local timezone: span idbkTz>/span> /div> /form> /div> div classcard idwaitlistCard styledisplay:none;> div classtitle2>day is fully booked/div> div classsub2>join the waitlist and get notified if a slot opens/div> form idwlForm novalidate> div styleheight:10px;>/div> div classgrid3> div> label forwlEmail>email (optional)/label> input idwlEmail typeemail autocompleteemail /> /div> div> label forwlPhone>phone (optional)/label> input idwlPhone typetel autocompletetel inputmodetel /> /div> div> label forwlIg>instagram (optional)/label> input idwlIg typetext placeholder@handle autocompleteusername /> /div> /div> div styleheight:10px;>/div> div> label forwlHours>hours you want/label> select idwlHours> option value>select…/option> option value1>1 hour/option> option value2>2 hours/option> option value3>3 hours/option> option value4>4 hours/option> /select> /div> div classcta stylemargin-top:12px;> button classbtn typesubmit>join waitlist/button> div classmsg idwlMsg rolestatus aria-livepolite>/div> /div> div classtiny stylemargin-top:10px;>no names shown publicly — just availability./div> /form> /div> div classcard idbookSuccessCard styledisplay:none;> div classtitle2>request received/div> div classsub2 idbkSuccessLine>/div> div classtiny>I will contact you to confirm and lock it in./div> div styleheight:12px;>/div> div classpriceRow idbkSuccessPrice>/div> div classcta stylemargin-top:14px;> button classbtn idbkAnother>book another session/button> /div> /div> /div> /details> /section> footer classsiteFooter> div classfooterLinks> a hrefhttps://instagram.com/b2nnyy target_blank relnoopener>dm me/a> span classfooterDot>•/span> a hrefhttps://open.spotify.com/artist/2tbEf5RXMJ4XXwssBVlJ2B target_blank relnoopener>spotify/a> span classfooterDot>•/span> button typebutton idfooterShare classfooterBtn>share/button> /div> div classtiny stylemargin-top:12px;>© script>document.write(new Date().getFullYear());/script> Harmony Hill/div> /footer> /main> div classmodalBackdrop idpopModal> div classmodal roledialog aria-modaltrue aria-labelledbypopTitle tabindex-1> div classmodalHeader> div> div classmodalTitle idpopTitle>Pop Dump/div> div classmodalSub>Pick your platform and stream the EP./div> /div> button classmodalClose typebutton dataClosepopModal aria-labelClose>×/button> /div> div classmodalLinks> a classbtn btn--ghost modalLink hrefhttps://open.spotify.com/album/58jeo8RCP2mkinc7HJpavb?sib71efb8642f44a66 target_blank relnoopener> span>strong>Spotify/strong>small>stream on spotify/small>/span> span classpill>→/span> /a> a classbtn btn--ghost modalLink hrefhttps://music.apple.com/us/album/pop-dump/1858773650 target_blank relnoopener> span>strong>Apple Music/strong>small>stream on apple/small>/span> span classpill>→/span> /a> a classbtn btn--ghost modalLink hrefhttps://soundcloud.com/b2nny/sets/popdump?si69c737891c974c8fa08064e44697c3d5&utm_sourceclipboard&utm_mediumtext&utm_campaignsocial_sharing target_blank relnoopener> span>strong>SoundCloud/strong>small>free streaming/small>/span> span classpill>→/span> /a> /div> /div> /div> div classmodalBackdrop idrapModal> div classmodal roledialog aria-modaltrue aria-labelledbyrapTitle tabindex-1> div classmodalHeader> div> div classmodalTitle idrapTitle>Rap Dump/div> div classmodalSub>Pick your platform and stream the EP./div> /div> button classmodalClose typebutton dataCloserapModal aria-labelClose>×/button> /div> div classmodalLinks> a classbtn btn--ghost modalLink hrefhttps://open.spotify.com/album/7jd2Z6etB43TvzNZ2Qx4fV?sidph-E2EQTL6pDoXsrmNNRQ target_blank relnoopener> span>strong>Spotify/strong>small>stream on spotify/small>/span> span classpill>→/span> /a> a classbtn btn--ghost modalLink hrefhttps://music.apple.com/ca/album/rap-dump/1862592487 target_blank relnoopener> span>strong>Apple Music/strong>small>stream on apple/small>/span> span classpill>→/span> /a> a classbtn btn--ghost modalLink hrefhttps://soundcloud.com/b2nny/sets/rapdump?utm_sourceclipboard&utm_mediumtext&utm_campaignsocial_sharing target_blank relnoopener> span>strong>SoundCloud/strong>small>free streaming/small>/span> span classpill>→/span> /a> /div> /div> /div> script> const API_URL https://script.google.com/macros/s/AKfycbxaScRgE9K1m9UKTj9BTqmPZ7c4k2IOU1g3hM9BwVT7TVTx1XKckK9cHBLX8gYDKzP2/exec; const PRICE_PER_HOUR 60; const MIN_HOURS 2; const DEPOSIT_AMOUNT 25; // optional // Optional: set these to enable deposit checkout buttons. // Example Stripe Payment Link: https://buy.stripe.com/xxxx // Example PayPal.me link: https://paypal.me/yourname (well append /{DEPOSIT_AMOUNT}) const STRIPE_DEPOSIT_LINK ; // -- paste your Stripe payment link const PAYPAL_DEPOSIT_LINK ; // -- paste your PayPal.me link const START_HOUR 11; const END_HOUR 23; const SPOTIFY_URL https://open.spotify.com/artist/2tbEf5RXMJ4XXwssBVlJ2B; function timeLabel(h){ const ap h > 12 ? PM : AM; const hh ((h + 11) % 12) + 1; return hh + :00 + ap; } function isoToday(){ const d new Date(); const y d.getFullYear(); const m String(d.getMonth() + 1).padStart(2,0); const day String(d.getDate()).padStart(2,0); return y + - + m + - + day; } function pad2(n){ return String(n).padStart(2,0); } function fmtLocalFromIso(iso){ const d new Date(iso); const y d.getFullYear(); const m pad2(d.getMonth() + 1); const day pad2(d.getDate()); let hh d.getHours(); const ap hh > 12 ? PM : AM; hh ((hh + 11) % 12) + 1; const mm pad2(d.getMinutes()); return { date: y + - + m + - + day, time: hh + : + mm + + ap }; } function openModal(id){ const el document.getElementById(id); if(el) el.classList.add(open); } function closeModal(id){ const el document.getElementById(id); if(el) el.classList.remove(open); } (function modals(){ const lastFocus new Map(); function getFocusable(container){ if(!container) return ; return Array.from(container.querySelectorAll( ahref, button:not(disabled), textarea, input, select, tabindex:not(tabindex-1) )).filter(el> !el.hasAttribute(disabled) && el.getAttribute(aria-hidden) ! true); } function focusFirst(modalEl){ const focusables getFocusable(modalEl); const first focusables0 || modalEl; try{ first.focus(); } catch(e){} } function trapTab(e, modalEl){ if(e.key ! Tab) return; const focusables getFocusable(modalEl); if(!focusables.length){ e.preventDefault(); modalEl.focus(); return; } const first focusables0; const last focusablesfocusables.length - 1; if(e.shiftKey && document.activeElement first){ e.preventDefault(); last.focus(); } else if(!e.shiftKey && document.activeElement last){ e.preventDefault(); first.focus(); } } function openModalA11y(id){ const el document.getElementById(id); if(!el) return; lastFocus.set(id, document.activeElement); el.classList.add(open); document.body.classList.add(modalOpen); const modal el.querySelector(.modal); if(modal) focusFirst(modal); } function closeModalA11y(id){ const el document.getElementById(id); if(!el) return; el.classList.remove(open); document.body.classList.remove(modalOpen); // If the modal was opened via hash, clean the URL so refresh doesnt reopen it. try{ const hash (location.hash || ).toLowerCase(); const wantsPop (id popModal && hash #pop); const wantsRap (id rapModal && hash #rap); if(wantsPop || wantsRap){ history.replaceState(null, , #home); } } catch(e){} const prev lastFocus.get(id); lastFocus.delete(id); if(prev && typeof prev.focus function){ try{ prev.focus(); } catch(e){} } } document.querySelectorAll(dataClose).forEach(btn>{ btn.addEventListener(click, ()> closeModalA11y(btn.getAttribute(dataClose))); }); popModal,rapModal.forEach(id>{ const el document.getElementById(id); el.addEventListener(click, (e)>{ if(e.target el) closeModalA11y(id); }); el.addEventListener(keydown, (e)>{ if(!el.classList.contains(open)) return; const modal el.querySelector(.modal); if(!modal) return; trapTab(e, modal); }); }); document.addEventListener(keydown, (e)>{ if(e.key Escape){ closeModalA11y(popModal); closeModalA11y(rapModal); } }); window.openPop ()> openModalA11y(popModal); window.openRap ()> openModalA11y(rapModal); window.closePop ()> closeModalA11y(popModal); window.closeRap ()> closeModalA11y(rapModal); })(); (function hashRoutes(){ const BASE_URL https://b2nny.com/; const DEFAULT_DESC Official site for b2nny — releases, links, and booking.; function setMeta({ title, desc, url }){ const t title || b2nny; const d desc || DEFAULT_DESC; const u url || BASE_URL; document.title t; const set (sel, val)>{ const el document.querySelector(sel); if(el) el.setAttribute(content, val); }; const descEl document.querySelector(metanamedescription); if(descEl) descEl.setAttribute(content, d); set(metapropertyog:title, t); set(metapropertyog:description, d); set(metapropertyog:url, u); set(metanametwitter:title, t); set(metanametwitter:description, d); } function onHash(){ const hash (location.hash || #top).toLowerCase(); // Close modals unless explicitly opened. if(hash ! #pop && hash ! #rap){ if(window.closePop) window.closePop(); if(window.closeRap) window.closeRap(); } if(hash #pop){ setMeta({ title: b2nny | Pop Dump, desc: Pop Dump by b2nny — pick Spotify, Apple Music, or SoundCloud and stream the EP., url: BASE_URL + #pop }); if(window.openPop) window.openPop(); return; } if(hash #rap){ setMeta({ title: b2nny | Rap Dump, desc: Rap Dump by b2nny — pick Spotify, Apple Music, or SoundCloud and stream the EP., url: BASE_URL + #rap }); if(window.openRap) window.openRap(); return; } if(hash #book){ setMeta({ title: b2nny | booking, desc: Book a studio session with b2nny — request a date and time, and I’ll confirm with you., url: BASE_URL + #book }); return; } setMeta({ title: b2nny, desc: DEFAULT_DESC, url: BASE_URL + (hash || #top) }); } window.addEventListener(hashchange, onHash); onHash(); })(); function jsonp(url){ return new Promise((resolve, reject)>{ const cb cb + Math.random().toString(36).slice(2); const sep url.includes(?) ? & : ?; const full url + sep + callback + cb; const s document.createElement(script); s.src full; s.async true; const t setTimeout(()>{ cleanup(); reject(new Error(timeout)); }, 12000); function cleanup(){ clearTimeout(t); delete windowcb; if(s && s.parentNode) s.parentNode.removeChild(s); } windowcb (data)>{ cleanup(); resolve(data); }; s.onerror ()>{ cleanup(); reject(new Error(load error)); }; document.head.appendChild(s); }); } async function copyText(text){ if(!text) return false; try{ if(navigator.clipboard && navigator.clipboard.writeText){ await navigator.clipboard.writeText(text); return true; } } catch(e){} try{ const ta document.createElement(textarea); ta.value text; ta.setAttribute(readonly,); ta.style.position fixed; ta.style.left -9999px; ta.style.top 0; document.body.appendChild(ta); ta.select(); const ok document.execCommand(copy); document.body.removeChild(ta); return !!ok; } catch(e){ return false; } } function setText(el, text){ if(!el) return; el.textContent text || ; } function bookingLink(){ try{ return location.origin + location.pathname + #book; } catch(e){ return #book; } } (function footerActions(){ const shareBtn document.getElementById(footerShare); if(shareBtn){ if(navigator.share){ shareBtn.addEventListener(click, async ()>{ try{ await navigator.share({ title: b2nny, text: music + booking, url: location.href }); } catch(e){} }); } else { // Fallback: copy link to clipboard shareBtn.addEventListener(click, async ()>{ const ok await copyText(location.href); if(ok) shareBtn.textContent copied!; setTimeout(()>{ shareBtn.textContent share; }, 1500); }); } } })(); // dropdown menu removed (function booking(){ const formCard document.getElementById(bookFormCard); const successCard document.getElementById(bookSuccessCard); const successLine document.getElementById(bkSuccessLine); const successPrice document.getElementById(bkSuccessPrice); const anotherBtn document.getElementById(bkAnother); const waitlistCard document.getElementById(waitlistCard); const formEl document.getElementById(bkForm); const dateEl document.getElementById(bkDate); const startEl document.getElementById(bkStart); const endEl document.getElementById(bkEnd); const tilesEl document.getElementById(bkTiles); const timeSummaryEl document.getElementById(bkTimeSummary); const firstEl document.getElementById(bkFirst); const lastEl document.getElementById(bkLast); const emailEl document.getElementById(bkEmail); const phoneEl document.getElementById(bkPhone); const igEl document.getElementById(bkIg); const notesEl document.getElementById(bkNotes); const submitBtn document.getElementById(bkSubmit); const msgEl document.getElementById(bkMsg); const tzEl document.getElementById(bkTz); const quoteWrap document.getElementById(bkQuote); const quoteLine document.getElementById(bkQuoteLine); const minHoursLabel document.getElementById(bkMinHoursLabel); const agreeEl document.getElementById(bkAgree); const depositWrap document.getElementById(bkDepositWrap); const depositStripe document.getElementById(bkDepositStripe); const depositPaypal document.getElementById(bkDepositPaypal); const calGrid document.getElementById(bkCal); const calMonthEl document.getElementById(bkCalMonth); const calPrev document.getElementById(bkCalPrev); const calNext document.getElementById(bkCalNext); const calMsg document.getElementById(bkCalMsg); const calLegend document.getElementById(bkCalLegend); const wlForm document.getElementById(wlForm); const wlEmail document.getElementById(wlEmail); const wlPhone document.getElementById(wlPhone); const wlIg document.getElementById(wlIg); const wlHours document.getElementById(wlHours); const wlMsg document.getElementById(wlMsg); let busy new Set(); let selStart null; // number (hour, 24h) let selEnd null; // number (hour, 24h), end hour, exclusive // Calendar cache: date -> { busy:Set, fullyBooked:boolean } const dayCache new Map(); let calYear null; let calMonth null; // 0-based function setTimezoneLabel(){ if(!tzEl) return; try{ const tz Intl.DateTimeFormat().resolvedOptions().timeZone; tzEl.textContent tz || your timezone; } catch(e){ tzEl.textContent your timezone; } } function clampSelection(){ if(selStart null || selEnd null) return; if(selStart START_HOUR) selStart START_HOUR; if(selEnd > END_HOUR) selEnd END_HOUR; if(selEnd selStart) selEnd Math.min(selStart + 1, END_HOUR); } function selectionText(){ if(selStart null || selEnd null) return tap a green tile to pick a time; const hours selEnd - selStart; const startText timeLabel(selStart); const endText timeLabel(selEnd); return selected: + startText + – + endText + ( + hours + hour + (hours 1 ? : s) + ); } function updateTimeSummary(){ if(!timeSummaryEl) return; timeSummaryEl.textContent selectionText(); } function updateQuote(){ if(!quoteWrap || !quoteLine) return; if(selStart null || selEnd null){ quoteWrap.style.display none; quoteLine.textContent ; return; } const hours selEnd - selStart; const total hours * PRICE_PER_HOUR; const minOk hours > MIN_HOURS; quoteWrap.style.display ; quoteLine.textContent (minOk ? : (minimum is + MIN_HOURS + hours. )) + (estimated total: $ + total + ( + hours + hr × $ + PRICE_PER_HOUR + )); } function setSelection(startHour, endHour){ selStart (startHour null) ? null : Number(startHour); selEnd (endHour null) ? null : Number(endHour); if(selStart ! null && selEnd null){ selEnd Math.min(selStart + 1, END_HOUR); } if(selStart ! null && selEnd ! null){ clampSelection(); // Hard rule: dont allow selection across busy slots. if(!isRangeFree(selStart, selEnd)){ // fallback to 1-hour slot if possible const fallbackEnd Math.min(selStart + 1, END_HOUR); if(isRangeFree(selStart, fallbackEnd)){ selEnd fallbackEnd; } else { selStart null; selEnd null; } } } msgEl.textContent ; syncSelectsFromSelection(); renderTiles(); updateTimeSummary(); updateQuote(); } function syncSelectionFromSelects(){ const s Number(startEl && startEl.value); const e Number(endEl && endEl.value); if(Number.isFinite(s) && Number.isFinite(e) && e > s){ selStart s; selEnd e; clampSelection(); } else { selStart null; selEnd null; } renderTiles(); updateTimeSummary(); updateQuote(); } function syncSelectsFromSelection(){ if(!startEl || !endEl) return; if(selStart null || selEnd null) return; // Ensure start options exist and select a valid start. buildStartOptions(); if(startEl.querySelector(optionvalue + String(selStart) + :not(disabled))){ startEl.value String(selStart); } // Ensure end options exist for this start. rebuildEndOptions(); if(endEl.querySelector(optionvalue + String(selEnd) + :not(disabled))){ endEl.value String(selEnd); } else { // pick nearest available end > selStart+1 const firstAvail endEl.querySelector(optgrouplabelAvailable option:not(disabled)) || endEl.querySelector(option:not(disabled)); if(firstAvail){ endEl.value firstAvail.value; selEnd Number(firstAvail.value); } } } function renderTiles(){ if(!tilesEl) return; tilesEl.innerHTML ; for(let h START_HOUR; h END_HOUR; h++){ const label timeLabel(h); const isBusy busy.has(label); const inSel (selStart ! null && selEnd ! null && h > selStart && h selEnd); const btn document.createElement(button); btn.type button; btn.className timeTile + (isBusy ? timeTile--busy : timeTile--avail) + (inSel ? timeTile--sel : ); btn.disabled isBusy; btn.setAttribute(aria-pressed, inSel ? true : false); btn.dataset.hour String(h); const main document.createElement(div); main.className tMain; main.textContent label; const sub document.createElement(div); sub.className tSub; sub.textContent isBusy ? unavailable : available; btn.appendChild(main); btn.appendChild(sub); btn.addEventListener(click, ()>{ if(isBusy) return; // First pick, or reset after a finished range. if(selStart null || selEnd null){ setSelection(h, Math.min(h + 1, END_HOUR)); return; } // If clicking inside the current range, shrink to 1 hour at that tile. if(h > selStart && h selEnd){ setSelection(h, Math.min(h + 1, END_HOUR)); return; } // Extend left if(h selStart){ if(isRangeFree(h, selEnd)){ setSelection(h, selEnd); } else { msgEl.textContent that range includes unavailable time; } return; } // Extend right if(h > selEnd){ const newEnd Math.min(h + 1, END_HOUR); if(isRangeFree(selStart, newEnd)){ setSelection(selStart, newEnd); } else { msgEl.textContent that range includes unavailable time; } return; } }); tilesEl.appendChild(btn); } } function setMinDate(){ const min 2026-01-01; dateEl.min min; const t isoToday(); dateEl.value (t min) ? min : t; } function setMinHoursLabel(){ setText(minHoursLabel, String(MIN_HOURS) + hours); } function setDepositButtons(){ if(!depositWrap) return; const hasStripe !!(STRIPE_DEPOSIT_LINK && STRIPE_DEPOSIT_LINK.trim()); const hasPaypal !!(PAYPAL_DEPOSIT_LINK && PAYPAL_DEPOSIT_LINK.trim()); if(!hasStripe && !hasPaypal){ depositWrap.style.display none; return; } depositWrap.style.display ; if(depositStripe){ depositStripe.style.display hasStripe ? : none; if(hasStripe) depositStripe.href STRIPE_DEPOSIT_LINK.trim(); } if(depositPaypal){ depositPaypal.style.display hasPaypal ? : none; if(hasPaypal){ const base PAYPAL_DEPOSIT_LINK.trim().replace(/\/+$/,); const withAmount /paypal\.me\/.+\/\d+(\.\d+)?$/i.test(base) ? base : (base + / + String(DEPOSIT_AMOUNT)); depositPaypal.href withAmount; } } } function buildStartOptions(){ const prev startEl.value; startEl.innerHTML ; const availGroup document.createElement(optgroup); availGroup.label Available; const unavailGroup document.createElement(optgroup); unavailGroup.label Unavailable; for(let h START_HOUR; h END_HOUR; h++){ const label timeLabel(h); const isBusy busy.has(label); const opt document.createElement(option); opt.value String(h); opt.textContent label + (isBusy ? (Unavailable) : (Available)); if(isBusy) opt.disabled true; (isBusy ? unavailGroup : availGroup).appendChild(opt); } if(availGroup.children.length) startEl.appendChild(availGroup); if(unavailGroup.children.length) startEl.appendChild(unavailGroup); // Keep previous selection if its still available; otherwise pick first available. const prevHour Number(prev); const canKeepPrev Number.isFinite(prevHour) && !busy.has(timeLabel(prevHour)); if(canKeepPrev && startEl.querySelector(optionvalue + prev + :not(disabled))){ startEl.value prev; return; } const firstAvail startEl.querySelector(optgrouplabelAvailable option:not(disabled)) || startEl.querySelector(option:not(disabled)); if(firstAvail){ startEl.value firstAvail.value; } else { startEl.value String(START_HOUR); } } function rebuildEndOptions(){ const s Number(startEl.value); const prev endEl.value; endEl.innerHTML ; const availGroup document.createElement(optgroup); availGroup.label Available; const unavailGroup document.createElement(optgroup); unavailGroup.label Unavailable; for(let h s + 1; h END_HOUR; h++){ const ok isRangeFree(s, h); const opt document.createElement(option); opt.value String(h); opt.textContent timeLabel(h) + (ok ? (Available) : (Unavailable)); if(!ok) opt.disabled true; (ok ? availGroup : unavailGroup).appendChild(opt); } if(availGroup.children.length) endEl.appendChild(availGroup); if(unavailGroup.children.length) endEl.appendChild(unavailGroup); // Keep previous selection if its still available; otherwise pick a sensible default. const prevEnd prev; if(prevEnd && endEl.querySelector(optionvalue + prevEnd + :not(disabled))){ endEl.value prevEnd; return; } const preferred String(Math.min(s + 2, END_HOUR)); if(endEl.querySelector(optionvalue + preferred + :not(disabled))){ endEl.value preferred; return; } const firstAvail endEl.querySelector(optgrouplabelAvailable option:not(disabled)) || endEl.querySelector(option:not(disabled)); if(firstAvail){ endEl.value firstAvail.value; } } function isRangeFree(s, e){ for(let h s; h e; h++){ if(busy.has(timeLabel(h))) return false; } return true; } function isFullyBooked(busySet){ let count 0; for(let h START_HOUR; h END_HOUR; h++){ if(busySet.has(timeLabel(h))) count++; } return count > (END_HOUR - START_HOUR); } async function getBusyForDate(date){ if(dayCache.has(date)) return dayCache.get(date); let set new Set(); if(API_URL && API_URL.indexOf(PASTE_) ! 0){ try{ const data await jsonp(API_URL + ?modebusy&date + encodeURIComponent(date)); if(data && data.ok && Array.isArray(data.busy)) set new Set(data.busy); } catch(e){ set new Set(); } } const info { busy: set, fullyBooked: isFullyBooked(set) }; dayCache.set(date, info); return info; } function daysInMonth(y, m0){ return new Date(y, m0 + 1, 0).getDate(); } function monthLabel(y, m0){ const d new Date(y, m0, 1); return d.toLocaleString(undefined, { month: long, year: numeric }); } function setCalLegend(){ if(!calLegend) return; calLegend.innerHTML span classtimeLegendItem>span classtimeSwatch timeSwatch--avail>/span>available day/span> + span classtimeLegendItem>span classtimeSwatch timeSwatch--busy>/span>fully booked day/span> + span classtimeLegendItem>span classtimeSwatch timeSwatch--sel>/span>selected/span>; } async function renderCalendar(y, m0){ if(!calGrid || !calMonthEl) return; calYear y; calMonth m0; calMonthEl.textContent monthLabel(y, m0); setCalLegend(); const today isoToday(); const totalDays daysInMonth(y, m0); const firstDow new Date(y, m0, 1).getDay(); // 0 Sun const usedCells firstDow + totalDays; const cellCount (usedCells 35) ? 35 : 42; // avoid an extra blank row when possible calGrid.innerHTML ; const cells ; for(let i0; icellCount; i++){ const cell document.createElement(button); cell.type button; cell.className calDay; cell.disabled true; cell.textContent ; cells.push(cell); calGrid.appendChild(cell); } for(let day1; daytotalDays; day++){ const idx firstDow + (day - 1); const date y + - + String(m0 + 1).padStart(2,0) + - + String(day).padStart(2,0); const cell cellsidx; cell.textContent String(day); cell.disabled (date today); cell.dataset.date date; cell.classList.toggle(calDay--sel, date dateEl.value); cell.classList.add(calDay--loading); cell.addEventListener(click, async ()>{ if(cell.disabled) return; dateEl.value date; await loadBusy(); buildStartOptions(); rebuildEndOptions(); syncSelectionFromSelects(); await updateCalHighlights(); }); } await updateCalHighlights(); } async function updateCalHighlights(){ if(!calGrid) return; const buttons Array.from(calGrid.querySelectorAll(.calDaydata-date)); if(!buttons.length) return; setText(calMsg, loading availability…); const queue buttons.slice(); const limit 5; let active 0; let done 0; await new Promise((resolve)>{ const pump ()>{ while(active limit && queue.length){ const btn queue.shift(); active++; const date btn.dataset.date; getBusyForDate(date).then((info)>{ btn.classList.remove(calDay--loading); btn.classList.toggle(calDay--busy, info.fullyBooked); btn.classList.toggle(calDay--avail, !info.fullyBooked); btn.classList.toggle(calDay--sel, date dateEl.value); if(date dateEl.value && waitlistCard){ waitlistCard.style.display info.fullyBooked ? : none; } }).finally(()>{ active--; done++; if(done > buttons.length){ setText(calMsg, ); resolve(); } else { pump(); } }); } }; pump(); }); } async function loadBusy(){ msgEl.textContent ; busy new Set(); if(!API_URL || API_URL.indexOf(PASTE_) 0){ msgEl.textContent backend not set; return; } const date dateEl.value; if(!date) return; try{ const data await jsonp(API_URL + ?modebusy&date + encodeURIComponent(date)); if(data && data.ok && Array.isArray(data.busy)){ busy new Set(data.busy); } } catch(e){ busy new Set(); } dayCache.set(date, { busy, fullyBooked: isFullyBooked(busy) }); if(waitlistCard) waitlistCard.style.display isFullyBooked(busy) ? : none; } function validate(){ // reset per-field errors const fields { el: firstEl, name: first name, type: text }, { el: lastEl, name: last name, type: text }, { el: emailEl, name: email, type: email }, { el: phoneEl, name: phone, type: tel }, { el: igEl, name: instagram, type: text }, { el: dateEl, name: date, type: date } ; fields.forEach(f>{ f.el.classList.remove(isInvalid); f.el.removeAttribute(aria-invalid); }); const problems ; function mark(el, text){ problems.push(text); el.classList.add(isInvalid); el.setAttribute(aria-invalid,true); } if(!firstEl.value.trim()) mark(firstEl, first name is required); if(!lastEl.value.trim()) mark(lastEl, last name is required); const emailVal emailEl.value.trim(); if(!emailVal) mark(emailEl, email is required); else if(!/^^\s@+@^\s@+\.^\s@+$/.test(emailVal)) mark(emailEl, email looks invalid); if(!phoneEl.value.trim()) mark(phoneEl, phone is required); if(!igEl.value.trim()) mark(igEl, instagram is required); if(!dateEl.value) mark(dateEl, date is required); const s Number(startEl.value); const e Number(endEl.value); if(!Number.isFinite(s) || !Number.isFinite(e) || e s) problems.push(select a valid time range); else if(!isRangeFree(s, e)) problems.push(that time is already booked); else { const hours e - s; if(hours MIN_HOURS) problems.push(minimum is + MIN_HOURS + hours); } if(agreeEl && !agreeEl.checked){ problems.push(confirm studio rules); agreeEl.classList.add(isInvalid); agreeEl.setAttribute(aria-invalid,true); } else if(agreeEl){ agreeEl.classList.remove(isInvalid); agreeEl.removeAttribute(aria-invalid); } if(problems.length){ // Keep button text consistent in case a prior attempt changed it. submitBtn.disabled false; submitBtn.textContent request booking; // Focus the first invalid field (if any). const firstBad document.querySelector(.isInvalid); if(firstBad && typeof firstBad.focus function){ try{ firstBad.focus(); } catch(e){} } return fix: + problems.join(, ); } return ; } function showSuccess(data, sHour, eHour){ const hours eHour - sHour; const total hours * PRICE_PER_HOUR; formCard.style.display none; successCard.style.display block; const st data && data.start ? fmtLocalFromIso(data.start) : null; const en data && data.end ? fmtLocalFromIso(data.end) : null; const dateText st ? st.date : dateEl.value; const startText st ? st.time : timeLabel(sHour); const endText en ? en.time : timeLabel(eHour); successLine.textContent dateText + + startText + to + endText + ( + hours + hour + (hours 1 ? : s) + ); successPrice.innerHTML strong>$ + total + /strong>span>estimated total/span>; msgEl.textContent ; } function resetFormView(){ successCard.style.display none; formCard.style.display block; msgEl.textContent ; notesEl.value ; } async function submit(){ const err validate(); if(err){ msgEl.textContent err; return; } if(!API_URL || API_URL.indexOf(PASTE_) 0){ msgEl.textContent backend not set; return; } const fullName firstEl.value.trim() + + lastEl.value.trim(); const sHour Number(startEl.value); const eHour Number(endEl.value); const hours eHour - sHour; const durationMinutes hours * 60; submitBtn.disabled true; submitBtn.textContent sending...; msgEl.textContent ; const q new URLSearchParams(); q.set(mode,book); q.set(name, fullName); q.set(email, emailEl.value.trim()); q.set(phone, phoneEl.value.trim()); q.set(instagram, igEl.value.trim()); q.set(date, dateEl.value); q.set(time, timeLabel(sHour)); q.set(durationMinutes, String(durationMinutes)); q.set(notes, String(notesEl.value || ).trim()); q.set(minHours, String(MIN_HOURS)); q.set(agreedRules, agreeEl && agreeEl.checked ? yes : no); q.set(depositAmount, String(DEPOSIT_AMOUNT)); try{ const data await jsonp(API_URL + ? + q.toString()); if(!data || !data.ok){ msgEl.textContent (data && data.error) ? data.error : error, try again; submitBtn.disabled false; submitBtn.textContent request booking; return; } showSuccess(data, sHour, eHour); await loadBusy(); buildStartOptions(); rebuildEndOptions(); } catch(e){ msgEl.textContent error, try again; submitBtn.disabled false; submitBtn.textContent request booking; } } anotherBtn.addEventListener(click, async ()>{ resetFormView(); await loadBusy(); buildStartOptions(); rebuildEndOptions(); }); setMinDate(); buildStartOptions(); rebuildEndOptions(); syncSelectionFromSelects(); updateQuote(); setMinHoursLabel(); setDepositButtons(); // Calendar init try{ const d new Date(dateEl.value || isoToday()); renderCalendar(d.getFullYear(), d.getMonth()); } catch(e){} if(calPrev){ calPrev.addEventListener(click, ()>{ if(calYear null || calMonth null) return; const d new Date(calYear, calMonth, 1); d.setMonth(d.getMonth() - 1); renderCalendar(d.getFullYear(), d.getMonth()); }); } if(calNext){ calNext.addEventListener(click, ()>{ if(calYear null || calMonth null) return; const d new Date(calYear, calMonth, 1); d.setMonth(d.getMonth() + 1); renderCalendar(d.getFullYear(), d.getMonth()); }); } dateEl.addEventListener(change, async ()>{ await loadBusy(); buildStartOptions(); rebuildEndOptions(); syncSelectionFromSelects(); if(calYear ! null && calMonth ! null) updateCalHighlights(); }); startEl.addEventListener(change, ()>{ rebuildEndOptions(); syncSelectionFromSelects(); }); if(formEl){ formEl.addEventListener(submit, (e)>{ e.preventDefault(); submit(); }); } else { submitBtn.addEventListener(click, submit); } loadBusy().then(()>{ buildStartOptions(); rebuildEndOptions(); syncSelectionFromSelects(); if(calYear ! null && calMonth ! null) updateCalHighlights(); }); setTimezoneLabel(); // Waitlist submission (requires backend support: modewaitlist) if(wlForm){ wlForm.addEventListener(submit, async (e)>{ e.preventDefault(); if(!API_URL || API_URL.indexOf(PASTE_) 0){ setText(wlMsg, backend not set); return; } const date dateEl.value; const email (wlEmail && wlEmail.value || ).trim(); const phone (wlPhone && wlPhone.value || ).trim(); const ig (wlIg && wlIg.value || ).trim(); const hrs (wlHours && wlHours.value || ).trim(); if(!date){ setText(wlMsg, pick a date first); return; } if(!email && !phone && !ig){ setText(wlMsg, enter email or phone or instagram); return; } if(!hrs){ setText(wlMsg, select hours); return; } setText(wlMsg, joining…); const q new URLSearchParams(); q.set(mode,waitlist); q.set(date, date); q.set(hours, hrs); q.set(email, email); q.set(phone, phone); q.set(instagram, ig); try{ const data await jsonp(API_URL + ? + q.toString()); if(data && data.ok){ setText(wlMsg, added to waitlist — you’ll be notified if a slot opens); wlForm.reset(); } else { setText(wlMsg, (data && data.error) ? data.error : error, try again); } } catch(err){ setText(wlMsg, error, try again); } }); } })(); /script>/body>/html>
View on OTX
|
View on ThreatMiner
Please enable JavaScript to view the
comments powered by Disqus.
Data with thanks to
AlienVault OTX
,
VirusTotal
,
Malwr
and
others
. [
Sitemap
]