A Phantom wallet–aware, Solana token-gated audio hub for FOOD4THOTH releases.
Holders with at least 1 RSTORY can view the special message; holders with 10,000 RSTORY can download gated tracks/videos.
The carousel opens on “Riviera of Bones.”
@solana/web3.js
initialSlide: 4
, currentSongIndex = 4
)/RstoryPlayList/
index.html # main page (the big file you pasted)
readme/
index.md # THIS README file (permalinked here)
MidwayBlitz.png
RivieraBones.png
TaxStarve.png
IMG_9573.JPG
navigation.html # loaded into #nav-container at runtime
You can rename the README to
README.md
, but for pretty site routing usereadme/index.md
with the permalink above.
@solana/web3.js@1.95.3
(ESM from esm.sh
)All are already referenced via CDN in index.html
.
In index.html
(Phantom section):
// ---- CONFIG ----
const ANKR_HEX = "<your_ankr_api_key_hex>"; // hex-encoded
const CHAIN = "solana"; // or "solana_devnet" / "solana_testnet"
const RSTORY_MINT = "RstyC4aoSD1JKPGTxnjB3PkWdRzV3yoePH1XiPMxBfz";
const RSTORY_DECIMALS = 6;
// Thresholds
const THRESHOLD_USER_UNITS = 10000; // download unlock ≥ 10,000 RSTORY
const VIEW_THRESHOLD_USER_UNITS = 1; // message unlock ≥ 1 RSTORY
`https://rpc.ankr.com/${CHAIN}/${hexToString(ANKR_HEX)}`
window.dispatchEvent(new CustomEvent("rstory:gate", {
detail: { hasAny, hasDownload, raw }
}));
…so other modules (videos, player, decoder) can react.
Tip: Hex-encode your ANKR key (DevTools):
[...new TextEncoder().encode("YOUR_ANKR_KEY")]
.map(b => b.toString(16).padStart(2,"0"))
.join("");
Start on “Riviera of Bones”:
let currentSongIndex = 4;
var swiper = new Swiper(".swiper", {
// ...
initialSlide: 4,
// ...
});
Slide ↔ Song mapping (adjust if you reorder slides):
const slideToSong = [0, 1, 2, 3, 4, 5, 6, 7, 8];
Keep them in sync:
function syncPlayerToSlide(i, { autoplay = false } = {}) {
const next = slideToSong[i];
if (next == null) return;
currentSongIndex = next;
updateSongInfo();
if (autoplay) playSong(); else pauseSong();
}
swiper.on("slideChange", () => syncPlayerToSlide(swiper.realIndex));
const songs = [
{ title: "Rstory BackTrack", name: "FOOD4THOTH",
source: "https://www.food4thoth.com/MusicLibraryVis/music/RstoryRainbowRap.m4a" },
{ title: "Time is Breaking", name: "INPROGRESSION",
source: "https://www.food4thoth.com/MusicLibraryVis/InproSqSpace/Inprogression+-+Time+and+Energy+-+02+Time+is+Breaking.mp3" },
{ title: "Blip Blox 3D", name: "DeJahn",
source: "https://www.food4thoth.com/MusicLibraryVis/music/BlipBlox3.wav" },
// GATED (hex sources)
{ title: "Midway Blitz — industrial indictment", name: "FOOD4THOTH", gated: true,
sourceHex: "68747470733a2f2f62726f6e7a652d6b696e642d7469636b2d3337302e6d7970696e6174612e636c6f75642f697066732f62616679626569676c6b6e636870327937656d6c6675757877696371776e7a65633668326663756a3376366d78617961617864673534676a336c75" },
{ title: "Riviera of Bones", name: "FOOD4THOTH", gated: true,
sourceHex: "68747470733a2f2f62726f6e7a652d6b696e642d7469636b2d3337302e6d7970696e6174612e636c6f75642f697066732f62616679626569666833763535706770376d677378346a656e696466777335683370767a656665716668786135657663356b70727a62323634676d" },
{ title: "You Pay Tax Dollars to Starve Children", name: "FOOD4THOTH", gated: true,
sourceHex: "68747470733a2f2f62726f6e7a652d6b696e642d7469636b2d3337302e6d7970696e6174612e636c6f75642f697066732f62616679626569627a6877326963716876646d7361736c326b66723669613279666b6d646b35696d79337470326d6e6a7472793368677261346d79" },
{ title: "Mescalito Amazing Story", name: "DeJahn",
source: "https://www.food4thoth.com/MusicLibraryVis/music/MescalinosAmazingStory.wav" },
{ title: "Rabbit Hole", name: "INPROGRESSION",
source: "https://www.food4thoth.com/MusicLibraryVis/InproSqSpace/Inprogression+-+Down+the+Rabbit+Hole+-+03+Rabbit+Hole%202.mp3" },
{ title: "Funkin Around", name: "DeJahn",
source: "https://static1.squarespace.com/static/569ded85a128e6228959a613/t/56b0bbfd2eeb819ad6daaf05/1454423107728/ZOOM0003_ST001.mp3/original/ZOOM0003_ST001.mp3" }
];
For gated tracks, the player decodes
sourceHex
to a playablesource
(so listening is allowed),
but downloads remain locked until the 10k RSTORY threshold.
Gate state flows in via the global event:
let gateOK = false;
window.addEventListener("rstory:gate", (e) => {
gateOK = !!(e.detail?.hasDownload); // true only when ≥ 10k
updateDownloadState();
});
Button guard:
function updateDownloadState() {
const s = songs[currentSongIndex];
const locked = s.gated && !gateOK;
downloadBtn.disabled = locked;
downloadBtn.title = locked ? "Hold 10k RSTORY to download" : "Download";
}
Force a file save when permitted:
downloadBtn.addEventListener("click", async () => {
const s = songs[currentSongIndex];
if (s.gated && !gateOK) return;
const url = s.downloadHex ? fromHex(s.downloadHex)
: s.source || (s.sourceHex ? fromHex(s.sourceHex) : "");
if (!url) return;
const resp = await fetch(url, { mode: "cors" });
const blob = await resp.blob();
const a = document.createElement("a");
a.href = URL.createObjectURL(blob);
a.download = `${(s.title||"track").replace(/[^\w\-]+/g,"_")}.${(blob.type?.split("/")[1]||"mp3")}`;
document.body.appendChild(a); a.click(); a.remove();
});
src
.<video>
src
from its hex map.const VIDEO_HEX_BY_ID = new Map([
["midway-blitz", "68747470733a2f2f62726f6e7a652d6b696e642d7469636b2d3337302e6d7970696e6174612e636c6f75642f697066732f62616679626569676c6b6e636870327937656d6c6675757877696371776e7a65633668326663756a3376366d78617961617864673534676a336c75"],
["riviera", "68747470733a2f2f62726f6e7a652d6b696e642d7469636b2d3337302e6d7970696e6174612e636c6f75642f697066732f62616679626569666833763535706770376d677378346a656e696466777335683370767a656665716668786135657663356b70727a62323634676d"],
["core-truth", "68747470733a2f2f62726f6e7a652d6b696e642d7469636b2d3337302e6d7970696e6174612e636c6f75642f697066732f62616679626569627a6877326963716876646d7361736c326b66723669613279666b6d646b35696d79337470326d6e6a7472793368677261346d79"],
]);
function hydrateVideosAfterGate() {
document.querySelectorAll(".song-card").forEach(card => {
const id = card.getAttribute("data-song-id");
const hex = VIDEO_HEX_BY_ID.get(id);
const v = card.querySelector("video");
if (!hex || !v || v.dataset.hydrated === "1") return;
v.src = fromHex(hex) + "#t=0.1"; // tiny seek for thumbnail on some browsers
v.dataset.hydrated = "1";
});
}
window.addEventListener("rstory:gate", (e) => {
if (e.detail?.hasAny) hydrateVideosAfterGate(); // ≥ 1 RSTORY
});
.btn#decryptBtn
is disabled until ≥ 1 RSTORYMSG_HEX
, then animates character “spins” into the plaintext.window.addEventListener("rstory:gate", (e) => {
const ok = !!(e.detail?.hasAny);
decryptBtn.disabled = !ok;
});
Set
AUTO_DECODE_ON_GATE = true
if you want it to auto-reveal as soon as the holder is verified.
The page fetches ../navigation.html
into #nav-container
and sets up:
#toggle-nav
).submenu
sections via data-expand
attributesIf your project doesn’t have navigation.html
at that path, create it or remove the fetch block.
songs[]
. For public media, use source
. For gated media, use gated: true
with sourceHex
:
{ title:"My New Track", name:"FOOD4THOTH", gated:true, sourceHex:"<hex url>" }
songs[]
index in slideToSong
.downloadHex
to that song.Encoding to hex (DevTools):
const toHex = s =>
[...new TextEncoder().encode(s)]
.map(b => b.toString(16).padStart(2,"0"))
.join("");
index.html
in a modern browser.CHAIN
.If balance reads 0 but you hold tokens, check:
RSTORY_MINT
is correctCHAIN
matches your holdings’ networkGET
. For IPFS gateways, prefer a trusted gateway..swiper
and .swiper-wrapper > .swiper-slide
structure and that observer: true
is set (already included).keyboard.enabled = true
)Food4Thoth is inspired by the principles of its namesake, Thoth:
The platform is a digital garden where ancient wisdom meets modern innovation.
Your contributions help support innovative projects like the Rainbow Glo-Calculato, community gardens, and esoteric tools, ensuring Food4Thoth continues to thrive.
Traditional Payments:
Cryptocurrency:
Ripple (XRP):
Address: <div class="wrap">rEAKseZ7yNgaDuxH74PkqB12cVWohpi7R6</div>
Memo: 3109966062
Stellar Lumens (XLM):
Address: <div class="wrap">GB2ES2N326MZK4EGJBKN3ZARCQ5RTFQSAWIJAAKFVIIIJSCC35TXIMLB</div>
Memo: 2967141893
artabilly.gm
We welcome contributions to enhance this project:
# 1) Fork the repository, then clone your fork
git clone <your-fork-url>
cd <repo>
# 2) Create a feature branch
git checkout -b feature-name
# 3) Commit your changes
git commit -m "Add feature or fix"
# 4) Push your branch
git push origin feature-name
# 5) Open a Pull Request on GitHub/GitLab
This platform is part of the Food4Thoth Initiative, which blends creativity, technology, and community to make a positive impact.
Special thanks to our supporters for enabling these innovative projects and empowering meaningful change.
Your contributions make a difference. Thank you for your support!
⚡ Credits
Designed, coded, and curated by DeJahn under Artabillies & FOOD4THOTH.
© 2025 Food4Thoth. All rights reserved.
Unauthorized redistribution, copying, or modification without explicit permission is prohibited.