Commit Graph

89 Commits

Author SHA1 Message Date
3dtours 500068e63e web: mark SAVE PHOTO PRO and keep MY PHOTOS for the accounts that have one
Saving into the account's own folder has always been the account's act —
the button opened the way in and the API answers an unproven address with
a 403 — but nothing on the button said so, so it read as a button that
quietly did nothing. It now wears the same PRO marker the chips do, and
only while the folder is not the visitor's.

MY PHOTOS is that folder's listing, so the tab is only offered once an
account can hold one. A guest loses the tab entirely rather than opening
it on an empty folder that could never fill; an account that has signed
up but not proven its address keeps the tab, and the tab keeps offering
the way to prove it.
2026-09-22 21:46:05 +07:00
3dtours b9ac7746aa web: gate the newest film sims and the HSL mixer behind PRO
The last three PHOTO STYLE looks (B&W HIGH CONTRAST, LC STREETLIFE
CLASSIC and LC STREETLIFE VIVID) and the whole mixer now belong to the
account, the way PRO frames and the geotag already do: the chip wears
the PRO badge, a guest who picks it is shown the way in, and the look
stays off. The HSL tab keeps its place in the rail but offers the one
PRO chip while locked, so the tab itself is not a dead end; a look that
arrives without the chips — an imported .recipe, or a photo saved
before the gate — is still caught where the gate bites, at export.

STRAIGHTEN's scale turns with the wheel, one degree a notch, because
the ruler is where the angle is being judged and reaching for a slider
elsewhere loses the thread. The listener is native and stops the notch
before the stage sees it, so the photo does not zoom under the pointer.
The scale gives up its opaque card, its blur and its shadow: the frame
it is levelling has to stay readable through it, so legibility comes
from a text shadow on the heading and a drop shadow on the graduations
instead.
2026-09-22 21:33:11 +07:00
3dtours f201deee46 web: export a big photo without inventing pixels it already has
The export menu measured the photo off the 1600px preview copy, so a 2400px
photo was believed to be 1600px across: the hint named the wrong size, the
model was asked to upscale a photo that already had more pixels than the
target, and a guest's 2048 ceiling was skipped because 1600 never crossed it.
A committed crop made it worse — the crop's longest edge was taken from the
wider side of the crop rect rather than the side the frame actually keeps, so
a 2400x1800 photo with the default 0.8 frame was called 1280px and ran the
model over 80 tiles (158.7s) to reach 2K.

The photo's own dimensions are now read off the original bytes, and the crop's
long edge is the same axis-aware fraction the stage already uses. The export
asks the model only when the photo itself is short of the requested size, or
when the crop would have to be stretched past 1.5x to get there; otherwise it
resamples — down, or a hair up to make up for the crop — which is what a photo
that already holds the pixels deserves.

Measured, wasm path, 2400x1800: no crop at 2K went 2.7s/2400px (wrong size) to
3.4s/2048px, the default 0.8 crop went 158.7s/80 tiles to 3.5s/no model, and a
1:1 crop went 2.5s/1800px to 4.0s/2048px. A 1200x900 photo cropped to 1:1 and
exported at 2K still runs the model (2048 from a 900px crop, 40.3s), and the
superres suite is unchanged: 640x480 to 2K/4K/custom still comes out exact,
with the model's 16.6 edge energy against bilinear's 4.8.
2026-09-22 20:43:46 +07:00
3dtours 1d4c6b1d66 web: upscale on a thread pool instead of one core
The super-resolution export ran single-threaded because the site was not
cross-origin isolated and the runtime had no SharedArrayBuffer to spread a tile
over. nginx now sends COOP and COEP — on the document, and on the script
responses a nested worker fetches, which Chromium checks the same way and blocks
as `coep-frame-resource-needs-coep-header` without them — and the loader asks
for `min(8, hardwareConcurrency)` threads whenever the page is isolated, falling
back to one if the headers ever go missing. A worker script is also why the
landing's QR image needed `crossOrigin`: COEP refuses a cross-origin image that
did not opt in with CORS.

The unpack was the other half. Each tile was clamped a channel at a time and
painted whole, padded ring and all; it now writes straight into the
Uint8ClampedArray, which clamps and rounds on assignment, and skips the ring
rather than drawing it and clipping it away.

640x480 to 4096: 39.0s to 16.2s. 1000x750 to 4096: 89.6s to 30.9s. One 256px
tile through the model: 6.8s to 1.8s. Measured on the wasm path — the test
browser has no GPU adapter — so a WebGPU export, still per-tile inference, keeps
its own times.
2026-09-22 20:30:02 +07:00
3dtours c403dd04c8 web: add the B&W HIGH CONTRAST film sim to the PRESETS rail
The chip lands after ACRIPES and is a mono stock of its own, so it gets its own
baseFilter ('mono-high-contrast') rather than borrowing Acros': the PHOTO STYLE
chips are keyed by baseFilter, and the two greys must sit side by side.

The look is the B&W MIX plus a push at both ends. The mix rides the matrix — a
non-BT.709 row set (0.38/0.56/0.06, identical rows, sum 1.00) so a red roof
reads bright, a blue sky deep, and the separation is contrast before any curve.
The push rides FILM_TONE (shadow -0.32, highlight +0.26) so the ends move
without touching the midtones, and the midtone slope is SIM_CONTRAST_BIAS (4
contrast units) next to the sim's own exposure bias. The knobs stay at neutral:
a sim is colour and tone only.

Both B&W stocks being mono is now asked once, through isMonochromeBase, so the
colour-only stages (saturation, white balance, R/B fine-tune, chrome, hue
mixer) and the MONO strip label can never half-apply to one of them.
2026-09-22 17:56:00 +07:00
3dtours 80512d11b5 web: print the shared frame's own labels on the QR recipe card
The card carried studio copy — SUNSET GLOW, a made-up warmth/grain/bloom line
and a creator handle borrowed from a testimonial — no matter which still the
arrows landed on. Stepping the frame changed the picture and the code but left
the text behind, so the card described a photo it was not showing.

The card now reads the frame's own labels: the tagline over the still and the
title and ISO/grain line under it are the ones the uploader saved with the
photo, the same three the film strip prints. The fabricated creator/imports
line goes with them, since nothing behind it was real.
2026-09-22 17:22:54 +07:00
3dtours 2ee49cfd41 web: keep the film strip seamless on a wide window
The marquee loops by translating the track by half its width, so the first half
has to be at least as wide as the window. A short reel — seven stills, about
1780px — covered a 1440px window but not a 1920px or 2560px one: the strip ran
out of frames before the loop restarted, and the band on the right stayed blank
until the next pass drifted in.

The track now measures one frame's pitch and repeats the reel until a half
covers the window, re-measuring on resize. One still in the strip is repeated
enough times to loop cleanly on its own; a reel already wide enough is left at
a single copy, so nothing is duplicated without cause.
2026-09-22 17:11:12 +07:00
3dtours e1f6943f8e web: step the creator and QR previews through their tagged stills
The landing's tester preview has had a ‹ › pair for a while; the custom recipe
creator and the QR recipe card still showed one arbitrary frame from whatever
the curator tagged for them. Both now cycle their own slot the same way, from a
random start so the page does not look identical on every load, wrapping at
both ends.

The QR card's payload is not decoration: it is the link of the frame on screen,
so the code under it is redrawn from the same frame the arrows land on. A slot
holding fewer than two stills gets no arrows, since there is nowhere to step.

The tester's arrows move to the shared FrameArrows component, which is what the
two new pairs use — one implementation, three slots.
2026-09-22 17:03:46 +07:00
3dtours bb51b83399 web: the hero stat labels wear the accent as gradient text
COLOR RECIPES, DOWNLOADS and STORE RATING were flat grey under their numbers.
They now sweep from the theme's own accent to the film red and are cut out of
that gradient, the same treatment the headline above them gets. Because the
sweep starts on var(--accent), the colour group in the Theme menu still
retints it; the light theme starts from the darker ink accent so the labels
keep their contrast on white.
2026-09-22 16:47:35 +07:00
3dtours cfe8512636 web: the landing draws only the stills the studio uploaded
Six bundled sample negatives stood behind eight built-in looks, and they were
the only frames on the page nobody had uploaded. They are gone, along with the
REEL table and the SAMPLE() helper that pointed at them.

The film strip is now exactly the photos the curator put in the strip slot,
repeated once for the marquee loop; a section whose slot holds nothing simply
draws no frame instead of falling back to a stock photo. The reel's rating keys
are all photo:<id> now, and the QR card's filter follows the sunset preset it
claims rather than an index that moved.
2026-09-22 16:35:40 +07:00
3dtours 7f2a5b0b5a web: drop the 300 ppi lines from the landing copy
The claim was printed in four places — the RAW feature card, the quality FAQ,
the custom-recipe readout and the Lite plan list. Each now reads as
print-ready instead, and the readout keeps only RECIPE CUSTOM_01 · EXIF KEPT.
The exporter's own JFIF density is untouched, web-smoke still checks it.
2026-09-22 16:04:51 +07:00
3dtours 339d36eb3e web: the preset tester picks its recipe from a grouped dropdown
The row of pills grew as the library did, so the landing's live tester now
offers the stocks through one native select, grouped into Landscape, Portrait
and Streetlife, five stocks each — fifteen in all. Choosing one still lands on
the preview immediately: the frame, the HUD and the spec card all follow the
selection. Stock names stay proper nouns, the group names are translated with
the rest of the page.
2026-09-22 12:49:18 +07:00
3dtours 7dea0f1e7b web: the mixer's card can be dragged off the colour it was read at
The card hangs on the point the eyedropper read, which is exactly where the
user wants to watch the band move — so it covers the patch it is editing. Its
body now takes a drag: the offset is a fraction of the photo, which is the
layer the card lives in, so a zoom keeps it where it was put and a fresh pick
drops it back on its own point. The knobs and the close button keep the pointer
to themselves, and the anchor cannot leave the photo, so the card is always
half in reach of a drag back.
2026-09-22 11:13:21 +07:00
3dtours 88afdf6611 web: compare the same frame rendered twice, whatever its geometry
The split used to paint the photo file beside the render, so it only lined up
while nothing had moved: a turn, a straighten, a printed frame and the halves
were two different pictures. The app now renders the same frame twice — once
through the look, once through the neutral stock — and the left of the bar is
that second copy. Rotation, straighten, crop and frame land on both halves by
construction, so the CSS that tried to map the crop onto the file goes away.

The toggle lives in the app now, which is what knows how to ask for the extra
render; it is only asked for while the split is up. The layer waits for that
copy rather than flashing the raw file, whose geometry is already wrong.
2026-09-22 10:59:37 +07:00
3dtours 1d96269139 web: keep compare on offer, and compare at the cropped size
Choosing a crop ratio used to disable COMPARE outright, because the split
painted the whole original into a box that was now the crop's shape. The
original is now looked at through a window of the render's own shape: with a
crop applied the photo is scaled and slid by the crop rect so the same
rectangle lines up, and the split compares like with like.

The window needs the image free to overflow it, so the inline style lifts the
box clamp that .canvas-wrap img puts on every preview.
2026-09-22 10:45:07 +07:00
3dtours b394bad09e web: a wall frame keeps the crop that was applied
CROP + APPLY then a wall frame handed back the whole photo: the crop block
was skipped outright for both walls, so the artwork hung the original. The
walls' own opening still ignores the aspect chip — that is what the
exclusion was for — but the visitor's crop is theirs to keep.

Measured with a source banded red on top and blue below, cut away by a
16:9 crop: through WALL FRAME and WALL FRAME LANDSCAPE the bands used to
come back (569k and 350k red pixels); both now export clean.
2026-09-22 10:31:27 +07:00
3dtours 711e2fc5d6 web: the history arrows sit with the name they undo
Undo and redo were on the far side of the spacer, past RESET, SAVE PHOTO
and EXPORT. They belong next to what they step through: the header now
reads mark, page name, preset name, the two arrows, then the actions.
2026-09-22 10:14:51 +07:00
3dtours 2f78216b6f web: the upscale drops its tile seams
A tile's destination rectangle was placed at x0 * scale, and that scale
is rarely whole, so every 256px boundary landed on a fraction of a pixel.
The edge was drawn half covered, stayed transparent, and the JPEG export
flattened that transparency onto black: a dark line down each seam.

Snap both destination edges to whole pixels instead, so neighbouring
tiles share the exact same boundary, and make the destination context
opaque so no partly covered pixel can survive as transparency again.

Measured on a 640px source: the seam at 2K was 46 levels darker than its
neighbours (96 at 4K); it is now within one level of them.
2026-09-22 09:56:12 +07:00
3dtours c0c99a9672 web: EXPORT offers a size, and a bigger one is upscaled in the browser
The server still never sees a photo, so the model has to run in the page.
Real-ESRGAN x4v3 ships as a 4.9MB ONNX in public/models and is loaded
lazily on the first export that actually needs it; the wasm runtime is
copied next to CanvasKit at build time and stays lazily fetched, cached
for 30 days. Vite is told onnxruntime-web is external-wasm so no 28MB
asset lands in the bundle.

UNCHANGED keeps the old path and the tier cap; 2K/4K/custom upscale only
when the request is larger than the photo being edited, otherwise they
resize down. Guests keep UNCHANGED and 2K. Tiling is 256px with an 8px
overlap, so memory follows the target size rather than four times it.
2026-09-22 09:40:18 +07:00
3dtours f9a40a9e1c web: the histogram opens top-left, and CLEAR asks before it forgets
The histogram used to park itself in the top-right corner on the first
paint; it now starts at the top-left of the photo and is dragged from
there, the way the rest of the overlay is. Nothing else changed in it —
same drag, same clamping, same resize.

The stage also gains a CLEAR button, sitting before the picker button,
which is now OPEN PHOTO. CLEAR takes the photo off the stage, but not
before asking: SAVE PHOTO files it first and only then clears, EXPORT
IMAGE writes the JPEG and then clears, CLEAR WITHOUT SAVING drops it
there and then, and CANCEL leaves everything alone. Saving from that
modal resumes the clear once the file has really landed — a guest, a
capped account or a cancelled name prompt never loses the frame.

Clearing forgets the working photo (source, preview, GPS, ISO, and the
IndexedDB copy session.ts now deletes), while the look, the crop and the
undo history stay put, so the next photo opens on the same settings the
way replacing a photo already did.
2026-09-22 08:56:58 +07:00
3dtours 59d90ae068 web: the sim chips keep their legacy names
The name table is a reference for what each sim has to look like, not a
renaming order: the ten PHOTO STYLE chips go back to PROVIPES, VELVIPES,
CLASSIC CHRIPES, CLASSIC VIVIDIPES, CLASSIC NEGIPES, ASTIPES, ETERNIPES,
ACRIPES, LC STREETLIFE CLASSIC and LC STREETLIFE VIVID. The comment above
FILM_SIMS now says so outright — label on the left, the stock's colour and tone
it must match on the right, and neither side moves the other.

The grading is untouched: a sim is still colour and tone only, its `adjustments`
stay neutral, and LC STREETLIFE VIVID keeps its +2 exposure as SIM_EXPOSURE_BIAS
in colorUtils rather than as a knob.
2026-09-22 08:37:19 +07:00
3dtours 428e7fa682 web: a film sim is colour and tone only
The ten PHOTO STYLE sims now carry nothing but their stock's own grade, and
each is named for the stock it stands for: PROVIA, VELVIA, CLASSIC CHROME,
CLASSIC VIVID (Velvia spliced with Classic Chrome at the blue row), CLASSIC
NEGATIVE, ASTIA, ETERNA, ACROS, LC STREETLIFE CLASSIC, LC STREETLIFE VIVID.
Grain, clarity, saturation and light moves were dropped from their
`adjustments`, so a sim is a clean starting point and the general knobs read
their defaults while the look still lands on the pixels.

LC STREETLIFE VIVID keeps the one brightness step its stock needs, but as
SIM_EXPOSURE_BIAS in colorUtils rather than as an adjustment: it is folded in
where the Exposure slider applies, so the picture gets the lift and the
parameter stays at 0.

Also in this checkpoint: the watermark/GPS boxes and their colour pickers, the
WATERMARK chip column, the real admin stats, and the fix that stopped presets
from doubling and a frame from refusing to come off when a photo was reopened
(/file is the finished render, /base the editable pixels).
2026-09-22 08:32:28 +07:00
3dtours 52b672deec web: PRO needs a proven address — email verification gates the studio
A signed-in account is served exactly like a guest until it opens the
verification link: watermarked 2048px export, no saving, no PRO frames,
GPS stamp or HDF. SMTP is declared in .env; with SMTP_HOST unset the link
goes to the container log. Allowlisted admins count as verified.
2026-09-20 07:39:03 +07:00
3dtours efe578f61c web: the mobile chip strip lies down
Tapping a tab on a phone opened a 148px column with the chips stacked one per
line, so the strip read as a ladder down the side of the photo. Android's own
panel runs its chips as a row — see src/components/AdjustmentPanel.tsx — and
that is what the phone now gets: the columns stack into one vertical scroll
and each chip row runs sideways again, wrapping inside the full width.

Desktop and tablet keep the columns and the stacked chips; the change lives in
the <=860px block.
2026-09-20 07:00:55 +07:00
3dtours 6afb7d9fea web: the mobile rail wears Android's pills
On a phone the studio's tabs are now the row the Android app draws: text
pills in uppercase mono, rounded full, amber and a step larger when open, no
glyph, scrolling sideways when the ten tabs outrun the screen.

Desktop keeps its icon-over-label column — the change lives in the <=860px
block, so nothing above that breakpoint moves.
2026-09-20 06:24:27 +07:00
3dtours 15bacacafa web: logging out ends the studio session, not just the cookie
A session followed the browser, not the account: log in, open a frame, log
out, come back as a guest — the same photo stood on the stage, because the
studio's own store (localStorage knobs + the photo in IndexedDB) outlived the
cookie with nothing to clear it.

clearSession() now drops both, and the three log-out buttons call it. The
studio's own button reloads after the delete has committed — a reload mid-
delete aborts the transaction, so the promise resolves on tx.oncomplete, not
on the request. The account's frames are untouched: they reopen from MY
PHOTOS.
2026-09-18 21:48:19 +07:00
3dtours e57444e88b web: draw each landing photo cut to its own box, not to its own shape 2026-09-18 21:24:02 +07:00
3dtours 2da045f232 web: the preset tester steps through every frame in its slot 2026-09-18 21:02:30 +07:00
3dtours d3c5c47973 web: the reel is curated, not crowd-sourced — no CONTRIBUTE button 2026-09-18 21:02:30 +07:00
3dtours d25649a26b web: each PICTURES column is an album shelf, a frame and a strip
Both columns now have the shape the curator asked for: a narrow shelf of
albums down the left — one per account on the uploads side, one per landing
section on the other — the frame that is up in the middle, and the open
album's thumbnails as a strip across the bottom.

The frame keeps its labels, the four section boxes, the look's QR code and
the delete button under the picture, where before they sat beside it. Each
column previews its own frame; both obey the same name, sort and rating
filters.
2026-09-18 20:38:46 +07:00
3dtours f263413313 web: PICTURES back to two album columns, every frame editable in place
The pane is two columns of the same thing: the uploads on the left, one
album per account, and the landing on the right, one album per section —
Film strip, Live preset tester, Custom recipe creator. QR is no longer a
shelf of its own: the code belongs to the frame.

Both columns list their albums the same way and draw the open album's
frames as cards, each with its labels, the four section boxes, its QR
code and the delete button under the picture. Ticking a box files the
frame into that album on the other side straight away.
2026-09-18 20:17:25 +07:00
3dtours 07fbadcdc5 web: star ratings on the reel, and PICTURES becomes an album browser
The landing strip now carries a score: each look and each contributed
frame shows an average, five stars the visitor can press, and how many
votes it has. Votes are keyed photo:<id> or look:<TAG> and one visitor
has one vote per key, so pressing a second star moves a score instead of
stacking one. The API is public and rate-limited; look: scores survive a
cleared pool, photo: scores are pruned with their photo.

PICTURES was four destination rows; it is now an album per uploader with
a search box, a recipe/rating/newest sort, a minimum-star filter, a big
preview and a filmstrip of thumbnails. Deleting and slot picking still
live in the big box.
2026-09-18 19:17:38 +07:00
3dtours 4057566a14 web: the three mixer knobs move the whole image, a frame chip toggles itself, ROTATE lets go when STRAIGHTEN steers
HUE, SAT and LUM leave the colour row: behind an IMAGE divider they are
hslHue/hslSat/hslLum, seeded into the shader's band accumulator at full
weight for every hue, while the eight band chips keep picking which colour
the panel on the photo edits. The image lightness term stays ungated so a
frame drained to grey by -SAT still answers +LUM.

FRAME loses its NO FRAME chip: pressing the frame already on the photo
takes it off.

ROTATE's quarter turns stop lighting the moment the fine angle leaves 0,
so the strip shows which of the two is steering the photo.
2026-09-18 18:45:41 +07:00
3dtours 27035c4acb web: the mixer hangs a panel on the colour it read, and STRAIGHTEN becomes a scale on the photo 2026-09-18 18:27:28 +07:00
3dtours 10466e122a web: the frame tab straightens the photo by hand 2026-09-18 18:07:14 +07:00
3dtours 1b71c0196f web: the eyedropper reads a colour and the mixer moves that hue band 2026-09-18 17:41:45 +07:00
3dtours 8a889db069 web: the QR card hands out the look that made the photo
A photo's landing section can now be the QR card, and that section is the
only one that hands something out: the server writes the photo's own stored
look back as the app's .recipe file, at
GET /api/photos/:id/preset.recipe, for any row the curator ticked into the
qr slot. Nothing new is stored — the file is built from the recipe the
upload already carried, so it works for a photo uploaded by the phone too.

The admin pane grows a fourth checkbox and a fourth row (QR card); the
row draws the download link as a scannable code, and the box is dead for a
photo with no stored look. The landing's QR card now encodes the curated
photo's own link instead of a mock address. The listing exposes
hasPreset, never the recipe itself.
2026-09-18 16:57:46 +07:00
3dtours a35ecf4f1c web: the landing page hands out no more pixels than it draws
A save cannot be beaten — the bytes are already on the machine — so the page
stops handing over the uploader's 4000px original: each photo is decoded,
redrawn at the size of the box it sits in times the screen's pixel ratio (2x at
most) and only that smaller copy reaches the tag. A visitor who saves one gets
a screen-sized file.

Right-click, drag and long-press are turned off on top of it, and the QR code
card — a link image, not a contribution — is left as it was.
2026-09-18 16:47:15 +07:00
3dtours 260517c547 web: a photo can sit in every landing section at once
The picker was one dropdown, so a photo lived in exactly one place. The three
destinations are now independent checkboxes on the card, and the column holds
the set as a comma list — the landing page draws a photo in every section it
was ticked into, each still picking one of its own at random per visit.

Ticking nothing is what `off` used to be: the row is kept and the landing page
stops drawing it, which is what the old "not on the landing page" option did.
2026-09-18 16:47:12 +07:00
3dtours ee2c402438 web: pick a photo's landing slot with two columns
The pictures pane now reads as a pool of every upload on the left, three
destination rows on the right. Each photo carries one button per
destination; picking the one it already sits in takes it off the landing
page without deleting the row (slot off), which is what the old select's
"not on the landing page" option did.
2026-09-18 16:28:55 +07:00
3dtours 4d0d44170f web: an FX chip that flips the photo to monochrome
It is a switch, not a look: it swaps the base filter for the mono stock and
swaps right back to the one the photo was wearing — name and sim included. The
adjustments are never touched, so a knob moved while it is on survives the trip
back, and the swap rides the undo stack like any other edit.
2026-09-18 16:16:28 +07:00
3dtours d7d7be2c6b web: add CLASSIC VIVIDIPES, spliced at the blue row
Classic Chrome's blue row, verbatim, with the red and green rows taken from
VELVIPES: skies keep the muted teal/cyan lean while everything that is not
blue reads loud. The shadow crush rides along from FILM_TONE, since it belongs
to the stock rather than to a row. CLASSIC CHRIPES stays untouched beside it.
2026-09-18 16:09:09 +07:00
3dtours abaa980f93 web: undo/redo, a clean preview, and a histogram that stays put
Four things the studio owed the visitor:
- UNDO/REDO in the header, so a look can be taken back and put back without
  reloading the photo; a fresh edit clears the redo trail.
- Opening a saved frame, or picking a look out of its history, now drops the
  stale preview buffer instead of leaving the previous render on the stage.
- The picked history look is marked in the accent, so it is plain which look
  the photo is wearing.
- The histogram is re-clamped against the photo box on resize, so opening a
  chip column no longer pushes the overlay past the edge of the canvas.

The 'NEW SAVES: FILM STRIP' chip goes: a save already lands in the strip.
2026-09-18 15:47:08 +07:00
3dtours 78fe2b097b web: ask before a saved photo goes
Removing a saved frame is not like removing a note: its history goes with it,
and nothing on screen said so. A shared modal now names the photo, warns how
many looks ride along, offers the stored file as a download first, and only
then deletes — from MY PHOTOS in the studio and from the folder page alike.
2026-09-18 15:47:06 +07:00
3dtours 97836fde8a api+admin: let the curator take a photo off the landing, not just move it
Uploads still land in the community strip with consent on, by the uploader's own
tick — that default stays. What was missing is the curator's removal: the slot
select only offered the other three live placements, so "off the strip" meant
publishing the photo somewhere else or deleting the uploader's row.

`off` is a fifth slot value. The reel draws slot === 'strip' and each live slot
draws its own, so an `off` photo renders nowhere on the landing, while its owner
still has it in MY PHOTOS.
2026-09-18 15:07:04 +07:00
3dtours dcea6b926f web: name a photo on first save, and file looks from a SAVE RECENT tab
Two saves that never had a name of their own now ask for one, through a single
modal (ui/NameModal, shared by both flows).

The first filing of an upload asks what the folder keeps it as, and that name
rides along as the frame's title. A re-save keeps the name it already has, so
it never asks twice.

SAVE RECENT leaves CREATE RECIPES and becomes its own rail tab: it files the
look standing on the stage — sim, WB, light, FX and the frame — as a recipe of
this account's own, refusing a name the account has already spent. The frame
travels in the recipe's JSON, so applying the entry puts the whole look back.
The tab lists those files and is the one place they can be deleted from; the
API already scopes both by user. They also show up in PRESETS/RECIPES, deduped
against anything CREATE filed under the same name in this session.
2026-09-18 14:57:49 +07:00
3dtours 72d5ce1c3e web: re-save over the open frame, keeping its last three looks
Opening one of the folder's own photos and hitting SAVE PHOTO used to
make a second copy of it. Now it replaces that row — same id, same place
— and the look the row carried steps into its history, newest first and
capped at three, because the pixels it described are gone. The frame's
own column in MY PHOTOS lists those looks (click one to put its settings
back on the stage) and carries the landing-page consent as a plain tick,
which answers the click at once. A file from the disk clears the open
id, so a fresh frame still adds one.
2026-09-18 14:47:30 +07:00
3dtours 27099c2877 web: overlay a draggable histogram on the photo
The panel in Lightroom is a running read of the render, so this one reads
the preview blob itself: one downscaled 320px canvas pass bins 256
values per channel, and four SVG paths draw them — the grey luma fill
with the three channel curves screened over it, from absolute black to
absolute white. Dragged by its header, clamped inside the photo, parked
top-right on first paint, and dismissed either from its own frame or from
the toolbar button. It steps aside while the crop frame is up.
2026-09-18 14:35:50 +07:00
3dtours 8308b3e65f web: move WHITE/BLACK onto the WB tab as per-channel points
LIGHT already spends its slope budget on the tone knees, so the two end
points were doing nothing a tone knob could not. On WB they act on each
channel's own distance from the end: the darker channel of a shadow and
the brighter channel of a highlight move most, which neutralises a cast
at the toe and the shoulder. Both shuffles stay cubic in the channel
value, so every channel's curve is still monotonic (>= 0.46).
2026-09-18 14:35:48 +07:00
3dtours 89beb5f160 web: put the traffic screen in the admin frame
installTracking() beacons one view per page load and one click per control that
carries a data-key, so every existing button is already counted. The new STATS
pane reads it back: a 7/30/90-day range, the three totals, an SVG timeline and
eight proportional bar lists (pages, clicked features, country, region, city,
browser, system, device).
2026-09-18 14:23:25 +07:00