DevLog #2 — A PDF Dialog and the Mobile Iframe Problem

Three days after the first session, I sat down with Claude Code again. The goal was simple: add my English test scores to the certificates section. It turned into a full component-building session.

Starting Point

The certificates section was missing scores. Quick data update — scores added, a typo fixed (CU-TEC → CU-TEP). TU-GET had a scanned paper PDF, so instead of a plain download link I wanted to show it inline. That led to building the dialog.

The PDF Dialog

The first version was a PdfModal component — an iframe wrapped in a fixed overlay. It worked, but the name was wrong (we'd started calling everything "Dialog" instead of "Modal") and it wasn't reusable.

So we built it properly: two components.

Dialog.astro

A generic overlay that accepts any slot content:

<Dialog id="my-dialog" title="Title" panelClass="max-w-2xl">
  <p>Any content here</p>
</Dialog>

The JS API is exposed on window:

dialog_my-dialog.open('Optional title override')
dialog_my-dialog.close()

It handles Escape key, backdrop click, and body scroll lock. Open and close both animate — backdrop fades, panel scales up from 96% with a slight vertical offset. The close animation runs to completion before the element is hidden, which required filtering transitionend to only fire on the outer element's opacity transition (child elements like the close button also emit transitionend on hover, which would cut the animation short).

PdfDialog.astro

Wraps Dialog with a PDF-specific open function:

openPdfModal('/files/doc.pdf', 'Document Title')

For desktop, it creates an <iframe> with #toolbar=0&navpanes=0&scrollbar=0 appended to the URL — hides the browser's PDF toolbar. The element is created dynamically on open and destroyed on close, so there's no stale src sitting in the DOM.

The Mobile Problem

Testing on mobile revealed two things:

  1. iOS Safari doesn't render PDFs in iframes at all — it shows the filename and an "Open" button.
  2. Android Chrome has the same limitation — dropped inline PDF support years ago.

The fix was a platform check. Mobile devices open the PDF in a new tab instead:

const isMobile =
    /iPad|iPhone|iPod|Android/.test(navigator.userAgent) ||
    (/Mac/.test(navigator.userAgent) && navigator.maxTouchPoints > 1);

The second condition catches iPadOS, which reports its user agent as Mac but has touch points. navigator.platform would have worked too, but it's deprecated.

Accent Color

Also swapped the site's accent color. The original green (#6ee7b7) felt too warm against the dark background. Tried a few blues — sky, indigo, a custom hex — and landed on #4fc1ff, the variable highlight color from VS Code's Dark Modern theme. Felt right.

One line change in tailwind.config.mjs.

Accessibility Audit

An in-browser audit flagged two issues:

  • href="#" on the navbar logo — browsers flag empty hash hrefs as invalid. Changed to href="/".
  • iframe missing title attribute — required for screen readers. Added title="Document viewer".

Neither was a regression from today's work, but good to catch.

What It Felt Like

The component work was fast. The interesting part was the mobile discovery — I assumed iframes would work on Android since Chrome handles PDFs natively when you open them directly. It doesn't when they're embedded. Claude knew this immediately and had the detection logic ready before I finished asking.

The broader pattern holds from last time: fast to build, but you still need to test the thing on real devices. No tool catches that for you.