03YouTubeプライバシー強化埋め込み — 明るさを周期的に求め直し脈打つバッジ
フレーム隅の鍵バッジの明るさの値を一定周期で求め直し、その値が上下する分だけバッジが穏やかに脈打ち、追跡クッキーなしの埋め込みであることを示します。
使いどころプライバシーポリシー案内動画、EU向けLPの動画セクション
aria-liverequestAnimationFramematchMedia

<main class="stage">
<section class="panel">
<div class="frame" id="frame">
<canvas class="thumb" id="thumb" width="320" height="180" aria-hidden="true"></canvas>
<div class="playBtn" role="button" aria-label="再生">
<svg class="playIcon" viewBox="0 0 24 24" aria-hidden="true"><polygon points="7,4 20,12 7,20"></polygon></svg>
</div>
<div class="badge" id="badge">
<div class="badgeGlow" id="badgeGlow"></div>
<span class="badgeIcon" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><g><path d="M216 96v112a8 8 0 0 1-8 8H48a8 8 0 0 1-8-8V96a8 8 0 0 1 8-8h160a8 8 0 0 1 8 8" opacity=".2"/><path d="M208 80h-32V56a48 48 0 0 0-96 0v24H48a16 16 0 0 0-16 16v112a16 16 0 0 0 16 16h160a16 16 0 0 0 16-16V96a16 16 0 0 0-16-16M96 56a32 32 0 0 1 64 0v24H96Zm112 152H48V96h160zm-68-56a12 12 0 1 1-12-12a12 12 0 0 1 12 12"/></g></svg></span>
<span class="badgeLabel">トラッキングCookieなし</span>
</div>
<div class="scrim">
<span class="scrimTitle">トラッキングなし動画埋め込み</span>
</div>
</div>
<div class="readout" id="readout" aria-live="polite">明るさ 50%</div>
</section>
</main>@use "../../../../site/static/demo-tokens" as *;
@include stage($stage-orange);
.panel {
display: flex;
flex-direction: column;
align-items: center;
gap: $sp-3;
}
.frame {
position: relative;
width: 200px;
aspect-ratio: 16 / 9;
border-radius: $r-card;
overflow: hidden;
box-shadow: $shadow-raised;
}
.thumb {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
display: block;
}
.playBtn {
position: absolute;
inset: 0;
display: grid;
place-items: center;
}
.playIcon {
width: 34px;
height: 34px;
filter: drop-shadow(0 2px 4px rgba(23, 20, 26, .4));
polygon {
fill: $subject-cream;
}
}
.badge {
position: absolute;
top: $sp-2;
right: $sp-2;
display: inline-flex;
align-items: center;
gap: $sp-1;
padding: $sp-1 $sp-2;
border-radius: $r-pill;
background: $subject-blue-deep;
overflow: hidden;
}
.badgeGlow {
position: absolute;
inset: 0;
background: $subject-cream;
opacity: .1;
pointer-events: none;
}
.badgeIcon {
position: relative;
display: block;
width: 12px;
height: 12px;
svg {
display: block;
width: 100%;
height: 100%;
}
path {
fill: $subject-cream;
}
}
.badgeLabel {
position: relative;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
font-weight: 600;
font-size: 9px;
line-height: 1.3;
color: $subject-cream;
}
.scrim {
position: absolute;
inset: auto 0 0 0;
padding: $sp-2 $sp-3;
background: rgba(23, 20, 26, .66);
}
.scrimTitle {
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
font-weight: 600;
font-size: 11px;
line-height: 1.3;
color: $subject-cream;
opacity: .96;
}
.readout {
display: inline-flex;
align-items: center;
padding: $sp-1 $sp-3;
border-radius: $r-pill;
background: $subject-cream;
color: $subject-ink;
font-family: "Space Mono", ui-monospace, monospace;
font-weight: 700;
font-size: 12px;
line-height: 1.3;
}
@media (max-width: 340px) {
.frame { width: 140px; }
.playIcon { width: 24px; height: 24px; }
.badgeIcon { width: 10px; height: 10px; }
.badgeLabel { font-size: 8px; }
.scrimTitle { font-size: 10px; }
.readout { font-size: 11px; }
}
@media (prefers-reduced-motion: reduce) {
.badge { 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);
}
.panel {
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
}
.frame {
position: relative;
width: 200px;
aspect-ratio: 16/9;
border-radius: 16px;
overflow: hidden;
box-shadow: 0 14px 24px rgba(23, 20, 26, 0.32);
}
.thumb {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
display: block;
}
.playBtn {
position: absolute;
inset: 0;
display: grid;
place-items: center;
}
.playIcon {
width: 34px;
height: 34px;
filter: drop-shadow(0 2px 4px rgba(23, 20, 26, 0.4));
}
.playIcon polygon {
fill: #fff7e6;
}
.badge {
position: absolute;
top: 8px;
right: 8px;
display: inline-flex;
align-items: center;
gap: 4px;
padding: 4px 8px;
border-radius: 999px;
background: #0a4fe8;
overflow: hidden;
}
.badgeGlow {
position: absolute;
inset: 0;
background: #fff7e6;
opacity: 0.1;
pointer-events: none;
}
.badgeIcon {
position: relative;
display: block;
width: 12px;
height: 12px;
}
.badgeIcon svg {
display: block;
width: 100%;
height: 100%;
}
.badgeIcon path {
fill: #fff7e6;
}
.badgeLabel {
position: relative;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
font-weight: 600;
font-size: 9px;
line-height: 1.3;
color: #fff7e6;
}
.scrim {
position: absolute;
inset: auto 0 0 0;
padding: 8px 12px;
background: rgba(23, 20, 26, 0.66);
}
.scrimTitle {
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
font-weight: 600;
font-size: 11px;
line-height: 1.3;
color: #fff7e6;
opacity: 0.96;
}
.readout {
display: inline-flex;
align-items: center;
padding: 4px 12px;
border-radius: 999px;
background: #fff7e6;
color: #17141a;
font-family: "Space Mono", ui-monospace, monospace;
font-weight: 700;
font-size: 12px;
line-height: 1.3;
}
@media (max-width: 340px) {
.frame {
width: 140px;
}
.playIcon {
width: 24px;
height: 24px;
}
.badgeIcon {
width: 10px;
height: 10px;
}
.badgeLabel {
font-size: 8px;
}
.scrimTitle {
font-size: 10px;
}
.readout {
font-size: 11px;
}
}
@media (prefers-reduced-motion: reduce) {
.badge {
transition: none;
}
}// 추적 없는 임베드(03) — 배지 광택은 매 프레임 sin 파형(pulse)을 다시 계산해
// 광택 레이어 불투명도와 배지 자체 스케일(자기 중심 기준)에 함께 반영한다.
// 계산 겹: 700ms 위상의 pulse 재계산 → 광택·스케일 동시 반영. 실키·실서버 요청 없음.
(function () {
function mulberry32(seed) {
var a = seed >>> 0;
return function () {
a |= 0; a = (a + 0x6D2B79F5) | 0;
var t = Math.imul(a ^ (a >>> 15), 1 | a);
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
};
}
function drawSeedThumb(canvas, seed) {
var ctx = canvas.getContext('2d');
if (!ctx) return;
var rnd = mulberry32(seed);
var w = canvas.width, h = canvas.height;
var hues = [216, 32, 268, 168];
ctx.fillStyle = 'hsl(' + hues[Math.floor(rnd() * hues.length)] + ', 38%, 22%)';
ctx.fillRect(0, 0, w, h);
for (var i = 0; i < 6; i++) {
var cx = rnd() * w, cy = rnd() * h, r = (0.18 + rnd() * 0.3) * Math.min(w, h);
ctx.fillStyle = 'hsla(' + hues[Math.floor(rnd() * hues.length)] + ', ' + (44 + rnd() * 22).toFixed(1) + '%, ' + (38 + rnd() * 26).toFixed(1) + '%, .5)';
ctx.beginPath(); ctx.arc(cx, cy, r, 0, Math.PI * 2); ctx.fill();
}
ctx.fillStyle = 'rgba(255, 247, 230, .06)';
for (var j = 0; j < h; j += 4) ctx.fillRect(0, j, w, 1);
}
var thumbEl = document.getElementById('thumb');
var badgeEl = document.getElementById('badge');
var badgeGlowEl = document.getElementById('badgeGlow');
var readoutEl = document.getElementById('readout');
if (!thumbEl || !badgeEl || !badgeGlowEl || !readoutEl) return;
drawSeedThumb(thumbEl, 103);
var reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
var CYCLE_MS = 2000;
function apply(t) {
var pulse = 0.5 + 0.5 * Math.sin(2 * Math.PI * t / 700);
badgeGlowEl.style.opacity = (0.1 + 0.45 * pulse).toFixed(3);
badgeEl.style.transform = 'scale(' + (1 + 0.04 * pulse).toFixed(4) + ')';
readoutEl.textContent = '明るさ ' + Math.round(pulse * 100) + '%';
}
apply(0);
if (reduce) return;
var start = null;
function loop(ts) {
if (start === null) start = ts;
var t = (ts - start) % CYCLE_MS;
apply(t);
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
})();import { useEffect, useRef } from "react";
import styles from "./EmbedYoutubeNocookie.module.scss";
// 추적 없는 임베드 — 배지 광택은 매 프레임 sin 파형(pulse)을 다시 계산해
// 광택 레이어 불투명도와 배지 자체 스케일(자기 중심 기준)에 함께 반영한다.
// 계산 겹: 700ms 위상의 pulse 재계산 → 광택·스케일 동시 반영. 실키·실서버 요청 없음.
const CYCLE_MS = 2000;
function mulberry32(seed: number) {
let a = seed >>> 0;
return function () {
a |= 0; a = (a + 0x6D2B79F5) | 0;
let t = Math.imul(a ^ (a >>> 15), 1 | a);
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
};
}
function drawSeedThumb(canvas: HTMLCanvasElement, seed: number) {
const ctx = canvas.getContext("2d");
if (!ctx) return;
const rnd = mulberry32(seed);
const w = canvas.width, h = canvas.height;
const hues = [216, 32, 268, 168];
ctx.fillStyle = "hsl(" + hues[Math.floor(rnd() * hues.length)] + ", 38%, 22%)";
ctx.fillRect(0, 0, w, h);
for (let i = 0; i < 6; i++) {
const cx = rnd() * w, cy = rnd() * h, r = (0.18 + rnd() * 0.3) * Math.min(w, h);
ctx.fillStyle = "hsla(" + hues[Math.floor(rnd() * hues.length)] + ", " + (44 + rnd() * 22).toFixed(1) + "%, " + (38 + rnd() * 26).toFixed(1) + "%, .5)";
ctx.beginPath(); ctx.arc(cx, cy, r, 0, Math.PI * 2); ctx.fill();
}
ctx.fillStyle = "rgba(255, 247, 230, .06)";
for (let j = 0; j < h; j += 4) ctx.fillRect(0, j, w, 1);
}
export default function EmbedYoutubeNocookie() {
const thumbRef = useRef<HTMLCanvasElement>(null);
const badgeRef = useRef<HTMLDivElement>(null);
const badgeGlowRef = useRef<HTMLDivElement>(null);
const readoutRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const thumb = thumbRef.current;
const badge = badgeRef.current;
const badgeGlow = badgeGlowRef.current;
const readout = readoutRef.current;
if (!thumb || !badge || !badgeGlow || !readout) return;
drawSeedThumb(thumb, 103);
const reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
function apply(t: number) {
const pulse = 0.5 + 0.5 * Math.sin((2 * Math.PI * t) / 700);
badgeGlow!.style.opacity = (0.1 + 0.45 * pulse).toFixed(3);
badge!.style.transform = "scale(" + (1 + 0.04 * pulse).toFixed(4) + ")";
readout!.textContent = "밝기 " + Math.round(pulse * 100) + "%";
}
apply(0);
if (reduce) return;
let start: number | null = null;
let raf = 0;
function loop(ts: number) {
if (start === null) start = ts;
const t = (ts - start) % CYCLE_MS;
apply(t);
raf = requestAnimationFrame(loop);
}
raf = requestAnimationFrame(loop);
return () => cancelAnimationFrame(raf);
}, []);
return (
<div className={styles.stage}>
<section className={styles.panel}>
<div className={styles.frame}>
<canvas className={styles.thumb} ref={thumbRef} width={320} height={180} aria-hidden="true" />
<div className={styles.playBtn} role="button" aria-label="재생">
<svg className={styles.playIcon} viewBox="0 0 24 24" aria-hidden="true">
<polygon points="7,4 20,12 7,20" />
</svg>
</div>
<div className={styles.badge} ref={badgeRef}>
<div className={styles.badgeGlow} ref={badgeGlowRef} />
<span className={styles.badgeIcon} aria-hidden="true">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256">
<g>
<path d="M216 96v112a8 8 0 0 1-8 8H48a8 8 0 0 1-8-8V96a8 8 0 0 1 8-8h160a8 8 0 0 1 8 8" opacity=".2" />
<path d="M208 80h-32V56a48 48 0 0 0-96 0v24H48a16 16 0 0 0-16 16v112a16 16 0 0 0 16 16h160a16 16 0 0 0 16-16V96a16 16 0 0 0-16-16M96 56a32 32 0 0 1 64 0v24H96Zm112 152H48V96h160zm-68-56a12 12 0 1 1-12-12a12 12 0 0 1 12 12" />
</g>
</svg>
</span>
<span className={styles.badgeLabel}>추적 쿠키 없음</span>
</div>
<div className={styles.scrim}>
<span className={styles.scrimTitle}>추적 없는 영상 임베드</span>
</div>
</div>
<div className={styles.readout} ref={readoutRef} aria-live="polite">밝기 50%</div>
</section>
</div>
);
}03 YouTubeプライバシー強化埋め込み — 明るさを周期的に求め直し脈打つバッジ — 計測 累積面積 1.876% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · youtube 埋め込みコード UI 9選 — SNSカードもコピペOK
このデモが属する記事
youtube 埋め込みコード UI 9選 — SNSカードもコピペOK「youtube 埋め込みコード」の検索結果はiframeタグの取得方法ばかりですが、この記事はその外側のUI計算を9種示します。フレーム幅に合わせて縦の比率が求め直され、表示されてからの経過時間が一定値を超えると軽量カードが自動で完全なフ この記事のデモをすべて見る


