/* global React, ReactDOM */
const { useState, useEffect } = React;
/* ============ Section glyphs (small pixel icons) ============ */
const GlyphPixel = ({ cells, color = "#FFD23F", scale = 3 }) => (
);
const GlyphFood = () => (
);
const GlyphBike = () => (
);
const GlyphScreen = () => (
);
const GlyphStar = () => (
);
const GlyphWarn = () => (
);
const GlyphCog = () => (
);
const GlyphCar = () => (
);
/* ============ Nav ============ */
function Nav() {
return (
);
}
/* ============ Hero ============ */
function Hero() {
return (
VER 1.33
·
FREE
·
WIN / MAC
冷めるまでに、届けろ。
料理を抱えて、ハイウェイへ。
あなたはフードデリバリー会社「MOTORFOODS」のクルー。
料理を冷まさず、形も崩さず、お客さまに笑顔をお届けするために——
制限時間と料理ゲージを意識しながら、行き交う車の間を駆け抜けるバイクアクションです。
GENREBIKE ACTION
PRICEFREE
PLAY TIME20-30 MIN
STAGES15 + RANDOM
▸ PLAY FREE NOW
ダウンロード・プレイはこちら
外部のフリーゲームサイトへ移動します
);
}
/* ============ Story ============ */
function Story() {
return (
STORY
MOTORFOODS、
本日も出動。
フードデリバリー会社「MOTORFOODS」のクルーとなり、
街のハイウェイを愛車で駆け抜けます。
料理を冷まさず、形も崩さず、お客さまに笑顔をお届けするための、シンプルなドライブゲームです。
制限時間と料理ゲージ、お客さまからの★評価。全15コース+ランダムモード、6車種の乗り換え、3段階の難易度をご用意しています。
短い時間でサクッと1本、じっくり何時間でも——お好みのスタイルでお楽しみいただけます。
Windows / Mac / Google Chrome ブラウザ上でお楽しみいただけます。
);
}
/* ============ Modes ============ */
function Modes() {
const items = [
{
num: "MODE 01",
label: "CHALLENGE MODE",
title: "通しプレイで腕試し",
body: "用意されたコースを順番に走り抜ける、本作の中心となるメインモードです。途中で失敗しても、その場でリトライしていただけます。完走時にはまとまった報酬ボーナスをご用意。クリア後には上級コースも解放され、得た資金で新しい車両を購入しながら攻略を進めていけます。",
img: "assets/screens/StageSelect.webp",
},
{
num: "MODE 02",
label: "RANDOM MODE",
title: "毎回違うコース、一本勝負",
body: "これまでに走ったことのあるコースの中から、毎回ランダムで1本が選ばれるモードです。何が出てくるか分からないからこそ、集中力と適応力が試されます。短い時間でサッと一本走りたい時にもおすすめです。",
img: "assets/screens/Stage1.webp",
},
{
num: "MODE 03",
label: "コースプレイ",
title: "お気に入りのコースを、いつでも個別に",
body: "お好きなコースを1つ選んでプレイするモードです。タイム短縮を狙ったり、お気に入りのコースをじっくり味わっていただけます。リザルトの収支はゴール直後に累積へ加算されますので、得意なコースを周回して資金集めにお使いいただくのもおすすめです。",
img: "assets/screens/Result.webp",
},
];
return (
GAME MODES
3つの走り方。
通しで腕を試すもよし、ランダムに賭けるもよし、お気に入りのコースを繰り返すもよし。走破したコースの BGM は、タイトル画面の「サウンドトラック」からいつでもお楽しみいただけます。
{items.map((it, i) => (
{it.num}
{it.label}
{it.title}
{it.body}
))}
);
}
/* ============ Hazards ============ */
function Hazards() {
const items = [
{
tag: "TRAFFIC",
label: "一般車との衝突",
body: "左右から追い越してくる一般車に接触すると、料理が大きく傷み、修理費もかかります。さらに一時的に速度が落ちますので、中央寄りのラインを保って走るのが基本です。",
},
{
tag: "POLICE",
label: "パトカー (BUSTED!)",
body: "パトカーに接触すると、即レース終了 (BUSTED!) となります。サイレン音で接近をお知らせしますので、聞こえたら早めの車線変更で安全な距離を保ってください。",
},
{
tag: "OFF-ROAD",
label: "コース外走行",
body: "路肩より外を走り続けると、料理が少しずつ傷み、速度も低下します。長時間外を走ると料理がゼロになるおそれがありますので、コース内のキープを心がけてください。",
},
{
tag: "CONES",
label: "コーン破壊",
body: "コーン地帯では「LEFT / CENTER / RIGHT CONES AHEAD」の警告バナーが事前に表示されます。指示通りに避ければ大丈夫ですが、破壊してしまうと料理がわずかに傷み、本数に応じて修理費がかかります。",
},
{
tag: "TIME UP",
label: "タイムアップ / 料理ゼロ",
body: "制限時間切れも、料理ゲージがゼロになっても、配達は失敗となります。残り時間と FOOD ゲージを意識しながら走ってください。",
},
];
return (
{Array(2).fill(0).map((_, i) => (
CAUTION · CAUTION · CAUTION · CAUTION · CAUTION · CAUTION · CAUTION · CAUTION · CAUTION · CAUTION ·{" "}
))}
HAZARDS
配達を阻む、5つの危険。
シンプルな見た目に反して、ハイウェイには色々な危険が潜んでいます。事前の警告を活用しながら、安全運転で配達を成功させてください。
{items.map((h, i) => (
{String(i + 1).padStart(2, '0')}
{h.tag}
{h.label}
{h.body}
))}
POLICE APPROACHING
サイレンが聞こえたら、即避難を。
パトカーに接触すると、レースはその場で即終了となります。出現の合図はサイレン音。聞こえたら早めに車線変更で安全な距離を確保してください。HARD難易度では出現頻度が高くなりますので、十分にご注意を。
);
}
/* ============ Garage / Vehicles ============ */
function Garage() {
const cars = [
{ name: "バイク", en: "BIKE", price: "$0", spec: "初期装備、バランス型" },
{ name: "スポーツ", en: "SPORT", price: "$300", spec: "軽量で加速良し" },
{ name: "アメリカン", en: "AMERICAN", price: "$500", spec: "最高速 180km/h、重め" },
{ name: "スポーツカー", en: "SPORTS CAR", price: "$1,000", spec: "高最高速 187km/h" },
{ name: "トラック", en: "TRUCK", price: "$1,500", spec: "最高速 195km/h、加速はもっさり" },
{ name: "馬", en: "HORSE", price: "$3,000", spec: "最高速 127km/h、蹄音付きロマン枠" },
];
return (
GARAGE
稼いで、乗り換える。
初回起動時にお渡しする $500 と、配達で貯めた累積収支で新しい車両を解禁いただけます。加速重視のバイクから、最高速重視のトラック、ロマン枠の馬まで、全6車種をご用意しました。
FEATURED
TRUCK
最高速 195km/h
#
VEHICLE
JP
PRICE
SPEC
{cars.map((c, i) => (
{String(i + 1).padStart(2, '0')}
{c.en}
{c.name}
{c.price}
{c.spec}
))}
※ 全15コースを完走できるギリギリのラインとなるのが「馬」、上級者向けのロマン枠です。資金を貯めたい時は、得意なコースをコースプレイで繰り返すのが手堅い方法です。
);
}
/* ============ Features ============ */
function Features() {
const items = [
{
tag: "AUTO ACCEL",
title: "自動アクセル",
body: "PAUSE メニュー (ESCキー) から ON にすると、アクセル操作を自動化していただけます。回避操作だけに集中したい時にも便利な機能で、一度有効にすれば次のレースにも引き継がれます。",
},
{
tag: "GHOST",
title: "ゴースト表示",
body: "同じコースの過去ベスト走行を、半透明のゴーストとして並走表示します。ベストより遅れている時にだけ控えめに現れる、タイム短縮の良い目安となる機能です。",
},
{
tag: "DIFFICULTY",
title: "3段階の難易度",
body: "EASY・NORMAL・HARD の3段階からお選びいただけます。EASY は制限時間が長めで操作アシストも自動でオンに。HARD では敵車もパトカーも増えますが、その分配達報酬も上乗せされます。",
},
{
tag: "SOUNDTRACK",
title: "BGM コレクション",
body: "走破したコースの BGM は、タイトル画面の「サウンドトラック」からいつでも試聴いただけます。全15曲を集めていくのも、本作のお楽しみのひとつです。",
},
];
return (
SYSTEM
親切で、寛容な設計。
難しすぎず、簡単すぎず。リトライやゴースト、自動アクセル、軽量モードなど、お使いの環境や腕前に合わせてプレイをカスタマイズいただけます。
{items.map((f, i) => (
{f.tag}
{f.title}
{f.body}
))}
);
}
/* ============ Screenshots ============ */
function Screenshots() {
const shots = [
{ img: "assets/screens/StageCity1.webp", title: "ナイトハイウェイ", badge: "01", large: true },
{ img: "assets/screens/Stage1.webp", title: "朝のハイウェイ", badge: "02" },
{ img: "assets/screens/Truck.webp", title: "雨夜のハイウェイ", badge: "03" },
{ img: "assets/screens/Result.webp", title: "リザルト", badge: "04" },
{ img: "assets/screens/Review.webp", title: "REVIEWS / 戦歴", badge: "05" },
];
return (
SCREENSHOTS
ゲーム画面
実際のゲーム画面よりお届けします。ピクセルアートで描かれた、レトロな世界観をぜひお楽しみください。
{shots.map((s, i) => (
{s.title}
{s.badge} / 05
))}
);
}
/* ============ Controls / Specs ============ */
function Specs() {
return (
CONTROLS
キー操作
-
←→
バイクの移動
-
SpaceZ
決定・アクセル
-
ESCX
キャンセル・ブレーキ / PAUSE
※ ゲームパッドでの動作は未検証となっております。キーボードでの操作を推奨いたします。
SYSTEM REQUIREMENTS
動作環境
- OSWindows 8.1/10 (64bit) / macOS 10.13+
- CPUIntel Core i3-4340 相当以上
- RAM8 GB 以上
- GPUOpenGL 対応 / VRAM 1 GB 以上
- DISPLAY1280 × 768 以上
- INPUTKEYBOARD
);
}
/* ============ Credits ============ */
function Credits() {
const rows = [
["DIRECTION / DESIGN", "kotonoha*"],
["GRAPHICS", "kotonoha* / ChatGPT"],
["BGM · ME", "Suno"],
["SE · 背景音素材", "OtoLogic / 無料効果音で遊ぼう! / 効果音ラボ"],
["PLUGINS", "kotonoha* + Claude Code"],
["SPECIAL THANKS", "RiCoChoco"],
["ENGINE", "RPGツクール MZ"],
];
return (
CREDITS
スタッフロール
{rows.map(([k, v], i) => (
{k}
{v}
))}
);
}
/* ============ Footer ============ */
function Footer() {
return (
);
}
/* ============ Tweaks ============ */
function TweaksController({ tweaks, setTweak }) {
if (!window.TweaksPanel) return null;
const { TweaksPanel, TweakSection, TweakColor, TweakToggle } = window;
return (
setTweak('palette', v)}
options={[
['#060924', '#FF8A1A', '#FFD23F'],
['#1A0A1E', '#FF4DA6', '#21D3F5'],
['#0A1E0E', '#FFD23F', '#FF3D2E'],
]}
/>
setTweak('stars', v)} />
);
}
/* ============ Loading Screen ============ */
function LoadingScreen() {
const alreadyBooted = (() => {
try { return sessionStorage.getItem('dk-booted') === '1'; }
catch (e) { return false; }
})();
const [progress, setProgress] = useState(alreadyBooted ? 100 : 0);
const [phase, setPhase] = useState(alreadyBooted ? 'hidden' : 'loading'); // loading | ready | done | hidden
useEffect(() => {
if (alreadyBooted) {
document.body.classList.remove('booting');
return;
}
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
setPhase('hidden');
document.body.classList.remove('booting');
try { sessionStorage.setItem('dk-booted', '1'); } catch (e) {}
return;
}
document.body.classList.add('booting');
const preload = [
'assets/top.webp',
'assets/logo.webp',
'assets/screens/Title.webp',
'assets/screens/StageSelect.webp',
'assets/screens/StageCity1.webp',
'assets/screens/Patcar.webp',
];
let loaded = 0;
const total = preload.length;
preload.forEach((src) => {
const img = new Image();
const done = () => { loaded++; };
img.onload = done;
img.onerror = done;
img.src = src;
});
const start = performance.now();
const minDuration = 1400;
let raf;
let timers = [];
const tick = () => {
const elapsed = performance.now() - start;
const timeFrac = Math.min(elapsed / minDuration, 1);
const loadFrac = total > 0 ? loaded / total : 1;
const target = Math.min(timeFrac, loadFrac);
const eased = 1 - Math.pow(1 - target, 1.7);
const pct = Math.floor(eased * 100);
setProgress(pct);
if (target < 1) {
raf = requestAnimationFrame(tick);
} else {
setProgress(100);
try { sessionStorage.setItem('dk-booted', '1'); } catch (e) {}
timers.push(setTimeout(() => {
setPhase('ready');
timers.push(setTimeout(() => {
setPhase('done');
document.body.classList.remove('booting');
timers.push(setTimeout(() => {
setPhase('hidden');
}, 700));
}, 550));
}, 150));
}
};
raf = requestAnimationFrame(tick);
return () => {
if (raf) cancelAnimationFrame(raf);
timers.forEach(clearTimeout);
document.body.classList.remove('booting');
};
}, []);
if (phase === 'hidden') return null;
const pctStr = String(progress).padStart(3, '0');
const isReady = phase === 'ready' || phase === 'done';
return (
MOTORFOODS · DELIVERY CREW
DELIVERY KID
デリバリーキッド
VER 1.33 · BIKE ACTION
{Array.from({ length: 20 }).map((_, i) => )}
LOADING ASSETS
{pctStr}%
{isReady ? (
▸ READY
) : (
NOW LOADING...
)}
);
}
/* ============ Effects ============ */
function ScrollProgress() {
const [pct, setPct] = useState(0);
useEffect(() => {
const onScroll = () => {
const h = document.documentElement.scrollHeight - window.innerHeight;
setPct(h > 0 ? (window.scrollY / h) * 100 : 0);
};
onScroll();
window.addEventListener('scroll', onScroll, { passive: true });
window.addEventListener('resize', onScroll, { passive: true });
return () => {
window.removeEventListener('scroll', onScroll);
window.removeEventListener('resize', onScroll);
};
}, []);
return (
);
}
function useSiteEffects() {
useEffect(() => {
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
document.body.classList.add('reduce-motion');
return;
}
// 1) stagger index for grid groups
const groupSelectors = [
'.modes-list', '.hazards-grid', '.features-grid', '.shots-grid',
'.garage-list', '.credits-table', '.kbd-list', '.spec-list',
'.hero-stats', '.hero-tagline',
];
groupSelectors.forEach((sel) => {
const c = document.querySelector(sel);
if (!c) return;
[...c.children].forEach((el, i) => el.style.setProperty('--reveal-i', i));
});
// 2) reveal on scroll
const revealSel = [
'.sec-eyebrow', '.sec-title', '.sec-sub', '.lead', '.about-meta',
'.mode', '.hazard', '.feature', '.garage-row', '.shot', '.cr-row',
'.hazards-banner', '.garage-feature', '.kbd-list li', '.spec-list li',
'.hero-stats .row', '.hero-tagline > *', '.garage-note',
].join(', ');
const revealEls = document.querySelectorAll(revealSel);
revealEls.forEach((el) => el.classList.add('reveal'));
const io = new IntersectionObserver(
(entries) => {
entries.forEach((e) => {
if (e.isIntersecting) {
e.target.classList.add('in');
io.unobserve(e.target);
}
});
},
{ threshold: 0.06, rootMargin: '0px 0px -4% 0px' }
);
revealEls.forEach((el) => io.observe(el));
// 3) spotlight cursor on cards (fine pointer only)
let onMove = null;
if (window.matchMedia('(pointer: fine)').matches) {
const spotlightSel = '.feature, .hazard, .mode, .garage-feature, .shot, .foot-official-link, .release-cta';
const sp = Array.from(document.querySelectorAll(spotlightSel));
sp.forEach((el) => el.classList.add('spotlight'));
const vh = () => window.innerHeight;
onMove = (e) => {
for (let i = 0; i < sp.length; i++) {
const el = sp[i];
const r = el.getBoundingClientRect();
if (r.bottom < -40 || r.top > vh() + 40) continue;
el.style.setProperty('--mx', `${e.clientX - r.left}px`);
el.style.setProperty('--my', `${e.clientY - r.top}px`);
}
};
document.addEventListener('mousemove', onMove, { passive: true });
}
// 4) nav scrolled state
const nav = document.querySelector('.nav');
const onNavScroll = () => {
if (!nav) return;
nav.classList.toggle('scrolled', window.scrollY > 24);
};
onNavScroll();
window.addEventListener('scroll', onNavScroll, { passive: true });
return () => {
io.disconnect();
if (onMove) document.removeEventListener('mousemove', onMove);
window.removeEventListener('scroll', onNavScroll);
};
}, []);
}
/* ============ App ============ */
function App() {
const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
"palette": ["#060924", "#FF8A1A", "#FFD23F"],
"stars": true
}/*EDITMODE-END*/;
const [tweaks, setTweaksState] = useState(TWEAK_DEFAULTS);
const setTweak = (k, v) => {
setTweaksState((p) => {
const next = typeof k === 'object' ? { ...p, ...k } : { ...p, [k]: v };
try { window.parent?.postMessage({ type: '__edit_mode_set_keys', edits: next }, '*'); } catch (e) {}
return next;
});
};
useEffect(() => {
const [bg, orange, yellow] = tweaks.palette;
const r = document.documentElement.style;
r.setProperty('--bg-deep', bg);
r.setProperty('--orange', orange);
r.setProperty('--yellow', yellow);
const bs = document.querySelector('.bg-stars');
if (bs) bs.style.display = tweaks.stars ? '' : 'none';
}, [tweaks]);
useSiteEffects();
return (
<>
>
);
}
ReactDOM.createRoot(document.getElementById('root')).render();