Compare commits
4 commits
fd88979c32
...
bbf662fb6d
Author | SHA1 | Date | |
---|---|---|---|
bbf662fb6d | |||
5240aec346 | |||
1282bf2bda | |||
ad05afbb83 |
7 changed files with 996 additions and 268 deletions
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
docker-compose.yml
|
||||||
|
docker-compose.override.yml
|
||||||
|
tailscale
|
||||||
|
tailscale-config
|
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
|
// Create the floating button
|
||||||
const button = document.createElement('button');
|
const floatingButton = document.createElement('button');
|
||||||
button.id = 'fumbleButton';
|
floatingButton.id = 'openInNewTab';
|
||||||
button.textContent = 'Fumble!';
|
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
|
// Add click handler
|
||||||
button.addEventListener('click', () => {
|
floatingButton.addEventListener('click', () => {
|
||||||
window.location.href = 'https://wiby.me/surprise/';
|
window.open(window.location.href, '_blank');
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add button to page
|
// Add styles
|
||||||
document.body.appendChild(button);
|
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
|
||||||
|
|
688
index.html
688
index.html
|
@ -1,302 +1,470 @@
|
||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
<meta
|
||||||
|
name="viewport"
|
||||||
|
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"
|
||||||
|
/>
|
||||||
<title>FumbleAround</title>
|
<title>FumbleAround</title>
|
||||||
<link rel="stylesheet" href="styles.css">
|
<link rel="stylesheet" href="styles.css" />
|
||||||
<link rel="icon" type="image/png" href="./Assets/smily.png">
|
<link rel="icon" type="image/png" href="/Assets/smily.png" />
|
||||||
|
|
||||||
<!-- PWA Meta Tags -->
|
<!-- PWA Meta Tags -->
|
||||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
<meta
|
||||||
<meta name="apple-mobile-web-app-title" content="FumbleAround">
|
name="apple-mobile-web-app-status-bar-style"
|
||||||
<meta name="mobile-web-app-capable" content="yes">
|
content="black-translucent"
|
||||||
<meta name="theme-color" content="#ffffff">
|
/>
|
||||||
<meta name="application-name" content="FumbleAround">
|
<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 -->
|
<!-- Apple Touch Icons -->
|
||||||
<link rel="apple-touch-icon" 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="152x152" href="/Assets/smily.png" />
|
||||||
<link rel="apple-touch-icon" sizes="180x180" 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" sizes="167x167" href="/Assets/smily.png" />
|
||||||
|
|
||||||
<!-- Web Manifest -->
|
<!-- Web Manifest -->
|
||||||
<link rel="manifest" href="manifest.json">
|
<link rel="manifest" href="manifest.json" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header>
|
<header>
|
||||||
<div class="logo">
|
<div class="logo">
|
||||||
<img src="./Assets/smily.png" alt="FumbleAround Logo" class="logo-icon">
|
<img
|
||||||
FumbleAround
|
src="/Assets/smily.png"
|
||||||
</div>
|
alt="FumbleAround Logo"
|
||||||
|
class="logo-icon"
|
||||||
|
/>
|
||||||
|
FumbleAround
|
||||||
|
</div>
|
||||||
<div class="header-buttons">
|
<div class="header-buttons">
|
||||||
<div class="social-buttons">
|
<div class="social-buttons">
|
||||||
<button id="helpButton">❓</button>
|
<button id="donateButton">💝</button>
|
||||||
<button id="donateButton">💝</button>
|
<button id="githubButton">
|
||||||
<button id="githubButton">
|
<svg viewBox="0 0 24 24" width="24" height="24">
|
||||||
<svg viewBox="0 0 24 24" width="24" height="24">
|
<path
|
||||||
<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"/>
|
fill="currentColor"
|
||||||
</svg>
|
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"
|
||||||
</button>
|
/>
|
||||||
</div>
|
</svg>
|
||||||
<div class="app-controls">
|
</button>
|
||||||
<div class="theme-toggle">
|
</div>
|
||||||
<button id="darkModeToggle">🌙</button>
|
<div class="app-controls">
|
||||||
</div>
|
<button id="settingsButton">
|
||||||
<button id="fumbleButton">Fumble!</button>
|
<svg viewBox="0 0 24 24" width="24" height="24">
|
||||||
</div>
|
<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>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div id="helpModal" class="modal">
|
<div id="settingsPanel" class="settings-panel">
|
||||||
<div class="modal-content">
|
<div class="settings-content">
|
||||||
<span class="close-button">×</span>
|
<div class="settings-header">
|
||||||
<h2>Disclaimer</h2>
|
<h2>Settings</h2>
|
||||||
<p>FumbleAround uses Wiby.me's search engine to provide random web pages. We are not responsible for the content that appears. Use at your own discretion.</p>
|
<button class="close-settings">×</button>
|
||||||
<p>This project is a homage to the classic StumbleUpon, reimagined for the modern web.</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="settings-section">
|
||||||
|
<h3>Display</h3>
|
||||||
|
<div class="setting-item" id="darkModeToggleArea">
|
||||||
|
<label>Dark Mode</label>
|
||||||
|
<button id="darkModeToggle">🌙</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="settings-section">
|
||||||
|
<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">
|
||||||
|
<h3>About</h3>
|
||||||
|
<div class="setting-item about-section">
|
||||||
|
<div class="about-content">
|
||||||
|
<div class="about-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>
|
||||||
|
<div class="disclaimer">
|
||||||
|
<h5>Disclaimer</h5>
|
||||||
|
<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>
|
||||||
|
</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>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="warningModal" class="modal warning-modal">
|
<div id="warningModal" class="modal warning-modal">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<span class="close-button" style="display: none;">×</span>
|
<span class="close-button" style="display: none">×</span>
|
||||||
<h2>⚠️ Slow Down!</h2>
|
<h2>⚠️ Slow Down!</h2>
|
||||||
<p>Clicking too quickly may get you flagged as spam by Wiby.me.</p>
|
<p>Clicking too quickly may get you flagged as spam by Wiby.me.</p>
|
||||||
<p>Please wait a moment between fumbles.</p>
|
<p>Please wait a moment between fumbles.</p>
|
||||||
<div id="cooldownTimer" class="cooldown-timer">
|
<div id="cooldownTimer" class="cooldown-timer">
|
||||||
You can close this warning in: <span id="cooldownSeconds">5</span>s
|
You can close this warning in: <span id="cooldownSeconds">5</span>s
|
||||||
</div>
|
|
||||||
<button id="warningCloseBtn" class="warning-close-btn" disabled>I Understand</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
<button id="warningCloseBtn" class="warning-close-btn" disabled>
|
||||||
|
I Understand
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
<iframe
|
<iframe
|
||||||
id="contentFrame"
|
id="contentFrame"
|
||||||
src="landing.html"
|
src="landing.html"
|
||||||
title="Content"
|
title="Content"
|
||||||
sandbox="allow-scripts allow-same-origin allow-forms allow-popups"
|
sandbox="allow-scripts allow-same-origin allow-forms allow-popups"
|
||||||
></iframe>
|
></iframe>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
if (performance.navigation.type === performance.navigation.TYPE_RELOAD) {
|
if (performance.navigation.type === performance.navigation.TYPE_RELOAD) {
|
||||||
if (sessionStorage.getItem('hasVisited')) {
|
if (sessionStorage.getItem("hasVisited")) {
|
||||||
window.stop();
|
window.stop();
|
||||||
requestAnimationFrame(() => fumble());
|
requestAnimationFrame(() => fumble());
|
||||||
|
}
|
||||||
|
} else if (!sessionStorage.getItem("hasVisited")) {
|
||||||
|
sessionStorage.setItem("hasVisited", "true");
|
||||||
|
}
|
||||||
|
|
||||||
|
let lastFumbleTime = 0;
|
||||||
|
const cooldownPeriod = 5000; // 5 seconds cooldown
|
||||||
|
let cooldownTimer = null;
|
||||||
|
|
||||||
|
const fumble = () => {
|
||||||
|
const currentTime = Date.now();
|
||||||
|
const timeSinceLastFumble = currentTime - lastFumbleTime;
|
||||||
|
|
||||||
|
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");
|
||||||
|
closeBtn.disabled = true;
|
||||||
|
|
||||||
|
// Update countdown timer
|
||||||
|
let remainingTime = 5; // 5 second countdown for closing
|
||||||
|
cooldownSeconds.textContent = remainingTime;
|
||||||
|
|
||||||
|
if (cooldownTimer) clearInterval(cooldownTimer);
|
||||||
|
cooldownTimer = setInterval(() => {
|
||||||
|
remainingTime--;
|
||||||
|
|
||||||
|
if (remainingTime <= 0) {
|
||||||
|
cooldownSeconds.textContent = "0";
|
||||||
|
closeBtn.disabled = false;
|
||||||
|
clearInterval(cooldownTimer);
|
||||||
|
} else {
|
||||||
|
cooldownSeconds.textContent = remainingTime;
|
||||||
}
|
}
|
||||||
} else if (!sessionStorage.getItem('hasVisited')) {
|
}, 1000);
|
||||||
sessionStorage.setItem('hasVisited', 'true');
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let lastFumbleTime = 0;
|
lastFumbleTime = currentTime;
|
||||||
const cooldownPeriod = 5000; // 5 seconds cooldown
|
const frame = document.getElementById("contentFrame");
|
||||||
let cooldownTimer = null;
|
frame.src = "https://wiby.me/surprise/";
|
||||||
|
|
||||||
const fumble = () => {
|
// Wait for the page to load then focus
|
||||||
const currentTime = Date.now();
|
frame.onload = () => {
|
||||||
const timeSinceLastFumble = currentTime - lastFumbleTime;
|
// Check if we landed on a blocked/error page
|
||||||
|
try {
|
||||||
if (timeSinceLastFumble < cooldownPeriod) {
|
const title = frame.contentWindow.document.title.toLowerCase();
|
||||||
// Show warning modal
|
if (
|
||||||
const warningModal = document.getElementById('warningModal');
|
title.includes("blocked") ||
|
||||||
const cooldownSeconds = document.getElementById('cooldownSeconds');
|
title.includes("error") ||
|
||||||
const closeBtn = document.getElementById('warningCloseBtn');
|
title.includes("refused") ||
|
||||||
warningModal.classList.add('show');
|
title.includes("cannot") ||
|
||||||
closeBtn.disabled = true;
|
title.includes("denied")
|
||||||
|
) {
|
||||||
// Update countdown timer
|
// Try again if we hit an error page
|
||||||
let remainingTime = 5; // 5 second countdown for closing
|
fumble();
|
||||||
cooldownSeconds.textContent = remainingTime;
|
return;
|
||||||
|
|
||||||
if (cooldownTimer) clearInterval(cooldownTimer);
|
|
||||||
cooldownTimer = setInterval(() => {
|
|
||||||
remainingTime--;
|
|
||||||
|
|
||||||
if (remainingTime <= 0) {
|
|
||||||
cooldownSeconds.textContent = '0';
|
|
||||||
closeBtn.disabled = false;
|
|
||||||
clearInterval(cooldownTimer);
|
|
||||||
} else {
|
|
||||||
cooldownSeconds.textContent = remainingTime;
|
|
||||||
}
|
|
||||||
}, 1000);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Can't access title due to CORS - assume page is OK
|
||||||
|
}
|
||||||
|
|
||||||
lastFumbleTime = currentTime;
|
frame.focus();
|
||||||
const frame = document.getElementById('contentFrame');
|
try {
|
||||||
frame.src = 'https://wiby.me/surprise/';
|
frame.contentWindow.focus();
|
||||||
|
} catch (e) {
|
||||||
// Wait for the page to load then focus
|
// Ignore cross-origin errors
|
||||||
frame.onload = () => {
|
}
|
||||||
// Check if we landed on a blocked/error page
|
|
||||||
try {
|
|
||||||
const title = frame.contentWindow.document.title.toLowerCase();
|
|
||||||
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 title due to CORS - assume page is OK
|
|
||||||
}
|
|
||||||
|
|
||||||
frame.focus();
|
|
||||||
try {
|
|
||||||
frame.contentWindow.focus();
|
|
||||||
} catch (e) {
|
|
||||||
// Ignore cross-origin errors
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Handle load errors
|
|
||||||
frame.onerror = () => {
|
|
||||||
fumble(); // Try again if loading fails
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
document.getElementById('fumbleButton').addEventListener('click', fumble);
|
// Handle load errors
|
||||||
|
frame.onerror = () => {
|
||||||
|
fumble(); // Try again if loading fails
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
document.getElementById("fumbleButton").addEventListener("click", fumble);
|
||||||
|
|
||||||
// Dark mode toggle
|
// Dark mode toggle
|
||||||
const darkModeToggle = document.getElementById('darkModeToggle');
|
const darkModeToggle = document.getElementById('darkModeToggle');
|
||||||
const body = document.body;
|
const darkModeToggleArea = document.getElementById('darkModeToggleArea');
|
||||||
|
const body = document.body;
|
||||||
// Check for saved preference
|
|
||||||
if (localStorage.getItem('darkMode') === 'true') {
|
// Check for saved preference, default to dark if not set
|
||||||
body.classList.add('dark-mode');
|
if (localStorage.getItem('darkMode') === null) {
|
||||||
darkModeToggle.textContent = '☀️';
|
localStorage.setItem('darkMode', 'true');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (localStorage.getItem('darkMode') === 'true') {
|
||||||
|
body.classList.add('dark-mode');
|
||||||
|
darkModeToggle.textContent = '☀️';
|
||||||
|
}
|
||||||
|
|
||||||
|
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");
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add donate button click handler
|
||||||
|
document.getElementById("donateButton").addEventListener("click", () => {
|
||||||
|
window.open("https://wiby.me/donate/", "_blank");
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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');
|
||||||
|
|
||||||
|
const closeSettingsPanel = () => {
|
||||||
|
settingsPanel.classList.remove('show');
|
||||||
|
};
|
||||||
|
|
||||||
|
settingsButton.addEventListener('click', (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
settingsPanel.classList.add('show');
|
||||||
|
});
|
||||||
|
|
||||||
|
closeSettings.addEventListener('click', closeSettingsPanel);
|
||||||
|
|
||||||
|
// Handle clicks on the document
|
||||||
|
document.addEventListener('click', (event) => {
|
||||||
|
if (settingsPanel.classList.contains('show') &&
|
||||||
|
!settingsContent.contains(event.target) &&
|
||||||
|
!settingsButton.contains(event.target)) {
|
||||||
|
closeSettingsPanel();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Prevent clicks inside settings from closing
|
||||||
|
settingsContent.addEventListener('click', (event) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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");
|
||||||
|
if (cooldownTimer) clearInterval(cooldownTimer);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
darkModeToggle.addEventListener('click', () => {
|
// Remove the click-outside-to-close functionality for warning modal
|
||||||
body.classList.toggle('dark-mode');
|
window.addEventListener("click", (event) => {
|
||||||
const isDark = body.classList.contains('dark-mode');
|
if (event.target === warningModal) {
|
||||||
darkModeToggle.textContent = isDark ? '☀️' : '🌙';
|
// Only for warning modal
|
||||||
localStorage.setItem('darkMode', isDark);
|
warningModal.classList.remove("show");
|
||||||
});
|
|
||||||
|
|
||||||
// Add GitHub button click handler
|
|
||||||
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');
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add help button click handler
|
|
||||||
const modal = document.getElementById('helpModal');
|
|
||||||
const helpButton = document.getElementById('helpButton');
|
|
||||||
const closeButton = document.querySelector('.close-button');
|
|
||||||
|
|
||||||
helpButton.addEventListener('click', () => {
|
|
||||||
modal.classList.add('show');
|
|
||||||
});
|
|
||||||
|
|
||||||
closeButton.addEventListener('click', () => {
|
|
||||||
modal.classList.remove('show');
|
|
||||||
});
|
|
||||||
|
|
||||||
// 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');
|
|
||||||
if (cooldownTimer) clearInterval(cooldownTimer);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Remove the click-outside-to-close functionality for warning modal
|
|
||||||
window.addEventListener('click', (event) => {
|
|
||||||
if (event.target === modal) { // Only for help modal
|
|
||||||
modal.classList.remove('show');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add shake detection
|
|
||||||
let lastX = 0;
|
|
||||||
let lastY = 0;
|
|
||||||
let lastZ = 0;
|
|
||||||
let lastUpdate = 0;
|
|
||||||
const shakeThreshold = 15; // Adjust sensitivity
|
|
||||||
const shakeTimeout = 1000; // Prevent multiple shakes
|
|
||||||
let lastShake = 0;
|
|
||||||
|
|
||||||
function handleMotion(event) {
|
|
||||||
const current = event.accelerationIncludingGravity;
|
|
||||||
const currentTime = new Date().getTime();
|
|
||||||
const timeDiff = currentTime - lastUpdate;
|
|
||||||
|
|
||||||
if (timeDiff > 100) {
|
|
||||||
const deltaX = Math.abs(current.x - lastX);
|
|
||||||
const deltaY = Math.abs(current.y - lastY);
|
|
||||||
const deltaZ = Math.abs(current.z - lastZ);
|
|
||||||
|
|
||||||
if (((deltaX > shakeThreshold && deltaY > shakeThreshold) ||
|
|
||||||
(deltaX > shakeThreshold && deltaZ > shakeThreshold) ||
|
|
||||||
(deltaY > shakeThreshold && deltaZ > shakeThreshold)) &&
|
|
||||||
(currentTime - lastShake > shakeTimeout)) {
|
|
||||||
|
|
||||||
// Vibrate if available
|
|
||||||
if ('vibrate' in navigator) {
|
|
||||||
navigator.vibrate(200);
|
|
||||||
}
|
|
||||||
|
|
||||||
fumble();
|
|
||||||
lastShake = currentTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
lastX = current.x;
|
|
||||||
lastY = current.y;
|
|
||||||
lastZ = current.z;
|
|
||||||
lastUpdate = currentTime;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Request permission and start shake detection on mobile
|
// Add shake detection
|
||||||
function initShakeDetection() {
|
let lastX = 0;
|
||||||
if (typeof DeviceMotionEvent.requestPermission === 'function') {
|
let lastY = 0;
|
||||||
// iOS 13+ requires permission
|
let lastZ = 0;
|
||||||
DeviceMotionEvent.requestPermission()
|
let lastUpdate = 0;
|
||||||
.then(permissionState => {
|
const shakeThreshold = 15; // Adjust sensitivity
|
||||||
if (permissionState === 'granted') {
|
const shakeTimeout = 1000; // Prevent multiple shakes
|
||||||
window.addEventListener('devicemotion', handleMotion);
|
let lastShake = 0;
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(console.error);
|
|
||||||
} else {
|
|
||||||
// Non iOS 13+ devices
|
|
||||||
window.addEventListener('devicemotion', handleMotion);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize shake detection when page loads
|
function handleMotion(event) {
|
||||||
if ('DeviceMotionEvent' in window) {
|
const current = event.accelerationIncludingGravity;
|
||||||
// Add a button to request permission on iOS
|
const currentTime = new Date().getTime();
|
||||||
if (typeof DeviceMotionEvent.requestPermission === 'function') {
|
const timeDiff = currentTime - lastUpdate;
|
||||||
const modal = document.getElementById('helpModal');
|
|
||||||
const modalContent = modal.querySelector('.modal-content');
|
if (timeDiff > 100) {
|
||||||
|
const deltaX = Math.abs(current.x - lastX);
|
||||||
const permissionButton = document.createElement('button');
|
const deltaY = Math.abs(current.y - lastY);
|
||||||
permissionButton.textContent = 'Enable Shake to Fumble';
|
const deltaZ = Math.abs(current.z - lastZ);
|
||||||
permissionButton.className = 'permission-button';
|
|
||||||
permissionButton.addEventListener('click', initShakeDetection);
|
if (
|
||||||
|
((deltaX > shakeThreshold && deltaY > shakeThreshold) ||
|
||||||
modalContent.appendChild(permissionButton);
|
(deltaX > shakeThreshold && deltaZ > shakeThreshold) ||
|
||||||
} else {
|
(deltaY > shakeThreshold && deltaZ > shakeThreshold)) &&
|
||||||
// Automatically start for non-iOS devices
|
currentTime - lastShake > shakeTimeout
|
||||||
initShakeDetection();
|
) {
|
||||||
|
// Vibrate if available
|
||||||
|
if ("vibrate" in navigator) {
|
||||||
|
navigator.vibrate(200);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fumble();
|
||||||
|
lastShake = currentTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
lastX = current.x;
|
||||||
|
lastY = current.y;
|
||||||
|
lastZ = current.z;
|
||||||
|
lastUpdate = currentTime;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Request permission and start shake detection on mobile
|
||||||
|
function initShakeDetection() {
|
||||||
|
if (typeof DeviceMotionEvent.requestPermission === "function") {
|
||||||
|
// iOS 13+ requires permission
|
||||||
|
DeviceMotionEvent.requestPermission()
|
||||||
|
.then((permissionState) => {
|
||||||
|
if (permissionState === "granted") {
|
||||||
|
window.addEventListener("devicemotion", handleMotion);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(console.error);
|
||||||
|
} else {
|
||||||
|
// Non iOS 13+ devices
|
||||||
|
window.addEventListener("devicemotion", handleMotion);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize shake detection when page loads
|
||||||
|
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");
|
||||||
|
|
||||||
|
const permissionButton = document.createElement("button");
|
||||||
|
permissionButton.textContent = "Enable Shake to Fumble";
|
||||||
|
permissionButton.className = "permission-button";
|
||||||
|
permissionButton.addEventListener("click", initShakeDetection);
|
||||||
|
|
||||||
|
settingsContent.appendChild(permissionButton);
|
||||||
|
} else {
|
||||||
|
// Automatically start for non-iOS devices
|
||||||
|
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>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</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";
|
||||||
|
}
|
||||||
|
}
|
402
styles.css
402
styles.css
|
@ -100,6 +100,7 @@ header {
|
||||||
main {
|
main {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
#contentFrame {
|
#contentFrame {
|
||||||
|
@ -507,4 +508,405 @@ button {
|
||||||
|
|
||||||
.dark-mode .warning-close-btn:not(:disabled):hover {
|
.dark-mode .warning-close-btn:not(:disabled):hover {
|
||||||
background-color: #ff7c5c;
|
background-color: #ff7c5c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-panel {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
right: -400px;
|
||||||
|
width: 400px;
|
||||||
|
height: 100vh;
|
||||||
|
background-color: #ffffff;
|
||||||
|
box-shadow: -2px 0 10px rgba(0,0,0,0.1);
|
||||||
|
transition: right 0.3s ease;
|
||||||
|
z-index: 2000;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark-mode .settings-panel {
|
||||||
|
background-color: #1a1a1a;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-panel.show {
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-content {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark-mode .settings-header {
|
||||||
|
border-bottom-color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-settings {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
font-size: 24px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #666;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark-mode .close-settings {
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-section {
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-section h3 {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
color: #ff4500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark-mode .settings-section h3 {
|
||||||
|
color: #ff6b4a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
padding: 10px;
|
||||||
|
background: #f5f5f5;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark-mode .setting-item {
|
||||||
|
background: #2a2a2a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-button {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px;
|
||||||
|
background-color: #ff4500;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark-mode .settings-button {
|
||||||
|
background-color: #ff6b4a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-button:hover {
|
||||||
|
background-color: #ff5722;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.settings-panel {
|
||||||
|
width: 100%;
|
||||||
|
right: -100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#settingsButton {
|
||||||
|
padding: 8px;
|
||||||
|
background-color: transparent;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: transform 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
#settingsButton svg {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
color: #333;
|
||||||
|
transition: color 0.3s ease, transform 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark-mode #settingsButton svg {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
#settingsButton:hover svg {
|
||||||
|
transform: rotate(30deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
#settingsButton:active svg {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (hover: none) {
|
||||||
|
#settingsButton:hover svg {
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.about-section {
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.about-content {
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.about-logo {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.about-logo img {
|
||||||
|
width: 64px;
|
||||||
|
height: 64px;
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.about-logo h4 {
|
||||||
|
font-size: 1.2em;
|
||||||
|
margin: 0;
|
||||||
|
color: #ff4500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark-mode .about-logo h4 {
|
||||||
|
color: #ff6b4a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.about-text {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.about-text p {
|
||||||
|
margin: 0;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.disclaimer {
|
||||||
|
background: rgba(255, 69, 0, 0.1);
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark-mode .disclaimer {
|
||||||
|
background: rgba(255, 107, 74, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.disclaimer h5 {
|
||||||
|
color: #ff4500;
|
||||||
|
margin: 0 0 10px 0;
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark-mode .disclaimer h5 {
|
||||||
|
color: #ff6b4a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tribute {
|
||||||
|
font-style: italic;
|
||||||
|
opacity: 0.8;
|
||||||
|
padding-top: 10px;
|
||||||
|
border-top: 1px solid rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.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