Image Space reservation

This commit is contained in:
DrMint 2024-05-25 10:03:21 +02:00
parent c1363afb55
commit 2e0b586569
14 changed files with 322 additions and 114 deletions

View File

@ -23,6 +23,7 @@
## Long term ## Long term
- More data caching between the CMS and Astro
- [Folders] Support for nameless section - [Folders] Support for nameless section
- [Scripts] Can't run the scripts using node (ts-node?) - [Scripts] Can't run the scripts using node (ts-node?)
- [Scans] Adapt size of obi based on cover/dustjacket - [Scans] Adapt size of obi based on cover/dustjacket

View File

@ -35,6 +35,7 @@ const style = `
sizes="100vw" sizes="100vw"
width={width} width={width}
height={height} height={height}
loading="lazy"
class="when-no-print when-js" class="when-no-print when-js"
/> />
<img <img
@ -44,6 +45,7 @@ const style = `
sizes="100vw" sizes="100vw"
width={width} width={width}
height={height} height={height}
loading="lazy"
class="when-no-print when-no-js" class="when-no-print when-no-js"
/> />

View File

@ -64,7 +64,7 @@ const hasNavigation = previousImageHref || nextImageHref;
) )
} }
<a href={url} target="_blank"> <a id="image-anchor" href={url} target="_blank" style=`aspect-ratio:${width}/${height};`>
<img <img
src={url} src={url}
srcset={sizesToSrcset(sizes)} srcset={sizesToSrcset(sizes)}
@ -132,11 +132,45 @@ const hasNavigation = previousImageHref || nextImageHref;
visibility: hidden; visibility: hidden;
} }
img { & > a {
&:focus-visible {
outline: 3px solid var(--color-base-1000);
outline-offset: unset;
}
&.pressable {
border-radius: 9999px;
padding-left: 1em;
padding-right: 1em;
height: 2.5em;
display: flex;
place-items: center;
place-content: center;
& > svg {
width: 1.5em;
height: 1.5em;
}
}
}
& > #image-anchor {
display: block;
transition: 100ms scale;
box-shadow: 0 5px 20px -10px var(--color-shadow);
&:hover,
&:focus-visible {
scale: 102%;
}
max-height: 70vh; max-height: 70vh;
max-width: 100%;
height: auto; & > img {
width: auto; max-width: 100%;
height: auto;
}
} }
} }

View File

@ -52,6 +52,7 @@ const {
sizes={sizesForGridLayout(250, 1.15)} sizes={sizesForGridLayout(250, 1.15)}
width={thumbnail.width} width={thumbnail.width}
height={thumbnail.height} height={thumbnail.height}
loading="lazy"
/> />
) )
} }

View File

@ -15,7 +15,7 @@ const { node, context } = Astro.props;
{ {
node.children.length > 0 && ( node.children.length > 0 && (
<p style={`text-align: ${node.format};`}> <p style={node.format ? `text-align: ${node.format};` : undefined}>
{node.children.map((node) => ( {node.children.map((node) => (
<RTNode node={node} context={context} /> <RTNode node={node} context={context} />
))} ))}

View File

@ -37,7 +37,7 @@ const mediaPage = getLocalizedUrl(`/images/${id}`);
</HeaderTitle> </HeaderTitle>
) )
} }
<a href={mediaPage}> <a href={mediaPage} style=`aspect-ratio: ${width}/${height};`>
<img <img
src={url} src={url}
srcset={sizesToSrcset(sizes)} srcset={sizesToSrcset(sizes)}
@ -53,13 +53,13 @@ const mediaPage = getLocalizedUrl(`/images/${id}`);
{/* ------------------------------------------- CSS -------------------------------------------- */} {/* ------------------------------------------- CSS -------------------------------------------- */}
<style> <style>
div {
margin-block: 4em;
}
a { a {
display: block; display: block;
line-height: 0; line-height: 0;
box-shadow: 0 5px 20px -10px var(--color-shadow);
border-radius: 16px;
overflow: hidden;
width: fit-content;
margin-bottom: 0.5em; margin-bottom: 0.5em;
transition-property: scale, margin-bottom; transition-property: scale, margin-bottom;
@ -75,12 +75,15 @@ const mediaPage = getLocalizedUrl(`/images/${id}`);
outline: 3px solid var(--color-base-1000); outline: 3px solid var(--color-base-1000);
outline-offset: unset; outline-offset: unset;
} }
}
img {
max-height: 70vh; max-height: 70vh;
max-width: 100%;
width: auto; & > img {
height: auto; box-shadow: 0 5px 20px -10px var(--color-shadow);
border-radius: 16px;
max-width: 100%;
height: auto;
}
} }
</style> </style>

View File

@ -69,8 +69,6 @@ export type WordingKey =
| "collectibles.bookFormat.binding.hardcover" | "collectibles.bookFormat.binding.hardcover"
| "collectibles.bookFormat.binding.readingDirection.leftToRight" | "collectibles.bookFormat.binding.readingDirection.leftToRight"
| "collectibles.bookFormat.binding.readingDirection.rightToLeft" | "collectibles.bookFormat.binding.readingDirection.rightToLeft"
| "collectibles.gallery"
| "collectibles.scans"
| "collectibles.imageCount" | "collectibles.imageCount"
| "header.topbar.settings.tooltip" | "header.topbar.settings.tooltip"
| "collectibles.contents" | "collectibles.contents"
@ -89,29 +87,29 @@ export type WordingKey =
| "pages.tableOfContent.break" | "pages.tableOfContent.break"
| "global.languageOverride.availableLanguages" | "global.languageOverride.availableLanguages"
| "timeline.title" | "timeline.title"
| "timeline.eras.drakengard3" | "timeline.description"
| "timeline.eras.cataclysm"
| "timeline.eras.drakengard" | "timeline.eras.drakengard"
| "timeline.eras.drakengard2" | "timeline.eras.drakengard2"
| "timeline.eras.drakengard3"
| "timeline.eras.nier" | "timeline.eras.nier"
| "timeline.eras.nierAutomata" | "timeline.eras.nierAutomata"
| "timeline.eras.cataclysm" | "timeline.jumpTo"
| "timeline.description"
| "timeline.notes.title"
| "timeline.notes.content" | "timeline.notes.content"
| "timeline.notes.title"
| "timeline.priorCataclysmNote.title" | "timeline.priorCataclysmNote.title"
| "timeline.priorCataclysmNote.content" | "timeline.priorCataclysmNote.content"
| "timeline.jumpTo"
| "timeline.year.during" | "timeline.year.during"
| "timeline.eventFooter.languages"
| "timeline.eventFooter.sources" | "timeline.eventFooter.sources"
| "timeline.eventFooter.languages"
| "timeline.eventFooter.note" | "timeline.eventFooter.note"
| "global.sources.typeLabel.url"
| "global.sources.typeLabel.page"
| "global.sources.typeLabel.collectible" | "global.sources.typeLabel.collectible"
| "global.sources.typeLabel.folder" | "global.sources.typeLabel.collectible.range.custom"
| "global.sources.typeLabel.collectible.range.page" | "global.sources.typeLabel.collectible.range.page"
| "global.sources.typeLabel.collectible.range.timestamp" | "global.sources.typeLabel.collectible.range.timestamp"
| "global.sources.typeLabel.collectible.range.custom" | "global.sources.typeLabel.folder"
| "global.sources.typeLabel.page"
| "global.sources.typeLabel.url"
| "global.openMediaPage" | "global.openMediaPage"
| "global.downloadButton" | "global.downloadButton"
| "global.previewTypes.video" | "global.previewTypes.video"
@ -121,6 +119,8 @@ export type WordingKey =
| "global.previewTypes.collectible" | "global.previewTypes.collectible"
| "global.previewTypes.unknown" | "global.previewTypes.unknown"
| "collectibles.scans.title" | "collectibles.scans.title"
| "collectibles.gallery.title"
| "collectibles.gallery.subtitle"
| "collectibles.scans.subtitle" | "collectibles.scans.subtitle"
| "collectibles.scans.shortIndex.flapFront" | "collectibles.scans.shortIndex.flapFront"
| "collectibles.scans.shortIndex.front" | "collectibles.scans.shortIndex.front"
@ -134,11 +134,9 @@ export type WordingKey =
| "collectibles.scans.obi" | "collectibles.scans.obi"
| "collectibles.scans.obiInside" | "collectibles.scans.obiInside"
| "collectibles.scans.pages" | "collectibles.scans.pages"
| "collectibles.gallery.title"
| "collectibles.gallery.subtitle"
| "global.sources.typeLabel.scans"
| "collectibles.scans.dustjacket.description" | "collectibles.scans.dustjacket.description"
| "collectibles.scans.obi.description" | "collectibles.scans.obi.description"
| "global.sources.typeLabel.scans"
| "global.sources.typeLabel.gallery" | "global.sources.typeLabel.gallery"
| "global.media.attributes.filename" | "global.media.attributes.filename"
| "global.media.attributes.duration" | "global.media.attributes.duration"

View File

@ -26,6 +26,7 @@ const { img, name, href } = Astro.props;
class="when-light-theme" class="when-light-theme"
alt={name} alt={name}
title={name} title={name}
loading="lazy"
/> />
<img <img
src={img.dark.url} src={img.dark.url}
@ -36,6 +37,7 @@ const { img, name, href } = Astro.props;
class="when-dark-theme" class="when-dark-theme"
alt={name} alt={name}
title={name} title={name}
loading="lazy"
/> />
</> </>
) : ( ) : (

View File

@ -68,7 +68,10 @@ if (updatedBy) {
<Fragment slot="header-aside"> <Fragment slot="header-aside">
{ {
thumbnail && ( thumbnail && (
<a id="thumbnail" href={getLocalizedUrl(`/images/${thumbnail.id}`)}> <a
id="thumbnail"
href={getLocalizedUrl(`/images/${thumbnail.id}`)}
style={`aspect-ratio: ${thumbnail.width}/${thumbnail.height};`}>
<img <img
src={thumbnail.url} src={thumbnail.url}
srcset={sizesToSrcset(thumbnail.sizes)} srcset={sizesToSrcset(thumbnail.sizes)}
@ -166,17 +169,18 @@ if (updatedBy) {
#thumbnail { #thumbnail {
border-radius: 16px; border-radius: 16px;
box-shadow: 0 5px 20px -10px var(--color-shadow);
overflow: hidden;
transition: 100ms scale; transition: 100ms scale;
display: block; display: block;
width: fit-content;
max-height: 80vh;
max-width: min(35rem, 100%);
& > img { & > img {
max-width: min(35rem, 100%); max-width: 100%;
max-height: 80vh;
width: auto;
height: auto; height: auto;
border-radius: 16px;
box-shadow: 0 5px 20px -10px var(--color-shadow);
} }
@media (max-width: 1280.5px) { @media (max-width: 1280.5px) {

View File

@ -40,7 +40,9 @@ const translation = getLocalizedMatch(translations);
<div> <div>
{ {
images.map(({ url, width, height, sizes }, index) => ( images.map(({ url, width, height, sizes }, index) => (
<a href={getLocalizedUrl(`/collectibles/${slug}/gallery/${index}`)}> <a
href={getLocalizedUrl(`/collectibles/${slug}/gallery/${index}`)}
style={`aspect-ratio: ${width}/${height};`}>
<img <img
src={url} src={url}
srcset={sizesToSrcset(sizes)} srcset={sizesToSrcset(sizes)}
@ -49,6 +51,7 @@ const translation = getLocalizedMatch(translations);
${(width / height) * 320}px`} ${(width / height) * 320}px`}
width={width} width={width}
height={height} height={height}
loading="lazy"
/> />
</a> </a>
)) ))
@ -71,18 +74,25 @@ const translation = getLocalizedMatch(translations);
gap: 1em; gap: 1em;
text-align: center; text-align: center;
place-items: center; place-items: center;
border-radius: 16px;
transition: 100ms scale; transition: 100ms scale;
&:hover { &:hover,
&:focus-visible {
scale: 104%; scale: 104%;
} }
&:focus-visible {
outline: 3px solid var(--color-base-1000);
outline-offset: unset;
}
max-height: 320px;
& > img { & > img {
max-height: 320px;
max-width: 100%; max-width: 100%;
height: auto; height: auto;
width: auto;
box-shadow: 0 5px 20px -10px var(--color-shadow); box-shadow: 0 5px 20px -10px var(--color-shadow);
border-radius: 16px; border-radius: 16px;
} }

View File

@ -155,9 +155,11 @@ if (price) {
<div id="images"> <div id="images">
{ {
thumbnail && ( thumbnail && (
<a href={getLocalizedUrl(`/images/${thumbnail.id}`)}> <a
id="thumbnail"
href={getLocalizedUrl(`/images/${thumbnail.id}`)}
style={`aspect-ratio: ${thumbnail.width}/${thumbnail.height};`}>
<img <img
id="thumbnail"
src={thumbnail.url} src={thumbnail.url}
srcset={sizesToSrcset(thumbnail.sizes)} srcset={sizesToSrcset(thumbnail.sizes)}
sizes={`(max-width: 550px) 90vw, 550px`} sizes={`(max-width: 550px) 90vw, 550px`}
@ -254,8 +256,7 @@ if (price) {
} }
} }
& a { & > #thumbnail {
width: fit-content;
transition: 100ms scale; transition: 100ms scale;
&:hover, &:hover,
@ -268,12 +269,13 @@ if (price) {
outline-offset: unset; outline-offset: unset;
} }
& > #thumbnail { max-height: 80vh;
max-width: min(35rem, 100%); max-width: min(35rem, 100%);
max-height: 80vh;
width: auto; & > img {
height: auto;
box-shadow: 0 5px 20px -10px var(--color-shadow); box-shadow: 0 5px 20px -10px var(--color-shadow);
max-width: 100%;
height: auto;
} }
} }

View File

@ -1,15 +1,17 @@
--- ---
import { getI18n } from "src/i18n/i18n"; import { getI18n } from "src/i18n/i18n";
import type { EndpointScanImage } from "src/shared/payload/payload-sdk"; import type { EndpointScanImage } from "src/shared/payload/payload-sdk";
import { sizesToSrcset } from "src/utils/img"; import { sizesForGridLayout, sizesToSrcset } from "src/utils/img";
interface Props { interface Props {
scan: EndpointScanImage; scan: EndpointScanImage;
collectibleSlug: string; collectibleSlug: string;
aspectRatio?: { width: number; height: number } | undefined;
} }
const { const {
scan: { url, index, width, height, sizes }, scan: { url, index, width, height, sizes },
aspectRatio,
collectibleSlug, collectibleSlug,
} = Astro.props; } = Astro.props;
@ -18,41 +20,51 @@ const { getLocalizedUrl, formatScanIndexShort } = await getI18n(Astro.locals.cur
{/* ------------------------------------------- HTML ------------------------------------------- */} {/* ------------------------------------------- HTML ------------------------------------------- */}
<a href={getLocalizedUrl(`/collectibles/${collectibleSlug}/scans/${index}`)}> <div style={aspectRatio ? `aspect-ratio:${aspectRatio.width}/${aspectRatio.height};` : undefined}>
<img <a
src={url} href={getLocalizedUrl(`/collectibles/${collectibleSlug}/scans/${index}`)}
srcset={sizesToSrcset(sizes)} style=`aspect-ratio:${width}/${height};`>
sizes={` <img
(max-width: ${(width / height) * 320}px) 90vw, src={url}
${(width / height) * 320}px`} srcset={sizesToSrcset(sizes)}
width={width} sizes={sizesForGridLayout(200, 1.15)}
height={height} width={width}
/> height={height}
loading="lazy"
/>
</a>
<p>{formatScanIndexShort(index)}</p> <p>{formatScanIndexShort(index)}</p>
</a> </div>
{/* ------------------------------------------- CSS -------------------------------------------- */} {/* ------------------------------------------- CSS -------------------------------------------- */}
<style> <style>
a { div {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 1em; gap: 1em;
text-align: center; text-align: center;
place-items: center; place-items: center;
}
a {
transition: 100ms scale; transition: 100ms scale;
&:hover { &:hover,
&:focus-visible {
scale: 104%; scale: 104%;
} }
&:focus-visible {
outline: 3px solid var(--color-base-1000);
outline-offset: unset;
}
max-height: 100%;
& > img { & > img {
max-height: 320px;
max-width: 100%; max-width: 100%;
height: auto; height: auto;
width: auto;
aspect-ratio: auto 21/29.7;
box-shadow: 0 5px 20px -10px var(--color-shadow); box-shadow: 0 5px 20px -10px var(--color-shadow);
} }
} }

View File

@ -63,11 +63,29 @@ const hasOutsideObi = obi ? Object.keys(obi).some((value) => !value.includes("in
<section> <section>
<h2>{t("collectibles.scans.cover")}</h2> <h2>{t("collectibles.scans.cover")}</h2>
<div> <div>
{cover.flapFront && <ScanPreview collectibleSlug={slug} scan={cover.flapFront} />} {cover.flapFront && (
{cover.front && <ScanPreview collectibleSlug={slug} scan={cover.front} />} <ScanPreview
{cover.spine && <ScanPreview collectibleSlug={slug} scan={cover.spine} />} aspectRatio={cover.front}
{cover.back && <ScanPreview collectibleSlug={slug} scan={cover.back} />} collectibleSlug={slug}
{cover.flapBack && <ScanPreview collectibleSlug={slug} scan={cover.flapBack} />} scan={cover.flapFront}
/>
)}
{cover.front && (
<ScanPreview aspectRatio={cover.front} collectibleSlug={slug} scan={cover.front} />
)}
{cover.spine && (
<ScanPreview aspectRatio={cover.front} collectibleSlug={slug} scan={cover.spine} />
)}
{cover.back && (
<ScanPreview aspectRatio={cover.front} collectibleSlug={slug} scan={cover.back} />
)}
{cover.flapBack && (
<ScanPreview
aspectRatio={cover.front}
collectibleSlug={slug}
scan={cover.flapBack}
/>
)}
</div> </div>
</section> </section>
)} )}
@ -76,12 +94,32 @@ const hasOutsideObi = obi ? Object.keys(obi).some((value) => !value.includes("in
<h2>{t("collectibles.scans.coverInside")}</h2> <h2>{t("collectibles.scans.coverInside")}</h2>
<div> <div>
{cover.insideFlapFront && ( {cover.insideFlapFront && (
<ScanPreview collectibleSlug={slug} scan={cover.insideFlapFront} /> <ScanPreview
aspectRatio={cover.front}
collectibleSlug={slug}
scan={cover.insideFlapFront}
/>
)}
{cover.insideFront && (
<ScanPreview
aspectRatio={cover.front}
collectibleSlug={slug}
scan={cover.insideFront}
/>
)}
{cover.insideBack && (
<ScanPreview
aspectRatio={cover.front}
collectibleSlug={slug}
scan={cover.insideBack}
/>
)} )}
{cover.insideFront && <ScanPreview collectibleSlug={slug} scan={cover.insideFront} />}
{cover.insideBack && <ScanPreview collectibleSlug={slug} scan={cover.insideBack} />}
{cover.insideFlapBack && ( {cover.insideFlapBack && (
<ScanPreview collectibleSlug={slug} scan={cover.insideFlapBack} /> <ScanPreview
aspectRatio={cover.front}
collectibleSlug={slug}
scan={cover.insideFlapBack}
/>
)} )}
</div> </div>
</section> </section>
@ -99,13 +137,39 @@ const hasOutsideObi = obi ? Object.keys(obi).some((value) => !value.includes("in
<p class="prose">{t("collectibles.scans.dustjacket.description")}</p> <p class="prose">{t("collectibles.scans.dustjacket.description")}</p>
<div> <div>
{dustjacket.flapFront && ( {dustjacket.flapFront && (
<ScanPreview collectibleSlug={slug} scan={dustjacket.flapFront} /> <ScanPreview
aspectRatio={dustjacket.front}
collectibleSlug={slug}
scan={dustjacket.flapFront}
/>
)}
{dustjacket.front && (
<ScanPreview
aspectRatio={dustjacket.front}
collectibleSlug={slug}
scan={dustjacket.front}
/>
)}
{dustjacket.spine && (
<ScanPreview
aspectRatio={dustjacket.front}
collectibleSlug={slug}
scan={dustjacket.spine}
/>
)}
{dustjacket.back && (
<ScanPreview
aspectRatio={dustjacket.front}
collectibleSlug={slug}
scan={dustjacket.back}
/>
)} )}
{dustjacket.front && <ScanPreview collectibleSlug={slug} scan={dustjacket.front} />}
{dustjacket.spine && <ScanPreview collectibleSlug={slug} scan={dustjacket.spine} />}
{dustjacket.back && <ScanPreview collectibleSlug={slug} scan={dustjacket.back} />}
{dustjacket.flapBack && ( {dustjacket.flapBack && (
<ScanPreview collectibleSlug={slug} scan={dustjacket.flapBack} /> <ScanPreview
aspectRatio={dustjacket.front}
collectibleSlug={slug}
scan={dustjacket.flapBack}
/>
)} )}
</div> </div>
</section> </section>
@ -115,19 +179,39 @@ const hasOutsideObi = obi ? Object.keys(obi).some((value) => !value.includes("in
<h2>{t("collectibles.scans.dustjacketInside")}</h2> <h2>{t("collectibles.scans.dustjacketInside")}</h2>
<div> <div>
{dustjacket.insideFlapFront && ( {dustjacket.insideFlapFront && (
<ScanPreview collectibleSlug={slug} scan={dustjacket.insideFlapFront} /> <ScanPreview
aspectRatio={dustjacket.front}
collectibleSlug={slug}
scan={dustjacket.insideFlapFront}
/>
)} )}
{dustjacket.insideFront && ( {dustjacket.insideFront && (
<ScanPreview collectibleSlug={slug} scan={dustjacket.insideFront} /> <ScanPreview
aspectRatio={dustjacket.front}
collectibleSlug={slug}
scan={dustjacket.insideFront}
/>
)} )}
{dustjacket.insideSpine && ( {dustjacket.insideSpine && (
<ScanPreview collectibleSlug={slug} scan={dustjacket.insideSpine} /> <ScanPreview
aspectRatio={dustjacket.front}
collectibleSlug={slug}
scan={dustjacket.insideSpine}
/>
)} )}
{dustjacket.insideBack && ( {dustjacket.insideBack && (
<ScanPreview collectibleSlug={slug} scan={dustjacket.insideBack} /> <ScanPreview
aspectRatio={dustjacket.front}
collectibleSlug={slug}
scan={dustjacket.insideBack}
/>
)} )}
{dustjacket.insideFlapBack && ( {dustjacket.insideFlapBack && (
<ScanPreview collectibleSlug={slug} scan={dustjacket.insideFlapBack} /> <ScanPreview
aspectRatio={dustjacket.front}
collectibleSlug={slug}
scan={dustjacket.insideFlapBack}
/>
)} )}
</div> </div>
</section> </section>
@ -144,11 +228,21 @@ const hasOutsideObi = obi ? Object.keys(obi).some((value) => !value.includes("in
<h2>{t("collectibles.scans.obi")}</h2> <h2>{t("collectibles.scans.obi")}</h2>
<p class="prose">{t("collectibles.scans.obi.description")}</p> <p class="prose">{t("collectibles.scans.obi.description")}</p>
<div> <div>
{obi.flapFront && <ScanPreview collectibleSlug={slug} scan={obi.flapFront} />} {obi.flapFront && (
{obi.front && <ScanPreview collectibleSlug={slug} scan={obi.front} />} <ScanPreview aspectRatio={obi.front} collectibleSlug={slug} scan={obi.flapFront} />
{obi.spine && <ScanPreview collectibleSlug={slug} scan={obi.spine} />} )}
{obi.back && <ScanPreview collectibleSlug={slug} scan={obi.back} />} {obi.front && (
{obi.flapBack && <ScanPreview collectibleSlug={slug} scan={obi.flapBack} />} <ScanPreview aspectRatio={obi.front} collectibleSlug={slug} scan={obi.front} />
)}
{obi.spine && (
<ScanPreview aspectRatio={obi.front} collectibleSlug={slug} scan={obi.spine} />
)}
{obi.back && (
<ScanPreview aspectRatio={obi.front} collectibleSlug={slug} scan={obi.back} />
)}
{obi.flapBack && (
<ScanPreview aspectRatio={obi.front} collectibleSlug={slug} scan={obi.flapBack} />
)}
</div> </div>
</section> </section>
)} )}
@ -157,13 +251,35 @@ const hasOutsideObi = obi ? Object.keys(obi).some((value) => !value.includes("in
<h2>{t("collectibles.scans.obiInside")}</h2> <h2>{t("collectibles.scans.obiInside")}</h2>
<div> <div>
{obi.insideFlapFront && ( {obi.insideFlapFront && (
<ScanPreview collectibleSlug={slug} scan={obi.insideFlapFront} /> <ScanPreview
aspectRatio={obi.front}
collectibleSlug={slug}
scan={obi.insideFlapFront}
/>
)}
{obi.insideFront && (
<ScanPreview
aspectRatio={obi.front}
collectibleSlug={slug}
scan={obi.insideFront}
/>
)}
{obi.insideSpine && (
<ScanPreview
aspectRatio={obi.front}
collectibleSlug={slug}
scan={obi.insideSpine}
/>
)}
{obi.insideBack && (
<ScanPreview aspectRatio={obi.front} collectibleSlug={slug} scan={obi.insideBack} />
)} )}
{obi.insideFront && <ScanPreview collectibleSlug={slug} scan={obi.insideFront} />}
{obi.insideSpine && <ScanPreview collectibleSlug={slug} scan={obi.insideSpine} />}
{obi.insideBack && <ScanPreview collectibleSlug={slug} scan={obi.insideBack} />}
{obi.insideFlapBack && ( {obi.insideFlapBack && (
<ScanPreview collectibleSlug={slug} scan={obi.insideFlapBack} /> <ScanPreview
aspectRatio={obi.front}
collectibleSlug={slug}
scan={obi.insideFlapBack}
/>
)} )}
</div> </div>
</section> </section>
@ -178,7 +294,7 @@ const hasOutsideObi = obi ? Object.keys(obi).some((value) => !value.includes("in
<h2>{t("collectibles.scans.pages")}</h2> <h2>{t("collectibles.scans.pages")}</h2>
<div> <div>
{pages.map((image) => ( {pages.map((image) => (
<ScanPreview collectibleSlug={slug} scan={image} /> <ScanPreview aspectRatio={pages[0]} collectibleSlug={slug} scan={image} />
))} ))}
</div> </div>
</section> </section>
@ -204,9 +320,10 @@ const hasOutsideObi = obi ? Object.keys(obi).some((value) => !value.includes("in
& > div { & > div {
margin-top: 1em; margin-top: 1em;
display: flex; display: grid;
gap: 2em; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
flex-wrap: wrap; gap: 32px 16px;
align-items: end;
} }
} }
</style> </style>

View File

@ -17,7 +17,7 @@ if (recorder instanceof Response) {
return recorder; return recorder;
} }
const { t, getLocalizedMatch } = await getI18n(Astro.locals.currentLocale); const { t, getLocalizedMatch, getLocalizedUrl } = await getI18n(Astro.locals.currentLocale);
const { username, languages, avatar, translations } = recorder; const { username, languages, avatar, translations } = recorder;
@ -47,13 +47,17 @@ if (languages.length > 0) {
{ {
avatar && ( avatar && (
<Fragment slot="header-aside"> <Fragment slot="header-aside">
<img <a
src={avatar.url} href={getLocalizedUrl(`/images/${avatar.id}`)}
srcset={sizesToSrcset(avatar.sizes)} style={`aspect-ratio: ${avatar.width}/${avatar.height};`}>
sizes={`(max-width: 550px) 90vw, 550px`} <img
width={avatar.width} src={avatar.url}
height={avatar.height} srcset={sizesToSrcset(avatar.sizes)}
/> sizes={`(max-width: 550px) 90vw, 550px`}
width={avatar.width}
height={avatar.height}
/>
</a>
</Fragment> </Fragment>
) )
} }
@ -80,16 +84,34 @@ if (languages.length > 0) {
{/* ------------------------------------------- CSS -------------------------------------------- */} {/* ------------------------------------------- CSS -------------------------------------------- */}
<style> <style>
img { a {
max-width: min(35rem, 100%); display: block;
max-height: 50vh;
width: auto;
height: auto;
margin-bottom: 2em; margin-bottom: 2em;
border-radius: 16px;
transition: 100ms scale;
@media (max-width: 1280.5px) { @media (max-width: 1280.5px) {
margin-top: 2em; margin-top: 2em;
} }
&:hover,
&:focus-visible {
scale: 102%;
}
&:focus-visible {
outline: 3px solid var(--color-base-1000);
outline-offset: unset;
}
max-width: min(35rem, 100%);
max-height: 50vh;
& > img {
border-radius: 16px;
max-width: 100%;
height: auto;
}
} }
#info { #info {