/* ===========================================================
   Alessio Salatino — Portfolio '26
   Animated scroll-deck site, generated from the PDF portfolio
   =========================================================== */

@font-face{
  font-family:'Martian Mono';
  src:url('assets/fonts/MartianMono.ttf') format('truetype-variations'),
      url('assets/fonts/MartianMono.ttf') format('truetype');
  font-weight:100 800;
  font-display:swap;
}
@font-face{
  font-family:'Xanh Mono';
  src:url('assets/fonts/XanhMono.ttf') format('truetype');
  font-weight:400;
  font-display:swap;
}

:root{
  --red: rgb(179,28,18);
  --ink:  rgb(0,0,0);
  --offwhite: rgb(222,222,222);
  --paper:#f4efe9;
  --mono: 'Martian Mono', monospace;
  --display: 'Xanh Mono', 'Martian Mono', monospace;
  --canvas-w: 2800;
  --canvas-h: 1575;
}

*{ box-sizing: border-box; }

html,body{
  margin:0; padding:0; height:100%; width:100%;
  background:#000; color:var(--ink);
  font-family: var(--mono);
  overflow:hidden;
  -webkit-font-smoothing:antialiased;
}

#stage{
  position:fixed; inset:0;
  overflow:hidden;
  background:#000;
  transition: background .4s ease;
}

#scaler{
  position:absolute; top:50%; left:50%;
  width:2800px; height:1575px;
  transform-origin:center center;
}

.slide{
  position:absolute; inset:0;
  width:2800px; height:1575px;
  overflow:hidden;
  will-change: transform;
  transform: translateY(100%);
  z-index:1;
}
.slide.is-current{ transform: translateY(0%); z-index:3; }
.slide.is-prev{ transform: translateY(-100%); z-index:2; }
.slide.is-instant{ transition:none !important; }
.slide:not(.is-instant){ transition: transform 900ms cubic-bezier(.65,0,.2,1); }

#redflash{
  position:absolute; inset:0;
  width:2800px; height:1575px;
  background: var(--red);
  transform: translateY(100%);
  z-index:2;
  pointer-events:none;
}
#redflash.go{ transition: transform 620ms cubic-bezier(.7,0,.25,1); transform: translateY(0%); }
#redflash.leave{ transition: transform 520ms cubic-bezier(.6,0,.3,1) 260ms; transform: translateY(-100%); }

.slide-inner{ position:absolute; inset:0; }

/* ---------- generic reveal utility classes ---------- */
.rv{ opacity:0; }
.rv-up{ opacity:0; transform: translateY(40px); }
.rv-left{ opacity:0; transform: translateX(-50px); }
.rv-right{ opacity:0; transform: translateX(50px); }
.rv-scale{ opacity:0; transform: scale(.92); }

.is-current .rv{ animation: fadeIn .7s ease forwards; }
.is-current .rv-up{ animation: upIn .8s cubic-bezier(.2,.8,.2,1) forwards; }
.is-current .rv-left{ animation: leftIn .8s cubic-bezier(.2,.8,.2,1) forwards; }
.is-current .rv-right{ animation: rightIn .8s cubic-bezier(.2,.8,.2,1) forwards; }
.is-current .rv-scale{ animation: scaleIn .9s cubic-bezier(.2,.8,.2,1) forwards; }

@keyframes fadeIn{ to{ opacity:1; } }
@keyframes upIn{ to{ opacity:1; transform:translateY(0);} }
@keyframes leftIn{ to{ opacity:1; transform:translateX(0);} }
@keyframes rightIn{ to{ opacity:1; transform:translateX(0);} }
@keyframes scaleIn{ to{ opacity:1; transform:scale(1);} }

.d1{ animation-delay:.05s !important; }
.d2{ animation-delay:.35s !important; }
.d3{ animation-delay:.65s !important; }
.d4{ animation-delay:.95s !important; }
.d5{ animation-delay:1.25s !important; }
.d6{ animation-delay:1.55s !important; }

/* ---------- corner logo mark (present, fixed, on every inner page) ----------
   Exact vector geometry copied from the source PDF's own 9-square mark
   (page.rects on any inner page) — never move the squares relative to
   each other; only this box's own placement is uniform (top:50/right:50).
   Colour swaps via currentColor so the same markup works on red or black
   backgrounds, same .mark.light/.mark.dark convention as before. */
.mark{
  position:absolute; top:50px; right:50px; width:66.5px; height:41.6px;
  z-index:5; color:#000;
}
.mark svg{ width:100%; height:100%; display:block; }
.mark.dark{ color:#000; }
.mark.light{ color:#fff; }
/* single fixed instance, sibling of every .slide inside #scaler — stays
   pinned to the canvas (scales with it) but never moves with a slide's
   own enter/exit transform; only its colour switches, in script.js.
   Now a real link to the homepage (opens in a new tab), so it needs
   pointer-events back on and a cursor/hover to read as clickable. */
#fixedmark{ z-index:20; cursor:pointer; }
#fixedmark:hover{ opacity:.7; }

/* keyart-netflix's 5 consecutive gallery pages (26-30) repeat the same
   "KEY ART & VISUAL SYSTEMS" label — client wants it to stay visually
   fixed while paging through them rather than sliding off with each
   page transition. Same trick as #fixedmark: a single instance that is
   a sibling of every .slide (so it never gets the slide's own enter/exit
   transform), toggled on/off in script.js; the per-slide copies are
   hidden only on those 5 pages so nothing doubles up. */
[data-id="keyart-netflix-gallery-1"] .gal-label,
[data-id="keyart-netflix-gallery-2"] .gal-label,
[data-id="keyart-netflix-gallery-3"] .gal-label,
[data-id="keyart-netflix-gallery-4"] .gal-label,
[data-id="keyart-netflix-gallery-5"] .gal-label{ visibility:hidden; }
#fixedprojtitle{
  position:absolute; top:52.4px; left:50px; font-family:var(--mono); color:var(--red);
  font-size:30px; z-index:20; text-transform:uppercase; display:none; pointer-events:none;
  letter-spacing:0;
}

/* ---------- page-eyebrow label ---------- */
.eyebrow{
  font-family:var(--mono); font-size:20px; letter-spacing:.12em;
  text-transform:uppercase; color:var(--red);
}

/* progress dots */
#nav{
  position:fixed; right:22px; top:50%; transform:translateY(-50%);
  z-index:50; display:flex; flex-direction:column; gap:7px;
}
#nav .dot{ width:4.2px; height:4.2px; border-radius:0; background:rgba(255,255,255,.25); transition:.3s; cursor:pointer; }
#nav .dot.active{ width:6px; background:#fff; height:18px; border-radius:0; }

#pagenum{
  position:fixed; left:30px; bottom:20px; z-index:50;
  font-family:var(--mono); font-size:10.8px; color:rgba(255,255,255,.55);
  letter-spacing:.1em;
}

#hint{ display:none; }

/* =========================================================
   PAGE 01 — COVER  (exact coords from PDF: pt == px on 2800x1575)
   ========================================================= */
.p-cover{ background:#000; }
.cover-name{
  position:absolute; top:50px; left:110px; right:110px; height:253px;
  display:flex; justify-content:space-between; align-items:flex-start;
}
.cover-name img{
  height:253px; width:auto; display:block;
  transform: translate(var(--crop-x,0px), var(--crop-y,0px)) scale(var(--crop-s,1));
}
.cover-name .n1, .cover-name .n2{ opacity:0; }
.is-current .cover-name .n1,
.is-current .cover-name .n2{ animation: coverName 1s cubic-bezier(.2,.8,.2,1) .2s forwards; }
@keyframes coverName{ from{opacity:0; transform:translateY(150px);} to{opacity:1; transform:translateY(0);} }
.cover-sub{
  position:absolute; top:125px; left:0; right:0; width:100%; text-align:center;
  font-family:var(--mono); font-size:80px; color:var(--offwhite); letter-spacing:0;
  opacity:0; z-index:2;
}
.is-current .cover-sub{ animation: fadeIn .7s ease .9s forwards; }
.cover-foot{
  position:absolute; left:110px; right:110px; top:1300px;
  display:flex; justify-content:space-between; align-items:flex-end;
  font-family:var(--mono); color:var(--offwhite); font-weight:100;
  opacity:0;
}
.is-current .cover-foot{ animation: upIn .8s ease 1.3s forwards; }
.cover-foot .role{ font-size:80px; letter-spacing:0; font-weight:100; }
.cover-foot .mail{ font-size:30px; color:var(--offwhite); margin-top:10px; opacity:.75; font-weight:300; text-transform:uppercase; }
.cover-foot .right{ text-align:right; }
/* .right .mail is a real link (site URL) — keep it looking like the plain
   text it always was, same treatment as .close-site on the closing page */
.cover-foot .right .mail{ text-decoration:none; display:inline-block; cursor:pointer; }
.cover-foot .right .mail:hover{ opacity:.5; }
.cover-logo{
  position:absolute; left:50%; top:1312px; transform:translateX(-50%);
  width:105.6px; height:66px; color:var(--red);
  opacity:0;
}
.cover-logo svg{ width:100%; height:100%; display:block; }
.cover-logo svg rect{ opacity:0; }
/* same 1.3s delay as .cover-foot so the logo appears together with
   ART DIRECTOR / BRAND DESIGNER, then the same assemble/hold/disassemble
   loop used on the closing page (logoAssemble, defined below) */
.is-current .cover-logo{ animation: fadeIn .8s ease 1.3s forwards; }
.is-current .cover-logo svg rect{ animation: logoAssemble 3.08s ease-in-out infinite; }

/* =========================================================
   PAGE 02 — SERVICE epigraph (true PDF page 3)
   Static scattered keyword layout at exact PDF coordinates, plus the
   PDF's own 7 small black square decorations — replaces the old
   horizontal marquee.
   ========================================================= */
.p-service{ background:var(--red); }
.svc-kw{
  position:absolute; font-family:var(--display); color:var(--ink);
  font-size:150px; letter-spacing:-.02em; line-height:1; white-space:nowrap;
}
.svc-word{
  position:absolute; font-family:var(--mono); font-weight:100; color:var(--offwhite);
  font-size:60px; letter-spacing:.1em; z-index:3; white-space:nowrap;
}
/* rows drift slightly sideways, alternating direction row by row.
   Each word's travel distance is set per-element via --dx (see index.html)
   rather than a single shared amount — every word's own position/width
   means a different amount of room before it would hit the canvas edge,
   so each one is tuned to get close to (but never cross) that edge. */
.is-current .svc-kw{
  animation: upIn .8s cubic-bezier(.2,.8,.2,1) forwards, svcDrift 3s ease-in-out infinite alternate;
}
@keyframes svcDrift{ from{ translate:0 0; } to{ translate:var(--dx,0px) 0; } }

/* =========================================================
   PAGE 03 — SELECTED WORK floating collage (true PDF page 4)
   images placed at exact PDF bounding boxes
   ========================================================= */
.p-selwork{ background:var(--red); }
.sw-tile{
  position:absolute; overflow:hidden; background:#000;
}
.sw-tile img{ width:100%; height:100%; object-fit:cover; display:block; }
.is-current .sw-tile{ animation: tileIn .9s ease var(--ti,0s) both; }
@keyframes tileIn{ from{opacity:0; transform:scale(.7);} to{opacity:1; transform:scale(1);} }
/* the ambient float/zoom pulse AND the mouse-linked parallax both live on
   the <img> itself, never on the tile div: the div only ever scales <=1
   (tileIn's entrance), so it can never visually bleed past its own box
   into a neighboring tile — the img's zoom/pan is safely clipped by the
   tile's own overflow:hidden. Parallax offset (--mx/--my, set by script.js)
   is folded into the same keyframes via calc() so the two effects never
   fight over the 'transform' property. The 1.12 base scale gives enough
   bleed for the pan to move within without exposing the tile's edges. */
.sw-tile img, .fin-tile img{
  transform: scale(calc(1.12 * var(--crop-s,1))) translate(calc(var(--mx,0px) + var(--crop-x,0px)), calc(var(--my,0px) + var(--crop-y,0px)));
  transition: transform .2s ease-out;
}
.is-current .sw-tile img{ animation: floatZoomImg var(--fz,7s) ease-in-out var(--fd,0s) infinite; }
@keyframes floatZoomImg{
  0%,100%{ transform:scale(calc(1.12 * var(--crop-s,1))) translate(calc(var(--mx,0px) + var(--crop-x,0px)), calc(var(--my,0px) + var(--crop-y,0px))); }
  50%{ transform:scale(calc(1.19 * var(--crop-s,1))) translate(calc(var(--mx,0px) - 4px + var(--crop-x,0px)), calc(var(--my,0px) + 6px + var(--crop-y,0px))); }
}
.sw-title{
  position:absolute; text-align:center;
  font-family:var(--mono); color:var(--ink); font-size:140px; line-height:1.04; text-transform:uppercase;
  font-weight:100; z-index:4; letter-spacing:-0.06em;
}
/* small black square decorations copied from the PDF (selected-work + final page) */
.deco-sq{ position:absolute; background:#000; z-index:2; }

/* =========================================================
   PROJECT INTRO pattern — block L (title+eyebrow+tag) then R (desc)
   exact per-project coords supplied inline by the generator
   ========================================================= */
.p-intro{ background:#000; }
.in-eyebrow{ position:absolute; font-family:var(--mono); color:var(--red); font-size:30px; letter-spacing:0; font-weight:300; }
.in-title{
  position:absolute; font-family:var(--display); color:var(--red);
  font-size:140px; line-height:1; margin:0; font-weight:400;
}
.in-tag{ position:absolute; font-family:var(--mono); color:var(--red); font-size:20px; letter-spacing:.05em; font-weight:300; }
.in-descwrap{ position:absolute; top:114px; left:1415px; width:993.8px; z-index:2; }
.in-desc{
  font-family:var(--mono); color:var(--offwhite); font-size:27px; line-height:1.5; margin:0; font-weight:300;
  text-align:justify;
}
.in-cta{
  display:inline-block; margin-top:44px; font-family:var(--mono); color:var(--red);
  font-size:20px; letter-spacing:.05em; text-decoration:none; border-bottom:1px solid var(--red);
  padding-bottom:4px; cursor:pointer;
}
.in-cta:hover{ opacity:.75; }
.in-imgs .im{ position:absolute; overflow:hidden; background:#000; }
.in-imgs .im img{ width:100%; height:100%; object-fit:cover; display:block; }
.in-imgs .im::after{
  content:''; position:absolute; inset:0; background:var(--red);
  transform-origin:top; transform:scaleY(1);
}
.is-current .in-imgs .im::after{ animation: maskDown .99s cubic-bezier(.7,0,.15,1) both; animation-delay: var(--md,0s); }
/* leaving a slide (either direction) must never re-cover its images with
   the red mask — it should only ever play on arrival */
.is-prev .in-imgs .im::after,
.is-exiting .in-imgs .im::after{ transform:scaleY(0); }
.is-current .in-imgs .im img{ animation: imgSettle 1.08s ease both; animation-delay: var(--md,0s); }

/* =========================================================
   GALLERY grids (project detail pages) — exact PDF image
   placement + mask reveal
   ========================================================= */
.p-gallery{ background:#000; }
.gal-label{ position:absolute; top:52.4px; left:50px; font-family:var(--mono); color:var(--red); font-size:30px; z-index:3; letter-spacing:0; }
.gal-sublabel{
  position:absolute; font-family:var(--mono); color:#fff; font-size:20px; letter-spacing:.05em;
  text-shadow:0 2px 10px rgba(0,0,0,.85); z-index:3;
}
.gal-grid{ position:absolute; inset:0; }
.gal-grid .cell{ position:absolute; overflow:hidden; background:#000; }
.gal-grid .cell img{ width:100%; height:100%; object-fit:cover; display:block; }
.gal-grid .cell::after{
  content:''; position:absolute; inset:0; background:var(--red);
  transform-origin:top; transform:scaleY(1);
}
.is-current .gal-grid .cell::after{ animation: maskDown .99s cubic-bezier(.7,0,.15,1) both; animation-delay: var(--md,0s); }
/* leaving a slide (either direction) must never re-cover its images with
   the red mask — the mask should only ever play on arrival, so a
   departing slide just keeps its images visible while it slides off-screen */
.is-prev .gal-grid .cell::after,
.is-exiting .gal-grid .cell::after{ transform:scaleY(0); }
@keyframes maskDown{ from{ transform:scaleY(1); transform-origin:top;} to{ transform:scaleY(0); transform-origin:top; } }
.is-current .gal-grid .cell img{ animation: imgSettle 1.08s ease both; animation-delay: var(--md,0s); }
@keyframes imgSettle{
  from{ transform:scale(calc(1.12 * var(--crop-s,1))) translate(var(--crop-x,0px), var(--crop-y,0px)); }
  to{ transform:scale(var(--crop-s,1)) translate(var(--crop-x,0px), var(--crop-y,0px)); }
}

/* =========================================================
   PAGE — BIO (true PDF page 2, new)
   ========================================================= */
.p-bio{ background:var(--red); }
.bio-head{
  position:absolute; left:277.5px; top:279.2px; margin:0; width:1700px;
  font-family:var(--mono); color:var(--ink); font-size:80px; line-height:1.2; font-weight:100;
}
.bio-sq{ position:absolute; background:#000; z-index:2; }
.bio-sq1{ left:286.1px; top:691.2px; width:51.1px; height:51.1px; }
.bio-sq2{ left:890.8px; top:1285.3px; width:30.5px; height:30.5px; }
.is-current .bio-sq{ animation: bioSqPulse 2s ease-in-out 1.5s infinite; }
@keyframes bioSqPulse{ 0%,100%{ opacity:1; } 50%{ opacity:.15; } }
.bio-photo{ position:absolute; overflow:hidden; background:#000; }
.bio-photo img{
  width:100%; height:100%; object-fit:cover; display:block;
  transform: translate(var(--crop-x,0px), var(--crop-y,0px)) scale(var(--crop-s,1));
}
.bio-photo .pixel-reveal{
  position:absolute; inset:0; display:grid;
  grid-template-columns:repeat(8,1fr); grid-template-rows:repeat(8,1fr);
  pointer-events:none;
}
.bio-photo .pixel-reveal i{ background:var(--red); opacity:1; }
.is-current .bio-photo .pixel-reveal i{ animation:pxOut .51s ease-out forwards; animation-delay:calc(var(--dl) * 2); }
@keyframes pxOut{ to{ opacity:0; } }
.bio-copy{
  position:absolute; left:968px; top:744.1px; width:1576px;
  font-family:var(--mono); color:var(--offwhite); font-size:31px; line-height:1.5; font-weight:100;
  text-align:justify;
}
.bio-copy p{ margin:0 0 30px; }
.bio-copy p:last-child{ margin-bottom:0; }

/* =========================================================
   PAGE — FINAL "discover more work" collage (true PDF page 33)
   Same collage mechanic as SELECTED WORK, but tiles slowly zoom OUT
   (opposite direction from floatZoom's zoom-in pulse).
   ========================================================= */
.p-final{ background:var(--red); }
.fin-tile{ position:absolute; overflow:hidden; background:#000; }
.fin-tile img{ width:100%; height:100%; object-fit:cover; display:block; }
.is-current .fin-tile{ animation: tileIn .9s ease var(--ti,0s) both; }
.is-current .fin-tile img{ animation: zoomOutLoopImg var(--fz,7s) ease-in-out var(--fd,0s) infinite; }
@keyframes zoomOutLoopImg{
  0%,100%{ transform:scale(calc(1.19 * var(--crop-s,1))) translate(calc(var(--mx,0px) + var(--crop-x,0px)), calc(var(--my,0px) + var(--crop-y,0px))); }
  50%{ transform:scale(calc(1.12 * var(--crop-s,1))) translate(calc(var(--mx,0px) + var(--crop-x,0px)), calc(var(--my,0px) + var(--crop-y,0px))); }
}
.fin-link{ display:contents; }
.fin-title{
  position:absolute; font-family:var(--mono); color:var(--ink); font-size:140px; line-height:1.04;
  text-transform:uppercase; text-decoration:underline; text-decoration-thickness:4px;
  text-underline-offset:14px; z-index:4;
  font-weight:100; cursor:pointer; letter-spacing:-0.06em;
}
.fin-link:hover .fin-title{ opacity:.75; }

/* =========================================================
   PAGE — CLOSING (true PDF page 34, new)
   ========================================================= */
.p-close{ background:var(--red); }
.close-kicker{
  position:absolute; left:515px; right:485px; font-family:var(--mono); color:var(--offwhite);
  font-size:46.5px; font-weight:100; white-space:nowrap;
  text-align:justify; text-align-last:justify;
}
.close-mail{
  position:absolute; left:510px; right:510px; font-family:var(--display); color:var(--ink);
  font-size:140px; line-height:1; white-space:nowrap;
}
.close-site{
  position:absolute; left:510px; right:510px; text-align:center;
  font-family:var(--mono); color:var(--ink); font-size:30px; letter-spacing:.05em;
  text-decoration:none; cursor:pointer;
}
.close-site:hover{ opacity:.75; }
.close-logo{
  position:absolute; left:50%; top:1419.97px; transform:translateX(-50%);
  width:88px; height:55px; color:#000;
}
.close-logo svg{ width:100%; height:100%; display:block; }
.close-logo svg rect{ opacity:0; }
.is-current .close-logo svg rect{ animation: logoAssemble 4.4s ease-in-out infinite; }
@keyframes logoAssemble{
  0%{ opacity:0; }
  5%{ opacity:1; }
  64%{ opacity:1; }
  69%{ opacity:0; }
  100%{ opacity:0; }
}

/* =========================================================
   EDITOR UI — added on top of the generated site so every
   text block and image/video can be edited in place.
   ========================================================= */
body:not(.edit-mode) .editor-ui{ display:none !important; }

.ed-toolbar{
  position:fixed; right:20px; bottom:20px; z-index:9999;
  display:flex; flex-direction:column; align-items:flex-end; gap:8px;
  font-family:var(--mono); font-size:13px;
}
.ed-toolbar button{
  font-family:var(--mono); font-size:12px; letter-spacing:.05em; text-transform:uppercase;
  background:#000; color:#f4efe9; border:1px solid rgba(244,239,233,.3);
  padding:10px 16px; cursor:pointer; border-radius:2px; white-space:nowrap;
}
.ed-toolbar .ed-toggle{ background:var(--red); border-color:var(--red); color:#fff; display:block !important; }
.ed-toolbar button:hover{ opacity:.85; }
.ed-toolbar-extra{ display:flex; flex-direction:column; gap:6px; align-items:flex-end; }
body:not(.edit-mode) .ed-toolbar-extra{ display:none !important; }

.edit-mode [data-edit="text"]{ cursor:text; }
.edit-mode [data-edit="text"]:hover,
.edit-mode [data-edit="text"]:focus{ outline:1px dashed var(--red); outline-offset:6px; }

.edit-mode [data-edit="media"]{ cursor:default; }
.cover-name .n1, .cover-name .n2{ position:relative; }
.media-controls{
  position:absolute; inset:0; display:none; align-items:center; justify-content:center; gap:8px;
  background:rgba(0,0,0,.45); z-index:5;
}
.edit-mode [data-edit="media"]:hover .media-controls{ display:flex; }
/* videos keep the Replace/Crop buttons reachable on hover (crop needs a way
   in), but skip the dark scrim so the video itself stays clearly visible
   underneath — client wants to see the video, not lose the controls. */
.edit-mode [data-edit="media"]:has(video) .media-controls{ background:transparent; }
.media-controls button{
  font-family:var(--mono); font-size:16px; text-transform:uppercase; letter-spacing:.05em;
  background:#fff; color:#000; border:none; padding:10px 18px; cursor:pointer; border-radius:2px;
}
.media-controls button:hover{ background:var(--red); color:#fff; }

.mc-link{
  display:inline-block; margin-left:12px; font-family:var(--mono); font-size:18px;
  background:none; border:1px solid var(--red); color:var(--red); padding:4px 10px; cursor:pointer;
}
.mc-link:hover{ background:var(--red); color:#fff; }

[data-edit="media"].cropping{ outline:3px solid var(--red); outline-offset:-3px; }
[data-edit="media"].cropping img, [data-edit="media"].cropping video{ cursor:grab; }

.ed-crop-hud{
  position:fixed; left:50%; bottom:28px; transform:translateX(-50%); z-index:9999;
  background:#000; color:#f4efe9; font-family:var(--mono); font-size:14px;
  padding:12px 20px; border-radius:2px; display:flex; align-items:center; gap:14px;
}
.ed-crop-hud button{
  font-family:var(--mono); background:var(--red); color:#fff; border:none; padding:6px 14px; cursor:pointer;
}

.ed-modal-overlay{
  position:fixed; inset:0; background:rgba(0,0,0,.6); z-index:10000;
  display:flex; align-items:center; justify-content:center;
}
.ed-modal{
  background:#f4efe9; color:#000; width:min(560px,90vw); max-height:85vh; overflow:auto;
  padding:32px; font-family:var(--mono); border-radius:2px;
}
.ed-modal h3{ margin:0 0 20px; font-size:20px; text-transform:uppercase; letter-spacing:.05em; }
.ed-modal label{ display:block; margin-bottom:16px; font-size:12px; text-transform:uppercase; letter-spacing:.05em; }
.ed-modal input, .ed-modal textarea{
  display:block; width:100%; margin-top:6px; font-family:var(--mono); font-size:14px;
  padding:10px; border:1px solid #000; background:#fff; box-sizing:border-box;
}
.ed-modal-actions{ display:flex; justify-content:flex-end; gap:10px; margin-top:8px; }
.ed-modal-actions button{
  font-family:var(--mono); font-size:13px; text-transform:uppercase; padding:10px 20px; cursor:pointer;
  border:1px solid #000; background:#fff; color:#000;
}
.ed-modal-actions .ed-submit{ background:var(--red); border-color:var(--red); color:#fff; }

/* Add-project modal: gallery layout picker (real templates + "free" hand-draw) */
.ed-modal.ed-modal-wide{ width:min(900px,94vw); }
.ed-tpl-grid{ display:grid; grid-template-columns:repeat(4,1fr); gap:10px; margin-top:8px; }
.ed-tpl-option{ cursor:pointer; border:2px solid #000; padding:4px; background:#fff; }
.ed-tpl-option.selected{ border-color:var(--red); }
.ed-tpl-thumb{ position:relative; width:100%; aspect-ratio:2800/1575; background:#111; overflow:hidden; }
.ed-tpl-box{ position:absolute; background:var(--red); opacity:.85; box-sizing:border-box; border:1px solid #000; }
.ed-tpl-thumb-free{ display:flex; align-items:center; justify-content:center; font-size:28px; color:#fff; }
.ed-tpl-name{ display:block; margin-top:4px; font-size:10px; letter-spacing:0; text-transform:none; line-height:1.3; }
.ed-free-wrap{ margin-top:-8px; margin-bottom:16px; }
.ed-free-hint{ font-size:11px; text-transform:none; letter-spacing:0; opacity:.7; margin:0 0 8px; }
.ed-free-stage{
  position:relative; width:100%; aspect-ratio:2800/1575; background:#111;
  outline:1px dashed #000; cursor:crosshair; user-select:none;
}
/* Safe-area guide: the outer bounding box actually used by every real
   gallery template (min left/top, max right/bottom across all 7 presets,
   in 2800x1575 canvas px — 48/114/2752/1529). Drawn as a ::before so it
   always sits under the hand-drawn boxes and survives freeStage.innerHTML
   being cleared/rebuilt every time a box is added or deleted. */
.ed-free-stage::before{
  content:'';
  position:absolute;
  left:1.714%; top:7.238%; right:1.714%; bottom:2.921%;
  border:1px dashed var(--red);
  pointer-events:none;
}
.ed-free-box{ position:absolute; background:rgba(199,44,32,.35); border:1px solid var(--red); box-sizing:border-box; cursor:pointer; }
.ed-free-box-ghost{ background:rgba(199,44,32,.5); border-style:dashed; pointer-events:none; }
.ed-free-clear{
  margin-top:8px; font-family:var(--mono); font-size:12px; text-transform:uppercase; letter-spacing:.05em;
  padding:8px 14px; border:1px solid #000; background:#fff; cursor:pointer;
}

/* Move-slide modal: drag-to-reorder project list */
.ed-move-hint{ margin:-8px 0 14px; }
.ed-move-list{ display:flex; flex-direction:column; gap:8px; }
.ed-move-row{
  display:flex; align-items:center; gap:12px; background:#fff; border:1px solid #000;
  padding:8px; box-sizing:border-box;
}
.ed-move-row.dragging{ box-shadow:0 6px 16px rgba(0,0,0,.35); background:#fdf7ee; }
.ed-move-handle{
  cursor:grab; font-size:20px; line-height:1; padding:0 6px; user-select:none; flex:0 0 auto;
}
.ed-move-row.dragging .ed-move-handle{ cursor:grabbing; }
.ed-move-thumb{ flex:0 0 auto; width:72px; height:44px; background:#111; overflow:hidden; }
.ed-move-thumb img{ width:100%; height:100%; object-fit:cover; display:block; }
.ed-move-thumb-empty{ background:#333; }
.ed-move-label{
  font-size:12px; text-transform:none; letter-spacing:0; line-height:1.35; flex:1 1 auto;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.ed-move-label strong{ display:block; font-size:10px; text-transform:uppercase; letter-spacing:.04em; opacity:.6; }

img[data-placeholder="1"]{ opacity:.5; }

.im video, .cell video, .sw-tile video, .fin-tile video, .bio-photo video{
  width:100%; height:100%; object-fit:cover; display:block;
  transform: translate(var(--crop-x,0px), var(--crop-y,0px)) scale(var(--crop-s,1));
}
.cover-name video{
  height:150px; width:auto; display:block;
  transform: translate(var(--crop-x,0px), var(--crop-y,0px)) scale(var(--crop-s,1));
}
