Skip to main content
Maria Silva
← Back to Work

A Mobile Companion App for a University of Porto Exhibition

VueTypeScriptWeb DevAccessibilityUX/UI Design

Stack: Vue 3 · TypeScript · Pinia · Vue Router · Vite · Bun · Vitest

Context

This was a freelance project supporting an exhibition at the University of Porto: a webapp visitors could pull up on their phones while walking through the physical exhibition, giving them additional context and information about each piece on display.

I was responsible for the project end-to-end — concept refinement, UX decisions, and the full frontend implementation in Vue. Because the exhibition and its accompanying digital materials would be preserved with a DOI, making the experience polished, intuitive, and lasting mattered more than it might for a typical microsite.

Initial Design: A Visual Gallery

The first version focused on quick access to individual exhibition pieces: a visual gallery letting visitors navigate by image and item number. To make exploration easier, I also built a searchable navigation menu so visitors could jump directly to any item without scrolling through the whole collection.

From Gallery to Guided Narrative

Like many real-world projects, the brief evolved significantly as new content became available. As the exhibition's narrative took shape and the client delivered the real historical material, the goal shifted — rather than presenting isolated pieces, the application needed to become a structured storytelling experience, with introductory sections, historical context before and after the Portuguese Revolution of April 25th, and multiple themed chapters connecting the content together.

To support that shift, I redesigned the navigation flow into a seamless, linear journey:

  • Introductory pages integrated into the overall experience
  • Previous and Next navigation throughout the exhibition
  • Consistent movement between chapters and items
  • A final Restart action that naturally returns visitors to the beginning

The final structure holds 160 catalogued items across 11 chapters spanning two historical periods — all driven from a single JSON dataset rather than hardcoded into the views.

Designed to Feel Like an App

One of the key requirements was that the experience should feel like a native mobile app, even though it was delivered as a website. I designed and implemented a mobile-first interface — the primary use case being a phone in hand while walking through the exhibition — while making sure it stayed fully functional across all devices and screen sizes.

Under the Hood

Because the client would keep editing content after launch, and because the exhibition materials were being preserved long-term, I put more engineering into the data and content layer than a typical microsite would need:

  • Custom HTML sanitizer. Item descriptions are rich text (bold, links, block quotes) authored by the client and loaded from a JSON file at runtime — not static, trusted markup. Rather than pull in a dependency, I wrote an allowlist-based sanitizer: it strips disallowed tags and attributes, blocks javascript: and other unsafe URL schemes on links, and forces safe rel/target attributes on anything it lets through. It's covered by unit tests that specifically try to break it with real XSS payloads (<script>, inline onclick, javascript: hrefs).
  • Runtime validation of untrusted content. Since exhibition data loads from JSON at runtime rather than being compiled in, I wrote type guards that validate the shape of every record before it's trusted as app data, with a normalization step that supports two content-authoring formats so the client isn't locked into one.
  • Centralized state with explicit loading/error states, using Pinia — if the content fails to load, the app shows a real error state instead of a blank screen.
  • A small internal content tool so non-technical staff could add, edit, and reorder exhibition items without touching code, gated out of the production bundle entirely in dev builds.
  • Deployment-safe asset resolution for every image, video, and data path, since the app needed to run correctly deployed under a university subdirectory rather than a domain root.
  • Test suite (Vitest + Vue Test Utils) covering the sanitizer, data validation, and navigation logic — not just component smoke tests.

Outcome

The exhibition received strong engagement from students, staff, and visitors, and was invited to remain open beyond its original schedule because of the response. I'm proud to have contributed to a project that helped make the exhibition's content more accessible and engaging for the people experiencing it in person.

Reflections

What I found most rewarding wasn't the development work in isolation, but the collaboration: the project went through multiple iterations as the exhibition's plans evolved, from a simple image gallery to a full guided narrative spanning 160 items. That kind of shift only works with continuous communication and a codebase built to absorb changing content, not just changing pixels.

Let's Get
in Touch

Open for full-time roles, freelance projects, and interesting collaborations.