google-site-verification=QxSR5RbKx1TEFVqBUBRSw6h5pSmcCfukQEj_nzdr3vQ
100546119797916
297200882177665
import React, { useEffect, useState } from "react";
import { motion, AnimatePresence } from "framer-motion";
import {
Download,
Image as ImageIcon,
Lock,
LockOpen,
Save,
Trash2,
} from "lucide-react";
// -------------------------------------------------------------
// Minimal Freesongs Arc Builder (no Shadcn UI)
// -------------------------------------------------------------
const COLUMN_TITLES = ["Phenomena", "Action / Verb", "Outcome"] as const;
const PROMPTS = [
"What does this image say is happening?",
"What does this image say you are doing?",
"What does this image say has happened?",
];
type Cell = { text: string; imageUrl: string; seed: string };
type Columns = [Cell[], Cell[], Cell[]];
function newEmptyColumns(): Columns {
return [0, 1, 2].map(() =>
Array.from({ length: 12 }, () => ({ text: "", imageUrl: "", seed: "" }))
) as Columns;
}
function randomSeed() {
return (
Math.random().toString(36).slice(2) + Date.now().toString(36).slice(-4)
);
}
function picsumUrl(seed: string, w = 800, h = 600) {
return `https://picsum.photos/seed/${seed}/${w}/${h}`;
}
function useLocalStorage(key: string, initial: T) {
const [state, setState] = useState(() => {
try {
const raw = localStorage.getItem(key);
return raw ? (JSON.parse(raw) as T) : initial;
} catch (err) {
console.warn(`Failed to load localStorage key "${key}"`, err);
return initial;
}
});
useEffect(() => {
try {
localStorage.setItem(key, JSON.stringify(state));
} catch (err) {
console.warn(`Failed to save localStorage key "${key}"`, err);
}
}, [key, state]);
return [state, setState] as const;
}
// 👉 Minimal replacements for UI components
const Button: React.FC> = ({
children,
...props
}) => (
{children}
);
const Input: React.FC> = (
props
) => (
);
const Textarea: React.FC> = (
props
) => (
);
export default function FreesongsArcBuilder() {
const [columns, setColumns] = useLocalStorage(
"fs-columns",
newEmptyColumns()
);
const [phase, setPhase] = useLocalStorage("fs-phase", 0); // 0..2, 3 = done
const [row, setRow] = useLocalStorage("fs-row", 0);
const [subjectLines, setSubjectLines] = useLocalStorage(
"fs-subject-lines",
Array.from({ length: 12 }, () => "")
);
const [verses, setVerses] = useLocalStorage(
"fs-verses",
Array.from({ length: 12 }, () => "")
);
const isFinalPhase = phase >= 3;
const currentPrompt = PROMPTS[Math.min(phase, 2)];
// Helpers
function newImageUrl() {
const seed = randomSeed();
return { url: picsumUrl(seed), seed };
}
function handleGenerateImage() {
if (isFinalPhase) return;
const { url, seed } = newImageUrl();
setColumns((prev) => {
const next = prev.map((col) => col.slice()) as Columns;
next[phase][row] = { ...next[phase][row], imageUrl: url, seed };
return next;
});
}
function handleTextChange(val: string) {
if (isFinalPhase) return;
setColumns((prev) => {
const next = prev.map((col) => col.slice()) as Columns;
next[phase][row] = { ...next[phase][row], text: val };
return next;
});
}
function handleNext() {
if (isFinalPhase) return;
if (row < 11) {
setRow(row + 1);
} else {
if (phase < 2) {
setPhase(phase + 1);
setRow(0);
} else {
setPhase(3);
setRow(0);
}
}
}
function handlePrev() {
if (!isFinalPhase && row > 0) setRow(row - 1);
}
// Subject lines after completion
useEffect(() => {
if (phase === 3) {
setSubjectLines((prev) =>
prev.map((line, i) => {
const a = columns[0][i]?.text?.trim() || "";
const b = columns[1][i]?.text?.trim() || "";
const c = columns[2][i]?.text?.trim() || "";
const joined = [a, b, c].filter(Boolean).join(" — ");
return joined || line;
})
);
}
}, [phase, columns, setSubjectLines]);
// --- Simple render ---
return (
🎶 Freesongs Arc Builder
Phase {phase <= 2 ? phase + 1 : "Final"} – Row {row + 1}/12
{phase <= 2 ? (
{currentPrompt}
Random Image
◀ Prev
Next ▶
{columns[phase][row].imageUrl ? (
) : (
No image yet
)}
) : (
Subject Lines
{subjectLines.map((line, i) => (
{
const next = subjectLines.slice();
next[i] = e.target.value;
setSubjectLines(next);
}}
placeholder="Tie into subject line..."
/>
))}
Verses
{verses.map((v, i) => (
))}
)}
);
}
top of page
Email Tarot Reading You get a detailed analysis of a Tarot spread that addresses the Past, Present and future choices you have made in response to the experiences life has offered. Included is a picture of your spread, an inventory of positions and a personal analysis. Please provide email details in Notes during payment process or send it as text to 0431616001.
Services: Stores Product Widget bottom of page