290 lines
17 KiB
PHP
290 lines
17 KiB
PHP
<?php
|
||
declare(strict_types=1);
|
||
@date_default_timezone_set('UTC');
|
||
$projectName = $_SERVER['PROJECT_NAME'] ?? 'Laegna & Spireason Trivia: LandScaper';
|
||
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'A structural console and document map for the Laegna & Spireason main webpage.';
|
||
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
||
$now = date('Y-m-d H:i:s');
|
||
|
||
require_once __DIR__ . "/includes/curated_data.php";
|
||
|
||
function link_filename(array $link): string
|
||
{
|
||
$path = parse_url((string)($link['href'] ?? ''), PHP_URL_PATH);
|
||
return $path ? rawurldecode(basename($path)) : '';
|
||
}
|
||
|
||
function link_host(array $link): string
|
||
{
|
||
return strtolower((string)(parse_url((string)($link['href'] ?? ''), PHP_URL_HOST) ?: ''));
|
||
}
|
||
|
||
function link_category(array $link): string
|
||
{
|
||
$kind = strtolower((string)($link['kind'] ?? ''));
|
||
$href = strtolower((string)($link['href'] ?? ''));
|
||
$name = strtolower((string)($link['name'] ?? ''));
|
||
$host = link_host($link);
|
||
$path = strtolower((string)(parse_url($href, PHP_URL_PATH) ?: ''));
|
||
if ($kind === 'pdf' || str_ends_with($path, '.pdf')) return 'pdf';
|
||
if (str_contains($name . ' ' . $href, 'bot') || str_contains($host, 'perplexity.ai')) return 'bot';
|
||
if ($kind === 'dataset' || str_ends_with($path, '.json')) return 'dataset';
|
||
if ($kind === 'source' || str_ends_with($path, '.py')) return 'source';
|
||
if (str_contains($name, 'calculator') || str_contains($name, 'octave') || str_contains($name, 'frequency') || str_contains($name, 'counter')) return 'tool';
|
||
if ($kind === 'github' || str_contains($host, 'github')) return 'github';
|
||
if (str_contains($href, 'apples.html') || str_contains($kind, 'app')) return 'applet';
|
||
if ($kind === 'archive' || str_ends_with($path, '.zip')) return 'archive';
|
||
if ($kind === 'presentation' || str_contains($host, 'prezi')) return 'presentation';
|
||
if ($kind === 'media' || str_contains($host, 'youtube') || str_contains($host, 'notion')) return 'media';
|
||
if ($host !== '' && $host !== 'spireason.neocities.org') return 'external';
|
||
return $kind !== '' ? preg_replace('/[^a-z0-9-]+/', '-', $kind) : 'onsite';
|
||
}
|
||
|
||
function link_category_label(string $category): string
|
||
{
|
||
return [
|
||
'pdf' => 'PDF text',
|
||
'applet' => 'Applet',
|
||
'bot' => 'External bot/system',
|
||
'tool' => 'Tool',
|
||
'github' => 'Repository',
|
||
'archive' => 'Archive',
|
||
'presentation' => 'Presentation',
|
||
'media' => 'Media',
|
||
'dataset' => 'Dataset',
|
||
'source' => 'Source file',
|
||
'external' => 'External system',
|
||
'onsite' => 'On-site branch',
|
||
'branch' => 'Branch',
|
||
'research' => 'Research system',
|
||
'manual' => 'Manual',
|
||
'table' => 'Table',
|
||
][$category] ?? ucfirst(str_replace('-', ' ', $category));
|
||
}
|
||
|
||
$pdfLinks = array_values(array_filter($links, fn($link) => ($link['kind'] ?? '') === 'pdf'));
|
||
|
||
?>
|
||
<!doctype html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<title><?= htmlspecialchars($projectName) ?></title>
|
||
<?php if ($projectDescription): ?>
|
||
<meta name="description" content="<?= htmlspecialchars($projectDescription) ?>">
|
||
<meta property="og:description" content="<?= htmlspecialchars($projectDescription) ?>">
|
||
<meta property="twitter:description" content="<?= htmlspecialchars($projectDescription) ?>">
|
||
<?php endif; ?>
|
||
<?php if ($projectImageUrl): ?>
|
||
<meta property="og:image" content="<?= htmlspecialchars($projectImageUrl) ?>">
|
||
<meta property="twitter:image" content="<?= htmlspecialchars($projectImageUrl) ?>">
|
||
<?php endif; ?>
|
||
<meta name="theme-color" content="#111111">
|
||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||
<link rel="stylesheet" href="assets/css/custom.css?v=<?= time() ?>">
|
||
</head>
|
||
<body>
|
||
<a class="skip-link" href="#document">Skip to document</a>
|
||
<header class="topbar" aria-label="Primary navigation">
|
||
<nav class="container-fluid d-flex align-items-center justify-content-between gap-3">
|
||
<a class="brand" href="#console" aria-label="Scroll to console"><span class="brand-mark">LS</span><span>LandScaper</span></a>
|
||
<div class="top-links" aria-label="Page areas">
|
||
<a href="#console">Console</a>
|
||
<a href="#natura">✦ Foundational Texts</a>
|
||
<a href="#bigbang">⟲ Continuing Studies</a>
|
||
<a href="#pdf-library">PDF Library</a>
|
||
<a href="curated-links.php">Curated Links</a>
|
||
<a href="#live-audit">Live Links</a>
|
||
</div>
|
||
<button class="theme-toggle" type="button" data-theme-toggle aria-pressed="false">Day</button>
|
||
</nav>
|
||
</header>
|
||
|
||
<main id="console">
|
||
<section class="console-screen" aria-labelledby="main-title">
|
||
<div class="fixed-cluster cluster-left" aria-label="Console fixed branch buttons">
|
||
<a href="https://laegna.notaku.site/" target="_blank" rel="noopener">Study</a>
|
||
<a href="https://spireason.neocities.org/repos.html" target="_blank" rel="noopener">Repos</a>
|
||
<a href="https://huggingface.co/datasets/tvaeli/LaegnaSpi/resolve/main/Spireason.zip" target="_blank" rel="noopener">PDFs.zip</a>
|
||
</div>
|
||
<div class="fixed-cluster cluster-right" aria-label="Console fixed discovery buttons">
|
||
<a href="https://spireason.neocities.org/Additional/bots.html" target="_blank" rel="noopener">Chat</a>
|
||
<a href="https://www.perplexity.ai/spaces/laegna-spireason-and-related-m-dLTaZz8CSd2FUCjIiYQARA" target="_blank" rel="noopener">Find</a>
|
||
<a href="https://spireason.neocities.org/apples.html" target="_blank" rel="noopener">Apps</a>
|
||
</div>
|
||
<div class="console-panel">
|
||
<p class="eyebrow">Structural clone · main webpage only</p>
|
||
<h1 id="main-title">Laegna & Spireason Trivia: LandScaper.</h1>
|
||
<p class="lead">A restrained console layer folds into a document map. Each icon is treated as an owner: its source anchor owns the visible links until the next large icon, with a bonus shadow-books icon for the prelude.</p>
|
||
<div class="title-icons" aria-label="Major page parts">
|
||
<?php foreach ($sections as $section): ?>
|
||
<a href="#<?= htmlspecialchars($section['id']) ?>" data-section-jump data-title="<?= htmlspecialchars($section['title']) ?>" aria-label="<?= htmlspecialchars($section['title']) ?>"><?= htmlspecialchars($section['icon']) ?></a>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
<div class="console-actions">
|
||
<a class="btn btn-dark btn-sm" href="#document">Enter Document</a>
|
||
<a class="btn btn-outline-dark btn-sm" href="curated-links.php">Curated links page</a>
|
||
<button class="btn btn-outline-dark btn-sm" type="button" data-open-map>Open structural map</button>
|
||
</div>
|
||
</div>
|
||
<a href="#document" class="scroll-cue" aria-label="Scroll down into document">↓</a>
|
||
</section>
|
||
|
||
<nav class="bottom-nav" data-bottom-nav aria-label="Section icon navigation">
|
||
<a class="chapter-link left" href="#natura">✦ <span>Foundational</span></a>
|
||
<div class="bottom-icons">
|
||
<?php foreach ($sections as $section): ?>
|
||
<a href="#<?= htmlspecialchars($section['id']) ?>" data-bs-toggle="tooltip" data-bs-title="<?= htmlspecialchars($section['title']) ?>"><?= htmlspecialchars($section['icon']) ?></a>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
<a class="chapter-link right" href="#bigbang"><span>Continuing</span> ⟲</a>
|
||
</nav>
|
||
|
||
<section id="document" class="document-shell" aria-labelledby="document-title">
|
||
<div class="container-fluid px-3 px-lg-4">
|
||
<div class="doc-heading">
|
||
<p class="eyebrow">Document layer</p>
|
||
<h2 id="document-title">Console-to-document structural index</h2>
|
||
<p>This slice now follows the live ownership rule: <?= htmlspecialchars($sourceOwnershipNote) ?> It exposes <?= count($pdfLinks) ?> verified root PDFs, curated links, and a filtered visible-link scanner for the current reference page.</p>
|
||
</div>
|
||
|
||
<div class="toolbar" role="region" aria-label="View tools">
|
||
<label class="search-box"><span>Filter</span><input type="search" id="linkFilter" placeholder="Search links, PDFs, tools…"></label>
|
||
<div class="filter-status" id="filterStatus" role="status">Filter keeps card heights stable; non-matches dim instead of disappearing.</div>
|
||
<div class="mode-switch" aria-label="Display mode">
|
||
<button class="active" type="button" data-mode="blocks">Block mode</button>
|
||
<button type="button" data-mode="files">File mode</button>
|
||
<button type="button" data-mode="text">Text mode</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="chapter-grid">
|
||
<article id="foundational" class="chapter-card">
|
||
<div class="chapter-kicker">✦ Foundational Texts</div>
|
||
<h3>Start: infinity, material magic, karma, chakra, meditation.</h3>
|
||
<p>PDF documents are treated as central files: they keep their exact names, direct links, and short summaries.</p>
|
||
</article>
|
||
<article id="continuing" class="chapter-card">
|
||
<div class="chapter-kicker">⟲ Continuing Studies</div>
|
||
<h3>Continuation: apps, repositories, datasets, generated studies.</h3>
|
||
<p>Main branches remain first-class paths; child links are represented as branch/file entities rather than hidden prose.</p>
|
||
</article>
|
||
</div>
|
||
|
||
|
||
<section id="pdf-library" class="pdf-library" data-pdf-library data-pdf-mode="intro" aria-labelledby="pdf-library-title">
|
||
<div class="pdf-library-head">
|
||
<div>
|
||
<p class="eyebrow">Verified PDF library</p>
|
||
<h2 id="pdf-library-title">25 root-folder PDFs, exact filenames preserved</h2>
|
||
<p>These are direct <code>spireason.neocities.org/<filename>.pdf</code> URLs. Mojibake dash text is normalized to the real en dash (<code>–</code>), while filenames stay visible in a quieter line.</p>
|
||
</div>
|
||
<div class="pdf-mode-switch" aria-label="PDF display mode">
|
||
<button type="button" data-pdf-view="icons">Icon-only</button>
|
||
<button type="button" data-pdf-view="title">Icon + title</button>
|
||
<button type="button" data-pdf-view="summary">Title + summary</button>
|
||
<button class="active" type="button" data-pdf-view="intro">Intro blocks</button>
|
||
</div>
|
||
</div>
|
||
<div class="pdf-grid" aria-label="Verified PDF files">
|
||
<?php foreach ($pdfLinks as $pdf): ?>
|
||
<?php $filename = link_filename($pdf); ?>
|
||
<a class="pdf-card link-kind-pdf" href="<?= htmlspecialchars($pdf['href']) ?>" target="_blank" rel="noopener" data-pdf-card data-search="<?= htmlspecialchars(strtolower($pdf['name'].' '.$pdf['summary'].' '.$filename)) ?>">
|
||
<span class="pdf-symbol" aria-hidden="true"><?= htmlspecialchars($pdf['icon']) ?></span>
|
||
<span class="pdf-title"><?= htmlspecialchars($pdf['name']) ?></span>
|
||
<span class="pdf-summary"><?= htmlspecialchars($pdf['summary']) ?></span>
|
||
<span class="pdf-filename"><?= htmlspecialchars($filename) ?></span>
|
||
<span class="pdf-open">Open direct PDF ↗</span>
|
||
</a>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
</section>
|
||
|
||
<div class="sections-grid" data-section-grid>
|
||
<?php foreach ($sections as $section): ?>
|
||
<?php $sectionLinks = array_values(array_filter($links, fn($link) => $link['section'] === $section['id'])); ?>
|
||
<?php $previewLinks = array_slice($sectionLinks, 0, 4); $remainingLinks = max(0, count($sectionLinks) - count($previewLinks)); ?>
|
||
<article class="section-card" id="<?= htmlspecialchars($section['id']) ?>" data-section-card data-title="<?= htmlspecialchars(strtolower($section['title'])) ?>" data-chapter="<?= htmlspecialchars($section['chapter']) ?>">
|
||
<div class="section-head">
|
||
<div class="section-icon" aria-hidden="true"><?= htmlspecialchars($section['icon']) ?></div>
|
||
<div>
|
||
<div class="section-chapter"><?= htmlspecialchars($section['chapter']) ?></div>
|
||
<h3><?= htmlspecialchars($section['title']) ?></h3>
|
||
<div class="section-meta"><span>#<?= htmlspecialchars($section['id']) ?></span><span><?= (int)($section['source_count'] ?? count($sectionLinks)) ?> source links → <?= htmlspecialchars((string)($section['source_until'] ?? 'next')) ?></span></div>
|
||
</div>
|
||
</div>
|
||
<p><?= htmlspecialchars($section['summary']) ?></p>
|
||
<div class="file-list">
|
||
<?php if ($sectionLinks): foreach ($previewLinks as $link): ?>
|
||
<?php $category = link_category($link); $filename = link_filename($link); ?>
|
||
<a class="file-row link-kind-<?= htmlspecialchars($category) ?>" href="<?= htmlspecialchars($link['href']) ?>" target="_blank" rel="noopener" data-link-row data-kind="<?= htmlspecialchars($category) ?>" data-search="<?= htmlspecialchars(strtolower($link['name'].' '.$link['summary'].' '.$link['kind'].' '.$category.' '.$filename.' '.$section['title'])) ?>">
|
||
<span class="file-icon"><?= htmlspecialchars($link['icon']) ?></span>
|
||
<span class="file-copy">
|
||
<strong><?= htmlspecialchars($link['name']) ?></strong>
|
||
<small><span class="kind-pill kind-<?= htmlspecialchars($category) ?>"><?= htmlspecialchars(link_category_label($category)) ?></span><?php if ($filename): ?> <span class="filename"><?= htmlspecialchars($filename) ?></span><?php endif; ?> · <?= htmlspecialchars($link['summary']) ?></small>
|
||
</span>
|
||
<span class="file-arrow">↗</span>
|
||
</a>
|
||
<?php endforeach; ?>
|
||
<?php if ($remainingLinks > 0): ?>
|
||
<a class="file-row more-row" href="curated-links.php#<?= htmlspecialchars($section['id']) ?>" data-link-row data-kind="more" data-search="<?= htmlspecialchars(strtolower($section['title'] . ' curated full reference all resources')) ?>">
|
||
<span class="file-icon">↳</span>
|
||
<span class="file-copy"><strong>View all <?= (int)count($sectionLinks) ?> curated entries</strong><small>Full category browse lives on the dedicated curated links page.</small></span>
|
||
<span class="file-arrow">→</span>
|
||
</a>
|
||
<?php endif; ?>
|
||
<?php else: ?>
|
||
<div class="empty-state">No curated links yet. Use the live scanner below for current source links.</div>
|
||
<?php endif; ?>
|
||
</div>
|
||
</article>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
|
||
<section id="live-audit" class="audit-panel" aria-labelledby="audit-title">
|
||
<div>
|
||
<p class="eyebrow">Automated scanner</p>
|
||
<h2 id="audit-title">New Links scanner</h2>
|
||
<p>Checks the live reference page, lists visible, human-facing anchors only, suppresses asset/script/noise and deliberately removed URLs, then flags links not represented in this curated structure.</p>
|
||
</div>
|
||
<button class="btn btn-dark btn-sm" type="button" id="scanLinks">Scan source links</button>
|
||
<div class="audit-status" id="auditStatus" role="status">Idle. Click scan to compare with the live page.</div>
|
||
<div class="scanner-policy">Policy: visible anchors only · no JavaScript/mail/asset links · known removals suppressed · unknown external hosts marked for review.</div>
|
||
<div class="audit-results" id="auditResults" data-known='<?= htmlspecialchars(json_encode(array_column($links, 'href'), JSON_UNESCAPED_SLASHES)) ?>'></div>
|
||
</section>
|
||
</div>
|
||
</section>
|
||
</main>
|
||
|
||
<div class="toast-wrap" aria-live="polite" aria-atomic="true"><div class="mini-toast" id="miniToast">Ready.</div></div>
|
||
|
||
<div class="modal fade" id="mapModal" tabindex="-1" aria-labelledby="mapModalTitle" aria-hidden="true">
|
||
<div class="modal-dialog modal-lg modal-dialog-scrollable">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h2 class="modal-title fs-5" id="mapModalTitle">Structural map</h2>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<ol class="map-list">
|
||
<?php foreach ($sections as $i => $section): ?>
|
||
<li><a href="#<?= htmlspecialchars($section['id']) ?>" data-map-target="<?= htmlspecialchars($section['id']) ?>" data-bs-dismiss="modal"><span><?= htmlspecialchars($section['icon']) ?></span><?= str_pad((string)($i + 1), 2, '0', STR_PAD_LEFT) ?> · <?= htmlspecialchars($section['title']) ?></a><small><?= htmlspecialchars($section['summary']) ?></small></li>
|
||
<?php endforeach; ?>
|
||
</ol>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<footer class="site-footer">
|
||
<span>Runtime: PHP <?= htmlspecialchars(PHP_VERSION) ?></span>
|
||
<span>Updated <?= htmlspecialchars($now) ?> UTC</span>
|
||
<a href="api/scan_links.php">Scanner API</a>
|
||
</footer>
|
||
|
||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||
<script src="assets/js/main.js?v=<?= time() ?>" defer></script>
|
||
</body>
|
||
</html>
|