From 9bf99f1abb1bde2b891e3a8d91244d3f3e7b8c67 Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Thu, 21 May 2026 22:10:06 -0400 Subject: [PATCH] =?UTF-8?q?Add=20Slide=20Decks=20&=20Presenter=20Mode=20(v?= =?UTF-8?q?1:=20Phases=20A=E2=80=93D)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Build a Nostr-native presentation system on top of the existing canvas editor: - Slide (templateMode 'slide') — a 16:9 Custom Art canvas with speaker notes; publishes/forks/loads as casewrap-slide. - Deck Builder (templateMode 'deck') — a Template-tab container with an inline ordered slide list. Slide-sorter view: add blank, import from Nostr or file (non-16:9 imports letterboxed), duplicate, delete, drag-reorder, inline-rename; edit a slide in the canvas editor with write-back. Self-contained save/load/publish/fork as casewrap-deck; deck feed-card and publish previews show the first slide + count badge. - Deck theme — deck-level font + background, render-time composition, per-slide ignoreDeckTheme opt-out. - Presenter Mode — full-screen runtime: themed slide, notes, next thumbnail, counter; keyboard/click/on-screen nav; Presenter ⇄ Audience view toggle (slide-only full-screen with auto-hiding chrome). Launches from the deck tool palette and a deck's community-browser preview. Supporting changes: - New categories: slide ("Slide design") and slide-deck ("Slide deck"); both use the free-form Custom tag field, not media identifiers. - Swap the Template/Designer tab icons; 50% larger. - Remove the IMDb/OMDb metadata import (button, handler, omdbApiKey); the imdbId field stays. - Update README, SLIDE_DECK_FEATURE.md status, and TODO.md. Co-Authored-By: Claude Opus 4.7 --- README.md | 20 +- TODO.md | 8 +- docs/SLIDE_DECK_FEATURE.md | 50 ++ src/design_icon_32px.png | Bin 1339 -> 6752 bytes src/index.html | 1276 +++++++++++++++++++++++++++++++++--- src/paper_icon_32px.png | Bin 1497 -> 5291 bytes 6 files changed, 1246 insertions(+), 108 deletions(-) diff --git a/README.md b/README.md index c22d3e7..7fe259d 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,19 @@ # Artstr Studio -Artstr Studio is a static, browser-only design tool for printable physical media packaging — and increasingly, a general-purpose Nostr-native graphics editor. It started as a DVD/Blu-ray case wrap maker and now also covers Avery 8960/8944 disc labels, Avery 8693/8943 CD jewel-case inserts, single-disc designs, and free-form custom art canvases. Designs publish to and load from Nostr, are forkable and editable in place, and can be tipped via Lightning. +Artstr Studio is a static, browser-only design tool for printable physical media packaging — and increasingly, a general-purpose Nostr-native graphics editor. It started as a DVD/Blu-ray case wrap maker and now also covers Avery 8960/8944 disc labels, Avery 8693/8943 CD jewel-case inserts, single-disc designs, free-form custom art canvases, and 16:9 slides assembled into presentable slide decks. Designs publish to and load from Nostr, are forkable and editable in place, and can be tipped via Lightning. ## Tabs and layouts The editor has two tabs at the top level, each with its own Layout panel: -- **Template** — designs that map to a real printed surface. +- **Template** — designs that map to a real printed surface, plus the deck container. - **Case cover** — DVD / Blu-ray wraps with separate front / spine / back artwork or a single combined wrap image. - **Disc labels** — Avery 8960 / 8944 sheets with two disc positions. - **CD jewel inserts** — Avery 8693 / 8943 sheets with a front insert + tray insert (chosen via the Template preset). + - **Slide deck** — an ordered, self-contained presentation deck that embeds its slides inline (see below). - **Designer** — free-form and reusable designs. - **Custom art** (the Designer tab's default) — a free-form canvas with size presets (1920×1080, square sizes, etc.) or custom dimensions. Treats the project as generic art rather than packaging. + - **Slide** — a 16:9 design surface (a Custom Art canvas locked to 16:9) with a speaker-notes field. A standalone, publishable design that can be imported into a slide deck. - **Disc design** — a reusable single-disc design you can publish on Nostr and import later into either disc position of a disc-label sheet. Each layout keeps its own metadata (title / identifiers / category / language) so switching layouts — or loading a project — doesn't overwrite a design already in progress. @@ -56,6 +58,16 @@ Renders consistently in the editor, the preview surfaces, and the canvas / PDF / Cross-target and cross-tool copy/paste — Copy / Cut on a selected layer, Copy all / Paste for the whole panel. Paste retargets to the current sub-mode and clamps positions so layers copied from a wide cover don't land off-canvas on a narrow disc. +## Slide decks & presenting + +Artstr Studio doubles as a Nostr-native presentation tool. + +- **Slide** — a 16:9 design surface on the Designer tab. Mechanically a Custom Art canvas locked to 16:9, plus a **speaker-notes** field. A slide is a first-class design: publish it, fork it, load it. +- **Slide deck** — a Template-tab container holding an ordered, variable-length list of slides. The deck **embeds every slide's full data inline**, so a published deck is a single self-contained event — no external lookups. + - **Deck Builder** — a reorderable grid of slide thumbnails. Add a blank slide, import a slide from Nostr or a file (non-16:9 imports are scaled-to-fit and letterboxed), duplicate, delete, and drag to reorder. Click a thumbnail to edit that slide in the canvas editor; edits write back into the deck. + - **Deck theme** — a deck-level font and background that restyle every slide at render time without altering the slides themselves; any slide can opt out. +- **Presenter Mode** — a full-screen runtime that plays a deck: the themed current slide, speaker notes, a next-slide thumbnail, and a slide counter. Navigate with on-screen controls, click-to-advance, or the keyboard (arrows / space / Page keys / Home / End / Esc). A **Presenter ⇄ Audience** view toggle switches between the notes-and-thumbnail layout and a clean slide-only full-screen view. Launch it from the ▶ button in the Deck Builder tool palette, or from **Start Presentation** on a deck's preview page in the community browser. + ## Editor canvas The canvas is a unified, Illustrator-style workspace: the tool palette, the @@ -117,14 +129,14 @@ Two flavors of Lightning are wired in: - `src/index.html` — the complete single-file app. Open it directly in a browser, or serve it from any static host. Vercel rewrites in `vercel.json` map `/share/:id` and `/u/:npub` to the SPA entry. - `src/vendor/qrcode.min.js` — vendored QR encoder (used for Lightning tip invoices). -- `docs/` — feature specs. `PEN_TOOL_FEATURE.md` covers the now-shipped pen / pencil / vector tooling; badges, collections, NWC zaps, and zap-gated templates are still unbuilt. +- `docs/` — feature specs. `PEN_TOOL_FEATURE.md` covers the shipped pen / pencil / vector tooling; `SLIDE_DECK_FEATURE.md` covers the shipped slide / deck / presenter system (Phases A–D; dual-window presenter is future). Badges, collections, NWC zaps, and zap-gated templates are still unbuilt. - `TODO.md` — running list of deferred work and cleanup items. ## Schema - Current project schema version: **5**. - Minimum supported version: **4**. Older payloads load but won't round-trip cleanly. -- Template-mode discriminator in payloads: `cover`, `disc`, `jewel`, `customart`, `disc-design`. +- Template-mode discriminator in payloads: `cover`, `disc`, `jewel`, `customart`, `disc-design`, `slide`, `deck`. A `slide` payload carries a `slide` object (canvas dims + speaker `notes`); a `deck` payload carries a `deck` object (`theme` + an inline ordered `slides` array). - Layer types: `image`, `text`, `color`, `shape`, `qr`. Shape kinds: `rect`, `rounded-rect`, `circle`, `ellipse`, `triangle`, `polygon`, `star`, `line`, `path`, `svg`. Any layer may carry a `clip` field for masking. ## Defaults and stack diff --git a/TODO.md b/TODO.md index 94dc6c2..042de49 100644 --- a/TODO.md +++ b/TODO.md @@ -19,13 +19,9 @@ surface + canvas export, JSON round-trip). ## Cleanup -- **Remove the IMDb import.** The OMDb prompt-based lookup isn't pulling its - weight — rip out the `imdbImportBtn` button, its handler, and the OMDb - fetch. (The `imdbId` metadata field itself stays — only the auto-import - goes.) - **Delete `docs/TMDB_AUTOFILL_FEATURE.md`.** TMDB auto-fill was declined — - the metadata fields work fine filled by hand and the IMDb-style import is - being removed anyway. + the metadata fields work fine filled by hand, and the IMDb-style import + has been removed. ## Profile pages — deferred polish diff --git a/docs/SLIDE_DECK_FEATURE.md b/docs/SLIDE_DECK_FEATURE.md index 0fbaf85..e529378 100644 --- a/docs/SLIDE_DECK_FEATURE.md +++ b/docs/SLIDE_DECK_FEATURE.md @@ -36,6 +36,56 @@ the deck — all Nostr-native, forkable, and driven entirely off JSON data. - A per-property theme matrix — v1 theme overrides a small fixed set (font family, background); per-property locking is future polish. +### Implementation status + +- **Phase A — Slide format: done.** `templateMode: 'slide'` (a thin alias + of the Custom Art canvas engine via `isCanvasMode()`), Slide Designer + layout on the Designer tab, 16:9-locked Canvas panel + Speaker notes + textarea (`state.customArt.notes`), and `slide` save / load / publish / + fork as `casewrap-slide`. Slides appear in the community browser. +- **Phase B — Deck Builder: done.** `templateMode: 'deck'` with the + embedded-slide `deck` data model; Deck Builder layout on the Template + tab; the slide-sorter view (thumbnail grid with add-blank / import from + Nostr / import from file / duplicate / delete / drag-reorder); open a + slide in the canvas editor + and write back via `persistEditingDeckSlide()`; deck save / load / + publish / fork as `casewrap-deck`, self-contained; deck feed-card and + publish-confirm previews show the first slide with a slide-count badge. + Import letterboxes non-16:9 sources onto the slide. +- **Phase C — Deck theme: done.** `deck.theme` (font family + optional + background-color override) with controls in the Deck Builder sidebar; + per-slide `ignoreDeckTheme` opt-out in the slide editor; render-time + theme composition (`composeSlideForDeck`) applied non-destructively to + deck thumbnails and the community-browser preview. +- **Phase D — Presenter Mode (single-window): done.** Full-screen runtime + (`#presenterMode`): the themed current slide, speaker notes, a next-slide + thumbnail, and a `n / total` counter. On-screen prev/next, click-to- + advance, and keyboard nav (arrows / space / PageUp-Down / Home / End / + Esc); a Presenter-view ⇄ Audience-view toggle (Audience view shows the + slide alone, full-screen, with auto-hiding chrome — for single-screen + use). Presenter owns the keyboard via a + capture-phase handler, so editor shortcuts are suppressed while it runs. + Launches from a ▶ button in the Deck Builder tool palette and a + "▶ Start Presentation" button on a deck's community-browser preview. +- **Phase E (dual-window presenter):** not started — explicit future work. + +**The v1 plan (Phases A–D) is complete and shipping.** Two new categories — +`slide` ("Slide design") and `slide-deck` ("Slide deck") — were added so +slides and decks classify on their own terms, and both use the free-form +"Custom tag" field rather than the media-identifier panel. + +### Remaining work + +- **Phase E — dual-window presenter** (audience window + a separate + presenter window with notes/timer on a second screen). +- v1 non-goals still deferred: slide transitions/animations, a presenter + timer / rehearsal stats / laser pointer, rich-text speaker notes, + PowerPoint/Keynote/PDF import, deck → PDF/video export, per-property + theme locking, and a Keynote-style always-on filmstrip. +- Minor polish: the "Back to deck" bar is absolutely positioned (scrolls + away if the slide-edit canvas overflows); deck theme background is + color-only (an image-URL background was floated as future). + --- ## Why this fits the repo diff --git a/src/design_icon_32px.png b/src/design_icon_32px.png index 2be2f921d4db9981d3db23bf6866fc50f96a105d..a429f40e323b19953e583ae2a4f1fc6499d9da00 100644 GIT binary patch literal 6752 zcma)B1ydY6v|Y4|FYd+Nr8pFKio3g8aVxNRai>^Iaai2lt;pi;e0b3@y)&)N=tbS6#&4W9smdl z2LK-5Lm@{1fEOnKaAF1k2xS2Pgl@U*>LTw6WD6xZX~5h6_FrA4sqc{wo(d{5A5M_) zafJAFvYW2}0OCajX$dXg)iYziMq_Qix1gt7*m=Ok0UZa2A8nk1JeZG?DxA1{ejgF6 z+ypVuf>bpv3|YW68I(7%acix$PYX3-8h`(#4MG}1DiWw7_)r3%qNU=>61><~x!c%5 ze>$ZIxbV0Yyz9(V>g=S--7?^48z8=2*?Nt9dFt-sQ2Qkw3Xg<}$M}CZOo%Ez2sUU5 zqPNSHj!mohU|@0@06-lgA0nQ_y5qmubH38`lzi;s7%C;+X7q{&$o~uV*zoz{BisO* z+Xz_!XDDx=H%3K-7)sL?hJ73m*{E`g6yCtdifP?n%*lJYc+p!mD4gd_(dcE9bs=K2Wjpy(J26!tk^zX9l|9yZ{?Pf2u{L`^IkQp2MVnw9T6 zbmj;p5J&(1CkQUtVj_f}lBzb-Q%G)#;k&pgK!mrRO6BQgB9zd)7J->$ZFejVm zEgnnJbj5vY!(LHQQT{Lc4fybpC1lBnBNKq|Xk#v(UW4h7@Ka_u5}wJ7n&SIx#NRep zj5LD9P@Q`-A~>7RaR9enLne5h0e4YRYDCJoZ_Boo61@jJ%@+gZeVq8dG7gu1J;9r( zJv*5LoM2G+nYI|JF`9&ANFi{31_>1?4#X8dLwme*swFe`0)1N!Z8QF8A_H%Rk^oj0AlYcTWms;5?9S^D_Zu{xqjlT-2qv7J!;FM1sogsy9j;U}Z_h zjVgY65#IB_z;z?x0(Cu@I&0?!_A`E`7Dx!|MndDeSZ`u++4?OLJB2#1-JnjpC*d|R zGh)>CdVIstxc7h+0GAkt&QYN?jEfTnJj{g|um&$M1J}CYIpM2i0GXnkbTDIe6!E=o zYCZt3`2F!RY7izIcgWlN%eF1<)AX8B;lKb*PvaWw-<)dcs*l-G@A@>>;%ew}KN8wu zA@%xe=&5*bc%PbRJ;4wMZ7 zmW+Icdo7))d9i%yW5`+}4hWncg%58ksx}ftX`Rrwy$X63ewCJ)B{9x3egnzOEKNQY z6cm))ej>TyxbJ?Gg^5AQlvc#LA8T>lh$77nirT>;^6&}tM9a++?$;k{+4!)xf!1P3 z*m~S#J0YLE6wH#)lRgzEc(_fbUQ-70^88j_zwFq4U#)pGD5Rg{)wce;+Nz^@qwQ~FS(+{PniBC*YZch4&_dD=144@%k zwi6;au^_o5+}b@#0M9W4rnAFuFbLCSH5K#eH0bg(GkgEK{NEe<(*O(9sL>Z208WeB zAJ1gr$tlG*2uV*y#zbHIwO89pB4=O6cbfB2{Ic)@uKiD8M}vp&F$&ApzJMf>39Z`s z`M2G=k&m$)JvNispGf&co)VL%;O=g3+1qyp_^XZl`cd2$ps9&(B>X|omi7g$XSm}K zg3IUb?g+yc=a-N)MSm%&4{ne8hr20C9IE&Oa2dbQQ+^TO7(^F@G6T4^c;&Ms3Oo%k zT@YNKw(dP==H`|~_`J8Q&R{RHJr^GciJd8kh$2 zBsB!N?aM#?6hxr#1sC)7nv=yYftXf<9JBtV=5|Bv)iBv$(rfmE)Fzj{ZnBa`<94ax z5M*|H?XyS6i26Kn9qp2PN51d%*z4CeGAL;T=;(y?hl`?WSdvy-y_F_M>ETc`dG%v% zo%ukj8zLofG<6K+rO(va@yY;zOoB0U9D#<0ru>%N@4w`6LRP)nuZL5412_JnMLKx;%bJN8I#m54BCQ~h=DK$mu~B~?w}uxU;#lR>*kApyeC#S!f2QQbScA8JJva({` zw$S`afU)73@BLH|*3gVVLp^~8H~eQwkUXM9LvyzU5;~yPwn|=(ts`MiEd`W_{smZeyo^LTeU0E zNc{ZjA#yqJ773C~qJ4TiFg+2#%%Y1%JebUB>BFpGZ@mOtV}1U2dB$4kI~sVoO1cOU z^l}cIcz%F+DVvE={arKeTQ0-Oz)7Ha-?_OlV_NKL$_*|cKj39_)J|mZ>5*Xf&*yGb zaSNB?AE!m5`-37+@t^=7U5gX0CS(c6w`OO<%RLH)+@V}S59wkJg=C_DyK|MMNi1-i zI-ao=JQuv%MBT{l^HL8L(_-v~xTJrgZlIPd>6Mqmg2-!o5Ow|xeY?7A(E*ir^Dt@* zgyt(efs_T{%5(G^6up)B=h|%hChH*vz8dz57!B^Wgi5^f69e~9l~u99P#i(6TQ25P z5Rtn9LY05)zcN)YGZkYOgeSkg_8=+si|S8NL26A}*FLObd|qo<5`p0`SqtaSF(LGz zO9h6!MzisQNhSPbN)RH|7m?#n`~F=eqrfq}M&D#@vD8G)Bp?ln9f_K(0-UgYg5N0` zDVLZ@#GxQiD#pRf$+gXB)13AFA~MNHNtKM>fn3{2oGx?hFNB8Vof+II0oY$XaSiSS zqfW-T?1L;o^!UGC<+d;;5wffK*MU{Ws_@=?gtG#TW@7Tl|k6~D%bad z7wu0Q?m&7MzRs_-#4Vft|#(@r!bhZwXc(cg2J_%57v~jf76afGC(tM|1bbO z>IZmvP%-kH*-yD-)nHg=q;vAnpkoe%_lWNBD?FwgiXU0irDZSK2&?)~B50(3>{rkT zx9R2Nu^LpVL^HT*n>EPphfE#2p(IO-V5xTOYaK^#DC!ATuLm^ENkhpfay7qWhx?@` z;)-?>Ku|n)0}JT68gB)5r*CIRPyeaW!%nO|=9?zu+{e%=Et~{>Bb*?DYdFZ9Y)A!N z8m&d`rn?Mo^A*E2?r_uC*m!JU(7xiY=IYs(Qs#!!)fCP9%d!eG3W{K_CWCl;3aD4z zZJbA1%%o^xo8lM6;UjseC#{8V%l-gT;CC7@{vR2eMY87;)kkQ4>HZIYvqkicJpD`=nbyP@{*4@Z6%@bV;*EBI+?|~}3ATIT& z5AobJ-k$m{%a2`;Uda9P1FPTTO;LoPGi^i3pfo>W2A4mwYu`Hg3qUU9669SY z>-GAisoLf@VAk1InHI;%%W{(LM@qJun7DC&DF4SDFjCNC+Ua|R6%Eu-Y^@eA z-x%M&HjbCIEcScLh}~?y&(rPU{}?R^44>85%*EVl2q3KfHnK)#T|YM!@cK`*b*J6& zYLq5%`|-4fKQS9bWmAobd_`O6+WfHHIdc*ryuEl^$^V~tdn{ycq34^Z+89F4XsI;x zB{GrCFkd0P>z2ZOusP`IRy0y!`zc=AoG9OE)5pilVnKnSluUwkJ@*GG59l64}v0O$)CsbIGk9 z9?+f`Dv()iGL*XE{Ah^DkIzvt*Vsk?85|Hp~O;9Bp2ktQ9 z#bJa$85kig(z?)pDqY)LL(iDX;i2dpqT{0v1xBVy;H&mE*y{abkbhV$aem{0<|LG& z57?M;yxCOEo3(t;9Fq3&_GZ5e3Qh;XYbdgsFlKhUs89g3XLo_24=|C~T^e98p-|+x z99}v3VKh;A6Cl?29RcPUUab+f^uF_ygx>r6EK~MO3Pi2$MR&4^A3TRLUD zcZoxvHvA6|dq;)PNMPxFP;&_~Hg;H2XFcb!TyLLRaYg-0y=M2Cr8;`R!p3anYGeS` zDJ@P+ryUl?X}|HGlRTfay62Cy>(r70_67GPl}a<=hAf?VzGT^N7zLE_#<_0XV64BozTs>=-S3arT|LCx&Ewawg^SzT+G>_WYrtisPv!P%gxDS6 zlCL16p2ej|xA^skm+vyd*H!$qVA3Xl8>3Pm1VS`2GCF#@$+ghefV5=h>tXnGwi;5m zqE6R+pik4%G84YmsOR--6vMVWBXzPSfD`}8N_s4OO*|Px@=W15-7T5qeb)%~DC`(- z>v07`E^7xVp3|Vnj;Ra{(y3LOAGFyBL-MkhL8kU>X>O-pOwsYu{DHaPVjzxpAa2!f~u;hSSXDSx2!6p z2uv|xbJ!}-H9(9m!Iym>H1AGMPoI?WWOX^UI9qPx8Wi7rVa^7O zCs~#W#@zOw7ag@b_iqk|_A4bpn=jQqJ1e$77yN8u5)u+<4Hna>vsV=lf1aUvT$_^F z?aR028DgsH%S4G*!%5=t_NtQ?F^v^q50{Y73LyiUi>tBk<^Bde9*-%+)y>RoQlcj+ zyD+#vU*jfK*42tNLzHRh@`Jxw{y}4owEn?OGbKP|+G1{32q9 z$WiVO_FSboymMiY1LRXedLwJ?23G6-o#t8uYy&RimFi@T3myI#w+8d+aMZyND;b+I zre6jdX4THf(s+@7s)~G%0}J+-K27jpe7OckGa+>mtpdB!O>^uc21hMh=ufI+zS(kL z@bPa2@`ydHK*bf`(Q80Z9TOoAqSkQb_fSijQJ`}>3|w)0>>##Eyqz=z-x$0D6^AMU#0%5fwo8jW*baO1$(6<+=>gl17qX_a`7fkm@&<+0gpir~Z z)gto;^mW zqz2w@r8eG3?r5jhk8;X9s%7)0aZ-Z2rVjT$Pra$PPW{_U)1jjNv0~xml1fzkU`g3X zPEXwp)6r&m#)yRJ%XKk}iAs~)+y+B+&)9+uCH)ur)qTwE=JR@3z4XingU|um=dL3 zOg;PQ6{$Ki*E*{en18PANd6C=AV)O#Y^~+uk(?a`P^MD}t6XqUw%{efAoKd`-fqZR z)yT{?r#6@IQ=}f#^yNBJ9PrKvh)VPPydl;pp+OxiN!XRN;l!j)&5rf)wP)?lc}>To zwBQzE=7X3$2r;3I>H?P7e$}4Wm{Xn^h2`y!iDpe~3W>Bp>};!WuVeX(E+m#?%3?&R zldq<7Go=K&&YAnPq!0!9^5zd;Y;)*|!vOtmkw1=@p2p_m0teJAma5(5ZL*{_x8;CR zse03JKebNNCJ3UN+|$qMEI$g5Tk6OV$)u&YAM(45@b^6UK}>?!T^>pCZ~q4!`V&$$v3L{ElR?Alv=D||1l{cd-AmU{JiT% zc8|{VuB|*QCS(_MnQ&IP=*m*2V#MdV62kbMle3ldbaA7QA~&Vp?xlbI>*->A5_f-W z&jvVZX~Q!8Dx0EOt0Vw+fT052m@@XZ;Z-c21*I?^<6p^12loF-uUuP*;_)x@^!C2} z;zL@3P?I$&U%Iy>R7%nC>!{k;&I@;%mwue0i)=|C?MWpN6T6^jJY+SLm~Gq7!=K=8 zf=Sp+hS)zpiZkw?r<^lR1#=*<`m|ui?#KbnEBCPN=;%ni%0vz?{M`w$LV;@L5v zKGRCcR@q{QpWN@EqiCShB!} WuasmVV(&#CfP%~y>1s(c@c#fUW$elT literal 1339 zcmV-B1;qM^P)-4R6o#MEJu~-m6Oxh&3Ri@RWfcoct3W{$h-Bp-a8J2G{();1e}LsqEvhVb{st}l zV8KR_?*a<24Dw+}xR)CtckZ3(IzVF66cA7a%J%7cLZ>>AKWf`!N}m6H?Ilf7$gU4K?JM;mm?yG5nS$vjgOBHlmB0#)oPVL=9@l~ z!o)WXUyQ3Q{{aJ^?L~ZzO1w&?qHz@czY>TH^A{v{s8~QiB5=zAH82d(>7=A-`rimx zgE|Kbt!3210*Z?A)2jxt!r`SF^5GuTITXaVXc>z71#Gwk#u!~{cQD5Abh+cVn!?rd z4dPgExx>W5)bwt))~X<|)M^dmlx#MvfOC%Jc7~v6u>w)z7+mhK)*zygcUW5LtN^an zYRo-YWc%0_bRz_epeBlW@Mx*5sd)Xlak)wE1Z#!2r(PhbRQ!OU7#SUzA5d34xiJ9XO9G z-A=chT&W~_^UgfoTmxzN?OJO?1TYZ5{bE$5^j>QOF%IXz2)Lo+FHJjasaDxi$bi!6 zX6q)PjlWWyy524lxN)(GQ?S<1>E;}Hrw$-P;A)KUu-Qh%fl4X;x=Uchpz388@vdgA zK~=GlAxRQyV+JFFQ(ZB?Xi~!b-jLqdwFqcZsc`S%<6dGFRF%sY7O_UiGv&>x=SY$Y z&Nqe!6;y!vR=YGP->Bgo5VM8?0xBZhZ$8C}mr8LU!qp26OlY{N=^dEh?P;10GS}XV zidR9zH(w!%%X_o_!BFO)1}|Gxct?Dz-l-n|D^#RPdqYY7M6 zIKa{Ok89Q8euymfdssH4HSgbU|? z;pA5*ecqx(ef0Yk(DdvpfI-Coj*%ZmG27;F3WpBuq*|@gKV6KdI%U4q#!5I5gVRln zRY8O-%gC~@@52eGeJTvyh>Y)2G9*gKa>%+4V;o5mW6aQS=lj9XODbZ5-}&WQYkM`9 z(3orafVdnr5$S{9$uq~;dpXLk@;1}yS*(h{;!;ZPT3AhgqzB#J@_ zykEF}ABe0~L^ycx0P*3o7!h(+s+9^#UYfjxOevs^_?$)K)34<1rsbgFb=65?KCez3PpyMT65(9e>RrGo8p|} z6kgmu!mXRP8GpXc^-GTsG0e{H;`HgcGVSc_F24JAjyMt22{SW08QPJnM-)Yx=h>=# xFdT(LuI7{jf>!_l diff --git a/src/index.html b/src/index.html index 2fa7412..7e0cc60 100644 --- a/src/index.html +++ b/src/index.html @@ -429,10 +429,27 @@ off .template-only / .designer-only — show it for either. */ #designerLayoutFieldset { display: none; } body.app-designer #designerLayoutFieldset, - body.template-customart #designerLayoutFieldset { display: block; } - /* Custom Art now lives on the Designer tab, so the Template-tab Layout - panel hides while Custom Art is active. */ - body.template-customart #layoutFieldset { display: none !important; } + body.template-customart #designerLayoutFieldset, + body.template-slide #designerLayoutFieldset { display: block; } + /* Custom Art and Slide Designer live on the Designer tab, so the + Template-tab Layout panel hides while either is active. */ + body.template-customart #layoutFieldset, + body.template-slide #layoutFieldset { display: none !important; } + /* Editing a deck's slide opens the slide editor; suppress the layout + panels so the only way out is the "Back to deck" control. */ + body.editing-deck-slide #designerLayoutFieldset, + body.editing-deck-slide #layoutFieldset, + body.editing-deck-slide #metadataFieldset, + body.editing-deck-slide #exportFieldset, + body.editing-deck-slide #printSaveFieldset { display: none !important; } + /* The Deck Builder's "Slides" and "Deck theme" panels show only in + deck mode; the per-slide theme opt-out shows only while editing a + deck slide. */ + #deckFieldset, #deckThemeFieldset { display: none; } + body.template-deck #deckFieldset, + body.template-deck #deckThemeFieldset { display: block; } + #deckSlideThemeWrap { display: none; margin-top: 10px; } + body.editing-deck-slide #deckSlideThemeWrap { display: block; } #layersFieldset { order: 40; } #designerMetadataFieldset { order: 45; } #exportFieldset { order: 50; } @@ -524,6 +541,175 @@ body.app-designer #discDesignerView { display: flex; } + /* ---- Deck Builder (slide sorter) ---- */ + #deckSorterView { display: none; } + #deckEditBar { display: none; } + body.template-deck:not(.editing-deck-slide) #deckSorterView { + display: flex; + flex-direction: column; + width: 100%; + gap: 16px; + padding: 22px 26px; + box-sizing: border-box; + min-height: calc(100vh - 160px); + } + /* In the sorter view there is nothing to draw on or zoom — hide the + sheet, the tool palette, the tool options, and the zoom bar. */ + body.template-deck:not(.editing-deck-slide) #sheetWrap, + body.template-deck:not(.editing-deck-slide) #toolOptions, + body.template-deck:not(.editing-deck-slide) #canvasToolbar, + body.template-deck:not(.editing-deck-slide) #layersFieldset { display: none !important; } + /* Deck sorter: the tool palette shows only the Present (▶) action. */ + body.template-deck:not(.editing-deck-slide) #tool-select, + body.template-deck:not(.editing-deck-slide) #tool-hand, + body.template-deck:not(.editing-deck-slide) #tool-pen, + body.template-deck:not(.editing-deck-slide) #tool-pencil, + body.template-deck:not(.editing-deck-slide) #tool-shape, + body.template-deck:not(.editing-deck-slide) #tool-text, + body.template-deck:not(.editing-deck-slide) .toolPaletteDivider { display: none !important; } + #deckPresentBtn { display: none; font-size: .9rem; } + body.template-deck #deckPresentBtn, + body.editing-deck-slide #deckPresentBtn { display: flex; } + .deckSorterBar { + display: flex; align-items: center; justify-content: space-between; + gap: 12px; flex-wrap: wrap; + } + .deckSorterCount { font-weight: 600; color: var(--ink); font-size: .92rem; } + .deckSlideGrid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); + gap: 16px; align-content: start; + } + .deckSlideEmpty { + grid-column: 1 / -1; text-align: center; padding: 48px 16px; + color: #64748b; border: 1px dashed var(--line); border-radius: 10px; + } + .deckSlideCard { + position: relative; border: 1px solid var(--line); border-radius: 9px; + background: #fff; overflow: hidden; cursor: pointer; + transition: box-shadow .12s ease, transform .12s ease; + } + .deckSlideCard:hover { box-shadow: 0 6px 20px rgba(15,23,42,.16); } + .deckSlideCard.dragging { opacity: .35; } + .deckSlideCard.dragover { outline: 2px solid var(--accent); outline-offset: -2px; } + .deckSlideThumb { + position: relative; width: 100%; aspect-ratio: 16 / 9; + overflow: hidden; background: #fff; + display: flex; align-items: center; justify-content: center; + } + /* Stop flexbox from horizontally shrinking the fixed-width mini-preview + (it would squish the slide); scale uniformly via fitMiniPreview. */ + .deckSlideThumb > * { flex: 0 0 auto; transform-origin: center center; } + .deckSlideMeta { + display: flex; align-items: center; gap: 6px; + padding: 6px 8px; border-top: 1px solid var(--line); font-size: .78rem; + } + .deckSlideNum { + font-weight: 700; color: #fff; background: var(--accent); + border-radius: 4px; padding: 1px 6px; flex: 0 0 auto; + } + .deckSlideName { + flex: 1; min-width: 0; + border: 1px solid transparent; background: transparent; + font: inherit; color: inherit; border-radius: 4px; padding: 1px 5px; + } + .deckSlideName:hover { border-color: var(--line); } + .deckSlideName:focus { border-color: var(--accent); background: #fff; outline: none; } + .deckSlideName::placeholder { color: #94a3b8; } + body.dark .deckSlideName:focus { background: #0b1220; } + .deckSlideActions { display: flex; gap: 4px; flex: 0 0 auto; } + .deckSlideActions button { + border: 1px solid var(--line); background: #fff; border-radius: 4px; + cursor: pointer; font-size: .8rem; padding: 2px 6px; line-height: 1.1; + } + .deckSlideActions button:hover { background: #f1f5f9; } + #deckEditBar { + position: absolute; top: 10px; left: 50%; transform: translateX(-50%); + z-index: 60; align-items: center; gap: 10px; + background: var(--panel, #fff); border: 1px solid var(--line); + border-radius: 999px; padding: 5px 14px 5px 8px; + box-shadow: 0 6px 20px rgba(15,23,42,.18); font-size: .82rem; + } + body.editing-deck-slide #deckEditBar { display: flex; } + #deckBackBtn { + border: 0; background: var(--accent); color: #fff; cursor: pointer; + border-radius: 999px; padding: 4px 12px; font-weight: 600; font-size: .8rem; + } + #deckEditLabel { color: var(--ink); font-weight: 600; } + body.dark .deckSlideCard, + body.dark .deckSlideThumb, + body.dark .deckSlideActions button { background: var(--panel); } + body.dark .deckSlideActions button:hover { background: #1e293b; } + .deckCountBadge { + position: absolute; right: 6px; bottom: 6px; z-index: 5; + background: rgba(15,23,42,.82); color: #fff; + font-size: .68rem; font-weight: 700; padding: 2px 7px; + border-radius: 999px; pointer-events: none; + } + + /* ---- Presenter Mode ---- */ + #presenterMode { + position: fixed; inset: 0; z-index: 100000; + background: #0b0d12; display: none; flex-direction: column; + } + #presenterMode.open { display: flex; } + #presenterStage { + flex: 1; min-height: 0; position: relative; + display: flex; align-items: center; justify-content: center; + overflow: hidden; background: #000; cursor: pointer; + } + #presenterStage > * { flex: 0 0 auto; transform-origin: center center; } + #presenterNotesPanel { + display: flex; gap: 18px; padding: 14px 22px; box-sizing: border-box; + height: 188px; background: #14171f; border-top: 1px solid #2a2f3a; + } + #presenterNotesInner { flex: 1; min-width: 0; display: flex; flex-direction: column; } + #presenterNotesInner h4, + .presenterNextLabel { + margin: 0 0 6px; font-size: .7rem; letter-spacing: .08em; + text-transform: uppercase; color: #8b93a7; + } + #presenterNotesText { + flex: 1; overflow-y: auto; color: #e6e9f0; + font-size: 1.02rem; line-height: 1.5; white-space: pre-wrap; + } + #presenterNextWrap { flex: 0 0 auto; width: 256px; display: flex; flex-direction: column; } + #presenterNextThumb { + flex: 1; position: relative; overflow: hidden; + background: #000; border: 1px solid #2a2f3a; border-radius: 6px; + display: flex; align-items: center; justify-content: center; + } + #presenterNextThumb > * { flex: 0 0 auto; transform-origin: center center; } + #presenterBar { + display: flex; align-items: center; gap: 12px; + padding: 8px 18px; background: #0b0d12; border-top: 1px solid #2a2f3a; + } + .presenterBarSpacer { flex: 1; } + #presenterCounter { + color: #e6e9f0; font-weight: 700; font-size: .95rem; + min-width: 72px; text-align: center; + } + .presenterBtn { + border: 1px solid #2a2f3a; background: #1c2029; color: #e6e9f0; + border-radius: 7px; padding: 7px 14px; font-size: .85rem; font-weight: 600; + cursor: pointer; + } + .presenterBtn:hover:not(:disabled) { background: #262b36; } + .presenterBtn:disabled { opacity: .4; cursor: default; } + #presenterExitBtn { border-color: #5b2330; background: #3a1820; } + #presenterExitBtn:hover { background: #4d1f2a; } + /* Audience view — the slide fills the screen; the control bar floats + over the bottom and auto-hides when the mouse goes idle. */ + #presenterMode.audience #presenterNotesPanel { display: none !important; } + #presenterMode.audience #presenterBar { + position: absolute; left: 0; right: 0; bottom: 0; + background: rgba(11,13,18,.92); + transition: transform .25s ease, opacity .25s ease; + } + #presenterMode.audience.chromeHidden #presenterBar { + transform: translateY(115%); opacity: 0; pointer-events: none; + } + .designerDiscWrap { position: relative; width: 380px; @@ -856,14 +1042,20 @@ /* Export toggles: fold/safe/trim/bleed/crops only apply to case covers */ body.template-disc .cover-toggle, body.template-jewel .cover-toggle, - body.template-customart .cover-toggle { + body.template-customart .cover-toggle, + body.template-slide .cover-toggle { display: none !important; } - /* Custom Art mode: hide all the slot/region UI and decorations, show only the canvas + layers */ - body:not(.template-customart) #customArtFieldset { + /* Custom Art / Slide mode: hide all the slot/region UI and decorations, + show only the canvas + layers (the Canvas panel serves both). */ + body:not(.template-customart):not(.template-slide) #customArtFieldset { display: none !important; } + /* Speaker notes belong to slides only. */ + #slideNotesWrap { display: none; } + body.template-slide #slideNotesWrap { display: block; } + #slideNotes { width: 100%; box-sizing: border-box; resize: vertical; font-family: inherit; font-size: .82rem; } body.template-customart #coverSizeFieldset, body.template-customart #coverArtworkFieldset, body.template-customart #discArtworkFieldset, @@ -875,10 +1067,33 @@ body.template-customart .trimArea, body.template-customart .foldLine, body.template-customart .discSpineStrip, - body.template-customart .jewelSpineGuide { + body.template-customart .jewelSpineGuide, + body.template-slide #coverSizeFieldset, + body.template-slide #coverArtworkFieldset, + body.template-slide #discArtworkFieldset, + body.template-slide #jewelArtworkFieldset, + body.template-slide .coverOnly, + body.template-slide .diskLayer, + body.template-slide .jewelLayer, + body.template-slide .bleedFill, + body.template-slide .trimArea, + body.template-slide .foldLine, + body.template-slide .discSpineStrip, + body.template-slide .jewelSpineGuide { display: none !important; } + /* Deck Builder (sorter): only Layout, Slides, Theme, Metadata and + Print/Save make sense — hide every artwork / canvas / layers panel. */ + body.template-deck #coverSizeFieldset, + body.template-deck #coverArtworkFieldset, + body.template-deck #discArtworkFieldset, + body.template-deck #jewelArtworkFieldset, + body.template-deck #customArtFieldset, + body.template-deck #exportFieldset, + body.template-deck #layersFieldset, + body.template-deck #printExportRow { display: none !important; } + .templateLayerPanels { display: grid; gap: 10px; @@ -2079,8 +2294,8 @@
- - + +
@@ -2102,6 +2317,7 @@
+
@@ -2180,10 +2396,7 @@ -
- -
-

IMDb import uses OMDb-compatible endpoint + API key. Title and category are required to save or export.

+

Title and category are required to save or publish.

@@ -2208,7 +2421,7 @@
Print & Save -
+
@@ -2245,7 +2458,41 @@
-

100–8192 px. Add layers (image / text / color) using the Layers panel below.

+

100–8192 px. Add layers (image / text / color) using the Layers panel below.

+
+ + +
+
+ +

The deck theme restyles fonts and backgrounds across the deck — turn this on to keep this slide's own look.

+
+
+ +
+ Slides +
+ +
+
+ + +
+ +

No slides yet — add one to start your deck.

+

Click a slide thumbnail to edit it. Drag thumbnails to reorder.

+
+ +
+ Deck theme +

A deck theme restyles every slide in the thumbnails and Presenter Mode — the slides themselves are never changed.

+ + + +
@@ -2382,6 +2629,7 @@ Layout
+
@@ -2905,6 +3153,19 @@
Design A
+ +
+
+
0 slides
+ +
+
+
+ +
+ + +
@@ -2915,6 +3176,7 @@ +
+ +