feat: almost done

This commit is contained in:
trafficlunar 2026-03-26 17:12:45 +00:00
parent e8353b601c
commit ab0015ef2a
21 changed files with 439 additions and 474 deletions

View file

@ -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>
); );
} }

View file

@ -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 */}

View file

@ -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,12 +73,14 @@ 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) => {
const actualIndex = i + getExtraSlice().start;
return (
<button <button
type="button" type="button"
key={i} key={actualIndex}
onClick={() => setColor(i)} onClick={() => setColor(actualIndex)}
className={`size-7.5 cursor-pointer rounded-md ring-orange-500 ring-offset-2 ${color === i ? "ring-2 z-10" : ""}`} className={`size-7.5 cursor-pointer rounded-md ring-orange-500 ring-offset-2 ${color === actualIndex ? "ring-2 z-10" : ""}`}
style={{ style={{
backgroundColor: `#${c}`, backgroundColor: `#${c}`,
opacity: isVisible ? 1 : 0, opacity: isVisible ? 1 : 0,
@ -70,10 +90,11 @@ export default function ColorPicker({ disabled, color, setColor }: Props) {
transitionDelay: isVisible ? `${120 + (i % 10) * 18 + Math.floor(i / 10) * 10}ms` : "0ms", transitionDelay: isVisible ? `${120 + (i % 10) * 18 + Math.floor(i / 10) * 10}ms` : "0ms",
}} }}
></button> ></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"

View file

@ -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>
); );
})} })}

View file

@ -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>

View file

@ -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>
); );
} }

View file

@ -1,26 +1,20 @@
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) => {
@ -30,7 +24,6 @@ export default function EyebrowsTab({ instructions }: Props) {
/> />
<NumberInputs target={instructions.current.eyebrows} /> <NumberInputs target={instructions.current.eyebrows} />
</div> </div>
</div> </>
</div>
); );
} }

View file

@ -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,13 +36,10 @@ export default function OtherTab({ instructions }: Props) {
}; };
return ( return (
<div className="relative grow p-3 pb-0!"> <>
<div className="flex h-full">
<div className="grow flex flex-col">
<div className="flex items-center h-8">
<h1 className="absolute font-bold text-xl">Eyes</h1> <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
@ -51,21 +48,16 @@ export default function OtherTab({ instructions }: Props) {
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>
</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">
<div className={`${currentTab.colorsDisabled ? "hidden" : "w-full"}`}> <ColorPicker disabled={currentTab.colorsDisabled} color={colors[tab]} setColor={setColor} tab={tab === 5 ? "eyeliner" : "eyes"} />
<ColorPicker color={colors[tab]} setColor={setColor} />
</div>
<NumberInputs target={instructions.current.eyes[currentTab.name]} /> <NumberInputs target={instructions.current.eyes[currentTab.name]} />
</div> </div>
</div> </>
</div>
); );
} }

View file

@ -12,21 +12,17 @@ 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 <ColorPicker
color={shadesColor} color={shadesColor}
@ -34,10 +30,10 @@ export default function GlassesTab({ instructions }: Props) {
setShadesColor(i); setShadesColor(i);
instructions.current.glasses.shadesColor = i; instructions.current.glasses.shadesColor = i;
}} }}
tab="glasses"
/> />
<NumberInputs target={instructions.current.glasses} /> <NumberInputs target={instructions.current.glasses} />
</div> </div>
</div> </>
</div>
); );
} }

View file

@ -16,16 +16,11 @@ 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">
<div className="grow flex flex-col">
<div className="flex items-center h-8">
<h1 className="absolute font-bold text-xl">Hair</h1> <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")}
@ -51,10 +46,8 @@ export default function HairTab({ instructions }: Props) {
</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) => {
@ -63,7 +56,7 @@ export default function HairTab({ instructions }: Props) {
}} }}
/> />
<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"
@ -99,7 +92,7 @@ export default function HairTab({ instructions }: Props) {
/> />
<p className="text-sm mb-1">Tying style</p> <p className="text-sm mb-1">Tying style</p>
<div className="grid grid-cols-3 w-full gap-0.5"> <div className="grid grid-cols-3 gap-0.5">
{Array.from({ length: 3 }).map((_, i) => ( {Array.from({ length: 3 }).map((_, i) => (
<button <button
type="button" type="button"
@ -115,7 +108,7 @@ export default function HairTab({ instructions }: Props) {
))} ))}
</div> </div>
<div className="flex gap-1.5 items-center w-full mt-auto"> <div className="flex gap-1.5 items-center mt-4">
<input <input
type="checkbox" type="checkbox"
id="subcolor" id="subcolor"
@ -131,7 +124,6 @@ export default function HairTab({ instructions }: Props) {
</label> </label>
</div> </div>
</div> </div>
</div> </>
</div>
); );
} }

View file

@ -10,18 +10,12 @@ 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) => {
@ -30,7 +24,7 @@ export default function HeadTab({ instructions }: Props) {
}} }}
/> />
<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"
@ -42,7 +36,6 @@ export default function HeadTab({ instructions }: Props) {
))} ))}
</div> </div>
</div> </div>
</div> </>
</div>
); );
} }

View file

@ -12,25 +12,21 @@ 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} /> <NumberInputs target={instructions.current.lips} />
<div className="flex gap-1.5 items-center w-full mt-auto"> <div className="flex gap-1.5 items-center mt-4">
<input <input
type="checkbox" type="checkbox"
id="subcolor" id="subcolor"
@ -46,7 +42,6 @@ export default function LipsTab({ instructions }: Props) {
</label> </label>
</div> </div>
</div> </div>
</div> </>
</div>
); );
} }

View file

@ -31,15 +31,11 @@ export default function HeadTab({ instructions }: Props) {
}); });
return ( return (
<div className="relative grow p-3 pb-0!"> <>
<div className="flex h-full">
<div className="grow flex flex-col">
<div className="flex items-center h-8">
<h1 className="font-bold text-xl">Misc</h1> <h1 className="font-bold text-xl">Misc</h1>
</div>
<div className="grow overflow-y-auto"> <div className="grow h-full overflow-y-auto pb-3">
<div className="grid grid-cols-2 gap-4"> <div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div> <div>
<div className="flex items-center gap-4 text-zinc-500 text-sm font-medium"> <div className="flex items-center gap-4 text-zinc-500 text-sm font-medium">
<hr className="grow border-zinc-300" /> <hr className="grow border-zinc-300" />
@ -142,8 +138,6 @@ export default function HeadTab({ instructions }: Props) {
}} }}
/> />
</div> </div>
</div> </>
</div>
</div>
); );
} }

View file

@ -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>
); );
} }

View file

@ -38,13 +38,10 @@ export default function OtherTab({ instructions }: Props) {
}; };
return ( return (
<div className="relative grow p-3 pb-0!"> <>
<div className="flex h-full">
<div className="grow flex flex-col">
<div className="flex items-center h-8">
<h1 className="absolute font-bold text-xl">Other</h1> <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
@ -53,20 +50,18 @@ export default function OtherTab({ instructions }: Props) {
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>
</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 color={colors[tab]} setColor={setColor} /> <ColorPicker disabled={tab === 0 || tab === 1} color={colors[tab]} setColor={setColor} />
<NumberInputs target={instructions.current.other[currentTab.name]} /> <NumberInputs target={instructions.current.other[currentTab.name]} />
{tab === 3 && ( {tab === 3 && (
<div className="flex gap-1.5 items-center w-full mt-auto"> <div className="flex gap-1.5 items-center mt-4">
<input <input
type="checkbox" type="checkbox"
id="subcolor" id="subcolor"
@ -83,7 +78,6 @@ export default function OtherTab({ instructions }: Props) {
</div> </div>
)} )}
</div> </div>
</div> </>
</div>
); );
} }

View file

@ -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&apos;s parts here Drag and drop a screenshot of your Mii&apos;s features here
<br /> <br />
or click to open or click to open
</> </>

View file

@ -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"
}`} }`}
> >

View file

@ -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" },

View file

@ -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>

View file

@ -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,

View file

@ -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",
]; ];