Static Astro shell with PHP endpoints for directory scanning and zip upload. The build output can live in a PHP-served folder such as J:\ads, which maps to https://static.justgains.com/ads.
bun install
bun run check
bun run build
bun run build:single
bun run deployEdit the variables at the top of src/pages/index.astro to rename the gallery or swap branding defaults:
appName: browser title, header name, and default gallery heading.appKicker: small header label above the gallery name.galleryRootLabel: label for the top-level media folder in the browser UI.appDescription: meta description for the generated page.faviconHref: favicon URL or data URI.themeColor: browser theme color.
bun run deploy builds the app, packages dist, and deploys it over SSH using the TekPrepper config at F:\tekprepper.com\.env. The default remote target is:
/home/tekprepp/static.justgains.com/ads
The deploy script replaces generated files (index.html, _astro, api), moves the previous Files Gallery PHP app out of the way, and preserves/creates gallery/ for separately uploaded creative assets.
Override paths when needed:
$env:TEKPREPPER_ENV_FILE="F:\tekprepper.com\.env"
$env:GALLERY_REMOTE_WEBROOT="/home/tekprepp/static.justgains.com/ads"
bun run deploybun run build:singleCompiles the entire app into dist-single/index.php — drop that one file into any PHP-served folder next to your creative asset directories and it just works. Details:
- CSS and JS are inlined into the HTML, so the page loads with zero extra asset requests.
- Branding values from
src/pages/index.astro, including the favicon, are compiled into the single PHP file. - The HTML is gzipped at build time and stored as raw bytes after
__halt_compiler(), so PHP never parses or compresses it at runtime; clients that accept gzip get the precompressed bytes with an exactContent-Length. - A build-hash
ETaggives repeat visitors304 Not Modifiedresponses. - The API files are merged into the same file (one opcache entry, no
requirestat calls) and routed via?api=gallery.php,?api=upload.php, etc. The page injectswindow.GALLERY_API_BASEso the frontend targets that scheme automatically; the regular multi-file build keeps usingapi/*.php.
dist/ and dist-single/ are generated outputs and are intentionally ignored by Git.
The gallery root resolution is the same as below, except the site root is the folder containing index.php itself.
Verified against the live TekPrepper server (LiteSpeed, PHP 8.1, behind Cloudflare); requires PHP 7.4+. One hosting caveat: if a parent .htaccess sets DirectoryIndex without index.php (the deployed ads app does), the bare folder URL will 403 — either request index.php directly or add DirectoryIndex index.php in the folder. Cloudflare strips the ETag at the edge, which only disables the 304 shortcut; the shell is ~11 KB gzipped either way.
By default the PHP API scans:
gallery-root.txtif present, containing a relative media directory.gallery/when it exists, even if it is still empty.- The deployed site root, excluding app directories such as
_astroandapi.
That fallback keeps existing folders like competitors/ and programs/ visible on J:\ads.
The upload form posts a zip to api/upload.php, extracts supported creative files into the requested folder, and deletes the temporary zip. Uploads require the admin token when one is configured.
The gallery previews browser-native images and videos directly, opens PDFs in the full-screen viewer, reads markdown and text files in the document reader (see Documents), and renders format-aware download cards for non-previewable files. The supported library includes common Adobe/Affinity source files, AI, PSD/PSB, EPS/PostScript, InDesign, TIFF and camera-raw formats, CAD/3D files, office documents, fonts, audio, archives, and other production formats. Every listed file has a forced attachment download route, so uncommon extensions do not depend on the web host's MIME configuration.
Large zip uploads show browser transfer progress. After the progress reaches 100%, the dialog switches to a server-side processing state while PHP extracts the archive.
Markdown and plain-text files open in a built-in reader instead of the media
lightbox. Click any .md, .txt, .csv, .json, .yml, .log (and similar)
card, or use the Read README.md button that appears when a folder contains a
README, index, or about markdown file.
The reader has:
- A Documents sidebar listing every readable file in the current folder or
search result, with a filter box.
/focuses it. - A Contents outline built from the document's headings, with scroll tracking. Click a heading to jump; heading anchors are linkable.
- Reading and Source modes, a
Wraptoggle for monospace views,A-/A+text sizing, and per-code-block copy buttons. All three preferences persist. - Keyboard control:
←/→(orj/k) move between documents,+/-resize text,Escapecloses. - A
?doc=<path>URL parameter, so an open document is linkable and survives back/forward navigation.
Rendering is format-aware:
| Format | Rendered as |
|---|---|
md, markdown, mdx, mkd |
Full markdown document |
csv, tsv |
Table with quoted fields honoured and numeric columns aligned right |
json, yaml, yml, toml, ini, cfg, conf, xml, properties |
Highlighted code |
log, diff, patch, srt, vtt, nfo |
Monospace, highlighted where applicable |
txt, text, rst, adoc |
Reading column that keeps the file's own line breaks, indentation, and column-aligned blocks |
Text cards in the grid show a real excerpt of the file, fetched lazily for only the cards on screen.
The renderer is dependency-free and builds DOM nodes directly — no HTML is ever parsed from a document, so untrusted markdown cannot inject markup or scripts. It covers the CommonMark/GFM subset that documentation actually uses: ATX and setext headings, fenced and indented code with syntax highlighting, nested and lazy lists, task lists, tables with alignment, blockquotes, thematic breaks, emphasis/strong/strikethrough, code spans, images, reference links and definitions, autolinks, entities, hard breaks, and escapes.
Links and images are resolved against the document's own folder:
- A relative link to another readable document opens it in the reader,
including
other.md#sectionanchors. - A relative link to any other gallery file opens that file.
- Relative image paths resolve to the gallery URL for that image.
- Links to files that are not in the gallery render as plain text, and only
http,https,mailto,tel,ftp, andsmsURLs are allowed —javascript:anddata:links are dropped.
api/text.php?path=<relative path> returns the file as JSON:
{ "ok": true, "name": "README.md", "type": "markdown", "size": 1715,
"bytesRead": 1715, "truncated": false, "content": "# ..." }Files are read up to 3 MB (GALLERY_TEXT_MAX_BYTES); anything larger comes back
with truncated: true and the reader shows a notice pointing at the download.
Content is normalized to UTF-8 with LF line endings — BOMs are stripped, UTF-16
and Windows-1252 files are converted, and a multi-byte character cut off by the
read limit is trimmed rather than mangled. Add &preview=<bytes> for the short
excerpt used by grid cards.
Click Manage and enter the admin token to enable multi-select, moving, and deleting files. The token is checked by PHP on every write request; the browser only stores it in sessionStorage for the current tab session.
On the deployed server the token is stored outside the web root at:
/home/tekprepp/.ads-gallery-admin-token
bun run deploy creates that file if it is missing and preserves it on later deploys.