diff --git a/app/buying-guide/finder/page.tsx b/app/buying-guide/finder/page.tsx index 99d10d2a..d3724f85 100644 --- a/app/buying-guide/finder/page.tsx +++ b/app/buying-guide/finder/page.tsx @@ -1,321 +1,479 @@ -// app/buying-guide/finder/page.tsx "use client"; -import { useMemo, useState } from "react"; -import { useForm } from "react-hook-form"; -import type { Answers, LaserType } from "@/lib/laser-finder"; -import { scoreAnswers, LASER_LABEL, TYPE_INFO } from "@/lib/laser-finder"; -import Link from "next/link"; +import React, { useMemo, useState } from "react"; -export default function LaserFinderPage() { - const [result, setResult] = useState<{ - top: LaserType[]; - score: Record; - why: Record; - } | null>(null); +/** + * Buying Guide → Laser Finder + * Path: /buying-guide/finder + * + * - Lightweight scoring based on answers + * - Nice, readable "Cautions" chips + * - Clear callouts and reset / refine controls + * - Pure Tailwind, no external deps + */ - const { register, handleSubmit, reset, watch } = useForm({ - defaultValues: { - materials: [], - operations: [], - part_size: "medium", - detail: "medium", - throughput: "medium", - budget: "mid", - }, - }); +type LaserKind = "fiber" | "co2-galvo" | "co2-gantry" | "uv"; - const onSubmit = (vals: Answers) => { - const { ranked, score, why } = scoreAnswers(vals); - setResult({ top: ranked.slice(0, 2), score, why }); - // (Optional) later: POST vals to Directus for analytics +type Answers = { + material?: "metal" | "organic" | "mixed"; + task?: "mark" | "engrave" | "cut"; + detail?: "fine" | "medium" | "coarse"; + size?: "small" | "medium" | "large"; + colorChangeOk?: boolean; // okay with color/anneal vs. deep mark + budget?: "tight" | "moderate" | "flexible"; +}; + +type Profile = { + id: LaserKind; + title: string; + wavelength: string; + bestFor: string[]; + cautions: string[]; + notes?: string; + learnLink?: string; // internal doc/page if you add one later +}; + +const PROFILES: Record = { + fiber: { + id: "fiber", + title: "Fiber (1064 nm • Galvo)", + wavelength: "1064 nm", + bestFor: [ + "Marking/engraving bare metals", + "High-detail logos & serials", + "Fast batch work on small parts", + ], + cautions: [ + "Small scan field vs gantry machines", + "Not for cutting non-metals", + "Ventilation needed for some plastics", + ], + notes: + "Ideal for stainless, aluminum, tool steels. Deep marks possible with multiple passes.", + }, + "co2-galvo": { + id: "co2-galvo", + title: "CO₂ Galvo (10.6 µm)", + wavelength: "10.6 µm", + bestFor: [ + "Rapid engraving on wood, leather, glass, acrylic", + "Small items in trays/fixtures", + "Branding & raster graphics", + ], + cautions: [ + "Poor on bare metals (needs marking spray)", + "Smaller working area than gantry CO₂", + "Not ideal for thick cutting", + ], + notes: + "When you need speed on organic materials at smaller sizes, CO₂ galvo is excellent.", + }, + "co2-gantry": { + id: "co2-gantry", + title: "CO₂ Gantry (10.6 µm)", + wavelength: "10.6 µm", + bestFor: [ + "Cutting acrylic & organics", + "Larger panels/signage", + "Prototyping jigs and fixtures", + ], + cautions: [ + "Not for bare metals without compound", + "Finer micro-detail is slower than galvos", + ], + notes: + "Your general-purpose cutter/engraver for wood, acrylic, cardboard, leather, and more.", + }, + uv: { + id: "uv", + title: "UV (355 nm • Galvo)", + wavelength: "355 nm", + bestFor: [ + "Heat-sensitive plastics & films", + "Glass, ceramics, and clear/translucent parts", + "High-contrast codes on consumer packaging", + ], + cautions: [ + "Higher cost per watt than fiber/CO₂", + "Slower for deep material removal", + "Keep optics clean; sensitive to contamination", + ], + notes: + "When you need crisp marks with minimal heat on delicate or clear materials, UV is the tool.", + }, +}; + +/** ──────────────────────────────────────────────────────────── + * Scoring rules + * - Very simple heuristic = good enough for a first pass. + * - Feel free to tweak weights as you gather user feedback. + * ──────────────────────────────────────────────────────────── */ +function scoreProfiles(a: Answers): Array<{ id: LaserKind; score: number; why: string[] }> { + const why: Record = { + fiber: [], + "co2-galvo": [], + "co2-gantry": [], + uv: [], }; + const s: Record = { fiber: 0, "co2-galvo": 0, "co2-gantry": 0, uv: 0 }; - const selectedMaterials = watch("materials"); - const selectedOps = watch("operations"); + // Material + if (a.material === "metal") { + s.fiber += 6; why.fiber.push("Best for bare metals"); + s.uv += 1; why.uv.push("Can mark some coated metals"); + } + if (a.material === "organic") { + s["co2-gantry"] += 4; why["co2-gantry"].push("Great on wood & acrylic"); + s["co2-galvo"] += 4; why["co2-galvo"].push("Super fast on organics"); + } + if (a.material === "mixed") { + s.fiber += 2; why.fiber.push("Covers metals"); + s["co2-gantry"] += 2; why["co2-gantry"].push("Cuts & engraves organics"); + s["co2-galvo"] += 2; why["co2-galvo"].push("Fast organic engraving"); + s.uv += 1; why.uv.push("Excellent for delicate/clear materials"); + } + // Task + if (a.task === "mark" || a.task === "engrave") { + s.fiber += 3; why.fiber.push("High-contrast metal marking"); + s["co2-galvo"] += 2; why["co2-galvo"].push("Fast raster engraving"); + s.uv += 2; why.uv.push("Clean marks on delicate substrates"); + } + if (a.task === "cut") { + if (a.material !== "metal") { + s["co2-gantry"] += 6; why["co2-gantry"].push("Best for cutting non-metals"); + } + } + + // Detail + if (a.detail === "fine") { + s.fiber += 2; why.fiber.push("Very fine vector detail"); + s.uv += 2; why.uv.push("Minimal heat, crisp small features"); + s["co2-galvo"] += 1; why["co2-galvo"].push("Fast raster detail (smaller fields)"); + } + if (a.detail === "coarse") { + s["co2-gantry"] += 1; why["co2-gantry"].push("Large letters & cuts ok"); + } + + // Part size + if (a.size === "large") { + s["co2-gantry"] += 3; why["co2-gantry"].push("Large work area"); + } + if (a.size === "small") { + s.fiber += 1; why.fiber.push("Small parts throughput"); + s["co2-galvo"] += 1; why["co2-galvo"].push("Tray/fixture friendly"); + } + + // Color change acceptable? + if (a.colorChangeOk === true) { + s.fiber += 1; why.fiber.push("Black/anneal OK"); + s.uv += 1; why.uv.push("High contrast on plastics/glass"); + } + + // Budget (soft nudge) + if (a.budget === "tight") { + s["co2-gantry"] += 1; why["co2-gantry"].push("Often best $/area for organics"); + } + if (a.budget === "flexible") { + s.uv += 1; why.uv.push("Premium for delicate materials"); + } + + return (Object.keys(s) as LaserKind[]) + .map((id) => ({ id, score: s[id], why: why[id] })) + .sort((a, b) => b.score - a.score); +} + +/** ──────────────────────────────────────────────────────────── + * UI Bits + * ──────────────────────────────────────────────────────────── */ +function Section({ title, children }: { title: string; children: React.ReactNode }) { return ( -
-

Laser Type Finder

-

- Answer a few questions and we’ll suggest the best laser types for your work - with clear use-cases, materials, and cautions. No product pitches—just guidance. -

+
+

{title}

+ {children} +
+ ); +} - {!result && ( -
- {/* Materials */} -
- Materials (select all that apply) -
- {[ - ["metals_bare", "Bare metals"], - ["metals_coated", "Coated/painted metals"], - ["plastics", "Plastics"], - ["wood_paper_leather", "Wood, paper, leather"], - ["glass_ceramic", "Glass / ceramic"], - ["stone", "Stone"], - ["textiles", "Textiles"], - ].map(([val, label]) => ( - - ))} -
- {selectedMaterials?.length === 0 && ( -

Tip: choose at least one material for a better match.

- )} -
+function Seg({ + value, + onChange, + items, +}: { + value?: string; + onChange: (v: string) => void; + items: { value: string; label: string }[]; +}) { + return ( +
+ {items.map((it) => { + const active = value === it.value; + return ( + + ); + })} +
+ ); +} - {/* Operations */} -
- Typical operations (select all that apply) -
- {[ - ["deep_mark_metal", "Deep mark on metal"], - ["color_mark_stainless", "Color mark stainless"], - ["fine_engraving", "Fine engraving (small features)"], - ["photo_engrave", "Photo engraving"], - ["cut_nonmetals_thick", "Cut thick non-metals (e.g., 6+ mm acrylic/wood)"], - ["cut_nonmetals_thin", "Cut thin non-metals"], - ["mark_coated", "Mark coated items"], - ].map(([val, label]) => ( - - ))} -
- {selectedOps?.length === 0 && ( -

Tip: pick one or more to sharpen the recommendation.

- )} -
+function YesNo({ value, onChange }: { value?: boolean; onChange: (v: boolean) => void }) { + return ( + onChange(v === "yes")} + items={[ + { value: "yes", label: "Yes" }, + { value: "no", label: "No" }, + ]} + /> + ); +} - {/* Size / Detail / Speed / Budget */} -
-
- - -
-
- - -
-
- - -
-
- - -
-
+function Pill({ children }: { children: React.ReactNode }) { + return ( +
  • + {children} +
  • + ); +} -
    - - - Back to Buying Guide - -
    - - )} - - {!!result && ( -
    - - - {result.top[1] && ( - - )} - - - -
    - - - - Back to Buying Guide - -
    -
    - )} + {c} + + ))} +
    ); } function ResultCard({ - title, - type, + pick, why, - secondary, + rank, }: { - title: string; - type: LaserType; - why?: string[]; - secondary?: boolean; + pick: Profile; + why: string[]; + rank: number; }) { - const info = TYPE_INFO[type]; - return ( -
    -
    -

    {title}

    - {!secondary && Best match} +
    +
    +

    + {rank === 0 ? "Recommended" : "Alternative"} · {pick.title} +

    + {pick.wavelength}
    -
    {LASER_LABEL[type]}
    -

    {info.summary}

    - {!!why?.length && ( -
    -
    Why this fits
    -
      - {why.slice(0, 5).map((w, i) =>
    • {w}
    • )} -
    + {pick.notes &&

    {pick.notes}

    } + +
    +

    Best For

    +
      + {pick.bestFor.map((b, i) => ( + {b} + ))} +
    +
    + + + + {why.length > 0 && ( +
    + Why you’re seeing this:{" "} + {why.join(" · ")}
    )} - -
    - - - -
    - -
    - - See community settings - - - Suggest new settings - -
    ); } -function TagList({ - label, - items, - tone, -}: { - label: string; - items: string[]; - tone?: "warn"; -}) { - return ( -
    -
    {label}
    -
    - {items.map((t, i) => ( - - {t} - - ))} -
    -
    - ); -} +/** ──────────────────────────────────────────────────────────── + * Page + * ──────────────────────────────────────────────────────────── */ +export default function FinderPage() { + const [a, setA] = useState({}); -function CompareMatrix() { - const rows: Array<{ - k: LaserType; - label: string; - best: string[]; - ok: string[]; - avoid: string[]; - }> = [ - { - k: "fiber", - label: LASER_LABEL.fiber, - best: ["Bare metals", "Deep metal engrave", "Color marking stainless"], - ok: ["Some coated items", "Some plastics w/ additives"], - avoid: ["Thick organics cutting"], - }, - { - k: "co2_gantry", - label: LASER_LABEL.co2_gantry, - best: ["Acrylic cutting", "Wood cutting/engraving", "Large panels"], - ok: ["Leathers, textiles, rubber"], - avoid: ["Bare metals (no coat)"], - }, - { - k: "co2_galvo", - label: LASER_LABEL.co2_galvo, - best: ["Fast marking organics", "Photo engraving organics"], - ok: ["Coated metals/non-metals"], - avoid: ["Thick sheet cutting", "Large panels"], - }, - { - k: "uv", - label: LASER_LABEL.uv, - best: ["Micro features", "Glass/ceramic/plastics marking"], - ok: ["Fine logos on coated metals"], - avoid: ["Thick cutting"], - }, - ]; + const results = useMemo(() => scoreProfiles(a), [a]); + + const complete = + a.material && a.task && a.detail && a.size && a.colorChangeOk !== undefined; + + const top = results[0]; + const alt = results.slice(1, 3); + + function reset() { + setA({}); + window.scrollTo({ top: 0, behavior: "smooth" }); + } return ( -
    -
    Compare laser types
    -
    - - - - - - - - - - - {rows.map((r) => ( - - - - - - - ))} - -
    TypeBest forOkay forNot ideal
    {r.label}{r.best.join(", ")}{r.ok.join(", ")}{r.avoid.join(", ")}
    +
    +
    +

    Laser Finder

    +

    + Answer a few quick questions and we’ll suggest the laser family that + fits your work best. +

    +
    + +
    +
    + setA((p) => ({ ...p, material: v as Answers["material"] }))} + items={[ + { value: "metal", label: "Metals" }, + { value: "organic", label: "Organics / Acrylic" }, + { value: "mixed", label: "Mixed" }, + ]} + /> +
    + +
    + setA((p) => ({ ...p, task: v as Answers["task"] }))} + items={[ + { value: "mark", label: "Mark" }, + { value: "engrave", label: "Engrave" }, + { value: "cut", label: "Cut" }, + ]} + /> +
    + +
    + setA((p) => ({ ...p, detail: v as Answers["detail"] }))} + items={[ + { value: "fine", label: "Fine" }, + { value: "medium", label: "Medium" }, + { value: "coarse", label: "Coarse" }, + ]} + /> +
    + +
    + setA((p) => ({ ...p, size: v as Answers["size"] }))} + items={[ + { value: "small", label: "Small" }, + { value: "medium", label: "Medium" }, + { value: "large", label: "Large" }, + ]} + /> +
    + +
    + setA((p) => ({ ...p, colorChangeOk: v }))} + /> +
    + +
    + setA((p) => ({ ...p, budget: v as Answers["budget"] }))} + items={[ + { value: "tight", label: "Tight" }, + { value: "moderate", label: "Moderate" }, + { value: "flexible", label: "Flexible" }, + ]} + /> +
    +
    + + {/* Results */} +
    + {!complete ? ( +
    + Fill out the questions above to see recommendations. +
    + ) : ( + <> + {top && ( + + )} + {alt.map((r, i) => ( + + ))} + + )} +
    + + {/* Actions */} +
    + + + {/* Quick links back into your existing app sections */} + + See Fiber Examples + + + See CO₂ Gantry + + + See CO₂ Galvo + + + See UV +
    );