+ {/* Screen-only controls float over the sheet's top margin so the
+ sheet's top edge stays level with both sidebars. */}
+
+ {pageLabel(page) ? : }
+
+ setPageShape(page, shape)}
+ />
+
+
))}
@@ -145,6 +149,19 @@ export function A4Preview() {
);
}
+function PageBadge({ label }: { label: string }) {
+ return (
+
+ {label}
+
+ );
+}
+
+/**
+ * Collapses to a single glassy icon of the current shape so it stays out of the
+ * way of the sheet's margin guide; reveals the full selector on hover or focus
+ * (focus covers touch, where there's no hover).
+ */
function PageShapeToggle({
value,
onChange,
@@ -152,31 +169,51 @@ function PageShapeToggle({
value: Orientation;
onChange: (shape: Orientation) => void;
}) {
+ const CurrentIcon = SHAPE_ICONS[value];
+ const glass =
+ "rounded-md bg-white/40 ring-1 ring-black/5 backdrop-blur-md";
return (
-
- {FRAME_SHAPES.map(({ id, label }) => {
- const Icon = SHAPE_ICONS[id];
- const active = value === id;
- return (
-
-
-
-
- {label} frames on this page
-
- );
- })}
+
+
+
+ {FRAME_SHAPES.map(({ id, label }) => {
+ const Icon = SHAPE_ICONS[id];
+ const active = value === id;
+ return (
+
+
+
+
+ {label} frames on this page
+
+ );
+ })}
+
);
}
diff --git a/apps/web/src/components/options-panel.tsx b/apps/web/src/components/options-panel.tsx
index ef9c6d3..7e28c84 100644
--- a/apps/web/src/components/options-panel.tsx
+++ b/apps/web/src/components/options-panel.tsx
@@ -1,5 +1,5 @@
import { type ReactNode, useState } from "react";
-import { Minus, Plus } from "lucide-react";
+import { FileDown, Minus, Plus } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
@@ -335,8 +335,9 @@ export function OptionsPanel() {
diff --git a/apps/web/src/components/photo-sidebar.tsx b/apps/web/src/components/photo-sidebar.tsx
index 379aaf4..0902ec6 100644
--- a/apps/web/src/components/photo-sidebar.tsx
+++ b/apps/web/src/components/photo-sidebar.tsx
@@ -10,12 +10,15 @@ const ACCEPT =
'image/jpeg,image/png,image/webp,image/heic,image/heif,.jpg,.jpeg,.png,.webp,.heic,.heif'
/**
- * Left rail: an "Add photos" button on top, the reorderable strip below.
+ * Left rail: the reorderable photo strip, with an "Add photos" button pinned
+ * to the bottom. The button only appears once there's at least one photo —
+ * before that, the centre empty-state hero carries the add action.
* Dragging a file anywhere over the app reveals a square drop pad here, with
* the strip blurred behind it — and a drop is accepted wherever it lands.
*/
export function PhotoSidebar() {
const addFiles = usePhotoStore((state) => state.addFiles)
+ const hasPhotos = usePhotoStore((state) => state.photos.length > 0)
const inputRef = useRef(null)
const onFiles = useCallback((files: File[]) => addFiles(files), [addFiles])
@@ -36,17 +39,21 @@ export function PhotoSidebar() {
}}
/>
-
-
+ {hasPhotos && (
+