add initial accessibility panel

This commit is contained in:
tmshv committed 2025-08-25 19:05:44 +03:00
1 parent 605ff872e8
commit 85c0d8c4ab
15 files changed
+763 -55

No files matched your search

+51 -7
View File
@@ -24,7 +24,8 @@
"sharp": "^0.32.6",
"sitemap": "^1.5.0",
"typograf": "^6.11.1",
"use-media": "^1.4.0"
"use-media": "^1.4.0",
"valtio": "^2.1.7"
},
"devDependencies": {
"@eslint/eslintrc": "^3.3.1",
@@ -1268,13 +1269,13 @@
"version": "15.7.5",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz",
"integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==",
"dev": true
"devOptional": true
},
"node_modules/@types/react": {
"version": "18.0.18",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.18.tgz",
"integrity": "sha512-6hI08umYs6NaiHFEEGioXnxJ+oEhY3eRz8VCUaudZmGdtvPviCJB8mgaMxaDWAdPSYd4eFavrPk2QIolwbLYrg==",
"dev": true,
"devOptional": true,
"dependencies": {
"@types/prop-types": "*",
"@types/scheduler": "*",
@@ -1285,7 +1286,7 @@
"version": "0.16.2",
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz",
"integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==",
"dev": true
"devOptional": true
},
"node_modules/@types/sharp": {
"version": "0.29.2",
@@ -4735,6 +4736,12 @@
"react-is": "^16.13.1"
}
},
"node_modules/proxy-compare": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/proxy-compare/-/proxy-compare-3.0.1.tgz",
"integrity": "sha512-V9plBAt3qjMlS1+nC8771KNf6oJ12gExvaxnNzN/9yVRLdTv/lc+oJlnSzrdYDAvBfTStPCoiaCOTmTs0adv7Q==",
"license": "MIT"
},
"node_modules/pump": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
@@ -5911,6 +5918,30 @@
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
},
"node_modules/valtio": {
"version": "2.1.7",
"resolved": "https://registry.npmjs.org/valtio/-/valtio-2.1.7.tgz",
"integrity": "sha512-DwJhCDpujuQuKdJ2H84VbTjEJJteaSmqsuUltsfbfdbotVfNeTE4K/qc/Wi57I9x8/2ed4JNdjEna7O6PfavRg==",
"license": "MIT",
"dependencies": {
"proxy-compare": "^3.0.1"
},
"engines": {
"node": ">=12.20.0"
},
"peerDependencies": {
"@types/react": ">=18.0.0",
"react": ">=18.0.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"react": {
"optional": true
}
}
},
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@@ -6778,13 +6809,13 @@
"version": "15.7.5",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz",
"integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==",
"dev": true
"devOptional": true
},
"@types/react": {
"version": "18.0.18",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.18.tgz",
"integrity": "sha512-6hI08umYs6NaiHFEEGioXnxJ+oEhY3eRz8VCUaudZmGdtvPviCJB8mgaMxaDWAdPSYd4eFavrPk2QIolwbLYrg==",
"dev": true,
"devOptional": true,
"requires": {
"@types/prop-types": "*",
"@types/scheduler": "*",
@@ -6795,7 +6826,7 @@
"version": "0.16.2",
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz",
"integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==",
"dev": true
"devOptional": true
},
"@types/sharp": {
"version": "0.29.2",
@@ -9131,6 +9162,11 @@
"react-is": "^16.13.1"
}
},
"proxy-compare": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/proxy-compare/-/proxy-compare-3.0.1.tgz",
"integrity": "sha512-V9plBAt3qjMlS1+nC8771KNf6oJ12gExvaxnNzN/9yVRLdTv/lc+oJlnSzrdYDAvBfTStPCoiaCOTmTs0adv7Q=="
},
"pump": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
@@ -9951,6 +9987,14 @@
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
},
"valtio": {
"version": "2.1.7",
"resolved": "https://registry.npmjs.org/valtio/-/valtio-2.1.7.tgz",
"integrity": "sha512-DwJhCDpujuQuKdJ2H84VbTjEJJteaSmqsuUltsfbfdbotVfNeTE4K/qc/Wi57I9x8/2ed4JNdjEna7O6PfavRg==",
"requires": {
"proxy-compare": "^3.0.1"
}
},
"which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+2 -1
View File
@@ -34,7 +34,8 @@
"sharp": "^0.32.6",
"sitemap": "^1.5.0",
"typograf": "^6.11.1",
"use-media": "^1.4.0"
"use-media": "^1.4.0",
"valtio": "^2.1.7"
},
"devDependencies": {
"@eslint/eslintrc": "^3.3.1",
-3
View File
@@ -8,7 +8,6 @@ import App from "next/app"
import Head from "next/head"
import Script from "next/script"
import { YMetrika } from "src/components/YMetrika"
import { Theme } from "@/ui/theme/Theme"
import { ThemeColor } from "@/ui/ThemeColor"
export default class MyApp extends App {
@@ -51,9 +50,7 @@ export default class MyApp extends App {
</Head>
<Script src="https://culturaltracking.ru/static/js/spxl.js?pixelId=27154" data-pixel-id="27154"></Script>
<Theme>
<Component {...pageProps} />
</Theme>
</>
)
}
+5 -6
View File
@@ -1,19 +1,18 @@
import { theme, toggleTheme } from "@/store/theme"
import { Button } from "@/ui/Button"
import { HiOutlineEye, HiOutlineEyeOff } from "react-icons/hi"
import { useTheme } from "@/ui/theme/useTheme"
import { useSnapshot } from "valtio"
export function AccessibilityButton() {
const { theme, setTheme } = useTheme()
const t = useSnapshot(theme)
return (
<Button
theme="icon"
size="default"
onClick={() => {
setTheme(theme === "default" ? "contrast" : "default")
}}
onClick={toggleTheme}
>
{theme === "default" ? (
{t.theme === "default" ? (
<HiOutlineEyeOff size={24} />
) : (
<HiOutlineEye size={24} />
@@ -0,0 +1,61 @@
import { decreaseFontSize, hideImages, increaseFontSize, reset, setBlackOnWhite, setBlueOnBlue, setBrownOnYellow, setDefaultTheme, setGreenOnBrown, setLetterSpacingOne, setLetterSpacingTwo, setLetterSpacingFour, setLineHeightOneAndHalf, setLineHeightTwo, setLineHeightTwoAndHalf, setSansFont, setSerifFont, setWhiteOnBlack, showImages } from "@/store/theme"
import s from "./panel.module.css"
function AccessibilityPanel() {
return ( <div className={s.viewsettings}>
<div className={s.row}>
<div className={s.block}>
<p>Размер шрифта:</p>
<a onClick={decreaseFontSize} title="уменьшить размер шрифта">A-</a>
<a onClick={increaseFontSize} title="увеличить размер шрифта"><strong>A+</strong></a>
</div>
<div className={s.block}>
<p>Изображения:</p>
<a onClick={showImages}>Включить</a>
<a onClick={hideImages}>Выключить</a>
</div>
<div className={s.block}>
<p>Цвет сайта:</p>
<a onClick={setBlackOnWhite} title="черные буквы на белом фоне" style={{color: "black", backgroundColor: "white"}}>Ц</a>
<a onClick={setWhiteOnBlack} title="белые буквы на черном фоне" style={{color: "white", backgroundColor: "black"}}>Ц</a>
<a onClick={setBlueOnBlue} title="синии буквы на голубом фоне" style={{color: "oklch(32% 24% 253deg)", backgroundColor: "oklch(84% 21% 245deg)"}}>Ц</a>
<a onClick={setBrownOnYellow} title="коричневые буквы на желтом фоне" style={{color: "oklch(41% 3% 92deg)", backgroundColor: "oklch(96% 9% 101deg)"}}>Ц</a>
<a onClick={setGreenOnBrown} title="зеленые буквы на коричневом фоне" style={{color: "oklch(85% 47% 128deg)", backgroundColor: "oklch(29% 10% 61deg)"}}>Ц</a>
</div>
<div className={s.block}>
<p>Шрифт:</p>
<a onClick={setSansFont}>С засечками</a>
<a onClick={setSerifFont}>Без засечек</a>
{/*<a id="btn-on-braille" href="javascript:void(0)" onClick="smSetting('fontBraille', 'brailleOn')" title="только для печати">Брайля</a>*/}
{/*<a id="btn-off-braille" href="javascript:void(0)" onClick="smSetting('fontBraille', 'brailleOff')">Обычный</a>*/}
</div>
<div className={s.block}>
<p>Высота строк:</p>
<a onClick={setLineHeightOneAndHalf}>1.5</a>
<a onClick={setLineHeightTwo}>2.0</a>
<a onClick={setLineHeightTwoAndHalf}>2.5</a>
</div>
<div className={s.block}>
<p>Межбуквенный интервал:</p>
<a onClick={setLetterSpacingOne}>1</a>
<a onClick={setLetterSpacingTwo}>2</a>
<a onClick={setLetterSpacingFour}>4</a>
</div>
</div>
<div className={s.row}>
<a onClick={reset}><i className="fa fa-cog fa-fw"></i>Сброс настроек</a>
{/*<a id="mobile-version" href="?mobile=1"><i className="fa fa-mobile fa-fw"></i> Мобильная версия</a>*/}
<a onClick={setDefaultTheme}><i className="fa fa-eye-slash fa-fw"></i>Обычная версия</a>
{/*<a id="_playlist" href="javascript:void(0)" onClick="playSound('all', this);"><i className="fa fa-play fa-fw"></i>Прослушать всё</a>*/}
</div>
</div>
)
}
export default AccessibilityPanel
@@ -0,0 +1,433 @@
/*body {
margin: 0 auto;
padding: 0;
width: 96%;
padding-top: 90px;
}*/
.viewsettings {
background: white;
z-index: 10;
display: block;
/*width: 96%;*/
font-size: 20px !important;
line-height: 30px;
font-family: sans-serif, arial;
letter-spacing: normal !important;
position: fixed;
top: 0;
margin: 10px;
text-align: center;
border: 2px solid;
/*border-right: 2px solid;
border-bottom: 2px solid;
border-bottom-right-radius: 25px;
border-bottom-left-radius: 25px;*/
}
.viewsettings .row {
display: flex;
flex-wrap: wrap;
gap: 8px;
justify-content: center;
margin: 8px 0;
}
.viewsettings p {
margin: 0;
}
.viewsettings a {
border: 1px solid;
text-decoration: none;
padding: 4px 10px;
cursor: pointer;
color: black;
}
.viewsettings a:hover {
background-color: #cfcfcf !important;
color: #000 !important;
}
.viewsettings a:focus {
outline: 1px solid !important;
}
.viewsettings .block {
display: inline-block;
vertical-align: top;
line-height: 30px;
border: 2px solid;
padding: 6px 10px;
margin: 2px;
}
.viewsettings .block a {
margin-right: 8px;
}
#mainContent {
padding-top: 60px;
}
.feedback label {
display: block;
margin: 16px 0 0 0;
}
.feedback input {
width: 50%;
}
.feedback textarea {
width: 50%;
height: 100px;
}
.feedback input[name="agree"] {
display: block;
width: 100px;
}
#captcha {
display: none;
}
body.line_1_5 {
line-height: 1.5;
}
body.line_2_0 {
line-height: 2;
}
body.line_2_5 {
line-height: 2.5;
}
.letter_spacing1 {
letter-spacing: normal;
}
.letter_spacing2 {
letter-spacing: 2px;
}
.letter_spacing4 {
letter-spacing: 4px;
}
#btn-black-text {
background-color: white;
color: black;
}
#btn-white-text {
background-color: black;
color: white;
}
#btn-blue-text {
background-color: #9dd1ff;
color: #063462;
}
#btn-sand-text {
background-color: #f7f3d6;
color: #4d4b43;
}
#btn-brown-text {
background-color: #3b2716;
color: #a9e44d;
}
body.imageOff #btn-hide-images,
body.imageOff img {
display: none;
}
body.imageOn #btn-show-images {
display: none;
}
body.brailleOff #btn-off-braille {
display: none;
}
body.brailleOn #btn-on-braille {
display: none;
}
body.brailleOn {
font-family: "Kanischev_Braille" !important;
}
body.noSerif #btn-font-sans {
display: none;
}
body.noSerif {
font-family: sans-serif, Arial;
}
body.sans #btn-font-serif {
display: none;
}
body.sans {
font-family: serif, "Times New Roman";
}
body.BlackOnWhite {
background-color: white;
color: black;
}
body.BlackOnWhite a:link,
body.BlackOnWhite a:visited,
body.BlackOnWhite .viewsettings .block,
body.BlackOnWhite .viewsettings,
body.BlackOnWhite .additional_text,
body.BlackOnWhite .img-wrp::after,
body.BlackOnWhite .date {
color: black;
}
body.BlackOnWhite a#active {
color: white;
background-color: black;
}
body.BlackOnWhite .image_placeholder {
border: 1px solid black;
}
body.BlackOnWhite a:focus,
body.BlackOnWhite input:focus,
body.BlackOnWhite textarea:focus,
body.BlackOnWhite #attach_cont:focus,
body.BlackOnWhite button:focus {
outline: #000 solid 5px;
}
body.BlackOnWhite button,
body.BlackOnWhite input,
body.BlackOnWhite textarea {
background-color: #fff;
color: #000;
border: 1px solid #000;
}
body.WhiteOnBlack {
background-color: black;
color: white;
}
body.WhiteOnBlack a:link,
body.WhiteOnBlack a:visited,
body.WhiteOnBlack .viewsettings .block,
body.WhiteOnBlack .viewsettings,
body.WhiteOnBlack .additional_text,
body.WhiteOnBlack .img-wrp::after,
body.WhiteOnBlack .date {
color: white;
}
body.WhiteOnBlack a#active {
color: black;
background-color: white;
}
body.WhiteOnBlack .image_placeholder {
border: 1px solid white;
}
body.WhiteOnBlack a:focus,
body.WhiteOnBlack input:focus,
body.WhiteOnBlack textarea:focus,
body.WhiteOnBlack #attach_cont:focus,
body.WhiteOnBlack button:focus {
outline: #fff solid 5px;
}
body.WhiteOnBlack button,
body.BlackOnWhite input,
body.BlackOnWhite textarea {
background-color: #000;
color: #fff;
border: 1px solid #fff;
}
body.BlueOnBlue {
background-color: #9dd1ff;
color: #063462;
}
body.BlueOnBlue a:link,
body.BlueOnBlue a:visited,
body.BlueOnBlue .viewsettings .block,
body.BlueOnBlue .viewsettings,
body.BlueOnBlue .additional_text,
body.BlueOnBlue .img-wrp::after,
body.BlueOnBlue .date {
color: #063462;
}
body.BlueOnBlue a#active {
color: #9dd1ff;
background-color: #063462;
}
body.BlueOnBlue .image_placeholder {
border: 1px solid #063462;
}
body.BlueOnBlue a:focus,
body.BlueOnBlue input:focus,
body.BlueOnBlue textarea:focus,
body.BlueOnBlue #attach_cont:focus,
body.BlueOnBlue button:focus {
outline: #063462 solid 5px;
}
body.BlueOnBlue button,
body.BlueOnBlue input,
body.BlueOnBlue textarea {
background-color: #9dd1ff;
color: #063462;
border: 1px solid #063462;
}
body.SandTheme {
background-color: #f7f3d6;
color: #4d4b43;
}
body.SandTheme a:link,
body.SandTheme a:visited,
body.SandTheme .viewsettings .block,
body.SandTheme .viewsettings,
body.SandTheme .additional_text,
body.SandTheme .img-wrp::after,
body.SandTheme .date {
color: #4d4b43;
}
body.SandTheme a#active {
color: #f7f3d6;
background-color: #4d4b43;
}
body.SandTheme .image_placeholder {
border: 1px solid #4d4b43;
}
body.SandTheme a:focus,
body.SandTheme input:focus,
body.SandTheme textarea:focus,
body.SandTheme #attach_cont:focus,
body.SandTheme button:focus {
outline: #4d4b43 solid 5px;
}
body.SandTheme button,
body.SandTheme input,
body.SandTheme textarea {
background-color: #f7f3d6;
color: #4d4b43;
border: 1px solid #4d4b43;
}
body.BrownTheme {
background-color: #3b2716;
color: #a9e44d;
}
body.BrownTheme a:link,
body.BrownTheme a:visited,
body.BrownTheme .viewsettings .block,
body.BrownTheme .viewsettings,
body.BrownTheme .additional_text,
body.BrownTheme .img-wrp::after,
body.BrownTheme .date {
color: #a9e44d;
}
body.BrownTheme a#active {
color: #3b2716;
background-color: #a9e44d;
}
body.BrownTheme .image_placeholder {
border: 1px solid #a9e44d;
}
body.BrownTheme a:focus,
body.BrownTheme input:focus,
body.BrownTheme textarea:focus,
body.BrownTheme #attach_cont:focus,
body.BrownTheme button:focus {
outline: #a9e44d solid 5px;
}
body.BrownTheme button,
body.BrownTheme input,
body.BrownTheme textarea {
background-color: #3b2716;
color: #a9e44d;
border: 1px solid #a9e44d;
}
ul#navmenu-h li a {
padding: 0 6px 0 6px;
}
.news_list_item strong {
padding-bottom: 4px;
padding-right: 8px;
}
.date {
display: block;
}
.s-on {
background-position: 0 0;
}
.s-of {
background-position: 0 30px;
}
.image_alt {
width: 144px;
height: 144px;
border: 1px solid black;
font-size: 18px;
overflow: hidden;
}
.gallery_album_thumbail {
display: block;
max-width: 150px;
max-height: 150px;
overflow: hidden;
word-wrap: break-word;
}
ul#navmenu-h li a {
text-decoration: none;
}
ul#navmenu-h li a:hover {
text-decoration: underline;
}
.additional_text {
margin: 0;
padding: 0;
font-size: 70%;
font-weight: normal;
}
.image_placeholder {
font-size: 70%;
font-weight: normal;
margin: 5px;
text-align: center;
word-wrap: break-word;
overflow: hidden;
min-width: 100px;
min-height: 100px;
}
.play-button {
display: block;
min-width: 30px;
min-height: 30px;
cursor: pointer;
text-align: right;
}
#playlist {
float: right;
display: block;
font-size: inherit;
}
._afisha_table_content {
width: 100%;
}
.img-wrp {
display: inline-block;
position: relative;
}
.img-wrp::after {
content: attr(data-alt);
bottom: 0;
left: 0;
width: 100%;
z-index: 2;
font-size: 70%;
font-style: italic;
text-align: center;
}
+3 -2
View File
@@ -10,7 +10,6 @@ import { useMobile } from "src/hooks/useMobile"
import { Navigation } from "@/components/Navigation"
import { Box } from "@/ui/Box"
import { AccessibilityButton } from "../AccessibilityButton"
import { useTheme } from "@/ui/theme/useTheme"
import { Copyright } from "../Copyright"
import { Spacer } from "../Spacer"
import { Author } from "../Author"
@@ -22,6 +21,7 @@ import dynamic from "next/dynamic"
import qr0 from "src/assets/qr00.png"
import qr1 from "src/assets/qr01.png"
import Image from "next/image"
import AccessibilityPanel from "../AccessibilityPanel"
const Gosuslugi = dynamic(import("../Gosuslugi").then(module => module.Gosuslugi), {
ssr: false,
@@ -41,7 +41,6 @@ export const App: React.FC<AppProps> = ({ showAuthor = false, menu, ...props })
const blockStyle = {
justifyContent: "center",
}
const { theme, setTheme } = useTheme()
return (
<ConfigContext.Provider value={{
@@ -146,6 +145,8 @@ export const App: React.FC<AppProps> = ({ showAuthor = false, menu, ...props })
</Footer>
)}
>
<AccessibilityPanel/>
<Box style={blockStyle}>
<Content style={props.contentStyle}>
{props.children}
+4 -3
View File
@@ -1,7 +1,8 @@
import s from "./wrapper.module.css"
import cx from "classnames"
import { useTheme } from "@/ui/theme/useTheme"
import { useSnapshot } from "valtio"
import { theme } from "@/store/theme"
export type WrapperProps = {
header: React.ReactNode
@@ -12,10 +13,10 @@ export type WrapperProps = {
}
export const Wrapper: React.FC<WrapperProps> = props => {
const { theme } = useTheme()
const t = useSnapshot(theme)
return (
<div className={cx(s.container, `theme-${theme}`)} style={props.style}>
<div className={cx(s.container, `theme-${t.theme}`, !t.showImages ? "no-images" : null)} style={props.style}>
{props.header}
<main className={s.main} style={props.mainStyle}>
+162
View File
@@ -0,0 +1,162 @@
import { proxy, subscribe, useSnapshot } 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"
const MIN_FONT_SIZE = 8
const MAX_FONT_SIZE = 48
const CHANGE_FONT_SIZE_DELTA = 0.2
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: string
fontSize: number
lineHeight: number
letterSpacing: number
showImages: boolean
colorScheme: ColorSchemeVariant
}
export const theme = proxy<ThemeOptions>({
theme: "default",
fontSize: 14,
lineHeight: 1.5,
letterSpacing: 1,
fontType: "Times New Roman",
showImages: true,
colorScheme: "black-on-white",
})
export function reset() {
theme.fontSize = 14
theme.lineHeight = 1.5
theme.letterSpacing = 1
theme.fontType = "Times New Roman"
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 setSerifFont() {
theme.fontType = "sans-serif"
}
export function setSansFont() {
theme.fontType = "Times New Roman"
}
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
}
function getColor(scheme: ColorSchemeVariant): [string, string] {
switch (theme.colorScheme) {
case "black-on-white":
return ["black", "white"]
case "white-on-black":
return ["white", "black"]
case "blue-on-blue":
return ["oklch(32% 24% 253deg)", "oklch(84% 21% 245deg)"]
case "brown-on-yellow":
return ["oklch(41% 3% 92deg)", "oklch(96% 9% 101deg)"]
case "green-on-brown":
return ["oklch(85% 47% 128deg)", "oklch(29% 10% 61deg)"]
default:
return ["black", "white"]
}
}
const unsubscribe = subscribe(theme, () => {
document.documentElement.style.setProperty("--font-size-default", `${theme.fontSize}pt`)
document.documentElement.style.setProperty("--title-font", theme.fontType)
document.documentElement.style.setProperty("--text-font", theme.fontType)
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`)
}
const [t, b] = getColor(theme.colorScheme)
document.documentElement.style.setProperty("--color-text", t)
document.documentElement.style.setProperty("--color-text-second", t)
document.documentElement.style.setProperty("--color-text-opposite", b)
document.documentElement.style.setProperty("--color-back-main", b)
document.documentElement.style.setProperty("--color-back", b)
})
+23 -9
View File
@@ -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 */
@@ -168,21 +171,19 @@ h3 {
/* CONTRAST THEME */
.theme-contrast {
/* FONTS */
--title-font: 'Roboto', Helvetica, sans-serif;
--text-font: 'Roboto', Helvetica, sans-serif;
/*--title-font: "Roboto", Helvetica, sans-serif;*/
/*--text-font: "Roboto", Helvetica, sans-serif;*/
/* TEXT COLOR */
--color-text: black;
/*--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-link-opposite: hsl(240, 10%, 60%);*/
--color-hover: white;
--color-hover-back: black;
@@ -193,9 +194,9 @@ h3 {
--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,6 +208,19 @@ h3 {
--border: 5px solid var(--color-back-opposite);
}
.theme-contrast.no-images img {
display: none;
/*opacity: 0.5;*/
/*position: relative;*/
}
/*.theme-contrast.no-images img:after {
position: absolute;
width: 100%;
height: 100%;
border: 1px solid black;
}*/
/* CONTRAST + DARK THEME */
@media (prefers-color-scheme: dark) {
.theme-contrast {
-17
View File
@@ -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<ThemeProps> = ({ children }) => {
const [theme, setTheme] = useLocalStorage<ThemeType>("hudozka-theme", "default")
return (
<ThemeContext.Provider value={[theme ?? "default", setTheme]}>
{children}
</ThemeContext.Provider>
)
}
+3 -2
View File
@@ -1,4 +1,5 @@
import { createContext } from "react"
import { SetTheme, ThemeType } from "./types"
import { DEFAULT } from "./const"
import { SetTheme, ThemeOptions } from "./types"
export const ThemeContext = createContext<[ThemeType, SetTheme]>(["default", () => {}])
export const ThemeContext = createContext<[ThemeOptions, SetTheme]>([DEFAULT, () => {}])
+5
View File
@@ -0,0 +1,5 @@
import { ThemeOptions } from "./types"
export const DEFAULT: ThemeOptions = {
theme: "default",
}
+5 -2
View File
@@ -1,2 +1,5 @@
export type ThemeType = "default" | "contrast"
export type SetTheme = (theme: ThemeType) => void
export type ThemeOptions = {
theme: "default" | "contrast"
fontSize?: number
}
export type SetTheme = (theme: ThemeOptions["theme"]) => void
+6 -3
View File
@@ -1,9 +1,9 @@
import { useContext } from "react"
import { ThemeContext } from "./ThemeContext"
import { SetTheme, ThemeType } from "./types"
import { SetTheme, ThemeOptions } from "./types"
export type UseThemeResult = {
theme: ThemeType
theme: ThemeOptions["theme"]
setTheme: SetTheme
// toggleTheme:
}
@@ -11,5 +11,8 @@ export type UseThemeResult = {
export function useTheme(): UseThemeResult {
const [theme, setTheme] = useContext(ThemeContext)
return { theme, setTheme }
return {
setTheme,
theme: theme.theme ?? "default",
}
}