this page so far
php /
May 14, 2026 1:02 PM /
41,857 bytes
Raw
<?php
declare(strict_types=1);
$storageDir = __DIR__ . DIRECTORY_SEPARATOR . '_pastes';
$maxBytes = 512 * 1024;
if (!is_dir($storageDir)) {
mkdir($storageDir, 0755, true);
}
function h(string $value): string {
return htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
function slug(): string {
return bin2hex(random_bytes(5));
}
function paste_path(string $id): string {
global $storageDir;
return $storageDir . DIRECTORY_SEPARATOR . $id . '.json';
}
function valid_id(string $id): bool {
return (bool) preg_match('/^[a-f0-9]{10}$/', $id);
}
function load_paste(string $id): ?array {
if (!valid_id($id)) {
return null;
}
$path = paste_path($id);
if (!is_file($path)) {
return null;
}
$data = json_decode((string) file_get_contents($path), true);
return is_array($data) ? $data : null;
}
function language_options(): array {
return [
'text' => 'Text',
'html' => 'HTML',
'css' => 'CSS',
'javascript' => 'JavaScript',
'php' => 'PHP',
'python' => 'Python',
'powershell' => 'PowerShell',
'markdown' => 'Markdown',
'json' => 'JSON',
'sql' => 'SQL',
];
}
function language_label(string $language): string {
$options = language_options();
return $options[$language] ?? ucfirst($language);
}
function normalize_tags(string $value): array {
$rawTags = preg_split('/\s*,\s*/', $value) ?: [];
$tags = [];
foreach ($rawTags as $tag) {
$tag = strtolower(trim($tag));
$tag = ltrim($tag, '#');
$tag = preg_replace('/[^a-z0-9_-]+/', '-', $tag) ?? '';
$tag = trim($tag, '-_');
if ($tag !== '' && !in_array($tag, $tags, true)) {
$tags[] = mb_substr($tag, 0, 40, 'UTF-8');
}
}
return array_slice($tags, 0, 20);
}
function paste_tags(array $data): array {
$rawTags = $data['tags'] ?? [];
if (is_string($rawTags)) {
return normalize_tags($rawTags);
}
if (!is_array($rawTags)) {
return [];
}
return normalize_tags(implode(',', array_map('strval', $rawTags)));
}
function all_pastes(string $pasteDir): array {
$items = [];
$files = glob($pasteDir . DIRECTORY_SEPARATOR . '*.json') ?: [];
foreach ($files as $file) {
$data = json_decode((string) file_get_contents($file), true);
if (!is_array($data) || empty($data['id']) || !valid_id((string) $data['id'])) {
continue;
}
$created = strtotime((string) ($data['created_at'] ?? '')) ?: filemtime($file);
$body = (string) ($data['body'] ?? '');
$items[] = [
'id' => (string) $data['id'],
'title' => (string) ($data['title'] ?? 'Untitled paste'),
'language' => strtolower((string) ($data['language'] ?? 'text')),
'tags' => paste_tags($data),
'body' => $body,
'created_at' => gmdate('c', $created),
'created_ts' => $created,
'bytes' => (int) ($data['bytes'] ?? strlen($body)),
];
}
return $items;
}
function clamp_choice(string $value, array $allowed, string $fallback): string {
return in_array($value, $allowed, true) ? $value : $fallback;
}
function current_url_with(array $changes): string {
$query = $_GET;
foreach ($changes as $key => $value) {
if ($value === null || $value === '') {
unset($query[$key]);
} else {
$query[$key] = (string) $value;
}
}
$qs = http_build_query($query);
return '/list' . ($qs === '' ? '' : '?' . $qs);
}
function paste_preview(string $body, int $limit = 260): string {
$text = trim(preg_replace('/\s+/', ' ', $body) ?? '');
if (mb_strlen($text, 'UTF-8') <= $limit) {
return $text;
}
return mb_substr($text, 0, $limit - 3, 'UTF-8') . '...';
}
$error = '';
$createdUrl = '';
$paste = null;
$pasteId = '';
$isList = false;
$listPastes = [];
$filteredPastes = [];
$pagePastes = [];
$typeCounts = [];
$tagCounts = [];
$selectedType = '';
$selectedTag = '';
$selectedSort = 'newest';
$selectedView = 'detailed';
$perPage = 10;
$page = 1;
$totalPages = 1;
$totalPastes = 0;
$path = parse_url((string) ($_SERVER['REQUEST_URI'] ?? '/'), PHP_URL_PATH) ?: '/';
if (preg_match('#^/raw/([a-f0-9]{10})/?$#', $path, $matches)) {
$paste = load_paste($matches[1]);
if ($paste === null) {
http_response_code(404);
header('Content-Type: text/plain; charset=utf-8');
echo 'Paste not found.';
exit;
}
header('Content-Type: text/plain; charset=utf-8');
header('X-Content-Type-Options: nosniff');
echo (string) $paste['body'];
exit;
}
if (preg_match('#^/p/([a-f0-9]{10})/?$#', $path, $matches)) {
$pasteId = $matches[1];
}
if ($path === '/list' || $path === '/list/') {
$isList = true;
}
if ($_SERVER['REQUEST_METHOD'] === 'POST' && !$isList) {
$title = trim((string) ($_POST['title'] ?? ''));
$language = trim((string) ($_POST['language'] ?? 'text'));
$tags = normalize_tags((string) ($_POST['tags'] ?? ''));
$body = (string) ($_POST['body'] ?? '');
if ($title === '') {
$error = 'Add a title first.';
} elseif ($body === '') {
$error = 'Paste something first.';
} elseif (strlen($body) > $maxBytes) {
$error = 'That paste is too large. Keep it under 512 KB for now.';
} else {
do {
$pasteId = slug();
$path = paste_path($pasteId);
} while (is_file($path));
$record = [
'id' => $pasteId,
'title' => mb_substr($title, 0, 120, 'UTF-8'),
'language' => mb_substr($language, 0, 40, 'UTF-8'),
'tags' => $tags,
'body' => $body,
'created_at' => gmdate('c'),
'bytes' => strlen($body),
];
file_put_contents(
$path,
json_encode($record, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE),
LOCK_EX
);
$scheme = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
$host = $_SERVER['HTTP_HOST'] ?? 'scriptjerk.online';
$createdUrl = $scheme . '://' . $host . '/p/' . $pasteId;
$paste = $record;
}
} elseif (!$isList) {
if ($pasteId === '') {
$pasteId = (string) ($_GET['p'] ?? '');
}
if ($pasteId !== '') {
$paste = load_paste($pasteId);
if ($paste === null) {
http_response_code(404);
$error = 'Paste not found.';
}
}
}
if ($isList) {
$listPastes = all_pastes($storageDir);
foreach ($listPastes as $item) {
$type = (string) $item['language'];
$typeCounts[$type] = ($typeCounts[$type] ?? 0) + 1;
foreach ($item['tags'] as $tag) {
$tagCounts[$tag] = ($tagCounts[$tag] ?? 0) + 1;
}
}
ksort($typeCounts);
ksort($tagCounts);
$selectedType = strtolower(trim((string) ($_GET['type'] ?? '')));
$selectedTag = strtolower(trim((string) ($_GET['tag'] ?? '')));
$selectedTag = ltrim($selectedTag, '#');
$selectedSort = clamp_choice((string) ($_GET['sort'] ?? 'newest'), ['newest', 'oldest', 'title', 'type', 'tag'], 'newest');
$selectedView = clamp_choice((string) ($_GET['view'] ?? 'detailed'), ['detailed', 'simple', 'preview'], 'detailed');
$perPage = (int) ($_GET['per_page'] ?? 10);
if (!in_array($perPage, [10, 25, 50, 100], true)) {
$perPage = 10;
}
$filteredPastes = array_values(array_filter($listPastes, static function (array $item) use ($selectedType, $selectedTag): bool {
$typeMatches = $selectedType === '' || $item['language'] === $selectedType;
$tagMatches = $selectedTag === '' || in_array($selectedTag, $item['tags'], true);
return $typeMatches && $tagMatches;
}));
usort($filteredPastes, static function (array $a, array $b) use ($selectedSort): int {
if ($selectedSort === 'oldest') {
return $a['created_ts'] <=> $b['created_ts'];
}
if ($selectedSort === 'title') {
return strcasecmp((string) $a['title'], (string) $b['title']);
}
if ($selectedSort === 'type') {
return strcasecmp((string) $a['language'], (string) $b['language'])
?: ($b['created_ts'] <=> $a['created_ts']);
}
if ($selectedSort === 'tag') {
return strcasecmp((string) ($a['tags'][0] ?? ''), (string) ($b['tags'][0] ?? ''))
?: ($b['created_ts'] <=> $a['created_ts']);
}
return $b['created_ts'] <=> $a['created_ts'];
});
$totalPastes = count($filteredPastes);
$totalPages = max(1, (int) ceil($totalPastes / $perPage));
$page = max(1, min((int) ($_GET['page'] ?? 1), $totalPages));
$offset = ($page - 1) * $perPage;
$pagePastes = array_slice($filteredPastes, $offset, $perPage);
}
$pageTitle = $isList
? 'All Pastes | ScriptJerk Online'
: ($paste ? ($paste['title'] . ' | ScriptJerk Online') : 'ScriptJerk Online | Copy Paste Pages');
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title><?= h($pageTitle) ?></title>
<meta name="description" content="ScriptJerk Online is a simple copy-paste reference tool for saving code, notes, and text as dedicated pages.">
<style>
:root {
color-scheme: dark;
--bg: #0d1013;
--ink: #f4efe7;
--muted: #aeb7ba;
--panel: #171c20;
--panel-2: #20272c;
--line: #354047;
--green: #65d68f;
--teal: #55d6cf;
--blue: #78a8ff;
--amber: #f3bf5a;
--red: #ff7a70;
--code: #0a0d0f;
--shadow: 0 24px 70px rgba(0, 0, 0, .35);
}
* {
box-sizing: border-box;
}
html {
scroll-behavior: smooth;
}
body {
margin: 0;
min-height: 100vh;
background:
radial-gradient(circle at 12% 8%, rgba(85, 214, 207, .11), transparent 31%),
radial-gradient(circle at 88% 0%, rgba(120, 168, 255, .12), transparent 28%),
linear-gradient(140deg, #0d1013, #121719 44%, #0b0e10);
color: var(--ink);
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
letter-spacing: 0;
}
a {
color: inherit;
}
.shell {
width: min(1180px, calc(100% - 32px));
margin: 0 auto;
padding: 24px 0 44px;
}
header {
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
gap: 18px;
align-items: end;
margin-bottom: 24px;
}
.brand {
display: flex;
align-items: center;
gap: 11px;
color: var(--muted);
font-weight: 800;
margin-bottom: 28px;
}
.mark {
display: grid;
place-items: center;
width: 36px;
height: 36px;
border: 1px solid rgba(101, 214, 143, .5);
border-radius: 8px;
background: linear-gradient(135deg, rgba(85, 214, 207, .28), rgba(120, 168, 255, .18));
color: var(--green);
box-shadow: 0 0 28px rgba(85, 214, 207, .12);
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
h1 {
margin: 0;
max-width: 820px;
font-size: clamp(2.15rem, 5vw, 5.1rem);
line-height: .95;
letter-spacing: 0;
}
.lede {
max-width: 760px;
margin: 18px 0 0;
color: var(--muted);
font-size: 1.05rem;
line-height: 1.7;
}
.nav {
display: flex;
gap: 10px;
flex-wrap: wrap;
justify-content: flex-end;
}
.btn,
button {
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 42px;
border: 1px solid var(--line);
border-radius: 8px;
background: var(--panel-2);
color: var(--ink);
padding: 0 14px;
font: inherit;
font-weight: 800;
text-decoration: none;
cursor: pointer;
}
.btn.primary,
button.primary {
border-color: rgba(101, 214, 143, .7);
background: linear-gradient(135deg, var(--green), var(--teal));
color: #06100d;
}
.grid {
display: grid;
grid-template-columns: minmax(0, 1fr) 360px;
gap: 18px;
align-items: start;
}
section,
aside {
border: 1px solid var(--line);
border-radius: 8px;
background: rgba(23, 28, 32, .9);
box-shadow: var(--shadow);
}
.panel {
padding: 18px;
}
label {
display: block;
margin-bottom: 8px;
color: var(--muted);
font-size: .82rem;
font-weight: 900;
text-transform: uppercase;
}
input,
textarea,
select {
width: 100%;
border: 1px solid var(--line);
border-radius: 8px;
background: #0b0f12;
color: var(--ink);
padding: 12px 13px;
font: inherit;
outline: none;
}
input:focus,
textarea:focus,
select:focus {
border-color: var(--teal);
box-shadow: 0 0 0 3px rgba(85, 214, 207, .14);
}
textarea {
min-height: 430px;
resize: vertical;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
line-height: 1.55;
tab-size: 2;
}
.row {
display: grid;
grid-template-columns: minmax(0, 1fr) 190px;
gap: 12px;
margin-bottom: 14px;
}
.actions {
display: flex;
gap: 10px;
flex-wrap: wrap;
align-items: center;
margin-top: 14px;
}
.note,
.error,
.success {
border-radius: 8px;
padding: 12px 13px;
line-height: 1.55;
}
.note {
border: 1px solid var(--line);
color: var(--muted);
background: rgba(255, 255, 255, .03);
}
.error {
border: 1px solid rgba(255, 122, 112, .55);
color: #ffd4d0;
background: rgba(255, 122, 112, .08);
margin-bottom: 14px;
}
.success {
border: 1px solid rgba(101, 214, 143, .55);
color: #d9ffe4;
background: rgba(101, 214, 143, .08);
margin-bottom: 14px;
}
.linkbox {
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
gap: 8px;
margin-top: 10px;
}
.paste-head {
display: flex;
justify-content: space-between;
gap: 14px;
align-items: start;
margin-bottom: 14px;
}
.view-actions {
display: flex;
gap: 8px;
flex-wrap: wrap;
justify-content: flex-end;
}
.paste-title {
margin: 0;
font-size: 1.3rem;
}
.meta {
color: var(--muted);
font-size: .9rem;
line-height: 1.5;
}
pre {
overflow: auto;
margin: 0;
border: 1px solid var(--line);
border-radius: 8px;
background: var(--code);
padding: 16px;
min-height: 360px;
line-height: 1.55;
white-space: pre-wrap;
word-break: break-word;
}
code {
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
font-size: .94rem;
}
.side-list {
display: grid;
gap: 12px;
margin: 0;
padding: 0;
list-style: none;
}
.side-list li {
border-top: 1px solid var(--line);
padding-top: 12px;
color: var(--muted);
line-height: 1.55;
}
.side-list li:first-child {
border-top: 0;
padding-top: 0;
}
footer {
margin-top: 24px;
color: var(--muted);
font-size: .9rem;
}
@media (max-width: 880px) {
header,
.grid,
.row {
grid-template-columns: 1fr;
}
.nav {
justify-content: flex-start;
}
.paste-head,
.view-actions {
align-items: stretch;
flex-direction: column;
}
textarea {
min-height: 320px;
}
}
</style>
<link rel="stylesheet" href="/style.css?v=20260514-tags1">
</head>
<body data-theme="dark">
<div class="shell">
<header>
<div>
<a class="brand" href="/">
<span class="mark"></></span>
<span>ScriptJerk Online</span>
</a>
<h1>Copy paste code, notes, and receipts into dedicated pages.</h1>
<p class="lede">Drop text here, create a clean reference link, and come back to it later. Simple on purpose: no account wall, no database ceremony, no dashboard maze.</p>
</div>
<nav class="nav" aria-label="Site links">
<a class="btn" href="/">Create</a>
<a class="btn" href="/list">Browse</a>
<a class="btn" href="https://scriptjerk.fun/">Fun</a>
<a class="btn" href="https://scriptjerk.org/">Brain</a>
<a class="btn" href="https://scriptjerk.store/">Store</a>
<label class="theme-picker" for="themeSelect">
<span>Theme</span>
<select id="themeSelect" aria-label="Theme">
<option value="dark">Dark</option>
<option value="middle">Inbetween</option>
<option value="light">Light</option>
<option value="contrast">High Contrast</option>
</select>
</label>
</nav>
</header>
<?php if ($error !== ''): ?>
<div class="error"><?= h($error) ?></div>
<?php endif; ?>
<?php if ($createdUrl !== ''): ?>
<div class="success">
Saved. This paste now has its own page:
<div class="linkbox">
<input id="createdUrl" value="<?= h($createdUrl) ?>" readonly>
<button type="button" onclick="copyValue('createdUrl')">Copy</button>
</div>
</div>
<?php endif; ?>
<?php if ($isList): ?>
<main class="list-layout">
<section class="panel list-panel" aria-label="Paste list">
<div class="list-head">
<div>
<h2 class="paste-title">Saved pastes</h2>
<div class="meta">
<?= number_format($totalPastes) ?> shown /
<?= number_format(count($listPastes)) ?> total
</div>
</div>
<a class="btn primary" href="/">New Paste</a>
</div>
<form class="list-controls" method="get" action="/list">
<div>
<label for="type">Type</label>
<select id="type" name="type">
<option value="">All types</option>
<?php foreach ($typeCounts as $type => $count): ?>
<option value="<?= h((string) $type) ?>" <?= $selectedType === $type ? 'selected' : '' ?>>
<?= h(language_label((string) $type)) ?> (<?= number_format((int) $count) ?>)
</option>
<?php endforeach; ?>
</select>
</div>
<div>
<label for="tag">Tag</label>
<select id="tag" name="tag">
<option value="">All tags</option>
<?php foreach ($tagCounts as $tag => $count): ?>
<option value="<?= h((string) $tag) ?>" <?= $selectedTag === $tag ? 'selected' : '' ?>>
#<?= h((string) $tag) ?> (<?= number_format((int) $count) ?>)
</option>
<?php endforeach; ?>
</select>
</div>
<div>
<label for="sort">Sort</label>
<select id="sort" name="sort">
<option value="newest" <?= $selectedSort === 'newest' ? 'selected' : '' ?>>Newest first</option>
<option value="oldest" <?= $selectedSort === 'oldest' ? 'selected' : '' ?>>Oldest first</option>
<option value="title" <?= $selectedSort === 'title' ? 'selected' : '' ?>>Title A-Z</option>
<option value="type" <?= $selectedSort === 'type' ? 'selected' : '' ?>>Type A-Z</option>
<option value="tag" <?= $selectedSort === 'tag' ? 'selected' : '' ?>>First tag A-Z</option>
</select>
</div>
<div>
<label for="view">View</label>
<select id="view" name="view">
<option value="detailed" <?= $selectedView === 'detailed' ? 'selected' : '' ?>>Detailed list</option>
<option value="simple" <?= $selectedView === 'simple' ? 'selected' : '' ?>>Simple list</option>
<option value="preview" <?= $selectedView === 'preview' ? 'selected' : '' ?>>Simple preview</option>
</select>
</div>
<div>
<label for="per_page">Per page</label>
<select id="per_page" name="per_page">
<?php foreach ([10, 25, 50, 100] as $choice): ?>
<option value="<?= $choice ?>" <?= $perPage === $choice ? 'selected' : '' ?>><?= $choice ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="control-action">
<button class="primary" type="submit">Apply</button>
</div>
</form>
<?php if ($totalPastes === 0): ?>
<div class="empty-state">
<h2 class="paste-title">No pastes found</h2>
<p class="meta">Create a paste first, or clear the current type or tag filter.</p>
</div>
<?php else: ?>
<div class="paste-list paste-list-<?= h($selectedView) ?>">
<?php foreach ($pagePastes as $item): ?>
<article class="paste-item">
<div class="paste-item-main">
<a class="paste-item-title" href="/p/<?= h((string) $item['id']) ?>">
<?= h((string) $item['title']) ?>
</a>
<?php if ($selectedView !== 'simple'): ?>
<div class="meta">
<?= h(language_label((string) $item['language'])) ?> /
<?= h(date('M j, Y g:i A', (int) $item['created_ts'])) ?> /
<?= number_format((int) $item['bytes']) ?> bytes
</div>
<?php if ($item['tags'] !== []): ?>
<div class="tag-row">
<?php foreach ($item['tags'] as $tag): ?>
<a class="tag-chip" href="<?= h(current_url_with(['tag' => $tag, 'page' => 1])) ?>">#<?= h((string) $tag) ?></a>
<?php endforeach; ?>
</div>
<?php endif; ?>
<?php endif; ?>
<?php if ($selectedView === 'preview'): ?>
<p class="paste-preview"><?= h(paste_preview((string) $item['body'])) ?></p>
<?php endif; ?>
</div>
<div class="paste-item-actions">
<span class="type-pill"><?= h(language_label((string) $item['language'])) ?></span>
<?php if ($selectedView === 'simple' && $item['tags'] !== []): ?>
<span class="type-pill">#<?= h((string) $item['tags'][0]) ?></span>
<?php endif; ?>
<a class="btn" href="/raw/<?= h((string) $item['id']) ?>">Raw</a>
</div>
</article>
<?php endforeach; ?>
</div>
<nav class="pager" aria-label="Paste pages">
<a class="btn <?= $page <= 1 ? 'disabled' : '' ?>" href="<?= h($page <= 1 ? '#' : current_url_with(['page' => $page - 1])) ?>">Previous</a>
<span class="page-status">Page <?= number_format($page) ?> of <?= number_format($totalPages) ?></span>
<a class="btn <?= $page >= $totalPages ? 'disabled' : '' ?>" href="<?= h($page >= $totalPages ? '#' : current_url_with(['page' => $page + 1])) ?>">Next</a>
</nav>
<?php endif; ?>
</section>
</main>
<?php else: ?>
<main class="grid">
<section class="panel" aria-label="<?= $paste ? 'Paste viewer' : 'Create paste' ?>">
<?php if ($paste): ?>
<div class="paste-head">
<div>
<h2 class="paste-title"><?= h((string) $paste['title']) ?></h2>
<div class="meta">
<?= h((string) ($paste['language'] ?? 'text')) ?> /
<?= h(date('M j, Y g:i A', strtotime((string) $paste['created_at']))) ?> /
<?= number_format((int) ($paste['bytes'] ?? 0)) ?> bytes
</div>
<?php $detailTags = paste_tags($paste); ?>
<?php if ($detailTags !== []): ?>
<div class="tag-row">
<?php foreach ($detailTags as $tag): ?>
<a class="tag-chip" href="/list?tag=<?= h(urlencode($tag)) ?>">#<?= h($tag) ?></a>
<?php endforeach; ?>
</div>
<?php endif; ?>
</div>
<div class="view-actions">
<label class="code-picker" for="codeThemeSelect">
<span>Code View</span>
<select id="codeThemeSelect" aria-label="Code view">
<option value="auto">Auto</option>
<option value="text">Plain Text</option>
<option value="javascript">JavaScript</option>
<option value="javascript:contrast">JavaScript High Vis</option>
<option value="python">Python</option>
<option value="python:contrast">Python High Vis</option>
<option value="html">HTML5</option>
<option value="html:contrast">HTML5 High Vis</option>
<option value="css">CSS</option>
<option value="css:contrast">CSS High Vis</option>
<option value="php">PHP</option>
<option value="php:contrast">PHP High Vis</option>
<option value="powershell">PowerShell</option>
<option value="powershell:contrast">PowerShell High Vis</option>
<option value="json">JSON</option>
<option value="json:contrast">JSON High Vis</option>
<option value="sql">SQL</option>
<option value="sql:contrast">SQL High Vis</option>
<option value="markdown">Markdown</option>
<option value="markdown:contrast">Markdown High Vis</option>
</select>
</label>
<a class="btn" href="/raw/<?= h((string) $paste['id']) ?>">Raw</a>
<button type="button" onclick="copyText('pasteBody')">Copy Text</button>
</div>
</div>
<pre id="pasteBody" data-language="<?= h((string) ($paste['language'] ?? 'text')) ?>"><code><?= h((string) $paste['body']) ?></code></pre>
<?php else: ?>
<form method="post" action="/">
<div class="row">
<div>
<label for="title">Title</label>
<input id="title" name="title" maxlength="120" required placeholder="Example: VPS deploy notes">
</div>
<div>
<label for="language">Type</label>
<select id="language" name="language">
<option value="text">Text</option>
<option value="html">HTML</option>
<option value="css">CSS</option>
<option value="javascript">JavaScript</option>
<option value="php">PHP</option>
<option value="python">Python</option>
<option value="powershell">PowerShell</option>
<option value="markdown">Markdown</option>
<option value="json">JSON</option>
<option value="sql">SQL</option>
</select>
</div>
</div>
<label for="tagInput">Tags</label>
<div class="tag-editor" id="tagEditor">
<div class="tag-stack" id="tagStack" aria-live="polite"></div>
<input id="tagInput" autocomplete="off" placeholder="Type hashtags, then comma">
</div>
<input type="hidden" id="tags" name="tags">
<p class="field-help">Use comma or comma + space to lock in tags. Example: html, landing-page, vps</p>
<label for="body">Paste</label>
<textarea id="body" name="body" required spellcheck="false" placeholder="Paste code, notes, prompts, commands, or anything you want to reference later."></textarea>
<div class="actions">
<button class="primary" type="submit">Create Page</button>
<button type="button" onclick="clearPaste()">Clear</button>
</div>
</form>
<?php endif; ?>
</section>
<aside class="panel">
<h2 class="paste-title">How this works</h2>
<ul class="side-list">
<li>Every save creates a random private-by-obscurity link like <code>/p/a1b2c3d4e5</code>.</li>
<li>Storage is file-based, so it is easy to back up or move later.</li>
<li>Pastes are escaped before display, so HTML and scripts show as text instead of running.</li>
<li>This is for useful reference material. Do not paste passwords, private keys, bank info, or anything that would hurt if shared.</li>
</ul>
</aside>
</main>
<?php endif; ?>
<footer>
<span>ScriptJerk Online is the quick-reference shelf for code, notes, snippets, and project evidence.</span>
<span class="donate-line">
Donate SOL:
<code id="solAddress">3aVj5JcfXvvWpfLCfuXrcQPaZp36r6K35HG34k3FTsZV</code>
<button class="mini-btn" type="button" onclick="copyValue('solAddress')">Copy</button>
</span>
</footer>
</div>
<script>
const themeSelect = document.getElementById('themeSelect');
const savedTheme = localStorage.getItem('scriptjerk-online-theme') || 'dark';
function setTheme(theme) {
const allowed = ['dark', 'middle', 'light', 'contrast'];
const nextTheme = allowed.includes(theme) ? theme : 'dark';
document.body.dataset.theme = nextTheme;
localStorage.setItem('scriptjerk-online-theme', nextTheme);
if (themeSelect) themeSelect.value = nextTheme;
}
setTheme(savedTheme);
if (themeSelect) {
themeSelect.addEventListener('change', (event) => {
setTheme(event.target.value);
});
}
const languageAliases = {
html5: 'html',
js: 'javascript',
ps1: 'powershell',
md: 'markdown'
};
const keywordSets = {
javascript: ['async', 'await', 'break', 'case', 'catch', 'class', 'const', 'continue', 'default', 'delete', 'do', 'else', 'export', 'extends', 'false', 'finally', 'for', 'from', 'function', 'if', 'import', 'in', 'instanceof', 'let', 'new', 'null', 'return', 'static', 'super', 'switch', 'this', 'throw', 'true', 'try', 'typeof', 'undefined', 'var', 'void', 'while', 'yield'],
php: ['array', 'as', 'break', 'case', 'catch', 'class', 'const', 'continue', 'declare', 'default', 'echo', 'else', 'elseif', 'extends', 'false', 'finally', 'for', 'foreach', 'function', 'global', 'if', 'implements', 'interface', 'namespace', 'new', 'null', 'private', 'protected', 'public', 'require', 'return', 'static', 'switch', 'throw', 'trait', 'true', 'try', 'use', 'while'],
python: ['and', 'as', 'assert', 'async', 'await', 'break', 'class', 'continue', 'def', 'elif', 'else', 'except', 'False', 'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is', 'lambda', 'None', 'nonlocal', 'not', 'or', 'pass', 'raise', 'return', 'True', 'try', 'while', 'with', 'yield'],
powershell: ['Begin', 'Break', 'Catch', 'Class', 'Continue', 'Data', 'Do', 'DynamicParam', 'Else', 'ElseIf', 'End', 'Exit', 'Filter', 'Finally', 'For', 'ForEach', 'From', 'Function', 'If', 'In', 'Param', 'Process', 'Return', 'Switch', 'Throw', 'Trap', 'Try', 'Until', 'Using', 'Var', 'While'],
css: ['align-items', 'background', 'border', 'box-shadow', 'color', 'display', 'font', 'gap', 'grid', 'height', 'justify-content', 'margin', 'max-width', 'min-height', 'padding', 'position', 'text-decoration', 'transform', 'transition', 'width'],
sql: ['ALTER', 'AND', 'AS', 'ASC', 'BETWEEN', 'BY', 'CREATE', 'DELETE', 'DESC', 'DROP', 'FROM', 'GROUP', 'HAVING', 'IN', 'INSERT', 'INTO', 'JOIN', 'LEFT', 'LIKE', 'LIMIT', 'NOT', 'NULL', 'OR', 'ORDER', 'RIGHT', 'SELECT', 'SET', 'TABLE', 'UPDATE', 'VALUES', 'WHERE'],
json: ['true', 'false', 'null']
};
function escapeHtml(value) {
return value
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
function tokenSpan(type, value) {
return `<span class="tok-${type}">${value}</span>`;
}
function highlightKeywords(html, language) {
const words = keywordSets[language] || [];
if (words.length === 0) return html;
const pattern = new RegExp(`\\b(${words.map((word) => word.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|')})\\b`, language === 'sql' ? 'g' : 'gi');
return html
.split(/(<span class="tok-[^"]+">[\s\S]*?<\/span>|<[^>]+>)/g)
.map((chunk) => chunk.startsWith('<') ? chunk : chunk.replace(pattern, (match) => tokenSpan('keyword', match)))
.join('');
}
function highlightCode(text, language) {
let html = escapeHtml(text);
const requestedLang = String(language || 'text').toLowerCase();
const lang = languageAliases[requestedLang] || requestedLang;
if (lang === 'text') return html;
if (lang === 'html') {
html = html.replace(/(<!--[\s\S]*?-->)/g, (_, value) => tokenSpan('comment', value));
html = html.replace(/(<\/?)([a-zA-Z][\w:-]*)([\s\S]*?)(\/?>)/g, (_, open, tag, attrs, close) => {
const attrHtml = attrs.replace(/([\w:-]+)(=)(".*?"|'.*?'|[^\s&]+)/g, (_m, name, eq, value) => `${tokenSpan('attr', name)}${eq}${tokenSpan('string', value)}`);
return `${tokenSpan('punct', open)}${tokenSpan('tag', tag)}${attrHtml}${tokenSpan('punct', close)}`;
});
return html;
}
if (lang === 'css') {
html = html.replace(/(\/\*[\s\S]*?\*\/)/g, (_, value) => tokenSpan('comment', value));
html = html.replace(/([.#]?[a-zA-Z][\w-]*)(\s*\{)/g, (_, selector, brace) => `${tokenSpan('selector', selector)}${brace}`);
html = html.replace(/([\w-]+)(\s*:)/g, (_, prop, colon) => `${tokenSpan('property', prop)}${colon}`);
html = html.replace(/(#(?:[0-9a-fA-F]{3}){1,2}\b|rgba?\([^)]+\)|hsla?\([^)]+\))/g, (_, value) => tokenSpan('number', value));
return highlightKeywords(html, lang);
}
if (lang === 'json') {
html = html.replace(/("[^&]*?")(\s*:)/g, (_, key, colon) => `${tokenSpan('property', key)}${colon}`);
html = html.replace(/(:\s*)("[^&]*?")/g, (_, colon, value) => `${colon}${tokenSpan('string', value)}`);
html = html.replace(/\b(-?\d+(?:\.\d+)?)\b/g, (_, value) => tokenSpan('number', value));
return highlightKeywords(html, lang);
}
html = html.replace(/(".*?"|'.*?'|`.*?`)/g, (_, value) => tokenSpan('string', value));
html = html.replace(/\b(-?\d+(?:\.\d+)?)\b/g, (_, value) => tokenSpan('number', value));
if (['javascript', 'php', 'python', 'powershell'].includes(lang)) {
html = html.replace(/(\/\/.*?$|#.*?$|\/\*[\s\S]*?\*\/)/gm, (_, value) => tokenSpan('comment', value));
html = html.replace(/(\$[a-zA-Z_]\w*)/g, (_, value) => tokenSpan('variable', value));
return highlightKeywords(html, lang);
}
if (lang === 'sql') {
html = html.replace(/(--.*?$|\/\*[\s\S]*?\*\/)/gm, (_, value) => tokenSpan('comment', value));
return highlightKeywords(html, lang);
}
if (lang === 'markdown') {
html = html.replace(/^(#{1,6}\s.*)$/gm, (_, value) => tokenSpan('keyword', value));
html = html.replace(/(\*\*[^*]+\*\*|__[^_]+__)/g, (_, value) => tokenSpan('strong', value));
html = html.replace(/(`[^`]+`)/g, (_, value) => tokenSpan('string', value));
}
return html;
}
function applyCodeView(block, value) {
const code = block.querySelector('code');
if (!code) return false;
const originalText = block.dataset.sourceText || code.innerText;
block.dataset.sourceText = originalText;
const savedLanguage = block.dataset.savedLanguage || block.dataset.language || 'text';
const [selectedLanguage, selectedMode] = String(value || 'auto').split(':');
const resolvedLanguage = selectedLanguage === 'auto' ? savedLanguage : selectedLanguage;
block.dataset.language = resolvedLanguage;
block.dataset.codeMode = selectedMode === 'contrast' ? 'contrast' : 'normal';
code.innerHTML = highlightCode(originalText, resolvedLanguage);
block.classList.add('is-highlighted');
return true;
}
document.querySelectorAll('pre[data-language]').forEach((block) => {
block.dataset.savedLanguage = block.dataset.language || 'text';
applyCodeView(block, 'auto');
});
const codeThemeSelect = document.getElementById('codeThemeSelect');
const pasteBlock = document.getElementById('pasteBody');
if (codeThemeSelect && pasteBlock) {
codeThemeSelect.addEventListener('change', (event) => {
applyCodeView(pasteBlock, event.target.value);
});
}
const tagInput = document.getElementById('tagInput');
const tagStack = document.getElementById('tagStack');
const tagsField = document.getElementById('tags');
const activeTags = [];
function cleanTag(value) {
return String(value || '')
.trim()
.replace(/^#+/, '')
.toLowerCase()
.replace(/[^a-z0-9_-]+/g, '-')
.replace(/^[-_]+|[-_]+$/g, '')
.slice(0, 40);
}
function syncTags() {
if (!tagsField || !tagStack) return;
tagsField.value = activeTags.join(', ');
tagStack.innerHTML = '';
activeTags.forEach((tag) => {
const chip = document.createElement('span');
chip.className = 'tag-chip';
chip.textContent = `#${tag}`;
const remove = document.createElement('button');
remove.type = 'button';
remove.setAttribute('aria-label', `Remove ${tag}`);
remove.textContent = 'x';
remove.addEventListener('click', () => {
const index = activeTags.indexOf(tag);
if (index >= 0) activeTags.splice(index, 1);
syncTags();
tagInput?.focus();
});
chip.appendChild(remove);
tagStack.appendChild(chip);
});
}
function addTag(value) {
const tag = cleanTag(value);
if (tag && !activeTags.includes(tag) && activeTags.length < 20) {
activeTags.push(tag);
}
if (tagInput) tagInput.value = '';
syncTags();
}
if (tagInput) {
tagInput.addEventListener('keydown', (event) => {
if (event.key === ',') {
event.preventDefault();
addTag(tagInput.value);
} else if (event.key === 'Enter' && tagInput.value.trim() !== '') {
event.preventDefault();
addTag(tagInput.value);
} else if (event.key === 'Backspace' && tagInput.value === '' && activeTags.length > 0) {
activeTags.pop();
syncTags();
}
});
tagInput.addEventListener('input', () => {
if (tagInput.value.includes(',')) {
const parts = tagInput.value.split(',');
parts.slice(0, -1).forEach(addTag);
tagInput.value = parts[parts.length - 1].trimStart();
}
});
tagInput.form?.addEventListener('submit', () => {
if (tagInput.value.trim() !== '') {
addTag(tagInput.value);
}
syncTags();
});
}
async function copyValue(id) {
const input = document.getElementById(id);
await navigator.clipboard.writeText(input.value);
input.select();
}
async function copyText(id) {
const node = document.getElementById(id);
await navigator.clipboard.writeText(node.innerText);
}
function clearPaste() {
const body = document.getElementById('body');
if (body) body.value = '';
activeTags.splice(0, activeTags.length);
if (tagInput) tagInput.value = '';
syncTags();
}
</script>
</body>
</html>