diff --git a/app/buying-guide/finder/page.tsx b/app/buying-guide/finder/page.tsx index 66b29b94..99d10d2a 100644 --- a/app/buying-guide/finder/page.tsx +++ b/app/buying-guide/finder/page.tsx @@ -1,536 +1,322 @@ -'use client'; +// app/buying-guide/finder/page.tsx +"use client"; -import { useMemo, useState } from 'react'; +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"; -/* ----------------------------------------------------------- - * Data: laser types (copy tweaks welcome) - * ----------------------------------------------------------- */ -type LaserKey = 'co2_gantry' | 'co2_galvo' | 'fiber' | 'uv'; +export default function LaserFinderPage() { + const [result, setResult] = useState<{ + top: LaserType[]; + score: Record; + why: Record; + } | null>(null); -const LASER_TYPES: Record = { - co2_gantry: { - title: 'CO₂ Gantry', - blurb: - 'Large work areas, great for cutting/engraving organics (wood, leather, paper) and many plastics. Best for sheet work and signage.', - cautions: [ - 'Requires robust ventilation & fire safety for cutting organics.', - 'Not suitable for bare metals without coatings/pastes.', - ], - strengths: [ - 'Big beds (e.g. 600×400mm+)', - 'Cuts thicker organics cleanly', - 'Lower cost per watt than galvos', - ], - typical: ['signs', 'boxes', 'inlays', 'production sheet work'], - }, - co2_galvo: { - title: 'CO₂ Galvo', - blurb: - 'High-speed galvo for organics/plastics in smaller fields (e.g. 110×110mm). Excellent for rapid engraving and serialization.', - cautions: [ - 'Smaller field; “stitching” required for large designs.', - 'Requires smoke extraction for organics/plastics.', - ], - strengths: [ - 'Very fast engraving', - 'Sharp detail in small fields', - 'Great for coated/anodized items', - ], - typical: ['batch marking', 'brand marks', 'rapid engraving'], - }, - fiber: { - title: 'Fiber (1064nm)', - blurb: - 'The go-to for metals. Deep engraving, anneal/black marking, and serial/UID marking. Small F-theta fields; superb precision.', - cautions: [ - 'Not suited for clear/white acrylic; limited plastics.', - 'Eye safety is critical; use certified eyewear/enclosure.', - ], - strengths: [ - 'Marks/engraves steel, aluminum, brass, Ti, etc.', - 'High precision in small fields', - 'Supports color marking on some alloys (with tuning)', - ], - typical: ['metal tags', 'tools', 'knives', 'jewelry', 'UIDs'], - }, - uv: { - title: 'UV (355nm)', - blurb: - 'Best for fine marking of plastics, PCBs, some glass/ceramics with minimal heat. Micro features and delicate substrates.', - cautions: [ - 'Generally slower and pricier per watt.', - 'UV eye/skin safety needs strict controls.', - ], - strengths: [ - 'Minimal heat-affected zone', - 'Excellent for delicate plastics & micro text', - 'Can mark glass/ceramics with good contrast', - ], - typical: ['medical devices', 'plastics, PCB legends', 'micro text'], - }, -}; + const { register, handleSubmit, reset, watch } = useForm({ + defaultValues: { + materials: [], + operations: [], + part_size: "medium", + detail: "medium", + throughput: "medium", + budget: "mid", + }, + }); -/* ----------------------------------------------------------- - * Styling-only Cautions component (contrast fix) - * ----------------------------------------------------------- */ -function CautionBox({ items }: { items?: string[] }) { - if (!items || items.length === 0) return null; + 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 + }; + + const selectedMaterials = watch("materials"); + const selectedOps = watch("operations"); return ( -
-
- - ! - - -
-
- Cautions -
- -
    - {items.map((it, i) => ( -
  • - {it} -
  • - ))} -
-
-
-
- ); -} - -/* ----------------------------------------------------------- - * Questionnaire model - * (Focused on use-cases; no product ids) - * ----------------------------------------------------------- */ -type Answers = { - materials: 'organics' | 'metals' | 'both' | 'plastics_glass'; - workSize: 'small' | 'medium' | 'large' | 'micro'; - detail: 'low' | 'medium' | 'high' | 'micro'; - throughput: 'normal' | 'high'; - colorOnMetal: 'needed' | 'nice' | 'no'; - budget: 'low' | 'mid' | 'high'; - reflectivity: 'low' | 'mixed' | 'high'; - enclosure: 'enclosed' | 'open_ok'; -}; - -const DEFAULT_ANSWERS: Answers = { - materials: 'both', - workSize: 'medium', - detail: 'medium', - throughput: 'normal', - colorOnMetal: 'no', - budget: 'mid', - reflectivity: 'mixed', - enclosure: 'enclosed', -}; - -/* ----------------------------------------------------------- - * Scoring rules (easy to tweak) - * ----------------------------------------------------------- */ -type ScoreMap = Record; - -const BASE_WEIGHTS: ScoreMap = { - co2_gantry: 0, - co2_galvo: 0, - fiber: 0, - uv: 0, -}; - -function score(answers: Answers): ScoreMap { - const s: ScoreMap = { ...BASE_WEIGHTS }; - - // Materials - if (answers.materials === 'organics') { - s.co2_gantry += 4; - s.co2_galvo += 3; - } else if (answers.materials === 'metals') { - s.fiber += 5; - } else if (answers.materials === 'plastics_glass') { - s.uv += 5; - s.co2_galvo += 2; - } else if (answers.materials === 'both') { - s.co2_gantry += 2; - s.co2_galvo += 2; - s.fiber += 2; - } - - // Work size - if (answers.workSize === 'large') s.co2_gantry += 5; - if (answers.workSize === 'medium') s.co2_gantry += 2; - if (answers.workSize === 'small') s.co2_galvo += 2; - if (answers.workSize === 'micro') { - s.fiber += 3; - s.uv += 3; - } - - // Detail - if (answers.detail === 'low') s.co2_gantry += 1; - if (answers.detail === 'medium') { - s.co2_gantry += 1; - s.co2_galvo += 1; - } - if (answers.detail === 'high') { - s.co2_galvo += 2; - s.fiber += 2; - } - if (answers.detail === 'micro') { - s.fiber += 4; - s.uv += 4; - } - - // Throughput - if (answers.throughput === 'high') { - s.co2_galvo += 3; - s.fiber += 3; - } - - // Color on metal - if (answers.colorOnMetal === 'needed') s.fiber += 3; - if (answers.colorOnMetal === 'nice') s.fiber += 1; - - // Budget - if (answers.budget === 'low') s.co2_gantry += 2; - if (answers.budget === 'mid') { - s.co2_gantry += 1; - s.co2_galvo += 1; - s.fiber += 1; - } - if (answers.budget === 'high') { - s.fiber += 1; - s.uv += 1; - s.co2_galvo += 1; - } - - // Reflectivity exposure - if (answers.reflectivity === 'high') s.fiber += 1; // designed for metals - if (answers.reflectivity === 'low') s.co2_gantry += 1; - - // Enclosure requirement - if (answers.enclosure === 'enclosed') { - s.co2_gantry += 1; // many enclosed units exist - s.fiber += 1; // many enclosed stations exist - s.uv += 1; // usually enclosed - } else { - s.co2_galvo += 1; // many open-table galvos - } - - return s; -} - -/* ----------------------------------------------------------- - * Small UI helpers - * ----------------------------------------------------------- */ -function Card({ - children, - className = '', -}: { - children: React.ReactNode; - className?: string; -}) { - return ( -
- {children} -
- ); -} - -function SectionTitle({ children }: { children: React.ReactNode }) { - return ( -

- {children} -

- ); -} - -/* ----------------------------------------------------------- - * The Page - * ----------------------------------------------------------- */ -export default function FinderPage() { - const [answers, setAnswers] = useState({ ...DEFAULT_ANSWERS }); - const [showResults, setShowResults] = useState(false); - - const results = useMemo(() => { - const scores = score(answers); - return (Object.keys(scores) as LaserKey[]) - .map((k) => ({ key: k, score: scores[k] })) - .sort((a, b) => b.score - a.score); - }, [answers]); - - const top = results.slice(0, 2); // show top 1–2 - - return ( -
-
-

- Laser Type Finder -

-

- Answer a few questions about your work. We’ll suggest the laser{' '} - type (CO₂ gantry, CO₂ galvo, Fiber, UV) that fits best. No - product ads—just use-case guidance. +

+

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.

-
-
- - What are you working with? -
- + {!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.

+ )} +
- + {/* 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.

+ )} +
- + {/* Size / Detail / Speed / Budget */} +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
- -
-
+
+ + + Back to Buying Guide + +
+ + )} - - Constraints & preferences -
- + {!!result && ( +
+ - + {result.top[1] && ( + + )} - + - -
- -
- -
- - -
- - {showResults && ( -
- {top.map(({ key, score }) => { - const t = LASER_TYPES[key]; - return ( - -
-
-

{t.title}

-

- {t.blurb} -

-
- - Score {score} - -
- -
-
-
- Strengths -
-
    - {t.strengths.map((s, i) => ( -
  • - {s} -
  • - ))} -
-
- -
-
- Typical jobs -
-
    - {t.typical.map((s, i) => ( -
  • - {s} -
  • - ))} -
-
-
- - {/* High-contrast cautions */} - -
- ); - })} +
+ + + + Back to Buying Guide + +
)}
); } + +function ResultCard({ + title, + type, + why, + secondary, +}: { + title: string; + type: LaserType; + why?: string[]; + secondary?: boolean; +}) { + const info = TYPE_INFO[type]; + + return ( +
+
+

{title}

+ {!secondary && Best match} +
+
{LASER_LABEL[type]}
+

{info.summary}

+ + {!!why?.length && ( +
+
Why this fits
+
    + {why.slice(0, 5).map((w, i) =>
  • {w}
  • )} +
+
+ )} + +
+ + + +
+ +
+ + See community settings + + + Suggest new settings + +
+
+ ); +} + +function TagList({ + label, + items, + tone, +}: { + label: string; + items: string[]; + tone?: "warn"; +}) { + return ( +
+
{label}
+
+ {items.map((t, i) => ( + + {t} + + ))} +
+
+ ); +} + +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"], + }, + ]; + + return ( +
+
Compare laser types
+
+ + + + + + + + + + + {rows.map((r) => ( + + + + + + + ))} + +
TypeBest forOkay forNot ideal
{r.label}{r.best.join(", ")}{r.ok.join(", ")}{r.avoid.join(", ")}
+
+
+ ); +}