01Press hero
무대 뒤 흐릿한 원 세 개가 계속 떠다니는 동안 워드마크 플레이스홀더·헤드라인·부제·CTA 두 개가 한 화면에 자리 잡고, 오른쪽 '보도자료 구독' 버튼은 클릭마다 aria-pressed가 실제로 바뀝니다.
Best for
transformopacityaria-pressed

<main class="stage">
<section class="ph">
<span class="ph__orb ph__orb--1" aria-hidden="true"></span>
<span class="ph__orb ph__orb--2" aria-hidden="true"></span>
<span class="ph__orb ph__orb--3" aria-hidden="true"></span>
<span class="ph__mark" aria-hidden="true"></span>
<p class="ph__eyebrow">PRESS ROOM</p>
<h1 class="ph__title">For press & partners<br>Latest news</h1>
<p class="ph__sub">Press releases, media kit, and contacts in one place.</p>
<div class="ph__row">
<a class="ph__cta" href="#">View releases</a>
<button class="ph__sub-btn" type="button" aria-pressed="false">
<span class="ph__sub-ic" aria-hidden="true"></span>
Subscribe
</button>
</div>
</section>
</main>@use "../../../../site/static/demo-tokens" as *;
// ── 변수 3개 ──────────────────────────────────────────
$duration: $dur-quick; // 구독 버튼 전환
$easing: $ease-pop;
$color: $subject-blue; // CTA·아이콘 강조색
@include stage($stage-ink);
.ph {
position: relative;
box-sizing: border-box; // width 에 패딩을 포함 — 아니면 320px 에서 패딩만큼 넘친다(실측 2026-09-10)
width: min(400px, 92vw);
overflow: hidden;
border-radius: $r-card;
padding: $sp-3 $sp-5 $sp-2;
text-align: center;
isolation: isolate;
}
// ── 배경 원 세 개: 계속 떠다니는 장식(호버 아님, 무조건 재생) ──────────────
.ph__orb {
position: absolute;
border-radius: 50%;
filter: blur(18px);
opacity: .55;
z-index: -1;
}
.ph__orb--1 { width: 170px; height: 170px; left: -45px; top: -35px; background: radial-gradient(circle, $subject-blue, transparent 62%); opacity: 1; filter: blur(3px); animation: ph-float-a 2s ease-in-out infinite; }
.ph__orb--2 { width: 130px; height: 130px; right: -25px; top: 6px; background: radial-gradient(circle, $subject-cream, transparent 62%); opacity: .7; filter: blur(3px); animation: ph-float-b 2s ease-in-out infinite; }
.ph__orb--3 { width: 150px; height: 150px; left: 28%; bottom: -70px; background: radial-gradient(circle, $subject-blue, transparent 62%); opacity: .95; filter: blur(3px); animation: ph-float-a 2s ease-in-out infinite reverse; }
@keyframes ph-float-a {
0%, 100% { transform: translate(0, 0) scale(1); }
50% { transform: translate($sp-10, -$sp-8) scale(1.5); }
}
@keyframes ph-float-b {
0%, 100% { transform: translate(0, 0) scale(1); }
50% { transform: translate(-$sp-8, $sp-8) scale(.6); }
}
// ── 플레이스홀더 워드마크: 실제 로고가 아니라 도형 하나 ──────────────
.ph__mark {
display: block;
width: $sp-5; height: $sp-5;
margin: 0 auto $sp-1;
background: $color;
border-radius: $r-xs;
transform: rotate(45deg);
}
.ph__eyebrow {
margin: 0 0 $sp-1;
font-weight: 700; font-size: 10px; line-height: 1; font-family: inherit;
letter-spacing: .14em;
color: rgba(255, 247, 230, .6);
}
.ph__title {
margin: 0 0 $sp-1;
font-weight: 800; font-size: clamp(16px, 4.6vw, 19px); line-height: 1.14; font-family: inherit;
color: $subject-cream;
}
.ph__sub {
margin: 0 0 $sp-2;
font-weight: 500; font-size: clamp(10px, 2.8vw, 11px); line-height: 1.4; font-family: inherit;
color: rgba(255, 247, 230, .68);
}
.ph__row { display: flex; justify-content: center; align-items: center; gap: $sp-2; flex-wrap: wrap; }
.ph__cta {
padding: $sp-2 $sp-5;
border-radius: $r-control;
background: $subject-blue-deep;
color: $subject-cream;
font-weight: 700; font-size: 12px; line-height: 1.4; font-family: inherit;
text-decoration: none;
}
.ph__sub-btn {
display: inline-flex; align-items: center; gap: $sp-1;
padding: $sp-2 $sp-3;
border-radius: $r-control;
border: 1px solid rgba(255, 247, 230, .3);
background: transparent;
color: rgba(255, 247, 230, .82);
font-weight: 600; font-size: 12px; line-height: 1.4; font-family: inherit;
cursor: pointer;
transition: background $duration $easing, border-color $duration $easing, transform $duration $easing;
}
.ph__sub-ic {
width: $sp-3; height: $sp-3;
border-radius: 0;
background: currentColor;
clip-path: polygon(50% 0%, 63% 38%, 100% 38%, 70% 60%, 82% 100%, 50% 76%, 18% 100%, 30% 60%, 0% 38%, 37% 38%);
opacity: .5;
transition: opacity $duration $easing, transform $duration $easing;
}
.ph__sub-btn[aria-pressed="true"] {
background: rgba(255, 247, 230, .12);
border-color: $subject-cream;
}
.ph__sub-btn[aria-pressed="true"] .ph__sub-ic {
opacity: 1;
color: $color;
transform: scale(1.15);
}
// ── 반응형: 320×200 처럼 세로가 짧은 화면에선 장식을 줄인다(BD9) ──────────────
@media (max-width: 340px), (max-height: 220px) {
.ph { padding: $sp-2 $sp-4; }
.ph__mark { display: none; }
.ph__eyebrow { display: none; }
.ph__title { font-size: 14px; line-height: 1.15; margin-bottom: $sp-1; }
.ph__sub { display: none; }
.ph__row { gap: $sp-1; }
.ph__cta, .ph__sub-btn { padding: 4px $sp-3; font-size: 10px; }
}
// ── 접근성 ──────────────────────────────────────────
@media (prefers-reduced-motion: reduce) {
.ph__orb { animation: none !important; }
.ph__sub-btn, .ph__sub-ic { transition: none; }
}html, body {
margin: 0;
overflow: hidden;
}
.stage {
box-sizing: border-box;
min-height: 100vh;
display: grid;
place-items: center;
margin: 0;
background: #17141a;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.ph {
position: relative;
box-sizing: border-box;
width: min(400px, 92vw);
overflow: hidden;
border-radius: 16px;
padding: 12px 20px 8px;
text-align: center;
isolation: isolate;
}
.ph__orb {
position: absolute;
border-radius: 50%;
filter: blur(18px);
opacity: 0.55;
z-index: -1;
}
.ph__orb--1 {
width: 170px;
height: 170px;
left: -45px;
top: -35px;
background: radial-gradient(circle, #2f6df6, transparent 62%);
opacity: 1;
filter: blur(3px);
animation: ph-float-a 2s ease-in-out infinite;
}
.ph__orb--2 {
width: 130px;
height: 130px;
right: -25px;
top: 6px;
background: radial-gradient(circle, #fff7e6, transparent 62%);
opacity: 0.7;
filter: blur(3px);
animation: ph-float-b 2s ease-in-out infinite;
}
.ph__orb--3 {
width: 150px;
height: 150px;
left: 28%;
bottom: -70px;
background: radial-gradient(circle, #2f6df6, transparent 62%);
opacity: 0.95;
filter: blur(3px);
animation: ph-float-a 2s ease-in-out infinite reverse;
}
@keyframes ph-float-a {
0%, 100% {
transform: translate(0, 0) scale(1);
}
50% {
transform: translate(40px, -32px) scale(1.5);
}
}
@keyframes ph-float-b {
0%, 100% {
transform: translate(0, 0) scale(1);
}
50% {
transform: translate(-32px, 32px) scale(0.6);
}
}
.ph__mark {
display: block;
width: 20px;
height: 20px;
margin: 0 auto 4px;
background: #2f6df6;
border-radius: 4px;
transform: rotate(45deg);
}
.ph__eyebrow {
margin: 0 0 4px;
font-weight: 700;
font-size: 10px;
line-height: 1;
font-family: inherit;
letter-spacing: 0.14em;
color: rgba(255, 247, 230, 0.6);
}
.ph__title {
margin: 0 0 4px;
font-weight: 800;
font-size: clamp(16px, 4.6vw, 19px);
line-height: 1.14;
font-family: inherit;
color: #fff7e6;
}
.ph__sub {
margin: 0 0 8px;
font-weight: 500;
font-size: clamp(10px, 2.8vw, 11px);
line-height: 1.4;
font-family: inherit;
color: rgba(255, 247, 230, 0.68);
}
.ph__row {
display: flex;
justify-content: center;
align-items: center;
gap: 8px;
flex-wrap: wrap;
}
.ph__cta {
padding: 8px 20px;
border-radius: 12px;
background: #0a4fe8;
color: #fff7e6;
font-weight: 700;
font-size: 12px;
line-height: 1.4;
font-family: inherit;
text-decoration: none;
}
.ph__sub-btn {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 8px 12px;
border-radius: 12px;
border: 1px solid rgba(255, 247, 230, 0.3);
background: transparent;
color: rgba(255, 247, 230, 0.82);
font-weight: 600;
font-size: 12px;
line-height: 1.4;
font-family: inherit;
cursor: pointer;
transition: background 220ms cubic-bezier(0.2, 1.4, 0.4, 1), border-color 220ms cubic-bezier(0.2, 1.4, 0.4, 1), transform 220ms cubic-bezier(0.2, 1.4, 0.4, 1);
}
.ph__sub-ic {
width: 12px;
height: 12px;
border-radius: 0;
background: currentColor;
clip-path: polygon(50% 0%, 63% 38%, 100% 38%, 70% 60%, 82% 100%, 50% 76%, 18% 100%, 30% 60%, 0% 38%, 37% 38%);
opacity: 0.5;
transition: opacity 220ms cubic-bezier(0.2, 1.4, 0.4, 1), transform 220ms cubic-bezier(0.2, 1.4, 0.4, 1);
}
.ph__sub-btn[aria-pressed=true] {
background: rgba(255, 247, 230, 0.12);
border-color: #fff7e6;
}
.ph__sub-btn[aria-pressed=true] .ph__sub-ic {
opacity: 1;
color: #2f6df6;
transform: scale(1.15);
}
@media (max-width: 340px), (max-height: 220px) {
.ph {
padding: 8px 16px;
}
.ph__mark {
display: none;
}
.ph__eyebrow {
display: none;
}
.ph__title {
font-size: 14px;
line-height: 1.15;
margin-bottom: 4px;
}
.ph__sub {
display: none;
}
.ph__row {
gap: 4px;
}
.ph__cta, .ph__sub-btn {
padding: 4px 12px;
font-size: 10px;
}
}
@media (prefers-reduced-motion: reduce) {
.ph__orb {
animation: none !important;
}
.ph__sub-btn, .ph__sub-ic {
transition: none;
}
}// 실제 동작: 클릭마다 Subscribe 상태가 실제로 바뀐다(아이콘 채움 + aria-pressed).
var btn = document.querySelector('.ph__sub-btn');
btn.addEventListener('click', function () {
var on = btn.getAttribute('aria-pressed') === 'true';
btn.setAttribute('aria-pressed', String(!on));
});import { useState } from "react";
import type { CSSProperties } from "react";
import styles from "./PressHero.module.scss";
export type PressHeroProps = {
duration?: number;
easing?: string;
color?: string;
};
type Vars = CSSProperties & { "--duration"?: string; "--easing"?: string; "--color"?: string };
export default function PressHero({ duration = 220, easing = "cubic-bezier(.2,1.4,.4,1)", color = "#2f6df6" }: PressHeroProps) {
const [subscribed, setSubscribed] = useState(false);
const vars: Vars = { "--duration": `${duration}ms`, "--easing": easing, "--color": color };
return (
<section className={styles.ph} style={vars}>
<span className={`${styles.orb} ${styles.orb1}`} aria-hidden="true" />
<span className={`${styles.orb} ${styles.orb2}`} aria-hidden="true" />
<span className={`${styles.orb} ${styles.orb3}`} aria-hidden="true" />
<span className={styles.mark} aria-hidden="true" />
<p className={styles.eyebrow}>PRESS ROOM</p>
<h1 className={styles.title}>언론과 파트너를 위한<br />최신 소식</h1>
<p className={styles.sub}>보도자료·미디어 키트·연락처를 한 곳에서 확인하세요.</p>
<div className={styles.row}>
<a className={styles.cta} href="#">보도자료 보기</a>
<button
type="button"
className={styles.subBtn}
aria-pressed={subscribed}
onClick={() => setSubscribed((v) => !v)}
>
<span className={styles.subIc} aria-hidden="true" />
보도자료 구독
</button>
</div>
</section>
);
}01 Press hero — Measured area 30.411% · moving frames 18/23Original by GODRICH · SCSS and React ship in the zip · 9 Media Page Templates — Copy-Paste
The post this demo lives in
9 Media Page Templates — Copy-PasteA media page template bundles the sections a press room needs — release feed, downloadable assets, spokesperson contact See all demos in this post


