mirror of
https://github.com/trafficlunar/tomodachi-share.git
synced 2026-03-28 11:13:16 +00:00
feat: almost done
This commit is contained in:
parent
e8353b601c
commit
ab0015ef2a
21 changed files with 439 additions and 474 deletions
|
|
@ -40,16 +40,18 @@ function ColorPosition({ color }: { color: number }) {
|
||||||
if (!color) return null;
|
if (!color) return null;
|
||||||
if (color <= 7) {
|
if (color <= 7) {
|
||||||
return (
|
return (
|
||||||
<>
|
<span className="flex items-center">
|
||||||
|
<div className="size-5 rounded mr-1.5" style={{ backgroundColor: `#${COLORS[color]}` }}></div>
|
||||||
Color menu on left, <GridPosition index={color} cols={1} />
|
Color menu on left, <GridPosition index={color} cols={1} />
|
||||||
</>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (color >= 108) {
|
if (color >= 108) {
|
||||||
return (
|
return (
|
||||||
<>
|
<span className="flex items-center">
|
||||||
|
<div className="size-5 rounded mr-1.5" style={{ backgroundColor: `#${COLORS[color]}` }}></div>
|
||||||
Outside color menu, <GridPosition index={color - 108} cols={2} />
|
Outside color menu, <GridPosition index={color - 108} cols={2} />
|
||||||
</>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -372,6 +372,8 @@ export default function SubmitForm() {
|
||||||
<PortraitUpload setImage={setMiiPortraitUri} />
|
<PortraitUpload setImage={setMiiPortraitUri} />
|
||||||
<SwitchSubmitTutorialButton />
|
<SwitchSubmitTutorialButton />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<p className="text-xs text-zinc-400 text-center mt-2">You must upload a screenshot of the features, check tutorial on how.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* (3DS only) QR code scanning */}
|
{/* (3DS only) QR code scanning */}
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,30 @@ interface Props {
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
color: number;
|
color: number;
|
||||||
setColor: (color: number) => void;
|
setColor: (color: number) => void;
|
||||||
|
tab?: "hair" | "eyes" | "lips" | "glasses" | "eyeliner";
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ColorPicker({ disabled, color, setColor }: Props) {
|
export default function ColorPicker({ disabled, color, setColor, tab = "hair" }: Props) {
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const [isVisible, setIsVisible] = useState(false);
|
const [isVisible, setIsVisible] = useState(false);
|
||||||
|
|
||||||
|
const getExtraSlice = () => {
|
||||||
|
switch (tab) {
|
||||||
|
case "hair":
|
||||||
|
return { start: 0, end: 8 };
|
||||||
|
case "eyes":
|
||||||
|
return { start: 122, end: 128 };
|
||||||
|
case "lips":
|
||||||
|
return { start: 128, end: 133 };
|
||||||
|
case "glasses":
|
||||||
|
return { start: 133, end: 139 };
|
||||||
|
case "eyeliner":
|
||||||
|
return { start: 139, end: 152 };
|
||||||
|
default:
|
||||||
|
return { start: 108, end: 122 };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const close = () => {
|
const close = () => {
|
||||||
setIsVisible(false);
|
setIsVisible(false);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
|
@ -38,7 +56,7 @@ export default function ColorPicker({ disabled, color, setColor }: Props) {
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
className={`w-full flex gap-1.5 mb-2 p-2 rounded-xl shadow ${disabled ? "bg-zinc-300 opacity-50 cursor-not-allowed" : "bg-zinc-100 cursor-pointer"}`}
|
className={`w-20 flex gap-1.5 mb-2 p-2 rounded-xl shadow ${disabled ? "bg-zinc-300 opacity-50 cursor-not-allowed" : "bg-zinc-100 cursor-pointer"}`}
|
||||||
>
|
>
|
||||||
<Icon icon={"material-symbols:palette"} className="text-xl" />
|
<Icon icon={"material-symbols:palette"} className="text-xl" />
|
||||||
<div className="grow rounded" style={{ backgroundColor: `#${COLORS[color]}` }}></div>
|
<div className="grow rounded" style={{ backgroundColor: `#${COLORS[color]}` }}></div>
|
||||||
|
|
@ -46,7 +64,7 @@ export default function ColorPicker({ disabled, color, setColor }: Props) {
|
||||||
|
|
||||||
{isOpen && (
|
{isOpen && (
|
||||||
<div
|
<div
|
||||||
className={`absolute inset-0 w-122 p-4 bg-orange-100 rounded-lg transition-transform duration-500
|
className={`absolute inset-0 z-10 w-full p-4 bg-orange-100 rounded-lg transition-transform duration-500
|
||||||
flex items-center ${isVisible ? "opacity-100" : "opacity-0"}`}
|
flex items-center ${isVisible ? "opacity-100" : "opacity-0"}`}
|
||||||
style={{
|
style={{
|
||||||
transition: isVisible
|
transition: isVisible
|
||||||
|
|
@ -55,25 +73,28 @@ export default function ColorPicker({ disabled, color, setColor }: Props) {
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="mr-8 flex flex-col gap-0.5">
|
<div className="mr-8 flex flex-col gap-0.5">
|
||||||
{COLORS.slice(0, 8).map((c, i) => (
|
{COLORS.slice(getExtraSlice().start, getExtraSlice().end).map((c, i) => {
|
||||||
<button
|
const actualIndex = i + getExtraSlice().start;
|
||||||
type="button"
|
return (
|
||||||
key={i}
|
<button
|
||||||
onClick={() => setColor(i)}
|
type="button"
|
||||||
className={`size-7.5 cursor-pointer rounded-md ring-orange-500 ring-offset-2 ${color === i ? "ring-2 z-10" : ""}`}
|
key={actualIndex}
|
||||||
style={{
|
onClick={() => setColor(actualIndex)}
|
||||||
backgroundColor: `#${c}`,
|
className={`size-7.5 cursor-pointer rounded-md ring-orange-500 ring-offset-2 ${color === actualIndex ? "ring-2 z-10" : ""}`}
|
||||||
opacity: isVisible ? 1 : 0,
|
style={{
|
||||||
transform: isVisible ? "scale(1)" : "scale(0.7)",
|
backgroundColor: `#${c}`,
|
||||||
transition: `opacity 250ms ease, transform 320ms cubic-bezier(0.34, 1.4, 0.64, 1)`,
|
opacity: isVisible ? 1 : 0,
|
||||||
// stagger by column then row for a wave effect
|
transform: isVisible ? "scale(1)" : "scale(0.7)",
|
||||||
transitionDelay: isVisible ? `${120 + (i % 10) * 18 + Math.floor(i / 10) * 10}ms` : "0ms",
|
transition: `opacity 250ms ease, transform 320ms cubic-bezier(0.34, 1.4, 0.64, 1)`,
|
||||||
}}
|
// stagger by column then row for a wave effect
|
||||||
></button>
|
transitionDelay: isVisible ? `${120 + (i % 10) * 18 + Math.floor(i / 10) * 10}ms` : "0ms",
|
||||||
))}
|
}}
|
||||||
|
></button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-10 gap-0.5">
|
<div className="grid grid-cols-10 gap-0.5 overflow-x-auto">
|
||||||
{COLORS.slice(8, 108).map((c, i) => (
|
{COLORS.slice(8, 108).map((c, i) => (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|
|
||||||
|
|
@ -50,14 +50,14 @@ export default function MiiEditor({ instructions }: Props) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="w-full aspect-video flex bg-orange-100 border-2 border-orange-200 rounded-xl overflow-hidden">
|
<div className="w-full h-91 flex flex-col sm:flex-row bg-orange-100 border-2 border-orange-200 rounded-xl overflow-hidden">
|
||||||
<div className="w-9 h-full flex flex-col">
|
<div className="h-9 flex flex-row sm:flex-col">
|
||||||
{(Object.keys(TAB_COMPONENTS) as Tab[]).map((t) => (
|
{(Object.keys(TAB_COMPONENTS) as Tab[]).map((t) => (
|
||||||
<button
|
<button
|
||||||
key={t}
|
key={t}
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setTab(t)}
|
onClick={() => setTab(t)}
|
||||||
className={`size-9 flex justify-center items-center text-[1.35rem] cursor-pointer bg-orange-200 hover:bg-orange-300 transition-colors duration-75 ${tab === t ? "bg-orange-100!" : ""}`}
|
className={`size-full aspect-square flex justify-center items-center text-[1.35rem] cursor-pointer bg-orange-200 hover:bg-orange-300 transition-colors duration-75 ${tab === t ? "bg-orange-100!" : ""}`}
|
||||||
>
|
>
|
||||||
{/* ml because of border on left causing icons to look miscentered */}
|
{/* ml because of border on left causing icons to look miscentered */}
|
||||||
<Icon icon={TAB_ICONS[t]} className="-ml-0.5" />
|
<Icon icon={TAB_ICONS[t]} className="-ml-0.5" />
|
||||||
|
|
@ -69,9 +69,8 @@ export default function MiiEditor({ instructions }: Props) {
|
||||||
{(Object.keys(TAB_COMPONENTS) as Tab[]).map((t) => {
|
{(Object.keys(TAB_COMPONENTS) as Tab[]).map((t) => {
|
||||||
const TabComponent = TAB_COMPONENTS[t];
|
const TabComponent = TAB_COMPONENTS[t];
|
||||||
return (
|
return (
|
||||||
<div key={t} className={t === tab ? "grow flex relative" : "hidden"}>
|
<div key={t} className={t === tab ? "grow relative p-3" : "hidden"}>
|
||||||
<TabComponent instructions={instructions} />
|
<TabComponent instructions={instructions} />
|
||||||
<p className="absolute top-32 left-32 z-10 text-lg font-bold w-48 text-center">Your parts screenshot should handle the types!</p>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
|
||||||
|
|
@ -14,9 +14,9 @@ export default function NumberInputs({ target }: Props) {
|
||||||
if (!target) return null;
|
if (!target) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-rows-5 min-h-0">
|
<div className="grid grid-cols-2 gap-x-4 h-min w-fit">
|
||||||
{target.height !== undefined && (
|
{target.height !== undefined && (
|
||||||
<div className="w-full">
|
<div>
|
||||||
<label htmlFor="height" className="text-xs">
|
<label htmlFor="height" className="text-xs">
|
||||||
Height
|
Height
|
||||||
</label>
|
</label>
|
||||||
|
|
@ -37,7 +37,7 @@ export default function NumberInputs({ target }: Props) {
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{target.distance !== undefined && (
|
{target.distance !== undefined && (
|
||||||
<div className="w-full">
|
<div>
|
||||||
<label htmlFor="distance" className="text-xs">
|
<label htmlFor="distance" className="text-xs">
|
||||||
Distance
|
Distance
|
||||||
</label>
|
</label>
|
||||||
|
|
@ -58,7 +58,7 @@ export default function NumberInputs({ target }: Props) {
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{target.rotation !== undefined && (
|
{target.rotation !== undefined && (
|
||||||
<div className="w-full">
|
<div>
|
||||||
<label htmlFor="rotation" className="text-xs">
|
<label htmlFor="rotation" className="text-xs">
|
||||||
Rotation
|
Rotation
|
||||||
</label>
|
</label>
|
||||||
|
|
@ -79,7 +79,7 @@ export default function NumberInputs({ target }: Props) {
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{target.size !== undefined && (
|
{target.size !== undefined && (
|
||||||
<div className="w-full">
|
<div>
|
||||||
<label htmlFor="size" className="text-xs">
|
<label htmlFor="size" className="text-xs">
|
||||||
Size
|
Size
|
||||||
</label>
|
</label>
|
||||||
|
|
@ -100,7 +100,7 @@ export default function NumberInputs({ target }: Props) {
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{target.stretch !== undefined && (
|
{target.stretch !== undefined && (
|
||||||
<div className="w-full">
|
<div>
|
||||||
<label htmlFor="stretch" className="text-xs">
|
<label htmlFor="stretch" className="text-xs">
|
||||||
Stretch
|
Stretch
|
||||||
</label>
|
</label>
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,18 @@
|
||||||
import { SwitchMiiInstructions } from "@/types";
|
import { SwitchMiiInstructions } from "@/types";
|
||||||
import NumberInputs from "../number-inputs";
|
import NumberInputs from "../number-inputs";
|
||||||
import { useState } from "react";
|
|
||||||
|
|
||||||
interface Props {
|
interface EarsProps {
|
||||||
instructions: React.RefObject<SwitchMiiInstructions>;
|
instructions: React.RefObject<SwitchMiiInstructions>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function EarsTab({ instructions }: Props) {
|
export default function EarsTab({ instructions }: EarsProps) {
|
||||||
const [type, setType] = useState(0);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative grow p-3 pb-0!">
|
<>
|
||||||
<div className="flex h-full">
|
<h1 className="absolute font-bold text-xl">Ears</h1>
|
||||||
<div className="grow flex flex-col">
|
|
||||||
<div className="flex items-center h-8">
|
|
||||||
<h1 className="font-bold text-xl">Ears</h1>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="shrink-0 w-21 pb-3 flex flex-col items-center">
|
<div className="size-full flex flex-col justify-center items-center">
|
||||||
<NumberInputs target={instructions.current.ears} />
|
<NumberInputs target={instructions.current.ears} />
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,36 +1,29 @@
|
||||||
|
import { useState } from "react";
|
||||||
import { SwitchMiiInstructions } from "@/types";
|
import { SwitchMiiInstructions } from "@/types";
|
||||||
import ColorPicker from "../color-picker";
|
import ColorPicker from "../color-picker";
|
||||||
import NumberInputs from "../number-inputs";
|
import NumberInputs from "../number-inputs";
|
||||||
import { useState } from "react";
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
instructions: React.RefObject<SwitchMiiInstructions>;
|
instructions: React.RefObject<SwitchMiiInstructions>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function EyebrowsTab({ instructions }: Props) {
|
export default function EyebrowsTab({ instructions }: Props) {
|
||||||
const [type, setType] = useState(27);
|
|
||||||
const [color, setColor] = useState(0);
|
const [color, setColor] = useState(0);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative grow p-3 pb-0!">
|
<>
|
||||||
<div className="flex h-full">
|
<h1 className="absolute font-bold text-xl">Eyebrows</h1>
|
||||||
<div className="grow flex flex-col">
|
|
||||||
<div className="flex items-center h-8">
|
|
||||||
<h1 className="font-bold text-xl">Eyebrows</h1>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="shrink-0 w-21 pb-3 flex flex-col items-center">
|
<div className="size-full flex flex-col justify-center items-center">
|
||||||
<ColorPicker
|
<ColorPicker
|
||||||
color={color}
|
color={color}
|
||||||
setColor={(i) => {
|
setColor={(i) => {
|
||||||
setColor(i);
|
setColor(i);
|
||||||
instructions.current.eyebrows.color = i;
|
instructions.current.eyebrows.color = i;
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<NumberInputs target={instructions.current.eyebrows} />
|
<NumberInputs target={instructions.current.eyebrows} />
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ const TABS: { name: keyof SwitchMiiInstructions["eyes"]; length: number; colorsD
|
||||||
{ name: "pupil", length: 10, colorsDisabled: true },
|
{ name: "pupil", length: 10, colorsDisabled: true },
|
||||||
];
|
];
|
||||||
|
|
||||||
export default function OtherTab({ instructions }: Props) {
|
export default function EyesTab({ instructions }: Props) {
|
||||||
const [tab, setTab] = useState(0);
|
const [tab, setTab] = useState(0);
|
||||||
|
|
||||||
// One type/color state per tab
|
// One type/color state per tab
|
||||||
|
|
@ -36,36 +36,28 @@ export default function OtherTab({ instructions }: Props) {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative grow p-3 pb-0!">
|
<>
|
||||||
<div className="flex h-full">
|
<h1 className="absolute font-bold text-xl">Eyes</h1>
|
||||||
<div className="grow flex flex-col">
|
|
||||||
<div className="flex items-center h-8">
|
|
||||||
<h1 className="absolute font-bold text-xl">Eyes</h1>
|
|
||||||
|
|
||||||
<div className="flex justify-center grow">
|
<div className="absolute right-3 z-10 flex justify-end">
|
||||||
<div className="rounded-2xl bg-orange-200">
|
<div className="rounded-2xl bg-orange-200">
|
||||||
{TABS.map((_, i) => (
|
{TABS.map((_, i) => (
|
||||||
<button
|
<button
|
||||||
key={i}
|
key={i}
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setTab(i)}
|
onClick={() => setTab(i)}
|
||||||
className={`px-3 py-1 rounded-2xl cursor-pointer hover:bg-orange-300/50 transition-colors duration-75 ${tab === i ? "bg-orange-300!" : "orange-200"}`}
|
className={`px-3 py-1 rounded-2xl cursor-pointer hover:bg-orange-300/50 transition-colors duration-75 ${tab === i ? "bg-orange-300!" : "orange-200"}`}
|
||||||
>
|
>
|
||||||
{i}
|
{i + 1}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="shrink-0 w-21 pb-3 flex flex-col items-center">
|
|
||||||
<div className={`${currentTab.colorsDisabled ? "hidden" : "w-full"}`}>
|
|
||||||
<ColorPicker color={colors[tab]} setColor={setColor} />
|
|
||||||
</div>
|
|
||||||
<NumberInputs target={instructions.current.eyes[currentTab.name]} />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
<div className="absolute inset-0 flex flex-col justify-center items-center">
|
||||||
|
<ColorPicker disabled={currentTab.colorsDisabled} color={colors[tab]} setColor={setColor} tab={tab === 5 ? "eyeliner" : "eyes"} />
|
||||||
|
<NumberInputs target={instructions.current.eyes[currentTab.name]} />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,32 +12,28 @@ export default function GlassesTab({ instructions }: Props) {
|
||||||
const [shadesColor, setShadesColor] = useState(0);
|
const [shadesColor, setShadesColor] = useState(0);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative grow p-3 pb-0!">
|
<>
|
||||||
<div className="flex h-full">
|
<h1 className="absolute font-bold text-xl">Glasses</h1>
|
||||||
<div className="grow flex flex-col">
|
|
||||||
<div className="flex items-center h-8">
|
|
||||||
<h1 className="font-bold text-xl">Glasses</h1>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="shrink-0 w-21 pb-3 flex flex-col items-center">
|
<div className="size-full flex flex-col justify-center items-center">
|
||||||
<ColorPicker
|
<ColorPicker
|
||||||
color={ringColor}
|
color={ringColor}
|
||||||
setColor={(i) => {
|
setColor={(i) => {
|
||||||
setRingColor(i);
|
setRingColor(i);
|
||||||
instructions.current.glasses.ringColor = i;
|
instructions.current.glasses.ringColor = i;
|
||||||
}}
|
}}
|
||||||
/>
|
tab="glasses"
|
||||||
<ColorPicker
|
/>
|
||||||
color={shadesColor}
|
<ColorPicker
|
||||||
setColor={(i) => {
|
color={shadesColor}
|
||||||
setShadesColor(i);
|
setColor={(i) => {
|
||||||
instructions.current.glasses.shadesColor = i;
|
setShadesColor(i);
|
||||||
}}
|
instructions.current.glasses.shadesColor = i;
|
||||||
/>
|
}}
|
||||||
<NumberInputs target={instructions.current.glasses} />
|
tab="glasses"
|
||||||
</div>
|
/>
|
||||||
|
<NumberInputs target={instructions.current.glasses} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,122 +16,114 @@ export default function HairTab({ instructions }: Props) {
|
||||||
const [style, setStyle] = useState<number | null>(null);
|
const [style, setStyle] = useState<number | null>(null);
|
||||||
const [isFlipped, setIsFlipped] = useState(false);
|
const [isFlipped, setIsFlipped] = useState(false);
|
||||||
|
|
||||||
const length = tab === "sets" ? 245 : tab === "bangs" ? 83 : 111;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative grow p-3 pb-0!">
|
<>
|
||||||
<div className="flex h-full">
|
<h1 className="absolute font-bold text-xl">Hair</h1>
|
||||||
<div className="grow flex flex-col">
|
|
||||||
<div className="flex items-center h-8">
|
|
||||||
<h1 className="absolute font-bold text-xl">Hair</h1>
|
|
||||||
|
|
||||||
<div className="flex justify-center grow">
|
<div className="absolute right-3 z-10 flex justify-end">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setTab("sets")}
|
onClick={() => setTab("sets")}
|
||||||
className={`px-3 py-1 rounded-2xl bg-orange-200 mr-1 cursor-pointer hover:bg-orange-300/50 transition-colors duration-75 ${tab === "sets" ? "bg-orange-300!" : "orange-200"}`}
|
className={`px-3 py-1 rounded-2xl bg-orange-200 mr-1 cursor-pointer hover:bg-orange-300/50 transition-colors duration-75 ${tab === "sets" ? "bg-orange-300!" : "orange-200"}`}
|
||||||
>
|
>
|
||||||
Sets
|
Sets
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div className="rounded-2xl bg-orange-200 flex">
|
<div className="rounded-2xl bg-orange-200 flex">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setTab("bangs")}
|
onClick={() => setTab("bangs")}
|
||||||
className={`px-3 py-1 rounded-2xl cursor-pointer hover:bg-orange-300/50 transition-colors duration-75 ${tab === "bangs" ? "bg-orange-300!" : "orange-200"}`}
|
className={`px-3 py-1 rounded-2xl cursor-pointer hover:bg-orange-300/50 transition-colors duration-75 ${tab === "bangs" ? "bg-orange-300!" : "orange-200"}`}
|
||||||
>
|
>
|
||||||
Bangs
|
Bangs
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setTab("back")}
|
onClick={() => setTab("back")}
|
||||||
className={`px-3 py-1 rounded-2xl cursor-pointer hover:bg-orange-300/50 transition-colors duration-75 ${tab === "back" ? "bg-orange-300!" : "orange-200"}`}
|
className={`px-3 py-1 rounded-2xl cursor-pointer hover:bg-orange-300/50 transition-colors duration-75 ${tab === "back" ? "bg-orange-300!" : "orange-200"}`}
|
||||||
>
|
>
|
||||||
Back
|
Back
|
||||||
</button>
|
</button>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="shrink-0 w-21 pb-3 flex flex-col items-center">
|
<div className="absolute inset-0 flex flex-col justify-center items-center">
|
||||||
<ColorPicker
|
<ColorPicker
|
||||||
color={color}
|
color={color}
|
||||||
setColor={(i) => {
|
setColor={(i) => {
|
||||||
setColor(i);
|
setColor(i);
|
||||||
instructions.current.hair.color = i;
|
instructions.current.hair.color = i;
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="flex gap-1.5 items-center mb-2 w-full">
|
<div className="flex gap-1.5 items-center mb-2">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
id="subcolor"
|
id="subcolor"
|
||||||
className="checkbox"
|
className="checkbox"
|
||||||
checked={tab === "back" ? subColor2 !== null : subColor !== null}
|
checked={tab === "back" ? subColor2 !== null : subColor !== null}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
if (tab === "back") {
|
|
||||||
setSubColor2(e.target.checked ? 0 : null);
|
|
||||||
instructions.current.hair.subColor2 = e.target.checked ? 0 : null;
|
|
||||||
} else {
|
|
||||||
setSubColor(e.target.checked ? 0 : null);
|
|
||||||
instructions.current.hair.subColor = e.target.checked ? 0 : null;
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<label htmlFor="subcolor" className="text-xs">
|
|
||||||
Sub color
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ColorPicker
|
|
||||||
disabled={tab === "back" ? subColor2 === null : subColor === null}
|
|
||||||
color={tab === "back" ? (subColor2 ?? 0) : (subColor ?? 0)}
|
|
||||||
setColor={(i) => {
|
|
||||||
if (tab === "back") {
|
if (tab === "back") {
|
||||||
setSubColor2(i);
|
setSubColor2(e.target.checked ? 0 : null);
|
||||||
instructions.current.hair.subColor2 = i;
|
instructions.current.hair.subColor2 = e.target.checked ? 0 : null;
|
||||||
} else {
|
} else {
|
||||||
setSubColor(i);
|
setSubColor(e.target.checked ? 0 : null);
|
||||||
instructions.current.hair.subColor = i;
|
instructions.current.hair.subColor = e.target.checked ? 0 : null;
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<label htmlFor="subcolor" className="text-xs">
|
||||||
|
Sub color
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
<p className="text-sm mb-1">Tying style</p>
|
<ColorPicker
|
||||||
<div className="grid grid-cols-3 w-full gap-0.5">
|
disabled={tab === "back" ? subColor2 === null : subColor === null}
|
||||||
{Array.from({ length: 3 }).map((_, i) => (
|
color={tab === "back" ? (subColor2 ?? 0) : (subColor ?? 0)}
|
||||||
<button
|
setColor={(i) => {
|
||||||
type="button"
|
if (tab === "back") {
|
||||||
key={i}
|
setSubColor2(i);
|
||||||
onClick={() => {
|
instructions.current.hair.subColor2 = i;
|
||||||
setStyle(i);
|
} else {
|
||||||
instructions.current.hair.style = i;
|
setSubColor(i);
|
||||||
}}
|
instructions.current.hair.subColor = i;
|
||||||
className={`size-full aspect-square cursor-pointer hover:bg-orange-300 transition-colors duration-100 rounded-lg ${style === i ? "bg-orange-400!" : ""}`}
|
}
|
||||||
>
|
}}
|
||||||
{i + 1}
|
/>
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex gap-1.5 items-center w-full mt-auto">
|
<p className="text-sm mb-1">Tying style</p>
|
||||||
<input
|
<div className="grid grid-cols-3 gap-0.5">
|
||||||
type="checkbox"
|
{Array.from({ length: 3 }).map((_, i) => (
|
||||||
id="subcolor"
|
<button
|
||||||
className="checkbox"
|
type="button"
|
||||||
checked={isFlipped}
|
key={i}
|
||||||
onChange={(e) => {
|
onClick={() => {
|
||||||
setIsFlipped(e.target.checked);
|
setStyle(i);
|
||||||
instructions.current.hair.isFlipped = e.target.checked;
|
instructions.current.hair.style = i;
|
||||||
}}
|
}}
|
||||||
/>
|
className={`size-full aspect-square cursor-pointer hover:bg-orange-300 transition-colors duration-100 rounded-lg ${style === i ? "bg-orange-400!" : ""}`}
|
||||||
<label htmlFor="subcolor" className="text-xs">
|
>
|
||||||
Flip
|
{i + 1}
|
||||||
</label>
|
</button>
|
||||||
</div>
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex gap-1.5 items-center mt-4">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
id="subcolor"
|
||||||
|
className="checkbox"
|
||||||
|
checked={isFlipped}
|
||||||
|
onChange={(e) => {
|
||||||
|
setIsFlipped(e.target.checked);
|
||||||
|
instructions.current.hair.isFlipped = e.target.checked;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<label htmlFor="subcolor" className="text-xs">
|
||||||
|
Flip
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,39 +10,32 @@ const COLORS = ["FFD8BA", "FFD5AC", "FEC1A4", "FEC68F", "FEB089", "FEBA6B", "F39
|
||||||
|
|
||||||
export default function HeadTab({ instructions }: Props) {
|
export default function HeadTab({ instructions }: Props) {
|
||||||
const [color, setColor] = useState(109);
|
const [color, setColor] = useState(109);
|
||||||
const [type, setType] = useState(1);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative grow p-3 pb-0!">
|
<>
|
||||||
<div className="flex h-full">
|
<h1 className="absolute font-bold text-xl">Head</h1>
|
||||||
<div className="grow flex flex-col">
|
|
||||||
<div className="flex items-center h-8">
|
|
||||||
<h1 className="font-bold text-xl">Head</h1>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="shrink-0 w-21 pb-3 flex flex-col items-center">
|
<div className="size-full flex flex-col justify-center items-center">
|
||||||
<ColorPicker
|
<ColorPicker
|
||||||
color={color}
|
color={color}
|
||||||
setColor={(i) => {
|
setColor={(i) => {
|
||||||
setColor(i);
|
setColor(i);
|
||||||
instructions.current.head.skinColor = i;
|
instructions.current.head.skinColor = i;
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="grid grid-cols-2 gap-1 w-fit mt-auto">
|
<div className="grid grid-cols-7 gap-1">
|
||||||
{COLORS.map((hex, i) => (
|
{COLORS.map((hex, i) => (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
key={i + 108}
|
key={i + 108}
|
||||||
onClick={() => setColor(i + 108)}
|
onClick={() => setColor(i + 108)}
|
||||||
className={`size-9 rounded-lg cursor-pointer ring-offset-2 ring-orange-500 ${color === i + 108 ? "ring-2" : ""}`}
|
className={`size-9 rounded-lg cursor-pointer ring-offset-2 ring-orange-500 ${color === i + 108 ? "ring-2" : ""}`}
|
||||||
style={{ backgroundColor: `#${hex}` }}
|
style={{ backgroundColor: `#${hex}` }}
|
||||||
></button>
|
></button>
|
||||||
))}
|
))}
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,41 +12,36 @@ export default function LipsTab({ instructions }: Props) {
|
||||||
const [hasLipstick, setHasLipstick] = useState(false);
|
const [hasLipstick, setHasLipstick] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative grow p-3 pb-0!">
|
<>
|
||||||
<div className="flex h-full">
|
<h1 className="absolute font-bold text-xl">Lips</h1>
|
||||||
<div className="grow flex flex-col">
|
|
||||||
<div className="flex items-center h-8">
|
|
||||||
<h1 className="font-bold text-xl">Lips</h1>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="shrink-0 w-21 pb-3 flex flex-col items-center">
|
<div className="size-full flex flex-col justify-center items-center">
|
||||||
<ColorPicker
|
<ColorPicker
|
||||||
color={color}
|
color={color}
|
||||||
setColor={(i) => {
|
setColor={(i) => {
|
||||||
setColor(i);
|
setColor(i);
|
||||||
instructions.current.lips.color = i;
|
instructions.current.lips.color = i;
|
||||||
|
}}
|
||||||
|
tab="lips"
|
||||||
|
/>
|
||||||
|
<NumberInputs target={instructions.current.lips} />
|
||||||
|
|
||||||
|
<div className="flex gap-1.5 items-center mt-4">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
id="subcolor"
|
||||||
|
className="checkbox"
|
||||||
|
checked={hasLipstick}
|
||||||
|
onChange={(e) => {
|
||||||
|
setHasLipstick(e.target.checked);
|
||||||
|
instructions.current.lips.hasLipstick = e.target.checked;
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<NumberInputs target={instructions.current.lips} />
|
<label htmlFor="subcolor" className="text-xs">
|
||||||
|
Lipstick
|
||||||
<div className="flex gap-1.5 items-center w-full mt-auto">
|
</label>
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
id="subcolor"
|
|
||||||
className="checkbox"
|
|
||||||
checked={hasLipstick}
|
|
||||||
onChange={(e) => {
|
|
||||||
setHasLipstick(e.target.checked);
|
|
||||||
instructions.current.lips.hasLipstick = e.target.checked;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<label htmlFor="subcolor" className="text-xs">
|
|
||||||
Lipstick
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,119 +31,113 @@ export default function HeadTab({ instructions }: Props) {
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative grow p-3 pb-0!">
|
<>
|
||||||
<div className="flex h-full">
|
<h1 className="font-bold text-xl">Misc</h1>
|
||||||
<div className="grow flex flex-col">
|
|
||||||
<div className="flex items-center h-8">
|
<div className="grow h-full overflow-y-auto pb-3">
|
||||||
<h1 className="font-bold text-xl">Misc</h1>
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<div className="flex items-center gap-4 text-zinc-500 text-sm font-medium">
|
||||||
|
<hr className="grow border-zinc-300" />
|
||||||
|
<span>Body</span>
|
||||||
|
<hr className="grow border-zinc-300" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<label htmlFor="height" className="text-sm">
|
||||||
|
Height
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
id="height"
|
||||||
|
min={0}
|
||||||
|
max={100}
|
||||||
|
step={1}
|
||||||
|
value={height}
|
||||||
|
onChange={(e) => {
|
||||||
|
setHeight(e.target.valueAsNumber);
|
||||||
|
instructions.current.height = e.target.valueAsNumber;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<label htmlFor="weight" className="text-sm">
|
||||||
|
Weight
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
id="weight"
|
||||||
|
min={0}
|
||||||
|
max={100}
|
||||||
|
step={1}
|
||||||
|
value={weight}
|
||||||
|
onChange={(e) => {
|
||||||
|
setWeight(e.target.valueAsNumber);
|
||||||
|
instructions.current.weight = e.target.valueAsNumber;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-4 text-zinc-500 text-sm font-medium mt-1.5 mb-2">
|
||||||
|
<hr className="grow border-zinc-300" />
|
||||||
|
<span>Dating Preferences</span>
|
||||||
|
<hr className="grow border-zinc-300" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-1.5">
|
||||||
|
<DatingPreferencesViewer
|
||||||
|
data={datingPreferences}
|
||||||
|
onChecked={(e, gender) => {
|
||||||
|
setDatingPreferences((prev) => {
|
||||||
|
const updated = e.target.checked ? (prev.includes(gender) ? prev : [...prev, gender]) : prev.filter((p) => p !== gender);
|
||||||
|
instructions.current.datingPreferences = updated;
|
||||||
|
return updated;
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grow overflow-y-auto">
|
<div>
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="flex items-center gap-4 text-zinc-500 text-sm font-medium">
|
||||||
<div>
|
|
||||||
<div className="flex items-center gap-4 text-zinc-500 text-sm font-medium">
|
|
||||||
<hr className="grow border-zinc-300" />
|
|
||||||
<span>Body</span>
|
|
||||||
<hr className="grow border-zinc-300" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex flex-col">
|
|
||||||
<label htmlFor="height" className="text-sm">
|
|
||||||
Height
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="range"
|
|
||||||
id="height"
|
|
||||||
min={0}
|
|
||||||
max={100}
|
|
||||||
step={1}
|
|
||||||
value={height}
|
|
||||||
onChange={(e) => {
|
|
||||||
setHeight(e.target.valueAsNumber);
|
|
||||||
instructions.current.height = e.target.valueAsNumber;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex flex-col">
|
|
||||||
<label htmlFor="weight" className="text-sm">
|
|
||||||
Weight
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="range"
|
|
||||||
id="weight"
|
|
||||||
min={0}
|
|
||||||
max={100}
|
|
||||||
step={1}
|
|
||||||
value={weight}
|
|
||||||
onChange={(e) => {
|
|
||||||
setWeight(e.target.valueAsNumber);
|
|
||||||
instructions.current.weight = e.target.valueAsNumber;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center gap-4 text-zinc-500 text-sm font-medium mt-1.5 mb-2">
|
|
||||||
<hr className="grow border-zinc-300" />
|
|
||||||
<span>Dating Preferences</span>
|
|
||||||
<hr className="grow border-zinc-300" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex flex-col gap-1.5">
|
|
||||||
<DatingPreferencesViewer
|
|
||||||
data={datingPreferences}
|
|
||||||
onChecked={(e, gender) => {
|
|
||||||
setDatingPreferences((prev) => {
|
|
||||||
const updated = e.target.checked ? (prev.includes(gender) ? prev : [...prev, gender]) : prev.filter((p) => p !== gender);
|
|
||||||
instructions.current.datingPreferences = updated;
|
|
||||||
return updated;
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<div className="flex items-center gap-4 text-zinc-500 text-sm font-medium">
|
|
||||||
<hr className="grow border-zinc-300" />
|
|
||||||
<span>Voice</span>
|
|
||||||
<hr className="grow border-zinc-300" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<VoiceViewer
|
|
||||||
data={voice}
|
|
||||||
onClick={(e, label) => {
|
|
||||||
setVoice((p) => ({ ...p, [label]: e.target.valueAsNumber }));
|
|
||||||
instructions.current.voice[label as keyof typeof voice] = e.target.valueAsNumber;
|
|
||||||
}}
|
|
||||||
onClickTone={(i) => {
|
|
||||||
setVoice((p) => ({ ...p, tone: i }));
|
|
||||||
instructions.current.voice.tone = i;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center gap-4 text-zinc-500 text-sm font-medium mt-2 mb-2">
|
|
||||||
<hr className="grow border-zinc-300" />
|
<hr className="grow border-zinc-300" />
|
||||||
<span>Personality</span>
|
<span>Voice</span>
|
||||||
<hr className="grow border-zinc-300" />
|
<hr className="grow border-zinc-300" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<PersonalityViewer
|
<VoiceViewer
|
||||||
data={personality}
|
data={voice}
|
||||||
onClick={(key, i) => {
|
onClick={(e, label) => {
|
||||||
setPersonality((p) => {
|
setVoice((p) => ({ ...p, [label]: e.target.valueAsNumber }));
|
||||||
const updated = { ...p, [key]: i };
|
instructions.current.voice[label as keyof typeof voice] = e.target.valueAsNumber;
|
||||||
instructions.current.personality = updated;
|
}}
|
||||||
return updated;
|
onClickTone={(i) => {
|
||||||
});
|
setVoice((p) => ({ ...p, tone: i }));
|
||||||
instructions.current.personality = personality;
|
instructions.current.voice.tone = i;
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-4 text-zinc-500 text-sm font-medium mt-2 mb-2">
|
||||||
|
<hr className="grow border-zinc-300" />
|
||||||
|
<span>Personality</span>
|
||||||
|
<hr className="grow border-zinc-300" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<PersonalityViewer
|
||||||
|
data={personality}
|
||||||
|
onClick={(key, i) => {
|
||||||
|
setPersonality((p) => {
|
||||||
|
const updated = { ...p, [key]: i };
|
||||||
|
instructions.current.personality = updated;
|
||||||
|
return updated;
|
||||||
|
});
|
||||||
|
instructions.current.personality = personality;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,18 +7,12 @@ interface Props {
|
||||||
|
|
||||||
export default function NoseTab({ instructions }: Props) {
|
export default function NoseTab({ instructions }: Props) {
|
||||||
return (
|
return (
|
||||||
<div className="relative grow p-3 pb-0!">
|
<>
|
||||||
<div className="flex h-full">
|
<h1 className="absolute font-bold text-xl">Nose</h1>
|
||||||
<div className="grow flex flex-col">
|
|
||||||
<div className="flex items-center h-8">
|
|
||||||
<h1 className="font-bold text-xl">Nose</h1>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="shrink-0 w-21 pb-3 flex flex-col items-center">
|
<div className="size-full flex flex-col justify-center items-center">
|
||||||
<NumberInputs target={instructions.current.nose} />
|
<NumberInputs target={instructions.current.nose} />
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,52 +38,46 @@ export default function OtherTab({ instructions }: Props) {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative grow p-3 pb-0!">
|
<>
|
||||||
<div className="flex h-full">
|
<h1 className="absolute font-bold text-xl">Other</h1>
|
||||||
<div className="grow flex flex-col">
|
|
||||||
<div className="flex items-center h-8">
|
|
||||||
<h1 className="absolute font-bold text-xl">Other</h1>
|
|
||||||
|
|
||||||
<div className="flex justify-center grow">
|
<div className="absolute right-3 z-10 flex justify-end">
|
||||||
<div className="rounded-2xl bg-orange-200">
|
<div className="rounded-2xl bg-orange-200">
|
||||||
{TABS.map((_, i) => (
|
{TABS.map((_, i) => (
|
||||||
<button
|
<button
|
||||||
key={i}
|
key={i}
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setTab(i)}
|
onClick={() => setTab(i)}
|
||||||
className={`px-3 py-1 rounded-2xl cursor-pointer hover:bg-orange-300/50 transition-colors duration-75 ${tab === i ? "bg-orange-300!" : "orange-200"}`}
|
className={`px-3 py-1 rounded-2xl cursor-pointer hover:bg-orange-300/50 transition-colors duration-75 ${tab === i ? "bg-orange-300!" : "orange-200"}`}
|
||||||
>
|
>
|
||||||
{i}
|
{i + 1}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="shrink-0 w-21 pb-3 flex flex-col items-center">
|
|
||||||
<ColorPicker color={colors[tab]} setColor={setColor} />
|
|
||||||
<NumberInputs target={instructions.current.other[currentTab.name]} />
|
|
||||||
|
|
||||||
{tab === 3 && (
|
|
||||||
<div className="flex gap-1.5 items-center w-full mt-auto">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
id="subcolor"
|
|
||||||
className="checkbox"
|
|
||||||
checked={isFlipped}
|
|
||||||
onChange={(e) => {
|
|
||||||
setIsFlipped(e.target.checked);
|
|
||||||
instructions.current.other.moustache.isFlipped = e.target.checked;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<label htmlFor="subcolor" className="text-xs">
|
|
||||||
Flip
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
<div className="absolute inset-0 flex flex-col justify-center items-center">
|
||||||
|
<ColorPicker disabled={tab === 0 || tab === 1} color={colors[tab]} setColor={setColor} />
|
||||||
|
<NumberInputs target={instructions.current.other[currentTab.name]} />
|
||||||
|
|
||||||
|
{tab === 3 && (
|
||||||
|
<div className="flex gap-1.5 items-center mt-4">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
id="subcolor"
|
||||||
|
className="checkbox"
|
||||||
|
checked={isFlipped}
|
||||||
|
onChange={(e) => {
|
||||||
|
setIsFlipped(e.target.checked);
|
||||||
|
instructions.current.other.moustache.isFlipped = e.target.checked;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<label htmlFor="subcolor" className="text-xs">
|
||||||
|
Flip
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ export default function PortraitUpload({ setImage }: Props) {
|
||||||
<p className="text-center text-sm">
|
<p className="text-center text-sm">
|
||||||
{!hasImage ? (
|
{!hasImage ? (
|
||||||
<>
|
<>
|
||||||
Drag and drop a screenshot of your Mii's parts here
|
Drag and drop a screenshot of your Mii's features here
|
||||||
<br />
|
<br />
|
||||||
or click to open
|
or click to open
|
||||||
</>
|
</>
|
||||||
|
|
|
||||||
|
|
@ -105,7 +105,7 @@ export default function Tutorial({ tutorials, isOpen, setIsOpen }: Props) {
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className={`z-50 bg-orange-50 border-2 border-amber-500 rounded-2xl shadow-lg w-full max-w-md h-120 transition-discrete duration-300 flex flex-col ${
|
className={`z-50 bg-orange-50 border-2 border-amber-500 rounded-2xl shadow-lg w-full max-w-xl h-120 transition-discrete duration-300 flex flex-col ${
|
||||||
isVisible ? "scale-100 opacity-100" : "scale-75 opacity-0"
|
isVisible ? "scale-100 opacity-100" : "scale-75 opacity-0"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -29,11 +29,11 @@ export default function SubmitTutorialButton() {
|
||||||
imageSrc: "/tutorial/switch/submitting/step2.jpg",
|
imageSrc: "/tutorial/switch/submitting/step2.jpg",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: "3. Press Y to open the parts list, take a screenshot then upload to this submit form",
|
text: "3. Press Y to open the features list, then take a screenshot and upload to this submit form",
|
||||||
imageSrc: "/tutorial/switch/submitting/step3.jpg",
|
imageSrc: "/tutorial/switch/submitting/step3.jpg",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: "4. Adding Mii colors and settings is recommended. All instructions are optional; for values like height or distance, use the number of button clicks (positive for up/left, negative for down/right)",
|
text: "4. Adding Mii colors and settings is recommended. All instructions are optional; for values like height or distance, use the number of button clicks (positive for buttons on right, negative for buttons on left)",
|
||||||
imageSrc: "/tutorial/switch/step4.jpg",
|
imageSrc: "/tutorial/switch/step4.jpg",
|
||||||
},
|
},
|
||||||
{ type: "finish" },
|
{ type: "finish" },
|
||||||
|
|
|
||||||
|
|
@ -181,7 +181,7 @@ export async function generateMetadataImage(mii: Mii, author: string): Promise<{
|
||||||
) : (
|
) : (
|
||||||
<div tw="w-40 bg-amber-200 rounded-xl flex flex-col justify-center items-center p-6">
|
<div tw="w-40 bg-amber-200 rounded-xl flex flex-col justify-center items-center p-6">
|
||||||
<span tw="text-amber-900 font-extrabold text-xl text-center leading-tight">Switch Guide</span>
|
<span tw="text-amber-900 font-extrabold text-xl text-center leading-tight">Switch Guide</span>
|
||||||
<p tw="text-amber-800 text-sm text-center mt-1.5">You need to manually create the Mii, visit site for instructions.</p>
|
<p tw="text-amber-800 text-sm text-center mt-1.5">To fully create the Mii, visit the site for instructions.</p>
|
||||||
<div tw="mt-auto bg-amber-600 rounded-lg w-full py-2 flex justify-center">
|
<div tw="mt-auto bg-amber-600 rounded-lg w-full py-2 flex justify-center">
|
||||||
<span tw="text-white font-semibold">View Steps</span>
|
<span tw="text-white font-semibold">View Steps</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -83,17 +83,14 @@ export const userNameSchema = z
|
||||||
error: "Name can only contain letters, numbers, dashes, underscores, apostrophes, and spaces.",
|
error: "Name can only contain letters, numbers, dashes, underscores, apostrophes, and spaces.",
|
||||||
});
|
});
|
||||||
|
|
||||||
const colorSchema = z.number().int().min(0).max(107).optional();
|
const colorSchema = z.number().int().min(0).max(152).optional();
|
||||||
const geometrySchema = z.number().int().min(-10).max(10).optional();
|
const geometrySchema = z.number().int().min(-10).max(10).optional();
|
||||||
|
|
||||||
export const switchMiiInstructionsSchema = z
|
export const switchMiiInstructionsSchema = z
|
||||||
.object({
|
.object({
|
||||||
head: z.object({ type: z.number().int().min(0).max(15).optional(), skinColor: z.number().int().min(0).max(121).optional() }).optional(),
|
head: z.object({ skinColor: colorSchema }).optional(),
|
||||||
hair: z
|
hair: z
|
||||||
.object({
|
.object({
|
||||||
setType: z.number().int().min(0).max(244).optional(),
|
|
||||||
bangsType: z.number().int().min(0).max(82).optional(),
|
|
||||||
backType: z.number().int().min(0).max(110).optional(),
|
|
||||||
color: colorSchema,
|
color: colorSchema,
|
||||||
subColor: colorSchema,
|
subColor: colorSchema,
|
||||||
style: z.number().int().min(1).max(3).optional(),
|
style: z.number().int().min(1).max(3).optional(),
|
||||||
|
|
@ -102,7 +99,6 @@ export const switchMiiInstructionsSchema = z
|
||||||
.optional(),
|
.optional(),
|
||||||
eyebrows: z
|
eyebrows: z
|
||||||
.object({
|
.object({
|
||||||
type: z.number().int().min(0).max(42).optional(),
|
|
||||||
color: colorSchema,
|
color: colorSchema,
|
||||||
height: geometrySchema,
|
height: geometrySchema,
|
||||||
distance: geometrySchema,
|
distance: geometrySchema,
|
||||||
|
|
@ -115,7 +111,6 @@ export const switchMiiInstructionsSchema = z
|
||||||
.object({
|
.object({
|
||||||
main: z
|
main: z
|
||||||
.object({
|
.object({
|
||||||
type: z.number().int().min(0).max(120).optional(),
|
|
||||||
color: colorSchema,
|
color: colorSchema,
|
||||||
height: geometrySchema,
|
height: geometrySchema,
|
||||||
distance: geometrySchema,
|
distance: geometrySchema,
|
||||||
|
|
@ -126,7 +121,6 @@ export const switchMiiInstructionsSchema = z
|
||||||
.optional(),
|
.optional(),
|
||||||
eyelashesTop: z
|
eyelashesTop: z
|
||||||
.object({
|
.object({
|
||||||
type: z.number().int().min(0).max(5).optional(),
|
|
||||||
height: geometrySchema,
|
height: geometrySchema,
|
||||||
distance: geometrySchema,
|
distance: geometrySchema,
|
||||||
rotation: geometrySchema,
|
rotation: geometrySchema,
|
||||||
|
|
@ -136,7 +130,6 @@ export const switchMiiInstructionsSchema = z
|
||||||
.optional(),
|
.optional(),
|
||||||
eyelashesBottom: z
|
eyelashesBottom: z
|
||||||
.object({
|
.object({
|
||||||
type: z.number().int().min(0).max(1).optional(),
|
|
||||||
height: geometrySchema,
|
height: geometrySchema,
|
||||||
distance: geometrySchema,
|
distance: geometrySchema,
|
||||||
rotation: geometrySchema,
|
rotation: geometrySchema,
|
||||||
|
|
@ -146,7 +139,6 @@ export const switchMiiInstructionsSchema = z
|
||||||
.optional(),
|
.optional(),
|
||||||
eyelidTop: z
|
eyelidTop: z
|
||||||
.object({
|
.object({
|
||||||
type: z.number().int().min(0).max(2).optional(),
|
|
||||||
height: geometrySchema,
|
height: geometrySchema,
|
||||||
distance: geometrySchema,
|
distance: geometrySchema,
|
||||||
rotation: geometrySchema,
|
rotation: geometrySchema,
|
||||||
|
|
@ -156,7 +148,6 @@ export const switchMiiInstructionsSchema = z
|
||||||
.optional(),
|
.optional(),
|
||||||
eyelidBottom: z
|
eyelidBottom: z
|
||||||
.object({
|
.object({
|
||||||
type: z.number().int().min(0).max(2).optional(),
|
|
||||||
height: geometrySchema,
|
height: geometrySchema,
|
||||||
distance: geometrySchema,
|
distance: geometrySchema,
|
||||||
rotation: geometrySchema,
|
rotation: geometrySchema,
|
||||||
|
|
@ -166,13 +157,11 @@ export const switchMiiInstructionsSchema = z
|
||||||
.optional(),
|
.optional(),
|
||||||
eyeliner: z
|
eyeliner: z
|
||||||
.object({
|
.object({
|
||||||
type: z.number().int().min(0).max(1).optional(),
|
|
||||||
color: colorSchema,
|
color: colorSchema,
|
||||||
})
|
})
|
||||||
.optional(),
|
.optional(),
|
||||||
pupil: z
|
pupil: z
|
||||||
.object({
|
.object({
|
||||||
type: z.number().int().min(0).max(9).optional(),
|
|
||||||
height: geometrySchema,
|
height: geometrySchema,
|
||||||
distance: geometrySchema,
|
distance: geometrySchema,
|
||||||
rotation: geometrySchema,
|
rotation: geometrySchema,
|
||||||
|
|
@ -184,14 +173,12 @@ export const switchMiiInstructionsSchema = z
|
||||||
.optional(),
|
.optional(),
|
||||||
nose: z
|
nose: z
|
||||||
.object({
|
.object({
|
||||||
type: z.number().int().min(0).max(31).optional(),
|
|
||||||
height: geometrySchema,
|
height: geometrySchema,
|
||||||
size: geometrySchema,
|
size: geometrySchema,
|
||||||
})
|
})
|
||||||
.optional(),
|
.optional(),
|
||||||
lips: z
|
lips: z
|
||||||
.object({
|
.object({
|
||||||
type: z.number().int().min(0).max(52).optional(),
|
|
||||||
color: colorSchema,
|
color: colorSchema,
|
||||||
height: geometrySchema,
|
height: geometrySchema,
|
||||||
rotation: geometrySchema,
|
rotation: geometrySchema,
|
||||||
|
|
@ -202,14 +189,12 @@ export const switchMiiInstructionsSchema = z
|
||||||
.optional(),
|
.optional(),
|
||||||
ears: z
|
ears: z
|
||||||
.object({
|
.object({
|
||||||
type: z.number().int().min(0).max(4).optional(),
|
|
||||||
height: geometrySchema,
|
height: geometrySchema,
|
||||||
size: geometrySchema,
|
size: geometrySchema,
|
||||||
})
|
})
|
||||||
.optional(),
|
.optional(),
|
||||||
glasses: z
|
glasses: z
|
||||||
.object({
|
.object({
|
||||||
type: z.number().int().min(0).max(57).optional(),
|
|
||||||
ringColor: colorSchema,
|
ringColor: colorSchema,
|
||||||
shadesColor: colorSchema,
|
shadesColor: colorSchema,
|
||||||
height: geometrySchema,
|
height: geometrySchema,
|
||||||
|
|
@ -221,7 +206,6 @@ export const switchMiiInstructionsSchema = z
|
||||||
.object({
|
.object({
|
||||||
wrinkles1: z
|
wrinkles1: z
|
||||||
.object({
|
.object({
|
||||||
type: z.number().int().min(0).max(8).optional(),
|
|
||||||
color: colorSchema,
|
color: colorSchema,
|
||||||
height: geometrySchema,
|
height: geometrySchema,
|
||||||
distance: geometrySchema,
|
distance: geometrySchema,
|
||||||
|
|
@ -231,7 +215,6 @@ export const switchMiiInstructionsSchema = z
|
||||||
.optional(),
|
.optional(),
|
||||||
wrinkles2: z
|
wrinkles2: z
|
||||||
.object({
|
.object({
|
||||||
type: z.number().int().min(0).max(14).optional(),
|
|
||||||
color: colorSchema,
|
color: colorSchema,
|
||||||
height: geometrySchema,
|
height: geometrySchema,
|
||||||
distance: geometrySchema,
|
distance: geometrySchema,
|
||||||
|
|
@ -241,7 +224,6 @@ export const switchMiiInstructionsSchema = z
|
||||||
.optional(),
|
.optional(),
|
||||||
beard: z
|
beard: z
|
||||||
.object({
|
.object({
|
||||||
type: z.number().int().min(0).max(14).optional(),
|
|
||||||
color: colorSchema,
|
color: colorSchema,
|
||||||
height: geometrySchema,
|
height: geometrySchema,
|
||||||
distance: geometrySchema,
|
distance: geometrySchema,
|
||||||
|
|
@ -251,17 +233,16 @@ export const switchMiiInstructionsSchema = z
|
||||||
.optional(),
|
.optional(),
|
||||||
moustache: z
|
moustache: z
|
||||||
.object({
|
.object({
|
||||||
type: z.number().int().min(0).max(15).optional(),
|
|
||||||
color: colorSchema,
|
color: colorSchema,
|
||||||
height: geometrySchema,
|
height: geometrySchema,
|
||||||
distance: geometrySchema,
|
distance: geometrySchema,
|
||||||
size: geometrySchema,
|
size: geometrySchema,
|
||||||
stretch: geometrySchema,
|
stretch: geometrySchema,
|
||||||
|
isFlipped: z.boolean().optional(),
|
||||||
})
|
})
|
||||||
.optional(),
|
.optional(),
|
||||||
goatee: z
|
goatee: z
|
||||||
.object({
|
.object({
|
||||||
type: z.number().int().min(0).max(13).optional(),
|
|
||||||
color: colorSchema,
|
color: colorSchema,
|
||||||
height: geometrySchema,
|
height: geometrySchema,
|
||||||
distance: geometrySchema,
|
distance: geometrySchema,
|
||||||
|
|
@ -271,7 +252,6 @@ export const switchMiiInstructionsSchema = z
|
||||||
.optional(),
|
.optional(),
|
||||||
mole: z
|
mole: z
|
||||||
.object({
|
.object({
|
||||||
type: z.number().int().min(0).max(1).optional(),
|
|
||||||
color: colorSchema,
|
color: colorSchema,
|
||||||
height: geometrySchema,
|
height: geometrySchema,
|
||||||
distance: geometrySchema,
|
distance: geometrySchema,
|
||||||
|
|
@ -281,7 +261,6 @@ export const switchMiiInstructionsSchema = z
|
||||||
.optional(),
|
.optional(),
|
||||||
eyeShadow: z
|
eyeShadow: z
|
||||||
.object({
|
.object({
|
||||||
type: z.number().int().min(0).max(3).optional(),
|
|
||||||
color: colorSchema,
|
color: colorSchema,
|
||||||
height: geometrySchema,
|
height: geometrySchema,
|
||||||
distance: geometrySchema,
|
distance: geometrySchema,
|
||||||
|
|
@ -291,7 +270,6 @@ export const switchMiiInstructionsSchema = z
|
||||||
.optional(),
|
.optional(),
|
||||||
blush: z
|
blush: z
|
||||||
.object({
|
.object({
|
||||||
type: z.number().int().min(0).max(7).optional(),
|
|
||||||
color: colorSchema,
|
color: colorSchema,
|
||||||
height: geometrySchema,
|
height: geometrySchema,
|
||||||
distance: geometrySchema,
|
distance: geometrySchema,
|
||||||
|
|
|
||||||
|
|
@ -118,7 +118,7 @@ export const COLORS: string[] = [
|
||||||
"2B1B3F",
|
"2B1B3F",
|
||||||
"7B2D2D",
|
"7B2D2D",
|
||||||
"8B3A0E",
|
"8B3A0E",
|
||||||
// Head tab extra colors
|
// Hair tab extra colors
|
||||||
"FFD8BA",
|
"FFD8BA",
|
||||||
"FFD5AC",
|
"FFD5AC",
|
||||||
"FEC1A4",
|
"FEC1A4",
|
||||||
|
|
@ -133,4 +133,33 @@ export const COLORS: string[] = [
|
||||||
"59371F",
|
"59371F",
|
||||||
"662D16",
|
"662D16",
|
||||||
"392D1E",
|
"392D1E",
|
||||||
|
// Eye tab extra colors
|
||||||
|
"000100",
|
||||||
|
"6B6F6E",
|
||||||
|
"663F2D",
|
||||||
|
"605F34",
|
||||||
|
"3B6F59",
|
||||||
|
"4856A6",
|
||||||
|
// Lips tab extra colors
|
||||||
|
"D65413",
|
||||||
|
"F21415",
|
||||||
|
"F54A4A",
|
||||||
|
"EE9670",
|
||||||
|
"8A4E40",
|
||||||
|
// Glasses tab extra colors
|
||||||
|
"000000",
|
||||||
|
"776F66",
|
||||||
|
"603915",
|
||||||
|
"A65F00",
|
||||||
|
"A61615",
|
||||||
|
"273465",
|
||||||
|
// Eye shade extra colors
|
||||||
|
"A54E21",
|
||||||
|
"653E2C",
|
||||||
|
"EC946F",
|
||||||
|
"FC9414",
|
||||||
|
"F97595",
|
||||||
|
"F54A4A",
|
||||||
|
"86E1B0",
|
||||||
|
"6E44B0",
|
||||||
];
|
];
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue