@xcpcio/board-app 0.6.4 → 0.13.0

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 (112) hide show
  1. package/.eslintrc.json +6 -0
  2. package/LICENSE +1 -1
  3. package/README.md +13 -30
  4. package/cypress.config.ts +14 -0
  5. package/dist/_headers +3 -0
  6. package/dist/about.html +11 -0
  7. package/dist/assets/_...all_-27c7ae93.css +1 -0
  8. package/dist/assets/_...all_-d56798b5.js +3 -0
  9. package/dist/assets/_name_-8eab6137.js +1 -0
  10. package/dist/assets/about-a8cb8700.js +11 -0
  11. package/dist/assets/app-37f77a84.js +65 -0
  12. package/dist/assets/board-layout-deaedfc1.js +1 -0
  13. package/dist/assets/en-caedd340.js +1 -0
  14. package/dist/assets/home-49c336e5.js +1 -0
  15. package/dist/assets/index-a270cacd.css +5 -0
  16. package/dist/assets/index-layout-d65c80ea.js +1 -0
  17. package/dist/assets/test-0a3d6f7a.js +1 -0
  18. package/dist/assets/user-108782a1.js +1 -0
  19. package/dist/assets/virtual_pwa-register-1c1b9161.js +1 -0
  20. package/dist/assets/workbox-window.prod.es5-a7b12eab.js +2 -0
  21. package/dist/assets/zh-CN-86269804.js +1 -0
  22. package/dist/favicon-dark.svg +1 -0
  23. package/dist/favicon.svg +1 -0
  24. package/dist/index.html +1 -171
  25. package/dist/manifest.webmanifest +1 -0
  26. package/dist/pwa-192x192.png +0 -0
  27. package/dist/pwa-512x512.png +0 -0
  28. package/dist/robots.txt +4 -0
  29. package/dist/safari-pinned-tab.svg +41 -0
  30. package/dist/sitemap.xml +1 -0
  31. package/dist/ssr-manifest.json +486 -0
  32. package/dist/sw.js +1 -0
  33. package/dist/test.html +1 -0
  34. package/dist/workbox-b8d87ee1.js +1 -0
  35. package/package.json +94 -50
  36. package/public/_headers +3 -0
  37. package/public/favicon-dark.svg +1 -0
  38. package/public/favicon.svg +1 -0
  39. package/public/pwa-192x192.png +0 -0
  40. package/public/pwa-512x512.png +0 -0
  41. package/public/safari-pinned-tab.svg +41 -0
  42. package/src/App.vue +33 -0
  43. package/src/auto-imports.d.ts +909 -0
  44. package/src/components/ContestIndex.vue +227 -0
  45. package/src/components/Footer.vue +94 -0
  46. package/src/components/GoBack.vue +22 -0
  47. package/src/components/NavBar.vue +152 -0
  48. package/src/components/SearchInput.vue +50 -0
  49. package/src/components/TheCounter.vue +19 -0
  50. package/src/components/TheInput.vue +20 -0
  51. package/src/components/board/Balloon.vue +5 -0
  52. package/src/components/board/Board.vue +396 -0
  53. package/src/components/board/BottomStatistics.vue +159 -0
  54. package/src/components/board/ContestStateBadge.vue +41 -0
  55. package/src/components/board/Export.vue +75 -0
  56. package/src/components/board/Modal.vue +107 -0
  57. package/src/components/board/ModalMenu.vue +64 -0
  58. package/src/components/board/OptionsModal.vue +179 -0
  59. package/src/components/board/Progress.less +442 -0
  60. package/src/components/board/Progress.vue +229 -0
  61. package/src/components/board/SecondLevelMenu.vue +190 -0
  62. package/src/components/board/Standings.less +1162 -0
  63. package/src/components/board/Standings.vue +154 -0
  64. package/src/components/board/StandingsAnnotate.vue +38 -0
  65. package/src/components/board/Statistics.vue +77 -0
  66. package/src/components/board/SubmissionsTable.vue +312 -0
  67. package/src/components/board/SubmissionsTableModal.vue +52 -0
  68. package/src/components/board/TeamAwards.vue +93 -0
  69. package/src/components/board/TeamInfoModal.vue +128 -0
  70. package/src/components/board/TeamProblemBlock.vue +100 -0
  71. package/src/components/board/TeamUI.vue +161 -0
  72. package/src/components/board/Utility.vue +28 -0
  73. package/src/components/icon/GirlIcon.vue +80 -0
  74. package/src/components/icon/RightArrowIcon.vue +26 -0
  75. package/src/components/icon/StarIcon.vue +19 -0
  76. package/src/components/table/TablePagination.vue +108 -0
  77. package/src/components.d.ts +44 -0
  78. package/src/composables/dark.ts +4 -0
  79. package/src/composables/pagination.ts +81 -0
  80. package/src/composables/statistics.ts +280 -0
  81. package/src/composables/useLocalStorage.ts +29 -0
  82. package/src/composables/useQueryBoardData.ts +43 -0
  83. package/src/composables/utils.ts +11 -0
  84. package/src/layouts/board-layout.vue +14 -0
  85. package/src/layouts/default.vue +10 -0
  86. package/src/layouts/home.vue +12 -0
  87. package/src/layouts/index-layout.vue +15 -0
  88. package/src/main.ts +36 -0
  89. package/src/modules/README.md +11 -0
  90. package/src/modules/i18n.ts +52 -0
  91. package/src/modules/nprogress.ts +15 -0
  92. package/src/modules/pinia.ts +18 -0
  93. package/src/modules/pwa.ts +15 -0
  94. package/src/modules/toast.ts +10 -0
  95. package/src/pages/[...all].vue +34 -0
  96. package/src/pages/about.md +21 -0
  97. package/src/pages/hi/[name].vue +50 -0
  98. package/src/pages/index.vue +129 -0
  99. package/src/pages/test.vue +57 -0
  100. package/src/shims.d.ts +16 -0
  101. package/src/stores/user.ts +36 -0
  102. package/src/styles/color.css +51 -0
  103. package/src/styles/main.css +30 -0
  104. package/src/styles/markdown.css +28 -0
  105. package/src/styles/submission-status.css +123 -0
  106. package/src/types.ts +3 -0
  107. package/tsconfig.json +39 -0
  108. package/uno.config.ts +65 -0
  109. package/vite.config.ts +176 -0
  110. package/dist/favicon.ico +0 -0
  111. package/dist/umi.00ae29f6.js +0 -1
  112. package/dist/umi.bd64c248.css +0 -1
@@ -0,0 +1,11 @@
1
+ ## Modules
2
+
3
+ A custom user module system. Place a `.ts` file with the following template, it will be installed automatically.
4
+
5
+ ```ts
6
+ import { type UserModule } from "~/types";
7
+
8
+ export const install: UserModule = ({ app, router, isClient }) => {
9
+ // do something
10
+ };
11
+ ```
@@ -0,0 +1,52 @@
1
+ import type { Locale } from "vue-i18n";
2
+ import { createI18n } from "vue-i18n";
3
+ import { type UserModule } from "~/types";
4
+
5
+ // Import i18n resources
6
+ // https://vitejs.dev/guide/features.html#glob-import
7
+ const i18n = createI18n({
8
+ legacy: false,
9
+ locale: "",
10
+ messages: {},
11
+ });
12
+
13
+ const localesMap = Object.fromEntries(
14
+ Object.entries(import.meta.glob("../../locales/*.yml"))
15
+ .map(([path, loadLocale]) => [path.match(/([\w-]*)\.yml$/)?.[1], loadLocale]),
16
+ ) as Record<Locale, () => Promise<{ default: Record<string, string> }>>;
17
+
18
+ export const availableLocales = Object.keys(localesMap);
19
+
20
+ const loadedLanguages: string[] = [];
21
+
22
+ function setI18nLanguage(lang: Locale) {
23
+ i18n.global.locale.value = lang as any;
24
+ if (typeof document !== "undefined") {
25
+ document.querySelector("html")?.setAttribute("lang", lang);
26
+ }
27
+
28
+ return lang;
29
+ }
30
+
31
+ export async function loadLanguageAsync(lang: string): Promise<Locale> {
32
+ // If the same language
33
+ if (i18n.global.locale.value === lang) {
34
+ return setI18nLanguage(lang);
35
+ }
36
+
37
+ // If the language was already loaded
38
+ if (loadedLanguages.includes(lang)) {
39
+ return setI18nLanguage(lang);
40
+ }
41
+
42
+ // If the language hasn't been loaded yet
43
+ const messages = await localesMap[lang]();
44
+ i18n.global.setLocaleMessage(lang, messages.default);
45
+ loadedLanguages.push(lang);
46
+ return setI18nLanguage(lang);
47
+ }
48
+
49
+ export const install: UserModule = async ({ app }) => {
50
+ app.use(i18n);
51
+ await loadLanguageAsync("en");
52
+ };
@@ -0,0 +1,15 @@
1
+ import NProgress from "nprogress";
2
+ import { type UserModule } from "~/types";
3
+
4
+ export const install: UserModule = ({ isClient, router }) => {
5
+ if (isClient) {
6
+ router.beforeEach((to, from) => {
7
+ if (to.path !== from.path) {
8
+ NProgress.start();
9
+ }
10
+ });
11
+ router.afterEach(() => {
12
+ NProgress.done();
13
+ });
14
+ }
15
+ };
@@ -0,0 +1,18 @@
1
+ import { createPinia } from "pinia";
2
+ import { type UserModule } from "~/types";
3
+
4
+ // Setup Pinia
5
+ // https://pinia.vuejs.org/
6
+ export const install: UserModule = ({ isClient, initialState, app }) => {
7
+ const pinia = createPinia();
8
+ app.use(pinia);
9
+
10
+ // Refer to
11
+ // https://github.com/antfu/vite-ssg/blob/main/README.md#state-serialization
12
+ // for other serialization strategies.
13
+ if (isClient) {
14
+ pinia.state.value = (initialState.pinia) || {};
15
+ } else {
16
+ initialState.pinia = pinia.state.value;
17
+ }
18
+ };
@@ -0,0 +1,15 @@
1
+ import { type UserModule } from "~/types";
2
+
3
+ // https://github.com/antfu/vite-plugin-pwa#automatic-reload-when-new-content-available
4
+ export const install: UserModule = ({ isClient, router }) => {
5
+ if (!isClient) {
6
+ return;
7
+ }
8
+
9
+ router.isReady()
10
+ .then(async () => {
11
+ const { registerSW } = await import("virtual:pwa-register");
12
+ registerSW({ immediate: true });
13
+ })
14
+ .catch(() => {});
15
+ };
@@ -0,0 +1,10 @@
1
+ import ToastPlugin from "vue-toast-notification";
2
+ import "vue-toast-notification/dist/theme-sugar.css";
3
+
4
+ import { type UserModule } from "~/types";
5
+
6
+ export const install: UserModule = ({ app }) => {
7
+ app.use(ToastPlugin, {
8
+ position: "bottom-right",
9
+ });
10
+ };
@@ -0,0 +1,34 @@
1
+ <script setup lang="ts">
2
+ const { t } = useI18n();
3
+ const route = useRoute();
4
+
5
+ const contestTypes = [
6
+ "camp",
7
+ "icpc",
8
+ "ccpc",
9
+ "provincial-contest",
10
+ ];
11
+
12
+ const isNotFound = !contestTypes.some(c => route.fullPath.startsWith(`/${c}`));
13
+ </script>
14
+
15
+ <template>
16
+ <div
17
+ v-if="isNotFound"
18
+ class="flex flex-col items-center"
19
+ >
20
+ <div text-4xl>
21
+ <div i-carbon-warning />
22
+ </div>
23
+ {{ t('not-found') }}
24
+ <GoBack />
25
+ </div>
26
+ <div v-else>
27
+ <Board />
28
+ </div>
29
+ </template>
30
+
31
+ <route lang="yaml">
32
+ meta:
33
+ layout: board-layout
34
+ </route>
@@ -0,0 +1,21 @@
1
+ ---
2
+ title: About
3
+ ---
4
+
5
+ <div class="text-center">
6
+ <!-- You can use Vue components inside markdown -->
7
+ <div i-carbon-dicom-overlay class="text-4xl -mb-6 m-auto" />
8
+ <h3>About</h3>
9
+ </div>
10
+
11
+ [Vitesse](https://github.com/antfu/vitesse) is an opinionated [Vite](https://github.com/vitejs/vite) starter template made by [@antfu](https://github.com/antfu) for mocking apps swiftly. With **file-based routing**, **components auto importing**, **markdown support**, I18n, PWA and uses **UnoCSS** for styling and icons.
12
+
13
+ ```js
14
+ // syntax highlighting example
15
+ function vitesse() {
16
+ const foo = "bar";
17
+ console.log(foo);
18
+ }
19
+ ```
20
+
21
+ Check out the [GitHub repo](https://github.com/antfu/vitesse) for more details.
@@ -0,0 +1,50 @@
1
+ <script setup lang="ts">
2
+ const props = defineProps<{ name: string }>();
3
+ const router = useRouter();
4
+ const user = useUserStore();
5
+ const { t } = useI18n();
6
+
7
+ watchEffect(() => {
8
+ user.setNewName(props.name);
9
+ });
10
+ </script>
11
+
12
+ <template>
13
+ <div
14
+ w-full
15
+ flex flex-col justify-center items-center
16
+ >
17
+ <div text-4xl>
18
+ <div i-carbon-pedestrian inline-block />
19
+ </div>
20
+ <p>
21
+ {{ t('intro.hi', { name: props.name }) }}
22
+ </p>
23
+
24
+ <p text-sm opacity-75>
25
+ <em>{{ t('intro.dynamic-route') }}</em>
26
+ </p>
27
+
28
+ <template v-if="user.otherNames.length">
29
+ <p mt-4 text-sm>
30
+ <span opacity-75>{{ t('intro.aka') }}:</span>
31
+ <ul>
32
+ <li v-for="otherName in user.otherNames" :key="otherName">
33
+ <RouterLink :to="`/hi/${otherName}`" replace>
34
+ {{ otherName }}
35
+ </RouterLink>
36
+ </li>
37
+ </ul>
38
+ </p>
39
+ </template>
40
+
41
+ <div>
42
+ <button
43
+ m="3 t6" text-sm btn
44
+ @click="router.back()"
45
+ >
46
+ {{ t('button.back') }}
47
+ </button>
48
+ </div>
49
+ </div>
50
+ </template>
@@ -0,0 +1,129 @@
1
+ <script setup lang="ts">
2
+ import { useFetch } from "@vueuse/core";
3
+ import { useRouteQuery } from "@vueuse/router";
4
+ import { createContestIndexList } from "@xcpcio/core";
5
+ import type { ContestIndexList } from "@xcpcio/core";
6
+ import SearchInput from "~/components/SearchInput.vue";
7
+
8
+ const { t } = useI18n();
9
+
10
+ const now = ref(new Date());
11
+ const url = ref(`${window.DATA_HOST}index/contest_list.json?t=${now.value.getTime()}`);
12
+ const refetch = ref(false);
13
+
14
+ const s = useRouteQuery<string | null>("s", "", { transform: String });
15
+ const searchText = ref<string | null>(s.value);
16
+ const searchInputRef = ref<InstanceType<typeof SearchInput> | null>(null);
17
+
18
+ const contestIndexAllList = ref([] as ContestIndexList);
19
+ const contestIndexList = ref([] as ContestIndexList);
20
+
21
+ const {
22
+ error,
23
+ isFetching,
24
+ isFinished,
25
+ } = useFetch(url, {
26
+ refetch,
27
+ afterFetch: (ctx) => {
28
+ contestIndexAllList.value = createContestIndexList(JSON.parse(ctx.data));
29
+ contestIndexList.value = contestIndexAllList.value.map(c => c);
30
+
31
+ return ctx;
32
+ },
33
+ }).get();
34
+
35
+ watch(searchText, () => {
36
+ contestIndexList.value = contestIndexAllList.value.filter((c) => {
37
+ if (searchText.value?.length === 0) {
38
+ searchText.value = null;
39
+ }
40
+
41
+ if (searchText.value === null) {
42
+ s.value = undefined as unknown as string;
43
+ return true;
44
+ }
45
+
46
+ s.value = searchText.value;
47
+
48
+ if (c.contest.name.includes(searchText.value)
49
+ || c.contest.name.toLowerCase().includes(searchText.value.toLowerCase())) {
50
+ return true;
51
+ }
52
+
53
+ return false;
54
+ });
55
+ });
56
+
57
+ function clearSearch() {
58
+ searchText.value = null;
59
+ searchInputRef.value?.focus();
60
+ }
61
+ </script>
62
+
63
+ <template>
64
+ <div
65
+ class="sm:w-[1024px] lg:w-screen"
66
+ lg:of-x-hidden
67
+ flex flex-col justify-center items-center
68
+ >
69
+ <div>
70
+ <div
71
+ v-if="isFetching"
72
+ class="sm:w-[1000px] lg:w-screen"
73
+ flex justify-center
74
+ >
75
+ {{ t("common.loading") }}...
76
+ </div>
77
+
78
+ <div v-if="error">
79
+ {{ error }}
80
+ </div>
81
+
82
+ <div
83
+ v-if="isFinished"
84
+ class="sm:w-[1000px] lg:w-screen min-h-120"
85
+ flex flex-col items-center
86
+ >
87
+ <div w-240>
88
+ <SearchInput
89
+ ref="searchInputRef"
90
+ v-model="searchText"
91
+ />
92
+ </div>
93
+
94
+ <div
95
+ v-if="contestIndexList.length"
96
+ mt-4
97
+ >
98
+ <template
99
+ v-for="item in contestIndexList"
100
+ :key="item.boardLink"
101
+ >
102
+ <ContestIndex
103
+ :data="item"
104
+ />
105
+ </template>
106
+ </div>
107
+
108
+ <div v-else p10>
109
+ <div op40 italic mb5>
110
+ No result found
111
+ </div>
112
+ <div row justify-center>
113
+ <button
114
+ btn
115
+ @click="clearSearch()"
116
+ >
117
+ Clear search
118
+ </button>
119
+ </div>
120
+ </div>
121
+ </div>
122
+ </div>
123
+ </div>
124
+ </template>
125
+
126
+ <route lang="yaml">
127
+ meta:
128
+ layout: index-layout
129
+ </route>
@@ -0,0 +1,57 @@
1
+ <script setup lang="ts">
2
+ defineOptions({
3
+ name: "IndexPage",
4
+ });
5
+ const user = useUserStore();
6
+ const name = ref(user.savedName);
7
+
8
+ const router = useRouter();
9
+ function go() {
10
+ if (name.value) {
11
+ router.push(`/hi/${encodeURIComponent(name.value)}`);
12
+ }
13
+ }
14
+
15
+ const { t } = useI18n();
16
+ </script>
17
+
18
+ <template>
19
+ <div>
20
+ <div text-4xl>
21
+ <div i-carbon-campsite />
22
+ </div>
23
+ <p>
24
+ <a rel="noreferrer" href="https://github.com/antfu/vitesse" target="_blank">
25
+ Vitesse
26
+ </a>
27
+ </p>
28
+ <p>
29
+ <em text-sm opacity-75>{{ t('intro.desc') }}</em>
30
+ </p>
31
+
32
+ <div py-4 />
33
+
34
+ <TheInput
35
+ v-model="name"
36
+ :placeholder="t('intro.whats-your-name')"
37
+ autocomplete="false"
38
+ @keydown.enter="go"
39
+ />
40
+ <label class="hidden" for="input">{{ t('intro.whats-your-name') }}</label>
41
+
42
+ <div>
43
+ <button
44
+ m-3 text-sm btn
45
+ :disabled="!name"
46
+ @click="go"
47
+ >
48
+ {{ t('button.go') }}
49
+ </button>
50
+ </div>
51
+ </div>
52
+ </template>
53
+
54
+ <route lang="yaml">
55
+ meta:
56
+ layout: home
57
+ </route>
package/src/shims.d.ts ADDED
@@ -0,0 +1,16 @@
1
+ declare interface Window {
2
+ DATA_HOST: string;
3
+ }
4
+
5
+ // with vite-plugin-vue-markdown, markdown files can be treated as Vue components
6
+ declare module '*.md' {
7
+ import { type DefineComponent } from 'vue'
8
+ const component: DefineComponent<{}, {}, any>
9
+ export default component
10
+ }
11
+
12
+ declare module '*.vue' {
13
+ import { type DefineComponent } from 'vue'
14
+ const component: DefineComponent<{}, {}, any>
15
+ export default component
16
+ }
@@ -0,0 +1,36 @@
1
+ import { acceptHMRUpdate, defineStore } from "pinia";
2
+
3
+ export const useUserStore = defineStore("user", () => {
4
+ /**
5
+ * Current name of the user.
6
+ */
7
+ const savedName = ref("");
8
+ const previousNames = ref(new Set<string>());
9
+
10
+ const usedNames = computed(() => Array.from(previousNames.value));
11
+ const otherNames = computed(() => usedNames.value.filter(name => name !== savedName.value));
12
+
13
+ /**
14
+ * Changes the current name of the user and saves the one that was used
15
+ * before.
16
+ *
17
+ * @param name - new name to set
18
+ */
19
+ function setNewName(name: string) {
20
+ if (savedName.value) {
21
+ previousNames.value.add(savedName.value);
22
+ }
23
+
24
+ savedName.value = name;
25
+ }
26
+
27
+ return {
28
+ setNewName,
29
+ otherNames,
30
+ savedName,
31
+ };
32
+ });
33
+
34
+ if (import.meta.hot) {
35
+ import.meta.hot.accept(acceptHMRUpdate(useUserStore as any, import.meta.hot));
36
+ }
@@ -0,0 +1,51 @@
1
+ html {
2
+ --global-background: #fff;
3
+
4
+ --theme-status-pending: #6cf;
5
+ --theme-status-configuration-error: #e28989;
6
+ --theme-status-system-error: grey;
7
+ --theme-status-compilation-error: #1679dc;
8
+ --theme-status-canceled: #676fc1;
9
+ --theme-status-file-error: darkorchid;
10
+ --theme-status-runtime-error: darkorchid;
11
+ --theme-status-time-limit-exceeded: sandybrown;
12
+ --theme-status-memory-limit-exceeded: sandybrown;
13
+ --theme-status-output-limit-exceeded: sandybrown;
14
+ --theme-status-partially-correct: #01bab2;
15
+ --theme-status-wrong-answer: #ff4545;
16
+ --theme-status-accepted: #37da58;
17
+ --theme-status-judgement-failed: #FF5722;
18
+ --theme-status-waiting: grey;
19
+ --theme-status-preparing: #de4d9e;
20
+ --theme-status-compiling: #00b5ad;
21
+ --theme-status-running: #6cf;
22
+ --theme-status-skipped: #78909C;
23
+ --theme-status-unknown: #000;
24
+ --theme-status-undefined: var(--theme-status-unknown);
25
+ }
26
+
27
+ html.dark {
28
+ --global-background: #323443;
29
+
30
+ --theme-status-pending: #6cf;
31
+ --theme-status-configuration-error: #e28989;
32
+ --theme-status-system-error: #a5a5a5;
33
+ --theme-status-compilation-error: #3387da;
34
+ --theme-status-canceled: #6770d0;
35
+ --theme-status-file-error: #bc35ff;
36
+ --theme-status-runtime-error: #bc35ff;
37
+ --theme-status-time-limit-exceeded: #ff9840;
38
+ --theme-status-memory-limit-exceeded: #ff9840;
39
+ --theme-status-output-limit-exceeded: #ff9840;
40
+ --theme-status-partially-correct: #01bab2;
41
+ --theme-status-wrong-answer: #ff4545;
42
+ --theme-status-accepted: #37da58;
43
+ --theme-status-judgement-failed: #FF5722;
44
+ --theme-status-waiting: #a5a5a5;
45
+ --theme-status-preparing: #de4d9e;
46
+ --theme-status-compiling: #00b5ad;
47
+ --theme-status-running: #6cf;
48
+ --theme-status-skipped: #78909C;
49
+ --theme-status-unknown: #fff;
50
+ --theme-status-undefined: var(--theme-status-unknown);
51
+ }
@@ -0,0 +1,30 @@
1
+ @import './markdown.css';
2
+ @import './color.css';
3
+
4
+ html,
5
+ body,
6
+ #app {
7
+ height: 100%;
8
+ margin: 0;
9
+ padding: 0;
10
+ }
11
+
12
+ html.dark {
13
+ background: var(--global-background);
14
+ color-scheme: dark;
15
+ }
16
+
17
+ #nprogress {
18
+ pointer-events: none;
19
+ }
20
+
21
+ #nprogress .bar {
22
+ background: rgb(13,148,136);
23
+ opacity: 0.75;
24
+ position: fixed;
25
+ z-index: 1031;
26
+ top: 0;
27
+ left: 0;
28
+ width: 100%;
29
+ height: 2px;
30
+ }
@@ -0,0 +1,28 @@
1
+ .prose pre:not(.shiki) {
2
+ padding: 0;
3
+ }
4
+
5
+ .prose .shiki {
6
+ font-family: 'DM Mono', monospace;
7
+ font-size: 1.2em;
8
+ line-height: 1.4;
9
+ }
10
+
11
+ .prose img {
12
+ width: 100%;
13
+ }
14
+
15
+ .shiki-light {
16
+ background: #f8f8f8 !important;
17
+ }
18
+ .shiki-dark {
19
+ background: #0e0e0e !important;
20
+ }
21
+
22
+ html.dark .shiki-light {
23
+ display: none;
24
+ }
25
+
26
+ html:not(.dark) .shiki-dark {
27
+ display: none;
28
+ }