diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue
index 0148e0c27e82829e65890d78b73995290882d757..e34c7b07cbd3f60fb3f3921075ea556623d2c868 100644
--- a/packages/frontend/src/components/MkNote.vue
+++ b/packages/frontend/src/components/MkNote.vue
@@ -70,6 +70,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 							:emojiUrls="appearNote.emojis"
 							:enableEmojiMenu="true"
 							:enableEmojiMenuReaction="true"
+							:isAnim="allowAnim"
 						/>
 						<div v-if="translating || translation" :class="$style.translation">
 							<MkLoading v-if="translating" mini/>
@@ -78,6 +79,8 @@ SPDX-License-Identifier: AGPL-3.0-only
 								<Mfm :text="translation.text" :author="appearNote.user" :nyaize="'account'" :emojiUrls="appearNote.emojis"/>
 							</div>
 						</div>
+						<MkButton v-if="!allowAnim && animated" :small="true" @click="animatedMFM()" v-on:click.stop><i class="ph-play ph-bold ph-lg "></i> Animated MFM</MkButton>
+						<MkButton v-else-if="!defaultStore.state.animatedMfm && allowAnim && animated" :small="true" @click="animatedMFM()" v-on:click.stop><i class="ph-stop ph-bold ph-lg "></i> Animated MFM</MkButton>
 					</div>
 					<div v-if="appearNote.files.length > 0">
 						<MkMediaList :mediaList="appearNote.files" v-on:click.stop/>
@@ -175,6 +178,7 @@ import MkPoll from '@/components/MkPoll.vue';
 import MkUsersTooltip from '@/components/MkUsersTooltip.vue';
 import MkUrlPreview from '@/components/MkUrlPreview.vue';
 import MkInstanceTicker from '@/components/MkInstanceTicker.vue';
+import MkButton from '@/components/MkButton.vue';
 import { pleaseLogin } from '@/scripts/please-login.js';
 import { focusPrev, focusNext } from '@/scripts/focus.js';
 import { checkWordMute } from '@/scripts/check-word-mute.js';
@@ -183,9 +187,10 @@ import * as os from '@/os.js';
 import { defaultStore, noteViewInterruptors } from '@/store.js';
 import { reactionPicker } from '@/scripts/reaction-picker.js';
 import { extractUrlFromMfm } from '@/scripts/extract-url-from-mfm.js';
+import { checkAnimationFromMfm } from '@/scripts/check-animated-mfm.js';
 import { $i } from '@/account.js';
 import { i18n } from '@/i18n.js';
-import { getAbuseNoteMenu, getCopyNoteLinkMenu, getNoteClipMenu, getNoteMenu, getRenoteMenu } from '@/scripts/get-note-menu.js';
+import { getAbuseNoteMenu, getCopyNoteLinkMenu, getNoteClipMenu, getNoteMenu } from '@/scripts/get-note-menu.js';
 import { getNoteVersionsMenu } from '@/scripts/get-note-versions-menu.js';
 import { useNoteCapture } from '@/scripts/use-note-capture.js';
 import { deepClone } from '@/scripts/clone.js';
@@ -261,6 +266,8 @@ const isMyRenote = $i && ($i.id === note.userId);
 const showContent = ref(false);
 const parsed = $computed(() => appearNote.text ? mfm.parse(appearNote.text) : null);
 const urls = parsed ? extractUrlFromMfm(parsed) : null;
+const animated = $computed(() => parsed ? checkAnimationFromMfm(parsed) : null);
+const allowAnim = ref(defaultStore.state.advancedMfm && defaultStore.state.animatedMfm ? true : false);
 const isLong = shouldCollapsed(appearNote, urls ?? []);
 const collapsed = ref(appearNote.cw == null && isLong);
 const isDeleted = ref(false);
@@ -700,6 +707,18 @@ function showRenoteMenu(viaKeyboard = false): void {
 	}
 }
 
+function animatedMFM() {
+	if (allowAnim.value) {
+		allowAnim.value = false;
+	} else {
+		os.confirm({
+			type: 'warning',
+			text: 'Animated MFMs could include flashing lights and fast moving text/emojis.',
+			okText: 'Animate',
+		}).then((res) => { if (!res.canceled) allowAnim.value = true; });
+	}
+}
+
 function focus() {
 	el.value.focus();
 }
diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue
index bb8a0aa7366b64edc02213a98130d69719aadea7..ecc2187903f244bd06d75b564db8a5710067624c 100644
--- a/packages/frontend/src/components/MkNoteDetailed.vue
+++ b/packages/frontend/src/components/MkNoteDetailed.vue
@@ -83,6 +83,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 					:emojiUrls="appearNote.emojis"
 					:enableEmojiMenu="true"
 					:enableEmojiMenuReaction="true"
+					:isAnim="allowAnim"
 				/>
 				<a v-if="appearNote.renote != null" :class="$style.rn">RN:</a>
 				<div v-if="translating || translation" :class="$style.translation">
@@ -92,6 +93,8 @@ SPDX-License-Identifier: AGPL-3.0-only
 						<Mfm :text="translation.text" :author="appearNote.user" :nyaize="'account'" :emojiUrls="appearNote.emojis"/>
 					</div>
 				</div>
+				<MkButton v-if="!allowAnim && animated" :small="true" @click="animatedMFM()" v-on:click.stop><i class="ph-play ph-bold ph-lg "></i> Animated MFM</MkButton>
+				<MkButton v-else-if="!defaultStore.state.animatedMfm && allowAnim && animated" :small="true" @click="animatedMFM()" v-on:click.stop><i class="ph-stop ph-bold ph-lg "></i> Animated MFM</MkButton>
 				<div v-if="appearNote.files.length > 0">
 					<MkMediaList :mediaList="appearNote.files"/>
 				</div>
@@ -247,6 +250,7 @@ import { deepClone } from '@/scripts/clone.js';
 import { useTooltip } from '@/scripts/use-tooltip.js';
 import { claimAchievement } from '@/scripts/achievements.js';
 import { MenuItem } from '@/types/menu.js';
+import { checkAnimationFromMfm } from '@/scripts/check-animated-mfm.js';
 import MkRippleEffect from '@/components/MkRippleEffect.vue';
 import { showMovedDialog } from '@/scripts/show-moved-dialog.js';
 import MkUserCardMini from '@/components/MkUserCardMini.vue';
@@ -306,6 +310,8 @@ const translation = ref(null);
 const translating = ref(false);
 const parsed = $computed(() => appearNote.text ? mfm.parse(appearNote.text) : null);
 const urls = parsed ? extractUrlFromMfm(parsed) : null;
+const animated = $computed(() => parsed ? checkAnimationFromMfm(parsed) : null);
+const allowAnim = ref(defaultStore.state.advancedMfm && defaultStore.state.animatedMfm ? true : false);
 const showTicker = (defaultStore.state.instanceTicker === 'always') || (defaultStore.state.instanceTicker === 'remote' && appearNote.user.instance);
 const conversation = ref<Misskey.entities.Note[]>([]);
 const replies = ref<Misskey.entities.Note[]>([]);
@@ -734,6 +740,18 @@ function loadConversation() {
 }
 
 if (appearNote.reply && appearNote.reply.replyId && defaultStore.state.autoloadConversation) loadConversation();
+
+function animatedMFM() {
+	if (allowAnim.value) {
+		allowAnim.value = false;
+	} else {
+		os.confirm({
+			type: 'warning',
+			text: 'Animated MFMs could include flashing lights and fast moving text/emojis.',
+			okText: 'Animate',
+		}).then((res) => { if (!res.canceled) allowAnim.value = true; });
+	}
+}
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/components/MkSubNoteContent.vue b/packages/frontend/src/components/MkSubNoteContent.vue
index ac07e994ece026581d8c95e5fcca26903d446108..0cedcb4ac57ca20623dca005933179e0840d311b 100644
--- a/packages/frontend/src/components/MkSubNoteContent.vue
+++ b/packages/frontend/src/components/MkSubNoteContent.vue
@@ -9,7 +9,9 @@ SPDX-License-Identifier: AGPL-3.0-only
 		<span v-if="note.isHidden" style="opacity: 0.5">({{ i18n.ts.private }})</span>
 		<span v-if="note.deletedAt" style="opacity: 0.5">({{ i18n.ts.deleted }})</span>
 		<MkA v-if="note.replyId" :class="$style.reply" :to="`/notes/${note.replyId}`" v-on:click.stop><i class="ph-arrow-bend-left-up ph-bold ph-lg"></i></MkA>
-		<Mfm v-if="note.text" :text="note.text" :author="note.user" :nyaize="'account'" :emojiUrls="note.emojis"/>
+		<Mfm v-if="note.text" :text="note.text" :author="note.user" :nyaize="'account'" :isAnim="allowAnim" :emojiUrls="note.emojis"/>
+		<MkButton v-if="!allowAnim && animated && !hideFiles" :small="true" @click="animatedMFM()" v-on:click.stop><i class="ph-play ph-bold ph-lg "></i> Animated MFM</MkButton>
+		<MkButton v-else-if="!defaultStore.state.animatedMfm && allowAnim && animated && !hideFiles" :small="true" @click="animatedMFM()" v-on:click.stop><i class="ph-stop ph-bold ph-lg "></i> Animated MFM</MkButton>
 		<div v-if="note.text && translating || note.text && translation" :class="$style.translation">
 			<MkLoading v-if="translating" mini/>
 			<div v-else>
@@ -39,13 +41,17 @@ SPDX-License-Identifier: AGPL-3.0-only
 <script lang="ts" setup>
 import { } from 'vue';
 import * as Misskey from 'misskey-js';
+import * as mfm from 'mfm-js';
 import MkMediaList from '@/components/MkMediaList.vue';
 import MkPoll from '@/components/MkPoll.vue';
+import MkButton from '@/components/MkButton.vue';
 import { i18n } from '@/i18n.js';
 import { $i } from '@/account.js';
 import { shouldCollapsed } from '@/scripts/collapsed.js';
 import { defaultStore } from '@/store.js';
 import { useRouter } from '@/router.js';
+import * as os from '@/os.js';
+import { checkAnimationFromMfm } from '@/scripts/check-animated-mfm.js';
 
 const props = defineProps<{
 	note: Misskey.entities.Note;
@@ -60,8 +66,24 @@ function noteclick(id: string) {
 	router.push(`/notes/${id}`);
 }
 
+const parsed = $computed(() => props.note.text ? mfm.parse(props.note.text) : null);
+const animated = $computed(() => parsed ? checkAnimationFromMfm(parsed) : null);
+let allowAnim = $ref(defaultStore.state.advancedMfm && defaultStore.state.animatedMfm ? true : false);
+
 const isLong = shouldCollapsed(props.note, []);
 
+function animatedMFM() {
+	if (allowAnim) {
+		allowAnim = false;
+	} else {
+		os.confirm({
+			type: 'warning',
+			text: 'Animated MFMs could include flashing lights and fast moving text/emojis.',
+			okText: 'Animate',
+		}).then((res) => { if (!res.canceled) allowAnim = true; });
+	}
+}
+
 const collapsed = $ref(isLong);
 </script>
 
diff --git a/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.ts b/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.ts
index 5ff20fbe9bebf773a12699ea64ad937e44f142ad..9208285e990e5230a65468168422ff639dea8de5 100644
--- a/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.ts
+++ b/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.ts
@@ -40,6 +40,7 @@ type MfmProps = {
 	parsedNodes?: mfm.MfmNode[] | null;
 	enableEmojiMenu?: boolean;
 	enableEmojiMenuReaction?: boolean;
+	isAnim?: boolean;
 };
 
 // eslint-disable-next-line import/no-default-export
@@ -57,7 +58,7 @@ export default function(props: MfmProps) {
 		return t.match(/^[0-9.]+s$/) ? t : null;
 	};
 
-	const useAnim = defaultStore.state.advancedMfm && defaultStore.state.animatedMfm;
+	const useAnim = defaultStore.state.advancedMfm && defaultStore.state.animatedMfm ? true : props.isAnim ? true : false;
 
 	/**
 	 * Gen Vue Elements from MFM AST
diff --git a/packages/frontend/src/scripts/check-animated-mfm.ts b/packages/frontend/src/scripts/check-animated-mfm.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f47c40d4bc6ea46a825ef7741ac58789b77c0091
--- /dev/null
+++ b/packages/frontend/src/scripts/check-animated-mfm.ts
@@ -0,0 +1,29 @@
+import * as mfm from 'mfm-js';
+
+export function checkAnimationFromMfm(nodes: mfm.MfmNode[]): boolean {
+	const animatedNodes = mfm.extract(nodes, (node) => {
+		if (node.type === 'fn') {
+			if (node.props.name === 'tada' ||
+			node.props.name === 'jelly' ||
+			node.props.name === 'twitch' ||
+			node.props.name === 'shake' ||
+			node.props.name === 'spin' ||
+			node.props.name === 'jump' || 
+			node.props.name === 'bounce' || 
+			node.props.name === 'rainbow' || 
+			node.props.name === 'sparkle') {
+				return true;
+			} else {
+				return false;
+			}
+		} else {
+			return false;
+		}
+	});
+	
+	if (animatedNodes.length > 0) {
+		return true;
+	} else {
+		return false;
+	}
+}
diff --git a/packages/frontend/src/store.ts b/packages/frontend/src/store.ts
index 56fa958f08e2711bc72edb32ba5b6306849458eb..a4f8f4d1102bd7f6338942f08f1926171501a904 100644
--- a/packages/frontend/src/store.ts
+++ b/packages/frontend/src/store.ts
@@ -200,7 +200,7 @@ export const defaultStore = markRaw(new Storage('base', {
 	},
 	animatedMfm: {
 		where: 'device',
-		default: true,
+		default: false,
 	},
 	advancedMfm: {
 		where: 'device',