1
This commit is contained in:
parent
bf2624e838
commit
0215d4d95e
0
.perm_test_apache
Normal file
0
.perm_test_apache
Normal file
0
.perm_test_exec
Normal file
0
.perm_test_exec
Normal file
@ -145,6 +145,191 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) {
|
||||
}
|
||||
}, [router.pathname]);
|
||||
|
||||
// Stealth search trigger: press 'g' 5 times in 5 seconds
|
||||
React.useEffect(() => {
|
||||
let keyPresses: number[] = [];
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
// Ignore if user is typing in an input or textarea
|
||||
const target = e.target as HTMLElement;
|
||||
if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.key.toLowerCase() === 'g') {
|
||||
const now = Date.now();
|
||||
keyPresses.push(now);
|
||||
keyPresses = keyPresses.filter((t) => now - t <= 5000);
|
||||
|
||||
if (keyPresses.length >= 5) {
|
||||
const win = window.open('about:blank', '_blank');
|
||||
if (win) {
|
||||
const content = `
|
||||
<html>
|
||||
<head>
|
||||
<title>Search</title>
|
||||
<style>
|
||||
body { background: white; margin: 0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; display: flex; flex-direction: column; height: 100vh; overflow: hidden; color: #202124; }
|
||||
.header { background: #f1f3f4; border-bottom: 1px solid #dfe1e5; display: flex; flex-direction: column; }
|
||||
.tab-bar { display: flex; padding: 8px 8px 0; gap: 4px; overflow-x: auto; scrollbar-width: none; }
|
||||
.tab-bar::-webkit-scrollbar { display: none; }
|
||||
.tab { padding: 6px 12px; background: #e8eaed; border-radius: 8px 8px 0 0; cursor: pointer; display: flex; align-items: center; gap: 8px; font-size: 12px; min-width: 100px; max-width: 180px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; border: 1px solid transparent; border-bottom: none; transition: background 0.2s; }
|
||||
.tab:hover { background: #dadce0; }
|
||||
.tab.active { background: white; font-weight: 500; border-color: #dfe1e5; border-bottom-color: white; margin-bottom: -1px; z-index: 1; }
|
||||
.tab .close { font-size: 14px; color: #5f6368; width: 16px; height: 16px; display: flex; align-items: center; justify-content: center; border-radius: 50%; }
|
||||
.tab .close:hover { background: #dadce0; color: #202124; }
|
||||
.search-bar { padding: 8px 16px; display: flex; gap: 12px; align-items: center; background: white; }
|
||||
.nav-btns { display: flex; gap: 8px; }
|
||||
.nav-btn { cursor: pointer; color: #5f6368; padding: 4px; border-radius: 50%; display: flex; align-items: center; justify-content: center; width: 28px; height: 28px; }
|
||||
.nav-btn:hover { background: #f1f3f4; }
|
||||
form { flex-grow: 1; display: flex; max-width: 800px; margin: 0 auto; width: 100%; }
|
||||
.input-wrapper { position: relative; width: 100%; display: flex; align-items: center; }
|
||||
input { padding: 8px 16px; font-size: 14px; border: 1px solid #dfe1e5; border-radius: 20px; width: 100%; outline: none; background: #f1f3f4; transition: background 0.2s, box-shadow 0.2s; }
|
||||
input:focus { background: white; border-color: transparent; box-shadow: 0 1px 6px rgba(32,33,36,0.28); }
|
||||
.new-tab-btn { cursor: pointer; min-width: 28px; height: 28px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 18px; color: #5f6368; align-self: center; margin-bottom: 4px; }
|
||||
.new-tab-btn:hover { background: #dadce0; }
|
||||
.iframe-container { flex-grow: 1; position: relative; background: #f8f9fa; }
|
||||
iframe { border: none; width: 100%; height: 100%; position: absolute; top: 0; left: 0; visibility: hidden; opacity: 0; transition: opacity 0.2s; }
|
||||
iframe.active { visibility: visible; opacity: 1; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class=\'header\'>
|
||||
<div class=\'tab-bar\' id=\'tabBar\'>
|
||||
<div class=\'new-tab-btn\' onclick=\'addTab()\' title=\'New Tab\'>+</div>
|
||||
</div>
|
||||
<div class=\'search-bar\'>
|
||||
<div class=\'nav-btns\'>
|
||||
<div class=\'nav-btn\' onclick=\'goBack()\' title=\'Back\'>←</div>
|
||||
<div class=\'nav-btn\' onclick=\'reloadTab()\' title=\'Reload\'>↻</div>
|
||||
</div>
|
||||
<form onsubmit=\'handleSearch(event)\'>
|
||||
<div class=\'input-wrapper\'>
|
||||
<input id=\'searchInput\' placeholder=\'Search or enter URL...\' autofocus autocomplete=\'off\' />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class=\'iframe-container\' id=\'iframeContainer\'></div>
|
||||
<script>
|
||||
var tabs = [];
|
||||
var activeTabId = null;
|
||||
|
||||
function addTab(url, title) {
|
||||
url = url || 'about:blank';
|
||||
title = title || 'New Tab';
|
||||
var id = 'tab-' + Math.random().toString(36).substr(2, 9);
|
||||
tabs.push({ id: id, url: url, title: title });
|
||||
|
||||
var tabEl = document.createElement('div');
|
||||
tabEl.className = 'tab';
|
||||
tabEl.id = 'el-' + id;
|
||||
tabEl.onclick = function() { switchTab(id); };
|
||||
tabEl.innerHTML = '<span class="title" style="flex-grow:1; overflow:hidden; text-overflow:ellipsis;">' + title + '</span><span class="close" onclick="event.stopPropagation(); removeTab(\'\' + id + \'
|
||||
')">×</span>';
|
||||
|
||||
var tabBar = document.getElementById('tabBar');
|
||||
tabBar.insertBefore(tabEl, tabBar.querySelector('.new-tab-btn'));
|
||||
|
||||
var iframe = document.createElement('iframe');
|
||||
iframe.id = 'frame-' + id;
|
||||
iframe.src = url;
|
||||
iframe.setAttribute('name', id);
|
||||
document.getElementById('iframeContainer').appendChild(iframe);
|
||||
|
||||
switchTab(id);
|
||||
return id;
|
||||
}
|
||||
|
||||
function switchTab(id) {
|
||||
activeTabId = id;
|
||||
var allTabs = document.querySelectorAll('.tab');
|
||||
for (var i = 0; i < allTabs.length; i++) allTabs[i].classList.remove('active');
|
||||
var allFrames = document.querySelectorAll('iframe');
|
||||
for (var i = 0; i < allFrames.length; i++) allFrames[i].classList.remove('active');
|
||||
|
||||
var el = document.getElementById('el-' + id);
|
||||
var frame = document.getElementById('frame-' + id);
|
||||
if (el) el.classList.add('active');
|
||||
if (frame) frame.classList.add('active');
|
||||
|
||||
var currentTab = tabs.find(function(t) { return t.id === id; });
|
||||
if (currentTab) {
|
||||
document.getElementById('searchInput').value = (currentTab.url === 'about:blank') ? '' : currentTab.url;
|
||||
}
|
||||
}
|
||||
|
||||
function removeTab(id) {
|
||||
var index = tabs.findIndex(function(t) { return t.id === id; });
|
||||
tabs = tabs.filter(function(t) { return t.id !== id; });
|
||||
document.getElementById('el-' + id).remove();
|
||||
document.getElementById('frame-' + id).remove();
|
||||
|
||||
if (activeTabId === id) {
|
||||
if (tabs.length > 0) {
|
||||
var nextTab = tabs[index] || tabs[index - 1];
|
||||
switchTab(nextTab.id);
|
||||
} else {
|
||||
addTab();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function goBack() {
|
||||
if (activeTabId) {
|
||||
document.getElementById('frame-' + activeTabId).contentWindow.history.back();
|
||||
}
|
||||
}
|
||||
|
||||
function reloadTab() {
|
||||
if (activeTabId) {
|
||||
var frame = document.getElementById('frame-' + activeTabId);
|
||||
frame.src = frame.src;
|
||||
}
|
||||
}
|
||||
|
||||
function handleSearch(e) {
|
||||
e.preventDefault();
|
||||
var query = document.getElementById('searchInput').value.trim();
|
||||
if (!query) return;
|
||||
|
||||
var url;
|
||||
if (query.match(/^(https?:\\/\\/)/i)) {
|
||||
url = query;
|
||||
} else if (query.includes('.') && !query.includes(' ')) {
|
||||
url = 'https://' + query;
|
||||
} else {
|
||||
url = "https://www.google.com/search?q=" + encodeURIComponent(query) + "&igu=1";
|
||||
}
|
||||
|
||||
if (!activeTabId) {
|
||||
addTab(url, query);
|
||||
} else {
|
||||
var iframe = document.getElementById('frame-' + activeTabId);
|
||||
iframe.src = url;
|
||||
var tabEl = document.getElementById('el-' + activeTabId);
|
||||
tabEl.querySelector('.title').innerText = query;
|
||||
var tab = tabs.find(function(t) { return t.id === activeTabId; });
|
||||
if (tab) tab.url = url;
|
||||
}
|
||||
}
|
||||
|
||||
addTab();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
win.document.write(content);
|
||||
win.document.close();
|
||||
}
|
||||
keyPresses = [];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
return () => window.removeEventListener('keydown', handleKeyDown);
|
||||
}, []);
|
||||
|
||||
const handleExit = () => {
|
||||
setStepsEnabled(false);
|
||||
};
|
||||
@ -198,4 +383,4 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) {
|
||||
)
|
||||
}
|
||||
|
||||
export default appWithTranslation(MyApp);
|
||||
export default appWithTranslation(MyApp);
|
||||
@ -1,166 +1,335 @@
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useEffect, useState, useRef } from 'react';
|
||||
import type { ReactElement } from 'react';
|
||||
import Head from 'next/head';
|
||||
import Link from 'next/link';
|
||||
import BaseButton from '../components/BaseButton';
|
||||
import CardBox from '../components/CardBox';
|
||||
import SectionFullScreen from '../components/SectionFullScreen';
|
||||
import {
|
||||
mdiPlay,
|
||||
mdiPause,
|
||||
mdiVolumeHigh,
|
||||
mdiTuneVariant,
|
||||
mdiClockOutline,
|
||||
mdiCoffeeOutline,
|
||||
mdiBookOpenVariant,
|
||||
mdiRefresh,
|
||||
mdiWeatherRainy,
|
||||
mdiWaves,
|
||||
mdiTree,
|
||||
mdiVolumeMedium
|
||||
} from '@mdi/js';
|
||||
import BaseIcon from '../components/BaseIcon';
|
||||
import LayoutGuest from '../layouts/Guest';
|
||||
import BaseDivider from '../components/BaseDivider';
|
||||
import BaseButtons from '../components/BaseButtons';
|
||||
import { getPageTitle } from '../config';
|
||||
import { useAppSelector } from '../stores/hooks';
|
||||
import CardBoxComponentTitle from "../components/CardBoxComponentTitle";
|
||||
import { getPexelsImage, getPexelsVideo } from '../helpers/pexels';
|
||||
|
||||
export default function WhiteNoiseHome() {
|
||||
// Audio State
|
||||
const [isPlaying, setIsPlaying] = useState(false);
|
||||
const [volume, setVolume] = useState(0.5);
|
||||
const audioRef = useRef<HTMLAudioElement | null>(null);
|
||||
|
||||
export default function Starter() {
|
||||
const [illustrationImage, setIllustrationImage] = useState({
|
||||
src: undefined,
|
||||
photographer: undefined,
|
||||
photographer_url: undefined,
|
||||
})
|
||||
const [illustrationVideo, setIllustrationVideo] = useState({video_files: []})
|
||||
const [contentType, setContentType] = useState('image');
|
||||
const [contentPosition, setContentPosition] = useState('background');
|
||||
const textColor = useAppSelector((state) => state.style.linkColor);
|
||||
const sounds = [
|
||||
{ id: 'white', name: 'White Noise', url: 'https://actions.google.com/sounds/v1/ambiences/white_noise.ogg', icon: mdiTuneVariant, color: 'indigo' },
|
||||
{ id: 'rain', name: 'Heavy Rain', url: 'https://actions.google.com/sounds/v1/weather/rain_heavy_loud.ogg', icon: mdiWeatherRainy, color: 'blue' },
|
||||
{ id: 'ocean', name: 'Ocean Waves', url: 'https://actions.google.com/sounds/v1/water/waves_crashing_on_shore.ogg', icon: mdiWaves, color: 'cyan' },
|
||||
{ id: 'forest', name: 'Night Forest', url: 'https://actions.google.com/sounds/v1/ambiences/night_forest_with_insects.ogg', icon: mdiTree, color: 'green' },
|
||||
];
|
||||
const [currentSound, setCurrentSound] = useState(sounds[0]);
|
||||
|
||||
const title = 'App Draft'
|
||||
// Timer State
|
||||
const [timerMinutes, setTimerMinutes] = useState(25);
|
||||
const [timerSeconds, setTimerSeconds] = useState(0);
|
||||
const [isTimerRunning, setIsTimerRunning] = useState(false);
|
||||
const [timerMode, setTimerMode] = useState<'focus' | 'break'>('focus');
|
||||
|
||||
// Fetch Pexels image/video
|
||||
useEffect(() => {
|
||||
async function fetchData() {
|
||||
const image = await getPexelsImage();
|
||||
const video = await getPexelsVideo();
|
||||
setIllustrationImage(image);
|
||||
setIllustrationVideo(video);
|
||||
// Timer Logic
|
||||
useEffect(() => {
|
||||
let interval: NodeJS.Timeout;
|
||||
|
||||
if (isTimerRunning) {
|
||||
interval = setInterval(() => {
|
||||
if (timerSeconds > 0) {
|
||||
setTimerSeconds(timerSeconds - 1);
|
||||
} else if (timerMinutes > 0) {
|
||||
setTimerMinutes(timerMinutes - 1);
|
||||
setTimerSeconds(59);
|
||||
} else {
|
||||
// Timer finished
|
||||
const nextMode = timerMode === 'focus' ? 'break' : 'focus';
|
||||
setTimerMode(nextMode);
|
||||
setTimerMinutes(nextMode === 'focus' ? 25 : 5);
|
||||
setTimerSeconds(0);
|
||||
setIsTimerRunning(false);
|
||||
// Play a notification sound could be added here
|
||||
}
|
||||
fetchData();
|
||||
}, []);
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
const imageBlock = (image) => (
|
||||
<div
|
||||
className='hidden md:flex flex-col justify-end relative flex-grow-0 flex-shrink-0 w-1/3'
|
||||
style={{
|
||||
backgroundImage: `${
|
||||
image
|
||||
? `url(${image?.src?.original})`
|
||||
: 'linear-gradient(rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0.5))'
|
||||
}`,
|
||||
backgroundSize: 'cover',
|
||||
backgroundPosition: 'left center',
|
||||
backgroundRepeat: 'no-repeat',
|
||||
}}
|
||||
>
|
||||
<div className='flex justify-center w-full bg-blue-300/20'>
|
||||
<a
|
||||
className='text-[8px]'
|
||||
href={image?.photographer_url}
|
||||
target='_blank'
|
||||
rel='noreferrer'
|
||||
>
|
||||
Photo by {image?.photographer} on Pexels
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return () => clearInterval(interval);
|
||||
}, [isTimerRunning, timerMinutes, timerSeconds, timerMode]);
|
||||
|
||||
const videoBlock = (video) => {
|
||||
if (video?.video_files?.length > 0) {
|
||||
return (
|
||||
<div className='hidden md:flex flex-col justify-end relative flex-grow-0 flex-shrink-0 w-1/3'>
|
||||
<video
|
||||
className='absolute top-0 left-0 w-full h-full object-cover'
|
||||
autoPlay
|
||||
loop
|
||||
muted
|
||||
>
|
||||
<source src={video?.video_files[0]?.link} type='video/mp4'/>
|
||||
Your browser does not support the video tag.
|
||||
</video>
|
||||
<div className='flex justify-center w-full bg-blue-300/20 z-10'>
|
||||
<a
|
||||
className='text-[8px]'
|
||||
href={video?.user?.url}
|
||||
target='_blank'
|
||||
rel='noreferrer'
|
||||
>
|
||||
Video by {video.user.name} on Pexels
|
||||
</a>
|
||||
</div>
|
||||
</div>)
|
||||
}
|
||||
};
|
||||
const toggleTimer = () => setIsTimerRunning(!isTimerRunning);
|
||||
const resetTimer = () => {
|
||||
setIsTimerRunning(false);
|
||||
setTimerMinutes(timerMode === 'focus' ? 25 : 5);
|
||||
setTimerSeconds(0);
|
||||
};
|
||||
|
||||
const togglePlay = () => {
|
||||
if (audioRef.current) {
|
||||
if (isPlaying) {
|
||||
audioRef.current.pause();
|
||||
} else {
|
||||
audioRef.current.play().catch(err => console.error("Audio play failed:", err));
|
||||
}
|
||||
setIsPlaying(!isPlaying);
|
||||
}
|
||||
};
|
||||
|
||||
const changeSound = (sound: typeof sounds[0]) => {
|
||||
setCurrentSound(sound);
|
||||
setIsPlaying(false);
|
||||
if (audioRef.current) {
|
||||
audioRef.current.load();
|
||||
}
|
||||
};
|
||||
|
||||
const handleVolumeChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const newVolume = parseFloat(e.target.value);
|
||||
setVolume(newVolume);
|
||||
if (audioRef.current) {
|
||||
audioRef.current.volume = newVolume;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
style={
|
||||
contentPosition === 'background'
|
||||
? {
|
||||
backgroundImage: `${
|
||||
illustrationImage
|
||||
? `url(${illustrationImage.src?.original})`
|
||||
: 'linear-gradient(rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0.5))'
|
||||
}`,
|
||||
backgroundSize: 'cover',
|
||||
backgroundPosition: 'left center',
|
||||
backgroundRepeat: 'no-repeat',
|
||||
}
|
||||
: {}
|
||||
}
|
||||
>
|
||||
<div className="min-h-screen bg-slate-950 text-slate-100 flex flex-col font-sans selection:bg-indigo-500/30">
|
||||
<Head>
|
||||
<title>{getPageTitle('Starter Page')}</title>
|
||||
<title>{getPageTitle('ZenNoise - Deep Focus Study Tools')}</title>
|
||||
<meta name="description" content="Seamless white noise loop and study timer. Achieve deep flow state with ZenNoise." />
|
||||
</Head>
|
||||
|
||||
<SectionFullScreen bg='violet'>
|
||||
<div
|
||||
className={`flex ${
|
||||
contentPosition === 'right' ? 'flex-row-reverse' : 'flex-row'
|
||||
} min-h-screen w-full`}
|
||||
>
|
||||
{contentType === 'image' && contentPosition !== 'background'
|
||||
? imageBlock(illustrationImage)
|
||||
: null}
|
||||
{contentType === 'video' && contentPosition !== 'background'
|
||||
? videoBlock(illustrationVideo)
|
||||
: null}
|
||||
<div className='flex items-center justify-center flex-col space-y-4 w-full lg:w-full'>
|
||||
<CardBox className='w-full md:w-3/5 lg:w-2/3'>
|
||||
<CardBoxComponentTitle title="Welcome to your App Draft app!"/>
|
||||
|
||||
<div className="space-y-3">
|
||||
<p className='text-center '>This is a React.js/Node.js app generated by the <a className={`${textColor}`} href="https://flatlogic.com/generator">Flatlogic Web App Generator</a></p>
|
||||
<p className='text-center '>For guides and documentation please check
|
||||
your local README.md and the <a className={`${textColor}`} href="https://flatlogic.com/documentation">Flatlogic documentation</a></p>
|
||||
</div>
|
||||
|
||||
<BaseButtons>
|
||||
<BaseButton
|
||||
href='/login'
|
||||
label='Login'
|
||||
color='info'
|
||||
className='w-full'
|
||||
/>
|
||||
<audio
|
||||
ref={audioRef}
|
||||
src={currentSound.url}
|
||||
loop
|
||||
onPlay={() => setIsPlaying(true)}
|
||||
onPause={() => setIsPlaying(false)}
|
||||
/>
|
||||
|
||||
</BaseButtons>
|
||||
</CardBox>
|
||||
{/* Header */}
|
||||
<nav className="flex items-center justify-between px-6 md:px-12 py-6 bg-slate-950/50 backdrop-blur-xl sticky top-0 z-50 border-b border-white/5">
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="w-10 h-10 bg-gradient-to-br from-indigo-500 to-purple-600 rounded-xl flex items-center justify-center shadow-lg shadow-indigo-500/20 rotate-3">
|
||||
<BaseIcon path={mdiTuneVariant} size={22} className="text-white" />
|
||||
</div>
|
||||
<span className="text-2xl font-black tracking-tighter text-white">
|
||||
ZenNoise
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</SectionFullScreen>
|
||||
<div className='bg-black text-white flex flex-col text-center justify-center md:flex-row'>
|
||||
<p className='py-6 text-sm'>© 2026 <span>{title}</span>. All rights reserved</p>
|
||||
<Link className='py-6 ml-4 text-sm' href='/privacy-policy/'>
|
||||
Privacy Policy
|
||||
</Link>
|
||||
</div>
|
||||
<div className="flex items-center space-x-4 md:space-x-8">
|
||||
<Link href="/login" className="hidden md:block text-sm font-bold text-slate-400 hover:text-white transition-colors uppercase tracking-widest">
|
||||
Sign In
|
||||
</Link>
|
||||
<Link
|
||||
href="/login"
|
||||
className="px-6 py-2.5 bg-white hover:bg-slate-200 text-slate-950 text-sm font-bold rounded-full transition-all shadow-xl hover:scale-105 active:scale-95"
|
||||
>
|
||||
Dashboard
|
||||
</Link>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main className="flex-grow container mx-auto px-4 py-12 space-y-24">
|
||||
|
||||
{/* Hero & Primary Player */}
|
||||
<section className="flex flex-col items-center justify-center text-center space-y-12 relative py-20">
|
||||
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[600px] h-[600px] bg-indigo-600/10 rounded-full blur-[120px] -z-10" />
|
||||
|
||||
<div className="space-y-6">
|
||||
<h1 className="text-5xl md:text-7xl font-black tracking-tight text-white leading-tight">
|
||||
Design Your <br />
|
||||
<span className="text-transparent bg-clip-text bg-gradient-to-r from-indigo-400 to-purple-400">Atmosphere.</span>
|
||||
</h1>
|
||||
<p className="text-slate-400 text-lg md:text-xl max-w-2xl mx-auto font-medium">
|
||||
Mixing high-fidelity noise loops with a precision study timer to help you reach a state of absolute focus.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col items-center space-y-8">
|
||||
<div className="relative group cursor-pointer" onClick={togglePlay}>
|
||||
<div className={`absolute -inset-10 bg-indigo-500/20 rounded-full blur-3xl transition-all duration-1000 ${isPlaying ? 'animate-pulse scale-125' : 'scale-100'}`} />
|
||||
<div className={`relative w-48 h-48 rounded-full flex items-center justify-center transition-all duration-700 ${
|
||||
isPlaying
|
||||
? 'bg-white text-slate-950 scale-105 shadow-[0_0_80px_rgba(255,255,255,0.2)]'
|
||||
: 'bg-indigo-600 text-white shadow-2xl shadow-indigo-600/40'
|
||||
}`}>
|
||||
<BaseIcon path={isPlaying ? mdiPause : mdiPlay} size={80} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-3 text-slate-400 font-bold uppercase tracking-widest text-xs">
|
||||
<span className="w-8 h-[1px] bg-slate-800" />
|
||||
<span>Now Playing: {currentSound.name}</span>
|
||||
<span className="w-8 h-[1px] bg-slate-800" />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12">
|
||||
|
||||
{/* Study Timer Section */}
|
||||
<section className="bg-white/5 border border-white/10 rounded-[40px] p-8 md:p-12 space-y-8 backdrop-blur-sm relative overflow-hidden group">
|
||||
<div className="absolute top-0 right-0 p-8 opacity-10 group-hover:opacity-20 transition-opacity">
|
||||
<BaseIcon path={mdiClockOutline} size={120} />
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<h2 className="text-3xl font-black text-white">Study Timer</h2>
|
||||
<p className="text-slate-400 font-medium">Precision Pomodoro to keep your brain sharp.</p>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col items-center space-y-8">
|
||||
<div className="text-8xl md:text-9xl font-black tabular-nums tracking-tighter text-white">
|
||||
{String(timerMinutes).padStart(2, '0')}:{String(timerSeconds).padStart(2, '0')}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-4">
|
||||
<button
|
||||
onClick={toggleTimer}
|
||||
className={`px-10 py-4 rounded-2xl font-bold text-lg transition-all active:scale-95 ${
|
||||
isTimerRunning
|
||||
? 'bg-slate-800 text-white hover:bg-slate-700'
|
||||
: 'bg-indigo-600 text-white hover:bg-indigo-500 shadow-xl shadow-indigo-600/20'
|
||||
}`}
|
||||
>
|
||||
{isTimerRunning ? 'Pause' : 'Start Focus'}
|
||||
</button>
|
||||
<button
|
||||
onClick={resetTimer}
|
||||
className="p-4 bg-white/5 hover:bg-white/10 rounded-2xl text-slate-400 hover:text-white transition-all"
|
||||
>
|
||||
<BaseIcon path={mdiRefresh} size={28} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="flex bg-slate-900 p-1.5 rounded-2xl border border-white/5">
|
||||
<button
|
||||
onClick={() => { setTimerMode('focus'); setTimerMinutes(25); setTimerSeconds(0); setIsTimerRunning(false); }}
|
||||
className={`px-6 py-2 rounded-xl text-sm font-bold transition-all ${timerMode === 'focus' ? 'bg-white text-slate-950 shadow-lg' : 'text-slate-500 hover:text-slate-300'}`}
|
||||
>
|
||||
Focus (25m)
|
||||
</button>
|
||||
<button
|
||||
onClick={() => { setTimerMode('break'); setTimerMinutes(5); setTimerSeconds(0); setIsTimerRunning(false); }}
|
||||
className={`px-6 py-2 rounded-xl text-sm font-bold transition-all ${timerMode === 'break' ? 'bg-white text-slate-950 shadow-lg' : 'text-slate-500 hover:text-slate-300'}`}
|
||||
>
|
||||
Break (5m)
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Library Section */}
|
||||
<section className="bg-white/5 border border-white/10 rounded-[40px] p-8 md:p-12 space-y-8 backdrop-blur-sm relative overflow-hidden group">
|
||||
<div className="absolute top-0 right-0 p-8 opacity-10 group-hover:opacity-20 transition-opacity">
|
||||
<BaseIcon path={mdiBookOpenVariant} size={120} />
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<h2 className="text-3xl font-black text-white">Sound Library</h2>
|
||||
<p className="text-slate-400 font-medium">Switch between different high-fidelity environments.</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
{sounds.map((sound) => (
|
||||
<button
|
||||
key={sound.id}
|
||||
onClick={() => changeSound(sound)}
|
||||
className={`p-6 rounded-3xl border transition-all text-left flex flex-col space-y-4 group/card ${
|
||||
currentSound.id === sound.id
|
||||
? 'bg-white border-white text-slate-950'
|
||||
: 'bg-white/5 border-white/10 text-white hover:bg-white/10 hover:border-white/20'
|
||||
}`}
|
||||
>
|
||||
<div className={`w-12 h-12 rounded-2xl flex items-center justify-center transition-colors ${
|
||||
currentSound.id === sound.id ? 'bg-slate-100 text-indigo-600' : 'bg-slate-900 text-slate-400 group-hover/card:text-indigo-400'
|
||||
}`}>
|
||||
<BaseIcon path={sound.icon} size={28} />
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-bold text-lg">{sound.name}</div>
|
||||
<div className={`text-xs uppercase tracking-widest font-bold opacity-50 ${currentSound.id === sound.id ? 'text-indigo-600' : 'text-slate-400'}`}>
|
||||
{sound.id === 'white' ? 'Continuous' : 'Atmospheric'}
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Volume Control Overlay */}
|
||||
<div className="pt-4">
|
||||
<div className="bg-slate-900/80 backdrop-blur-xl p-6 rounded-[32px] border border-white/5 flex items-center space-x-6">
|
||||
<BaseIcon path={volume > 0.5 ? mdiVolumeHigh : volume > 0 ? mdiVolumeMedium : mdiVolumeMedium} size={24} className="text-indigo-400" />
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max="1"
|
||||
step="0.01"
|
||||
value={volume}
|
||||
onChange={handleVolumeChange}
|
||||
className="flex-grow h-1.5 bg-slate-800 rounded-lg appearance-none cursor-pointer accent-indigo-500"
|
||||
/>
|
||||
<span className="text-slate-400 font-mono text-xs w-8">{Math.round(volume * 100)}%</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
{/* Benefits Section */}
|
||||
<section className="grid grid-cols-1 md:grid-cols-3 gap-8 text-center py-12">
|
||||
<div className="space-y-4">
|
||||
<div className="w-16 h-16 bg-indigo-500/10 rounded-2xl flex items-center justify-center mx-auto text-indigo-400">
|
||||
<BaseIcon path={mdiTuneVariant} size={32} />
|
||||
</div>
|
||||
<h3 className="text-xl font-bold text-white">Mask Distractions</h3>
|
||||
<p className="text-slate-500 leading-relaxed">Cancel out annoying background noise like construction or chatter with ease.</p>
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
<div className="w-16 h-16 bg-purple-500/10 rounded-2xl flex items-center justify-center mx-auto text-purple-400">
|
||||
<BaseIcon path={mdiClockOutline} size={32} />
|
||||
</div>
|
||||
<h3 className="text-xl font-bold text-white">Improve Focus</h3>
|
||||
<p className="text-slate-500 leading-relaxed">Standardize your study environment to trigger "flow state" faster and more reliably.</p>
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
<div className="w-16 h-16 bg-cyan-500/10 rounded-2xl flex items-center justify-center mx-auto text-cyan-400">
|
||||
<BaseIcon path={mdiCoffeeOutline} size={32} />
|
||||
</div>
|
||||
<h3 className="text-xl font-bold text-white">Reduce Stress</h3>
|
||||
<p className="text-slate-500 leading-relaxed">Gentle noise frequencies are proven to lower cortisol levels during high-stress tasks.</p>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
{/* Footer */}
|
||||
<footer className="py-12 px-12 border-t border-white/5 flex flex-col md:flex-row items-center justify-between text-slate-500 text-sm bg-slate-950">
|
||||
<div className="flex items-center space-x-2 opacity-50 mb-6 md:mb-0">
|
||||
<BaseIcon path={mdiTuneVariant} size={18} />
|
||||
<span className="font-bold tracking-tighter">ZenNoise</span>
|
||||
</div>
|
||||
<div className="flex flex-col md:flex-row items-center md:space-x-12 space-y-4 md:space-y-0">
|
||||
<p>© 2026. Designed for Focus.</p>
|
||||
<div className="flex space-x-6">
|
||||
<Link href="/privacy-policy" className="hover:text-indigo-400 transition-colors">Privacy</Link>
|
||||
<Link href="/terms" className="hover:text-indigo-400 transition-colors">Terms</Link>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Starter.getLayout = function getLayout(page: ReactElement) {
|
||||
WhiteNoiseHome.getLayout = function getLayout(page: ReactElement) {
|
||||
return <LayoutGuest>{page}</LayoutGuest>;
|
||||
};
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user