diff --git a/public/cat.html b/public/cat.html new file mode 100644 index 0000000..3fb7015 --- /dev/null +++ b/public/cat.html @@ -0,0 +1,394 @@ + + + + + + Static TV Cat Component + + + +
+
+
+ +
+
+
+
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/favicon.ico b/public/favicon.ico deleted file mode 100644 index a11777c..0000000 Binary files a/public/favicon.ico and /dev/null differ diff --git a/public/index.html b/public/index.html index aa069f2..20f5ebd 100644 --- a/public/index.html +++ b/public/index.html @@ -1,43 +1,13 @@ - - - - - - - - - - - React App - - - + + + + Purrgram Login + + + +
- - - + + \ No newline at end of file diff --git a/public/logo192.png b/public/logo192.png deleted file mode 100644 index fc44b0a..0000000 Binary files a/public/logo192.png and /dev/null differ diff --git a/public/logo512.png b/public/logo512.png deleted file mode 100644 index a4e47a6..0000000 Binary files a/public/logo512.png and /dev/null differ diff --git a/public/manifest.json b/public/manifest.json deleted file mode 100644 index 080d6c7..0000000 --- a/public/manifest.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "short_name": "React App", - "name": "Create React App Sample", - "icons": [ - { - "src": "favicon.ico", - "sizes": "64x64 32x32 24x24 16x16", - "type": "image/x-icon" - }, - { - "src": "logo192.png", - "type": "image/png", - "sizes": "192x192" - }, - { - "src": "logo512.png", - "type": "image/png", - "sizes": "512x512" - } - ], - "start_url": ".", - "display": "standalone", - "theme_color": "#000000", - "background_color": "#ffffff" -} diff --git a/public/robots.txt b/public/robots.txt deleted file mode 100644 index e9e57dc..0000000 --- a/public/robots.txt +++ /dev/null @@ -1,3 +0,0 @@ -# https://www.robotstxt.org/robotstxt.html -User-agent: * -Disallow: diff --git a/src/App.css b/src/App.css index 74b5e05..0fc813a 100644 --- a/src/App.css +++ b/src/App.css @@ -1,38 +1,11 @@ -.App { - text-align: center; +* { + margin: 0; + padding: 0; + box-sizing: border-box; } -.App-logo { - height: 40vmin; - pointer-events: none; -} - -@media (prefers-reduced-motion: no-preference) { - .App-logo { - animation: App-logo-spin infinite 20s linear; - } -} - -.App-header { - background-color: #282c34; - min-height: 100vh; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - font-size: calc(10px + 2vmin); - color: white; -} - -.App-link { - color: #61dafb; -} - -@keyframes App-logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} +body { + margin: 0; + padding: 0; + overflow-x: hidden; +} \ No newline at end of file diff --git a/src/App.js b/src/App.js index 3784575..bb016a4 100644 --- a/src/App.js +++ b/src/App.js @@ -1,25 +1,13 @@ -import logo from './logo.svg'; +import React from 'react'; +import CRTTerminal from './components/CRT/CRTTerminal'; import './App.css'; function App() { - return ( -
-
- logo -

- Edit src/App.js and save to reload. -

- - Learn React - -
-
- ); + return ( +
+ +
+ ); } -export default App; +export default App; \ No newline at end of file diff --git a/src/App.test.js b/src/App.test.js deleted file mode 100644 index 1f03afe..0000000 --- a/src/App.test.js +++ /dev/null @@ -1,8 +0,0 @@ -import { render, screen } from '@testing-library/react'; -import App from './App'; - -test('renders learn react link', () => { - render(); - const linkElement = screen.getByText(/learn react/i); - expect(linkElement).toBeInTheDocument(); -}); diff --git a/src/components/CRT/CRTTerminal.css b/src/components/CRT/CRTTerminal.css new file mode 100644 index 0000000..eaf6c42 --- /dev/null +++ b/src/components/CRT/CRTTerminal.css @@ -0,0 +1,456 @@ +/* CRT Terminal Styles */ +* { + margin: 0; + padding: 0; + box-sizing: border-box; + image-rendering: crisp-edges; + image-rendering: pixelated; +} + +/* Base body styles - applied to container */ +.crt-terminal { + background: #0a0a0a; + min-height: 100vh; + width: 100%; + display: flex; + align-items: center; + justify-content: center; + font-family: 'Press Start 2P', 'Courier New', monospace; + padding: 20px; + transition: background 0.1s linear; + position: relative; +} + +/* CRT static grain overlay */ +.crt-terminal::before { + content: ''; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-image: repeating-linear-gradient(0deg, rgba(0,0,0,0.06) 0px, rgba(0,0,0,0.06) 1px, transparent 1px, transparent 4px); + pointer-events: none; + z-index: 9998; +} + +/* LIGHT MODE */ +.crt-terminal.light-mode { + background: #e0dcd0; +} + +/* Hide iframe scrollbars */ +.cat-iframe { + overflow: hidden; +} + +.cat-iframe::-webkit-scrollbar { + display: none; /* Hides scrollbar in Chrome/Safari */ +} + +.crt-terminal.light-mode .pixel-card { + background: #f0ece0; + border-color: #2a2a2a; + box-shadow: 8px 8px 0 #3a3a3a; +} + +.crt-terminal.light-mode .pixel-input { + background: #ffffff; + border-color: #4a4a4a; + color: #111111; +} + +.crt-terminal.light-mode .pixel-input:focus { + border-color: #000000; + box-shadow: 0 0 0 2px #666666; +} + +.crt-terminal.light-mode .pixel-btn { + background: #d0ccc0; + border-color: #3a3a3a; + color: #111111; + box-shadow: inset 0 1px 0 #ffffff, 3px 3px 0 #4a4a4a; +} + +.crt-terminal.light-mode .pixel-btn:active { + transform: translate(2px, 2px); + box-shadow: inset 0 1px 0 #ffffff, 1px 1px 0 #4a4a4a; +} + +.crt-terminal.light-mode .toggle-pixel { + background: #c8c4b8; + border-color: #4a4a4a; + color: #1a1a1a; +} + +.crt-terminal.light-mode .toggle-pixel.active { + background: #2a2a2a; + border-color: #e0e0e0; + color: #f0f0f0; + box-shadow: inset 0 0 0 1px #5a5a5a; +} + +.crt-terminal.light-mode .side-block { + border-color: #5a5a5a; + background: #d8d4c8; + color: #2a2a2a; +} + +.crt-terminal.light-mode .theme-pixel { + background: #c8c4b8; + border-color: #3a3a3a; + color: #111; +} + +.crt-terminal.light-mode::before { + opacity: 0.3; +} + +/* MAIN GRID */ +.pixel-grid { + max-width: 1260px; + width: 100%; + display: flex; + gap: 28px; + justify-content: center; + align-items: center; + flex-wrap: wrap; + min-width: 320px; +} + +/* SIDE PANELS */ +.side-pixel { + flex: 1; + min-width: 100px; + max-width: 180px; + display: flex; + justify-content: center; + align-items: center; +} + +.side-block { + width: 100%; + height: 280px; + border: 3px solid #5a5a5a; + background: #1a1a1a; + image-rendering: pixelated; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + font-size: 9px; + text-align: center; + color: #888888; + letter-spacing: 1px; + gap: 14px; + box-shadow: inset 0 0 0 2px #2a2a2a, 6px 6px 0 #000000; + border-radius: 0px; +} + +.side-block span { + display: block; + font-size: 20px; + filter: grayscale(100%); +} + +/* CENTER CARD */ +.pixel-card { + background: #121212; + border: 3px solid #5a5a5a; + padding: 20px 24px 28px; + width: 100%; + max-width: 490px; + box-shadow: 12px 12px 0 #050505; + position: relative; + border-radius: 0px; +} + +/* CRT scanline effect */ +.pixel-card::before { + content: ""; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: repeating-linear-gradient(0deg, rgba(0, 0, 0, 0.15) 0px, rgba(0, 0, 0, 0.15) 2px, transparent 2px, transparent 6px); + pointer-events: none; + z-index: 2; +} + +/* Static noise */ +.pixel-card::after { + content: ""; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-image: radial-gradient(circle at 20% 40%, rgba(100, 100, 100, 0.1) 1px, transparent 1px); + background-size: 6px 6px; + pointer-events: none; + z-index: 1; +} + +/* CAT IFRAME CONTAINER */ +.cat-zone { + display: flex; + justify-content: center; + margin-bottom: 22px; + border: 2px solid #4a4a4a; + background: #000000; + box-shadow: inset 0 0 0 2px #2a2a2a, 4px 4px 0 #00000066; + border-radius: 0px; +} + +.cat-iframe { + display: block; + width: 100%; + height: auto; + max-width: 352px; + aspect-ratio: 416 / 352; + border: none; + background: black; + image-rendering: crisp-edges; + image-rendering: pixelated; + border-radius: 0px; +} + +/* Toggle group */ +.toggle-row { + display: flex; + gap: 12px; + margin-bottom: 28px; + justify-content: center; +} + +.toggle-pixel { + font-family: 'Press Start 2P', monospace; + background: #1a1a1a; + border: 2px solid #6a6a6a; + font-size: 11px; + padding: 8px 18px; + cursor: pointer; + text-transform: uppercase; + color: #e0e0e0; + transition: all 0.03s linear; + letter-spacing: 1px; + box-shadow: inset 0 1px 0 #4a4a4a, 2px 2px 0 #030303; + border-radius: 0px; + filter: grayscale(100%); +} + +.toggle-pixel.active { + background: #e0e0e0; + border-color: #2a2a2a; + color: #0a0a0a; + box-shadow: inset 0 0 0 1px #ffffff70, 1px 1px 0 #2a2a2a; +} + +/* Form elements */ +.input-pixel-group { + margin-bottom: 20px; +} + +.pixel-label { + display: block; + font-size: 9px; + margin-bottom: 10px; + letter-spacing: 1px; + color: #aaaaaa; + text-transform: uppercase; + filter: grayscale(100%); +} + +.pixel-input { + width: 100%; + background: #0a0a0a; + border: 2px solid #5a5a5a; + font-family: 'Press Start 2P', monospace; + font-size: 10px; + padding: 12px 14px; + color: #eeeeee; + outline: none; + transition: 0.05s linear; + letter-spacing: 0.4px; + border-radius: 0px; +} + +.pixel-input:focus { + border-color: #aaaaaa; + background: #111111; +} + +.pixel-input::placeholder { + color: #555555; + font-size: 8px; +} + +/* Buttons */ +.pixel-btn { + width: 100%; + background: #2a2a2a; + border: none; + font-family: 'Press Start 2P', monospace; + font-size: 12px; + padding: 12px 12px; + margin-top: 12px; + color: #eeeeee; + border-bottom: 3px solid #6a6a6a; + cursor: pointer; + text-transform: uppercase; + transition: 0.03s linear; + text-align: center; + border-radius: 0px; + filter: grayscale(100%); +} + +.pixel-btn:active { + transform: translate(2px, 2px); + border-bottom-width: 1px; +} + +.google-pixel { + width: 100%; + background: #1a1a1a; + border: 2px solid #6a6a6a; + font-family: 'Press Start 2P', monospace; + font-size: 10px; + padding: 10px; + margin-top: 18px; + display: flex; + gap: 12px; + justify-content: center; + align-items: center; + cursor: pointer; + color: #cccccc; + transition: 0.05s linear; + border-radius: 0px; + filter: grayscale(100%); +} + +.google-pixel:active { + background: #2a2a2a; + transform: scale(0.98); +} + +/* POPUP NOTIFICATION */ +.popup-notification { + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background: #0a0a0a; + border: 3px solid #aa2222; + padding: 20px 28px; + z-index: 10000; + font-family: 'Press Start 2P', monospace; + font-size: 11px; + text-align: center; + max-width: 380px; + width: 90%; + box-shadow: 12px 12px 0 rgba(0,0,0,0.6); + letter-spacing: 0.5px; + color: #eeeeee; + border-radius: 0px; +} + +.popup-notification.error { + border-color: #cc3333; + background: #1a0a0a; + color: #ff8888; +} + +.popup-notification.success { + border-color: #888888; + background: #1a1a1a; + color: #dddddd; +} + +.popup-notification .popup-message { + margin-bottom: 20px; + line-height: 1.6; + word-break: break-word; +} + +.popup-button { + background: #2a2a2a; + border: 2px solid #6a6a6a; + font-family: 'Press Start 2P', monospace; + font-size: 9px; + padding: 8px 18px; + cursor: pointer; + color: #dddddd; + transition: 0.03s linear; + display: inline-block; + margin-top: 8px; + border-radius: 0px; +} + +.popup-button:active { + transform: translate(1px, 1px); +} + +/* Theme toggle button */ +.theme-pixel { + position: fixed; + bottom: 20px; + right: 20px; + background: #1a1a1a; + border: 2px solid #6a6a6a; + width: 48px; + height: 48px; + display: flex; + align-items: center; + justify-content: center; + font-size: 24px; + cursor: pointer; + z-index: 999; + transition: 0.05s linear; + box-shadow: 4px 4px 0 #00000044; + border-radius: 0px; + filter: grayscale(100%); +} + +.theme-pixel:active { + transform: translate(2px, 2px); +} + +/* Utility - ensure no rounded corners */ +.pixel-card, .pixel-btn, .google-pixel, .toggle-pixel, +.pixel-input, .side-block, .cat-zone, .cat-iframe, +.theme-pixel, .popup-notification { + border-radius: 0px !important; +} + +/* Responsive */ +@media (max-width: 860px) { + .side-pixel { + display: none; + } + .pixel-grid { + gap: 0; + } +} + +@media (max-width: 540px) { + .crt-terminal { + padding: 12px; + } + .pixel-card { + padding: 16px; + } + .toggle-pixel { + font-size: 8px; + padding: 6px 12px; + } + .pixel-input { + font-size: 9px; + padding: 10px; + } + .popup-notification { + font-size: 9px; + padding: 16px; + width: 85%; + } +} \ No newline at end of file diff --git a/src/components/CRT/CRTTerminal.jsx b/src/components/CRT/CRTTerminal.jsx new file mode 100644 index 0000000..28a9786 --- /dev/null +++ b/src/components/CRT/CRTTerminal.jsx @@ -0,0 +1,159 @@ +import React, { useState, useEffect, useRef } from 'react'; +import LoginForm from './LoginForm'; +import SignupForm from './SignupForm'; +import PopupNotification from './PopupNotification'; +import ThemeToggle from './ThemeToggle'; +import { usePopup } from '../../hooks/usePopup'; +import './CRTTerminal.css'; + +const CRTTerminal = () => { + const [mode, setMode] = useState('login'); // 'login' or 'signup' + const [isLightMode, setIsLightMode] = useState(false); + const { popup, showPopup, hidePopup } = usePopup(); + const iframeRef = useRef(null); + + // Theme management + useEffect(() => { + const stored = localStorage.getItem('crtPixelTheme'); + if (stored === 'light') { + setIsLightMode(true); + } + }, []); + + useEffect(() => { + if (isLightMode) { + document.body.classList.add('light-mode'); + localStorage.setItem('crtPixelTheme', 'light'); + } else { + document.body.classList.remove('light-mode'); + localStorage.setItem('crtPixelTheme', 'dark'); + } + }, [isLightMode]); + + const toggleTheme = () => { + setIsLightMode(prev => !prev); + }; + + // Iframe scaling + useEffect(() => { + const adjustIframe = () => { + if (iframeRef.current) { + const container = iframeRef.current.parentElement; + if (container) { + const containerWidth = container.clientWidth; + iframeRef.current.style.width = containerWidth < 352 ? '100%' : '352px'; + } + } + }; + + window.addEventListener('resize', adjustIframe); + adjustIframe(); + + return () => window.removeEventListener('resize', adjustIframe); + }, []); + + // Auth handlers + const handleLogin = (email, password) => { + // Demo login validation + if (password.toLowerCase() === 'wrong' || email === 'denied@error.com') { + showPopup('ACCESS DENIED: INVALID CREDENTIALS', true); + return; + } + + const usernamePart = email.split('@')[0]; + showPopup(`ACCESS GRANTED: WELCOME ${usernamePart}`, false); + }; + + const handleSignup = (userData) => { + showPopup(`ACCOUNT CREATED: USERNAME [${userData.username}] EMAIL [${userData.email}]`, false); + // Switch to login mode after successful signup + setMode('login'); + }; + + const handleGoogleAuth = () => { + showPopup('GOOGLE AUTHENTICATION: DEMO FLOW CONNECTED', false); + }; + + return ( +
+
+ {/* LEFT empty zone */} +
+
+ ◢◤ + [ 8-BIT ZONE ] + ◥◣ +
+
+ + {/* MAIN CARD */} +
+ {/* CAT IFRAME */} +
+