GODRICH

언론보도 페이지 템플릿 9종 — 프레스룸 섹션 모음

미디어·프레스룸 섹션 템플릿은 보도자료·미디어 키트·연락처 같은 언론보도 페이지 구성 요소를 통째로 담은 코드 모음입니다. 랜딩 페이지에 바로 붙이는 히어로부터 구독 푸터까지, 아홉 개 중 하나는 클립보드로 실제 이메일을 복사하는 버튼을 담았습니다.

자동 재생 · 눌러서 해당 항목으로 · 전부 한 zip

아홉 개는 인기순이 아니라 기자·투자자가 프레스룸을 훑는 순서대로 놓았습니다. 먼저 첫인상을 주고(히어로), 최신 소식을 확인시키고(보도자료 목록), 취재에 쓸 자료를 내려받게 하고(미디어 키트), 이미 실린 곳을 보여주고(언론 언급 바), 규모를 숫자로 증명하고(숫자 하이라이트), 담당자에게 바로 연락하게 하고(문의 카드), 브랜드의 목소리를 들려주고(창업자 인용), 지금까지 걸어온 길을 보여준 다음(연혁 타임라인), 다음 소식을 받아보게 합니다(구독 푸터). 담당자 얼굴·언론사 로고 자리는 실제 이미지 대신 이 사이트의 먹·파랑·크림 토큰만으로 그린 도형 플레이스홀더입니다. 이미지 파일을 교체해도 배치와 여백은 그대로 유지됩니다.

01프레스 히어로

무대 뒤 흐릿한 원 세 개가 계속 떠다니는 동안 워드마크(도형 플레이스홀더)·헤드라인·부제·CTA 두 개가 한 화면에 자리 잡습니다. 오른쪽 "보도자료 구독" 버튼을 누를 때마다 구독 상태와 아이콘 채움이 함께 바뀝니다.

transformopacityaria-pressed
.ph__orb--1 { animation: ph-float-a 2s ease-in-out infinite; }
@keyframes ph-float-a {
  0%, 100% { transform: translate(0, 0) scale(1); }
  50%      { transform: translate($sp-10, -$sp-8) scale(1.5); }
}
.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);
}

02보도자료 목록

분류 칩(전체·제품·파트너십·수상)을 클릭하면 그 분류가 아닌 행은 실제로 접히고 나머지만 남습니다. 각 행 왼쪽 악센트 바가 순서대로 밝아지는 스캔 동작을 항상 반복합니다.

clickfilteraria-pressed
chips.forEach(function (chip) {
  chip.addEventListener('click', function () {
    root.classList.remove('is-demo');
    chips.forEach(function (c) { c.setAttribute('aria-pressed', 'false'); });
    chip.setAttribute('aria-pressed', 'true');
    var cat = chip.getAttribute('data-cat');
    rows.forEach(function (row) {
      var show = cat === 'all' || row.getAttribute('data-cat') === cat;
      row.classList.toggle('is-hidden', !show);
    });
  });
});

03미디어 키트 다운로드

로고 팩·브랜드 가이드·제품 스크린샷 중 원하는 파일을 체크하면 "선택한 파일 받기" 버튼이 실제로 활성화됩니다. 누르면 선택한 파일마다 체크 아이콘이 그려지고 받은 개수가 문구로 안내됩니다.

checkboxstroke-dashoffsetclick
function 갱신() {
  var 켜짐 = Array.prototype.filter.call(checks, function (c) { return c.checked; });
  btn.disabled = 켜짐.length === 0;
  btn.lastChild.textContent = 켜짐.length ? ' 선택한 파일 받기 (' + 켜짐.length + ')' : ' 선택한 파일 받기';
}
checks.forEach(function (c) { c.addEventListener('change', 갱신); });

btn.addEventListener('click', function () {
  var 켜짐 = Array.prototype.filter.call(checks, function (c) { return c.checked; });
  켜짐.forEach(function (c) { c.closest('.mk__row').classList.add('is-done'); });
  status.textContent = '파일 ' + 켜짐.length + '개를 받았습니다.';
});

04언론 보도 로고 바

도형으로 그린 언론사 마크 여덟 개(실제 로고 아님)가 한 줄로 이어져 끊김 없이 흘러갑니다. 커서를 올리거나 옆 버튼을 눌러 두면 흐름이 그 지점에서 정지합니다.

translateX(-50%)animation-play-statepointerenter
// 트랙 자체엔 gap 을 쓰지 않는다 — 항목마다 margin-right 로 간격을 준다
.mb__track { display: flex; width: max-content; animation: mb-scroll 14s linear infinite; }
.mb__logo { flex: 0 0 auto; margin-right: $sp-6; }
@keyframes mb-scroll {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); }
}

05숫자 하이라이트

카드 세 장의 큰 숫자가 페이지를 열자마자 0에서 실제 목표값(언론 보도 건수·협력 매체 수·누적 다운로드)까지 실제로 올라가며 멈춥니다. 카드 위 악센트 바는 순서대로 스캔 동작을 계속 반복합니다.

requestAnimationFramecounteropacity
var 시작 = performance.now();
var 길이 = 1200;
function easeOut(t) { return 1 - Math.pow(1 - t, 3); }
function tick(now) {
  var t = Math.min(1, (now - 시작) / 길이);
  var e = easeOut(t);
  nums.forEach(function (el) {
    var target = Number(el.getAttribute('data-target'));
    el.textContent = Math.round(target * e).toLocaleString('ko-KR');
  });
  if (t < 1) requestAnimationFrame(tick);
}
requestAnimationFrame(tick);

06미디어 문의 카드

미디어 담당자 이름·직함 아래 이메일 버튼을 누르면 실제로 클립보드에 복사됩니다. 버튼엔 체크 아이콘이 잠깐 그려졌다가 1.6초 뒤 사라집니다.

clipboardstroke-dashoffsetclick
btn.addEventListener('click', function () {
  var email = btn.getAttribute('data-email');
  var 복사 = function () {
    btn.classList.add('is-copied');
    hint.textContent = '이메일 주소를 복사했습니다.';
    clearTimeout(타이머);
    타이머 = setTimeout(function () { btn.classList.remove('is-copied'); }, 1600);
  };
  if (navigator.clipboard && navigator.clipboard.writeText) {
    navigator.clipboard.writeText(email).then(복사, 복사);
  }
});

07창업자 인용

창업자 인용문 두 개가 점 두 개로 오갑니다. 점을 클릭하면 그 인용문으로 실제로 크로스페이드 전환되고, 큰 따옴표 장식이 은은하게 흔들립니다.

opacityclickaria-selected
dots.forEach(function (dot) {
  dot.addEventListener('click', function () {
    root.classList.remove('is-demo');
    var i = dot.getAttribute('data-i');
    dots.forEach(function (d) { d.classList.remove('is-active'); d.setAttribute('aria-selected', 'false'); });
    slides.forEach(function (s) { s.classList.remove('is-active'); });
    dot.classList.add('is-active'); dot.setAttribute('aria-selected', 'true');
    root.querySelector('.fq__slide[data-i="' + i + '"]').classList.add('is-active');
  });
});

08연혁 타임라인

가로선 위 연혁 네 지점(창립·시리즈 A·해외 진출·100만 사용자)을 클릭하면 그 지점까지 진행바가 scaleX로 실제로 채워지고, 위 설명 문단이 그 시점 내용으로 바뀝니다.

scaleXclickaria-current
stops.forEach(function (btn) {
  btn.addEventListener('click', function () {
    root.classList.remove('is-demo');
    var i = Number(btn.getAttribute('data-i'));
    root.setAttribute('data-active', String(i));
    root.querySelector('.mt__fill').style.transform = 'scaleX(' + ((i + 1) / 4) + ')';
    stops.forEach(function (s) { s.removeAttribute('aria-current'); });
    btn.setAttribute('aria-current', 'step');
    desc.textContent = 설명[i];
  });
});

09프레스 푸터

이메일을 넣고 구독 버튼을 누르면 실제로 형식을 검사합니다. 통과하면 체크가 그려지고 안내 문구가 뜨며, 형식이 틀리면 입력칸이 좌우로 짧게 흔들립니다. 아래 링크 세 칸은 항상 그 자리에 있습니다.

submitstroke-dashoffsetclick
form.addEventListener('submit', function (e) {
  e.preventDefault();
  var ok = /^\S+@\S+\.\S+$/.test(input.value);
  if (ok) {
    form.classList.add('is-done');
    status.textContent = '구독 완료 — 확인 메일을 보냈습니다.';
  } else {
    input.classList.remove('is-shake');
    void input.offsetWidth;
    input.classList.add('is-shake');
    status.textContent = '올바른 이메일을 입력해 주세요.';
  }
});

어디서 깨지는가 — 함정

아홉 개를 만드는 동안 실제로 두 번 걸렸습니다. 첫 번째는 미디어 문의 카드(06)였습니다. 처음엔 카드 배경을 불투명한 크림색으로 칠했는데, 배경에 넣은 드리프트 원이 z-index:-1이라 그 불투명한 배경 뒤에 완전히 가려져 버렸습니다. 화면엔 아무 문제가 없어 보였지만 실제로 움직이는 픽셀이 거의 없어 애니메이션이라 부르기 민망한 수준이었습니다. 카드 배경을 없애고 원이 무대 색 위에서 바로 보이게 하자 해결됐습니다.

두 번째는 보도자료 목록(02)과 미디어 키트(03)의 반응형이었습니다. 두 항목 모두 max-height로 행을 접었다 펴는데, box-sizing을 지정하지 않으면 padding이 지정한 높이 위에 그대로 더해져 실제 렌더 높이가 더 커집니다. 320px 폭 화면에서 이 차이가 쌓이며 조용히 세로 스크롤이 생겼습니다. 두 항목 모두 box-sizing: border-box를 넣고 좁은 화면용 패딩을 줄이자 통과했습니다.

마퀴로 도는 언론 보도 로고 바(04)에도 흔한 함정이 있습니다. 트랙 항목 사이 간격을 gap으로 주면 두 벌(2N개) 사이 이음새가 gap ÷ 2만큼 모자라 한 바퀴 돌 때마다 살짝 튑니다. 이 템플릿은 처음부터 margin-right로 간격을 줘서 이 문제를 피했고, 7a6mw6kt으로 열어 보는 zip 안의 코드도 그 방식 그대로입니다. 위 두 함정을 포함해 아홉 개가 표대로 정확히 동작하는 최신 상태입니다.

접근성

아홉 개 전부 prefers-reduced-motion: reduce에서 장식 애니메이션을 끄되 상태 자체는 남깁니다 — 연혁 진행바는 채워진 채로, 필터링된 목록은 접힌 채로 고정되고 숫자 하이라이트의 카운트업은 정보이므로 그대로 유지합니다. 미디어 키트(03)의 체크박스는 네이티브 input[type="checkbox"]라 Tab·Space로 그대로 조작되고, 미디어 문의 카드(06)와 프레스 푸터(09)의 결과 문구는 role="status"(aria-live)라 스크린 리더가 복사·제출 결과를 바로 읽습니다. 연혁 타임라인(08)은 aria-current="step"으로 지금 보는 지점을 알리고, 창업자 인용(07)의 점 버튼은 role="group"aria-selected로 상태를 전달합니다. 클립보드 복사(06)는 MDN Clipboard API 명세를 따르고, 지원하지 않는 환경에서는 textarea 폴백으로 넘어갑니다.

이메일·대시보드 같은 다른 정적 템플릿은 템플릿 카테고리 목록에서 더 볼 수 있고, 이 블로그를 운영하는 사람이 누구인지는 소개 페이지에 적어 뒀습니다.

FAQ

이 템플릿에 실제 로고나 사진을 써도 되나요?

네, 애초에 그러라고 만들었습니다. 아바타 원·언론사 로고 블록을 실제 파일로 바꿔 넣어도 크기·정렬은 그대로 유지됩니다. 도형 자체는 demo-tokens.scss의 먹·파랑·크림 값을 쓰고, 무대 배경(옐로·오렌지 등)은 완전히 다른 토큰이라 둘을 섞어 쓸 일이 없습니다.

React 버전은 CSS 버전과 뭐가 다른가요?

동작은 같습니다. 필터 상태·체크박스·활성 인용문 같은 값은 useState로 관리하고, 변수 3개(duration·easing·color)는 props로 받아 CSS 변수(--duration --easing --color)로 넘깁니다. 숫자 하이라이트(05)의 카운트업은 useEffect 안에서 같은 requestAnimationFrame 로직을 그대로 씁니다.

미디어 키트 다운로드는 실제로 파일을 내려받나요, 아니면 장식인가요?

체크박스 선택과 버튼 활성화, 완료 표시는 전부 실제 상태 변화입니다. 다만 데모 자체엔 연결된 실제 파일이 없어 클릭한다고 진짜 다운로드가 시작되지는 않습니다. 실제로 쓸 때는 각 파일 행에 실제 href만 연결하면 나머지 체크·카운트 로직은 그대로 재사용할 수 있습니다.

압축 비밀번호를 입력하세요

비밀번호는 이 글 본문 안에 있습니다. 읽다 보면 나옵니다.