Merge pull request #42 from zonghaoyuan/worktree-portrait-card-support
feat(web): support portrait preset story cards on mobile
@@ -897,11 +897,10 @@ function PlayInner() {
|
|||||||
// Lock the visible orientation BEFORE the first paint, so portrait phones
|
// Lock the visible orientation BEFORE the first paint, so portrait phones
|
||||||
// never flash the landscape loading chrome. The state inits to "landscape"
|
// never flash the landscape loading chrome. The state inits to "landscape"
|
||||||
// for SSR-safety; this corrects it pre-paint (no-op re-render on landscape
|
// for SSR-safety; this corrects it pre-paint (no-op re-render on landscape
|
||||||
// devices). Prebaked cards (decision C) stay landscape-baked regardless of
|
// devices). The bootstrap effect below re-derives the same value for the
|
||||||
// device. The bootstrap effect below re-derives the same value for the
|
|
||||||
// /api/start payload.
|
// /api/start payload.
|
||||||
useIsomorphicLayoutEffect(() => {
|
useIsomorphicLayoutEffect(() => {
|
||||||
setOrientation(params.get("card") ? "landscape" : detectOrientation());
|
setOrientation(detectOrientation());
|
||||||
}, [params]);
|
}, [params]);
|
||||||
|
|
||||||
// ── Bootstrap: start session ─────────────────────────────────────────
|
// ── Bootstrap: start session ─────────────────────────────────────────
|
||||||
@@ -952,14 +951,10 @@ function PlayInner() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lock orientation for the whole session. Prebaked cards (decision C) are
|
// Lock orientation for the whole session. Both prebaked-card and live paths
|
||||||
// landscape-baked, so they stay landscape regardless of device; only the
|
// now respect device orientation — portrait prebaked assets live under
|
||||||
// live /api/start path requests a portrait paint when the phone is upright.
|
// firstact-portrait/ and firstscene-portrait/.
|
||||||
// The visible state is already set pre-paint by the layout effect above;
|
const sessionOrientation: Orientation = detectOrientation();
|
||||||
// here we only need the value for the /api/start payload.
|
|
||||||
const sessionOrientation: Orientation = cardName
|
|
||||||
? "landscape"
|
|
||||||
: detectOrientation();
|
|
||||||
if (livePayload) livePayload.orientation = sessionOrientation;
|
if (livePayload) livePayload.orientation = sessionOrientation;
|
||||||
|
|
||||||
if (!cardName && !livePayload) {
|
if (!cardName && !livePayload) {
|
||||||
@@ -980,11 +975,20 @@ function PlayInner() {
|
|||||||
cardGender?: string;
|
cardGender?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const firstactDir = sessionOrientation === "portrait"
|
||||||
|
? "firstact-portrait"
|
||||||
|
: "firstact";
|
||||||
|
|
||||||
const fetchStart: Promise<PrebakedFirstAct> = cardName
|
const fetchStart: Promise<PrebakedFirstAct> = cardName
|
||||||
? fetch(`/home/firstact/${encodeURIComponent(cardName)}.json`).then(
|
? fetch(`/home/${firstactDir}/${encodeURIComponent(cardName)}.json`).then(
|
||||||
async (r) => {
|
async (r) => {
|
||||||
if (!r.ok) throw new Error(`找不到精选剧情:${cardName}`);
|
if (r.ok) return (await r.json()) as PrebakedFirstAct;
|
||||||
return (await r.json()) as PrebakedFirstAct;
|
if (sessionOrientation === "portrait") {
|
||||||
|
console.warn(`[play] portrait firstact missing for ${cardName} (HTTP ${r.status}), falling back to landscape`);
|
||||||
|
const fb = await fetch(`/home/firstact/${encodeURIComponent(cardName)}.json`);
|
||||||
|
if (fb.ok) return (await fb.json()) as PrebakedFirstAct;
|
||||||
|
}
|
||||||
|
throw new Error(`找不到精选剧情:${cardName}`);
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
: fetch("/api/start", {
|
: fetch("/api/start", {
|
||||||
|
|||||||
|
After Width: | Height: | Size: 149 KiB |
|
After Width: | Height: | Size: 211 KiB |
|
After Width: | Height: | Size: 160 KiB |
|
After Width: | Height: | Size: 71 KiB |
|
After Width: | Height: | Size: 97 KiB |
|
After Width: | Height: | Size: 220 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 100 KiB |
|
After Width: | Height: | Size: 105 KiB |
|
After Width: | Height: | Size: 195 KiB |
|
After Width: | Height: | Size: 180 KiB |
|
After Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 309 KiB |
|
After Width: | Height: | Size: 143 KiB |
|
After Width: | Height: | Size: 122 KiB |
|
After Width: | Height: | Size: 90 KiB |
|
After Width: | Height: | Size: 98 KiB |
|
After Width: | Height: | Size: 135 KiB |
|
After Width: | Height: | Size: 91 KiB |
|
After Width: | Height: | Size: 179 KiB |
|
After Width: | Height: | Size: 104 KiB |
|
After Width: | Height: | Size: 156 KiB |
|
After Width: | Height: | Size: 62 KiB |
|
After Width: | Height: | Size: 248 KiB |
|
After Width: | Height: | Size: 341 KiB |
|
After Width: | Height: | Size: 187 KiB |
|
After Width: | Height: | Size: 161 KiB |
|
After Width: | Height: | Size: 247 KiB |
|
After Width: | Height: | Size: 106 KiB |
|
After Width: | Height: | Size: 154 KiB |
|
After Width: | Height: | Size: 146 KiB |
|
After Width: | Height: | Size: 166 KiB |
|
After Width: | Height: | Size: 200 KiB |
|
After Width: | Height: | Size: 88 KiB |
|
After Width: | Height: | Size: 127 KiB |
|
After Width: | Height: | Size: 121 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 82 KiB |
|
After Width: | Height: | Size: 139 KiB |