From 130ff361c34282dc1e472adff53dc04ddec28c65 Mon Sep 17 00:00:00 2001
From: syuilo <4439005+syuilo@users.noreply.github.com>
Date: Mon, 19 Aug 2024 17:32:27 +0900
Subject: refactor

---
 packages/frontend/src/components/MkSubNoteContent.vue | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

(limited to 'packages/frontend/src/components/MkSubNoteContent.vue')

diff --git a/packages/frontend/src/components/MkSubNoteContent.vue b/packages/frontend/src/components/MkSubNoteContent.vue
index 9a07826f1a..25412cc2e5 100644
--- a/packages/frontend/src/components/MkSubNoteContent.vue
+++ b/packages/frontend/src/components/MkSubNoteContent.vue
@@ -62,7 +62,7 @@ const collapsed = ref(isLong);
 			left: 0;
 			width: 100%;
 			height: 64px;
-			background: linear-gradient(0deg, var(--panel), var(--X15));
+			background: linear-gradient(0deg, var(--panel), color(from var(--panel) srgb r g b / 0));
 
 			> .fadeLabel {
 				display: inline-block;
-- 
cgit v1.2.3-freya


From 837a8e15d893a670ab2ce51b3ec87e6b62a51da7 Mon Sep 17 00:00:00 2001
From: かっこかり <67428053+kakkokari-gtyih@users.noreply.github.com>
Date: Tue, 10 Sep 2024 18:39:53 +0900
Subject: refactor(frontend): frontend-embed/src/to-be-sharedを共通化 (#14536)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* refactor(frontend): shouldCollapsedを共通化

* refactor(frontend): config.js, worker-multi-dispatch.js, intl-const.jsを共通化

* fix(frontend-shared): fix type error

* refactor(frontend): is-link.jsと、同一の振る舞いをする記述を共通化

* fix

* fix lint

* lint fixes
---
 packages/frontend-embed/src/boot.ts                |  2 +-
 packages/frontend-embed/src/components/EmAcct.vue  |  2 +-
 .../src/components/EmImgWithBlurhash.vue           |  2 +-
 packages/frontend-embed/src/components/EmLink.vue  |  2 +-
 .../frontend-embed/src/components/EmMention.vue    |  2 +-
 packages/frontend-embed/src/components/EmMfm.ts    |  2 +-
 packages/frontend-embed/src/components/EmNote.vue  |  4 +-
 .../src/components/EmNoteDetailed.vue              |  4 +-
 .../src/components/EmSubNoteContent.vue            |  4 +-
 packages/frontend-embed/src/components/EmTime.vue  |  2 +-
 packages/frontend-embed/src/components/EmUrl.vue   |  2 +-
 packages/frontend-embed/src/config.ts              | 18 -----
 packages/frontend-embed/src/i18n.ts                |  2 +-
 packages/frontend-embed/src/misskey-api.ts         |  2 +-
 packages/frontend-embed/src/pages/clip.vue         |  4 +-
 packages/frontend-embed/src/pages/tag.vue          |  4 +-
 .../frontend-embed/src/pages/user-timeline.vue     |  2 +-
 .../frontend-embed/src/to-be-shared/collapsed.ts   | 22 ------
 .../frontend-embed/src/to-be-shared/intl-const.ts  | 50 -------------
 .../frontend-embed/src/to-be-shared/is-link.ts     | 12 ---
 .../src/to-be-shared/worker-multi-dispatch.ts      | 82 ---------------------
 packages/frontend-embed/src/utils.ts               |  2 +-
 packages/frontend-shared/@types/global.d.ts        | 25 +++++++
 packages/frontend-shared/eslint.config.js          |  6 +-
 packages/frontend-shared/js/collapsed.ts           | 22 ++++++
 packages/frontend-shared/js/config.ts              | 28 +++++++
 packages/frontend-shared/js/emoji-base.ts          |  4 +-
 packages/frontend-shared/js/intl-const.ts          | 51 +++++++++++++
 packages/frontend-shared/js/is-link.ts             | 12 +++
 .../frontend-shared/js/worker-multi-dispatch.ts    | 86 ++++++++++++++++++++++
 packages/frontend-shared/tsconfig.json             |  7 ++
 packages/frontend/src/account.ts                   |  2 +-
 packages/frontend/src/boot/common.ts               |  2 +-
 packages/frontend/src/boot/main-boot.ts            |  2 +-
 .../frontend/src/components/MkAccountMoved.vue     |  2 +-
 .../frontend/src/components/MkCropperDialog.vue    |  2 +-
 packages/frontend/src/components/MkDonation.vue    |  2 +-
 .../src/components/MkEmbedCodeGenDialog.vue        |  2 +-
 .../frontend/src/components/MkFollowButton.vue     |  2 +-
 .../frontend/src/components/MkImgWithBlurhash.vue  |  2 +-
 .../frontend/src/components/MkInstanceTicker.vue   |  2 +-
 packages/frontend/src/components/MkLink.vue        |  2 +-
 packages/frontend/src/components/MkMention.vue     |  2 +-
 packages/frontend/src/components/MkNote.vue        | 15 +---
 .../frontend/src/components/MkNoteDetailed.vue     | 11 +--
 packages/frontend/src/components/MkPageWindow.vue  |  2 +-
 packages/frontend/src/components/MkPoll.vue        |  2 +-
 packages/frontend/src/components/MkPostForm.vue    |  2 +-
 packages/frontend/src/components/MkPreview.vue     |  2 +-
 packages/frontend/src/components/MkSignin.vue      |  2 +-
 .../src/components/MkSignupDialog.form.vue         |  2 +-
 .../src/components/MkSourceCodeAvailablePopup.vue  |  2 +-
 .../frontend/src/components/MkSubNoteContent.vue   |  2 +-
 .../frontend/src/components/MkTutorialDialog.vue   |  2 +-
 packages/frontend/src/components/MkUpdated.vue     |  2 +-
 packages/frontend/src/components/MkUrlPreview.vue  |  4 +-
 .../frontend/src/components/MkUserSelectDialog.vue |  2 +-
 .../frontend/src/components/MkUserSetupDialog.vue  |  2 +-
 .../frontend/src/components/MkVisitorDashboard.vue |  2 +-
 packages/frontend/src/components/MkWidgets.vue     |  8 +-
 .../frontend/src/components/MkYouTubePlayer.vue    |  2 +-
 packages/frontend/src/components/global/MkA.vue    |  2 +-
 packages/frontend/src/components/global/MkAcct.vue |  2 +-
 packages/frontend/src/components/global/MkAd.vue   |  2 +-
 .../components/global/MkMisskeyFlavoredMarkdown.ts |  2 +-
 .../src/components/global/MkTime.stories.impl.ts   |  2 +-
 packages/frontend/src/components/global/MkTime.vue |  2 +-
 packages/frontend/src/components/global/MkUrl.vue  |  2 +-
 packages/frontend/src/config.ts                    | 27 -------
 packages/frontend/src/filters/date.ts              |  2 +-
 packages/frontend/src/filters/number.ts            |  2 +-
 packages/frontend/src/filters/user.ts              |  2 +-
 packages/frontend/src/i18n.ts                      |  2 +-
 packages/frontend/src/navbar.ts                    |  2 +-
 packages/frontend/src/pages/_error_.vue            |  2 +-
 packages/frontend/src/pages/about-misskey.vue      |  2 +-
 packages/frontend/src/pages/about.overview.vue     |  2 +-
 packages/frontend/src/pages/admin-user.vue         |  2 +-
 packages/frontend/src/pages/admin/branding.vue     |  2 +-
 packages/frontend/src/pages/admin/queue.vue        |  2 +-
 packages/frontend/src/pages/channel.vue            |  2 +-
 packages/frontend/src/pages/clip.vue               |  2 +-
 .../frontend/src/pages/drop-and-fusion.game.vue    |  2 +-
 packages/frontend/src/pages/flash/flash.vue        |  2 +-
 packages/frontend/src/pages/gallery/post.vue       |  2 +-
 .../frontend/src/pages/page-editor/page-editor.vue |  2 +-
 packages/frontend/src/pages/page.vue               |  2 +-
 packages/frontend/src/pages/reversi/game.board.vue |  2 +-
 packages/frontend/src/pages/reversi/game.vue       |  2 +-
 packages/frontend/src/pages/role.vue               |  2 +-
 packages/frontend/src/pages/settings/general.vue   |  2 +-
 .../src/pages/settings/preferences-backups.vue     |  2 +-
 packages/frontend/src/pages/theme-editor.vue       |  2 +-
 packages/frontend/src/pages/welcome.setup.vue      |  2 +-
 packages/frontend/src/pages/welcome.vue            |  2 +-
 packages/frontend/src/scripts/aiscript/api.ts      |  2 +-
 packages/frontend/src/scripts/collapsed.ts         | 22 ------
 packages/frontend/src/scripts/gen-search-query.ts  |  2 +-
 packages/frontend/src/scripts/get-embed-code.ts    |  2 +-
 packages/frontend/src/scripts/get-note-menu.ts     |  2 +-
 packages/frontend/src/scripts/get-user-menu.ts     |  2 +-
 packages/frontend/src/scripts/initialize-sw.ts     |  2 +-
 packages/frontend/src/scripts/intl-const.ts        |  2 +-
 packages/frontend/src/scripts/is-link.ts           | 12 ---
 packages/frontend/src/scripts/media-proxy.ts       |  2 +-
 packages/frontend/src/scripts/misskey-api.ts       |  2 +-
 .../frontend/src/scripts/player-url-transform.ts   |  2 +-
 packages/frontend/src/scripts/popout.ts            |  2 +-
 packages/frontend/src/scripts/upload.ts            |  2 +-
 .../frontend/src/scripts/worker-multi-dispatch.ts  | 82 ---------------------
 packages/frontend/src/store.ts                     |  4 +-
 packages/frontend/src/stream.ts                    |  2 +-
 packages/frontend/src/ui/_common_/common.ts        |  2 +-
 packages/frontend/src/ui/classic.sidebar.vue       |  2 +-
 packages/frontend/src/ui/classic.vue               | 10 +--
 packages/frontend/src/ui/deck/main-column.vue      |  7 +-
 packages/frontend/src/ui/minimum.vue               |  2 +-
 packages/frontend/src/ui/universal.vue             |  9 +--
 packages/frontend/src/ui/visitor.vue               |  2 +-
 packages/frontend/src/ui/zen.vue                   |  2 +-
 .../frontend/src/widgets/WidgetInstanceInfo.vue    |  2 +-
 packages/frontend/src/widgets/WidgetRss.vue        |  2 +-
 packages/frontend/src/widgets/WidgetRssTicker.vue  |  2 +-
 123 files changed, 356 insertions(+), 484 deletions(-)
 delete mode 100644 packages/frontend-embed/src/config.ts
 delete mode 100644 packages/frontend-embed/src/to-be-shared/collapsed.ts
 delete mode 100644 packages/frontend-embed/src/to-be-shared/intl-const.ts
 delete mode 100644 packages/frontend-embed/src/to-be-shared/is-link.ts
 delete mode 100644 packages/frontend-embed/src/to-be-shared/worker-multi-dispatch.ts
 create mode 100644 packages/frontend-shared/@types/global.d.ts
 create mode 100644 packages/frontend-shared/js/collapsed.ts
 create mode 100644 packages/frontend-shared/js/config.ts
 create mode 100644 packages/frontend-shared/js/intl-const.ts
 create mode 100644 packages/frontend-shared/js/is-link.ts
 create mode 100644 packages/frontend-shared/js/worker-multi-dispatch.ts
 delete mode 100644 packages/frontend/src/config.ts
 delete mode 100644 packages/frontend/src/scripts/collapsed.ts
 delete mode 100644 packages/frontend/src/scripts/is-link.ts
 delete mode 100644 packages/frontend/src/scripts/worker-multi-dispatch.ts

(limited to 'packages/frontend/src/components/MkSubNoteContent.vue')

diff --git a/packages/frontend-embed/src/boot.ts b/packages/frontend-embed/src/boot.ts
index 6c73fecd76..fcea7d32ea 100644
--- a/packages/frontend-embed/src/boot.ts
+++ b/packages/frontend-embed/src/boot.ts
@@ -17,7 +17,7 @@ import { applyTheme, assertIsTheme } from '@/theme.js';
 import { fetchCustomEmojis } from '@/custom-emojis.js';
 import { DI } from '@/di.js';
 import { serverMetadata } from '@/server-metadata.js';
-import { url } from '@/config.js';
+import { url } from '@@/js/config.js';
 import { parseEmbedParams } from '@@/js/embed-page.js';
 import { postMessageToParentWindow, setIframeId } from '@/post-message.js';
 
diff --git a/packages/frontend-embed/src/components/EmAcct.vue b/packages/frontend-embed/src/components/EmAcct.vue
index 07315e6a8b..6856b8272e 100644
--- a/packages/frontend-embed/src/components/EmAcct.vue
+++ b/packages/frontend-embed/src/components/EmAcct.vue
@@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 <script lang="ts" setup>
 import * as Misskey from 'misskey-js';
 import { toUnicode } from 'punycode/';
-import { host as hostRaw } from '@/config.js';
+import { host as hostRaw } from '@@/js/config.js';
 
 defineProps<{
 	user: Misskey.entities.UserLite;
diff --git a/packages/frontend-embed/src/components/EmImgWithBlurhash.vue b/packages/frontend-embed/src/components/EmImgWithBlurhash.vue
index d19cd08d0a..bf976c71ae 100644
--- a/packages/frontend-embed/src/components/EmImgWithBlurhash.vue
+++ b/packages/frontend-embed/src/components/EmImgWithBlurhash.vue
@@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 <script lang="ts">
 import DrawBlurhash from '@/workers/draw-blurhash?worker';
 import TestWebGL2 from '@/workers/test-webgl2?worker';
-import { WorkerMultiDispatch } from '@/to-be-shared/worker-multi-dispatch.js';
+import { WorkerMultiDispatch } from '@@/js/worker-multi-dispatch.js';
 import { extractAvgColorFromBlurhash } from '@@/js/extract-avg-color-from-blurhash.js';
 
 const canvasPromise = new Promise<WorkerMultiDispatch | HTMLCanvasElement>(resolve => {
diff --git a/packages/frontend-embed/src/components/EmLink.vue b/packages/frontend-embed/src/components/EmLink.vue
index 319ad72399..aec9b33072 100644
--- a/packages/frontend-embed/src/components/EmLink.vue
+++ b/packages/frontend-embed/src/components/EmLink.vue
@@ -16,7 +16,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 <script lang="ts" setup>
 import { ref } from 'vue';
 import EmA from './EmA.vue';
-import { url as local } from '@/config.js';
+import { url as local } from '@@/js/config.js';
 
 const props = withDefaults(defineProps<{
 	url: string;
diff --git a/packages/frontend-embed/src/components/EmMention.vue b/packages/frontend-embed/src/components/EmMention.vue
index 5eadf828c7..777033bd3e 100644
--- a/packages/frontend-embed/src/components/EmMention.vue
+++ b/packages/frontend-embed/src/components/EmMention.vue
@@ -16,7 +16,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 import { toUnicode } from 'punycode';
 import { } from 'vue';
 import tinycolor from 'tinycolor2';
-import { host as localHost } from '@/config.js';
+import { host as localHost } from '@@/js/config.js';
 
 const props = defineProps<{
 	username: string;
diff --git a/packages/frontend-embed/src/components/EmMfm.ts b/packages/frontend-embed/src/components/EmMfm.ts
index 7543d3cd54..b2bcf4597e 100644
--- a/packages/frontend-embed/src/components/EmMfm.ts
+++ b/packages/frontend-embed/src/components/EmMfm.ts
@@ -13,7 +13,7 @@ import EmMention from '@/components/EmMention.vue';
 import EmEmoji from '@/components/EmEmoji.vue';
 import EmCustomEmoji from '@/components/EmCustomEmoji.vue';
 import EmA from '@/components/EmA.vue';
-import { host } from '@/config.js';
+import { host } from '@@/js/config.js';
 
 function safeParseFloat(str: unknown): number | null {
 	if (typeof str !== 'string' || str === '') return null;
diff --git a/packages/frontend-embed/src/components/EmNote.vue b/packages/frontend-embed/src/components/EmNote.vue
index dce997f0ef..02475898c5 100644
--- a/packages/frontend-embed/src/components/EmNote.vue
+++ b/packages/frontend-embed/src/components/EmNote.vue
@@ -121,8 +121,8 @@ import EmUserName from '@/components/EmUserName.vue';
 import EmTime from '@/components/EmTime.vue';
 import { userPage } from '@/utils.js';
 import { i18n } from '@/i18n.js';
-import { shouldCollapsed } from '@/to-be-shared/collapsed.js';
-import { url } from '@/config.js';
+import { shouldCollapsed } from '@@/js/collapsed.js';
+import { url } from '@@/js/config.js';
 
 function getAppearNote(note: Misskey.entities.Note) {
 	return Misskey.note.isPureRenote(note) ? note.renote : note;
diff --git a/packages/frontend-embed/src/components/EmNoteDetailed.vue b/packages/frontend-embed/src/components/EmNoteDetailed.vue
index 74a26856c8..8169f500a9 100644
--- a/packages/frontend-embed/src/components/EmNoteDetailed.vue
+++ b/packages/frontend-embed/src/components/EmNoteDetailed.vue
@@ -142,9 +142,9 @@ import EmAcct from '@/components/EmAcct.vue';
 import { userPage } from '@/utils.js';
 import { notePage } from '@/utils.js';
 import { i18n } from '@/i18n.js';
-import { shouldCollapsed } from '@/to-be-shared/collapsed.js';
+import { shouldCollapsed } from '@@/js/collapsed.js';
 import { serverMetadata } from '@/server-metadata.js';
-import { url } from '@/config.js';
+import { url } from '@@/js/config.js';
 import EmMfm from '@/components/EmMfm.js';
 
 const props = defineProps<{
diff --git a/packages/frontend-embed/src/components/EmSubNoteContent.vue b/packages/frontend-embed/src/components/EmSubNoteContent.vue
index f7d653ab3f..db2666a45f 100644
--- a/packages/frontend-embed/src/components/EmSubNoteContent.vue
+++ b/packages/frontend-embed/src/components/EmSubNoteContent.vue
@@ -35,8 +35,8 @@ import * as Misskey from 'misskey-js';
 import EmMediaList from '@/components/EmMediaList.vue';
 import EmPoll from '@/components/EmPoll.vue';
 import { i18n } from '@/i18n.js';
-import { url } from '@/config.js';
-import { shouldCollapsed } from '@/to-be-shared/collapsed.js';
+import { url } from '@@/js/config.js';
+import { shouldCollapsed } from '@@/js/collapsed.js';
 import EmA from '@/components/EmA.vue';
 import EmMfm from '@/components/EmMfm.js';
 
diff --git a/packages/frontend-embed/src/components/EmTime.vue b/packages/frontend-embed/src/components/EmTime.vue
index a8627e02c8..c3986f7d70 100644
--- a/packages/frontend-embed/src/components/EmTime.vue
+++ b/packages/frontend-embed/src/components/EmTime.vue
@@ -15,7 +15,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 <script lang="ts" setup>
 import { onMounted, onUnmounted, ref, computed } from 'vue';
 import { i18n } from '@/i18n.js';
-import { dateTimeFormat } from '@/to-be-shared/intl-const.js';
+import { dateTimeFormat } from '@@/js/intl-const.js';
 
 const props = withDefaults(defineProps<{
 	time: Date | string | number | null;
diff --git a/packages/frontend-embed/src/components/EmUrl.vue b/packages/frontend-embed/src/components/EmUrl.vue
index a96bfdb493..94424cab28 100644
--- a/packages/frontend-embed/src/components/EmUrl.vue
+++ b/packages/frontend-embed/src/components/EmUrl.vue
@@ -27,7 +27,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 import { ref } from 'vue';
 import { toUnicode as decodePunycode } from 'punycode/';
 import EmA from './EmA.vue';
-import { url as local } from '@/config.js';
+import { url as local } from '@@/js/config.js';
 
 function safeURIDecode(str: string): string {
 	try {
diff --git a/packages/frontend-embed/src/config.ts b/packages/frontend-embed/src/config.ts
deleted file mode 100644
index f9850ba461..0000000000
--- a/packages/frontend-embed/src/config.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-/*
- * SPDX-FileCopyrightText: syuilo and misskey-project
- * SPDX-License-Identifier: AGPL-3.0-only
- */
-
-const address = new URL(document.querySelector<HTMLMetaElement>('meta[property="instance_url"]')?.content || location.href);
-const siteName = document.querySelector<HTMLMetaElement>('meta[property="og:site_name"]')?.content;
-
-export const host = address.host;
-export const hostname = address.hostname;
-export const url = address.origin;
-export const apiUrl = location.origin + '/api';
-export const lang = localStorage.getItem('lang') ?? 'en-US';
-export const langs = _LANGS_;
-const preParseLocale = localStorage.getItem('locale');
-export const locale = preParseLocale ? JSON.parse(preParseLocale) : null;
-export const instanceName = siteName === 'Misskey' || siteName == null ? host : siteName;
-export const debug = localStorage.getItem('debug') === 'true';
diff --git a/packages/frontend-embed/src/i18n.ts b/packages/frontend-embed/src/i18n.ts
index 17e787f9fc..6ad503b089 100644
--- a/packages/frontend-embed/src/i18n.ts
+++ b/packages/frontend-embed/src/i18n.ts
@@ -6,7 +6,7 @@
 import { markRaw } from 'vue';
 import { I18n } from '@@/js/i18n.js';
 import type { Locale } from '../../../locales/index.js';
-import { locale } from '@/config.js';
+import { locale } from '@@/js/config.js';
 
 export const i18n = markRaw(new I18n<Locale>(locale, _DEV_));
 
diff --git a/packages/frontend-embed/src/misskey-api.ts b/packages/frontend-embed/src/misskey-api.ts
index 13630590b6..0d3c679359 100644
--- a/packages/frontend-embed/src/misskey-api.ts
+++ b/packages/frontend-embed/src/misskey-api.ts
@@ -5,7 +5,7 @@
 
 import * as Misskey from 'misskey-js';
 import { ref } from 'vue';
-import { apiUrl } from '@/config.js';
+import { apiUrl } from '@@/js/config.js';
 
 export const pendingApiRequestsCount = ref(0);
 
diff --git a/packages/frontend-embed/src/pages/clip.vue b/packages/frontend-embed/src/pages/clip.vue
index 29b5480c35..957d425d93 100644
--- a/packages/frontend-embed/src/pages/clip.vue
+++ b/packages/frontend-embed/src/pages/clip.vue
@@ -50,8 +50,8 @@ import EmTimelineContainer from '@/components/EmTimelineContainer.vue';
 import { misskeyApi } from '@/misskey-api.js';
 import { i18n } from '@/i18n.js';
 import { serverMetadata } from '@/server-metadata.js';
-import { url, instanceName } from '@/config.js';
-import { isLink } from '@/to-be-shared/is-link.js';
+import { url, instanceName } from '@@/js/config.js';
+import { isLink } from '@@/js/is-link.js';
 import { defaultEmbedParams } from '@@/js/embed-page.js';
 import { DI } from '@/di.js';
 
diff --git a/packages/frontend-embed/src/pages/tag.vue b/packages/frontend-embed/src/pages/tag.vue
index ea45d7129e..d9759a47e7 100644
--- a/packages/frontend-embed/src/pages/tag.vue
+++ b/packages/frontend-embed/src/pages/tag.vue
@@ -46,8 +46,8 @@ import XNotFound from '@/pages/not-found.vue';
 import EmTimelineContainer from '@/components/EmTimelineContainer.vue';
 import { i18n } from '@/i18n.js';
 import { serverMetadata } from '@/server-metadata.js';
-import { url, instanceName } from '@/config.js';
-import { isLink } from '@/to-be-shared/is-link.js';
+import { url, instanceName } from '@@/js/config.js';
+import { isLink } from '@@/js/is-link.js';
 import { DI } from '@/di.js';
 import { defaultEmbedParams } from '@@/js/embed-page.js';
 
diff --git a/packages/frontend-embed/src/pages/user-timeline.vue b/packages/frontend-embed/src/pages/user-timeline.vue
index 431577d04b..8f587d2604 100644
--- a/packages/frontend-embed/src/pages/user-timeline.vue
+++ b/packages/frontend-embed/src/pages/user-timeline.vue
@@ -59,7 +59,7 @@ import EmTimelineContainer from '@/components/EmTimelineContainer.vue';
 import { misskeyApi } from '@/misskey-api.js';
 import { i18n } from '@/i18n.js';
 import { serverMetadata } from '@/server-metadata.js';
-import { url, instanceName } from '@/config.js';
+import { url, instanceName } from '@@/js/config.js';
 import { defaultEmbedParams } from '@@/js/embed-page.js';
 import { DI } from '@/di.js';
 
diff --git a/packages/frontend-embed/src/to-be-shared/collapsed.ts b/packages/frontend-embed/src/to-be-shared/collapsed.ts
deleted file mode 100644
index 4ec88a3c65..0000000000
--- a/packages/frontend-embed/src/to-be-shared/collapsed.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * SPDX-FileCopyrightText: syuilo and misskey-project
- * SPDX-License-Identifier: AGPL-3.0-only
- */
-
-import * as Misskey from 'misskey-js';
-
-export function shouldCollapsed(note: Misskey.entities.Note, urls: string[]): boolean {
-	const collapsed = note.cw == null && (
-		note.text != null && (
-			(note.text.includes('$[x2')) ||
-			(note.text.includes('$[x3')) ||
-			(note.text.includes('$[x4')) ||
-			(note.text.includes('$[scale')) ||
-			(note.text.split('\n').length > 9) ||
-			(note.text.length > 500) ||
-			(urls.length >= 4)
-		) || note.files.length >= 5
-	);
-
-	return collapsed;
-}
diff --git a/packages/frontend-embed/src/to-be-shared/intl-const.ts b/packages/frontend-embed/src/to-be-shared/intl-const.ts
deleted file mode 100644
index aaa4f0a86e..0000000000
--- a/packages/frontend-embed/src/to-be-shared/intl-const.ts
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * SPDX-FileCopyrightText: syuilo and misskey-project
- * SPDX-License-Identifier: AGPL-3.0-only
- */
-
-import { lang } from '@/config.js';
-
-export const versatileLang = (lang ?? 'ja-JP').replace('ja-KS', 'ja-JP');
-
-let _dateTimeFormat: Intl.DateTimeFormat;
-try {
-	_dateTimeFormat = new Intl.DateTimeFormat(versatileLang, {
-		year: 'numeric',
-		month: 'numeric',
-		day: 'numeric',
-		hour: 'numeric',
-		minute: 'numeric',
-		second: 'numeric',
-	});
-} catch (err) {
-	console.warn(err);
-	if (_DEV_) console.log('[Intl] Fallback to en-US');
-
-	// Fallback to en-US
-	_dateTimeFormat = new Intl.DateTimeFormat('en-US', {
-		year: 'numeric',
-		month: 'numeric',
-		day: 'numeric',
-		hour: 'numeric',
-		minute: 'numeric',
-		second: 'numeric',
-	});
-}
-export const dateTimeFormat = _dateTimeFormat;
-
-export const timeZone = dateTimeFormat.resolvedOptions().timeZone;
-
-export const hemisphere = /^(australia|pacific|antarctica|indian)\//i.test(timeZone) ? 'S' : 'N';
-
-let _numberFormat: Intl.NumberFormat;
-try {
-	_numberFormat = new Intl.NumberFormat(versatileLang);
-} catch (err) {
-	console.warn(err);
-	if (_DEV_) console.log('[Intl] Fallback to en-US');
-
-	// Fallback to en-US
-	_numberFormat = new Intl.NumberFormat('en-US');
-}
-export const numberFormat = _numberFormat;
diff --git a/packages/frontend-embed/src/to-be-shared/is-link.ts b/packages/frontend-embed/src/to-be-shared/is-link.ts
deleted file mode 100644
index 946f86400e..0000000000
--- a/packages/frontend-embed/src/to-be-shared/is-link.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-/*
- * SPDX-FileCopyrightText: syuilo and misskey-project
- * SPDX-License-Identifier: AGPL-3.0-only
- */
-
-export function isLink(el: HTMLElement) {
-	if (el.tagName === 'A') return true;
-	if (el.parentElement) {
-		return isLink(el.parentElement);
-	}
-	return false;
-}
diff --git a/packages/frontend-embed/src/to-be-shared/worker-multi-dispatch.ts b/packages/frontend-embed/src/to-be-shared/worker-multi-dispatch.ts
deleted file mode 100644
index 6b3fcd9383..0000000000
--- a/packages/frontend-embed/src/to-be-shared/worker-multi-dispatch.ts
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * SPDX-FileCopyrightText: syuilo and misskey-project
- * SPDX-License-Identifier: AGPL-3.0-only
- */
-
-function defaultUseWorkerNumber(prev: number, totalWorkers: number) {
-	return prev + 1;
-}
-
-export class WorkerMultiDispatch<POST = any, RETURN = any> {
-	private symbol = Symbol('WorkerMultiDispatch');
-	private workers: Worker[] = [];
-	private terminated = false;
-	private prevWorkerNumber = 0;
-	private getUseWorkerNumber = defaultUseWorkerNumber;
-	private finalizationRegistry: FinalizationRegistry<symbol>;
-
-	constructor(workerConstructor: () => Worker, concurrency: number, getUseWorkerNumber = defaultUseWorkerNumber) {
-		this.getUseWorkerNumber = getUseWorkerNumber;
-		for (let i = 0; i < concurrency; i++) {
-			this.workers.push(workerConstructor());
-		}
-
-		this.finalizationRegistry = new FinalizationRegistry(() => {
-			this.terminate();
-		});
-		this.finalizationRegistry.register(this, this.symbol);
-
-		if (_DEV_) console.log('WorkerMultiDispatch: Created', this);
-	}
-
-	public postMessage(message: POST, options?: Transferable[] | StructuredSerializeOptions, useWorkerNumber: typeof defaultUseWorkerNumber = this.getUseWorkerNumber) {
-		let workerNumber = useWorkerNumber(this.prevWorkerNumber, this.workers.length);
-		workerNumber = Math.abs(Math.round(workerNumber)) % this.workers.length;
-		if (_DEV_) console.log('WorkerMultiDispatch: Posting message to worker', workerNumber, useWorkerNumber);
-		this.prevWorkerNumber = workerNumber;
-
-		// 不毛だがunionをoverloadに突っ込めない
-		// https://stackoverflow.com/questions/66507585/overload-signatures-union-types-and-no-overload-matches-this-call-error
-		// https://github.com/microsoft/TypeScript/issues/14107
-		if (Array.isArray(options)) {
-			this.workers[workerNumber].postMessage(message, options);
-		} else {
-			this.workers[workerNumber].postMessage(message, options);
-		}
-		return workerNumber;
-	}
-
-	public addListener(callback: (this: Worker, ev: MessageEvent<RETURN>) => any, options?: boolean | AddEventListenerOptions) {
-		this.workers.forEach(worker => {
-			worker.addEventListener('message', callback, options);
-		});
-	}
-
-	public removeListener(callback: (this: Worker, ev: MessageEvent<RETURN>) => any, options?: boolean | AddEventListenerOptions) {
-		this.workers.forEach(worker => {
-			worker.removeEventListener('message', callback, options);
-		});
-	}
-
-	public terminate() {
-		this.terminated = true;
-		if (_DEV_) console.log('WorkerMultiDispatch: Terminating', this);
-		this.workers.forEach(worker => {
-			worker.terminate();
-		});
-		this.workers = [];
-		this.finalizationRegistry.unregister(this);
-	}
-
-	public isTerminated() {
-		return this.terminated;
-	}
-
-	public getWorkers() {
-		return this.workers;
-	}
-
-	public getSymbol() {
-		return this.symbol;
-	}
-}
diff --git a/packages/frontend-embed/src/utils.ts b/packages/frontend-embed/src/utils.ts
index 9a2fd0beef..48e06b21ef 100644
--- a/packages/frontend-embed/src/utils.ts
+++ b/packages/frontend-embed/src/utils.ts
@@ -4,7 +4,7 @@
  */
 
 import * as Misskey from 'misskey-js';
-import { url } from '@/config.js';
+import { url } from '@@/js/config.js';
 
 export const acct = (user: Misskey.Acct) => {
 	return Misskey.acct.toString(user);
diff --git a/packages/frontend-shared/@types/global.d.ts b/packages/frontend-shared/@types/global.d.ts
new file mode 100644
index 0000000000..4b8d679e75
--- /dev/null
+++ b/packages/frontend-shared/@types/global.d.ts
@@ -0,0 +1,25 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+type FIXME = any;
+
+declare const _LANGS_: string[][];
+declare const _VERSION_: string;
+declare const _ENV_: string;
+declare const _DEV_: boolean;
+declare const _PERF_PREFIX_: string;
+declare const _DATA_TRANSFER_DRIVE_FILE_: string;
+declare const _DATA_TRANSFER_DRIVE_FOLDER_: string;
+declare const _DATA_TRANSFER_DECK_COLUMN_: string;
+
+// for dev-mode
+declare const _LANGS_FULL_: string[][];
+
+// TagCanvas
+interface Window {
+	// eslint-disable-next-line @typescript-eslint/no-explicit-any
+	TagCanvas: any;
+}
diff --git a/packages/frontend-shared/eslint.config.js b/packages/frontend-shared/eslint.config.js
index a15fb29e37..cd4641a270 100644
--- a/packages/frontend-shared/eslint.config.js
+++ b/packages/frontend-shared/eslint.config.js
@@ -14,7 +14,11 @@ export default [
 	},
 	...pluginVue.configs['flat/recommended'],
 	{
-		files: ['js/**/*.{ts,vue}', '**/*.vue'],
+		files: [
+			'@types/**/*.ts',
+			'js/**/*.ts',
+			'**/*.vue',
+		],
 		languageOptions: {
 			globals: {
 				...Object.fromEntries(Object.entries(globals.node).map(([key]) => [key, 'off'])),
diff --git a/packages/frontend-shared/js/collapsed.ts b/packages/frontend-shared/js/collapsed.ts
new file mode 100644
index 0000000000..af1f88cb73
--- /dev/null
+++ b/packages/frontend-shared/js/collapsed.ts
@@ -0,0 +1,22 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import * as Misskey from 'misskey-js';
+
+export function shouldCollapsed(note: Misskey.entities.Note, urls: string[]): boolean {
+	const collapsed = note.cw == null && (
+		(note.text != null && (
+			(note.text.includes('$[x2')) ||
+			(note.text.includes('$[x3')) ||
+			(note.text.includes('$[x4')) ||
+			(note.text.includes('$[scale')) ||
+			(note.text.split('\n').length > 9) ||
+			(note.text.length > 500) ||
+			(urls.length >= 4)
+		)) || (note.files != null && note.files.length >= 5)
+	);
+
+	return collapsed;
+}
diff --git a/packages/frontend-shared/js/config.ts b/packages/frontend-shared/js/config.ts
new file mode 100644
index 0000000000..ae1dcae10b
--- /dev/null
+++ b/packages/frontend-shared/js/config.ts
@@ -0,0 +1,28 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import type { Locale } from '../../../locales/index.js';
+
+// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
+const address = new URL(document.querySelector<HTMLMetaElement>('meta[property="instance_url"]')?.content || location.href);
+const siteName = document.querySelector<HTMLMetaElement>('meta[property="og:site_name"]')?.content;
+
+export const host = address.host;
+export const hostname = address.hostname;
+export const url = address.origin;
+export const apiUrl = location.origin + '/api';
+export const wsOrigin = location.origin;
+export const lang = localStorage.getItem('lang') ?? 'en-US';
+export const langs = _LANGS_;
+const preParseLocale = localStorage.getItem('locale');
+export let locale: Locale = preParseLocale ? JSON.parse(preParseLocale) : null;
+export const version = _VERSION_;
+export const instanceName = (siteName === 'Misskey' || siteName == null) ? host : siteName;
+export const ui = localStorage.getItem('ui');
+export const debug = localStorage.getItem('debug') === 'true';
+
+export function updateLocale(newLocale: Locale): void {
+	locale = newLocale;
+}
diff --git a/packages/frontend-shared/js/emoji-base.ts b/packages/frontend-shared/js/emoji-base.ts
index a01540a3e4..5fbbc4ea84 100644
--- a/packages/frontend-shared/js/emoji-base.ts
+++ b/packages/frontend-shared/js/emoji-base.ts
@@ -19,7 +19,7 @@ export function char2fluentEmojiFilePath(char: string): string {
 	// Fluent Emojiは国旗非対応 https://github.com/microsoft/fluentui-emoji/issues/25
 	if (codes[0]?.startsWith('1f1')) return char2twemojiFilePath(char);
 	if (!codes.includes('200d')) codes = codes.filter(x => x !== 'fe0f');
-	codes = codes.filter(x => x && x.length);
-	const fileName = codes.map(x => x!.padStart(4, '0')).join('-');
+	codes = codes.filter(x => x != null && x.length > 0);
+	const fileName = (codes as string[]).map(x => x.padStart(4, '0')).join('-');
 	return `${fluentEmojiPngBase}/${fileName}.png`;
 }
diff --git a/packages/frontend-shared/js/intl-const.ts b/packages/frontend-shared/js/intl-const.ts
new file mode 100644
index 0000000000..33b65b6e9b
--- /dev/null
+++ b/packages/frontend-shared/js/intl-const.ts
@@ -0,0 +1,51 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { lang } from '@@/js/config.js';
+
+// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
+export const versatileLang = (lang ?? 'ja-JP').replace('ja-KS', 'ja-JP');
+
+let _dateTimeFormat: Intl.DateTimeFormat;
+try {
+	_dateTimeFormat = new Intl.DateTimeFormat(versatileLang, {
+		year: 'numeric',
+		month: 'numeric',
+		day: 'numeric',
+		hour: 'numeric',
+		minute: 'numeric',
+		second: 'numeric',
+	});
+} catch (err) {
+	console.warn(err);
+	if (_DEV_) console.log('[Intl] Fallback to en-US');
+
+	// Fallback to en-US
+	_dateTimeFormat = new Intl.DateTimeFormat('en-US', {
+		year: 'numeric',
+		month: 'numeric',
+		day: 'numeric',
+		hour: 'numeric',
+		minute: 'numeric',
+		second: 'numeric',
+	});
+}
+export const dateTimeFormat = _dateTimeFormat;
+
+export const timeZone = dateTimeFormat.resolvedOptions().timeZone;
+
+export const hemisphere = /^(australia|pacific|antarctica|indian)\//i.test(timeZone) ? 'S' : 'N';
+
+let _numberFormat: Intl.NumberFormat;
+try {
+	_numberFormat = new Intl.NumberFormat(versatileLang);
+} catch (err) {
+	console.warn(err);
+	if (_DEV_) console.log('[Intl] Fallback to en-US');
+
+	// Fallback to en-US
+	_numberFormat = new Intl.NumberFormat('en-US');
+}
+export const numberFormat = _numberFormat;
diff --git a/packages/frontend-shared/js/is-link.ts b/packages/frontend-shared/js/is-link.ts
new file mode 100644
index 0000000000..946f86400e
--- /dev/null
+++ b/packages/frontend-shared/js/is-link.ts
@@ -0,0 +1,12 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export function isLink(el: HTMLElement) {
+	if (el.tagName === 'A') return true;
+	if (el.parentElement) {
+		return isLink(el.parentElement);
+	}
+	return false;
+}
diff --git a/packages/frontend-shared/js/worker-multi-dispatch.ts b/packages/frontend-shared/js/worker-multi-dispatch.ts
new file mode 100644
index 0000000000..5d393ed1ed
--- /dev/null
+++ b/packages/frontend-shared/js/worker-multi-dispatch.ts
@@ -0,0 +1,86 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+function defaultUseWorkerNumber(prev: number) {
+	return prev + 1;
+}
+
+type WorkerNumberGetter = (prev: number, totalWorkers: number) => number;
+
+export class WorkerMultiDispatch<POST = unknown, RETURN = unknown> {
+	private symbol = Symbol('WorkerMultiDispatch');
+	private workers: Worker[] = [];
+	private terminated = false;
+	private prevWorkerNumber = 0;
+	private getUseWorkerNumber: WorkerNumberGetter;
+	private finalizationRegistry: FinalizationRegistry<symbol>;
+
+	constructor(workerConstructor: () => Worker, concurrency: number, getUseWorkerNumber = defaultUseWorkerNumber) {
+		this.getUseWorkerNumber = getUseWorkerNumber;
+		for (let i = 0; i < concurrency; i++) {
+			this.workers.push(workerConstructor());
+		}
+
+		this.finalizationRegistry = new FinalizationRegistry(() => {
+			this.terminate();
+		});
+		this.finalizationRegistry.register(this, this.symbol);
+
+		if (_DEV_) console.log('WorkerMultiDispatch: Created', this);
+	}
+
+	public postMessage(message: POST, options?: Transferable[] | StructuredSerializeOptions, useWorkerNumber: WorkerNumberGetter = this.getUseWorkerNumber) {
+		let workerNumber = useWorkerNumber(this.prevWorkerNumber, this.workers.length);
+		workerNumber = Math.abs(Math.round(workerNumber)) % this.workers.length;
+		if (_DEV_) console.log('WorkerMultiDispatch: Posting message to worker', workerNumber, useWorkerNumber);
+		this.prevWorkerNumber = workerNumber;
+
+		// 不毛だがunionをoverloadに突っ込めない
+		// https://stackoverflow.com/questions/66507585/overload-signatures-union-types-and-no-overload-matches-this-call-error
+		// https://github.com/microsoft/TypeScript/issues/14107
+		if (Array.isArray(options)) {
+			this.workers[workerNumber].postMessage(message, options);
+		} else {
+			this.workers[workerNumber].postMessage(message, options);
+		}
+		return workerNumber;
+	}
+
+	// eslint-disable-next-line @typescript-eslint/no-explicit-any
+	public addListener(callback: (this: Worker, ev: MessageEvent<RETURN>) => any, options?: boolean | AddEventListenerOptions) {
+		this.workers.forEach(worker => {
+			worker.addEventListener('message', callback, options);
+		});
+	}
+
+	// eslint-disable-next-line @typescript-eslint/no-explicit-any
+	public removeListener(callback: (this: Worker, ev: MessageEvent<RETURN>) => any, options?: boolean | AddEventListenerOptions) {
+		this.workers.forEach(worker => {
+			worker.removeEventListener('message', callback, options);
+		});
+	}
+
+	public terminate() {
+		this.terminated = true;
+		if (_DEV_) console.log('WorkerMultiDispatch: Terminating', this);
+		this.workers.forEach(worker => {
+			worker.terminate();
+		});
+		this.workers = [];
+		this.finalizationRegistry.unregister(this);
+	}
+
+	public isTerminated() {
+		return this.terminated;
+	}
+
+	public getWorkers() {
+		return this.workers;
+	}
+
+	public getSymbol() {
+		return this.symbol;
+	}
+}
diff --git a/packages/frontend-shared/tsconfig.json b/packages/frontend-shared/tsconfig.json
index fa0b765534..09a8ff76aa 100644
--- a/packages/frontend-shared/tsconfig.json
+++ b/packages/frontend-shared/tsconfig.json
@@ -16,7 +16,13 @@
 		"experimentalDecorators": true,
 		"noImplicitReturns": true,
 		"esModuleInterop": true,
+		"baseUrl": ".",
+		"paths": {
+			"@/*": ["./*"],
+			"@@/*": ["./*"]
+		},
 		"typeRoots": [
+			"./@types",
 			"./node_modules/@types"
 		],
 		"lib": [
@@ -25,6 +31,7 @@
 		]
 	},
 	"include": [
+		"@types/**/*.ts",
 		"js/**/*"
 	],
 	"exclude": [
diff --git a/packages/frontend/src/account.ts b/packages/frontend/src/account.ts
index 4172016f89..f388397466 100644
--- a/packages/frontend/src/account.ts
+++ b/packages/frontend/src/account.ts
@@ -10,7 +10,7 @@ import { i18n } from '@/i18n.js';
 import { miLocalStorage } from '@/local-storage.js';
 import { MenuButton } from '@/types/menu.js';
 import { del, get, set } from '@/scripts/idb-proxy.js';
-import { apiUrl } from '@/config.js';
+import { apiUrl } from '@@/js/config.js';
 import { waiting, popup, popupMenu, success, alert } from '@/os.js';
 import { misskeyApi } from '@/scripts/misskey-api.js';
 import { unisonReload, reloadChannel } from '@/scripts/unison-reload.js';
diff --git a/packages/frontend/src/boot/common.ts b/packages/frontend/src/boot/common.ts
index 19d30f64ce..287788bc8e 100644
--- a/packages/frontend/src/boot/common.ts
+++ b/packages/frontend/src/boot/common.ts
@@ -8,7 +8,7 @@ import { compareVersions } from 'compare-versions';
 import widgets from '@/widgets/index.js';
 import directives from '@/directives/index.js';
 import components from '@/components/index.js';
-import { version, lang, updateLocale, locale } from '@/config.js';
+import { version, lang, updateLocale, locale } from '@@/js/config.js';
 import { applyTheme } from '@/scripts/theme.js';
 import { isDeviceDarkmode } from '@/scripts/is-device-darkmode.js';
 import { updateI18n } from '@/i18n.js';
diff --git a/packages/frontend/src/boot/main-boot.ts b/packages/frontend/src/boot/main-boot.ts
index b31281dcf2..ddd47ca448 100644
--- a/packages/frontend/src/boot/main-boot.ts
+++ b/packages/frontend/src/boot/main-boot.ts
@@ -6,7 +6,7 @@
 import { createApp, defineAsyncComponent, markRaw } from 'vue';
 import { common } from './common.js';
 import type * as Misskey from 'misskey-js';
-import { ui } from '@/config.js';
+import { ui } from '@@/js/config.js';
 import { i18n } from '@/i18n.js';
 import { alert, confirm, popup, post, toast } from '@/os.js';
 import { useStream } from '@/stream.js';
diff --git a/packages/frontend/src/components/MkAccountMoved.vue b/packages/frontend/src/components/MkAccountMoved.vue
index 6c0774b634..796524fce9 100644
--- a/packages/frontend/src/components/MkAccountMoved.vue
+++ b/packages/frontend/src/components/MkAccountMoved.vue
@@ -16,7 +16,7 @@ import { ref } from 'vue';
 import * as Misskey from 'misskey-js';
 import MkMention from './MkMention.vue';
 import { i18n } from '@/i18n.js';
-import { host as localHost } from '@/config.js';
+import { host as localHost } from '@@/js/config.js';
 import { misskeyApi } from '@/scripts/misskey-api.js';
 
 const user = ref<Misskey.entities.UserLite>();
diff --git a/packages/frontend/src/components/MkCropperDialog.vue b/packages/frontend/src/components/MkCropperDialog.vue
index 54f6f39c9d..2e1e92cbdf 100644
--- a/packages/frontend/src/components/MkCropperDialog.vue
+++ b/packages/frontend/src/components/MkCropperDialog.vue
@@ -39,7 +39,7 @@ import MkModalWindow from '@/components/MkModalWindow.vue';
 import * as os from '@/os.js';
 import { $i } from '@/account.js';
 import { defaultStore } from '@/store.js';
-import { apiUrl } from '@/config.js';
+import { apiUrl } from '@@/js/config.js';
 import { i18n } from '@/i18n.js';
 import { getProxiedImageUrl } from '@/scripts/media-proxy.js';
 
diff --git a/packages/frontend/src/components/MkDonation.vue b/packages/frontend/src/components/MkDonation.vue
index 434fc81582..098be07a8c 100644
--- a/packages/frontend/src/components/MkDonation.vue
+++ b/packages/frontend/src/components/MkDonation.vue
@@ -38,7 +38,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 <script lang="ts" setup>
 import MkButton from '@/components/MkButton.vue';
 import MkLink from '@/components/MkLink.vue';
-import { host } from '@/config.js';
+import { host } from '@@/js/config.js';
 import { i18n } from '@/i18n.js';
 import * as os from '@/os.js';
 import { miLocalStorage } from '@/local-storage.js';
diff --git a/packages/frontend/src/components/MkEmbedCodeGenDialog.vue b/packages/frontend/src/components/MkEmbedCodeGenDialog.vue
index c1de803007..7bfdfbc20a 100644
--- a/packages/frontend/src/components/MkEmbedCodeGenDialog.vue
+++ b/packages/frontend/src/components/MkEmbedCodeGenDialog.vue
@@ -103,7 +103,7 @@ import MkInfo from '@/components/MkInfo.vue';
 
 import * as os from '@/os.js';
 import { i18n } from '@/i18n.js';
-import { url } from '@/config.js';
+import { url } from '@@/js/config.js';
 import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
 import { normalizeEmbedParams, getEmbedCode } from '@/scripts/get-embed-code.js';
 import { embedRouteWithScrollbar } from '@@/js/embed-page.js';
diff --git a/packages/frontend/src/components/MkFollowButton.vue b/packages/frontend/src/components/MkFollowButton.vue
index d8ac8024b4..370d5f75c5 100644
--- a/packages/frontend/src/components/MkFollowButton.vue
+++ b/packages/frontend/src/components/MkFollowButton.vue
@@ -43,7 +43,7 @@ import { useStream } from '@/stream.js';
 import { i18n } from '@/i18n.js';
 import { claimAchievement } from '@/scripts/achievements.js';
 import { pleaseLogin } from '@/scripts/please-login.js';
-import { host } from '@/config.js';
+import { host } from '@@/js/config.js';
 import { $i } from '@/account.js';
 import { defaultStore } from '@/store.js';
 
diff --git a/packages/frontend/src/components/MkImgWithBlurhash.vue b/packages/frontend/src/components/MkImgWithBlurhash.vue
index eeecf052af..c04d0864fb 100644
--- a/packages/frontend/src/components/MkImgWithBlurhash.vue
+++ b/packages/frontend/src/components/MkImgWithBlurhash.vue
@@ -23,7 +23,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 <script lang="ts">
 import DrawBlurhash from '@/workers/draw-blurhash?worker';
 import TestWebGL2 from '@/workers/test-webgl2?worker';
-import { WorkerMultiDispatch } from '@/scripts/worker-multi-dispatch.js';
+import { WorkerMultiDispatch } from '@@/js/worker-multi-dispatch.js';
 import { extractAvgColorFromBlurhash } from '@@/js/extract-avg-color-from-blurhash.js';
 
 const canvasPromise = new Promise<WorkerMultiDispatch | HTMLCanvasElement>(resolve => {
diff --git a/packages/frontend/src/components/MkInstanceTicker.vue b/packages/frontend/src/components/MkInstanceTicker.vue
index 82c82199b5..fae22baa3f 100644
--- a/packages/frontend/src/components/MkInstanceTicker.vue
+++ b/packages/frontend/src/components/MkInstanceTicker.vue
@@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <script lang="ts" setup>
 import { computed } from 'vue';
-import { instanceName } from '@/config.js';
+import { instanceName } from '@@/js/config.js';
 import { instance as Instance } from '@/instance.js';
 import { getProxiedImageUrlNullable } from '@/scripts/media-proxy.js';
 
diff --git a/packages/frontend/src/components/MkLink.vue b/packages/frontend/src/components/MkLink.vue
index e842ec2d6e..bda2161eb8 100644
--- a/packages/frontend/src/components/MkLink.vue
+++ b/packages/frontend/src/components/MkLink.vue
@@ -16,7 +16,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <script lang="ts" setup>
 import { defineAsyncComponent, ref } from 'vue';
-import { url as local } from '@/config.js';
+import { url as local } from '@@/js/config.js';
 import { useTooltip } from '@/scripts/use-tooltip.js';
 import * as os from '@/os.js';
 import { isEnabledUrlPreview } from '@/instance.js';
diff --git a/packages/frontend/src/components/MkMention.vue b/packages/frontend/src/components/MkMention.vue
index bfb49a416e..9d9661e816 100644
--- a/packages/frontend/src/components/MkMention.vue
+++ b/packages/frontend/src/components/MkMention.vue
@@ -17,7 +17,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 import { toUnicode } from 'punycode';
 import { computed } from 'vue';
 import tinycolor from 'tinycolor2';
-import { host as localHost } from '@/config.js';
+import { host as localHost } from '@@/js/config.js';
 import { $i } from '@/account.js';
 import { defaultStore } from '@/store.js';
 import { getStaticImageUrl } from '@/scripts/media-proxy.js';
diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue
index 2927a46977..eca94e99d8 100644
--- a/packages/frontend/src/components/MkNote.vue
+++ b/packages/frontend/src/components/MkNote.vue
@@ -163,6 +163,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 import { computed, inject, onMounted, ref, shallowRef, Ref, watch, provide } from 'vue';
 import * as mfm from 'mfm-js';
 import * as Misskey from 'misskey-js';
+import { isLink } from '@@/js/is-link.js';
 import MkNoteSub from '@/components/MkNoteSub.vue';
 import MkNoteHeader from '@/components/MkNoteHeader.vue';
 import MkNoteSimple from '@/components/MkNoteSimple.vue';
@@ -195,8 +196,8 @@ import { getNoteSummary } from '@/scripts/get-note-summary.js';
 import { MenuItem } from '@/types/menu.js';
 import MkRippleEffect from '@/components/MkRippleEffect.vue';
 import { showMovedDialog } from '@/scripts/show-moved-dialog.js';
-import { shouldCollapsed } from '@/scripts/collapsed.js';
-import { host } from '@/config.js';
+import { shouldCollapsed } from '@@/js/collapsed.js';
+import { host } from '@@/js/config.js';
 import { isEnabledUrlPreview } from '@/instance.js';
 import { type Keymap } from '@/scripts/hotkey.js';
 import { focusPrev, focusNext } from '@/scripts/focus.js';
@@ -506,16 +507,6 @@ function onContextmenu(ev: MouseEvent): void {
 		return;
 	}
 
-	const isLink = (el: HTMLElement): boolean => {
-		if (el.tagName === 'A') return true;
-		// 再生速度の選択などのために、Audio要素のコンテキストメニューはブラウザデフォルトとする。
-		if (el.tagName === 'AUDIO') return true;
-		if (el.parentElement) {
-			return isLink(el.parentElement);
-		}
-		return false;
-	};
-
 	if (ev.target && isLink(ev.target as HTMLElement)) return;
 	if (window.getSelection()?.toString() !== '') return;
 
diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue
index 2b7d2afa04..1867f82c0f 100644
--- a/packages/frontend/src/components/MkNoteDetailed.vue
+++ b/packages/frontend/src/components/MkNoteDetailed.vue
@@ -199,6 +199,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 import { computed, inject, onMounted, provide, ref, shallowRef } from 'vue';
 import * as mfm from 'mfm-js';
 import * as Misskey from 'misskey-js';
+import { isLink } from '@@/js/is-link.js';
 import MkNoteSub from '@/components/MkNoteSub.vue';
 import MkNoteSimple from '@/components/MkNoteSimple.vue';
 import MkReactionsViewer from '@/components/MkReactionsViewer.vue';
@@ -222,7 +223,7 @@ import { reactionPicker } from '@/scripts/reaction-picker.js';
 import { extractUrlFromMfm } from '@/scripts/extract-url-from-mfm.js';
 import { $i } from '@/account.js';
 import { i18n } from '@/i18n.js';
-import { host } from '@/config.js';
+import { host } from '@@/js/config.js';
 import { getNoteClipMenu, getNoteMenu, getRenoteMenu } from '@/scripts/get-note-menu.js';
 import { useNoteCapture } from '@/scripts/use-note-capture.js';
 import { deepClone } from '@/scripts/clone.js';
@@ -468,14 +469,6 @@ function toggleReact() {
 }
 
 function onContextmenu(ev: MouseEvent): void {
-	const isLink = (el: HTMLElement): boolean => {
-		if (el.tagName === 'A') return true;
-		if (el.parentElement) {
-			return isLink(el.parentElement);
-		}
-		return false;
-	};
-
 	if (ev.target && isLink(ev.target as HTMLElement)) return;
 	if (window.getSelection()?.toString() !== '') return;
 
diff --git a/packages/frontend/src/components/MkPageWindow.vue b/packages/frontend/src/components/MkPageWindow.vue
index 8049f88051..2b993ab12f 100644
--- a/packages/frontend/src/components/MkPageWindow.vue
+++ b/packages/frontend/src/components/MkPageWindow.vue
@@ -34,7 +34,7 @@ import RouterView from '@/components/global/RouterView.vue';
 import MkWindow from '@/components/MkWindow.vue';
 import { popout as _popout } from '@/scripts/popout.js';
 import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
-import { url } from '@/config.js';
+import { url } from '@@/js/config.js';
 import { useScrollPositionManager } from '@/nirax.js';
 import { i18n } from '@/i18n.js';
 import { PageMetadata, provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js';
diff --git a/packages/frontend/src/components/MkPoll.vue b/packages/frontend/src/components/MkPoll.vue
index 8e230cce4f..e1d5db2730 100644
--- a/packages/frontend/src/components/MkPoll.vue
+++ b/packages/frontend/src/components/MkPoll.vue
@@ -35,7 +35,7 @@ import { pleaseLogin } from '@/scripts/please-login.js';
 import * as os from '@/os.js';
 import { misskeyApi } from '@/scripts/misskey-api.js';
 import { i18n } from '@/i18n.js';
-import { host } from '@/config.js';
+import { host } from '@@/js/config.js';
 import { useInterval } from '@@/js/use-interval.js';
 
 const props = defineProps<{
diff --git a/packages/frontend/src/components/MkPostForm.vue b/packages/frontend/src/components/MkPostForm.vue
index df251d9192..039393887d 100644
--- a/packages/frontend/src/components/MkPostForm.vue
+++ b/packages/frontend/src/components/MkPostForm.vue
@@ -109,7 +109,7 @@ import MkNoteSimple from '@/components/MkNoteSimple.vue';
 import MkNotePreview from '@/components/MkNotePreview.vue';
 import XPostFormAttaches from '@/components/MkPostFormAttaches.vue';
 import MkPollEditor, { type PollEditorModelValue } from '@/components/MkPollEditor.vue';
-import { host, url } from '@/config.js';
+import { host, url } from '@@/js/config.js';
 import { erase, unique } from '@/scripts/array.js';
 import { extractMentions } from '@/scripts/extract-mentions.js';
 import { formatTimeString } from '@/scripts/format-time-string.js';
diff --git a/packages/frontend/src/components/MkPreview.vue b/packages/frontend/src/components/MkPreview.vue
index 649dee2fdb..6efd99d14b 100644
--- a/packages/frontend/src/components/MkPreview.vue
+++ b/packages/frontend/src/components/MkPreview.vue
@@ -42,7 +42,7 @@ import MkSwitch from '@/components/MkSwitch.vue';
 import MkTextarea from '@/components/MkTextarea.vue';
 import MkRadio from '@/components/MkRadio.vue';
 import * as os from '@/os.js';
-import * as config from '@/config.js';
+import * as config from '@@/js/config.js';
 import { $i } from '@/account.js';
 
 const text = ref('');
diff --git a/packages/frontend/src/components/MkSignin.vue b/packages/frontend/src/components/MkSignin.vue
index dabbe97468..231a6dfcf5 100644
--- a/packages/frontend/src/components/MkSignin.vue
+++ b/packages/frontend/src/components/MkSignin.vue
@@ -72,7 +72,7 @@ import { showSuspendedDialog } from '@/scripts/show-suspended-dialog.js';
 import MkButton from '@/components/MkButton.vue';
 import MkInput from '@/components/MkInput.vue';
 import MkInfo from '@/components/MkInfo.vue';
-import { host as configHost } from '@/config.js';
+import { host as configHost } from '@@/js/config.js';
 import * as os from '@/os.js';
 import { misskeyApi } from '@/scripts/misskey-api.js';
 import { login } from '@/account.js';
diff --git a/packages/frontend/src/components/MkSignupDialog.form.vue b/packages/frontend/src/components/MkSignupDialog.form.vue
index 5f08e416c1..4ab4380ad5 100644
--- a/packages/frontend/src/components/MkSignupDialog.form.vue
+++ b/packages/frontend/src/components/MkSignupDialog.form.vue
@@ -84,7 +84,7 @@ import * as Misskey from 'misskey-js';
 import MkButton from './MkButton.vue';
 import MkInput from './MkInput.vue';
 import MkCaptcha, { type Captcha } from '@/components/MkCaptcha.vue';
-import * as config from '@/config.js';
+import * as config from '@@/js/config.js';
 import * as os from '@/os.js';
 import { misskeyApi } from '@/scripts/misskey-api.js';
 import { login } from '@/account.js';
diff --git a/packages/frontend/src/components/MkSourceCodeAvailablePopup.vue b/packages/frontend/src/components/MkSourceCodeAvailablePopup.vue
index 80f3a6709c..1845b01b69 100644
--- a/packages/frontend/src/components/MkSourceCodeAvailablePopup.vue
+++ b/packages/frontend/src/components/MkSourceCodeAvailablePopup.vue
@@ -41,7 +41,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <script lang="ts" setup>
 import MkButton from '@/components/MkButton.vue';
-import { host } from '@/config.js';
+import { host } from '@@/js/config.js';
 import { i18n } from '@/i18n.js';
 import { instance } from '@/instance.js';
 import { miLocalStorage } from '@/local-storage.js';
diff --git a/packages/frontend/src/components/MkSubNoteContent.vue b/packages/frontend/src/components/MkSubNoteContent.vue
index 25412cc2e5..3bbb163f0f 100644
--- a/packages/frontend/src/components/MkSubNoteContent.vue
+++ b/packages/frontend/src/components/MkSubNoteContent.vue
@@ -35,7 +35,7 @@ import * as Misskey from 'misskey-js';
 import MkMediaList from '@/components/MkMediaList.vue';
 import MkPoll from '@/components/MkPoll.vue';
 import { i18n } from '@/i18n.js';
-import { shouldCollapsed } from '@/scripts/collapsed.js';
+import { shouldCollapsed } from '@@/js/collapsed.js';
 
 const props = defineProps<{
 	note: Misskey.entities.Note;
diff --git a/packages/frontend/src/components/MkTutorialDialog.vue b/packages/frontend/src/components/MkTutorialDialog.vue
index 9adc8d466c..1f5a2b9381 100644
--- a/packages/frontend/src/components/MkTutorialDialog.vue
+++ b/packages/frontend/src/components/MkTutorialDialog.vue
@@ -158,7 +158,7 @@ import XSensitive from '@/components/MkTutorialDialog.Sensitive.vue';
 import MkAnimBg from '@/components/MkAnimBg.vue';
 import { i18n } from '@/i18n.js';
 import { instance } from '@/instance.js';
-import { host } from '@/config.js';
+import { host } from '@@/js/config.js';
 import { claimAchievement } from '@/scripts/achievements.js';
 import * as os from '@/os.js';
 
diff --git a/packages/frontend/src/components/MkUpdated.vue b/packages/frontend/src/components/MkUpdated.vue
index 188cc37f41..f8af276836 100644
--- a/packages/frontend/src/components/MkUpdated.vue
+++ b/packages/frontend/src/components/MkUpdated.vue
@@ -19,7 +19,7 @@ import { onMounted, shallowRef } from 'vue';
 import MkModal from '@/components/MkModal.vue';
 import MkButton from '@/components/MkButton.vue';
 import MkSparkle from '@/components/MkSparkle.vue';
-import { version } from '@/config.js';
+import { version } from '@@/js/config.js';
 import { i18n } from '@/i18n.js';
 import { confetti } from '@/scripts/confetti.js';
 
diff --git a/packages/frontend/src/components/MkUrlPreview.vue b/packages/frontend/src/components/MkUrlPreview.vue
index c868a22045..f5f9b43197 100644
--- a/packages/frontend/src/components/MkUrlPreview.vue
+++ b/packages/frontend/src/components/MkUrlPreview.vue
@@ -85,12 +85,12 @@ SPDX-License-Identifier: AGPL-3.0-only
 <script lang="ts" setup>
 import { defineAsyncComponent, onDeactivated, onUnmounted, ref } from 'vue';
 import type { summaly } from '@misskey-dev/summaly';
-import { url as local } from '@/config.js';
+import { url as local } from '@@/js/config.js';
 import { i18n } from '@/i18n.js';
 import * as os from '@/os.js';
 import { deviceKind } from '@/scripts/device-kind.js';
 import MkButton from '@/components/MkButton.vue';
-import { versatileLang } from '@/scripts/intl-const.js';
+import { versatileLang } from '@@/js/intl-const.js';
 import { transformPlayerUrl } from '@/scripts/player-url-transform.js';
 import { defaultStore } from '@/store.js';
 
diff --git a/packages/frontend/src/components/MkUserSelectDialog.vue b/packages/frontend/src/components/MkUserSelectDialog.vue
index cbb40924f6..1374817c72 100644
--- a/packages/frontend/src/components/MkUserSelectDialog.vue
+++ b/packages/frontend/src/components/MkUserSelectDialog.vue
@@ -70,7 +70,7 @@ import { misskeyApi } from '@/scripts/misskey-api.js';
 import { defaultStore } from '@/store.js';
 import { i18n } from '@/i18n.js';
 import { $i } from '@/account.js';
-import { host as currentHost, hostname } from '@/config.js';
+import { host as currentHost, hostname } from '@@/js/config.js';
 
 const emit = defineEmits<{
 	(ev: 'ok', selected: Misskey.entities.UserDetailed): void;
diff --git a/packages/frontend/src/components/MkUserSetupDialog.vue b/packages/frontend/src/components/MkUserSetupDialog.vue
index 514350c930..1fb1eda039 100644
--- a/packages/frontend/src/components/MkUserSetupDialog.vue
+++ b/packages/frontend/src/components/MkUserSetupDialog.vue
@@ -137,7 +137,7 @@ import XPrivacy from '@/components/MkUserSetupDialog.Privacy.vue';
 import MkAnimBg from '@/components/MkAnimBg.vue';
 import { i18n } from '@/i18n.js';
 import { instance } from '@/instance.js';
-import { host } from '@/config.js';
+import { host } from '@@/js/config.js';
 import MkPushNotificationAllowButton from '@/components/MkPushNotificationAllowButton.vue';
 import { defaultStore } from '@/store.js';
 import * as os from '@/os.js';
diff --git a/packages/frontend/src/components/MkVisitorDashboard.vue b/packages/frontend/src/components/MkVisitorDashboard.vue
index 445780eca7..a6c8baeaaa 100644
--- a/packages/frontend/src/components/MkVisitorDashboard.vue
+++ b/packages/frontend/src/components/MkVisitorDashboard.vue
@@ -58,7 +58,7 @@ import XSignupDialog from '@/components/MkSignupDialog.vue';
 import MkButton from '@/components/MkButton.vue';
 import MkTimeline from '@/components/MkTimeline.vue';
 import MkInfo from '@/components/MkInfo.vue';
-import { instanceName } from '@/config.js';
+import { instanceName } from '@@/js/config.js';
 import * as os from '@/os.js';
 import { misskeyApi } from '@/scripts/misskey-api.js';
 import { i18n } from '@/i18n.js';
diff --git a/packages/frontend/src/components/MkWidgets.vue b/packages/frontend/src/components/MkWidgets.vue
index 7550edd120..0c51cfa9ce 100644
--- a/packages/frontend/src/components/MkWidgets.vue
+++ b/packages/frontend/src/components/MkWidgets.vue
@@ -57,6 +57,7 @@ import MkButton from '@/components/MkButton.vue';
 import { widgets as widgetDefs } from '@/widgets/index.js';
 import * as os from '@/os.js';
 import { i18n } from '@/i18n.js';
+import { isLink } from '@@/js/is-link.js';
 
 const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default));
 
@@ -98,13 +99,6 @@ const updateWidget = (id, data) => {
 
 function onContextmenu(widget: Widget, ev: MouseEvent) {
 	const element = ev.target as HTMLElement | null;
-	const isLink = (el: HTMLElement): boolean => {
-		if (el.tagName === 'A') return true;
-		if (el.parentElement) {
-			return isLink(el.parentElement);
-		}
-		return false;
-	};
 	if (element && isLink(element)) return;
 	if (element && (['INPUT', 'TEXTAREA', 'IMG', 'VIDEO', 'CANVAS'].includes(element.tagName) || element.attributes['contenteditable'])) return;
 	if (window.getSelection()?.toString() !== '') return;
diff --git a/packages/frontend/src/components/MkYouTubePlayer.vue b/packages/frontend/src/components/MkYouTubePlayer.vue
index e3711b3463..1122976436 100644
--- a/packages/frontend/src/components/MkYouTubePlayer.vue
+++ b/packages/frontend/src/components/MkYouTubePlayer.vue
@@ -26,7 +26,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 <script lang="ts" setup>
 import { ref } from 'vue';
 import MkWindow from '@/components/MkWindow.vue';
-import { versatileLang } from '@/scripts/intl-const.js';
+import { versatileLang } from '@@/js/intl-const.js';
 import { transformPlayerUrl } from '@/scripts/player-url-transform.js';
 import { defaultStore } from '@/store.js';
 
diff --git a/packages/frontend/src/components/global/MkA.vue b/packages/frontend/src/components/global/MkA.vue
index 3a45ca429f..87fa9c8252 100644
--- a/packages/frontend/src/components/global/MkA.vue
+++ b/packages/frontend/src/components/global/MkA.vue
@@ -17,7 +17,7 @@ export type MkABehavior = 'window' | 'browser' | null;
 import { computed, inject, shallowRef } from 'vue';
 import * as os from '@/os.js';
 import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
-import { url } from '@/config.js';
+import { url } from '@@/js/config.js';
 import { i18n } from '@/i18n.js';
 import { useRouter } from '@/router/supplier.js';
 
diff --git a/packages/frontend/src/components/global/MkAcct.vue b/packages/frontend/src/components/global/MkAcct.vue
index bbcb070803..8a03f7846e 100644
--- a/packages/frontend/src/components/global/MkAcct.vue
+++ b/packages/frontend/src/components/global/MkAcct.vue
@@ -17,7 +17,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 <script lang="ts" setup>
 import * as Misskey from 'misskey-js';
 import { toUnicode } from 'punycode/';
-import { host as hostRaw } from '@/config.js';
+import { host as hostRaw } from '@@/js/config.js';
 import { defaultStore } from '@/store.js';
 
 defineProps<{
diff --git a/packages/frontend/src/components/global/MkAd.vue b/packages/frontend/src/components/global/MkAd.vue
index bdaa8a809f..f0e943960d 100644
--- a/packages/frontend/src/components/global/MkAd.vue
+++ b/packages/frontend/src/components/global/MkAd.vue
@@ -45,7 +45,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 import { ref, computed } from 'vue';
 import { i18n } from '@/i18n.js';
 import { instance } from '@/instance.js';
-import { url as local, host } from '@/config.js';
+import { url as local, host } from '@@/js/config.js';
 import MkButton from '@/components/MkButton.vue';
 import { defaultStore } from '@/store.js';
 import * as os from '@/os.js';
diff --git a/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.ts b/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.ts
index ea1f3e2988..d914492231 100644
--- a/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.ts
+++ b/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.ts
@@ -17,7 +17,7 @@ import MkCodeInline from '@/components/MkCodeInline.vue';
 import MkGoogle from '@/components/MkGoogle.vue';
 import MkSparkle from '@/components/MkSparkle.vue';
 import MkA, { MkABehavior } from '@/components/global/MkA.vue';
-import { host } from '@/config.js';
+import { host } from '@@/js/config.js';
 import { defaultStore } from '@/store.js';
 
 function safeParseFloat(str: unknown): number | null {
diff --git a/packages/frontend/src/components/global/MkTime.stories.impl.ts b/packages/frontend/src/components/global/MkTime.stories.impl.ts
index ffd4a849a2..ccf7f200b5 100644
--- a/packages/frontend/src/components/global/MkTime.stories.impl.ts
+++ b/packages/frontend/src/components/global/MkTime.stories.impl.ts
@@ -8,7 +8,7 @@ import { expect } from '@storybook/test';
 import { StoryObj } from '@storybook/vue3';
 import MkTime from './MkTime.vue';
 import { i18n } from '@/i18n.js';
-import { dateTimeFormat } from '@/scripts/intl-const.js';
+import { dateTimeFormat } from '@@/js/intl-const.js';
 const now = new Date('2023-04-01T00:00:00.000Z');
 const future = new Date('2024-04-01T00:00:00.000Z');
 const oneHourAgo = new Date(now.getTime() - 3600000);
diff --git a/packages/frontend/src/components/global/MkTime.vue b/packages/frontend/src/components/global/MkTime.vue
index 027b226f3f..50bec990a1 100644
--- a/packages/frontend/src/components/global/MkTime.vue
+++ b/packages/frontend/src/components/global/MkTime.vue
@@ -16,7 +16,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 import isChromatic from 'chromatic/isChromatic';
 import { onMounted, onUnmounted, ref, computed } from 'vue';
 import { i18n } from '@/i18n.js';
-import { dateTimeFormat } from '@/scripts/intl-const.js';
+import { dateTimeFormat } from '@@/js/intl-const.js';
 
 const props = withDefaults(defineProps<{
 	time: Date | string | number | null;
diff --git a/packages/frontend/src/components/global/MkUrl.vue b/packages/frontend/src/components/global/MkUrl.vue
index 8f4e3b853a..e789251659 100644
--- a/packages/frontend/src/components/global/MkUrl.vue
+++ b/packages/frontend/src/components/global/MkUrl.vue
@@ -27,7 +27,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 <script lang="ts" setup>
 import { defineAsyncComponent, ref } from 'vue';
 import { toUnicode as decodePunycode } from 'punycode/';
-import { url as local } from '@/config.js';
+import { url as local } from '@@/js/config.js';
 import * as os from '@/os.js';
 import { useTooltip } from '@/scripts/use-tooltip.js';
 import { isEnabledUrlPreview } from '@/instance.js';
diff --git a/packages/frontend/src/config.ts b/packages/frontend/src/config.ts
deleted file mode 100644
index 277dfc12aa..0000000000
--- a/packages/frontend/src/config.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * SPDX-FileCopyrightText: syuilo and misskey-project
- * SPDX-License-Identifier: AGPL-3.0-only
- */
-
-import { miLocalStorage } from '@/local-storage.js';
-
-const address = new URL(document.querySelector<HTMLMetaElement>('meta[property="instance_url"]')?.content || location.href);
-const siteName = document.querySelector<HTMLMetaElement>('meta[property="og:site_name"]')?.content;
-
-export const host = address.host;
-export const hostname = address.hostname;
-export const url = address.origin;
-export const apiUrl = location.origin + '/api';
-export const wsOrigin = location.origin;
-export const lang = miLocalStorage.getItem('lang') ?? 'en-US';
-export const langs = _LANGS_;
-const preParseLocale = miLocalStorage.getItem('locale');
-export let locale = preParseLocale ? JSON.parse(preParseLocale) : null;
-export const version = _VERSION_;
-export const instanceName = siteName === 'Misskey' || siteName == null ? host : siteName;
-export const ui = miLocalStorage.getItem('ui');
-export const debug = miLocalStorage.getItem('debug') === 'true';
-
-export function updateLocale(newLocale): void {
-	locale = newLocale;
-}
diff --git a/packages/frontend/src/filters/date.ts b/packages/frontend/src/filters/date.ts
index 2ffe93e868..d13d1a5e42 100644
--- a/packages/frontend/src/filters/date.ts
+++ b/packages/frontend/src/filters/date.ts
@@ -3,7 +3,7 @@
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
-import { dateTimeFormat } from '@/scripts/intl-const.js';
+import { dateTimeFormat } from '@@/js/intl-const.js';
 
 export default (d: Date | number | undefined) => dateTimeFormat.format(d);
 export const dateString = (d: string) => dateTimeFormat.format(new Date(d));
diff --git a/packages/frontend/src/filters/number.ts b/packages/frontend/src/filters/number.ts
index 2e7cc60ff4..10fb64deb4 100644
--- a/packages/frontend/src/filters/number.ts
+++ b/packages/frontend/src/filters/number.ts
@@ -3,6 +3,6 @@
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
-import { numberFormat } from '@/scripts/intl-const.js';
+import { numberFormat } from '@@/js/intl-const.js';
 
 export default n => n == null ? 'N/A' : numberFormat.format(n);
diff --git a/packages/frontend/src/filters/user.ts b/packages/frontend/src/filters/user.ts
index a87766764d..d9bc316764 100644
--- a/packages/frontend/src/filters/user.ts
+++ b/packages/frontend/src/filters/user.ts
@@ -4,7 +4,7 @@
  */
 
 import * as Misskey from 'misskey-js';
-import { url } from '@/config.js';
+import { url } from '@@/js/config.js';
 
 export const acct = (user: Misskey.Acct) => {
 	return Misskey.acct.toString(user);
diff --git a/packages/frontend/src/i18n.ts b/packages/frontend/src/i18n.ts
index 17e787f9fc..6ad503b089 100644
--- a/packages/frontend/src/i18n.ts
+++ b/packages/frontend/src/i18n.ts
@@ -6,7 +6,7 @@
 import { markRaw } from 'vue';
 import { I18n } from '@@/js/i18n.js';
 import type { Locale } from '../../../locales/index.js';
-import { locale } from '@/config.js';
+import { locale } from '@@/js/config.js';
 
 export const i18n = markRaw(new I18n<Locale>(locale, _DEV_));
 
diff --git a/packages/frontend/src/navbar.ts b/packages/frontend/src/navbar.ts
index d7910935fa..a96a4f0539 100644
--- a/packages/frontend/src/navbar.ts
+++ b/packages/frontend/src/navbar.ts
@@ -11,7 +11,7 @@ import { openInstanceMenu, openToolsMenu } from '@/ui/_common_/common.js';
 import { lookup } from '@/scripts/lookup.js';
 import * as os from '@/os.js';
 import { i18n } from '@/i18n.js';
-import { ui } from '@/config.js';
+import { ui } from '@@/js/config.js';
 import { unisonReload } from '@/scripts/unison-reload.js';
 
 export const navbarItemDef = reactive({
diff --git a/packages/frontend/src/pages/_error_.vue b/packages/frontend/src/pages/_error_.vue
index c04f399c6d..f09a8e4285 100644
--- a/packages/frontend/src/pages/_error_.vue
+++ b/packages/frontend/src/pages/_error_.vue
@@ -29,7 +29,7 @@ import { ref, computed } from 'vue';
 import * as Misskey from 'misskey-js';
 import MkButton from '@/components/MkButton.vue';
 import MkLink from '@/components/MkLink.vue';
-import { version } from '@/config.js';
+import { version } from '@@/js/config.js';
 import { misskeyApi } from '@/scripts/misskey-api.js';
 import { unisonReload } from '@/scripts/unison-reload.js';
 import { i18n } from '@/i18n.js';
diff --git a/packages/frontend/src/pages/about-misskey.vue b/packages/frontend/src/pages/about-misskey.vue
index a16c1eeacc..960df59485 100644
--- a/packages/frontend/src/pages/about-misskey.vue
+++ b/packages/frontend/src/pages/about-misskey.vue
@@ -132,7 +132,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <script lang="ts" setup>
 import { nextTick, onBeforeUnmount, ref, shallowRef, computed } from 'vue';
-import { version } from '@/config.js';
+import { version } from '@@/js/config.js';
 import FormLink from '@/components/form/link.vue';
 import FormSection from '@/components/form/section.vue';
 import MkButton from '@/components/MkButton.vue';
diff --git a/packages/frontend/src/pages/about.overview.vue b/packages/frontend/src/pages/about.overview.vue
index 84419b3bef..b645506eff 100644
--- a/packages/frontend/src/pages/about.overview.vue
+++ b/packages/frontend/src/pages/about.overview.vue
@@ -126,7 +126,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { host, version } from '@/config.js';
+import { host, version } from '@@/js/config.js';
 import { i18n } from '@/i18n.js';
 import { instance } from '@/instance.js';
 import number from '@/filters/number.js';
diff --git a/packages/frontend/src/pages/admin-user.vue b/packages/frontend/src/pages/admin-user.vue
index 7cab8bf8bd..d40d1eee58 100644
--- a/packages/frontend/src/pages/admin-user.vue
+++ b/packages/frontend/src/pages/admin-user.vue
@@ -220,7 +220,7 @@ import MkFileListForAdmin from '@/components/MkFileListForAdmin.vue';
 import MkInfo from '@/components/MkInfo.vue';
 import * as os from '@/os.js';
 import { misskeyApi } from '@/scripts/misskey-api.js';
-import { url } from '@/config.js';
+import { url } from '@@/js/config.js';
 import { acct } from '@/filters/user.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import { i18n } from '@/i18n.js';
diff --git a/packages/frontend/src/pages/admin/branding.vue b/packages/frontend/src/pages/admin/branding.vue
index fe1b7c561d..947dde767e 100644
--- a/packages/frontend/src/pages/admin/branding.vue
+++ b/packages/frontend/src/pages/admin/branding.vue
@@ -117,7 +117,7 @@ import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import MkButton from '@/components/MkButton.vue';
 import MkColorInput from '@/components/MkColorInput.vue';
-import { host } from '@/config.js';
+import { host } from '@@/js/config.js';
 
 const iconUrl = ref<string | null>(null);
 const app192IconUrl = ref<string | null>(null);
diff --git a/packages/frontend/src/pages/admin/queue.vue b/packages/frontend/src/pages/admin/queue.vue
index 284db894b8..512039242e 100644
--- a/packages/frontend/src/pages/admin/queue.vue
+++ b/packages/frontend/src/pages/admin/queue.vue
@@ -20,7 +20,7 @@ import { ref, computed, type Ref } from 'vue';
 import XQueue from './queue.chart.vue';
 import XHeader from './_header_.vue';
 import * as os from '@/os.js';
-import * as config from '@/config.js';
+import * as config from '@@/js/config.js';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import MkButton from '@/components/MkButton.vue';
diff --git a/packages/frontend/src/pages/channel.vue b/packages/frontend/src/pages/channel.vue
index 7e7b724023..8b014c7a4e 100644
--- a/packages/frontend/src/pages/channel.vue
+++ b/packages/frontend/src/pages/channel.vue
@@ -82,7 +82,7 @@ import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import { deviceKind } from '@/scripts/device-kind.js';
 import MkNotes from '@/components/MkNotes.vue';
-import { url } from '@/config.js';
+import { url } from '@@/js/config.js';
 import { favoritedChannelsCache } from '@/cache.js';
 import MkButton from '@/components/MkButton.vue';
 import MkInput from '@/components/MkInput.vue';
diff --git a/packages/frontend/src/pages/clip.vue b/packages/frontend/src/pages/clip.vue
index aad6acb4b5..7bfa343b1d 100644
--- a/packages/frontend/src/pages/clip.vue
+++ b/packages/frontend/src/pages/clip.vue
@@ -39,7 +39,7 @@ import { i18n } from '@/i18n.js';
 import * as os from '@/os.js';
 import { misskeyApi } from '@/scripts/misskey-api.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
-import { url } from '@/config.js';
+import { url } from '@@/js/config.js';
 import MkButton from '@/components/MkButton.vue';
 import { clipsCache } from '@/cache.js';
 import { isSupportShare } from '@/scripts/navigator.js';
diff --git a/packages/frontend/src/pages/drop-and-fusion.game.vue b/packages/frontend/src/pages/drop-and-fusion.game.vue
index b5e4902126..4db952eac2 100644
--- a/packages/frontend/src/pages/drop-and-fusion.game.vue
+++ b/packages/frontend/src/pages/drop-and-fusion.game.vue
@@ -206,7 +206,7 @@ import { defaultStore } from '@/store.js';
 import { misskeyApi } from '@/scripts/misskey-api.js';
 import { i18n } from '@/i18n.js';
 import { useInterval } from '@@/js/use-interval.js';
-import { apiUrl } from '@/config.js';
+import { apiUrl } from '@@/js/config.js';
 import { $i } from '@/account.js';
 import * as sound from '@/scripts/sound.js';
 import MkRange from '@/components/MkRange.vue';
diff --git a/packages/frontend/src/pages/flash/flash.vue b/packages/frontend/src/pages/flash/flash.vue
index a6a99ba633..3b4deaf537 100644
--- a/packages/frontend/src/pages/flash/flash.vue
+++ b/packages/frontend/src/pages/flash/flash.vue
@@ -68,7 +68,7 @@ import { Interpreter, Parser, values } from '@syuilo/aiscript';
 import MkButton from '@/components/MkButton.vue';
 import * as os from '@/os.js';
 import { misskeyApi } from '@/scripts/misskey-api.js';
-import { url } from '@/config.js';
+import { url } from '@@/js/config.js';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import MkAsUi from '@/components/MkAsUi.vue';
diff --git a/packages/frontend/src/pages/gallery/post.vue b/packages/frontend/src/pages/gallery/post.vue
index 5a9c978dab..dfee66d906 100644
--- a/packages/frontend/src/pages/gallery/post.vue
+++ b/packages/frontend/src/pages/gallery/post.vue
@@ -72,7 +72,7 @@ import MkContainer from '@/components/MkContainer.vue';
 import MkPagination from '@/components/MkPagination.vue';
 import MkGalleryPostPreview from '@/components/MkGalleryPostPreview.vue';
 import MkFollowButton from '@/components/MkFollowButton.vue';
-import { url } from '@/config.js';
+import { url } from '@@/js/config.js';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import { defaultStore } from '@/store.js';
diff --git a/packages/frontend/src/pages/page-editor/page-editor.vue b/packages/frontend/src/pages/page-editor/page-editor.vue
index eaef7c337a..ddb808390c 100644
--- a/packages/frontend/src/pages/page-editor/page-editor.vue
+++ b/packages/frontend/src/pages/page-editor/page-editor.vue
@@ -69,7 +69,7 @@ import MkButton from '@/components/MkButton.vue';
 import MkSelect from '@/components/MkSelect.vue';
 import MkSwitch from '@/components/MkSwitch.vue';
 import MkInput from '@/components/MkInput.vue';
-import { url } from '@/config.js';
+import { url } from '@@/js/config.js';
 import * as os from '@/os.js';
 import { misskeyApi } from '@/scripts/misskey-api.js';
 import { selectFile } from '@/scripts/select-file.js';
diff --git a/packages/frontend/src/pages/page.vue b/packages/frontend/src/pages/page.vue
index 7ae61236e8..381b80cd29 100644
--- a/packages/frontend/src/pages/page.vue
+++ b/packages/frontend/src/pages/page.vue
@@ -104,7 +104,7 @@ import XPage from '@/components/page/page.vue';
 import MkButton from '@/components/MkButton.vue';
 import * as os from '@/os.js';
 import { misskeyApi } from '@/scripts/misskey-api.js';
-import { url } from '@/config.js';
+import { url } from '@@/js/config.js';
 import MkMediaImage from '@/components/MkMediaImage.vue';
 import MkImgWithBlurhash from '@/components/MkImgWithBlurhash.vue';
 import MkFollowButton from '@/components/MkFollowButton.vue';
diff --git a/packages/frontend/src/pages/reversi/game.board.vue b/packages/frontend/src/pages/reversi/game.board.vue
index 578fd65ba1..54e66f6e16 100644
--- a/packages/frontend/src/pages/reversi/game.board.vue
+++ b/packages/frontend/src/pages/reversi/game.board.vue
@@ -151,7 +151,7 @@ import MkSwitch from '@/components/MkSwitch.vue';
 import { deepClone } from '@/scripts/clone.js';
 import { useInterval } from '@@/js/use-interval.js';
 import { signinRequired } from '@/account.js';
-import { url } from '@/config.js';
+import { url } from '@@/js/config.js';
 import { i18n } from '@/i18n.js';
 import { misskeyApi } from '@/scripts/misskey-api.js';
 import { userPage } from '@/filters/user.js';
diff --git a/packages/frontend/src/pages/reversi/game.vue b/packages/frontend/src/pages/reversi/game.vue
index a25595e884..10ea3717ab 100644
--- a/packages/frontend/src/pages/reversi/game.vue
+++ b/packages/frontend/src/pages/reversi/game.vue
@@ -20,7 +20,7 @@ import { useStream } from '@/stream.js';
 import { signinRequired } from '@/account.js';
 import { useRouter } from '@/router/supplier.js';
 import * as os from '@/os.js';
-import { url } from '@/config.js';
+import { url } from '@@/js/config.js';
 import { i18n } from '@/i18n.js';
 import { useInterval } from '@@/js/use-interval.js';
 
diff --git a/packages/frontend/src/pages/role.vue b/packages/frontend/src/pages/role.vue
index ce80579cf9..45f8ef21ed 100644
--- a/packages/frontend/src/pages/role.vue
+++ b/packages/frontend/src/pages/role.vue
@@ -43,7 +43,7 @@ import MkUserList from '@/components/MkUserList.vue';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import { i18n } from '@/i18n.js';
 import MkTimeline from '@/components/MkTimeline.vue';
-import { instanceName } from '@/config.js';
+import { instanceName } from '@@/js/config.js';
 import { serverErrorImageUrl, infoImageUrl } from '@/instance.js';
 
 const props = withDefaults(defineProps<{
diff --git a/packages/frontend/src/pages/settings/general.vue b/packages/frontend/src/pages/settings/general.vue
index 94ef3b8485..15af5617cc 100644
--- a/packages/frontend/src/pages/settings/general.vue
+++ b/packages/frontend/src/pages/settings/general.vue
@@ -254,7 +254,7 @@ import FormSection from '@/components/form/section.vue';
 import FormLink from '@/components/form/link.vue';
 import MkLink from '@/components/MkLink.vue';
 import MkInfo from '@/components/MkInfo.vue';
-import { langs } from '@/config.js';
+import { langs } from '@@/js/config.js';
 import { defaultStore } from '@/store.js';
 import * as os from '@/os.js';
 import { misskeyApi } from '@/scripts/misskey-api.js';
diff --git a/packages/frontend/src/pages/settings/preferences-backups.vue b/packages/frontend/src/pages/settings/preferences-backups.vue
index dace2cd847..86a044490d 100644
--- a/packages/frontend/src/pages/settings/preferences-backups.vue
+++ b/packages/frontend/src/pages/settings/preferences-backups.vue
@@ -49,7 +49,7 @@ import { unisonReload } from '@/scripts/unison-reload.js';
 import { useStream } from '@/stream.js';
 import { $i } from '@/account.js';
 import { i18n } from '@/i18n.js';
-import { version, host } from '@/config.js';
+import { version, host } from '@@/js/config.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import { miLocalStorage } from '@/local-storage.js';
 const { t, ts } = i18n;
diff --git a/packages/frontend/src/pages/theme-editor.vue b/packages/frontend/src/pages/theme-editor.vue
index fe7896b7d9..a62fe5d581 100644
--- a/packages/frontend/src/pages/theme-editor.vue
+++ b/packages/frontend/src/pages/theme-editor.vue
@@ -88,7 +88,7 @@ import MkFolder from '@/components/MkFolder.vue';
 
 import { $i } from '@/account.js';
 import { Theme, applyTheme } from '@/scripts/theme.js';
-import { host } from '@/config.js';
+import { host } from '@@/js/config.js';
 import * as os from '@/os.js';
 import { ColdDeviceStorage, defaultStore } from '@/store.js';
 import { addTheme } from '@/theme-store.js';
diff --git a/packages/frontend/src/pages/welcome.setup.vue b/packages/frontend/src/pages/welcome.setup.vue
index 5c31259499..a227c7c4bc 100644
--- a/packages/frontend/src/pages/welcome.setup.vue
+++ b/packages/frontend/src/pages/welcome.setup.vue
@@ -38,7 +38,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 import { ref } from 'vue';
 import MkButton from '@/components/MkButton.vue';
 import MkInput from '@/components/MkInput.vue';
-import { host, version } from '@/config.js';
+import { host, version } from '@@/js/config.js';
 import * as os from '@/os.js';
 import { misskeyApi } from '@/scripts/misskey-api.js';
 import { login } from '@/account.js';
diff --git a/packages/frontend/src/pages/welcome.vue b/packages/frontend/src/pages/welcome.vue
index 915fe35025..38d257506c 100644
--- a/packages/frontend/src/pages/welcome.vue
+++ b/packages/frontend/src/pages/welcome.vue
@@ -15,7 +15,7 @@ import { computed, ref } from 'vue';
 import * as Misskey from 'misskey-js';
 import XSetup from './welcome.setup.vue';
 import XEntrance from './welcome.entrance.a.vue';
-import { instanceName } from '@/config.js';
+import { instanceName } from '@@/js/config.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import { fetchInstance } from '@/instance.js';
 
diff --git a/packages/frontend/src/scripts/aiscript/api.ts b/packages/frontend/src/scripts/aiscript/api.ts
index 417ba08c3f..46aed49330 100644
--- a/packages/frontend/src/scripts/aiscript/api.ts
+++ b/packages/frontend/src/scripts/aiscript/api.ts
@@ -10,7 +10,7 @@ import { misskeyApi } from '@/scripts/misskey-api.js';
 import { $i } from '@/account.js';
 import { miLocalStorage } from '@/local-storage.js';
 import { customEmojis } from '@/custom-emojis.js';
-import { url, lang } from '@/config.js';
+import { url, lang } from '@@/js/config.js';
 
 export function aiScriptReadline(q: string): Promise<string> {
 	return new Promise(ok => {
diff --git a/packages/frontend/src/scripts/collapsed.ts b/packages/frontend/src/scripts/collapsed.ts
deleted file mode 100644
index 4ec88a3c65..0000000000
--- a/packages/frontend/src/scripts/collapsed.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * SPDX-FileCopyrightText: syuilo and misskey-project
- * SPDX-License-Identifier: AGPL-3.0-only
- */
-
-import * as Misskey from 'misskey-js';
-
-export function shouldCollapsed(note: Misskey.entities.Note, urls: string[]): boolean {
-	const collapsed = note.cw == null && (
-		note.text != null && (
-			(note.text.includes('$[x2')) ||
-			(note.text.includes('$[x3')) ||
-			(note.text.includes('$[x4')) ||
-			(note.text.includes('$[scale')) ||
-			(note.text.split('\n').length > 9) ||
-			(note.text.length > 500) ||
-			(urls.length >= 4)
-		) || note.files.length >= 5
-	);
-
-	return collapsed;
-}
diff --git a/packages/frontend/src/scripts/gen-search-query.ts b/packages/frontend/src/scripts/gen-search-query.ts
index 60884d08d3..a85ee01e26 100644
--- a/packages/frontend/src/scripts/gen-search-query.ts
+++ b/packages/frontend/src/scripts/gen-search-query.ts
@@ -4,7 +4,7 @@
  */
 
 import * as Misskey from 'misskey-js';
-import { host as localHost } from '@/config.js';
+import { host as localHost } from '@@/js/config.js';
 
 export async function genSearchQuery(v: any, q: string) {
 	let host: string;
diff --git a/packages/frontend/src/scripts/get-embed-code.ts b/packages/frontend/src/scripts/get-embed-code.ts
index 007cd6561b..158ab9c7f8 100644
--- a/packages/frontend/src/scripts/get-embed-code.ts
+++ b/packages/frontend/src/scripts/get-embed-code.ts
@@ -5,7 +5,7 @@
 import { defineAsyncComponent } from 'vue';
 import { v4 as uuid } from 'uuid';
 import type { EmbedParams, EmbeddableEntity } from '@@/js/embed-page.js';
-import { url } from '@/config.js';
+import { url } from '@@/js/config.js';
 import * as os from '@/os.js';
 import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
 import { defaultEmbedParams, embedRouteWithScrollbar } from '@@/js/embed-page.js';
diff --git a/packages/frontend/src/scripts/get-note-menu.ts b/packages/frontend/src/scripts/get-note-menu.ts
index e0ccea813d..49f3199887 100644
--- a/packages/frontend/src/scripts/get-note-menu.ts
+++ b/packages/frontend/src/scripts/get-note-menu.ts
@@ -12,7 +12,7 @@ import { instance } from '@/instance.js';
 import * as os from '@/os.js';
 import { misskeyApi } from '@/scripts/misskey-api.js';
 import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
-import { url } from '@/config.js';
+import { url } from '@@/js/config.js';
 import { defaultStore, noteActions } from '@/store.js';
 import { miLocalStorage } from '@/local-storage.js';
 import { getUserMenu } from '@/scripts/get-user-menu.js';
diff --git a/packages/frontend/src/scripts/get-user-menu.ts b/packages/frontend/src/scripts/get-user-menu.ts
index 035abc7bd0..33316b4ab6 100644
--- a/packages/frontend/src/scripts/get-user-menu.ts
+++ b/packages/frontend/src/scripts/get-user-menu.ts
@@ -8,7 +8,7 @@ import { defineAsyncComponent, ref, watch } from 'vue';
 import * as Misskey from 'misskey-js';
 import { i18n } from '@/i18n.js';
 import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
-import { host, url } from '@/config.js';
+import { host, url } from '@@/js/config.js';
 import * as os from '@/os.js';
 import { misskeyApi } from '@/scripts/misskey-api.js';
 import { defaultStore, userActions } from '@/store.js';
diff --git a/packages/frontend/src/scripts/initialize-sw.ts b/packages/frontend/src/scripts/initialize-sw.ts
index 1517e4e1e8..867ebf19ed 100644
--- a/packages/frontend/src/scripts/initialize-sw.ts
+++ b/packages/frontend/src/scripts/initialize-sw.ts
@@ -3,7 +3,7 @@
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
-import { lang } from '@/config.js';
+import { lang } from '@@/js/config.js';
 
 export async function initializeSw() {
 	if (!('serviceWorker' in navigator)) return;
diff --git a/packages/frontend/src/scripts/intl-const.ts b/packages/frontend/src/scripts/intl-const.ts
index aaa4f0a86e..385f59ec39 100644
--- a/packages/frontend/src/scripts/intl-const.ts
+++ b/packages/frontend/src/scripts/intl-const.ts
@@ -3,7 +3,7 @@
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
-import { lang } from '@/config.js';
+import { lang } from '@@/js/config.js';
 
 export const versatileLang = (lang ?? 'ja-JP').replace('ja-KS', 'ja-JP');
 
diff --git a/packages/frontend/src/scripts/is-link.ts b/packages/frontend/src/scripts/is-link.ts
deleted file mode 100644
index 946f86400e..0000000000
--- a/packages/frontend/src/scripts/is-link.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-/*
- * SPDX-FileCopyrightText: syuilo and misskey-project
- * SPDX-License-Identifier: AGPL-3.0-only
- */
-
-export function isLink(el: HTMLElement) {
-	if (el.tagName === 'A') return true;
-	if (el.parentElement) {
-		return isLink(el.parentElement);
-	}
-	return false;
-}
diff --git a/packages/frontend/src/scripts/media-proxy.ts b/packages/frontend/src/scripts/media-proxy.ts
index 68a5a1dcf8..78eba35ead 100644
--- a/packages/frontend/src/scripts/media-proxy.ts
+++ b/packages/frontend/src/scripts/media-proxy.ts
@@ -4,7 +4,7 @@
  */
 
 import { MediaProxy } from '@@/js/media-proxy.js';
-import { url } from '@/config.js';
+import { url } from '@@/js/config.js';
 import { instance } from '@/instance.js';
 
 let _mediaProxy: MediaProxy | null = null;
diff --git a/packages/frontend/src/scripts/misskey-api.ts b/packages/frontend/src/scripts/misskey-api.ts
index 49fb6f9e59..1b1159fd01 100644
--- a/packages/frontend/src/scripts/misskey-api.ts
+++ b/packages/frontend/src/scripts/misskey-api.ts
@@ -5,7 +5,7 @@
 
 import * as Misskey from 'misskey-js';
 import { ref } from 'vue';
-import { apiUrl } from '@/config.js';
+import { apiUrl } from '@@/js/config.js';
 import { $i } from '@/account.js';
 export const pendingApiRequestsCount = ref(0);
 
diff --git a/packages/frontend/src/scripts/player-url-transform.ts b/packages/frontend/src/scripts/player-url-transform.ts
index 53b2a9e441..39c6df6500 100644
--- a/packages/frontend/src/scripts/player-url-transform.ts
+++ b/packages/frontend/src/scripts/player-url-transform.ts
@@ -2,7 +2,7 @@
  * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
-import { hostname } from '@/config.js';
+import { hostname } from '@@/js/config.js';
 
 export function transformPlayerUrl(url: string): string {
 	const urlObj = new URL(url);
diff --git a/packages/frontend/src/scripts/popout.ts b/packages/frontend/src/scripts/popout.ts
index ed49611b4f..5b141222e8 100644
--- a/packages/frontend/src/scripts/popout.ts
+++ b/packages/frontend/src/scripts/popout.ts
@@ -4,7 +4,7 @@
  */
 
 import { appendQuery } from '@@/js/url.js';
-import * as config from '@/config.js';
+import * as config from '@@/js/config.js';
 
 export function popout(path: string, w?: HTMLElement) {
 	let url = path.startsWith('http://') || path.startsWith('https://') ? path : config.url + path;
diff --git a/packages/frontend/src/scripts/upload.ts b/packages/frontend/src/scripts/upload.ts
index abb0e1e677..22dce609c6 100644
--- a/packages/frontend/src/scripts/upload.ts
+++ b/packages/frontend/src/scripts/upload.ts
@@ -9,7 +9,7 @@ import { v4 as uuid } from 'uuid';
 import { readAndCompressImage } from '@misskey-dev/browser-image-resizer';
 import { getCompressionConfig } from './upload/compress-config.js';
 import { defaultStore } from '@/store.js';
-import { apiUrl } from '@/config.js';
+import { apiUrl } from '@@/js/config.js';
 import { $i } from '@/account.js';
 import { alert } from '@/os.js';
 import { i18n } from '@/i18n.js';
diff --git a/packages/frontend/src/scripts/worker-multi-dispatch.ts b/packages/frontend/src/scripts/worker-multi-dispatch.ts
deleted file mode 100644
index 6b3fcd9383..0000000000
--- a/packages/frontend/src/scripts/worker-multi-dispatch.ts
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * SPDX-FileCopyrightText: syuilo and misskey-project
- * SPDX-License-Identifier: AGPL-3.0-only
- */
-
-function defaultUseWorkerNumber(prev: number, totalWorkers: number) {
-	return prev + 1;
-}
-
-export class WorkerMultiDispatch<POST = any, RETURN = any> {
-	private symbol = Symbol('WorkerMultiDispatch');
-	private workers: Worker[] = [];
-	private terminated = false;
-	private prevWorkerNumber = 0;
-	private getUseWorkerNumber = defaultUseWorkerNumber;
-	private finalizationRegistry: FinalizationRegistry<symbol>;
-
-	constructor(workerConstructor: () => Worker, concurrency: number, getUseWorkerNumber = defaultUseWorkerNumber) {
-		this.getUseWorkerNumber = getUseWorkerNumber;
-		for (let i = 0; i < concurrency; i++) {
-			this.workers.push(workerConstructor());
-		}
-
-		this.finalizationRegistry = new FinalizationRegistry(() => {
-			this.terminate();
-		});
-		this.finalizationRegistry.register(this, this.symbol);
-
-		if (_DEV_) console.log('WorkerMultiDispatch: Created', this);
-	}
-
-	public postMessage(message: POST, options?: Transferable[] | StructuredSerializeOptions, useWorkerNumber: typeof defaultUseWorkerNumber = this.getUseWorkerNumber) {
-		let workerNumber = useWorkerNumber(this.prevWorkerNumber, this.workers.length);
-		workerNumber = Math.abs(Math.round(workerNumber)) % this.workers.length;
-		if (_DEV_) console.log('WorkerMultiDispatch: Posting message to worker', workerNumber, useWorkerNumber);
-		this.prevWorkerNumber = workerNumber;
-
-		// 不毛だがunionをoverloadに突っ込めない
-		// https://stackoverflow.com/questions/66507585/overload-signatures-union-types-and-no-overload-matches-this-call-error
-		// https://github.com/microsoft/TypeScript/issues/14107
-		if (Array.isArray(options)) {
-			this.workers[workerNumber].postMessage(message, options);
-		} else {
-			this.workers[workerNumber].postMessage(message, options);
-		}
-		return workerNumber;
-	}
-
-	public addListener(callback: (this: Worker, ev: MessageEvent<RETURN>) => any, options?: boolean | AddEventListenerOptions) {
-		this.workers.forEach(worker => {
-			worker.addEventListener('message', callback, options);
-		});
-	}
-
-	public removeListener(callback: (this: Worker, ev: MessageEvent<RETURN>) => any, options?: boolean | AddEventListenerOptions) {
-		this.workers.forEach(worker => {
-			worker.removeEventListener('message', callback, options);
-		});
-	}
-
-	public terminate() {
-		this.terminated = true;
-		if (_DEV_) console.log('WorkerMultiDispatch: Terminating', this);
-		this.workers.forEach(worker => {
-			worker.terminate();
-		});
-		this.workers = [];
-		this.finalizationRegistry.unregister(this);
-	}
-
-	public isTerminated() {
-		return this.terminated;
-	}
-
-	public getWorkers() {
-		return this.workers;
-	}
-
-	public getSymbol() {
-		return this.symbol;
-	}
-}
diff --git a/packages/frontend/src/store.ts b/packages/frontend/src/store.ts
index 0bf499bb4d..40615cfc7d 100644
--- a/packages/frontend/src/store.ts
+++ b/packages/frontend/src/store.ts
@@ -8,7 +8,7 @@ import * as Misskey from 'misskey-js';
 import { miLocalStorage } from './local-storage.js';
 import type { SoundType } from '@/scripts/sound.js';
 import { Storage } from '@/pizzax.js';
-import { hemisphere } from '@/scripts/intl-const.js';
+import { hemisphere } from '@@/js/intl-const.js';
 
 interface PostFormAction {
 	title: string,
@@ -558,7 +558,7 @@ export class ColdDeviceStorage {
 	public static set<T extends keyof typeof ColdDeviceStorage.default>(key: T, value: typeof ColdDeviceStorage.default[T]): void {
 		// 呼び出し側のバグ等で undefined が来ることがある
 		// undefined を文字列として miLocalStorage に入れると参照する際の JSON.parse でコケて不具合の元になるため無視
-		 
+
 		if (value === undefined) {
 			console.error(`attempt to store undefined value for key '${key}'`);
 			return;
diff --git a/packages/frontend/src/stream.ts b/packages/frontend/src/stream.ts
index 9d7edce890..e63dac951c 100644
--- a/packages/frontend/src/stream.ts
+++ b/packages/frontend/src/stream.ts
@@ -6,7 +6,7 @@
 import * as Misskey from 'misskey-js';
 import { markRaw } from 'vue';
 import { $i } from '@/account.js';
-import { wsOrigin } from '@/config.js';
+import { wsOrigin } from '@@/js/config.js';
 // TODO: No WebsocketモードでStreamMockが使えそう
 //import { StreamMock } from '@/scripts/stream-mock.js';
 
diff --git a/packages/frontend/src/ui/_common_/common.ts b/packages/frontend/src/ui/_common_/common.ts
index 74c3028745..b067e721a5 100644
--- a/packages/frontend/src/ui/_common_/common.ts
+++ b/packages/frontend/src/ui/_common_/common.ts
@@ -7,7 +7,7 @@ import { defineAsyncComponent } from 'vue';
 import type { MenuItem } from '@/types/menu.js';
 import * as os from '@/os.js';
 import { instance } from '@/instance.js';
-import { host } from '@/config.js';
+import { host } from '@@/js/config.js';
 import { i18n } from '@/i18n.js';
 import { $i } from '@/account.js';
 
diff --git a/packages/frontend/src/ui/classic.sidebar.vue b/packages/frontend/src/ui/classic.sidebar.vue
index d8574a915f..87b4515d46 100644
--- a/packages/frontend/src/ui/classic.sidebar.vue
+++ b/packages/frontend/src/ui/classic.sidebar.vue
@@ -51,7 +51,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 <script lang="ts" setup>
 import { defineAsyncComponent, computed, watch, ref, shallowRef } from 'vue';
 import { openInstanceMenu } from './_common_/common.js';
-// import { host } from '@/config.js';
+// import { host } from '@@/js/config.js';
 import * as os from '@/os.js';
 import { navbarItemDef } from '@/navbar.js';
 import { openAccountMenu as openAccountMenu_, $i } from '@/account.js';
diff --git a/packages/frontend/src/ui/classic.vue b/packages/frontend/src/ui/classic.vue
index b833e9f6be..fa04409d2d 100644
--- a/packages/frontend/src/ui/classic.vue
+++ b/packages/frontend/src/ui/classic.vue
@@ -49,7 +49,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 import { defineAsyncComponent, onMounted, provide, ref, computed, shallowRef } from 'vue';
 import XSidebar from './classic.sidebar.vue';
 import XCommon from './_common_/common.vue';
-import { instanceName } from '@/config.js';
+import { instanceName } from '@@/js/config.js';
 import { StickySidebar } from '@/scripts/sticky-sidebar.js';
 import * as os from '@/os.js';
 import { PageMetadata, provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js';
@@ -57,6 +57,8 @@ import { defaultStore } from '@/store.js';
 import { i18n } from '@/i18n.js';
 import { miLocalStorage } from '@/local-storage.js';
 import { mainRouter } from '@/router/main.js';
+import { isLink } from '@@/js/is-link.js';
+
 const XHeaderMenu = defineAsyncComponent(() => import('./classic.header.vue'));
 const XWidgets = defineAsyncComponent(() => import('./universal.widgets.vue'));
 
@@ -104,12 +106,6 @@ function top() {
 }
 
 function onContextmenu(ev: MouseEvent) {
-	const isLink = (el: HTMLElement) => {
-		if (el.tagName === 'A') return true;
-		if (el.parentElement) {
-			return isLink(el.parentElement);
-		}
-	};
 	if (isLink(ev.target)) return;
 	if (['INPUT', 'TEXTAREA', 'IMG', 'VIDEO', 'CANVAS'].includes(ev.target.tagName) || ev.target.attributes['contenteditable']) return;
 	if (window.getSelection().toString() !== '') return;
diff --git a/packages/frontend/src/ui/deck/main-column.vue b/packages/frontend/src/ui/deck/main-column.vue
index e7ecf7fd20..f8c712c371 100644
--- a/packages/frontend/src/ui/deck/main-column.vue
+++ b/packages/frontend/src/ui/deck/main-column.vue
@@ -27,6 +27,7 @@ import { i18n } from '@/i18n.js';
 import { PageMetadata, provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js';
 import { useScrollPositionManager } from '@/nirax.js';
 import { getScrollContainer } from '@@/js/scroll.js';
+import { isLink } from '@@/js/is-link.js';
 import { mainRouter } from '@/router/main.js';
 
 defineProps<{
@@ -52,12 +53,6 @@ function back() {
 function onContextmenu(ev: MouseEvent) {
 	if (!ev.target) return;
 
-	const isLink = (el: HTMLElement) => {
-		if (el.tagName === 'A') return true;
-		if (el.parentElement) {
-			return isLink(el.parentElement);
-		}
-	};
 	if (isLink(ev.target as HTMLElement)) return;
 	if (['INPUT', 'TEXTAREA', 'IMG', 'VIDEO', 'CANVAS'].includes((ev.target as HTMLElement).tagName) || (ev.target as HTMLElement).attributes['contenteditable']) return;
 	if (window.getSelection()?.toString() !== '') return;
diff --git a/packages/frontend/src/ui/minimum.vue b/packages/frontend/src/ui/minimum.vue
index db5eb19c20..9e41c48c5b 100644
--- a/packages/frontend/src/ui/minimum.vue
+++ b/packages/frontend/src/ui/minimum.vue
@@ -17,7 +17,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 import { computed, provide, ref } from 'vue';
 import XCommon from './_common_/common.vue';
 import { PageMetadata, provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js';
-import { instanceName } from '@/config.js';
+import { instanceName } from '@@/js/config.js';
 import { mainRouter } from '@/router/main.js';
 
 const isRoot = computed(() => mainRouter.currentRoute.value.name === 'index');
diff --git a/packages/frontend/src/ui/universal.vue b/packages/frontend/src/ui/universal.vue
index 00a6811fc9..a2a79c74a1 100644
--- a/packages/frontend/src/ui/universal.vue
+++ b/packages/frontend/src/ui/universal.vue
@@ -98,7 +98,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 import { defineAsyncComponent, provide, onMounted, computed, ref, watch, shallowRef, Ref } from 'vue';
 import XCommon from './_common_/common.vue';
 import type MkStickyContainer from '@/components/global/MkStickyContainer.vue';
-import { instanceName } from '@/config.js';
+import { instanceName } from '@@/js/config.js';
 import XDrawerMenu from '@/ui/_common_/navbar-for-mobile.vue';
 import * as os from '@/os.js';
 import { defaultStore } from '@/store.js';
@@ -111,6 +111,7 @@ import { miLocalStorage } from '@/local-storage.js';
 import { CURRENT_STICKY_BOTTOM } from '@@/js/const.js';
 import { useScrollPositionManager } from '@/nirax.js';
 import { mainRouter } from '@/router/main.js';
+import { isLink } from '@@/js/is-link.js';
 
 const XWidgets = defineAsyncComponent(() => import('./universal.widgets.vue'));
 const XSidebar = defineAsyncComponent(() => import('@/ui/_common_/navbar.vue'));
@@ -195,12 +196,6 @@ onMounted(() => {
 });
 
 const onContextmenu = (ev) => {
-	const isLink = (el: HTMLElement) => {
-		if (el.tagName === 'A') return true;
-		if (el.parentElement) {
-			return isLink(el.parentElement);
-		}
-	};
 	if (isLink(ev.target)) return;
 	if (['INPUT', 'TEXTAREA', 'IMG', 'VIDEO', 'CANVAS'].includes(ev.target.tagName) || ev.target.attributes['contenteditable']) return;
 	if (window.getSelection()?.toString() !== '') return;
diff --git a/packages/frontend/src/ui/visitor.vue b/packages/frontend/src/ui/visitor.vue
index c229946bd4..01d0737123 100644
--- a/packages/frontend/src/ui/visitor.vue
+++ b/packages/frontend/src/ui/visitor.vue
@@ -71,7 +71,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 <script lang="ts" setup>
 import { onMounted, provide, ref, computed } from 'vue';
 import XCommon from './_common_/common.vue';
-import { instanceName } from '@/config.js';
+import { instanceName } from '@@/js/config.js';
 import * as os from '@/os.js';
 import { instance } from '@/instance.js';
 import XSigninDialog from '@/components/MkSigninDialog.vue';
diff --git a/packages/frontend/src/ui/zen.vue b/packages/frontend/src/ui/zen.vue
index bb8cffaf52..f22bf41fd7 100644
--- a/packages/frontend/src/ui/zen.vue
+++ b/packages/frontend/src/ui/zen.vue
@@ -25,7 +25,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 import { computed, provide, ref } from 'vue';
 import XCommon from './_common_/common.vue';
 import { PageMetadata, provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js';
-import { instanceName, ui } from '@/config.js';
+import { instanceName, ui } from '@@/js/config.js';
 import { i18n } from '@/i18n.js';
 import { mainRouter } from '@/router/main.js';
 
diff --git a/packages/frontend/src/widgets/WidgetInstanceInfo.vue b/packages/frontend/src/widgets/WidgetInstanceInfo.vue
index 5d8beaf9a9..ec12aa265c 100644
--- a/packages/frontend/src/widgets/WidgetInstanceInfo.vue
+++ b/packages/frontend/src/widgets/WidgetInstanceInfo.vue
@@ -22,7 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 <script lang="ts" setup>
 import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
 import { GetFormResultType } from '@/scripts/form.js';
-import { host } from '@/config.js';
+import { host } from '@@/js/config.js';
 import { instance } from '@/instance.js';
 
 const name = 'instanceInfo';
diff --git a/packages/frontend/src/widgets/WidgetRss.vue b/packages/frontend/src/widgets/WidgetRss.vue
index 13f5a4802a..511777a570 100644
--- a/packages/frontend/src/widgets/WidgetRss.vue
+++ b/packages/frontend/src/widgets/WidgetRss.vue
@@ -28,7 +28,7 @@ import * as Misskey from 'misskey-js';
 import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
 import { GetFormResultType } from '@/scripts/form.js';
 import MkContainer from '@/components/MkContainer.vue';
-import { url as base } from '@/config.js';
+import { url as base } from '@@/js/config.js';
 import { i18n } from '@/i18n.js';
 import { useInterval } from '@@/js/use-interval.js';
 import { infoImageUrl } from '@/instance.js';
diff --git a/packages/frontend/src/widgets/WidgetRssTicker.vue b/packages/frontend/src/widgets/WidgetRssTicker.vue
index 51f1cac97f..b393ecd74b 100644
--- a/packages/frontend/src/widgets/WidgetRssTicker.vue
+++ b/packages/frontend/src/widgets/WidgetRssTicker.vue
@@ -34,7 +34,7 @@ import MarqueeText from '@/components/MkMarquee.vue';
 import { GetFormResultType } from '@/scripts/form.js';
 import MkContainer from '@/components/MkContainer.vue';
 import { shuffle } from '@/scripts/shuffle.js';
-import { url as base } from '@/config.js';
+import { url as base } from '@@/js/config.js';
 import { useInterval } from '@@/js/use-interval.js';
 
 const name = 'rssTicker';
-- 
cgit v1.2.3-freya

