Connection lost. Reconnecting… attempt 1 of 8
Paused. Your work is held on the server.
Could not reconnect.
This session has expired on the server.
An unhandled error has occurred.
Sedna.UI
v0.17.0 · main

Media

tier 2 — classes

An image whose size the app does not control, and a gallery of attachments. Long-form text from a source the app did not author is Prose.

Photographs from Unsplash Illustration only, attributed under each picture. They are the one thing this site loads from a remote host; the package still loads nothing remote.

Media frame

A bounded box for an image whose size you do not control. The aspect-ratio is what stops the page reflowing as images arrive — the box has its height before the first byte — and width/height on the <img> is worth writing anyway, because it tells the browser the ratio of the file rather than of the frame. loading="lazy" on anything below the fold; a real alt always, and <figure class="media-figure"> when there is a caption.

<div class="gallery" style="max-width:560px">
    <figure class="media-figure">
        <div class="media-frame media-frame--square media-frame--cover">
            <img src="https://images.unsplash.com/photo-1587293852726-70cdb56c2866?w=480&amp;h=480&amp;fit=crop&amp;q=70&amp;auto=format"
                 width="480" height="480" loading="lazy"
                 alt="A warehouse aisle of tall pallet racking, seen down its length" />
        </div>
        <figcaption>Square &mdash; a thumbnail. Photo by CHUTTERSNAP on Unsplash.</figcaption>
    </figure>
    <figure class="media-figure">
        <div class="media-frame media-frame--cover">
            <img src="https://images.unsplash.com/photo-1586528116311-ad8dd3c8310d?w=640&amp;h=400&amp;fit=crop&amp;q=70&amp;auto=format"
                 width="640" height="400" loading="lazy"
                 alt="A warehouse floor stacked with yellow crates, staff working in the aisle" />
        </div>
        <figcaption>16:10 &mdash; the default. Photo by Adrian Sulyok on Unsplash.</figcaption>
    </figure>
    <figure class="media-figure">
        <div class="media-frame media-frame--wide media-frame--cover">
            <img src="https://images.unsplash.com/photo-1565891741441-64926e441838?w=840&amp;h=360&amp;fit=crop&amp;q=70&amp;auto=format"
                 width="840" height="360" loading="lazy"
                 alt="An industrial yard: chevron-marked barriers beside a service road" />
        </div>
        <figcaption>21:9 &mdash; a banner. Photo by Marcin Jozwiak on Unsplash.</figcaption>
    </figure>
</div>

Contain or cover

The same photograph in the same square frame, and the whole decision in one pair. The default contains: the image is letterboxed and all of it is there, which is what a screenshot, a scanned document or a label needs, because cropping one removes the part somebody needed. .media-frame--cover fills and crops, which is what a thumbnail or a banner needs. Picking the wrong one is silent — the image still appears, just without the corner that mattered.

<div class="gallery" style="max-width:560px">
    <figure class="media-figure">
        <div class="media-frame media-frame--square">
            <img src="https://images.unsplash.com/photo-1553413077-190dd305871c?w=640&amp;h=400&amp;fit=crop&amp;q=70&amp;auto=format"
                 width="640" height="400" loading="lazy"
                 alt="A long warehouse aisle between tall racks of stored goods" />
        </div>
        <figcaption>
            The default. The whole photograph is there, letterboxed against the frame's own
            surface &mdash; right for a document, a label, a screenshot, anything a reader has to
            be able to read all of. Photo by Ruchindra Gunasekara on Unsplash.
        </figcaption>
    </figure>
    <figure class="media-figure">
        <div class="media-frame media-frame--square media-frame--cover">
            <img src="https://images.unsplash.com/photo-1553413077-190dd305871c?w=640&amp;h=400&amp;fit=crop&amp;q=70&amp;auto=format"
                 width="640" height="400" loading="lazy"
                 alt="The same warehouse aisle, cropped to a square" />
        </div>
        <figcaption>
            <code>--cover</code>. The frame is filled and the sides are cropped away &mdash; right
            for a decorative banner or a thumbnail, and wrong the moment the edges carry
            information.
        </figcaption>
    </figure>
</div>

.gallery is a breakpoint-free grid of thumbnails. Each tile is a real <button> or <a> directly inside it — never a <div> with a click handler — and every frame is square whatever aspect modifier it carries, so a row of mixed attachments lines up. Which is the point of the mix here: two photographs somebody took at the dock and two files, in one grid, squared by the same rule.

Photographs by Arno Senoner and Hannes Egler on Unsplash.

<div class="gallery" style="max-width:520px">
    <button class="btn-ghost" type="button" style="padding:0; text-align:start">
        <span class="media-frame media-frame--cover">
            <img src="https://images.unsplash.com/photo-1592085198739-ffcad7f36b54?w=280&amp;h=280&amp;fit=crop&amp;q=70&amp;auto=format"
                 width="280" height="280" loading="lazy"
                 alt="Photo of the bay: blue steel racking uprights and beams, seen close up" />
        </span>
        <span class="text-sm text-muted text-truncate sedna-mt-1">racking-bay-4209.jpg</span>
    </button>
    <button class="btn-ghost" type="button" style="padding:0; text-align:start">
        <span class="media-frame media-frame--cover">
            <img src="https://images.unsplash.com/photo-1504376830547-506dedfe1fe9?w=280&amp;h=280&amp;fit=crop&amp;q=70&amp;auto=format"
                 width="280" height="280" loading="lazy"
                 alt="Photo of the dock: hazard-striped loading bays and a red line marked on the floor" />
        </span>
        <span class="text-sm text-muted text-truncate sedna-mt-1">dock-bays-4209.jpg</span>
    </button>
    <button class="btn-ghost" type="button" style="padding:0; text-align:start">
        <span class="media-frame">
            <i class="ri-file-pdf-2-line" style="font-size:26px; color:var(--border-hover)"></i>
        </span>
        <span class="text-sm text-muted text-truncate sedna-mt-1">pod-ord-4209.pdf</span>
    </button>
    <button class="btn-ghost" type="button" style="padding:0; text-align:start">
        <span class="media-frame">
            <i class="ri-file-excel-2-line" style="font-size:26px; color:var(--border-hover)"></i>
        </span>
        <span class="text-sm text-muted text-truncate sedna-mt-1">stock-check.xlsx</span>
    </button>
</div>
<p class="form-hint" style="max-width:520px">
    Photographs by Arno Senoner and Hannes Egler on Unsplash.
</p>

Pictures sharing one frame

.gallery--tight packs pictures that belong to one thing — the photos in a chat message, the ones on a card — two across with a hairline between them. The frame around them has the corners, so the pictures have none. Three leave a slot; four fill it.

Photos from the dock

Photographs by CHUTTERSNAP and Adrian Sulyok on Unsplash.

<div class="card" style="max-width:360px">
    <div class="card-head"><h3>Photos from the dock</h3></div>
    <div class="card-body">
        <div class="gallery gallery--tight">
            <button class="media-open" type="button" aria-label="Open the photo of the racking">
                <span class="media-frame media-frame--square media-frame--cover">
                    <img src="https://images.unsplash.com/photo-1587293852726-70cdb56c2866?w=280&amp;h=280&amp;fit=crop&amp;q=70&amp;auto=format"
                         width="280" height="280" loading="lazy" alt="A warehouse aisle of tall pallet racking" />
                </span>
            </button>
            <button class="media-open" type="button" aria-label="Open the photo of the crates">
                <span class="media-frame media-frame--square media-frame--cover">
                    <img src="https://images.unsplash.com/photo-1586528116311-ad8dd3c8310d?w=280&amp;h=280&amp;fit=crop&amp;q=70&amp;auto=format"
                         width="280" height="280" loading="lazy" alt="A warehouse floor stacked with yellow crates" />
                </span>
            </button>
        </div>
        <p class="form-hint">Photographs by CHUTTERSNAP and Adrian Sulyok on Unsplash.</p>
    </div>
</div>

.link-preview is a link unfurled: the picture, then the title, a line and the host in .link-preview-body. The app fetches the preview and decides whether to show one; the picture's alt is empty because the title already names the link. A chat message carries the same card.

The dock schedule for week 38 is up, with the Thursday cut-off moved to 14:00.

Dock schedule — week 38 Inbound and outbound slots for all four docks, with the Thursday cut-off marked. ops.example.com

Photograph by Adrian Sulyok on Unsplash.

<div class="card" style="max-width:520px">
    <div class="card-body sedna-col sedna-gap-3">
        <p>The dock schedule for week 38 is up, with the Thursday cut-off moved to 14:00.</p>
        <a class="link-preview" href="#">
            <img src="https://images.unsplash.com/photo-1586528116311-ad8dd3c8310d?w=176&amp;h=176&amp;fit=crop&amp;q=70&amp;auto=format" width="88" height="88" alt="" loading="lazy" />
            <span class="link-preview-body">
                <strong>Dock schedule — week 38</strong>
                <span>Inbound and outbound slots for all four docks, with the Thursday cut-off marked.</span>
                <small>ops.example.com</small>
            </span>
        </a>
        <p class="form-hint">Photograph by Adrian Sulyok on Unsplash.</p>
    </div>
</div>

Looking at one large

Live — open a picture. .lightbox is a <dialog> opened with showModal()ShowModalAsync from C# — so the top layer, the focus trap, Escape and the inert page behind it are the platform's. The .lightbox-stage holds the picture as large as the viewport allows; .lightbox-head carries the .lightbox-title, the .lightbox-meta and the .lightbox-actions — download, open, close — and .lightbox-prev / .lightbox-next go to the neighbours, with the .lightbox-caption under it all. Which picture is in it is the app's state, and so are the arrow keys: one dialog whose stage the app re-renders, or one per attachment as here. A picture that opens one is a .media-open button.

<div class="gallery" style="max-width:520px">
    <button class="btn-ghost" type="button" style="padding:0; text-align:start"
            onclick="document.getElementById('lb-racking').showModal()">
        <span class="media-frame media-frame--cover">
            <img src="https://images.unsplash.com/photo-1587293852726-70cdb56c2866?w=280&amp;h=280&amp;fit=crop&amp;q=70&amp;auto=format"
                 width="280" height="280" loading="lazy"
                 alt="A warehouse aisle of tall pallet racking, seen down its length" />
        </span>
        <span class="text-sm text-muted text-truncate sedna-mt-1">racking-bay-4209.jpg</span>
    </button>
    <button class="btn-ghost" type="button" style="padding:0; text-align:start"
            onclick="document.getElementById('lb-dock').showModal()">
        <span class="media-frame media-frame--cover">
            <img src="https://images.unsplash.com/photo-1586528116311-ad8dd3c8310d?w=280&amp;h=280&amp;fit=crop&amp;q=70&amp;auto=format"
                 width="280" height="280" loading="lazy"
                 alt="A warehouse floor stacked with yellow crates, staff working in the aisle" />
        </span>
        <span class="text-sm text-muted text-truncate sedna-mt-1">crates-4209.jpg</span>
    </button>
</div>

<dialog class="lightbox" id="lb-racking" aria-labelledby="lb-racking-title">
    <div class="lightbox-head">
        <strong class="lightbox-title" id="lb-racking-title">racking-bay-4209.jpg</strong>
        <span class="lightbox-meta">1 of 2 · 1.2 MB</span>
        <div class="lightbox-actions">
            <a class="btn btn-ghost btn-icon" href="https://images.unsplash.com/photo-1587293852726-70cdb56c2866?w=1600&amp;q=80&amp;auto=format" target="_blank" rel="noopener" aria-label="Open the original in a new tab"><i class="ri-external-link-line"></i></a>
            <form method="dialog"><button class="btn btn-ghost btn-icon" aria-label="Close"><i class="ri-close-line"></i></button></form>
        </div>
    </div>
    <div class="lightbox-stage">
        <img src="https://images.unsplash.com/photo-1587293852726-70cdb56c2866?w=1600&amp;q=80&amp;auto=format"
             alt="A warehouse aisle of tall pallet racking, seen down its length" />
    </div>
    <button class="lightbox-prev" type="button" aria-label="Previous" disabled><i class="ri-arrow-left-s-line"></i></button>
    <button class="lightbox-next" type="button" aria-label="Next"
            onclick="document.getElementById('lb-racking').close(); document.getElementById('lb-dock').showModal()"><i class="ri-arrow-right-s-line"></i></button>
    <p class="lightbox-caption">Bay 4 after the racking was moved. Photo by CHUTTERSNAP on Unsplash.</p>
</dialog>

<dialog class="lightbox" id="lb-dock" aria-labelledby="lb-dock-title">
    <div class="lightbox-head">
        <strong class="lightbox-title" id="lb-dock-title">crates-4209.jpg</strong>
        <span class="lightbox-meta">2 of 2 · 940 KB</span>
        <div class="lightbox-actions">
            <a class="btn btn-ghost btn-icon" href="https://images.unsplash.com/photo-1586528116311-ad8dd3c8310d?w=1600&amp;q=80&amp;auto=format" target="_blank" rel="noopener" aria-label="Open the original in a new tab"><i class="ri-external-link-line"></i></a>
            <form method="dialog"><button class="btn btn-ghost btn-icon" aria-label="Close"><i class="ri-close-line"></i></button></form>
        </div>
    </div>
    <div class="lightbox-stage">
        <img src="https://images.unsplash.com/photo-1586528116311-ad8dd3c8310d?w=1600&amp;q=80&amp;auto=format"
             alt="A warehouse floor stacked with yellow crates, staff working in the aisle" />
    </div>
    <button class="lightbox-prev" type="button" aria-label="Previous"
            onclick="document.getElementById('lb-dock').close(); document.getElementById('lb-racking').showModal()"><i class="ri-arrow-left-s-line"></i></button>
    <button class="lightbox-next" type="button" aria-label="Next" disabled><i class="ri-arrow-right-s-line"></i></button>
    <p class="lightbox-caption">The crates on the floor before the belt. Photo by Adrian Sulyok on Unsplash.</p>
</dialog>

A document in it

A PDF or a text file is a .lightbox-frame in the stage — an <object> with a download link as its fallback, or an <iframe> with a title — and the download link in the actions is the way to keep it. Name the frame the way a screen reader would want the tab it replaces named.

<dialog class="lightbox" id="lb-pod" aria-labelledby="lb-pod-title">
    <div class="lightbox-head">
        <strong class="lightbox-title" id="lb-pod-title">pod-ord-4209.pdf</strong>
        <span class="lightbox-meta">84 KB</span>
        <div class="lightbox-actions">
            <a class="btn btn-ghost btn-icon" href="…" download aria-label="Download"><i class="ri-download-2-line"></i></a>
            <form method="dialog"><button class="btn btn-ghost btn-icon" aria-label="Close"><i class="ri-close-line"></i></button></form>
        </div>
    </div>
    <div class="lightbox-stage">
        <object class="lightbox-frame" data="…" type="application/pdf" aria-label="Proof of delivery for ORD-4209">
            <a href="…" download>The browser cannot show this PDF here — download it.</a>
        </object>
    </div>
</dialog>