/* ===== Fraud Contest LP — Components ===== */
const { useState, useEffect, useRef } = React;
/* ---------- Icons (tiny SVG, no external) ---------- */
const IconVideo = ({size=36}) => (
);
const IconPhoto = ({size=36}) => (
);
const IconCrown = ({size=22}) => (
);
const IconArrow = () => →;
const IconShare = () => (
);
const IconMail = () => (
);
const IconUpload = () => (
);
const IconEdit = ({size=40}) => (
);
const IconBulb = ({size=18}) => (
);
/* ---------- Reveal hook ---------- */
function useReveal(){
useEffect(()=>{
const els = document.querySelectorAll('.reveal');
const io = new IntersectionObserver((entries)=>{
entries.forEach(e=>{
if(e.isIntersecting){ e.target.classList.add('in'); io.unobserve(e.target); }
});
},{threshold:0.12});
els.forEach(el=>io.observe(el));
return ()=>io.disconnect();
},[]);
}
/* ---------- Countdown hook ---------- */
function useCountdown(target){
const [t,setT] = useState(()=>diff(target));
useEffect(()=>{
const id = setInterval(()=>setT(diff(target)),1000);
return ()=>clearInterval(id);
},[target]);
return t;
}
function diff(target){
const ms = Math.max(0, target - Date.now());
const d = Math.floor(ms/86400000);
const h = Math.floor(ms/3600000)%24;
const m = Math.floor(ms/60000)%60;
const s = Math.floor(ms/1000)%60;
return {d,h,m,s,done:ms===0};
}
/* ---------- Count-up when in view ---------- */
function CountUp({to, dur=1600, suffix="", className=""}){
const [v,setV] = useState(0);
const ref = useRef(null);
useEffect(()=>{
const el = ref.current; if(!el) return;
const io = new IntersectionObserver((es)=>{
es.forEach(e=>{
if(e.isIntersecting){
const start = performance.now();
const tick = (now)=>{
const p = Math.min(1,(now-start)/dur);
const eased = 1 - Math.pow(1-p, 3);
setV(Math.floor(eased*to));
if(p<1) requestAnimationFrame(tick);
else setV(to);
};
requestAnimationFrame(tick);
io.unobserve(el);
}
});
},{threshold:0.4});
io.observe(el);
return ()=>io.disconnect();
},[to,dur]);
return {v.toLocaleString()}{suffix};
}
/* =================================================================== */
/* NAV */
/* =================================================================== */
function Nav(){
const [open,setOpen] = useState(false);
const links = [
["#about","概要"],
["#categories","募集部門"],
["#schedule","スケジュール"],
["#prizes","賞品"],
["#workshop","ワークショップ"],
["#gallery","過去作品"],
["#form","応募フォーム"],
];
return (
);
}
/* =================================================================== */
/* HERO */
/* =================================================================== */
function Hero(){
const [tc,setTc] = useState("00:00:00:00");
useEffect(()=>{
const start = performance.now();
const id = setInterval(()=>{
const ms = performance.now() - start;
const f = Math.floor((ms/1000*30))%30;
const s = Math.floor(ms/1000)%60;
const m = Math.floor(ms/60000)%60;
const h = Math.floor(ms/3600000)%24;
setTc(`${String(h).padStart(2,'0')}:${String(m).padStart(2,'0')}:${String(s).padStart(2,'0')}:${String(f).padStart(2,'0')}`);
},100);
return ()=>clearInterval(id);
},[]);
return (
);
}
/* =================================================================== */
/* COUNTDOWN bar */
/* =================================================================== */
function Countdown(){
// Target: 2026-10-31 23:59:59 JST
const target = new Date("2026-10-31T23:59:59+09:00").getTime();
const {d,h,m,s} = useCountdown(target);
return (
APPLICATION OPEN
DEADLINE · 2026.10.31 23:59 JST
応募締切まで
{String(d).padStart(3,'0')}
Days
{String(h).padStart(2,'0')}
Hours
{String(m).padStart(2,'0')}
Minutes
{String(s).padStart(2,'0')}
Seconds
);
}
/* =================================================================== */
/* OVERVIEW */
/* =================================================================== */
function Overview(){
return (
About the Contest · 01
君のクリエイティブで、闇バイトから誰かを救え。
大分県警察が主催するメッセージコンテストは、今年で3回目。
SNSや動画投稿サイトを通じて、闇バイトの手口とその危険性を身近な仲間や家族に伝える——
そんなメッセージ動画・静止画を県内から広く募集します。
入賞作品は大分県警察のSNS、YouTube、県内の広報啓発イベントで活用され、
あなたの一本が、本当に誰かの被害を止めるかもしれません。
#STOP闇バイト被害
#友達を救うのは君だ
#大分県警察
);
}
/* =================================================================== */
/* CATEGORIES */
/* =================================================================== */
function Categories(){
return (
Categories · 02
2つの部門で募集中
伝えたいメッセージに合わせて、動画または静止画のどちらかで応募できます。両部門への応募も可能です。
A
CATEGORY A · MOVIE
動画部門
YouTubeやSNS等での広報啓発に活用できる、15秒のショート動画を募集。
ドラマ仕立て、アニメーション、インタビュー形式などなど形式は自由です。
尺 15秒
MP4 / MOV
〜100MB
B
CATEGORY B · PHOTO
静止画部門
SNS等で拡散しやすい、1枚の静止画・ポスター・イラスト・グラフィックを募集。
シンプルで伝わる一枚で、誰かの気づきを生んでください。
JPEG / PDF
A3
〜10MB
テーマは自由
闇バイトの危険性が伝われば、切り口はあなた次第。
例:甘い勧誘の手口 / “ホワイト案件”の罠 / 抜け出せない恐怖
);
}
/* =================================================================== */
/* SCHEDULE */
/* =================================================================== */
function Schedule(){
// 通常は現在日時で判定。?preview=2026-08-01 のように指定すると基準日を差し替えてプレビュー可能。
const preview = new URLSearchParams(window.location.search).get('preview');
const now = preview ? new Date(preview).getTime() : Date.now();
const steps = [
{date:"2026.07.01", h:"応募受付スタート", p:"特設サイトより応募開始", end:"2026-07-01T23:59:59+09:00", link:{href:"#form", label:"応募はこちら →"}},
{date:"2026.07.18", h:"1Dayワークショップ", p:"スマホで動画・静止画制作を学べる1日講座(申込締切 7/17 14:59)", end:"2026-07-18T23:59:59+09:00", link:{href:"#workshop", label:"申し込みはこちら →"}},
{date:"2026.10.31", h:"応募締切", p:"23:59 まで / 必着", end:"2026-10-31T23:59:59+09:00"},
{date:"2026.11頃", h:"審査期間", p:"大分県警察・特別審査員による選考", end:"2026-11-30T23:59:59+09:00"},
{date:"2026.12頃", h:"結果発表・表彰式", p:"特設サイト・大分県警察SNSにて発表", end:"2026-12-15T23:59:59+09:00"},
];
return (
{steps.map((s,i)=>{
const past = now > new Date(s.end).getTime();
return (
{past ? '✓' : String(i+1).padStart(2,'0')}
{s.date}
{s.h}
{s.p}{s.link && <>
{s.link.label}>}
);
})}
);
}
/* =================================================================== */
/* PRIZES */
/* =================================================================== */
function Prizes(){
return (
Prizes · 04
賞品内容
Amazonギフトカード
各部門ごとに受賞者を選出。入賞作品は大分県警察のSNS等で広報啓発に活用させていただきます。
★
Grand Prix · 各部門 1作品
グランプリ
万円分
最も優秀で、広く伝わるメッセージ性の高い作品に贈られます。
A
Silver · 各部門 1作品
準グランプリ
万円分
独創性・訴求力に優れた作品に贈られます。
B
Jury Award · 各部門 1作品
審査員特別賞
万円分
審査員が特に注目した作品に贈られます。
P
Participation
参加賞
円分
ご応募いただいた方への参加賞です。数量に限りがございますので、あらかじめご了承ください。
);
}
/* =================================================================== */
/* STEPS */
/* =================================================================== */
function Steps(){
const steps = [
{n:"01", h:"メッセージを考える", p:"テーマは自由。闇バイトの危険性が伝わる切り口を、自由な発想で見つけよう。"},
{n:"02", h:"作品をつくる", p:"動画 or 静止画、得意な表現で。スマホだけでもOK。"},
{n:"03", h:"フォームに入力", p:"応募者情報と作品情報、ファイルをアップロード。"},
{n:"04", h:"応募完了", p:"受付確認メールが届いたら完了。結果発表を待とう。"},
];
return (
How to Apply · 05
応募の4ステップ
);
}
/* =================================================================== */
/* WORKSHOP */
/* =================================================================== */
const WORKSHOP_FORM_URL = "https://docs.google.com/forms/d/e/1FAIpQLSc7F6g0Ge-jD3rE7UXVDmAjG5RcitbgAGCpfIGq3rbasqJNdg/viewform";
function Workshop(){
// 締切判定。?preview=YYYY-MM-DD で基準日を差し替えてプレビュー可能(Scheduleと共通)。
const preview = new URLSearchParams(window.location.search).get('preview');
const now = preview ? new Date(preview).getTime() : Date.now();
// 7/17 14:59までは申込ボタン有効。7/17 15:00以降は締切表示に切替。
const closed = now > new Date("2026-07-17T14:59:59+09:00").getTime();
// 締切後〜7/18 11:59までは直接事務局への問い合わせ案内を表示。7/18 12:00以降は締切文のみ。
const over = now > new Date("2026-07-18T11:59:59+09:00").getTime();
const info = [
{k:"対象", v:"大分県内在住の16〜29歳の方(個人・団体問わず)"},
{k:"日程", v:"2026年7月18日(土) 13:30スタート(受付13:00)"},
{k:"場所", v:"Garraway0(大分市府内町1丁目1-12)"},
{k:"申込締切", v:"2026年7月17日(金)14:59まで", hot:true},
];
return (
Workshop · 06
1Day ワークショップ開催!
全国・県内の広告業界で活躍するクリエイターを講師として迎え、スマホでの動画・静止画制作ワークショップを開催します。
{info.map((x)=>(
- {x.k}
- {x.v}
))}
{closed ? (
<>
ワークショップに申し込む
ワークショップの申込期限(2026年7月17日 14:59)は終了しました。
{!over && (
参加希望の場合は直接事務局までお問い合わせください。
)}
>
) : (
<>
ワークショップに申し込む
別ウィンドウでGoogleフォームが開きます
>
)}
);
}
/* =================================================================== */
/* GALLERY (past winners placeholders) */
/* =================================================================== */
function Gallery(){
/* ===== 受賞作品の登録 =====================================================
動画(YouTube): { y, t, cat:"VIDEO", youtube:"<11桁のYouTube動画ID>", poster?:"画像パス(任意)" }
例) https://www.youtube.com/watch?v=AbCdEfGhIjk → youtube:"AbCdEfGhIjk"
poster 省略時は YouTube のサムネイルを自動取得します。
静止画 : { y, t, cat:"PHOTO", image:"画像パス(例 uploads/winners/2025-photo.jpg)" }
※ youtube / image が未設定の項目は、従来どおりプレースホルダー表示になります。
========================================================================= */
const items = [
// 動画部門
{y:"2025", t:"グランプリ", cat:"VIDEO", youtube:"stLL-3GHibU"},
{y:"2025", t:"準グランプリ", cat:"VIDEO", youtube:"o1rEu9fNtoo"},
{y:"2025", t:"審査員特別賞", cat:"VIDEO", youtube:"6xZxO4TCRNc"},
// 静止画部門
{y:"2025", t:"グランプリ", cat:"PHOTO", image:"uploads/winners/2025-grandprix.jpg"},
{y:"2025", t:"準グランプリ", cat:"PHOTO", image:"uploads/winners/2025-second.jpg"},
{y:"2025", t:"審査員特別賞", cat:"PHOTO", image:"uploads/winners/2025-jury.jpg"},
];
const [active, setActive] = useState(null);
const isVideo = (it)=> it.cat === "VIDEO";
const ready = (it)=> isVideo(it) ? !!it.youtube : !!it.image;
const thumb = (it)=> isVideo(it)
? (it.poster || (it.youtube ? `https://i.ytimg.com/vi/${it.youtube}/maxresdefault.jpg` : ""))
: (it.image || "");
useEffect(()=>{
if(active===null) return;
const onKey = (e)=>{ if(e.key==="Escape") setActive(null); };
window.addEventListener("keydown", onKey);
return ()=>window.removeEventListener("keydown", onKey);
},[active]);
return (
Past Winners · 07
過去の受賞作品
第1回・第2回の受賞作品を一部ご紹介。どれも見る人の心に残る力作です。
{items.map((it,i)=>{
const src = thumb(it);
const can = ready(it);
return (
can && setActive(i)}
role={can?"button":undefined}
tabIndex={can?0:undefined}
onKeyDown={(e)=>{ if(can && (e.key==="Enter"||e.key===" ")){ e.preventDefault(); setActive(i); } }}>
{src
?

{ const t=e.currentTarget; if(t.dataset.fb) return; t.dataset.fb="1"; t.src=`https://i.ytimg.com/vi/${it.youtube}/mqdefault.jpg`; }
: undefined}/>
:
[{it.cat}]
}
{it.cat}
{it.t}
{it.y}
);
})}
※ 一部の作品はイメージです。実際の作品は大分県警察公式SNS・YouTubeにてご覧いただけます。
{active!==null && (
setActive(null)}>
e.stopPropagation()}>
{isVideo(items[active])
?
:
![{`${items[active].y}]({items[active].image})
}
{items[active].t}{items[active].y}
)}
);
}
/* =================================================================== */
/* FORM (Googleフォームへ誘導) */
/* ------------------------------------------------------------------- */
/* 応募は外部のGoogleフォームで受け付けます。 */
/* ↓ を実際の公開URL(https://forms.gle/... 等)に差し替えてください。 */
/* 旧:サイト内ステップ式フォームは backup/Form.component.jsx に保管。 */
/* =================================================================== */
const GOOGLE_FORM_URL = "https://docs.google.com/forms/d/e/1FAIpQLSc--pevu5QsHyZT47L2KMZInmIbPftB6dkz93R1pEP_4NQT7g/viewform?usp=dialog";
function Form(){
return (
);
}
/* =================================================================== */
/* TERMS */
/* =================================================================== */
function Terms({onOpenPrivacy}){
const items = [
"応募資格は16~29歳までです。個人/団体は問いません。",
"18歳未満の方は、保護者の同意・確認の上でご応募ください。",
"参加賞は応募いただいた個人または団体につき各部門1つまでです。",
"複数応募の場合でも重複表彰はいたしません。",
"入賞作品は YouTube や SNS 等での広報啓発に活用いたします。",
"作品は応募者自身のオリジナル作品に限ります。第三者の権利を侵害するものは無効とします。",
"応募作品は未発表の作品に限ります。AI画像生成サービス等を利用した作品は応募できません。",
"作品内に第三者の肖像・音楽・著作物等を含む場合は、事前に権利者の同意・許諾を得てください。",
"応募多数の場合は抽選とさせていただく場合があります。",
"応募いただいた作品(データ・書類等)は返却いたしません。応募前にコピーの保管をお願いします。",
"本キャンペーンは大分県警察による提供です。",
"本キャンペーンについてのお問い合わせは、事務局までお願いします。",
];
return (
Terms & Notes · 09
応募規約・注意事項
{items.map((t,i)=>(
!
{t}
))}
個人情報の取扱いについては、
をご確認ください。
);
}
/* =================================================================== */
/* CONTACT / SHARE */
/* =================================================================== */
function Contact(){
return (
Share / Contact · 10
広げよう、このメッセージ
SNSでシェア
このコンテストを友達に教えて、一緒に応募しよう。
お問い合わせ
メッセージ動画&静止画コンテスト 事務局
oita_ad@jupiter.jcom.co.jp
);
}
/* =================================================================== */
/* PRIVACY POLICY (modal content) */
/* =================================================================== */
const MAIL = "oita_ad@jupiter.jcom.co.jp";
const PRIVACY_CONTENT = [
{type:"h", text:"応募についての注意点(必読)"},
{type:"note", text:"応募作品が未発表作品であることに限ります。"},
{type:"note", text:"AI画像生成サービス等を利用した作品は応募不可となります。"},
{type:"note", text:"18歳未満の方の応募の際には保護者へ確認の上応募してください。"},
{type:"note", text:"応募作品は応募者本人または応募団体が撮影・制作したオリジナル作品に限ります。"},
{type:"note", text:"応募作品の著作権は大分県警察に帰属します。"},
{type:"note", text:"応募作品を若者を犯罪に加担させないための注意喚起やイベント・広告宣伝などに使用する場合があります。"},
{type:"note", text:"応募作品を改変・編集する場合があります。"},
{type:"note", text:"応募作品内で応募者本人または団体メンバー以外の人物などが映っている場合は、事前に必ず本人の同意を得た上で応募してください。"},
{type:"note", text:"応募作品内で応募者以外の方が権利を有するデザイン、音楽を使用する場合には、必ず事前に権利者の同意を得た上で応募してください。"},
{type:"p", text:"取得した個人情報は、応募者の同意なく第三者に提供しません。ただし、法令に基づく場合(例:裁判所や警察からの要請)や、コンテスト運営に必要な委託先(例:配送業者、システム管理会社)に提供する場合を除きます。委託先には、個人情報保護法に基づく適切な管理を義務付け、監督を行います。"},
{type:"sub", text:"安全管理措置"},
{type:"p", text:"個人情報の漏洩、滅失、毀損を防止するため、情報セキュリティポリシーに基づく厳格な管理を行います(詳細は情報セキュリティポリシーを参照)。"},
{type:"p", text:"不要となった個人情報は、適切な方法で速やかに削除または廃棄します。"},
{type:"sub", text:"個人情報の開示・訂正・削除"},
{type:"p", text:`応募者は、自身の個人情報の開示、訂正、削除、利用停止を求める権利を有します。ご依頼はお問い合わせ先( ${MAIL} )にて受け付けます。`},
{type:"p", text:"開示等の手続きには、本人確認が必要です。ご不明点やご懸念がある場合は、上記お問い合わせ先までご連絡ください。"},
{type:"p", text:`詳細は応募規定をご確認ください。ご不明点はお問い合わせ先( ${MAIL} )までご連絡ください。`},
{type:"h", text:"情報セキュリティポリシー"},
{type:"p", text:"主催者は、応募者の個人情報や応募作品を安全に管理するため、情報セキュリティに関する法律(個人情報保護法、情報セキュリティ基本方針等)および以下のポリシーに基づき、適切な対策を講じます。"},
{type:"sub", text:"セキュリティ対策"},
{type:"p", text:"技術的対策:ウェブサイトはSSL/TLS暗号化を採用し、通信の安全性を確保します。サーバーはファイアウォールや侵入検知システムで保護します。"},
{type:"p", text:"物理的対策:データセンターやサーバールームは、アクセス制限や監視システムにより不正侵入を防止します。"},
{type:"p", text:"組織的対策:従業員および委託先に対し、情報セキュリティ教育を定期的に実施し、機密保持契約を締結します。"},
{type:"p", text:"必須項目と任意項目を明確に区別し、応募者の同意に基づき取得します。"},
{type:"sub", text:"第三者提供の制限"},
{type:"p", text:"個人情報は、応募者の同意なく第三者に提供しません。ただし、以下の場合は除きます。"},
{type:"p", text:"・法令に基づく場合(例:裁判所、警察からの要請)/委託先(例:システム管理会社、配送業者)への提供(機密保持契約を締結)/公的機関としての業務遂行に必要な場合。"},
{type:"sub", text:"安全管理措置"},
{type:"p", text:"個人情報の漏洩、滅失、毀損を防止するため、情報セキュリティポリシーに定める対策を講じます。個人情報の取り扱いを委託する場合は、委託先の選定基準を定め、定期的な監査を実施します。"},
{type:"sub", text:"開示・訂正・削除等の請求"},
{type:"p", text:"応募者は、個人情報保護法に基づき、自身の個人情報の開示、訂正、追加、削除、利用停止、第三者提供の停止を求めることができます。"},
{type:"p", text:"応募作品に第三者の著作物(音楽、画像、映像、引用等)を含む場合、応募者は事前に権利者の許諾を得るか、著作権法で認められた範囲で使用してください。第三者の肖像を含む場合、肖像権者の同意を得てください。"},
{type:"p", text:"権利侵害が判明した場合、応募は無効となり、応募者が一切の責任(損害賠償含む)を負います。"},
{type:"sub", text:"著作権侵害への対応"},
{type:"p", text:"主催者は、応募作品に関する著作権侵害の申し立てを受けた場合、事実関係を調査し、必要に応じて作品の公開停止や応募資格の取り消しを行います。"},
{type:"p", text:`申し立ては、お問い合わせ先( ${MAIL} )にて受け付けます。`},
{type:"sub", text:"作品の返却"},
{type:"p", text:"応募作品(データ、書類等)は返却しません。必要な場合は、応募前にコピーを保管してください。"},
{type:"p", text:`ご不明点は、応募規定またはお問い合わせ先( ${MAIL} )にてご確認ください。`},
];
function PrivacyModal({open, onClose}){
useEffect(()=>{
if(!open) return;
const onKey = (e)=>{ if(e.key==="Escape") onClose(); };
document.addEventListener("keydown", onKey);
const prev = document.body.style.overflow;
document.body.style.overflow = "hidden";
return ()=>{ document.removeEventListener("keydown", onKey); document.body.style.overflow = prev; };
},[open,onClose]);
if(!open) return null;
return (
e.stopPropagation()}>
プライバシーポリシー
{PRIVACY_CONTENT.map((b,i)=>{
if(b.type==="h") return
{b.text}
;
if(b.type==="sub") return
{b.text}
;
if(b.type==="note") return
{b.text}
;
return
{b.text}
;
})}
);
}
/* =================================================================== */
/* FOOTER */
/* =================================================================== */
function Footer({onOpenPrivacy}){
return (
);
}
/* =================================================================== */
/* TWEAKS PANEL */
/* =================================================================== */
function TweakPanel({visible, palette, setPalette}){
return (
PALETTE
{[
{k:"red",l:"レッド基調"},
{k:"yellow",l:"イエロー基調"},
{k:"mono",l:"モノクロ+差し色"},
].map(o=>(
))}
);
}
/* =================================================================== */
/* APP */
/* =================================================================== */
function App(){
useReveal();
// Tweak state
const DEFAULTS = window.__EDIT_DEFAULTS__ || {palette:"red"};
const [palette,setPalette] = useState(DEFAULTS.palette || "red");
const [tweaksVisible,setTweaksVisible] = useState(false);
const [privacyOpen,setPrivacyOpen] = useState(false);
useEffect(()=>{ document.body.setAttribute('data-palette',palette); },[palette]);
// Tweak host integration
useEffect(()=>{
const onMsg = (e)=>{
if(!e.data || !e.data.type) return;
if(e.data.type==="__activate_edit_mode") setTweaksVisible(true);
if(e.data.type==="__deactivate_edit_mode") setTweaksVisible(false);
};
window.addEventListener('message',onMsg);
window.parent.postMessage({type:"__edit_mode_available"},"*");
return ()=>window.removeEventListener('message',onMsg);
},[]);
const changePalette = (p)=>{
setPalette(p);
window.parent.postMessage({type:"__edit_mode_set_keys",edits:{palette:p}},"*");
};
return (
<>
setPrivacyOpen(true)}/>