Added docker compose file and as well as made changes to LICENSE and how
settings panel handles things. Also removed open in new tab. CORS blocks any ability to do anything cool with iframes so it will likely have to be reserved for a extension or something of that kind.
This commit is contained in:
parent
ad05afbb83
commit
1282bf2bda
6 changed files with 765 additions and 311 deletions
21
LICENSE
Normal file
21
LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2024 Michael Maurakis
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
81
content.js
81
content.js
|
@ -1,12 +1,77 @@
|
|||
// Create the button
|
||||
const button = document.createElement('button');
|
||||
button.id = 'fumbleButton';
|
||||
button.textContent = 'Fumble!';
|
||||
// Create the floating button
|
||||
const floatingButton = document.createElement('button');
|
||||
floatingButton.id = 'openInNewTab';
|
||||
floatingButton.className = 'floating-button';
|
||||
floatingButton.title = 'Open in new window';
|
||||
floatingButton.innerHTML = `
|
||||
<svg viewBox="0 0 24 24" width="24" height="24">
|
||||
<path fill="currentColor" d="M19 19H5V5h7V3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14c1.1 0 2-.9 2-2v-7h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z"/>
|
||||
</svg>
|
||||
`;
|
||||
|
||||
// Add click handler
|
||||
button.addEventListener('click', () => {
|
||||
window.location.href = 'https://wiby.me/surprise/';
|
||||
floatingButton.addEventListener('click', () => {
|
||||
window.open(window.location.href, '_blank');
|
||||
});
|
||||
|
||||
// Add button to page
|
||||
document.body.appendChild(button);
|
||||
// Add styles
|
||||
const style = document.createElement('style');
|
||||
style.textContent = `
|
||||
.floating-button {
|
||||
position: fixed;
|
||||
right: 20px;
|
||||
top: 20px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
background: rgba(255, 69, 0, 0.9);
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
opacity: 0;
|
||||
transform: scale(0.9);
|
||||
transition: opacity 0.3s ease, transform 0.3s ease, background-color 0.3s ease;
|
||||
z-index: 999999;
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.floating-button svg {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
body:hover .floating-button {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
.floating-button:hover {
|
||||
background: rgba(255, 69, 0, 1);
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
@media (hover: none) {
|
||||
.floating-button {
|
||||
opacity: 0.9;
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
.floating-button:hover {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.floating-button {
|
||||
bottom: 20px;
|
||||
top: auto;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
// Add elements to page
|
||||
document.head.appendChild(style);
|
||||
document.body.appendChild(floatingButton);
|
33
docker-compose.yml
Normal file
33
docker-compose.yml
Normal file
|
@ -0,0 +1,33 @@
|
|||
services:
|
||||
tailscale-fumble:
|
||||
image: tailscale/tailscale:latest
|
||||
hostname: fumblearound # Assign a name this so that you can set your domain name ex. https://tail.penguin-dory.ts.net
|
||||
ports: # Uncomment the next two lines to expose the container to your local area network.
|
||||
- 80:80
|
||||
environment:
|
||||
- TS_AUTHKEY= # BE SURE TO ADD YOUR KEY AS NOTHING WILL WORK
|
||||
#- TS_EXTRA_ARGS=--advertise-tags=tag:container # Uncomment this if you want to add a tag to your node. Useful for access control lists.
|
||||
#- TS_FUNNEL_CONFIG=/config/funnel.json # Uncomment this and comment the next line if you want to host your app publicly.
|
||||
#- TS_SERVE_CONFIG=/config/serve.json # Comment this line if you uncomment the one above.
|
||||
- TS_STATE_DIR=/var/lib/tailscale
|
||||
- TS_USERSPACE=true
|
||||
- TS_ACCEPT_DNS=false
|
||||
volumes:
|
||||
- ${PWD}/tailserve-config:/config
|
||||
- ${PWD}/tailscale/state:/var/lib/tailscale
|
||||
- /dev/net/tun:/dev/net/tun
|
||||
cap_add:
|
||||
- net_admin
|
||||
- sys_module
|
||||
restart: unless-stopped
|
||||
|
||||
fumblearound:
|
||||
image: nginx:alpine
|
||||
volumes:
|
||||
- .:/usr/share/nginx/html
|
||||
- ./nginx.conf:/etc/nginx/conf.d/default.conf
|
||||
restart: unless-stopped
|
||||
network_mode: service:tailscale-fumble
|
||||
depends_on:
|
||||
- tailscale-fumble
|
||||
|
319
index.html
319
index.html
|
@ -1,33 +1,43 @@
|
|||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<meta charset="UTF-8" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"
|
||||
/>
|
||||
<title>FumbleAround</title>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<link rel="icon" type="image/png" href="./Assets/smily.png">
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
<link rel="icon" type="image/png" href="/Assets/smily.png" />
|
||||
|
||||
<!-- PWA Meta Tags -->
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
||||
<meta name="apple-mobile-web-app-title" content="FumbleAround">
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
<meta name="application-name" content="FumbleAround">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta
|
||||
name="apple-mobile-web-app-status-bar-style"
|
||||
content="black-translucent"
|
||||
/>
|
||||
<meta name="apple-mobile-web-app-title" content="FumbleAround" />
|
||||
<meta name="mobile-web-app-capable" content="yes" />
|
||||
<meta name="theme-color" content="#ffffff" />
|
||||
<meta name="application-name" content="FumbleAround" />
|
||||
|
||||
<!-- Apple Touch Icons -->
|
||||
<link rel="apple-touch-icon" href="./Assets/smily.png">
|
||||
<link rel="apple-touch-icon" sizes="152x152" href="./Assets/smily.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="./Assets/smily.png">
|
||||
<link rel="apple-touch-icon" sizes="167x167" href="./Assets/smily.png">
|
||||
<link rel="apple-touch-icon" href="/Assets/smily.png" />
|
||||
<link rel="apple-touch-icon" sizes="152x152" href="/Assets/smily.png" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/Assets/smily.png" />
|
||||
<link rel="apple-touch-icon" sizes="167x167" href="/Assets/smily.png" />
|
||||
|
||||
<!-- Web Manifest -->
|
||||
<link rel="manifest" href="manifest.json">
|
||||
<link rel="manifest" href="manifest.json" />
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<div class="logo">
|
||||
<img src="./Assets/smily.png" alt="FumbleAround Logo" class="logo-icon">
|
||||
<img
|
||||
src="/Assets/smily.png"
|
||||
alt="FumbleAround Logo"
|
||||
class="logo-icon"
|
||||
/>
|
||||
FumbleAround
|
||||
</div>
|
||||
<div class="header-buttons">
|
||||
|
@ -35,14 +45,20 @@
|
|||
<button id="donateButton">💝</button>
|
||||
<button id="githubButton">
|
||||
<svg viewBox="0 0 24 24" width="24" height="24">
|
||||
<path fill="currentColor" d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z"/>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="app-controls">
|
||||
<button id="settingsButton">
|
||||
<svg viewBox="0 0 24 24" width="24" height="24">
|
||||
<path fill="currentColor" d="M19.14 12.94c.04-.3.06-.61.06-.94 0-.32-.02-.64-.07-.94l2.03-1.58c.18-.14.23-.41.12-.61l-1.92-3.32c-.12-.22-.37-.29-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54c-.04-.24-.24-.41-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.05.3-.07.62-.07.94s.02.64.07.94l-2.03 1.58c-.18.14-.23.41-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z"/>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M19.14 12.94c.04-.3.06-.61.06-.94 0-.32-.02-.64-.07-.94l2.03-1.58c.18-.14.23-.41.12-.61l-1.92-3.32c-.12-.22-.37-.29-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54c-.04-.24-.24-.41-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.05.3-.07.62-.07.94s.02.64.07.94l-2.03 1.58c-.18.14-.23.41-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<button id="fumbleButton">Fumble!</button>
|
||||
|
@ -58,15 +74,37 @@
|
|||
</div>
|
||||
<div class="settings-section">
|
||||
<h3>Display</h3>
|
||||
<div class="setting-item">
|
||||
<div class="setting-item" id="darkModeToggleArea">
|
||||
<label>Dark Mode</label>
|
||||
<button id="darkModeToggle">🌙</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-section">
|
||||
<h3>Actions</h3>
|
||||
<div class="setting-item">
|
||||
<button id="openInNewWindow" class="settings-button">Open in New Window</button>
|
||||
<h3>Interactions</h3>
|
||||
<div class="setting-item interactions-list">
|
||||
<div class="interaction-item">
|
||||
<div class="interaction-icon">🖱️</div>
|
||||
<div class="interaction-details">
|
||||
<h4>Fumble Button</h4>
|
||||
<p>Click the "Fumble!" button to discover a new website</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="interaction-item">
|
||||
<div class="interaction-icon">🔄</div>
|
||||
<div class="interaction-details">
|
||||
<h4>Page Refresh</h4>
|
||||
<p>Refresh the page to load a new website</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="interaction-item">
|
||||
<div class="interaction-icon">📱</div>
|
||||
<div class="interaction-details">
|
||||
<h4>Shake to Fumble</h4>
|
||||
<p>
|
||||
On mobile devices, shake your phone to discover a new site
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-section">
|
||||
|
@ -74,17 +112,66 @@
|
|||
<div class="setting-item about-section">
|
||||
<div class="about-content">
|
||||
<div class="about-logo">
|
||||
<img src="./Assets/smily.png" alt="FumbleAround Logo">
|
||||
<img src="/Assets/smily.png" alt="FumbleAround Logo" />
|
||||
<h4>FumbleAround</h4>
|
||||
</div>
|
||||
<div class="about-text">
|
||||
<p>Discover the hidden gems of the internet, powered by Wiby.me's search engine.</p>
|
||||
<p>
|
||||
Discover the hidden gems of the internet, powered by Wiby.me's
|
||||
search engine.
|
||||
</p>
|
||||
<div class="disclaimer">
|
||||
<h5>Disclaimer</h5>
|
||||
<p>We are not responsible for the content that appears. Use at your own discretion.</p>
|
||||
<p>
|
||||
We are not responsible for the content that appears. Use at
|
||||
your own discretion.
|
||||
</p>
|
||||
</div>
|
||||
<div class="tribute">
|
||||
<p>A homage to the classic StumbleUpon, reimagined for the modern web.</p>
|
||||
<p>
|
||||
A homage to the classic StumbleUpon, reimagined for the
|
||||
modern web.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-section">
|
||||
<h3>License</h3>
|
||||
<div class="setting-item license-section">
|
||||
<div class="license-content">
|
||||
<p>FumbleAround - A modern web discovery tool</p>
|
||||
<p>Copyright (c) 2024 Michael Maurakis</p>
|
||||
<p>Licensed under the MIT License</p>
|
||||
<div class="license-details">
|
||||
<button class="license-toggle">View Full License</button>
|
||||
<div class="full-license" style="display: none">
|
||||
<p>
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the
|
||||
Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute,
|
||||
sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
</p>
|
||||
<p>
|
||||
The above copyright notice and this permission notice shall
|
||||
be included in all copies or substantial portions of the
|
||||
Software.
|
||||
</p>
|
||||
<p>
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
|
||||
KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
||||
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
|
||||
OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -95,14 +182,16 @@
|
|||
|
||||
<div id="warningModal" class="modal warning-modal">
|
||||
<div class="modal-content">
|
||||
<span class="close-button" style="display: none;">×</span>
|
||||
<span class="close-button" style="display: none">×</span>
|
||||
<h2>⚠️ Slow Down!</h2>
|
||||
<p>Clicking too quickly may get you flagged as spam by Wiby.me.</p>
|
||||
<p>Please wait a moment between fumbles.</p>
|
||||
<div id="cooldownTimer" class="cooldown-timer">
|
||||
You can close this warning in: <span id="cooldownSeconds">5</span>s
|
||||
</div>
|
||||
<button id="warningCloseBtn" class="warning-close-btn" disabled>I Understand</button>
|
||||
<button id="warningCloseBtn" class="warning-close-btn" disabled>
|
||||
I Understand
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -117,18 +206,17 @@
|
|||
|
||||
<script>
|
||||
if (performance.navigation.type === performance.navigation.TYPE_RELOAD) {
|
||||
if (sessionStorage.getItem('hasVisited')) {
|
||||
if (sessionStorage.getItem("hasVisited")) {
|
||||
window.stop();
|
||||
requestAnimationFrame(() => fumble());
|
||||
}
|
||||
} else if (!sessionStorage.getItem('hasVisited')) {
|
||||
sessionStorage.setItem('hasVisited', 'true');
|
||||
} else if (!sessionStorage.getItem("hasVisited")) {
|
||||
sessionStorage.setItem("hasVisited", "true");
|
||||
}
|
||||
|
||||
let lastFumbleTime = 0;
|
||||
const cooldownPeriod = 5000; // 5 seconds cooldown
|
||||
let cooldownTimer = null;
|
||||
let currentPageUrl = null; // Add this to store the current URL
|
||||
|
||||
const fumble = () => {
|
||||
const currentTime = Date.now();
|
||||
|
@ -136,10 +224,10 @@
|
|||
|
||||
if (timeSinceLastFumble < cooldownPeriod) {
|
||||
// Show warning modal
|
||||
const warningModal = document.getElementById('warningModal');
|
||||
const cooldownSeconds = document.getElementById('cooldownSeconds');
|
||||
const closeBtn = document.getElementById('warningCloseBtn');
|
||||
warningModal.classList.add('show');
|
||||
const warningModal = document.getElementById("warningModal");
|
||||
const cooldownSeconds = document.getElementById("cooldownSeconds");
|
||||
const closeBtn = document.getElementById("warningCloseBtn");
|
||||
warningModal.classList.add("show");
|
||||
closeBtn.disabled = true;
|
||||
|
||||
// Update countdown timer
|
||||
|
@ -151,7 +239,7 @@
|
|||
remainingTime--;
|
||||
|
||||
if (remainingTime <= 0) {
|
||||
cooldownSeconds.textContent = '0';
|
||||
cooldownSeconds.textContent = "0";
|
||||
closeBtn.disabled = false;
|
||||
clearInterval(cooldownTimer);
|
||||
} else {
|
||||
|
@ -163,28 +251,27 @@
|
|||
}
|
||||
|
||||
lastFumbleTime = currentTime;
|
||||
const frame = document.getElementById('contentFrame');
|
||||
frame.src = 'https://wiby.me/surprise/';
|
||||
const frame = document.getElementById("contentFrame");
|
||||
frame.src = "https://wiby.me/surprise/";
|
||||
|
||||
// Wait for the page to load then focus
|
||||
frame.onload = () => {
|
||||
// Check if we landed on a blocked/error page
|
||||
try {
|
||||
// Store the actual URL after redirect
|
||||
currentPageUrl = frame.contentWindow.location.href;
|
||||
|
||||
const title = frame.contentWindow.document.title.toLowerCase();
|
||||
if (title.includes('blocked') ||
|
||||
title.includes('error') ||
|
||||
title.includes('refused') ||
|
||||
title.includes('cannot') ||
|
||||
title.includes('denied')) {
|
||||
currentPageUrl = null; // Reset if we hit an error
|
||||
if (
|
||||
title.includes("blocked") ||
|
||||
title.includes("error") ||
|
||||
title.includes("refused") ||
|
||||
title.includes("cannot") ||
|
||||
title.includes("denied")
|
||||
) {
|
||||
// Try again if we hit an error page
|
||||
fumble();
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
// Can't access due to CORS
|
||||
currentPageUrl = null;
|
||||
// Can't access title due to CORS - assume page is OK
|
||||
}
|
||||
|
||||
frame.focus();
|
||||
|
@ -201,71 +288,87 @@
|
|||
};
|
||||
};
|
||||
|
||||
document.getElementById('fumbleButton').addEventListener('click', fumble);
|
||||
document.getElementById("fumbleButton").addEventListener("click", fumble);
|
||||
|
||||
// Dark mode toggle
|
||||
const darkModeToggle = document.getElementById('darkModeToggle');
|
||||
const darkModeToggle = document.getElementById("darkModeToggle");
|
||||
const darkModeToggleArea = document.getElementById("darkModeToggleArea");
|
||||
const body = document.body;
|
||||
|
||||
// Check for saved preference
|
||||
if (localStorage.getItem('darkMode') === 'true') {
|
||||
body.classList.add('dark-mode');
|
||||
darkModeToggle.textContent = '☀️';
|
||||
if (localStorage.getItem("darkMode") === "true") {
|
||||
body.classList.add("dark-mode");
|
||||
darkModeToggle.textContent = "☀️";
|
||||
}
|
||||
|
||||
darkModeToggle.addEventListener('click', () => {
|
||||
body.classList.toggle('dark-mode');
|
||||
const isDark = body.classList.contains('dark-mode');
|
||||
darkModeToggle.textContent = isDark ? '☀️' : '🌙';
|
||||
localStorage.setItem('darkMode', isDark);
|
||||
});
|
||||
const toggleDarkMode = () => {
|
||||
body.classList.toggle("dark-mode");
|
||||
const isDark = body.classList.contains("dark-mode");
|
||||
darkModeToggle.textContent = isDark ? "☀️" : "🌙";
|
||||
localStorage.setItem("darkMode", isDark);
|
||||
};
|
||||
|
||||
darkModeToggle.addEventListener("click", toggleDarkMode);
|
||||
darkModeToggleArea.addEventListener("click", toggleDarkMode);
|
||||
|
||||
// Add GitHub button click handler
|
||||
document.getElementById('githubButton').addEventListener('click', () => {
|
||||
window.open('https://git.mauix.bio/michael/FumbleAround', '_blank');
|
||||
document.getElementById("githubButton").addEventListener("click", () => {
|
||||
window.open("https://git.mauix.bio/michael/FumbleAround", "_blank");
|
||||
});
|
||||
|
||||
// Add donate button click handler
|
||||
document.getElementById('donateButton').addEventListener('click', () => {
|
||||
window.open('https://wiby.me/donate/', '_blank');
|
||||
document.getElementById("donateButton").addEventListener("click", () => {
|
||||
window.open("https://wiby.me/donate/", "_blank");
|
||||
});
|
||||
|
||||
// Add settings button click handler
|
||||
// Update the settings panel click handlers
|
||||
const settingsPanel = document.getElementById('settingsPanel');
|
||||
const settingsButton = document.getElementById('settingsButton');
|
||||
const closeSettings = document.querySelector('.close-settings');
|
||||
const settingsContent = settingsPanel.querySelector('.settings-content');
|
||||
|
||||
settingsButton.addEventListener('click', () => {
|
||||
const closeSettingsPanel = () => {
|
||||
settingsPanel.classList.remove('show');
|
||||
};
|
||||
|
||||
settingsButton.addEventListener('click', (event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
settingsPanel.classList.add('show');
|
||||
});
|
||||
|
||||
closeSettings.addEventListener('click', () => {
|
||||
settingsPanel.classList.remove('show');
|
||||
});
|
||||
closeSettings.addEventListener('click', closeSettingsPanel);
|
||||
|
||||
// Update the open in new window handler
|
||||
document.getElementById('openInNewWindow').addEventListener('click', () => {
|
||||
if (currentPageUrl) {
|
||||
window.open(currentPageUrl, '_blank');
|
||||
settingsPanel.classList.remove('show');
|
||||
// Handle clicks on the document
|
||||
document.addEventListener('click', (event) => {
|
||||
if (settingsPanel.classList.contains('show') &&
|
||||
!settingsContent.contains(event.target) &&
|
||||
!settingsButton.contains(event.target)) {
|
||||
closeSettingsPanel();
|
||||
}
|
||||
});
|
||||
|
||||
// Update warning modal close handler
|
||||
const warningModal = document.getElementById('warningModal');
|
||||
const warningCloseBtn = document.getElementById('warningCloseBtn');
|
||||
// Prevent clicks inside settings from closing
|
||||
settingsContent.addEventListener('click', (event) => {
|
||||
event.stopPropagation();
|
||||
});
|
||||
|
||||
warningCloseBtn.addEventListener('click', () => {
|
||||
// Update warning modal close handler
|
||||
const warningModal = document.getElementById("warningModal");
|
||||
const warningCloseBtn = document.getElementById("warningCloseBtn");
|
||||
|
||||
warningCloseBtn.addEventListener("click", () => {
|
||||
if (!warningCloseBtn.disabled) {
|
||||
warningModal.classList.remove('show');
|
||||
warningModal.classList.remove("show");
|
||||
if (cooldownTimer) clearInterval(cooldownTimer);
|
||||
}
|
||||
});
|
||||
|
||||
// Remove the click-outside-to-close functionality for warning modal
|
||||
window.addEventListener('click', (event) => {
|
||||
if (event.target === warningModal) { // Only for warning modal
|
||||
warningModal.classList.remove('show');
|
||||
window.addEventListener("click", (event) => {
|
||||
if (event.target === warningModal) {
|
||||
// Only for warning modal
|
||||
warningModal.classList.remove("show");
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -288,13 +391,14 @@
|
|||
const deltaY = Math.abs(current.y - lastY);
|
||||
const deltaZ = Math.abs(current.z - lastZ);
|
||||
|
||||
if (((deltaX > shakeThreshold && deltaY > shakeThreshold) ||
|
||||
if (
|
||||
((deltaX > shakeThreshold && deltaY > shakeThreshold) ||
|
||||
(deltaX > shakeThreshold && deltaZ > shakeThreshold) ||
|
||||
(deltaY > shakeThreshold && deltaZ > shakeThreshold)) &&
|
||||
(currentTime - lastShake > shakeTimeout)) {
|
||||
|
||||
currentTime - lastShake > shakeTimeout
|
||||
) {
|
||||
// Vibrate if available
|
||||
if ('vibrate' in navigator) {
|
||||
if ("vibrate" in navigator) {
|
||||
navigator.vibrate(200);
|
||||
}
|
||||
|
||||
|
@ -311,32 +415,33 @@
|
|||
|
||||
// Request permission and start shake detection on mobile
|
||||
function initShakeDetection() {
|
||||
if (typeof DeviceMotionEvent.requestPermission === 'function') {
|
||||
if (typeof DeviceMotionEvent.requestPermission === "function") {
|
||||
// iOS 13+ requires permission
|
||||
DeviceMotionEvent.requestPermission()
|
||||
.then(permissionState => {
|
||||
if (permissionState === 'granted') {
|
||||
window.addEventListener('devicemotion', handleMotion);
|
||||
.then((permissionState) => {
|
||||
if (permissionState === "granted") {
|
||||
window.addEventListener("devicemotion", handleMotion);
|
||||
}
|
||||
})
|
||||
.catch(console.error);
|
||||
} else {
|
||||
// Non iOS 13+ devices
|
||||
window.addEventListener('devicemotion', handleMotion);
|
||||
window.addEventListener("devicemotion", handleMotion);
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize shake detection when page loads
|
||||
if ('DeviceMotionEvent' in window) {
|
||||
if ("DeviceMotionEvent" in window) {
|
||||
// Add a button to request permission on iOS
|
||||
if (typeof DeviceMotionEvent.requestPermission === 'function') {
|
||||
const settingsPanel = document.getElementById('settingsPanel');
|
||||
const settingsContent = settingsPanel.querySelector('.settings-content');
|
||||
if (typeof DeviceMotionEvent.requestPermission === "function") {
|
||||
const settingsPanel = document.getElementById("settingsPanel");
|
||||
const settingsContent =
|
||||
settingsPanel.querySelector(".settings-content");
|
||||
|
||||
const permissionButton = document.createElement('button');
|
||||
permissionButton.textContent = 'Enable Shake to Fumble';
|
||||
permissionButton.className = 'permission-button';
|
||||
permissionButton.addEventListener('click', initShakeDetection);
|
||||
const permissionButton = document.createElement("button");
|
||||
permissionButton.textContent = "Enable Shake to Fumble";
|
||||
permissionButton.className = "permission-button";
|
||||
permissionButton.addEventListener("click", initShakeDetection);
|
||||
|
||||
settingsContent.appendChild(permissionButton);
|
||||
} else {
|
||||
|
@ -344,6 +449,18 @@
|
|||
initShakeDetection();
|
||||
}
|
||||
}
|
||||
|
||||
const licenseToggle = document.querySelector(".license-toggle");
|
||||
const fullLicense = document.querySelector(".full-license");
|
||||
|
||||
licenseToggle.addEventListener("click", () => {
|
||||
const isHidden = fullLicense.style.display === "none";
|
||||
fullLicense.style.display = isHidden ? "block" : "none";
|
||||
licenseToggle.textContent = isHidden
|
||||
? "Hide Full License"
|
||||
: "View Full License";
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
|
35
nginx.conf
Normal file
35
nginx.conf
Normal file
|
@ -0,0 +1,35 @@
|
|||
server {
|
||||
listen 80;
|
||||
server_name localhost;
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
# Enable gzip compression
|
||||
gzip on;
|
||||
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
|
||||
|
||||
# Security headers
|
||||
add_header X-Frame-Options "SAMEORIGIN";
|
||||
add_header X-XSS-Protection "1; mode=block";
|
||||
add_header X-Content-Type-Options "nosniff";
|
||||
add_header Referrer-Policy "strict-origin-when-cross-origin";
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
expires 1h;
|
||||
add_header Cache-Control "public, no-transform";
|
||||
}
|
||||
|
||||
# Prevent access to .git and other hidden files
|
||||
location ~ /\. {
|
||||
deny all;
|
||||
access_log off;
|
||||
log_not_found off;
|
||||
}
|
||||
|
||||
# Assets caching
|
||||
location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
|
||||
expires 7d;
|
||||
add_header Cache-Control "public, no-transform";
|
||||
}
|
||||
}
|
183
styles.css
183
styles.css
|
@ -100,6 +100,7 @@ header {
|
|||
main {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#contentFrame {
|
||||
|
@ -727,3 +728,185 @@ button {
|
|||
.dark-mode .tribute {
|
||||
border-top-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.floating-button {
|
||||
position: fixed;
|
||||
right: 20px;
|
||||
top: 80px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
background: rgba(255, 69, 0, 0.9);
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
opacity: 0;
|
||||
transform: scale(0.9);
|
||||
transition: opacity 0.3s ease, transform 0.3s ease, background-color 0.3s ease;
|
||||
z-index: 1000;
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.floating-button svg {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
main:hover .floating-button {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
.floating-button:hover {
|
||||
background: rgba(255, 69, 0, 1);
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.dark-mode .floating-button {
|
||||
background: rgba(255, 107, 74, 0.9);
|
||||
}
|
||||
|
||||
.dark-mode .floating-button:hover {
|
||||
background: rgba(255, 107, 74, 1);
|
||||
}
|
||||
|
||||
@media (hover: none) {
|
||||
.floating-button {
|
||||
opacity: 0.9;
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
.floating-button:hover {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.floating-button {
|
||||
top: auto;
|
||||
bottom: 80px;
|
||||
right: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.setting-item#darkModeToggleArea {
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
.setting-item#darkModeToggleArea:hover {
|
||||
background: #eaeaea;
|
||||
}
|
||||
|
||||
.dark-mode .setting-item#darkModeToggleArea:hover {
|
||||
background: #333;
|
||||
}
|
||||
|
||||
.interactions-list {
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.interaction-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.interaction-icon {
|
||||
font-size: 24px;
|
||||
min-width: 40px;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgba(255, 69, 0, 0.1);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.dark-mode .interaction-icon {
|
||||
background: rgba(255, 107, 74, 0.1);
|
||||
}
|
||||
|
||||
.interaction-details {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.interaction-details h4 {
|
||||
margin: 0 0 5px 0;
|
||||
color: #ff4500;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.dark-mode .interaction-details h4 {
|
||||
color: #ff6b4a;
|
||||
}
|
||||
|
||||
.interaction-details p {
|
||||
margin: 0;
|
||||
font-size: 0.9em;
|
||||
line-height: 1.4;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.license-section {
|
||||
flex-direction: column;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.license-content {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.license-content p {
|
||||
margin: 0 0 10px 0;
|
||||
font-size: 0.9em;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.license-details {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.license-toggle {
|
||||
background: none;
|
||||
border: none;
|
||||
color: #ff4500;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
font-size: 0.9em;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.dark-mode .license-toggle {
|
||||
color: #ff6b4a;
|
||||
}
|
||||
|
||||
.full-license {
|
||||
margin-top: 15px;
|
||||
padding: 15px;
|
||||
background: rgba(255, 69, 0, 0.1);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.dark-mode .full-license {
|
||||
background: rgba(255, 107, 74, 0.1);
|
||||
}
|
||||
|
||||
.full-license a {
|
||||
color: #ff4500;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.dark-mode .full-license a {
|
||||
color: #ff6b4a;
|
||||
}
|
||||
|
||||
.full-license a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
Loading…
Reference in a new issue