erudit 2.0.0-dev.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (314) hide show
  1. package/.nuxtrc +1 -0
  2. package/app/app.vue +172 -0
  3. package/app/assets/icons/alert.svg +3 -0
  4. package/app/assets/icons/angle-right.svg +3 -0
  5. package/app/assets/icons/arrow-in-text.svg +3 -0
  6. package/app/assets/icons/arrow-left.svg +3 -0
  7. package/app/assets/icons/arrow-up-to-right.svg +3 -0
  8. package/app/assets/icons/aside-open.svg +3 -0
  9. package/app/assets/icons/asterisk.svg +3 -0
  10. package/app/assets/icons/book-question.svg +3 -0
  11. package/app/assets/icons/book.svg +3 -0
  12. package/app/assets/icons/bug.svg +3 -0
  13. package/app/assets/icons/chip.svg +3 -0
  14. package/app/assets/icons/circle-help.svg +3 -0
  15. package/app/assets/icons/construction.svg +3 -0
  16. package/app/assets/icons/cross.svg +3 -0
  17. package/app/assets/icons/draw.svg +3 -0
  18. package/app/assets/icons/ellipsis-vertical.svg +3 -0
  19. package/app/assets/icons/file-check.svg +3 -0
  20. package/app/assets/icons/file-lines.svg +3 -0
  21. package/app/assets/icons/file-star.svg +3 -0
  22. package/app/assets/icons/folder-open.svg +3 -0
  23. package/app/assets/icons/folder.svg +3 -0
  24. package/app/assets/icons/globe.svg +3 -0
  25. package/app/assets/icons/house.svg +3 -0
  26. package/app/assets/icons/link-external.svg +3 -0
  27. package/app/assets/icons/link.svg +3 -0
  28. package/app/assets/icons/moon.svg +3 -0
  29. package/app/assets/icons/outline/book.svg +3 -0
  30. package/app/assets/icons/outline/file-lines.svg +3 -0
  31. package/app/assets/icons/pirate.svg +3 -0
  32. package/app/assets/icons/plus-circle.svg +3 -0
  33. package/app/assets/icons/plus.svg +3 -0
  34. package/app/assets/icons/puzzle.svg +3 -0
  35. package/app/assets/icons/search.svg +3 -0
  36. package/app/assets/icons/shuffle.svg +3 -0
  37. package/app/assets/icons/star.svg +3 -0
  38. package/app/assets/icons/sun-moon.svg +3 -0
  39. package/app/assets/icons/sun.svg +3 -0
  40. package/app/assets/icons/user.svg +3 -0
  41. package/app/assets/icons/users.svg +3 -0
  42. package/app/components/Loading.vue +23 -0
  43. package/app/components/SiteAside.vue +382 -0
  44. package/app/components/SiteMain.vue +35 -0
  45. package/app/components/ads/BannerTemplate.vue +51 -0
  46. package/app/components/ads/BottomBanner.vue +45 -0
  47. package/app/components/ads/LeftBanner.vue +50 -0
  48. package/app/components/aside/AsideListItem.vue +74 -0
  49. package/app/components/aside/AsideMajor.vue +56 -0
  50. package/app/components/aside/AsideMinor.vue +71 -0
  51. package/app/components/aside/major/PaneContentScroll.vue +23 -0
  52. package/app/components/aside/major/PaneSwitch.vue +54 -0
  53. package/app/components/aside/major/PaneSwitchButton.vue +63 -0
  54. package/app/components/aside/major/SiteInfo.vue +85 -0
  55. package/app/components/aside/major/panes/Language.vue +79 -0
  56. package/app/components/aside/major/panes/Pages.vue +34 -0
  57. package/app/components/aside/major/panes/Search.vue +3 -0
  58. package/app/components/aside/major/panes/nav/Nav.vue +91 -0
  59. package/app/components/aside/major/panes/nav/NavBook.vue +86 -0
  60. package/app/components/aside/major/panes/nav/NavBookLoading.vue +24 -0
  61. package/app/components/aside/major/panes/nav/NavGlobal.vue +16 -0
  62. package/app/components/aside/major/panes/nav/fnav/FNav.vue +105 -0
  63. package/app/components/aside/major/panes/nav/fnav/FNavBook.vue +32 -0
  64. package/app/components/aside/major/panes/nav/fnav/FNavFlags.vue +40 -0
  65. package/app/components/aside/major/panes/nav/fnav/FNavFolder.vue +60 -0
  66. package/app/components/aside/major/panes/nav/fnav/FNavItem.vue +34 -0
  67. package/app/components/aside/major/panes/nav/fnav/FNavSeparator.vue +80 -0
  68. package/app/components/aside/major/panes/nav/fnav/FNavTopic.vue +24 -0
  69. package/app/components/aside/major/panes/other/ItemContent.vue +29 -0
  70. package/app/components/aside/major/panes/other/ItemGenerator.vue +15 -0
  71. package/app/components/aside/major/panes/other/ItemTheme.vue +54 -0
  72. package/app/components/aside/major/panes/other/Other.vue +16 -0
  73. package/app/components/aside/minor/AsideMinorContributor.vue +5 -0
  74. package/app/components/aside/minor/AsideMinorNews.vue +11 -0
  75. package/app/components/aside/minor/AsideMinorPane.vue +16 -0
  76. package/app/components/aside/minor/AsideMinorTopLink.vue +67 -0
  77. package/app/components/aside/minor/Contribute.vue +145 -0
  78. package/app/components/aside/minor/content/AsideMinorContent.vue +92 -0
  79. package/app/components/aside/minor/topic/AsideMinorTopic.vue +32 -0
  80. package/app/components/aside/minor/topic/TopicContributors.vue +177 -0
  81. package/app/components/aside/minor/topic/TopicNav.vue +49 -0
  82. package/app/components/aside/minor/topic/TopicToc.vue +202 -0
  83. package/app/components/aside/minor/topic/TopicTocItem.vue +31 -0
  84. package/app/components/aside/utils/AsideOverlayPane.vue +40 -0
  85. package/app/components/bitran/BitranContent.vue +64 -0
  86. package/app/components/bitran/RenderWrapper.vue +12 -0
  87. package/app/components/contributor/ContributorAvatar.vue +43 -0
  88. package/app/components/contributor/ContributorListItem.vue +35 -0
  89. package/app/components/main/topic/MainTopic.vue +79 -0
  90. package/app/components/main/topic/TopicPartSwitch.vue +118 -0
  91. package/app/components/main/utils/Breadcrumb.vue +75 -0
  92. package/app/components/main/utils/ContentDecoration.vue +29 -0
  93. package/app/components/main/utils/ContentDescription.vue +20 -0
  94. package/app/components/main/utils/ContentFlag.vue +16 -0
  95. package/app/components/main/utils/ContentPopover.vue +176 -0
  96. package/app/components/main/utils/ContentPopovers.vue +105 -0
  97. package/app/components/main/utils/ContentReferences.vue +76 -0
  98. package/app/components/main/utils/ContentSection.vue +42 -0
  99. package/app/components/main/utils/ContentTitle.vue +37 -0
  100. package/app/components/main/utils/reference/ReferenceGroup.vue +41 -0
  101. package/app/components/main/utils/reference/ReferenceItem.vue +64 -0
  102. package/app/components/main/utils/reference/ReferenceSource.vue +110 -0
  103. package/app/components/preview/Preview.vue +177 -0
  104. package/app/components/preview/PreviewDisplay.vue +139 -0
  105. package/app/components/preview/PreviewFooterAction.vue +73 -0
  106. package/app/components/preview/PreviewLoading.vue +15 -0
  107. package/app/components/preview/PreviewScreen.vue +99 -0
  108. package/app/components/preview/display/Alert.vue +50 -0
  109. package/app/components/preview/display/Custom.vue +18 -0
  110. package/app/components/preview/display/GenericLink.vue +48 -0
  111. package/app/components/preview/display/PageLink.vue +20 -0
  112. package/app/components/preview/display/Unique.vue +49 -0
  113. package/app/components/transition/Fade.vue +22 -0
  114. package/app/components/tree/TreeContainer.vue +12 -0
  115. package/app/components/tree/TreeItem.vue +89 -0
  116. package/app/composables/bitran.ts +132 -0
  117. package/app/composables/bitranContent.ts +36 -0
  118. package/app/composables/bitranLocation.ts +7 -0
  119. package/app/composables/contentData.ts +36 -0
  120. package/app/composables/contentPage.ts +156 -0
  121. package/app/composables/contentRoute.ts +45 -0
  122. package/app/composables/darkMagic.ts +24 -0
  123. package/app/composables/externalApi.ts +63 -0
  124. package/app/composables/favicon.ts +8 -0
  125. package/app/composables/formatText.ts +86 -0
  126. package/app/composables/majorPane.ts +60 -0
  127. package/app/composables/phrases.ts +80 -0
  128. package/app/composables/theme.ts +29 -0
  129. package/app/composables/url.ts +33 -0
  130. package/app/pages/_test/preview.vue +110 -0
  131. package/app/pages/article/[...articleId].vue +3 -0
  132. package/app/pages/book/[...bookId].vue +47 -0
  133. package/app/pages/group/[...groupId].vue +64 -0
  134. package/app/pages/index.vue +32 -0
  135. package/app/pages/members.vue +7 -0
  136. package/app/pages/practice/[...practice].vue +3 -0
  137. package/app/pages/summary/[...summaryId].vue +3 -0
  138. package/app/public/favicon/article.svg +10 -0
  139. package/app/public/favicon/default.svg +10 -0
  140. package/app/public/favicon/practice.svg +10 -0
  141. package/app/public/favicon/summary.svg +10 -0
  142. package/app/public/logotype.svg +17 -0
  143. package/app/public/og-default.png +0 -0
  144. package/app/public/user.svg +10 -0
  145. package/app/scripts/_immediate.js +4 -0
  146. package/app/scripts/aside/index.ts +59 -0
  147. package/app/scripts/aside/major/nav.ts +26 -0
  148. package/app/scripts/aside/minor/state.ts +37 -0
  149. package/app/scripts/aside/minor/topic.ts +3 -0
  150. package/app/scripts/flag.ts +28 -0
  151. package/app/scripts/og.ts +27 -0
  152. package/app/scripts/preview/build.ts +84 -0
  153. package/app/scripts/preview/data/alert.ts +19 -0
  154. package/app/scripts/preview/data/custom.ts +8 -0
  155. package/app/scripts/preview/data/genericLink.ts +24 -0
  156. package/app/scripts/preview/data/pageLink.ts +20 -0
  157. package/app/scripts/preview/data/unique.ts +70 -0
  158. package/app/scripts/preview/data.ts +26 -0
  159. package/app/scripts/preview/display.ts +39 -0
  160. package/app/scripts/preview/footer.ts +9 -0
  161. package/app/scripts/preview/request.ts +51 -0
  162. package/app/scripts/preview/state.ts +63 -0
  163. package/app/styles/_immediate.css +3 -0
  164. package/app/styles/_util.scss +50 -0
  165. package/app/styles/_utils.scss +44 -0
  166. package/app/styles/app.scss +91 -0
  167. package/app/styles/def/_bp.scss +24 -0
  168. package/app/styles/def/_size.scss +7 -0
  169. package/app/styles/def/_z.scss +5 -0
  170. package/app/styles/default.scss +85 -0
  171. package/app/styles/normalize.scss +63 -0
  172. package/app/styles/partials/_darkMagic.scss +7 -0
  173. package/app/styles/partials/_fnav.scss +18 -0
  174. package/app/styles/partials/_preview.scss +7 -0
  175. package/globalPath.ts +24 -0
  176. package/globals/bitran.ts +39 -0
  177. package/globals/content.ts +22 -0
  178. package/globals/contributor.ts +5 -0
  179. package/globals/erudit.ts +5 -0
  180. package/globals/register.ts +18 -0
  181. package/languages/en.ts +95 -0
  182. package/languages/ru.ts +99 -0
  183. package/module/bitran.ts +34 -0
  184. package/module/config.ts +34 -0
  185. package/module/imports.ts +46 -0
  186. package/module/index.ts +35 -0
  187. package/module/logger.ts +10 -0
  188. package/module/paths.ts +22 -0
  189. package/module/restart.ts +61 -0
  190. package/nuxt.config.ts +96 -0
  191. package/package.json +32 -0
  192. package/server/api/aside/major/nav/bookIds.ts +5 -0
  193. package/server/api/aside/major/nav/bookNav/[...bookId].ts +20 -0
  194. package/server/api/aside/major/nav/global.ts +7 -0
  195. package/server/api/aside/minor/news.ts +7 -0
  196. package/server/api/aside/minor/path.ts +78 -0
  197. package/server/api/bitran/content/[location].ts +7 -0
  198. package/server/api/bitran/toc/[location].ts +7 -0
  199. package/server/api/content/data.ts +72 -0
  200. package/server/api/contributor/count.ts +6 -0
  201. package/server/api/fake/content.ts +11 -0
  202. package/server/api/fake/shared/languages.ts +12 -0
  203. package/server/api/language/functions.ts +12 -0
  204. package/server/api/language/phrase/[phraseId].ts +19 -0
  205. package/server/api/language/phraseIds.ts +8 -0
  206. package/server/api/preview/page/[...parts].ts +51 -0
  207. package/server/api/preview/unique/[location].ts +55 -0
  208. package/server/plugin/bitran/content.ts +176 -0
  209. package/server/plugin/bitran/core.ts +51 -0
  210. package/server/plugin/bitran/location.ts +25 -0
  211. package/server/plugin/bitran/products/include.ts +229 -0
  212. package/server/plugin/bitran/products/link.ts +114 -0
  213. package/server/plugin/bitran/setup.ts +10 -0
  214. package/server/plugin/bitran/toc.ts +82 -0
  215. package/server/plugin/build/close.ts +10 -0
  216. package/server/plugin/build/jobs/content/builderArgs.ts +8 -0
  217. package/server/plugin/build/jobs/content/generic.ts +176 -0
  218. package/server/plugin/build/jobs/content/parse.ts +90 -0
  219. package/server/plugin/build/jobs/content/path.ts +6 -0
  220. package/server/plugin/build/jobs/content/type/book.ts +9 -0
  221. package/server/plugin/build/jobs/content/type/group.ts +37 -0
  222. package/server/plugin/build/jobs/content/type/topic.ts +36 -0
  223. package/server/plugin/build/jobs/contributors.ts +66 -0
  224. package/server/plugin/build/jobs/language.ts +36 -0
  225. package/server/plugin/build/jobs/nav.ts +209 -0
  226. package/server/plugin/build/process.ts +25 -0
  227. package/server/plugin/build/rebuild.ts +55 -0
  228. package/server/plugin/build/setup.ts +21 -0
  229. package/server/plugin/content/absoluteId.ts +94 -0
  230. package/server/plugin/content/context.ts +112 -0
  231. package/server/plugin/db/entities/Book.ts +7 -0
  232. package/server/plugin/db/entities/Content.ts +49 -0
  233. package/server/plugin/db/entities/Contribution.ts +10 -0
  234. package/server/plugin/db/entities/Contributor.ts +16 -0
  235. package/server/plugin/db/entities/Group.ts +14 -0
  236. package/server/plugin/db/entities/Hash.ts +15 -0
  237. package/server/plugin/db/entities/Topic.ts +20 -0
  238. package/server/plugin/db/entities/Unique.ts +21 -0
  239. package/server/plugin/db/setup.ts +34 -0
  240. package/server/plugin/global.ts +18 -0
  241. package/server/plugin/importer.ts +12 -0
  242. package/server/plugin/index.ts +9 -0
  243. package/server/plugin/logger.ts +23 -0
  244. package/server/plugin/nav/node.ts +26 -0
  245. package/server/plugin/nav/utils.ts +129 -0
  246. package/server/plugin/repository/book.ts +21 -0
  247. package/server/plugin/repository/content.ts +238 -0
  248. package/server/plugin/repository/contributor.ts +8 -0
  249. package/server/plugin/repository/frontNav.ts +148 -0
  250. package/server/plugin/repository/topic.ts +32 -0
  251. package/server/tsconfig.json +7 -0
  252. package/shared/aside/minor.ts +50 -0
  253. package/shared/asset.ts +15 -0
  254. package/shared/bitran/alias.ts +17 -0
  255. package/shared/bitran/context.ts +7 -0
  256. package/shared/bitran/location.ts +166 -0
  257. package/shared/bitran/products/alias/core/factory.ts +46 -0
  258. package/shared/bitran/products/alias/core/index.ts +13 -0
  259. package/shared/bitran/products/alias/render/Alias.vue +10 -0
  260. package/shared/bitran/products/alias/render/icon.svg +3 -0
  261. package/shared/bitran/products/alias/render/index.ts +17 -0
  262. package/shared/bitran/products/alias/render/languages/en.ts +5 -0
  263. package/shared/bitran/products/alias/render/languages/ru.ts +5 -0
  264. package/shared/bitran/products/alias/shared.ts +11 -0
  265. package/shared/bitran/products/heading/core/factory.ts +53 -0
  266. package/shared/bitran/products/heading/core/index.ts +19 -0
  267. package/shared/bitran/products/heading/render/Heading.vue +47 -0
  268. package/shared/bitran/products/heading/render/icon.svg +3 -0
  269. package/shared/bitran/products/heading/render/index.ts +17 -0
  270. package/shared/bitran/products/heading/render/languages/en.ts +5 -0
  271. package/shared/bitran/products/heading/render/languages/ru.ts +5 -0
  272. package/shared/bitran/products/heading/shared.ts +13 -0
  273. package/shared/bitran/products/include/core/factory.ts +61 -0
  274. package/shared/bitran/products/include/core/index.ts +13 -0
  275. package/shared/bitran/products/include/render/Include.vue +13 -0
  276. package/shared/bitran/products/include/render/icon.svg +3 -0
  277. package/shared/bitran/products/include/render/index.ts +18 -0
  278. package/shared/bitran/products/include/render/languages/en.ts +5 -0
  279. package/shared/bitran/products/include/render/languages/ru.ts +5 -0
  280. package/shared/bitran/products/include/shared.ts +15 -0
  281. package/shared/bitran/products/link/core/factory.ts +20 -0
  282. package/shared/bitran/products/link/core/index.ts +17 -0
  283. package/shared/bitran/products/link/render/Link.vue +174 -0
  284. package/shared/bitran/products/link/render/icon.svg +3 -0
  285. package/shared/bitran/products/link/render/index.ts +17 -0
  286. package/shared/bitran/products/link/render/languages/en.ts +5 -0
  287. package/shared/bitran/products/link/render/languages/ru.ts +5 -0
  288. package/shared/bitran/products/link/shared.ts +15 -0
  289. package/shared/bitran/products/link/target.ts +134 -0
  290. package/shared/bitran/toc.ts +8 -0
  291. package/shared/content/context.ts +9 -0
  292. package/shared/content/data/base.ts +32 -0
  293. package/shared/content/data/index.ts +5 -0
  294. package/shared/content/data/type/book.ts +5 -0
  295. package/shared/content/data/type/group.ts +6 -0
  296. package/shared/content/data/type/topic.ts +11 -0
  297. package/shared/content/previousNext.ts +9 -0
  298. package/shared/contributor.ts +5 -0
  299. package/shared/frontNav.ts +41 -0
  300. package/shared/icons.ts +38 -0
  301. package/shared/image.ts +5 -0
  302. package/shared/link.ts +25 -0
  303. package/shared/types/language.ts +75 -0
  304. package/shared/utils/objectsEqual.ts +4 -0
  305. package/shared/utils/stringColor.ts +9 -0
  306. package/test/bitran/alias.test.ts +44 -0
  307. package/test/bitran/location.test.ts +143 -0
  308. package/test/bitran/products/alias.test.ts +83 -0
  309. package/test/bitran/products/heading.test.ts +119 -0
  310. package/test/bitran/products/include.test.ts +77 -0
  311. package/test/bitran/products/link/factory.test.ts +30 -0
  312. package/test/bitran/products/link/target.test.ts +138 -0
  313. package/tsconfig.json +8 -0
  314. package/utils/stress.ts +9 -0
@@ -0,0 +1,86 @@
1
+ import eruditConfig from '#erudit/config';
2
+
3
+ let formatFunction: FormatFunction;
4
+
5
+ export function useFormatText() {
6
+ if (!formatFunction) {
7
+ const language = eruditConfig?.language;
8
+ formatFunction = createFormatFunction(language);
9
+ }
10
+
11
+ return formatFunction;
12
+ }
13
+
14
+ type FormatFunction = (text: string) => string;
15
+
16
+ function createFormatFunction(language?: string): FormatFunction {
17
+ const formatters: FormatFunction[] = [];
18
+
19
+ //
20
+ // Em Dashes
21
+ //
22
+
23
+ formatters.push((text) => text.replace(/(^| )--($| )/gm, '$1—$2'));
24
+
25
+ //
26
+ // Quotes
27
+ //
28
+
29
+ {
30
+ const quoteSymbols: [string, string] = (() => {
31
+ switch (language) {
32
+ case 'ru':
33
+ return ['«', '»'];
34
+ default:
35
+ return ['“', '”'];
36
+ }
37
+ })();
38
+
39
+ formatters.push((text) => {
40
+ let open = false;
41
+ return text.replaceAll(/"/gm, () => {
42
+ return (open = !open) ? quoteSymbols[0] : quoteSymbols[1];
43
+ });
44
+ });
45
+ }
46
+
47
+ //
48
+ // Ellipsis
49
+ //
50
+
51
+ formatters.push((text) => text.replace(/\.\.\./gm, '…'));
52
+
53
+ //
54
+ // Language specific formatters
55
+ //
56
+
57
+ if (language === 'ru') formatters.push(ruStickyPrepositions);
58
+
59
+ //
60
+ //
61
+ //
62
+
63
+ function formatText(text: string): string {
64
+ if (!text) return text;
65
+
66
+ for (const formatter of formatters) text = formatter(text);
67
+
68
+ return text;
69
+ }
70
+
71
+ return formatText;
72
+ }
73
+
74
+ //
75
+ //
76
+ //
77
+
78
+ /**
79
+ * Formats prepositions in Russian text so that they are always adjacent to the next word and are not left hanging “in the air” when the line breaks.
80
+ */
81
+ function ruStickyPrepositions(text: string): string {
82
+ return text.replace(
83
+ / (в|не|без|для|до|за|из|к|на|над|о|об|от|по|под|при|про|с|у|через|вокруг|около|после|перед|между|внутри|вне|из-за|из-под|ради|сквозь|среди|насчёт|вследствие|благодаря|несмотря|наперекор|вопреки|подле|возле|рядом|навстречу) /gimu,
84
+ ' $1\xa0',
85
+ );
86
+ }
@@ -0,0 +1,60 @@
1
+ import type { Component } from 'vue';
2
+
3
+ import type { MyIconName } from '#my-icons';
4
+
5
+ import Nav from '@app/components/aside/major/panes/nav/Nav.vue';
6
+ import Pages from '@app/components/aside/major/panes/Pages.vue';
7
+ import Search from '@app/components/aside/major/panes/Search.vue';
8
+ import Language from '@app/components/aside/major/panes/Language.vue';
9
+ import Other from '@app/components/aside/major/panes/other/Other.vue';
10
+
11
+ interface MajorPane {
12
+ icon: MyIconName;
13
+ phrase: EruditPhraseId;
14
+ content: Component;
15
+ }
16
+
17
+ function definePane<TPane extends MajorPane>(pane: TPane) {
18
+ return pane;
19
+ }
20
+
21
+ export const majorPanes = {
22
+ index: definePane({ icon: 'book', phrase: 'index', content: Nav }),
23
+ pages: definePane({ icon: 'file-lines', phrase: 'pages', content: Pages }),
24
+ search: definePane({ icon: 'search', phrase: 'search', content: Search }),
25
+ language: definePane({
26
+ icon: 'globe',
27
+ phrase: 'language',
28
+ content: Language,
29
+ }),
30
+ other: definePane({
31
+ icon: 'ellipsis-vertical',
32
+ phrase: 'other',
33
+ content: Other,
34
+ }),
35
+ };
36
+
37
+ function getPaneOrder(paneKey: MajorPaneKey) {
38
+ return Object.keys(majorPanes).indexOf(paneKey);
39
+ }
40
+
41
+ export type MajorPaneKey = keyof typeof majorPanes;
42
+
43
+ export function useMajorPane() {
44
+ const route = useRoute();
45
+
46
+ const activePane = useState<MajorPaneKey>('major-pane', () => {
47
+ switch (route.path) {
48
+ case '/members':
49
+ return 'pages';
50
+ default:
51
+ return 'index';
52
+ }
53
+ });
54
+
55
+ return {
56
+ panes: majorPanes,
57
+ activePane,
58
+ getPaneOrder,
59
+ };
60
+ }
@@ -0,0 +1,80 @@
1
+ type PhraseCaller<T extends EruditPhraseId[]> = {
2
+ [K in T[number]]: EruditPhrases[K];
3
+ };
4
+
5
+ const payloadKey = 'language';
6
+
7
+ interface LanguagePayload {
8
+ strPhrases: Partial<Record<EruditPhraseId, string>>;
9
+ strFunctions: Record<string, string>;
10
+ }
11
+
12
+ const functions: Record<string, Function> = {};
13
+ const functionPhrases: Record<string, Function> = {};
14
+
15
+ const phraseApiRoute = (phraseId: EruditPhraseId) =>
16
+ `/api/language/phrase/${phraseId}`;
17
+ const functionsApiRoute = '/api/language/functions';
18
+ const phraseIdsApiRoute = '/api/language/phraseIds';
19
+
20
+ export function usePhrases<T extends EruditPhraseId[]>(
21
+ ...phraseIds: T
22
+ ): Promise<PhraseCaller<T>> {
23
+ const nuxt = useNuxtApp();
24
+ const payload: LanguagePayload =
25
+ (nuxt.static.data[payloadKey] ||=
26
+ nuxt.payload.data[payloadKey] ||=
27
+ {});
28
+
29
+ const prerenderAllPromise = prerenderAllPhrases();
30
+ const prepareFunctionsPromise = prepareFunctions(payload);
31
+
32
+ return (async () => {
33
+ await prerenderAllPromise;
34
+ await prepareFunctionsPromise;
35
+
36
+ payload.strPhrases ||= {};
37
+ const phraseCaller: any = {};
38
+
39
+ for (const phraseId of phraseIds) {
40
+ const apiRoute = phraseApiRoute(phraseId);
41
+ const strPhrase = (payload.strPhrases[phraseId] ||= (await $fetch(
42
+ apiRoute,
43
+ )) as string);
44
+
45
+ if (strPhrase.startsWith('~!~FUNC~!~')) {
46
+ const strFunction = strPhrase.replace('~!~FUNC~!~', '');
47
+ functionPhrases[phraseId] ||= new Function(strFunction).bind(
48
+ functions,
49
+ )();
50
+ phraseCaller[phraseId] = functionPhrases[phraseId];
51
+ } else {
52
+ phraseCaller[phraseId] = strPhrase;
53
+ }
54
+ }
55
+
56
+ return phraseCaller as PhraseCaller<T>;
57
+ })();
58
+ }
59
+
60
+ async function prerenderAllPhrases() {
61
+ if (import.meta.dev) return;
62
+
63
+ if (import.meta.client) return;
64
+
65
+ const nuxt = useNuxtApp();
66
+
67
+ const phraseIds = await $fetch(phraseIdsApiRoute);
68
+ nuxt.runWithContext(() =>
69
+ prerenderRoutes(phraseIds.map((phraseId) => phraseApiRoute(phraseId))),
70
+ );
71
+ }
72
+
73
+ async function prepareFunctions(payload: LanguagePayload) {
74
+ if (payload?.strFunctions) return;
75
+
76
+ payload.strFunctions = await $fetch(functionsApiRoute);
77
+
78
+ for (const [funcName, strFunc] of Object.entries(payload.strFunctions))
79
+ functions[funcName] = new Function(strFunc)();
80
+ }
@@ -0,0 +1,29 @@
1
+ const themes = ['auto', 'light', 'dark'] as const;
2
+
3
+ export type Theme = (typeof themes)[number];
4
+ export type BinaryTheme = 'light' | 'dark';
5
+
6
+ export function useTheme() {
7
+ const theme = useState<Theme>('theme', () => {
8
+ return localStorage.getItem('theme') ?? ('auto' as any);
9
+ });
10
+
11
+ const binaryTheme = computed(() => {
12
+ return theme.value === 'auto'
13
+ ? window.matchMedia('(prefers-color-scheme: dark)').matches
14
+ ? 'dark'
15
+ : 'light'
16
+ : theme.value;
17
+ });
18
+
19
+ const cycle = () => {
20
+ theme.value =
21
+ themes[(themes.indexOf(theme.value) + 1) % themes.length]!;
22
+ };
23
+
24
+ return {
25
+ theme,
26
+ binaryTheme,
27
+ cycle,
28
+ };
29
+ }
@@ -0,0 +1,33 @@
1
+ import eruditConfig from '#erudit/config';
2
+
3
+ export function useBaseUrlPath() {
4
+ const runtimeConfig = useRuntimeConfig();
5
+ return (path: string) => {
6
+ const baseURL = runtimeConfig.app.baseURL;
7
+ if (path.startsWith(baseURL)) return path;
8
+ else if (path.startsWith('/')) return baseURL + path.substring(1);
9
+ else return path;
10
+ };
11
+ }
12
+
13
+ export function useSiteUrl() {
14
+ const runtimeConfig = useRuntimeConfig();
15
+ const baseUrl = runtimeConfig.app.baseURL;
16
+ const url = useRequestURL();
17
+
18
+ if (!import.meta.dev && eruditConfig.site?.buildUrl)
19
+ return eruditConfig.site.buildUrl + baseUrl.slice(0, -1);
20
+
21
+ return url.origin;
22
+ }
23
+
24
+ export function usePageUrl() {
25
+ const siteUrl = useSiteUrl();
26
+ const route = useRoute();
27
+
28
+ return computed(() => {
29
+ if (route.path === '/') return siteUrl;
30
+
31
+ return siteUrl + route.path;
32
+ });
33
+ }
@@ -0,0 +1,110 @@
1
+ <script lang="ts" setup>
2
+ import { PreviewDataType } from '@app/scripts/preview/data';
3
+ import { createPreviewError } from '@app/scripts/preview/data/alert';
4
+ import { PreviewRequestType } from '@app/scripts/preview/request';
5
+ import {
6
+ PreviewThemeName,
7
+ showPreview,
8
+ togglePreview,
9
+ } from '@app/scripts/preview/state';
10
+
11
+ function showCustom() {
12
+ showPreview({
13
+ type: PreviewRequestType.Data,
14
+ data: {
15
+ type: PreviewDataType.Custom,
16
+ message: 'This is custom message for preview!',
17
+ footer: {
18
+ //iconName: 'arrow-left',
19
+ iconSvg:
20
+ '<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <path fill-rule="evenodd" clip-rule="evenodd" d="M6.42436 0H9.57565L14.995 16H11.8276L10.8115 13H5.18855L4.17242 16H1.005L6.42436 0ZM6.20468 10H9.79533L8 4.69952L6.20468 10Z"></path> </g></svg>',
21
+ secondary: 'Secondary raw text',
22
+ primary: 'Primary raw text',
23
+ href: 'https://google.com',
24
+ },
25
+ },
26
+ });
27
+ }
28
+
29
+ function showAlert(theme: PreviewThemeName) {
30
+ showPreview({
31
+ type: PreviewRequestType.Data,
32
+ data: createPreviewError({
33
+ theme,
34
+ message: 'This is message!',
35
+ title: 'Custom Title',
36
+ pre: JSON.stringify(
37
+ {
38
+ foo: 1337,
39
+ bar: 'Some text',
40
+ },
41
+ null,
42
+ 4,
43
+ ),
44
+ }),
45
+ });
46
+ }
47
+ </script>
48
+
49
+ <template>
50
+ <button @click="() => togglePreview()">Toggle Preview</button>
51
+ <div>
52
+ Show:
53
+ <br />
54
+ <button :class="$style.button" @click="showCustom">Custom</button>
55
+ <br />
56
+ <button
57
+ :class="$style.button"
58
+ v-for="theme in PreviewThemeName"
59
+ @click="() => showAlert(theme)"
60
+ >
61
+ Alert {{ theme }}
62
+ </button>
63
+ <br />
64
+ <button
65
+ :class="$style.button"
66
+ @click="
67
+ showPreview({
68
+ type: PreviewRequestType.MissingElement,
69
+ elementId: 'foo-id',
70
+ })
71
+ "
72
+ >
73
+ Missing element
74
+ </button>
75
+ <button
76
+ :class="$style.button"
77
+ @click="
78
+ showPreview({
79
+ type: PreviewRequestType.MissingElement,
80
+ elementId: 'foo-id',
81
+ hashMismatch: { current: 'bar', expected: 'baz' },
82
+ })
83
+ "
84
+ >
85
+ Missing element + hash mismatch
86
+ </button>
87
+ <br />
88
+ <button
89
+ :class="$style.button"
90
+ @click="
91
+ showPreview({
92
+ type: PreviewRequestType.HashMismatch,
93
+ currentHash: 'foo',
94
+ expectedHash: 'bar',
95
+ })
96
+ "
97
+ >
98
+ Hash mismatch
99
+ </button>
100
+ </div>
101
+ </template>
102
+
103
+ <style lang="scss" module>
104
+ .button {
105
+ border: 2px solid var(--border);
106
+ border-radius: 3px;
107
+ padding: 3px;
108
+ margin: 3px;
109
+ }
110
+ </style>
@@ -0,0 +1,3 @@
1
+ <template>
2
+ <MainTopic />
3
+ </template>
@@ -0,0 +1,47 @@
1
+ <script lang="ts" setup>
2
+ import type { ContentBookData } from '@erudit/shared/content/data/type/book';
3
+ import type { MyIconName } from '#my-icons';
4
+
5
+ import ContentDecoration from '@app/components/main/utils/ContentDecoration.vue';
6
+ import Breadcrumb from '@app/components/main/utils/Breadcrumb.vue';
7
+ import ContentTitle from '@app/components/main/utils/ContentTitle.vue';
8
+ import ContentDescription from '@app/components/main/utils/ContentDescription.vue';
9
+ import ContentPopovers from '@app/components/main/utils/ContentPopovers.vue';
10
+
11
+ const bookData = await useContentData<ContentBookData>();
12
+ await useContentPage(bookData);
13
+
14
+ const phrase = await usePhrases('book');
15
+ </script>
16
+
17
+ <template>
18
+ <ContentDecoration
19
+ v-if="bookData.generic.decoration"
20
+ :decoration="bookData.generic.decoration"
21
+ />
22
+
23
+ <Breadcrumb
24
+ v-if="bookData.generic.context?.length > 1"
25
+ :context="bookData.generic.context"
26
+ />
27
+
28
+ <ContentTitle
29
+ :title="
30
+ bookData.generic?.title ||
31
+ bookData.generic.contentId.split('/').pop()!
32
+ "
33
+ :icon="<MyIconName>'outline/book'"
34
+ :hint="phrase.book"
35
+ />
36
+
37
+ <ContentDescription
38
+ v-if="bookData.generic?.description"
39
+ :description="bookData.generic?.description"
40
+ />
41
+
42
+ <ContentPopovers :generic="bookData.generic" />
43
+
44
+ <!-- Counters, fancy "GO LEARN" button and etc. -->
45
+
46
+ <div style="clear: both"></div>
47
+ </template>
@@ -0,0 +1,64 @@
1
+ <script lang="ts" setup>
2
+ import { type ContentGroupData } from '@shared/content/data/type/group';
3
+ import eruditConfig from '#erudit/config';
4
+
5
+ import ContentDecoration from '@app/components/main/utils/ContentDecoration.vue';
6
+ import Breadcrumb from '@app/components/main/utils/Breadcrumb.vue';
7
+ import ContentTitle from '@app/components/main/utils/ContentTitle.vue';
8
+ import ContentDescription from '@app/components/main/utils/ContentDescription.vue';
9
+ import ContentPopovers from '@app/components/main/utils/ContentPopovers.vue';
10
+ import ContentSection from '@app/components/main/utils/ContentSection.vue';
11
+ import { NO_ALIASES } from '@erudit/shared/bitran/alias';
12
+ import { locationIcon } from '@erudit/shared/icons';
13
+
14
+ const location = useBitranLocation();
15
+
16
+ const groupData = await useContentData<ContentGroupData>();
17
+ await useContentPage(groupData);
18
+
19
+ const content = await useBitranContent(location);
20
+ const phrase = await usePhrases('group');
21
+ </script>
22
+
23
+ <template>
24
+ <ContentDecoration
25
+ v-if="groupData.generic.decoration"
26
+ :decoration="groupData.generic.decoration"
27
+ />
28
+
29
+ <Breadcrumb
30
+ v-if="groupData.generic.context?.length > 1"
31
+ :context="groupData.generic.context"
32
+ />
33
+
34
+ <ContentTitle
35
+ :title="
36
+ groupData.generic?.title ||
37
+ groupData.generic.contentId.split('/').pop()!
38
+ "
39
+ :icon="locationIcon(location!)"
40
+ :hint="phrase.group"
41
+ />
42
+
43
+ <ContentDescription
44
+ v-if="groupData.generic?.description"
45
+ :description="groupData.generic?.description"
46
+ />
47
+
48
+ <ContentPopovers :generic="groupData.generic" />
49
+
50
+ <!-- TODO: List of materials in group -->
51
+
52
+ <div style="clear: both"></div>
53
+
54
+ <hr style="display: none" />
55
+
56
+ <ContentSection>
57
+ <BitranContent
58
+ :content
59
+ :context="{ location, aliases: NO_ALIASES() }"
60
+ />
61
+ </ContentSection>
62
+
63
+ <AdsBottomBanner v-if="eruditConfig.ads?.bottomBlockId" />
64
+ </template>
@@ -0,0 +1,32 @@
1
+ <script lang="ts" setup>
2
+ import eruditConfig from '#erudit/config';
3
+
4
+ const phrase = await usePhrases('seo_index_title', 'seo_index_description');
5
+
6
+ const seoTitle =
7
+ eruditConfig.seo?.indexTitle ||
8
+ eruditConfig.seo?.title ||
9
+ phrase.seo_index_title;
10
+ const seoDescription =
11
+ eruditConfig.seo?.indexDescription || phrase.seo_index_description;
12
+
13
+ useSeoMeta({
14
+ title: seoTitle,
15
+ ogTitle: seoTitle,
16
+ description: seoDescription,
17
+ ogDescription: seoDescription,
18
+ });
19
+ </script>
20
+
21
+ <template>
22
+ <div style="padding: var(--_pMainY) var(--_pMainX)">
23
+ <h1>
24
+ {{
25
+ eruditConfig.seo?.title ||
26
+ eruditConfig.site?.title ||
27
+ phrase.seo_index_title
28
+ }}
29
+ </h1>
30
+ <p>TODO</p>
31
+ </div>
32
+ </template>
@@ -0,0 +1,7 @@
1
+ <script lang="ts" setup>
2
+ </script>
3
+
4
+ <template>
5
+ <p>Members</p>
6
+ <NuxtLink to="/practice/topic-b">Погнали епты!</NuxtLink>
7
+ </template>
@@ -0,0 +1,3 @@
1
+ <template>
2
+ <MainTopic />
3
+ </template>
@@ -0,0 +1,3 @@
1
+ <template>
2
+ <MainTopic />
3
+ </template>
@@ -0,0 +1,10 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
2
+ <defs>
3
+ <linearGradient id="gradient" x1="74.98" y1="423.02" x2="437.02" y2="60.98" gradientUnits="userSpaceOnUse">
4
+ <stop offset="0" stop-color="#6fa552"/>
5
+ <stop offset="1" stop-color="#4475bc"/>
6
+ </linearGradient>
7
+ </defs>
8
+ <circle cx="256" cy="256" r="256" fill="url(#gradient)"/>
9
+ <path d="M124.13,354.9c-4.67,0-8.59-1.58-11.74-4.74-3.16-3.16-4.74-7.07-4.74-11.74s1.58-8.59,4.74-11.74c3.16-3.16,7.07-4.74,11.74-4.74h164.84c4.67,0,8.59,1.58,11.74,4.74,3.16,3.16,4.74,7.07,4.74,11.74s-1.58,8.59-4.74,11.74c-3.16,3.16-7.07,4.74-11.74,4.74H124.13ZM124.13,272.48c-4.67,0-8.59-1.58-11.74-4.74-3.16-3.16-4.74-7.07-4.74-11.74s1.58-8.59,4.74-11.74c3.16-3.16,7.07-4.74,11.74-4.74h263.74c4.67,0,8.59,1.58,11.74,4.74,3.16,3.16,4.74,7.07,4.74,11.74s-1.58,8.59-4.74,11.74c-3.16,3.16-7.07,4.74-11.74,4.74H124.13ZM124.13,190.07c-4.67,0-8.59-1.58-11.74-4.74-3.16-3.16-4.74-7.07-4.74-11.74s1.58-8.59,4.74-11.74c3.16-3.16,7.07-4.74,11.74-4.74h263.74c4.67,0,8.59,1.58,11.74,4.74,3.16,3.16,4.74,7.07,4.74,11.74s-1.58,8.59-4.74,11.74c-3.16,3.16-7.07,4.74-11.74,4.74H124.13Z" fill="#fff"/>
10
+ </svg>
@@ -0,0 +1,10 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
2
+ <defs>
3
+ <linearGradient id="gradient" x1="75" y1="423" x2="437" y2="61" gradientUnits="userSpaceOnUse">
4
+ <stop offset="0" stop-color="#6fa552"/>
5
+ <stop offset="1" stop-color="#4475bc"/>
6
+ </linearGradient>
7
+ </defs>
8
+ <circle fill="url(#gradient)" class="cls-2" cx="256" cy="256" r="256"/>
9
+ <path fill="white" d="M385.2,317.4v-91.1l-122.9,61.9c-5.5,2.8-11.4,4.3-17.5,4.3s-12-1.4-17.4-4.3l-126.5-63.9c-3.3-1.7-5.9-4.1-7.9-7s-2.9-6.1-2.9-9.6,1-6.6,2.9-9.6c2-3,4.6-5.4,7.9-7.2l126.5-63.9c2.8-1.5,5.7-2.6,8.6-3.3,2.9-.7,5.9-1,8.9-1s5.9.4,8.9,1c2.9.7,5.8,1.8,8.6,3.3l149.2,75.2c3.1,1.7,5.6,4,7.6,6.9,1.9,2.9,2.9,6.1,2.9,9.8v98.7c0,5-1.8,9.3-5.4,12.9-3.6,3.6-7.8,5.4-12.9,5.4s-9.3-1.8-13-5.4c-3.6-3.6-5.5-7.9-5.5-13h0ZM235.8,388.5c3,.5,5.9.8,8.9.8s5.9-.3,8.9-.8c2.9-.5,5.9-1.6,8.6-3.1l67.9-34.1c6.2-3.1,11.2-7.7,14.9-13.7,3.8-6.1,5.6-12.6,5.6-19.6v-37.9l-88.4,44.6c-5.4,2.9-11.3,4.3-17.4,4.3s-12-1.5-17.5-4.3l-88.4-44.6v37.9c0,7,1.9,13.6,5.6,19.6,3.8,6,8.7,10.6,14.9,13.7l67.9,34.1h0c2.8,1.5,5.7,2.6,8.6,3.1Z"/>
10
+ </svg>
@@ -0,0 +1,10 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
2
+ <defs>
3
+ <linearGradient id="gradient" x1="74.98" y1="423.02" x2="437.02" y2="60.98" gradientUnits="userSpaceOnUse">
4
+ <stop offset="0" stop-color="#6fa552"/>
5
+ <stop offset="1" stop-color="#4475bc"/>
6
+ </linearGradient>
7
+ </defs>
8
+ <circle cx="256" cy="256" r="256" fill="url(#gradient)"/>
9
+ <path d="M216.29,267.91l129.71-129.71,52.94,52.94-182.66,182.66-103.24-103.24,52.94-52.94,50.3,50.3Z" fill="#fff"/>
10
+ </svg>
@@ -0,0 +1,10 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
2
+ <defs>
3
+ <linearGradient id="gradient" x1="74.98" y1="423.02" x2="437.02" y2="60.98" gradientUnits="userSpaceOnUse">
4
+ <stop offset="0" stop-color="#6fa552"/>
5
+ <stop offset="1" stop-color="#4475bc"/>
6
+ </linearGradient>
7
+ </defs>
8
+ <circle cx="256" cy="256" r="256" fill="url(#gradient)"/>
9
+ <path d="M271.03,131.48c-2.77-5.75-8.63-9.42-15.07-9.42s-12.24,3.66-15.07,9.42l-33.64,69.21-75.12,11.09c-6.28.94-11.51,5.34-13.44,11.35s-.37,12.66,4.13,17.11l54.51,53.93-12.87,76.22c-1.05,6.28,1.57,12.66,6.75,16.37,5.18,3.71,12.03,4.18,17.68,1.2l67.12-35.83,67.12,35.83c5.65,2.98,12.5,2.56,17.68-1.2,5.18-3.77,7.79-10.1,6.75-16.37l-12.92-76.22,54.51-53.93c4.5-4.45,6.12-11.09,4.13-17.11-1.99-6.02-7.17-10.41-13.44-11.35l-75.17-11.09-33.64-69.21Z" fill="#fff"/>
10
+ </svg>
@@ -0,0 +1,17 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 135 135">
2
+ <defs>
3
+ <linearGradient id="gradient" x1="19.77" y1="115.23" x2="115.23" y2="19.77" gradientUnits="userSpaceOnUse">
4
+ <stop offset="0" stop-color="#4475bc"/>
5
+ <stop offset="1" stop-color="#6fa552"/>
6
+ <animateTransform
7
+ attributeName="gradientTransform"
8
+ type="rotate"
9
+ from="0 70 70"
10
+ to="360 70 70"
11
+ dur="5s"
12
+ repeatCount="indefinite"
13
+ />
14
+ </linearGradient>
15
+ </defs>
16
+ <path d="M95.99,81.04v-20.08l-27.1,13.64c-1.22.62-2.51.94-3.86.94-1.36,0-2.64-.31-3.84-.94l-27.89-14.1c-.73-.38-1.31-.9-1.74-1.55-.43-.65-.65-1.35-.65-2.11,0-.75.21-1.46.64-2.11.43-.66,1.01-1.18,1.74-1.58l27.89-14.09c.61-.33,1.25-.57,1.9-.73.65-.15,1.3-.23,1.96-.23s1.3.08,1.96.23c.65.15,1.28.39,1.9.73l32.89,16.57c.69.38,1.24.89,1.67,1.52.42.63.64,1.35.64,2.15v21.76c0,1.11-.39,2.06-1.18,2.85-.79.79-1.73,1.18-2.84,1.18s-2.06-.4-2.87-1.19c-.8-.8-1.21-1.75-1.21-2.86ZM61.18,96.02l-14.97-7.51c-1.37-.69-2.46-1.7-3.29-3.03-.83-1.33-1.24-2.77-1.24-4.32v-8.36l19.5,9.83c1.22.63,2.51.95,3.86.95s2.64-.32,3.84-.95l19.5-9.83v8.36c0,1.54-.41,2.98-1.24,4.32-.83,1.33-1.92,2.34-3.29,3.03l-14.97,7.51c-.61.34-1.25.58-1.9.69-.65.12-1.3.18-1.96.18s-1.3-.06-1.96-.18c-.65-.12-1.28-.35-1.9-.69ZM19.5,67.5C19.5,36.11,39.79,9.52,67.87,0c-.13,0-.25,0-.37,0C30.22,0,0,30.22,0,67.5s30.22,67.5,67.5,67.5c.13,0,.25,0,.37,0-28.09-9.51-48.37-36.1-48.37-67.49ZM67.93.01c28.18,9.51,48.37,36.1,48.37,67.49s-20.19,57.98-48.37,67.49c37.08-.23,67.07-30.35,67.07-67.49S105.01.24,67.93.01Z" fill="url(#gradient)"/>
17
+ </svg>
Binary file
@@ -0,0 +1,10 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500">
2
+ <defs>
3
+ <linearGradient id="gradient" x1="0" y1="500" x2="500" y2="0" gradientUnits="userSpaceOnUse">
4
+ <stop offset="0" stop-color="#bcbcbc"/>
5
+ <stop offset="1" stop-color="#e6e6e6"/>
6
+ </linearGradient>
7
+ </defs>
8
+ <rect width="500" height="500" fill="url(#gradient)"/>
9
+ <path d="M250,224c-20.62,0-38.28-7.34-52.97-22.03-14.69-14.69-22.03-32.34-22.03-52.97s7.34-38.28,22.03-52.97,32.34-22.03,52.97-22.03,38.28,7.34,52.97,22.03,22.03,32.34,22.03,52.97-7.34,38.28-22.03,52.97c-14.69,14.69-32.34,22.03-52.97,22.03ZM100,374v-52.5c0-10.62,2.73-20.39,8.2-29.3,5.47-8.91,12.73-15.7,21.8-20.39,19.38-9.69,39.06-16.95,59.06-21.8,20-4.84,40.31-7.27,60.94-7.27s40.94,2.42,60.94,7.27c20,4.84,39.69,12.11,59.06,21.8,9.06,4.69,16.33,11.48,21.8,20.39,5.47,8.91,8.2,18.67,8.2,29.3v52.5H100ZM137.5,336.5h225v-15c0-3.44-.86-6.56-2.58-9.37-1.72-2.81-3.98-5-6.8-6.56-16.87-8.44-33.91-14.77-51.09-18.98-17.19-4.22-34.53-6.33-52.03-6.33s-34.84,2.11-52.03,6.33c-17.19,4.22-34.22,10.55-51.09,18.98-2.81,1.56-5.08,3.75-6.8,6.56s-2.58,5.94-2.58,9.37v15ZM250,186.5c10.31,0,19.14-3.67,26.48-11.02,7.34-7.34,11.02-16.17,11.02-26.48s-3.67-19.14-11.02-26.48c-7.34-7.34-16.17-11.02-26.48-11.02s-19.14,3.67-26.48,11.02c-7.34,7.34-11.02,16.17-11.02,26.48s3.67,19.14,11.02,26.48c7.34,7.34,16.17,11.02,26.48,11.02Z" fill="#666"/>
10
+ </svg>
@@ -0,0 +1,4 @@
1
+ // Instant color theme setup
2
+ const theme = localStorage.getItem('theme') ?? 'auto';
3
+ const binaryTheme = theme === 'auto' ? window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light' : theme;
4
+ document.documentElement.setAttribute('data-theme', binaryTheme);