Compare commits

40 Commits
Author SHA1 Message Date
me b78eb3f0f4 apps, fix title and reword description for Sunset Alarm. 2026-11-04 13:07:39 -07:00
me 77d96bdfd9 apps, update Obtainium instructions. 2026-11-04 12:51:53 -07:00
me 851171609b apps, go back to using src URI for obtainium since it's just showing the text on Android FF. 2026-11-04 12:45:51 -07:00
me a45a0a6368 Add README's for the new folders. 2026-11-04 12:44:56 -07:00
me 032e551858 favicon, add var to favicon.io links and add sizes="any" to svg. 2026-11-02 14:47:43 -07:00
me a3192ec559 favicon, comment redundant entries. 2026-11-02 09:05:02 -07:00
me 826daabc72 main, add timestamps to the log. 2026-11-02 08:52:04 -07:00
me 53bb02063a banner, use asset images. 2026-11-02 08:13:47 -07:00
me 10bb283a44 favicon, add new favicons and manifests. 2026-11-02 08:13:31 -07:00
me eda39d07ba assets, add favicons and manifests from same websites used for blog site, this time based on peace icon. 2026-11-02 08:13:10 -07:00
me 845d7ecdcb art, add images frequently used as icons. 2026-11-02 08:12:12 -07:00
me 027a7875d8 main, implement an 'assets' folder for files needed at the website's root, allows having a local favicon and images. 2026-11-02 08:11:35 -07:00
me 47a022161c apps, point hypergames image to dev branch since main may not be done for a while. 2026-11-01 11:07:33 -07:00
me 31d9a8b706 apps, update TicTacToe and HyperGames to be new standard. 2026-11-01 10:28:32 -07:00
me d424a4c7c3 apps, update T45MR to new standard. 2026-11-01 08:34:25 -07:00
me e44e8a6d18 apps, update Expense Tracker to use the new standard. 2026-11-01 07:45:13 -07:00
me 3f0fff42d4 header, move favicons to their own file before adding better support. 2026-10-29 21:53:34 -07:00
me 25ff248b92 main, begin to minify HTML. 2026-10-29 21:53:08 -07:00
me ed934ea5d9 banner, hide the underlines. 2026-10-05 15:51:34 -07:00
me f7dd491553 font, fix whitespace. 2026-10-05 15:40:42 -07:00
me 4ba60b1c21 announcements, small refactor and add the video for F-Droid. 2026-10-05 15:21:12 -07:00
me 31217aa641 css, fix row behavior for banner. 2026-10-05 15:07:30 -07:00
me 82ad8a9134 main, fix garbage inserted from Android editor app. 2026-10-05 15:05:01 -07:00
me 6155a21a24 banner, fix not showing up on mobile views. 2026-10-05 15:04:30 -07:00
me 3c441f117a Commit mobile testing on Termux, will finish on laptop. 2026-10-05 13:40:41 -07:00
me 67c968e390 banner, change to go to root not about. 2026-10-05 11:53:18 -07:00
me 1544fd140f banner, fix width for tablet mode. 2026-10-05 11:51:43 -07:00
me cab5a4cbcf css, add commas for tablet mode. 2026-10-05 11:51:21 -07:00
me d58261f8c2 apps, shorten link for mobile. 2026-10-05 11:50:45 -07:00
me 2e8eefda7f css, fix the widths in tablet move. 2026-10-05 10:48:49 -07:00
me 2cae390404 css, further de-dupe mobile code. 2026-10-05 10:46:36 -07:00
me e285ccf5d5 css, add a section which is both phone and tablets rather than duplicating code. 2026-10-05 10:46:02 -07:00
me 74611fa707 css, hide banner images if phone or tablet. 2026-10-05 10:42:49 -07:00
me 11c990f1e4 menu, add colons back to headers. 2026-10-05 10:39:07 -07:00
me b8e3840d7a css, add spacing back to menu items. 2026-10-05 10:38:13 -07:00
me 6d497a238d menu, continue shrinking menu. 2026-10-05 10:36:58 -07:00
me 6b32473727 main css, border does not need to be related to font size. 2026-10-05 10:16:09 -07:00
me 8dcbbab181 main, use menu-list and change border to be relative. 2026-10-05 10:13:04 -07:00
me 0e029a9ab6 menu, take up much less space. 2026-10-05 10:12:45 -07:00
me c838fba60c main, add comment for testing later. 2026-10-05 10:00:51 -07:00
30 changed files with 308 additions and 140 deletions

No files matched your search

+3
View File
@@ -0,0 +1,3 @@
# Art
Support directory to store images which are used inderectly for the site.
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

+4
View File
@@ -0,0 +1,4 @@
# Assets
Similar to `static/` on a Hugo website, these files are served at the website's
root as-is. Useful for favicon type content. Can have folders if desired.
Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 662 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="1000" height="1000"><metadata><rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:dc="http://purl.org/dc/elements/1.1/"><rdf:Description><dc:creator>RealFaviconGenerator</dc:creator><dc:source>https://realfavicongenerator.net</dc:source></rdf:Description></rdf:RDF></metadata><g clip-path="url(#SvgjsClipPath1046)"><rect width="1000" height="1000" fill="#ffffff"></rect><g transform="matrix(3.3333333333333335,0,0,3.3333333333333335,0,0)"><svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="300" height="300" viewBox="0 0 300 300"><image width="300" height="300" xlink:href="data:image/png;base64,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 truncated

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.
Binary file not shown.
+21
View File
@@ -0,0 +1,21 @@
{
"name": "Hyperling.com",
"short_name": "Hyperling",
"icons": [
{
"src": "/web-app-manifest-192x192.png?v=20261102",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "/web-app-manifest-512x512.png?v=20261102",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
],
"theme_color": "#ffffff",
"background_color": "#ffffff",
"display": "standalone"
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

+137 -60
View File
@@ -14,6 +14,8 @@ const execSync = require('child_process').execSync;
const fs = require('fs'); const fs = require('fs');
const { minify } = require('html-minifier-terser');
//// Global Variables //// //// Global Variables ////
const DEBUG = false; const DEBUG = false;
@@ -24,12 +26,16 @@ let pages = [];
const pages_dir = "./pages/"; const pages_dir = "./pages/";
const file_types = ["php", "sh"]; const file_types = ["php", "sh"];
let assets = [];
const asset_dir = "./assets/";
const asset_types = ['png', 'svg', 'ico', 'webmanifest', 'jpg'];
let ports = []; let ports = [];
// Check parameters for numeric port numbers. // Check parameters for numeric port numbers.
process.argv.forEach(function (val, index, array) { process.argv.forEach(function (val, index, array) {
console.log("Parameter", index + ':', val, !isNaN(val)); console.log(now() + ": Parameter", index + ':', val, !isNaN(val));
if (!isNaN(val)) { if (!isNaN(val)) {
console.log("Adding Port", val) console.log(now() + ": Adding Port", val)
ports.push(val); ports.push(val);
} }
}); });
@@ -44,25 +50,76 @@ const stringsToRemove = [
//// Functions //// //// Functions ////
// Compact HTML and remove unnecessary content like comments and whitespace.
// May help with performance for slower connections. Also more professional.
async function mini(html) {
//console.log(`html=${html}`);
const minified = await minify(html, {
collapseWhitespace: true,
removeComments: true,
minifyJS: true,
minifyCSS: true
});
//console.log(`minified=${minified}`);
return minified.toString();
}
// Check extension and guess a MIME type.
function getMIME(extension) {
let mime;
switch (extension) {
case "apk":
mime = "application/vnd.android.package-archive";
break;
case "jpg":
case "jpeg":
mime = "image/jpeg";
break;
case "png":
mime = "image/png";
break;
case "html":
mime = "text/html";
break;
case "zip":
mime = "application/zip";
break;
case "md":
case "txt":
case "csv":
mime = "text/*";
break;
default:
mime = "application/" + extension;
break;
}
return mime;
}
// Print datetime for logging.
function now() {
return new Date().toISOString();
}
/* Code exists inside a main function so that we may use async/await. /* Code exists inside a main function so that we may use async/await.
*/ */
async function main() { async function main() {
console.log("...Starting Main..."); console.log(now() + ": ...Starting Main...");
// Getting dates in Node.js is awful, just use Linux. // Getting dates in Node.js is awful, just use Linux.
const start_datetime = "" + await execSync('date "+%Y-%m-%dT%H:%M:%S%:z"'); const start_datetime = "" + await execSync('date "+%Y-%m-%dT%H:%M:%S%:z"');
const start_datetime_trimmed = start_datetime.trim(); const start_datetime_trimmed = start_datetime.trim();
console.log("Set app to return HTML documents."); console.log(now() + ": Set app to return HTML documents.");
app.use(function (req, res, next) { app.use(function (req, res, next) {
res.contentType('text/html'); res.contentType('text/html');
next(); next();
}); });
/* Loop through all file in the pages subdirectory and add the allowed /* Loop through all files in the pages subdirectory and add the allowed
// file types into the pages array for automatic router creation. // file types into the pages array for automatic router creation.
*/ */
console.log("...Starting Main..."); console.log(now() + ": ...Starting Pages...");
let ls = await fs.promises.readdir(pages_dir); let ls = await fs.promises.readdir(pages_dir);
if (DEBUG) console.log("DEBUG: Results of ls, ", ls); if (DEBUG) console.log("DEBUG: Results of ls, ", ls);
for (let file of ls) { for (let file of ls) {
@@ -72,14 +129,35 @@ async function main() {
if (file_types.includes(file_type)) { if (file_types.includes(file_type)) {
if (DEBUG) console.log("DEBUG: Hooray!", file, "is being added."); if (DEBUG) console.log("DEBUG: Hooray!", file, "is being added.");
pages[file_name] = pages_dir + file; pages[file_name] = pages_dir + file;
console.log(" * Added page", file); console.log(now() + ": * Added page", file);
} else { } else {
if (DEBUG) console.log("DEBUG: ", file, "is not an approved type, skipping."); if (DEBUG) console.log("DEBUG: ", file, "is not an approved type, skipping.");
} }
} }
console.log(" * Pages loaded: ", pages); console.log(now() + ": * Pages loaded: ", pages);
//return; // Stop execution FORTESTING //return; // Stop execution FORTESTING
/* Loop through the assets folder and grab favicons, manifests, etc. and
// allow them to be accessed via the root of the website, such as a favicon
// at 'https://hyperling.com/favicon.ico'. May help with SEO!
*/
console.log(now() + ": ...Starting Assets...");
let ls_assets = await fs.promises.readdir(asset_dir);
if (DEBUG) console.log("DEBUG: Results of ls_assets, ", ls_assets);
for (let file of ls_assets) {
let file_test = file.split(".");
//let file_name = file_test[0]; // not needed (yet)
let file_type = file_test[file_test.length - 1];
if (asset_types.includes(file_type)) {
if (DEBUG) console.log("DEBUG: Hooray!", file, "is being added.");
assets[file] = asset_dir + file;
console.log(now() + ": * Added asset", file);
} else {
if (DEBUG) console.log("DEBUG: ", file, "is not an approved type, skipping.");
}
}
console.log(now() + ": * Assets loaded: ", assets);
/* Create both an XML and HTML sitemap based on these entries. XML is used for /* Create both an XML and HTML sitemap based on these entries. XML is used for
// bots like SEO scrapers. HTML will be for human users looking for a list of // bots like SEO scrapers. HTML will be for human users looking for a list of
// all pages. Some are not in the menu. Generated an example XML sitemap at // all pages. Some are not in the menu. Generated an example XML sitemap at
@@ -140,7 +218,7 @@ async function main() {
Sitemap: https://hyperling.com/sitemap.xml Sitemap: https://hyperling.com/sitemap.xml
`; `;
console.log("...Adding Routes..."); console.log(now() + ": ...Adding Routes...");
let router = express.Router(); let router = express.Router();
/* AUTOMATIC METHOD BASED ON OBJECT/ARRAY OF WEB SCRIPTS /* AUTOMATIC METHOD BASED ON OBJECT/ARRAY OF WEB SCRIPTS
@@ -148,19 +226,20 @@ async function main() {
*/ */
// TBD/TODO would be great to log this as "DATE: ADDRESS hit ROUTER requesting RESOURCE" // TBD/TODO would be great to log this as "DATE: ADDRESS hit ROUTER requesting RESOURCE"
for (let key in pages) { for (let key in pages) {
console.log(" * Creating router for", key); console.log(now() + ": * Creating router for", key);
router.get("/" + key, function (req,res) { router.get("/" + key, async function (req,res) {
console.log(key, "fulfilling request to", req.socket.remoteAddress, "asking for", req.url); console.log(now() + ":", key, "fulfilling request to", req.socket.remoteAddress, "asking for", req.url);
let html = "" + execSync(pages[key]); let html = "" + execSync(pages[key]);
stringsToRemove.forEach(string => { stringsToRemove.forEach(string => {
html = html.replace(string, ""); html = html.replace(string, "");
}); });
html = await mini(html);
res.send(html); res.send(html);
}); });
/* Append the page to the sitemap variables. /* Append the page to the sitemap variables.
*/ */
console.log(" * * Adding to sitemap.xml"); console.log(now() + ": * * Adding to sitemap.xml");
sitemap_xml = sitemap_xml + ` sitemap_xml = sitemap_xml + `
<url> <url>
<loc>https://hyperling.com/`+key+`/</loc> <loc>https://hyperling.com/`+key+`/</loc>
@@ -168,7 +247,7 @@ async function main() {
<priority>0.80</priority> <priority>0.80</priority>
</url> </url>
`; `;
console.log(" * * Adding to sitemap.html"); console.log(now() + ": * * Adding to sitemap.html");
sitemap_html = sitemap_html + ` sitemap_html = sitemap_html + `
<li> <li>
<b>`+key+`</b> <b>`+key+`</b>
@@ -187,69 +266,66 @@ async function main() {
</ul></body></html> </ul></body></html>
`; `;
console.log(now() + ": * Creating routers for assets");
for (let key in assets) {
console.log(now() + ": * * Adding /" + key);
router.get('/' + key, function (req, res) {
console.log(now() + ": asset response to", req.socket.remoteAddress, "asking for", req.url)
// Build variables.
const file = "./assets/" + req.path;
const extensions = req.path.split(".");
const extension = extensions[extensions.length-1];
let mime = getMIME(extension);
// Return the file
res.contentType(mime);
let f = fs.createReadStream(file)
.on("error", function(e) {
res.contentType("text/plain");
res.send(404, "Asset Not Found");
})
.pipe(res)
;
});
}
// Provide sitemap.xml file for "SEO". // Provide sitemap.xml file for "SEO".
// TBD/TODO would be great to log this as "DATE: ADDRESS hit ROUTER requesting RESOURCE" // TBD/TODO would be great to log this as "DATE: ADDRESS hit ROUTER requesting RESOURCE"
console.log(" * Creating router for sitemap.xml"); console.log(now() + ": * Creating router for sitemap.xml");
router.get('/sitemap.xml', function (req, res) { router.get('/sitemap.xml', function (req, res) {
console.log("sitemap.xml being provided to", req.socket.remoteAddress) console.log(now() + ": sitemap.xml being provided to", req.socket.remoteAddress)
res.contentType('text/xml'); res.contentType('text/xml');
res.send(sitemap_xml); res.send(sitemap_xml);
}); });
// Provide human-usable sitemap links. // Provide human-usable sitemap links.
// TBD/TODO would be great to log this as "DATE: ADDRESS hit ROUTER requesting RESOURCE" // TBD/TODO would be great to log this as "DATE: ADDRESS hit ROUTER requesting RESOURCE"
console.log(" * Creating router for sitemap*"); console.log(now() + ": * Creating router for sitemap*");
router.get('/sitemap*', function (req, res) { router.get('/sitemap*', async function (req, res) {
console.log("sitemap.html being provided to", req.socket.remoteAddress) console.log(now() + ": sitemap.html being provided to", req.socket.remoteAddress)
res.send(sitemap_html); html = await mini(sitemap_html);
res.send(mini(html));
}); });
// Provide a robots.txt for where search engines should find things. // Provide a robots.txt for where search engines should find things.
console.log(" * Creating router for robots.txt"); console.log(now() + ": * Creating router for robots.txt");
router.get('/robots.txt', function (req, res) { router.get('/robots.txt', function (req, res) {
console.log("robots.txt being provided to", req.socket.remoteAddress) console.log(now() + ": robots.txt being provided to", req.socket.remoteAddress)
res.send(robots_txt); res.send(robots_txt);
}); });
// Return a resource from the files folder. // Return a resource from the files folder.
// TBD/TODO would be great to log this as "DATE: ADDRESS hit ROUTER requesting RESOURCE" // TBD/TODO would be great to log this as "DATE: ADDRESS hit ROUTER requesting RESOURCE"
console.log(" * Creating router for files"); console.log(now() + ": * Creating router for files");
router.get('/files*', function (req, res) { router.get('/files*', function (req, res) {
console.log("file response to", req.socket.remoteAddress, "asking for", req.url) console.log(now() + ": file response to", req.socket.remoteAddress, "asking for", req.url)
// Build variables. // Build variables.
const file = "." + req.path; const file = "." + req.path;
const extensions = req.path.split("."); const extensions = req.path.split(".");
const extension = extensions[extensions.length-1]; const extension = extensions[extensions.length-1];
let mime = getMIME(extension);
// Check extension and guess a MIME type.
let mime;
switch (extension) {
case "apk":
mime = "application/vnd.android.package-archive";
break;
case "jpg":
case "jpeg":
mime = "image/jpeg";
break;
case "png":
mime = "image/png";
break;
case "html":
mime = "text/html";
break;
case "zip":
mime = "application/zip";
break;
case "md":
case "txt":
case "csv":
mime = "text/*";
break;
default:
mime = "application/" + extension;
break;
}
// Return the file // Return the file
res.contentType(mime); res.contentType(mime);
@@ -263,28 +339,29 @@ async function main() {
}); });
// Originally a test, now a catch-all redirection to Home! // Originally a test, now a catch-all redirection to Home!
console.log(" * Creating router for redirection"); console.log(now() + ": * Creating router for redirection");
router.get('/*', function (req, res) { router.get('/*', async function (req, res) {
// WARNING: These are huge so only look when you need to. // WARNING: These are huge so only look when you need to.
//console.log(req); //console.log(req);
//console.log(res); //console.log(res);
console.log("*wildcard* replying to", req.socket.remoteAddress, "asking for", req.url) console.log(now() + ": *wildcard* replying to", req.socket.remoteAddress, "asking for", req.url)
let html = "" + execSync("./pages/home.php"); let html = "" + execSync("./pages/home.php");
stringsToRemove.forEach(string => { stringsToRemove.forEach(string => {
html = html.replace(string, ""); html = html.replace(string, "");
}); });
html = await mini(html);
res.send(html); res.send(html);
}); });
app.use('', router); app.use('', router);
console.log("...Adding Ports..."); console.log(now() + ": ...Adding Ports...");
ports.forEach(port => { ports.forEach(port => {
app.listen(port); app.listen(port);
console.log(" * Now listening on port " + port + "."); console.log(now() + ": * Now listening on port " + port + ".");
console.log(" * URL: http://localhost:" + port); console.log(now() + ": * URL: http://localhost:" + port);
}); });
console.log("Done! Now we wait..."); console.log(now() + ": Done! Now we wait...");
} }
//// Program Execution //// //// Program Execution ////
+2 -1
View File
@@ -1,6 +1,7 @@
{ {
"dependencies": { "dependencies": {
"express": ">=4.18.1 < 5.0.0" "express": ">=4.18.1 < 5.0.0",
"html-minifier-terser": "^7.2.0"
}, },
"scripts": { "scripts": {
"start": "./run.sh" "start": "./run.sh"
+22 -16
View File
@@ -38,7 +38,7 @@
convenient group: convenient group:
</p> </p>
<ul class="indent"><li> <ul class="indent"><li>
<a href="https://git.hyperling.com/me/env-obtainium-hyperling/raw/branch/prod/obtainium-export.json" <a href="https://git.hyperling.com/me/env-obtainium-hyperling/src/branch/prod/obtainium-export.json"
target="_blank" rel="noopener noreferrer" target="_blank" rel="noopener noreferrer"
> >
[obtainium-export.json] | Hyperling's Apps [obtainium-export.json] | Hyperling's Apps
@@ -57,15 +57,13 @@
section below, here is how to show the correct version numbers: section below, here is how to show the correct version numbers:
</p> </p>
<ul> <ul>
<li>Enable <i>"Sort by only the last segment of the link"</i>.</li>
<li> <li>
Set the <i>"Version string extraction RegEx"</i> field to: Set the <i>"Version string extraction RegEx"</i> field to:
<code>[0-9\.]+[0-9]+</code> <code>\d+\.\d+\.\d+</code>
</li> </li>
</ul> </ul>
<p> <p>
Otherwise, using <i>"Take first link"</i> ensures you get the That should be all that's needed as of November 2026.
proper update but will show pseudo versions.
</p> </p>
</div> </div>
@@ -108,7 +106,7 @@
>[fdroid.Hyperling.com]</a> >[fdroid.Hyperling.com]</a>
or or
<a href="https://fdroid.link/#https://fdroid.hyperling.com/fdroid/repo"> <a href="https://fdroid.link/#https://fdroid.hyperling.com/fdroid/repo">
[open directly with F-Droid]</a> [open with F-Droid]</a>
</li> </li>
</ul> </ul>
<p> <p>
@@ -133,12 +131,12 @@
src="https://git.hyperling.com/me/flutter-sunset-alarm/raw/branch/main/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png" src="https://git.hyperling.com/me/flutter-sunset-alarm/raw/branch/main/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png"
> >
<figcaption> <figcaption>
Sunset Timer Sunset Alarm
</figcaption> </figcaption>
</figure> </figure>
</a> </a>
<p> <p>
Set an alarm to go off X minutes before sunset. (Linux & Android) Set an alarm time based on sunset. (Linux & Android)
</p> </p>
<p> <p>
[<a href="https://git.hyperling.com/me/flutter-sunset-alarm/releases/download/latest/sunset_alarm-arm64-v8a-release.apk/" [<a href="https://git.hyperling.com/me/flutter-sunset-alarm/releases/download/latest/sunset_alarm-arm64-v8a-release.apk/"
@@ -155,7 +153,7 @@
target="_blank" rel="noopener noreferrer"> target="_blank" rel="noopener noreferrer">
<figure> <figure>
<img class="app-icon" loading="lazy" alt="<<expense.png>>" <img class="app-icon" loading="lazy" alt="<<expense.png>>"
src="/files/media/icons/expense.png" src="https://git.hyperling.com/me/flutter-expense-tracker/raw/branch/main/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png"
> >
<figcaption> <figcaption>
Recurring Expense Tracker Recurring Expense Tracker
@@ -168,10 +166,12 @@
(Linux & Android) (Linux & Android)
</p> </p>
<p> <p>
[<a href="https://git.hyperling.com/me/flutter-expense-tracker/releases/download/latest/ExpenseTracker.apk"
target="_blank" rel="noopener noreferrer">APK</a>]
[<a href="https://git.hyperling.com/me/flutter-expense-tracker/" [<a href="https://git.hyperling.com/me/flutter-expense-tracker/"
target="_blank" rel="noopener noreferrer">Source Code</a>] target="_blank" rel="noopener noreferrer">Source Code</a>]
[<a href="https://git.hyperling.com/me/flutter-expense-tracker/releases" [<a href="https://git.hyperling.com/me/flutter-expense-tracker/releases"
target="_blank" rel="noopener noreferrer">APKs / Obtainium URL</a>] target="_blank" rel="noopener noreferrer">Obtainium URL</a>]
</p> </p>
</div> </div>
@@ -237,7 +237,7 @@
target="_blank" rel="noopener noreferrer"> target="_blank" rel="noopener noreferrer">
<figure> <figure>
<img class="app-icon" loading="lazy" alt="<<t45mr.png>>" <img class="app-icon" loading="lazy" alt="<<t45mr.png>>"
src="/files/media/icons/t45mr.png" src="https://git.hyperling.com/me/android-45-minute-rule/raw/branch/main/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png"
> >
<figcaption> <figcaption>
45 Minute Rule 45 Minute Rule
@@ -250,10 +250,12 @@
<p> <p>
<!--[<a href="https://play.google.com/store/apps/details?id=com.hyperling.apps.the45minuterule" <!--[<a href="https://play.google.com/store/apps/details?id=com.hyperling.apps.the45minuterule"
target="_blank" rel="noopener noreferrer">Play Store</a>]--> target="_blank" rel="noopener noreferrer">Play Store</a>]-->
[<a href="https://git.hyperling.com/me/android-45-minute-rule/releases/download/latest/The45MinuteRule.apk"
target="_blank" rel="noopener noreferrer">APK</a>]
[<a href="https://git.hyperling.com/me/android-45-minute-rule/" [<a href="https://git.hyperling.com/me/android-45-minute-rule/"
target="_blank" rel="noopener noreferrer">Source Code</a>] target="_blank" rel="noopener noreferrer">Source Code</a>]
[<a href="https://git.hyperling.com/me/android-45-minute-rule/releases" [<a href="https://git.hyperling.com/me/android-45-minute-rule/releases"
target="_blank" rel="noopener noreferrer">APKs / Obtainium URL</a>] target="_blank" rel="noopener noreferrer">Obtainium URL</a>]
</p> </p>
</div> </div>
@@ -262,7 +264,7 @@
target="_blank" rel="noopener noreferrer"> target="_blank" rel="noopener noreferrer">
<figure> <figure>
<img class="app-icon" loading="lazy" alt="<<tictactoe.png>>" <img class="app-icon" loading="lazy" alt="<<tictactoe.png>>"
src="/files/media/icons/tictactoe.png" src="https://git.hyperling.com/me/android-tictactoe/raw/branch/main/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png"
> >
<figcaption> <figcaption>
Tic-Tac-Toe Tic-Tac-Toe
@@ -274,10 +276,12 @@
Written to practice Kotlin/Compose. Written to practice Kotlin/Compose.
</p> </p>
<p> <p>
[<a href="https://git.hyperling.com/me/android-tictactoe/releases/download/latest/TicTacToe.apk"
target="_blank" rel="noopener noreferrer">APK</a>]
[<a href="https://git.hyperling.com/me/android-tictactoe/" [<a href="https://git.hyperling.com/me/android-tictactoe/"
target="_blank" rel="noopener noreferrer">Source Code</a>] target="_blank" rel="noopener noreferrer">Source Code</a>]
[<a href="https://git.hyperling.com/me/android-tictactoe/releases" [<a href="https://git.hyperling.com/me/android-tictactoe/releases"
target="_blank" rel="noopener noreferrer">APKs / Obtainium URL</a>] target="_blank" rel="noopener noreferrer">Obtainium URL</a>]
</p> </p>
</div> </div>
@@ -287,7 +291,7 @@
> >
<figure> <figure>
<img class="app-icon" loading="lazy" alt="<<hypergames.png>>" <img class="app-icon" loading="lazy" alt="<<hypergames.png>>"
src="/files/media/icons/hypergames.png" src="https://git.hyperling.com/me/android-hypergames/raw/branch/dev/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png"
> >
<figcaption> <figcaption>
HyperGames HyperGames
@@ -301,10 +305,12 @@
<p> <p>
<!--[<a href="https://play.google.com/store/apps/details?id=apps.hyperling.com.platformer" <!--[<a href="https://play.google.com/store/apps/details?id=apps.hyperling.com.platformer"
target="_blank" rel="noopener noreferrer">Play Store</a>]--> target="_blank" rel="noopener noreferrer">Play Store</a>]-->
[<a href="https://git.hyperling.com/me/android-hypergames/releases/download/latest/HyperGames.apk"
target="_blank" rel="noopener noreferrer">APK</a>]
[<a href="https://git.hyperling.com/me/android-hypergames/" [<a href="https://git.hyperling.com/me/android-hypergames/"
target="_blank" rel="noopener noreferrer">Source Code</a>] target="_blank" rel="noopener noreferrer">Source Code</a>]
[<a href="https://git.hyperling.com/me/android-hypergames/releases" [<a href="https://git.hyperling.com/me/android-hypergames/releases"
target="_blank" rel="noopener noreferrer">APKs / Obtainium URL</a>] target="_blank" rel="noopener noreferrer">Obtainium URL</a>]
</p> </p>
</div> </div>
+6 -2
View File
@@ -29,8 +29,12 @@
height: 237px; /* 69*3[banner stripes] + 15*2[padding top+bot] */ height: 237px; /* 69*3[banner stripes] + 15*2[padding top+bot] */
} }
.banner-image01 { .banner-image01 {
background-image: url("/files/media/icons/home.jpg"); background-image: url("/banner-image01.jpg");
} }
.banner-image02 { .banner-image02 {
background-image: url("/files/media/icons/contact.jpg"); background-image: url("/banner-image02.jpg");
}
.banner-anchor {
text-decoration: none;
} }
+19 -4
View File
@@ -5,14 +5,15 @@
$GLOBALS["SHOW_BANNER_PICS"] = true; $GLOBALS["SHOW_BANNER_PICS"] = true;
} }
$show_pics = $GLOBALS["SHOW_BANNER_PICS"]; $show_pics = $GLOBALS["SHOW_BANNER_PICS"];
$banner_width = $show_pics ? 6 : 12; $banner_width_desktop = $show_pics ? 6 : 12;
$banner_width_mobile = $show_pics ? 8 : 12;
?> ?>
<a href='/about/'> <a class="banner-anchor" href='/'>
<div class="row"> <div class="row banner-desktop">
<?php if ($show_pics) echo ' <?php if ($show_pics) echo '
<div class="col-3 header center banner-image banner-image01"></div> <div class="col-3 header center banner-image banner-image01"></div>
';?> ';?>
<div class="col-<?php echo "$banner_width"; ?> header"> <div class="col-<?php echo "$banner_width_desktop"; ?> header">
<div class="banner-top">Peace</div> <div class="banner-top">Peace</div>
<div class="banner-middle">Love</div> <div class="banner-middle">Love</div>
<div class="banner-bottom">Happiness</div> <div class="banner-bottom">Happiness</div>
@@ -21,4 +22,18 @@
<div class="col-3 header center banner-image banner-image02"></div> <div class="col-3 header center banner-image banner-image02"></div>
';?> ';?>
</div> </div>
<div class="row banner-mobile">
<?php if ($show_pics) echo '
<div class="col-2 header center banner-image banner-image01"></div>
';?>
<div class="col-<?php echo "$banner_width_mobile"; ?> header">
<div class="banner-top">Peace</div>
<div class="banner-middle">Love</div>
<div class="banner-bottom">Happiness</div>
</div>
<?php if ($show_pics) echo '
<div class="col-2 header center banner-image banner-image02"></div>
';?>
</div>
</a> </a>
+21
View File
@@ -0,0 +1,21 @@
<!-- Old, no longer needed
<link rel="icon" sizes="32x32" href="/favicon.ico">
<link rel="icon" sizes="192x192" href="/favicon.ico">
-->
<!-- https://favicon.io, added the ?v=20261102 manually. -->
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png?v=20261102">
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png?v=20261102">
<!-- Not needed, covered in the realfavicongenerator section.
<link rel="manifest" href="/site.webmanifest">
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
-->
<!-- https://realfavicongenerator.net , added the sizes=any manually. -->
<link rel="icon" type="image/png" href="/favicon-96x96.png?v=20261102" sizes="96x96" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg?v=20261102" sizes="any" />
<link rel="shortcut icon" href="/favicon.ico?v=20261102" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png?v=20261102" />
<meta name="apple-mobile-web-app-title" content="Hyperling" />
<link rel="manifest" href="/site.webmanifest?v=20261102" />
+7 -7
View File
@@ -20,14 +20,14 @@
/* */ /* */
code, pre, .mono { code, pre, .mono {
font-family: font-family:
ui-monospace, ui-monospace,
SFMono-Regular, SFMono-Regular,
Menlo, Monaco, Menlo, Monaco,
Consolas, Consolas,
"Liberation Mono", "Liberation Mono",
"Courier New", "Courier New",
monospace monospace
; ;
} }
+3 -6
View File
@@ -10,12 +10,9 @@
<html lang="en"> <html lang="en">
<head> <head>
<title>Hyperling<?php echo($GLOBALS["HEADER_TITLE"]); ?></title> <title>Hyperling<?php echo($GLOBALS["HEADER_TITLE"]); ?></title>
<link rel="icon" sizes="32x32" <?php
href="/files/media/icons/favicon.ico" include "favicon.php";
> ?>
<link rel="icon" sizes="192x192"
href="/files/media/icons/favicon.ico"
>
<!-- CSS --> <!-- CSS -->
<style> <style>
<?php include "main.css"; ?> <?php include "main.css"; ?>
+30 -12
View File
@@ -38,22 +38,40 @@
/* Handle mobile devices a bit better, making columns break out as rows. */ /* Handle mobile devices a bit better, making columns break out as rows. */
.banner-desktop { display: flex; }
.banner-mobile { display: none; }
/* Phones */ /* Phones */
@media (max-width: 767px) { @media (max-width: 767px) {
[class*="col-"] { [class*="col-"] {
float: none;
display: block;
width: 100%; width: 100%;
} }
} }
/* Tablets */ /* Tablets */
@media (min-width: 768px) and (max-width: 1023px) { @media (min-width: 768px) and (max-width: 1023px) {
.col-1, .col-2, .col-3, .col-4, .col-5, .col-6 {
width: 50%;
}
.col-7, .col-8, .col-9, .col-10, .col-11, .col-12 {
width: 100%;
}
}
/* Both Mobiles */
@media (max-width: 1023px) {
[class*="col-"] { [class*="col-"] {
float: none; float: none;
display: block; display: block;
width: 50%;
} }
.banner-image {display: none; }
.banner-desktop { display: none; }
.banner-mobile { display: flex; }
}
/* Desktop */
@media (min-width: 1024px) {
} }
/** Make the menu items centered and layout horizontal. **/ /** Make the menu items centered and layout horizontal. **/
@@ -66,12 +84,12 @@
.menu-list { .menu-list {
text-align: center; text-align: center;
list-style-type: none; list-style-type: none;
padding-left: 0px;
} }
.menu_item { .menu-item {
display: inline-block; display: inline-block;
padding: 0px;
white-space: nowrap; white-space: nowrap;
margin: 0.1rem;
padding: 0.1rem;
} }
/** Be able to position anything easily. **/ /** Be able to position anything easily. **/
@@ -92,8 +110,8 @@
/** Extra objects which need borders **/ /** Extra objects which need borders **/
.border { .border {
border: 1px solid #131313; border: 1px solid #131313;
border-radius: 4px; border-radius: 4px;
} }
/** Explicitly remove border from some objects. **/ /** Explicitly remove border from some objects. **/
@@ -140,10 +158,10 @@
overflow-x: scroll; overflow-x: scroll;
overflow-y: hidden; overflow-y: hidden;
padding: 10px; padding: 10px;
border: 2px solid #131313; border: 2px solid #131313;
border-radius: 8px; border-radius: 8px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
} }
body { body {
+18 -28
View File
@@ -36,55 +36,45 @@
</div> </div>
--> -->
<div class="row header menu center"> <div class="menu row header center">
<p class="col-4"> <div class="menu-list col-12">
<strong>Main</strong> <strong>Main:</strong>
<br> <a class="menu-item" href="/apps/">Apps</a>
<br> <a class="menu-item" href="/about/">About</a>
<a class="menu-item col-3 border" href="/apps/">Apps</a> <a class="menu-item" href="/contact/">Contact</a>
<a class="menu-item col-3 border" href="/about/">About</a> <a class="menu-item" href="/support/">Support</a>
<a class="menu-item col-3 border" href="/contact/">Contact</a> <strong>Media:</strong>
<a class="menu-item col-3 border" href="/support/">Support</a> <a class="menu-item"
</p>
<p class="col-4">
<strong>Media</strong>
<br>
<br>
<a class="menu-item col-3 border"
href="https://git.hyperling.com/me/" href="https://git.hyperling.com/me/"
target="_blank" rel="noopener noreferrer" target="_blank" rel="noopener noreferrer"
>Code</a> >Code</a>
<a class="menu-item col-3 border" <a class="menu-item"
href="https://works.hyperling.com/categories/books/" href="https://works.hyperling.com/categories/books/"
target="_blank" rel="noopener noreferrer" target="_blank" rel="noopener noreferrer"
>Books</a> >Books</a>
<a class="menu-item col-3 border" <a class="menu-item"
href="https://works.hyperling.com/videos/" href="https://works.hyperling.com/videos/"
target="_blank" rel="noopener noreferrer" target="_blank" rel="noopener noreferrer"
>Videos</a> >Videos</a>
<a class="menu-item col-3 border" <a class="menu-item"
href="/photos/" href="/photos/"
>Photos</a> >Photos</a>
</p> <strong>Blog:</strong>
<p class="col-4"> <a class="menu-item"
<strong>Blog</strong>
<br>
<br>
<a class="menu-item col-3 border"
href="https://blog.hyperling.com/categories/articles/" href="https://blog.hyperling.com/categories/articles/"
target="_blank" rel="noopener noreferrer" target="_blank" rel="noopener noreferrer"
>Articles</a> >Articles</a>
<a class="menu-item col-3 border" <a class="menu-item"
href="https://recipes.hyperling.com/categories/recipes/" href="https://recipes.hyperling.com/categories/recipes/"
target="_blank" rel="noopener noreferrer" target="_blank" rel="noopener noreferrer"
>Recipes</a> >Recipes</a>
<a class="menu-item col-3 border" <a class="menu-item"
href="https://blog.hyperling.com/categories/musings/" href="https://blog.hyperling.com/categories/musings/"
target="_blank" rel="noopener noreferrer" target="_blank" rel="noopener noreferrer"
>Poetry</a> >Poetry</a>
<a class="menu-item col-3 border" <a class="menu-item"
href="https://recipes.hyperling.com/categories/guides/" href="https://recipes.hyperling.com/categories/guides/"
target="_blank" rel="noopener noreferrer" target="_blank" rel="noopener noreferrer"
>Guides</a> >Guides</a>
</p> </div>
</div> </div>
+14 -4
View File
@@ -25,6 +25,12 @@
target="_blank" rel="noopener noreferrer" target="_blank" rel="noopener noreferrer"
>Find it here</a>! >Find it here</a>!
</p> </p>
<p>
Watch the announcement:
<a href="https://odysee.com/@HyperVegan:2/20260707-HyperVegan-FDroidRepo:a"
target="_blank" rel="noopener noreferrer"
>HyperVegan: "F-Droid Repository"</a>
</p>
</div> </div>
</div> </div>
@@ -46,9 +52,10 @@
</p> </p>
";?> ";?>
<p> <p>
Watch the announcement:
<a href="https://odysee.com/@HyperVegan:2/20251003-Announcements:0" <a href="https://odysee.com/@HyperVegan:2/20251003-Announcements:0"
target="_blank" rel="noopener noreferrer" target="_blank" rel="noopener noreferrer"
>Watch the announcement here | (HyperVegan: "Announcements: Skipped Midfest, Voluntary Sovereignty eBook, CheapRVLiving Interview")</a> >HyperVegan: "Announcements: Skipped Midfest, Voluntary Sovereignty eBook, CheapRVLiving Interview"</a>
</p> </p>
</div> </div>
</div> </div>
@@ -71,9 +78,10 @@
</p> </p>
";?> ";?>
<p> <p>
Watch the announcement:
<a href="https://odysee.com/@HyperVegan:2/20250402-HyperVegan-ExpenseTrackerApp:4" <a href="https://odysee.com/@HyperVegan:2/20250402-HyperVegan-ExpenseTrackerApp:4"
target="_blank" rel="noopener noreferrer" target="_blank" rel="noopener noreferrer"
>Watch the announcement here | (HyperVegan: "Expense Tracker App")</a> >HyperVegan: "Expense Tracker App"</a>
</p> </p>
</div> </div>
</div> </div>
@@ -98,9 +106,10 @@
</p> </p>
";?> ";?>
<p> <p>
Watch the announcement:
<a href="https://odysee.com/@HyperVegan:2/20240306_MyHealthProtocol-Book:9" <a href="https://odysee.com/@HyperVegan:2/20240306_MyHealthProtocol-Book:9"
target="_blank" rel="noopener noreferrer" target="_blank" rel="noopener noreferrer"
>Watch the announcement here | (HyperVegan: "My Health Protocol | eBook")</a> >HyperVegan: "My Health Protocol | eBook"</a>
</p> </p>
</div> </div>
</div> </div>
@@ -122,9 +131,10 @@
</p> </p>
";?> ";?>
<p> <p>
Watch the announcement:
<a href="https://odysee.com/@HyperVegan:2/20240401_NewYearNewApp:1" <a href="https://odysee.com/@HyperVegan:2/20240401_NewYearNewApp:1"
target="_blank" rel="noopener noreferrer" target="_blank" rel="noopener noreferrer"
>Watch the announcement here | (HyperVegan: "New Year, New App")</a> >HyperVegan: "New Year, New App"</a>
</p> </p>
</div> </div>
</div> </div>