V-009 Media medium

Cinematic Filmstrip Viewer シネマティックフィルムストリップ

CSS gradient landscape frames with film perforation aesthetic, interactive carousel navigation, and frame counters. 風景風グラデーションのフィルムコマ送りUIと現在位置表示。

ライブデモ Live Demo

Scene 01: Golden Sunset Valley1 / 3

概要・用途・特徴Overview, Usage & Features

何ができるかWhat it does

CSS gradient landscape frames with film perforation aesthetic, interactive carousel navigation, and frame counters.

風景風グラデーションのフィルムコマ送りUIと現在位置表示。

どこで使うかWhere to use

Portfolio, product showcase, landing page.

ポートフォリオ、製品紹介、ランディングページ。

特徴Key features

CSS gradient landscape frames with film perforation aesthetic, interactive carousel navigation, and frame counters.

風景風グラデーションのフィルムコマ送りUIと現在位置表示。

調整可能パラメータ Adjustable Parameters

Parameter Default Description
--demo-accent#f59e0bAccent color for the panel border and keyboard focus ring.

実装コード Implementation Code

<div class="demo-stage cf-root" role="region" aria-label="Cinematic Filmstrip Viewer"><div class="cf-theater"><div class="cf-sprocket-bar cf-top" aria-hidden="true"><span class="cf-hole"></span><span class="cf-hole"></span><span class="cf-hole"></span><span class="cf-hole"></span><span class="cf-hole"></span><span class="cf-hole"></span><span class="cf-hole"></span></div><div class="cf-screen-area"><div class="cf-frame cf-active" data-idx="1" data-caption="Scene 01: Golden Sunset Valley"><div class="cf-visual cf-scene-1" aria-hidden="true"></div></div><div class="cf-frame" data-idx="2" data-caption="Scene 02: Cyber Dusk Horizon" style="display:none;"><div class="cf-visual cf-scene-2" aria-hidden="true"></div></div><div class="cf-frame" data-idx="3" data-caption="Scene 03: Emerald Aurora Peak" style="display:none;"><div class="cf-visual cf-scene-3" aria-hidden="true"></div></div></div><div class="cf-sprocket-bar cf-bottom" aria-hidden="true"><span class="cf-hole"></span><span class="cf-hole"></span><span class="cf-hole"></span><span class="cf-hole"></span><span class="cf-hole"></span><span class="cf-hole"></span><span class="cf-hole"></span></div><div class="cf-controls"><div class="cf-caption-meta"><span class="cf-caption-text">Scene 01: Golden Sunset Valley</span><span class="cf-counter-text" aria-live="polite">1 / 3</span></div><div class="cf-btn-row"><button type="button" class="cf-nav-btn cf-prev" aria-label="View previous film frame">Prev</button><button type="button" class="cf-nav-btn cf-next" aria-label="View next film frame">Next</button></div></div></div></div>
<script>
document.querySelectorAll('.cf-root').forEach(root => {
  const frames = root.querySelectorAll('.cf-frame');
  const prevBtn = root.querySelector('.cf-prev');
  const nextBtn = root.querySelector('.cf-next');
  const captionEl = root.querySelector('.cf-caption-text');
  const counterEl = root.querySelector('.cf-counter-text');
  if (!frames.length || !prevBtn || !nextBtn || !captionEl || !counterEl) return;
  let current = 0;
  const showFrame = (idx) => {
    frames.forEach((f, i) => {
      const active = i === idx;
      f.style.display = active ? 'block' : 'none';
      f.classList.toggle('cf-active', active);
    });
    const curFrame = frames[idx];
    captionEl.textContent = curFrame.dataset.caption;
    counterEl.textContent = `${idx + 1} / ${frames.length}`;
  };
  prevBtn.addEventListener('click', () => {
    current = (current - 1 + frames.length) % frames.length;
    showFrame(current);
  });
  nextBtn.addEventListener('click', () => {
    current = (current + 1) % frames.length;
    showFrame(current);
  });
});
</script>
.cf-root {
  box-sizing: border-box;
  width: 100%;
  max-width: 560px;
  max-height: 420px;
  min-width: 0;
  margin: 0 auto;
  padding: 16px;
  background: #0e0e12;
  border-radius: 16px;
  border: 1px solid #23232c;
  box-shadow: 0 16px 36px rgba(0, 0, 0, 0.7);
  font-family: system-ui, -apple-system, sans-serif;
  color: #eaeaf0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}
.cf-root *, .cf-root *::before, .cf-root *::after {
  box-sizing: border-box;
  min-width: 0;
}
.cf-root .cf-theater {
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  background: #16161c;
  border-radius: 12px;
  padding: 10px;
  border: 1px solid #2d2d38;
}
.cf-root .cf-sprocket-bar {
  width: 100%;
  display: flex;
  justify-content: space-between;
  padding: 4px 6px;
}
.cf-root .cf-hole {
  width: 12px;
  height: 8px;
  background: #09090b;
  border-radius: 2px;
  border: 1px solid #2a2a33;
}
.cf-root .cf-screen-area {
  position: relative;
  width: 100%;
  height: 140px;
  background: #050507;
  border-radius: 6px;
  overflow: hidden;
  border: 1px solid #2a2a33;
}
.cf-root .cf-frame {
  width: 100%;
  height: 100%;
}
.cf-root .cf-visual {
  width: 100%;
  height: 100%;
}
.cf-root .cf-scene-1 {
  background: linear-gradient(180deg, #f59e0b 0%, #ea580c 40%, #7c2d12 70%, #1c1917 100%);
}
.cf-root .cf-scene-2 {
  background: linear-gradient(180deg, #3b82f6 0%, #8b5cf6 45%, #4c1d95 75%, #0f172a 100%);
}
.cf-root .cf-scene-3 {
  background: linear-gradient(180deg, #10b981 0%, #059669 40%, #064e3b 70%, #022c22 100%);
}
.cf-root .cf-controls {
  width: 100%;
  margin-top: 10px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 8px;
}
.cf-root .cf-caption-meta {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.cf-root .cf-caption-text {
  font-size: 12px;
  font-weight: 600;
  color: #f1f5f9;
}
.cf-root .cf-counter-text {
  font-size: 10px;
  color: #94a3b8;
}
.cf-root .cf-btn-row {
  display: flex;
  gap: 6px;
}
.cf-root .cf-nav-btn {
  appearance: none;
  border: 1px solid #3b3b4a;
  background: #252530;
  color: #ffffff;
  padding: 6px 14px;
  border-radius: 6px;
  font-size: 11px;
  font-weight: 600;
  cursor: pointer;
}
.cf-root .cf-nav-btn:hover {
  background: #323242;
  border-color: #6366f1;
}
.cf-root .cf-nav-btn:focus-visible {
  outline: 2px solid #818cf8;
  outline-offset: 2px;
}
@media (prefers-reduced-motion: no-preference) {
  .cf-root .cf-nav-btn {
    transition: background 0.2s ease, border-color 0.2s ease, transform 0.1s ease;
  }
  .cf-root .cf-nav-btn:active {
    transform: scale(0.95);
  }
  .cf-root .cf-visual {
    transition: opacity 0.3s ease;
  }
}
.cf-root{max-height:none;--demo-accent:#f59e0b;border-color:var(--demo-accent)}.cf-root button:focus-visible{outline-color:var(--demo-accent)}
.cf-root .cf-visual{position:relative;overflow:hidden}.cf-root .cf-visual::before{content:"";position:absolute;width:56px;height:56px;left:66%;top:18%;border-radius:50%;background:#fff5bc;box-shadow:0 0 34px #ffdd9d66}.cf-root .cf-visual::after{content:"";position:absolute;inset:32% -3% -1%;background:#281d34;clip-path:polygon(0 77%,19% 30%,33% 57%,55% 3%,76% 55%,90% 24%,100% 72%,100% 100%,0 100%)}.cf-root .cf-scene-2::before{background:#dec4ff;box-shadow:0 0 35px #a87aff}.cf-root .cf-scene-2::after{background:#171630}.cf-root .cf-scene-3::after{background:#052e26}.cf-root .cf-scene-3::before{width:180px;height:25px;left:25%;border-radius:50%;transform:rotate(-20deg);filter:blur(12px);background:#71ffd9}
import { useState } from "react";
import "./V-009.css";
const captions = ["Scene 01: Golden Sunset Valley", "Scene 02: Cyber Dusk Horizon", "Scene 03: Emerald Aurora Peak"];
function CinematicFilmstrip() {
  const [selected, setSelected] = useState(0);
  return <div className="demo-stage cf-root" role="region" aria-label="Cinematic Filmstrip Viewer"><div className="cf-theater"><div className="cf-sprocket-bar cf-top" aria-hidden="true"><span className="cf-hole" /><span className="cf-hole" /><span className="cf-hole" /><span className="cf-hole" /><span className="cf-hole" /><span className="cf-hole" /><span className="cf-hole" /></div><div className="cf-screen-area"><div data-idx="1" data-caption="Scene 01: Golden Sunset Valley" className={"cf-frame" + (selected === 0 ? " cf-active" : "")} style={{ display: selected === 0 ? "block" : "none" }}><div className="cf-visual cf-scene-1" aria-hidden="true" /></div><div data-idx="2" data-caption="Scene 02: Cyber Dusk Horizon" className={"cf-frame" + (selected === 1 ? " cf-active" : "")} style={{ display: selected === 1 ? "block" : "none" }}><div className="cf-visual cf-scene-2" aria-hidden="true" /></div><div data-idx="3" data-caption="Scene 03: Emerald Aurora Peak" className={"cf-frame" + (selected === 2 ? " cf-active" : "")} style={{ display: selected === 2 ? "block" : "none" }}><div className="cf-visual cf-scene-3" aria-hidden="true" /></div></div><div className="cf-sprocket-bar cf-bottom" aria-hidden="true"><span className="cf-hole" /><span className="cf-hole" /><span className="cf-hole" /><span className="cf-hole" /><span className="cf-hole" /><span className="cf-hole" /><span className="cf-hole" /></div><div className="cf-controls"><div className="cf-caption-meta"><span className="cf-caption-text">{captions[selected]}</span><span className="cf-counter-text" aria-live="polite">{selected + 1 + " / 3"}</span></div><div className="cf-btn-row"><button type="button" className="cf-nav-btn cf-prev" aria-label="View previous film frame" onClick={() => setSelected((value) => (value + 2) % 3)}>{"Prev"}</button><button type="button" className="cf-nav-btn cf-next" aria-label="View next film frame" onClick={() => setSelected((value) => (value + 1) % 3)}>{"Next"}</button></div></div></div></div>;
}
export {
  CinematicFilmstrip as default
};
.cf-root {
  box-sizing: border-box;
  width: 100%;
  max-width: 560px;
  max-height: 420px;
  min-width: 0;
  margin: 0 auto;
  padding: 16px;
  background: #0e0e12;
  border-radius: 16px;
  border: 1px solid #23232c;
  box-shadow: 0 16px 36px rgba(0, 0, 0, 0.7);
  font-family: system-ui, -apple-system, sans-serif;
  color: #eaeaf0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}
.cf-root *, .cf-root *::before, .cf-root *::after {
  box-sizing: border-box;
  min-width: 0;
}
.cf-root .cf-theater {
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  background: #16161c;
  border-radius: 12px;
  padding: 10px;
  border: 1px solid #2d2d38;
}
.cf-root .cf-sprocket-bar {
  width: 100%;
  display: flex;
  justify-content: space-between;
  padding: 4px 6px;
}
.cf-root .cf-hole {
  width: 12px;
  height: 8px;
  background: #09090b;
  border-radius: 2px;
  border: 1px solid #2a2a33;
}
.cf-root .cf-screen-area {
  position: relative;
  width: 100%;
  height: 140px;
  background: #050507;
  border-radius: 6px;
  overflow: hidden;
  border: 1px solid #2a2a33;
}
.cf-root .cf-frame {
  width: 100%;
  height: 100%;
}
.cf-root .cf-visual {
  width: 100%;
  height: 100%;
}
.cf-root .cf-scene-1 {
  background: linear-gradient(180deg, #f59e0b 0%, #ea580c 40%, #7c2d12 70%, #1c1917 100%);
}
.cf-root .cf-scene-2 {
  background: linear-gradient(180deg, #3b82f6 0%, #8b5cf6 45%, #4c1d95 75%, #0f172a 100%);
}
.cf-root .cf-scene-3 {
  background: linear-gradient(180deg, #10b981 0%, #059669 40%, #064e3b 70%, #022c22 100%);
}
.cf-root .cf-controls {
  width: 100%;
  margin-top: 10px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 8px;
}
.cf-root .cf-caption-meta {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.cf-root .cf-caption-text {
  font-size: 12px;
  font-weight: 600;
  color: #f1f5f9;
}
.cf-root .cf-counter-text {
  font-size: 10px;
  color: #94a3b8;
}
.cf-root .cf-btn-row {
  display: flex;
  gap: 6px;
}
.cf-root .cf-nav-btn {
  appearance: none;
  border: 1px solid #3b3b4a;
  background: #252530;
  color: #ffffff;
  padding: 6px 14px;
  border-radius: 6px;
  font-size: 11px;
  font-weight: 600;
  cursor: pointer;
}
.cf-root .cf-nav-btn:hover {
  background: #323242;
  border-color: #6366f1;
}
.cf-root .cf-nav-btn:focus-visible {
  outline: 2px solid #818cf8;
  outline-offset: 2px;
}
@media (prefers-reduced-motion: no-preference) {
  .cf-root .cf-nav-btn {
    transition: background 0.2s ease, border-color 0.2s ease, transform 0.1s ease;
  }
  .cf-root .cf-nav-btn:active {
    transform: scale(0.95);
  }
  .cf-root .cf-visual {
    transition: opacity 0.3s ease;
  }
}
.cf-root{max-height:none;--demo-accent:#f59e0b;border-color:var(--demo-accent)}.cf-root button:focus-visible{outline-color:var(--demo-accent)}
.cf-root .cf-visual{position:relative;overflow:hidden}.cf-root .cf-visual::before{content:"";position:absolute;width:56px;height:56px;left:66%;top:18%;border-radius:50%;background:#fff5bc;box-shadow:0 0 34px #ffdd9d66}.cf-root .cf-visual::after{content:"";position:absolute;inset:32% -3% -1%;background:#281d34;clip-path:polygon(0 77%,19% 30%,33% 57%,55% 3%,76% 55%,90% 24%,100% 72%,100% 100%,0 100%)}.cf-root .cf-scene-2::before{background:#dec4ff;box-shadow:0 0 35px #a87aff}.cf-root .cf-scene-2::after{background:#171630}.cf-root .cf-scene-3::after{background:#052e26}.cf-root .cf-scene-3::before{width:180px;height:25px;left:25%;border-radius:50%;transform:rotate(-20deg);filter:blur(12px);background:#71ffd9}

仕組みとカスタマイズHow It Works & Customization

仕組みHow it works

Scoped CSS creates the visual design; small vanilla JavaScript handlers update the local demo state.

スコープ化したCSSで描画し、Vanilla JavaScriptでデモ内の状態だけを更新します。

カスタマイズ方法Customization

Accent color for the panel border and keyboard focus ring.

パネルの枠線とキーボードフォーカスの色を変更します。

注意点Caveats

The sample content is fictional. No external assets or libraries are required. Motion follows the operating system preference. Demo labels are English; surrounding documentation supports Japanese and English.

デモ内容は架空です。外部素材・ライブラリは不要。OSの動きを減らす設定に対応します。デモ内のラベルは英語、解説は日英対応です。

よくある質問 Frequently Asked Questions

Does this demo need external libraries? 外部ライブラリは必要ですか?

The sample content is fictional. No external assets or libraries are required. Motion follows the operating system preference. Demo labels are English; surrounding documentation supports Japanese and English.

デモ内容は架空です。外部素材・ライブラリは不要。OSの動きを減らす設定に対応します。デモ内のラベルは英語、解説は日英対応です。

AIへの指示テンプレート AI Prompt Template

以下をAIに貼り付けると実装を依頼できます。 Paste the following into your AI assistant to request implementation.