mirror of
https://github.com/hazemKrimi/touch-programming.git
synced 2026-05-01 18:20:26 +00:00
Add fonts, spinner and small screen detection
This commit is contained in:
Binary file not shown.
Binary file not shown.
@@ -7,6 +7,7 @@ import { KEYS_TO_DISABLE } from 'constants/default';
|
|||||||
import { renderCharacter } from './utils';
|
import { renderCharacter } from './utils';
|
||||||
|
|
||||||
import './index.css';
|
import './index.css';
|
||||||
|
import Spinner from 'components/Spinner';
|
||||||
|
|
||||||
type CodeProps = {
|
type CodeProps = {
|
||||||
code: string;
|
code: string;
|
||||||
@@ -72,6 +73,12 @@ function Code({ code, loaded }: CodeProps) {
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [timer, characters]);
|
}, [timer, characters]);
|
||||||
|
|
||||||
|
if (!code) return (
|
||||||
|
<div className='code-container'>
|
||||||
|
<Spinner />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='code-container'>
|
<div className='code-container'>
|
||||||
{code.split('').map((char, index) => renderCharacter(code, characters, loaded, char, index))}
|
{code.split('').map((char, index) => renderCharacter(code, characters, loaded, char, index))}
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
.spinner-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner {
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
border: 5px solid var(--white);
|
||||||
|
border-top: 5px solid var(--crimson);
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
0% {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import './index.css';
|
||||||
|
|
||||||
|
function Spinner() {
|
||||||
|
return (
|
||||||
|
<div className='spinner-container'>
|
||||||
|
<div className='spinner'></div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Spinner;
|
||||||
@@ -34,7 +34,6 @@ export const KEYS_TO_DISABLE = [
|
|||||||
'ArrowLeft',
|
'ArrowLeft',
|
||||||
'ArrowRight',
|
'ArrowRight',
|
||||||
];
|
];
|
||||||
|
|
||||||
export const SUPPORTED_LANGUAGES = [
|
export const SUPPORTED_LANGUAGES = [
|
||||||
'javascript',
|
'javascript',
|
||||||
'typescript',
|
'typescript',
|
||||||
|
|||||||
+15
-1
@@ -1,10 +1,24 @@
|
|||||||
|
@font-face {
|
||||||
|
font-family: '0xProto';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: normal;
|
||||||
|
src: url(assets/fonts/0xProtoNerdFont-Regular.ttf) format(truetype);
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: '0xProto';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: bold;
|
||||||
|
src: url(assets/fonts/0xProtoNerdFont-Bold.ttf) format(truetype);
|
||||||
|
}
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--black: #131314;
|
--black: #131314;
|
||||||
--white: #dddddd;
|
--white: #dddddd;
|
||||||
--crimson: #bd1839;
|
--crimson: #bd1839;
|
||||||
--background: #1d1b1b;
|
--background: #1d1b1b;
|
||||||
|
|
||||||
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
|
font-family: '0xProto', sans-serif;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,10 @@ header p {
|
|||||||
color: var(--white);
|
color: var(--white);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
header span {
|
||||||
|
color: var(--crimson);
|
||||||
|
}
|
||||||
|
|
||||||
header .cta {
|
header .cta {
|
||||||
all: unset;
|
all: unset;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@@ -52,7 +56,6 @@ header .cta {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.features {
|
.features {
|
||||||
margin-top: 2rem;
|
|
||||||
padding: 2.5rem 1.25rem;
|
padding: 2.5rem 1.25rem;
|
||||||
|
|
||||||
display: grid;
|
display: grid;
|
||||||
|
|||||||
@@ -1,16 +1,24 @@
|
|||||||
import { NavLink } from 'react-router';
|
import { NavLink } from 'react-router';
|
||||||
|
|
||||||
|
import { isMobileBrowser } from 'utils';
|
||||||
|
|
||||||
import './index.css';
|
import './index.css';
|
||||||
|
|
||||||
function Home() {
|
function Home() {
|
||||||
|
const isMobile = isMobileBrowser();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='home-container'>
|
<div className='home-container'>
|
||||||
<header>
|
<header>
|
||||||
<h1>Touch Programming</h1>
|
<h1>Touch Programming</h1>
|
||||||
<p>Master touch typing with real code snippets from your favorite programming languages, powered by AI.</p>
|
<p>Master touch typing with real code snippets from your favorite programming languages, powered by AI.</p>
|
||||||
<NavLink to='/languages'>
|
<NavLink to='/languages'>
|
||||||
|
{isMobile ?
|
||||||
|
<span>This app is made to be used in a desktop device.</span> :
|
||||||
<button className='cta'>
|
<button className='cta'>
|
||||||
Start Typing
|
Start Typing
|
||||||
</button>
|
</button>
|
||||||
|
}
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,10 @@ header h1 {
|
|||||||
color: var(--crimson);
|
color: var(--crimson);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
header span {
|
||||||
|
color: var(--crimson);
|
||||||
|
}
|
||||||
|
|
||||||
.languages-wrapper {
|
.languages-wrapper {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||||
|
|||||||
@@ -1,10 +1,24 @@
|
|||||||
import { NavLink } from 'react-router';
|
import { NavLink } from 'react-router';
|
||||||
|
|
||||||
|
import { isMobileBrowser } from 'utils';
|
||||||
|
|
||||||
import { SUPPORTED_LANGUAGES } from 'constants/default';
|
import { SUPPORTED_LANGUAGES } from 'constants/default';
|
||||||
|
|
||||||
import './index.css';
|
import './index.css';
|
||||||
|
|
||||||
function Languages() {
|
function Languages() {
|
||||||
|
const isMobile = isMobileBrowser();
|
||||||
|
|
||||||
|
if (isMobile) {
|
||||||
|
return (
|
||||||
|
<div className='languages-container'>
|
||||||
|
<header>
|
||||||
|
<span>This app is made to be used in a desktop device.</span>
|
||||||
|
</header>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='languages-container'>
|
<div className='languages-container'>
|
||||||
<header>
|
<header>
|
||||||
|
|||||||
@@ -24,3 +24,7 @@ header h1 {
|
|||||||
|
|
||||||
text-transform: capitalize;
|
text-transform: capitalize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
header span {
|
||||||
|
color: var(--crimson);
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,17 +3,22 @@ import { useParams } from 'react-router';
|
|||||||
|
|
||||||
import TypingContextProvider from 'contexts/typing';
|
import TypingContextProvider from 'contexts/typing';
|
||||||
|
|
||||||
|
import { isMobileBrowser } from 'utils';
|
||||||
|
|
||||||
import Code from 'components/Code';
|
import Code from 'components/Code';
|
||||||
import Stats from 'components/Stats';
|
import Stats from 'components/Stats';
|
||||||
|
|
||||||
import './index.css';
|
import './index.css';
|
||||||
|
|
||||||
function Typing() {
|
function Typing() {
|
||||||
|
const isMobile = isMobileBrowser();
|
||||||
const { lang } = useParams();
|
const { lang } = useParams();
|
||||||
const [code, setCode] = useState<string>('');
|
const [code, setCode] = useState<string>('');
|
||||||
const [loaded, setLoaded] = useState<boolean>(false);
|
const [loaded, setLoaded] = useState<boolean>(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (isMobile) return;
|
||||||
|
|
||||||
(async function () {
|
(async function () {
|
||||||
setCode('');
|
setCode('');
|
||||||
|
|
||||||
@@ -39,7 +44,17 @@ function Typing() {
|
|||||||
setCode((prev) => prev.trim());
|
setCode((prev) => prev.trim());
|
||||||
setLoaded(true);
|
setLoaded(true);
|
||||||
})();
|
})();
|
||||||
}, [lang]);
|
}, [isMobile, lang]);
|
||||||
|
|
||||||
|
if (isMobile) {
|
||||||
|
return (
|
||||||
|
<div className='typing-container'>
|
||||||
|
<header>
|
||||||
|
<span>This app is made to be used in a desktop device.</span>
|
||||||
|
</header>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TypingContextProvider>
|
<TypingContextProvider>
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
export function isMobileBrowser(): boolean {
|
||||||
|
const width = window.innerWidth;
|
||||||
|
|
||||||
|
return width <= 768 || (width > 768 && width <= 1024);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user