const { Panel, Label, Badge, Button, Monogram } = window.MakiSClubDesignSystem_d11b87; const MK = window.MK; function SiteNav({ active, playersOnline }) { const tabs = [['Home', "Maki's Club.html"], ['Standings', 'Standings.html'], ['Skins', 'Skins.html'], ['House Rules', 'House Rules.html']]; // Staff comes from the server session (MK_STAFF_STEAMIDS), not the client — // there is no localStorage flag to flip any more. if (MK.SESSION && MK.SESSION.isStaff) tabs.push(['Admin', 'Admin.html']); const players = playersOnline != null ? playersOnline : MK.SERVERS.filter(s => s.online).reduce((n, s) => n + s.players, 0); return (
Maki's Club
); } // Site-wide Steam sign-in control. Identity is a signed session cookie set by // /api/auth/callback only after Steam confirms it signed the OpenID assertion — // there is no client-side auth state, and no way to browse as someone else. function SteamAuth() { const me = MK.SESSION; const [open, setOpen] = React.useState(false); const ref = React.useRef(null); React.useEffect(() => { const h = e => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); }; document.addEventListener('mousedown', h); return () => document.removeEventListener('mousedown', h); }, []); const signIn = () => { location.href = 'api/auth/login'; }; const signOut = () => { fetch('api/auth/logout', { method: 'POST', credentials: 'same-origin' }).catch(() => {}).then(() => location.reload()); }; const steam = ; if (!me) return ; return (
{open && (
signed in with steam
{me.name}
{!me.isMember &&
no rounds on record yet
}
{me.slug && View profile} My skins
)}
); } function MkFooter() { const linkCss = {fontFamily:'var(--mk-font-mono)',fontSize:11,fontWeight:600,letterSpacing:'0.16em',textTransform:'uppercase',textDecoration:'none',color:'var(--mk-violet)'}; return ( ); } /* Approximate ping. A browser cannot open a UDP socket, so the real in-game latency is out of reach — the number the imported design showed was Math.random() jitter around a constant. What is measurable: an empty HTTPS request to the game hostname, which is a DNS-only record on the club's own connection, so the round trip covers the same internet path the game traffic takes. Warm up first (that request pays DNS + TCP + TLS), then take the fastest of three samples — the minimum is the one least polluted by scheduling noise. responseStart - requestStart is the request/response pair on its own, and is readable cross-origin only because /mk-ping sends Timing-Allow-Origin. undefined = still measuring, null = unavailable. Never a made-up number. */ const PING_NOTE = 'approximate — timed over HTTPS from your browser to the server’s network. Your in-game ping will differ.'; function hostOf(addr) { return String(addr || '').split(':')[0]; } async function measurePing(host) { const url = n => 'https://' + host + '/mk-ping?n=' + n; const opts = { cache: 'no-store', mode: 'cors', credentials: 'omit' }; try { await fetch(url('warm'), opts); } catch (e) { return null; } const samples = []; for (let i = 0; i < 3; i++) { const u = url(i + '-' + Math.random().toString(36).slice(2)); const t0 = performance.now(); try { await fetch(u, opts); } catch (e) { return null; } const wall = performance.now() - t0; const e = performance.getEntriesByName(u).pop(); samples.push(e && e.responseStart && e.requestStart ? e.responseStart - e.requestStart : wall); } try { performance.clearResourceTimings(); } catch (e) {} return Math.max(1, Math.round(Math.min.apply(null, samples))); } /* Keyed by host, not by server: every room on one box shares one round trip. */ function useApproxPing(servers) { const [pings, setPings] = React.useState({}); const hosts = Array.from(new Set((servers || []).filter(s => s.online).map(s => hostOf(s.addr)))).sort().join(','); React.useEffect(() => { let live = true; (hosts ? hosts.split(',') : []).forEach(h => { measurePing(h).then(ms => { if (live) setPings(p => Object.assign({}, p, { [h]: ms })); }); }); return () => { live = false; }; }, [hosts]); return pings; } function PingText({ ms, style }) { const txt = ms === undefined ? '…' : ms === null ? '—' : '≈' + ms + ' ms'; return {txt}; } function StatusDot({ online }) { return {online ? 'online' : 'offline'} ; } /* Two different connect paths, and they do not accept the same thing. Steam's steam://connect/ URL handler needs a numeric address (server.connect, built from the resolved IP by the API); the in-game console resolves DNS, so the copied string keeps the hostname and survives a WAN IP change. */ function ConnectActions({ server, size }) { const [copied, setCopied] = React.useState(false); const signal = () => { try { mkChime(); } catch (e) {} window.dispatchEvent(new CustomEvent('mk-connect')); }; const copy = () => { navigator.clipboard.writeText('connect ' + server.addr).catch(()=>{}); signal(); setCopied(true); setTimeout(() => setCopied(false), 1600); }; return (
); } const mkMono = {fontFamily:'var(--mk-font-mono)',fontSize:12,fontWeight:600,letterSpacing:'0.12em',textTransform:'uppercase'}; // Marks the parts of the site that are NOT reading real data. Player stats and // standings come from the K4-System tables and server status from A2S; the // CounterStrikeSharp admin tables aren't installed, so moderation stays mock // and says so rather than passing itself off. function DemoNote({ children, style }) { return (

demo data — {children}

); } if (!document.getElementById('mk-modal-css')) { const st = document.createElement('style'); st.id = 'mk-modal-css'; st.textContent = [ '@keyframes mkFade{from{opacity:0}to{opacity:1}}', '@keyframes mkPop{from{opacity:0;transform:translateY(18px) scale(.965)}to{opacity:1;transform:none}}', '@keyframes mkRise{from{opacity:0;transform:translateY(22px)}to{opacity:1;transform:none}}', '@keyframes mkKey{0%,100%{opacity:.28}50%{opacity:1}}', '@keyframes mkKeyScarlet{0%,100%{opacity:.55}50%{opacity:1}}', '@keyframes mkPulse{0%{box-shadow:0 0 0 0 rgba(244,239,228,.5)}70%{box-shadow:0 0 0 6px rgba(244,239,228,0)}100%{box-shadow:0 0 0 0 rgba(244,239,228,0)}}', '@keyframes mkTick{0%,100%{transform:translateX(-2px)}50%{transform:translateX(2px)}}', '.mk-rise{animation:mkRise .55s cubic-bezier(.2,.7,.2,1) both}', '.mk-card-hit{cursor:pointer}', '.mk-card-hit img{transition:transform .5s cubic-bezier(.2,.7,.2,1),filter .3s}', '.mk-card-hit:hover img{transform:scale(1.045)}', '.mk-piano{display:flex;align-items:stretch}', '.mk-piano span{flex:1;min-height:100%;border-radius:2px;animation:mkKey 1.8s ease-in-out infinite both}', '.mk-piano span.sc{animation-name:mkKeyScarlet}', '.mk-piano.play span{animation-duration:.9s}', '.mk-reveal{opacity:0;transform:translateY(22px)}', '.mk-reveal.in{animation:mkRise .6s cubic-bezier(.2,.7,.2,1) both}', '.mk-live-dot{width:7px;height:7px;border-radius:50%;background:var(--mk-scarlet);animation:mkPulse 2s ease-out infinite}', '.mk-status-live{position:relative}', '.mk-status-live::after{content:"";position:absolute;inset:-4px;border-radius:50%;animation:mkPulse 2.4s ease-out infinite}', '.mk-metro{display:inline-block;animation:mkTick 1.1s ease-in-out infinite}', '.mk-tempo span{animation:mkKey var(--d,1.8s) ease-in-out infinite both}', '.mk-tempo span.sc{animation-name:mkKeyScarlet}', '.mk-tempo.flash span{animation:mkKey var(--d,1.8s) ease-in-out infinite both,mkTempoPop .62s cubic-bezier(.2,.7,.2,1) both}', '.mk-tempo.flash span.sc{animation-name:mkKeyScarlet,mkTempoPop}', '@keyframes mkTempoPop{0%{transform:scaleY(1)}32%{transform:scaleY(2.2);filter:brightness(1.6)}100%{transform:scaleY(1)}}', '.mk-modal-close{background:none;border:1px solid rgba(244,239,228,.25);border-radius:10px;color:var(--mk-smoke);font-family:var(--mk-font-mono);font-size:11px;font-weight:600;letter-spacing:.16em;text-transform:uppercase;padding:8px 14px;cursor:pointer}', '.mk-modal-close:hover{color:var(--mk-ivory);border-color:rgba(244,239,228,.5)}', '@media (prefers-reduced-motion:reduce){.mk-rise,.mk-reveal,.mk-reveal.in,.mk-piano span,.mk-live-dot,.mk-status-live::after,.mk-metro,.mk-tempo span{animation:none!important;opacity:1!important;transform:none!important}}' ].join(''); document.head.appendChild(st); } function PianoStripe({ height = 10, gap = 5, play, style }) { return (
{[0,1,2,3,4].map(i => )}
); } // Tempo marking from how busy the club is right now (an-empty-room to packed). function tempoMark(players) { if (players <= 0) return 'Tacet'; if (players < 8) return 'Adagio'; if (players < 18) return 'Andante'; if (players < 30) return 'Allegro'; return 'Presto'; } // A five-note motif on connect — one soft note per key of the stripe. function mkChime() { try { const AC = window.AudioContext || window.webkitAudioContext; if (!AC) return; const ctx = mkChime._ctx || (mkChime._ctx = new AC()); if (ctx.state === 'suspended') ctx.resume(); const now = ctx.currentTime; [523.25, 587.33, 659.25, 783.99, 1046.5].forEach((f, i) => { const o = ctx.createOscillator(), g = ctx.createGain(); o.type = 'triangle'; o.frequency.value = f; const t = now + i * 0.072; g.gain.setValueAtTime(0.0001, t); g.gain.exponentialRampToValueAtTime(0.12, t + 0.015); g.gain.exponentialRampToValueAtTime(0.0008, t + 0.34); o.connect(g).connect(ctx.destination); o.start(t); o.stop(t + 0.38); }); } catch (e) {} } // The five-key stripe as a living heartbeat: tempo scales with players online, // and every connect (Play / copy) ripples the keys via the 'mk-connect' event. function TempoStripe({ players = 0, height = 4, gap = 5, style }) { const dur = Math.max(2.0, 3.8 - Math.min(players, 40) / 40 * 1.8); const [flash, setFlash] = React.useState(false); React.useEffect(() => { let id; const on = () => { setFlash(true); clearTimeout(id); id = setTimeout(() => setFlash(false), 660); }; window.addEventListener('mk-connect', on); return () => { window.removeEventListener('mk-connect', on); clearTimeout(id); }; }, []); return (
{[0,1,2,3,4].map(i => )}
); } function useReveal(once) { const ref = React.useRef(null); const [seen, setSeen] = React.useState(false); React.useEffect(() => { const el = ref.current; if (!el) return; const io = new IntersectionObserver(([e]) => { if (e.isIntersecting) { setSeen(true); if (once) io.disconnect(); } else if (!once) setSeen(false); }, { threshold: 0.15 }); io.observe(el); return () => io.disconnect(); }, []); return [ref, seen]; } function Reveal({ children, delay = 0, once, as = 'div', className = '', style }) { const [ref, seen] = useReveal(once); return React.createElement(as, { ref, className: ('mk-reveal ' + (seen ? 'in ' : '') + className).trim(), style: { animationDelay: delay + 's', ...style } }, children); } /* Who's connected, from A2S. The protocol reports score and time only — no deaths, so there is no k/d to show — and no SteamID, so a row links to a member page only when the display name matches one. */ function Roster({ server }) { const list = (MK.ROSTERS[server.id] || []).slice().sort((a, b) => b.score - a.score); const known = Object.fromEntries((MK.PLAYERS || []).map(p => [p.name, p.slug])); const rGrid = {display:'grid',gridTemplateColumns:'2fr .8fr .9fr',gap:8,padding:'8px 20px',alignItems:'center'}; const since = m => m >= 60 ? Math.floor(m / 60) + 'h ' + (m % 60) + 'm' : m + 'm'; if (!server.online) return

Room's closed — no one inside.

; if (!list.length) return

Empty room — first one in picks the music.

; return (
{['player','score','in the room'].map(h => {h})}
{list.map((r, i) => (
{known[r.name] ? {r.name} : {r.name}} {r.score} {since(r.minutes)}
))}
); } function ServerModal({ server: s, ping, onClose }) { const [closing, setClosing] = React.useState(false); const close = () => { setClosing(true); setTimeout(onClose, 180); }; React.useEffect(() => { const onKey = e => { if (e.key === 'Escape') close(); }; document.addEventListener('keydown', onKey); const prev = document.body.style.overflow, prevH = document.documentElement.style.overflow; document.body.style.overflow = 'hidden'; document.documentElement.style.overflow = 'hidden'; return () => { document.removeEventListener('keydown', onKey); document.body.style.overflow = prev; document.documentElement.style.overflow = prevH; }; }, []); const full = s.online && s.players >= s.max; return ReactDOM.createPortal((
e.stopPropagation()} padding={0} style={{width:'min(680px,100%)',overflow:'hidden',animation:closing?'mkPop .18s reverse forwards':'mkPop .32s cubic-bezier(.2,.7,.2,1)'}}>
{s.map}
{s.map}{full && full}

{s.name}

{s.online ? s.players + ' / ' + s.max : '— / ' + s.max}

{s.addr} · ping is {PING_NOTE}

), document.body); } function ServerCard({ server: s, ping, layout, index }) { const [open, setOpen] = React.useState(false); const [ref, seen] = useReveal(); const full = s.online && s.players >= s.max; const rise = { animationDelay: ((index || 0) * 0.09) + 's' }; const revealCls = 'mk-reveal' + (seen ? ' in' : ''); const modal = open && setOpen(false)} />; if (layout === 'row') { return (
setOpen(true)} style={{borderRadius:8,overflow:'hidden'}}> {s.map}
setOpen(true)} style={{display:'flex',flexDirection:'column',gap:8,minWidth:0}}> {s.name}
{s.map}{full && full}
{s.online ? s.players + ' / ' + s.max + ' playing' : '— / ' + s.max}
{s.addr}
{modal}
); } return (
setOpen(true)}>
{s.map}
{s.map} {full && full}
details
{s.name}
{s.online ? s.players + ' / ' + s.max + ' playing' : '— / ' + s.max}
{s.addr}
{modal}
); } function LegalPage({ active, eyebrow, title, updated, children }) { return (

{title}

last updated — {updated}

{children}
); } // ── Nav player search: magnifier that expands into a live field on click ── function PlayerSearch() { const [q, setQ] = React.useState(''); const [open, setOpen] = React.useState(false); const [hi, setHi] = React.useState(0); const [exp, setExp] = React.useState(false); const boxRef = React.useRef(null); const inRef = React.useRef(null); const results = React.useMemo(() => { const s = q.trim().toLowerCase(); if (!s) return []; return (MK.PLAYERS || []).map(p => ({ p, i: (p.name + ' ' + p.rank).toLowerCase().indexOf(s) })).filter(x => x.i >= 0).sort((a, b) => a.i - b.i).slice(0, 6).map(x => x.p); }, [q]); React.useEffect(() => setHi(0), [q]); React.useEffect(() => { if (exp && inRef.current) inRef.current.focus(); }, [exp]); React.useEffect(() => { const onDoc = e => { if (boxRef.current && !boxRef.current.contains(e.target)) { setOpen(false); if (!q) setExp(false); } }; document.addEventListener('mousedown', onDoc); return () => document.removeEventListener('mousedown', onDoc); }, [q]); const go = p => { if (p) location.href = 'Profile.html?p=' + p.slug; }; const onKey = e => { if (e.key === 'ArrowDown') { e.preventDefault(); setOpen(true); setHi(h => Math.min(results.length - 1, h + 1)); } else if (e.key === 'ArrowUp') { e.preventDefault(); setHi(h => Math.max(0, h - 1)); } else if (e.key === 'Enter') go(results[hi]); else if (e.key === 'Escape') { setOpen(false); setExp(false); } }; const mag = (color) => ; const show = exp && open && q.trim().length > 0; return (
{exp ? (
{mag('var(--mk-smoke)')} { setQ(e.target.value); setOpen(true); }} onKeyDown={onKey} onBlur={() => setTimeout(() => { if (!q) setExp(false); }, 140)} placeholder="search members" aria-label="Search members" style={{width:'100%',boxSizing:'border-box',background:'transparent',border:'none',color:'var(--mk-ivory)',fontFamily:'var(--mk-font-mono)',fontSize:12,letterSpacing:'0.06em',padding:0,outline:'none'}} />
) : ( )} {show && (
{results.length === 0 ? (
No members match “{q.trim()}”.
) : results.map((p, idx) => ( go(p)} onMouseEnter={() => setHi(idx)} style={{display:'flex',alignItems:'center',justifyContent:'space-between',gap:12,padding:'10px 13px',textDecoration:'none',background:idx===hi?'rgba(244,239,228,.06)':'transparent',borderTop:idx?'1px solid var(--mk-border)':'none'}}> {p.name} {p.rank} {p.points.toLocaleString('en-US')} ))}
)}
); } // ── Mock API fetch with loading / error states. ?api=slow or ?api=down to demo. ── function useMockFetch(getData) { const [state, setState] = React.useState({ loading: true, error: null, data: null }); const load = React.useCallback(() => { setState({ loading: true, error: null, data: null }); const q = new URLSearchParams(location.search).get('api'); const delay = q === 'slow' ? 2600 : q === 'down' ? 900 : 600; const id = setTimeout(() => { if (q === 'down') { setState({ loading: false, error: 'unreachable', data: null }); return; } try { const d = getData(); setState({ loading: false, error: null, data: d }); } catch (e) { setState({ loading: false, error: 'unreachable', data: null }); } }, delay); return () => clearTimeout(id); }, []); React.useEffect(() => load(), [load]); return { ...state, retry: load }; } function SkeletonBlock({ w = '100%', h = 16, r = 8, style }) { return
; } function PlayerSkeleton() { return (
{[0,1,2,3,4,5].map(i =>
)}
loading stats from K4-System…
); } function ApiError({ onRetry, label }) { return (

Can't reach the server

The stats desk isn't answering. Give it a moment.

{label || 'K4-System · request timed out'}

{onRetry && }
); } function ApiEmpty({ slug }) { return (

No member by that name

{slug ? '“' + slug + '” isn\'t on the books.' : 'Pick a member from the standings.'}

← back to standings
); } if (!document.getElementById('mk-api-css')) { const st = document.createElement('style'); st.id = 'mk-api-css'; st.textContent = [ '@keyframes mkShimmer{0%{background-position:-200% 0}100%{background-position:200% 0}}', '.mk-shimmer{background:linear-gradient(90deg,rgba(244,239,228,.04) 25%,rgba(244,239,228,.10) 37%,rgba(244,239,228,.04) 63%);background-size:200% 100%;animation:mkShimmer 1.4s ease-in-out infinite;border-radius:8px}', '.mk-search-input:focus{border-color:var(--mk-violet)}', '.mk-authbtn{display:inline-flex;align-items:center;gap:8px;white-space:nowrap;font-family:var(--mk-font-mono);font-size:12px;font-weight:600;letter-spacing:.14em;text-transform:uppercase;color:var(--mk-ivory);background:var(--mk-surface);border:1px solid var(--mk-border);border-radius:10px;padding:9px 14px;cursor:pointer;transition:border-color .14s}', '.mk-authbtn:hover{border-color:rgba(244,239,228,.5)}', '.mk-authchip{display:flex;align-items:center;gap:9px;background:var(--mk-surface);border:1px solid var(--mk-border);border-radius:10px;padding:5px 11px 5px 6px;cursor:pointer;transition:border-color .14s}', '.mk-authchip:hover{border-color:var(--mk-line-violet)}', '.mk-authname{font-family:var(--mk-font-mono);font-size:12px;font-weight:600;letter-spacing:.06em;color:var(--mk-ivory);max-width:120px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}', '.mk-authitem{display:block;width:100%;box-sizing:border-box;text-align:left;padding:11px 15px;font-family:var(--mk-font-mono);font-size:11px;font-weight:600;letter-spacing:.14em;text-transform:uppercase;color:var(--mk-smoke);background:none;border:none;text-decoration:none;cursor:pointer}', '.mk-authitem+.mk-authitem{border-top:1px solid var(--mk-border)}', '.mk-authitem:hover{color:var(--mk-ivory);background:rgba(244,239,228,.05)}', '.mk-search-input::placeholder{color:var(--mk-smoke)}', '@media (prefers-reduced-motion:reduce){.mk-shimmer{animation:none}}' ].join(''); document.head.appendChild(st); } Object.assign(window, { DemoNote, SiteNav, SteamAuth, MkFooter, useApproxPing, PingText, ServerCard, ServerModal, ConnectActions, StatusDot, LegalPage, Roster, PianoStripe, TempoStripe, tempoMark, mkChime, useReveal, Reveal, PlayerSearch, useMockFetch, SkeletonBlock, PlayerSkeleton, ApiError, ApiEmpty });