From 6e037861b3ec7d97dafbf430ba086954a2ec59cd Mon Sep 17 00:00:00 2001 From: Jaime Date: Thu, 1 Oct 2026 13:19:24 -0400 Subject: [PATCH 1/8] fix: give both quiz pages their own share previews The quiz is the page people are most likely to share, but its layouts only set a title, so links showed the generic site card. The bonus quiz's description also named nuclear, petroleum and marine, which it doesn't cover. Co-Authored-By: Claude Opus 5.5 --- app/quiz/layout.tsx | 10 ++++++++-- app/quiz/more-majors/layout.tsx | 12 +++++++++--- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/app/quiz/layout.tsx b/app/quiz/layout.tsx index ec99169..56beffc 100644 --- a/app/quiz/layout.tsx +++ b/app/quiz/layout.tsx @@ -2,9 +2,15 @@ import type { Metadata } from "next"; import { fields } from "../data/fields"; import { quizQuestions } from "../data/quiz"; +const title = "Which Engineering Field Fits You? | Engineering Exploration"; +const description = `Take a free ${quizQuestions.length}-question interest quiz and get a percentage match across ${fields.length} engineering fields, based on how you actually answered — not a verdict, a starting point.`; + export const metadata: Metadata = { - title: "Which Engineering Field Fits You? | Engineering Exploration", - description: `Take a free ${quizQuestions.length}-question interest quiz and get a percentage match across ${fields.length} engineering fields, based on how you actually answered — not a verdict, a starting point.`, + title, + description, + alternates: { canonical: "/quiz" }, + openGraph: { title, description, url: "/quiz", images: "/opengraph-image" }, + twitter: { title, description, images: "/opengraph-image" }, }; export default function QuizLayout({ children }: { children: React.ReactNode }) { diff --git a/app/quiz/more-majors/layout.tsx b/app/quiz/more-majors/layout.tsx index 532cfb5..8059a2c 100644 --- a/app/quiz/more-majors/layout.tsx +++ b/app/quiz/more-majors/layout.tsx @@ -1,9 +1,15 @@ import type { Metadata } from "next"; +const title = "The Smaller Majors Quiz | Engineering Exploration"; +const description = + "An optional bonus round for seven easy-to-overlook fields — architectural, automotive, energy, manufacturing, semiconductor, structural, and systems engineering. A tally of interest, not a percentage match."; + export const metadata: Metadata = { - title: "The Smaller Majors Quiz | Engineering Exploration", - description: - "An optional, more informal quiz for fields like nuclear, petroleum, marine, and semiconductor engineering — a tally of interest, not a percentage match.", + title, + description, + alternates: { canonical: "/quiz/more-majors" }, + openGraph: { title, description, url: "/quiz/more-majors", images: "/opengraph-image" }, + twitter: { title, description, images: "/opengraph-image" }, }; export default function MoreMajorsQuizLayout({ children }: { children: React.ReactNode }) { From 918158d9c89f1593e19420325d58b5f958fe01cc Mon Sep 17 00:00:00 2001 From: Jaime Date: Thu, 1 Oct 2026 13:19:24 -0400 Subject: [PATCH 2/8] chore: remove unused create-next-app starter assets Co-Authored-By: Claude Opus 5.5 --- public/file.svg | 1 - public/globe.svg | 1 - public/next.svg | 1 - public/vercel.svg | 1 - public/window.svg | 1 - 5 files changed, 5 deletions(-) delete mode 100644 public/file.svg delete mode 100644 public/globe.svg delete mode 100644 public/next.svg delete mode 100644 public/vercel.svg delete mode 100644 public/window.svg diff --git a/public/file.svg b/public/file.svg deleted file mode 100644 index 004145c..0000000 --- a/public/file.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/public/globe.svg b/public/globe.svg deleted file mode 100644 index 567f17b..0000000 --- a/public/globe.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/public/next.svg b/public/next.svg deleted file mode 100644 index 5174b28..0000000 --- a/public/next.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/public/vercel.svg b/public/vercel.svg deleted file mode 100644 index 7705396..0000000 --- a/public/vercel.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/public/window.svg b/public/window.svg deleted file mode 100644 index b2b2a44..0000000 --- a/public/window.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file From e070337ddb8689528c4c228c3142d8f6fd60610d Mon Sep 17 00:00:00 2001 From: Jaime Date: Thu, 1 Oct 2026 13:19:24 -0400 Subject: [PATCH 3/8] fix: compute the share image's field count from the data The count was hardcoded, the same kind of drift that already had to be fixed twice elsewhere when the field list grew. Co-Authored-By: Claude Opus 5.5 --- app/opengraph-image.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/opengraph-image.tsx b/app/opengraph-image.tsx index ee9157d..1cfe1bd 100644 --- a/app/opengraph-image.tsx +++ b/app/opengraph-image.tsx @@ -1,4 +1,5 @@ import { ImageResponse } from "next/og"; +import { fields } from "./data/fields"; export const size = { width: 1200, height: 630 }; export const contentType = "image/png"; @@ -80,7 +81,8 @@ export default function Image() {
- Real engineering, no hype — 23 fields, honest tradeoffs, hands-on challenges + {/* One string, not inline {fields.length}: Satori lays out each JSX text fragment as its own flex item. */} + {`Real engineering, no hype — ${fields.length} fields, honest tradeoffs, hands-on challenges`}
), From 2c6849e9d59f6d2e89777f6293e84e8919996796 Mon Sep 17 00:00:00 2001 From: Jaime Date: Thu, 1 Oct 2026 13:19:24 -0400 Subject: [PATCH 4/8] ci: run typecheck, lint, tests and build on every push and PR Co-Authored-By: Claude Opus 5.5 --- .github/workflows/ci.yml | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..7751965 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + +jobs: + check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - uses: actions/setup-node@v5 + with: + # The tests are .mts files run by Node's built-in test runner, which + # needs a Node version that strips TypeScript types natively. + node-version: 24 + cache: npm + - run: npm ci + - name: Typecheck + run: npx tsc --noEmit + - name: Lint + run: npm run lint + - name: Test + run: npm test + - name: Build + run: npm run build From 64303cbe0a3ae14a93cbe407e34acb7bbcdf31ee Mon Sep 17 00:00:00 2001 From: Jaime Date: Thu, 1 Oct 2026 13:19:25 -0400 Subject: [PATCH 5/8] docs: add CI badge, photography, school themes and sharing to the README Co-Authored-By: Claude Opus 5.5 --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index 54cb3b8..836e3e2 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # Engineering Exploration +[![CI](https://github.com/jgomezrom/engineering-exploration/actions/workflows/ci.yml/badge.svg)](https://github.com/jgomezrom/engineering-exploration/actions/workflows/ci.yml) + A website to help students figure out which engineering field might actually fit them, built as a personal project by **Jaime Gomez**, a student at MSU. **Live site:** https://engineering-exploration-two.vercel.app @@ -17,6 +19,9 @@ Back in high school I remember looking at a long list of engineering majors and - **A 28-question interest quiz** scoring a percentage against all 23 fields, with a radar chart, plus a shorter bonus quiz for the less familiar majors - **12 hands-on challenges** with real materials lists, costs, time estimates and step-by-step instructions - **Fully bilingual (English/Spanish)** — every page, every simulation, every field +- **A real photograph for every field** — public-domain and CC0 images from NASA, the Library of Congress (the Historic American Engineering Record and the Carol M. Highsmith Archive), the U.S. Navy and Wikimedia Commons, each credited and deep-linked to its source record so a caption can be re-checked +- **Optional school color themes** — pick a Big Ten or SEC school and the whole site re-themes to its colors +- **Built to be shared and found** — every page has its own share preview with a generated image, a canonical URL, and a place in the sitemap, plus structured data so search engines know what the site is - 64 pages, all statically generated - Custom illustrations, icons and charts are hand-built SVG — no stock photos, no icon library, no charting library - Accessible: real focus management on dialogs, keyboard traps where modals need them, checked contrast and heading structure, and `prefers-reduced-motion` respected @@ -44,6 +49,8 @@ npm test 22 tests covering the three solvers, using Node's built-in test runner — **no test framework added as a dependency**. They check the beam against `Pab/L` and `PL³/48EI`, the pendulum against published elliptic-integral period ratios, and the projectile's drag solution against a second integrator written independently inside the test, since agreeing with a different method is a real check while agreeing with itself is not. They also check things that must be true regardless: equilibrium holds, moment returns to zero at both supports, amplitude doesn't decay in a frictionless swing, and air never adds range. +GitHub Actions runs the typecheck, lint, tests and a full production build on every push and pull request, so a broken change shows up before it reaches the live site. + ## Tech stack - **Next.js 16** (App Router), **React 19**, **TypeScript** @@ -51,6 +58,7 @@ npm test - No backend, no database — it's a fully static site, generated at build time - No UI kit, no charting library, no physics library, no test framework — the solvers, the radar chart and every illustration are hand-written - `app/lib/` holds the numerical solvers as plain functions, separate from the UI, which is what makes them testable +- **GitHub Actions** for CI, **Vercel** for hosting — every merge to `main` deploys automatically ## How this was actually built @@ -66,6 +74,7 @@ I didn't type most of these lines of code. But I know why every part of this sit - Why sourcing matters: I had this project verify salary numbers against the actual BLS pages instead of trusting a search summary, and it caught an earlier mistake where a sitemap pointed at the wrong domain because a similarly-named site already existed - That it's easy to accidentally write "advertising" language even when you're trying to be honest, and worth specifically reviewing your own content for it - The basics of deploying to Vercel, environment variables, and why a sitemap needs to know its own real production URL to be useful at all +- That a framework can quietly undo your work: in Next.js, a page that sets its own share-preview (Open Graph) title replaces the parent layout's share settings entirely instead of merging with them, so adding per-page titles silently dropped the preview image from every page but the homepage — caught by checking the actual `` tags, not by assuming it worked - That a numerical answer nobody has checked isn't really an answer — every solver here gets compared against an exact solution wherever one exists, and the error goes on the page instead of staying in my head ## Running it locally From a0faf9c4262d0f0cd8061137980501da4dfd8844 Mon Sep 17 00:00:00 2001 From: Jaime Date: Thu, 1 Oct 2026 13:21:08 -0400 Subject: [PATCH 6/8] ci: generate Next.js route types before typechecking PageProps and LayoutProps live in .next/types, which a fresh CI checkout doesn't have, so tsc failed on its first run even though it passed locally. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/ci.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7751965..06e33e5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,6 +17,10 @@ jobs: node-version: 24 cache: npm - run: npm ci + # PageProps/LayoutProps are generated into .next/types, which a fresh + # checkout doesn't have, so tsc can't see them without this first. + - name: Generate route types + run: npx next typegen - name: Typecheck run: npx tsc --noEmit - name: Lint From 93c863b775bf44f2f99c8f14b90260660f6c9b05 Mon Sep 17 00:00:00 2001 From: Jaime Date: Thu, 1 Oct 2026 13:33:00 -0400 Subject: [PATCH 7/8] fix: make three "research-backed" college tips say only what their sources say Two cited blog posts, not research. The active-learning tip now cites the 2019 PNAS randomized study (Deslauriers et al.) and states its actual finding. The burnout tip now cites a 2022 systematic review of physical activity and burnout, says it shows an association in medical students, and drops the claims nothing supported. The internship tip's own source never said internships "fundamentally alter career trajectories", so it now reports what that paper found. Also notes that "R1" is a U.S. classification. Co-Authored-By: Claude Opus 5.5 --- app/data/collegeTips.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/app/data/collegeTips.ts b/app/data/collegeTips.ts index aa86734..f96a85b 100644 --- a/app/data/collegeTips.ts +++ b/app/data/collegeTips.ts @@ -3,16 +3,16 @@ import { CollegeTip } from "./types"; export const collegeTips: CollegeTip[] = [ { slug: "active-problem-solving-vs-passive-review", - text: "Focus on active problem-solving and peer-led discussions rather than passively re-reading lecture slides. Students who engage in active learning develop better higher-order cognitive skills and retain information longer, even if the increased cognitive effort makes them feel like they are struggling more in the moment.", + text: "Focus on active problem-solving and peer-led discussions rather than passively re-reading lecture slides. In a 2019 randomized study of introductory college physics courses, students taught with active methods learned more than students taught by highly rated lecturers — yet felt like they had learned less, because they took the extra mental effort as a sign they weren't getting it. Feeling like you're struggling while you work problems doesn't mean it isn't working.", theme: "study-strategies", confidence: "research-backed", dependsOn: "The student's willingness to embrace the discomfort of working through difficult practice problems from scratch rather than taking the \"easy\" route of just watching a professor solve them on a board.", - sourceUrl: "https://adiutor.co/blog/impact-of-active-versus-passive-learning-approaches-on-student-academic-performance/", + sourceUrl: "https://pmc.ncbi.nlm.nih.gov/articles/PMC6765278/", }, { slug: "secure-an-internship-before-graduation", - text: "Prioritize securing at least one industry internship or co-op before graduation. Hands-on experience fundamentally alters career trajectories, clarifies career choices, and significantly builds professional competencies compared to classroom theory alone.", + text: "Prioritize securing at least one industry internship or co-op before graduation. Education research counts internships among the \"high-impact practices\" linked to higher student engagement and retention, and in a 2018 study, interns at an engineering firm could name specific skills they had picked up on the job.", theme: "internships", confidence: "research-backed", dependsOn: @@ -34,7 +34,7 @@ export const collegeTips: CollegeTip[] = [ theme: "research", confidence: "varies by situation", dependsOn: - "Faculty availability, department funding, and the type of institution. This is highly feasible at large research institutions (R1 universities) but may be much harder to secure at smaller teaching-focused colleges with limited graduate lab space.", + "Faculty availability, department funding, and the type of institution. This is highly feasible at large research institutions (R1 universities, in the U.S. classification) but may be much harder to secure at smaller teaching-focused colleges with limited graduate lab space.", }, { slug: "input-focused-study-goals", @@ -46,12 +46,12 @@ export const collegeTips: CollegeTip[] = [ sourceUrl: "https://www.youtube.com/watch?v=5JH130NIR8k", }, { - slug: "schedule-deliberate-breaks-and-exercise", - text: 'Schedule deliberate "waste-of-time" activities and physical exercise into your weekly calendar to combat the physical exhaustion and cognitive impairment that comes with intense coursework. The unrelenting quest for perfection in engineering makes stepping away entirely critical to preventing emotional detachment.', + slug: "schedule-regular-exercise", + text: "Put physical exercise on your weekly calendar as protected time, not something you'll get to once the work is done — in a heavy course load, the work is rarely done. A 2022 systematic review of 18 studies covering 11,500 medical students in 13 countries found that students who were more physically active had lower burnout and a better quality of life, with more activity tending to go with bigger differences. That's an association, not proof that exercise prevents burnout.", theme: "workload-and-burnout", confidence: "research-backed", dependsOn: - "The individual's time-management skills. Taking deliberate breaks is an effective recovery tool only if the student is consistently utilizing their study blocks productively and not falling behind on critical deadlines.", - sourceUrl: "https://www.sandipuniversity.edu.in/blog/signs-of-burnout-in-engineering-students/", + "Your schedule and how you manage it. Exercise only helps if it fits around your critical deadlines instead of eating study time you can't spare — and the research behind this tip comes from medical students, not engineering students specifically.", + sourceUrl: "https://pmc.ncbi.nlm.nih.gov/articles/PMC9826463/", }, ]; From f49f1e8f92474276a7ceed03bb4a3c0d9b303ae3 Mon Sep 17 00:00:00 2001 From: Jaime Date: Thu, 1 Oct 2026 13:33:00 -0400 Subject: [PATCH 8/8] feat: translate the college tips into Spanish /college was the only page of 64 still showing English text in Spanish mode. Co-Authored-By: Claude Opus 5.5 --- app/components/CollegeContent.tsx | 4 ++- app/data/collegeTips.es.ts | 60 +++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 app/data/collegeTips.es.ts diff --git a/app/components/CollegeContent.tsx b/app/components/CollegeContent.tsx index d0cd2b2..046327a 100644 --- a/app/components/CollegeContent.tsx +++ b/app/components/CollegeContent.tsx @@ -7,6 +7,7 @@ import { fields } from "../data/fields"; import { fieldsEs } from "../data/fields.es"; import { collegeCurricula } from "../data/collegeCurricula"; import { collegeTips } from "../data/collegeTips"; +import { collegeTipsEs } from "../data/collegeTips.es"; import { CurriculumSequence, CurriculumYear, FieldSlug, TipTheme } from "../data/types"; import { useLanguage } from "../context/LanguageContext"; import { collegeTranslations, tipThemeLabels, tipConfidenceLabels, collegeOnlyMajorNames } from "../data/translations/college"; @@ -57,6 +58,7 @@ export default function CollegeContent() { const themeLabel = tipThemeLabels[language]; const confidenceLabel = tipConfidenceLabels[language]; const displayFields = language === "es" ? fieldsEs : fields; + const displayTips = language === "es" ? collegeTipsEs : collegeTips; const majorNameFallback = collegeOnlyMajorNames[language]; const majorName = useCallback( (slug: FieldSlug) => displayFields.find((f) => f.slug === slug)?.name ?? majorNameFallback[slug] ?? slug, @@ -167,7 +169,7 @@ export default function CollegeContent() {
{THEME_ORDER.map((theme) => { - const themeTips = collegeTips.filter((tip) => tip.theme === theme); + const themeTips = displayTips.filter((tip) => tip.theme === theme); if (themeTips.length === 0) return null; return (
diff --git a/app/data/collegeTips.es.ts b/app/data/collegeTips.es.ts new file mode 100644 index 0000000..147f7c8 --- /dev/null +++ b/app/data/collegeTips.es.ts @@ -0,0 +1,60 @@ +import { CollegeTip } from "./types"; + +// Spanish translation of collegeTips.ts. `slug`, `theme`, `confidence`, and +// `sourceUrl` stay identical to the English entries — they're keys and links, +// not display text. Keep each claim exactly as strong as its source. +export const collegeTipsEs: CollegeTip[] = [ + { + slug: "active-problem-solving-vs-passive-review", + text: "Enfócate en resolver problemas activamente y en discusiones entre compañeros, en lugar de releer pasivamente las diapositivas de clase. En un estudio aleatorizado de 2019 con cursos introductorios de física universitaria, los estudiantes a quienes se les enseñó con métodos activos aprendieron más que los que tuvieron clases magistrales con profesores muy bien evaluados — pero sintieron que habían aprendido menos, porque interpretaron el esfuerzo mental adicional como señal de que no estaban entendiendo. Sentir que te cuesta mientras resuelves problemas no significa que no esté funcionando.", + theme: "study-strategies", + confidence: "research-backed", + dependsOn: + "La disposición del estudiante a aceptar la incomodidad de resolver problemas de práctica difíciles desde cero, en lugar de tomar el camino \"fácil\" de solo ver a un profesor resolverlos en el pizarrón.", + sourceUrl: "https://pmc.ncbi.nlm.nih.gov/articles/PMC6765278/", + }, + { + slug: "secure-an-internship-before-graduation", + text: "Dale prioridad a conseguir al menos una pasantía o prácticas profesionales en la industria (o un programa co-op) antes de graduarte. La investigación en educación cuenta las pasantías entre las \"prácticas de alto impacto\" asociadas con mayor participación y permanencia de los estudiantes, y en un estudio de 2018, los pasantes de una empresa de ingeniería pudieron nombrar habilidades específicas que aprendieron en el trabajo.", + theme: "internships", + confidence: "research-backed", + dependsOn: + "La meta profesional final del estudiante. Si un estudiante tiene la intención firme de hacer un doctorado y dedicarse a la academia, participar en investigación de laboratorio durante la licenciatura generalmente tendrá prioridad sobre las pasantías en la industria.", + sourceUrl: + "https://par.nsf.gov/biblio/10076373-exploring-how-engineering-internships-undergraduate-research-experiences-inform-influence-college-students-career-decisions-future-plans", + }, + { + slug: "front-load-hard-prerequisite-chains", + text: "Toma pronto las cadenas largas de prerrequisitos, y planea para tu segundo o tercer año las materias que tienen fama de ser más difíciles.", + theme: "course-planning", + confidence: "personal experience", + dependsOn: + "El mapa curricular específico de tu universidad y la disponibilidad de clases. Esta estrategia solo funciona si el departamento ofrece esas materias clave en los semestres en que las necesitas, y requiere una asesoría académica cuidadosa para no sobrecargar un solo semestre.", + }, + { + slug: "join-a-research-lab-sophomore-year", + text: "Ofrécete como voluntario en el laboratorio de investigación de un profesor a más tardar en tu segundo año, para ganar habilidades técnicas prácticas y experiencia con equipo que las materias básicas normales no te van a enseñar.", + theme: "research", + confidence: "varies by situation", + dependsOn: + "La disponibilidad del profesorado, el financiamiento del departamento y el tipo de institución. Es muy factible en universidades grandes de investigación (las llamadas universidades R1, según la clasificación de EE. UU.) pero puede ser mucho más difícil de conseguir en universidades más pequeñas enfocadas en la enseñanza, con poco espacio de laboratorio para posgrado.", + }, + { + slug: "input-focused-study-goals", + text: 'Ponte metas de estudio "enfocadas en el proceso" (por ejemplo, "voy a hacer problemas de práctica concentrado durante dos horas") en lugar de metas "enfocadas en el resultado" (por ejemplo, "hoy voy a memorizar todo este capítulo").', + theme: "study-strategies", + confidence: "varies by situation", + dependsOn: + "La autodisciplina básica del estudiante. Las metas enfocadas en el proceso solo funcionan si el estudiante se hace responsable de trabajar de verdad, concentrado y sin distracciones, durante esos bloques de tiempo, en lugar de solo dejar correr el reloj.", + sourceUrl: "https://www.youtube.com/watch?v=5JH130NIR8k", + }, + { + slug: "schedule-regular-exercise", + text: "Pon el ejercicio físico en tu calendario semanal como tiempo protegido, no como algo que harás cuando termines el trabajo — con una carga académica pesada, el trabajo casi nunca se termina. Una revisión sistemática de 2022, con 18 estudios que abarcaron a 11,500 estudiantes de medicina en 13 países, encontró que los estudiantes más activos físicamente tenían menos agotamiento (burnout) y mejor calidad de vida, y que más actividad tendía a ir de la mano con diferencias mayores. Es una asociación, no una prueba de que el ejercicio prevenga el burnout.", + theme: "workload-and-burnout", + confidence: "research-backed", + dependsOn: + "Tu horario y cómo lo administras. El ejercicio solo ayuda si se acomoda alrededor de tus fechas de entrega importantes, en lugar de quitarte tiempo de estudio que no te puedes permitir perder — y la investigación detrás de este consejo viene de estudiantes de medicina, no específicamente de estudiantes de ingeniería.", + sourceUrl: "https://pmc.ncbi.nlm.nih.gov/articles/PMC9826463/", + }, +];