21st.dev 사례 연구
Depth field · Light and distance
배경 · MIT
갤러리 안에서는 화면을 벗어나거나 움직임을 멈추면 예제도 닫힙니다. 다시 재생할 때는 처음부터 시작해요.
저장한 HTML에서는 갤러리의 재생 설정이 적용되지 않아요.
SOURCE FILES
독립 예제 코드 읽기
사례의 구도나 문서의 원리를 바탕으로 새로 만든 예제입니다. 아래 코드를 복사해 직접 응용할 수 있습니다. 실행 HTML에는 미리보기를 위한 실행 환경이 들어 있습니다.
index.html독립 구현
<!doctype html>
<!-- Independent observation study. Copyright (c) 2026 StyleGallery contributors. MIT. -->
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; img-src data: blob:; font-src 'none'; connect-src 'none'; media-src 'none'; object-src 'none'; base-uri 'none'; form-action 'none'"><title>Depth field · Light and distance</title><style>/* Original learning-example styling. Copyright (c) 2026 StyleGallery contributors. MIT. */
* { box-sizing: border-box; }
body { margin: 0; }
button, input, select { font: inherit; }
button { cursor: pointer; }
button:disabled { cursor: default; }
button, input, select { touch-action: manipulation; }
button:focus-visible, input:focus-visible, select:focus-visible, [tabindex="-1"]:focus-visible { outline: 3px solid #307865; outline-offset: 3px; }
[hidden] { display: none !important; }
h1, h2, p { margin: 0; }
svg { display: block; }
.sr-only { block-size: 1px; clip-path: inset(50%); inline-size: 1px; overflow: hidden; position: absolute; white-space: nowrap; }
.no-script { font: 12px/1.5 system-ui, sans-serif; padding: 12px; }
@media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation: none !important; scroll-behavior: auto !important; transition: none !important; } }
body { background: #dce4d9; color: #eaf0df; font: 13px/1.4 Arial, Helvetica, sans-serif; padding: 16px; }
.field_layout { background: #0c1814; border: 1px solid #34483b; border-radius: 17px; inline-size: 100%; margin-inline: auto; max-inline-size: 940px; overflow: hidden; }
.field_header { align-items: center; border-block-end: 1px solid #314237; display: flex; gap: 12px; justify-content: space-between; min-block-size: 45px; padding: 9px 20px; }
.field_label { align-items: center; display: flex; font-size: 10px; font-weight: 700; gap: 8px; letter-spacing: 1.7px; }
.field_mark { color: #c5ddb5; font-size: 22px; line-height: 1; }
.field_number { color: #94a18e; }
.field_edition { color: #a7b29c; font-size: 9px; letter-spacing: 1.5px; }
.field_stage { block-size: 235px; isolation: isolate; overflow: hidden; position: relative; }
.field_art { block-size: 100%; inline-size: 100%; inset: 0; position: absolute; z-index: -1; }
.field_copy { background: linear-gradient(90deg, #0c1814e8, #0c181400); block-size: 100%; inline-size: 61%; padding: 24px 26px; }
.field_kicker { color: #d0c09b; font-size: 8px; letter-spacing: 2.2px; }
.field_copy h1 { font-family: Georgia, 'Times New Roman', serif; font-size: clamp(38px, 7.6vw, 60px); font-weight: 400; letter-spacing: -2.5px; line-height: .94; margin-block: 14px 13px; }
.field_caption { color: #b1bea8; font-size: 11px; line-height: 1.45; }
.field_coordinates { bottom: 18px; color: #abbda5; display: flex; font: 8px/1.5 monospace; gap: 17px; letter-spacing: 1px; position: absolute; right: 20px; }
.field_controls { border-block-start: 1px solid #314237; padding: 10px 20px 12px; }
.field_control_top { align-items: center; display: flex; gap: 10px; justify-content: space-between; margin-block-end: 9px; }
.field_status { color: #c4d5b9; font-size: 10px; letter-spacing: .6px; }
.field_traces { align-items: center; color: #bdcab4; display: flex; font-size: 10px; gap: 4px; }
.field_traces input { accent-color: #c7dfad; block-size: 14px; inline-size: 14px; margin: 0; }
.field_control_row { align-items: center; display: flex; gap: 16px; }
.field_play { align-items: center; background: #d3e7bb; border: 0; border-radius: 7px; color: #152717; display: flex; flex-shrink: 0; font-size: 11px; font-weight: 700; gap: 21px; justify-content: space-between; min-block-size: 35px; min-inline-size: 126px; padding: 7px 12px; }
.field_play:hover { background: #e5f3d4; }
.field_play:disabled { background: #64765e; color: #fff; }
.field_range { color: #a8b79d; display: grid; flex: 1; font-size: 9px; gap: 4px; min-inline-size: 0; }
.field_range input { accent-color: #d1dfb3; inline-size: 100%; margin: 0; }
.field_value { color: #c7d9ba; font: 11px/1 monospace; min-inline-size: 36px; }
.field_motion_note { color: #9fad94; font-size: 9px; margin-block-start: 7px; }
.field_layout :focus-visible { outline-color: #f1c979; }
@media (min-width: 900px) { .field_stage { block-size: 340px; } .field_copy { padding: 44px 36px; } .field_copy h1 { font-size: 76px; } }
@media (max-width: 420px) { body { padding: 8px; } .field_header { padding-inline: 14px; } .field_edition { display: none; } .field_stage { block-size: 270px; } .field_copy { inline-size: 85%; padding: 25px 20px; } .field_copy h1 { font-size: 48px; } .field_controls { padding-inline: 14px; } .field_control_row { flex-wrap: wrap; gap: 10px; } .field_play { inline-size: 100%; } .field_range { min-inline-size: 160px; } .field_coordinates { bottom: 15px; } }
</style></head><body><main class="field_layout">
<header class="field_header"><p class="field_label"><span class="field_mark" aria-hidden="true">✳</span> FIELD NOTES <span class="field_number">/ 001</span></p><p class="field_edition">LIGHT & DISTANCE</p></header>
<section class="field_stage" aria-labelledby="field-title">
<svg class="field_art" viewBox="0 0 640 250" preserveAspectRatio="xMidYMid slice" aria-hidden="true"><defs><radialGradient id="field-glow"><stop stop-color="#304c3b" stop-opacity=".66"/><stop offset="1" stop-color="#0c1814" stop-opacity="0"/></radialGradient></defs><ellipse cx="452" cy="130" rx="280" ry="210" fill="url(#field-glow)"/><g stroke="#acc8b5" stroke-opacity=".1" fill="none"><ellipse cx="440" cy="122" rx="108" ry="56"/><ellipse cx="440" cy="122" rx="188" ry="98"/></g><g data-points><g data-point="0" fill="#e0c591" stroke="#e0c591" opacity="0.639"><line x1="485.68" y1="122.00" x2="488.19" y2="122.00" stroke-width="0.98" stroke-linecap="round"/><circle cx="485.68" cy="122.00" r="1.51" stroke="none"/></g><g data-point="1" fill="#b9ddc5" stroke="#b9ddc5" opacity="0.263"><line x1="387.64" y1="149.82" x2="386.73" y2="150.30" stroke-width="0.35" stroke-linecap="round"/><circle cx="387.64" cy="149.82" r="0.53" stroke="none"/></g><g data-point="2" fill="#b9ddc5" stroke="#b9ddc5" opacity="0.495"><line x1="455.27" y1="21.10" x2="455.89" y2="17.01" stroke-width="0.74" stroke-linecap="round"/><circle cx="455.27" cy="21.10" r="1.14" stroke="none"/></g><g data-point="3" fill="#b9ddc5" stroke="#b9ddc5" opacity="0.727"><line x1="507.36" y1="172.96" x2="511.66" y2="176.21" stroke-width="1.13" stroke-linecap="round"/><circle cx="507.36" cy="172.96" r="1.74" stroke="none"/></g><g data-point="4" fill="#b9ddc5" stroke="#b9ddc5" opacity="0.352"><line x1="339.73" y1="111.71" x2="337.10" y2="111.44" stroke-width="0.50" stroke-linecap="round"/><circle cx="339.73" cy="111.71" r="0.77" stroke="none"/></g><g data-point="5" fill="#e0c591" stroke="#e0c591" opacity="0.584"><line x1="646.48" y1="45.82" x2="656.68" y2="42.06" stroke-width="0.89" stroke-linecap="round"/><circle cx="646.48" cy="45.82" r="1.37" stroke="none"/></g><g data-point="6" fill="#b9ddc5" stroke="#b9ddc5" opacity="0.816"><line x1="378.10" y1="255.55" x2="373.61" y2="265.25" stroke-width="1.28" stroke-linecap="round"/><circle cx="378.10" cy="255.55" r="1.97" stroke="none"/></g><g data-point="7" fill="#b9ddc5" stroke="#b9ddc5" opacity="0.441"><line x1="374.36" y1="48.70" x2="372.06" y2="46.13" stroke-width="0.65" stroke-linecap="round"/><circle cx="374.36" cy="48.70" r="1.00" stroke="none"/></g><g data-point="8" fill="#b9ddc5" stroke="#b9ddc5" opacity="0.673"><line x1="502.34" y1="135.20" x2="505.97" y2="135.97" stroke-width="1.04" stroke-linecap="round"/><circle cx="502.34" cy="135.20" r="1.60" stroke="none"/></g><g data-point="9" fill="#b9ddc5" stroke="#b9ddc5" opacity="0.297"><line x1="364.33" y1="140.12" x2="362.76" y2="140.49" stroke-width="0.40" stroke-linecap="round"/><circle cx="364.33" cy="140.12" r="0.62" stroke="none"/></g><g data-point="10" fill="#e0c591" stroke="#e0c591" opacity="0.529"><line x1="524.04" y1="17.84" x2="527.73" y2="13.26" stroke-width="0.80" stroke-linecap="round"/><circle cx="524.04" cy="17.84" r="1.23" stroke="none"/></g><g data-point="11" fill="#b9ddc5" stroke="#b9ddc5" opacity="0.761"><line x1="484.39" y1="204.07" x2="487.37" y2="209.58" stroke-width="1.19" stroke-linecap="round"/><circle cx="484.39" cy="204.07" r="1.83" stroke="none"/></g><g data-point="12" fill="#b9ddc5" stroke="#b9ddc5" opacity="0.386"><line x1="339.68" y1="88.28" x2="336.71" y2="87.28" stroke-width="0.55" stroke-linecap="round"/><circle cx="339.68" cy="88.28" r="0.85" stroke="none"/></g><g data-point="13" fill="#b9ddc5" stroke="#b9ddc5" opacity="0.618"><line x1="713.54" y1="87.12" x2="727.98" y2="85.28" stroke-width="0.95" stroke-linecap="round"/><circle cx="713.54" cy="87.12" r="1.46" stroke="none"/></g><g data-point="14" fill="#b9ddc5" stroke="#b9ddc5" opacity="0.850"><line x1="251.92" y1="277.17" x2="237.62" y2="288.96" stroke-width="1.34" stroke-linecap="round"/><circle cx="251.92" cy="277.17" r="2.06" stroke="none"/></g><g data-point="15" fill="#e0c591" stroke="#e0c591" opacity="0.474"><line x1="419.24" y1="29.09" x2="418.44" y2="25.52" stroke-width="0.70" stroke-linecap="round"/><circle cx="419.24" cy="29.09" r="1.08" stroke="none"/></g><g data-point="16" fill="#b9ddc5" stroke="#b9ddc5" opacity="0.707"><line x1="510.21" y1="156.32" x2="514.54" y2="158.44" stroke-width="1.10" stroke-linecap="round"/><circle cx="510.21" cy="156.32" r="1.69" stroke="none"/></g><g data-point="17" fill="#b9ddc5" stroke="#b9ddc5" opacity="0.331"><line x1="346.26" y1="124.25" x2="344.00" y2="124.30" stroke-width="0.46" stroke-linecap="round"/><circle cx="346.26" cy="124.25" r="0.71" stroke="none"/></g><g data-point="18" fill="#b9ddc5" stroke="#b9ddc5" opacity="0.563"><line x1="599.92" y1="29.70" x2="607.48" y2="25.33" stroke-width="0.85" stroke-linecap="round"/><circle cx="599.92" cy="29.70" r="1.31" stroke="none"/></g><g data-point="19" fill="#b9ddc5" stroke="#b9ddc5" opacity="0.795"><line x1="430.84" y1="236.90" x2="430.19" y2="245.00" stroke-width="1.25" stroke-linecap="round"/><circle cx="430.84" cy="236.90" r="1.92" stroke="none"/></g><g data-point="20" fill="#e0c591" stroke="#e0c591" opacity="0.420"><line x1="355.61" y1="63.35" x2="352.83" y2="61.41" stroke-width="0.61" stroke-linecap="round"/><circle cx="355.61" cy="63.35" r="0.94" stroke="none"/></g><g data-point="21" fill="#b9ddc5" stroke="#b9ddc5" opacity="0.652"><line x1="492.63" y1="126.11" x2="495.59" y2="126.34" stroke-width="1.00" stroke-linecap="round"/><circle cx="492.63" cy="126.11" r="1.54" stroke="none"/></g><g data-point="22" fill="#b9ddc5" stroke="#b9ddc5" opacity="0.276"><line x1="378.41" y1="146.86" x2="377.26" y2="147.32" stroke-width="0.37" stroke-linecap="round"/><circle cx="378.41" cy="146.86" r="0.57" stroke="none"/></g><g data-point="23" fill="#b9ddc5" stroke="#b9ddc5" opacity="0.508"><line x1="480.23" y1="18.29" x2="481.91" y2="13.95" stroke-width="0.76" stroke-linecap="round"/><circle cx="480.23" cy="18.29" r="1.17" stroke="none"/></g><g data-point="24" fill="#b9ddc5" stroke="#b9ddc5" opacity="0.740"><line x1="501.61" y1="184.36" x2="505.62" y2="188.42" stroke-width="1.15" stroke-linecap="round"/><circle cx="501.61" cy="184.36" r="1.77" stroke="none"/></g><g data-point="25" fill="#e0c591" stroke="#e0c591" opacity="0.365"><line x1="338.02" y1="103.13" x2="335.22" y2="102.61" stroke-width="0.52" stroke-linecap="round"/><circle cx="338.02" cy="103.13" r="0.80" stroke="none"/></g><g data-point="26" fill="#b9ddc5" stroke="#b9ddc5" opacity="0.597"><line x1="673.77" y1="59.35" x2="685.62" y2="56.18" stroke-width="0.91" stroke-linecap="round"/><circle cx="673.77" cy="59.35" r="1.40" stroke="none"/></g><g data-point="27" fill="#b9ddc5" stroke="#b9ddc5" opacity="0.829"><line x1="336.48" y1="265.46" x2="328.83" y2="276.06" stroke-width="1.30" stroke-linecap="round"/><circle cx="336.48" cy="265.46" r="2.01" stroke="none"/></g><g data-point="28" fill="#b9ddc5" stroke="#b9ddc5" opacity="0.454"><line x1="389.42" y1="40.44" x2="387.58" y2="37.48" stroke-width="0.67" stroke-linecap="round"/><circle cx="389.42" cy="40.44" r="1.03" stroke="none"/></g><g data-point="29" fill="#b9ddc5" stroke="#b9ddc5" opacity="0.686"><line x1="506.78" y1="142.36" x2="510.76" y2="143.57" stroke-width="1.06" stroke-linecap="round"/><circle cx="506.78" cy="142.36" r="1.63" stroke="none"/></g><g data-point="30" fill="#e0c591" stroke="#e0c591" opacity="0.310"><line x1="356.56" y1="134.76" x2="354.72" y2="135.04" stroke-width="0.43" stroke-linecap="round"/><circle cx="356.56" cy="134.76" r="0.66" stroke="none"/></g><g data-point="31" fill="#b9ddc5" stroke="#b9ddc5" opacity="0.542"><line x1="552.63" y1="20.41" x2="557.72" y2="15.81" stroke-width="0.82" stroke-linecap="round"/><circle cx="552.63" cy="20.41" r="1.26" stroke="none"/></g><g data-point="32" fill="#b9ddc5" stroke="#b9ddc5" opacity="0.774"><line x1="468.06" y1="216.68" x2="469.97" y2="223.16" stroke-width="1.21" stroke-linecap="round"/><circle cx="468.06" cy="216.68" r="1.86" stroke="none"/></g><g data-point="33" fill="#b9ddc5" stroke="#b9ddc5" opacity="0.399"><line x1="343.73" y1="78.76" x2="340.76" y2="77.42" stroke-width="0.58" stroke-linecap="round"/><circle cx="343.73" cy="78.76" r="0.89" stroke="none"/></g><g data-point="34" fill="#b9ddc5" stroke="#b9ddc5" opacity="0.631"><line x1="734.28" y1="107.86" x2="750.19" y2="107.09" stroke-width="0.97" stroke-linecap="round"/><circle cx="734.28" cy="107.86" r="1.49" stroke="none"/></g><g data-point="35" fill="#e0c591" stroke="#e0c591" opacity="0.255"><line x1="393.42" y1="151.20" x2="392.65" y2="151.69" stroke-width="0.33" stroke-linecap="round"/><circle cx="393.42" cy="151.20" r="0.51" stroke="none"/></g><g data-point="36" fill="#b9ddc5" stroke="#b9ddc5" opacity="0.487"><line x1="440.83" y1="23.69" x2="440.86" y2="19.79" stroke-width="0.73" stroke-linecap="round"/><circle cx="440.83" cy="23.69" r="1.12" stroke="none"/></g><g data-point="37" fill="#b9ddc5" stroke="#b9ddc5" opacity="0.719"><line x1="509.32" y1="166.32" x2="513.68" y2="169.11" stroke-width="1.12" stroke-linecap="round"/><circle cx="509.32" cy="166.32" r="1.72" stroke="none"/></g><g data-point="38" fill="#b9ddc5" stroke="#b9ddc5" opacity="0.344"><line x1="341.71" y1="116.72" x2="339.22" y2="116.58" stroke-width="0.48" stroke-linecap="round"/><circle cx="341.71" cy="116.72" r="0.74" stroke="none"/></g><g data-point="39" fill="#b9ddc5" stroke="#b9ddc5" opacity="0.576"><line x1="628.94" y1="38.83" x2="638.12" y2="34.79" stroke-width="0.88" stroke-linecap="round"/><circle cx="628.94" cy="38.83" r="1.35" stroke="none"/></g><g data-point="40" fill="#e0c591" stroke="#e0c591" opacity="0.808"><line x1="400.25" y1="248.73" x2="397.40" y2="257.83" stroke-width="1.27" stroke-linecap="round"/><circle cx="400.25" cy="248.73" r="1.95" stroke="none"/></g><g data-point="41" fill="#b9ddc5" stroke="#b9ddc5" opacity="0.433"><line x1="366.38" y1="54.14" x2="363.85" y2="51.82" stroke-width="0.63" stroke-linecap="round"/><circle cx="366.38" cy="54.14" r="0.97" stroke="none"/></g><g data-point="42" fill="#b9ddc5" stroke="#b9ddc5" opacity="0.665"><line x1="498.93" y1="131.37" x2="502.32" y2="131.91" stroke-width="1.03" stroke-linecap="round"/><circle cx="498.93" cy="131.37" r="1.58" stroke="none"/></g><g data-point="43" fill="#b9ddc5" stroke="#b9ddc5" opacity="0.289"><line x1="369.53" y1="142.98" x2="368.13" y2="143.39" stroke-width="0.39" stroke-linecap="round"/><circle cx="369.53" cy="142.98" r="0.60" stroke="none"/></g><g data-point="44" fill="#b9ddc5" stroke="#b9ddc5" opacity="0.521"><line x1="506.88" y1="17.37" x2="509.76" y2="12.86" stroke-width="0.78" stroke-linecap="round"/><circle cx="506.88" cy="17.37" r="1.21" stroke="none"/></g><g data-point="45" fill="#e0c591" stroke="#e0c591" opacity="0.753"><line x1="492.22" y1="196.40" x2="495.69" y2="201.34" stroke-width="1.18" stroke-linecap="round"/><circle cx="492.22" cy="196.40" r="1.81" stroke="none"/></g><g data-point="46" fill="#b9ddc5" stroke="#b9ddc5" opacity="0.378"><line x1="338.36" y1="94.06" x2="335.43" y2="93.26" stroke-width="0.54" stroke-linecap="round"/><circle cx="338.36" cy="94.06" r="0.83" stroke="none"/></g><g data-point="47" fill="#b9ddc5" stroke="#b9ddc5" opacity="0.610"><line x1="699.13" y1="75.66" x2="712.61" y2="73.25" stroke-width="0.93" stroke-linecap="round"/><circle cx="699.13" cy="75.66" r="1.44" stroke="none"/></g><g data-point="48" fill="#b9ddc5" stroke="#b9ddc5" opacity="0.842"><line x1="286.95" y1="273.47" x2="275.44" y2="284.86" stroke-width="1.33" stroke-linecap="round"/><circle cx="286.95" cy="273.47" r="2.04" stroke="none"/></g><g data-point="49" fill="#b9ddc5" stroke="#b9ddc5" opacity="0.466"><line x1="407.08" y1="33.08" x2="405.84" y2="29.74" stroke-width="0.69" stroke-linecap="round"/><circle cx="407.08" cy="33.08" r="1.06" stroke="none"/></g><g data-point="50" fill="#e0c591" stroke="#e0c591" opacity="0.699"><line x1="509.54" y1="150.65" x2="513.77" y2="152.39" stroke-width="1.08" stroke-linecap="round"/><circle cx="509.54" cy="150.65" r="1.67" stroke="none"/></g><g data-point="51" fill="#b9ddc5" stroke="#b9ddc5" opacity="0.323"><line x1="349.81" y1="128.52" x2="347.71" y2="128.67" stroke-width="0.45" stroke-linecap="round"/><circle cx="349.81" cy="128.52" r="0.69" stroke="none"/></g><g data-point="52" fill="#b9ddc5" stroke="#b9ddc5" opacity="0.555"><line x1="581.81" y1="25.37" x2="588.40" y2="20.88" stroke-width="0.84" stroke-linecap="round"/><circle cx="581.81" cy="25.37" r="1.29" stroke="none"/></g><g data-point="53" fill="#b9ddc5" stroke="#b9ddc5" opacity="0.787"><line x1="446.74" y1="229.27" x2="447.22" y2="236.75" stroke-width="1.23" stroke-linecap="round"/><circle cx="446.74" cy="229.27" r="1.90" stroke="none"/></g><g data-point="54" fill="#b9ddc5" stroke="#b9ddc5" opacity="0.412"><line x1="350.27" y1="69.19" x2="347.39" y2="67.49" stroke-width="0.60" stroke-linecap="round"/><circle cx="350.27" cy="69.19" r="0.92" stroke="none"/></g><g data-point="55" fill="#e0c591" stroke="#e0c591" opacity="0.644"><line x1="488.39" y1="123.44" x2="491.07" y2="123.51" stroke-width="0.99" stroke-linecap="round"/><circle cx="488.39" cy="123.44" r="1.52" stroke="none"/></g><g data-point="56" fill="#b9ddc5" stroke="#b9ddc5" opacity="0.268"><line x1="384.09" y1="148.80" x2="383.09" y2="149.27" stroke-width="0.36" stroke-linecap="round"/><circle cx="384.09" cy="148.80" r="0.55" stroke="none"/></g><g data-point="57" fill="#b9ddc5" stroke="#b9ddc5" opacity="0.500"><line x1="464.58" y1="19.82" x2="465.59" y2="15.63" stroke-width="0.75" stroke-linecap="round"/><circle cx="464.58" cy="19.82" r="1.15" stroke="none"/></g><g data-point="58" fill="#b9ddc5" stroke="#b9ddc5" opacity="0.732"><line x1="505.56" y1="177.23" x2="509.78" y2="180.78" stroke-width="1.14" stroke-linecap="round"/><circle cx="505.56" cy="177.23" r="1.75" stroke="none"/></g><g data-point="59" fill="#b9ddc5" stroke="#b9ddc5" opacity="0.357"><line x1="338.85" y1="108.50" x2="336.15" y2="108.14" stroke-width="0.51" stroke-linecap="round"/><circle cx="338.85" cy="108.50" r="0.78" stroke="none"/></g><g data-point="60" fill="#e0c591" stroke="#e0c591" opacity="0.589"><line x1="657.09" y1="50.66" x2="667.92" y2="47.10" stroke-width="0.90" stroke-linecap="round"/><circle cx="657.09" cy="50.66" r="1.38" stroke="none"/></g><g data-point="61" fill="#b9ddc5" stroke="#b9ddc5" opacity="0.821"><line x1="363.08" y1="259.52" x2="357.46" y2="269.57" stroke-width="1.29" stroke-linecap="round"/><circle cx="363.08" cy="259.52" r="1.98" stroke="none"/></g><g data-point="62" fill="#b9ddc5" stroke="#b9ddc5" opacity="0.446"><line x1="379.80" y1="45.45" x2="377.66" y2="42.73" stroke-width="0.66" stroke-linecap="round"/><circle cx="379.80" cy="45.45" r="1.01" stroke="none"/></g><g data-point="63" fill="#b9ddc5" stroke="#b9ddc5" opacity="0.678"><line x1="504.21" y1="137.80" x2="507.98" y2="138.73" stroke-width="1.05" stroke-linecap="round"/><circle cx="504.21" cy="137.80" r="1.61" stroke="none"/></g><g data-point="64" fill="#b9ddc5" stroke="#b9ddc5" opacity="0.302"><line x1="361.26" y1="138.18" x2="359.59" y2="138.52" stroke-width="0.41" stroke-linecap="round"/><circle cx="361.26" cy="138.18" r="0.64" stroke="none"/></g><g data-point="65" fill="#e0c591" stroke="#e0c591" opacity="0.534"><line x1="534.86" y1="18.55" x2="539.07" y2="13.95" stroke-width="0.81" stroke-linecap="round"/><circle cx="534.86" cy="18.55" r="1.24" stroke="none"/></g><g data-point="66" fill="#b9ddc5" stroke="#b9ddc5" opacity="0.766"><line x1="478.70" y1="208.87" x2="481.32" y2="214.75" stroke-width="1.20" stroke-linecap="round"/><circle cx="478.70" cy="208.87" r="1.84" stroke="none"/></g><g data-point="67" fill="#b9ddc5" stroke="#b9ddc5" opacity="0.391"><line x1="340.94" y1="84.66" x2="337.96" y2="83.54" stroke-width="0.56" stroke-linecap="round"/><circle cx="340.94" cy="84.66" r="0.87" stroke="none"/></g><g data-point="68" fill="#b9ddc5" stroke="#b9ddc5" opacity="0.623"><line x1="721.86" y1="94.72" x2="736.88" y2="93.27" stroke-width="0.96" stroke-linecap="round"/><circle cx="721.86" cy="94.72" r="1.47" stroke="none"/></g><g data-point="69" fill="#b9ddc5" stroke="#b9ddc5" opacity="0.855"><line x1="228.41" y1="278.88" x2="212.22" y2="290.88" stroke-width="1.35" stroke-linecap="round"/><circle cx="228.41" cy="278.88" r="2.07" stroke="none"/></g><g data-point="70" fill="#e0c591" stroke="#e0c591" opacity="0.479"><line x1="427.22" y1="26.86" x2="426.72" y2="23.16" stroke-width="0.71" stroke-linecap="round"/><circle cx="427.22" cy="26.86" r="1.10" stroke="none"/></g><g data-point="71" fill="#b9ddc5" stroke="#b9ddc5" opacity="0.711"><line x1="510.18" y1="160.02" x2="514.54" y2="162.39" stroke-width="1.10" stroke-linecap="round"/><circle cx="510.18" cy="160.02" r="1.70" stroke="none"/></g><g data-point="72" fill="#b9ddc5" stroke="#b9ddc5" opacity="0.336"><line x1="344.34" y1="121.46" x2="341.99" y2="121.45" stroke-width="0.47" stroke-linecap="round"/><circle cx="344.34" cy="121.46" r="0.72" stroke="none"/></g><g data-point="73" fill="#b9ddc5" stroke="#b9ddc5" opacity="0.568"><line x1="611.07" y1="32.87" x2="619.24" y2="28.61" stroke-width="0.86" stroke-linecap="round"/><circle cx="611.07" cy="32.87" r="1.33" stroke="none"/></g><g data-point="74" fill="#b9ddc5" stroke="#b9ddc5" opacity="0.800"><line x1="419.89" y1="241.51" x2="418.46" y2="250.00" stroke-width="1.25" stroke-linecap="round"/><circle cx="419.89" cy="241.51" r="1.93" stroke="none"/></g><g data-point="75" fill="#e0c591" stroke="#e0c591" opacity="0.425"><line x1="359.41" y1="59.78" x2="356.71" y2="57.70" stroke-width="0.62" stroke-linecap="round"/><circle cx="359.41" cy="59.78" r="0.95" stroke="none"/></g><g data-point="76" fill="#b9ddc5" stroke="#b9ddc5" opacity="0.657"><line x1="495.14" y1="127.98" x2="498.26" y2="128.32" stroke-width="1.01" stroke-linecap="round"/><circle cx="495.14" cy="127.98" r="1.56" stroke="none"/></g><g data-point="77" fill="#b9ddc5" stroke="#b9ddc5" opacity="0.281"><line x1="374.96" y1="145.48" x2="373.72" y2="145.93" stroke-width="0.38" stroke-linecap="round"/><circle cx="374.96" cy="145.48" r="0.58" stroke="none"/></g><g data-point="78" fill="#b9ddc5" stroke="#b9ddc5" opacity="0.513"><line x1="490.23" y1="17.70" x2="492.35" y2="13.29" stroke-width="0.77" stroke-linecap="round"/><circle cx="490.23" cy="17.70" r="1.18" stroke="none"/></g><g data-point="79" fill="#b9ddc5" stroke="#b9ddc5" opacity="0.745"><line x1="498.48" y1="188.89" x2="502.31" y2="193.28" stroke-width="1.16" stroke-linecap="round"/><circle cx="498.48" cy="188.89" r="1.79" stroke="none"/></g><g data-point="80" fill="#e0c591" stroke="#e0c591" opacity="0.370"><line x1="337.90" y1="99.72" x2="335.04" y2="99.09" stroke-width="0.53" stroke-linecap="round"/><circle cx="337.90" cy="99.72" r="0.81" stroke="none"/></g><g data-point="81" fill="#b9ddc5" stroke="#b9ddc5" opacity="0.602"><line x1="683.73" y1="65.26" x2="696.20" y2="62.35" stroke-width="0.92" stroke-linecap="round"/><circle cx="683.73" cy="65.26" r="1.41" stroke="none"/></g><g data-point="82" fill="#b9ddc5" stroke="#b9ddc5" opacity="0.834"><line x1="318.55" y1="268.78" x2="309.52" y2="279.70" stroke-width="1.31" stroke-linecap="round"/><circle cx="318.55" cy="268.78" r="2.02" stroke="none"/></g><g data-point="83" fill="#b9ddc5" stroke="#b9ddc5" opacity="0.458"><line x1="395.87" y1="37.51" x2="394.24" y2="34.40" stroke-width="0.68" stroke-linecap="round"/><circle cx="395.87" cy="37.51" r="1.04" stroke="none"/></g><g data-point="84" fill="#b9ddc5" stroke="#b9ddc5" opacity="0.691"><line x1="508.06" y1="145.39" x2="512.14" y2="146.80" stroke-width="1.07" stroke-linecap="round"/><circle cx="508.06" cy="145.39" r="1.65" stroke="none"/></g><g data-point="85" fill="#e0c591" stroke="#e0c591" opacity="0.315"><line x1="353.85" y1="132.48" x2="351.91" y2="132.71" stroke-width="0.43" stroke-linecap="round"/><circle cx="353.85" cy="132.48" r="0.67" stroke="none"/></g><g data-point="86" fill="#b9ddc5" stroke="#b9ddc5" opacity="0.547"><line x1="563.73" y1="22.01" x2="569.38" y2="17.44" stroke-width="0.83" stroke-linecap="round"/><circle cx="563.73" cy="22.01" r="1.27" stroke="none"/></g><g data-point="87" fill="#b9ddc5" stroke="#b9ddc5" opacity="0.779"><line x1="460.53" y1="221.51" x2="461.95" y2="228.37" stroke-width="1.22" stroke-linecap="round"/><circle cx="460.53" cy="221.51" r="1.88" stroke="none"/></g><g data-point="88" fill="#b9ddc5" stroke="#b9ddc5" opacity="0.404"><line x1="345.93" y1="75.10" x2="342.98" y2="73.63" stroke-width="0.58" stroke-linecap="round"/><circle cx="345.93" cy="75.10" r="0.90" stroke="none"/></g><g data-point="89" fill="#b9ddc5" stroke="#b9ddc5" opacity="0.636"><line x1="741.23" y1="116.48" x2="757.67" y2="116.18" stroke-width="0.98" stroke-linecap="round"/><circle cx="741.23" cy="116.48" r="1.50" stroke="none"/></g><g data-point="90" fill="#e0c591" stroke="#e0c591" opacity="0.260"><line x1="389.85" y1="150.39" x2="388.99" y2="150.87" stroke-width="0.34" stroke-linecap="round"/><circle cx="389.85" cy="150.39" r="0.53" stroke="none"/></g><g data-point="91" fill="#b9ddc5" stroke="#b9ddc5" opacity="0.492"><line x1="449.66" y1="22.02" x2="450.05" y2="18.00" stroke-width="0.73" stroke-linecap="round"/><circle cx="449.66" cy="22.02" r="1.13" stroke="none"/></g><g data-point="92" fill="#b9ddc5" stroke="#b9ddc5" opacity="0.724"><line x1="508.25" y1="170.39" x2="512.58" y2="173.45" stroke-width="1.13" stroke-linecap="round"/><circle cx="508.25" cy="170.39" r="1.73" stroke="none"/></g><g data-point="93" fill="#b9ddc5" stroke="#b9ddc5" opacity="0.349"><line x1="340.41" y1="113.65" x2="337.83" y2="113.44" stroke-width="0.49" stroke-linecap="round"/><circle cx="340.41" cy="113.65" r="0.76" stroke="none"/></g></g></svg>
<div class="field_copy"><p class="field_kicker">AN EXERCISE IN DEPTH</p><h1 id="field-title">A little<br>distance.</h1><p class="field_caption">Near points leave<br>a longer trace.</p></div>
<div class="field_coordinates" aria-hidden="true"><span>44° / 12°</span><span>ORIGIN 01</span></div>
</section>
<footer class="field_controls">
<div class="field_control_top"><p class="field_status" data-field-status role="status">Still frame · 036.0</p><label class="field_traces"><input data-trails type="checkbox" checked> Show traces</label></div>
<div class="field_control_row"><button class="field_play" type="button" data-play aria-pressed="false">Play field <span aria-hidden="true">↗</span></button><label class="field_range">Frame position<input data-phase type="range" min="0" max="100" step="0.1" value="36" aria-label="Frame position" aria-describedby="field-motion-note"></label><output class="field_value" data-phase-value>036.0</output></div>
<p class="field_motion_note" id="field-motion-note" data-motion-note>Starts still. Move through a frame, or play at your own pace.</p>
</footer>
<noscript><p class="no-script">This is the still frame. Playback and frame controls require JavaScript.</p></noscript>
</main><script>/* Original learning example. Copyright (c) 2026 StyleGallery contributors. MIT. */
(() => {
'use strict';
const id = "21st-24203d73758a";
function reportPreviewError(error) { parent.postMessage({type:'sg-preview-error',id,message:String(error?.message || error || 'Initialization failed')},'*'); }
window.addEventListener('error', event => reportPreviewError(event.error || event.message));
window.addEventListener('unhandledrejection', event => reportPreviewError(event.reason));
try {
function createFieldState() {
return { phase: 0.36, running: false, reduced: false, trails: true };
}
function formatFieldPhase(state) {
return (state.phase * 100).toFixed(1).padStart(5, '0');
}
function updateField(state, action) {
if (action.type === 'motion') return { ...state, running: !state.reduced && !state.running };
if (action.type === 'preference') return { ...state, reduced: action.reduced, running: false };
if (action.type === 'pause') return { ...state, running: false };
if (action.type === 'trails') return { ...state, trails: action.enabled };
if (action.type === 'phase') return { ...state, phase: Math.round(Math.max(0, Math.min(1, action.value)) * 1000) / 1000 };
return state;
}
function projectPoint(index, phase) {
const fraction = value => value - Math.floor(value);
const angle = index * 2.3999632297;
const depth = 0.19 + fraction(index * 0.6180339887 + phase) * 0.81;
const radius = 22 + fraction(index * 0.3819660113) * 125;
const x = 440 + Math.cos(angle) * radius / depth;
const y = 122 + Math.sin(angle) * radius * 0.58 / depth;
const size = 0.5 + (1 - depth) * 1.95;
const trace = 0.016 + (1 - depth) * 0.075;
return { x, y, x2: x + (x - 440) * trace, y2: y + (y - 122) * trace, size, opacity: 0.25 + (1 - depth) * 0.75, warm: index % 5 === 0 };
}
let state = createFieldState();
const points = [...document.querySelectorAll('[data-point]')];
const play = document.querySelector('[data-play]');
const phase = document.querySelector('[data-phase]');
const readout = document.querySelector('[data-phase-value]');
const status = document.querySelector('[data-field-status]');
const preference = matchMedia('(prefers-reduced-motion: reduce)');
let frame = 0;
let startedTime = null;
let startedPhase = state.phase;
function renderField() {
points.forEach((node, index) => {
const p = projectPoint(index, state.phase), line = node.firstElementChild, dot = node.lastElementChild;
node.setAttribute('opacity', p.opacity.toFixed(3));
for (const [name, value] of Object.entries({ x1: p.x, y1: p.y, x2: p.x2, y2: p.y2 })) line.setAttribute(name, value.toFixed(2));
line.style.display = state.trails ? '' : 'none';
dot.setAttribute('cx', p.x.toFixed(2)); dot.setAttribute('cy', p.y.toFixed(2)); dot.setAttribute('r', p.size.toFixed(2));
});
phase.value = (state.phase * 100).toFixed(1);
readout.value = formatFieldPhase(state);
}
function renderPlayback() {
play.textContent = state.running ? 'Pause field Ⅱ' : 'Play field ↗';
play.setAttribute('aria-pressed', String(state.running));
play.disabled = state.reduced;
status.textContent = state.running ? 'Playing · point depth study' : 'Still frame · ' + formatFieldPhase(state);
document.querySelector('[data-motion-note]').textContent = state.reduced ? 'Reduced motion: playback stays off. Frame and trace controls still work.' : 'Starts still. Move through a frame, or play at your own pace.';
}
function stopFrame() { cancelAnimationFrame(frame); frame = 0; startedTime = null; }
function tick(time) {
if (!state.running) return;
if (startedTime === null) { startedTime = time; startedPhase = state.phase; }
state = updateField(state, { type: 'phase', value: (startedPhase + (time - startedTime) * 0.000035) % 1 });
renderField();
frame = requestAnimationFrame(tick);
}
function applyMotionPreference() {
state = updateField(state, { type: 'preference', reduced: preference.matches });
stopFrame(); renderPlayback();
}
play.addEventListener('click', () => { state = updateField(state, { type: 'motion' }); stopFrame(); renderPlayback(); if (state.running) frame = requestAnimationFrame(tick); });
phase.addEventListener('input', () => { state = updateField(updateField(state, { type: 'pause' }), { type: 'phase', value: Number(phase.value) / 100 }); stopFrame(); renderField(); renderPlayback(); });
document.querySelector('[data-trails]').addEventListener('change', event => { state = updateField(state, { type: 'trails', enabled: event.target.checked }); renderField(); });
preference.addEventListener('change', applyMotionPreference);
document.addEventListener('visibilitychange', () => { if (document.hidden) { state = updateField(state, { type: 'pause' }); stopFrame(); renderPlayback(); } });
window.addEventListener('pagehide', stopFrame);
applyMotionPreference(); renderField();
parent.postMessage({type:'sg-preview-ready',id},'*');
} catch (error) { reportPreviewError(error); }
})();
</script></body></html>
styles.css독립 구현
/* Original learning-example styling. Copyright (c) 2026 StyleGallery contributors. MIT. */
* { box-sizing: border-box; }
body { margin: 0; }
button, input, select { font: inherit; }
button { cursor: pointer; }
button:disabled { cursor: default; }
button, input, select { touch-action: manipulation; }
button:focus-visible, input:focus-visible, select:focus-visible, [tabindex="-1"]:focus-visible { outline: 3px solid #307865; outline-offset: 3px; }
[hidden] { display: none !important; }
h1, h2, p { margin: 0; }
svg { display: block; }
.sr-only { block-size: 1px; clip-path: inset(50%); inline-size: 1px; overflow: hidden; position: absolute; white-space: nowrap; }
.no-script { font: 12px/1.5 system-ui, sans-serif; padding: 12px; }
@media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation: none !important; scroll-behavior: auto !important; transition: none !important; } }
body { background: #dce4d9; color: #eaf0df; font: 13px/1.4 Arial, Helvetica, sans-serif; padding: 16px; }
.field_layout { background: #0c1814; border: 1px solid #34483b; border-radius: 17px; inline-size: 100%; margin-inline: auto; max-inline-size: 940px; overflow: hidden; }
.field_header { align-items: center; border-block-end: 1px solid #314237; display: flex; gap: 12px; justify-content: space-between; min-block-size: 45px; padding: 9px 20px; }
.field_label { align-items: center; display: flex; font-size: 10px; font-weight: 700; gap: 8px; letter-spacing: 1.7px; }
.field_mark { color: #c5ddb5; font-size: 22px; line-height: 1; }
.field_number { color: #94a18e; }
.field_edition { color: #a7b29c; font-size: 9px; letter-spacing: 1.5px; }
.field_stage { block-size: 235px; isolation: isolate; overflow: hidden; position: relative; }
.field_art { block-size: 100%; inline-size: 100%; inset: 0; position: absolute; z-index: -1; }
.field_copy { background: linear-gradient(90deg, #0c1814e8, #0c181400); block-size: 100%; inline-size: 61%; padding: 24px 26px; }
.field_kicker { color: #d0c09b; font-size: 8px; letter-spacing: 2.2px; }
.field_copy h1 { font-family: Georgia, 'Times New Roman', serif; font-size: clamp(38px, 7.6vw, 60px); font-weight: 400; letter-spacing: -2.5px; line-height: .94; margin-block: 14px 13px; }
.field_caption { color: #b1bea8; font-size: 11px; line-height: 1.45; }
.field_coordinates { bottom: 18px; color: #abbda5; display: flex; font: 8px/1.5 monospace; gap: 17px; letter-spacing: 1px; position: absolute; right: 20px; }
.field_controls { border-block-start: 1px solid #314237; padding: 10px 20px 12px; }
.field_control_top { align-items: center; display: flex; gap: 10px; justify-content: space-between; margin-block-end: 9px; }
.field_status { color: #c4d5b9; font-size: 10px; letter-spacing: .6px; }
.field_traces { align-items: center; color: #bdcab4; display: flex; font-size: 10px; gap: 4px; }
.field_traces input { accent-color: #c7dfad; block-size: 14px; inline-size: 14px; margin: 0; }
.field_control_row { align-items: center; display: flex; gap: 16px; }
.field_play { align-items: center; background: #d3e7bb; border: 0; border-radius: 7px; color: #152717; display: flex; flex-shrink: 0; font-size: 11px; font-weight: 700; gap: 21px; justify-content: space-between; min-block-size: 35px; min-inline-size: 126px; padding: 7px 12px; }
.field_play:hover { background: #e5f3d4; }
.field_play:disabled { background: #64765e; color: #fff; }
.field_range { color: #a8b79d; display: grid; flex: 1; font-size: 9px; gap: 4px; min-inline-size: 0; }
.field_range input { accent-color: #d1dfb3; inline-size: 100%; margin: 0; }
.field_value { color: #c7d9ba; font: 11px/1 monospace; min-inline-size: 36px; }
.field_motion_note { color: #9fad94; font-size: 9px; margin-block-start: 7px; }
.field_layout :focus-visible { outline-color: #f1c979; }
@media (min-width: 900px) { .field_stage { block-size: 340px; } .field_copy { padding: 44px 36px; } .field_copy h1 { font-size: 76px; } }
@media (max-width: 420px) { body { padding: 8px; } .field_header { padding-inline: 14px; } .field_edition { display: none; } .field_stage { block-size: 270px; } .field_copy { inline-size: 85%; padding: 25px 20px; } .field_copy h1 { font-size: 48px; } .field_controls { padding-inline: 14px; } .field_control_row { flex-wrap: wrap; gap: 10px; } .field_play { inline-size: 100%; } .field_range { min-inline-size: 160px; } .field_coordinates { bottom: 15px; } }
함께 쓰는 파일 5개 보기
script.js독립 구현
/* Original learning example. Copyright (c) 2026 StyleGallery contributors. MIT. */
(() => {
'use strict';
const id = "21st-24203d73758a";
function reportPreviewError(error) { parent.postMessage({type:'sg-preview-error',id,message:String(error?.message || error || 'Initialization failed')},'*'); }
window.addEventListener('error', event => reportPreviewError(event.error || event.message));
window.addEventListener('unhandledrejection', event => reportPreviewError(event.reason));
try {
function createFieldState() {
return { phase: 0.36, running: false, reduced: false, trails: true };
}
function formatFieldPhase(state) {
return (state.phase * 100).toFixed(1).padStart(5, '0');
}
function updateField(state, action) {
if (action.type === 'motion') return { ...state, running: !state.reduced && !state.running };
if (action.type === 'preference') return { ...state, reduced: action.reduced, running: false };
if (action.type === 'pause') return { ...state, running: false };
if (action.type === 'trails') return { ...state, trails: action.enabled };
if (action.type === 'phase') return { ...state, phase: Math.round(Math.max(0, Math.min(1, action.value)) * 1000) / 1000 };
return state;
}
function projectPoint(index, phase) {
const fraction = value => value - Math.floor(value);
const angle = index * 2.3999632297;
const depth = 0.19 + fraction(index * 0.6180339887 + phase) * 0.81;
const radius = 22 + fraction(index * 0.3819660113) * 125;
const x = 440 + Math.cos(angle) * radius / depth;
const y = 122 + Math.sin(angle) * radius * 0.58 / depth;
const size = 0.5 + (1 - depth) * 1.95;
const trace = 0.016 + (1 - depth) * 0.075;
return { x, y, x2: x + (x - 440) * trace, y2: y + (y - 122) * trace, size, opacity: 0.25 + (1 - depth) * 0.75, warm: index % 5 === 0 };
}
let state = createFieldState();
const points = [...document.querySelectorAll('[data-point]')];
const play = document.querySelector('[data-play]');
const phase = document.querySelector('[data-phase]');
const readout = document.querySelector('[data-phase-value]');
const status = document.querySelector('[data-field-status]');
const preference = matchMedia('(prefers-reduced-motion: reduce)');
let frame = 0;
let startedTime = null;
let startedPhase = state.phase;
function renderField() {
points.forEach((node, index) => {
const p = projectPoint(index, state.phase), line = node.firstElementChild, dot = node.lastElementChild;
node.setAttribute('opacity', p.opacity.toFixed(3));
for (const [name, value] of Object.entries({ x1: p.x, y1: p.y, x2: p.x2, y2: p.y2 })) line.setAttribute(name, value.toFixed(2));
line.style.display = state.trails ? '' : 'none';
dot.setAttribute('cx', p.x.toFixed(2)); dot.setAttribute('cy', p.y.toFixed(2)); dot.setAttribute('r', p.size.toFixed(2));
});
phase.value = (state.phase * 100).toFixed(1);
readout.value = formatFieldPhase(state);
}
function renderPlayback() {
play.textContent = state.running ? 'Pause field Ⅱ' : 'Play field ↗';
play.setAttribute('aria-pressed', String(state.running));
play.disabled = state.reduced;
status.textContent = state.running ? 'Playing · point depth study' : 'Still frame · ' + formatFieldPhase(state);
document.querySelector('[data-motion-note]').textContent = state.reduced ? 'Reduced motion: playback stays off. Frame and trace controls still work.' : 'Starts still. Move through a frame, or play at your own pace.';
}
function stopFrame() { cancelAnimationFrame(frame); frame = 0; startedTime = null; }
function tick(time) {
if (!state.running) return;
if (startedTime === null) { startedTime = time; startedPhase = state.phase; }
state = updateField(state, { type: 'phase', value: (startedPhase + (time - startedTime) * 0.000035) % 1 });
renderField();
frame = requestAnimationFrame(tick);
}
function applyMotionPreference() {
state = updateField(state, { type: 'preference', reduced: preference.matches });
stopFrame(); renderPlayback();
}
play.addEventListener('click', () => { state = updateField(state, { type: 'motion' }); stopFrame(); renderPlayback(); if (state.running) frame = requestAnimationFrame(tick); });
phase.addEventListener('input', () => { state = updateField(updateField(state, { type: 'pause' }), { type: 'phase', value: Number(phase.value) / 100 }); stopFrame(); renderField(); renderPlayback(); });
document.querySelector('[data-trails]').addEventListener('change', event => { state = updateField(state, { type: 'trails', enabled: event.target.checked }); renderField(); });
preference.addEventListener('change', applyMotionPreference);
document.addEventListener('visibilitychange', () => { if (document.hidden) { state = updateField(state, { type: 'pause' }); stopFrame(); renderPlayback(); } });
window.addEventListener('pagehide', stopFrame);
applyMotionPreference(); renderField();
parent.postMessage({type:'sg-preview-ready',id},'*');
} catch (error) { reportPreviewError(error); }
})();
LICENSE독립 구현
MIT License
Copyright (c) 2026 StyleGallery contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
This notice covers only the independently authored StyleGallery learning
example. The observed 21st source's reuse license remains unresolved.
NOTES.md독립 구현
# Depth field · Light and distance
Independent observation study. Domain: website consumer / Motion and Design Engineering. This is an unaffiliated learning example.
A deterministic point projection links size and trace length to depth. A stationary frame is useful before playback, and the same phase can be revisited with a native range control.
## Observation and original-source boundary
Observed public listing: [ATC shader](https://21st.dev/@xordev/components/atc-shader). Observed demo URL: https://cdn.21st.dev/larsen66/atc-shader/default/bundle.1756885507225.html.
The two ATC frames show a dark field of bright points and traces with different positions after time elapsed. They do not establish the original formula, exact speed, easing, loop length or input response.
Original source acquired: false. Original source acquisition status: unresolved. The supplied Stage 3 record says: 원래 게시자 license 빈값; 정확한 ATC 구현의 재사용 허락 미확인. That status is preserved, not re-investigated or replaced by this implementation's license.
The projection formula, green-and-amber palette, editorial lettering, initial still frame, frame slider, trail switch, pause control and reduced-motion policy are independently authored learning features.
The new copy, artwork, code and documentation are covered by the bundled MIT notice. No original source file, asset, distinctive copy, screenshot or brand is included. The evidence revision does not grant a license for the observed original.
## Evidence identity
sourceRevision: 0443ba9dca9ebd8fb0155c7370a07074c5366ed5939fc380a3e77d30fd5fc86c
The revision is SHA-256 of the JSON sourceEvidence object in the manifest. It binds the complete Stage 3 index and SceneBook hashes, this record's observation, and its named PNG and metadata hashes. It identifies actual research evidence, not an original Git checkout or a runtime result.
- Stage 3 index: artifacts/full-import/21st-author-sources/stage3/index.json, SHA-256 a9bd0da9712ea0a24e26b3612bfc87a09e13ee5cbb318eb97fe375d8c18d8c08.
- SceneBook: artifacts/full-import/21st-author-sources/stage3/SCENEBOOK.md, SHA-256 140b65a2248332fd415c9a4f42879cc060004f290ea83232af68558f2011e564.
- Relevant frame names: atc-initial, atc-later.
The initial PNG for this record was directly viewed during implementation as explicitly requested. Other states follow the Stage 3 transcription; their bytes and metadata hashes were checked, not claimed as newly observed interactions.
## Running and editing
Open index.html directly. The page contains its CSS, JavaScript and original SVG artwork and makes no outside requests. A content-security policy restricts runtime content to inline script/styles and data/blob images. There is no browser storage or remote write.
[styles.css](styles.css) and [script.js](script.js) are exact reference copies of the inline code. To edit a downloaded page, change its inline code or create your own build step. Editing only those companion copies does not change index.html. For repository generation, use StyleGallery/site as the working directory. The authoring modules are vendor/21st-studies/scenes.mjs and models.mjs; run node scripts/build-21st-studies.mjs there. Canonical Markdown lives in the parent StyleGallery checkout, so a binding such as motion/accessible-motion.md resolves as ../motion/accessible-motion.md from the site directory. Generation needs that parent corpus and the private research packet; running the download does not. A rebuild overwrites generated files.
Initial state: phase 0.36; playback stopped; traces shown; system reduced-motion preference applied during initialization.
## Interaction and sizing contract
The initial composition targets 640 × 420 CSS pixels. The field starts stationary; Play is explicit, a preference change stops it, and leaving the document pauses it without automatic resume. A native frame slider retains inspection without playback. At 320 pixels the controls wrap and the document owns scrolling. No horizontal pan or wheel interception is used. These are implementation contracts to check in the rendered page, not conformance results.
## Verification boundary
The generator verifies evidence bytes against recorded hashes, three-ID membership, unresolved source status, canonical Markdown anchors and hashes, named authored functions, JavaScript parsing, semantic landmarks, ID/label relationships, inline-copy equality, and forbidden runtime destinations/storage. This record's model checks cover finite projection, playback-to-pause frame revisit at matching tenth-percent precision, and preference invariants. These are source/model checks, not rendered interaction checks.
[KNOWLEDGE.json](KNOWLEDGE.json) holds actionable checks with status not_executed. runtime_verified: false. Browser interaction, full keyboard paths, screen-reader output, comfort and performance remain unverified. In the site checkout, research/full-import/preview-posters.md is the central SG initial-render report, artifacts/full-import/preview-posters/index.json owns capture receipts, and artifacts/full-import/preview-posters/visual-review.json owns frame observations. These private reports are not bundled in the download. Initial-render evidence does not change the interaction boundary.
consumer_reference: consumer-reference/agent-native/registry.json
KNOWLEDGE.json독립 구현
[
{
"id": "21st-24203d73758a-study-0",
"source": "motion/accessible-motion.md",
"anchor": "preference-changes-during-work",
"sourceSha256": "db0ad692d5bb3103a3cbe13ec08dbe5cf12552aed7ecf2c93b1d7cc0b8a34b8b",
"title": "Depth field · Light and distance · applyMotionPreference",
"principle": "One motion preference stops the presentation while preserving the inspectable phase and controls.",
"application": "A deterministic point projection links size and trace length to depth. A stationary frame is useful before playback, and the same phase can be revisited with a native range control. This binding describes the authored study; it is not verification of the original.",
"code": [
{
"file": "vendor/21st-studies/records/21st-24203d73758a/script.js",
"symbol": "applyMotionPreference",
"explanation": "This named function is part of the exact inline runtime in the downloadable HTML. codeFiles records the matching authored source hash."
}
],
"checks": [
{
"id": "21st-24203d73758a-0-applyMotionPreference",
"action": "Start playback, then enable system reduced motion. Disable reduced motion again and change Frame position.",
"expected": "Playback stops immediately, does not restart when the preference changes back, and manual frame inspection still works.",
"status": "not_executed"
}
],
"runtime_verified": false
},
{
"id": "21st-24203d73758a-study-1",
"source": "design-engineering/state-management/patterns/single-owner.md",
"anchor": "state-model-and-ownership",
"sourceSha256": "0657203d6f5cfcd2545c0f834b03e316efdc3f7d5fe8cbec868388bac9ce65ee",
"title": "Depth field · Light and distance · renderField",
"principle": "Point positions, slider value and readout share one accepted phase at 0.1-percent resolution. Playback derives that phase from elapsed time without accumulating rounding drift.",
"application": "A deterministic point projection links size and trace length to depth. A stationary frame is useful before playback, and the same phase can be revisited with a native range control. This binding describes the authored study; it is not verification of the original.",
"code": [
{
"file": "vendor/21st-studies/records/21st-24203d73758a/script.js",
"symbol": "renderField",
"explanation": "This named function is part of the exact inline runtime in the downloadable HTML. codeFiles records the matching authored source hash."
}
],
"checks": [
{
"id": "21st-24203d73758a-1-renderField",
"action": "Play and pause at a non-integer percentage, such as 36.4. Move to 80.0, then return to the displayed paused value. Turn Show traces off and on.",
"expected": "The point positions return to the exact paused coordinates; the slider and readout show the same tenth-percent value. The trace switch leaves the point positions unchanged.",
"status": "not_executed"
}
],
"runtime_verified": false
}
]
RETRIEVAL-LOG.md독립 구현
# Retrieval log
Task: create exactly three independent runnable learning examples from the Stage 3 public observation packet; preserve IDs and unresolved original-source acquisition.
1. Root AGENTS.md and README.md: domain ownership and guided local retrieval.
2. DOMAINS.md and GOVERNANCE.md: product-layer adaptation, generated artifacts, evidence separation.
3. SG discover: compiler configured at revision 6deb1970c80ed113b90be8d6ca617616bbe336c4. SG workflow read; SHA-256 0318a99707293af423e52b99b40cf1d87d9c7725e3ab06c5a231e3946f2bebb9. The provided workflow snapshot carries the same contract.
4. artifacts/full-import/21st-author-sources/stage3/SCENEBOOK.md and artifacts/full-import/21st-author-sources/stage3/index.json: primary implementation handoff. No original source code was inspected or copied.
5. design-engineering/index.md → decision-tree.md → component-contract.md → state-management/patterns/single-owner.md, state-management/recipes/multi-step-form.md and native-interaction-contracts.md: state, draft and disclosure ownership.
6. motion/index.md → accessible-motion.md and observed-choreography.md: stationary path and observation limits. Scroll-specific pin and choreography contracts were rejected because these three observations have no scroll choreography.
7. Opened atc-initial.png, sidebar-initial.png and form-initial.png through local image inspection because the delegated task explicitly requested actual PNG review. The authored output does not contain those images.
KNOWLEDGE.json contains exact current-authoring Markdown paths, real anchors and SHA-256 values for the applied sections. No design-terminology records are used. Named external sources inside the canonical guides were not re-checked in this lane; only local guidance and the supplied observation evidence were checked.
No canonical corpus edits, original 21st source-pack edits, shared UI edits, source-acquisition changes or new worker sessions belong to this lane.
consumer_reference: consumer-reference/agent-native/registry.json
BEHIND THE EXAMPLE
이 장면을 만드는 원리
Depth field · Light and distance · applyMotionPreference
One motion preference stops the presentation while preserving the inspectable phase and controls.
- 이 예제에서는
- A deterministic point projection links size and trace length to depth. A stationary frame is useful before playback, and the same phase can be revisited with a native range control. This binding describes the authored study; it is not verification of the original.
코드와 함께 확인하기
코드에서 찾기
applyMotionPreferencescript.jsThis named function is part of the exact inline runtime in the downloadable HTML. codeFiles records the matching authored source hash.
직접 해보기
Start playback, then enable system reduced motion. Disable reduced motion again and change Frame position.
살펴볼 변화Playback stops immediately, does not restart when the preference changes back, and manual frame inspection still works.
Depth field · Light and distance · renderField
Point positions, slider value and readout share one accepted phase at 0.1-percent resolution. Playback derives that phase from elapsed time without accumulating rounding drift.
- 이 예제에서는
- A deterministic point projection links size and trace length to depth. A stationary frame is useful before playback, and the same phase can be revisited with a native range control. This binding describes the authored study; it is not verification of the original.
코드와 함께 확인하기
코드에서 찾기
renderFieldscript.jsThis named function is part of the exact inline runtime in the downloadable HTML. codeFiles records the matching authored source hash.
직접 해보기
Play and pause at a non-integer percentage, such as 36.4. Move to 80.0, then return to the displayed paused value. Turn Show traces off and on.
살펴볼 변화The point positions return to the exact paused coordinates; the slider and readout show the same tenth-percent value. The trace switch leaves the point positions unchanged.
