<?php
/**
 * /transparency — live, verifiable numbers straight from the submission log.
 * Server-rendered on every request from:
 *   /etc/rapture-fm/paid-submissions.jsonl   (every submission ever)
 *   /etc/rapture-fm/submission-status.json   (curator decisions)
 *   api/saves.json                           (Spotify saves, 6h scraper)
 * Accepted tracks are public (they're on the playlist); declined artists are
 * never named — aggregate counts only.
 */

$PAID_LOG = '/etc/rapture-fm/paid-submissions.jsonl';
$STATUS = '/etc/rapture-fm/submission-status.json';
$SAVES = __DIR__ . '/api/saves.json';
$LEGACY = '/etc/rapture-fm/legacy-placements.json'; // optional, curator-provided

$subs = [];
if (file_exists($PAID_LOG)) {
    foreach (file($PAID_LOG, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) as $line) {
        $e = @json_decode($line, true);
        if ($e && !empty($e['session_id'])) $subs[$e['session_id']] = $e;
    }
}
$status = file_exists($STATUS) ? (@json_decode(@file_get_contents($STATUS), true) ?: []) : [];
$saves = @json_decode(@file_get_contents($SAVES), true) ?: [];
$legacy = file_exists($LEGACY) ? (@json_decode(@file_get_contents($LEGACY), true) ?: []) : [];

$total = count($subs);
$accepted = $declined = 0;
$decisionHours = [];
$feedbackLens = [];
$placements = [];

foreach ($status as $sid => $st) {
    $s = $subs[$sid] ?? null;
    $state = $st['status'] ?? '';
    if ($state === 'accepted') {
        $accepted++;
        if ($s) {
            $placements[] = [
                'artist' => $s['artist'] ?: '(unknown)',
                'title' => $s['track_title'] ?: '(untitled)',
                'track_id' => $st['spotify_track_id'] ?? null,
                'playlist' => $s['playlist'] ?? 'afro',
                'weeks' => $st['weeks_on_playlist'] ?? null,
                'at' => $st['updated_at'] ?? ($s['completed_at'] ?? ''),
            ];
        }
    } elseif ($state === 'declined') {
        $declined++;
    }
    if (in_array($state, ['accepted', 'declined'], true) && $s && !empty($st['updated_at'])) {
        $h = (strtotime($st['updated_at']) - strtotime($s['completed_at'])) / 3600;
        if ($h > 0 && $h < 24 * 14) $decisionHours[] = $h;
    }
    if (!empty($st['feedback'])) $feedbackLens[] = mb_strlen($st['feedback']);
}

usort($placements, fn($a, $b) => strcmp($b['at'], $a['at']));
$decided = $accepted + $declined;
$acceptRate = $decided ? round($accepted / $decided * 100) : null;
$avgHours = $decisionHours ? round(array_sum($decisionHours) / count($decisionHours), 1) : null;
$avgFeedback = $feedbackLens ? round(array_sum($feedbackLens) / count($feedbackLens)) : null;
$afroSaves = $saves['playlists']['afro']['saves'] ?? ($saves['saves'] ?? null);
$melSaves = $saves['playlists']['melodic']['saves'] ?? null;
$today = date('j F Y');

$plName = fn($k) => match ($k) {
    'afro' => "Chill Afro House '26",
    'melodic' => "Melodic Afro House '26",
    default => $k,
};
$fmt = fn($n) => $n === null ? '—' : number_format((int)$n);
$e = fn($s) => htmlspecialchars((string)$s, ENT_QUOTES);
?><!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
<title>Transparency — Real Submission Numbers — Rapture Records</title>
<meta name="description" content="Live, verifiable numbers from Rapture Records: total submissions, real accept rate, average decision time, and every placement currently earned — pulled straight from the submission log." />
<meta name="robots" content="index, follow, max-image-preview:large" />
<link rel="canonical" href="https://rapture-records.com/transparency" />
<meta property="og:type" content="article" />
<meta property="og:title" content="Transparency — Real Submission Numbers — Rapture Records" />
<meta property="og:description" content="Total submissions, real accept rate, average decision time, and every placement — live from the log." />
<meta property="og:image" content="https://rapture-records.com/og.jpg" />
<meta property="og:url" content="https://rapture-records.com/transparency" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:image" content="https://rapture-records.com/og.jpg" />
<link rel="icon" type="image/jpeg" href="branding/rapture-icon.jpg" />
<link rel="stylesheet" href="shared/tokens.css" />
<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "Article",
  "headline": "Rapture Records Transparency — Real Submission Numbers",
  "dateModified": "<?= date('Y-m-d') ?>",
  "author": { "@type": "Person", "name": "Ben Ferrier", "sameAs": ["https://open.spotify.com/user/fbnw96f8frhqrp7707ulbz52e", "https://www.instagram.com/rapture.ofc/"] },
  "publisher": { "@type": "Organization", "name": "Rapture Records" },
  "mainEntityOfPage": "https://rapture-records.com/transparency",
  "about": ["Spotify playlist submission", "curator transparency", "accept rate"]
}
</script>
<style>
  body { padding-top: 4rem; overflow-x: hidden; }
  .wrap { max-width: 860px; margin: 0 auto; padding: 0 1.4rem; }
  @media (min-width: 768px) { .wrap { padding: 0 2rem; } }
  .hero { text-align: center; padding: 5rem 0 2.5rem; border-bottom: 1px solid var(--hairline); }
  .hero .eyebrow { font-family: var(--f-utility); text-transform: uppercase; letter-spacing: 0.38em; font-size: 0.68rem; color: var(--sand); margin-bottom: 1.4rem; }
  .hero h1 { font-family: var(--f-display); font-weight: 300; font-size: clamp(2rem, 5vw, 3.4rem); line-height: 1.05; margin: 0 0 1.2rem; }
  .hero h1 i { color: var(--sand); font-style: italic; }
  .hero p.lead { font-family: var(--f-utility); color: var(--bone-dim); font-size: 1.02rem; line-height: 1.6; max-width: 640px; margin: 0 auto; }

  .stat-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 1px; background: var(--hairline-strong); border: 1px solid var(--hairline-strong); margin: 2.6rem 0; }
  @media (min-width: 720px) { .stat-grid { grid-template-columns: repeat(3, 1fr); } }
  .stat { background: var(--obsidian-2); padding: 1.6rem 1.2rem; text-align: center; }
  .stat .l { font-family: var(--f-utility); text-transform: uppercase; letter-spacing: 0.3em; font-size: 0.6rem; color: var(--stone); margin-bottom: 0.55rem; }
  .stat .v { font-family: var(--f-display); font-weight: 300; font-size: 2.1rem; color: var(--bone); line-height: 1; }
  .stat .v i { color: var(--sand); font-style: italic; font-size: 0.55em; }

  .note { font-family: var(--f-utility); color: var(--stone); font-size: 0.78rem; text-align: center; margin: -1.2rem 0 2rem; letter-spacing: 0.04em; }
  h2.section { font-family: var(--f-display); font-weight: 300; font-size: clamp(1.6rem, 3.5vw, 2.2rem); margin: 3.4rem 0 0.6rem; text-align: center; }
  h2.section i { color: var(--sand); font-style: italic; }
  .section-lead { font-family: var(--f-utility); color: var(--bone-dim); text-align: center; max-width: 620px; margin: 0 auto 2rem; line-height: 1.6; font-size: 0.92rem; }

  table.rows { width: 100%; border-collapse: collapse; font-family: var(--f-utility); font-size: 0.9rem; }
  table.rows td { padding: 0.75rem 0.6rem; border-bottom: 1px solid var(--hairline); vertical-align: middle; }
  td.c-meta { color: var(--stone); font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.14em; white-space: nowrap; text-align: right; }
  td.c-track b { color: var(--bone); font-weight: 600; }
  td.c-track .t { font-style: italic; color: var(--bone-dim); }
  td.c-track a { color: var(--sand); text-decoration: none; font-size: 0.78rem; margin-left: 0.6rem; }
  .empty { font-family: var(--f-utility); color: var(--stone); text-align: center; padding: 2rem 0; }

  .cta-band { margin: 4rem 0; text-align: center; padding: 3rem 1rem; border: 1px solid var(--sand-dim); background: rgba(201, 185, 154, 0.04); }
  .cta-band h2 { font-family: var(--f-display); font-weight: 300; font-size: clamp(1.6rem, 3.5vw, 2.2rem); margin: 0 0 0.7rem; }
  .cta-band h2 i { color: var(--sand); font-style: italic; }
  .cta-band p { font-family: var(--f-utility); color: var(--bone-dim); margin: 0 auto 1.6rem; max-width: 540px; line-height: 1.6; }
</style>
<style>/* Sit page content above the ambient smoke layer */
body > *:not(.smoke-layer):not(nav.topnav):not(.nav-drawer):not(.nav-drawer-backdrop) { position: relative; z-index: 1; }</style>
<script src="smoke.js" defer></script>
<script src="shared/consent.js" defer></script>
</head>
<body>

<nav class="topnav" aria-label="Primary">
  <div class="topnav-inner">
    <a href="/" class="wordmark"><span class="diamond" aria-hidden="true"></span> RAPTURE</a>
    <div class="nav-links">
      <a href="playlists" class="nav-link">Playlists</a>
      <a href="for-labels" class="nav-link">For Labels</a>
      <a href="/#pitch" class="nav-cta">Pitch a track</a>
    </div>
    <button class="nav-burger" id="navBurger" aria-label="Open menu" aria-expanded="false"><span></span><span></span><span></span></button>
  </div>
</nav>
<div class="nav-drawer-backdrop" id="navDrawerBackdrop"></div>
<aside class="nav-drawer" id="navDrawer" aria-hidden="true">
  <a href="playlists"><span class="diamond"></span> Playlists</a>
  <a href="for-labels"><span class="diamond"></span> For Labels</a>
  <a href="ai-mix-analyzer"><span class="diamond"></span> AI Mix Analyzer</a>
  <a href="is-this-song-ai"><span class="diamond"></span> Is This Song AI?</a>
  <a href="remove-vocals"><span class="diamond"></span> AI Vocal Remover</a>
  <a href="spotify-pitch-generator"><span class="diamond"></span> Spotify Pitch Generator</a>
  <a href="spotify-royalty-calculator"><span class="diamond"></span> Royalty Calculator</a>
  <a href="playlist-ready-quiz"><span class="diamond"></span> Track Quiz</a>
  <a href="smart-link"><span class="diamond"></span> Smart Link</a>
  <a href="spotify-algorithm-guide"><span class="diamond"></span> Algorithm Guide</a>
  <a href="vs-submithub"><span class="diamond"></span> vs SubmitHub</a>
  <a href="faq"><span class="diamond"></span> FAQ</a>
  <a href="transparency"><span class="diamond"></span> Transparency</a>
  <a href="press"><span class="diamond"></span> Press &amp; Brand</a>
  <a href="/#pitch"><span class="diamond"></span> Pitch a track</a>
  <div class="drawer-foot">© 2026 Rapture · Kranjska Gora</div>
</aside>
<script>
(function(){
  const burger = document.getElementById('navBurger');
  const drawer = document.getElementById('navDrawer');
  const backdrop = document.getElementById('navDrawerBackdrop');
  if (!burger || !drawer || !backdrop) return;
  function close(){ burger.classList.remove('open'); drawer.classList.remove('open'); backdrop.classList.remove('open'); burger.setAttribute('aria-expanded','false'); drawer.setAttribute('aria-hidden','true'); }
  function open(){ burger.classList.add('open'); drawer.classList.add('open'); backdrop.classList.add('open'); burger.setAttribute('aria-expanded','true'); drawer.setAttribute('aria-hidden','false'); }
  burger.addEventListener('click', () => burger.classList.contains('open') ? close() : open());
  backdrop.addEventListener('click', close);
  drawer.querySelectorAll('a').forEach(a => a.addEventListener('click', close));
  document.addEventListener('keydown', e => { if (e.key === 'Escape' && drawer.classList.contains('open')) close(); });
})();
</script>

<div class="wrap">
  <section class="hero">
    <div class="eyebrow"><span class="diamond diamond-sm"></span> Nothing to hide <span class="diamond diamond-sm"></span></div>
    <h1>The numbers, <i>live.</i></h1>
    <p class="lead">Every figure on this page is computed from the actual submission log the moment you load it — not marketing copy. Declined artists are never named; accepted tracks are public on the playlists anyway.</p>
  </section>

  <div class="stat-grid">
    <div class="stat"><div class="l">Submissions reviewed</div><div class="v"><?= $fmt($total) ?></div></div>
    <div class="stat"><div class="l">Real accept rate</div><div class="v"><?= $acceptRate === null ? '—' : $acceptRate . '<i>%</i>' ?></div></div>
    <div class="stat"><div class="l">Avg decision time</div><div class="v"><?= $avgHours === null ? '—' : $avgHours . '<i>hrs</i>' ?></div></div>
    <div class="stat"><div class="l">Avg feedback length</div><div class="v"><?= $avgFeedback === null ? '—' : $fmt($avgFeedback) . '<i>chars</i>' ?></div></div>
    <div class="stat"><div class="l">Afro room saves</div><div class="v"><?= $fmt($afroSaves) ?></div></div>
    <div class="stat"><div class="l">Melodic room saves</div><div class="v"><?= $fmt($melSaves) ?></div></div>
  </div>
  <p class="note">Computed live from the submission log · <?= $e($today) ?> · saves refresh from Spotify every 6 hours</p>

  <h2 class="section">Current &amp; recent <i>placements</i></h2>
  <p class="section-lead">Every track accepted through the submission process. Each one was heard in full and chosen by one human ear.</p>
  <?php if ($placements): ?>
  <table class="rows">
    <?php foreach (array_slice($placements, 0, 40) as $p): ?>
    <tr>
      <td class="c-track"><b><?= $e($p['artist']) ?></b> — <span class="t"><?= $e($p['title']) ?></span>
        <?php if ($p['track_id']): ?><a href="https://open.spotify.com/track/<?= $e($p['track_id']) ?>" target="_blank" rel="noopener">Listen ↗</a><?php endif; ?>
      </td>
      <td class="c-meta"><?= $e($plName($p['playlist'])) ?><?= $p['weeks'] ? ' · ' . (int)$p['weeks'] . 'wk' : '' ?> · <?= $e(date('M Y', strtotime($p['at']))) ?></td>
    </tr>
    <?php endforeach; ?>
  </table>
  <?php else: ?>
  <p class="empty">Placements appear here automatically the moment they're accepted.</p>
  <?php endif; ?>

  <?php if ($legacy): ?>
  <h2 class="section">Earlier <i>promotions</i></h2>
  <p class="section-lead">Tracks promoted before Rapture Records existed in its current form — the groundwork this service grew out of.</p>
  <table class="rows">
    <?php foreach ($legacy as $p): ?>
    <tr>
      <td class="c-track"><b><?= $e($p['artist'] ?? '') ?></b> — <span class="t"><?= $e($p['title'] ?? '') ?></span>
        <?php if (!empty($p['url'])): ?><a href="<?= $e($p['url']) ?>" target="_blank" rel="noopener">Listen ↗</a><?php endif; ?>
      </td>
      <td class="c-meta"><?= $e($p['note'] ?? '') ?></td>
    </tr>
    <?php endforeach; ?>
  </table>
  <?php endif; ?>

  <section class="cta-band">
    <h2>Like what the numbers <i>say?</i></h2>
    <p>€4 for the flagship afro room, free for the new melodic room during launch. A real person listens in full and writes back within 72 hours — that's what the log above measures.</p>
    <a href="/#pitch" class="btn">Pitch a track <span class="arrow">→</span></a>
  </section>
</div>

<footer class="site-footer">
  <div class="footer-inner">
    <div>© 2026 Rapture · Kranjska Gora, Slovenia</div>
    <div>
      <a href="https://open.spotify.com/user/fbnw96f8frhqrp7707ulbz52e" target="_blank" rel="noopener">Spotify</a>
      <a href="https://www.instagram.com/rapture.ofc/" target="_blank" rel="noopener">Instagram</a>
      <a href="for-labels">For Labels</a>
      <a href="press">Press</a>
      <a href="is-this-song-ai">AI Detector</a>
      <a href="ai-mix-analyzer">Mix Analyzer</a>
      <a href="remove-vocals">Vocal Remover</a>
      <a href="spotify-pitch-generator">Pitch Generator</a>
      <a href="spotify-royalty-calculator">Calculator</a>
      <a href="playlist-ready-quiz">Quiz</a>
      <a href="smart-link">Smart Link</a>
      <a href="spotify-algorithm-guide">Algorithm Guide</a>
      <a href="best-afro-house-playlists-2026">Best Playlists</a>
      <a href="vs-submithub">Compare</a>
      <a href="faq">FAQ</a>
      <a href="transparency">Transparency</a>
      <a href="privacy">Privacy</a>
      <a href="terms">Terms</a>
    </div>
  </div>
</footer>

</body>
</html>
