+
{props.header}
diff --git a/src/components/Wrapper/wrapper.module.css b/src/components/Wrapper/wrapper.module.css
index 6b8d26b9..bb3a26a1 100644
--- a/src/components/Wrapper/wrapper.module.css
+++ b/src/components/Wrapper/wrapper.module.css
@@ -1,13 +1,15 @@
.container {
- display: flex;
- flex-direction: column;
- background-color: var(--color-back-main);
+ display: flex;
+ flex-direction: column;
- font-family: var(--text-font);
- font-size: var(--font-size-default);
+ color: var(--color-text);
+ background-color: var(--color-back-main);
+
+ font-family: var(--text-font);
+ font-size: var(--font-size-default);
}
.main {
- min-height: 100vh;
- flex: 1;
+ min-height: 100vh;
+ flex: 1;
}
diff --git a/src/hooks/useAccessibility.ts b/src/hooks/useAccessibility.ts
new file mode 100644
index 00000000..c328c99f
--- /dev/null
+++ b/src/hooks/useAccessibility.ts
@@ -0,0 +1,10 @@
+import { theme } from "@/store/theme"
+import { useSnapshot } from "valtio"
+
+function useAccessibility(): boolean {
+ const t = useSnapshot(theme)
+
+ return t.theme === "contrast"
+}
+
+export default useAccessibility
diff --git a/src/play.ts b/src/play.ts
new file mode 100644
index 00000000..d5c1b3f2
--- /dev/null
+++ b/src/play.ts
@@ -0,0 +1,20 @@
+import { state } from "./store/play"
+
+
+function play() {
+ const synth = window.speechSynthesis
+
+ const a = document.querySelector("article")
+ if (!a || !a.textContent) {
+ return
+ }
+
+ const u = new SpeechSynthesisUtterance(a.textContent)
+ const voice = synth.getVoices().find((v) => v.name === state.voice)
+ if (voice) {
+ u.voice = voice
+ }
+ synth.speak(u)
+}
+
+export default play
diff --git a/src/store/play.ts b/src/store/play.ts
new file mode 100644
index 00000000..6cb2d192
--- /dev/null
+++ b/src/store/play.ts
@@ -0,0 +1,13 @@
+import { proxy } from "valtio"
+
+type State = {
+ voice: string
+}
+
+export const state = proxy({
+ voice: "Milena",
+})
+
+export function setVoice(value: string) {
+ state.voice = value
+}
diff --git a/src/store/theme.ts b/src/store/theme.ts
new file mode 100644
index 00000000..69b5384f
--- /dev/null
+++ b/src/store/theme.ts
@@ -0,0 +1,146 @@
+import { proxy, subscribe } from "valtio"
+
+export type ThemeName = "default" | "contrast"
+export type ColorSchemeVariant = "black-on-white" | "white-on-black" | "blue-on-blue" | "brown-on-yellow" | "green-on-brown"
+export type FontTypeVariant = "serif" | "sans-serif"
+
+const MIN_FONT_SIZE = 8
+const MAX_FONT_SIZE = 48
+const CHANGE_FONT_SIZE_DELTA = 1
+
+function clamp(val: number, min: number, max: number): number {
+ if (val < min) {
+ return min
+ }
+ if (val > max) {
+ return max
+ }
+ return val
+}
+
+export type ThemeOptions = {
+ theme: ThemeName
+ fontType: FontTypeVariant
+ fontSize: number
+ lineHeight: number
+ letterSpacing: number
+ showImages: boolean
+ colorScheme: ColorSchemeVariant
+}
+
+export const theme = proxy({
+ theme: "default",
+ fontSize: 14,
+ lineHeight: 1.5,
+ letterSpacing: 1,
+ fontType: "sans-serif",
+ showImages: true,
+ colorScheme: "black-on-white",
+})
+
+export function reset() {
+ theme.fontSize = 14
+ theme.lineHeight = 1.5
+ theme.letterSpacing = 1
+ theme.fontType = "sans-serif"
+ theme.showImages = true
+ theme.colorScheme = "black-on-white"
+}
+
+export function setDefaultTheme() {
+ theme.theme = "default"
+ reset()
+}
+
+export function toggleTheme() {
+ theme.theme = theme.theme === "default" ? "contrast" : "default"
+}
+
+export function increaseFontSize() {
+ theme.fontSize = clamp(theme.fontSize + CHANGE_FONT_SIZE_DELTA, MIN_FONT_SIZE, MAX_FONT_SIZE)
+}
+
+export function decreaseFontSize() {
+ theme.fontSize = clamp(theme.fontSize - CHANGE_FONT_SIZE_DELTA, MIN_FONT_SIZE, MAX_FONT_SIZE)
+}
+
+export function setSansSerifFont() {
+ theme.fontType = "sans-serif"
+}
+
+export function setSerifFont() {
+ theme.fontType = "serif"
+}
+
+export function showImages() {
+ theme.showImages = true
+}
+
+export function hideImages() {
+ theme.showImages = false
+}
+
+export function setBlackOnWhite() {
+ theme.colorScheme = "black-on-white"
+}
+
+export function setWhiteOnBlack() {
+ theme.colorScheme = "white-on-black"
+}
+
+export function setBlueOnBlue() {
+ theme.colorScheme = "blue-on-blue"
+}
+
+export function setBrownOnYellow() {
+ theme.colorScheme = "brown-on-yellow"
+}
+
+export function setGreenOnBrown() {
+ theme.colorScheme = "green-on-brown"
+}
+
+export function setLineHeightOneAndHalf() {
+ theme.lineHeight = 1.5
+}
+
+export function setLineHeightTwo() {
+ theme.lineHeight = 2
+}
+
+export function setLineHeightTwoAndHalf() {
+ theme.lineHeight = 2.5
+}
+
+export function setLetterSpacingOne() {
+ theme.letterSpacing = 1
+}
+
+export function setLetterSpacingTwo() {
+ theme.letterSpacing = 2
+}
+
+export function setLetterSpacingFour() {
+ theme.letterSpacing = 4
+}
+
+const unsubscribe = subscribe(theme, () => {
+ document.documentElement.style.setProperty("--font-size-default", `${theme.fontSize}pt`)
+ document.documentElement.style.setProperty("--font-size-second", `${theme.fontSize}pt`)
+ document.documentElement.style.setProperty("--font-size-accent", `${theme.fontSize}pt`)
+ document.documentElement.style.setProperty("--article-line-height", `${theme.lineHeight}em`)
+ if (theme.letterSpacing === 1) {
+ document.documentElement.style.setProperty("--letter-spacing", "normal")
+ } else {
+ document.documentElement.style.setProperty("--letter-spacing", `${theme.letterSpacing}px`)
+ }
+
+ // TODO remove this from here and move this logic directly to body tag after moving from pages to app
+ document.body.classList.forEach(name => {
+ document.body.classList.remove(name)
+ })
+ if (theme.theme === "contrast") {
+ document.body.classList.add("theme-contrast")
+ document.body.classList.add(theme.colorScheme)
+ }
+})
diff --git a/src/style/style.css b/src/style/style.css
index d112d8da..45c397a9 100644
--- a/src/style/style.css
+++ b/src/style/style.css
@@ -21,6 +21,8 @@
--font-size-h2: 3rem;
--font-size-h3: 1.5rem;
+ --letter-spacing: normal;
+
/* COLOR */
--color-text: hsl(0, 0%, 15%);
--color-text-second: hsl(240, 10%, 60%);
@@ -89,6 +91,7 @@ body {
background-color: var(--color-back-main);
font-family: var(--text-font);
+ letter-spacing: var(--letter-spacing);
}
/* TITLE */
@@ -167,35 +170,16 @@ h3 {
/* CONTRAST THEME */
.theme-contrast {
- /* FONTS */
- --title-font: 'Roboto', Helvetica, sans-serif;
- --text-font: 'Roboto', Helvetica, sans-serif;
-
- /* TEXT COLOR */
- --color-text: black;
- --color-text-second: black;
- --color-text-opposite: white;
-
- --color-back-main: white;
- --color-back: white;
- --color-back-active: hsl(240, 10%, 20%);
- /* --color-back-opposite: hsl(240, 10%, 80%); */
- --color-back-opposite: black;
- --color-link: hsl(240, 10%, 40%);
- --color-link-opposite: hsl(240, 10%, 60%);
-
- --color-hover: white;
- --color-hover-back: black;
+ /*margin-top: 235px;*/
/* EFFECTS */
- /* --radius: 0px; */
--shadow: none;
--shadow-active: none;
/* FONTS */
- --font-size-default: 16pt;
- --font-size-accent: 24pt;
- --font-size-second: 14pt;
+ /*--font-size-default: 16pt;*/
+ /*--font-size-accent: 24pt;*/
+ /*--font-size-second: 14pt;*/
--font-size-h1: 5rem;
--font-size-h2: 3rem;
@@ -207,23 +191,103 @@ h3 {
--border: 5px solid var(--color-back-opposite);
}
-/* CONTRAST + DARK THEME */
-@media (prefers-color-scheme: dark) {
- .theme-contrast {
- /* TEXT COLOR */
- --color-text: white;
- --color-text-second: white;
- --color-text-opposite: black;
-
- --color-back-main: black;
- --color-back: black;
- --color-back-opposite: white;
-
- /* --color-back-active: hsl(240, 10%, 20%); */
- /* --color-link: hsl(240, 10%, 40%); */
- /* --color-link-opposite: hsl(240, 10%, 60%); */
-
- --color-hover: white;
- --color-hover-back: white;
- }
+.theme-contrast.font-sans-serif {
+ --title-font: "Roboto", Helvetica, sans-serif;
+ --text-font: "Roboto", Helvetica, sans-serif;
}
+
+.theme-contrast.font-serif {
+ --title-font: "Times New Roman", serif;
+ --text-font: "Times New Roman", serif;
+}
+
+.theme-contrast.black-on-white {
+ --color-text: black;
+ --color-text-second: black;
+ --color-text-opposite: white;
+ --color-back-main: white;
+ --color-back: white;
+ --color-back-opposite: black;
+
+ --color-hover: white;
+ --color-hover-back: black;
+
+ --color-back-active: black;
+ --color-link: black;
+ --color-link-opposite: white;
+}
+
+.theme-contrast.white-on-black {
+ --color-text: white;
+ --color-text-second: white;
+ --color-text-opposite: black;
+ --color-back-main: black;
+ --color-back: black;
+ --color-back-opposite: white;
+
+ --color-hover: black;
+ --color-hover-back: white;
+
+ /*--color-back-active: hsl(240, 10%, 20%);*/
+ /*--color-link: hsl(240, 10%, 40%);*/
+ /*--color-link-opposite: hsl(240, 10%, 60%);*/
+}
+
+.theme-contrast.blue-on-blue {
+ --color-text: oklch(32% 24% 253deg);
+ --color-text-second: oklch(32% 24% 253deg);
+ --color-text-opposite: oklch(84% 21% 245deg);
+ --color-back-main: oklch(84% 21% 245deg);
+ --color-back: oklch(84% 21% 245deg);
+ --color-back-opposite: oklch(32% 24% 253deg);
+
+ --color-hover: oklch(84% 21% 245deg);
+ --color-hover-back: oklch(32% 24% 253deg);
+
+ /*--color-back-active: hsl(240, 10%, 20%);*/
+ /*--color-link: hsl(240, 10%, 40%);*/
+ /*--color-link-opposite: hsl(240, 10%, 60%);*/
+}
+
+.theme-contrast.brown-on-yellow {
+ --color-text: oklch(41% 3% 92deg);
+ --color-text-second: oklch(41% 3% 92deg);
+ --color-text-opposite: oklch(96% 9% 101deg);
+ --color-back-main: oklch(96% 9% 101deg);
+ --color-back: oklch(96% 9% 101deg);
+ --color-back-opposite: oklch(41% 3% 92deg);
+
+ --color-hover: oklch(96% 9% 101deg);
+ --color-hover-back: oklch(41% 3% 92deg);
+
+ /*--color-back-active: hsl(240, 10%, 20%);*/
+ /*--color-link: hsl(240, 10%, 40%);*/
+ /*--color-link-opposite: hsl(240, 10%, 60%);*/
+}
+
+.theme-contrast.green-on-brown {
+ --color-text: oklch(85% 47% 128deg);
+ --color-text-second: oklch(85% 47% 128deg);
+ --color-text-opposite: oklch(29% 10% 61deg);
+ --color-back-main: oklch(29% 10% 61deg);
+ --color-back: oklch(29% 10% 61deg);
+ --color-back-opposite: oklch(85% 47% 128deg);
+
+ --color-hover: oklch(29% 10% 61deg);
+ --color-hover-back: oklch(85% 47% 128deg);
+
+ /*--color-back-active: hsl(240, 10%, 20%);*/
+ /*--color-link: hsl(240, 10%, 40%);*/
+ /*--color-link-opposite: hsl(240, 10%, 60%);*/
+}
+
+.theme-contrast.no-images img {
+ display: none;
+}
+
+/*.theme-contrast.no-images img:after {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ border: 1px solid black;
+}*/
diff --git a/src/ui/theme/Theme.tsx b/src/ui/theme/Theme.tsx
deleted file mode 100644
index 5cf5bd02..00000000
--- a/src/ui/theme/Theme.tsx
+++ /dev/null
@@ -1,17 +0,0 @@
-import { useLocalStorage } from "react-use"
-import { ThemeContext } from "./ThemeContext"
-import { ThemeType } from "./types"
-
-export type ThemeProps = {
- children?: React.ReactNode
-}
-
-export const Theme: React.FC = ({ children }) => {
- const [theme, setTheme] = useLocalStorage("hudozka-theme", "default")
-
- return (
-
- {children}
-
- )
-}
diff --git a/src/ui/theme/ThemeContext.ts b/src/ui/theme/ThemeContext.ts
deleted file mode 100644
index 2dcb4d68..00000000
--- a/src/ui/theme/ThemeContext.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-import { createContext } from "react"
-import { SetTheme, ThemeType } from "./types"
-
-export const ThemeContext = createContext<[ThemeType, SetTheme]>(["default", () => {}])
diff --git a/src/ui/theme/types.ts b/src/ui/theme/types.ts
deleted file mode 100644
index 8baed57c..00000000
--- a/src/ui/theme/types.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-export type ThemeType = "default" | "contrast"
-export type SetTheme = (theme: ThemeType) => void
diff --git a/src/ui/theme/useTheme.ts b/src/ui/theme/useTheme.ts
deleted file mode 100644
index b3fb9232..00000000
--- a/src/ui/theme/useTheme.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import { useContext } from "react"
-import { ThemeContext } from "./ThemeContext"
-import { SetTheme, ThemeType } from "./types"
-
-export type UseThemeResult = {
- theme: ThemeType
- setTheme: SetTheme
- // toggleTheme:
-}
-
-export function useTheme(): UseThemeResult {
- const [theme, setTheme] = useContext(ThemeContext)
-
- return { theme, setTheme }
-}