Autosave: 20260612-091548
This commit is contained in:
parent
199922a335
commit
ccd646f13b
0
'Comparison
Normal file
0
'Comparison
Normal file
0
'Evolution')
s=s.replace('name'=
Normal file
0
'Evolution')
s=s.replace('name'=
Normal file
0
'Karma')
s=s.replace('name'=
Normal file
0
'Karma')
s=s.replace('name'=
Normal file
0
'Principles
Normal file
0
'Principles
Normal file
0
'Spireason
Normal file
0
'Spireason
Normal file
0
44,source_until=
Normal file
0
44,source_until=
Normal file
0
6,source_until=
Normal file
0
6,source_until=
Normal file
268
api/scan_links.php
Normal file
268
api/scan_links.php
Normal file
@ -0,0 +1,268 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('Cache-Control: no-store');
|
||||
|
||||
$source = 'https://spireason.neocities.org/';
|
||||
|
||||
// Human-curated suppressions: links intentionally excluded from LandScaper even if they remain in source.
|
||||
// Keep exact normalized URLs here when a source link is deliberately removed from the clone.
|
||||
$deliberatelyRemoved = [
|
||||
// Example format: 'https://spireason.neocities.org/removed-example.html',
|
||||
];
|
||||
|
||||
$context = stream_context_create([
|
||||
'http' => [
|
||||
'timeout' => 12,
|
||||
'header' => "User-Agent: Laegna-LandScaper-VisibleLinkScanner/1.1\r\n",
|
||||
],
|
||||
]);
|
||||
$html = @file_get_contents($source, false, $context);
|
||||
if ($html === false || $html === '') {
|
||||
http_response_code(502);
|
||||
echo json_encode(['success' => false, 'error' => 'Could not fetch source page.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$links = [];
|
||||
$seen = [];
|
||||
$ignoredCount = 0;
|
||||
$suppressedCount = 0;
|
||||
|
||||
if (preg_match_all('~<a\b([^>]*)>(.*?)</a>~is', $html, $matches, PREG_SET_ORDER)) {
|
||||
foreach ($matches as $match) {
|
||||
$attrs = $match[1] ?? '';
|
||||
$inner = $match[2] ?? '';
|
||||
|
||||
if (is_hidden_anchor($attrs)) {
|
||||
$ignoredCount++;
|
||||
continue;
|
||||
}
|
||||
if (!preg_match('~\bhref\s*=\s*(["\'])(.*?)\1~is', $attrs, $hrefMatch)) {
|
||||
$ignoredCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
$href = html_entity_decode(trim($hrefMatch[2]), ENT_QUOTES | ENT_HTML5, 'UTF-8');
|
||||
$label = visible_label($attrs, $inner);
|
||||
if ($label === '') {
|
||||
$ignoredCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
$absolute = absolutize_url($href, $source);
|
||||
$normalized = normalize_for_compare($absolute);
|
||||
$skipReason = skip_reason($href, $absolute, $normalized, $deliberatelyRemoved);
|
||||
if ($skipReason === 'suppressed') {
|
||||
$suppressedCount++;
|
||||
continue;
|
||||
}
|
||||
if ($skipReason !== '') {
|
||||
$ignoredCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isset($seen[$normalized])) {
|
||||
continue;
|
||||
}
|
||||
$seen[$normalized] = true;
|
||||
|
||||
$category = categorize_link($absolute, $label);
|
||||
$host = strtolower((string)(parse_url($absolute, PHP_URL_HOST) ?: ''));
|
||||
$links[] = [
|
||||
'text' => unicode_limit($label, 160),
|
||||
'href' => $absolute,
|
||||
'host' => $host,
|
||||
'category' => $category,
|
||||
'category_label' => category_label($category),
|
||||
'safety' => safety_class($host),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'source' => $source,
|
||||
'count' => count($links),
|
||||
'ignored_count' => $ignoredCount,
|
||||
'suppressed_count' => $suppressedCount,
|
||||
'scanned_at' => gmdate('Y-m-d H:i:s') . ' UTC',
|
||||
'filters' => [
|
||||
'visible_anchor_text_required' => true,
|
||||
'skips_schemes' => ['javascript', 'mailto', 'tel', 'data', 'blob'],
|
||||
'skips_asset_extensions' => ['js', 'mjs', 'css', 'map', 'png', 'jpg', 'jpeg', 'gif', 'svg', 'webp', 'ico', 'woff', 'woff2', 'ttf', 'eot'],
|
||||
'unknown_external_hosts' => 'included but marked review',
|
||||
],
|
||||
'links' => $links,
|
||||
], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
|
||||
|
||||
function is_hidden_anchor(string $attrs): bool
|
||||
{
|
||||
if (preg_match('~(?:^|\s)hidden(?:\s|=|$)~i', $attrs)) {
|
||||
return true;
|
||||
}
|
||||
if (preg_match('~\baria-hidden\s*=\s*(["\'])true\1~i', $attrs)) {
|
||||
return true;
|
||||
}
|
||||
if (preg_match('~\bstyle\s*=\s*(["\'])(.*?)\1~is', $attrs, $m)) {
|
||||
$style = strtolower($m[2]);
|
||||
return str_contains($style, 'display:none')
|
||||
|| str_contains($style, 'display: none')
|
||||
|| str_contains($style, 'visibility:hidden')
|
||||
|| str_contains($style, 'visibility: hidden')
|
||||
|| str_contains($style, 'opacity:0')
|
||||
|| str_contains($style, 'opacity: 0');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function visible_label(string $attrs, string $inner): string
|
||||
{
|
||||
$cleanInner = preg_replace('~<(script|style)\b[^>]*>.*?</\1>~is', ' ', $inner) ?? $inner;
|
||||
$text = html_entity_decode(strip_tags($cleanInner), ENT_QUOTES | ENT_HTML5, 'UTF-8');
|
||||
$text = trim(preg_replace('/\s+/u', ' ', $text) ?? '');
|
||||
if ($text !== '') {
|
||||
return $text;
|
||||
}
|
||||
|
||||
foreach ([$attrs, $inner] as $source) {
|
||||
if (preg_match_all('~\b(?:aria-label|title|alt)\s*=\s*(["\'])(.*?)\1~is', $source, $matches, PREG_SET_ORDER)) {
|
||||
foreach ($matches as $match) {
|
||||
$candidate = trim(preg_replace('/\s+/u', ' ', html_entity_decode($match[2], ENT_QUOTES | ENT_HTML5, 'UTF-8')) ?? '');
|
||||
if ($candidate !== '') {
|
||||
return $candidate;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function skip_reason(string $href, string $absolute, string $normalized, array $deliberatelyRemoved): string
|
||||
{
|
||||
$hrefLower = strtolower(trim($href));
|
||||
if ($hrefLower === '' || $hrefLower[0] === '#') {
|
||||
return 'fragment';
|
||||
}
|
||||
foreach (['javascript:', 'mailto:', 'tel:', 'data:', 'blob:'] as $scheme) {
|
||||
if (str_starts_with($hrefLower, $scheme)) {
|
||||
return 'scheme';
|
||||
}
|
||||
}
|
||||
if (!preg_match('~^https?://~i', $absolute)) {
|
||||
return 'scheme';
|
||||
}
|
||||
|
||||
$path = (string)(parse_url($absolute, PHP_URL_PATH) ?: '');
|
||||
if (preg_match('~\.(?:js|mjs|css|map|png|jpe?g|gif|svg|webp|ico|woff2?|ttf|eot)(?:$|[?#])~i', $path)) {
|
||||
return 'asset';
|
||||
}
|
||||
|
||||
foreach ($deliberatelyRemoved as $removed) {
|
||||
if ($normalized === normalize_for_compare((string)$removed)) {
|
||||
return 'suppressed';
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function categorize_link(string $url, string $label): string
|
||||
{
|
||||
$host = strtolower((string)(parse_url($url, PHP_URL_HOST) ?: ''));
|
||||
$path = strtolower((string)(parse_url($url, PHP_URL_PATH) ?: ''));
|
||||
$haystack = strtolower($url . ' ' . $label);
|
||||
if (str_ends_with($path, '.pdf')) return 'pdf';
|
||||
if (str_ends_with($path, '.zip')) return 'archive';
|
||||
if (str_ends_with($path, ".json") || str_contains($path, "numberdatabase")) return "dataset";
|
||||
if (str_ends_with($path, ".py") || str_contains($haystack, "script")) return "source";
|
||||
if (str_contains($haystack, 'frequency') || str_contains($haystack, 'frequential') || str_contains($haystack, 'octave') || str_contains($haystack, 'calculator') || str_contains($haystack, 'counter')) return 'tool';
|
||||
if (str_contains($haystack, 'bot') || str_contains($host, 'perplexity.ai')) return 'bot';
|
||||
if (str_contains($host, 'github')) return 'github';
|
||||
if (str_contains($path, 'apples') || str_contains($haystack, 'app')) return 'applet';
|
||||
if (str_contains($host, 'prezi')) return 'presentation';
|
||||
if (str_contains($host, 'youtube') || str_contains($host, 'notion')) return 'media';
|
||||
if ($host !== 'spireason.neocities.org') return 'external';
|
||||
return 'onsite';
|
||||
}
|
||||
|
||||
function category_label(string $category): string
|
||||
{
|
||||
return [
|
||||
'pdf' => 'PDF text',
|
||||
'archive' => 'Archive',
|
||||
"dataset" => "Dataset",
|
||||
"source" => "Source file",
|
||||
'tool' => 'Tool',
|
||||
'bot' => 'External bot/system',
|
||||
'github' => 'Repository',
|
||||
'applet' => 'Applet',
|
||||
'presentation' => 'Presentation',
|
||||
'media' => 'Media',
|
||||
'external' => 'External system',
|
||||
'onsite' => 'On-site branch',
|
||||
][$category] ?? ucfirst(str_replace('-', ' ', $category));
|
||||
}
|
||||
|
||||
function safety_class(string $host): string
|
||||
{
|
||||
if ($host === 'spireason.neocities.org') {
|
||||
return 'source';
|
||||
}
|
||||
$knownHosts = [
|
||||
'laegna.notaku.site',
|
||||
'prezi.com',
|
||||
'www.perplexity.ai',
|
||||
'huggingface.co',
|
||||
'assorted-canopy-961.notion.site',
|
||||
'www.youtube.com',
|
||||
'youtube.com',
|
||||
'github.com',
|
||||
'tambetvali.github.io',
|
||||
];
|
||||
foreach ($knownHosts as $knownHost) {
|
||||
if ($host === $knownHost || str_ends_with($host, '.' . $knownHost)) {
|
||||
return 'known-external';
|
||||
}
|
||||
}
|
||||
return 'review';
|
||||
}
|
||||
|
||||
function unicode_limit(string $text, int $limit): string
|
||||
{
|
||||
if (preg_match_all('/./us', $text, $chars) && count($chars[0]) > $limit) {
|
||||
return implode('', array_slice($chars[0], 0, $limit - 1)) . '…';
|
||||
}
|
||||
return $text;
|
||||
}
|
||||
|
||||
function normalize_for_compare(string $url): string
|
||||
{
|
||||
$url = trim($url);
|
||||
$url = preg_replace('/#$/', '', $url) ?? $url;
|
||||
return $url;
|
||||
}
|
||||
|
||||
function absolutize_url(string $href, string $base): string
|
||||
{
|
||||
$href = trim($href);
|
||||
if (preg_match('~^https?://~i', $href)) {
|
||||
return encode_spaces($href);
|
||||
}
|
||||
$parts = parse_url($base);
|
||||
$scheme = $parts['scheme'] ?? 'https';
|
||||
$host = $parts['host'] ?? 'spireason.neocities.org';
|
||||
if (str_starts_with($href, '//')) {
|
||||
return encode_spaces($scheme . ':' . $href);
|
||||
}
|
||||
if (str_starts_with($href, '/')) {
|
||||
return encode_spaces($scheme . '://' . $host . $href);
|
||||
}
|
||||
$path = $parts['path'] ?? '/';
|
||||
$dir = preg_replace('~/[^/]*$~', '/', $path) ?: '/';
|
||||
return encode_spaces($scheme . '://' . $host . $dir . $href);
|
||||
}
|
||||
|
||||
function encode_spaces(string $url): string
|
||||
{
|
||||
return str_replace(' ', '%20', $url);
|
||||
}
|
||||
316
api/scan_structure.php
Normal file
316
api/scan_structure.php
Normal file
@ -0,0 +1,316 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
header("Content-Type: application/json; charset=utf-8");
|
||||
|
||||
require_once __DIR__ . "/../includes/curated_data.php";
|
||||
|
||||
$source = "https://spireason.neocities.org/";
|
||||
$ids = [
|
||||
["id"=>"growing-out-of-the-shadows", "icon"=>"⚛️", "title"=>"Growing Out of the Shadows"],
|
||||
["id"=>"sheep", "icon"=>"🐑", "title"=>"Sheep / Laegna Counters"],
|
||||
["id"=>"infinity", "icon"=>"♾️", "title"=>"Infinity"],
|
||||
["id"=>"natura", "icon"=>"🌀", "title"=>"Natura"],
|
||||
["id"=>"sunrise", "icon"=>"🔆", "title"=>"Sunrise"],
|
||||
["id"=>"bigbang", "icon"=>"💥", "title"=>"Bigbang"],
|
||||
["id"=>"yggdrasill", "icon"=>"🌳", "title"=>"Yggdrasill"],
|
||||
["id"=>"spiritrise", "icon"=>"🔷", "title"=>"Spiritrise"],
|
||||
["id"=>"laelab", "icon"=>"🔬", "title"=>"LaeLab"],
|
||||
["id"=>"geneticar", "icon"=>"🧬", "title"=>"Geneticar"],
|
||||
["id"=>"handheldcal", "icon"=>"🖩", "title"=>"HandheldCal"],
|
||||
["id"=>"puzzled", "icon"=>"🧩", "title"=>"Puzzled"],
|
||||
["id"=>"chakra", "icon"=>"✴️", "title"=>"Chakra"],
|
||||
["id"=>"wheelsgoround", "icon"=>"⚙️", "title"=>"Wheels Go Round"],
|
||||
["id"=>"laemedics", "icon"=>"🧘", "title"=>"LaeMedics"],
|
||||
["id"=>"coffeeandcigarettes", "icon"=>"☕", "title"=>"Coffee and Cigarettes"],
|
||||
];
|
||||
|
||||
$html = fetch_source($source);
|
||||
if ($html === null) {
|
||||
http_response_code(502);
|
||||
echo json_encode(["success"=>false, "error"=>"Could not fetch source page"], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
$positions = [];
|
||||
foreach ($ids as $item) {
|
||||
$pattern = '~\bid\s*=\s*["\x27]' . preg_quote($item["id"], '~') . '["\x27]~i';
|
||||
if (preg_match($pattern, $html, $m, PREG_OFFSET_CAPTURE)) {
|
||||
$item["pos"] = $m[0][1];
|
||||
$positions[] = $item;
|
||||
}
|
||||
}
|
||||
usort($positions, fn($a, $b) => $a["pos"] <=> $b["pos"]);
|
||||
|
||||
$sections = [];
|
||||
$totalLinks = 0;
|
||||
$utilityChrome = [];
|
||||
foreach ($positions as $idx => $item) {
|
||||
$start = (int)$item["pos"];
|
||||
$end = isset($positions[$idx + 1]) ? (int)$positions[$idx + 1]["pos"] : strlen($html);
|
||||
$chunk = substr($html, $start, $end - $start);
|
||||
$ownedLinks = [];
|
||||
if ($item["id"] === "coffeeandcigarettes") {
|
||||
// Coffee is terminal prose. Links after it are footer/fixed/absolute utility chrome,
|
||||
// not branch links owned by the Coffee icon.
|
||||
$utilityChrome = parse_visible_links($chunk, $source);
|
||||
} else {
|
||||
$ownedLinks = parse_visible_links($chunk, $source);
|
||||
$totalLinks += count($ownedLinks);
|
||||
}
|
||||
$layout = $sectionLayout[$item["id"]] ?? null;
|
||||
$sections[] = [
|
||||
"id" => $item["id"],
|
||||
"icon" => $item["icon"],
|
||||
"title" => $item["title"],
|
||||
"source_anchor" => "#" . $item["id"],
|
||||
"owns_until" => $item["id"] === "coffeeandcigarettes" ? "utility chrome" : (isset($positions[$idx + 1]) ? "#" . $positions[$idx + 1]["id"] : "end"),
|
||||
"layout" => $layout,
|
||||
"excerpt" => unicode_limit(clean_text($chunk), 420),
|
||||
"link_count" => count($ownedLinks),
|
||||
"links" => $ownedLinks,
|
||||
];
|
||||
}
|
||||
|
||||
$shadowBonus = [
|
||||
["icon"=>"🎭", "title"=>"Dancing Shadows", "href"=>"https://material-psychic-gam-8mo3.bolt.host/"],
|
||||
["icon"=>"📘", "title"=>"Book of Shadows 2", "href"=>"https://app-bxfrqbbqegap.appmedo.com/"],
|
||||
["icon"=>"📕", "title"=>"Bulk Load Book of the Dead 3", "href"=>"https://app-by9gm7mu9ssh.appmedo.com/"],
|
||||
];
|
||||
|
||||
echo json_encode([
|
||||
"success" => true,
|
||||
"source" => $source,
|
||||
"scanned_at" => gmdate("Y-m-d H:i:s") . " UTC",
|
||||
"model" => "CSS-aware: first 100vh console and fixed/absolute utility chrome are separated; document-flow icon owners keep links until the next meaningful icon/title; Coffee is terminal prose and owns no branch links.",
|
||||
"layout_zones" => $layoutZones ?? [],
|
||||
"section_count" => count($sections),
|
||||
"link_count" => $totalLinks,
|
||||
"utility_count" => count($utilityChrome),
|
||||
"utility_chrome" => $utilityChrome,
|
||||
"shadow_bonus" => $shadowBonus,
|
||||
"sections" => $sections,
|
||||
], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
|
||||
|
||||
function fetch_source(string $url): ?string
|
||||
{
|
||||
$context = stream_context_create([
|
||||
"http" => [
|
||||
"timeout" => 18,
|
||||
"header" => "User-Agent: LandScaper structural scanner\r\n",
|
||||
],
|
||||
]);
|
||||
$html = @file_get_contents($url, false, $context);
|
||||
return is_string($html) && $html !== "" ? $html : null;
|
||||
}
|
||||
|
||||
function parse_visible_links(string $chunk, string $base): array
|
||||
{
|
||||
$links = [];
|
||||
if (!preg_match_all("~<a\b([^>]*)>(.*?)</a>~is", $chunk, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE)) {
|
||||
return $links;
|
||||
}
|
||||
foreach ($matches as $match) {
|
||||
$attrs = $match[1][0] ?? "";
|
||||
$inner = $match[2][0] ?? "";
|
||||
$offset = (int)($match[0][1] ?? 0);
|
||||
if (is_hidden_anchor($attrs)) {
|
||||
continue;
|
||||
}
|
||||
$href = attr_value($attrs, "href");
|
||||
$label = visible_label($attrs, $inner);
|
||||
if ($href === "" || $label === "") {
|
||||
continue;
|
||||
}
|
||||
$absolute = absolute_url($href, $base);
|
||||
$reason = skip_reason($href, $absolute);
|
||||
if ($reason !== "") {
|
||||
continue;
|
||||
}
|
||||
$host = strtolower((string)(parse_url($absolute, PHP_URL_HOST) ?: ""));
|
||||
$category = categorize_link($absolute, $label);
|
||||
$links[] = [
|
||||
"text" => unicode_limit($label, 150),
|
||||
"href" => $absolute,
|
||||
"host" => $host,
|
||||
"category" => $category,
|
||||
"category_label" => category_label($category),
|
||||
"context" => context_hint($chunk, $offset),
|
||||
"layout_hint" => layout_hint($attrs, $chunk, $offset),
|
||||
"safety" => safety_class($host),
|
||||
];
|
||||
}
|
||||
return $links;
|
||||
}
|
||||
|
||||
|
||||
function context_hint(string $chunk, int $offset): string
|
||||
{
|
||||
$prefix = substr($chunk, max(0, $offset - 1800), min(1800, $offset));
|
||||
if (!preg_match_all('~<(article|section|blockquote|div|p|big|center)\b([^>]*)>~i', $prefix, $tags, PREG_SET_ORDER)) {
|
||||
return 'flow text';
|
||||
}
|
||||
$tag = end($tags);
|
||||
$name = strtolower($tag[1] ?? 'flow');
|
||||
$attrs = $tag[2] ?? '';
|
||||
$id = attr_value($attrs, 'id');
|
||||
$class = attr_value($attrs, 'class');
|
||||
$label = $name;
|
||||
if ($id !== '') $label .= '#' . $id;
|
||||
if ($class !== '') $label .= '.' . preg_replace('/\s+/', '.', trim($class));
|
||||
return $label;
|
||||
}
|
||||
|
||||
function layout_hint(string $attrs, string $chunk, int $offset): string
|
||||
{
|
||||
$style = strtolower(attr_value($attrs, 'style'));
|
||||
$class = strtolower(attr_value($attrs, 'class'));
|
||||
$prefix = strtolower(substr($chunk, max(0, $offset - 1300), min(1300, $offset)));
|
||||
$haystack = $style . ' ' . $class . ' ' . $prefix;
|
||||
if (str_contains($haystack, 'position: fixed') || str_contains($haystack, 'position:fixed')) return 'fixed chrome';
|
||||
if (str_contains($haystack, 'position: absolute') || str_contains($haystack, 'position:absolute') || str_contains($haystack, 'calc(100vh')) return 'absolute-positioned chrome';
|
||||
if (str_contains($haystack, 'position: relative') || str_contains($haystack, 'position:relative')) return 'relative positioned block';
|
||||
if (str_contains($haystack, '<article')) return 'article branch link';
|
||||
if (str_contains($haystack, '<section')) return 'section branch link';
|
||||
if (str_contains($haystack, '<blockquote')) return 'blockquote branch link';
|
||||
return 'document-flow link';
|
||||
}
|
||||
|
||||
function attr_value(string $attrs, string $name): string
|
||||
{
|
||||
$pattern = '~\b' . preg_quote($name, '~') . '\s*=\s*(["\x27])(.*?)\1~is';
|
||||
if (preg_match($pattern, $attrs, $m)) {
|
||||
return html_entity_decode(trim($m[2]), ENT_QUOTES | ENT_HTML5, "UTF-8");
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
function is_hidden_anchor(string $attrs): bool
|
||||
{
|
||||
if (preg_match("~(?:^|\s)hidden(?:\s|=|$)~i", $attrs)) return true;
|
||||
if (preg_match('~\baria-hidden\s*=\s*(["\x27])true\1~i', $attrs)) return true;
|
||||
$style = attr_value($attrs, "style");
|
||||
if ($style !== "") {
|
||||
$style = strtolower($style);
|
||||
return str_contains($style, "display:none") || str_contains($style, "display: none") || str_contains($style, "visibility:hidden") || str_contains($style, "visibility: hidden") || str_contains($style, "opacity:0") || str_contains($style, "opacity: 0");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function visible_label(string $attrs, string $inner): string
|
||||
{
|
||||
$cleanInner = preg_replace("~<(script|style)\b[^>]*>.*?</\1>~is", " ", $inner) ?? $inner;
|
||||
$text = html_entity_decode(strip_tags($cleanInner), ENT_QUOTES | ENT_HTML5, "UTF-8");
|
||||
$text = trim(preg_replace("/\s+/u", " ", $text) ?? "");
|
||||
if ($text !== "") return $text;
|
||||
foreach (["aria-label", "title", "alt"] as $attr) {
|
||||
$candidate = attr_value($attrs, $attr);
|
||||
if ($candidate !== "") return trim(preg_replace("/\s+/u", " ", $candidate) ?? "");
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
function skip_reason(string $href, string $absolute): string
|
||||
{
|
||||
$hrefLower = strtolower(trim($href));
|
||||
if ($hrefLower === "" || $hrefLower[0] === "#") return "fragment";
|
||||
foreach (["javascript:", "mailto:", "tel:", "data:", "blob:"] as $scheme) {
|
||||
if (str_starts_with($hrefLower, $scheme)) return "scheme";
|
||||
}
|
||||
if (!preg_match("~^https?://~i", $absolute)) return "scheme";
|
||||
$path = (string)(parse_url($absolute, PHP_URL_PATH) ?: "");
|
||||
if (preg_match("~\.(?:js|mjs|css|map|png|jpe?g|gif|svg|webp|ico|woff2?|ttf|eot)(?:$|[?#])~i", $path)) return "asset";
|
||||
return "";
|
||||
}
|
||||
|
||||
function absolute_url(string $href, string $base): string
|
||||
{
|
||||
$href = html_entity_decode(trim($href), ENT_QUOTES | ENT_HTML5, "UTF-8");
|
||||
if (preg_match("~^https?://~i", $href)) return $href;
|
||||
if (str_starts_with($href, "//")) return "https:" . $href;
|
||||
$parts = parse_url($base);
|
||||
$scheme = $parts["scheme"] ?? "https";
|
||||
$host = $parts["host"] ?? "spireason.neocities.org";
|
||||
if (str_starts_with($href, "/")) return $scheme . "://" . $host . $href;
|
||||
$basePath = $parts["path"] ?? "/";
|
||||
$dir = rtrim(dirname($basePath), "/");
|
||||
if ($dir === "") $dir = "/";
|
||||
return $scheme . "://" . $host . rtrim($dir, "/") . "/" . str_replace(" ", "%20", $href);
|
||||
}
|
||||
|
||||
function clean_text(string $html): string
|
||||
{
|
||||
$html = preg_replace("~<(script|style)\b[^>]*>.*?</\1>~is", " ", $html) ?? $html;
|
||||
$text = html_entity_decode(strip_tags($html), ENT_QUOTES | ENT_HTML5, "UTF-8");
|
||||
$text = preg_replace("~\bid=\"[^\"]+\"\s*>~u", " ", $text) ?? $text;
|
||||
$text = preg_replace("~[#.][A-Za-z0-9_-][^{}]{0,220}\\{[^{}]*\\}~u", " ", $text) ?? $text;
|
||||
$text = preg_replace("~\b(?:font|color|background|padding|margin|position|display|border|box-shadow|width|height|transform|opacity|z-index|transition)[^.;]{0,180}[.;]~iu", " ", $text) ?? $text;
|
||||
$text = preg_replace("~/\\*.*?\\*/~s", " ", $text) ?? $text;
|
||||
$text = preg_replace("~@media[^{}]*\\{\\s*\\}~u", " ", $text) ?? $text;
|
||||
return trim(preg_replace("/\s+/u", " ", $text) ?? "");
|
||||
}
|
||||
|
||||
function categorize_link(string $url, string $label): string
|
||||
{
|
||||
$host = strtolower((string)(parse_url($url, PHP_URL_HOST) ?: ""));
|
||||
$path = strtolower((string)(parse_url($url, PHP_URL_PATH) ?: ""));
|
||||
$haystack = strtolower($url . " " . $label);
|
||||
if (str_ends_with($path, ".pdf")) return "pdf";
|
||||
if (str_ends_with($path, ".zip")) return "archive";
|
||||
if (str_ends_with($path, ".json") || str_contains($path, "numberdatabase")) return "dataset";
|
||||
if (str_ends_with($path, ".py") || str_contains($haystack, "script") || str_contains($haystack, "github")) return "source";
|
||||
if (str_contains($haystack, "frequency") || str_contains($haystack, "frequential") || str_contains($haystack, "octave") || str_contains($haystack, "calculator") || str_contains($haystack, "counter")) return "tool";
|
||||
if (str_contains($haystack, "bot") || str_contains($host, "perplexity.ai")) return "bot";
|
||||
if (str_contains($host, "github")) return "github";
|
||||
if (str_contains($path, "apples") || str_contains($haystack, "app") || str_contains($host, "lovable.app") || str_contains($host, "bolt.host") || str_contains($host, "appmedo.com")) return "applet";
|
||||
if (str_contains($host, "prezi") || str_contains($host, "docs.google")) return "presentation";
|
||||
if (str_contains($host, "youtube") || str_contains($host, "notion")) return "media";
|
||||
if ($host !== "spireason.neocities.org") return "external";
|
||||
return "onsite";
|
||||
}
|
||||
|
||||
function category_label(string $category): string
|
||||
{
|
||||
return [
|
||||
"pdf" => "PDF text",
|
||||
"archive" => "Archive",
|
||||
"dataset" => "Dataset",
|
||||
"source" => "Source file",
|
||||
"tool" => "Tool",
|
||||
"bot" => "External bot/system",
|
||||
"github" => "Repository",
|
||||
"applet" => "Applet",
|
||||
"presentation" => "Presentation",
|
||||
"media" => "Media",
|
||||
"external" => "External system",
|
||||
"onsite" => "On-site branch",
|
||||
][$category] ?? ucfirst(str_replace("-", " ", $category));
|
||||
}
|
||||
|
||||
function safety_class(string $host): string
|
||||
{
|
||||
if ($host === "spireason.neocities.org") return "source";
|
||||
$knownHosts = [
|
||||
"laegna.notaku.site",
|
||||
"prezi.com",
|
||||
"www.perplexity.ai",
|
||||
"huggingface.co",
|
||||
"assorted-canopy-961.notion.site",
|
||||
"www.youtube.com",
|
||||
"youtube.com",
|
||||
"github.com",
|
||||
"tambetvali.github.io",
|
||||
"archive.org",
|
||||
];
|
||||
foreach ($knownHosts as $knownHost) {
|
||||
if ($host === $knownHost || str_ends_with($host, "." . $knownHost)) return "known-external";
|
||||
}
|
||||
return "review";
|
||||
}
|
||||
|
||||
function unicode_limit(string $text, int $limit): string
|
||||
{
|
||||
if (preg_match_all("/./us", $text, $chars) && count($chars[0]) > $limit) {
|
||||
return implode("", array_slice($chars[0], 0, max(0, $limit - 1))) . "…";
|
||||
}
|
||||
return $text;
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@ -1,39 +1,535 @@
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const chatForm = document.getElementById('chat-form');
|
||||
const chatInput = document.getElementById('chat-input');
|
||||
const chatMessages = document.getElementById('chat-messages');
|
||||
(() => {
|
||||
const $ = (sel, root = document) => root.querySelector(sel);
|
||||
const $$ = (sel, root = document) => Array.from(root.querySelectorAll(sel));
|
||||
const toast = $("#miniToast");
|
||||
const showToast = (message) => {
|
||||
if (!toast) return;
|
||||
toast.textContent = message;
|
||||
toast.classList.add("show");
|
||||
window.clearTimeout(showToast.t);
|
||||
showToast.t = window.setTimeout(() => toast.classList.remove("show"), 2400);
|
||||
};
|
||||
|
||||
const appendMessage = (text, sender) => {
|
||||
const msgDiv = document.createElement('div');
|
||||
msgDiv.classList.add('message', sender);
|
||||
msgDiv.textContent = text;
|
||||
chatMessages.appendChild(msgDiv);
|
||||
chatMessages.scrollTop = chatMessages.scrollHeight;
|
||||
const bottomNav = $("[data-bottom-nav]");
|
||||
const syncNav = () => bottomNav?.classList.toggle("visible", window.scrollY > 8);
|
||||
window.addEventListener("scroll", syncNav, { passive: true });
|
||||
syncNav();
|
||||
|
||||
$$("[data-section-jump], .bottom-icons a, .top-links a, .scroll-cue, .chapter-link").forEach((link) => {
|
||||
link.addEventListener("click", () => {
|
||||
const label = link.getAttribute("data-title") || link.textContent.trim() || "section";
|
||||
showToast(`Navigating to ${label}`);
|
||||
});
|
||||
});
|
||||
|
||||
const themeToggle = $("[data-theme-toggle]");
|
||||
const storedTheme = localStorage.getItem("landscaper-theme");
|
||||
if (storedTheme === "night") {
|
||||
document.documentElement.dataset.theme = "night";
|
||||
if (themeToggle) {
|
||||
themeToggle.textContent = "Night";
|
||||
themeToggle.setAttribute("aria-pressed", "true");
|
||||
}
|
||||
}
|
||||
themeToggle?.addEventListener("click", () => {
|
||||
const next = document.documentElement.dataset.theme === "night" ? "day" : "night";
|
||||
if (next === "night") document.documentElement.dataset.theme = "night";
|
||||
else delete document.documentElement.dataset.theme;
|
||||
themeToggle.textContent = next === "night" ? "Night" : "Day";
|
||||
themeToggle.setAttribute("aria-pressed", String(next === "night"));
|
||||
localStorage.setItem("landscaper-theme", next === "night" ? "night" : "day");
|
||||
showToast(`${next === "night" ? "Night" : "Day"} mode enabled`);
|
||||
});
|
||||
|
||||
const grid = $("[data-section-grid]");
|
||||
$$(".mode-switch button").forEach((button) => {
|
||||
button.addEventListener("click", () => {
|
||||
$$(".mode-switch button").forEach((b) => b.classList.remove("active"));
|
||||
button.classList.add("active");
|
||||
grid?.classList.remove("blocks", "files", "text");
|
||||
grid?.classList.add(button.dataset.mode);
|
||||
showToast(`${button.dataset.mode} mode`);
|
||||
});
|
||||
});
|
||||
|
||||
const pdfLibrary = $("[data-pdf-library]");
|
||||
$$("[data-pdf-view]").forEach((button) => {
|
||||
button.addEventListener("click", () => {
|
||||
$$("[data-pdf-view]").forEach((b) => b.classList.remove("active"));
|
||||
button.classList.add("active");
|
||||
if (pdfLibrary) pdfLibrary.dataset.pdfMode = button.dataset.pdfView || "intro";
|
||||
showToast(`PDF view: ${button.textContent.trim()}`);
|
||||
});
|
||||
});
|
||||
|
||||
const referencePage = $("[data-reference-page]");
|
||||
const referenceModeButtons = $$("button[data-reference-mode]");
|
||||
const referencePanels = $$("[data-mode-panel]");
|
||||
const filter = $("#linkFilter");
|
||||
const filterDimToggle = $("#filterDimToggle");
|
||||
const filterStatus = $("#filterStatus");
|
||||
const filterModeLabel = $("[data-filter-mode-label]");
|
||||
|
||||
try {
|
||||
const savedFilterMode = localStorage.getItem("curated-filter-mode");
|
||||
if (filterDimToggle && savedFilterMode === "hide") filterDimToggle.checked = false;
|
||||
} catch {}
|
||||
|
||||
function setReferenceMode(mode, options = {}) {
|
||||
if (!referencePage || !mode) return;
|
||||
referencePage.dataset.referenceMode = mode;
|
||||
referenceModeButtons.forEach((button) => {
|
||||
const active = button.dataset.referenceMode === mode;
|
||||
button.classList.toggle("active", active);
|
||||
button.setAttribute("aria-selected", String(active));
|
||||
});
|
||||
referencePanels.forEach((panel) => {
|
||||
const active = panel.dataset.modePanel === mode;
|
||||
panel.hidden = !active;
|
||||
panel.classList.toggle("is-active", active);
|
||||
});
|
||||
if (!options.quiet) showToast(mode === "pdfs" ? "PDF block ready" : (mode === "chapters" ? "Chapter groups ready" : "Icon boxes ready"));
|
||||
scheduleVisibleHighlights();
|
||||
}
|
||||
|
||||
function panelForMode(mode) {
|
||||
return referencePanels.find((panel) => panel.dataset.modePanel === mode) || null;
|
||||
}
|
||||
|
||||
referenceModeButtons.forEach((button) => {
|
||||
button.addEventListener("click", () => {
|
||||
const mode = button.dataset.referenceMode;
|
||||
setReferenceMode(mode, { quiet: true });
|
||||
const panel = panelForMode(mode);
|
||||
if (panel?.id) window.setTimeout(() => markTarget(panel.id, { scroll: false, switchMode: false }), 30);
|
||||
});
|
||||
});
|
||||
|
||||
$$("[data-reference-mode-jump]").forEach((link) => {
|
||||
link.addEventListener("click", () => setReferenceMode(link.dataset.referenceModeJump, { quiet: true }));
|
||||
});
|
||||
|
||||
document.addEventListener("click", (event) => {
|
||||
const control = event.target.closest("[data-highlight-target]");
|
||||
if (!control) return;
|
||||
const targetId = control.dataset.highlightTarget;
|
||||
if (!targetId) return;
|
||||
const href = control.getAttribute("href") || "";
|
||||
const external = control.matches("a[href]") && href && !href.startsWith("#");
|
||||
if (!external) event.preventDefault();
|
||||
markTarget(targetId, { scroll: !external, switchMode: true });
|
||||
});
|
||||
|
||||
document.addEventListener("click", (event) => {
|
||||
const link = event.target.closest("a[href^=\"#\"]");
|
||||
if (!link || link.matches("[data-highlight-target]") || link.matches("[data-map-target]")) return;
|
||||
const raw = link.getAttribute("href") || "";
|
||||
if (raw.length <= 1) return;
|
||||
const targetId = decodeURIComponent(raw.slice(1));
|
||||
if (!document.getElementById(targetId)) return;
|
||||
event.preventDefault();
|
||||
if (link.dataset.referenceModeJump) setReferenceMode(link.dataset.referenceModeJump, { quiet: true });
|
||||
window.setTimeout(() => markTarget(targetId, { scroll: true, switchMode: true }), 30);
|
||||
});
|
||||
|
||||
function previewOwner(control, persist = false) {
|
||||
const targetId = control?.dataset?.ownerTarget;
|
||||
if (!targetId) return;
|
||||
$$(".is-owner-preview").forEach((el) => el.classList.remove("is-owner-preview"));
|
||||
$$(".is-owner-link").forEach((el) => el.classList.remove("is-owner-link"));
|
||||
control.classList.add("is-owner-link");
|
||||
markRelatedControls(targetId, true);
|
||||
const target = document.getElementById(targetId);
|
||||
const hiddenPanel = target?.closest("[data-mode-panel]")?.hidden;
|
||||
if (target && !hiddenPanel) target.classList.add("is-owner-preview");
|
||||
if (persist && target && !hiddenPanel) markTarget(targetId, { scroll: false, switchMode: false, toast: false });
|
||||
}
|
||||
|
||||
document.addEventListener("mouseover", (event) => {
|
||||
const control = event.target.closest("[data-owner-target]");
|
||||
if (control) previewOwner(control, false);
|
||||
});
|
||||
document.addEventListener("focusin", (event) => {
|
||||
const control = event.target.closest("[data-owner-target]");
|
||||
if (control) previewOwner(control, false);
|
||||
});
|
||||
document.addEventListener("click", (event) => {
|
||||
const control = event.target.closest("[data-owner-target]");
|
||||
if (control) previewOwner(control, true);
|
||||
});
|
||||
|
||||
const applyFilter = () => {
|
||||
const anchorState = captureScrollAnchor();
|
||||
const q = filter?.value.trim().toLowerCase() || "";
|
||||
const dimOut = filterDimToggle ? filterDimToggle.checked : true;
|
||||
let total = 0;
|
||||
let hits = 0;
|
||||
|
||||
const setFiltered = (el, hit) => {
|
||||
const miss = Boolean(q && !hit);
|
||||
el.classList.toggle("is-filter-muted", miss && dimOut);
|
||||
el.classList.toggle("is-hidden", miss && !dimOut);
|
||||
el.classList.toggle("is-filter-hit", Boolean(q && hit));
|
||||
};
|
||||
|
||||
chatForm.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
const message = chatInput.value.trim();
|
||||
if (!message) return;
|
||||
|
||||
appendMessage(message, 'visitor');
|
||||
chatInput.value = '';
|
||||
|
||||
try {
|
||||
const response = await fetch('api/chat.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ message })
|
||||
});
|
||||
const data = await response.json();
|
||||
|
||||
// Artificial delay for realism
|
||||
setTimeout(() => {
|
||||
appendMessage(data.reply, 'bot');
|
||||
}, 500);
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
appendMessage("Sorry, something went wrong. Please try again.", 'bot');
|
||||
}
|
||||
$$("[data-pdf-card]").forEach((card) => {
|
||||
total += 1;
|
||||
const hit = !q || (card.dataset.search || "").includes(q);
|
||||
if (hit) hits += 1;
|
||||
setFiltered(card, hit);
|
||||
});
|
||||
});
|
||||
|
||||
$$("[data-section-card]").forEach((card) => {
|
||||
const sectionMatch = !q || (card.dataset.title || "").includes(q) || (card.dataset.chapter || "").toLowerCase().includes(q);
|
||||
let any = sectionMatch;
|
||||
$$("[data-link-row]", card).forEach((row) => {
|
||||
total += 1;
|
||||
const hit = !q || (row.dataset.search || "").includes(q) || sectionMatch;
|
||||
if (hit) hits += 1;
|
||||
if (hit) any = true;
|
||||
setFiltered(row, hit);
|
||||
});
|
||||
setFiltered(card, any);
|
||||
});
|
||||
|
||||
if (filterModeLabel) filterModeLabel.textContent = dimOut ? "dim" : "hide";
|
||||
try { localStorage.setItem("curated-filter-mode", dimOut ? "dim" : "hide"); } catch {}
|
||||
if (filterStatus) filterStatus.textContent = q ? `${hits}/${total} · ${dimOut ? "dim" : "hide"}` : `All · ${dimOut ? "dim" : "hide"}`;
|
||||
restoreScrollAnchor(anchorState);
|
||||
scheduleVisibleHighlights();
|
||||
};
|
||||
|
||||
filter?.addEventListener("input", applyFilter);
|
||||
filterDimToggle?.addEventListener("change", applyFilter);
|
||||
applyFilter();
|
||||
|
||||
const mapBtn = $("[data-open-map]");
|
||||
mapBtn?.addEventListener("click", () => {
|
||||
const modalElement = $("#mapModal");
|
||||
if (!modalElement || !window.bootstrap) return;
|
||||
const modal = new bootstrap.Modal(modalElement);
|
||||
modal.show();
|
||||
});
|
||||
|
||||
$$("[data-map-target]").forEach((link) => {
|
||||
link.addEventListener("click", (event) => {
|
||||
event.preventDefault();
|
||||
const targetId = link.getAttribute("data-map-target");
|
||||
const modalEl = $("#mapModal");
|
||||
if (modalEl && window.bootstrap) bootstrap.Modal.getInstance(modalEl)?.hide();
|
||||
window.setTimeout(() => openSection(targetId), 170);
|
||||
});
|
||||
});
|
||||
|
||||
const scanBtn = $("#scanLinks");
|
||||
const auditStatus = $("#auditStatus");
|
||||
const auditResults = $("#auditResults");
|
||||
scanBtn?.addEventListener("click", async () => {
|
||||
scanBtn.disabled = true;
|
||||
auditStatus.textContent = "Scanning live source…";
|
||||
auditResults.innerHTML = "";
|
||||
try {
|
||||
const response = await fetch("api/scan_links.php", { headers: { "Accept": "application/json" }});
|
||||
if (!response.ok) throw new Error("Scanner returned " + response.status);
|
||||
const payload = await response.json();
|
||||
const known = new Set(JSON.parse(auditResults.dataset.known || "[]").map((u) => normalize(u)));
|
||||
const rows = (payload.links || []).map((link) => ({ ...link, known: known.has(normalize(link.href)) }));
|
||||
const newCount = rows.filter((row) => !row.known).length;
|
||||
const ignored = Number(payload.ignored_count || 0);
|
||||
const suppressed = Number(payload.suppressed_count || 0);
|
||||
auditStatus.textContent = `${rows.length} visible source links found · ${newCount} not curated · ${ignored} noise/asset links ignored · ${suppressed} deliberately removed · scanned ${payload.scanned_at || "now"}.`;
|
||||
auditResults.innerHTML = rows.map(renderAuditRow).join("");
|
||||
showToast("Live scan complete");
|
||||
} catch (error) {
|
||||
auditStatus.textContent = "Scan failed. The curated structure remains available.";
|
||||
showToast("Scanner unavailable");
|
||||
} finally {
|
||||
scanBtn.disabled = false;
|
||||
}
|
||||
});
|
||||
|
||||
const structureButtons = [$("#loadStructure"), $("#loadStructureBottom")].filter(Boolean);
|
||||
const structureStatus = $("#sourceStructureStatus");
|
||||
const structureTarget = $("#sourceStructure");
|
||||
structureButtons.forEach((button) => {
|
||||
button.addEventListener("click", () => loadSourceStructure());
|
||||
});
|
||||
|
||||
async function loadSourceStructure() {
|
||||
if (!structureTarget || !structureStatus) return;
|
||||
structureButtons.forEach((button) => { button.disabled = true; });
|
||||
structureStatus.textContent = "Reading live source icon ownership…";
|
||||
structureTarget.innerHTML = "";
|
||||
try {
|
||||
const response = await fetch("api/scan_structure.php", { headers: { "Accept": "application/json" }});
|
||||
if (!response.ok) throw new Error("Structure scanner returned " + response.status);
|
||||
const payload = await response.json();
|
||||
if (!payload.success) throw new Error(payload.error || "Structure scanner failed");
|
||||
structureStatus.textContent = `${payload.section_count} source owner icons · ${payload.link_count} grouped branch links · ${Number(payload.utility_count || 0)} separated utility links · scanned ${payload.scanned_at}.`;
|
||||
structureTarget.innerHTML = `
|
||||
<div class="shadow-bonus-live">
|
||||
<strong>Bonus shadow icon</strong>
|
||||
${(payload.shadow_bonus || []).map((item) => `<a href="${escapeAttr(item.href)}" target="_blank" rel="noopener">${escapeHtml(item.icon)} ${escapeHtml(item.title)}</a>`).join("")}
|
||||
</div>
|
||||
${renderUtilityChrome(payload.utility_chrome || [])}
|
||||
${(payload.sections || []).map(renderSourceSection).join("")}
|
||||
`;
|
||||
showToast("Source ownership loaded");
|
||||
} catch (error) {
|
||||
structureStatus.textContent = "Could not load live structure. The curated snapshot above remains available.";
|
||||
showToast("Source map unavailable");
|
||||
scheduleVisibleHighlights();
|
||||
} finally {
|
||||
structureButtons.forEach((button) => { button.disabled = false; });
|
||||
}
|
||||
}
|
||||
|
||||
function renderAuditRow(row) {
|
||||
const badgeText = row.known ? "curated" : (row.safety === "review" ? "review external" : (row.category || "new"));
|
||||
const badgeClass = row.known ? "audit-badge-known" : (row.safety === "review" ? "audit-badge-review" : (row.safety === "source" ? "audit-badge-source" : "audit-badge-new"));
|
||||
const category = String(row.category || "external").replace(/[^a-z0-9-]/gi, "-").toLowerCase();
|
||||
return `
|
||||
<div class="audit-row link-kind-${escapeAttr(category)}">
|
||||
<div><a href="${escapeAttr(row.href)}" target="_blank" rel="noopener nofollow">${escapeHtml(row.text || row.href)}</a><small>${escapeHtml(row.category_label || row.category || "link")} · ${escapeHtml(row.host || "")} · ${escapeHtml(row.href)}</small></div>
|
||||
<span class="badge-soft ${badgeClass}">${escapeHtml(badgeText)}</span>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
function renderUtilityChrome(items) {
|
||||
if (!items.length) return "";
|
||||
return `
|
||||
<div class="shadow-bonus-live utility-live">
|
||||
<strong>Separated utility chrome</strong>
|
||||
${items.slice(0, 18).map((item) => `<a href="${escapeAttr(item.href)}" target="_blank" rel="noopener">${escapeHtml(item.text || item.href)}</a>`).join("")}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function renderSourceSection(section) {
|
||||
return `
|
||||
<article class="source-owner-card" id="live-${escapeAttr(section.id)}" data-live-owner="${escapeAttr(section.id)}">
|
||||
<div class="source-owner-head">
|
||||
<span class="source-owner-icon">${escapeHtml(section.icon)}</span>
|
||||
<div>
|
||||
<h3>${escapeHtml(section.title)}</h3>
|
||||
<small>${escapeHtml(section.source_anchor)} owns ${Number(section.link_count || 0)} links until ${escapeHtml(section.owns_until || "next")}</small>
|
||||
</div>
|
||||
</div>
|
||||
<p>${escapeHtml(section.excerpt || "")}</p>
|
||||
<button class="manual-highlight" type="button" data-highlight-target="live-${escapeAttr(section.id)}">✨ Pin live box</button>
|
||||
${section.layout ? `<div class="layout-note"><b>${escapeHtml(section.layout.zone || "layout")}</b><span>${escapeHtml(section.layout.computed || "")}</span><small>${escapeHtml(section.layout.parent || "")} · ${escapeHtml(section.layout.position || "")}</small><em>${escapeHtml(section.layout.note || "")}</em></div>` : ""}
|
||||
<div class="source-owner-links">
|
||||
${(section.links || []).map((link) => {
|
||||
const category = String(link.category || "external").replace(/[^a-z0-9-]/gi, "-").toLowerCase();
|
||||
const context = [link.category_label || category, link.context || "", link.layout_hint || "", link.host || ""].filter(Boolean).join(" · ");
|
||||
return `<a class="source-link link-kind-${escapeAttr(category)}" href="${escapeAttr(link.href)}" target="_blank" rel="noopener" data-owner-target="live-${escapeAttr(section.id)}"><span>${escapeHtml(link.text || link.href)}</span><small>${escapeHtml(context)}</small></a>`;
|
||||
}).join("") || `<span class="empty-state">No visible links inside this owner in the current source scan.</span>`}
|
||||
</div>
|
||||
</article>
|
||||
`;
|
||||
}
|
||||
|
||||
const highlightableTargetSelector = [
|
||||
"[data-section-card][id]",
|
||||
".section-card[id]",
|
||||
".chapter-card[id]",
|
||||
".chapter-reference-card[id]",
|
||||
".layout-zone-card[id]",
|
||||
".layout-audit[id]",
|
||||
".ownership-strip[id]",
|
||||
".reference-tools[id]",
|
||||
".reference-hero[id]",
|
||||
".pdf-library[id]",
|
||||
".reference-mode-panel[id]",
|
||||
".utility-chrome-panel[id]",
|
||||
".source-owner-card[id]",
|
||||
".audit-panel[id]",
|
||||
".document-shell[id]",
|
||||
".console-screen[id]"
|
||||
].join(",");
|
||||
const boxClickTargetSelector = [
|
||||
"[data-section-card][id]",
|
||||
".section-card[id]",
|
||||
".chapter-card[id]",
|
||||
".chapter-reference-card[id]",
|
||||
".layout-zone-card[id]",
|
||||
".layout-audit[id]",
|
||||
".ownership-strip[id]",
|
||||
".reference-tools[id]",
|
||||
".reference-hero[id]",
|
||||
".pdf-library[id]",
|
||||
".reference-mode-panel[id]",
|
||||
".utility-chrome-panel[id]",
|
||||
".source-owner-card[id]",
|
||||
".audit-panel[id]"
|
||||
].join(",");
|
||||
const targetControlSelector = "[data-highlight-target],[data-owner-target],[data-map-target],a[href^='#']";
|
||||
const visibleTargetSelector = highlightableTargetSelector;
|
||||
const interactiveSelector = "a,button,input,select,textarea,label,summary,[role='button'],[tabindex]:not([tabindex='-1'])";
|
||||
|
||||
function controlTargetId(control) {
|
||||
if (!control) return "";
|
||||
const direct = control.dataset?.highlightTarget || control.dataset?.ownerTarget || control.dataset?.mapTarget || "";
|
||||
if (direct) return direct;
|
||||
const href = control.getAttribute?.("href") || "";
|
||||
if (!href.startsWith("#") || href.length <= 1) return "";
|
||||
try { return decodeURIComponent(href.slice(1)); }
|
||||
catch { return href.slice(1); }
|
||||
}
|
||||
|
||||
function isElementHidden(el) {
|
||||
if (!el || !el.isConnected || el.hidden || el.closest("[hidden]")) return true;
|
||||
const style = window.getComputedStyle(el);
|
||||
return style.display === "none" || style.visibility === "hidden" || Number(style.opacity || 1) === 0;
|
||||
}
|
||||
|
||||
function visiblePixels(el) {
|
||||
const rect = el.getBoundingClientRect();
|
||||
const width = Math.max(0, Math.min(rect.right, window.innerWidth) - Math.max(rect.left, 0));
|
||||
const height = Math.max(0, Math.min(rect.bottom, window.innerHeight) - Math.max(rect.top, 0));
|
||||
return { width, height, area: width * height };
|
||||
}
|
||||
|
||||
function isMeaningfullyVisible(el) {
|
||||
if (!el?.id || isElementHidden(el)) return false;
|
||||
const visible = visiblePixels(el);
|
||||
return visible.width >= 20 && visible.height >= 20 && visible.area >= 400;
|
||||
}
|
||||
|
||||
function visibleTargetIds() {
|
||||
const ids = new Set();
|
||||
$$(visibleTargetSelector).forEach((target) => {
|
||||
if (isMeaningfullyVisible(target)) ids.add(target.id);
|
||||
});
|
||||
return ids;
|
||||
}
|
||||
|
||||
function syncVisibleHighlights() {
|
||||
const visibleIds = visibleTargetIds();
|
||||
$$(".is-in-view-target").forEach((el) => el.classList.remove("is-in-view-target"));
|
||||
$$(".is-visible-owner-link").forEach((el) => el.classList.remove("is-visible-owner-link"));
|
||||
|
||||
visibleIds.forEach((id) => {
|
||||
document.getElementById(id)?.classList.add("is-in-view-target");
|
||||
});
|
||||
|
||||
$$(targetControlSelector).forEach((control) => {
|
||||
const targetId = controlTargetId(control);
|
||||
control.classList.toggle("is-visible-owner-link", Boolean(targetId && visibleIds.has(targetId)));
|
||||
});
|
||||
}
|
||||
|
||||
function scheduleVisibleHighlights() {
|
||||
window.cancelAnimationFrame(scheduleVisibleHighlights.raf || 0);
|
||||
scheduleVisibleHighlights.raf = window.requestAnimationFrame(syncVisibleHighlights);
|
||||
}
|
||||
|
||||
window.addEventListener("scroll", scheduleVisibleHighlights, { passive: true });
|
||||
window.addEventListener("resize", scheduleVisibleHighlights, { passive: true });
|
||||
window.addEventListener("hashchange", scheduleVisibleHighlights);
|
||||
|
||||
document.addEventListener("click", (event) => {
|
||||
const box = event.target.closest?.(boxClickTargetSelector);
|
||||
if (!box?.id) return;
|
||||
const interactive = event.target.closest(interactiveSelector);
|
||||
if (interactive && box.contains(interactive)) {
|
||||
const explicitTarget = controlTargetId(interactive);
|
||||
if (explicitTarget || !interactive.matches("a[href]")) return;
|
||||
}
|
||||
markTarget(box.id, { scroll: false, switchMode: false, toast: !interactive });
|
||||
});
|
||||
|
||||
function openSection(id) {
|
||||
markTarget(id, { scroll: true, switchMode: true });
|
||||
}
|
||||
|
||||
function clearTargetHighlights() {
|
||||
const activeSelector = [
|
||||
".section-card.is-active",
|
||||
".chapter-reference-card.is-active",
|
||||
".pdf-card.is-active",
|
||||
".layout-zone-card.is-active",
|
||||
".layout-audit.is-active",
|
||||
".ownership-strip.is-active",
|
||||
".reference-tools.is-active",
|
||||
".utility-chrome-panel.is-active",
|
||||
".source-owner-card.is-active",
|
||||
".audit-panel.is-active",
|
||||
".reference-hero.is-active",
|
||||
".chapter-card.is-active",
|
||||
".document-shell.is-active",
|
||||
".console-screen.is-active",
|
||||
".pdf-library.is-active"
|
||||
].join(",");
|
||||
$$(activeSelector).forEach((el) => el.classList.remove("is-active"));
|
||||
$$(".is-focus-target,.is-owner-preview,.is-owner-link").forEach((el) => el.classList.remove("is-focus-target", "is-owner-preview", "is-owner-link"));
|
||||
$$("[aria-current=\"location\"]").forEach((el) => el.removeAttribute("aria-current"));
|
||||
}
|
||||
|
||||
function markRelatedControls(targetId, lightweight = false) {
|
||||
$$(targetControlSelector).forEach((el) => {
|
||||
const match = controlTargetId(el) === targetId;
|
||||
el.classList.toggle("is-owner-link", match);
|
||||
if (match && !lightweight) el.setAttribute("aria-current", "location");
|
||||
});
|
||||
}
|
||||
|
||||
function markTarget(id, options = {}) {
|
||||
const target = id ? document.getElementById(id) : null;
|
||||
if (!target) return;
|
||||
const modePanel = target.closest("[data-mode-panel]");
|
||||
if (modePanel && modePanel.hidden && options.switchMode !== false) setReferenceMode(modePanel.dataset.modePanel || "icons", { quiet: true });
|
||||
clearTargetHighlights();
|
||||
target.classList.add("is-focus-target");
|
||||
if (!target.classList.contains("reference-mode-panel")) target.classList.add("is-active");
|
||||
markRelatedControls(id, false);
|
||||
if (options.scroll !== false) {
|
||||
target.scrollIntoView({ behavior: "smooth", block: "start" });
|
||||
if (history.replaceState) history.replaceState(null, "", `#${id}`);
|
||||
}
|
||||
window.clearTimeout(markTarget.pulseTimer);
|
||||
markTarget.pulseTimer = window.setTimeout(() => target.classList.remove("is-focus-target"), 4200);
|
||||
scheduleVisibleHighlights();
|
||||
window.setTimeout(scheduleVisibleHighlights, options.scroll !== false ? 520 : 40);
|
||||
if (options.toast !== false) showToast(`Pinned ${target.querySelector("h1,h2,h3")?.textContent?.trim() || id}`);
|
||||
}
|
||||
|
||||
function captureScrollAnchor() {
|
||||
const y = Math.min(Math.max(120, window.innerHeight * 0.25), window.innerHeight - 1);
|
||||
const el = document.elementFromPoint(Math.min(90, window.innerWidth - 1), y);
|
||||
return el ? { el, top: el.getBoundingClientRect().top } : null;
|
||||
}
|
||||
|
||||
function restoreScrollAnchor(state) {
|
||||
if (!state || !state.el || !state.el.isConnected) return;
|
||||
window.requestAnimationFrame(() => {
|
||||
const diff = state.el.getBoundingClientRect().top - state.top;
|
||||
if (Math.abs(diff) > 1) window.scrollBy(0, diff);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
scheduleVisibleHighlights();
|
||||
|
||||
if (window.location.hash && document.getElementById(window.location.hash.slice(1))) {
|
||||
window.setTimeout(() => openSection(window.location.hash.slice(1)), 250);
|
||||
}
|
||||
|
||||
function normalize(url) {
|
||||
try { return new URL(url, "https://spireason.neocities.org/").href.replace(/#$/, ""); }
|
||||
catch { return String(url || "").trim(); }
|
||||
}
|
||||
|
||||
function escapeHtml(value) {
|
||||
return String(value || "").replace(/[&<>"\x27]/g, (char) => {
|
||||
if (char === "&") return "&";
|
||||
if (char === "<") return "<";
|
||||
if (char === ">") return ">";
|
||||
if (char.charCodeAt(0) === 34) return """;
|
||||
return "'";
|
||||
});
|
||||
}
|
||||
function escapeAttr(value) { return escapeHtml(value); }
|
||||
})();
|
||||
|
||||
334
curated-links.php
Normal file
334
curated-links.php
Normal file
@ -0,0 +1,334 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
@date_default_timezone_set("UTC");
|
||||
$projectName = "Curated Links · Laegna & Spireason LandScaper";
|
||||
$projectDescription = "A CSS-aware reference page for Laegna and Spireason links, separating fixed console chrome, main chapter gates, icon-owned blocks, PDFs, and utility controls.";
|
||||
$projectImageUrl = $_SERVER["PROJECT_IMAGE_URL"] ?? "";
|
||||
$now = date("Y-m-d H:i:s");
|
||||
require_once __DIR__ . "/includes/curated_data.php";
|
||||
|
||||
function ref_filename(array $link): string
|
||||
{
|
||||
$path = parse_url((string)($link["href"] ?? ""), PHP_URL_PATH);
|
||||
return $path ? rawurldecode(basename($path)) : "";
|
||||
}
|
||||
|
||||
function ref_host(array $link): string
|
||||
{
|
||||
return strtolower((string)(parse_url((string)($link["href"] ?? ""), PHP_URL_HOST) ?: ""));
|
||||
}
|
||||
|
||||
function ref_category(array $link): string
|
||||
{
|
||||
$kind = strtolower((string)($link["kind"] ?? ""));
|
||||
$href = strtolower((string)($link["href"] ?? ""));
|
||||
$name = strtolower((string)($link["name"] ?? ""));
|
||||
$host = ref_host($link);
|
||||
$path = strtolower((string)(parse_url($href, PHP_URL_PATH) ?: ""));
|
||||
if ($kind === "pdf" || str_ends_with($path, ".pdf")) return "pdf";
|
||||
if ($kind === "dataset" || str_ends_with($path, ".json")) return "dataset";
|
||||
if ($kind === "source" || str_ends_with($path, ".py")) return "source";
|
||||
if (str_contains($name . " " . $href, "bot") || str_contains($host, "perplexity.ai")) return "bot";
|
||||
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") || str_contains($host, "docs.google") || str_contains($host, "popai.pro")) 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 ref_label(string $category): string
|
||||
{
|
||||
return [
|
||||
"pdf" => "PDF text",
|
||||
"applet" => "Applet",
|
||||
"bot" => "External bot/system",
|
||||
"tool" => "Tool",
|
||||
"github" => "Repository",
|
||||
"archive" => "Archive / utility",
|
||||
"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",
|
||||
"main-branch" => "Main branch",
|
||||
][$category] ?? ucfirst(str_replace("-", " ", $category));
|
||||
}
|
||||
|
||||
function section_by_id(array $sections, string $id): ?array
|
||||
{
|
||||
foreach ($sections as $section) {
|
||||
if (($section["id"] ?? "") === $id) return $section;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function links_for_sections(array $links, array $ids): array
|
||||
{
|
||||
$set = array_fill_keys($ids, true);
|
||||
return array_values(array_filter($links, static fn(array $link): bool => isset($set[$link["section"] ?? ""])));
|
||||
}
|
||||
|
||||
$categoryCounts = [];
|
||||
foreach ($links as $link) {
|
||||
$cat = ref_category($link);
|
||||
$categoryCounts[$cat] = ($categoryCounts[$cat] ?? 0) + 1;
|
||||
}
|
||||
ksort($categoryCounts);
|
||||
$pdfLinks = array_values(array_filter($links, fn($link) => ref_category($link) === "pdf"));
|
||||
$pdfCount = count($pdfLinks);
|
||||
$utilityCount = isset($utilityLinks) ? count($utilityLinks) : 0;
|
||||
?>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title><?= htmlspecialchars($projectName) ?></title>
|
||||
<meta name="description" content="<?= htmlspecialchars($projectDescription) ?>">
|
||||
<meta property="og:description" content="<?= htmlspecialchars($projectDescription) ?>">
|
||||
<meta property="twitter:description" content="<?= htmlspecialchars($projectDescription) ?>">
|
||||
<?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="#reference-main">Skip to references</a>
|
||||
<header class="topbar" aria-label="Reference navigation">
|
||||
<nav class="container-fluid d-flex align-items-center justify-content-between gap-3">
|
||||
<a class="brand" href="index.php#console" aria-label="Back to LandScaper"><span class="brand-mark">LS</span><span>LandScaper</span></a>
|
||||
<div class="top-links" aria-label="Reference areas">
|
||||
<a href="index.php">Home</a>
|
||||
<a href="#source-layout">Layout</a>
|
||||
<a href="#ownership">Ownership</a>
|
||||
<a href="#curated-index">Modes</a>
|
||||
<a href="#pdf-only-index" data-reference-mode-jump="pdfs">PDFs</a>
|
||||
<a href="#live-source-map">Live Source Map</a>
|
||||
</div>
|
||||
<button class="theme-toggle" type="button" data-theme-toggle aria-pressed="false">Day</button>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main id="reference-main" class="reference-page" data-reference-page>
|
||||
<section class="reference-hero" aria-labelledby="reference-title">
|
||||
<p class="eyebrow">CSS-aware reference system</p>
|
||||
<h1 id="reference-title">Separate console chrome, chapter gates, icon blocks, PDFs, and utility links.</h1>
|
||||
<p><?= htmlspecialchars($sourceOwnershipNote) ?></p>
|
||||
<div class="reference-stats" aria-label="Curated totals">
|
||||
<span><?= count($sections) ?> owner icons</span>
|
||||
<span><?= count($links) ?> branch entries</span>
|
||||
<span><?= $pdfCount ?> PDF-only entries</span>
|
||||
<span><?= $utilityCount ?> separated utility links</span>
|
||||
</div>
|
||||
<div class="console-actions">
|
||||
<a class="btn btn-dark btn-sm" href="#curated-index" data-reference-mode-jump="icons">Browse icon blocks</a>
|
||||
<a class="btn btn-joy btn-sm" href="#pdf-only-index" data-reference-mode-jump="pdfs">Open PDF block</a>
|
||||
<button class="btn btn-outline-dark btn-sm" id="loadStructure" type="button">Load live source ownership</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="source-layout" class="layout-audit" aria-labelledby="layout-title">
|
||||
<div class="layout-audit-head">
|
||||
<p class="eyebrow">Position model from CSS + computed layout</p>
|
||||
<h2 id="layout-title">The source is four layers before it is a link list.</h2>
|
||||
<p>The public page uses inline CSS, fixed clusters, absolute panels, nested articles/sections, and small JavaScript hover/scroll helpers. This page separates those layers before grouping links.</p>
|
||||
</div>
|
||||
<div class="layout-zone-grid">
|
||||
<?php foreach (($layoutZones ?? []) as $zone): ?>
|
||||
<article class="layout-zone-card" id="layout-<?= htmlspecialchars($zone["id"]) ?>">
|
||||
<span class="layout-zone-icon" aria-hidden="true"><?= htmlspecialchars($zone["icon"]) ?></span>
|
||||
<div>
|
||||
<h3><?= htmlspecialchars($zone["title"]) ?></h3>
|
||||
<p><?= htmlspecialchars($zone["summary"]) ?></p>
|
||||
<small><b>CSS:</b> <?= htmlspecialchars($zone["css"]) ?> · <b>Computed:</b> <?= htmlspecialchars($zone["computed"]) ?></small>
|
||||
</div>
|
||||
</article>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="ownership" class="ownership-strip" aria-labelledby="ownership-title">
|
||||
<div>
|
||||
<p class="eyebrow">Icon order + bright pins</p>
|
||||
<h2 id="ownership-title">Tap any icon, chapter pill, or box link to glow the exact owner on the page.</h2>
|
||||
</div>
|
||||
<div class="ownership-icons">
|
||||
<?php foreach ($sections as $section): ?>
|
||||
<a href="#<?= htmlspecialchars($section["id"]) ?>" data-highlight-target="<?= htmlspecialchars($section["id"]) ?>" aria-label="Highlight owner <?= htmlspecialchars($section["title"]) ?>"><span><?= htmlspecialchars($section["icon"]) ?></span><small><?= (int)($section["source_count"] ?? 0) ?></small></a>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="reference-tools" aria-label="Reference filters and modes">
|
||||
<label class="search-box"><span>Filter</span><input type="search" id="linkFilter" placeholder="Search owners, PDFs, sheep data, systems…"></label>
|
||||
<div class="reference-mode-switch" role="tablist" aria-label="Reference modes">
|
||||
<button class="active" type="button" data-reference-mode="icons" aria-selected="true">⚛ Icons & blocks</button>
|
||||
<button type="button" data-reference-mode="chapters" aria-selected="false">✦ Two chapters</button>
|
||||
<button type="button" data-reference-mode="pdfs" aria-selected="false">📄 All PDFs only</button>
|
||||
</div>
|
||||
<label class="filter-mode-toggle" title="Toggle out-filtered items between dim and hide">
|
||||
<input type="checkbox" id="filterDimToggle" checked>
|
||||
<span aria-hidden="true">☁</span>
|
||||
<span data-filter-mode-label>dim</span>
|
||||
</label>
|
||||
<div class="filter-status" id="filterStatus" role="status">All · dim</div>
|
||||
<div class="category-legend" aria-label="Category totals">
|
||||
<?php foreach ($categoryCounts as $category => $count): ?>
|
||||
<span class="legend-pill link-kind-<?= htmlspecialchars($category) ?>"><b><?= htmlspecialchars(ref_label($category)) ?></b> <?= (int)$count ?></span>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="curated-index" class="sections-grid blocks reference-mode-panel is-active" data-mode-panel="icons" data-section-grid aria-label="Curated source-owned icon groups">
|
||||
<?php foreach ($sections as $section): ?>
|
||||
<?php $sectionLinks = array_values(array_filter($links, fn($link) => $link["section"] === $section["id"])); $layout = $sectionLayout[$section["id"]] ?? null; ?>
|
||||
<article class="section-card reference-card" id="<?= htmlspecialchars($section["id"]) ?>" data-section-card data-title="<?= htmlspecialchars(strtolower($section["title"] . " " . ($layout["zone"] ?? "") . " " . ($layout["note"] ?? ""))) ?>" data-chapter="<?= htmlspecialchars($section["chapter"]) ?>" data-tone="<?= htmlspecialchars((string)($section["tone"] ?? "neutral")) ?>">
|
||||
<div class="section-head">
|
||||
<div class="section-icon" aria-hidden="true"><?= htmlspecialchars($section["icon"]) ?></div>
|
||||
<div>
|
||||
<div class="section-chapter"><?= htmlspecialchars($section["chapter"]) ?></div>
|
||||
<h2><?= htmlspecialchars($section["title"]) ?></h2>
|
||||
<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>
|
||||
<?php if ($layout): ?>
|
||||
<div class="layout-note">
|
||||
<b><?= htmlspecialchars($layout["zone"]) ?></b>
|
||||
<span><?= htmlspecialchars($layout["computed"]) ?></span>
|
||||
<small><?= htmlspecialchars($layout["parent"]) ?> · <?= htmlspecialchars($layout["position"]) ?></small>
|
||||
<em><?= htmlspecialchars($layout["note"]) ?></em>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<button class="manual-highlight" type="button" data-highlight-target="<?= htmlspecialchars($section["id"]) ?>">✨ Pin box</button>
|
||||
<div class="file-list">
|
||||
<?php if ($sectionLinks): foreach ($sectionLinks as $link): ?>
|
||||
<?php $category = ref_category($link); $filename = ref_filename($link); ?>
|
||||
<a class="file-row link-kind-<?= htmlspecialchars($category) ?>" href="<?= htmlspecialchars($link["href"]) ?>" target="_blank" rel="noopener" data-link-row data-owner-target="<?= htmlspecialchars($section["id"]) ?>" data-kind="<?= htmlspecialchars($category) ?>" data-search="<?= htmlspecialchars(strtolower($link["name"] . " " . $link["summary"] . " " . $link["kind"] . " " . $category . " " . $filename . " " . $section["title"] . " " . ($layout["zone"] ?? ""))) ?>">
|
||||
<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(ref_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; else: ?>
|
||||
<div class="empty-state">No branch links are assigned to this owner in CSS-aware mode. If the icon is Coffee, following links are utility chrome; if it is LaeLab, use live source text as context.</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</article>
|
||||
<?php endforeach; ?>
|
||||
</section>
|
||||
|
||||
<section id="chapter-index" class="chapter-reference reference-mode-panel" data-mode-panel="chapters" aria-label="Two main chapter link groups" hidden>
|
||||
<?php foreach (($chapterGroups ?? []) as $chapter): ?>
|
||||
<?php $chapterLinks = links_for_sections($links, $chapter["sections"]); $mainLinks = array_values(array_filter($chapterLinks, fn($link) => ref_category($link) === "pdf")); $branchLinks = array_values(array_filter($chapterLinks, fn($link) => ref_category($link) !== "pdf")); ?>
|
||||
<article class="chapter-reference-card" id="<?= htmlspecialchars($chapter["id"]) ?>" data-section-card data-title="<?= htmlspecialchars(strtolower($chapter["title"] . " " . $chapter["summary"])) ?>" data-chapter="<?= htmlspecialchars($chapter["title"]) ?>" data-tone="chapter">
|
||||
<div class="chapter-reference-head">
|
||||
<span class="chapter-reference-icon" aria-hidden="true"><?= htmlspecialchars($chapter["icon"]) ?></span>
|
||||
<div>
|
||||
<p class="eyebrow">Main title gate · <?= htmlspecialchars($chapter["computed"]) ?></p>
|
||||
<h2><?= htmlspecialchars($chapter["title"]) ?></h2>
|
||||
<p><?= htmlspecialchars($chapter["summary"]) ?></p>
|
||||
<button class="manual-highlight chapter-pin" type="button" data-highlight-target="<?= htmlspecialchars($chapter["id"]) ?>">✨ Pin chapter</button>
|
||||
</div>
|
||||
</div>
|
||||
<a class="chapter-trigger link-kind-presentation" href="<?= htmlspecialchars($chapter["trigger"]["href"]) ?>" target="_blank" rel="noopener" data-link-row data-owner-target="<?= htmlspecialchars($chapter["id"]) ?>" data-kind="presentation" data-search="<?= htmlspecialchars(strtolower($chapter["title"] . " " . $chapter["trigger"]["name"] . " " . $chapter["trigger"]["summary"])) ?>">
|
||||
<span><?= htmlspecialchars($chapter["trigger"]["icon"]) ?></span>
|
||||
<b><?= htmlspecialchars($chapter["trigger"]["name"]) ?></b>
|
||||
<small><?= htmlspecialchars($chapter["trigger"]["summary"]) ?></small>
|
||||
</a>
|
||||
<div class="chapter-section-pills" aria-label="Sections under <?= htmlspecialchars($chapter["title"]) ?>">
|
||||
<?php foreach ($chapter["sections"] as $sid): $s = section_by_id($sections, $sid); if (!$s) continue; ?>
|
||||
<a href="#<?= htmlspecialchars($sid) ?>" data-highlight-target="<?= htmlspecialchars($sid) ?>"><span><?= htmlspecialchars($s["icon"]) ?></span><?= htmlspecialchars($s["title"]) ?></a>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<div class="branch-columns">
|
||||
<div>
|
||||
<h3>📄 Main PDF block</h3>
|
||||
<div class="branch-link-list">
|
||||
<?php foreach ($mainLinks as $link): $filename = ref_filename($link); ?>
|
||||
<a class="branch-row branch-main link-kind-pdf" href="<?= htmlspecialchars($link["href"]) ?>" target="_blank" rel="noopener" data-link-row data-owner-target="<?= htmlspecialchars($link["section"]) ?>" data-kind="pdf" data-search="<?= htmlspecialchars(strtolower($chapter["title"] . " " . $link["name"] . " " . $link["summary"] . " " . $filename)) ?>"><span><?= htmlspecialchars($link["icon"]) ?></span><b><?= htmlspecialchars($link["name"]) ?></b><small><?= htmlspecialchars($filename) ?></small></a>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h3>Branch links / supporting systems</h3>
|
||||
<div class="branch-link-list">
|
||||
<?php if ($branchLinks): foreach ($branchLinks as $link): $category = ref_category($link); ?>
|
||||
<a class="branch-row link-kind-<?= htmlspecialchars($category) ?>" href="<?= htmlspecialchars($link["href"]) ?>" target="_blank" rel="noopener" data-link-row data-owner-target="<?= htmlspecialchars($link["section"]) ?>" data-kind="<?= htmlspecialchars($category) ?>" data-search="<?= htmlspecialchars(strtolower($chapter["title"] . " " . $link["name"] . " " . $link["summary"] . " " . $category)) ?>"><span><?= htmlspecialchars($link["icon"]) ?></span><b><?= htmlspecialchars($link["name"]) ?></b><small><?= htmlspecialchars(ref_label($category)) ?> · <?= htmlspecialchars($link["summary"]) ?></small></a>
|
||||
<?php endforeach; else: ?>
|
||||
<span class="empty-state">No supporting branch links in this chapter group.</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
<?php endforeach; ?>
|
||||
</section>
|
||||
|
||||
<section id="pdf-only-index" class="pdf-only-panel reference-mode-panel reference-card" data-mode-panel="pdfs" aria-labelledby="pdf-only-title" hidden>
|
||||
<div class="pdf-only-head">
|
||||
<p class="eyebrow">📄 PDF icon mode</p>
|
||||
<h2 id="pdf-only-title">All PDFs together, and only PDFs.</h2>
|
||||
<p>This mode ignores applets, repositories, datasets, media, counters and utility chrome. It is the final separation for the PDF-only icon path.</p>
|
||||
</div>
|
||||
<div class="pdf-grid" aria-label="PDF-only links">
|
||||
<?php foreach ($pdfLinks as $pdf): $filename = ref_filename($pdf); ?>
|
||||
<a class="pdf-card link-kind-pdf" href="<?= htmlspecialchars($pdf["href"]) ?>" target="_blank" rel="noopener" data-pdf-card data-link-row data-owner-target="<?= htmlspecialchars($pdf["section"]) ?>" data-kind="pdf" data-search="<?= htmlspecialchars(strtolower($pdf["name"] . " " . $pdf["summary"] . " " . $filename)) ?>">
|
||||
<span class="pdf-symbol" aria-hidden="true">📄</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 PDF ↗</span>
|
||||
</a>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="utility-chrome" class="utility-chrome-panel reference-card" aria-labelledby="utility-title">
|
||||
<div>
|
||||
<p class="eyebrow">Separated utility chrome</p>
|
||||
<h2 id="utility-title">After Coffee: footer/download/RSS/lander links are not Coffee-owned branches.</h2>
|
||||
<p>These links still exist in the source, but CSS/JS places some of them as fixed or absolute controls. They are listed here only as utility chrome.</p>
|
||||
</div>
|
||||
<div class="utility-link-grid">
|
||||
<?php foreach (($utilityLinks ?? []) as $link): $category = ref_category($link); ?>
|
||||
<a class="utility-row link-kind-<?= htmlspecialchars($category) ?>" href="<?= htmlspecialchars($link["href"]) ?>" target="_blank" rel="noopener" data-owner-target="utility-chrome"><span><?= htmlspecialchars($link["icon"]) ?></span><b><?= htmlspecialchars($link["name"]) ?></b><small><?= htmlspecialchars(ref_label($category)) ?> · <?= htmlspecialchars($link["summary"]) ?></small></a>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="live-source-map" class="audit-panel source-map-panel" aria-labelledby="live-source-title">
|
||||
<div>
|
||||
<p class="eyebrow">Live precision scanner</p>
|
||||
<h2 id="live-source-title">Source ownership map from spireason.neocities.org</h2>
|
||||
<p>This scanner now reports the same CSS-aware separation: fixed/absolute chrome is not Coffee ownership, and every link keeps its source-order block context when possible.</p>
|
||||
</div>
|
||||
<button class="btn btn-dark btn-sm" id="loadStructureBottom" type="button">Load live source ownership</button>
|
||||
<div class="audit-status" id="sourceStructureStatus" role="status">Not loaded yet.</div>
|
||||
<div id="sourceStructure" class="source-structure" data-source-structure></div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer class="site-footer">
|
||||
<span>Runtime: PHP <?= htmlspecialchars(PHP_VERSION) ?></span>
|
||||
<span>Updated <?= htmlspecialchars($now) ?> UTC</span>
|
||||
<a href="api/scan_structure.php">Source structure 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>
|
||||
4
healthz.php
Normal file
4
healthz.php
Normal file
@ -0,0 +1,4 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
echo json_encode(['ok' => true, 'time' => gmdate('c'), 'php' => PHP_VERSION]);
|
||||
206
includes/curated_data.php
Normal file
206
includes/curated_data.php
Normal file
@ -0,0 +1,206 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
$sourceOwnershipNote = 'CSS-aware source model: the public page starts with an absolute 100vh console panel, fixed button systems around it, then an absolute document flow at top:100%. Inside that flow, main chapter titles and large icon anchors own branch blocks until the next meaningful title/icon. Coffee is terminal prose; footer, RSS, scroll, archive and lander controls are utility chrome, not Coffee branch links.';
|
||||
|
||||
$sections = [
|
||||
['id'=>'shadow-books','icon'=>'🌑','title'=>'Books of Shadows','summary'=>'Bonus prelude before the standard icon sequence: Dancing Shadows, Book of Shadows 2, and Bulk Load Book of the Dead 3.','chapter'=>'Console → Shadow Prelude','tone'=>'shadow','source_count'=>3,'source_until'=>'#growing-out-of-the-shadows'],
|
||||
['id'=>'growing-out-of-the-shadows','icon'=>'⚛️','title'=>'Growing Out of the Shadows','summary'=>'Archetype, shadow, Wu Wei, AI narrative, illumination games, repositories, and the large shadow-story prelude before the counter material.','chapter'=>'Console → Document','tone'=>'atomic','source_count'=>42,'source_until'=>'#sheep'],
|
||||
['id'=>'sheep','icon'=>'🐑','title'=>'Sheep / Laegna Counters','summary'=>'Laegna number-learning area: sheep counters, logecs counter, octave counter, manuals, scripts, JSON/databases, coordinate visualizers, and counterspace introductions. No video-player links belong here.','chapter'=>'Document · counters','tone'=>'pastoral','source_count'=>32,'source_until'=>'#infinity'],
|
||||
['id'=>'infinity','icon'=>'♾️','title'=>'Infinity','summary'=>'Infinity portals, Simply about Infinities, Laegna manual, theorem repositories, physical infinity simulations, count/matter, proponential and infinity runtime surfaces.','chapter'=>'✦ Foundational Texts','tone'=>'infinite','source_count'=>23,'source_until'=>'#natura'],
|
||||
['id'=>'natura','icon'=>'🌀','title'=>'Natura','summary'=>'Compact nature/war/polarity opening: Holy War and the positive/negative text.','chapter'=>'✦ Foundational Texts','tone'=>'spiral','source_count'=>2,'source_until'=>'#sunrise'],
|
||||
['id'=>'sunrise','icon'=>'🔆','title'=>'Sunrise','summary'=>'Material Magic and practical-spiritual PDFs: imperfection, karma, star children, material truth, Buddhism/Christianity comparison, tuning, meditation polarity, conflict psychology, and an overview of the two parts.','chapter'=>'✦ Foundational Texts','tone'=>'solar','source_count'=>19,'source_until'=>'#bigbang'],
|
||||
['id'=>'bigbang','icon'=>'💥','title'=>'Bigbang','summary'=>'Spireason starting pair: Karma as Spireason 1, its add-on, and Evolution as Spireason 2.','chapter'=>'⟲ Continuing Studies','tone'=>'burst','source_count'=>3,'source_until'=>'#yggdrasill'],
|
||||
['id'=>'yggdrasill','icon'=>'🌳','title'=>'Yggdrasill','summary'=>'Tree-like continuation through meditation, truth tables, reincarnation, root chakra, material meditation, SOMA, magic as mental image, and karma/rationality notes.','chapter'=>'⟲ Continuing Studies','tone'=>'tree','source_count'=>10,'source_until'=>'#spiritrise'],
|
||||
['id'=>'spiritrise','icon'=>'🔷','title'=>'Spiritrise','summary'=>'Chakra frequency, metaphysical/lifelike structures, Logecs and E factor, social archetype, spiritual/material logecs, skepticism/science, and a collaboration presentation.','chapter'=>'⟲ Continuing Studies','tone'=>'crystal','source_count'=>8,'source_until'=>'#laelab'],
|
||||
['id'=>'laelab','icon'=>'🔬','title'=>'LaeLab','summary'=>'Conceptual lab text in the source: Laegna theory, lived experience, infinity, mathematics, logec, AI modelling, and resonant source frameworks.','chapter'=>'⟲ Continuing Studies','tone'=>'lab','source_count'=>0,'source_until'=>'#geneticar'],
|
||||
['id'=>'geneticar','icon'=>'🧬','title'=>'Geneticar','summary'=>'AI-native Laegna modelling and visualizations: atomic, molecular, and genetic surfaces.','chapter'=>'⟲ Continuing Studies','tone'=>'helix','source_count'=>4,'source_until'=>'#handheldcal'],
|
||||
['id'=>'handheldcal','icon'=>'🖩','title'=>'HandheldCal / Resonant Calculus','summary'=>'Laegna math introduction, Exponometer concept, Frequential Calculator, Octavial Calculator, and a relativity simplification branch.','chapter'=>'Tools','tone'=>'tool','source_count'=>5,'source_until'=>'#puzzled'],
|
||||
['id'=>'puzzled','icon'=>'🧩','title'=>'Puzzled / Source Texts','summary'=>'GitHub root and formal source repositories behind Laegna AI, calculators, integrals, and LaeMath.','chapter'=>'Document · source','tone'=>'puzzle','source_count'=>4,'source_until'=>'#chakra'],
|
||||
['id'=>'chakra','icon'=>'✴️','title'=>'Chakra','summary'=>'SpiRe primitives, Trinity, frequency/body/plane, enlightenment, infinity boundaries, infinite circle, exterior, and infinities playground.','chapter'=>'✦ Foundational Texts','tone'=>'center','source_count'=>8,'source_until'=>'#wheelsgoround'],
|
||||
['id'=>'wheelsgoround','icon'=>'⚙️','title'=>'Wheels Go Round','summary'=>'Open-source and cloneable calculator backbone: OS page, frequency/harmonics calculator, and Octave/Exponometer repository.','chapter'=>'⟲ Continuing Studies','tone'=>'gear','source_count'=>3,'source_until'=>'#laemedics'],
|
||||
['id'=>'laemedics','icon'=>'🧘','title'=>'LaeMedics / SpiBody','summary'=>'SpiBody/SpiZenTao health and body-research anchors, continuation jump, lettering for spheres, and Kybalion reference.','chapter'=>'✦ Foundational Texts','tone'=>'calm','source_count'=>5,'source_until'=>'#coffeeandcigarettes'],
|
||||
['id'=>'coffeeandcigarettes','icon'=>'☕','title'=>'Coffee and Cigarettes / Terminal Notes','summary'=>'Terminal prose and reading guidance. The visual source has no Coffee-owned branch links; footer/archive/RSS/lander/scroll controls after it are utility chrome, not part of this owner.','chapter'=>'Terminal prose','tone'=>'archive','source_count'=>0,'source_until'=>'utility chrome'],
|
||||
];
|
||||
|
||||
$links = [
|
||||
['section'=>'shadow-books','icon'=>'🎭','name'=>'Dancing Shadows · Material Psychic Game','href'=>'https://material-psychic-gam-8mo3.bolt.host/','kind'=>'applet','summary'=>'Interactive archetype/shadow game from the pre-icon shadow prelude.'],
|
||||
['section'=>'shadow-books','icon'=>'📘','name'=>'Book of Shadows 2','href'=>'https://app-bxfrqbbqegap.appmedo.com/','kind'=>'applet','summary'=>'Four-history symbolic frame: childhood unity, frozen roles, satire, illumination.'],
|
||||
['section'=>'shadow-books','icon'=>'📕','name'=>'Bulk Load Book of the Dead 3','href'=>'https://app-by9gm7mu9ssh.appmedo.com/','kind'=>'applet','summary'=>'Dark glowing index of conspiracy, matter, murder, and illumination narratives.'],
|
||||
['section'=>'growing-out-of-the-shadows','icon'=>'⚛️','name'=>'Tales and Stories of Me and an AI','href'=>'https://github.com/tambetvali/TalesAndStoriesOfMeAndAnAI/','kind'=>'github','summary'=>'Root repository for the AI/narrative shadow-story branch.'],
|
||||
['section'=>'growing-out-of-the-shadows','icon'=>'🧠','name'=>'Tales Illuminated','href'=>'https://tales-illuminated.lovable.app/','kind'=>'applet','summary'=>'Simplified interactive illumination version of the shadow-story material.'],
|
||||
['section'=>'growing-out-of-the-shadows','icon'=>'🕯️','name'=>'Simulations of Illumination','href'=>'https://github.com/tambetvali/IlluminationSimulatorCapable','kind'=>'github','summary'=>'Repository for illumination simulator material.'],
|
||||
['section'=>'wheelsgoround','icon'=>'⚙️','name'=>'Contribute','href'=>'https://spireason.neocities.org/repos.html','kind'=>'branch','summary'=>'Repository and contribution entry point for the network.'],
|
||||
['section'=>'sunrise','icon'=>'Study','name'=>'Laegna Study','href'=>'https://laegna.notaku.site/','kind'=>'main branch','summary'=>'Central Notaku study manual branch.'],
|
||||
['section'=>'puzzled','icon'=>'?','name'=>'Prezi intermediate introduction','href'=>'https://prezi.com/view/8jmEfNRocsOrwNAP8ejz/?referral_token=G4RyDElnB3FN','kind'=>'presentation','summary'=>'Question-mark explanatory bridge.'],
|
||||
['section'=>'yggdrasill','icon'=>'Chat','name'=>'Bots','href'=>'https://spireason.neocities.org/Additional/bots.html','kind'=>'bot','summary'=>'Chat and bot collection; categorized separately from source/PDF resources.'],
|
||||
['section'=>'laelab','icon'=>'Find','name'=>'Perplexity Space','href'=>'https://www.perplexity.ai/spaces/laegna-spireason-and-related-m-dLTaZz8CSd2FUCjIiYQARA','kind'=>'research','summary'=>'External research/search space for the knowledge network.'],
|
||||
['section'=>'wheelsgoround','icon'=>'Apps','name'=>'Apps','href'=>'https://spireason.neocities.org/apples.html','kind'=>'applet','summary'=>'Application branch list.'],
|
||||
['section'=>'handheldcal','icon'=>'🎶','name'=>'Frequency Calculator','href'=>'https://3oumxxajlfzw2.mocha.app/','kind'=>'tool','summary'=>'Harmonics/frequential calculator app; essential numeric Laegna tool.'],
|
||||
['section'=>'handheldcal','icon'=>'📈','name'=>'Octavial / Exponometer Calculator','href'=>'https://accelerative-complex-6gs6.bolt.host/','kind'=>'tool','summary'=>'Source-listed octavial calculator / Exponometer surface.'],
|
||||
['section'=>'handheldcal','icon'=>'📈','name'=>'Octave Counter','href'=>'https://spireason.neocities.org/Playground/OctaveCounter/','kind'=>'tool','summary'=>'On-site Laegna octave counter and exponometer path.'],
|
||||
['section'=>'sunrise','icon'=>'➔','name'=>'Introduce','href'=>'https://prezi.com/view/JHeiwaeN6qqPG3h5RMRd/?referral_token=G4RyDElnB3FN','kind'=>'presentation','summary'=>'Introductory presentation path.'],
|
||||
['section'=>'wheelsgoround','icon'=>'📦','name'=>'PDFs.zip','href'=>'https://huggingface.co/datasets/tvaeli/LaegnaSpi/resolve/main/Spireason.zip','kind'=>'archive','summary'=>'Critical downloadable archive of PDFs.'],
|
||||
['section'=>'coffeeandcigarettes','icon'=>'🎬','name'=>'Top Video Selection','href'=>'https://assorted-canopy-961.notion.site/Laegna-1a575bfc115480a38129e9a9787ab565','kind'=>'media','summary'=>'Curated video landing branch, intentionally outside Sheep.'],
|
||||
['section'=>'coffeeandcigarettes','icon'=>'▶️','name'=>'YouTube Channel','href'=>'https://www.youtube.com/@TambetV%C3%A4li','kind'=>'media','summary'=>'Main YouTube channel, intentionally outside Sheep.'],
|
||||
|
||||
['section'=>'sheep','icon'=>'🖼️','name'=>'Sheep Intro','href'=>'https://laegna-wave-decoder.lovable.app/','kind'=>'applet','summary'=>'Introductory visual decoder for the sheep/counter learning area.'],
|
||||
['section'=>'sheep','icon'=>'🐑','name'=>'Laegna Sheep Counter ❶','href'=>'https://spireason.neocities.org/Playground/SheepCounter1/sheepiotae.html','kind'=>'tool','summary'=>'Ordinary/logecs math sheep counter for learning Laegna numbers by counting.'],
|
||||
['section'=>'sheep','icon'=>'📜','name'=>'Sheep Counter Manual','href'=>'https://spireason.neocities.org/Playground/SheepCounter1/manual.html','kind'=>'manual','summary'=>'Manual for the first sheep counter.'],
|
||||
['section'=>'sheep','icon'=>'🐑🐑','name'=>'Laegna Logecs Counter ❷','href'=>'https://spireason.neocities.org/Playground/SheepCounter1/sheepiotae1.html','kind'=>'tool','summary'=>'Companion counter for logical structures of Laegna systems.'],
|
||||
['section'=>'sheep','icon'=>'📖','name'=>'Number System Manual','href'=>'https://spireason.neocities.org/Playground/SheepCounter1/manual','kind'=>'manual','summary'=>'Number-system manual linked from the sheep section.'],
|
||||
['section'=>'sheep','icon'=>'📁','name'=>'SheepCounter Folder','href'=>'https://spireason.neocities.org/Playground/SheepCounter1/','kind'=>'source','summary'=>'Open folder for the sheep counter resources.'],
|
||||
['section'=>'sheep','icon'=>'📜','name'=>'sheepdebug script','href'=>'https://spireason.neocities.org/Playground/SheepCounter1/sheepdebug.html','kind'=>'source','summary'=>'Visible script/debug timeline for the sheep counter.'],
|
||||
['section'=>'sheep','icon'=>'🖥️','name'=>'sheepdebug human table','href'=>'https://spireason.neocities.org/Playground/SheepCounter1/sheepdebug','kind'=>'table','summary'=>'Human number table/debug view.'],
|
||||
['section'=>'sheep','icon'=>'💻','name'=>'sheepcounter.json','href'=>'https://spireason.neocities.org/Playground/SheepCounter1/sheepcounter.json','kind'=>'dataset','summary'=>'AI/machine-readable sheep counter dataset.'],
|
||||
['section'=>'sheep','icon'=>'🧬','name'=>'sheepjason generator','href'=>'https://spireason.neocities.org/Playground/SheepCounter1/sheepjason.html','kind'=>'source','summary'=>'Number data generator used by the counter.'],
|
||||
['section'=>'sheep','icon'=>'🐑','name'=>'Laegna Sheep Counter ❸','href'=>'https://spireason.neocities.org/Playground/InferenceCounter/PlaceholderResearchitem/','kind'=>'tool','summary'=>'Mathematecs/higher-math counter branch.'],
|
||||
['section'=>'sheep','icon'=>'📜','name'=>'Inference counter manual','href'=>'https://spireason.neocities.org/Playground/InferenceCounter/PlaceholderResearchitem/manual.html','kind'=>'manual','summary'=>'Manual for the higher-math inference counter.'],
|
||||
['section'=>'sheep','icon'=>'⟣','name'=>'Waveweaver Tails and Bones','href'=>'https://spireason.neocities.org/Playground/InferenceCounter/Trailer/tailandbones','kind'=>'applet','summary'=>'Tails-and-bones visualization under the inference counter branch.'],
|
||||
['section'=>'sheep','icon'=>'⟢','name'=>'Dragontail visualization','href'=>'https://spireason.neocities.org/Playground/InferenceCounter/dragontail.html','kind'=>'applet','summary'=>'Coordinate/dragontail visualization.'],
|
||||
['section'=>'sheep','icon'=>'▧','name'=>'Angry birds wrap the coordinates','href'=>'https://spireason.neocities.org/Playground/InferenceCounter/angryspacebirdsbirdswwrap.html','kind'=>'applet','summary'=>'Coordinate wrapping visualizer.'],
|
||||
['section'=>'sheep','icon'=>'▤','name'=>'Flat List','href'=>'https://spireason.neocities.org/Playground/InferenceCounter/numberdatabase/flatlist/','kind'=>'dataset','summary'=>'Flat number database list.'],
|
||||
['section'=>'sheep','icon'=>'{}','name'=>'JSON List','href'=>'https://spireason.neocities.org/Playground/InferenceCounter/numberdatabase/complete.json','kind'=>'dataset','summary'=>'Complete JSON number database.'],
|
||||
['section'=>'sheep','icon'=>'🐑🐑','name'=>'Laegna Octave Counter','href'=>'https://spireason.neocities.org/Playground/OctaveCounter/','kind'=>'tool','summary'=>'Octave counter under the sheep learning area.'],
|
||||
['section'=>'sheep','icon'=>'⚖️','name'=>'Octave strict decoder','href'=>'https://spireason.neocities.org/Playground/OctaveCounter/decoder.html','kind'=>'tool','summary'=>'Strict octave decoder.'],
|
||||
['section'=>'sheep','icon'=>'📘','name'=>'Octave formal manual','href'=>'https://spireason.neocities.org/Playground/OctaveCounter/manual.html','kind'=>'manual','summary'=>'Formal manual for the octave counter.'],
|
||||
['section'=>'sheep','icon'=>'🔷','name'=>'Octave spiritual manual','href'=>'https://spireason.neocities.org/Playground/OctaveCounter/spirit.html','kind'=>'manual','summary'=>'Spiritual reading of the octave counter.'],
|
||||
['section'=>'sheep','icon'=>'🐍','name'=>'Octave decoder.py','href'=>'https://spireason.neocities.org/Playground/OctaveCounter/decoder.py','kind'=>'source','summary'=>'Visible Python decoder source.'],
|
||||
['section'=>'sheep','icon'=>'{}','name'=>'octaves.json','href'=>'https://spireason.neocities.org/Playground/OctaveCounter/octaves.json','kind'=>'dataset','summary'=>'Octave JSON database.'],
|
||||
['section'=>'sheep','icon'=>'📁','name'=>'InferenceCounter Folder','href'=>'https://spireason.neocities.org/Playground/InferenceCounter/','kind'=>'source','summary'=>'Open folder for inference counter resources.'],
|
||||
['section'=>'sheep','icon'=>'📜','name'=>'Inference script/user manual','href'=>'https://spireason.neocities.org/Playground/InferenceCounter/userman.html','kind'=>'manual','summary'=>'User manual/script for inference counter.'],
|
||||
['section'=>'sheep','icon'=>'📺','name'=>'Number database view','href'=>'https://spireason.neocities.org/Playground/InferenceCounter/numberdatum.html','kind'=>'table','summary'=>'Human-readable number database view.'],
|
||||
['section'=>'sheep','icon'=>'🖥️','name'=>'numbers.bin.json','href'=>'https://spireason.neocities.org/Playground/InferenceCounter/numbers.bin.json','kind'=>'dataset','summary'=>'Base-2 custom JSON number data.'],
|
||||
['section'=>'sheep','icon'=>'💻','name'=>'animation.json','href'=>'https://spireason.neocities.org/Playground/InferenceCounter/animation.json','kind'=>'dataset','summary'=>'Base-4 compiled animation JSON.'],
|
||||
['section'=>'sheep','icon'=>'🧬','name'=>'animatorjson generator','href'=>'https://spireason.neocities.org/Playground/InferenceCounter/animatorjson.html','kind'=>'source','summary'=>'Generator from genetic.py to numbers.json.'],
|
||||
['section'=>'sheep','icon'=>'☯','name'=>'Laegna Counterspaces Hologram','href'=>'https://spireason.neocities.org/counterspaces.html','kind'=>'applet','summary'=>'Counterspaces hologram introduction.'],
|
||||
['section'=>'sheep','icon'=>'🌌','name'=>'Grand Introduction','href'=>'https://materialmagic.floot.app/','kind'=>'applet','summary'=>'Grand introduction linked from the sheep/counterspace run.'],
|
||||
['section'=>'sheep','icon'=>'🌈','name'=>'The Unified Spectrum','href'=>'https://coreweave-cosmos.lovable.app/','kind'=>'applet','summary'=>'Unified spectrum continuation link.'],
|
||||
|
||||
['section'=>'infinity','icon'=>'♾️','name'=>'Simply about Infinities','href'=>'https://spireason.neocities.org/Simply%20about%20Infinities.pdf','kind'=>'pdf','summary'=>'Core infinity document.'],
|
||||
['section'=>'infinity','icon'=>'📚','name'=>'Laegna Manual','href'=>'https://laegna.notaku.site/#1ac75bfc1154809b8037da3fbfaaf000','kind'=>'manual','summary'=>'Manual entry for infinity and related theory.'],
|
||||
['section'=>'infinity','icon'=>'📙','name'=>'Science Study','href'=>'https://github.com/tambetvali/LaeSpiEssentialTheorems/tree/main/LaegnaTheorems/InfinityAndZero','kind'=>'github','summary'=>'Infinity and zero theorem source material.'],
|
||||
['section'=>'infinity','icon'=>'🟪','name'=>'Infinities are Logecs','href'=>'https://github.com/tambetvali/FuzzyLogecs/','kind'=>'github','summary'=>'Logecs and infinity source branch.'],
|
||||
['section'=>'infinity','icon'=>'♈','name'=>'Count and Matter','href'=>'https://laegnanumbers.freebuff.app/','kind'=>'applet','summary'=>'Source-listed count/matter surface.'],
|
||||
|
||||
['section'=>'natura','icon'=>'⚔️','name'=>'Holy War','href'=>'https://spireason.neocities.org/Holy%20War.pdf','kind'=>'pdf','summary'=>'Foundational spiritual conflict document.'],
|
||||
['section'=>'natura','icon'=>'☯️','name'=>'Short Text about Positive and Negative','href'=>'https://spireason.neocities.org/Short%20Text%20about%20what%20I%20say%20about%20Positive%20and%20Negative.pdf','kind'=>'pdf','summary'=>'Positive/negative conceptual frame.'],
|
||||
|
||||
['section'=>'sunrise','icon'=>'✨','name'=>'Principles of Material Magic','href'=>'https://spireason.neocities.org/Principles%20of%20Material%20Magic.pdf','kind'=>'pdf','summary'=>'Material magic foundation document.'],
|
||||
['section'=>'sunrise','icon'=>'🌸','name'=>'Principles of Material Magic – short version','href'=>'https://spireason.neocities.org/Principles%20of%20Material%20Magic%20%E2%80%93%20short%20version.pdf','kind'=>'pdf','summary'=>'Compact companion version.'],
|
||||
['section'=>'sunrise','icon'=>'⚠️','name'=>'The Power of Negative Thinking','href'=>'https://spireason.neocities.org/The%20Power%20of%20Negative%20Thinking.pdf','kind'=>'pdf','summary'=>'Critical karma/logecs document.'],
|
||||
['section'=>'sunrise','icon'=>'⚖️','name'=>'The Law of Imperfection and Karma','href'=>'https://spireason.neocities.org/The%20Law%20of%20Imperfection%20and%20Karma.pdf','kind'=>'pdf','summary'=>'Karma and imperfection study.'],
|
||||
['section'=>'sunrise','icon'=>'⭐','name'=>'One Short Theory about Star Children','href'=>'https://spireason.neocities.org/One%20Short%20Theory%20about%20Star%20Children.pdf','kind'=>'pdf','summary'=>'Spiritual child/star theory text.'],
|
||||
['section'=>'sunrise','icon'=>'📜','name'=>'The Material Laws of Truth','href'=>'https://spireason.neocities.org/The%20Material%20Laws%20of%20Truth.pdf','kind'=>'pdf','summary'=>'Truth and material law document.'],
|
||||
['section'=>'sunrise','icon'=>'📖','name'=>'Comparison of, mostly, Buddhism and Christianity','href'=>'https://spireason.neocities.org/Comparison%20of,%20mostly,%20Buddhism%20and%20Christianity.pdf','kind'=>'pdf','summary'=>'Comparative religious/spiritual text.'],
|
||||
['section'=>'sunrise','icon'=>'📡','name'=>'Tuning into Material Reality','href'=>'https://spireason.neocities.org/Tuning%20into%20Material%20Reality.pdf','kind'=>'pdf','summary'=>'Applied reality-tuning document.'],
|
||||
['section'=>'sunrise','icon'=>'☯️','name'=>'Yin and Yang in Meditation','href'=>'https://spireason.neocities.org/Yin%20and%20Yang%20in%20Meditation.pdf','kind'=>'pdf','summary'=>'Meditation and polarity study.'],
|
||||
['section'=>'sunrise','icon'=>'💬','name'=>'Healthy Conflict Psychology','href'=>'https://spireason.neocities.org/Healthy%20Conflict%20Psychology.pdf','kind'=>'pdf','summary'=>'Practical psychology document.'],
|
||||
|
||||
['section'=>'bigbang','icon'=>'🌱','name'=>'Karma','href'=>'https://spireason.neocities.org/Karma.pdf','kind'=>'pdf','summary'=>'Spireason 1 / first core text.'],
|
||||
['section'=>'bigbang','icon'=>'📝','name'=>'Evolution','href'=>'https://spireason.neocities.org/Evolution.pdf','kind'=>'pdf','summary'=>'Spireason 2 / second core text.'],
|
||||
|
||||
['section'=>'yggdrasill','icon'=>'🧘','name'=>'Meditation and Position','href'=>'https://spireason.neocities.org/Meditation%20and%20Position.pdf','kind'=>'pdf','summary'=>'Body position and meditation.'],
|
||||
['section'=>'yggdrasill','icon'=>'⚛️','name'=>'Truth Value Tables, Matter and Mind','href'=>'https://spireason.neocities.org/Truth%20Value%20Tables,%20Matter%20and%20Mind.pdf','kind'=>'pdf','summary'=>'Logic/alchemistry table document.'],
|
||||
['section'=>'yggdrasill','icon'=>'♻️','name'=>'The Simpler Theorem of Karma','href'=>'https://spireason.neocities.org/The%20Simpler%20Theorem%20of%20Karma.pdf','kind'=>'pdf','summary'=>'Karma theorem simplification.'],
|
||||
['section'=>'yggdrasill','icon'=>'🔄','name'=>'Reincarnation Basics','href'=>'https://spireason.neocities.org/Reincarnation%20Basics.pdf','kind'=>'pdf','summary'=>'Reincarnation foundation notes.'],
|
||||
['section'=>'yggdrasill','icon'=>'🔴','name'=>'Material Alchemy and Root Chakra','href'=>'https://spireason.neocities.org/Material%20Alchemy%20and%20Root%20Chakra.pdf','kind'=>'pdf','summary'=>'Root chakra and material alchemy.'],
|
||||
['section'=>'yggdrasill','icon'=>'💭','name'=>'Karma and Material Meditation','href'=>'https://spireason.neocities.org/Karma%20and%20Material%20Meditation.pdf','kind'=>'pdf','summary'=>'Karma in meditative material frame.'],
|
||||
['section'=>'yggdrasill','icon'=>'🔮','name'=>'Magic as Mental Image','href'=>'https://spireason.neocities.org/Magic%20as%20Mental%20Image.pdf','kind'=>'pdf','summary'=>'Mental-image theory of magic.'],
|
||||
['section'=>'yggdrasill','icon'=>'╼▣╾','name'=>'SOMA: Enter into Material World','href'=>'https://soma-material-path.lovable.app/','kind'=>'applet','summary'=>'SOMA material-world applet from the tree section.'],
|
||||
|
||||
['section'=>'spiritrise','icon'=>'📶','name'=>'Frequential Analysis of System of Chakras in Buddhism','href'=>'https://spireason.neocities.org/Frequential%20Analysis%20of%20System%20of%20Chakras%20in%20Buddhism.pdf','kind'=>'pdf','summary'=>'Frequency/chakra bridge document.'],
|
||||
['section'=>'spiritrise','icon'=>'❓','name'=>'Logecs and E factor – what is bad in positive affirmations','href'=>'https://spireason.neocities.org/Logecs%20and%20E%20factor%20%E2%80%93%20what%20is%20bad%20in%20positive%20affirmations.pdf','kind'=>'pdf','summary'=>'Positive affirmations critique via logecs.'],
|
||||
['section'=>'spiritrise','icon'=>'📋','name'=>'Spiritual Logecs Cheat Sheet','href'=>'https://spireason.neocities.org/Additional/bizpon','kind'=>'table','summary'=>'Spiritual logecs/Posegation reference.'],
|
||||
['section'=>'spiritrise','icon'=>'📋','name'=>'Material Logecs','href'=>'https://spireason.neocities.org/Additional/Ponegatetables.pdf','kind'=>'table','summary'=>'Ponegation/material logic tables.'],
|
||||
['section'=>'spiritrise','icon'=>'🔬','name'=>'Skepticism and Science for Spiritual People','href'=>'https://spireason.neocities.org/Skepticism%20and%20Science%20for%20Spiritual%20People.pdf','kind'=>'pdf','summary'=>'Science bridge for spiritual readers.'],
|
||||
['section'=>'spiritrise','icon'=>'🌐','name'=>'Laegna and Spireason: be the Force','href'=>'https://docs.google.com/presentation/d/e/2PACX-1vSHZ-w9sdtsUNt4T73wTpPIRNSDALDZ_Xph5NLDIMhFSdOaKkCi2tMddQI8Yuh_gQ/pub?start=false&loop=false','kind'=>'presentation','summary'=>'Collaboration presentation from the source.'],
|
||||
|
||||
['section'=>'geneticar','icon'=>'🤖','name'=>'Laegna AI — Intro to Intelligent Modelling','href'=>'https://nexus-warp.lovable.app/','kind'=>'applet','summary'=>'AI-native introduction to intelligent modelling.'],
|
||||
['section'=>'geneticar','icon'=>'⚛️','name'=>'Atomic Visualization of AI','href'=>'https://chem-visions-ai.lovable.app/','kind'=>'applet','summary'=>'Atomic AI visualization.'],
|
||||
['section'=>'geneticar','icon'=>'⟐','name'=>'Molecular Visualization of AI','href'=>'https://mind-spark-visions.lovable.app/','kind'=>'applet','summary'=>'Molecular AI visualization.'],
|
||||
['section'=>'geneticar','icon'=>'🌄','name'=>'Genetic Visualization of AI','href'=>'https://ai-jungle-viz.lovable.app/','kind'=>'applet','summary'=>'Genetic AI visualization.'],
|
||||
|
||||
['section'=>'puzzled','icon'=>'∿','name'=>'Laegna Source Texts — GitHub Space','href'=>'https://github.com/tambetvali','kind'=>'github','summary'=>'Root GitHub space behind Laegna source texts.'],
|
||||
['section'=>'puzzled','icon'=>'🧬','name'=>'Basis for Laegna AI Visualization','href'=>'https://github.com/tambetvali/LaegnaAIHDvisualization','kind'=>'github','summary'=>'Source basis for Laegna AI visualization.'],
|
||||
['section'=>'puzzled','icon'=>'📘','name'=>'Basis for Laegna Calculators & Integrals','href'=>'https://github.com/tambetvali/LaeMath/blob/main/MathFuncs/Docs/README.md','kind'=>'github','summary'=>'Calculator/integral documentation.'],
|
||||
['section'=>'puzzled','icon'=>'⌂','name'=>'LaeMath home page','href'=>'https://github.com/tambetvali/LaeMath','kind'=>'github','summary'=>'LaeMath repository home.'],
|
||||
|
||||
['section'=>'chakra','icon'=>'🌀','name'=>'SpiRe Primitives','href'=>'https://spireason.neocities.org/elementchakra.html','kind'=>'onsite','summary'=>'Symbolic correspondences between elements, bodies, and philosophical systems.'],
|
||||
['section'=>'chakra','icon'=>'📘','name'=>'Frequency, Body and Plane','href'=>'https://spireason.neocities.org/freqbodyplane','kind'=>'onsite','summary'=>'Frequency/body/plane reference.'],
|
||||
['section'=>'chakra','icon'=>'✨','name'=>'Enlightenment Atlas','href'=>'https://spireason.neocities.org/enlightenment.html','kind'=>'onsite','summary'=>'Enlightenment atlas branch.'],
|
||||
['section'=>'chakra','icon'=>'⭕','name'=>'Infinite Circle','href'=>'https://github.com/tambetvali/Landscopes/blob/main/Landscapes/infinitecircle.md','kind'=>'github','summary'=>'Infinite circle landscape note.'],
|
||||
['section'=>'chakra','icon'=>'🌌','name'=>'Exterior','href'=>'https://github.com/tambetvali/Landscopes/blob/main/Landscapes/exterior.md','kind'=>'github','summary'=>'Exterior landscape note.'],
|
||||
|
||||
['section'=>'wheelsgoround','icon'=>'⚙️','name'=>'Laegna Calculators — Open Source & Cloneable','href'=>'https://spireason.neocities.org/os.html','kind'=>'onsite','summary'=>'Open-source calculator hub.'],
|
||||
['section'=>'wheelsgoround','icon'=>'🔢','name'=>'Frequency / Harmonics Calculator Source','href'=>'https://getmocha.com/apps/01995d5d-50b9-759c-8547-535df4800815','kind'=>'tool','summary'=>'Source-listed frequency/harmonics calculator.'],
|
||||
['section'=>'wheelsgoround','icon'=>'📈','name'=>'Octave / Exponometer Calculator Source','href'=>'https://github.com/tambetvali/exponometer.app','kind'=>'github','summary'=>'Open-source Octave/Exponometer calculator repository.'],
|
||||
|
||||
['section'=>'laemedics','icon'=>'🗂️','name'=>'Laegna SpiBody — Core Directory','href'=>'https://4ycacx8u7s.youware.app','kind'=>'applet','summary'=>'Core SpiBody directory and external anchor.'],
|
||||
['section'=>'laemedics','icon'=>'🌱','name'=>'SpiBody Unified Theory','href'=>'https://embeddable.live/embed/Utg4YGE0tV','kind'=>'applet','summary'=>'Central SpiBody site.'],
|
||||
['section'=>'laemedics','icon'=>'🌬️','name'=>'SpiZenTao','href'=>'https://eal22hc5nr.youware.app/','kind'=>'applet','summary'=>'SpiBody 2 trivia and intuitive insights.'],
|
||||
['section'=>'laemedics','icon'=>'🧭','name'=>'Continue basic lettering for spheres','href'=>'https://spireason.neocities.org/exceetaebox.html','kind'=>'onsite','summary'=>'Continuation for sphere lettering.'],
|
||||
['section'=>'laemedics','icon'=>'📜','name'=>'Kybalion','href'=>'https://archive.org/details/kybalionstudyofh00init','kind'=>'archive','summary'=>'Hermetic philosophy reference.'],
|
||||
|
||||
['section'=>'coffeeandcigarettes','icon'=>'🤝','name'=>'Open creations','href'=>'https://spireason.neocities.org/collaborate.html','kind'=>'onsite','summary'=>'Collaboration/creation branch.'],
|
||||
['section'=>'coffeeandcigarettes','icon'=>'📚','name'=>'Access Spireason + Laegna','href'=>'https://huggingface.co/datasets/tvaeli/LaegnaSpi/tree/main','kind'=>'archive','summary'=>'Dataset/archive home for Spireason + Laegna.'],
|
||||
['section'=>'coffeeandcigarettes','icon'=>'📚','name'=>'Mar 19 2025 Texts archive','href'=>'https://huggingface.co/datasets/tvaeli/LaegnaSpi/resolve/main/Spireason.zip','kind'=>'archive','summary'=>'Text archive download.'],
|
||||
['section'=>'coffeeandcigarettes','icon'=>'🗄️','name'=>'17.05.2026 Database archive','href'=>'https://github.com/tambetvali/SpireasonWebsite17052026/archive/refs/heads/main.zip','kind'=>'archive','summary'=>'Database/source archive.'],
|
||||
['section'=>'coffeeandcigarettes','icon'=>'RSS','name'=>'RSS','href'=>'https://spireason.neocities.org/atom.html','kind'=>'onsite','summary'=>'RSS feed.'],
|
||||
['section'=>'coffeeandcigarettes','icon'=>'🌟','name'=>'Lander1','href'=>'https://spireason.freebuff.app/','kind'=>'external','summary'=>'External landing branch.'],
|
||||
];
|
||||
|
||||
// Links that appear after the Coffee icon in source are footer/download/navigation chrome.
|
||||
// Keep them available for layout notes, but do not treat them as Coffee-owned branch links.
|
||||
$utilityLinks = array_values(array_filter($links, static fn(array $link): bool => ($link['section'] ?? '') === 'coffeeandcigarettes'));
|
||||
$links = array_values(array_filter($links, static fn(array $link): bool => ($link['section'] ?? '') !== 'coffeeandcigarettes'));
|
||||
|
||||
$layoutZones = [
|
||||
['id'=>'first-panel','icon'=>'▣','title'=>'First 100vh console panel','css'=>'root absolute panel: position:absolute; top:0; padding-left/right:120px','computed'=>'Starts y=0; roughly 38 visible anchors before the document flow.','summary'=>'The welcome/console area is its own positioned surface, not the same stream as the document icons. It contains center links and is overlaid by fixed side/top menus.'],
|
||||
['id'=>'fixed-chrome','icon'=>'⌖','title'=>'Fixed static buttons around the viewport','css'=>'fixed clusters: left square-buttons/local-buttons, right city-buttons, top-right menu; some inner links are relative/absolute','computed'=>'Left fixed x≈20, right fixed x≈1790, top-right fixed x≈1188 on a 1911px viewport.','summary'=>'These buttons are symbolic menu chrome. The left-side ordering follows chakra/yin-yang logic rather than source-flow ownership: base/head groups separated by yin/yang spacing.'],
|
||||
['id'=>'document-flow','icon'=>'↧','title'=>'Document flow after the console','css'=>'#spireason-anchor: position:absolute; top:100%; padding-left/right:120px','computed'=>'Document begins at y≈988; main icon/navigation row appears before the deep blocks.','summary'=>'This is the normal reading stream. Main title anchors and icon IDs belong here; most later PDF and branch links are direct descendants of blockquotes, articles, sections, or prose in this flow.'],
|
||||
['id'=>'chapter-titles','icon'=>'✦','title'=>'Two main chapter title gates','css'=>'static h2 titles in document flow followed by styled blockquote branch openers','computed'=>'Foundational title y≈31,230; Continuing Studies title y≈37,752.','summary'=>'When a main title mode is selected, links should be read as branch main links (PDFs) plus branch links under these two title gates.'],
|
||||
['id'=>'terminal-utility','icon'=>'☕','title'=>'Coffee terminal and utility chrome','css'=>'Coffee is a static icon paragraph; after it fixed scroll/RSS and absolute lander buttons appear','computed'=>'Coffee y≈48,471. Lander buttons use top:calc(100vh + 20px), so their source position is late but visual position is near the initial panel.','summary'=>'Do not assign footer/download/RSS/lander controls to Coffee. Coffee is the last icon and closes the branch ownership model.'],
|
||||
];
|
||||
|
||||
$sectionLayout = [
|
||||
'shadow-books' => ['zone'=>'Shadow prelude', 'visual'=>'before first standard icon', 'computed'=>'pre-icon source region', 'parent'=>'rich prelude blocks', 'position'=>'relative sections/articles', 'note'=>'Promoted as a bonus owner because the shadow trilogy appears before the standard icon sequence.'],
|
||||
'growing-out-of-the-shadows' => ['zone'=>'Shadow document block', 'visual'=>'large rich section', 'computed'=>'y≈2,342; h≈1,652', 'parent'=>'section#growing-out-of-the-shadows', 'position'=>'relative', 'note'=>'This is a section block, not a simple icon paragraph; links sit inside hologram, illumination, article and grid substructures.'],
|
||||
'sheep' => ['zone'=>'Counter bridge', 'visual'=>'counter/manual/data columns before the first chapter title', 'computed'=>'y≈24,422', 'parent'=>'p#sheep then counter columns/blockquotes', 'position'=>'static in #spireason-anchor', 'note'=>'Counter links are direct flow/counter descendants: manuals, JSON/databases, scripts and calculators; no media/video ownership.'],
|
||||
'infinity' => ['zone'=>'Foundational chapter', 'visual'=>'left icon after 🌌 Grand Introduction', 'computed'=>'y≈31,731', 'parent'=>'i > #spireason-anchor', 'position'=>'static icon paragraph', 'note'=>'Branch main links in this chapter are PDFs; secondary links are manuals, simulations and repositories.'],
|
||||
'natura' => ['zone'=>'Foundational chapter', 'visual'=>'compact two-PDF block', 'computed'=>'y≈32,981', 'parent'=>'i > #spireason-anchor', 'position'=>'static icon paragraph', 'note'=>'Direct PDF block under the foundational title gate.'],
|
||||
'sunrise' => ['zone'=>'Foundational chapter', 'visual'=>'long PDF run with add-ons', 'computed'=>'y≈33,461', 'parent'=>'i > #spireason-anchor', 'position'=>'static icon paragraph', 'note'=>'Material Magic branch; PDF main links plus inline app/manual add-ons in prose.'],
|
||||
'bigbang' => ['zone'=>'Continuing Studies chapter', 'visual'=>'opening icon after 📚 Overview of the Two Parts', 'computed'=>'y≈38,428', 'parent'=>'i > #spireason-anchor', 'position'=>'static icon paragraph', 'note'=>'Karma/Evolution are the two branch-main PDFs for the second chapter.'],
|
||||
'yggdrasill' => ['zone'=>'Continuing Studies chapter', 'visual'=>'tree continuation', 'computed'=>'y≈38,838', 'parent'=>'i > #spireason-anchor', 'position'=>'static icon paragraph', 'note'=>'Mostly PDF branch-main texts with a few supporting references.'],
|
||||
'spiritrise' => ['zone'=>'Continuing Studies chapter', 'visual'=>'blue diamond continuation', 'computed'=>'y≈39,694', 'parent'=>'i > #spireason-anchor', 'position'=>'static icon paragraph', 'note'=>'PDFs plus table/presentation links; the Force presentation is a later styled branch opener.'],
|
||||
'laelab' => ['zone'=>'Continuing Studies chapter', 'visual'=>'conceptual text gate', 'computed'=>'y≈40,478', 'parent'=>'i > #spireason-anchor', 'position'=>'static icon paragraph', 'note'=>'Mostly text/concept block in source; curated external entries are intentionally sparse.'],
|
||||
'geneticar' => ['zone'=>'Continuing Studies chapter', 'visual'=>'inside blockquote cluster', 'computed'=>'y≈40,881', 'parent'=>'blockquote > i > #spireason-anchor', 'position'=>'static icon paragraph', 'note'=>'AI visualization applets are branch links under the ongoing chapter.'],
|
||||
'handheldcal' => ['zone'=>'Tools inside continuing flow', 'visual'=>'calculator branch', 'computed'=>'y≈41,562', 'parent'=>'blockquote > i > #spireason-anchor', 'position'=>'static icon paragraph', 'note'=>'Calculator and resonant calculus links are tool branches, not PDF chapter-openers.'],
|
||||
'puzzled' => ['zone'=>'Source texts inside continuing flow', 'visual'=>'repository branch', 'computed'=>'y≈42,509', 'parent'=>'blockquote > i > #spireason-anchor', 'position'=>'static icon paragraph', 'note'=>'Repository/source links sit under a nested blockquote region.'],
|
||||
'chakra' => ['zone'=>'Foundational cross-reference inside continuing flow', 'visual'=>'chakra/element branch', 'computed'=>'y≈43,089', 'parent'=>'blockquote > i > #spireason-anchor', 'position'=>'static icon paragraph', 'note'=>'Relates to elementchakra logic and the fixed chakra menu; links are branch references rather than fixed menu buttons.'],
|
||||
'wheelsgoround' => ['zone'=>'Continuing tool/source flow', 'visual'=>'gear branch', 'computed'=>'y≈44,320', 'parent'=>'blockquote > i > #spireason-anchor', 'position'=>'static icon paragraph', 'note'=>'Open-source calculator backbone.'],
|
||||
'laemedics' => ['zone'=>'Body/health study flow', 'visual'=>'last substantive branch before Coffee', 'computed'=>'y≈45,032', 'parent'=>'i > #spireason-anchor', 'position'=>'static icon paragraph', 'note'=>'SpiBody/SpiZenTao branch plus Kybalion reference before the terminal Coffee notes.'],
|
||||
'coffeeandcigarettes' => ['zone'=>'Terminal prose', 'visual'=>'last icon, no branch links', 'computed'=>'y≈48,471', 'parent'=>'i > #spireason-anchor', 'position'=>'static icon paragraph', 'note'=>'Closes branch ownership. Footer, archive, RSS, scroll and lander controls are utility chrome and are listed separately.'],
|
||||
];
|
||||
|
||||
$chapterGroups = [
|
||||
[
|
||||
'id' => 'chapter-foundational',
|
||||
'icon' => '✦',
|
||||
'title' => 'Foundational Texts',
|
||||
'trigger' => ['icon'=>'🌌', 'name'=>'Grand Introduction', 'href'=>'https://materialmagic.floot.app/', 'summary'=>'Styled blockquote opener with data-title="Introduction to first chapter of PDF documents".'],
|
||||
'sections' => ['infinity', 'natura', 'sunrise'],
|
||||
'computed' => 'h2 y≈31,230; trigger y≈31,569',
|
||||
'summary' => 'The first main title gate. PDFs are the branch-main links; manuals, repositories, simulations and applets are branch links under those owners.',
|
||||
],
|
||||
[
|
||||
'id' => 'chapter-continuing',
|
||||
'icon' => '⟲',
|
||||
'title' => 'Continuing Studies',
|
||||
'trigger' => ['icon'=>'📚', 'name'=>'Overview of the Two Parts', 'href'=>'https://www.popai.pro/ppt-share?shareKey=87543f5adbb61069fd42a0fc439738d802d1aaf72585bc0e0665d9d40fec1375', 'summary'=>'Styled blockquote opener with data-title="Introduction to both first and second part".'],
|
||||
'sections' => ['bigbang', 'yggdrasill', 'spiritrise', 'laelab', 'geneticar', 'handheldcal', 'puzzled', 'chakra', 'wheelsgoround', 'laemedics'],
|
||||
'computed' => 'h2 y≈37,752; trigger y≈38,267',
|
||||
'summary' => 'The second main title gate. Karma/Evolution start the run, then tree, spirit, lab, source, chakra and body branches continue in the same flow.',
|
||||
],
|
||||
];
|
||||
|
||||
409
index.php
409
index.php
@ -1,150 +1,289 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
@ini_set('display_errors', '1');
|
||||
@error_reporting(E_ALL);
|
||||
@date_default_timezone_set('UTC');
|
||||
|
||||
$phpVersion = PHP_VERSION;
|
||||
$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>New Style</title>
|
||||
<?php
|
||||
// Read project preview data from environment
|
||||
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? '';
|
||||
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
||||
?>
|
||||
<?php if ($projectDescription): ?>
|
||||
<!-- Meta description -->
|
||||
<meta name="description" content='<?= htmlspecialchars($projectDescription) ?>' />
|
||||
<!-- Open Graph meta tags -->
|
||||
<meta property="og:description" content="<?= htmlspecialchars($projectDescription) ?>" />
|
||||
<!-- Twitter meta tags -->
|
||||
<meta property="twitter:description" content="<?= htmlspecialchars($projectDescription) ?>" />
|
||||
<?php endif; ?>
|
||||
<?php if ($projectImageUrl): ?>
|
||||
<!-- Open Graph image -->
|
||||
<meta property="og:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
|
||||
<!-- Twitter image -->
|
||||
<meta property="twitter:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
|
||||
<?php endif; ?>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
:root {
|
||||
--bg-color-start: #6a11cb;
|
||||
--bg-color-end: #2575fc;
|
||||
--text-color: #ffffff;
|
||||
--card-bg-color: rgba(255, 255, 255, 0.01);
|
||||
--card-border-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: 'Inter', sans-serif;
|
||||
background: linear-gradient(45deg, var(--bg-color-start), var(--bg-color-end));
|
||||
color: var(--text-color);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
body::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><path d="M-10 10L110 10M10 -10L10 110" stroke-width="1" stroke="rgba(255,255,255,0.05)"/></svg>');
|
||||
animation: bg-pan 20s linear infinite;
|
||||
z-index: -1;
|
||||
}
|
||||
@keyframes bg-pan {
|
||||
0% { background-position: 0% 0%; }
|
||||
100% { background-position: 100% 100%; }
|
||||
}
|
||||
main {
|
||||
padding: 2rem;
|
||||
}
|
||||
.card {
|
||||
background: var(--card-bg-color);
|
||||
border: 1px solid var(--card-border-color);
|
||||
border-radius: 16px;
|
||||
padding: 2rem;
|
||||
backdrop-filter: blur(20px);
|
||||
-webkit-backdrop-filter: blur(20px);
|
||||
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
.loader {
|
||||
margin: 1.25rem auto 1.25rem;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border: 3px solid rgba(255, 255, 255, 0.25);
|
||||
border-top-color: #fff;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
@keyframes spin {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
.hint {
|
||||
opacity: 0.9;
|
||||
}
|
||||
.sr-only {
|
||||
position: absolute;
|
||||
width: 1px; height: 1px;
|
||||
padding: 0; margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap; border: 0;
|
||||
}
|
||||
h1 {
|
||||
font-size: 3rem;
|
||||
font-weight: 700;
|
||||
margin: 0 0 1rem;
|
||||
letter-spacing: -1px;
|
||||
}
|
||||
p {
|
||||
margin: 0.5rem 0;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
code {
|
||||
background: rgba(0,0,0,0.2);
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||
}
|
||||
footer {
|
||||
position: absolute;
|
||||
bottom: 1rem;
|
||||
font-size: 0.8rem;
|
||||
opacity: 0.7;
|
||||
}
|
||||
</style>
|
||||
<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>
|
||||
<main>
|
||||
<div class="card">
|
||||
<h1>Analyzing your requirements and generating your website…</h1>
|
||||
<div class="loader" role="status" aria-live="polite" aria-label="Applying initial changes">
|
||||
<span class="sr-only">Loading…</span>
|
||||
<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>
|
||||
<p class="hint"><?= ($_SERVER['HTTP_HOST'] ?? '') === 'appwizzy.com' ? 'AppWizzy' : 'Flatlogic' ?> AI is collecting your requirements and applying the first changes.</p>
|
||||
<p class="hint">This page will update automatically as the plan is implemented.</p>
|
||||
<p>Runtime: PHP <code><?= htmlspecialchars($phpVersion) ?></code> — UTC <code><?= htmlspecialchars($now) ?></code></p>
|
||||
</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>
|
||||
<footer>
|
||||
Page updated: <?= htmlspecialchars($now) ?> (UTC)
|
||||
|
||||
<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>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user