06メディア問い合わせ
미디어 담당자 이름·직함 아래 이메일 버튼을 누르면 실제로 클립보드에 복사되고, 버튼에 체크 아이콘이 잠깐 그려졌다가 원래 라벨로 돌아옵니다.
使いどころ
clipboardstroke-dashoffsetclick

<main class="stage">
<section class="mc is-demo">
<span class="mc__orb mc__orb--1" aria-hidden="true"></span>
<span class="mc__orb mc__orb--2" aria-hidden="true"></span>
<span class="mc__avatar" aria-hidden="true"></span>
<p class="mc__name">キム・プレス</p>
<p class="mc__role">PR・メディア担当</p>
<button class="mc__copy" type="button" data-email="[email protected]">
<span class="mc__copy-label">[email protected]</span>
<svg class="mc__copy-ic" viewBox="0 0 20 20" aria-hidden="true"><polyline points="4,10 8,14 16,5"/></svg>
</button>
<p class="mc__hint" role="status" aria-live="polite"></p>
</section>
</main>@use "../../../../site/static/demo-tokens" as *;
// ── 변수 3개 ──────────────────────────────────────────
$duration: $dur-base; // 복사 버튼 전환
$easing: $ease-pop;
$color: $subject-blue; // 버튼·체크 색
@include stage($stage-yellow);
.mc {
position: relative;
box-sizing: border-box; // width 에 패딩을 포함 — 아니면 320px 에서 패딩만큼 넘친다(실측 2026-09-10)
width: min(352px, 92vw); // 320 + 좌우패딩(sp-4×2=32) — border-box 전환 전 데스크톱 박스 크기를 그대로 유지(92vw 쪽은 그대로라 모바일엔 영향 없음)
text-align: center;
overflow: hidden;
isolation: isolate;
border-radius: $r-card;
padding: $sp-5 $sp-4 $sp-4;
}
.mc__orb { position: absolute; border-radius: 50%; filter: blur(16px); z-index: -1; }
.mc__orb--1 { width: 150px; height: 150px; left: -40px; top: -30px; background: radial-gradient(circle, $subject-blue, transparent 68%); opacity: .5; animation: mc-drift 8s ease-in-out infinite; }
.mc__orb--2 { width: 130px; height: 130px; right: -30px; bottom: -40px; background: radial-gradient(circle, $subject-ink, transparent 68%); opacity: .32; animation: mc-drift 7s ease-in-out infinite reverse; }
@keyframes mc-drift {
0%, 100% { transform: translate(0, 0) scale(1); }
50% { transform: translate($sp-6, -$sp-5) scale(1.25); }
}
.mc__avatar {
display: block;
width: 48px; height: 48px;
margin: 0 auto $sp-2;
border-radius: 50%;
background: $subject-ink;
}
.mc__name { margin: 0 0 $sp-1; font-weight: 800; font-size: clamp(14px, 4vw, 16px); line-height: 1.2; font-family: inherit; color: $subject-ink; }
.mc__role { margin: 0 0 $sp-4; font-weight: 500; font-size: clamp(10px, 2.8vw, 11px); line-height: 1.4; font-family: inherit; color: rgba($subject-ink, .72); }
.mc__copy {
display: inline-flex; align-items: center; gap: $sp-1;
padding: $sp-2 $sp-4;
border-radius: $r-control;
border: 1px solid rgba(23, 20, 26, .16);
background: none;
color: $subject-ink;
font-weight: 700; font-size: 11px; line-height: 1; font-family: inherit;
cursor: pointer;
max-width: 100%;
transition: border-color $duration $easing, background $duration $easing;
}
.mc__copy-ic {
width: 12px; height: 12px; flex: 0 0 auto;
fill: none;
stroke: $color;
stroke-width: 2.5;
stroke-linecap: round;
stroke-linejoin: round;
stroke-dasharray: 20;
stroke-dashoffset: 20;
opacity: 0;
transition: stroke-dashoffset $duration $easing, opacity $duration $easing;
}
.mc__copy.is-copied { border-color: $color; background: rgba(47, 109, 246, .08); }
.mc__copy.is-copied .mc__copy-ic { stroke-dashoffset: 0; opacity: 1; }
.mc__hint { min-height: 14px; margin: $sp-2 0 0; font-weight: 600; font-size: 10px; line-height: 1.4; font-family: inherit; color: $color; }
// ── 데모 루프: 복사 버튼을 클릭하기 전까지 체크가 옅게 나타났다 사라진다(항상 재생) ──────
.mc.is-demo .mc__copy-ic { animation: mc-hint 2s $easing infinite; }
@keyframes mc-hint {
0%, 60% { stroke-dashoffset: 20; opacity: 0; }
78%, 90% { stroke-dashoffset: 0; opacity: .5; }
100% { stroke-dashoffset: 20; opacity: 0; }
}
// ── 반응형(BD9) ──────────────────────────────────────────
@media (max-width: 340px), (max-height: 220px) {
.mc { padding: $sp-3 $sp-3; }
.mc__avatar { width: 36px; height: 36px; margin-bottom: $sp-1; }
.mc__role { margin-bottom: $sp-2; }
.mc__copy { padding: $sp-1 $sp-3; font-size: 10px; }
}
// ── 접근성 ──────────────────────────────────────────
@media (prefers-reduced-motion: reduce) {
.mc__orb { animation: none !important; }
.mc__copy, .mc__copy-ic { transition: none; }
.mc.is-demo .mc__copy-ic { animation: none !important; }
}html, body {
margin: 0;
overflow: hidden;
}
.stage {
box-sizing: border-box;
min-height: 100vh;
display: grid;
place-items: center;
margin: 0;
background: #ffd23f;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.mc {
position: relative;
box-sizing: border-box;
width: min(352px, 92vw);
text-align: center;
overflow: hidden;
isolation: isolate;
border-radius: 16px;
padding: 20px 16px 16px;
}
.mc__orb {
position: absolute;
border-radius: 50%;
filter: blur(16px);
z-index: -1;
}
.mc__orb--1 {
width: 150px;
height: 150px;
left: -40px;
top: -30px;
background: radial-gradient(circle, #2f6df6, transparent 68%);
opacity: 0.5;
animation: mc-drift 8s ease-in-out infinite;
}
.mc__orb--2 {
width: 130px;
height: 130px;
right: -30px;
bottom: -40px;
background: radial-gradient(circle, #17141a, transparent 68%);
opacity: 0.32;
animation: mc-drift 7s ease-in-out infinite reverse;
}
@keyframes mc-drift {
0%, 100% {
transform: translate(0, 0) scale(1);
}
50% {
transform: translate(24px, -20px) scale(1.25);
}
}
.mc__avatar {
display: block;
width: 48px;
height: 48px;
margin: 0 auto 8px;
border-radius: 50%;
background: #17141a;
}
.mc__name {
margin: 0 0 4px;
font-weight: 800;
font-size: clamp(14px, 4vw, 16px);
line-height: 1.2;
font-family: inherit;
color: #17141a;
}
.mc__role {
margin: 0 0 16px;
font-weight: 500;
font-size: clamp(10px, 2.8vw, 11px);
line-height: 1.4;
font-family: inherit;
color: rgba(23, 20, 26, 0.72);
}
.mc__copy {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 8px 16px;
border-radius: 12px;
border: 1px solid rgba(23, 20, 26, 0.16);
background: none;
color: #17141a;
font-weight: 700;
font-size: 11px;
line-height: 1;
font-family: inherit;
cursor: pointer;
max-width: 100%;
transition: border-color 300ms cubic-bezier(0.2, 1.4, 0.4, 1), background 300ms cubic-bezier(0.2, 1.4, 0.4, 1);
}
.mc__copy-ic {
width: 12px;
height: 12px;
flex: 0 0 auto;
fill: none;
stroke: #2f6df6;
stroke-width: 2.5;
stroke-linecap: round;
stroke-linejoin: round;
stroke-dasharray: 20;
stroke-dashoffset: 20;
opacity: 0;
transition: stroke-dashoffset 300ms cubic-bezier(0.2, 1.4, 0.4, 1), opacity 300ms cubic-bezier(0.2, 1.4, 0.4, 1);
}
.mc__copy.is-copied {
border-color: #2f6df6;
background: rgba(47, 109, 246, 0.08);
}
.mc__copy.is-copied .mc__copy-ic {
stroke-dashoffset: 0;
opacity: 1;
}
.mc__hint {
min-height: 14px;
margin: 8px 0 0;
font-weight: 600;
font-size: 10px;
line-height: 1.4;
font-family: inherit;
color: #2f6df6;
}
.mc.is-demo .mc__copy-ic {
animation: mc-hint 2s cubic-bezier(0.2, 1.4, 0.4, 1) infinite;
}
@keyframes mc-hint {
0%, 60% {
stroke-dashoffset: 20;
opacity: 0;
}
78%, 90% {
stroke-dashoffset: 0;
opacity: 0.5;
}
100% {
stroke-dashoffset: 20;
opacity: 0;
}
}
@media (max-width: 340px), (max-height: 220px) {
.mc {
padding: 12px 12px;
}
.mc__avatar {
width: 36px;
height: 36px;
margin-bottom: 4px;
}
.mc__role {
margin-bottom: 8px;
}
.mc__copy {
padding: 4px 12px;
font-size: 10px;
}
}
@media (prefers-reduced-motion: reduce) {
.mc__orb {
animation: none !important;
}
.mc__copy, .mc__copy-ic {
transition: none;
}
.mc.is-demo .mc__copy-ic {
animation: none !important;
}
}// 실제 동작: 버튼을 누르면 이메일이 실제로 클립보드에 복사되고, 잠깐 체크 아이콘으로 바뀐 뒤 되돌아간다.
var root = document.querySelector('.mc');
var btn = document.querySelector('.mc__copy');
var hint = document.querySelector('.mc__hint');
var revertTimer = null;
btn.addEventListener('click', function () {
root.classList.remove('is-demo');
var email = btn.getAttribute('data-email');
var showCopied = function () {
btn.classList.add('is-copied');
hint.textContent = 'メールアドレスをコピーしました。';
clearTimeout(revertTimer);
revertTimer = setTimeout(function () { btn.classList.remove('is-copied'); }, 1600);
};
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(email).then(showCopied, showCopied);
} else {
var t = document.createElement('textarea');
t.value = email; document.body.appendChild(t); t.select();
try { document.execCommand('copy'); } catch (e) {}
document.body.removeChild(t);
showCopied();
}
});import { useRef, useState } from "react";
import type { CSSProperties } from "react";
import styles from "./PressContact.module.scss";
export type PressContactProps = { duration?: number; easing?: string; color?: string };
type Vars = CSSProperties & { "--duration"?: string; "--easing"?: string; "--color"?: string };
const EMAIL = "[email protected]";
export default function PressContact({ duration = 300, easing = "cubic-bezier(.2,1.4,.4,1)", color = "#2f6df6" }: PressContactProps) {
const [copied, setCopied] = useState(false);
const [hint, setHint] = useState("");
const timer = useRef<ReturnType<typeof setTimeout> | null>(null);
const vars: Vars = { "--duration": `${duration}ms`, "--easing": easing, "--color": color };
async function onCopy() {
try {
await navigator.clipboard.writeText(EMAIL);
} catch {
// 클립보드 권한이 없어도 상태는 그대로 갱신한다(폴백은 vanilla 판 참고)
}
setCopied(true);
setHint("이메일 주소를 복사했습니다.");
if (timer.current) clearTimeout(timer.current);
timer.current = setTimeout(() => setCopied(false), 1600);
}
return (
<section className={styles.mc} style={vars}>
<span className={`${styles.orb} ${styles.orb1}`} aria-hidden="true" />
<span className={`${styles.orb} ${styles.orb2}`} aria-hidden="true" />
<span className={styles.avatar} aria-hidden="true" />
<p className={styles.name}>김프레스</p>
<p className={styles.role}>PR · 미디어 담당</p>
<button type="button" className={copied ? `${styles.copy} ${styles.copied}` : styles.copy} onClick={onCopy}>
<span className={styles.copyLabel}>{EMAIL}</span>
<svg className={styles.copyIc} viewBox="0 0 20 20" aria-hidden="true"><polyline points="4,10 8,14 16,5" /></svg>
</button>
<p className={styles.hint} role="status" aria-live="polite">{hint}</p>
</section>
);
}06 メディア問い合わせ — 計測 累積面積 3.774% · 動いたフレーム 6/23GODRICH オリジナル · SCSS と React は zip に同梱 · プレスキット テンプレート付き プレスルームセクション9選
このデモが属する記事
プレスキット テンプレート付き プレスルームセクション9選プレスルーム・メディアページのセクションテンプレートは、プレスリリース一覧・メディアキット・問い合わせ先といった要素をひとまとめにしたプレスキット テンプレート集です。LPにそのまま貼れるヒーローから購読フッターまで、9つのうち1つは実際に この記事のデモをすべて見る


