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
- More data caching between the CMS and Astro
- [Folders] Support for nameless section
- [Scripts] Can't run the scripts using node (ts-node?)
- [Scans] Adapt size of obi based on cover/dustjacket

View File

@ -35,6 +35,7 @@ const style = `
sizes="100vw"
width={width}
height={height}
loading="lazy"
class="when-no-print when-js"
/>
<img
@ -44,6 +45,7 @@ const style = `
sizes="100vw"
width={width}
height={height}
loading="lazy"
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
src={url}
srcset={sizesToSrcset(sizes)}
@ -132,11 +132,45 @@ const hasNavigation = previousImageHref || nextImageHref;
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-width: 100%;
height: auto;
width: auto;
& > img {
max-width: 100%;
height: auto;
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -155,9 +155,11 @@ if (price) {
<div id="images">
{
thumbnail && (
<a href={getLocalizedUrl(`/images/${thumbnail.id}`)}>
<a
id="thumbnail"
href={getLocalizedUrl(`/images/${thumbnail.id}`)}
style={`aspect-ratio: ${thumbnail.width}/${thumbnail.height};`}>
<img
id="thumbnail"
src={thumbnail.url}
srcset={sizesToSrcset(thumbnail.sizes)}
sizes={`(max-width: 550px) 90vw, 550px`}
@ -254,8 +256,7 @@ if (price) {
}
}
& a {
width: fit-content;
& > #thumbnail {
transition: 100ms scale;
&:hover,
@ -268,12 +269,13 @@ if (price) {
outline-offset: unset;
}
& > #thumbnail {
max-width: min(35rem, 100%);
max-height: 80vh;
width: auto;
height: auto;
max-height: 80vh;
max-width: min(35rem, 100%);
& > img {
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 type { EndpointScanImage } from "src/shared/payload/payload-sdk";
import { sizesToSrcset } from "src/utils/img";
import { sizesForGridLayout, sizesToSrcset } from "src/utils/img";
interface Props {
scan: EndpointScanImage;
collectibleSlug: string;
aspectRatio?: { width: number; height: number } | undefined;
}
const {
scan: { url, index, width, height, sizes },
aspectRatio,
collectibleSlug,
} = Astro.props;
@ -18,41 +20,51 @@ const { getLocalizedUrl, formatScanIndexShort } = await getI18n(Astro.locals.cur
{/* ------------------------------------------- HTML ------------------------------------------- */}
<a href={getLocalizedUrl(`/collectibles/${collectibleSlug}/scans/${index}`)}>
<img
src={url}
srcset={sizesToSrcset(sizes)}
sizes={`
(max-width: ${(width / height) * 320}px) 90vw,
${(width / height) * 320}px`}
width={width}
height={height}
/>
<div style={aspectRatio ? `aspect-ratio:${aspectRatio.width}/${aspectRatio.height};` : undefined}>
<a
href={getLocalizedUrl(`/collectibles/${collectibleSlug}/scans/${index}`)}
style=`aspect-ratio:${width}/${height};`>
<img
src={url}
srcset={sizesToSrcset(sizes)}
sizes={sizesForGridLayout(200, 1.15)}
width={width}
height={height}
loading="lazy"
/>
</a>
<p>{formatScanIndexShort(index)}</p>
</a>
</div>
{/* ------------------------------------------- CSS -------------------------------------------- */}
<style>
a {
div {
display: flex;
flex-direction: column;
gap: 1em;
text-align: center;
place-items: center;
}
a {
transition: 100ms scale;
&:hover {
&:hover,
&:focus-visible {
scale: 104%;
}
&:focus-visible {
outline: 3px solid var(--color-base-1000);
outline-offset: unset;
}
max-height: 100%;
& > img {
max-height: 320px;
max-width: 100%;
height: auto;
width: auto;
aspect-ratio: auto 21/29.7;
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>
<h2>{t("collectibles.scans.cover")}</h2>
<div>
{cover.flapFront && <ScanPreview collectibleSlug={slug} scan={cover.flapFront} />}
{cover.front && <ScanPreview collectibleSlug={slug} scan={cover.front} />}
{cover.spine && <ScanPreview collectibleSlug={slug} scan={cover.spine} />}
{cover.back && <ScanPreview collectibleSlug={slug} scan={cover.back} />}
{cover.flapBack && <ScanPreview collectibleSlug={slug} scan={cover.flapBack} />}
{cover.flapFront && (
<ScanPreview
aspectRatio={cover.front}
collectibleSlug={slug}
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>
</section>
)}
@ -76,12 +94,32 @@ const hasOutsideObi = obi ? Object.keys(obi).some((value) => !value.includes("in
<h2>{t("collectibles.scans.coverInside")}</h2>
<div>
{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 && (
<ScanPreview collectibleSlug={slug} scan={cover.insideFlapBack} />
<ScanPreview
aspectRatio={cover.front}
collectibleSlug={slug}
scan={cover.insideFlapBack}
/>
)}
</div>
</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>
<div>
{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 && (
<ScanPreview collectibleSlug={slug} scan={dustjacket.flapBack} />
<ScanPreview
aspectRatio={dustjacket.front}
collectibleSlug={slug}
scan={dustjacket.flapBack}
/>
)}
</div>
</section>
@ -115,19 +179,39 @@ const hasOutsideObi = obi ? Object.keys(obi).some((value) => !value.includes("in
<h2>{t("collectibles.scans.dustjacketInside")}</h2>
<div>
{dustjacket.insideFlapFront && (
<ScanPreview collectibleSlug={slug} scan={dustjacket.insideFlapFront} />
<ScanPreview
aspectRatio={dustjacket.front}
collectibleSlug={slug}
scan={dustjacket.insideFlapFront}
/>
)}
{dustjacket.insideFront && (
<ScanPreview collectibleSlug={slug} scan={dustjacket.insideFront} />
<ScanPreview
aspectRatio={dustjacket.front}
collectibleSlug={slug}
scan={dustjacket.insideFront}
/>
)}
{dustjacket.insideSpine && (
<ScanPreview collectibleSlug={slug} scan={dustjacket.insideSpine} />
<ScanPreview
aspectRatio={dustjacket.front}
collectibleSlug={slug}
scan={dustjacket.insideSpine}
/>
)}
{dustjacket.insideBack && (
<ScanPreview collectibleSlug={slug} scan={dustjacket.insideBack} />
<ScanPreview
aspectRatio={dustjacket.front}
collectibleSlug={slug}
scan={dustjacket.insideBack}
/>
)}
{dustjacket.insideFlapBack && (
<ScanPreview collectibleSlug={slug} scan={dustjacket.insideFlapBack} />
<ScanPreview
aspectRatio={dustjacket.front}
collectibleSlug={slug}
scan={dustjacket.insideFlapBack}
/>
)}
</div>
</section>
@ -144,11 +228,21 @@ const hasOutsideObi = obi ? Object.keys(obi).some((value) => !value.includes("in
<h2>{t("collectibles.scans.obi")}</h2>
<p class="prose">{t("collectibles.scans.obi.description")}</p>
<div>
{obi.flapFront && <ScanPreview collectibleSlug={slug} scan={obi.flapFront} />}
{obi.front && <ScanPreview collectibleSlug={slug} scan={obi.front} />}
{obi.spine && <ScanPreview collectibleSlug={slug} scan={obi.spine} />}
{obi.back && <ScanPreview collectibleSlug={slug} scan={obi.back} />}
{obi.flapBack && <ScanPreview collectibleSlug={slug} scan={obi.flapBack} />}
{obi.flapFront && (
<ScanPreview aspectRatio={obi.front} collectibleSlug={slug} scan={obi.flapFront} />
)}
{obi.front && (
<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>
</section>
)}
@ -157,13 +251,35 @@ const hasOutsideObi = obi ? Object.keys(obi).some((value) => !value.includes("in
<h2>{t("collectibles.scans.obiInside")}</h2>
<div>
{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 && (
<ScanPreview collectibleSlug={slug} scan={obi.insideFlapBack} />
<ScanPreview
aspectRatio={obi.front}
collectibleSlug={slug}
scan={obi.insideFlapBack}
/>
)}
</div>
</section>
@ -178,7 +294,7 @@ const hasOutsideObi = obi ? Object.keys(obi).some((value) => !value.includes("in
<h2>{t("collectibles.scans.pages")}</h2>
<div>
{pages.map((image) => (
<ScanPreview collectibleSlug={slug} scan={image} />
<ScanPreview aspectRatio={pages[0]} collectibleSlug={slug} scan={image} />
))}
</div>
</section>
@ -204,9 +320,10 @@ const hasOutsideObi = obi ? Object.keys(obi).some((value) => !value.includes("in
& > div {
margin-top: 1em;
display: flex;
gap: 2em;
flex-wrap: wrap;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 32px 16px;
align-items: end;
}
}
</style>

View File

@ -17,7 +17,7 @@ if (recorder instanceof Response) {
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;
@ -47,13 +47,17 @@ if (languages.length > 0) {
{
avatar && (
<Fragment slot="header-aside">
<img
src={avatar.url}
srcset={sizesToSrcset(avatar.sizes)}
sizes={`(max-width: 550px) 90vw, 550px`}
width={avatar.width}
height={avatar.height}
/>
<a
href={getLocalizedUrl(`/images/${avatar.id}`)}
style={`aspect-ratio: ${avatar.width}/${avatar.height};`}>
<img
src={avatar.url}
srcset={sizesToSrcset(avatar.sizes)}
sizes={`(max-width: 550px) 90vw, 550px`}
width={avatar.width}
height={avatar.height}
/>
</a>
</Fragment>
)
}
@ -80,16 +84,34 @@ if (languages.length > 0) {
{/* ------------------------------------------- CSS -------------------------------------------- */}
<style>
img {
max-width: min(35rem, 100%);
max-height: 50vh;
width: auto;
height: auto;
a {
display: block;
margin-bottom: 2em;
border-radius: 16px;
transition: 100ms scale;
@media (max-width: 1280.5px) {
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 {