diff --git a/Dockerfile b/Dockerfile index fda6e34..606e1a2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,29 @@ FROM node:22-alpine AS builder WORKDIR /app +# Install build dependencies for sharp and libvips with HEIF support +# See: https://sharp.pixelplumbing.com/install#alpine +RUN apk add --no-cache \ + build-base \ + pkgconf \ + # libvips runtime dependencies + libjpeg-turbo-dev \ + libpng-dev \ + libwebp-dev \ + tiff-dev \ + libexif-dev \ + lcms2-dev \ + glib-dev \ + # libvips itself and its HEIF support + vips-dev \ + libheif-dev \ + # Codecs for HEIF + libde265-dev \ + x265-dev + COPY package*.json ./ +# Tell sharp to use the system-wide libvips we just installed with HEIF support +ENV SHARP_IGNORE_GLOBAL_LIBVIPS=1 RUN npm ci COPY . . @@ -25,9 +47,11 @@ COPY --from=builder /app/.next/standalone ./ COPY --from=builder /app/.next/static ./.next/static COPY --from=builder /app/public ./public -# Copy sharp native binaries (needed for HEIC→JPEG conversion) -COPY --from=builder /app/node_modules/sharp ./node_modules/sharp -COPY --from=builder /app/node_modules/@img ./node_modules/@img +# Sharp native binaries are no longer copied directly, +# as sharp is built against system libvips and is part of node_modules copied with the standalone app. +# If sharp is needed outside standalone, node_modules would need to be copied. +# COPY --from=builder /app/node_modules/sharp ./node_modules/sharp +# COPY --from=builder /app/node_modules/@img ./node_modules/@img RUN mkdir -p /app/data/uploads/photos /app/data/uploads/videos /app/data/uploads/music \ && chown -R nextjs:nodejs /app/data diff --git a/src/components/TimelineSection.tsx b/src/components/TimelineSection.tsx index 0ab14e8..7f6b4c5 100644 --- a/src/components/TimelineSection.tsx +++ b/src/components/TimelineSection.tsx @@ -58,7 +58,7 @@ export default function TimelineSection({ entries }: TimelineSectionProps) {
{/* Entries */} -
+
{entries.map((entry, index) => { const isLeft = index % 2 === 0 const isBirth = index === birthIndex @@ -75,8 +75,8 @@ export default function TimelineSection({ entries }: TimelineSectionProps) { transition={{ duration: 0.6, delay: 0.1 }} className={`relative flex items-start ${ isLeft - ? 'pl-10 sm:pl-0 sm:pr-[52%]' - : 'pl-10 sm:pl-[52%]' + ? 'pl-10 sm:pl-0 sm:pr-[50%]' + : 'pl-10 sm:pl-[50%]' }`} > {/* Content Card */} @@ -97,7 +97,7 @@ export default function TimelineSection({ entries }: TimelineSectionProps) { key={i} src={`/api/files/${filename.trim()}?w=600`} alt="" - className="w-full max-h-40 object-cover rounded-lg" + className="w-full max-h-60 object-cover rounded-lg" loading="lazy" /> ))} @@ -185,7 +185,7 @@ export default function TimelineSection({ entries }: TimelineSectionProps) { animate={{ scale: 1, y: 0 }} exit={{ scale: 0.9, y: 20 }} onClick={(e) => e.stopPropagation()} - className="bg-cream rounded-2xl shadow-2xl max-w-2xl w-full max-h-[85vh] overflow-y-auto" + className="bg-cream rounded-2xl shadow-2xl max-w-4xl w-full max-h-[85vh] overflow-y-auto" > {/* Header */}
@@ -220,7 +220,7 @@ export default function TimelineSection({ entries }: TimelineSectionProps) { {selectedEntry.media_filenames.split(',').map((filename, i) => (