03メディアキット
로고 팩·브랜드 가이드·제품 컷 세 체크박스를 고르면 '선택한 파일 받기' 버튼이 실제로 활성화되고, 누르면 각 카드에 체크 아이콘이 그려지며 받은 개수가 올라갑니다.
使いどころ
checkboxstroke-dashoffsetclick

<main class="stage">
<section class="mk">
<h2 class="mk__title">メディアキット</h2>
<ul class="mk__list">
<li class="mk__row">
<label class="mk__label">
<input class="mk__check" type="checkbox" value="logo">
<span class="mk__name">ロゴパック(SVG・PNG)</span>
</label>
<span class="mk__size">1.2MB</span>
<svg class="mk__done-ic" viewBox="0 0 20 20" aria-hidden="true"><polyline points="4,10 8,14 16,5"/></svg>
</li>
<li class="mk__row">
<label class="mk__label">
<input class="mk__check" type="checkbox" value="guide">
<span class="mk__name">ブランドガイド(PDF)</span>
</label>
<span class="mk__size">3.4MB</span>
<svg class="mk__done-ic" viewBox="0 0 20 20" aria-hidden="true"><polyline points="4,10 8,14 16,5"/></svg>
</li>
<li class="mk__row">
<label class="mk__label">
<input class="mk__check" type="checkbox" value="shots">
<span class="mk__name">製品スクリーンショット(ZIP)</span>
</label>
<span class="mk__size">4.8MB</span>
<svg class="mk__done-ic" viewBox="0 0 20 20" aria-hidden="true"><polyline points="4,10 8,14 16,5"/></svg>
</li>
</ul>
<button class="mk__btn" type="button" disabled>
<span class="mk__btn-ic" aria-hidden="true"></span>
選択したファイルを取得
</button>
<p class="mk__status" 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-orange);
.mk {
position: relative;
box-sizing: border-box;
overflow: hidden;
width: min(360px, 94vw);
background: $subject-cream;
border-radius: $r-card;
padding: $sp-3;
}
.mk::before {
content: "";
position: absolute; left: 0; right: 0; top: 0;
height: 3px;
background: $color;
transform-origin: center;
animation: mk-scan 2s $easing infinite;
}
@keyframes mk-scan {
0%, 70%, 100% { transform: scaleX(.25); opacity: .5; }
30% { transform: scaleX(1); opacity: 1; }
}
.mk__title { margin: 0 0 $sp-3; font-weight: 800; font-size: clamp(14px, 4vw, 16px); line-height: 1.2; font-family: inherit; color: $subject-ink; }
.mk__list { list-style: none; margin: 0 0 $sp-3; padding: 0; display: flex; flex-direction: column; gap: $sp-1; }
.mk__row {
display: flex; align-items: center; gap: $sp-2;
padding: $sp-1 $sp-2;
border-radius: $r-sm;
background: rgba(23, 20, 26, .05);
}
.mk__label { display: flex; align-items: center; gap: $sp-2; flex: 1 1 auto; min-width: 0; cursor: pointer; }
.mk__check { width: 16px; height: 16px; accent-color: $color; margin: 0; flex: 0 0 auto; }
.mk__name {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-weight: 600; font-size: clamp(10px, 3vw, 11px); line-height: 1.3; font-family: inherit;
color: $subject-ink;
}
.mk__size { flex: 0 0 auto; font-weight: 600; font-size: 9px; line-height: 1; font-family: inherit; color: rgba($subject-ink, .72); }
.mk__done-ic {
width: 14px; height: 14px; 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;
}
.mk__row.is-done .mk__done-ic { stroke-dashoffset: 0; opacity: 1; }
.mk__row.is-done .mk__size { display: none; }
.mk__btn {
display: flex; align-items: center; justify-content: center;
width: 100%;
padding: $sp-2 $sp-4;
border-radius: $r-control;
border: 0;
background: $subject-ink;
color: $subject-cream;
font-weight: 700; font-size: 12px; line-height: 1.4; font-family: inherit;
cursor: pointer;
transition: opacity $duration $easing;
}
.mk__btn:disabled { opacity: .4; cursor: not-allowed; }
.mk__btn-ic {
width: 0; height: 0;
border-left: 5px solid transparent;
border-right: 5px solid transparent;
border-top: 6px solid currentColor;
margin-right: $sp-1;
animation: mk-bounce 2s ease-in-out infinite;
}
@keyframes mk-bounce {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(3px); }
}
.mk__status { min-height: 14px; margin: $sp-2 0 0; font-weight: 600; font-size: 11px; line-height: 1.4; font-family: inherit; color: $color; text-align: center; }
// ── 반응형(BD9) ──────────────────────────────────────────
@media (max-width: 340px), (max-height: 220px) {
.mk { padding: $sp-2; }
.mk__title { display: none; }
.mk__list { margin-bottom: $sp-1; }
.mk__size { display: none; }
.mk__row { padding: $sp-1 $sp-2; }
.mk__btn { padding: $sp-1 $sp-3; }
.mk__status { margin-top: $sp-1; min-height: 0; }
}
// ── 접근성 ──────────────────────────────────────────
@media (prefers-reduced-motion: reduce) {
.mk__btn-ic { animation: none !important; }
.mk::before { animation: none !important; }
.mk__done-ic, .mk__btn { transition: none; }
}html, body {
margin: 0;
overflow: hidden;
}
.stage {
box-sizing: border-box;
min-height: 100vh;
display: grid;
place-items: center;
margin: 0;
background: #ff4d1f;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.mk {
position: relative;
box-sizing: border-box;
overflow: hidden;
width: min(360px, 94vw);
background: #fff7e6;
border-radius: 16px;
padding: 12px;
}
.mk::before {
content: "";
position: absolute;
left: 0;
right: 0;
top: 0;
height: 3px;
background: #2f6df6;
transform-origin: center;
animation: mk-scan 2s cubic-bezier(0.2, 1.4, 0.4, 1) infinite;
}
@keyframes mk-scan {
0%, 70%, 100% {
transform: scaleX(0.25);
opacity: 0.5;
}
30% {
transform: scaleX(1);
opacity: 1;
}
}
.mk__title {
margin: 0 0 12px;
font-weight: 800;
font-size: clamp(14px, 4vw, 16px);
line-height: 1.2;
font-family: inherit;
color: #17141a;
}
.mk__list {
list-style: none;
margin: 0 0 12px;
padding: 0;
display: flex;
flex-direction: column;
gap: 4px;
}
.mk__row {
display: flex;
align-items: center;
gap: 8px;
padding: 4px 8px;
border-radius: 8px;
background: rgba(23, 20, 26, 0.05);
}
.mk__label {
display: flex;
align-items: center;
gap: 8px;
flex: 1 1 auto;
min-width: 0;
cursor: pointer;
}
.mk__check {
width: 16px;
height: 16px;
accent-color: #2f6df6;
margin: 0;
flex: 0 0 auto;
}
.mk__name {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-weight: 600;
font-size: clamp(10px, 3vw, 11px);
line-height: 1.3;
font-family: inherit;
color: #17141a;
}
.mk__size {
flex: 0 0 auto;
font-weight: 600;
font-size: 9px;
line-height: 1;
font-family: inherit;
color: rgba(23, 20, 26, 0.72);
}
.mk__done-ic {
width: 14px;
height: 14px;
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);
}
.mk__row.is-done .mk__done-ic {
stroke-dashoffset: 0;
opacity: 1;
}
.mk__row.is-done .mk__size {
display: none;
}
.mk__btn {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
padding: 8px 16px;
border-radius: 12px;
border: 0;
background: #17141a;
color: #fff7e6;
font-weight: 700;
font-size: 12px;
line-height: 1.4;
font-family: inherit;
cursor: pointer;
transition: opacity 300ms cubic-bezier(0.2, 1.4, 0.4, 1);
}
.mk__btn:disabled {
opacity: 0.4;
cursor: not-allowed;
}
.mk__btn-ic {
width: 0;
height: 0;
border-left: 5px solid transparent;
border-right: 5px solid transparent;
border-top: 6px solid currentColor;
margin-right: 4px;
animation: mk-bounce 2s ease-in-out infinite;
}
@keyframes mk-bounce {
0%, 100% {
transform: translateY(0);
}
50% {
transform: translateY(3px);
}
}
.mk__status {
min-height: 14px;
margin: 8px 0 0;
font-weight: 600;
font-size: 11px;
line-height: 1.4;
font-family: inherit;
color: #2f6df6;
text-align: center;
}
@media (max-width: 340px), (max-height: 220px) {
.mk {
padding: 8px;
}
.mk__title {
display: none;
}
.mk__list {
margin-bottom: 4px;
}
.mk__size {
display: none;
}
.mk__row {
padding: 4px 8px;
}
.mk__btn {
padding: 4px 12px;
}
.mk__status {
margin-top: 4px;
min-height: 0;
}
}
@media (prefers-reduced-motion: reduce) {
.mk__btn-ic {
animation: none !important;
}
.mk::before {
animation: none !important;
}
.mk__done-ic, .mk__btn {
transition: none;
}
}// 실제 동작: 체크한 ファイル 수만큼 버튼 라벨이 바뀌고, 1개 이상이어야 버튼이 눌린다.
var root = document.querySelector('.mk');
var checks = root.querySelectorAll('.mk__check');
var btn = root.querySelector('.mk__btn');
var status = root.querySelector('.mk__status');
function updateLabel() {
var checkedFiles = Array.prototype.filter.call(checks, function (c) { return c.checked; });
btn.disabled = checkedFiles.length === 0;
btn.lastChild.textContent = checkedFiles.length ? ' 選択したファイルを取得 (' + checkedFiles.length + ')' : ' 選択したファイルを取得';
}
checks.forEach(function (c) { c.addEventListener('change', updateLabel); });
btn.addEventListener('click', function () {
var checkedFiles = Array.prototype.filter.call(checks, function (c) { return c.checked; });
checkedFiles.forEach(function (c) { c.closest('.mk__row').classList.add('is-done'); });
status.textContent = 'ファイル ' + checkedFiles.length + '件を取得しました。';
});import { useState } from "react";
import type { CSSProperties } from "react";
import styles from "./PressMediaKit.module.scss";
export type PressMediaKitProps = {
duration?: number;
easing?: string;
color?: string;
};
type Vars = CSSProperties & { "--duration"?: string; "--easing"?: string; "--color"?: string };
const FILES = [
{ id: "logo", name: "로고 팩 (SVG · PNG)", size: "1.2MB" },
{ id: "guide", name: "브랜드 가이드 (PDF)", size: "3.4MB" },
{ id: "shots", name: "제품 스크린샷 (ZIP)", size: "4.8MB" },
];
export default function PressMediaKit({ duration = 300, easing = "cubic-bezier(.2,1.4,.4,1)", color = "#2f6df6" }: PressMediaKitProps) {
const [checked, setChecked] = useState<Record<string, boolean>>({});
const [done, setDone] = useState<Record<string, boolean>>({});
const [status, setStatus] = useState("");
const vars: Vars = { "--duration": `${duration}ms`, "--easing": easing, "--color": color };
const count = Object.values(checked).filter(Boolean).length;
function toggle(id: string, e: any) {
setChecked((c) => ({ ...c, [id]: e.target.checked }));
}
function download() {
const next: Record<string, boolean> = {};
FILES.forEach((f) => { if (checked[f.id]) next[f.id] = true; });
setDone((d) => ({ ...d, ...next }));
setStatus(`파일 ${count}개를 받았습니다.`);
}
return (
<section className={styles.mk} style={vars}>
<h2 className={styles.title}>미디어 키트</h2>
<ul className={styles.list}>
{FILES.map((f) => (
<li key={f.id} className={done[f.id] ? `${styles.row} ${styles.done}` : styles.row}>
<label className={styles.label}>
<input className={styles.check} type="checkbox" checked={!!checked[f.id]} onChange={(e: any) => toggle(f.id, e)} />
<span className={styles.name}>{f.name}</span>
</label>
{!done[f.id] && <span className={styles.size}>{f.size}</span>}
<svg className={styles.doneIc} viewBox="0 0 20 20" aria-hidden="true"><polyline points="4,10 8,14 16,5" /></svg>
</li>
))}
</ul>
<button type="button" className={styles.btn} disabled={count === 0} onClick={download}>
<span className={styles.btnIc} aria-hidden="true" />
{count ? ` 선택한 파일 받기 (${count})` : " 선택한 파일 받기"}
</button>
<p className={styles.status} role="status" aria-live="polite">{status}</p>
</section>
);
}03 メディアキット — 計測 累積面積 0.756% · 動いたフレーム 22/23GODRICH オリジナル · SCSS と React は zip に同梱 · プレスキット テンプレート付き プレスルームセクション9選
このデモが属する記事
プレスキット テンプレート付き プレスルームセクション9選プレスルーム・メディアページのセクションテンプレートは、プレスリリース一覧・メディアキット・問い合わせ先といった要素をひとまとめにしたプレスキット テンプレート集です。LPにそのまま貼れるヒーローから購読フッターまで、9つのうち1つは実際に この記事のデモをすべて見る


