feat(sw): viewer follow-up — SVG icon outline, shared SW popup frame, story grid, green layer buttons
- A: SWViewerIcon draws the SWHeader black outline as SVG stroke on a copy behind the icon (no CSS filter) - B: SWViewerPanel renders the common SWPopup frame (same DOM / CSS) and adds drag, corner resize, collapse, bounds and modal; SWPopup gains optional titleActions / footer; title art spans any width - C: shared Story canvas grid (--sw-story-canvas) behind the SW Header work area and dropdown room - D: SWViewerButton tone="green" for 전체 켜기 / 전체 끄기: default kept, hover = primary (확인) hover token, pressed = primary face - AI guide, verification scripts (76 checks, element regression) and evidence updated
This commit is contained in:
1 parent
7f42923ccf
commit
68fbcf6e15
45 files changed
+1463
-377
No files matched your search
@@ -29,15 +29,9 @@
|
||||
--hm-sw-viewer-switch-off: #0a221d;
|
||||
--hm-sw-viewer-switch-bevel: inset 0 1px 0 rgba(255, 255, 255, 0.1), inset 0 -1px 1px rgba(0, 0, 0, 0.25);
|
||||
/* Icon stroke: the SWHeader icon assets (print / K guide / layer icons) are a fill + a 1px black OUTSIDE stroke
|
||||
(Figma mask "outside" path) + a 0-offset glow (stdDeviation 1.615, #061A14). Program icons are free SVGs, so the
|
||||
same stroke is a drop-shadow chain that follows the icon's own contour: the four 1px shadows dilate the shape
|
||||
by 1px (each one also shadows the previous ones, so corners close), the last one is the glow. */
|
||||
(Figma mask "outside" path). SWViewerIcon draws it as SVG stroke on a copy behind the icon (see SWViewerIcon). */
|
||||
--hm-sw-viewer-icon-outline-color: var(--hm-sw-header-text-outline-color);
|
||||
--hm-sw-viewer-icon-glow: #061a14;
|
||||
--hm-sw-viewer-icon-outline:
|
||||
drop-shadow(1px 0 0 var(--hm-sw-viewer-icon-outline-color)) drop-shadow(-1px 0 0 var(--hm-sw-viewer-icon-outline-color))
|
||||
drop-shadow(0 1px 0 var(--hm-sw-viewer-icon-outline-color)) drop-shadow(0 -1px 0 var(--hm-sw-viewer-icon-outline-color))
|
||||
drop-shadow(0 0 1.615px var(--hm-sw-viewer-icon-glow));
|
||||
--hm-sw-viewer-icon-outline-width: 1px;
|
||||
|
||||
/* Dropdown (light) and More (beige) */
|
||||
--hm-sw-viewer-menu-background: #fff;
|
||||
@@ -51,31 +45,20 @@
|
||||
--hm-sw-viewer-more-background: #f5f0e5;
|
||||
--hm-sw-viewer-more-border-color: #897445;
|
||||
|
||||
/* Panel */
|
||||
--hm-sw-viewer-panel-background: #e0dace;
|
||||
--hm-sw-viewer-panel-field-background: #f5f0e5;
|
||||
--hm-sw-viewer-panel-border: #897445;
|
||||
--hm-sw-viewer-panel-inner-border: #9a9284;
|
||||
--hm-sw-viewer-panel-title-background: #221d13;
|
||||
--hm-sw-viewer-panel-title-highlight: #a98d53;
|
||||
--hm-sw-viewer-panel-title-color: #ffc600;
|
||||
--hm-sw-viewer-panel-title-button-hover: #604f32;
|
||||
--hm-sw-viewer-panel-text: #1d1810;
|
||||
/* Panel: the frame is SWPopup (title bar, title, close, border, radius, shadow). Content tokens map to the
|
||||
SW property window tokens so viewer content sits on the same surface as every SW popup. */
|
||||
--hm-sw-viewer-panel-background: var(--hm-sw-property-surface);
|
||||
--hm-sw-viewer-panel-field-background: var(--hm-sw-property-content);
|
||||
--hm-sw-viewer-panel-inner-border: var(--hm-sw-property-inner-border);
|
||||
--hm-sw-viewer-panel-text: var(--hm-sw-property-label);
|
||||
--hm-sw-viewer-panel-heading: #3e3523;
|
||||
--hm-sw-viewer-panel-label: #6e6759;
|
||||
--hm-sw-viewer-panel-spacing: 12px;
|
||||
--hm-sw-viewer-panel-title-height: 34px;
|
||||
--hm-sw-viewer-panel-shadow: 0 0 12px rgba(0, 0, 0, 0.2);
|
||||
--hm-sw-viewer-panel-primary: #123328;
|
||||
--hm-sw-viewer-panel-switch-off: #9a9284;
|
||||
--hm-sw-viewer-panel-switch-off: var(--hm-sw-property-inner-border);
|
||||
--hm-sw-viewer-panel-switch-on: #266651;
|
||||
--hm-sw-viewer-panel-badge-background: #f4f1ea;
|
||||
--hm-sw-viewer-panel-badge-background: var(--hm-sw-property-table);
|
||||
--hm-sw-viewer-panel-overlay: rgba(0, 0, 0, 0.1);
|
||||
/* Close: green curve, #E40000 on hover (user-supplied reference value). */
|
||||
--hm-sw-viewer-close-glow: #123328;
|
||||
--hm-sw-viewer-close-edge: #020a07;
|
||||
--hm-sw-viewer-close-hover: #e40000;
|
||||
--hm-sw-viewer-close-icon: #999;
|
||||
--hm-sw-viewer-panel-spacing: 12px;
|
||||
}
|
||||
|
||||
.hm-sw-viewer-sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
|
||||
@@ -194,6 +177,13 @@ button.hm-sw-viewer-header__brand { text-align: left; }
|
||||
.hm-sw-viewer-header__icon, .hm-sw-viewer-header__chevron { display: inline-flex; width: var(--hm-sw-viewer-icon-size); height: var(--hm-sw-viewer-icon-size); }
|
||||
.hm-sw-viewer-header__icon > svg, .hm-sw-viewer-header__chevron > svg { width: 100%; height: 100%; }
|
||||
|
||||
/* SWViewerIcon: the outline copy (black SVG stroke) right behind the untouched icon. */
|
||||
.hm-sw-viewer-icon { position: relative; display: inline-flex; width: 100%; height: 100%; }
|
||||
.hm-sw-viewer-icon__outline, .hm-sw-viewer-icon__glyph { display: inline-flex; width: 100%; height: 100%; }
|
||||
.hm-sw-viewer-icon__outline { position: absolute; inset: 0; overflow: visible; }
|
||||
.hm-sw-viewer-icon__glyph { position: relative; }
|
||||
.hm-sw-viewer-icon svg { width: 100%; height: 100%; overflow: visible; }
|
||||
|
||||
/* Tabs (Model / Layout): 40px triggers with an 11px count; the active one turns #FFC600. */
|
||||
.hm-sw-viewer-header__tabs { display: flex; align-items: center; gap: var(--hm-sw-viewer-gap); }
|
||||
.hm-sw-viewer-header .hm-sw-viewer-header__tab {
|
||||
@@ -233,7 +223,7 @@ button.hm-sw-viewer-header__brand { text-align: left; }
|
||||
/* Text and icon stroke (2026-10-19 request): the SWHeader black outline on the viewer header, same tokens and
|
||||
method — an outside outline of the glyph / icon shape, never a box border.
|
||||
Text: 14px = --hm-sw-header-text-outline (1.3px black), 11px captions / counts / status = --hm-sw-header-label-outline
|
||||
(1px black), as SWHeader's 12px labels. Icons: --hm-sw-viewer-icon-outline (see the token). Fill color, size,
|
||||
(1px black), as SWHeader's 12px labels. Icons: SWViewerIcon (SVG stroke, see the token). Fill color, size,
|
||||
weight and spacing are unchanged; hover / active / open change only the fill, disabled fades the whole button
|
||||
(opacity) like SWHeader. Dropdown faces (light / beige) and panels get no outline, like SWHeader's More menu. */
|
||||
.hm-sw-viewer-header :is(
|
||||
@@ -244,9 +234,6 @@ button.hm-sw-viewer-header__brand { text-align: left; }
|
||||
.hm-sw-viewer-header :is(.hm-sw-viewer-header__count, .hm-sw-viewer-header__switch-label, .hm-sw-viewer-status__label) {
|
||||
text-shadow: var(--hm-sw-header-label-outline);
|
||||
}
|
||||
.hm-sw-viewer-header :is(.hm-sw-viewer-header__icon, .hm-sw-viewer-header__chevron, .hm-sw-viewer-status > svg) {
|
||||
filter: var(--hm-sw-viewer-icon-outline);
|
||||
}
|
||||
/* Clipped (ellipsis) file name: the outline may paint 2px outside the box instead of being cut. */
|
||||
.hm-sw-viewer-header .hm-sw-viewer-header__document-name {
|
||||
overflow: clip;
|
||||
@@ -264,7 +251,7 @@ button.hm-sw-viewer-header__brand { text-align: left; }
|
||||
font-weight: 400;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.hm-sw-viewer-status > svg { width: var(--hm-sw-viewer-icon-size); height: var(--hm-sw-viewer-icon-size); }
|
||||
.hm-sw-viewer-status__icon { display: inline-flex; width: var(--hm-sw-viewer-icon-size); height: var(--hm-sw-viewer-icon-size); }
|
||||
.hm-sw-viewer-status[data-state="error"] { color: var(--hm-sw-viewer-accent); }
|
||||
.hm-sw-viewer-status__spin { animation: hm-sw-viewer-spin 1s linear infinite; }
|
||||
@keyframes hm-sw-viewer-spin { to { transform: rotate(360deg); } }
|
||||
@@ -356,105 +343,52 @@ button.hm-sw-viewer-header__brand { text-align: left; }
|
||||
.hm-sw-viewer-menu__separator { flex: 0 0 1px; height: 1px; margin: 4px -4px; background: var(--hm-sw-viewer-menu-border-color); }
|
||||
.hm-sw-viewer-menu[data-surface="beige"] .hm-sw-viewer-menu__separator { background: var(--hm-sw-viewer-panel-inner-border); }
|
||||
|
||||
/* ---------- Panel ---------- */
|
||||
.hm-sw-viewer-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-width: 90vw;
|
||||
max-height: 90vh;
|
||||
overflow: hidden;
|
||||
border: 2px solid var(--hm-sw-viewer-panel-border);
|
||||
border-radius: var(--hm-sw-viewer-radius);
|
||||
background: var(--hm-sw-viewer-panel-background);
|
||||
box-shadow: var(--hm-sw-viewer-panel-shadow), inset 0 0 0 1px var(--hm-sw-viewer-panel-field-background);
|
||||
color: var(--hm-sw-viewer-panel-text);
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
line-height: 20px;
|
||||
outline: none;
|
||||
}
|
||||
/* ---------- Panel (SWPopup frame + viewer window behavior) ---------- */
|
||||
.hm-sw-viewer-panel-backdrop { inset: 0; background: var(--hm-sw-viewer-panel-overlay); }
|
||||
.hm-sw-viewer-panel__titlebar {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
min-height: var(--hm-sw-viewer-panel-title-height);
|
||||
padding-left: var(--hm-sw-viewer-panel-spacing);
|
||||
border-bottom: 1px solid var(--hm-sw-viewer-panel-border);
|
||||
background: var(--hm-sw-viewer-panel-title-background);
|
||||
box-shadow: inset 0 1px 0 var(--hm-sw-viewer-panel-title-highlight);
|
||||
user-select: none;
|
||||
.hm-sw-viewer-panel[data-collapsed] .hm-sw-property__body { display: none; }
|
||||
.hm-sw-viewer-panel[data-draggable] .hm-sw-property__titlebar { cursor: grab; touch-action: none; user-select: none; }
|
||||
.hm-sw-viewer-panel[data-dragging] .hm-sw-property__titlebar { cursor: grabbing; }
|
||||
/* Any width: the Figma title art keeps its 308px curve at the close button (right-aligned; at 314px this is exactly
|
||||
the SWPopup position), and a stretched copy of the same art fills the flat part on the left of wider panels.
|
||||
The right one hides its own left 20px (left edge + corner) and the left copy ends exactly there: no seam, no
|
||||
doubled shadow. */
|
||||
.hm-sw-viewer-panel .hm-sw-property__title-art { left: auto; right: 4px; clip-path: inset(-4px -4px -4px 20px); }
|
||||
.hm-sw-viewer-panel .hm-sw-property__titlebar::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
left: 0;
|
||||
top: -2px;
|
||||
width: max(0px, calc(100% - 292px));
|
||||
height: 38px;
|
||||
background: url("../assets/property-title-single.svg") left top / calc(100% + 288px) 38px no-repeat;
|
||||
transform: scaleY(-1);
|
||||
pointer-events: none;
|
||||
}
|
||||
.hm-sw-viewer-panel__titlebar[data-draggable] { cursor: grab; touch-action: none; }
|
||||
.hm-sw-viewer-panel__titlebar[data-dragging] { cursor: grabbing; }
|
||||
.hm-sw-viewer-panel__title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
margin: 0;
|
||||
color: var(--hm-sw-viewer-panel-title-color);
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.hm-sw-viewer-panel__title-icon { display: inline-flex; flex: 0 0 16px; width: 16px; height: 16px; }
|
||||
.hm-sw-viewer-panel__title-icon > svg { width: 100%; height: 100%; }
|
||||
.hm-sw-viewer-panel__title-text { min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
|
||||
.hm-sw-viewer-panel__actions { display: flex; flex-shrink: 0; align-items: center; }
|
||||
.hm-sw-viewer-panel__actions { position: relative; z-index: 2; display: flex; flex-shrink: 0; align-items: center; height: 30px; }
|
||||
/* Title-bar buttons: SW text-only look (no face), #FFC600 on hover like the SW title-bar controls. */
|
||||
.hm-sw-viewer-panel :is(.hm-sw-viewer-panel__icon-button, .hm-sw-viewer-button[data-variant="ghost"]) {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: var(--hm-sw-viewer-panel-title-height);
|
||||
height: var(--hm-sw-viewer-panel-title-height);
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
color: var(--hm-sw-viewer-muted);
|
||||
}
|
||||
.hm-sw-viewer-panel :is(.hm-sw-viewer-panel__icon-button, .hm-sw-viewer-button[data-variant="ghost"]):hover:not(:disabled) { background: var(--hm-sw-viewer-panel-title-button-hover); color: var(--hm-sw-viewer-panel-title-color); }
|
||||
.hm-sw-viewer-panel :is(.hm-sw-viewer-panel__icon-button, .hm-sw-viewer-button[data-variant="ghost"], .hm-sw-viewer-panel__close):focus-visible { outline: 2px solid var(--hm-sw-viewer-panel-title-color); outline-offset: -3px; }
|
||||
.hm-sw-viewer-panel .hm-sw-viewer-panel__close {
|
||||
position: relative;
|
||||
isolation: isolate;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
width: calc(var(--hm-sw-viewer-panel-title-height) * 1.53);
|
||||
height: var(--hm-sw-viewer-panel-title-height);
|
||||
padding: 0 0 0 calc(var(--hm-sw-viewer-panel-title-height) * 0.7);
|
||||
overflow: hidden;
|
||||
color: var(--hm-sw-viewer-close-icon);
|
||||
}
|
||||
.hm-sw-viewer-panel__close-shape { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }
|
||||
.hm-sw-viewer-panel__close-curve { stroke: var(--hm-sw-viewer-panel-border); stroke-width: 1; }
|
||||
.hm-sw-viewer-panel__close-icon { position: relative; width: 16px; height: 16px; pointer-events: none; }
|
||||
.hm-sw-viewer-panel .hm-sw-viewer-panel__close:focus-visible { --hm-sw-viewer-close-glow: #266651; color: #fff; }
|
||||
.hm-sw-viewer-panel .hm-sw-viewer-panel__close:is(:hover, :active) { --hm-sw-viewer-close-glow: var(--hm-sw-viewer-close-hover); --hm-sw-viewer-close-edge: var(--hm-sw-viewer-close-hover); color: #fff; }
|
||||
.hm-sw-viewer-panel__body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: auto;
|
||||
overscroll-behavior: contain;
|
||||
padding: var(--hm-sw-viewer-panel-spacing);
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--hm-sw-viewer-panel-inner-border) var(--hm-sw-viewer-panel-background);
|
||||
}
|
||||
.hm-sw-viewer-panel :is(.hm-sw-viewer-panel__icon-button, .hm-sw-viewer-button[data-variant="ghost"]):hover:not(:disabled) { background: transparent; color: var(--hm-sw-header-selected-color); }
|
||||
.hm-sw-viewer-panel :is(.hm-sw-viewer-panel__icon-button, .hm-sw-viewer-button[data-variant="ghost"]):focus-visible { outline: 2px solid var(--hm-sw-header-selected-color); outline-offset: -2px; }
|
||||
.hm-sw-viewer-panel :is(.hm-sw-viewer-panel__icon-button, .hm-sw-viewer-button[data-variant="ghost"]) svg { width: 16px; height: 16px; }
|
||||
.hm-sw-viewer-panel__footer {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
padding: 8px var(--hm-sw-viewer-panel-spacing);
|
||||
padding: 7px;
|
||||
border-top: 1px solid var(--hm-sw-viewer-panel-inner-border);
|
||||
background: var(--hm-sw-viewer-panel-background);
|
||||
}
|
||||
|
||||
/* Panel content */
|
||||
@@ -525,6 +459,10 @@ button.hm-sw-viewer-header__brand { text-align: left; }
|
||||
}
|
||||
.hm-sw-theme .hm-sw-viewer-button[data-variant="outline"]:hover:not(:disabled) { background: var(--hm-sw-viewer-panel-field-background); }
|
||||
.hm-sw-theme .hm-sw-viewer-button[data-variant="primary"] { background: var(--hm-sw-viewer-panel-primary); color: #fff; }
|
||||
/* tone="green" (e.g. 전체 켜기 / 전체 끄기): default unchanged. Hover = the primary (확인) button hover, the same token;
|
||||
pressed = the primary button face. White text as on the primary button. Back to default / hover after the press. */
|
||||
.hm-sw-theme .hm-sw-viewer-button[data-tone="green"]:hover:not(:disabled) { border-color: var(--hm-sw-viewer-hover); background: var(--hm-sw-viewer-hover); color: #fff; }
|
||||
.hm-sw-theme .hm-sw-viewer-button[data-tone="green"]:active:not(:disabled) { border-color: var(--hm-sw-viewer-panel-primary); background: var(--hm-sw-viewer-panel-primary); color: #fff; }
|
||||
.hm-sw-theme .hm-sw-viewer-button[data-variant="primary"]:hover:not(:disabled) { background: var(--hm-sw-viewer-hover); border-color: var(--hm-sw-viewer-hover); }
|
||||
.hm-sw-theme .hm-sw-viewer-button:focus-visible { outline: 2px solid var(--hm-sw-viewer-panel-switch-on); outline-offset: 1px; }
|
||||
.hm-sw-theme .hm-sw-viewer-button:disabled { opacity: 0.5; cursor: not-allowed; }
|
||||
|
||||
Reference in new issue
Block a user