({ id: "layer", name:
const meta = {
title: "SW/Popup",
parameters: { layout: "fullscreen", controls: { disable: true } },
+ decorators: [withSWNotice],
} satisfies Meta
export default meta
type Story = StoryObj
diff --git a/src/sw/sw-process-header.stories.tsx b/src/sw/sw-process-header.stories.tsx
index e49f993..90f883b 100644
--- a/src/sw/sw-process-header.stories.tsx
+++ b/src/sw/sw-process-header.stories.tsx
@@ -1,5 +1,6 @@
import { useState, type ReactNode } from "react"
import type { Meta, StoryObj } from "@storybook/react-vite"
+import { withSWNotice } from "./sw-story-notice"
import {
SWProcessBar, SWProcessHeader, SWProcessSplitButton, SWTheme, SWToolButton, type SWProcessGroup, type SWProcessHeaderProps, type SWProcessSplitType,
} from "@hm-design/sw"
@@ -31,6 +32,7 @@ const LOGO = { logoSrc: placeholderLogo, logoAlt: "임시 로고" } satisfies Pa
const meta = {
title: "SW/Step Header",
parameters: { layout: "fullscreen", controls: { disable: true } },
+ decorators: [withSWNotice],
} satisfies Meta
export default meta
type Story = StoryObj
diff --git a/src/sw/sw-property-panel.stories.tsx b/src/sw/sw-property-panel.stories.tsx
index cb6f4bb..bac20af 100644
--- a/src/sw/sw-property-panel.stories.tsx
+++ b/src/sw/sw-property-panel.stories.tsx
@@ -1,4 +1,5 @@
import type { Meta, StoryObj } from "@storybook/react-vite"
+import { withSWNotice } from "./sw-story-notice"
import { PropertyPanelComparePage } from "./sw-header.stories"
const meta = {
@@ -8,6 +9,7 @@ const meta = {
// Custom Content and Multiple Popups.
id: "sw-header",
parameters: { layout: "fullscreen", controls: { disable: true } },
+ decorators: [withSWNotice],
} satisfies Meta
export default meta
diff --git a/src/sw/sw-stories.css b/src/sw/sw-stories.css
index ecf37ee..36bc2b8 100644
--- a/src/sw/sw-stories.css
+++ b/src/sw/sw-stories.css
@@ -68,20 +68,20 @@
.sw-story-detail-row > div { display: flex; align-items: center; gap: 8px; }
/* Overview host layout only. Native controls below are Storybook data controls, outside the product. */
-.sw-story-overview.sb-page { height: 100dvh; min-height: 0; padding-block: 16px; }
-.sw-story-overview > .sb-page-inner { height: 100%; display: flex; flex-direction: column; gap: 12px; }
-.sw-story-overview .sb-page-header { flex: 0 0 auto; padding-bottom: 8px; }
-.sw-story-overview .sb-page-header .sb-page-kicker { margin-bottom: 4px; }
-.sw-story-overview .sb-page-header .sb-page-title { margin-block: 0 4px; }
-.sw-overview-toolbar { display: flex; flex: 0 0 auto; flex-wrap: wrap; align-items: center; gap: 8px; font-size: 13px; }
+/* 2026-11-09: the page frame (gutter, title block, Inner) is the common SW Story page; only the Stage keeps a viewport-based height.
+ The Stage fits the slot (16:9 inside it), so the slot gets a definite height instead of the page being locked to 100dvh. */
+.sw-story-overview > .sb-page-inner { display: flex; flex-direction: column; }
+.sw-overview-toolbar { display: flex; flex: 0 0 auto; flex-wrap: wrap; align-items: center; gap: 8px; margin-bottom: 12px; font-size: 13px; }
.sw-overview-toolbar select, .sw-overview-data button { font: inherit; padding: 3px 8px; color: inherit; background: var(--hm-sem-bg-surface); border: 1px solid var(--hm-sem-stroke-input-default); border-radius: 4px; }
.sw-overview-toolbar details { flex-basis: 100%; }
.sw-overview-toolbar summary { width: max-content; cursor: pointer; }
.sw-overview-data { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; padding-block: 8px; }
.sw-overview-data label { display: inline-flex; align-items: center; gap: 4px; }
-.sw-overview-slot { flex: 1 1 0; min-width: 0; min-height: 0; display: flex; align-items: center; justify-content: center; }
+.sw-overview-slot { flex: none; height: max(360px, calc(100dvh - 260px)); min-width: 0; min-height: 0; display: flex; align-items: center; justify-content: center; }
.sw-overview-stage { flex: 0 0 auto; overflow: auto; overscroll-behavior: contain; background: var(--sw-story-canvas); outline: 1px solid var(--hm-sem-stroke-input-default); }
-.sw-overview-world { min-width: 390px; min-height: 100%; position: relative; }
+/* 2026-11-09: floor 390 -> 358, the measured minimum width of SWHeader (content width 358px; narrower overflows). 390 forced a 32px
+ Stage scroll at a 390px viewport (Inner 358px). Below 358px the Stage still scrolls instead of clipping. */
+.sw-overview-world { min-width: 358px; min-height: 100%; position: relative; }
/* Placement-only Storybook exception: the panel sits in the Stage. 2026-10-26 (user request): no Stage scrollbar when
it opens — the panel is clamped to the Stage height (as the product clamps it to the viewport) and its body scrolls.
The Stage height comes from the Story (--sw-preview-stage-height): the panel's containing block is the Header.
diff --git a/src/sw/sw-story-notice.tsx b/src/sw/sw-story-notice.tsx
new file mode 100644
index 0000000..aa6e622
--- /dev/null
+++ b/src/sw/sw-story-notice.tsx
@@ -0,0 +1,22 @@
+import type { Decorator } from "@storybook/react-vite"
+import { StoryNotice } from "@/components/story-page"
+
+// Storybook presentation only. Common notice shown above the page title of every SW Story (SW/* groups only):
+// each SW stories file opts in with `decorators: [withSWNotice]`, so other menus never render it.
+// Hidden verification / internal Stories are skipped; `parameters.swNotice: false` opts a Story out
+// (use it where the page already carries the same notice, so it is never shown twice).
+export const SW_NOTICE_TEXT =
+ "SW UI는 공통 컴포넌트의 기능과 동작을 유지하면서, 제품별 특성에 따라 시각적 스타일을 다르게 적용할 수 있습니다."
+
+const HIDDEN_TAGS = ["verification", "internal"]
+
+export const withSWNotice: Decorator = (Story, context) => {
+ const hidden = (context.tags ?? []).some(tag => HIDDEN_TAGS.includes(tag))
+ if (hidden || context.parameters.swNotice === false) return
+ return (
+ <>
+ {SW_NOTICE_TEXT}
+
+ >
+ )
+}
diff --git a/src/sw/sw-theme.stories.tsx b/src/sw/sw-theme.stories.tsx
index 9833ebe..e3087c4 100644
--- a/src/sw/sw-theme.stories.tsx
+++ b/src/sw/sw-theme.stories.tsx
@@ -1,6 +1,7 @@
import { useState, type CSSProperties, type ReactNode } from "react"
import { createPortal } from "react-dom"
import type { Meta, StoryObj } from "@storybook/react-vite"
+import { withSWNotice } from "./sw-story-notice"
import {
SWFooter, SWFooterCommandLine, SWHeader, SWTheme, SWViewerButton, SWViewerHeader, SWViewerHint, SWViewerSection,
} from "@hm-design/sw"
@@ -23,6 +24,7 @@ const NESTED_THEME = { "--hm-sw-header-font-family": '"Malgun Gothic", sans-seri
const meta = {
title: "SW/Theme",
parameters: { layout: "fullscreen", controls: { disable: true } },
+ decorators: [withSWNotice],
} satisfies Meta
export default meta
type Story = StoryObj
diff --git a/src/sw/sw-viewer.stories.tsx b/src/sw/sw-viewer.stories.tsx
index 85af3b2..c220369 100644
--- a/src/sw/sw-viewer.stories.tsx
+++ b/src/sw/sw-viewer.stories.tsx
@@ -1,5 +1,6 @@
import { useRef, useState, type ReactNode } from "react"
import type { Meta, StoryObj } from "@storybook/react-vite"
+import { withSWNotice } from "./sw-story-notice"
import {
SWHeader, SWViewerButton, SWViewerHeader, SWViewerHint, SWViewerMenu, SWViewerPanel, SWViewerSection,
type SWViewerHeaderGroup, type SWViewerMenuEntry,
@@ -20,6 +21,7 @@ const KICKER = "HM DESIGN SYSTEM / SW"
const meta = {
title: "SW/Viewer",
parameters: { layout: "fullscreen", controls: { disable: true } },
+ decorators: [withSWNotice],
} satisfies Meta
export default meta
type Story = StoryObj