This commit is contained in:
Ty Clifford
2026-06-24 09:19:08 -04:00
commit 5f7c62f8a7
151 changed files with 18705 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
<FilesMatch "^(file\.db|updates\.json|api_keys\.txt)$">
Require all denied
</FilesMatch>
+84
View File
@@ -0,0 +1,84 @@
<?php
/**
* index_config/bludit.php
* ─────────────────────────────────────────────────────────────────────────────
* Fetches the latest posts from a Bludit blog via its REST API.
* Returns an array of post arrays, or an empty array on failure.
*
* Requires BLUDIT_URL, BLUDIT_API_TOKEN, BLUDIT_POST_COUNT,
* BLUDIT_SNIPPET_LEN, BLUDIT_CACHE_TTL (all set in config.php).
*
* Usage (from index.php):
* $blog_posts = bludit_get_posts();
*/
function bludit_get_posts(): array {
// Bail early if not configured
if (!defined('BLUDIT_API_TOKEN') || BLUDIT_API_TOKEN === '') {
return [];
}
$cache_file = sys_get_temp_dir() . '/tc_bludit_cache_' . md5(BLUDIT_URL) . '.json';
$ttl = (int)(BLUDIT_CACHE_TTL ?? 300);
// Serve from cache if fresh
if ($ttl > 0 && file_exists($cache_file) && (time() - filemtime($cache_file)) < $ttl) {
$cached = json_decode(file_get_contents($cache_file), true);
if (is_array($cached)) return $cached;
}
// Build API request
$api_url = rtrim(BLUDIT_URL, '/') . '/api/pages';
$count = max(1, min(20, (int)(BLUDIT_POST_COUNT ?? 5)));
$ctx = stream_context_create([
'http' => [
'method' => 'GET',
'header' => implode("\r\n", [
'X-Bludit-API-Token: ' . BLUDIT_API_TOKEN,
'Accept: application/json',
]),
'timeout' => 5,
'ignore_errors' => true,
],
'ssl' => ['verify_peer' => true],
]);
$raw = @file_get_contents($api_url, false, $ctx);
if ($raw === false) return [];
$data = json_decode($raw, true);
if (!isset($data['data']) || !is_array($data['data'])) return [];
$snippet_len = (int)(BLUDIT_SNIPPET_LEN ?? 110);
$posts = [];
foreach (array_slice($data['data'], 0, $count) as $page) {
// Skip drafts / non-published
if (($page['type'] ?? '') !== 'published') continue;
// Build snippet from content (strip HTML, collapse whitespace)
$raw_body = $page['content'] ?? $page['contentRaw'] ?? '';
$plain = preg_replace('/\s+/', ' ', strip_tags(html_entity_decode($raw_body)));
$snippet = mb_strlen($plain) > $snippet_len
? mb_substr($plain, 0, $snippet_len) . '…'
: $plain;
$posts[] = [
'title' => html_entity_decode($page['title'] ?? 'Untitled'),
'snippet' => $snippet,
'url' => $page['permalink'] ?? (rtrim(BLUDIT_URL, '/') . '/' . ($page['key'] ?? '')),
'date' => $page['date'] ?? '',
'tags' => array_values((array)($page['tags'] ?? [])),
];
if (count($posts) >= $count) break;
}
// Cache result
if ($ttl > 0 && !empty($posts)) {
file_put_contents($cache_file, json_encode($posts), LOCK_EX);
}
return $posts;
}
+118
View File
@@ -0,0 +1,118 @@
<?php
/**
* index_config/config.php
* ─────────────────────────────────────────────────────────────────────────────
* All site-wide configuration lives here.
* Edit this file only — nothing else needs touching for routine updates.
*/
// ── Site identity ─────────────────────────────────────────────────────────────
define('SITE_NAME', 'Ty Clifford');
define('SITE_TAGLINE', 'Hobbies are my hobby.');
define('SITE_URL', 'https://tyclifford.com');
define('SITE_DESCRIPTION', 'The web space of Ty Clifford');
define('SITE_AUTHOR', 'Ty Clifford');
define('SITE_KEYWORDS', 'Ty Clifford, Keyser, West Virginia, United States, Nerd, TikTok, Freedom of Expression');
define('SITE_OG_IMAGE', SITE_URL . '/images/fa2.jpg');
define('SITE_OG_ALT', 'Profile image of Ty Clifford. Making a funny face squinting.');
define('SITE_TWITTER', '@_tyclifford');
// ── Profile ───────────────────────────────────────────────────────────────────
define('PROFILE_IMG', SITE_URL . '/images/fa2.jpg');
define('PROFILE_IMG_ALT', 'Ty Clifford — making a funny face, squinting. Eye everything.');
// ── Background video ──────────────────────────────────────────────────────────
// Set BG_VIDEO_MP4 to '' to disable the video (falls back to static gradient).
define('BG_VIDEO_MP4', SITE_URL . '/bg/BGV2.mp4');
define('BG_VIDEO_POSTER', SITE_URL . '/images/fa2.jpg');
// Overlay opacity over the video: 0 = none 1 = fully black
define('BG_OVERLAY', '0.78');
// ── Typed cycling strings (after "living in") ─────────────────────────────────
// Format: ['text', 'optional_css_class']
// CSS classes available: '' | 'grad-green' | 'grad-purple'
define('TYPED_STRINGS', serialize([
['West Virginia', ''],
['Keyser.', ''],
['curiosity.', ''],
['wonder.', 'grad-green'],
['the multiverse.', ''],
['your mind now.', 'grad-purple'],
]));
// ── Horizontal nav pills ──────────────────────────────────────────────────────
// Format: ['Label', 'url', 'color_class', 'emoji or ""']
// Color classes: col-blue | col-green | col-orange | col-pink | col-peach
// col-red | col-hotpink | col-sky
define('NAV_LINKS', serialize([
['Live', 'https://tyclifford.com/live/', 'col-blue', ''],
['Shop', 'https://shop.tyclifford.com/', 'col-green', ''],
['Shares', 'https://tyclifford.com/trade/', 'col-red', ''],
['World', 'https://tyclifford.com/world/', 'col-orange', ''],
['Patreon', 'https://go.tyclifford.com/patreon', 'col-pink', ''],
['Blog', 'https://tyclifford.com/blog', 'col-peach', ''],
['YouTube', 'https://go.tyclifford.com/youtube', 'col-red', ''],
['TikTok', 'https://go.tyclifford.com/tiktok', 'col-hotpink', ''],
['Bluesky', 'https://go.tyclifford.com/bluesky', 'col-sky', ''],
// ['Tip Jar', '/tip/', 'col-green', ''],
]));
// ── Bludit blog integration ───────────────────────────────────────────────────
// Base URL of your Bludit install (no trailing slash).
define('BLUDIT_URL', 'https://blog.tyclifford.com');
// Bludit API token — find it in Admin Settings API.
// Set to '' to disable the blog widget entirely.
define('BLUDIT_API_TOKEN', '');
// Number of posts to display (max 5 recommended).
define('BLUDIT_POST_COUNT', 5);
// Snippet length in characters (post body is stripped of HTML).
define('BLUDIT_SNIPPET_LEN', 110);
// Cache TTL in seconds for the API response (0 = no cache).
define('BLUDIT_CACHE_TTL', 300);
// ── Updates feed ─────────────────────────────────────────────────────────────
// Path to the JSON file that drives the "Updates" widget (absolute path).
define('UPDATES_JSON', __DIR__ . '/updates.json');
// ── Updates auto-refresh ──────────────────────────────────────────────────────
// How often (in seconds) the browser polls for new updates.
// Minimum enforced by JS: 5 seconds. Recommended: 30120.
define('UPDATES_REFRESH_INTERVAL', 30);
// URL to an audio file played when new updates are detected.
// Supports any browser-compatible format: .mp3, .ogg, .wav
// Set to '' to disable the sound notification entirely.
define('UPDATES_REFRESH_SOUND', SITE_URL . '');
// How many updates to display per page in the widget.
// Recommended: 1015. Visitors can page through older posts.
define('UPDATES_PER_PAGE', 10);
// URL to the lightweight JSON feed endpoint used by the auto-refresher.
// Default: the updates_feed.php file inside index_config/ (no need to change).
define('UPDATES_FEED_URL', '/index_config/updates_feed.php');
// ── Donation buttons ─────────────────────────────────────────────────────────
// Format: ['Label', 'url', 'style_key']
// style_key: 'patreon' | 'liberapay' | 'paypal' | 'generic'
define('DONATE_LINKS', serialize([
['Become a Patron', 'https://go.tyclifford.com/patreon', 'patreon'],
// ['Donate via Liberapay', 'https://go.tyclifford.com/liberapay', 'liberapay'],
['Tip via PayPal', 'https://www.paypal.com/donate/?hosted_button_id=ZDJ9TXPPMNZ6U', 'paypal'],
]));
// ── Social / contact links ────────────────────────────────────────────────────
// Format: ['Label', 'url', 'icon_key']
// icon_key: 'github' | 'email' | 'patreon' | 'bluesky' | 'tiktok' | 'youtube' | 'web'
define('SOCIAL_LINKS', serialize([
['Git', 'https://git.tyclifford.com/snick/comv3', 'gitea'],
['Bluesky', 'https://go.tyclifford.com/bluesky', 'bluesky'],
['TikTok', 'https://go.tyclifford.com/tiktok', 'tiktok'],
['YouTube', 'https://tyclifford.com/yt', 'youtube'],
['ty@tyclifford.com', 'mailto:ty@tyclifford.com', 'email'],
]));
// ── CTA buttons ───────────────────────────────────────────────────────────────
// Set to '' to hide the respective button.
define('CALENDLY_URL', 'https://calendly.com/tyclifford/list');
define('SCHEDULE_URL', '/schedule/');
+118
View File
@@ -0,0 +1,118 @@
<?php
/**
* index_config/config.php
* ─────────────────────────────────────────────────────────────────────────────
* All site-wide configuration lives here.
* Edit this file only — nothing else needs touching for routine updates.
*/
// ── Site identity ─────────────────────────────────────────────────────────────
define('SITE_NAME', 'Ty Clifford');
define('SITE_TAGLINE', 'Hobbies are my hobby.');
define('SITE_URL', 'https://tyclifford.com');
define('SITE_DESCRIPTION', 'The web space of Ty Clifford');
define('SITE_AUTHOR', 'Ty Clifford');
define('SITE_KEYWORDS', 'Ty Clifford, Keyser, West Virginia, United States, Nerd, TikTok, Freedom of Expression');
define('SITE_OG_IMAGE', SITE_URL . '/images/fa2.jpg');
define('SITE_OG_ALT', 'Profile image of Ty Clifford. Making a funny face squinting.');
define('SITE_TWITTER', '@_tyclifford');
// ── Profile ───────────────────────────────────────────────────────────────────
define('PROFILE_IMG', SITE_URL . '/images/fa2.jpg');
define('PROFILE_IMG_ALT', 'Ty Clifford — making a funny face, squinting. Eye everything.');
// ── Background video ──────────────────────────────────────────────────────────
// Set BG_VIDEO_MP4 to '' to disable the video (falls back to static gradient).
define('BG_VIDEO_MP4', SITE_URL . '/bg/BGV2.mp4');
define('BG_VIDEO_POSTER', SITE_URL . '/images/fa2.jpg');
// Overlay opacity over the video: 0 = none 1 = fully black
define('BG_OVERLAY', '0.78');
// ── Typed cycling strings (after "living in") ─────────────────────────────────
// Format: ['text', 'optional_css_class']
// CSS classes available: '' | 'grad-green' | 'grad-purple'
define('TYPED_STRINGS', serialize([
['West Virginia', ''],
['Keyser.', ''],
['curiosity.', ''],
['wonder.', 'grad-green'],
['the multiverse.', ''],
['your mind now.', 'grad-purple'],
]));
// ── Horizontal nav pills ──────────────────────────────────────────────────────
// Format: ['Label', 'url', 'color_class', 'emoji or ""']
// Color classes: col-blue | col-green | col-orange | col-pink | col-peach
// col-red | col-hotpink | col-sky
define('NAV_LINKS', serialize([
['Live', 'https://tyclifford.com/live/', 'col-blue', ''],
//['Shares', 'https://tyclifford.com/trade/', 'col-green', ''],
['
//['World', 'https://tyclifford.com/world/', 'col-orange', ''],
['Patreon', 'https://go.tyclifford.com/patreon', 'col-pink', ''],
['Blog', 'https://tyclifford.com/blog', 'col-peach', ''],
['YouTube', 'https://go.tyclifford.com/youtube', 'col-red', ''],
['TikTok', 'https://go.tyclifford.com/tiktok', 'col-hotpink', ''],
['Bluesky', 'https://go.tyclifford.com/bluesky', 'col-sky', ''],
// ['Tip Jar', '/tip/', 'col-green', ''],
]));
// ── Bludit blog integration ───────────────────────────────────────────────────
// Base URL of your Bludit install (no trailing slash).
define('BLUDIT_URL', 'https://blog.tyclifford.com');
// Bludit API token — find it in Admin Settings API.
// Set to '' to disable the blog widget entirely.
define('BLUDIT_API_TOKEN', '');
// Number of posts to display (max 5 recommended).
define('BLUDIT_POST_COUNT', 5);
// Snippet length in characters (post body is stripped of HTML).
define('BLUDIT_SNIPPET_LEN', 110);
// Cache TTL in seconds for the API response (0 = no cache).
define('BLUDIT_CACHE_TTL', 300);
// ── Updates feed ─────────────────────────────────────────────────────────────
// Path to the JSON file that drives the "Updates" widget (absolute path).
define('UPDATES_JSON', __DIR__ . '/updates.json');
// ── Updates auto-refresh ──────────────────────────────────────────────────────
// How often (in seconds) the browser polls for new updates.
// Minimum enforced by JS: 5 seconds. Recommended: 30120.
define('UPDATES_REFRESH_INTERVAL', 30);
// URL to an audio file played when new updates are detected.
// Supports any browser-compatible format: .mp3, .ogg, .wav
// Set to '' to disable the sound notification entirely.
define('UPDATES_REFRESH_SOUND', SITE_URL . '');
// How many updates to display per page in the widget.
// Recommended: 1015. Visitors can page through older posts.
define('UPDATES_PER_PAGE', 10);
// URL to the lightweight JSON feed endpoint used by the auto-refresher.
// Default: the updates_feed.php file inside index_config/ (no need to change).
define('UPDATES_FEED_URL', '/index_config/updates_feed.php');
// ── Donation buttons ─────────────────────────────────────────────────────────
// Format: ['Label', 'url', 'style_key']
// style_key: 'patreon' | 'liberapay' | 'paypal' | 'generic'
define('DONATE_LINKS', serialize([
['Become a Patron', 'https://go.tyclifford.com/patreon', 'patreon'],
// ['Donate via Liberapay', 'https://go.tyclifford.com/liberapay', 'liberapay'],
['Tip via PayPal', 'https://www.paypal.com/donate/?hosted_button_id=ZDJ9TXPPMNZ6U', 'paypal'],
]));
// ── Social / contact links ────────────────────────────────────────────────────
// Format: ['Label', 'url', 'icon_key']
// icon_key: 'github' | 'email' | 'patreon' | 'bluesky' | 'tiktok' | 'youtube' | 'web'
define('SOCIAL_LINKS', serialize([
['Git', 'https://git.tyclifford.com/snick/comv3', 'gitea'],
['Bluesky', 'https://go.tyclifford.com/bluesky', 'bluesky'],
['TikTok', 'https://go.tyclifford.com/tiktok', 'tiktok'],
['YouTube', 'https://tyclifford.com/yt', 'youtube'],
['ty@tyclifford.com', 'mailto:ty@tyclifford.com', 'email'],
]));
// ── CTA buttons ───────────────────────────────────────────────────────────────
// Set to '' to hide the respective button.
define('CALENDLY_URL', 'https://calendly.com/tyclifford/list');
define('SCHEDULE_URL', '/schedule/');
+421
View File
@@ -0,0 +1,421 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=0">
<meta name="theme-color" content="#000000">
<title>Freesound - Login</title>
<meta name="description" content="Freesound: collaborative database of creative-commons licensed sound for musicians and sound lovers. Have you freed your sound today?">
<meta name="keywords" content="free, sound">
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
<link rel="manifest" href="/site.webmanifest">
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#fd5b65">
<meta name="theme-color" content="#fd5b65">
<meta name="msapplication-config" content="none" />
<link id="style" rel="stylesheet" href="/static/bw-frontend/dist/index.e9e6280b633e.css">
</head>
<body>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<div class="modal " id="loginModal" tabindex="-1" role="dialog" aria-label="Login modal" aria-hidden="true">
<div class="modal-wrapper">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<span class="close" data-dismiss="modal" aria-label="Close">
<span class="bw-icon-close ">
</span>
</span>
</div>
<div class="modal-body">
<div class="col-12">
<div class="text-center">
<h4 class="v-spacing-5">Log in to Freesound</h4>
<form id="loginForm" class="bw-form bw-form-less-spacing" method="post" action="/home/login/"><input type="hidden" name="csrfmiddlewaretoken" value="OENJEDIyCmcoXEKFCbltnYDFbsT4UjkkYSX1vJTyncQAXWDqAWh9cl1OkavuTvGp">
<p>
<label for="id_username">Username:</label>
<input type="text" name="username" autofocus autocapitalize="none" autocomplete="username" maxlength="150" placeholder="Enter your email or username" required id="id_username">
</p>
<p>
<label for="id_password">Password:</label>
<input type="password" name="password" autocomplete="current-password" placeholder="Enter your password" required id="id_password">
</p>
<input type="hidden" name="next" value="/people/InfiniteLifespan/sounds/266455/" />
<button type="submit" class="btn-primary v-spacing-top-2">Log in</button>
</form>
</div>
</div>
</div>
<div class="modal-footer">
<a class="v-spacing-top-3" data-toggle="problems-logging-in-modal">Problems logging in?</a>
</div>
<div class="modal-extra-info">
<a data-toggle="registration-modal">Don't have an account? Join now</a>
</div>
</div>
</div>
</div>
</div>
<div class="modal " id="problemsLoggingInModal" tabindex="-1" role="dialog" aria-label="Problems logging in modal" aria-hidden="true">
<div class="modal-wrapper">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<span class="close" data-dismiss="modal" aria-label="Close">
<span class="bw-icon-close ">
</span>
</span>
</div>
<div class="modal-body">
<div class="col-12">
<div class="text-center">
<h4 class="v-spacing-5">Problems logging in?</h4>
<div class="text-grey v-spacing-top-negative-1 v-spacing-4">
Enter your email or username below and we'll send you a link to help you login into your account.
</div>
<form id="problemsLoggingInModalForm" class="bw-form bw-form-less-spacing" method="post" action="/home/problems/"><input type="hidden" name="csrfmiddlewaretoken" value="OENJEDIyCmcoXEKFCbltnYDFbsT4UjkkYSX1vJTyncQAXWDqAWh9cl1OkavuTvGp">
<p>
<input type="text" name="username_or_email" maxlength="254" placeholder="Your email or username" required id="id_username_or_email">
</p>
<button id="recovery-account" type="submit" class="btn-primary v-spacing-top-2">Send me a link</button>
</form>
</div>
</div>
</div>
<div class="modal-footer">
<a class="v-spacing-top-3" data-link="loginModal"><span class="bw-icon-chevron bw-icon__chevron_left">
</span> Back to log in</a>
</div>
<div class="modal-extra-info">
</div>
</div>
</div>
</div>
</div>
<div class="modal " id="feedbackRegistration" tabindex="-1" role="dialog" aria-label="Registration complete modal" aria-hidden="true">
<div class="modal-wrapper">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<span class="close" data-dismiss="modal" aria-label="Close">
<span class="bw-icon-close ">
</span>
</span>
</div>
<div class="modal-body">
<div class="col-12">
<div class="text-center">
<h4 class="v-spacing-5">Almost there!</h4>
<p class="main-text">We've sent a verification link by email</p>
<p class="secondary-text">Didn't receive the email? Check your Spam folder, it may have been caught by a filter. If you still don't see it, you can <a href="/home/reactivate/">resend the verification email</a>.</p>
</div>
</div>
</div>
<div class="modal-footer">
</div>
<div class="modal-extra-info">
</div>
</div>
</div>
</div>
</div>
<div class="modal zindex2000" id="confirmationModal" tabindex="-1" role="dialog" aria-label="Confirmation modal" aria-hidden="true">
<div class="modal-wrapper">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<span class="close" data-dismiss="modal" aria-label="Close">
<span class="bw-icon-close ">
</span>
</span>
</div>
<div class="modal-body">
<div class="text-center">
<h4 id="confirmationModalTitle">Default title</h4>
<div id="confirmationModalHelpText" class="v-spacing-top-2"></div>
<div class="row v-spacing-top-4 v-spacing-3">
<div class="col-4 offset-2">
<form id="confirmationModalAcceptSubmitForm" method="post" action=""><input type="hidden" name="csrfmiddlewaretoken" value="OENJEDIyCmcoXEKFCbltnYDFbsT4UjkkYSX1vJTyncQAXWDqAWh9cl1OkavuTvGp">
<button type="submit" class="btn-primary">Yes</button>
</form>
</div>
<div class="col-4">
<button class="btn-secondary" data-dismiss="modal">Cancel</button>
</div>
</div>
</div>
</div>
<div class="modal-footer">
</div>
<div class="modal-extra-info">
</div>
</div>
</div>
</div>
</div>
<div id="genericModalWrapper"></div>
<div class="toast" role="alert" aria-live="assertive" aria-atomic="true">
<div class="toast-body"></div>
</div>
<div class="notifications-wrapper"></div>
<div class="bw-page" aria-hidden="false">
<nav class="bw-nav">
<div class="container bw-nav__container">
<form method="get" action="/search/">
<div class="bw-nav__logo-search-container">
<div class="bw-nav__logo">
<a href="/" class="no-hover"></a>
</div>
<div class="input-wrapper">
<input name="q" type="search" placeholder="Search sounds..." autocomplete="off" />
</div>
</div>
</form>
<ul class="bw-nav__actions" role="menu">
<li class="bw-nav__action d-none d-lg-flex">
<a class="bw-link--grey font-weight-bold" href="/browse/" role="menuitem">Sounds</a>
</li>
<li class="bw-nav__action d-none d-lg-flex">
<a class="bw-link--grey font-weight-bold" href="/browse/tags/" role="menuitem">Tags</a>
</li>
<li class="bw-nav__action d-none d-lg-flex">
<a class="bw-link--grey font-weight-bold" href="/forum/" role="menuitem">Forum</a>
</li>
<li class="bw-nav__action d-none d-lg-flex">
<a class="bw-link--grey font-weight-bold" href="/browse/geotags/" role="menuitem">Map</a>
</li>
<li class="bw-nav__action dropdown">
<a class="bw-link--grey bw-nav__menu dropdown-toggle no-hover bw-icon-ellipsis" id="three-dots-menu" aria-label="Menu"
data-toggle="dropdown" tabindex="0"></a>
<ul class="dropdown-menu" aria-labelledby="three-dots-menu">
<li class="bw-nav__action dropdown-item d-lg-none">
<a class="bw-link--black" href="/browse/">Sounds</a>
</li>
<li class="bw-nav__action dropdown-item">
<a class="bw-link--black" href="/browse/packs/">Packs</a>
</li>
<li class="bw-nav__action dropdown-item d-lg-none">
<a class="bw-link--black" href="/forum/">Forum</a>
</li>
<li class="bw-nav__action dropdown-item d-lg-none">
<a class="bw-link--black" href="/browse/geotags/">Map</a>
</li>
<li class="bw-nav__action dropdown-item d-lg-none">
<a class="bw-link--black" href="/browse/tags/">Tags</a>
</li>
<li class="bw-nav__action dropdown-item">
<a class="bw-link--black" href="/browse/random/">Random sound</a>
</li>
<li class="bw-nav__action dropdown-item">
<a class="bw-link--black" href="/charts/">Charts</a>
</li>
<li class="bw-nav__action dropdown-item d-lg-none">
<a class="bw-link--black" href="/donations/donate/">Donate</a>
</li>
<li class="bw-nav__action dropdown-item">
<a class="bw-link--grey" href="/help/faq/">Help</a>
</li>
</ul>
</li>
<li class="bw-nav__action">
<button class="btn-secondary" data-toggle="login-modal" data-target="#loginModal" role="menuitem">Log in</button>
</li>
<li class="bw-nav__action d-none d-lg-flex">
<button class="btn-primary" data-toggle="registration-modal" data-target="#registerModal" role="menuitem">Join</button>
</li>
</ul>
</div>
</nav>
<div class="container">
<div class="navbar-space-filler v-spacing-7 v-spacing-top-5">
<div class="no-paddings col-12 v-spacing-3">
<h1>Log in to Freesound</h1>
</div>
<div class="col-12 no-paddings v-spacing-5">
<div class="login-page-form">
<div class="row no-gutters">
<div class="v-spacing-5 v-spacing-top-5 col-lg-4">
<form class="bw-form bw-form-less-spacing" method="post" action="."><input type="hidden" name="csrfmiddlewaretoken" value="OENJEDIyCmcoXEKFCbltnYDFbsT4UjkkYSX1vJTyncQAXWDqAWh9cl1OkavuTvGp">
<p>
<label for="id_username">Username:</label>
<input type="text" name="username" autofocus autocapitalize="none" autocomplete="username" maxlength="150" placeholder="Enter your email or username" required id="id_username">
</p>
<p>
<label for="id_password">Password:</label>
<input type="password" name="password" autocomplete="current-password" placeholder="Enter your password" required id="id_password">
</p>
<input type="hidden" name="next" value="/people/InfiniteLifespan/sounds/266455/" />
<button type="submit" class="btn-primary v-spacing-top-2">Log in</button>
</form>
</div>
</div>
<div>
<a class="bw-link--grey cursor-pointer" data-toggle="problems-logging-in-modal">Problems logging in?</a>
</div>
<div class="v-spacing-top-2">
<a class="bw-link--grey cursor-pointer" data-toggle="registration-modal">Don't have an account? Join now</a>
</div>
</div>
</div>
</div>
</div>
<footer class="bw-footer padding-7 v-spacing-top-6">
<div class="center">
<div class="footer-logo-container">
<div class="row">
<div class="col-sm-8">
<a href="https://www.upf.edu/web/mtg" class="no-hover">
<img class="w-100" src="/static/bw-frontend/public/upf_logo.06e9f326ce68.png" />
</a>
</div>
<div class="col-sm-4">
<a href="https://www.upf.edu/web/phonos/" class="no-hover">
<img class="w-100" src="/static/bw-frontend/public/phonos_logo.7c586cfd83d2.png" />
</a>
</div>
</div>
</div>
</div>
<div class="row v-spacing-top-3 center">
<a class="bw-link--grey h-spacing-1" href="/help/about/">About Freesound</a>
<a class="bw-link--grey h-spacing-1" href="/help/tos_web/">Terms of use</a>
<a class="bw-link--grey h-spacing-1" href="/help/privacy/">Privacy</a>
<a class="bw-link--grey h-spacing-1" href="/help/cookies_policy/">Cookies</a>
<a class="bw-link--grey h-spacing-1" href="/help/developers/">Developers</a>
<a class="bw-link--grey h-spacing-1" href="/help/faq/">Help</a>
<a class="bw-link--grey h-spacing-1" href="/donations/donors/">Donations</a>
<a class="bw-link--grey h-spacing-1" href="https://blog.freesound.org">Blog</a>
<a class="bw-link--grey h-spacing-1" href="https://labs.freesound.org">Freesound Labs</a>
<a class="bw-link--grey h-spacing-1" href="/help/tshirt/">Get your t-shirt!</a>
</div>
<div class="center v-spacing-top-3">
<span>© 2026 Universitat Pompeu Fabra</span>
</div>
</footer>
</div>
<script defer data-domain="freesound.org" src="https://analytics.freesound.org/js/plausible.manual.js"></script>
<script>window.plausible = window.plausible || function() { (window.plausible.q = window.plausible.q || []).push(arguments) }</script>
<script>
let redactedUrl = window.location.href.replace(/\/\d+\//g, "/_ID_/"); // Replace numberic IDs
redactedUrl = redactedUrl.replace(/people\/([^/]+)\//ig, "people/_USERNAME_/"); // Replace usernames
redactedUrl = redactedUrl.replace(/browse\/geotags\/(\w+)\//ig, "browse/geotags/_TAG_/"); // Replace tag from geotags
redactedUrl = redactedUrl.replace(/browse\/tags\/.*/ig, "browse/tags/_TAGS_/"); // Replace multiple tags from tags page
if (redactedUrl.includes("/forum/")){ // Replace forum names, be careful with some hard coded URLs
if (!(redactedUrl.includes("/moderate/") || redactedUrl.includes("/forums-search/") || redactedUrl.includes("/hot-treads/"))){
redactedUrl = redactedUrl.replace(/forum\/([^/]+)\//ig, "forum/_FORUM_NAME_/"); // Replace forum name
}
}
redactedUrl = redactedUrl.replace(/activate\/([^/]+)\/([^/]+)\//ig, "activate/_USERNAME_/_CODE_/"); // Replace activation urls
redactedUrl = redactedUrl.replace(/reset\/([^/]+)\//ig, "reset/_CODE_/"); // Replace password reset
redactedUrl = redactedUrl.replace(/tickets\/([^/]+)\//ig, "tickets/_ID_/"); // Replace moderation tickets
plausible('pageview', { u: redactedUrl });
</script>
<script src='https://api.tiles.mapbox.com/mapbox-gl-js/v2.6.1/mapbox-gl.js'></script>
<link href='https://api.tiles.mapbox.com/mapbox-gl-js/v2.6.1/mapbox-gl.css' rel='stylesheet' />
<script src='https://api.mapbox.com/mapbox-gl-js/plugins/mapbox-gl-geocoder/v4.7.2/mapbox-gl-geocoder.min.js'></script>
<link rel='stylesheet' href='https://api.mapbox.com/mapbox-gl-js/plugins/mapbox-gl-geocoder/v4.7.2/mapbox-gl-geocoder.css' type='text/css' />
<script type="text/javascript">
mapboxgl.accessToken = 'pk.eyJ1IjoiZnJlZXNvdW5kIiwiYSI6ImNrd3E0Mm9lbjBqM2Qyb2wwdmwxaWI3a3oifQ.MZkgLSByRuk_Xql67CySAg';
</script>
<script>
document.cookie = "preferSpectrogram=no;path=/";
document.cookie = "disallowSimultaneousAudioPlayback=no;path=/"
const userIsAuthenticated = false;
</script>
<script src="/static/bw-frontend/dist/index.2b66cae7a11f.js"></script>
</body>
</html>
+59
View File
@@ -0,0 +1,59 @@
<?php
/**
* index_config/updates_feed.php
* ─────────────────────────────────────────────────────────────────────────────
* Lightweight JSON endpoint consumed exclusively by the JS auto-refresh in
* index.php. Returns the full updates array (JS handles pagination client-side
* so every poll can detect new posts regardless of which page is visible).
*
* Usage: GET index_config/updates_feed.php
* GET index_config/updates_feed.php?since=<ISO-timestamp>
*
* The optional ?since= parameter lets the client ask "give me only posts newer
* than this timestamp" so it can detect new arrivals without comparing full
* arrays.
*
* Response:
* {
* "ok": true,
* "total": <int>, // total posts in the feed
* "new_since": <int>, // posts newer than ?since= (0 if not supplied)
* "items": [ … ] // full array, newest first
* }
*/
require_once __DIR__ . '/config.php';
header('Content-Type: application/json; charset=utf-8');
header('Cache-Control: no-store');
header('X-Content-Type-Options: nosniff');
// ── Load feed ─────────────────────────────────────────────────────────────────
$posts = [];
if (file_exists(UPDATES_JSON)) {
$raw = json_decode(file_get_contents(UPDATES_JSON), true);
if (is_array($raw)) $posts = $raw;
}
// ── Count posts newer than ?since= ───────────────────────────────────────────
$since = trim($_GET['since'] ?? '');
$new_count = 0;
if ($since !== '') {
try {
$since_dt = new DateTime($since, new DateTimeZone('UTC'));
foreach ($posts as $p) {
try {
$post_dt = new DateTime($p['date'] ?? '', new DateTimeZone('UTC'));
if ($post_dt > $since_dt) $new_count++;
} catch (Exception $e) { /* skip malformed dates */ }
}
} catch (Exception $e) { /* ignore bad since param */ }
}
echo json_encode([
'ok' => true,
'total' => count($posts),
'new_since' => $new_count,
'items' => $posts,
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);