aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLoek Le Blansch <32883851+lonkaars@users.noreply.github.com>2021-04-21 10:40:52 +0200
committerGitHub <noreply@github.com>2021-04-21 10:40:52 +0200
commitdadc722875b2095bd3d6c4ab628a644197b85f7b (patch)
tree9e061708fad5bfdcc40f4c40662d77fbc42cfe64
parentc603cb79e7ba7fdbb101a506e36f6d8d70b3a8f4 (diff)
parent5cb39d822716c650e520c3855ef049ff308a348c (diff)
Merge pull request #12 from lonkaars/css-files
big redesign css move thing
-rw-r--r--components/account.tsx4
-rw-r--r--components/dialogBox.tsx29
-rw-r--r--components/footer.tsx4
-rw-r--r--components/gameBar.tsx92
-rw-r--r--components/gameSettings.tsx372
-rw-r--r--components/logo.tsx26
-rw-r--r--components/navbar.tsx78
-rw-r--r--components/notificationsArea.tsx103
-rw-r--r--components/page.tsx34
-rw-r--r--components/recentGames.tsx56
-rw-r--r--components/toast.tsx75
-rw-r--r--components/ui.tsx239
-rw-r--r--components/voerBord.tsx50
-rw-r--r--package.json3
-rw-r--r--pages/_app.tsx14
-rw-r--r--pages/game.tsx109
-rw-r--r--pages/index.tsx153
-rw-r--r--pages/login.tsx46
-rw-r--r--pages/register.tsx40
-rw-r--r--pages/search.tsx64
-rw-r--r--pages/settings.tsx63
-rw-r--r--pages/user.tsx456
-rw-r--r--public/favicon.pngbin13855 -> 14117 bytes
-rw-r--r--public/favicon.svg8
-rw-r--r--readme.md9
-rw-r--r--styles/dark.css22
-rw-r--r--styles/disk.css4
-rw-r--r--styles/game.css76
-rw-r--r--styles/gameSettings.css77
-rw-r--r--styles/global.css90
-rw-r--r--styles/index.css71
-rw-r--r--styles/loginregister.css13
-rw-r--r--styles/navbar.css36
-rw-r--r--styles/notifications.css52
-rw-r--r--styles/readme.md103
-rw-r--r--styles/recentGames.css21
-rw-r--r--styles/search.css11
-rw-r--r--styles/settings.css8
-rw-r--r--styles/toast.css66
-rw-r--r--styles/ui.css118
-rw-r--r--styles/user.css46
-rw-r--r--styles/utility.css102
-rw-r--r--todo43
-rw-r--r--tsconfig.json7
-rw-r--r--yarn.lock233
45 files changed, 1689 insertions, 1637 deletions
diff --git a/components/account.tsx b/components/account.tsx
index f24135f..cb44346 100644
--- a/components/account.tsx
+++ b/components/account.tsx
@@ -19,14 +19,12 @@ export function AccountAvatar(props: {
if (props.dummy) image = dummy;
return <div
+ className={'accountAvatar dispinbl ' + (props.round ? 'round' : '')}
style={{
width: props.size,
height: props.size,
backgroundColor: props.fallbackFill || 'var(--background)',
backgroundImage: `url(${image})`,
- backgroundSize: 'cover',
- display: 'inline-block',
- borderRadius: props.size / 2 * Number(props.round || 0),
}}
/>;
}
diff --git a/components/dialogBox.tsx b/components/dialogBox.tsx
index 7abbded..a5b02fa 100644
--- a/components/dialogBox.tsx
+++ b/components/dialogBox.tsx
@@ -1,4 +1,4 @@
-import { CSSProperties, ReactNode } from 'react';
+import { ReactNode } from 'react';
import { Vierkant } from './ui';
@@ -7,32 +7,17 @@ import CancelIcon from '@material-ui/icons/Cancel';
export function DialogBox(props: {
children: ReactNode;
title: string;
- style?: CSSProperties;
onclick?: () => void;
+ hidden?: boolean;
+ className?: string;
}) {
return <Vierkant
- style={{
- position: 'fixed',
- top: '50%',
- left: '50%',
- transform: 'translate(-50%, -50%)',
- boxShadow: '0 8px 32px -5px #0007',
- width: 392,
- ...props.style,
- }}
+ className={'dialogbox bg-800 drop-2 pad-l posfix abscenter ' + (props.hidden ? 'dispnone' : '') + ' '
+ + props.className}
>
- <h2 style={{ marginBottom: 24 }}>{props.title}</h2>
+ <h2 className='title'>{props.title}</h2>
<span onClick={props.onclick}>
- <CancelIcon
- style={{
- position: 'absolute',
- top: 25,
- right: 25,
- color: 'var(--text)',
- opacity: .85,
- cursor: 'pointer',
- }}
- />
+ <CancelIcon className='posabs close icon subtile' />
</span>
{props.children}
</Vierkant>;
diff --git a/components/footer.tsx b/components/footer.tsx
index 36ebef3..f39946c 100644
--- a/components/footer.tsx
+++ b/components/footer.tsx
@@ -1,5 +1,5 @@
import { ReactNode } from 'react';
-import { LogoDark } from '../components/logo';
+import Logo from '../components/logo';
import ExitToAppOutlinedIcon from '@material-ui/icons/ExitToAppOutlined';
import ExtensionIcon from '@material-ui/icons/Extension';
@@ -25,7 +25,7 @@ function PageLink(props: {
export function Footer() {
return <div className='footer'>
<div className='header'>
- <LogoDark />
+ <Logo />
<h2>4 op een rij</h2>
</div>
<div className='content'>
diff --git a/components/gameBar.tsx b/components/gameBar.tsx
index 0d7d4d9..9a7ca59 100644
--- a/components/gameBar.tsx
+++ b/components/gameBar.tsx
@@ -1,5 +1,5 @@
-import { CSSProperties, ReactNode } from 'react';
-import { Bubble, Vierkant } from './ui';
+import { ReactNode } from 'react';
+import { Vierkant } from './ui';
import ExitToAppRoundedIcon from '@material-ui/icons/ExitToAppRounded';
import NavigateBeforeRoundedIcon from '@material-ui/icons/NavigateBeforeRounded';
@@ -9,61 +9,30 @@ import SettingsRoundedIcon from '@material-ui/icons/SettingsRounded';
function GameBarModule(props: {
children?: ReactNode;
onclick?: () => void;
+ className?: string;
}) {
return <Vierkant
- style={{
- backgroundColor: 'var(--background-alt)',
- padding: 12,
- borderRadius: 6,
- margin: 0,
- verticalAlign: 'top',
- cursor: props.onclick ? 'pointer' : 'default',
- }}
+ className={'gameBarButton bg-700 pad-m round-t valigntop ' + props.className + ' '
+ + (props.onclick ? 'cpointer' : '')}
onclick={props.onclick}
>
{props.children}
</Vierkant>;
}
-var GameBarSpacer = () => <div style={{ width: 8, display: 'inline-block' }}></div>;
-
-var GameBarAlignStyle: CSSProperties = {
- display: 'inline-block',
-};
-
export function GameBar(props: {
turn: boolean;
player1: boolean;
active: boolean;
resignFunction: () => void;
}) {
- return <Vierkant
- className='gameBar'
- style={{
- padding: 8,
- width: 'calc(100% - 12px)',
- }}
- >
- <div style={{ gridAutoColumns: 'auto' }}>
- <div style={{ ...GameBarAlignStyle, float: 'left' }}>
- <div
- style={{
- width: 32,
- height: 32,
- margin: 8,
- backgroundColor: props.turn ? 'var(--disk-b)' : 'var(--disk-a)',
- borderRadius: 16,
- display: 'inline-block',
- }}
- />
- <h2
- style={{
- fontSize: 20,
- margin: 12,
- verticalAlign: 'top',
- display: 'inline-block',
- }}
- >
+ return <Vierkant className='gameBar bg-800 w100m2m pad-s'>
+ <div>
+ <div className='floatl dispinbl'>
+ {props.active && <div
+ className={'move dispinbl ' + (props.turn ? 'move-a' : 'move-b')}
+ />}
+ <h2 className='pad-m valigntop dispinbl'>
{!props.active
? 'Wachten op tegenstander...'
: props.turn == props.player1
@@ -71,49 +40,22 @@ export function GameBar(props: {
: 'Tegenstander'}
</h2>
</div>
- <div
- style={{
- ...GameBarAlignStyle,
- position: 'absolute',
- top: '50%',
- left: '50%',
- transform: 'translate(-50%, -50%)',
- }}
- >
- <span
- style={{
- color: 'var(--text)',
- fontSize: 20,
- opacity: .75 - .75,
- }}
- >
- 0-0
- </span>
+ <div className='score winning dispnone subtile posabs abscenter'>
+ <span>0-0</span>
</div>
- <div style={{ ...GameBarAlignStyle, float: 'right' }}>
+ <div className='buttons floatr dispinbl'>
<GameBarModule>
<SettingsRoundedIcon />
</GameBarModule>
- <GameBarSpacer />
- <GameBarModule>
- <span
- style={{
- margin: '0 4px',
- fontSize: 20,
- }}
- >
- 00:00
- </span>
+ <GameBarModule className='timer nosel'>
+ <span>00:00</span>
</GameBarModule>
- <GameBarSpacer />
<GameBarModule onclick={props.resignFunction}>
<ExitToAppRoundedIcon />
</GameBarModule>
- <GameBarSpacer />
<GameBarModule>
<NavigateBeforeRoundedIcon />
</GameBarModule>
- <GameBarSpacer />
<GameBarModule>
<NavigateNextRoundedIcon />
</GameBarModule>
diff --git a/components/gameSettings.tsx b/components/gameSettings.tsx
index f562e5d..e2c60e6 100644
--- a/components/gameSettings.tsx
+++ b/components/gameSettings.tsx
@@ -1,5 +1,5 @@
import axios from 'axios';
-import { Component, CSSProperties, ReactNode } from 'react';
+import { ReactNode, useEffect, useState } from 'react';
import { ruleset, userPreferences } from '../api/api';
import { DialogBox } from './dialogBox';
@@ -7,106 +7,57 @@ import { Button, CheckBox, Input, Vierkant } from './ui';
import BuildOutlinedIcon from '@material-ui/icons/BuildOutlined';
-type CurrentGameSettingsStateType = {
- editGameRulesDialogVisible: boolean;
- ruleset: ruleset;
-};
-
-export class CurrentGameSettings extends Component {
- state: CurrentGameSettingsStateType = {
- editGameRulesDialogVisible: false,
- ruleset: {
- timelimit: {
- enabled: false,
- shared: false,
- },
- ranked: false,
+export function CurrentGameSettings() {
+ var [editGameRulesDialogVisible, setEditGameRulesDialogVisible] = useState(false);
+ var [ruleset, setRuleset] = useState<ruleset>({
+ timelimit: {
+ enabled: false,
+ shared: false,
+ minutes: 0,
+ seconds: 0,
+ addmove: 0,
},
- };
-
- constructor(props: {}) {
- super(props);
+ ranked: false,
+ });
- if (typeof window === 'undefined') return; // return if run on server
-
- axios.request<userPreferences>({
+ useEffect(() => {
+ axios.request<{ preferences: userPreferences; }>({
method: 'get',
url: `/api/user/preferences`,
headers: { 'content-type': 'application/json' },
})
- // FIXME: this assumes the request ruleset has all properties of a ruleset
- .then(request => this.setState({ ruleset: request.data.ruleset || this.state.ruleset }))
+ .then(request => setRuleset(request.data.preferences.ruleset))
.catch(() => {});
- }
-
- showEditGameRules = () => this.setState({ editGameRulesDialogVisible: true });
- hideEditGameRules = () => this.setState({ editGameRulesDialogVisible: false });
- setGameRules = (newRules: ruleset) => this.setState({ ruleset: newRules });
+ }, []);
- render() {
- var timelimit_str = this.state.ruleset.timelimit.enabled
- ? `${this.state.ruleset.timelimit.minutes}m${this.state.ruleset.timelimit.seconds}s plus ${this.state.ruleset.timelimit.addmove}`
- : 'Geen tijdslimiet';
- var ranked_str = this.state.ruleset.ranked
- ? 'Gerangschikt'
- : 'Niet gerangschikt';
- return <div
- style={{
- position: 'relative',
- height: 80,
- overflow: 'visible',
- zIndex: 1,
- }}
+ var timelimit_str = ruleset.timelimit.enabled
+ ? `${ruleset.timelimit.minutes}m${ruleset.timelimit.seconds}s plus ${ruleset.timelimit.addmove}`
+ : 'Geen tijdslimiet';
+ var ranked_str = ruleset.ranked
+ ? 'Gerangschikt'
+ : 'Niet gerangschikt';
+ return <div className='editGameSettings posrel'>
+ <p className='currentRules subtile nosel posabs l0'>
+ {timelimit_str}
+ <br />
+ {ranked_str}
+ </p>
+ <Button
+ className='posabs r0 pad-m'
+ onclick={() => setEditGameRulesDialogVisible(true)}
>
- <p
- style={{
- opacity: .75,
- fontStyle: 'italic',
- userSelect: 'none',
- position: 'absolute',
- top: '50%',
- left: 0,
- transform: 'translateY(-50%)',
- }}
- >
- {timelimit_str}
- <br />
- {ranked_str}
- </p>
- <Button
- style={{
- width: 150,
- position: 'absolute',
- top: '50%',
- right: 0,
- transform: 'translateY(-50%)',
- }}
- onclick={this.showEditGameRules}
- >
- <BuildOutlinedIcon style={{ fontSize: 48 }} />
- <span
- style={{
- fontWeight: 600,
- position: 'absolute',
- right: 24,
- top: '50%',
- width: 85,
- verticalAlign: 'middle',
- textAlign: 'center',
- transform: 'translateY(-50%)',
- userSelect: 'none',
- }}
- >
- Spelregels aanpassen
- </span>
- </Button>
- <EditGameSettings
- parentState={this.state}
- hideEditGameRules={this.hideEditGameRules}
- setGameRules={this.setGameRules}
- />
- </div>;
- }
+ <BuildOutlinedIcon className='icon' />
+ <span className='posabs center nosel text'>
+ Spelregels aanpassen
+ </span>
+ </Button>
+ <EditGameSettings
+ hideEditGameRules={() => setEditGameRulesDialogVisible(false)}
+ setGameRules={setRuleset}
+ ruleset={ruleset}
+ visible={editGameRulesDialogVisible}
+ />
+ </div>;
}
function GameSettingsSection(props: {
@@ -118,32 +69,17 @@ function GameSettingsSection(props: {
}) {
return <Vierkant
id={props.id}
- style={{
- backgroundColor: 'var(--background-alt)',
- width: '100%',
- padding: 16,
- margin: 0,
- marginBottom: props.noMarginBottom ? 0 : 24,
- }}
+ className='pad-m editableRulesSection'
>
- <span
- style={{
- verticalAlign: 'top',
- fontSize: 14,
- fontWeight: 600,
- }}
- >
+ <span className='valigntop nosel'>
{props.title}
</span>
- <CheckBox
- state={props.state}
- id={`${props.id}_enabled`}
- style={{
- verticalAlign: 'top',
- float: 'right',
- margin: -3,
- }}
- />
+ <div className='checkboxWrapper valigntop floatr'>
+ <CheckBox
+ state={props.state}
+ id={props.id + '_enabled'}
+ />
+ </div>
<div>{props.children}</div>
</Vierkant>;
}
@@ -151,137 +87,99 @@ function GameSettingsSection(props: {
function GameRule(props: {
title: string;
description: string;
- style?: CSSProperties;
}) {
- return <div
- style={{
- backgroundColor: 'var(--page-background)',
- borderRadius: 8,
- padding: '16px 0',
- textAlign: 'center',
- ...props.style,
- }}
- >
- <h1 style={{ color: 'var(--disk-a)', fontSize: 42 }}>{props.title}</h1>
- <p style={{ color: 'var(--text-alt)', maxWidth: 250, margin: '0 auto' }}>{props.description}</p>
+ return <div className='gamerule pad-m round-t center bg-900'>
+ <h1>{props.title}</h1>
+ <p>{props.description}</p>
</div>;
}
type editGameSettingsProps = {
- parentState: CurrentGameSettingsStateType;
+ visible: boolean;
+ ruleset: ruleset;
hideEditGameRules: () => void;
setGameRules: (newRules: ruleset) => void;
};
-export class EditGameSettings extends Component<editGameSettingsProps> {
- render() {
- return <DialogBox
- title='Spelregels aanpassen'
- style={{
- margin: 0,
- display: this.props.parentState.editGameRulesDialogVisible ? 'block' : 'none',
+export function EditGameSettings(props: editGameSettingsProps) {
+ return <DialogBox
+ title='Spelregels aanpassen'
+ hidden={!props.visible}
+ onclick={props.hideEditGameRules}
+ className='gameRuleEdit'
+ >
+ <div className='editableRules round-t'>
+ <GameSettingsSection
+ title='Tijdslimiet'
+ state={props.ruleset.timelimit.enabled}
+ id='timelimit'
+ >
+ <div className='sidebyside timeControls'>
+ <Input
+ id='timelimit_minutes'
+ type='number'
+ label='min'
+ min={0}
+ max={60}
+ value={props.ruleset.timelimit.minutes}
+ className='pad-m round-t'
+ />
+ <Input
+ id='timelimit_seconds'
+ type='number'
+ label='sec'
+ min={0}
+ max={60}
+ value={props.ruleset.timelimit.seconds}
+ className='pad-m round-t'
+ />
+ <Input
+ id='timelimit_addmove'
+ type='number'
+ label='plus'
+ min={0}
+ value={props.ruleset.timelimit.addmove}
+ className='pad-m round-t'
+ />
+ </div>
+ <CheckBox id='timelimit_shared' state={props.ruleset.timelimit.shared} />
+ <span className='valignsup'>
+ Timer gebruiken voor bijde spelers
+ </span>
+ </GameSettingsSection>
+ {false && <GameSettingsSection id='gamemodes' title='Regelset' state={false}>
+ <div className='sidebyside'>
+ <GameRule title='+2' description='Extra kolommen' />
+ <GameRule title='+4' description='Extra kolommen' />
+ </div>
+ <GameRule title='Gravity' description='De zwaartekracht draait soms' />
+ <GameRule title='Flashlight' description='Het veld wordt opgelicht door de vallende fiches' />
+ </GameSettingsSection>}
+ <GameSettingsSection
+ title='Gerangschikt spel'
+ state={props.ruleset.ranked}
+ id='ranked'
+ noMarginBottom
+ />
+ </div>
+ <Button
+ className='save'
+ onclick={() => {
+ var rules: ruleset = {
+ timelimit: {
+ enabled: document.getElementById('timelimit_enabled').classList.contains('on'),
+ minutes: Number((document.getElementById('timelimit_minutes') as HTMLInputElement).value),
+ seconds: Number((document.getElementById('timelimit_seconds') as HTMLInputElement).value),
+ addmove: Number((document.getElementById('timelimit_addmove') as HTMLInputElement).value),
+ shared: document.getElementById('timelimit_shared').classList.contains('on'),
+ },
+ ranked: document.getElementById('ranked_enabled').classList.contains('on'),
+ };
+ props.setGameRules(rules);
+ props.hideEditGameRules();
}}
- onclick={this.props.hideEditGameRules}
>
- <div
- style={{
- marginTop: 24,
- maxHeight: 500,
- overflowY: 'scroll',
- borderRadius: 8,
- }}
- >
- <GameSettingsSection
- title='Tijdslimiet'
- state={this.props.parentState.ruleset.timelimit.enabled}
- id='timelimit'
- >
- <div
- style={{
- display: 'grid',
- gridTemplateColumns: '1fr 1fr 1fr',
- gridGap: 16,
- margin: '16px 0',
- }}
- >
- <Input
- id='timelimit_minutes'
- type='number'
- label='min'
- min={0}
- max={60}
- value={this.props.parentState.ruleset.timelimit.minutes}
- />
- <Input
- id='timelimit_seconds'
- type='number'
- label='sec'
- min={0}
- max={60}
- value={this.props.parentState.ruleset.timelimit.seconds}
- />
- <Input
- id='timelimit_addmove'
- type='number'
- label='plus'
- min={0}
- value={this.props.parentState.ruleset.timelimit.addmove}
- />
- </div>
- <CheckBox id='timelimit_shared' state={this.props.parentState.ruleset.timelimit.shared} />
- <span
- style={{
- verticalAlign: 'super',
- marginLeft: 4,
- }}
- >
- Timer gebruiken voor bijde spelers
- </span>
- </GameSettingsSection>
- {false && <GameSettingsSection title='Regelset' state={false}>
- <div
- style={{
- display: 'grid',
- gridTemplateColumns: '1fr 1fr',
- gridGap: 16,
- margin: '16px 0',
- }}
- >
- <GameRule title='+2' description='Extra kolommen' />
- <GameRule title='+4' description='Extra kolommen' />
- </div>
- <GameRule style={{ marginBottom: 16 }} title='Gravity' description='De zwaartekracht draait soms' />
- <GameRule title='Flashlight' description='Het veld wordt opgelicht door de vallende fiches' />
- </GameSettingsSection>}
- <GameSettingsSection
- title='Gerangschikt spel'
- state={this.props.parentState.ruleset.ranked}
- id='ranked'
- noMarginBottom
- />
- </div>
- <Button
- style={{
- textAlign: 'center',
- marginTop: 24,
- }}
- onclick={() => {
- var rules: ruleset = {
- timelimit: {
- enabled: document.getElementById('timelimit_enabled').classList.contains('on'),
- minutes: Number((document.getElementById('timelimit_minutes') as HTMLInputElement).value),
- seconds: Number((document.getElementById('timelimit_seconds') as HTMLInputElement).value),
- addmove: Number((document.getElementById('timelimit_addmove') as HTMLInputElement).value),
- shared: document.getElementById('timelimit_shared').classList.contains('on'),
- },
- ranked: document.getElementById('ranked_enabled').classList.contains('on'),
- };
- this.props.setGameRules(rules);
- this.props.hideEditGameRules();
- }}
- >
- Instellingen opslaan
- </Button>
- </DialogBox>;
- }
+ Instellingen opslaan
+ </Button>
+ </DialogBox>;
}
diff --git a/components/logo.tsx b/components/logo.tsx
index e43aa88..df35370 100644
--- a/components/logo.tsx
+++ b/components/logo.tsx
@@ -1,26 +1,12 @@
-export function LogoDark() {
+export default function Logo() {
return (
<div className='noclick'>
<svg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'>
- <rect width='24' height='24' fill='var(--background)' />
- <circle cx='6.5' cy='6.5' r='4.5' fill='var(--disk-b)' />
- <circle cx='6.5' cy='17.5' r='4.5' fill='var(--disk-a)' />
- <circle cx='17.5' cy='17.5' r='4.5' fill='var(--disk-b)' />
- <circle cx='17.5' cy='6.5' r='3.5' stroke='var(--text)' strokeWidth='2' />
- </svg>
- </div>
- );
-}
-
-export function LogoLight() {
- return (
- <div className='noclick'>
- <svg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'>
- <rect width='24' height='24' fill='var(--page-background)' />
- <circle cx='6.5' cy='6.5' r='4.5' fill='var(--disk-b)' />
- <circle cx='6.5' cy='17.5' r='4.5' fill='var(--disk-a)' />
- <circle cx='17.5' cy='17.5' r='4.5' fill='var(--disk-b)' />
- <circle cx='17.5' cy='6.5' r='3.5' stroke='var(--background)' strokeWidth='2' />
+ <rect width='24' height='24' fill='none' />
+ <circle className='green' cx='6.5' cy='6.5' r='4.5' fill='var(--confirm)' />
+ <circle className='red' cx='6.5' cy='17.5' r='4.5' fill='var(--error)' />
+ <circle className='green' cx='17.5' cy='17.5' r='4.5' fill='var(--confirm)' />
+ <circle className='circle' cx='17.5' cy='6.5' r='3.5' stroke='var(--foreground)' strokeWidth='2' />
</svg>
</div>
);
diff --git a/components/navbar.tsx b/components/navbar.tsx
index 70de574..5ce43bb 100644
--- a/components/navbar.tsx
+++ b/components/navbar.tsx
@@ -1,8 +1,8 @@
import axios from 'axios';
-import { CSSProperties, useContext, useEffect, useState } from 'react';
+import { useContext, useEffect, useState } from 'react';
import { userInfo } from '../api/api';
-import { LogoDark } from '../components/logo';
+import Logo from '../components/logo';
import { AccountAvatar } from './account';
import { NotificationsArea } from './notificationsArea';
import { SocketContext } from './socketContext';
@@ -15,12 +15,6 @@ import SearchIcon from '@material-ui/icons/Search';
import SettingsIcon from '@material-ui/icons/Settings';
import VideogameAssetIcon from '@material-ui/icons/VideogameAsset';
-var NavBarItemStyle: CSSProperties = {
- margin: 12,
- marginBottom: 16,
- display: 'block',
-};
-
export function NavBar() {
var [loggedIn, setLoggedIn] = useState(false);
var [gotData, setGotData] = useState(false);
@@ -60,73 +54,31 @@ export function NavBar() {
})();
}, []);
- return <div
- className='navbar'
- style={{
- width: 48,
- height: '100%',
-
- lineHeight: 0,
-
- backgroundColor: 'var(--background)',
- display: 'inline-block',
-
- position: 'fixed',
- top: 0,
- left: 0,
-
- overflow: 'visible',
- whiteSpace: 'nowrap',
- zIndex: 2,
- }}
- >
- <div style={NavBarItemStyle}>
- <LogoDark />
+ return <div className='navbar bg-800 h100vh dispinbl t0 l0 posfix'>
+ <div className='item'>
+ <Logo />
</div>
- <a href='/' style={NavBarItemStyle}>
+ <a href='/' className='item'>
<Home />
</a>
- <a href='/game' style={NavBarItemStyle}>
+ <a href='/game' className='item'>
<VideogameAssetIcon />
</a>
- {false && <a href='/' style={NavBarItemStyle}>
+ {false && <a href='/' className='item'>
<ExtensionIcon />
</a>}
- <a href='/search' style={NavBarItemStyle}>
+ <a href='/search' className='item'>
<SearchIcon />
</a>
- <div
- style={{
- position: 'absolute',
- bottom: -4,
- left: 0,
- backgroundColor: 'var(--background)',
- }}
- >
- {loggedIn && <a
- style={{
- overflow: 'visible',
- position: 'relative',
- ...NavBarItemStyle,
- }}
- >
+ <div className='bg-800 bottomArea'>
+ {loggedIn && <a className='notifications item posrel'>
<div
- style={{ cursor: 'pointer' }}
+ className='iconWrapper'
onClick={() => setNotificationsAreaVisible(!notificationsAreaVisible)}
>
<NotificationsIcon />
- {gotNotifications && <div
- style={{
- backgroundColor: 'var(--disk-a)',
- width: 8,
- height: 8,
- borderRadius: 4,
- position: 'absolute',
- top: 2,
- right: 2,
- }}
- />}
+ {gotNotifications && <div className='notificationDot posabs' />}
</div>
<NotificationsArea
visible={notificationsAreaVisible}
@@ -134,12 +86,12 @@ export function NavBar() {
rerender={getNotifications}
/>
</a>}
- <a href={loggedIn ? '/user' : '/login'} style={NavBarItemStyle}>
+ <a href={loggedIn ? '/user' : '/login'} className='item'>
{loggedIn
? <AccountAvatar size={24} round />
: <PersonIcon />}
</a>
- {loggedIn && <a href='/settings' style={NavBarItemStyle}>
+ {loggedIn && <a href='/settings' className='item'>
<SettingsIcon />
</a>}
</div>
diff --git a/components/notificationsArea.tsx b/components/notificationsArea.tsx
index 9573b72..8ee554a 100644
--- a/components/notificationsArea.tsx
+++ b/components/notificationsArea.tsx
@@ -1,5 +1,5 @@
import axios from 'axios';
-import { CSSProperties, ReactNode, useContext, useEffect, useState } from 'react';
+import { ReactNode, useContext, useEffect, useState } from 'react';
import { gameInfo, userInfo } from '../api/api';
import { AccountAvatar } from './account';
@@ -35,52 +35,14 @@ export function NotificationsArea(props: {
setPreviousMessages(messages);
});
- return props.visible && <Bubble
- style={{
- left: 48 + 12,
- top: 92,
- transform: 'translateY(-100%)',
- textAlign: 'left',
- width: 400,
- height: 450,
- }}
- tuitjeStyle={{
- left: 12,
- bottom: 86,
- transform: 'translate(-100%, 100%) rotate(90deg)',
- }}
- >
- <h2 style={{ marginBottom: 24 }}>Meldingen</h2>
- <div
- style={{
- overflowY: 'scroll',
- whiteSpace: 'normal',
- height: 450 - 24 * 4,
- borderRadius: 6,
- }}
- >
+ return props.visible && <Bubble className='notificationsArea bg-700 pad-l'>
+ <h2 className='title'>Meldingen</h2>
+ <div className='inner round-t'>
{props.gameInvites?.map(game => <GameInvite hide={props.rerender} game={game} />)}
{props.friendRequests?.map(user => <FriendRequest hide={props.rerender} user={user} />)}
{messages == 0
- && <div
- style={{
- position: 'absolute',
- left: 0,
- right: 0,
- bottom: 0,
- top: 0,
- }}
- >
- <h1
- style={{
- position: 'absolute',
- top: '50%',
- left: '50%',
- whiteSpace: 'nowrap',
- transform: 'translate(-50%, -50%)',
- opacity: .7,
- }}
- >
+ && <div className='noMsgsWrapper posabs a0'>
+ <h1 className='posabs abscenter subtile'>
Geen meldingen
</h1>
</div>}
@@ -88,48 +50,24 @@ export function NotificationsArea(props: {
</Bubble>;
}
-var FriendRequestButtonStyle: CSSProperties = {
- borderRadius: 6,
- display: 'inline-block',
- marginLeft: 0,
- textAlign: 'center',
-};
-
function Acceptable(props: {
children?: ReactNode;
onAccept?: () => void;
onDeny?: () => void;
}) {
- return <Vierkant
- style={{
- borderRadius: 8,
- background: 'var(--background-alt)',
- margin: 0,
- padding: 12,
- width: '100%',
- marginBottom: 12,
- }}
- >
- <div style={{ position: 'relative' }}>
+ return <Vierkant className='acceptable bg-800 round-t pad-m fullwidth'>
+ <div className='posrel'>
{props.children}
- <div
- style={{
- display: 'grid',
- gridTemplateColumns: '1fr, 1fr',
- gridGap: 12,
- marginTop: 12,
- gridAutoFlow: 'column',
- }}
- >
+ <div className='sidebyside buttons'>
<IconLabelButton
+ className='accept'
onclick={props.onAccept}
- style={FriendRequestButtonStyle}
icon={<DoneIcon />}
text='Accepteren'
/>
<IconLabelButton
+ className='deny'
onclick={props.onDeny}
- style={FriendRequestButtonStyle}
icon={<CloseIcon />}
text='Verwijderen'
/>
@@ -171,14 +109,8 @@ function FriendRequest(props: {
>
<a href={'/user?id=' + props.user.id}>
<AccountAvatar size={48} id={props.user.id} />
- <div
- style={{
- display: 'inline-block',
- verticalAlign: 'top',
- marginLeft: 6,
- }}
- >
- <i style={{ display: 'block' }}>Vriendschapsverzoek</i>
+ <div className='userInfo dispinbl valigntop'>
+ <i className='dispbl'>Vriendschapsverzoek</i>
<b>{props.user.username}</b>
</div>
</a>
@@ -191,13 +123,8 @@ function GameInvite(props: {
}) {
return <Acceptable>
<a>
- <div
- style={{
- display: 'inline-block',
- verticalAlign: 'top',
- }}
- >
- <i style={{ display: 'block' }}>Partijuitnodiging</i>
+ <div className='userInfo dispinbl valigntop'>
+ <i className='dispbl'>Partijuitnodiging</i>
<p>
<b>
<a href={'/user?id=' + props.game.opponent?.id}>{props.game.opponent?.username}</a>
diff --git a/components/page.tsx b/components/page.tsx
index 506e2db..b8e8770 100644
--- a/components/page.tsx
+++ b/components/page.tsx
@@ -1,27 +1,15 @@
-import { Component, CSSProperties, ReactNode } from 'react';
+import { Component, ReactNode } from 'react';
-interface CenteredPageProps {
+export function CenteredPage(props: {
width?: number;
children?: ReactNode;
- style?: CSSProperties;
-}
-
-export function CenteredPage(props: CenteredPageProps) {
+ className?: string;
+}) {
return <div
className='CenteredPageOuter'
- style={{
- maxWidth: props.width,
- margin: '0 auto',
- }}
+ style={{ maxWidth: props.width }}
>
- <div
- className='CenteredPageInner'
- style={{
- margin: '0 6px',
- lineHeight: 0,
- ...props.style,
- }}
- >
+ <div className={'CenteredPageInner ' + props.className}>
{props.children}
</div>
</div>;
@@ -29,15 +17,7 @@ export function CenteredPage(props: CenteredPageProps) {
export class PageTitle extends Component {
render() {
- return <h1
- style={{
- color: 'var(--text-alt)',
- marginLeft: 6,
- marginTop: 32,
- marginBottom: 64,
- fontSize: 25,
- }}
- >
+ return <h1 className='pageTitle'>
{this.props.children}
</h1>;
}
diff --git a/components/recentGames.tsx b/components/recentGames.tsx
index 988126f..6683e20 100644
--- a/components/recentGames.tsx
+++ b/components/recentGames.tsx
@@ -1,18 +1,7 @@
import friendlyTime from 'friendly-time';
-import { CSSProperties } from 'react';
import { gameInfo } from '../api/api';
-var LeftAlignedTableColumn: CSSProperties = {
- textAlign: 'left',
- paddingLeft: 16,
-};
-
-var RightAlignedTableColumn: CSSProperties = {
- textAlign: 'right',
- paddingRight: 16,
-};
-
function GameOutcome(props: { game: gameInfo; }) {
var gameStatus = (() => {
return {
@@ -28,26 +17,24 @@ function GameOutcome(props: { game: gameInfo; }) {
},
}[props.game.status]();
})();
- var outcome = props.game.outcome;
- return <td
- style={{
- color: outcome == 'w'
- ? 'var(--disk-b-text)'
- : outcome == 'l'
- ? 'var(--disk-a-text)'
- : 'var(--text)',
- opacity: !['w', 'l'].includes(outcome) ? 0.75 : 1.0,
- }}
- >
- {gameStatus}
+ return <td>
+ <span
+ className={'outcome ' + {
+ 'w': 'win',
+ 'l': 'lose',
+ 'd': 'draw',
+ }[props.game.outcome]}
+ >
+ {gameStatus}
+ </span>
</td>;
}
export default function RecentGames(props: { games?: Array<gameInfo>; }) {
- return <div>
+ return <div className='recentGames'>
<h2>Recente partijen</h2>
{props.games?.length > 0
- ? <table width='100%' style={{ marginTop: '16px', textAlign: 'center' }}>
+ ? <table width='100%' className='recentGames center'>
<tbody>
<tr>
<th style={{ width: '50%' }}>Tegenstander</th>
@@ -57,19 +44,14 @@ export default function RecentGames(props: { games?: Array<gameInfo>; }) {
</tr>
{props.games?.map(game =>
<tr key={game.id}>
- <td style={LeftAlignedTableColumn}>
- <a
- href={'/user?id=' + game.opponent?.id}
- style={{
- fontWeight: 500,
- }}
- >
+ <td className='leftAlignedColumn'>
+ <a href={'/user?id=' + game.opponent?.id}>
{game.opponent?.username}
</a>
</td>
<GameOutcome game={game} />
<td>{Math.max(0, game.moves.length - 1)}</td>
- <td style={RightAlignedTableColumn}>
+ <td className='rightAlignedColumn'>
{(() => {
var timeCreated = new Date(game.created);
return friendlyTime(timeCreated);
@@ -79,13 +61,7 @@ export default function RecentGames(props: { games?: Array<gameInfo>; }) {
)}
</tbody>
</table>
- : <h1
- style={{
- textAlign: 'center',
- opacity: .6,
- margin: '32px 64px',
- }}
- >
+ : <h1 className='nogames center subtile'>
Deze gebruiker heeft nog geen partijen gespeeld
</h1>}
</div>;
diff --git a/components/toast.tsx b/components/toast.tsx
index 97e17e6..3a72ae1 100644
--- a/components/toast.tsx
+++ b/components/toast.tsx
@@ -1,26 +1,14 @@
-import { createContext, CSSProperties, ReactNode, useState } from 'react';
+import { createContext, ReactNode, useState } from 'react';
import CloseIcon from '@material-ui/icons/Close';
function ToastArea(props: {
- style?: CSSProperties;
children?: ReactNode;
rerender?: boolean;
}) {
return <div
id='ToastArea'
- style={{
- position: 'fixed',
- whiteSpace: 'nowrap',
- bottom: 12,
- left: '50%',
- transform: 'translateX(-50%)',
- zIndex: 1,
- maxWidth: 600,
- width: 'calc(100% - 48px - 48px)',
- margin: '0 24px',
- ...props.style,
- }}
+ className='posfix abscenterh'
>
{props.children}
</div>;
@@ -32,71 +20,30 @@ function Toast(props: {
icon?: ReactNode;
children?: ReactNode;
type?: 'normal' | 'confirmation' | 'error';
- style?: CSSProperties;
}) {
var [visible, setVisibility] = useState(true);
setTimeout(() => setVisibility(false), 10e3);
- return visible && <div
- style={{
- padding: 0,
- marginBottom: 12,
- borderRadius: 6,
- boxShadow: '0 8px 12px -4px #00000033',
- color: props.type === 'confirmation' ? 'var(--background)' : 'var(--text)',
- backgroundColor: props.type === 'normal'
- ? 'var(--background)'
- : props.type === 'confirmation'
- ? 'var(--disk-b)'
- : props.type === 'error'
- ? 'var(--disk-a)'
- : 'var(--background)',
- ...props.style,
- }}
- >
+ return visible && <div className={'round-t drop-1 toast ' + props.type}>
{props.children
|| <div
- style={{
- lineHeight: 0,
- padding: 12,
- minHeight: props.description ? 36 : 24,
- position: 'relative',
- }}
+ className={'inner pad-m posrel '
+ + (props.description ? 'hasDescription' : '') + ' '
+ + (props.icon ? 'hasIcon' : '')}
>
- <div
- style={{
- position: 'absolute',
- left: 12,
- top: '50%',
- transform: 'translateY(-50%)',
- }}
- >
+ <div className='icon posabs abscenterv'>
{props.icon}
</div>
- <div
- style={{
- userSelect: 'none',
- position: 'absolute',
- left: props.icon ? 48 : 12,
- top: '50%',
- transform: 'translateY(-50%)',
- }}
- >
- <h2 style={{ fontSize: 16 }}>{props.text}</h2>
+ <div className='content nosel posabs abscenterv'>
+ <h2>{props.text}</h2>
<p>{props.description}</p>
</div>
<div
- style={{
- cursor: 'pointer',
- position: 'absolute',
- right: 12,
- top: '50%',
- transform: 'translateY(-50%)',
- }}
+ className='closeIcon posabs abscenterv'
onClick={() => setVisibility(false)}
>
- <CloseIcon style={{ fontSize: 24 }} />
+ <CloseIcon />
</div>
</div>}
</div>;
diff --git a/components/ui.tsx b/components/ui.tsx
index c3f950b..7474240 100644
--- a/components/ui.tsx
+++ b/components/ui.tsx
@@ -1,4 +1,4 @@
-import { Component, CSSProperties, ReactNode, useEffect, useState } from 'react';
+import { ReactNode, useEffect, useState } from 'react';
import CheckBoxIcon from '@material-ui/icons/CheckBox';
import CheckBoxOutlineBlankIcon from '@material-ui/icons/CheckBoxOutlineBlank';
@@ -9,33 +9,14 @@ export function Vierkant(props: {
href?: string;
width?: string;
height?: string;
- style?: CSSProperties;
children?: ReactNode;
className?: string;
id?: string;
- fullwidth?: boolean;
onclick?: () => void;
}) {
return <a
- style={{
- padding: 24,
- backgroundColor: 'var(--background)',
- borderRadius: 8,
- color: 'var(--text)',
- margin: 6, // geen margin collapse = 12px marge
- display: 'inline-block',
- position: 'relative',
- boxSizing: 'border-box',
- width: props.width
- ? props.width
- : props.fullwidth
- ? 'calc(100% - 12px)'
- : undefined,
- height: props.height ? props.height : undefined,
- ...props.style,
- }}
href={props.href}
- className={props.className}
+ className={['round-l', 'vierkant', props.className].join(' ')}
id={props.id}
onClick={props.onclick}
>
@@ -46,35 +27,19 @@ export function Vierkant(props: {
export function Button(props: {
text?: string;
children?: ReactNode;
- style?: CSSProperties;
href?: string;
- onclick?: (() => void);
+ className?: string;
+ onclick?: () => void;
id?: string;
}) {
return <a
onClick={props.onclick}
href={props.href}
id={props.id}
- style={{
- padding: props.text ? 8 : 16,
- textAlign: props.text ? 'center' : 'left',
- borderRadius: 8,
- backgroundColor: 'var(--disk-a)',
- cursor: 'pointer',
- position: 'relative',
- textDecoration: 'none',
- display: 'block',
- userSelect: 'none',
- ...props.style,
- }}
+ className={'button pad-s round-t ' + props.className}
>
{props.text
- ? <span
- style={{
- fontWeight: 600,
- userSelect: 'none',
- }}
- >
+ ? <span>
{props.text}
</span>
: undefined}
@@ -86,33 +51,18 @@ export function IconLabelButton(props: {
text: string;
icon: ReactNode;
onclick?: () => void;
- style?: CSSProperties;
href?: string;
+ className?: string;
}) {
return <Button
onclick={props.onclick}
href={props.href}
- style={{
- display: 'inline-block',
- verticalAlign: 'top',
- padding: 8,
- float: 'right',
- marginLeft: 12,
- ...props.style,
- }}
+ className={'iconlabel dispinbl valigntop floatr' + ' ' + props.className}
>
- {props.icon}
- <span
- style={{
- display: 'inline-block',
- verticalAlign: 'top',
- fontWeight: 500,
- marginLeft: 8,
- marginTop: 3,
- marginBottom: 3,
- marginRight: 3,
- }}
- >
+ <div className='dispinbl icon'>
+ {props.icon}
+ </div>
+ <span className='dispinbl valigntop label'>
{props.text}
</span>
</Button>;
@@ -120,7 +70,6 @@ export function IconLabelButton(props: {
export function Input(props: {
label?: string;
- style?: CSSProperties;
type?: string;
id?: string;
min?: number;
@@ -129,6 +78,7 @@ export function Input(props: {
dark?: boolean;
autocomplete?: string;
autofocus?: boolean;
+ className?: string;
}) {
return <input
id={props.id}
@@ -138,65 +88,26 @@ export function Input(props: {
placeholder={props.label}
spellCheck={false}
defaultValue={props.value ? String(props.value) : ''}
- className={props.dark ? 'dark' : 'light'}
+ className={'input' + ' ' + (props.dark ? 'dark' : 'light') + ' ' + props.className}
autoComplete={props.autocomplete}
autoFocus={props.autofocus}
- style={{
- padding: 12,
- border: 0,
- width: 'calc(100% - 24px)',
- fontSize: 14,
- backgroundColor: 'var(--page-background)',
- color: 'var(--text-alt)',
- borderRadius: 8,
- fontFamily: 'Inter',
- ...props.style,
- }}
/>;
}
export function SearchBar(props: { label?: string; }) {
- return <div
- style={{
- marginTop: 24,
- borderRadius: 8,
- overflow: 'hidden',
- width: '100%',
- }}
- >
+ return <div className='searchBar round-t fullwidth'>
<Input
label={props.label}
- style={{
- width: 'calc(100% - 24px - 41px)',
- borderTopRightRadius: 0,
- borderBottomRightRadius: 0,
- }}
+ className='pad-m bg-700'
/>
- <div
- style={{
- width: 41,
- height: 41,
- backgroundColor: 'var(--disk-a)',
- display: 'inline-block',
- verticalAlign: 'top',
- position: 'relative',
- }}
- >
- <SearchIcon
- style={{
- position: 'absolute',
- top: '50%',
- left: '50%',
- transform: 'translate(-50%, -50%)',
- }}
- />
- </div>
+ <Button className='dispinbl valigntop'>
+ <SearchIcon className='icon' />
+ </Button>
</div>;
}
export function CheckBox(props: {
state?: boolean;
- style?: CSSProperties;
id?: string;
onclick?: (state: boolean) => void;
}) {
@@ -217,12 +128,7 @@ export function CheckBox(props: {
return <div
onClick={toggle}
id={props.id}
- className={on ? 'on' : 'off'}
- style={{
- ...props.style,
- display: 'inline-block',
- cursor: 'pointer',
- }}
+ className={'checkbox dispinbl ' + (on ? 'on' : 'off')}
>
{on
? <CheckBoxIcon />
@@ -230,110 +136,41 @@ export function CheckBox(props: {
</div>;
}
-export class ColorPicker extends Component<{
- style?: CSSProperties;
-}> {
- state: {
- color: string;
- dark: boolean;
- } = {
- color: '#012345',
- dark: true,
- };
+export function ColorPicker() {
+ var [dark, setDark] = useState(false);
+ var [color, setColor] = useState('#012345');
- render() {
- return <Button
- style={{
- display: 'inline-block',
- verticalAlign: 'top',
- padding: 6,
- float: 'right',
- marginLeft: 12,
- color: this.state.dark ? 'var(--text)' : 'var(--text-alt)',
- borderColor: this.state.dark ? 'var(--text)' : 'var(--text-alt)',
- borderWidth: 2,
- borderStyle: 'solid',
- backgroundColor: this.state.color,
- ...this.props.style,
- }}
- >
- <div>
- <EditOutlinedIcon />
- <div
- style={{
- width: 150,
- height: 24,
- display: 'inline-block',
- textAlign: 'center',
- verticalAlign: 'top',
- position: 'relative',
- }}
- >
- <span
- style={{
- position: 'absolute',
- top: '50%',
- left: '50%',
- transform: 'translate(-50%, -50%)',
- fontWeight: 600,
- fontFeatureSettings: '"tnum", "ss01"',
- }}
- >
- {this.state.color}
- </span>
- </div>
+ return <Button className='colorpicker dispinbl valigntop pad-s floatr'>
+ <div>
+ <EditOutlinedIcon />
+ <div className='labelwrapper valigntop dispinbl center posrel'>
+ <span className='label posabs'>
+ {color}
+ </span>
</div>
- </Button>;
- }
+ </div>
+ </Button>;
}
-export function Tuitje(props: {
- style?: CSSProperties;
- rotation?: number;
-}) {
+export function Tuitje() {
return <svg
width='36'
height='12'
viewBox='0 0 36 12'
fill='none'
xmlns='http://www.w3.org/2000/svg'
- style={{
- ...props.style,
- }}
+ className='tuitje posabs'
>
- <path
- d='M18 12C24 12 27 0 36 0L0 0C9 0 12 12 18 12Z'
- fill={props.style?.background as string || 'var(--background)'}
- />
+ <path d='M18 12C24 12 27 0 36 0L0 0C9 0 12 12 18 12Z' />
</svg>;
}
export function Bubble(props: {
children?: ReactNode;
- style?: CSSProperties;
- tuitjeStyle?: CSSProperties;
+ className?: string;
}) {
- return <Vierkant
- style={{
- position: 'absolute',
- textAlign: 'center',
- margin: 0,
- overflow: 'visible',
- left: '50%',
- top: -24,
- transform: 'translateY(-100%) translateX(-50%)',
- boxShadow: '0 8px 32px rgba(0, 0, 0, 0.3)',
- ...props.style,
- }}
- >
+ return <Vierkant className={'bubble posabs center drop-2 ' + props.className}>
{props.children}
- <Tuitje
- style={{
- position: 'absolute',
- bottom: -12,
- transform: 'translate(-50%, 0%) rotate(0deg)',
- ...props.tuitjeStyle,
- }}
- />
+ <Tuitje />
</Vierkant>;
}
diff --git a/components/voerBord.tsx b/components/voerBord.tsx
index 93e350c..ffb291b 100644
--- a/components/voerBord.tsx
+++ b/components/voerBord.tsx
@@ -1,62 +1,22 @@
-function Disc() {
- return <div
- className='disk'
- style={{
- position: 'absolute',
- top: 0,
- left: 0,
- right: 0,
- bottom: 0,
- borderRadius: 999999,
- margin: 3,
- }}
- />;
-}
-
export function VoerBord(props: {
width: number;
height: number;
onMove: (move: number) => void;
active: boolean;
}) {
- return <table
- className='voerBord'
- style={{
- borderSpacing: 8,
- width: '100%',
- }}
- >
+ return <table className={'voerBord fullwidth ' + (props.active ? 'active' : '')}>
<tbody>
{[...Array(props.height).keys()].map((row) => (
<tr key={`r-${row}`}>
{[...Array(props.width).keys()].map((column) => (
<td
- style={{
- position: 'relative',
- width: '100%',
- padding: 0,
- }}
+ className='posrel outer cell fullwidth'
key={`c-${row}x${column}`}
>
+ <div className='dispbl square' />
+ <div className='disk posabs a0' />
<div
- style={{
- display: 'block',
- marginTop: '100%',
- }}
- />
- <Disc />
- <div
- style={{
- position: 'absolute',
- top: 0,
- left: 0,
- right: 0,
- bottom: 0,
- borderRadius: 6,
- border: '2px solid var(--background-alt)',
- opacity: .5,
- cursor: props.active ? 'pointer' : 'default',
- }}
+ className='posabs a0 round-t inner cell'
id={`pos-${(props.height - row - 1) * props.width + column}`}
onClick={event => {
props.onMove(Number((event.target as HTMLElement).id.split('-')[1]));
diff --git a/package.json b/package.json
index df620cc..75fa8b1 100644
--- a/package.json
+++ b/package.json
@@ -59,6 +59,7 @@
"devDependencies": {
"@types/react": "^17.0.0",
"@types/react-dom": "^17.0.0",
- "@types/react-router-dom": "^5.1.7"
+ "@types/react-router-dom": "^5.1.7",
+ "typescript-plugin-css-modules": "^3.2.0"
}
}
diff --git a/pages/_app.tsx b/pages/_app.tsx
index 0682a4d..c1347e8 100644
--- a/pages/_app.tsx
+++ b/pages/_app.tsx
@@ -7,6 +7,20 @@ import '../styles/dark.css';
import '../styles/disk.css';
import '../styles/footer.css';
import '../styles/global.css';
+import '../styles/navbar.css';
+import '../styles/notifications.css';
+import '../styles/recentGames.css';
+import '../styles/toast.css';
+import '../styles/ui.css';
+import '../styles/utility.css';
+
+import '../styles/game.css';
+import '../styles/gameSettings.css';
+import '../styles/index.css';
+import '../styles/loginregister.css';
+import '../styles/search.css';
+import '../styles/settings.css';
+import '../styles/user.css';
export default function VierOpEenRijWebsite({ Component, pageProps }) {
return <div>
diff --git a/pages/game.tsx b/pages/game.tsx
index b5200a7..de2c089 100644
--- a/pages/game.tsx
+++ b/pages/game.tsx
@@ -1,7 +1,7 @@
import Icon from '@mdi/react';
import axios from 'axios';
import copy from 'copy-to-clipboard';
-import { CSSProperties, useContext, useEffect, useState } from 'react';
+import { useContext, useEffect, useState } from 'react';
import * as cookies from 'react-cookies';
import { Socket } from 'socket.io-client';
import { SocketContext } from '../components/socketContext';
@@ -65,15 +65,7 @@ function VoerGame(props: {
});
}, []);
- return <div
- style={{
- position: 'relative',
- top: '50%',
- transform: 'translateY(-50%)',
- maxWidth: '100vh',
- margin: '0 auto',
- }}
- >
+ return <div className='voerGame posrel abscenterv'>
<VoerBord
width={width}
height={height}
@@ -109,30 +101,22 @@ function GameOutcomeDialog(props: {
}) {
return <DialogBox
title='Speluitkomst'
- style={{ display: props.visible ? 'inline-block' : 'none' }}
+ hidden={!props.visible}
+ className='outcomeDialog'
onclick={() => {
window.history.replaceState(null, null, '/');
window.location.reload();
}}
>
- <div
- style={{
- width: '100%',
- textAlign: 'center',
- }}
- >
+ <div className='inner fullwidth center'>
<h2
- style={{
- color: props.outcome == 0
- ? 'var(--text)'
- : props.outcome == props.player
- ? 'var(--disk-a-text)'
- : props.outcome != props.player
- ? 'var(--disk-b-text)'
- : 'var(--text)',
- opacity: props.outcome == 0 ? .75 : 1,
- marginTop: 8,
- }}
+ className={'outcome' + ' ' + (props.outcome == 0
+ ? 'draw'
+ : props.outcome == props.player
+ ? 'lose'
+ : props.outcome != props.player
+ ? 'win'
+ : 'draw')}
>
{props.outcome == 0
? 'Gelijkspel'
@@ -142,7 +126,7 @@ function GameOutcomeDialog(props: {
? 'Gewonnen'
: '???'}
</h2>
- {false && <p style={{ marginTop: 24 }}>
+ {false && <p className='analysis'>
0 Gemiste winstzetten<br />
6 Optimale zetten<br />
0 Blunders
@@ -150,42 +134,11 @@ function GameOutcomeDialog(props: {
{false && <IconLabelButton
text='Opnieuw spelen'
icon={<RefreshIcon />}
- style={{
- float: 'none',
- marginTop: 24,
- padding: '12px 32px',
- }}
/>}
</div>
</DialogBox>;
}
-var InviteButtonStyle: CSSProperties = {
- backgroundColor: 'var(--page-background)',
- height: 160,
- padding: 12,
-};
-
-var InviteButtonIconStyle: CSSProperties = {
- fontSize: 100,
- position: 'absolute',
- top: 12,
- left: '50%',
- transform: 'translateX(-50%)',
-};
-
-var InviteButtonLabelStyle: CSSProperties = {
- position: 'absolute',
- bottom: 12,
- left: '50%',
- transform: 'translateX(-50%)',
- textAlign: 'center',
- color: 'var(--text-alt)',
- width: 136,
- fontSize: 20,
- userSelect: 'none',
-};
-
export default function GamePage() {
var [gameID, setGameID] = useState('');
var [player1, setPlayer1] = useState(true);
@@ -226,7 +179,7 @@ export default function GamePage() {
return <div>
<NavBar />
- <CenteredPage width={900} style={{ height: '100vh' }}>
+ <CenteredPage width={900} className='h100vh'>
<VoerGame
active={active}
gameID={gameID}
@@ -236,22 +189,16 @@ export default function GamePage() {
/>
<DialogBox
title='Nieuw spel'
- style={{ display: gameIDUrl || gameID ? 'none' : 'inline-block' }}
+ className='newGameDialog'
+ hidden={!!(gameIDUrl || gameID)}
onclick={() => {
window.history.go(-1);
}}
>
<CurrentGameSettings />
- <div
- style={{
- marginTop: 24,
- display: 'grid',
- gridTemplateColumns: '1fr 1fr',
- gridGap: 24,
- }}
- >
+ <div className='sidebyside gg-l'>
<Button
- style={InviteButtonStyle}
+ className='inviteButton random pad-m'
onclick={() => {
axios.request<{ id: string; player_1: boolean; game_started: boolean; }>({
url: '/api/game/random',
@@ -266,16 +213,11 @@ export default function GamePage() {
.catch(() => {});
}}
>
- <WifiTetheringRoundedIcon
- style={{
- color: 'var(--disk-b)',
- ...InviteButtonIconStyle,
- }}
- />
- <h2 style={InviteButtonLabelStyle}>Willekeurige speler</h2>
+ <WifiTetheringRoundedIcon className='icon posabs abscenterh' />
+ <h2 className='label center posabs abscenterh nosel'>Willekeurige speler</h2>
</Button>
<Button
- style={InviteButtonStyle}
+ className='inviteButton link pad-m'
onclick={() => {
axios.request<{ id: string; }>({
method: 'post',
@@ -300,13 +242,8 @@ export default function GamePage() {
.catch(() => {});
}}
>
- <LinkRoundedIcon
- style={{
- color: 'var(--disk-a)',
- ...InviteButtonIconStyle,
- }}
- />
- <h2 style={InviteButtonLabelStyle}>Uitnodigen via link</h2>
+ <LinkRoundedIcon className='icon posabs abscenterh' />
+ <h2 className='label center posabs abscenterh nosel'>Uitnodigen via link</h2>
</Button>
</div>
<SearchBar label='Zoeken in vriendenlijst' />
diff --git a/pages/index.tsx b/pages/index.tsx
index 354efc5..c4b4baf 100644
--- a/pages/index.tsx
+++ b/pages/index.tsx
@@ -1,5 +1,5 @@
import axios from 'axios';
-import { CSSProperties, useContext, useEffect, useState } from 'react';
+import { useContext, useEffect, useState } from 'react';
import { userGames, userGameTotals, userInfo } from '../api/api';
import { Footer } from '../components/footer';
import { SocketContext } from '../components/socketContext';
@@ -16,77 +16,14 @@ import VideogameAssetIcon from '@material-ui/icons/VideogameAsset';
import { mdiRobotExcited } from '@mdi/js';
import Icon from '@mdi/react';
-var GameModeIconStyle: CSSProperties = {
- fontSize: 64,
- width: 64,
- height: 64,
- display: 'inline-block',
- position: 'absolute',
- top: 24,
- left: '50%',
- transform: 'translateX(-50%)',
-};
-
-var GameModeTextStyle: CSSProperties = {
- whiteSpace: 'nowrap',
- display: 'inline-block',
- position: 'absolute',
- bottom: 24,
- left: '50%',
- transform: 'translateX(-50%)',
- userSelect: 'none',
- fontWeight: 500,
-};
-
-var SquareSize: CSSProperties = {
- width: 90,
- height: 90,
-};
-
-var LoginOrRegisterBoxStyle: CSSProperties = {
- verticalAlign: 'top',
- height: `calc(${SquareSize.height}px + 24px * 2)`,
- width: '100%',
- maxWidth: `calc(100% - ${SquareSize.width}px - 12px * 2 - 24px * 2)`,
-};
-
-var InnerLoginOrRegisterBoxStyle: CSSProperties = {
- position: 'relative',
- width: '100%',
- height: '100%',
-};
-
function LoginOrRegisterBox() {
- return <div style={{ ...InnerLoginOrRegisterBoxStyle, textAlign: 'center' }}>
- <span
- style={{
- userSelect: 'none',
- display: 'inline-block',
- position: 'absolute',
- fontSize: 14,
- left: 0,
- right: 0,
- top: 0,
- margin: '0 auto',
- minWidth: 240,
- maxWidth: 350,
- }}
- >
- Log in of maak een account aan om je scores op te slaan en toegang te krijgen tot meer functies
+ return <div className='inner'>
+ <span className='registerMessage posabs h0 t0'>
+ Log in of maak een account aan om toegang tot meer functies te krijgen
</span>
- <div
- style={{
- display: 'grid',
- gridGap: 24,
- gridTemplateColumns: '1fr 1fr',
- position: 'absolute',
- left: 0,
- right: 0,
- bottom: 0,
- }}
- >
- <Button href='/register' text='Registreren' style={{ backgroundColor: 'var(--background-alt)' }} />
- <Button href='/login' text='Inloggen' />
+ <div className='sidebyside posabs h0 b0'>
+ <Button href='/register' text='Registreren' className='register' />
+ <Button href='/login' text='Inloggen' className='login' />
</div>
</div>;
}
@@ -95,44 +32,19 @@ function AccountBox(props: {
info: userInfo;
sumGameInfo: userGameTotals;
}) {
- return <div style={InnerLoginOrRegisterBoxStyle}>
- <div
- style={{
- position: 'absolute',
- top: 0,
- left: 0,
- ...SquareSize,
- }}
- >
+ return <div className='inner profile'>
+ <div className='picture posabs l0 t0'>
<AccountAvatar size={90} />
</div>
- <div
- style={{
- position: 'absolute',
- top: 0,
- left: 102,
- width: 'calc(100% - 90px - 12px)',
- height: '100%',
- }}
- >
- <h2
- style={{
- maxWidth: 178,
- fontSize: 20,
- whiteSpace: 'nowrap',
- overflow: 'hidden',
- textOverflow: 'ellipsis',
- }}
- >
- {props.info?.username}
- </h2>
- <p style={{ marginTop: 6 }}>Score: {props.info?.rating}</p>
- <p style={{ position: 'absolute', bottom: 0, left: 0 }}>
- <span style={{ color: 'var(--disk-b-text)' }}>{props.sumGameInfo?.win} W</span>
- <span style={{ margin: '0 3px' }}>/</span>
- <span style={{ color: 'var(--disk-a-text)' }}>{props.sumGameInfo?.lose} V</span>
- <span style={{ margin: '0 3px' }}>/</span>
- <span style={{ opacity: .75 }}>{props.sumGameInfo?.draw} G</span>
+ <div className='info posabs t0'>
+ <h2 className='username truncate'>{props.info?.username}</h2>
+ <p className='score'>Score: {props.info?.rating}</p>
+ <p className='games posabs b0 l0'>
+ <span className='outcome win'>{props.sumGameInfo?.win} W</span>
+ <span className='divider'>/</span>
+ <span className='outcome lose'>{props.sumGameInfo?.lose} V</span>
+ <span className='divider'>/</span>
+ <span className='outcome draw'>{props.sumGameInfo?.draw} G</span>
</p>
</div>
</div>;
@@ -180,35 +92,32 @@ export default function HomePage() {
<NavBar />
<CenteredPage width={802}>
<PageTitle>4 op een rij</PageTitle>
- <div>
- <Vierkant href='/game'>
- <VideogameAssetIcon style={GameModeIconStyle} />
- <span style={GameModeTextStyle}>Nieuw spel</span>
- <div style={SquareSize}></div>
+ <div className='topbar'>
+ <Vierkant className='gamemode bg-800' href='/game'>
+ <VideogameAssetIcon className='icon' />
+ <span className='text'>Nieuw spel</span>
</Vierkant>
{false
- && <Vierkant href='/'>
- <ExtensionIcon style={GameModeIconStyle} />
- <span style={GameModeTextStyle}>Puzzels</span>
- <div style={SquareSize}></div>
+ && <Vierkant className='gamemode bg-800' href='/'>
+ <ExtensionIcon className='icon' />
+ <span className='text'>Puzzels</span>
</Vierkant>}
{false
- && <Vierkant href='/'>
- <Icon path={mdiRobotExcited} style={GameModeIconStyle} />
- <span style={GameModeTextStyle}>Tegen computer</span>
- <div style={SquareSize}></div>
+ && <Vierkant className='gamemode bg-800' href='/'>
+ <Icon path={mdiRobotExcited} className='icon' />
+ <span className='text'>Tegen computer</span>
</Vierkant>}
- <Vierkant style={LoginOrRegisterBoxStyle}>
+ <Vierkant className='loginOrRegisterBox pad-l valigntop bg-800'>
{loggedIn
? <AccountBox info={userInfo} sumGameInfo={gameInfo?.totals} />
: <LoginOrRegisterBox />}
</Vierkant>
</div>
{loggedIn
- && <Vierkant fullwidth>
+ && <Vierkant className='w100m2m pad-l bg-800'>
<RecentGames games={gameInfo?.games} />
</Vierkant>}
- <Vierkant fullwidth>
+ <Vierkant className='w100m2m pad-l bg-800'>
<h2>Nieuws ofzo</h2>
<p style={{ margin: '6px 0' }}>Chess.com heeft heel veel troep waar niemand naar kijkt</p>
</Vierkant>
diff --git a/pages/login.tsx b/pages/login.tsx
index 1e14573..da13f45 100644
--- a/pages/login.tsx
+++ b/pages/login.tsx
@@ -48,45 +48,33 @@ export default function LoginPage() {
return (
<div>
<NavBar />
- <CenteredPage width={500} style={{ height: '100vh' }}>
- <div
- style={{
- position: 'relative',
- top: '50%',
- transform: 'translateY(-50%)',
- margin: '0 auto',
- textAlign: 'center',
- }}
- >
- <Vierkant>
+ <CenteredPage width={500} className='h100vh'>
+ <div className='posrel center centeredForm'>
+ <Vierkant className='pad-l bg-800'>
<form onSubmit={(e) => submitLogin(e, toast)}>
<Input
autofocus
autocomplete='username'
id='email'
label='email of gebruikersnaam'
- style={{ marginBottom: 12 }}
- >
- </Input>
- <Input autocomplete='current-password' id='password' label='wachtwoord' type='password'>
- </Input>
- <div
- style={{
- marginTop: 24,
- gridGap: 24,
- display: 'grid',
- gridTemplateColumns: '1fr 1fr',
- }}
- >
+ className='pad-m fullwidth bg-900 round-t'
+ />
+ <Input
+ autocomplete='current-password'
+ id='password'
+ label='wachtwoord'
+ type='password'
+ className='pad-m fullwidth bg-900 round-t'
+ />
+ <div className='sidebyside'>
<Button
href='/register'
text='Registreren'
- style={{ backgroundColor: 'var(--background-alt)' }}
- >
- </Button>
- <Button text='Inloggen' onclick={() => submitLogin(null, toast)}></Button>
+ className='register bg-700 fg-100'
+ />
+ <Button text='Inloggen' className='login' onclick={() => submitLogin(null, toast)} />
</div>
- <input type='submit' style={{ display: 'none' }} />
+ <input type='submit' className='dispnone' />
</form>
</Vierkant>
</div>
diff --git a/pages/register.tsx b/pages/register.tsx
index f78d092..bc2fc0f 100644
--- a/pages/register.tsx
+++ b/pages/register.tsx
@@ -88,35 +88,35 @@ export default function RegisterPage() {
return (
<div>
<NavBar />
- <CenteredPage width={500} style={{ height: '100vh' }}>
- <div
- style={{
- position: 'relative',
- top: '50%',
- transform: 'translateY(-50%)',
- margin: '0 auto',
- textAlign: 'center',
- }}
- >
- <Vierkant>
+ <CenteredPage width={500} className='h100vh'>
+ <div className='posrel center centeredForm'>
+ <Vierkant className='pad-l bg-800'>
<form onSubmit={(e) => submitRegister(e, toast)}>
<Input
autofocus
autocomplete='username'
id='username'
label='gebruikersnaam'
- style={{ marginBottom: 12 }}
- >
- </Input>
- <Input autocomplete='email' id='email' label='email' style={{ marginBottom: 12 }}></Input>
- <Input autocomplete='new-password' id='password' label='wachtwoord' type='password'></Input>
+ className='pad-m fullwidth bg-900 round-t'
+ />
+ <Input
+ autocomplete='email'
+ id='email'
+ label='email'
+ className='pad-m fullwidth bg-900 round-t'
+ />
+ <Input
+ autocomplete='new-password'
+ id='password'
+ label='wachtwoord'
+ type='password'
+ className='pad-m fullwidth bg-900 round-t'
+ />
<Button
text='Registreren'
- style={{ marginTop: 24 }}
onclick={() => submitRegister(null, toast)}
- >
- </Button>
- <input type='submit' style={{ display: 'none' }} />
+ />
+ <input type='submit' className='dispnone' />
</form>
</Vierkant>
</div>
diff --git a/pages/search.tsx b/pages/search.tsx
index 2b8668a..99f99b0 100644
--- a/pages/search.tsx
+++ b/pages/search.tsx
@@ -10,7 +10,7 @@ import { Button, Input, Vierkant } from '../components/ui';
import SearchOutlinedIcon from '@material-ui/icons/SearchOutlined';
function search(callback: (results: Array<userInfo>) => void) {
- var query: string = (document.getElementById('searchBar') as HTMLInputElement).value;
+ var query: string = (document.getElementById('bigSearchBar') as HTMLInputElement).value;
if (query.length < 3) return;
axios.request<{ 'results': Array<userInfo>; }>({
@@ -24,74 +24,45 @@ function search(callback: (results: Array<userInfo>) => void) {
}
function SearchResults(props: { userList: Array<userInfo>; }) {
- return <div>
+ return <div className='results w100m2m'>
{props.userList?.map(user => <SearchResult user={user} key={user.id} />)}
</div>;
}
function SearchResult(props: { user: userInfo; }) {
return <Vierkant
- style={{
- padding: 12,
- }}
- fullwidth
+ className='result bg-800 pad-m fullwidth'
href={'/user?id=' + props.user.id}
>
- <div style={{ position: 'relative' }}>
+ <div className='inner posrel'>
<AccountAvatar size={48} id={props.user.id} />
- <div
- style={{
- position: 'absolute',
- top: 0,
- right: 0,
- bottom: 0,
- left: 48 + 12,
- }}
- >
- <b>{props.user.username}</b>
- <p>{props.user.status}</p>
+ <div className='userInfo posabs v0 r0'>
+ <b className='username'>{props.user.username}</b>
+ <p className='status'>{props.user.status}</p>
</div>
</div>
</Vierkant>;
}
-function SearchBar(props: {
+function BigSearchBar(props: {
searchFunction: (event?: FormEvent<HTMLFormElement>) => void;
}) {
- return <Vierkant
- fullwidth
- style={{
- padding: 8,
- marginBottom: 24,
- }}
- >
+ return <Vierkant className='pad-m bg-800 w100m2m bigSearchBar posrel'>
<form onSubmit={props.searchFunction}>
<Input
- id='searchBar'
+ id='bigSearchBar'
label='Zoeken voor gebruikers...'
autocomplete='off'
- dark
autofocus
- style={{
- backgroundColor: 'var(--background)',
- color: 'var(--text)',
- padding: 14,
- fontSize: 16,
- width: 'calc(100% - 48px - 14px * 2)',
- }}
+ className='pad-m posabs abscenterv'
/>
<Button
- style={{
- padding: 12,
- float: 'right',
- display: 'inline-block',
- borderRadius: 4,
- }}
+ className='pad-m dispinbl valigntop floatr'
onclick={props.searchFunction}
>
<SearchOutlinedIcon />
</Button>
- <input type='submit' style={{ display: 'none' }} />
+ <input type='submit' className='dispnone' />
</form>
</Vierkant>;
}
@@ -109,15 +80,10 @@ export default function HomePage() {
<NavBar />
<CenteredPage width={802}>
<PageTitle>Zoeken</PageTitle>
- <SearchBar searchFunction={getSearchResults} />
+ <BigSearchBar searchFunction={getSearchResults} />
<SearchResults userList={results} />
{searched && results.length == 0 && <h1
- style={{
- opacity: .6,
- color: 'var(--text)',
- textAlign: 'center',
- margin: '24px 32px',
- }}
+ className='noresults center subtile'
>
Geen zoekresultaten gevonden
</h1>}
diff --git a/pages/settings.tsx b/pages/settings.tsx
index 0f40a90..0ca2c30 100644
--- a/pages/settings.tsx
+++ b/pages/settings.tsx
@@ -1,7 +1,6 @@
import axios from 'axios';
import reduce from 'image-blob-reduce';
-import { CSSProperties, useContext } from 'react';
-import * as cookies from 'react-cookies';
+import { useContext } from 'react';
import { AccountAvatar } from '../components/account';
import { Footer } from '../components/footer';
@@ -16,11 +15,6 @@ import ExitToAppOutlinedIcon from '@material-ui/icons/ExitToAppOutlined';
import PublishOutlinedIcon from '@material-ui/icons/PublishOutlined';
import VisibilityOutlinedIcon from '@material-ui/icons/VisibilityOutlined';
-var SettingsSubsectionStyle: CSSProperties = {
- marginTop: 24,
- minHeight: 40,
-};
-
async function uploadNewProfileImage() {
if (!this.result) return;
@@ -54,9 +48,9 @@ export default function SettingsPage() {
<NavBar />
<CenteredPage width={802}>
<PageTitle>Instellingen</PageTitle>
- <Vierkant fullwidth>
+ <Vierkant className='section account w100m2m pad-l bg-800'>
<h2>Account</h2>
- <div style={SettingsSubsectionStyle}>
+ <div className='subsection'>
<AccountAvatar size={100} />
<label htmlFor='pfUpload'>
<IconLabelButton text='Nieuwe profielfoto uploaden' icon={<PublishOutlinedIcon />} />
@@ -65,7 +59,7 @@ export default function SettingsPage() {
type='file'
id='pfUpload'
accept='.png,.jpg,.jpeg'
- style={{ display: 'none' }}
+ className='dispnone'
onChange={event => {
var file = event.target.files[0];
if (!file) return;
@@ -76,45 +70,45 @@ export default function SettingsPage() {
}}
/>
</div>
- <div style={SettingsSubsectionStyle}>
+ <div className='subsection'>
<IconLabelButton text='Bewerken' icon={<EditOutlinedIcon />} />
- <div style={{ display: 'block' }}>
+ <div className='dispbl'>
<h3>Gebruikersnaam</h3>
<p>Hier staat hij dan</p>
</div>
</div>
- <div style={SettingsSubsectionStyle}>
+ <div className='subsection'>
<IconLabelButton text='Bewerken' icon={<EditOutlinedIcon />} />
<IconLabelButton text='Onthullen' icon={<VisibilityOutlinedIcon />} />
- <div style={{ display: 'block' }}>
+ <div className='dispbl'>
<h3>Email</h3>
<p>******@example.com</p>
</div>
</div>
- <div style={SettingsSubsectionStyle}>
+ <div className='subsection'>
<IconLabelButton text='Bewerken' icon={<EditOutlinedIcon />} />
- <div style={{ display: 'block' }}>
+ <div className='dispbl'>
<h3>Wachtwoord</h3>
</div>
</div>
</Vierkant>
- <Vierkant fullwidth>
+ <Vierkant className='section colors w100m2m pad-l bg-800'>
<h2>Kleuren</h2>
- <div style={SettingsSubsectionStyle}>
+ <div className='subsection'>
<ColorPicker />
<ColorPicker />
- <div style={{ display: 'block' }}>
+ <div className='dispbl'>
<h3>Schijfjes</h3>
</div>
</div>
- <div style={SettingsSubsectionStyle}>
+ <div className='subsection'>
<ColorPicker />
- <div style={{ display: 'block' }}>
+ <div className='dispbl'>
<h3>Achtergrond</h3>
</div>
</div>
- <div style={SettingsSubsectionStyle}>
- <div style={{ float: 'right' }}>
+ <div className='subsection'>
+ <div className='floatr'>
<CheckBox
state={preferences?.darkMode}
onclick={state => updatePreference({ 'darkMode': state })}
@@ -123,31 +117,20 @@ export default function SettingsPage() {
<h3>Donkere modus</h3>
</div>
</Vierkant>
- <Vierkant fullwidth>
+ <Vierkant className='section gamerules w100m2m pad-l bg-800'>
<h2>Standaard spelregels</h2>
- <div style={SettingsSubsectionStyle}>
+ <div className='subsection'>
<CurrentGameSettings />
</div>
</Vierkant>
- <Vierkant fullwidth>
+ <Vierkant className='section logout w100m2m pad-l bg-800'>
<h2>Uitloggen</h2>
- <div
- style={{
- width: '100%',
- textAlign: 'center',
- }}
- >
+ <div className='center'>
<IconLabelButton
+ className='dispinbl'
icon={<ExitToAppOutlinedIcon />}
text='Uitloggen'
- style={{
- float: 'none',
- marginLeft: 0,
- }}
- onclick={() => {
- cookies.remove('token');
- window.location.pathname = '/';
- }}
+ href='/logout'
/>
</div>
</Vierkant>
diff --git a/pages/user.tsx b/pages/user.tsx
index 4f7331c..9741275 100644
--- a/pages/user.tsx
+++ b/pages/user.tsx
@@ -1,6 +1,6 @@
import Icon from '@mdi/react';
import axios from 'axios';
-import { Children, ReactNode, useContext, useEffect, useState } from 'react';
+import { ReactNode, useContext, useEffect, useState } from 'react';
import { userGames, userInfo } from '../api/api';
import { AccountAvatar } from '../components/account';
@@ -35,60 +35,18 @@ function InfoModule(props: {
label: string;
icon: ReactNode;
}) {
- return <div
- style={{
- position: 'relative',
- height: '100%',
- }}
- >
- <div
- style={{
- position: 'absolute',
- left: '50%',
- transform: 'translateX(-50%)',
- }}
- >
+ return <div className='infoModule posrel'>
+ <div className='iconWrapper posabs'>
{props.icon}
</div>
- <div
- style={{
- position: 'absolute',
- top: 24 + 6,
- left: 0,
- right: 0,
- bottom: 0,
- }}
- >
- <span
- style={{
- position: 'absolute',
- top: '50%',
- transform: 'translateY(-50%)',
- width: '100%',
- textAlign: 'center',
- }}
- >
+ <div className='labelWrapper posabs h0 b0'>
+ <span className='label posabs center fullwidth'>
{props.label}
</span>
</div>
</div>;
}
-function InfoSection(props: { children: ReactNode; }) {
- return <Vierkant fullwidth>
- <div
- style={{
- display: 'grid',
- gridTemplateColumns: `repeat(${Children.count(props.children)}, 1fr)`,
- gridGap: 12,
- height: 64,
- }}
- >
- {props.children}
- </div>
- </Vierkant>;
-}
-
export default function AccountPage() {
var server = typeof window === 'undefined';
var loggedIn = !server && document.cookie.includes('token');
@@ -167,226 +125,222 @@ export default function AccountPage() {
<NavBar />
<CenteredPage width={802}>
<PageTitle>Profiel</PageTitle>
- <Vierkant fullwidth>
- <AccountAvatar size={128} id={user?.id || ''} />
- <div
- style={{
- display: 'inline-block',
- verticalAlign: 'top',
- marginLeft: 12,
- width: 'calc(100% - 128px - 12px)',
- }}
- >
- <h2 style={{ fontSize: 32 }}>{user?.username}</h2>
- <p
- id='status'
- contentEditable={editingStatus ? 'true' : 'false'}
- style={{
- marginTop: 6,
- transitionDuration: '.3s',
- }}
- suppressContentEditableWarning={true}
- >
- {user?.status}
- </p>
- </div>
- <div
- style={{
- position: 'absolute',
- backgroundColor: 'var(--background)',
- height: '40px',
- bottom: 24,
- left: 24 + 12 + 128,
- right: 24,
- }}
- >
- {loggedIn && <div>
- {ownPage
- ? <div>
- <IconLabelButton icon={<SettingsOutlinedIcon />} href='/settings' text='Instellingen' />
- {!editingStatus
- ? <IconLabelButton
- icon={<EditOutlinedIcon />}
- text='Status bewerken'
- onclick={() => setEditingStatus(true)}
+ <Vierkant className='accountHeader w100m2m pad-l bg-800'>
+ <div className='inner posrel'>
+ <AccountAvatar size={128} id={user?.id || ''} />
+ <div className='userInfo dispinbl valigntop'>
+ <h2 className='username'>{user?.username}</h2>
+ <p
+ id='status'
+ className='status round-t'
+ contentEditable={editingStatus ? 'true' : 'false'}
+ suppressContentEditableWarning={true}
+ >
+ {user?.status}
+ </p>
+ </div>
+ <div className='posabs b0 r0'>
+ {loggedIn && <div>
+ {ownPage
+ ? <div>
+ <IconLabelButton
+ icon={<SettingsOutlinedIcon />}
+ href='/settings'
+ text='Instellingen'
/>
- : <IconLabelButton
- icon={<DoneOutlinedIcon />}
- text='Status opslaan'
- onclick={() => {
- setEditingStatus(false);
- axios.request({
- method: 'post',
- url: `/api/user/status`,
- headers: { 'content-type': 'application/json' },
- data: { 'status': document.getElementById('status').innerText },
- });
- }}
- />}
- </div>
- : <div>
- {(() => {
- var icon = {
- 'blocked': <Icon size={1} path={mdiAccountCancelOutline} />,
- }[relation] || <Icon size={1} path={mdiAccountCancelOutline} />;
+ {!editingStatus
+ ? <IconLabelButton
+ icon={<EditOutlinedIcon />}
+ text='Status bewerken'
+ onclick={() => setEditingStatus(true)}
+ />
+ : <IconLabelButton
+ icon={<DoneOutlinedIcon />}
+ text='Status opslaan'
+ onclick={() => {
+ setEditingStatus(false);
+ axios.request({
+ method: 'post',
+ url: `/api/user/status`,
+ headers: { 'content-type': 'application/json' },
+ data: { 'status': document.getElementById('status').innerText },
+ });
+ }}
+ />}
+ </div>
+ : <div>
+ {(() => {
+ var icon = {
+ 'blocked': <Icon size={1} path={mdiAccountCancelOutline} />,
+ }[relation] || <Icon size={1} path={mdiAccountCancelOutline} />;
- var text = {
- 'blocked': 'Deblokkeren',
- }[relation] || 'Blokkeren';
+ var text = {
+ 'blocked': 'Deblokkeren',
+ }[relation] || 'Blokkeren';
- return <IconLabelButton
- icon={icon}
- text={text}
- onclick={() => {
- var nextRelation = {
- 'blocked': {
- 'endpoint': '/api/social/unblock',
- 'action': `${user.username} gedeblokkeerd`,
- 'relation': 'none',
+ return <IconLabelButton
+ icon={icon}
+ text={text}
+ onclick={() => {
+ var nextRelation = {
+ 'blocked': {
+ 'endpoint': '/api/social/unblock',
+ 'action': `${user.username} gedeblokkeerd`,
+ 'relation': 'none',
+ 'icon': <Icon size={1} path={mdiAccountCancelOutline} />,
+ },
+ }[relation] || {
+ 'endpoint': '/api/social/block',
+ 'action': `${user.username} geblokkeerd`,
+ 'relation': 'blocked',
'icon': <Icon size={1} path={mdiAccountCancelOutline} />,
- },
- }[relation] || {
- 'endpoint': '/api/social/block',
- 'action': `${user.username} geblokkeerd`,
- 'relation': 'blocked',
- 'icon': <Icon size={1} path={mdiAccountCancelOutline} />,
- };
+ };
- axios.request({
- method: 'post',
- url: nextRelation.endpoint,
- headers: { 'content-type': 'application/json' },
- data: { 'id': user?.id },
- })
- .then(() => {
- toast({
- message: nextRelation.action,
- type: 'confirmation',
- icon: nextRelation.icon,
+ axios.request({
+ method: 'post',
+ url: nextRelation.endpoint,
+ headers: { 'content-type': 'application/json' },
+ data: { 'id': user?.id },
+ })
+ .then(() => {
+ toast({
+ message: nextRelation.action,
+ type: 'confirmation',
+ icon: nextRelation.icon,
+ });
+ setRelation(nextRelation.relation);
});
- setRelation(nextRelation.relation);
- });
- }}
- />;
- })()}
- {(() => {
- var icon = {
- 'friends': <Icon size={1} path={mdiAccountMinusOutline} />,
- 'outgoing': <Icon size={1} path={mdiAccountRemoveOutline} />,
- 'incoming': <PersonAddOutlinedIcon />,
- }[relation] || <PersonAddOutlinedIcon />;
+ }}
+ />;
+ })()}
+ {(() => {
+ var icon = {
+ 'friends': <Icon size={1} path={mdiAccountMinusOutline} />,
+ 'outgoing': <Icon size={1} path={mdiAccountRemoveOutline} />,
+ 'incoming': <PersonAddOutlinedIcon />,
+ }[relation] || <PersonAddOutlinedIcon />;
- var text = {
- 'friends': 'Vriend verwijderen',
- 'outgoing': 'Vriendschapsverzoek annuleren',
- 'incoming': 'Vriendschapsverzoek accepteren',
- }[relation] || 'Vriendschapsverzoek sturen';
+ var text = {
+ 'friends': 'Vriend verwijderen',
+ 'outgoing': 'Vriendschapsverzoek annuleren',
+ 'incoming': 'Vriendschapsverzoek accepteren',
+ }[relation] || 'Vriendschapsverzoek sturen';
- return <IconLabelButton
- icon={icon}
- text={text}
- onclick={() => {
- var nextRelation = {
- 'friends': {
- 'endpoint': '/api/social/remove',
- 'action': `${user.username} succesvol verwijderd als vriend`,
- 'relation': 'none',
- 'icon': <Icon size={1} path={mdiAccountMinusOutline} />,
- },
- 'outgoing': {
- 'endpoint': '/api/social/remove',
- 'action': `Vriendschapsverzoek naar ${user.username} geannuleerd`,
- 'relation': 'none',
- 'icon': <Icon size={1} path={mdiAccountMinusOutline} />,
- },
- 'incoming': {
- 'endpoint': '/api/social/accept',
- 'action': `Vriendschapsverzoek van ${user.username} geaccepteerd`,
- 'relation': 'friends',
+ return <IconLabelButton
+ icon={icon}
+ text={text}
+ onclick={() => {
+ var nextRelation = {
+ 'friends': {
+ 'endpoint': '/api/social/remove',
+ 'action': `${user.username} succesvol verwijderd als vriend`,
+ 'relation': 'none',
+ 'icon': <Icon size={1} path={mdiAccountMinusOutline} />,
+ },
+ 'outgoing': {
+ 'endpoint': '/api/social/remove',
+ 'action':
+ `Vriendschapsverzoek naar ${user.username} geannuleerd`,
+ 'relation': 'none',
+ 'icon': <Icon size={1} path={mdiAccountMinusOutline} />,
+ },
+ 'incoming': {
+ 'endpoint': '/api/social/accept',
+ 'action':
+ `Vriendschapsverzoek van ${user.username} geaccepteerd`,
+ 'relation': 'friends',
+ 'icon': <PersonAddOutlinedIcon />,
+ },
+ }[relation] || {
+ 'endpoint': '/api/social/request',
+ 'action': `Vriendschapsverzoek gestuurd naar ${user.username}`,
+ 'relation': 'outgoing',
'icon': <PersonAddOutlinedIcon />,
- },
- }[relation] || {
- 'endpoint': '/api/social/request',
- 'action': `Vriendschapsverzoek gestuurd naar ${user.username}`,
- 'relation': 'outgoing',
- 'icon': <PersonAddOutlinedIcon />,
- };
+ };
- axios.request({
- method: 'post',
- url: nextRelation.endpoint,
- headers: { 'content-type': 'application/json' },
- data: { 'id': user?.id },
- })
- .then(() => {
- toast({
- message: nextRelation.action,
- type: 'confirmation',
- icon: nextRelation.icon,
+ axios.request({
+ method: 'post',
+ url: nextRelation.endpoint,
+ headers: { 'content-type': 'application/json' },
+ data: { 'id': user?.id },
+ })
+ .then(() => {
+ toast({
+ message: nextRelation.action,
+ type: 'confirmation',
+ icon: nextRelation.icon,
+ });
+ setRelation(nextRelation.relation);
});
- setRelation(nextRelation.relation);
- });
- }}
- />;
- })()}
- </div>}
- </div>}
+ }}
+ />;
+ })()}
+ </div>}
+ </div>}
+ </div>
</div>
</Vierkant>
- <InfoSection>
- <InfoModule
- icon={<Icon size={1} path={mdiCheckboxBlankCircle} color='var(--disk-b-text)' />}
- label='Online'
- />
- <InfoModule
- icon={<AssignmentIndOutlinedIcon />}
- label={(() => {
- var memberSince = 'Lid sinds';
+ <Vierkant className='infosection pad-l w100m2m bg-800'>
+ <div className='inner sidebyside'>
+ <InfoModule
+ icon={<Icon size={1} path={mdiCheckboxBlankCircle} className='outcome win' />}
+ label='Online'
+ />
+ <InfoModule
+ icon={<AssignmentIndOutlinedIcon />}
+ label={(() => {
+ var memberSince = 'Lid sinds';
- var registered = new Date(user?.registered);
- memberSince += ' ' + registered.toLocaleString('nl-nl', { month: 'long', day: 'numeric' });
+ var registered = new Date(user?.registered);
+ memberSince += ' ' + registered.toLocaleString('nl-nl', { month: 'long', day: 'numeric' });
- var currentYear = new Date().getFullYear();
- var memberYear = registered.getFullYear();
- if (currentYear != memberYear) memberSince += ' ' + memberYear;
+ var currentYear = new Date().getFullYear();
+ var memberYear = registered.getFullYear();
+ if (currentYear != memberYear) memberSince += ' ' + memberYear;
- return memberSince;
- })()}
- />
- <InfoModule
- icon={<PeopleOutlineOutlinedIcon />}
- label={(() => {
- var label = user?.friends.toString() + ' ';
- label += user?.friends == 1 ? 'vriend' : 'vrienden';
- return label;
- })()}
- />
- <InfoModule icon={<Icon size={1} path={mdiEarth} />} label='Nederland' />
- </InfoSection>
- <InfoSection>
- <InfoModule
- icon={<ArrowUpwardOutlinedIcon style={{ color: 'var(--disk-b-text)' }} />}
- label={gameInfo?.totals.win + ' keer gewonnen'}
- />
- <InfoModule
- icon={<Icon size={1} path={mdiEqual} />}
- label={gameInfo?.totals.draw + ' keer gelijkspel'}
- />
- <InfoModule
- icon={<ArrowDownwardOutlinedIcon style={{ color: 'var(--disk-a-text)' }} />}
- label={gameInfo?.totals.lose + ' keer verloren'}
- />
- <InfoModule icon={<Icon size={1} path={mdiClipboardTextOutline} />} label={'Score: ' + user?.rating} />
- <InfoModule
- icon={<Icon size={1} path={mdiGamepadSquareOutline} />}
- label={(() => {
- var label = gameInfo?.totals.games.toString() + ' ';
- label += gameInfo?.totals.games == 1 ? 'potje' : 'potjes';
- return label;
- })()}
- />
- </InfoSection>
- <Vierkant>
+ return memberSince;
+ })()}
+ />
+ <InfoModule
+ icon={<PeopleOutlineOutlinedIcon />}
+ label={(() => {
+ var label = user?.friends.toString() + ' ';
+ label += user?.friends == 1 ? 'vriend' : 'vrienden';
+ return label;
+ })()}
+ />
+ <InfoModule icon={<Icon size={1} path={mdiEarth} />} label='Nederland' />
+ </div>
+ </Vierkant>
+ <Vierkant className='infosection pad-l w100m2m sidebyside bg-800'>
+ <div className='inner sidebyside'>
+ <InfoModule
+ icon={<ArrowUpwardOutlinedIcon className='outcome win' />}
+ label={gameInfo?.totals.win + ' keer gewonnen'}
+ />
+ <InfoModule
+ icon={<Icon size={1} path={mdiEqual} className='subtile' />}
+ label={gameInfo?.totals.draw + ' keer gelijkspel'}
+ />
+ <InfoModule
+ icon={<ArrowDownwardOutlinedIcon className='outcome lose' />}
+ label={gameInfo?.totals.lose + ' keer verloren'}
+ />
+ <InfoModule
+ icon={<Icon size={1} path={mdiClipboardTextOutline} />}
+ label={'Score: ' + user?.rating}
+ />
+ <InfoModule
+ icon={<Icon size={1} path={mdiGamepadSquareOutline} />}
+ label={(() => {
+ var label = gameInfo?.totals.games.toString() + ' ';
+ label += gameInfo?.totals.games == 1 ? 'potje' : 'potjes';
+ return label;
+ })()}
+ />
+ </div>
+ </Vierkant>
+ <Vierkant className='pad-l bg-800'>
<RecentGames games={gameInfo?.games} />
</Vierkant>
</CenteredPage>
diff --git a/public/favicon.png b/public/favicon.png
index 15b2b22..e03870b 100644
--- a/public/favicon.png
+++ b/public/favicon.png
Binary files differ
diff --git a/public/favicon.svg b/public/favicon.svg
index c1f7520..63e5b7a 100644
--- a/public/favicon.svg
+++ b/public/favicon.svg
@@ -1,14 +1,16 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<style>
- .red { fill: #E16D82; }
- .green { fill: #71D9CC; }
+ .red { fill: #A63A4D; }
+ .green { fill: #3AA699; }
.outline {
- stroke: #5D737E;
+ stroke: #141619;
stroke-width: 2px;
}
@media (prefers-color-scheme: dark) {
.outline { stroke: #FCFFFD; }
+ .red { fill: #FF4365; }
+ .green { fill: #00D9C0; }
}
</style>
<circle class="green" cx="6.5" cy="6.5" r="4.5"/>
diff --git a/readme.md b/readme.md
index 3edf03a..4f172d8 100644
--- a/readme.md
+++ b/readme.md
@@ -42,11 +42,16 @@
A design prototype of the website can be found on
[Figma](https://www.figma.com/file/rTciVQApAe6cwrH1Prl5Wn/4-op-een-rij?node-id=0%3A1).
-An outdated version of the API documentation is still on Google Docs, but I'm
-moving and updating it to be in api/readme.md.
+The api documentation can be found in api/readme.md.
![](./diagram.png)
+## other readme's
+
+- [api](api/readme.md)
+- [voerbak](voerbak/readme.md)
+- [styles](styles/readme.md)
+
## setup
To set up this project you'll need to install npm and pip dependencies, pull all
diff --git a/styles/dark.css b/styles/dark.css
index 681116f..b44171b 100644
--- a/styles/dark.css
+++ b/styles/dark.css
@@ -1,9 +1,19 @@
html.dark {
- --text: #FCFFFD;
- --text-alt: var(--text);
+ --background: var(--gray-900);
+ --foreground: #FFFFF3;
- --page-background: #11171a;
- --background: #222d33;
- --background-alt: #344047;
-}
+ --accent: #AD34F3;
+
+ --error: #FF4365;
+ --disk-b-alt: #F49BA1;
+ --confirm: #00D9C0;
+ --disk-a-alt: #86F3F3;
+
+ --gray-100: #CED2DC;
+ --gray-200: #A9AFC0;
+ --gray-300: #757D92;
+ --gray-700: #293140;
+ --gray-800: #1F242D;
+ --gray-900: #141619;
+}
diff --git a/styles/disk.css b/styles/disk.css
index d858335..2646fed 100644
--- a/styles/disk.css
+++ b/styles/disk.css
@@ -1,3 +1,3 @@
.voerBord .disk.state-0 { background-color: var(--page-background); }
-.voerBord .disk.state-1 { background-color: var(--disk-a); }
-.voerBord .disk.state-2 { background-color: var(--disk-b); }
+.voerBord .disk.state-1 { background-color: var(--confirm); }
+.voerBord .disk.state-2 { background-color: var(--error); }
diff --git a/styles/game.css b/styles/game.css
new file mode 100644
index 0000000..d482f9b
--- /dev/null
+++ b/styles/game.css
@@ -0,0 +1,76 @@
+.outcomeDialog .inner > * {
+ margin-top: var(--spacing-medium);
+}
+
+.outcomeDialog .inner .button {
+ float: unset;
+ padding: var(--spacing-medium) var(--spacing-large);
+}
+
+.newGameDialog .inviteButton { height: 160px; }
+
+.newGameDialog .inviteButton.random { background-color: var(--confirm); }
+
+.newGameDialog .inviteButton.link { background-color: var(--error); }
+
+.newGameDialog .inviteButton .icon {
+ top: var(--spacing-medium);
+ font-size: 100px;
+}
+
+.newGameDialog .inviteButton .label { bottom: var(--spacing-medium); }
+
+.newGameDialog > div { margin-top: var(--spacing-large); }
+
+.voerBord { border-spacing: var(--spacing-small); }
+
+.voerBord .cell.inner { border: 2px solid var(--gray-800); }
+
+.voerBord .square { margin-top: 100%; }
+
+.voerBord.active .cell.inner { cursor: pointer; }
+
+.voerBord .disk {
+ border-radius: 999999999px;
+ margin: 3px;
+}
+
+.gameBar .gameBarButton {
+ margin: 0;
+ margin-left: var(--spacing-small);
+}
+
+.gameBar .move {
+ width: 32px;
+ height: 32px;
+ border-radius: 16px;
+
+ margin: 8px;
+}
+
+.gameBar .move.move-a { background-color: var(--error); }
+.gameBar .move.move-b { background-color: var(--confirm); }
+
+.gameBar .timer span {
+ margin: 0 4px;
+ font-size: 20px;
+}
+
+.gameBar .score {
+ font-size: 20px;
+ z-index: 1;
+}
+
+.gameBar .score.winning { color: var(--disk-a-alt); }
+.gameBar .score.losing { color: var(--disk-b-alt); }
+
+.voerGame {
+ max-width: 100vh;
+ margin: 0 auto;
+}
+
+html.dark .newGameDialog .inviteButton { background-color: var(--gray-800); }
+html.dark .newGameDialog .inviteButton.random .icon { color: var(--confirm); }
+html.dark .newGameDialog .inviteButton.link .icon { color: var(--error); }
+html.dark .newGameDialog .searchBar .input { background-color: var(--gray-800); }
+
diff --git a/styles/gameSettings.css b/styles/gameSettings.css
new file mode 100644
index 0000000..3c45fb4
--- /dev/null
+++ b/styles/gameSettings.css
@@ -0,0 +1,77 @@
+.editGameSettings {
+ height: 80px;
+ overflow: visible;
+ z-index: 1;
+}
+
+.editGameSettings .currentRules {
+ top: 50%;
+ transform: translateY(-50%);
+}
+
+.editGameSettings > .button {
+ width: 150px;
+ top: 50%;
+ transform: translateY(-50%);
+ text-align: left;
+}
+
+.editGameSettings > .button .icon {
+ font-size: 48px;
+}
+
+.editGameSettings > .button .text {
+ right: var(--spacing-medium);
+ width: 85px;
+ top: 50%;
+ vertical-align: middle;
+ transform: translateY(-50%);
+}
+
+.editGameSettings .editableRules {
+ margin: var(--spacing-large) 0;
+ max-height: 500px;
+ overflow-y: scroll;
+}
+
+.editGameSettings .editableRules .editableRulesSection {
+ width: 100%;
+ background-color: var(--gray-700);
+ margin: 0;
+ margin-bottom: var(--spacing-medium);
+}
+html.dark .editGameSettings .editableRules .editableRulesSection {
+ background-color: var(--gray-800);
+}
+.editGameSettings .editableRules .editableRulesSection:last-child {
+ margin-bottom: 0;
+}
+
+.editGameSettings .button { line-height: normal; }
+
+.editGameSettings .checkboxWrapper { margin: -3px; }
+
+.editGameSettings .editableRules .editableRulesSection .timeControls {
+ margin: var(--spacing-medium);
+ margin-left: 0;
+ margin-right: 0;
+}
+
+.editGameSettings .editableRules .editableRulesSection .timeControls input {
+ background-color: var(--background);
+ width: calc(100% - 2 * var(--spacing-medium));
+}
+
+.editGameSettings .editableRules .gamerule {
+ margin-top: var(--spacing-medium);
+}
+
+.editGameSettings .editableRules .gamerule h1 {
+ color: var(--accent);
+ font-size: 2.5rem;
+}
+
+#timelimit .valignsup {
+ margin-left: var(--spacing-small);
+}
+
diff --git a/styles/global.css b/styles/global.css
index 1e87524..79aa026 100644
--- a/styles/global.css
+++ b/styles/global.css
@@ -1,15 +1,36 @@
-html {
- --text: #FCFFFD;
- --page-background: var(--text);
-
- --background: #5D737E;
- --text-alt: var(--background);
- --background-alt: #81949E;
-
- --disk-a: #E16D82;
- --disk-a-text: #FDC0C4;
- --disk-b: #71D9CC;
- --disk-b-text: #C0FDEB;
+:root {
+ --background: var(--gray-900);
+ --foreground: var(--gray-100);
+
+ --accent: #7E3AA6;
+
+ --error: #A63A4D;
+ --disk-b-alt: #582D35;
+
+ --confirm: #3AA699;
+ --disk-a-alt: #244743;
+
+ /* shade */
+ --gray-100: #141619;
+ --gray-200: #1F242D;
+ --gray-300: #293140;
+ --gray-600: #757D92;
+ --gray-700: #A9AFC0;
+ --gray-800: #CED2DC;
+ --gray-900: #E3E6EE;
+
+ /* box-shadow */
+ --drop-level-2: 0px 8px 32px 0px rgba(0, 0, 0, 0.3);
+ --drop-level-1: 0px 8px 12px -4px rgba(0, 0, 0, 0.15);
+
+ /* border-radius */
+ --tight-corner: 6px;
+ --loose-corner: 8px;
+
+ /* margin/padding */
+ --spacing-small: 6px;
+ --spacing-medium: 12px;
+ --spacing-large: 24px;
}
/* default margin */
@@ -18,6 +39,9 @@ html, body {
padding: 0;
}
+/* section font size */
+h1 { font-size: 25px; }
+
/* subsection font size */
h2 { font-size: 20px; }
@@ -27,17 +51,17 @@ h3 {
padding-bottom: 6px;
}
-/* global font zize */
-p, b, i, span, td, th { font-size: 15px; }
-
/* navbar fix */
body { padding-left: 48px; }
/* font */
-html { font-family: "Inter"; }
+html { font-family: "Inter", sans-serif; }
-/* background color */
-html, body { background-color: var(--page-background); }
+/* color */
+:root, html, body {
+ background-color: var(--background);
+ color: var(--foreground);
+}
/* link fix */
a {
@@ -59,14 +83,20 @@ h1, h2, h3, p, b, i, span, td, th {
table { table-layout: fixed; }
/* table styles */
-td, th {
- padding: 4px;
- font-size: 15px;
+td, th { padding: 4px; }
+
+input {
+ color: var(--foreground);
+ background-color: transparent;
+ font-family: inherit;
+ border: 0;
+ font-size: 1rem; /* why? */
}
input::placeholder {
font-style: italic;
- opacity: .8;
+ opacity: 1;
+ color: var(--gray-600);
}
/* remove chrome's ugly :focus outline */
@@ -78,19 +108,3 @@ input::placeholder {
/* material-ui default state */
svg.MuiSvgIcon-root { transition: none !important; }
-input::placeholder { opacity: .75; }
-input.dark::placeholder { color: var(--text); }
-input.light::placeholder { color: var(--text-alt); }
-
-/* editable field status */
-*[contenteditable] { border-color: var(--background-alt); }
-*[contenteditable="true"]:focus { border-color: var(--disk-a); }
-*[contenteditable="true"] {
- background-color: var(--page-background);
- color: var(--text-alt);
- padding: 6px;
- border-radius: 6px;
- border-style: solid;
- border-width: 2px;
-}
-
diff --git a/styles/index.css b/styles/index.css
new file mode 100644
index 0000000..fcda8e0
--- /dev/null
+++ b/styles/index.css
@@ -0,0 +1,71 @@
+.topbar * {
+ --squareSize: 90px;
+}
+
+.loginOrRegisterBox {
+ height: calc(var(--squareSize) + 2 * var(--spacing-large));
+ width: 100%;
+ max-width: calc(100% - var(--squareSize) - var(--spacing-medium) * 2 - var(--spacing-large) * 2);
+}
+
+.loginOrRegisterBox .inner {
+ position: relative;
+ width: 100%;
+ height: 100%;
+}
+
+.loginOrRegisterBox .inner .registerMessage {
+ user-select: none;
+ display: inline-block;
+ margin: 0 auto;
+ min-width: 240px;
+ max-width: 350px;
+ text-align: center;
+}
+
+.loginOrRegisterBox .inner .button.register {
+ background-color: var(--gray-700);
+ color: var(--foreground);
+}
+
+.gamemode .icon {
+ font-size: 64px;
+ font-size: 64px;
+ height: 64px;
+ display: inline-block;
+ position: absolute;
+ top: var(--spacing-large);
+ left: 50%;
+ transform: translateX(-50%);
+}
+
+.gamemode .text {
+ white-space: nowrap;
+ display: inline-block;
+ position: absolute;
+ bottom: var(--spacing-large);
+ left: 50%;
+ transform: translateX(-50%);
+ user-select: none;
+ font-weight: 500;
+}
+
+.topbar .gamemode {
+ --size: calc(var(--squareSize) + 2 * var(--spacing-large));
+ width: var(--size);
+ height: var(--size);
+}
+
+.topbar .profile .info {
+ left: calc(90px + 12px);
+ width: calc(100% - 90px - 12px);
+ height: 100%;
+}
+
+.topbar .profile .info .username { max-width: 178px; }
+.topbar .profile .info .score { margin-top: var(--spacing-small); }
+
+.topbar .profile .info .games .divider {
+ margin: 0 3px;
+}
+
diff --git a/styles/loginregister.css b/styles/loginregister.css
new file mode 100644
index 0000000..854aeb2
--- /dev/null
+++ b/styles/loginregister.css
@@ -0,0 +1,13 @@
+.centeredForm {
+ top: 50%;
+ transform: translateY(-50%);
+ margin: 0 auto;
+}
+
+.centeredForm .input {
+ margin-bottom: var(--spacing-medium);
+}
+
+.centeredForm .input:nth-last-of-type(2) {
+ margin-bottom: var(--spacing-large);
+}
diff --git a/styles/navbar.css b/styles/navbar.css
new file mode 100644
index 0000000..baf9fd6
--- /dev/null
+++ b/styles/navbar.css
@@ -0,0 +1,36 @@
+.navbar .item {
+ margin: var(--spacing-medium);
+ margin-bottom: calc(var(--spacing-medium) + 4px);
+ display: block;
+}
+
+.navbar .bottomArea {
+ position: absolute;
+ bottom: -4px;
+ left: 0;
+}
+
+.navbar {
+ width: 48px;
+ line-height: 0;
+ overflow: visible;
+ white-space: nowrap;
+ z-index: 2;
+}
+
+.navbar .bottomArea .item.notifications {
+ overflow: visible;
+}
+
+.navbar .bottomArea .item.notifications > .iconWrapper {
+ cursor: pointer;
+}
+
+.notifications .notificationDot {
+ background-color: var(--accent);
+ width: 8px;
+ height: 8px;
+ border-radius: 4px;
+ top: 2px;
+ right: 2px;
+}
diff --git a/styles/notifications.css b/styles/notifications.css
new file mode 100644
index 0000000..db1598d
--- /dev/null
+++ b/styles/notifications.css
@@ -0,0 +1,52 @@
+/* a tags are here because of css specificity */
+a.notificationsArea {
+ left: calc(48px + var(--spacing-medium));
+ top: 92px;
+ transform: translateY(-100%);
+ text-align: left;
+ width: 400px;
+ height: 450px;
+}
+
+a.notificationsArea .tuitje {
+ left: var(--spacing-medium);
+ bottom: 86px;
+ transform: translate(-100%, 100%) rotate(90deg);
+ fill: var(--gray-700);
+}
+
+.notificationsArea .title {
+ margin-bottom: var(--spacing-large);
+}
+
+.notificationsArea .inner {
+ overflow-y: scroll;
+ white-space: normal;
+ height: calc(450px - 4 * var(--spacing-large));
+}
+
+.notificationsArea .acceptable {
+ margin: 0;
+ margin-bottom: var(--spacing-medium);
+}
+
+.notificationsArea .acceptable:last-child {
+ margin-bottom: 0;
+}
+
+.notificationsArea .buttons .button {
+ margin: 0;
+}
+
+.notificationsArea .buttons {
+ margin-top: var(--spacing-medium);
+}
+
+.notificationsArea .noMsgsWrapper h1 {
+ white-space: nowrap;
+ user-select: none;
+}
+
+.notificationsArea .acceptable .userInfo {
+ margin-left: var(--spacing-small);
+}
diff --git a/styles/readme.md b/styles/readme.md
index 1d1e96f..58e6db0 100644
--- a/styles/readme.md
+++ b/styles/readme.md
@@ -1,103 +1,22 @@
-# Styles
+# styles
-This is the folder where all website css should be. Currently most of the css is
-done using inline styles in the .tsx files, but they're being moved to seperate
-.css files in here. A redesign has also been in the making and we now have a
-style guide to follow while writing the website:
+## dark
-## Dark
+![dark mode style guide](./styleguidedark.png)
-![Dark mode style guide](./styleguidedark.png)
+## light
-## Light
-
-![Light mode style guide](./styleguidelight.png)
+![light mode style guide](./styleguidelight.png)
The new designs can be found on
[Figma](https://www.figma.com/file/rTciVQApAe6cwrH1Prl5Wn/4-op-een-rij?node-id=0%3A1).
The Figma designs are in dark mode, but the website will (for now) still have
light mode as it's default theme.
-After the move to .css files is done I'll also start working on the new theming
-settings, and allow custom themes to be created with css that can easily be
-selected in the settings page instead of having to copy-paste color codes
-manually.
-
-## Show me the css
-
-These will eventually be moved to global.css when the move to .css files is
-done.
-
-```css
-:root {
- --background: #E3E6EE;
- --foreground: var(--gray-900);
-
- --accent: #7E3AA6;
-
- --error: #A63A4D;
- --disk-b-alt: #582D35;
-
- --confirm: #3AA699;
- --disk-a-alt: #244743;
-}
-
-@media (prefers-color-scheme: dark) {
- :root {
- --background: var(--gray-900);
- --foreground: #FFFFF3;
-
- --accent: #AD34F3;
-
- --error: #FF4365;
- --disk-b-alt: #F49BA1;
-
- --confirm: #00D9C0;
- --disk-a-alt: #86F3F3;
- }
-}
-
-:root {
- /* shade */
- --gray-100: #CED2DC;
- --gray-200: #A9AFC0;
- --gray-300: #757D92;
- --gray-700: #293140;
- --gray-800: #1F242D;
- --gray-900: #141619;
-
- /* box-shadow */
- --drop-level-2: 0px 8px 32px 0px rgba(0, 0, 0, 0.3);
- --drop-level-1: 0px 8px 12px -4px rgba(0, 0, 0, 0.15);
-
- /* border-radius */
- --tight-corner: 6px;
- --loose-corner: 8px;
-
- /* margin/padding */
- --spacing-small: 6px;
- --spacing-medium: 12px;
- --spacing-large: 24px;
-}
-
-html {
- font-family: "Inter", sans-serif;
- font-size: 14px;
-}
-
-/* headings */
-h1, h2, h3 { font-weight: 700; }
-h1 { font-size: 25px; }
-h2 { font-size: 20px; }
-
-.subtile {
- color: var(--gray-300);
- font-style: italic;
-}
+I'm now working on the new theming settings, and allow custom themes to be
+created with css that can easily be selected in the settings page instead of
+having to copy-paste color codes manually.
-/* no-select */
-.nosel {
- user-select: none;
- font-weight: 600;
-}
-```
+There are utility styles in ./utility.css. These are supposed to mimick
+something like tailwind css. Most styles from the style guide are also defined
+in ./global.css.
diff --git a/styles/recentGames.css b/styles/recentGames.css
new file mode 100644
index 0000000..c7150aa
--- /dev/null
+++ b/styles/recentGames.css
@@ -0,0 +1,21 @@
+table .leftAlignedColumn {
+ text-align: left;
+ padding-left: var(--spacing-medium);
+}
+
+table .rightAlignedColumn {
+ text-align: right;
+ padding-left: var(--spacing-medium);
+}
+
+table.recentGames {
+ margin-top: var(--spacing-medium);
+}
+
+table.recentGames a {
+ font-weight: 500;
+}
+
+div.recentGames .nogames {
+ margin: 32px 64px;
+}
diff --git a/styles/search.css b/styles/search.css
new file mode 100644
index 0000000..696294d
--- /dev/null
+++ b/styles/search.css
@@ -0,0 +1,11 @@
+.bigSearchBar .input {
+ width: calc(100% - 2 * var(--spacing-medium) - 48px);
+}
+
+.results .result .inner .userInfo {
+ left: calc(48px + var(--spacing-medium));
+}
+
+h1.noresults {
+ margin: 24px 32px;
+}
diff --git a/styles/settings.css b/styles/settings.css
new file mode 100644
index 0000000..bcce5b5
--- /dev/null
+++ b/styles/settings.css
@@ -0,0 +1,8 @@
+.section.logout .button {
+ float: unset;
+}
+
+.subsection {
+ margin-top: var(--spacing-large);
+ min-height: 40px;
+}
diff --git a/styles/toast.css b/styles/toast.css
new file mode 100644
index 0000000..2ecbcfb
--- /dev/null
+++ b/styles/toast.css
@@ -0,0 +1,66 @@
+.toast.error {
+ background-color: var(--error);
+}
+.toast.confirmation {
+ background-color: var(--confirm);
+}
+.toast.normal {
+ background-color: var(--gray-700);
+}
+
+.toast.confirmation,
+.toast.error {
+ color: var(--gray-900);
+}
+
+html.dark .toast.confirmation,
+html.dark .toast.error {
+ background-color: var(--gray-700);
+ color: var(--foreground);
+}
+html.dark .toast.error {
+ box-shadow: inset 0 0 0 2px var(--error);
+}
+html.dark .toast.confirmation {
+ box-shadow: inset 0 0 0 2px var(--confirm);
+}
+
+.toast {
+ margin-bottom: var(--spacing-medium);
+}
+
+#ToastArea {
+ white-space: nowrap;
+ bottom: var(--spacing-medium);
+ z-index: 1;
+ max-width: 600px;
+ width: calc(100% - 2 * 48px);
+ margin: 0 var(--spacing-large);
+}
+
+.toast .inner {
+ line-height: 0;
+ min-height: 24px;
+}
+
+.toast .inner.hasDescription {
+ min-height: 36px;
+}
+
+.toast .inner .icon,
+.toast .inner .content {
+ left: var(--spacing-medium);
+}
+
+.toast .inner.hasIcon .content {
+ left: calc(36px + var(--spacing-medium));
+}
+
+.toast .inner .content h2 {
+ font-size: 1rem;
+}
+
+.toast .inner .closeIcon {
+ right: var(--spacing-medium);
+ cursor: pointer;
+}
diff --git a/styles/ui.css b/styles/ui.css
new file mode 100644
index 0000000..2736a2c
--- /dev/null
+++ b/styles/ui.css
@@ -0,0 +1,118 @@
+.vierkant {
+ margin: var(--spacing-small);
+ display: inline-block;
+ position: relative;
+ box-sizing: border-box;
+}
+
+html.dark .button { color: var(--foreground); }
+.button {
+ background-color: var(--accent);
+ color: var(--gray-900);
+ text-align: center;
+ cursor: pointer;
+ position: relative;
+ text-decoration: none;
+ display: block;
+ user-select: none;
+ font-weight: 600;
+}
+
+.button.iconlabel,
+.button.colorpicker {
+ margin-left: var(--spacing-medium);
+}
+
+.button.colorpicker {
+ --color: var(--gray-100);
+ --background: #ff00ff;
+
+ background-color: var(--background);
+ color: var(--color);
+ border-width: 2px;
+ border-style: solid;
+ border-color: var(--color);
+}
+
+.button.iconlabel .label {
+ margin: 3px;
+ margin-left: 8px;
+}
+
+.button.colorpicker .labelwrapper {
+ width: 150px;
+ height: 24px;
+}
+
+.button.colorpicker .labelwrapper .label {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ font-feature-settings: "tnum", "ss01";
+}
+
+.bubble {
+ margin: 0;
+ overflow: visible;
+ left: 50%;
+ top: -24px;
+ transform: translateY(-100%) translateX(-50%);
+}
+
+.bubble .tuitje {
+ bottom: -12px;
+ transform: translate(-50%, 0%) rotate(0deg);
+}
+
+.dialogbox { width: 392px; }
+.dialogbox > .title { margin-bottom: var(--spacing-large); }
+.dialogbox .icon.close {
+ top: 25px;
+ right: 25px;
+ cursor: pointer;
+}
+
+html.dark .dialogbox { background-color: var(--gray-700); }
+
+.searchBar {
+ overflow: hidden;
+}
+
+.searchBar .button {
+ border-radius: 0;
+ padding: 10px;
+}
+
+.searchBar .input {
+ width: calc(100% - 44px);
+ box-sizing: border-box;
+}
+
+.checkbox {
+ cursor: pointer;
+}
+
+.CenteredPageOuter {
+ margin: 0 auto;
+}
+
+.CenteredPageInner {
+ margin: 0 var(--spacing-small);
+ line-height: 0;
+}
+
+.pageTitle {
+ margin-left: var(--spacing-small);
+ margin-top: 32px;
+ margin-bottom: 64px;
+}
+
+.accountAvatar {
+ background-size: cover;
+}
+
+.accountAvatar.round {
+ border-radius: 9999999px;
+}
+
diff --git a/styles/user.css b/styles/user.css
new file mode 100644
index 0000000..d66fa94
--- /dev/null
+++ b/styles/user.css
@@ -0,0 +1,46 @@
+.infosection .inner {
+ height: 64px;
+}
+
+/* editable field status */
+*[contenteditable] {
+ box-shadow: inset 0 0 0 0px var(--foreground);
+}
+*[contenteditable="true"]:focus {
+ box-shadow: inset 0 0 0 2px var(--accent);
+}
+*[contenteditable="true"] {
+ background-color: var(--background);
+ color: var(--text-alt);
+ padding: 8px;
+ box-shadow: inset 0 0 0 2px var(--foreground);
+}
+
+.accountHeader .userInfo {
+ margin-left: var(--spacing-medium);
+ width: calc(100% - 128px - var(--spacing-medium));
+}
+
+.accountHeader .userInfo .username {
+ font-size: 2rem;
+}
+
+.accountHeader .userInfo .status {
+ transition-duration: .3s;
+ margin-top: var(--spacing-small);
+}
+
+.infoModule .iconWrapper {
+ left: 50%;
+ transform: translateX(-50%);
+}
+
+.infoModule .labelWrapper {
+ top: calc(24px + var(--spacing-small));
+}
+
+.infoModule .labelWrapper .label {
+ top: 50%;
+ transform: translateY(-50%);
+}
+
diff --git a/styles/utility.css b/styles/utility.css
new file mode 100644
index 0000000..cc358e6
--- /dev/null
+++ b/styles/utility.css
@@ -0,0 +1,102 @@
+.drop-1 { box-shadow: var(--drop-level-1); }
+.drop-2 { box-shadow: var(--drop-level-2); }
+
+.pad-s { padding: var(--spacing-small); }
+.pad-m { padding: var(--spacing-medium); }
+.pad-l { padding: var(--spacing-large); }
+
+.round-l { border-radius: var(--loose-corner); }
+.round-t { border-radius: var(--tight-corner); }
+
+.bg-100 { background-color: var(--gray-100); }
+.bg-200 { background-color: var(--gray-200); }
+.bg-300 { background-color: var(--gray-300); }
+.bg-700 { background-color: var(--gray-700); }
+.bg-800 { background-color: var(--gray-800); }
+.bg-900 { background-color: var(--gray-900); }
+
+.fg-100 { color: var(--gray-100); }
+.fg-900 { color: var(--gray-900); }
+
+.outcome.win { color: var(--disk-a-alt); }
+.outcome.lose { color: var(--disk-b-alt); }
+.outcome.draw { color: var(--gray-600); }
+
+.posabs { position: absolute; }
+.posrel { position: relative; }
+.posfix { position: fixed; }
+
+.t0 { top: 0; }
+.b0 { bottom: 0; }
+.r0 { right: 0; }
+.l0 { left: 0; }
+
+.h0 { left: 0; right: 0; }
+.v0 { top: 0; bottom: 0; }
+
+.a0 { top: 0; bottom: 0; left: 0; right: 0; }
+
+.dispnone { display: none; }
+.dispinbl { display: inline-block; }
+.dispbl { display: block; }
+
+.valigntop { vertical-align: top; }
+.valignsup { vertical-align: super; }
+
+.cpointer { cursor: pointer; }
+.cdefault { cursor: default; }
+
+.center { text-align: center; }
+
+.floatr { float: right; }
+.floatl { float: left; }
+
+.w100m2m { width: calc(100% - var(--spacing-medium)); }
+
+.w100vw { width: 100vw; }
+.h100vh { height: 100vh; }
+
+.fullwidth {
+ width: 100%;
+ box-sizing: border-box;
+}
+
+.subtile {
+ color: var(--gray-600);
+ font-style: italic;
+}
+
+.nosel {
+ user-select: none;
+ font-weight: 600;
+}
+
+.gg-l { grid-gap: var(--spacing-large) !important; }
+.sidebyside {
+ display: grid;
+ grid-gap: var(--spacing-medium);
+ grid-auto-flow: column;
+}
+
+.truncate {
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.abscenterh {
+ left: 50%;
+ transform: translateX(-50%);
+}
+
+.abscenterv {
+ top: 50%;
+ transform: translateY(-50%);
+}
+
+.abscenter {
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+}
+
diff --git a/todo b/todo
new file mode 100644
index 0000000..1e85c6f
--- /dev/null
+++ b/todo
@@ -0,0 +1,43 @@
+pages:
+
+- 404
+- maintenance
+- /logout
+- privacy info
+
+
+
+unimplemented:
+
+- view friend/blocked/request list
+- game stuff:
+ - rulesets
+ - timer / time limit
+
+
+
+website fixes:
+
+- unaccessible when not logged in:
+ - /user/* pages
+ - /settings
+- unaccessible when logged in:
+ - /register
+ - /login
+
+
+
+backend fixes:
+
+- don't include unrated games in rating.py
+
+
+
+security measures:
+
+- repeated login prevention
+- api rate limiting
+- write unit tests
+- bind tokens to ip adress (csrf)
+
+
diff --git a/tsconfig.json b/tsconfig.json
index 283a43f..6866dec 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -17,7 +17,12 @@
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
- "jsx": "preserve"
+ "jsx": "preserve",
+ "plugins": [
+ {
+ "name": "typescript-plugin-css-modules"
+ }
+ ]
},
"include": [
"src"
diff --git a/yarn.lock b/yarn.lock
index 06a9373..05ae7bb 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3104,6 +3104,11 @@ bfj@^7.0.2:
hoopy "^0.1.4"
tryer "^1.0.1"
+big.js@^3.1.3:
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/big.js/-/big.js-3.2.0.tgz#a5fc298b81b9e0dca2e458824784b65c52ba588e"
+ integrity sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q==
+
big.js@^5.2.2:
version "5.2.2"
resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328"
@@ -3602,6 +3607,21 @@ chokidar@3.4.3, chokidar@^3.4.1:
optionalDependencies:
fsevents "~2.1.2"
+"chokidar@>=3.0.0 <4.0.0":
+ version "3.5.1"
+ resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.1.tgz#ee9ce7bbebd2b79f49f304799d5468e31e14e68a"
+ integrity sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==
+ dependencies:
+ anymatch "~3.1.1"
+ braces "~3.0.2"
+ glob-parent "~5.1.0"
+ is-binary-path "~2.1.0"
+ is-glob "~4.0.1"
+ normalize-path "~3.0.0"
+ readdirp "~3.5.0"
+ optionalDependencies:
+ fsevents "~2.3.1"
+
chokidar@^2.1.8:
version "2.1.8"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917"
@@ -3935,6 +3955,13 @@ cookie@^0.3.1:
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb"
integrity sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=
+copy-anything@^2.0.1:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/copy-anything/-/copy-anything-2.0.3.tgz#842407ba02466b0df844819bbe3baebbe5d45d87"
+ integrity sha512-GK6QUtisv4fNS+XcI7shX0Gx9ORg7QqIznyfho79JTnX1XhLiyZHfftvGiziqzRiEi/Bjhgpi+D2o7HxJFPnDQ==
+ dependencies:
+ is-what "^3.12.0"
+
copy-concurrently@^1.0.0:
version "1.0.5"
resolved "https://registry.yarnpkg.com/copy-concurrently/-/copy-concurrently-1.0.5.tgz#92297398cae34937fcafd6ec8139c18051f0b5e0"
@@ -4145,6 +4172,13 @@ css-loader@4.3.0:
schema-utils "^2.7.1"
semver "^7.3.2"
+css-parse@~2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/css-parse/-/css-parse-2.0.0.tgz#a468ee667c16d81ccf05c58c38d2a97c780dbfd4"
+ integrity sha1-pGjuZnwW2BzPBcWMONKpfHgNv9Q=
+ dependencies:
+ css "^2.0.0"
+
css-prefers-color-scheme@^3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/css-prefers-color-scheme/-/css-prefers-color-scheme-3.1.1.tgz#6f830a2714199d4f0d0d0bb8a27916ed65cff1f4"
@@ -4177,6 +4211,14 @@ css-select@^2.0.0:
domutils "^1.7.0"
nth-check "^1.0.2"
+css-selector-tokenizer@^0.7.0:
+ version "0.7.3"
+ resolved "https://registry.yarnpkg.com/css-selector-tokenizer/-/css-selector-tokenizer-0.7.3.tgz#735f26186e67c749aaf275783405cf0661fae8f1"
+ integrity sha512-jWQv3oCEL5kMErj4wRnK/OPoBi0D+P1FR2cDCKYPaMeD2eW3/mttav8HT4hT1CKopiJI/psEULjkClhvJo4Lvg==
+ dependencies:
+ cssesc "^3.0.0"
+ fastparse "^1.1.2"
+
css-tree@1.0.0-alpha.37:
version "1.0.0-alpha.37"
resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.0.0-alpha.37.tgz#98bebd62c4c1d9f960ec340cf9f7522e30709a22"
@@ -4428,6 +4470,13 @@ debug@^3.1.1, debug@^3.2.5:
dependencies:
ms "^2.1.1"
+debug@~3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
+ integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
+ dependencies:
+ ms "2.0.0"
+
decamelize@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
@@ -4806,7 +4855,7 @@ dotenv-expand@5.1.0:
resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-5.1.0.tgz#3fbaf020bfd794884072ea26b1e9791d45a629f0"
integrity sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==
-dotenv@8.2.0:
+dotenv@8.2.0, dotenv@^8.2.0:
version "8.2.0"
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.2.0.tgz#97e619259ada750eea3e4ea3e26bceea5424b16a"
integrity sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==
@@ -4963,6 +5012,13 @@ entities@^2.0.0:
resolved "https://registry.yarnpkg.com/entities/-/entities-2.1.0.tgz#992d3129cf7df6870b96c57858c249a120f8b8b5"
integrity sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==
+errno@^0.1.1:
+ version "0.1.8"
+ resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.8.tgz#8bb3e9c7d463be4976ff888f76b4809ebc2e811f"
+ integrity sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==
+ dependencies:
+ prr "~1.0.1"
+
errno@^0.1.3, errno@~0.1.7:
version "0.1.7"
resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.7.tgz#4684d71779ad39af177e3f007996f7c67c852618"
@@ -5546,6 +5602,11 @@ fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6:
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=
+fastparse@^1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/fastparse/-/fastparse-1.1.2.tgz#91728c5a5942eced8531283c79441ee4122c35a9"
+ integrity sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ==
+
fastq@^1.6.0:
version "1.9.0"
resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.9.0.tgz#e16a72f338eaca48e91b5c23593bcc2ef66b7947"
@@ -5846,6 +5907,11 @@ fsevents@~2.1.2:
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e"
integrity sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==
+fsevents@~2.3.1:
+ version "2.3.2"
+ resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
+ integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
+
function-bind@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
@@ -5870,6 +5936,13 @@ gauge@~2.7.3:
strip-ansi "^3.0.1"
wide-align "^1.1.0"
+generic-names@^1.0.2:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/generic-names/-/generic-names-1.0.3.tgz#2d786a121aee508876796939e8e3bff836c20917"
+ integrity sha1-LXhqEhruUIh2eWk56OO/+DbCCRc=
+ dependencies:
+ loader-utils "^0.2.16"
+
gensync@^1.0.0-beta.1:
version "1.0.0-beta.2"
resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0"
@@ -6379,6 +6452,13 @@ iconv-lite@0.4.24:
dependencies:
safer-buffer ">= 2.1.2 < 3"
+icss-utils@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-3.0.1.tgz#ee70d3ae8cac38c6be5ed91e851b27eed343ad0f"
+ integrity sha1-7nDTroysOMa+XtkehRsn7tNDrQ8=
+ dependencies:
+ postcss "^6.0.2"
+
icss-utils@^4.0.0, icss-utils@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-4.1.1.tgz#21170b53789ee27447c2f47dd683081403f9a467"
@@ -6420,6 +6500,11 @@ image-blob-reduce@^2.2.2:
dependencies:
pica "^6.1.1"
+image-size@~0.5.0:
+ version "0.5.5"
+ resolved "https://registry.yarnpkg.com/image-size/-/image-size-0.5.5.tgz#09dfd4ab9d20e29eb1c3e80b8990378df9e3cb9c"
+ integrity sha1-Cd/Uq50g4p6xw+gLiZA3jfnjy5w=
+
immer@7.0.9:
version "7.0.9"
resolved "https://registry.yarnpkg.com/immer/-/immer-7.0.9.tgz#28e7552c21d39dd76feccd2b800b7bc86ee4a62e"
@@ -6904,6 +6989,11 @@ is-typedarray@^1.0.0, is-typedarray@~1.0.0:
resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=
+is-what@^3.12.0:
+ version "3.14.1"
+ resolved "https://registry.yarnpkg.com/is-what/-/is-what-3.14.1.tgz#e1222f46ddda85dead0fd1c9df131760e77755c1"
+ integrity sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==
+
is-windows@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d"
@@ -7524,6 +7614,11 @@ json3@^3.3.2:
resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.3.tgz#7fc10e375fc5ae42c4705a5cc0aa6f62be305b81"
integrity sha512-c7/8mbUsKigAbLkD5B010BK4D9LZm7A1pNItkEwiUZRpIN66exu/e7YQWysGun+TRKaJp8MhemM+VkfWv42aCA==
+json5@^0.5.0:
+ version "0.5.1"
+ resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821"
+ integrity sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=
+
json5@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe"
@@ -7702,6 +7797,22 @@ last-call-webpack-plugin@^3.0.0:
lodash "^4.17.5"
webpack-sources "^1.1.0"
+less@^3.11.1:
+ version "3.13.1"
+ resolved "https://registry.yarnpkg.com/less/-/less-3.13.1.tgz#0ebc91d2a0e9c0c6735b83d496b0ab0583077909"
+ integrity sha512-SwA1aQXGUvp+P5XdZslUOhhLnClSLIjWvJhmd+Vgib5BFIr9lMNlQwmwUNOjXThF/A0x+MCYYPeWEfeWiLRnTw==
+ dependencies:
+ copy-anything "^2.0.1"
+ tslib "^1.10.0"
+ optionalDependencies:
+ errno "^0.1.1"
+ graceful-fs "^4.1.2"
+ image-size "~0.5.0"
+ make-dir "^2.1.0"
+ mime "^1.4.1"
+ native-request "^1.0.5"
+ source-map "~0.6.0"
+
leven@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2"
@@ -7769,6 +7880,16 @@ loader-utils@2.0.0, loader-utils@^2.0.0:
emojis-list "^3.0.0"
json5 "^2.1.2"
+loader-utils@^0.2.16:
+ version "0.2.17"
+ resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-0.2.17.tgz#f86e6374d43205a6e6c60e9196f17c0299bfb348"
+ integrity sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g=
+ dependencies:
+ big.js "^3.1.3"
+ emojis-list "^2.0.0"
+ json5 "^0.5.0"
+ object-assign "^4.0.1"
+
loader-utils@^1.1.0, loader-utils@^1.2.3, loader-utils@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613"
@@ -7806,6 +7927,11 @@ lodash._reinterpolate@^3.0.0:
resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d"
integrity sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=
+lodash.camelcase@^4.3.0:
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6"
+ integrity sha1-soqmKIorn8ZRA1x3EfZathkDMaY=
+
lodash.memoize@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
@@ -7841,6 +7967,11 @@ lodash.uniq@^4.5.0:
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52"
integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==
+lodash@^4.17.4:
+ version "4.17.21"
+ resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
+ integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
+
loglevel@^1.6.8:
version "1.7.0"
resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.7.0.tgz#728166855a740d59d38db01cf46f042caa041bb0"
@@ -7886,7 +8017,7 @@ magic-string@^0.25.0, magic-string@^0.25.7:
dependencies:
sourcemap-codec "^1.4.4"
-make-dir@^2.0.0:
+make-dir@^2.0.0, make-dir@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5"
integrity sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==
@@ -8045,7 +8176,7 @@ mime-types@^2.1.12, mime-types@^2.1.27, mime-types@~2.1.17, mime-types@~2.1.19,
dependencies:
mime-db "1.44.0"
-mime@1.6.0:
+mime@1.6.0, mime@^1.4.1:
version "1.6.0"
resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
@@ -8187,7 +8318,7 @@ mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@^0.5.5, mkdirp@~0.5.1:
dependencies:
minimist "^1.2.5"
-mkdirp@^1.0.3, mkdirp@^1.0.4:
+mkdirp@^1.0.3, mkdirp@^1.0.4, mkdirp@~1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
@@ -8277,6 +8408,11 @@ napi-build-utils@^1.0.1:
resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-1.0.2.tgz#b1fddc0b2c46e380a0b7a76f984dd47c41a13806"
integrity sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==
+native-request@^1.0.5:
+ version "1.0.8"
+ resolved "https://registry.yarnpkg.com/native-request/-/native-request-1.0.8.tgz#8f66bf606e0f7ea27c0e5995eb2f5d03e33ae6fb"
+ integrity sha512-vU2JojJVelUGp6jRcLwToPoWGxSx23z/0iX+I77J3Ht17rf2INGjrhOoQnjVo60nQd8wVsgzKkPfRXBiVdD2ag==
+
native-url@0.3.4:
version "0.3.4"
resolved "https://registry.yarnpkg.com/native-url/-/native-url-0.3.4.tgz#29c943172aed86c63cee62c8c04db7f5756661f8"
@@ -9305,6 +9441,13 @@ postcss-env-function@^2.0.2:
postcss "^7.0.2"
postcss-values-parser "^2.0.0"
+postcss-filter-plugins@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/postcss-filter-plugins/-/postcss-filter-plugins-3.0.1.tgz#9d226e946d56542ab7c26123053459a331df545d"
+ integrity sha512-tRKbW4wWBEkSSFuJtamV2wkiV9rj6Yy7P3Y13+zaynlPEEZt8EgYKn3y/RBpMeIhNmHXFlSdzofml65hD5OafA==
+ dependencies:
+ postcss "^6.0.14"
+
postcss-flexbugs-fixes@4.2.1:
version "4.2.1"
resolved "https://registry.yarnpkg.com/postcss-flexbugs-fixes/-/postcss-flexbugs-fixes-4.2.1.tgz#9218a65249f30897deab1033aced8578562a6690"
@@ -9340,6 +9483,26 @@ postcss-gap-properties@^2.0.0:
dependencies:
postcss "^7.0.2"
+postcss-icss-keyframes@^0.2.1:
+ version "0.2.1"
+ resolved "https://registry.yarnpkg.com/postcss-icss-keyframes/-/postcss-icss-keyframes-0.2.1.tgz#80c4455e0112b0f2f9c3c05ac7515062bb9ff295"
+ integrity sha1-gMRFXgESsPL5w8Bax1FQYruf8pU=
+ dependencies:
+ icss-utils "^3.0.1"
+ postcss "^6.0.2"
+ postcss-value-parser "^3.3.0"
+
+postcss-icss-selectors@^2.0.3:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/postcss-icss-selectors/-/postcss-icss-selectors-2.0.3.tgz#27fa1afcaab6c602c866cbb298f3218e9bc1c9b3"
+ integrity sha1-J/oa/Kq2xgLIZsuymPMhjpvBybM=
+ dependencies:
+ css-selector-tokenizer "^0.7.0"
+ generic-names "^1.0.2"
+ icss-utils "^3.0.1"
+ lodash "^4.17.4"
+ postcss "^6.0.2"
+
postcss-image-set-function@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/postcss-image-set-function/-/postcss-image-set-function-3.0.1.tgz#28920a2f29945bed4c3198d7df6496d410d3f288"
@@ -9365,7 +9528,7 @@ postcss-lab-function@^2.0.1:
postcss "^7.0.2"
postcss-values-parser "^2.0.0"
-postcss-load-config@^2.0.0:
+postcss-load-config@^2.0.0, postcss-load-config@^2.1.0:
version "2.1.2"
resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-2.1.2.tgz#c5ea504f2c4aef33c7359a34de3573772ad7502a"
integrity sha512-/rDeGV6vMUo3mwJZmeHfEDvwnTKKqQ0S7OHUi/kJvvtx3aWtyWG2/0ZWnzCt2keEclwN6Tf0DST2v9kITdOKYw==
@@ -9777,7 +9940,7 @@ postcss-unique-selectors@^4.0.1:
postcss "^7.0.0"
uniqs "^2.0.0"
-postcss-value-parser@^3.0.0:
+postcss-value-parser@^3.0.0, postcss-value-parser@^3.3.0:
version "3.3.1"
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281"
integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==
@@ -9824,6 +9987,15 @@ postcss@8.1.7:
nanoid "^3.1.16"
source-map "^0.6.1"
+postcss@^6.0.14, postcss@^6.0.2:
+ version "6.0.23"
+ resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.23.tgz#61c82cc328ac60e677645f979054eb98bc0e3324"
+ integrity sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==
+ dependencies:
+ chalk "^2.4.1"
+ source-map "^0.6.1"
+ supports-color "^5.4.0"
+
postcss@^7, postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.17, postcss@^7.0.2, postcss@^7.0.26, postcss@^7.0.27, postcss@^7.0.32, postcss@^7.0.5, postcss@^7.0.6:
version "7.0.35"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.35.tgz#d2be00b998f7f211d8a276974079f2e92b970e24"
@@ -10559,6 +10731,11 @@ requires-port@^1.0.0:
resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=
+reserved-words@^0.1.2:
+ version "0.1.2"
+ resolved "https://registry.yarnpkg.com/reserved-words/-/reserved-words-0.1.2.tgz#00a0940f98cd501aeaaac316411d9adc52b31ab1"
+ integrity sha1-AKCUD5jNUBrqqsMWQR2a3FKzGrE=
+
resolve-cwd@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a"
@@ -10766,7 +10943,7 @@ safe-regex@^1.1.0:
dependencies:
ret "~0.1.10"
-"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0:
+"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@^2.1.2, safer-buffer@~2.1.0:
version "2.1.2"
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
@@ -10813,6 +10990,13 @@ sass-loader@8.0.2:
schema-utils "^2.6.1"
semver "^6.3.0"
+sass@^1.26.5:
+ version "1.32.10"
+ resolved "https://registry.yarnpkg.com/sass/-/sass-1.32.10.tgz#d40da4e20031b450359ee1c7e69bc8cc89569241"
+ integrity sha512-Nx0pcWoonAkn7CRp0aE/hket1UP97GiR1IFw3kcjV3pnenhWgZEWUf0ZcfPOV2fK52fnOcK3JdC/YYZ9E47DTQ==
+ dependencies:
+ chokidar ">=3.0.0 <4.0.0"
+
sax@~1.2.4:
version "1.2.4"
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
@@ -11653,7 +11837,21 @@ stylis@3.5.4:
resolved "https://registry.yarnpkg.com/stylis/-/stylis-3.5.4.tgz#f665f25f5e299cf3d64654ab949a57c768b73fbe"
integrity sha512-8/3pSmthWM7lsPBKv7NXkzn2Uc9W7NotcwGNpJaa3k7WMM1XDCA4MgT5k/8BIexd5ydZdboXtU90XH9Ec4Bv/Q==
-supports-color@^5.3.0:
+stylus@^0.54.7:
+ version "0.54.8"
+ resolved "https://registry.yarnpkg.com/stylus/-/stylus-0.54.8.tgz#3da3e65966bc567a7b044bfe0eece653e099d147"
+ integrity sha512-vr54Or4BZ7pJafo2mpf0ZcwA74rpuYCZbxrHBsH8kbcXOwSfvBFwsRfpGO5OD5fhG5HDCFW737PKaawI7OqEAg==
+ dependencies:
+ css-parse "~2.0.0"
+ debug "~3.1.0"
+ glob "^7.1.6"
+ mkdirp "~1.0.4"
+ safer-buffer "^2.1.2"
+ sax "~1.2.4"
+ semver "^6.3.0"
+ source-map "^0.7.3"
+
+supports-color@^5.3.0, supports-color@^5.4.0:
version "5.5.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
@@ -12120,6 +12318,25 @@ typedarray@^0.0.6:
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
+typescript-plugin-css-modules@^3.2.0:
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/typescript-plugin-css-modules/-/typescript-plugin-css-modules-3.2.0.tgz#44241064395d565f242689b96eb50bf413e68ddf"
+ integrity sha512-oMe5IDKuPvLBOeaYupAqqq8UMTu7Jo5e0InqkQSSKXuZCcRm0+LQfUVMyM62IFpgzxHLuN32a7YdBxAaSXZrRQ==
+ dependencies:
+ dotenv "^8.2.0"
+ icss-utils "^4.1.1"
+ less "^3.11.1"
+ lodash.camelcase "^4.3.0"
+ postcss "^7.0.27"
+ postcss-filter-plugins "^3.0.1"
+ postcss-icss-keyframes "^0.2.1"
+ postcss-icss-selectors "^2.0.3"
+ postcss-load-config "^2.1.0"
+ reserved-words "^0.1.2"
+ sass "^1.26.5"
+ stylus "^0.54.7"
+ tsconfig-paths "^3.9.0"
+
typescript@^4.1.3:
version "4.1.3"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.1.3.tgz#519d582bd94cba0cf8934c7d8e8467e473f53bb7"