This commit is contained in:
Ty Clifford
2026-05-20 21:08:23 -04:00
commit 6ab3058a92
187 changed files with 18355 additions and 0 deletions
+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;
}
+117
View File
@@ -0,0 +1,117 @@
<?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://blog.tyclifford.com', '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([
['GitHub', 'https://github.com/snick512', 'github'],
['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/');
+10
View File
@@ -0,0 +1,10 @@
[
{
"id": "001",
"text": "Deployed a new homepage design 🙈",
"date": "2025-05-13T20:00:00Z",
"tags": ["site", "design"],
"link": "https://tyclifford.com/",
"link_label": "Homepage"
}
]
+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);