feat(quests): improve 3d island styling
fix(test): fix context image not appearing on test screen
This commit is contained in:
@ -20,14 +20,6 @@ const VW = 420;
|
||||
const TOP_PAD = 80;
|
||||
const ROW_H = 520; // vertical step per island (increased for more separation)
|
||||
|
||||
const ISLAND_SCALE = 1.2;
|
||||
const LAND_H_BASE = 40;
|
||||
const LAND_H = LAND_H_BASE * ISLAND_SCALE;
|
||||
|
||||
const CARD_W = 130;
|
||||
const CARD_H = 170;
|
||||
const CARD_H_CLAIMABLE = 235;
|
||||
|
||||
// ─── Seeded RNG ───────────────────────────────────────────────────────────────
|
||||
const mkRng = (seed: number) => {
|
||||
let s = seed >>> 0;
|
||||
@ -108,23 +100,6 @@ const generateIslandPositions = (
|
||||
return positions;
|
||||
};
|
||||
|
||||
const svgHeight = (positions: { x: number; y: number }[]) => {
|
||||
if (!positions.length) return 600;
|
||||
return (
|
||||
Math.max(...positions.map((p) => p.y)) + TOP_PAD + CARD_H_CLAIMABLE + LAND_H
|
||||
);
|
||||
};
|
||||
|
||||
// ─── Island shapes ────────────────────────────────────────────────────────────
|
||||
const SHAPES = [
|
||||
`<ellipse cx="0" cy="0" rx="57" ry="33"/>`,
|
||||
`<polygon points="0,-38 28,-14 48,10 40,33 22,38 -22,38 -40,33 -48,10 -28,-14"/>`,
|
||||
`<ellipse cx="0" cy="5" rx="62" ry="26"/>`,
|
||||
`<polygon points="0,-38 20,-14 50,-8 32,12 42,36 16,24 0,38 -16,24 -42,36 -32,12 -50,-8 -20,-14"/>`,
|
||||
`<path d="M-50,0 C-50,-34 -20,-38 0,-36 C22,-34 48,-18 50,4 C52,24 36,30 18,24 C6,20 4,10 10,4 C16,-4 26,-4 28,4 C30,12 22,18 12,16 C-4,10 -10,-8 0,-20 C12,-32 -30,-28 -50,0 Z"/>`,
|
||||
`<path d="M0,-38 C18,-38 44,-18 44,8 C44,28 26,38 0,38 C-26,38 -44,28 -44,8 C-44,-18 -18,-38 0,-38 Z"/>`,
|
||||
];
|
||||
|
||||
// ─── Styles ───────────────────────────────────────────────────────────────────
|
||||
const STYLES = `
|
||||
@import url('https://fonts.googleapis.com/css2?family=Nunito:wght@700;800;900&family=Nunito+Sans:wght@400;600;700&family=Cinzel:wght@700;900&display=swap');
|
||||
@ -504,6 +479,127 @@ const STYLES = `
|
||||
.qmlp-arc-dot { width:7px; height:7px; border-radius:50%; background:#ef4444; box-shadow:0 0 7px #ef4444; flex-shrink:0; animation:qmDotBlink 1.4s ease-in-out infinite; }
|
||||
`;
|
||||
|
||||
const ERROR_STYLES = `
|
||||
.qme-bg {
|
||||
position:absolute; inset:0;
|
||||
background: radial-gradient(ellipse 80% 60% at 50% 100%, #023e6e 0%, #011a38 50%, #020b18 100%);
|
||||
z-index:0;
|
||||
}
|
||||
.qme-fog {
|
||||
position:absolute; pointer-events:none; z-index:1;
|
||||
border-radius:50%; filter:blur(60px);
|
||||
}
|
||||
.qme-fog1 {
|
||||
width:500px; height:200px; left:-80px; top:30%;
|
||||
background:rgba(0,119,182,0.12);
|
||||
animation: qmeFogDrift 18s ease-in-out infinite alternate;
|
||||
}
|
||||
.qme-fog2 {
|
||||
width:400px; height:160px; right:-60px; top:50%;
|
||||
background:rgba(0,96,145,0.09);
|
||||
animation: qmeFogDrift 22s ease-in-out infinite alternate-reverse;
|
||||
}
|
||||
@keyframes qmeFogDrift { 0%{transform:translate(0,0);} 100%{transform:translate(40px,-20px);} }
|
||||
|
||||
.qme-debris {
|
||||
position:absolute; border-radius:2px; pointer-events:none; z-index:2;
|
||||
background:rgba(100,65,35,0.55);
|
||||
}
|
||||
.qme-debris-0 { width:22px;height:5px; left:12%; top:58%; animation:qmeDebrisBob 5.1s ease-in-out infinite; }
|
||||
.qme-debris-1 { width:14px;height:4px; left:78%; top:62%; animation:qmeDebrisBob 4.3s ease-in-out infinite 0.7s; }
|
||||
.qme-debris-2 { width:8px; height:8px; border-radius:50%; left:22%; top:72%; background:rgba(251,191,36,0.18); animation:qmeDebrisBob 6.2s ease-in-out infinite 1.1s; }
|
||||
.qme-debris-3 { width:18px;height:4px; left:64%; top:54%; animation:qmeDebrisBob 4.8s ease-in-out infinite 0.3s; }
|
||||
.qme-debris-4 { width:6px; height:6px; border-radius:50%; left:85%; top:44%; background:rgba(0,180,216,0.25); animation:qmeDebrisBob 3.9s ease-in-out infinite 1.8s; }
|
||||
.qme-debris-5 { width:10px;height:3px; left:8%; top:80%; animation:qmeDebrisBob 5.5s ease-in-out infinite 0.5s; }
|
||||
.qme-debris-6 { width:24px;height:5px; left:48%; top:76%; animation:qmeDebrisBob 6.7s ease-in-out infinite 2.2s; }
|
||||
.qme-debris-7 { width:7px; height:7px; border-radius:50%; left:35%; top:45%; background:rgba(100,65,35,0.3); animation:qmeDebrisBob 4.1s ease-in-out infinite 1.4s; }
|
||||
@keyframes qmeDebrisBob { 0%,100%{transform:translateY(0) rotate(0deg);opacity:0.7;} 50%{transform:translateY(-9px) rotate(6deg);opacity:1;} }
|
||||
|
||||
.qme-stage {
|
||||
position:relative; z-index:10; display:flex; flex-direction:column; align-items:center;
|
||||
padding:1rem 2rem 3rem; max-width:380px; width:100%;
|
||||
animation:qmeStageIn 0.9s cubic-bezier(0.22,1,0.36,1) both;
|
||||
}
|
||||
@keyframes qmeStageIn { 0%{opacity:0;transform:translateY(28px);} 100%{opacity:1;transform:translateY(0);} }
|
||||
|
||||
.qme-ship-wrap {
|
||||
width:240px; margin-bottom:0.4rem;
|
||||
animation:qmeShipRock 5s ease-in-out infinite;
|
||||
transform-origin:center bottom;
|
||||
filter:drop-shadow(0 12px 40px rgba(0,80,160,0.5)) drop-shadow(0 2px 8px rgba(0,0,0,0.8));
|
||||
}
|
||||
@keyframes qmeShipRock { 0%,100%{transform:rotate(-2.5deg) translateY(0);} 50%{transform:rotate(2.5deg) translateY(-4px);} }
|
||||
.qme-ship-svg { width:100%; height:auto; display:block; }
|
||||
.qme-mast { animation:qmeMastSway 7s ease-in-out infinite; transform-origin:130px 148px; }
|
||||
@keyframes qmeMastSway { 0%,100%{transform:rotate(-1deg);} 50%{transform:rotate(1.5deg);} }
|
||||
.qme-hull { animation:qmeHullSettle 5s ease-in-out infinite 0.3s; transform-origin:120px 145px; }
|
||||
@keyframes qmeHullSettle { 0%,100%{transform:rotate(-1.5deg);} 50%{transform:rotate(1deg);} }
|
||||
|
||||
.qme-content {
|
||||
text-align:center; display:flex; flex-direction:column; align-items:center; gap:0.55rem;
|
||||
animation:qmeStageIn 1s cubic-bezier(0.22,1,0.36,1) 0.12s both;
|
||||
}
|
||||
|
||||
.qme-eyebrow {
|
||||
font-family:'Cinzel',serif; font-size:0.58rem; font-weight:700;
|
||||
letter-spacing:0.28em; text-transform:uppercase; color:rgba(251,191,36,0.5);
|
||||
}
|
||||
|
||||
.qme-title {
|
||||
font-family:'Sorts Mill Goudy',serif; font-size:2.4rem; font-weight:900; margin:0;
|
||||
color:#ffffff; letter-spacing:0.02em;
|
||||
text-shadow:0 0 40px rgba(0,150,220,0.45), 0 2px 20px rgba(0,0,0,0.8);
|
||||
line-height:1.05;
|
||||
}
|
||||
|
||||
.qme-subtitle {
|
||||
font-family:'Nunito Sans',sans-serif; font-size:0.82rem; font-weight:600; margin:0;
|
||||
color:rgba(255,255,255,0.38); max-width:260px; line-height:1.5;
|
||||
}
|
||||
|
||||
.qme-error-badge {
|
||||
display:flex; align-items:center; gap:0.45rem;
|
||||
background:rgba(239,68,68,0.08); border:1px solid rgba(239,68,68,0.25);
|
||||
border-radius:100px; padding:0.32rem 0.85rem;
|
||||
max-width:100%; overflow:hidden;
|
||||
}
|
||||
.qme-error-dot {
|
||||
width:6px; height:6px; border-radius:50%; background:#ef4444;
|
||||
box-shadow:0 0 8px #ef4444; flex-shrink:0;
|
||||
animation:qmDotBlink 1.4s ease-in-out infinite;
|
||||
}
|
||||
.qme-error-text {
|
||||
font-family:'Nunito Sans',sans-serif; font-size:0.68rem; font-weight:700;
|
||||
color:rgba(239,68,68,0.85); white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
|
||||
}
|
||||
|
||||
.qme-retry-btn {
|
||||
position:relative; overflow:hidden;
|
||||
margin-top:0.35rem; padding:0.7rem 2rem;
|
||||
background:linear-gradient(135deg,#0077b6,#023e8a);
|
||||
border:1px solid rgba(0,180,216,0.45); border-radius:100px; cursor:pointer;
|
||||
font-family:'Sorts Mill Goudy',serif; font-size:0.88rem; font-weight:700; letter-spacing:0.06em;
|
||||
color:white;
|
||||
box-shadow:0 4px 0 rgba(0,30,80,0.6), 0 6px 28px rgba(0,100,200,0.25);
|
||||
transition:all 0.14s ease;
|
||||
}
|
||||
.qme-retry-btn:hover { transform:translateY(-2px); box-shadow:0 6px 0 rgba(0,30,80,0.6), 0 10px 32px rgba(0,120,220,0.35); }
|
||||
.qme-retry-btn:active { transform:translateY(1px); box-shadow:0 2px 0 rgba(0,30,80,0.6); }
|
||||
.qme-btn-wake {
|
||||
position:absolute; inset:0; border-radius:100px;
|
||||
background:linear-gradient(90deg, transparent 0%, rgba(255,255,255,0.12) 50%, transparent 100%);
|
||||
transform:translateX(-100%);
|
||||
animation:qmeBtnSheen 3.5s ease-in-out infinite 1s;
|
||||
}
|
||||
@keyframes qmeBtnSheen { 0%,100%{transform:translateX(-100%);} 40%,60%{transform:translateX(100%);} }
|
||||
.qme-btn-label { position:relative; z-index:1; }
|
||||
|
||||
.qme-hint {
|
||||
font-family:'Nunito Sans',sans-serif; font-size:0.62rem; font-weight:600; margin:0;
|
||||
color:rgba(255,255,255,0.18); max-width:240px; line-height:1.5; font-style:italic;
|
||||
}
|
||||
`;
|
||||
|
||||
// ─── Arc theme ────────────────────────────────────────────────────────────────
|
||||
export interface ArcTheme {
|
||||
accent: string;
|
||||
@ -936,17 +1032,16 @@ const IslandScene = ({
|
||||
modalOpen,
|
||||
}: MapContentProps) => {
|
||||
const nodeCount = arc.nodes.length;
|
||||
const tropicalTerrain = {
|
||||
l: "#3ecf6a",
|
||||
m: "#1fa84a",
|
||||
d: "#157a36",
|
||||
s: "#03045e",
|
||||
};
|
||||
|
||||
const sorted = useMemo(
|
||||
() => [...arc.nodes].sort((a, b) => a.sequence_order - b.sequence_order),
|
||||
[arc.nodes],
|
||||
);
|
||||
|
||||
const currentIdx = sorted.findIndex(
|
||||
(n) => n.status === "ACTIVE" || n.status === "CLAIMABLE",
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<WaterPlane />
|
||||
@ -974,9 +1069,10 @@ const IslandScene = ({
|
||||
index={i}
|
||||
position={[x3, 0, z3]}
|
||||
accent={theme.accent}
|
||||
terrain={tropicalTerrain}
|
||||
terrain={theme.terrain}
|
||||
onTap={onNodeTap}
|
||||
onClaim={onClaim}
|
||||
isCurrent={i === currentIdx} // ← add this
|
||||
modalOpen={modalOpen}
|
||||
/>
|
||||
);
|
||||
@ -1583,45 +1679,356 @@ export const QuestMap = () => {
|
||||
style={{
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
padding: "2rem",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
<style>{STYLES}</style>
|
||||
<div
|
||||
style={{
|
||||
textAlign: "center",
|
||||
color: "rgba(255,255,255,0.5)",
|
||||
fontFamily: "'Nunito',sans-serif",
|
||||
}}
|
||||
>
|
||||
<div style={{ fontSize: "2.5rem", marginBottom: "1rem" }}>🌊</div>
|
||||
<p
|
||||
style={{
|
||||
fontSize: "0.85rem",
|
||||
fontWeight: 800,
|
||||
color: "#ef4444",
|
||||
marginBottom: "0.5rem",
|
||||
}}
|
||||
>
|
||||
{fetchError ?? "No quest data found"}
|
||||
</p>
|
||||
<button
|
||||
onClick={() => window.location.reload()}
|
||||
style={{
|
||||
marginTop: "1rem",
|
||||
padding: "0.5rem 1.25rem",
|
||||
borderRadius: "100px",
|
||||
border: "1px solid rgba(255,255,255,0.15)",
|
||||
background: "transparent",
|
||||
color: "rgba(255,255,255,0.5)",
|
||||
cursor: "pointer",
|
||||
fontFamily: "'Nunito',sans-serif",
|
||||
fontWeight: 800,
|
||||
fontSize: "0.75rem",
|
||||
}}
|
||||
>
|
||||
Try again
|
||||
</button>
|
||||
<style>
|
||||
{STYLES}
|
||||
{ERROR_STYLES}
|
||||
</style>
|
||||
|
||||
{/* Deep-sea atmospheric background */}
|
||||
<div className="qme-bg" />
|
||||
<div className="qme-fog qme-fog1" />
|
||||
<div className="qme-fog qme-fog2" />
|
||||
|
||||
{/* Floating wreckage debris */}
|
||||
{[...Array(8)].map((_, i) => (
|
||||
<div key={i} className={`qme-debris qme-debris-${i}`} />
|
||||
))}
|
||||
|
||||
<div className="qme-stage">
|
||||
{/* ── Shipwreck SVG illustration ── */}
|
||||
<div className="qme-ship-wrap">
|
||||
<svg
|
||||
className="qme-ship-svg"
|
||||
viewBox="0 0 240 180"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
{/* Ocean surface rings behind the wreck */}
|
||||
<ellipse
|
||||
cx="120"
|
||||
cy="145"
|
||||
rx="100"
|
||||
ry="18"
|
||||
fill="rgba(3,4,94,0.6)"
|
||||
/>
|
||||
<path
|
||||
d="M20 145 Q45 132 70 145 Q95 158 120 145 Q145 132 170 145 Q195 158 220 145"
|
||||
stroke="#0077b6"
|
||||
strokeWidth="2"
|
||||
fill="none"
|
||||
opacity="0.5"
|
||||
strokeDasharray="6 4"
|
||||
>
|
||||
<animate
|
||||
attributeName="stroke-dashoffset"
|
||||
values="0;20"
|
||||
dur="2s"
|
||||
repeatCount="indefinite"
|
||||
/>
|
||||
</path>
|
||||
<path
|
||||
d="M10 155 Q40 143 68 155 Q96 167 124 155 Q152 143 180 155 Q208 167 230 155"
|
||||
stroke="#0096c7"
|
||||
strokeWidth="1.5"
|
||||
fill="none"
|
||||
opacity="0.35"
|
||||
strokeDasharray="5 5"
|
||||
>
|
||||
<animate
|
||||
attributeName="stroke-dashoffset"
|
||||
values="20;0"
|
||||
dur="2.6s"
|
||||
repeatCount="indefinite"
|
||||
/>
|
||||
</path>
|
||||
|
||||
{/* Broken mast — leaning left */}
|
||||
<g className="qme-mast">
|
||||
<line
|
||||
x1="130"
|
||||
y1="148"
|
||||
x2="88"
|
||||
y2="62"
|
||||
stroke="#6b4226"
|
||||
strokeWidth="5"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
<line
|
||||
x1="88"
|
||||
y1="62"
|
||||
x2="52"
|
||||
y2="44"
|
||||
stroke="#6b4226"
|
||||
strokeWidth="3.5"
|
||||
strokeLinecap="round"
|
||||
opacity="0.7"
|
||||
/>
|
||||
{/* Shredded sail */}
|
||||
<path
|
||||
d="M88 62 Q74 75 64 92 Q78 88 94 78 Z"
|
||||
fill="rgba(200,160,80,0.35)"
|
||||
stroke="rgba(200,160,80,0.5)"
|
||||
strokeWidth="1"
|
||||
/>
|
||||
<path
|
||||
d="M88 62 Q98 72 104 84 Q96 80 88 62 Z"
|
||||
fill="rgba(200,160,80,0.2)"
|
||||
stroke="rgba(200,160,80,0.3)"
|
||||
strokeWidth="1"
|
||||
/>
|
||||
{/* Torn pirate flag */}
|
||||
<path d="M52 44 L72 51 L58 61 Z" fill="rgba(239,68,68,0.7)" />
|
||||
<path
|
||||
d="M58 61 Q65 57 72 51"
|
||||
stroke="rgba(239,68,68,0.5)"
|
||||
strokeWidth="1"
|
||||
fill="none"
|
||||
strokeDasharray="3 2"
|
||||
>
|
||||
<animate
|
||||
attributeName="stroke-dashoffset"
|
||||
values="0;10"
|
||||
dur="1.2s"
|
||||
repeatCount="indefinite"
|
||||
/>
|
||||
</path>
|
||||
</g>
|
||||
|
||||
{/* Hull — cracked, half-submerged */}
|
||||
<g className="qme-hull">
|
||||
<path
|
||||
d="M58 130 Q72 110 100 108 Q128 106 152 112 Q172 116 174 130 Q160 148 120 152 Q80 156 58 130 Z"
|
||||
fill="#3d1c0c"
|
||||
stroke="#6b4226"
|
||||
strokeWidth="2"
|
||||
/>
|
||||
{/* Crack lines */}
|
||||
<path
|
||||
d="M100 108 L96 128 L104 140"
|
||||
stroke="rgba(0,0,0,0.6)"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
fill="none"
|
||||
/>
|
||||
<path
|
||||
d="M138 110 L142 132"
|
||||
stroke="rgba(0,0,0,0.5)"
|
||||
strokeWidth="1"
|
||||
strokeLinecap="round"
|
||||
fill="none"
|
||||
/>
|
||||
{/* Porthole */}
|
||||
<circle
|
||||
cx="118"
|
||||
cy="128"
|
||||
r="7"
|
||||
fill="#1a0900"
|
||||
stroke="#6b4226"
|
||||
strokeWidth="1.5"
|
||||
/>
|
||||
<circle
|
||||
cx="118"
|
||||
cy="128"
|
||||
r="4"
|
||||
fill="rgba(251,191,36,0.06)"
|
||||
stroke="rgba(251,191,36,0.15)"
|
||||
strokeWidth="1"
|
||||
/>
|
||||
{/* Water-line sheen */}
|
||||
<path
|
||||
d="M70 140 Q95 133 120 135 Q145 133 168 138"
|
||||
stroke="rgba(0,180,216,0.3)"
|
||||
strokeWidth="1"
|
||||
fill="none"
|
||||
/>
|
||||
</g>
|
||||
|
||||
{/* Barnacles */}
|
||||
<circle cx="85" cy="133" r="2" fill="rgba(100,200,100,0.25)" />
|
||||
<circle cx="92" cy="140" r="1.5" fill="rgba(100,200,100,0.2)" />
|
||||
<circle cx="155" cy="130" r="2" fill="rgba(100,200,100,0.25)" />
|
||||
|
||||
{/* Floating planks */}
|
||||
<rect
|
||||
x="40"
|
||||
y="138"
|
||||
width="14"
|
||||
height="5"
|
||||
rx="2"
|
||||
fill="#4a2c10"
|
||||
opacity="0.8"
|
||||
>
|
||||
<animateTransform
|
||||
attributeName="transform"
|
||||
type="translate"
|
||||
values="0,0;0,-3;0,0"
|
||||
dur="3.2s"
|
||||
repeatCount="indefinite"
|
||||
/>
|
||||
</rect>
|
||||
<rect
|
||||
x="185"
|
||||
y="141"
|
||||
width="10"
|
||||
height="4"
|
||||
rx="1.5"
|
||||
fill="#4a2c10"
|
||||
opacity="0.6"
|
||||
>
|
||||
<animateTransform
|
||||
attributeName="transform"
|
||||
type="translate"
|
||||
values="0,0;0,-2;0,0"
|
||||
dur="2.7s"
|
||||
begin="0.5s"
|
||||
repeatCount="indefinite"
|
||||
/>
|
||||
</rect>
|
||||
|
||||
{/* Half-submerged treasure chest */}
|
||||
<g opacity="0.9">
|
||||
<rect
|
||||
x="106"
|
||||
y="148"
|
||||
width="28"
|
||||
height="18"
|
||||
rx="3"
|
||||
fill="#5c3d1a"
|
||||
stroke="#fbbf24"
|
||||
strokeWidth="1.2"
|
||||
/>
|
||||
<rect
|
||||
x="106"
|
||||
y="148"
|
||||
width="28"
|
||||
height="8"
|
||||
rx="3"
|
||||
fill="#7a5228"
|
||||
stroke="#fbbf24"
|
||||
strokeWidth="1.2"
|
||||
/>
|
||||
<rect
|
||||
x="117"
|
||||
y="153"
|
||||
width="6"
|
||||
height="5"
|
||||
rx="1"
|
||||
fill="#fbbf24"
|
||||
opacity="0.8"
|
||||
/>
|
||||
<circle cx="120" cy="152" r="1" fill="white" opacity="0.7">
|
||||
<animate
|
||||
attributeName="opacity"
|
||||
values="0.7;0.1;0.7"
|
||||
dur="2s"
|
||||
repeatCount="indefinite"
|
||||
/>
|
||||
</circle>
|
||||
</g>
|
||||
|
||||
{/* Rising bubbles */}
|
||||
<circle
|
||||
cx="108"
|
||||
cy="130"
|
||||
r="2"
|
||||
fill="none"
|
||||
stroke="rgba(0,180,216,0.5)"
|
||||
strokeWidth="1"
|
||||
>
|
||||
<animate
|
||||
attributeName="cy"
|
||||
values="130;90"
|
||||
dur="3s"
|
||||
repeatCount="indefinite"
|
||||
begin="0s"
|
||||
/>
|
||||
<animate
|
||||
attributeName="opacity"
|
||||
values="0.6;0"
|
||||
dur="3s"
|
||||
repeatCount="indefinite"
|
||||
begin="0s"
|
||||
/>
|
||||
</circle>
|
||||
<circle
|
||||
cx="135"
|
||||
cy="125"
|
||||
r="1.5"
|
||||
fill="none"
|
||||
stroke="rgba(0,180,216,0.4)"
|
||||
strokeWidth="1"
|
||||
>
|
||||
<animate
|
||||
attributeName="cy"
|
||||
values="125;85"
|
||||
dur="2.5s"
|
||||
repeatCount="indefinite"
|
||||
begin="0.8s"
|
||||
/>
|
||||
<animate
|
||||
attributeName="opacity"
|
||||
values="0.5;0"
|
||||
dur="2.5s"
|
||||
repeatCount="indefinite"
|
||||
begin="0.8s"
|
||||
/>
|
||||
</circle>
|
||||
<circle
|
||||
cx="122"
|
||||
cy="132"
|
||||
r="1"
|
||||
fill="none"
|
||||
stroke="rgba(0,180,216,0.35)"
|
||||
strokeWidth="0.8"
|
||||
>
|
||||
<animate
|
||||
attributeName="cy"
|
||||
values="132;100"
|
||||
dur="2.1s"
|
||||
repeatCount="indefinite"
|
||||
begin="1.4s"
|
||||
/>
|
||||
<animate
|
||||
attributeName="opacity"
|
||||
values="0.4;0"
|
||||
dur="2.1s"
|
||||
repeatCount="indefinite"
|
||||
begin="1.4s"
|
||||
/>
|
||||
</circle>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
{/* Copy & actions */}
|
||||
<div className="qme-content">
|
||||
<div className="qme-eyebrow">⚓ SHIPWRECKED</div>
|
||||
<h1 className="qme-title">Lost at Sea</h1>
|
||||
<p className="qme-subtitle">
|
||||
{fetchError
|
||||
? "The charts couldn't be retrieved from the depths."
|
||||
: "Your quest map has sunk without a trace."}
|
||||
</p>
|
||||
<div className="qme-error-badge">
|
||||
<span className="qme-error-dot" />
|
||||
<span className="qme-error-text">
|
||||
{fetchError ?? "No quest data found"}
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
className="qme-retry-btn"
|
||||
onClick={() => window.location.reload()}
|
||||
>
|
||||
<span className="qme-btn-wake" />
|
||||
<span className="qme-btn-label">⛵ Set Sail Again</span>
|
||||
</button>
|
||||
<p className="qme-hint">
|
||||
Your progress and treasures are safely stored in Davy Jones'
|
||||
vault.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user