crlf to lf
Browse files- src/components/button/index.tsx +38 -38
- src/components/button/style.module.scss +105 -105
- src/components/container/index.tsx +9 -9
- src/components/container/style.module.scss +7 -7
- src/components/form/index.tsx +11 -11
- src/components/form/style.module.scss +14 -14
- src/components/input/index.tsx +85 -85
- src/components/input/style.module.scss +44 -44
- src/components/layout/index.tsx +28 -28
- src/components/preview/style.module.scss +102 -102
- src/components/settings/index.tsx +65 -65
- src/components/slider/style.module.scss +145 -145
- src/components/spinner/index.tsx +11 -11
- src/components/spinner/style.module.scss +45 -45
- src/components/topic/style.module.scss +50 -50
- src/components/topics/style.module.scss +76 -76
- src/index.ts +3 -3
- src/reset.scss +114 -114
- src/style.module.scss +12 -12
- src/styles.scss +138 -138
- src/utils/dates.ts +50 -50
- src/utils/model.ts +106 -106
- src/utils/route.ts +8 -8
- src/utils/settings.ts +29 -29
- src/utils/smileys.ts +78 -78
- src/utils/topics.ts +134 -134
- src/utils/uuids.ts +4 -4
src/components/button/index.tsx
CHANGED
@@ -1,39 +1,39 @@
|
|
1 |
-
import {ComponentChildren, JSX} from 'preact';
|
2 |
-
import cn from 'classnames';
|
3 |
-
import style from "./style.module.scss";
|
4 |
-
|
5 |
-
export function Button(props: {
|
6 |
-
loading?: boolean;
|
7 |
-
onClick?: () => void;
|
8 |
-
className?: string;
|
9 |
-
secondary?: boolean;
|
10 |
-
disabled?: boolean;
|
11 |
-
title?: string;
|
12 |
-
children: ComponentChildren;
|
13 |
-
}) {
|
14 |
-
const disabled = props.disabled || props.loading;
|
15 |
-
const buttonClass = cn(style.btn, {[style.secondary]: props.secondary}, 'button', props.className, {[style.disabled]:disabled});
|
16 |
-
|
17 |
-
let spinner: JSX.Element = undefined;
|
18 |
-
|
19 |
-
if (props.loading) {
|
20 |
-
spinner = <span className={style.spinner}/>
|
21 |
-
}
|
22 |
-
|
23 |
-
return (
|
24 |
-
<button
|
25 |
-
type="button"
|
26 |
-
onClick={() => {
|
27 |
-
if (!disabled) {
|
28 |
-
props.onClick()
|
29 |
-
}
|
30 |
-
}}
|
31 |
-
className={buttonClass}
|
32 |
-
disabled={disabled}
|
33 |
-
title={props.title}
|
34 |
-
>
|
35 |
-
{spinner}
|
36 |
-
{props.children}
|
37 |
-
</button>
|
38 |
-
);
|
39 |
}
|
|
|
1 |
+
import {ComponentChildren, JSX} from 'preact';
|
2 |
+
import cn from 'classnames';
|
3 |
+
import style from "./style.module.scss";
|
4 |
+
|
5 |
+
export function Button(props: {
|
6 |
+
loading?: boolean;
|
7 |
+
onClick?: () => void;
|
8 |
+
className?: string;
|
9 |
+
secondary?: boolean;
|
10 |
+
disabled?: boolean;
|
11 |
+
title?: string;
|
12 |
+
children: ComponentChildren;
|
13 |
+
}) {
|
14 |
+
const disabled = props.disabled || props.loading;
|
15 |
+
const buttonClass = cn(style.btn, {[style.secondary]: props.secondary}, 'button', props.className, {[style.disabled]:disabled});
|
16 |
+
|
17 |
+
let spinner: JSX.Element = undefined;
|
18 |
+
|
19 |
+
if (props.loading) {
|
20 |
+
spinner = <span className={style.spinner}/>
|
21 |
+
}
|
22 |
+
|
23 |
+
return (
|
24 |
+
<button
|
25 |
+
type="button"
|
26 |
+
onClick={() => {
|
27 |
+
if (!disabled) {
|
28 |
+
props.onClick()
|
29 |
+
}
|
30 |
+
}}
|
31 |
+
className={buttonClass}
|
32 |
+
disabled={disabled}
|
33 |
+
title={props.title}
|
34 |
+
>
|
35 |
+
{spinner}
|
36 |
+
{props.children}
|
37 |
+
</button>
|
38 |
+
);
|
39 |
}
|
src/components/button/style.module.scss
CHANGED
@@ -1,105 +1,105 @@
|
|
1 |
-
.btn {
|
2 |
-
position: relative;
|
3 |
-
display: inline-flex;
|
4 |
-
justify-content: center;
|
5 |
-
align-items: center;
|
6 |
-
padding: 0 1.25rem;
|
7 |
-
margin: 0;
|
8 |
-
border: 0.0625rem solid var(--text-secondary);
|
9 |
-
height: 2.25rem;
|
10 |
-
line-height: 2.25rem;
|
11 |
-
font-size: 0.9375rem;
|
12 |
-
text-decoration: none;
|
13 |
-
color: var(--text-secondary);
|
14 |
-
border-radius: 1.125rem;
|
15 |
-
|
16 |
-
white-space: nowrap;
|
17 |
-
cursor: pointer;
|
18 |
-
//overflow: hidden;
|
19 |
-
|
20 |
-
&:not(.disabled):hover {
|
21 |
-
border-color: var(--text-hover-secondary);
|
22 |
-
color: var(--text-hover-secondary);
|
23 |
-
}
|
24 |
-
//
|
25 |
-
//&:active {
|
26 |
-
// //border-color: var(--text-secondary);
|
27 |
-
// background: var(--text-secondary);
|
28 |
-
//}
|
29 |
-
|
30 |
-
&.secondary {
|
31 |
-
color: #fff;
|
32 |
-
background-color: var(--text-secondary);
|
33 |
-
|
34 |
-
&:not(.disabled):hover {
|
35 |
-
border-color: var(--text-hover-secondary);
|
36 |
-
background: var(--text-hover-secondary);
|
37 |
-
color: #fff;
|
38 |
-
}
|
39 |
-
}
|
40 |
-
}
|
41 |
-
|
42 |
-
.spinner {
|
43 |
-
font-size: 10px;
|
44 |
-
//margin: 50px auto;
|
45 |
-
margin-right: 1em;
|
46 |
-
width: 1.5em;
|
47 |
-
height: 1.5em;
|
48 |
-
border-radius: 50%;
|
49 |
-
background: #ffffff;
|
50 |
-
background: linear-gradient(to right, #ffffff 10%, rgba(255, 255, 255, 0) 42%);
|
51 |
-
position: relative;
|
52 |
-
animation: load3 1.4s infinite linear;
|
53 |
-
transform: translateZ(0);
|
54 |
-
|
55 |
-
&:before {
|
56 |
-
width: 50%;
|
57 |
-
height: 50%;
|
58 |
-
background: #ffffff;
|
59 |
-
border-radius: 100% 0 0 0;
|
60 |
-
position: absolute;
|
61 |
-
top: 0;
|
62 |
-
left: 0;
|
63 |
-
content: '';
|
64 |
-
}
|
65 |
-
|
66 |
-
&:after {
|
67 |
-
background: var(--text-secondary);
|
68 |
-
width: 75%;
|
69 |
-
height: 75%;
|
70 |
-
border-radius: 50%;
|
71 |
-
content: '';
|
72 |
-
margin: auto;
|
73 |
-
position: absolute;
|
74 |
-
top: 0;
|
75 |
-
left: 0;
|
76 |
-
bottom: 0;
|
77 |
-
right: 0;
|
78 |
-
}
|
79 |
-
}
|
80 |
-
|
81 |
-
@keyframes load3 {
|
82 |
-
0% {
|
83 |
-
transform: rotate(0deg);
|
84 |
-
}
|
85 |
-
100% {
|
86 |
-
transform: rotate(360deg);
|
87 |
-
}
|
88 |
-
}
|
89 |
-
|
90 |
-
.disabled {
|
91 |
-
cursor: not-allowed;
|
92 |
-
|
93 |
-
&:after {
|
94 |
-
content: "";
|
95 |
-
position: absolute;
|
96 |
-
width: 100%;
|
97 |
-
height: 100%;
|
98 |
-
background: #4f4f4f;
|
99 |
-
border: 0.0625rem solid #4f4f4f;
|
100 |
-
border-radius: 1.125rem;
|
101 |
-
opacity: 0.5;
|
102 |
-
box-sizing: content-box;
|
103 |
-
}
|
104 |
-
}
|
105 |
-
|
|
|
1 |
+
.btn {
|
2 |
+
position: relative;
|
3 |
+
display: inline-flex;
|
4 |
+
justify-content: center;
|
5 |
+
align-items: center;
|
6 |
+
padding: 0 1.25rem;
|
7 |
+
margin: 0;
|
8 |
+
border: 0.0625rem solid var(--text-secondary);
|
9 |
+
height: 2.25rem;
|
10 |
+
line-height: 2.25rem;
|
11 |
+
font-size: 0.9375rem;
|
12 |
+
text-decoration: none;
|
13 |
+
color: var(--text-secondary);
|
14 |
+
border-radius: 1.125rem;
|
15 |
+
|
16 |
+
white-space: nowrap;
|
17 |
+
cursor: pointer;
|
18 |
+
//overflow: hidden;
|
19 |
+
|
20 |
+
&:not(.disabled):hover {
|
21 |
+
border-color: var(--text-hover-secondary);
|
22 |
+
color: var(--text-hover-secondary);
|
23 |
+
}
|
24 |
+
//
|
25 |
+
//&:active {
|
26 |
+
// //border-color: var(--text-secondary);
|
27 |
+
// background: var(--text-secondary);
|
28 |
+
//}
|
29 |
+
|
30 |
+
&.secondary {
|
31 |
+
color: #fff;
|
32 |
+
background-color: var(--text-secondary);
|
33 |
+
|
34 |
+
&:not(.disabled):hover {
|
35 |
+
border-color: var(--text-hover-secondary);
|
36 |
+
background: var(--text-hover-secondary);
|
37 |
+
color: #fff;
|
38 |
+
}
|
39 |
+
}
|
40 |
+
}
|
41 |
+
|
42 |
+
.spinner {
|
43 |
+
font-size: 10px;
|
44 |
+
//margin: 50px auto;
|
45 |
+
margin-right: 1em;
|
46 |
+
width: 1.5em;
|
47 |
+
height: 1.5em;
|
48 |
+
border-radius: 50%;
|
49 |
+
background: #ffffff;
|
50 |
+
background: linear-gradient(to right, #ffffff 10%, rgba(255, 255, 255, 0) 42%);
|
51 |
+
position: relative;
|
52 |
+
animation: load3 1.4s infinite linear;
|
53 |
+
transform: translateZ(0);
|
54 |
+
|
55 |
+
&:before {
|
56 |
+
width: 50%;
|
57 |
+
height: 50%;
|
58 |
+
background: #ffffff;
|
59 |
+
border-radius: 100% 0 0 0;
|
60 |
+
position: absolute;
|
61 |
+
top: 0;
|
62 |
+
left: 0;
|
63 |
+
content: '';
|
64 |
+
}
|
65 |
+
|
66 |
+
&:after {
|
67 |
+
background: var(--text-secondary);
|
68 |
+
width: 75%;
|
69 |
+
height: 75%;
|
70 |
+
border-radius: 50%;
|
71 |
+
content: '';
|
72 |
+
margin: auto;
|
73 |
+
position: absolute;
|
74 |
+
top: 0;
|
75 |
+
left: 0;
|
76 |
+
bottom: 0;
|
77 |
+
right: 0;
|
78 |
+
}
|
79 |
+
}
|
80 |
+
|
81 |
+
@keyframes load3 {
|
82 |
+
0% {
|
83 |
+
transform: rotate(0deg);
|
84 |
+
}
|
85 |
+
100% {
|
86 |
+
transform: rotate(360deg);
|
87 |
+
}
|
88 |
+
}
|
89 |
+
|
90 |
+
.disabled {
|
91 |
+
cursor: not-allowed;
|
92 |
+
|
93 |
+
&:after {
|
94 |
+
content: "";
|
95 |
+
position: absolute;
|
96 |
+
width: 100%;
|
97 |
+
height: 100%;
|
98 |
+
background: #4f4f4f;
|
99 |
+
border: 0.0625rem solid #4f4f4f;
|
100 |
+
border-radius: 1.125rem;
|
101 |
+
opacity: 0.5;
|
102 |
+
box-sizing: content-box;
|
103 |
+
}
|
104 |
+
}
|
105 |
+
|
src/components/container/index.tsx
CHANGED
@@ -1,10 +1,10 @@
|
|
1 |
-
import type {ComponentChildren} from "preact";
|
2 |
-
import style from "./style.module.scss"
|
3 |
-
|
4 |
-
export function Container(props: { children: ComponentChildren }) {
|
5 |
-
return (
|
6 |
-
<div class={style.container}>
|
7 |
-
{props.children}
|
8 |
-
</div>
|
9 |
-
);
|
10 |
}
|
|
|
1 |
+
import type {ComponentChildren} from "preact";
|
2 |
+
import style from "./style.module.scss"
|
3 |
+
|
4 |
+
export function Container(props: { children: ComponentChildren }) {
|
5 |
+
return (
|
6 |
+
<div class={style.container}>
|
7 |
+
{props.children}
|
8 |
+
</div>
|
9 |
+
);
|
10 |
}
|
src/components/container/style.module.scss
CHANGED
@@ -1,8 +1,8 @@
|
|
1 |
-
.container {
|
2 |
-
margin: 0 auto;
|
3 |
-
//min-width: 20rem;
|
4 |
-
//max-width: 73.5rem;
|
5 |
-
padding: 0 1rem;
|
6 |
-
|
7 |
-
max-width: 800px;
|
8 |
}
|
|
|
1 |
+
.container {
|
2 |
+
margin: 0 auto;
|
3 |
+
//min-width: 20rem;
|
4 |
+
//max-width: 73.5rem;
|
5 |
+
padding: 0 1rem;
|
6 |
+
|
7 |
+
max-width: 800px;
|
8 |
}
|
src/components/form/index.tsx
CHANGED
@@ -1,12 +1,12 @@
|
|
1 |
-
import type {ComponentChildren} from "preact";
|
2 |
-
import style from "./style.module.scss"
|
3 |
-
|
4 |
-
export function FormGroup(props: {
|
5 |
-
children: ComponentChildren,
|
6 |
-
}) {
|
7 |
-
return (
|
8 |
-
<div className={style.formGroup}>
|
9 |
-
{props.children}
|
10 |
-
</div>
|
11 |
-
)
|
12 |
}
|
|
|
1 |
+
import type {ComponentChildren} from "preact";
|
2 |
+
import style from "./style.module.scss"
|
3 |
+
|
4 |
+
export function FormGroup(props: {
|
5 |
+
children: ComponentChildren,
|
6 |
+
}) {
|
7 |
+
return (
|
8 |
+
<div className={style.formGroup}>
|
9 |
+
{props.children}
|
10 |
+
</div>
|
11 |
+
)
|
12 |
}
|
src/components/form/style.module.scss
CHANGED
@@ -1,15 +1,15 @@
|
|
1 |
-
.formGroup {
|
2 |
-
display: flex;
|
3 |
-
flex-direction: column;
|
4 |
-
margin-bottom: 1rem;
|
5 |
-
|
6 |
-
justify-content: space-between;
|
7 |
-
& > * {
|
8 |
-
flex-grow: 0;
|
9 |
-
}
|
10 |
-
|
11 |
-
label {
|
12 |
-
font-size: 1rem;
|
13 |
-
margin-bottom: .5rem;
|
14 |
-
}
|
15 |
}
|
|
|
1 |
+
.formGroup {
|
2 |
+
display: flex;
|
3 |
+
flex-direction: column;
|
4 |
+
margin-bottom: 1rem;
|
5 |
+
|
6 |
+
justify-content: space-between;
|
7 |
+
& > * {
|
8 |
+
flex-grow: 0;
|
9 |
+
}
|
10 |
+
|
11 |
+
label {
|
12 |
+
font-size: 1rem;
|
13 |
+
margin-bottom: .5rem;
|
14 |
+
}
|
15 |
}
|
src/components/input/index.tsx
CHANGED
@@ -1,86 +1,86 @@
|
|
1 |
-
import {FunctionalComponent} from "preact";
|
2 |
-
import {useState} from "preact/hooks";
|
3 |
-
import cn from "classnames";
|
4 |
-
import {JSX} from "preact";
|
5 |
-
// import GenericEventHandler = JSXInternal.GenericEventHandler;
|
6 |
-
import style from "./style.module.scss";
|
7 |
-
import {Settings} from "preact-feather";
|
8 |
-
import {FeatherProps} from "preact-feather/src/types";
|
9 |
-
|
10 |
-
interface InputProps<T> {
|
11 |
-
type: "text" | "number" | "email" | "password";
|
12 |
-
icon: FunctionalComponent<FeatherProps>;
|
13 |
-
value: T;
|
14 |
-
placeholder?: string;
|
15 |
-
onChange: (value: T) => void;
|
16 |
-
className?: string;
|
17 |
-
disabled?: boolean;
|
18 |
-
id?: string;
|
19 |
-
name?: string;
|
20 |
-
}
|
21 |
-
|
22 |
-
export const Input: FunctionalComponent<InputProps<string | number>> = ({
|
23 |
-
type,
|
24 |
-
icon,
|
25 |
-
value,
|
26 |
-
placeholder,
|
27 |
-
onChange,
|
28 |
-
className,
|
29 |
-
disabled,
|
30 |
-
id,
|
31 |
-
name,
|
32 |
-
}) => {
|
33 |
-
const [focused, setFocused] = useState(false);
|
34 |
-
|
35 |
-
const inputClass = cn(style.input, "generic-input", className, {
|
36 |
-
focused,
|
37 |
-
disabled,
|
38 |
-
});
|
39 |
-
|
40 |
-
const handleInputChange: JSX.GenericEventHandler<HTMLInputElement> = (event) => {
|
41 |
-
console.log("handleInputChange")
|
42 |
-
const target = event.target as HTMLInputElement;
|
43 |
-
onChange(type === "number" ? (parseFloat(target.value) || 0) : target.value);
|
44 |
-
};
|
45 |
-
|
46 |
-
const Icon = icon;
|
47 |
-
|
48 |
-
return (
|
49 |
-
<div className={style.wrapper}>
|
50 |
-
<Icon className={style.icon} size={18}/>
|
51 |
-
<input
|
52 |
-
// required=""
|
53 |
-
// minLength="3"
|
54 |
-
// maxLength="15"
|
55 |
-
title="Le pseudo doit avoir une longueur comprise entre 3 et 15 caractères."
|
56 |
-
// tabIndex="10"
|
57 |
-
type={type}
|
58 |
-
id={id}
|
59 |
-
name={name}
|
60 |
-
value={value}
|
61 |
-
placeholder={placeholder}
|
62 |
-
// onChange={handleInputChange}
|
63 |
-
onInput={handleInputChange}
|
64 |
-
className={inputClass}
|
65 |
-
disabled={disabled}
|
66 |
-
onFocus={() => setFocused(true)}
|
67 |
-
onBlur={() => setFocused(false)}
|
68 |
-
/>
|
69 |
-
</div>
|
70 |
-
)
|
71 |
-
|
72 |
-
return (
|
73 |
-
<input
|
74 |
-
type={type}
|
75 |
-
id={id}
|
76 |
-
name={name}
|
77 |
-
value={value}
|
78 |
-
placeholder={placeholder}
|
79 |
-
onChange={handleInputChange}
|
80 |
-
className={inputClass}
|
81 |
-
disabled={disabled}
|
82 |
-
onFocus={() => setFocused(true)}
|
83 |
-
onBlur={() => setFocused(false)}
|
84 |
-
/>
|
85 |
-
);
|
86 |
};
|
|
|
1 |
+
import {FunctionalComponent} from "preact";
|
2 |
+
import {useState} from "preact/hooks";
|
3 |
+
import cn from "classnames";
|
4 |
+
import {JSX} from "preact";
|
5 |
+
// import GenericEventHandler = JSXInternal.GenericEventHandler;
|
6 |
+
import style from "./style.module.scss";
|
7 |
+
import {Settings} from "preact-feather";
|
8 |
+
import {FeatherProps} from "preact-feather/src/types";
|
9 |
+
|
10 |
+
interface InputProps<T> {
|
11 |
+
type: "text" | "number" | "email" | "password";
|
12 |
+
icon: FunctionalComponent<FeatherProps>;
|
13 |
+
value: T;
|
14 |
+
placeholder?: string;
|
15 |
+
onChange: (value: T) => void;
|
16 |
+
className?: string;
|
17 |
+
disabled?: boolean;
|
18 |
+
id?: string;
|
19 |
+
name?: string;
|
20 |
+
}
|
21 |
+
|
22 |
+
export const Input: FunctionalComponent<InputProps<string | number>> = ({
|
23 |
+
type,
|
24 |
+
icon,
|
25 |
+
value,
|
26 |
+
placeholder,
|
27 |
+
onChange,
|
28 |
+
className,
|
29 |
+
disabled,
|
30 |
+
id,
|
31 |
+
name,
|
32 |
+
}) => {
|
33 |
+
const [focused, setFocused] = useState(false);
|
34 |
+
|
35 |
+
const inputClass = cn(style.input, "generic-input", className, {
|
36 |
+
focused,
|
37 |
+
disabled,
|
38 |
+
});
|
39 |
+
|
40 |
+
const handleInputChange: JSX.GenericEventHandler<HTMLInputElement> = (event) => {
|
41 |
+
console.log("handleInputChange")
|
42 |
+
const target = event.target as HTMLInputElement;
|
43 |
+
onChange(type === "number" ? (parseFloat(target.value) || 0) : target.value);
|
44 |
+
};
|
45 |
+
|
46 |
+
const Icon = icon;
|
47 |
+
|
48 |
+
return (
|
49 |
+
<div className={style.wrapper}>
|
50 |
+
<Icon className={style.icon} size={18}/>
|
51 |
+
<input
|
52 |
+
// required=""
|
53 |
+
// minLength="3"
|
54 |
+
// maxLength="15"
|
55 |
+
title="Le pseudo doit avoir une longueur comprise entre 3 et 15 caractères."
|
56 |
+
// tabIndex="10"
|
57 |
+
type={type}
|
58 |
+
id={id}
|
59 |
+
name={name}
|
60 |
+
value={value}
|
61 |
+
placeholder={placeholder}
|
62 |
+
// onChange={handleInputChange}
|
63 |
+
onInput={handleInputChange}
|
64 |
+
className={inputClass}
|
65 |
+
disabled={disabled}
|
66 |
+
onFocus={() => setFocused(true)}
|
67 |
+
onBlur={() => setFocused(false)}
|
68 |
+
/>
|
69 |
+
</div>
|
70 |
+
)
|
71 |
+
|
72 |
+
return (
|
73 |
+
<input
|
74 |
+
type={type}
|
75 |
+
id={id}
|
76 |
+
name={name}
|
77 |
+
value={value}
|
78 |
+
placeholder={placeholder}
|
79 |
+
onChange={handleInputChange}
|
80 |
+
className={inputClass}
|
81 |
+
disabled={disabled}
|
82 |
+
onFocus={() => setFocused(true)}
|
83 |
+
onBlur={() => setFocused(false)}
|
84 |
+
/>
|
85 |
+
);
|
86 |
};
|
src/components/input/style.module.scss
CHANGED
@@ -1,45 +1,45 @@
|
|
1 |
-
.wrapper {
|
2 |
-
position: relative;
|
3 |
-
margin-bottom: 0.9375rem
|
4 |
-
}
|
5 |
-
|
6 |
-
.icon {
|
7 |
-
position: absolute;
|
8 |
-
top: .625rem;
|
9 |
-
left: .625rem;
|
10 |
-
font-size: 1.125rem;
|
11 |
-
line-height: 1em;
|
12 |
-
}
|
13 |
-
|
14 |
-
.input {
|
15 |
-
|
16 |
-
display: block;
|
17 |
-
width: 100%;
|
18 |
-
//padding: 0.4375rem 0.75rem;
|
19 |
-
//font-size: 0.9375rem;
|
20 |
-
font-weight: 400;
|
21 |
-
line-height: 1.5;
|
22 |
-
color: var(--input-text-color);
|
23 |
-
background-color: var(--input-bg-color);
|
24 |
-
background-clip: padding-box;
|
25 |
-
border: 0.0625rem solid var(--input-border-color);
|
26 |
-
-webkit-appearance: none;
|
27 |
-
appearance: none;
|
28 |
-
border-radius: 0.75rem;
|
29 |
-
box-shadow: inset 0 0.0625rem 0.125rem rgba(0, 0, 0, .075);
|
30 |
-
transition: border-color .15s ease-in-out, box-shadow .15s ease-in-out;
|
31 |
-
|
32 |
-
padding-top: 0;
|
33 |
-
padding-right: .3125rem;
|
34 |
-
//padding-left: 0.4375rem;
|
35 |
-
padding-left: 2.5rem;
|
36 |
-
padding-bottom: 0;
|
37 |
-
font-weight: 700;
|
38 |
-
height: 2.5rem;
|
39 |
-
font-size: 1rem;
|
40 |
-
|
41 |
-
&::placeholder {
|
42 |
-
color: var(--input-placeholder-color);
|
43 |
-
font-weight: 400;
|
44 |
-
}
|
45 |
}
|
|
|
1 |
+
.wrapper {
|
2 |
+
position: relative;
|
3 |
+
margin-bottom: 0.9375rem
|
4 |
+
}
|
5 |
+
|
6 |
+
.icon {
|
7 |
+
position: absolute;
|
8 |
+
top: .625rem;
|
9 |
+
left: .625rem;
|
10 |
+
font-size: 1.125rem;
|
11 |
+
line-height: 1em;
|
12 |
+
}
|
13 |
+
|
14 |
+
.input {
|
15 |
+
|
16 |
+
display: block;
|
17 |
+
width: 100%;
|
18 |
+
//padding: 0.4375rem 0.75rem;
|
19 |
+
//font-size: 0.9375rem;
|
20 |
+
font-weight: 400;
|
21 |
+
line-height: 1.5;
|
22 |
+
color: var(--input-text-color);
|
23 |
+
background-color: var(--input-bg-color);
|
24 |
+
background-clip: padding-box;
|
25 |
+
border: 0.0625rem solid var(--input-border-color);
|
26 |
+
-webkit-appearance: none;
|
27 |
+
appearance: none;
|
28 |
+
border-radius: 0.75rem;
|
29 |
+
box-shadow: inset 0 0.0625rem 0.125rem rgba(0, 0, 0, .075);
|
30 |
+
transition: border-color .15s ease-in-out, box-shadow .15s ease-in-out;
|
31 |
+
|
32 |
+
padding-top: 0;
|
33 |
+
padding-right: .3125rem;
|
34 |
+
//padding-left: 0.4375rem;
|
35 |
+
padding-left: 2.5rem;
|
36 |
+
padding-bottom: 0;
|
37 |
+
font-weight: 700;
|
38 |
+
height: 2.5rem;
|
39 |
+
font-size: 1rem;
|
40 |
+
|
41 |
+
&::placeholder {
|
42 |
+
color: var(--input-placeholder-color);
|
43 |
+
font-weight: 400;
|
44 |
+
}
|
45 |
}
|
src/components/layout/index.tsx
CHANGED
@@ -1,29 +1,29 @@
|
|
1 |
-
import type {ComponentChildren} from "preact";
|
2 |
-
import style from "./style.module.scss"
|
3 |
-
import {Settings} from "preact-feather"
|
4 |
-
import {Route, routes} from "../../utils/route";
|
5 |
-
|
6 |
-
export function Layout(props: {
|
7 |
-
breadcrumbs: string,
|
8 |
-
title: string,
|
9 |
-
setRoute: (route: Route) => void,
|
10 |
-
children: ComponentChildren
|
11 |
-
}) {
|
12 |
-
return(
|
13 |
-
<div>
|
14 |
-
<nav className={style.breadcrumbs}>
|
15 |
-
{props.breadcrumbs}
|
16 |
-
<div className={style.actions}>
|
17 |
-
<a href="#" title="Paramètres" onClick={e => {
|
18 |
-
e.preventDefault();
|
19 |
-
props.setRoute(routes.settings);
|
20 |
-
}}>
|
21 |
-
<Settings size={18}/>
|
22 |
-
</a>
|
23 |
-
</div>
|
24 |
-
</nav>
|
25 |
-
<h2>{props.title}</h2>
|
26 |
-
{props.children}
|
27 |
-
</div>
|
28 |
-
)
|
29 |
}
|
|
|
1 |
+
import type {ComponentChildren} from "preact";
|
2 |
+
import style from "./style.module.scss"
|
3 |
+
import {Settings} from "preact-feather"
|
4 |
+
import {Route, routes} from "../../utils/route";
|
5 |
+
|
6 |
+
export function Layout(props: {
|
7 |
+
breadcrumbs: string,
|
8 |
+
title: string,
|
9 |
+
setRoute: (route: Route) => void,
|
10 |
+
children: ComponentChildren
|
11 |
+
}) {
|
12 |
+
return(
|
13 |
+
<div>
|
14 |
+
<nav className={style.breadcrumbs}>
|
15 |
+
{props.breadcrumbs}
|
16 |
+
<div className={style.actions}>
|
17 |
+
<a href="#" title="Paramètres" onClick={e => {
|
18 |
+
e.preventDefault();
|
19 |
+
props.setRoute(routes.settings);
|
20 |
+
}}>
|
21 |
+
<Settings size={18}/>
|
22 |
+
</a>
|
23 |
+
</div>
|
24 |
+
</nav>
|
25 |
+
<h2>{props.title}</h2>
|
26 |
+
{props.children}
|
27 |
+
</div>
|
28 |
+
)
|
29 |
}
|
src/components/preview/style.module.scss
CHANGED
@@ -1,103 +1,103 @@
|
|
1 |
-
.wrapper {
|
2 |
-
//white-space: pre-line;
|
3 |
-
|
4 |
-
:global {
|
5 |
-
.bloc-spoil-jv {
|
6 |
-
//margin-bottom: 0.9375rem;
|
7 |
-
margin-bottom: 0;
|
8 |
-
display: inline;
|
9 |
-
|
10 |
-
.open-spoil {
|
11 |
-
position: absolute;
|
12 |
-
left: -999rem;
|
13 |
-
}
|
14 |
-
|
15 |
-
.barre-head {
|
16 |
-
//height: 1.5rem;
|
17 |
-
//line-height: 1.5rem;
|
18 |
-
//cursor: pointer;
|
19 |
-
//position: relative;
|
20 |
-
//display: block;
|
21 |
-
|
22 |
-
height: auto;
|
23 |
-
line-height: inherit;
|
24 |
-
text-align: center;
|
25 |
-
cursor: pointer;
|
26 |
-
position: relative;
|
27 |
-
display: inline;
|
28 |
-
|
29 |
-
.txt-spoil {
|
30 |
-
//position: absolute;
|
31 |
-
//top: 0;
|
32 |
-
//left: 0;
|
33 |
-
//width: 4.6875rem;
|
34 |
-
//font-size: .9285em;
|
35 |
-
//text-align: center;
|
36 |
-
//color: #fff;
|
37 |
-
//text-transform: uppercase;
|
38 |
-
//background: #fd374e;
|
39 |
-
//font-weight: 700;
|
40 |
-
//display: block;
|
41 |
-
//border-radius: 0.25rem;
|
42 |
-
|
43 |
-
position: static;
|
44 |
-
display: inline;
|
45 |
-
padding: 0.125rem 1.25rem;
|
46 |
-
line-height: inherit;
|
47 |
-
top: 0;
|
48 |
-
left: 0;
|
49 |
-
width: 4.6875rem;
|
50 |
-
font-size: .9285em;
|
51 |
-
text-align: center;
|
52 |
-
color: #fff;
|
53 |
-
text-transform: uppercase;
|
54 |
-
background: #fd374e;
|
55 |
-
font-weight: 700;
|
56 |
-
//display: block;
|
57 |
-
border-radius: 0.25rem;
|
58 |
-
}
|
59 |
-
}
|
60 |
-
|
61 |
-
.contenu-spoil {
|
62 |
-
background: #f4d6da;
|
63 |
-
color: #212121;
|
64 |
-
padding: 0.625rem;
|
65 |
-
display: none;
|
66 |
-
//text-align: justify;
|
67 |
-
text-align: left;
|
68 |
-
overflow: hidden;
|
69 |
-
}
|
70 |
-
|
71 |
-
.open-spoil:checked ~ .contenu-spoil {
|
72 |
-
display: inline;
|
73 |
-
}
|
74 |
-
}
|
75 |
-
}
|
76 |
-
|
77 |
-
blockquote {
|
78 |
-
text-align: left;
|
79 |
-
//margin-bottom: 0.9375rem;
|
80 |
-
border-left: 0.3125rem solid rgba(0, 0, 0, .2);
|
81 |
-
color: var(--text-blockquote-color);
|
82 |
-
//margin-left: 0.9375rem;
|
83 |
-
padding: 0.625rem;
|
84 |
-
background: none;
|
85 |
-
font-style: normal;
|
86 |
-
font-size: inherit;
|
87 |
-
margin: 0 0 0.9375rem 1rem;
|
88 |
-
}
|
89 |
-
|
90 |
-
a {
|
91 |
-
color: var(--link-color);
|
92 |
-
text-decoration: none;
|
93 |
-
|
94 |
-
&:hover {
|
95 |
-
color: var(--text-hover-secondary);
|
96 |
-
text-decoration: none;
|
97 |
-
}
|
98 |
-
}
|
99 |
-
}
|
100 |
-
|
101 |
-
.img {
|
102 |
-
|
103 |
}
|
|
|
1 |
+
.wrapper {
|
2 |
+
//white-space: pre-line;
|
3 |
+
|
4 |
+
:global {
|
5 |
+
.bloc-spoil-jv {
|
6 |
+
//margin-bottom: 0.9375rem;
|
7 |
+
margin-bottom: 0;
|
8 |
+
display: inline;
|
9 |
+
|
10 |
+
.open-spoil {
|
11 |
+
position: absolute;
|
12 |
+
left: -999rem;
|
13 |
+
}
|
14 |
+
|
15 |
+
.barre-head {
|
16 |
+
//height: 1.5rem;
|
17 |
+
//line-height: 1.5rem;
|
18 |
+
//cursor: pointer;
|
19 |
+
//position: relative;
|
20 |
+
//display: block;
|
21 |
+
|
22 |
+
height: auto;
|
23 |
+
line-height: inherit;
|
24 |
+
text-align: center;
|
25 |
+
cursor: pointer;
|
26 |
+
position: relative;
|
27 |
+
display: inline;
|
28 |
+
|
29 |
+
.txt-spoil {
|
30 |
+
//position: absolute;
|
31 |
+
//top: 0;
|
32 |
+
//left: 0;
|
33 |
+
//width: 4.6875rem;
|
34 |
+
//font-size: .9285em;
|
35 |
+
//text-align: center;
|
36 |
+
//color: #fff;
|
37 |
+
//text-transform: uppercase;
|
38 |
+
//background: #fd374e;
|
39 |
+
//font-weight: 700;
|
40 |
+
//display: block;
|
41 |
+
//border-radius: 0.25rem;
|
42 |
+
|
43 |
+
position: static;
|
44 |
+
display: inline;
|
45 |
+
padding: 0.125rem 1.25rem;
|
46 |
+
line-height: inherit;
|
47 |
+
top: 0;
|
48 |
+
left: 0;
|
49 |
+
width: 4.6875rem;
|
50 |
+
font-size: .9285em;
|
51 |
+
text-align: center;
|
52 |
+
color: #fff;
|
53 |
+
text-transform: uppercase;
|
54 |
+
background: #fd374e;
|
55 |
+
font-weight: 700;
|
56 |
+
//display: block;
|
57 |
+
border-radius: 0.25rem;
|
58 |
+
}
|
59 |
+
}
|
60 |
+
|
61 |
+
.contenu-spoil {
|
62 |
+
background: #f4d6da;
|
63 |
+
color: #212121;
|
64 |
+
padding: 0.625rem;
|
65 |
+
display: none;
|
66 |
+
//text-align: justify;
|
67 |
+
text-align: left;
|
68 |
+
overflow: hidden;
|
69 |
+
}
|
70 |
+
|
71 |
+
.open-spoil:checked ~ .contenu-spoil {
|
72 |
+
display: inline;
|
73 |
+
}
|
74 |
+
}
|
75 |
+
}
|
76 |
+
|
77 |
+
blockquote {
|
78 |
+
text-align: left;
|
79 |
+
//margin-bottom: 0.9375rem;
|
80 |
+
border-left: 0.3125rem solid rgba(0, 0, 0, .2);
|
81 |
+
color: var(--text-blockquote-color);
|
82 |
+
//margin-left: 0.9375rem;
|
83 |
+
padding: 0.625rem;
|
84 |
+
background: none;
|
85 |
+
font-style: normal;
|
86 |
+
font-size: inherit;
|
87 |
+
margin: 0 0 0.9375rem 1rem;
|
88 |
+
}
|
89 |
+
|
90 |
+
a {
|
91 |
+
color: var(--link-color);
|
92 |
+
text-decoration: none;
|
93 |
+
|
94 |
+
&:hover {
|
95 |
+
color: var(--text-hover-secondary);
|
96 |
+
text-decoration: none;
|
97 |
+
}
|
98 |
+
}
|
99 |
+
}
|
100 |
+
|
101 |
+
.img {
|
102 |
+
|
103 |
}
|
src/components/settings/index.tsx
CHANGED
@@ -1,66 +1,66 @@
|
|
1 |
-
import {Input} from "../input";
|
2 |
-
import {Link, Thermometer, Sliders} from "preact-feather";
|
3 |
-
import {Button} from "../button";
|
4 |
-
import {useEffect, useState} from "preact/hooks";
|
5 |
-
import {fetchSettings, saveSettings, Settings as SettingsType} from "../../utils/settings";
|
6 |
-
import {Slider} from "../slider";
|
7 |
-
import {FormGroup} from "../form";
|
8 |
-
|
9 |
-
export function Settings(props: {
|
10 |
-
settings: SettingsType,
|
11 |
-
setSettings: (settings: SettingsType) => void,
|
12 |
-
resetApp: () => void,
|
13 |
-
}) {
|
14 |
-
// const [settings, setSettings] = useState(fetchSettings)
|
15 |
-
//
|
16 |
-
// useEffect(() => {
|
17 |
-
// saveSettings(settings);
|
18 |
-
// }, [settings]);
|
19 |
-
|
20 |
-
return <div>
|
21 |
-
<form>
|
22 |
-
<FormGroup>
|
23 |
-
<label htmlFor="api">API</label>
|
24 |
-
<Input
|
25 |
-
type="text"
|
26 |
-
placeholder="URl d'API ex: https://ouruq7zepnehg2-5000.proxy.runpod.net/"
|
27 |
-
icon={Link}
|
28 |
-
value={props.settings.apiURL}
|
29 |
-
onChange={(v) => props.setSettings({...props.settings, apiURL: v as string})}
|
30 |
-
/>
|
31 |
-
</FormGroup>
|
32 |
-
<FormGroup>
|
33 |
-
<label for="temperature">Temperature</label>
|
34 |
-
<Slider
|
35 |
-
name="temperature"
|
36 |
-
value={props.settings.temperature}
|
37 |
-
onChange={(v) => props.setSettings({...props.settings, temperature: v})}
|
38 |
-
min={0.1}
|
39 |
-
max={2}
|
40 |
-
step={0.1}
|
41 |
-
/>
|
42 |
-
</FormGroup>
|
43 |
-
<div>
|
44 |
-
<Button
|
45 |
-
onClick={() => {
|
46 |
-
props.resetApp()
|
47 |
-
}}
|
48 |
-
secondary={true}
|
49 |
-
title="Tout réinitialiser"
|
50 |
-
>
|
51 |
-
Réinitialiser
|
52 |
-
</Button>
|
53 |
-
</div>
|
54 |
-
<br/>
|
55 |
-
<div>
|
56 |
-
<Button
|
57 |
-
onClick={() => {
|
58 |
-
history.go(-1)
|
59 |
-
}}
|
60 |
-
>
|
61 |
-
Retour
|
62 |
-
</Button>
|
63 |
-
</div>
|
64 |
-
</form>
|
65 |
-
</div>
|
66 |
}
|
|
|
1 |
+
import {Input} from "../input";
|
2 |
+
import {Link, Thermometer, Sliders} from "preact-feather";
|
3 |
+
import {Button} from "../button";
|
4 |
+
import {useEffect, useState} from "preact/hooks";
|
5 |
+
import {fetchSettings, saveSettings, Settings as SettingsType} from "../../utils/settings";
|
6 |
+
import {Slider} from "../slider";
|
7 |
+
import {FormGroup} from "../form";
|
8 |
+
|
9 |
+
export function Settings(props: {
|
10 |
+
settings: SettingsType,
|
11 |
+
setSettings: (settings: SettingsType) => void,
|
12 |
+
resetApp: () => void,
|
13 |
+
}) {
|
14 |
+
// const [settings, setSettings] = useState(fetchSettings)
|
15 |
+
//
|
16 |
+
// useEffect(() => {
|
17 |
+
// saveSettings(settings);
|
18 |
+
// }, [settings]);
|
19 |
+
|
20 |
+
return <div>
|
21 |
+
<form>
|
22 |
+
<FormGroup>
|
23 |
+
<label htmlFor="api">API</label>
|
24 |
+
<Input
|
25 |
+
type="text"
|
26 |
+
placeholder="URl d'API ex: https://ouruq7zepnehg2-5000.proxy.runpod.net/"
|
27 |
+
icon={Link}
|
28 |
+
value={props.settings.apiURL}
|
29 |
+
onChange={(v) => props.setSettings({...props.settings, apiURL: v as string})}
|
30 |
+
/>
|
31 |
+
</FormGroup>
|
32 |
+
<FormGroup>
|
33 |
+
<label for="temperature">Temperature</label>
|
34 |
+
<Slider
|
35 |
+
name="temperature"
|
36 |
+
value={props.settings.temperature}
|
37 |
+
onChange={(v) => props.setSettings({...props.settings, temperature: v})}
|
38 |
+
min={0.1}
|
39 |
+
max={2}
|
40 |
+
step={0.1}
|
41 |
+
/>
|
42 |
+
</FormGroup>
|
43 |
+
<div>
|
44 |
+
<Button
|
45 |
+
onClick={() => {
|
46 |
+
props.resetApp()
|
47 |
+
}}
|
48 |
+
secondary={true}
|
49 |
+
title="Tout réinitialiser"
|
50 |
+
>
|
51 |
+
Réinitialiser
|
52 |
+
</Button>
|
53 |
+
</div>
|
54 |
+
<br/>
|
55 |
+
<div>
|
56 |
+
<Button
|
57 |
+
onClick={() => {
|
58 |
+
history.go(-1)
|
59 |
+
}}
|
60 |
+
>
|
61 |
+
Retour
|
62 |
+
</Button>
|
63 |
+
</div>
|
64 |
+
</form>
|
65 |
+
</div>
|
66 |
}
|
src/components/slider/style.module.scss
CHANGED
@@ -1,146 +1,146 @@
|
|
1 |
-
$ticks-height: 5px;
|
2 |
-
$ticks-thickness: 1px;
|
3 |
-
$max-ticks-allowed: 30;
|
4 |
-
$thumb-size: 20px;
|
5 |
-
$track-height: #{calc($thumb-size / 2)};
|
6 |
-
|
7 |
-
.slider {
|
8 |
-
// Define css variables
|
9 |
-
--ticks-count: calc(var(--max) - var(--min)) / var(--step);
|
10 |
-
--too-many-ticks: min(1, Max(var(--ticks-count) - #{$max-ticks-allowed}, 0));
|
11 |
-
--x-step: max(var(--step), var(--too-many-ticks) * (var(--max) - var(--min)));
|
12 |
-
|
13 |
-
// Define properties
|
14 |
-
position: relative;
|
15 |
-
|
16 |
-
// Draw the steps
|
17 |
-
background: linear-gradient(to right, var(--text-muted-color) $ticks-thickness, transparent 1px) repeat-x;
|
18 |
-
background-size: calc((100% - #{$thumb-size}) / ((var(--max) - var(--min)) / var(--x-step))) #{$ticks-height};
|
19 |
-
background-position-x: #{calc($track-height - $ticks-thickness / 2)};
|
20 |
-
background-position-y: bottom;
|
21 |
-
|
22 |
-
font-weight: 700;
|
23 |
-
color: var(--text-muted-color);
|
24 |
-
font-size: 0.8125rem;
|
25 |
-
height: 22px;
|
26 |
-
margin-top: 2.5ch;
|
27 |
-
margin-bottom: 2.5ch;
|
28 |
-
|
29 |
-
// mix/max texts
|
30 |
-
&::before,
|
31 |
-
&::after {
|
32 |
-
content: counter(x);
|
33 |
-
position: absolute;
|
34 |
-
bottom: calc(-2.5ch - 2px);
|
35 |
-
top: calc(2.5ch + 2px);
|
36 |
-
opacity: .5;
|
37 |
-
transform: translateX(calc(50% * var(--before, -1) * -1));
|
38 |
-
pointer-events: none;
|
39 |
-
}
|
40 |
-
|
41 |
-
&::before {
|
42 |
-
--before: 1;
|
43 |
-
counter-reset: x var(--min);
|
44 |
-
left: #{$track-height};
|
45 |
-
}
|
46 |
-
|
47 |
-
&::after {
|
48 |
-
counter-reset: x var(--max);
|
49 |
-
right: #{$track-height};
|
50 |
-
}
|
51 |
-
}
|
52 |
-
|
53 |
-
.outputContainer {
|
54 |
-
position: absolute;
|
55 |
-
top: 0;
|
56 |
-
left: #{$track-height};
|
57 |
-
right: #{$track-height};
|
58 |
-
height: #{$track-height};
|
59 |
-
pointer-events: none;
|
60 |
-
}
|
61 |
-
|
62 |
-
.output {
|
63 |
-
pointer-events: none;
|
64 |
-
position: absolute;
|
65 |
-
z-index: 5;
|
66 |
-
width: #{$thumb-size};
|
67 |
-
height: #{$thumb-size};
|
68 |
-
left: calc((((var(--value) - var(--min)) / (var(--max) - var(--min))) * 100%) - #{calc($thumb-size / 2)});
|
69 |
-
bottom: #{calc($thumb-size / 2 + 6px)};
|
70 |
-
text-align: center;
|
71 |
-
}
|
72 |
-
|
73 |
-
|
74 |
-
|
75 |
-
.progress {
|
76 |
-
--completed-a: calc((clamp(var(--min), var(--value, 0), var(--max)) - var(--min)) / (var(--max) - var(--min)) * 100);
|
77 |
-
--completed-b: calc((var(--value) - var(--min)) / (var(--max) - var(--min)) * 100);
|
78 |
-
--cb: max(var(--completed-a), var(--completed-b));
|
79 |
-
--start-end: #{$track-height};
|
80 |
-
--clip-end: calc(100% - (var(--cb)) * 1%);
|
81 |
-
--clip: inset(-20px var(--clip-end) -20px 0);
|
82 |
-
|
83 |
-
position: absolute;
|
84 |
-
width: 100%;
|
85 |
-
height: #{$track-height};
|
86 |
-
background: var(--text-color);
|
87 |
-
border-radius: #{$track-height};
|
88 |
-
z-index: -1;
|
89 |
-
|
90 |
-
// fill area
|
91 |
-
&::before {
|
92 |
-
content: "";
|
93 |
-
position: absolute;
|
94 |
-
left: 0;
|
95 |
-
right: 0;
|
96 |
-
clip-path: var(--clip);
|
97 |
-
top: 0;
|
98 |
-
bottom: 0;
|
99 |
-
background: var(--text-secondary);
|
100 |
-
z-index: 1;
|
101 |
-
border-radius: inherit;
|
102 |
-
}
|
103 |
-
}
|
104 |
-
|
105 |
-
.input {
|
106 |
-
--thumb-shadow: 0 0 3px rgba(0, 0, 0, 0.4), 0 0 1px rgba(0, 0, 0, 0.5) inset, 0 0 0 99px var(--text-color) inset;
|
107 |
-
|
108 |
-
-webkit-appearance: none;
|
109 |
-
width: 100%;
|
110 |
-
height: #{$thumb-size};
|
111 |
-
margin: 0;
|
112 |
-
position: absolute;
|
113 |
-
left: 0;
|
114 |
-
cursor: grab;
|
115 |
-
outline: none;
|
116 |
-
background: none;
|
117 |
-
|
118 |
-
@mixin thumb {
|
119 |
-
appearance: none;
|
120 |
-
height: #{$thumb-size};
|
121 |
-
width: #{$thumb-size};
|
122 |
-
transform: translateY(-5px);
|
123 |
-
border-radius: 50%;
|
124 |
-
background: var(--text-color);
|
125 |
-
box-shadow: var(--thumb-shadow);
|
126 |
-
border: none;
|
127 |
-
pointer-events: auto;
|
128 |
-
}
|
129 |
-
|
130 |
-
&::-webkit-slider-thumb {
|
131 |
-
@include thumb;
|
132 |
-
}
|
133 |
-
|
134 |
-
&::-moz-range-thumb {
|
135 |
-
@include thumb;
|
136 |
-
}
|
137 |
-
|
138 |
-
&::-ms-thumb {
|
139 |
-
@include thumb;
|
140 |
-
}
|
141 |
-
|
142 |
-
&:active {
|
143 |
-
--thumb-shadow: 0 0 0 #{calc($thumb-size / 4)} inset var(--text-color), 0 0 0 99px var(--text-secondary) inset, 0 0 3px rgba(0, 0, 0, 0.4);
|
144 |
-
cursor: grabbing;
|
145 |
-
}
|
146 |
}
|
|
|
1 |
+
$ticks-height: 5px;
|
2 |
+
$ticks-thickness: 1px;
|
3 |
+
$max-ticks-allowed: 30;
|
4 |
+
$thumb-size: 20px;
|
5 |
+
$track-height: #{calc($thumb-size / 2)};
|
6 |
+
|
7 |
+
.slider {
|
8 |
+
// Define css variables
|
9 |
+
--ticks-count: calc(var(--max) - var(--min)) / var(--step);
|
10 |
+
--too-many-ticks: min(1, Max(var(--ticks-count) - #{$max-ticks-allowed}, 0));
|
11 |
+
--x-step: max(var(--step), var(--too-many-ticks) * (var(--max) - var(--min)));
|
12 |
+
|
13 |
+
// Define properties
|
14 |
+
position: relative;
|
15 |
+
|
16 |
+
// Draw the steps
|
17 |
+
background: linear-gradient(to right, var(--text-muted-color) $ticks-thickness, transparent 1px) repeat-x;
|
18 |
+
background-size: calc((100% - #{$thumb-size}) / ((var(--max) - var(--min)) / var(--x-step))) #{$ticks-height};
|
19 |
+
background-position-x: #{calc($track-height - $ticks-thickness / 2)};
|
20 |
+
background-position-y: bottom;
|
21 |
+
|
22 |
+
font-weight: 700;
|
23 |
+
color: var(--text-muted-color);
|
24 |
+
font-size: 0.8125rem;
|
25 |
+
height: 22px;
|
26 |
+
margin-top: 2.5ch;
|
27 |
+
margin-bottom: 2.5ch;
|
28 |
+
|
29 |
+
// mix/max texts
|
30 |
+
&::before,
|
31 |
+
&::after {
|
32 |
+
content: counter(x);
|
33 |
+
position: absolute;
|
34 |
+
bottom: calc(-2.5ch - 2px);
|
35 |
+
top: calc(2.5ch + 2px);
|
36 |
+
opacity: .5;
|
37 |
+
transform: translateX(calc(50% * var(--before, -1) * -1));
|
38 |
+
pointer-events: none;
|
39 |
+
}
|
40 |
+
|
41 |
+
&::before {
|
42 |
+
--before: 1;
|
43 |
+
counter-reset: x var(--min);
|
44 |
+
left: #{$track-height};
|
45 |
+
}
|
46 |
+
|
47 |
+
&::after {
|
48 |
+
counter-reset: x var(--max);
|
49 |
+
right: #{$track-height};
|
50 |
+
}
|
51 |
+
}
|
52 |
+
|
53 |
+
.outputContainer {
|
54 |
+
position: absolute;
|
55 |
+
top: 0;
|
56 |
+
left: #{$track-height};
|
57 |
+
right: #{$track-height};
|
58 |
+
height: #{$track-height};
|
59 |
+
pointer-events: none;
|
60 |
+
}
|
61 |
+
|
62 |
+
.output {
|
63 |
+
pointer-events: none;
|
64 |
+
position: absolute;
|
65 |
+
z-index: 5;
|
66 |
+
width: #{$thumb-size};
|
67 |
+
height: #{$thumb-size};
|
68 |
+
left: calc((((var(--value) - var(--min)) / (var(--max) - var(--min))) * 100%) - #{calc($thumb-size / 2)});
|
69 |
+
bottom: #{calc($thumb-size / 2 + 6px)};
|
70 |
+
text-align: center;
|
71 |
+
}
|
72 |
+
|
73 |
+
|
74 |
+
|
75 |
+
.progress {
|
76 |
+
--completed-a: calc((clamp(var(--min), var(--value, 0), var(--max)) - var(--min)) / (var(--max) - var(--min)) * 100);
|
77 |
+
--completed-b: calc((var(--value) - var(--min)) / (var(--max) - var(--min)) * 100);
|
78 |
+
--cb: max(var(--completed-a), var(--completed-b));
|
79 |
+
--start-end: #{$track-height};
|
80 |
+
--clip-end: calc(100% - (var(--cb)) * 1%);
|
81 |
+
--clip: inset(-20px var(--clip-end) -20px 0);
|
82 |
+
|
83 |
+
position: absolute;
|
84 |
+
width: 100%;
|
85 |
+
height: #{$track-height};
|
86 |
+
background: var(--text-color);
|
87 |
+
border-radius: #{$track-height};
|
88 |
+
z-index: -1;
|
89 |
+
|
90 |
+
// fill area
|
91 |
+
&::before {
|
92 |
+
content: "";
|
93 |
+
position: absolute;
|
94 |
+
left: 0;
|
95 |
+
right: 0;
|
96 |
+
clip-path: var(--clip);
|
97 |
+
top: 0;
|
98 |
+
bottom: 0;
|
99 |
+
background: var(--text-secondary);
|
100 |
+
z-index: 1;
|
101 |
+
border-radius: inherit;
|
102 |
+
}
|
103 |
+
}
|
104 |
+
|
105 |
+
.input {
|
106 |
+
--thumb-shadow: 0 0 3px rgba(0, 0, 0, 0.4), 0 0 1px rgba(0, 0, 0, 0.5) inset, 0 0 0 99px var(--text-color) inset;
|
107 |
+
|
108 |
+
-webkit-appearance: none;
|
109 |
+
width: 100%;
|
110 |
+
height: #{$thumb-size};
|
111 |
+
margin: 0;
|
112 |
+
position: absolute;
|
113 |
+
left: 0;
|
114 |
+
cursor: grab;
|
115 |
+
outline: none;
|
116 |
+
background: none;
|
117 |
+
|
118 |
+
@mixin thumb {
|
119 |
+
appearance: none;
|
120 |
+
height: #{$thumb-size};
|
121 |
+
width: #{$thumb-size};
|
122 |
+
transform: translateY(-5px);
|
123 |
+
border-radius: 50%;
|
124 |
+
background: var(--text-color);
|
125 |
+
box-shadow: var(--thumb-shadow);
|
126 |
+
border: none;
|
127 |
+
pointer-events: auto;
|
128 |
+
}
|
129 |
+
|
130 |
+
&::-webkit-slider-thumb {
|
131 |
+
@include thumb;
|
132 |
+
}
|
133 |
+
|
134 |
+
&::-moz-range-thumb {
|
135 |
+
@include thumb;
|
136 |
+
}
|
137 |
+
|
138 |
+
&::-ms-thumb {
|
139 |
+
@include thumb;
|
140 |
+
}
|
141 |
+
|
142 |
+
&:active {
|
143 |
+
--thumb-shadow: 0 0 0 #{calc($thumb-size / 4)} inset var(--text-color), 0 0 0 99px var(--text-secondary) inset, 0 0 3px rgba(0, 0, 0, 0.4);
|
144 |
+
cursor: grabbing;
|
145 |
+
}
|
146 |
}
|
src/components/spinner/index.tsx
CHANGED
@@ -1,12 +1,12 @@
|
|
1 |
-
import cn from "classnames"
|
2 |
-
import style from "./style.module.scss"
|
3 |
-
|
4 |
-
export function Spinner(props: {className?: string}) {
|
5 |
-
return (
|
6 |
-
<div className={cn(style.spinnerSquare, props.className)}>
|
7 |
-
<div className={style.square1}></div>
|
8 |
-
<div className={style.square2}></div>
|
9 |
-
<div className={style.square3}></div>
|
10 |
-
</div>
|
11 |
-
)
|
12 |
}
|
|
|
1 |
+
import cn from "classnames"
|
2 |
+
import style from "./style.module.scss"
|
3 |
+
|
4 |
+
export function Spinner(props: {className?: string}) {
|
5 |
+
return (
|
6 |
+
<div className={cn(style.spinnerSquare, props.className)}>
|
7 |
+
<div className={style.square1}></div>
|
8 |
+
<div className={style.square2}></div>
|
9 |
+
<div className={style.square3}></div>
|
10 |
+
</div>
|
11 |
+
)
|
12 |
}
|
src/components/spinner/style.module.scss
CHANGED
@@ -1,46 +1,46 @@
|
|
1 |
-
.spinnerSquare {
|
2 |
-
display: flex;
|
3 |
-
flex-direction: row;
|
4 |
-
width: 90px;
|
5 |
-
height: 120px;
|
6 |
-
|
7 |
-
> div {
|
8 |
-
width: 17px;
|
9 |
-
height: 80px;
|
10 |
-
margin: auto auto;
|
11 |
-
border-radius: 4px;
|
12 |
-
}
|
13 |
-
}
|
14 |
-
|
15 |
-
.square1 {
|
16 |
-
animation: square-anim 1200ms cubic-bezier(0.445, 0.05, 0.55, 0.95) 0s infinite;
|
17 |
-
}
|
18 |
-
|
19 |
-
.square2 {
|
20 |
-
animation: square-anim 1200ms cubic-bezier(0.445, 0.05, 0.55, 0.95) 200ms infinite;
|
21 |
-
}
|
22 |
-
|
23 |
-
.square3 {
|
24 |
-
animation: square-anim 1200ms cubic-bezier(0.445, 0.05, 0.55, 0.95) 400ms infinite;
|
25 |
-
}
|
26 |
-
|
27 |
-
@keyframes square-anim {
|
28 |
-
0% {
|
29 |
-
height: 80px;
|
30 |
-
background-color: var(--text-secondary);
|
31 |
-
}
|
32 |
-
20% {
|
33 |
-
height: 80px;
|
34 |
-
}
|
35 |
-
40% {
|
36 |
-
height: 120px;
|
37 |
-
background-color: var(--text-hover-secondary);
|
38 |
-
}
|
39 |
-
80% {
|
40 |
-
height: 80px;
|
41 |
-
}
|
42 |
-
100% {
|
43 |
-
height: 80px;
|
44 |
-
background-color: var(--text-secondary);
|
45 |
-
}
|
46 |
}
|
|
|
1 |
+
.spinnerSquare {
|
2 |
+
display: flex;
|
3 |
+
flex-direction: row;
|
4 |
+
width: 90px;
|
5 |
+
height: 120px;
|
6 |
+
|
7 |
+
> div {
|
8 |
+
width: 17px;
|
9 |
+
height: 80px;
|
10 |
+
margin: auto auto;
|
11 |
+
border-radius: 4px;
|
12 |
+
}
|
13 |
+
}
|
14 |
+
|
15 |
+
.square1 {
|
16 |
+
animation: square-anim 1200ms cubic-bezier(0.445, 0.05, 0.55, 0.95) 0s infinite;
|
17 |
+
}
|
18 |
+
|
19 |
+
.square2 {
|
20 |
+
animation: square-anim 1200ms cubic-bezier(0.445, 0.05, 0.55, 0.95) 200ms infinite;
|
21 |
+
}
|
22 |
+
|
23 |
+
.square3 {
|
24 |
+
animation: square-anim 1200ms cubic-bezier(0.445, 0.05, 0.55, 0.95) 400ms infinite;
|
25 |
+
}
|
26 |
+
|
27 |
+
@keyframes square-anim {
|
28 |
+
0% {
|
29 |
+
height: 80px;
|
30 |
+
background-color: var(--text-secondary);
|
31 |
+
}
|
32 |
+
20% {
|
33 |
+
height: 80px;
|
34 |
+
}
|
35 |
+
40% {
|
36 |
+
height: 120px;
|
37 |
+
background-color: var(--text-hover-secondary);
|
38 |
+
}
|
39 |
+
80% {
|
40 |
+
height: 80px;
|
41 |
+
}
|
42 |
+
100% {
|
43 |
+
height: 80px;
|
44 |
+
background-color: var(--text-secondary);
|
45 |
+
}
|
46 |
}
|
src/components/topic/style.module.scss
CHANGED
@@ -1,51 +1,51 @@
|
|
1 |
-
.post {
|
2 |
-
margin-bottom: 0.9375rem;
|
3 |
-
font-size: .9375rem;
|
4 |
-
line-height: 1.5;
|
5 |
-
background: var(--block-bg-color);
|
6 |
-
border: 0.0625rem solid var(--border-color);
|
7 |
-
border-radius: 0.5rem;
|
8 |
-
overflow: hidden;
|
9 |
-
padding: 0.75rem;
|
10 |
-
}
|
11 |
-
|
12 |
-
.postHeader {
|
13 |
-
border-bottom: 0.0625rem dotted var(--border-color);
|
14 |
-
height: 3.625rem;
|
15 |
-
margin-bottom: 0.75rem;
|
16 |
-
display: grid;
|
17 |
-
grid-template-areas:
|
18 |
-
"avatar user"
|
19 |
-
"avatar date";
|
20 |
-
grid-template-rows: 1.5rem 1.5rem;
|
21 |
-
grid-template-columns: auto 1fr;
|
22 |
-
column-gap: .625rem;
|
23 |
-
}
|
24 |
-
|
25 |
-
.avatar {
|
26 |
-
grid-area: avatar;
|
27 |
-
height: 3rem;
|
28 |
-
width: 3rem;
|
29 |
-
object-fit: cover;
|
30 |
-
border-radius: 50%;
|
31 |
-
}
|
32 |
-
|
33 |
-
.user {
|
34 |
-
grid-area: user;
|
35 |
-
font-size: 1.0625rem;
|
36 |
-
font-weight: 500;
|
37 |
-
font-family: "Roboto", Arial, Helvetica, sans-serif;
|
38 |
-
line-height: 1.25;
|
39 |
-
}
|
40 |
-
|
41 |
-
.date {
|
42 |
-
grid-area: date;
|
43 |
-
color: var(--link-color);
|
44 |
-
text-decoration: none;
|
45 |
-
font-size: .8125rem;
|
46 |
-
}
|
47 |
-
|
48 |
-
.generationSettings {
|
49 |
-
display: grid;
|
50 |
-
grid-template-columns: 1fr 1fr;
|
51 |
}
|
|
|
1 |
+
.post {
|
2 |
+
margin-bottom: 0.9375rem;
|
3 |
+
font-size: .9375rem;
|
4 |
+
line-height: 1.5;
|
5 |
+
background: var(--block-bg-color);
|
6 |
+
border: 0.0625rem solid var(--border-color);
|
7 |
+
border-radius: 0.5rem;
|
8 |
+
overflow: hidden;
|
9 |
+
padding: 0.75rem;
|
10 |
+
}
|
11 |
+
|
12 |
+
.postHeader {
|
13 |
+
border-bottom: 0.0625rem dotted var(--border-color);
|
14 |
+
height: 3.625rem;
|
15 |
+
margin-bottom: 0.75rem;
|
16 |
+
display: grid;
|
17 |
+
grid-template-areas:
|
18 |
+
"avatar user"
|
19 |
+
"avatar date";
|
20 |
+
grid-template-rows: 1.5rem 1.5rem;
|
21 |
+
grid-template-columns: auto 1fr;
|
22 |
+
column-gap: .625rem;
|
23 |
+
}
|
24 |
+
|
25 |
+
.avatar {
|
26 |
+
grid-area: avatar;
|
27 |
+
height: 3rem;
|
28 |
+
width: 3rem;
|
29 |
+
object-fit: cover;
|
30 |
+
border-radius: 50%;
|
31 |
+
}
|
32 |
+
|
33 |
+
.user {
|
34 |
+
grid-area: user;
|
35 |
+
font-size: 1.0625rem;
|
36 |
+
font-weight: 500;
|
37 |
+
font-family: "Roboto", Arial, Helvetica, sans-serif;
|
38 |
+
line-height: 1.25;
|
39 |
+
}
|
40 |
+
|
41 |
+
.date {
|
42 |
+
grid-area: date;
|
43 |
+
color: var(--link-color);
|
44 |
+
text-decoration: none;
|
45 |
+
font-size: .8125rem;
|
46 |
+
}
|
47 |
+
|
48 |
+
.generationSettings {
|
49 |
+
display: grid;
|
50 |
+
grid-template-columns: 1fr 1fr;
|
51 |
}
|
src/components/topics/style.module.scss
CHANGED
@@ -1,77 +1,77 @@
|
|
1 |
-
.spinner {
|
2 |
-
margin: 5rem auto;
|
3 |
-
}
|
4 |
-
|
5 |
-
.list {
|
6 |
-
display: grid;
|
7 |
-
grid-template-columns: 10fr 3fr 1fr 3fr;
|
8 |
-
|
9 |
-
list-style-type: none;
|
10 |
-
//margin: 0;
|
11 |
-
//margin-bottom: 1.5rem;
|
12 |
-
background: var(--block-bg-color);
|
13 |
-
border: .0625rem solid var(--border-color);
|
14 |
-
border-radius: 0.75rem;
|
15 |
-
width: 100%;
|
16 |
-
padding: 0;
|
17 |
-
overflow: hidden;
|
18 |
-
margin-bottom: 1.5rem;
|
19 |
-
|
20 |
-
> li {
|
21 |
-
display: contents;
|
22 |
-
list-style: none;
|
23 |
-
padding: 0;
|
24 |
-
margin: 0;
|
25 |
-
|
26 |
-
> span {
|
27 |
-
font-weight: var(--topic-font-weight);
|
28 |
-
font-size: var(--topic-font-size);
|
29 |
-
padding: var(--topic-gap);
|
30 |
-
|
31 |
-
a {
|
32 |
-
overflow: hidden;
|
33 |
-
display: -webkit-box;
|
34 |
-
-webkit-line-clamp: var(--topic-title-lines);
|
35 |
-
-webkit-box-orient: vertical;
|
36 |
-
//margin-left: var(--topic-gap);
|
37 |
-
color: var(--link-color);
|
38 |
-
text-decoration: none;
|
39 |
-
font-size: var(--topic-title-font-size);
|
40 |
-
font-weight: var(--topic-title-font-weight);
|
41 |
-
|
42 |
-
&:hover {
|
43 |
-
color: var(--text-hover-secondary);
|
44 |
-
}
|
45 |
-
}
|
46 |
-
}
|
47 |
-
|
48 |
-
&:nth-child(even) {
|
49 |
-
> span {
|
50 |
-
background: var(--block-even-bg-color);
|
51 |
-
}
|
52 |
-
}
|
53 |
-
|
54 |
-
&.highlight {
|
55 |
-
> span {
|
56 |
-
background: var(--block-highlighted-bg-color);
|
57 |
-
}
|
58 |
-
}
|
59 |
-
}
|
60 |
-
}
|
61 |
-
|
62 |
-
.head {
|
63 |
-
> span {
|
64 |
-
text-transform: uppercase;
|
65 |
-
}
|
66 |
-
}
|
67 |
-
|
68 |
-
.generationSettings {
|
69 |
-
max-width: 400px;
|
70 |
-
//display: grid;
|
71 |
-
//grid-template-columns: 1fr 0;
|
72 |
-
//
|
73 |
-
//
|
74 |
-
//@media screen and (min-width: 600px) {
|
75 |
-
// grid-template-columns: 1fr 1fr;
|
76 |
-
//}
|
77 |
}
|
|
|
1 |
+
.spinner {
|
2 |
+
margin: 5rem auto;
|
3 |
+
}
|
4 |
+
|
5 |
+
.list {
|
6 |
+
display: grid;
|
7 |
+
grid-template-columns: 10fr 3fr 1fr 3fr;
|
8 |
+
|
9 |
+
list-style-type: none;
|
10 |
+
//margin: 0;
|
11 |
+
//margin-bottom: 1.5rem;
|
12 |
+
background: var(--block-bg-color);
|
13 |
+
border: .0625rem solid var(--border-color);
|
14 |
+
border-radius: 0.75rem;
|
15 |
+
width: 100%;
|
16 |
+
padding: 0;
|
17 |
+
overflow: hidden;
|
18 |
+
margin-bottom: 1.5rem;
|
19 |
+
|
20 |
+
> li {
|
21 |
+
display: contents;
|
22 |
+
list-style: none;
|
23 |
+
padding: 0;
|
24 |
+
margin: 0;
|
25 |
+
|
26 |
+
> span {
|
27 |
+
font-weight: var(--topic-font-weight);
|
28 |
+
font-size: var(--topic-font-size);
|
29 |
+
padding: var(--topic-gap);
|
30 |
+
|
31 |
+
a {
|
32 |
+
overflow: hidden;
|
33 |
+
display: -webkit-box;
|
34 |
+
-webkit-line-clamp: var(--topic-title-lines);
|
35 |
+
-webkit-box-orient: vertical;
|
36 |
+
//margin-left: var(--topic-gap);
|
37 |
+
color: var(--link-color);
|
38 |
+
text-decoration: none;
|
39 |
+
font-size: var(--topic-title-font-size);
|
40 |
+
font-weight: var(--topic-title-font-weight);
|
41 |
+
|
42 |
+
&:hover {
|
43 |
+
color: var(--text-hover-secondary);
|
44 |
+
}
|
45 |
+
}
|
46 |
+
}
|
47 |
+
|
48 |
+
&:nth-child(even) {
|
49 |
+
> span {
|
50 |
+
background: var(--block-even-bg-color);
|
51 |
+
}
|
52 |
+
}
|
53 |
+
|
54 |
+
&.highlight {
|
55 |
+
> span {
|
56 |
+
background: var(--block-highlighted-bg-color);
|
57 |
+
}
|
58 |
+
}
|
59 |
+
}
|
60 |
+
}
|
61 |
+
|
62 |
+
.head {
|
63 |
+
> span {
|
64 |
+
text-transform: uppercase;
|
65 |
+
}
|
66 |
+
}
|
67 |
+
|
68 |
+
.generationSettings {
|
69 |
+
max-width: 400px;
|
70 |
+
//display: grid;
|
71 |
+
//grid-template-columns: 1fr 0;
|
72 |
+
//
|
73 |
+
//
|
74 |
+
//@media screen and (min-width: 600px) {
|
75 |
+
// grid-template-columns: 1fr 1fr;
|
76 |
+
//}
|
77 |
}
|
src/index.ts
CHANGED
@@ -1,4 +1,4 @@
|
|
1 |
-
import {render, h} from "preact";
|
2 |
-
import {App} from "./app";
|
3 |
-
|
4 |
render(h(App, null), document.getElementById('app'))
|
|
|
1 |
+
import {render, h} from "preact";
|
2 |
+
import {App} from "./app";
|
3 |
+
|
4 |
render(h(App, null), document.getElementById('app'))
|
src/reset.scss
CHANGED
@@ -1,115 +1,115 @@
|
|
1 |
-
/***
|
2 |
-
The new CSS reset - version 1.11.3 (last updated 25.08.2024)
|
3 |
-
GitHub page: https://github.com/elad2412/the-new-css-reset
|
4 |
-
***/
|
5 |
-
|
6 |
-
/*
|
7 |
-
Remove all the styles of the "User-Agent-Stylesheet", except for the 'display' property
|
8 |
-
- The "symbol *" part is to solve Firefox SVG sprite bug
|
9 |
-
- The "html" element is excluded, otherwise a bug in Chrome breaks the CSS hyphens property (https://github.com/elad2412/the-new-css-reset/issues/36)
|
10 |
-
*/
|
11 |
-
*:where(:not(html, iframe, canvas, img, svg, video, audio):not(svg *, symbol *)) {
|
12 |
-
all: unset;
|
13 |
-
display: revert;
|
14 |
-
}
|
15 |
-
|
16 |
-
/* Preferred box-sizing value */
|
17 |
-
*,
|
18 |
-
*::before,
|
19 |
-
*::after {
|
20 |
-
box-sizing: border-box;
|
21 |
-
}
|
22 |
-
|
23 |
-
/* Fix mobile Safari increase font-size on landscape mode */
|
24 |
-
html {
|
25 |
-
-moz-text-size-adjust: none;
|
26 |
-
-webkit-text-size-adjust: none;
|
27 |
-
text-size-adjust: none;
|
28 |
-
}
|
29 |
-
|
30 |
-
/* Reapply the pointer cursor for anchor tags */
|
31 |
-
a, button {
|
32 |
-
cursor: revert;
|
33 |
-
}
|
34 |
-
|
35 |
-
/* Remove list styles (bullets/numbers) */
|
36 |
-
ol, ul, menu, summary {
|
37 |
-
list-style: none;
|
38 |
-
}
|
39 |
-
|
40 |
-
/* Firefox: solve issue where nested ordered lists continue numbering from parent (https://bugzilla.mozilla.org/show_bug.cgi?id=1881517) */
|
41 |
-
ol {
|
42 |
-
counter-reset: revert;
|
43 |
-
}
|
44 |
-
|
45 |
-
/* For images to not be able to exceed their container */
|
46 |
-
img {
|
47 |
-
max-inline-size: 100%;
|
48 |
-
max-block-size: 100%;
|
49 |
-
}
|
50 |
-
|
51 |
-
/* removes spacing between cells in tables */
|
52 |
-
table {
|
53 |
-
border-collapse: collapse;
|
54 |
-
}
|
55 |
-
|
56 |
-
/* Safari - solving issue when using user-select:none on the <body> text input doesn't working */
|
57 |
-
input, textarea {
|
58 |
-
-webkit-user-select: auto;
|
59 |
-
}
|
60 |
-
|
61 |
-
/* revert the 'white-space' property for textarea elements on Safari */
|
62 |
-
textarea {
|
63 |
-
white-space: revert;
|
64 |
-
}
|
65 |
-
|
66 |
-
/* minimum style to allow to style meter element */
|
67 |
-
meter {
|
68 |
-
-webkit-appearance: revert;
|
69 |
-
appearance: revert;
|
70 |
-
}
|
71 |
-
|
72 |
-
/* preformatted text - use only for this feature */
|
73 |
-
:where(pre) {
|
74 |
-
all: revert;
|
75 |
-
box-sizing: border-box;
|
76 |
-
}
|
77 |
-
|
78 |
-
/* reset default text opacity of input placeholder */
|
79 |
-
::placeholder {
|
80 |
-
color: unset;
|
81 |
-
}
|
82 |
-
|
83 |
-
/* fix the feature of 'hidden' attribute.
|
84 |
-
display:revert; revert to element instead of attribute */
|
85 |
-
:where([hidden]) {
|
86 |
-
display: none;
|
87 |
-
}
|
88 |
-
|
89 |
-
/* revert for bug in Chromium browsers
|
90 |
-
- fix for the content editable attribute will work properly.
|
91 |
-
- webkit-user-select: auto; added for Safari in case of using user-select:none on wrapper element*/
|
92 |
-
:where([contenteditable]:not([contenteditable="false"])) {
|
93 |
-
-moz-user-modify: read-write;
|
94 |
-
-webkit-user-modify: read-write;
|
95 |
-
overflow-wrap: break-word;
|
96 |
-
-webkit-line-break: after-white-space;
|
97 |
-
-webkit-user-select: auto;
|
98 |
-
}
|
99 |
-
|
100 |
-
/* apply back the draggable feature - exist only in Chromium and Safari */
|
101 |
-
:where([draggable="true"]) {
|
102 |
-
-webkit-user-drag: element;
|
103 |
-
}
|
104 |
-
|
105 |
-
/* Revert Modal native behavior */
|
106 |
-
//noinspection CssInvalidPseudoSelector
|
107 |
-
:where(dialog:modal) {
|
108 |
-
all: revert;
|
109 |
-
box-sizing: border-box;
|
110 |
-
}
|
111 |
-
|
112 |
-
/* Remove details summary webkit styles */
|
113 |
-
::-webkit-details-marker {
|
114 |
-
display: none;
|
115 |
}
|
|
|
1 |
+
/***
|
2 |
+
The new CSS reset - version 1.11.3 (last updated 25.08.2024)
|
3 |
+
GitHub page: https://github.com/elad2412/the-new-css-reset
|
4 |
+
***/
|
5 |
+
|
6 |
+
/*
|
7 |
+
Remove all the styles of the "User-Agent-Stylesheet", except for the 'display' property
|
8 |
+
- The "symbol *" part is to solve Firefox SVG sprite bug
|
9 |
+
- The "html" element is excluded, otherwise a bug in Chrome breaks the CSS hyphens property (https://github.com/elad2412/the-new-css-reset/issues/36)
|
10 |
+
*/
|
11 |
+
*:where(:not(html, iframe, canvas, img, svg, video, audio):not(svg *, symbol *)) {
|
12 |
+
all: unset;
|
13 |
+
display: revert;
|
14 |
+
}
|
15 |
+
|
16 |
+
/* Preferred box-sizing value */
|
17 |
+
*,
|
18 |
+
*::before,
|
19 |
+
*::after {
|
20 |
+
box-sizing: border-box;
|
21 |
+
}
|
22 |
+
|
23 |
+
/* Fix mobile Safari increase font-size on landscape mode */
|
24 |
+
html {
|
25 |
+
-moz-text-size-adjust: none;
|
26 |
+
-webkit-text-size-adjust: none;
|
27 |
+
text-size-adjust: none;
|
28 |
+
}
|
29 |
+
|
30 |
+
/* Reapply the pointer cursor for anchor tags */
|
31 |
+
a, button {
|
32 |
+
cursor: revert;
|
33 |
+
}
|
34 |
+
|
35 |
+
/* Remove list styles (bullets/numbers) */
|
36 |
+
ol, ul, menu, summary {
|
37 |
+
list-style: none;
|
38 |
+
}
|
39 |
+
|
40 |
+
/* Firefox: solve issue where nested ordered lists continue numbering from parent (https://bugzilla.mozilla.org/show_bug.cgi?id=1881517) */
|
41 |
+
ol {
|
42 |
+
counter-reset: revert;
|
43 |
+
}
|
44 |
+
|
45 |
+
/* For images to not be able to exceed their container */
|
46 |
+
img {
|
47 |
+
max-inline-size: 100%;
|
48 |
+
max-block-size: 100%;
|
49 |
+
}
|
50 |
+
|
51 |
+
/* removes spacing between cells in tables */
|
52 |
+
table {
|
53 |
+
border-collapse: collapse;
|
54 |
+
}
|
55 |
+
|
56 |
+
/* Safari - solving issue when using user-select:none on the <body> text input doesn't working */
|
57 |
+
input, textarea {
|
58 |
+
-webkit-user-select: auto;
|
59 |
+
}
|
60 |
+
|
61 |
+
/* revert the 'white-space' property for textarea elements on Safari */
|
62 |
+
textarea {
|
63 |
+
white-space: revert;
|
64 |
+
}
|
65 |
+
|
66 |
+
/* minimum style to allow to style meter element */
|
67 |
+
meter {
|
68 |
+
-webkit-appearance: revert;
|
69 |
+
appearance: revert;
|
70 |
+
}
|
71 |
+
|
72 |
+
/* preformatted text - use only for this feature */
|
73 |
+
:where(pre) {
|
74 |
+
all: revert;
|
75 |
+
box-sizing: border-box;
|
76 |
+
}
|
77 |
+
|
78 |
+
/* reset default text opacity of input placeholder */
|
79 |
+
::placeholder {
|
80 |
+
color: unset;
|
81 |
+
}
|
82 |
+
|
83 |
+
/* fix the feature of 'hidden' attribute.
|
84 |
+
display:revert; revert to element instead of attribute */
|
85 |
+
:where([hidden]) {
|
86 |
+
display: none;
|
87 |
+
}
|
88 |
+
|
89 |
+
/* revert for bug in Chromium browsers
|
90 |
+
- fix for the content editable attribute will work properly.
|
91 |
+
- webkit-user-select: auto; added for Safari in case of using user-select:none on wrapper element*/
|
92 |
+
:where([contenteditable]:not([contenteditable="false"])) {
|
93 |
+
-moz-user-modify: read-write;
|
94 |
+
-webkit-user-modify: read-write;
|
95 |
+
overflow-wrap: break-word;
|
96 |
+
-webkit-line-break: after-white-space;
|
97 |
+
-webkit-user-select: auto;
|
98 |
+
}
|
99 |
+
|
100 |
+
/* apply back the draggable feature - exist only in Chromium and Safari */
|
101 |
+
:where([draggable="true"]) {
|
102 |
+
-webkit-user-drag: element;
|
103 |
+
}
|
104 |
+
|
105 |
+
/* Revert Modal native behavior */
|
106 |
+
//noinspection CssInvalidPseudoSelector
|
107 |
+
:where(dialog:modal) {
|
108 |
+
all: revert;
|
109 |
+
box-sizing: border-box;
|
110 |
+
}
|
111 |
+
|
112 |
+
/* Remove details summary webkit styles */
|
113 |
+
::-webkit-details-marker {
|
114 |
+
display: none;
|
115 |
}
|
src/style.module.scss
CHANGED
@@ -1,13 +1,13 @@
|
|
1 |
-
.header {
|
2 |
-
background-color: var(--header-bg-color);
|
3 |
-
color: var(--text-color);
|
4 |
-
}
|
5 |
-
|
6 |
-
.logo {
|
7 |
-
font-size: 2rem;
|
8 |
-
font-weight: 700;
|
9 |
-
}
|
10 |
-
|
11 |
-
.main {
|
12 |
-
|
13 |
}
|
|
|
1 |
+
.header {
|
2 |
+
background-color: var(--header-bg-color);
|
3 |
+
color: var(--text-color);
|
4 |
+
}
|
5 |
+
|
6 |
+
.logo {
|
7 |
+
font-size: 2rem;
|
8 |
+
font-weight: 700;
|
9 |
+
}
|
10 |
+
|
11 |
+
.main {
|
12 |
+
|
13 |
}
|
src/styles.scss
CHANGED
@@ -1,139 +1,139 @@
|
|
1 |
-
@use "./reset";
|
2 |
-
|
3 |
-
@import url('https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100..900;1,100..900&display=swap');
|
4 |
-
|
5 |
-
// JVC color theme
|
6 |
-
:root {
|
7 |
-
color-scheme: dark;
|
8 |
-
--body-bg: #22252a;
|
9 |
-
--text-primary: #3d87f5;
|
10 |
-
--text-hover-primary: #5596f6;
|
11 |
-
--text-secondary: #f66031;
|
12 |
-
--text-hover-secondary: #f7734a;
|
13 |
-
--text-danger: #e4606d;
|
14 |
-
--text-hover-danger: #dc3545;
|
15 |
-
--text-dark: #e0e0e0;
|
16 |
-
--text-hover-dark: #fff;
|
17 |
-
--text-light: #000;
|
18 |
-
--bg-color-light: #272a30;
|
19 |
-
--bg-color-light-transparent: rgba(39, 42, 48, 0);
|
20 |
-
--bg-color: #22252a;
|
21 |
-
--text-color: #f2f2f2;
|
22 |
-
--border-color: #4a4c4f;
|
23 |
-
--border-even-color: #454f5e;
|
24 |
-
--border-highlighted-color: #765d3f;
|
25 |
-
--block-bg-color: #2e3238;
|
26 |
-
--block-even-bg-color: #363e49;
|
27 |
-
--block-highlighted-bg-color: #594126;
|
28 |
-
--block-bg-color-hover: #353941;
|
29 |
-
--block-bg-color-hover-dark: #454b54;
|
30 |
-
--text-muted-color: #9e9e9e;
|
31 |
-
--text-muted-hover-color: #bababa;
|
32 |
-
--text-blockquote-color: #c7c7d1;
|
33 |
-
--link-color: #7dc3f7;
|
34 |
-
--link-hover-bg-color: #4c5767;
|
35 |
-
--header-bg-color: #18191b;
|
36 |
-
--header-bottom-bg-color: #000;
|
37 |
-
--header-bottom-bg-color-hover: #303236;
|
38 |
-
--video-footer-bg-color: #1d1e20;
|
39 |
-
--layout-bg-color: #292d32;
|
40 |
-
--disabled-bg-color: #1d1e20;
|
41 |
-
--disabled-text-color: #787e87;
|
42 |
-
--disabled-border-color: #787e87;
|
43 |
-
--ad-bg-color: #18191b;
|
44 |
-
--ad-border-color: #000;
|
45 |
-
--game-header-bg-color: #2c2f35;
|
46 |
-
--bg-light: #2c2f35;
|
47 |
-
--game-tab-hover-bg-color: #17191c;
|
48 |
-
--price-color: #9fafc6;
|
49 |
-
--blue-light: #414a58;
|
50 |
-
--live-color: #ff4d4d;
|
51 |
-
--live-hover-color: #ff1a1a;
|
52 |
-
--gauge-bg-color: #616161;
|
53 |
-
--blue-gray-color: #9fafc6;
|
54 |
-
--message-delete: #104e60;
|
55 |
-
--message-delete-border: #092a34;
|
56 |
-
--message-delete-gta: #46435b;
|
57 |
-
--message-delete-gta-border: #191820;
|
58 |
-
--input-bg-color: #303236;
|
59 |
-
--input-border-color: #636569;
|
60 |
-
--input-text-color: #f2f2f2;
|
61 |
-
--input-placeholder-color: #616161;
|
62 |
-
--form-select-indicator: url("data:image/svg+xml,%3csvg xmlns=%27http://www.w3.org/2000/svg%27 viewBox=%270 0 16 16%27%3e%3cpath stroke=%27%23e0e0e0%27 stroke-width=%272px%27 d=%27M2 5l6 6 6-6%27 fill=%27none%27 stroke-linecap=%27round%27 stroke-linejoin=%27round%27/%3e%3c/svg%3e");
|
63 |
-
--form-switch-bg-image: url("data:image/svg+xml,<svg xmlns=%27http://www.w3.org/2000/svg%27 viewBox=%27-4 -4 8 8%27><circle r=%273%27 fill=%27%23636569%27/></svg>");
|
64 |
-
--admin-color: #f24444;
|
65 |
-
--modo-color: #42b228;
|
66 |
-
--ad-placeholder-opacity: 0.4;
|
67 |
-
--alert-brightness: 0.85;
|
68 |
-
--filters-bg-color: #16191d;
|
69 |
-
--gray-100: #f8f9fa;
|
70 |
-
--gray-200: #e9ecef;
|
71 |
-
--gray-300: #dee2e6;
|
72 |
-
--gray-400: #ced4da;
|
73 |
-
--gray-500: #adb5bd;
|
74 |
-
--gray-600: #6c757d;
|
75 |
-
--gray-700: #495057;
|
76 |
-
--gray-800: #343a40;
|
77 |
-
--gray-900: #212529;
|
78 |
-
--white-rgb: #fff;
|
79 |
-
--black-rgb: #000;
|
80 |
-
--body-color-rgb: var(--text-color);
|
81 |
-
--body-bg-rgb: var(--body-bg);
|
82 |
-
--font-sans-serif: "Roboto", Arial, Helvetica, sans-serif;
|
83 |
-
--font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
84 |
-
--gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
|
85 |
-
--root-font-size: 1rem;
|
86 |
-
--body-font-family: "Roboto", Arial, Helvetica, sans-serif;
|
87 |
-
--body-font-size: 0.9375rem;
|
88 |
-
--body-font-weight: 400;
|
89 |
-
--body-line-height: 1.5;
|
90 |
-
--body-color: var(--text-color);
|
91 |
-
--border-width: 0.0625rem;
|
92 |
-
--border-style: solid;
|
93 |
-
--border-color-translucent: rgba(0, 0, 0, 0.175);
|
94 |
-
--border-radius: 0.75rem;
|
95 |
-
--border-radius-sm: 0.5rem;
|
96 |
-
--border-radius-lg: 0.75rem;
|
97 |
-
--border-radius-xl: 1rem;
|
98 |
-
--border-radius-2xl: 2rem;
|
99 |
-
--border-radius-pill: 50rem;
|
100 |
-
--link-hover-color: var(--text-hover-secondary);
|
101 |
-
--code-color: #d63384;
|
102 |
-
--highlight-bg: #fff3cd;
|
103 |
-
--topic-font-size: 0.8125rem;
|
104 |
-
--topic-font-weight: 700;
|
105 |
-
--topic-line-height: 1.25;
|
106 |
-
--topic-gap: 0.625rem;
|
107 |
-
--topic-title-font-size: 0.9375rem;
|
108 |
-
--topic-title-font-weight: 500;
|
109 |
-
}
|
110 |
-
|
111 |
-
body {
|
112 |
-
margin: 0;
|
113 |
-
font-family: var(--body-font-family), sans-serif;
|
114 |
-
font-size: var(--body-font-size);
|
115 |
-
font-weight: var(--body-font-weight);
|
116 |
-
line-height: var(--body-line-height);
|
117 |
-
color: var(--body-color);
|
118 |
-
text-align: var(--body-text-align);
|
119 |
-
background-color: var(--body-bg);
|
120 |
-
-webkit-text-size-adjust: 100%;
|
121 |
-
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
122 |
-
}
|
123 |
-
|
124 |
-
hr {
|
125 |
-
//height: 0.0625rem;
|
126 |
-
height: 1px;
|
127 |
-
width: 100%;
|
128 |
-
background-color: var(--border-color);
|
129 |
-
display: block;
|
130 |
-
margin: 1.5rem 0;
|
131 |
-
}
|
132 |
-
|
133 |
-
h2 {
|
134 |
-
font-size: 1.59375rem;
|
135 |
-
line-height: 1.25;
|
136 |
-
font-weight: 500;
|
137 |
-
color: var(--text-color);
|
138 |
-
padding-bottom: 1.5rem;
|
139 |
}
|
|
|
1 |
+
@use "./reset";
|
2 |
+
|
3 |
+
@import url('https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100..900;1,100..900&display=swap');
|
4 |
+
|
5 |
+
// JVC color theme
|
6 |
+
:root {
|
7 |
+
color-scheme: dark;
|
8 |
+
--body-bg: #22252a;
|
9 |
+
--text-primary: #3d87f5;
|
10 |
+
--text-hover-primary: #5596f6;
|
11 |
+
--text-secondary: #f66031;
|
12 |
+
--text-hover-secondary: #f7734a;
|
13 |
+
--text-danger: #e4606d;
|
14 |
+
--text-hover-danger: #dc3545;
|
15 |
+
--text-dark: #e0e0e0;
|
16 |
+
--text-hover-dark: #fff;
|
17 |
+
--text-light: #000;
|
18 |
+
--bg-color-light: #272a30;
|
19 |
+
--bg-color-light-transparent: rgba(39, 42, 48, 0);
|
20 |
+
--bg-color: #22252a;
|
21 |
+
--text-color: #f2f2f2;
|
22 |
+
--border-color: #4a4c4f;
|
23 |
+
--border-even-color: #454f5e;
|
24 |
+
--border-highlighted-color: #765d3f;
|
25 |
+
--block-bg-color: #2e3238;
|
26 |
+
--block-even-bg-color: #363e49;
|
27 |
+
--block-highlighted-bg-color: #594126;
|
28 |
+
--block-bg-color-hover: #353941;
|
29 |
+
--block-bg-color-hover-dark: #454b54;
|
30 |
+
--text-muted-color: #9e9e9e;
|
31 |
+
--text-muted-hover-color: #bababa;
|
32 |
+
--text-blockquote-color: #c7c7d1;
|
33 |
+
--link-color: #7dc3f7;
|
34 |
+
--link-hover-bg-color: #4c5767;
|
35 |
+
--header-bg-color: #18191b;
|
36 |
+
--header-bottom-bg-color: #000;
|
37 |
+
--header-bottom-bg-color-hover: #303236;
|
38 |
+
--video-footer-bg-color: #1d1e20;
|
39 |
+
--layout-bg-color: #292d32;
|
40 |
+
--disabled-bg-color: #1d1e20;
|
41 |
+
--disabled-text-color: #787e87;
|
42 |
+
--disabled-border-color: #787e87;
|
43 |
+
--ad-bg-color: #18191b;
|
44 |
+
--ad-border-color: #000;
|
45 |
+
--game-header-bg-color: #2c2f35;
|
46 |
+
--bg-light: #2c2f35;
|
47 |
+
--game-tab-hover-bg-color: #17191c;
|
48 |
+
--price-color: #9fafc6;
|
49 |
+
--blue-light: #414a58;
|
50 |
+
--live-color: #ff4d4d;
|
51 |
+
--live-hover-color: #ff1a1a;
|
52 |
+
--gauge-bg-color: #616161;
|
53 |
+
--blue-gray-color: #9fafc6;
|
54 |
+
--message-delete: #104e60;
|
55 |
+
--message-delete-border: #092a34;
|
56 |
+
--message-delete-gta: #46435b;
|
57 |
+
--message-delete-gta-border: #191820;
|
58 |
+
--input-bg-color: #303236;
|
59 |
+
--input-border-color: #636569;
|
60 |
+
--input-text-color: #f2f2f2;
|
61 |
+
--input-placeholder-color: #616161;
|
62 |
+
--form-select-indicator: url("data:image/svg+xml,%3csvg xmlns=%27http://www.w3.org/2000/svg%27 viewBox=%270 0 16 16%27%3e%3cpath stroke=%27%23e0e0e0%27 stroke-width=%272px%27 d=%27M2 5l6 6 6-6%27 fill=%27none%27 stroke-linecap=%27round%27 stroke-linejoin=%27round%27/%3e%3c/svg%3e");
|
63 |
+
--form-switch-bg-image: url("data:image/svg+xml,<svg xmlns=%27http://www.w3.org/2000/svg%27 viewBox=%27-4 -4 8 8%27><circle r=%273%27 fill=%27%23636569%27/></svg>");
|
64 |
+
--admin-color: #f24444;
|
65 |
+
--modo-color: #42b228;
|
66 |
+
--ad-placeholder-opacity: 0.4;
|
67 |
+
--alert-brightness: 0.85;
|
68 |
+
--filters-bg-color: #16191d;
|
69 |
+
--gray-100: #f8f9fa;
|
70 |
+
--gray-200: #e9ecef;
|
71 |
+
--gray-300: #dee2e6;
|
72 |
+
--gray-400: #ced4da;
|
73 |
+
--gray-500: #adb5bd;
|
74 |
+
--gray-600: #6c757d;
|
75 |
+
--gray-700: #495057;
|
76 |
+
--gray-800: #343a40;
|
77 |
+
--gray-900: #212529;
|
78 |
+
--white-rgb: #fff;
|
79 |
+
--black-rgb: #000;
|
80 |
+
--body-color-rgb: var(--text-color);
|
81 |
+
--body-bg-rgb: var(--body-bg);
|
82 |
+
--font-sans-serif: "Roboto", Arial, Helvetica, sans-serif;
|
83 |
+
--font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
84 |
+
--gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
|
85 |
+
--root-font-size: 1rem;
|
86 |
+
--body-font-family: "Roboto", Arial, Helvetica, sans-serif;
|
87 |
+
--body-font-size: 0.9375rem;
|
88 |
+
--body-font-weight: 400;
|
89 |
+
--body-line-height: 1.5;
|
90 |
+
--body-color: var(--text-color);
|
91 |
+
--border-width: 0.0625rem;
|
92 |
+
--border-style: solid;
|
93 |
+
--border-color-translucent: rgba(0, 0, 0, 0.175);
|
94 |
+
--border-radius: 0.75rem;
|
95 |
+
--border-radius-sm: 0.5rem;
|
96 |
+
--border-radius-lg: 0.75rem;
|
97 |
+
--border-radius-xl: 1rem;
|
98 |
+
--border-radius-2xl: 2rem;
|
99 |
+
--border-radius-pill: 50rem;
|
100 |
+
--link-hover-color: var(--text-hover-secondary);
|
101 |
+
--code-color: #d63384;
|
102 |
+
--highlight-bg: #fff3cd;
|
103 |
+
--topic-font-size: 0.8125rem;
|
104 |
+
--topic-font-weight: 700;
|
105 |
+
--topic-line-height: 1.25;
|
106 |
+
--topic-gap: 0.625rem;
|
107 |
+
--topic-title-font-size: 0.9375rem;
|
108 |
+
--topic-title-font-weight: 500;
|
109 |
+
}
|
110 |
+
|
111 |
+
body {
|
112 |
+
margin: 0;
|
113 |
+
font-family: var(--body-font-family), sans-serif;
|
114 |
+
font-size: var(--body-font-size);
|
115 |
+
font-weight: var(--body-font-weight);
|
116 |
+
line-height: var(--body-line-height);
|
117 |
+
color: var(--body-color);
|
118 |
+
text-align: var(--body-text-align);
|
119 |
+
background-color: var(--body-bg);
|
120 |
+
-webkit-text-size-adjust: 100%;
|
121 |
+
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
122 |
+
}
|
123 |
+
|
124 |
+
hr {
|
125 |
+
//height: 0.0625rem;
|
126 |
+
height: 1px;
|
127 |
+
width: 100%;
|
128 |
+
background-color: var(--border-color);
|
129 |
+
display: block;
|
130 |
+
margin: 1.5rem 0;
|
131 |
+
}
|
132 |
+
|
133 |
+
h2 {
|
134 |
+
font-size: 1.59375rem;
|
135 |
+
line-height: 1.25;
|
136 |
+
font-weight: 500;
|
137 |
+
color: var(--text-color);
|
138 |
+
padding-bottom: 1.5rem;
|
139 |
}
|
src/utils/dates.ts
CHANGED
@@ -1,51 +1,51 @@
|
|
1 |
-
const iso8601ToFrenchRegex = /(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})/;
|
2 |
-
export function iso8601ToFrench(iso8601: string): string {
|
3 |
-
console.log("iso8601ToTokens", iso8601)
|
4 |
-
|
5 |
-
const matches = iso8601.match(iso8601ToFrenchRegex);
|
6 |
-
|
7 |
-
const year = matches[1];
|
8 |
-
const month = months[parseInt(matches[2], 10) - 1];
|
9 |
-
const day = matches[3];
|
10 |
-
const hours = matches[4];
|
11 |
-
const minutes = matches[5];
|
12 |
-
const seconds = matches[6];
|
13 |
-
|
14 |
-
return `${day} ${month} ${year} à ${hours}:${minutes}:${seconds}`;
|
15 |
-
}
|
16 |
-
|
17 |
-
const frenchToIso8601Regex = /(\d{1,2}) ([a-zA-Z\u00C0-\u024F]+) (\d{4}) à (\d{2}):(\d{2}):(\d{2})/;
|
18 |
-
export function frenchToIso8601(french: string): string {
|
19 |
-
console.log("tokensToIso8601", french)
|
20 |
-
// Match the components of the date and time from the input string
|
21 |
-
|
22 |
-
// const months = {
|
23 |
-
// janvier: '01', février: '02', mars: '03', avril: '04', mai: '05',
|
24 |
-
// juin: '06', juillet: '07', août: '08', septembre: '09',
|
25 |
-
// octobre: '10', novembre: '11', décembre: '12'
|
26 |
-
// };
|
27 |
-
|
28 |
-
const match = french.match(frenchToIso8601Regex);
|
29 |
-
|
30 |
-
if (!match) {
|
31 |
-
throw new Error('Invalid date format');
|
32 |
-
}
|
33 |
-
|
34 |
-
const [, day, month, year, hours, minutes, seconds] = match;
|
35 |
-
|
36 |
-
// Convert the month name to a numeric format
|
37 |
-
const monthNumber = (months.indexOf(month) + 1).toString();
|
38 |
-
|
39 |
-
if (!monthNumber) {
|
40 |
-
throw new Error('Invalid month name');
|
41 |
-
}
|
42 |
-
|
43 |
-
// Construct the ISO 8601 formatted string
|
44 |
-
const isoDate = `${year}-${monthNumber.padStart(2, '0')}-${day.padStart(2, '0')}T${hours}:${minutes}:${seconds}`;
|
45 |
-
return isoDate;
|
46 |
-
}
|
47 |
-
|
48 |
-
const months = [
|
49 |
-
"janvier", "février", "mars", "avril", "mai", "juin",
|
50 |
-
"juillet", "août", "septembre", "octobre", "novembre", "décembre"
|
51 |
];
|
|
|
1 |
+
const iso8601ToFrenchRegex = /(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})/;
|
2 |
+
export function iso8601ToFrench(iso8601: string): string {
|
3 |
+
console.log("iso8601ToTokens", iso8601)
|
4 |
+
|
5 |
+
const matches = iso8601.match(iso8601ToFrenchRegex);
|
6 |
+
|
7 |
+
const year = matches[1];
|
8 |
+
const month = months[parseInt(matches[2], 10) - 1];
|
9 |
+
const day = matches[3];
|
10 |
+
const hours = matches[4];
|
11 |
+
const minutes = matches[5];
|
12 |
+
const seconds = matches[6];
|
13 |
+
|
14 |
+
return `${day} ${month} ${year} à ${hours}:${minutes}:${seconds}`;
|
15 |
+
}
|
16 |
+
|
17 |
+
const frenchToIso8601Regex = /(\d{1,2}) ([a-zA-Z\u00C0-\u024F]+) (\d{4}) à (\d{2}):(\d{2}):(\d{2})/;
|
18 |
+
export function frenchToIso8601(french: string): string {
|
19 |
+
console.log("tokensToIso8601", french)
|
20 |
+
// Match the components of the date and time from the input string
|
21 |
+
|
22 |
+
// const months = {
|
23 |
+
// janvier: '01', février: '02', mars: '03', avril: '04', mai: '05',
|
24 |
+
// juin: '06', juillet: '07', août: '08', septembre: '09',
|
25 |
+
// octobre: '10', novembre: '11', décembre: '12'
|
26 |
+
// };
|
27 |
+
|
28 |
+
const match = french.match(frenchToIso8601Regex);
|
29 |
+
|
30 |
+
if (!match) {
|
31 |
+
throw new Error('Invalid date format');
|
32 |
+
}
|
33 |
+
|
34 |
+
const [, day, month, year, hours, minutes, seconds] = match;
|
35 |
+
|
36 |
+
// Convert the month name to a numeric format
|
37 |
+
const monthNumber = (months.indexOf(month) + 1).toString();
|
38 |
+
|
39 |
+
if (!monthNumber) {
|
40 |
+
throw new Error('Invalid month name');
|
41 |
+
}
|
42 |
+
|
43 |
+
// Construct the ISO 8601 formatted string
|
44 |
+
const isoDate = `${year}-${monthNumber.padStart(2, '0')}-${day.padStart(2, '0')}T${hours}:${minutes}:${seconds}`;
|
45 |
+
return isoDate;
|
46 |
+
}
|
47 |
+
|
48 |
+
const months = [
|
49 |
+
"janvier", "février", "mars", "avril", "mai", "juin",
|
50 |
+
"juillet", "août", "septembre", "octobre", "novembre", "décembre"
|
51 |
];
|
src/utils/model.ts
CHANGED
@@ -1,107 +1,107 @@
|
|
1 |
-
import {Post, Topic} from "./topics";
|
2 |
-
import {iso8601ToFrench, frenchToIso8601} from "./dates"
|
3 |
-
import {generateUUID} from "./uuids";
|
4 |
-
|
5 |
-
// const titleRegex = /Sujet\s+:\s+"([^"]+)"/;
|
6 |
-
const titleRegex = /Sujet\s+:\s+"(.+?)"?<\|eot_id\|>/;
|
7 |
-
const userRegex = /<\|im_pseudo\|>([^<]+)<\|end_pseudo\|>/;
|
8 |
-
const dateRegex = /<\|im_date\|>([^<]+)<\|end_date\|>/;
|
9 |
-
const contentRegex = /<\|begin_of_post\|>([\s\S]+)(?:<\|end_of_post\|>)?$/;
|
10 |
-
|
11 |
-
export function tokensToTopic(tokens: string): Topic {
|
12 |
-
const topic: Topic = {
|
13 |
-
id: generateUUID(),
|
14 |
-
title: "",
|
15 |
-
posts: [],
|
16 |
-
};
|
17 |
-
|
18 |
-
// const splits = tokens.split("<|end_of_post|>")
|
19 |
-
// console.log("Splits:")
|
20 |
-
// console.log(splits);
|
21 |
-
|
22 |
-
// Split token in posts
|
23 |
-
// The last element is always vois, so remove it
|
24 |
-
for(const postTokens of tokens.split("<|end_of_post|>").slice(0, -1)) {
|
25 |
-
console.log("Post tokens:")
|
26 |
-
console.log(postTokens);
|
27 |
-
|
28 |
-
// If it's the first post
|
29 |
-
if(topic.posts.length < 1) {
|
30 |
-
const titleMatch = postTokens.match(titleRegex);
|
31 |
-
console.log(`title: ${titleMatch[1]}`)
|
32 |
-
|
33 |
-
topic.title = titleMatch[1];
|
34 |
-
}
|
35 |
-
|
36 |
-
// topic.posts.push(tokensToPosts());
|
37 |
-
topic.posts = topic.posts.concat(tokensToPosts(postTokens));
|
38 |
-
}
|
39 |
-
|
40 |
-
return topic;
|
41 |
-
}
|
42 |
-
|
43 |
-
export function tokensToPosts(tokens: string): Post[] {
|
44 |
-
const posts: Post[] = [];
|
45 |
-
|
46 |
-
for(const postTokens of tokens.split("<|end_of_post|>")) {
|
47 |
-
|
48 |
-
// TODO: remove the last instead of doing this, because the last can be incomplete
|
49 |
-
if(postTokens.length < 1) {
|
50 |
-
continue;
|
51 |
-
}
|
52 |
-
|
53 |
-
console.log("Post tokens:")
|
54 |
-
console.log(postTokens);
|
55 |
-
|
56 |
-
const userMatch = postTokens.match(userRegex);
|
57 |
-
console.log(`user: ${userMatch[1]}`)
|
58 |
-
|
59 |
-
const dateMatch = postTokens.match(dateRegex);
|
60 |
-
console.log(`date: ${dateMatch[1]}`)
|
61 |
-
|
62 |
-
const contentMatch = postTokens.match(contentRegex);
|
63 |
-
console.log(`content: ${contentMatch[1]}`)
|
64 |
-
|
65 |
-
posts.push({
|
66 |
-
user: userMatch[1],
|
67 |
-
date: frenchToIso8601(dateMatch[1]),
|
68 |
-
content: contentMatch[1],
|
69 |
-
});
|
70 |
-
}
|
71 |
-
|
72 |
-
return posts;
|
73 |
-
}
|
74 |
-
|
75 |
-
|
76 |
-
export function tokenizeTopic(topic: Topic): string {
|
77 |
-
if (topic.posts.length === 0) {
|
78 |
-
throw new Error("Topic must have at least one post")
|
79 |
-
}
|
80 |
-
|
81 |
-
const tokenizedPosts = topic.posts.map(post => tokenizePost(post, topic.posts[0].user)).flat().join("");
|
82 |
-
|
83 |
-
// console.log("Tokenized posts:")
|
84 |
-
// console.log(tokenizedPosts)
|
85 |
-
|
86 |
-
let lines = [
|
87 |
-
"<|start_header_id|><|sujet|><|end_header_id|>",
|
88 |
-
"",
|
89 |
-
`Sujet : "${topic.title}"`,
|
90 |
-
];
|
91 |
-
|
92 |
-
return lines.join("\n") + tokenizedPosts;
|
93 |
-
}
|
94 |
-
|
95 |
-
export function tokenizePost(post: Post, poster: string): string {
|
96 |
-
let lines = [
|
97 |
-
`<|eot_id|><|start_header_id|><|${post.user === poster ? "autheur" : "khey"}|>`,
|
98 |
-
"<|end_header_id|>",
|
99 |
-
"",
|
100 |
-
`<|im_pseudo|>${post.user}<|end_pseudo|>`,
|
101 |
-
`<|im_date|>Le ${iso8601ToFrench(post.date)}<|end_date|>`,
|
102 |
-
"",
|
103 |
-
`<|begin_of_post|>${post.content}<|end_of_post|>`
|
104 |
-
];
|
105 |
-
|
106 |
-
return lines.join("\n");
|
107 |
}
|
|
|
1 |
+
import {Post, Topic} from "./topics";
|
2 |
+
import {iso8601ToFrench, frenchToIso8601} from "./dates"
|
3 |
+
import {generateUUID} from "./uuids";
|
4 |
+
|
5 |
+
// const titleRegex = /Sujet\s+:\s+"([^"]+)"/;
|
6 |
+
const titleRegex = /Sujet\s+:\s+"(.+?)"?<\|eot_id\|>/;
|
7 |
+
const userRegex = /<\|im_pseudo\|>([^<]+)<\|end_pseudo\|>/;
|
8 |
+
const dateRegex = /<\|im_date\|>([^<]+)<\|end_date\|>/;
|
9 |
+
const contentRegex = /<\|begin_of_post\|>([\s\S]+)(?:<\|end_of_post\|>)?$/;
|
10 |
+
|
11 |
+
export function tokensToTopic(tokens: string): Topic {
|
12 |
+
const topic: Topic = {
|
13 |
+
id: generateUUID(),
|
14 |
+
title: "",
|
15 |
+
posts: [],
|
16 |
+
};
|
17 |
+
|
18 |
+
// const splits = tokens.split("<|end_of_post|>")
|
19 |
+
// console.log("Splits:")
|
20 |
+
// console.log(splits);
|
21 |
+
|
22 |
+
// Split token in posts
|
23 |
+
// The last element is always vois, so remove it
|
24 |
+
for(const postTokens of tokens.split("<|end_of_post|>").slice(0, -1)) {
|
25 |
+
console.log("Post tokens:")
|
26 |
+
console.log(postTokens);
|
27 |
+
|
28 |
+
// If it's the first post
|
29 |
+
if(topic.posts.length < 1) {
|
30 |
+
const titleMatch = postTokens.match(titleRegex);
|
31 |
+
console.log(`title: ${titleMatch[1]}`)
|
32 |
+
|
33 |
+
topic.title = titleMatch[1];
|
34 |
+
}
|
35 |
+
|
36 |
+
// topic.posts.push(tokensToPosts());
|
37 |
+
topic.posts = topic.posts.concat(tokensToPosts(postTokens));
|
38 |
+
}
|
39 |
+
|
40 |
+
return topic;
|
41 |
+
}
|
42 |
+
|
43 |
+
export function tokensToPosts(tokens: string): Post[] {
|
44 |
+
const posts: Post[] = [];
|
45 |
+
|
46 |
+
for(const postTokens of tokens.split("<|end_of_post|>")) {
|
47 |
+
|
48 |
+
// TODO: remove the last instead of doing this, because the last can be incomplete
|
49 |
+
if(postTokens.length < 1) {
|
50 |
+
continue;
|
51 |
+
}
|
52 |
+
|
53 |
+
console.log("Post tokens:")
|
54 |
+
console.log(postTokens);
|
55 |
+
|
56 |
+
const userMatch = postTokens.match(userRegex);
|
57 |
+
console.log(`user: ${userMatch[1]}`)
|
58 |
+
|
59 |
+
const dateMatch = postTokens.match(dateRegex);
|
60 |
+
console.log(`date: ${dateMatch[1]}`)
|
61 |
+
|
62 |
+
const contentMatch = postTokens.match(contentRegex);
|
63 |
+
console.log(`content: ${contentMatch[1]}`)
|
64 |
+
|
65 |
+
posts.push({
|
66 |
+
user: userMatch[1],
|
67 |
+
date: frenchToIso8601(dateMatch[1]),
|
68 |
+
content: contentMatch[1],
|
69 |
+
});
|
70 |
+
}
|
71 |
+
|
72 |
+
return posts;
|
73 |
+
}
|
74 |
+
|
75 |
+
|
76 |
+
export function tokenizeTopic(topic: Topic): string {
|
77 |
+
if (topic.posts.length === 0) {
|
78 |
+
throw new Error("Topic must have at least one post")
|
79 |
+
}
|
80 |
+
|
81 |
+
const tokenizedPosts = topic.posts.map(post => tokenizePost(post, topic.posts[0].user)).flat().join("");
|
82 |
+
|
83 |
+
// console.log("Tokenized posts:")
|
84 |
+
// console.log(tokenizedPosts)
|
85 |
+
|
86 |
+
let lines = [
|
87 |
+
"<|start_header_id|><|sujet|><|end_header_id|>",
|
88 |
+
"",
|
89 |
+
`Sujet : "${topic.title}"`,
|
90 |
+
];
|
91 |
+
|
92 |
+
return lines.join("\n") + tokenizedPosts;
|
93 |
+
}
|
94 |
+
|
95 |
+
export function tokenizePost(post: Post, poster: string): string {
|
96 |
+
let lines = [
|
97 |
+
`<|eot_id|><|start_header_id|><|${post.user === poster ? "autheur" : "khey"}|>`,
|
98 |
+
"<|end_header_id|>",
|
99 |
+
"",
|
100 |
+
`<|im_pseudo|>${post.user}<|end_pseudo|>`,
|
101 |
+
`<|im_date|>Le ${iso8601ToFrench(post.date)}<|end_date|>`,
|
102 |
+
"",
|
103 |
+
`<|begin_of_post|>${post.content}<|end_of_post|>`
|
104 |
+
];
|
105 |
+
|
106 |
+
return lines.join("\n");
|
107 |
}
|
src/utils/route.ts
CHANGED
@@ -1,9 +1,9 @@
|
|
1 |
-
export const routes = {
|
2 |
-
home: "home",
|
3 |
-
topic: "topic",
|
4 |
-
settings: "settings",
|
5 |
-
} as const;
|
6 |
-
|
7 |
-
export type Route = typeof routes[keyof typeof routes];
|
8 |
-
|
9 |
export type RouteSetter = (route: Route, page?: number, id?: string) => void;
|
|
|
1 |
+
export const routes = {
|
2 |
+
home: "home",
|
3 |
+
topic: "topic",
|
4 |
+
settings: "settings",
|
5 |
+
} as const;
|
6 |
+
|
7 |
+
export type Route = typeof routes[keyof typeof routes];
|
8 |
+
|
9 |
export type RouteSetter = (route: Route, page?: number, id?: string) => void;
|
src/utils/settings.ts
CHANGED
@@ -1,30 +1,30 @@
|
|
1 |
-
const itemKey = "settings";
|
2 |
-
|
3 |
-
export type Settings = {
|
4 |
-
apiURL: string;
|
5 |
-
temperature: number;
|
6 |
-
postCount: number;
|
7 |
-
}
|
8 |
-
|
9 |
-
const defaultSettings: Settings = {
|
10 |
-
apiURL: "http://localhost:5000",
|
11 |
-
temperature: 0.9,
|
12 |
-
postCount: 3,
|
13 |
-
}
|
14 |
-
|
15 |
-
export function fetchSettings(): Settings {
|
16 |
-
const storedSettings = localStorage.getItem(itemKey);
|
17 |
-
if (storedSettings) {
|
18 |
-
return {...defaultSettings, ...JSON.parse(storedSettings)} as Settings;
|
19 |
-
} else {
|
20 |
-
return defaultSettings;
|
21 |
-
}
|
22 |
-
}
|
23 |
-
|
24 |
-
export function saveSettings(settings: Settings) {
|
25 |
-
localStorage.setItem(itemKey, JSON.stringify(settings));
|
26 |
-
}
|
27 |
-
|
28 |
-
export function resetSettings() {
|
29 |
-
localStorage.removeItem(itemKey);
|
30 |
}
|
|
|
1 |
+
const itemKey = "settings";
|
2 |
+
|
3 |
+
export type Settings = {
|
4 |
+
apiURL: string;
|
5 |
+
temperature: number;
|
6 |
+
postCount: number;
|
7 |
+
}
|
8 |
+
|
9 |
+
const defaultSettings: Settings = {
|
10 |
+
apiURL: "http://localhost:5000",
|
11 |
+
temperature: 0.9,
|
12 |
+
postCount: 3,
|
13 |
+
}
|
14 |
+
|
15 |
+
export function fetchSettings(): Settings {
|
16 |
+
const storedSettings = localStorage.getItem(itemKey);
|
17 |
+
if (storedSettings) {
|
18 |
+
return {...defaultSettings, ...JSON.parse(storedSettings)} as Settings;
|
19 |
+
} else {
|
20 |
+
return defaultSettings;
|
21 |
+
}
|
22 |
+
}
|
23 |
+
|
24 |
+
export function saveSettings(settings: Settings) {
|
25 |
+
localStorage.setItem(itemKey, JSON.stringify(settings));
|
26 |
+
}
|
27 |
+
|
28 |
+
export function resetSettings() {
|
29 |
+
localStorage.removeItem(itemKey);
|
30 |
}
|
src/utils/smileys.ts
CHANGED
@@ -1,79 +1,79 @@
|
|
1 |
-
export const smileysMap: [string, string][] = [
|
2 |
-
[":)", "1"], // https://image.jeuxvideo.com/smileys_img/1.gif
|
3 |
-
[":snif:", "20"],
|
4 |
-
[":gba:", "17"],
|
5 |
-
[":g)", "3"],
|
6 |
-
[":-)", "46"],
|
7 |
-
[":snif2:", "13"],
|
8 |
-
[":bravo:", "69"],
|
9 |
-
[":d)", "4"],
|
10 |
-
[":hap:", "18"],
|
11 |
-
[":ouch:", "22"],
|
12 |
-
[":pacg:", "9"],
|
13 |
-
[":cd:", "5"],
|
14 |
-
[":-)))", "23"],
|
15 |
-
[":ouch2:", "57"],
|
16 |
-
[":pacd:", "10"],
|
17 |
-
[":cute:", "nyu"],
|
18 |
-
[":content:", "24"],
|
19 |
-
[":p)", "7"],
|
20 |
-
[":-p", "31"],
|
21 |
-
[":noel:", "11"],
|
22 |
-
[":oui:", "37"],
|
23 |
-
[":(", "45"],
|
24 |
-
[":peur:", "47"],
|
25 |
-
[":question:", "2"],
|
26 |
-
[":cool:", "26"],
|
27 |
-
[":-(", "14"],
|
28 |
-
[":coeur:", "54"],
|
29 |
-
[":mort:", "21"],
|
30 |
-
[":rire:", "39"],
|
31 |
-
[":-((", "15"],
|
32 |
-
[":fou:", "50"],
|
33 |
-
[":sleep:", "27"],
|
34 |
-
[":-D", "40"],
|
35 |
-
[":nonnon:", "25"],
|
36 |
-
[":fier:", "53"],
|
37 |
-
[":honte:", "30"],
|
38 |
-
[":rire2:", "41"],
|
39 |
-
[":non2:", "33"],
|
40 |
-
[":sarcastic:", "43"],
|
41 |
-
[":monoeil:", "34"],
|
42 |
-
[":o))", "12"],
|
43 |
-
[":nah:", "19"],
|
44 |
-
[":doute:", "28"],
|
45 |
-
[":rouge:", "55"],
|
46 |
-
[":ok:", "36"],
|
47 |
-
[":non:", "35"],
|
48 |
-
[":malade:", "8"],
|
49 |
-
[":fete:", "66"],
|
50 |
-
[":sournois:", "67"],
|
51 |
-
[":hum:", "68"],
|
52 |
-
[":ange:", "60"],
|
53 |
-
[":diable:", "61"],
|
54 |
-
[":gni:", "62"],
|
55 |
-
[":play:", "play"],
|
56 |
-
[":desole:", "65"],
|
57 |
-
[":spoiler:", "63"],
|
58 |
-
[":merci:", "58"],
|
59 |
-
[":svp:", "59"],
|
60 |
-
[":sors:", "56"],
|
61 |
-
[":salut:", "42"],
|
62 |
-
[":rechercher:", "38"],
|
63 |
-
[":hello:", "29"],
|
64 |
-
[":up:", "44"],
|
65 |
-
[":bye:", "48"],
|
66 |
-
[":gne:", "51"],
|
67 |
-
[":lol:", "32"],
|
68 |
-
[":dpdr:", "49"],
|
69 |
-
[":dehors:", "52"],
|
70 |
-
[":hs:", "64"],
|
71 |
-
[":banzai:", "70"],
|
72 |
-
[":bave:", "71"],
|
73 |
-
[":pf:", "pf"],
|
74 |
-
[":cimer:", "cimer"],
|
75 |
-
[":ddb:", "ddb"],
|
76 |
-
[":pave:", "pave"],
|
77 |
-
[":objection:", "objection"],
|
78 |
-
[":siffle:", "siffle"],
|
79 |
];
|
|
|
1 |
+
export const smileysMap: [string, string][] = [
|
2 |
+
[":)", "1"], // https://image.jeuxvideo.com/smileys_img/1.gif
|
3 |
+
[":snif:", "20"],
|
4 |
+
[":gba:", "17"],
|
5 |
+
[":g)", "3"],
|
6 |
+
[":-)", "46"],
|
7 |
+
[":snif2:", "13"],
|
8 |
+
[":bravo:", "69"],
|
9 |
+
[":d)", "4"],
|
10 |
+
[":hap:", "18"],
|
11 |
+
[":ouch:", "22"],
|
12 |
+
[":pacg:", "9"],
|
13 |
+
[":cd:", "5"],
|
14 |
+
[":-)))", "23"],
|
15 |
+
[":ouch2:", "57"],
|
16 |
+
[":pacd:", "10"],
|
17 |
+
[":cute:", "nyu"],
|
18 |
+
[":content:", "24"],
|
19 |
+
[":p)", "7"],
|
20 |
+
[":-p", "31"],
|
21 |
+
[":noel:", "11"],
|
22 |
+
[":oui:", "37"],
|
23 |
+
[":(", "45"],
|
24 |
+
[":peur:", "47"],
|
25 |
+
[":question:", "2"],
|
26 |
+
[":cool:", "26"],
|
27 |
+
[":-(", "14"],
|
28 |
+
[":coeur:", "54"],
|
29 |
+
[":mort:", "21"],
|
30 |
+
[":rire:", "39"],
|
31 |
+
[":-((", "15"],
|
32 |
+
[":fou:", "50"],
|
33 |
+
[":sleep:", "27"],
|
34 |
+
[":-D", "40"],
|
35 |
+
[":nonnon:", "25"],
|
36 |
+
[":fier:", "53"],
|
37 |
+
[":honte:", "30"],
|
38 |
+
[":rire2:", "41"],
|
39 |
+
[":non2:", "33"],
|
40 |
+
[":sarcastic:", "43"],
|
41 |
+
[":monoeil:", "34"],
|
42 |
+
[":o))", "12"],
|
43 |
+
[":nah:", "19"],
|
44 |
+
[":doute:", "28"],
|
45 |
+
[":rouge:", "55"],
|
46 |
+
[":ok:", "36"],
|
47 |
+
[":non:", "35"],
|
48 |
+
[":malade:", "8"],
|
49 |
+
[":fete:", "66"],
|
50 |
+
[":sournois:", "67"],
|
51 |
+
[":hum:", "68"],
|
52 |
+
[":ange:", "60"],
|
53 |
+
[":diable:", "61"],
|
54 |
+
[":gni:", "62"],
|
55 |
+
[":play:", "play"],
|
56 |
+
[":desole:", "65"],
|
57 |
+
[":spoiler:", "63"],
|
58 |
+
[":merci:", "58"],
|
59 |
+
[":svp:", "59"],
|
60 |
+
[":sors:", "56"],
|
61 |
+
[":salut:", "42"],
|
62 |
+
[":rechercher:", "38"],
|
63 |
+
[":hello:", "29"],
|
64 |
+
[":up:", "44"],
|
65 |
+
[":bye:", "48"],
|
66 |
+
[":gne:", "51"],
|
67 |
+
[":lol:", "32"],
|
68 |
+
[":dpdr:", "49"],
|
69 |
+
[":dehors:", "52"],
|
70 |
+
[":hs:", "64"],
|
71 |
+
[":banzai:", "70"],
|
72 |
+
[":bave:", "71"],
|
73 |
+
[":pf:", "pf"],
|
74 |
+
[":cimer:", "cimer"],
|
75 |
+
[":ddb:", "ddb"],
|
76 |
+
[":pave:", "pave"],
|
77 |
+
[":objection:", "objection"],
|
78 |
+
[":siffle:", "siffle"],
|
79 |
];
|
src/utils/topics.ts
CHANGED
@@ -1,135 +1,135 @@
|
|
1 |
-
export type Post = {
|
2 |
-
user: string;
|
3 |
-
date: string; // ISO 8601, YYYY-MM-DDTHH:MM:SS
|
4 |
-
content: string;
|
5 |
-
}
|
6 |
-
|
7 |
-
export type Topic = {
|
8 |
-
id: string; // UUID
|
9 |
-
title: string;
|
10 |
-
posts: Post[];
|
11 |
-
}
|
12 |
-
|
13 |
-
const itemKey = "topics";
|
14 |
-
|
15 |
-
export function loadTopics(): Topic[] {
|
16 |
-
const storedTopics = localStorage.getItem(itemKey);
|
17 |
-
if (storedTopics) {
|
18 |
-
return JSON.parse(storedTopics) as Topic[];
|
19 |
-
} else {
|
20 |
-
return [];
|
21 |
-
}
|
22 |
-
}
|
23 |
-
|
24 |
-
export function saveTopics(topics: Topic[]) {
|
25 |
-
localStorage.setItem(itemKey, JSON.stringify(topics));
|
26 |
-
}
|
27 |
-
|
28 |
-
|
29 |
-
const testTopics: Topic[] = [
|
30 |
-
{
|
31 |
-
id: "dbf575b3-fc93-47b8-8773-bd91692dc97c",
|
32 |
-
title: "Les néerlandais font tous 2m",
|
33 |
-
posts: [
|
34 |
-
{
|
35 |
-
user: "elegante12",
|
36 |
-
date: "2025-01-15T09:13:44",
|
37 |
-
content: "https://vocaroo.com/1cCsqH5ai8AW\nN'allais pas ma bas si vous faites pas mini 1m80 même les meufs sont dans les 1m75 minimum"
|
38 |
-
},
|
39 |
-
{
|
40 |
-
user: "Pajoad",
|
41 |
-
date: "2025-01-15T09:14:45",
|
42 |
-
content: "Je suis (oui c'est on EST notre taille) 1m73 et j'ai été à Amsterdam en 2022, j'ai eu l'impression d'avoir 10 ans"
|
43 |
-
},
|
44 |
-
{
|
45 |
-
user: "Qui-es-tu",
|
46 |
-
date: "2025-01-15T09:15:13",
|
47 |
-
content: "Un enfer sur terre quoi."
|
48 |
-
},
|
49 |
-
]
|
50 |
-
},
|
51 |
-
{
|
52 |
-
id: "0dbfad91-c5b7-4e11-a41f-eae8162ddfa5",
|
53 |
-
title: "C'est normal qu'une fille pète au lit ?",
|
54 |
-
posts: [
|
55 |
-
{
|
56 |
-
user: "Muskis",
|
57 |
-
date: "2025-01-15T10:47:14",
|
58 |
-
content: "Quand on couche avec une fille c'est normal qu'elle pète au lit avant ou après l'amour en mode balécouilles que tu sois là ?"
|
59 |
-
},
|
60 |
-
{
|
61 |
-
user: "edreMaLeuPcvJ-4",
|
62 |
-
date: "2025-01-15T10:49:45",
|
63 |
-
content: "Non, les filles ne pètent pas.\nSi elle pète c'est qu'elle est trans :ok:"
|
64 |
-
},
|
65 |
-
{
|
66 |
-
user: "PereAttali",
|
67 |
-
date: "2025-01-15T10:49:37",
|
68 |
-
content: "Quand tu lui fous dans le fion et que ta queue est trop large c'est ce qui peut arriver (lié à la dilatation rapide)"
|
69 |
-
},
|
70 |
-
{
|
71 |
-
user: "Pajoad",
|
72 |
-
date: "2025-01-15T10:56:37",
|
73 |
-
content: "Ça a quelle odeur d'ailleurs un pet féminin ?"
|
74 |
-
},
|
75 |
-
{
|
76 |
-
user: "hypertendu",
|
77 |
-
date: "2025-01-15T10:58:15",
|
78 |
-
content: "Ça peut être un pet vaginal :("
|
79 |
-
},
|
80 |
-
]
|
81 |
-
},
|
82 |
-
{
|
83 |
-
id: "816dbd52-3f8e-4e44-9b8a-fcc388485e9f",
|
84 |
-
title: "Ta cousine veut plaquer ses FEETS sur ton visage",
|
85 |
-
posts: [
|
86 |
-
{
|
87 |
-
user: "BanDef411",
|
88 |
-
date: "2025-01-15T10:42:00",
|
89 |
-
content: "Es-tu préparé face a l’odeur? :("
|
90 |
-
},
|
91 |
-
{
|
92 |
-
user: "McLoviin",
|
93 |
-
date: "2025-01-15T10:43:35",
|
94 |
-
content: "Etant donné qu'elle me fait bander comme un taureau depuis l'adolescence je suis son tapis, son fauteuil, tout est dispo pour elle."
|
95 |
-
},
|
96 |
-
]
|
97 |
-
},
|
98 |
-
{
|
99 |
-
id: "e1504b59-3918-4ee6-bced-efcd3af3f278",
|
100 |
-
title: "[CDD] Je BAISE ta FEMME contre REMUNERATION",
|
101 |
-
posts: [
|
102 |
-
{
|
103 |
-
user: "LeFeetBossV3",
|
104 |
-
date: "2025-01-15T11:04:30",
|
105 |
-
content: "Vu qu'apparement tu sais pas la satisfaire, pour 500 balles je la baise vite fait https://image.noelshack.com/minis/2018/25/2/1529422413-risitaszoom.png\n\nElle ira mieux et votre couple sera sauvé https://image.noelshack.com/minis/2018/25/2/1529422413-risitaszoom.png"
|
106 |
-
},
|
107 |
-
{
|
108 |
-
user: "Platitude24",
|
109 |
-
date: "2025-01-15T11:05:51",
|
110 |
-
content: "Ma femme possède un pénis féminin et elle demande si elle peut te sodomiser ?"
|
111 |
-
},
|
112 |
-
]
|
113 |
-
},
|
114 |
-
{
|
115 |
-
id: "e5c288db-45a0-43e0-aa37-df485da475e7",
|
116 |
-
title: "La policière au gros CUL de Marseille est devenu MILLIONNAIRE",
|
117 |
-
posts: [
|
118 |
-
{
|
119 |
-
user: "Samanthabagare3",
|
120 |
-
date: "2025-01-15T11:59:32",
|
121 |
-
content: "https://image.noelshack.com/fichiers/2025/03/3/1736938679-screenshot-2025-01-15-11-57-10-521-com-instagram-android-edit.jpg\nhttps://image.noelshack.com/minis/2025/03/3/1736938679-screenshot-2025-01-15-11-57-10-521-com-instagram-android-edit.png\nEntrepreneuriat\" = Mym + onlyfan https://image.noelshack.com/fichiers/2018/25/2/1529422413-risitaszoom.png\n\nFrom 500 abonnés to 232 000 grâce au buzz https://image.noelshack.com/fichiers/2018/25/2/1529422413-risitaszoom.png"
|
122 |
-
},
|
123 |
-
{
|
124 |
-
user: "Platitude24",
|
125 |
-
date: "2025-01-15T11:59:36",
|
126 |
-
content: "Comment ça peut être policière ça ? :("
|
127 |
-
},
|
128 |
-
{
|
129 |
-
user: "Samanthabagare3",
|
130 |
-
date: "2025-01-15T12:00:02",
|
131 |
-
content: "> Le 15 janvier 2025 à 11:59:36 :\n> Comment ça peut être policière ça ?\n\nAHI <spoil>loool</spoil> AHI\nShitholisation du pays https://image.noelshack.com/fichiers/2018/25/2/1529422413-risitaszoom.png"
|
132 |
-
},
|
133 |
-
]
|
134 |
-
},
|
135 |
];
|
|
|
1 |
+
export type Post = {
|
2 |
+
user: string;
|
3 |
+
date: string; // ISO 8601, YYYY-MM-DDTHH:MM:SS
|
4 |
+
content: string;
|
5 |
+
}
|
6 |
+
|
7 |
+
export type Topic = {
|
8 |
+
id: string; // UUID
|
9 |
+
title: string;
|
10 |
+
posts: Post[];
|
11 |
+
}
|
12 |
+
|
13 |
+
const itemKey = "topics";
|
14 |
+
|
15 |
+
export function loadTopics(): Topic[] {
|
16 |
+
const storedTopics = localStorage.getItem(itemKey);
|
17 |
+
if (storedTopics) {
|
18 |
+
return JSON.parse(storedTopics) as Topic[];
|
19 |
+
} else {
|
20 |
+
return [];
|
21 |
+
}
|
22 |
+
}
|
23 |
+
|
24 |
+
export function saveTopics(topics: Topic[]) {
|
25 |
+
localStorage.setItem(itemKey, JSON.stringify(topics));
|
26 |
+
}
|
27 |
+
|
28 |
+
|
29 |
+
const testTopics: Topic[] = [
|
30 |
+
{
|
31 |
+
id: "dbf575b3-fc93-47b8-8773-bd91692dc97c",
|
32 |
+
title: "Les néerlandais font tous 2m",
|
33 |
+
posts: [
|
34 |
+
{
|
35 |
+
user: "elegante12",
|
36 |
+
date: "2025-01-15T09:13:44",
|
37 |
+
content: "https://vocaroo.com/1cCsqH5ai8AW\nN'allais pas ma bas si vous faites pas mini 1m80 même les meufs sont dans les 1m75 minimum"
|
38 |
+
},
|
39 |
+
{
|
40 |
+
user: "Pajoad",
|
41 |
+
date: "2025-01-15T09:14:45",
|
42 |
+
content: "Je suis (oui c'est on EST notre taille) 1m73 et j'ai été à Amsterdam en 2022, j'ai eu l'impression d'avoir 10 ans"
|
43 |
+
},
|
44 |
+
{
|
45 |
+
user: "Qui-es-tu",
|
46 |
+
date: "2025-01-15T09:15:13",
|
47 |
+
content: "Un enfer sur terre quoi."
|
48 |
+
},
|
49 |
+
]
|
50 |
+
},
|
51 |
+
{
|
52 |
+
id: "0dbfad91-c5b7-4e11-a41f-eae8162ddfa5",
|
53 |
+
title: "C'est normal qu'une fille pète au lit ?",
|
54 |
+
posts: [
|
55 |
+
{
|
56 |
+
user: "Muskis",
|
57 |
+
date: "2025-01-15T10:47:14",
|
58 |
+
content: "Quand on couche avec une fille c'est normal qu'elle pète au lit avant ou après l'amour en mode balécouilles que tu sois là ?"
|
59 |
+
},
|
60 |
+
{
|
61 |
+
user: "edreMaLeuPcvJ-4",
|
62 |
+
date: "2025-01-15T10:49:45",
|
63 |
+
content: "Non, les filles ne pètent pas.\nSi elle pète c'est qu'elle est trans :ok:"
|
64 |
+
},
|
65 |
+
{
|
66 |
+
user: "PereAttali",
|
67 |
+
date: "2025-01-15T10:49:37",
|
68 |
+
content: "Quand tu lui fous dans le fion et que ta queue est trop large c'est ce qui peut arriver (lié à la dilatation rapide)"
|
69 |
+
},
|
70 |
+
{
|
71 |
+
user: "Pajoad",
|
72 |
+
date: "2025-01-15T10:56:37",
|
73 |
+
content: "Ça a quelle odeur d'ailleurs un pet féminin ?"
|
74 |
+
},
|
75 |
+
{
|
76 |
+
user: "hypertendu",
|
77 |
+
date: "2025-01-15T10:58:15",
|
78 |
+
content: "Ça peut être un pet vaginal :("
|
79 |
+
},
|
80 |
+
]
|
81 |
+
},
|
82 |
+
{
|
83 |
+
id: "816dbd52-3f8e-4e44-9b8a-fcc388485e9f",
|
84 |
+
title: "Ta cousine veut plaquer ses FEETS sur ton visage",
|
85 |
+
posts: [
|
86 |
+
{
|
87 |
+
user: "BanDef411",
|
88 |
+
date: "2025-01-15T10:42:00",
|
89 |
+
content: "Es-tu préparé face a l’odeur? :("
|
90 |
+
},
|
91 |
+
{
|
92 |
+
user: "McLoviin",
|
93 |
+
date: "2025-01-15T10:43:35",
|
94 |
+
content: "Etant donné qu'elle me fait bander comme un taureau depuis l'adolescence je suis son tapis, son fauteuil, tout est dispo pour elle."
|
95 |
+
},
|
96 |
+
]
|
97 |
+
},
|
98 |
+
{
|
99 |
+
id: "e1504b59-3918-4ee6-bced-efcd3af3f278",
|
100 |
+
title: "[CDD] Je BAISE ta FEMME contre REMUNERATION",
|
101 |
+
posts: [
|
102 |
+
{
|
103 |
+
user: "LeFeetBossV3",
|
104 |
+
date: "2025-01-15T11:04:30",
|
105 |
+
content: "Vu qu'apparement tu sais pas la satisfaire, pour 500 balles je la baise vite fait https://image.noelshack.com/minis/2018/25/2/1529422413-risitaszoom.png\n\nElle ira mieux et votre couple sera sauvé https://image.noelshack.com/minis/2018/25/2/1529422413-risitaszoom.png"
|
106 |
+
},
|
107 |
+
{
|
108 |
+
user: "Platitude24",
|
109 |
+
date: "2025-01-15T11:05:51",
|
110 |
+
content: "Ma femme possède un pénis féminin et elle demande si elle peut te sodomiser ?"
|
111 |
+
},
|
112 |
+
]
|
113 |
+
},
|
114 |
+
{
|
115 |
+
id: "e5c288db-45a0-43e0-aa37-df485da475e7",
|
116 |
+
title: "La policière au gros CUL de Marseille est devenu MILLIONNAIRE",
|
117 |
+
posts: [
|
118 |
+
{
|
119 |
+
user: "Samanthabagare3",
|
120 |
+
date: "2025-01-15T11:59:32",
|
121 |
+
content: "https://image.noelshack.com/fichiers/2025/03/3/1736938679-screenshot-2025-01-15-11-57-10-521-com-instagram-android-edit.jpg\nhttps://image.noelshack.com/minis/2025/03/3/1736938679-screenshot-2025-01-15-11-57-10-521-com-instagram-android-edit.png\nEntrepreneuriat\" = Mym + onlyfan https://image.noelshack.com/fichiers/2018/25/2/1529422413-risitaszoom.png\n\nFrom 500 abonnés to 232 000 grâce au buzz https://image.noelshack.com/fichiers/2018/25/2/1529422413-risitaszoom.png"
|
122 |
+
},
|
123 |
+
{
|
124 |
+
user: "Platitude24",
|
125 |
+
date: "2025-01-15T11:59:36",
|
126 |
+
content: "Comment ça peut être policière ça ? :("
|
127 |
+
},
|
128 |
+
{
|
129 |
+
user: "Samanthabagare3",
|
130 |
+
date: "2025-01-15T12:00:02",
|
131 |
+
content: "> Le 15 janvier 2025 à 11:59:36 :\n> Comment ça peut être policière ça ?\n\nAHI <spoil>loool</spoil> AHI\nShitholisation du pays https://image.noelshack.com/fichiers/2018/25/2/1529422413-risitaszoom.png"
|
132 |
+
},
|
133 |
+
]
|
134 |
+
},
|
135 |
];
|
src/utils/uuids.ts
CHANGED
@@ -1,5 +1,5 @@
|
|
1 |
-
export function generateUUID(): string {
|
2 |
-
return "10000000-1000-4000-8000-100000000000".replace(/[018]/g, c =>
|
3 |
-
(+c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> +c / 4).toString(16)
|
4 |
-
);
|
5 |
}
|
|
|
1 |
+
export function generateUUID(): string {
|
2 |
+
return "10000000-1000-4000-8000-100000000000".replace(/[018]/g, c =>
|
3 |
+
(+c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> +c / 4).toString(16)
|
4 |
+
);
|
5 |
}
|