/* ============================================================================
   gallery-frame.css — the immersive gallery's framed-plate render.
   SHARED single source of truth, imported by BOTH the live portfolio gallery
   (portfolio/index.html, via the `pictures` symlink) and the admin frame-studio
   preview (admin/, as ../pictures/gallery-frame.css). Keep the two consumers
   pixel-identical: edit the plate's look HERE, never inline.

   THE MM MODEL (PASSEPARTOUT-MM.md; math in mat-geometry.js, DOM + vars in
   gallery-frame.js). A framed plate is a framer's stack of real millimetres —
   image ⊃ opening, opening + mat + moulding = outer frame — and the FRAME SIZES
   ITSELF around the art: the plate's aspect (--frame-ar) is the OUTER frame's.
   Every layer below is placed by percentages that gallery-frame.js derives from
   those mm, so the plate can be any pixel size with the same proportions.

   `.g-plate.is-framed` stacks, bottom → top:
     1. .g-mat        — the CSS passe-partout board: ONE continuous mat from the
                        wood's inner edge to the opening (never a second layer or
                        letterbox band). Its box is --mat-box-* (the moulding band,
                        tucked a little under the wood so no wall shows at the join).
                        Colour --mat-color (per-work gallery.matColor, else the
                        frame's), + a fine paper tooth + a top-lit gradient.
     2. .g-mount      — fills the mat box; the opening's containing block.
     3. .g-window     — the cut OPENING at --win-* (% of the mat box, from the mm
                        mat widths). overflow:hidden. Carries the bevel MAT LIP
                        (::before). Holds img.g-art = the FULL image rect at
                        --art-l/t/w/h (% of the opening): it extends UNDER the mat
                        by the overlap on each side (overlap ≥ 0), so the image is
                        never smaller than the opening — no band, ever.
     4. .g-moulding   — the photographed frame PNG as a CSS 9-SLICE border-image:
                        slice = --frame-slice (px in the PNG, config.frames.*.slice),
                        width = --mould-* (the mm moulding as % of the plate).
                        Corners stay photographic; the straight runs STRETCH, so
                        one PNG serves any aspect. (img.g-frame-img is only the
                        hidden loader that feeds --frame-src.)
   `.g-dim` is the wall's resting overlay.

   SHADOWS: the "hung on the wall" cast shadow is a box-shadow on .g-plate (the
   outer rectangle only). There is NO `filter: drop-shadow` anywhere on the frame
   or the art: a drop-shadow follows the alpha of whatever it's on — the moulding
   is a ring with a transparent centre — so it would cast the inner opening
   edge's shadow DOWN onto the cream mat, which reads as a grey "middle layer"
   band. Same for the art: a drop-shadow haloed the mat around it. The only
   in-opening shading is the mat lip (inset box-shadows over the art).

   UNFRAMED (`.g-plate.is-unframed`, gallery.frame:"none"): the plate IS the
   artwork (window inset 0), --frame-ar = the art's aspect, and the data-driven
   crop (--art-fit/pos/zoom/ox/oy) applies. Crop vars are inert on framed plates.
   --pan-x/--pan-y/--zoom are the live inspect knobs (both kinds).
   ========================================================================== */

/* the plate = the OUTER frame rectangle, sized from --frame-ar (the outer mm aspect for a framed work).
   The "hung on the wall" cast shadow lives HERE as a box-shadow (the plate's OUTER rectangle), NOT as a
   drop-shadow on the moulding: a drop-shadow follows the frame's alpha shape — a ring with a transparent
   window — so it also cast the inner opening edge's shadow DOWN onto the cream mat, which read as a grey
   "middle layer" band. A box-shadow casts only from the outer box, never inward. */
.g-plate{position:relative;
  --plate-w:min(var(--pw-vw,46vw),calc(var(--pw-vh,80vh) * var(--frame-ar,0.761)),var(--pw-max,560px));
  width:var(--plate-w);height:calc(var(--plate-w) / var(--frame-ar,0.761));
  box-shadow:0 36px 50px rgba(0,0,0,0.6),0 10px 18px rgba(0,0,0,0.5);}
/* MAT (passe-partout): a CSS board that IS the whole mat, from the wood's inner edge to the opening.
   The moulding is wood-only (the PNG's window is cut to the wood's inner edge), so this one continuous
   board shows from the wood lip to the artwork with no seam. Its box (--mat-box-*, % of the plate) is
   the moulding band tucked ~20% under the wood (set by gallery-frame.js), so the slice's anti-aliased
   inner edge never shows the wall through a hairline gap. */
.g-mat{position:absolute;z-index:1;
  left:var(--mat-box-l,4%);right:var(--mat-box-r,4%);top:var(--mat-box-t,4%);bottom:var(--mat-box-b,4%);
  background-color:var(--mat-color,#EAD1B6);
  /* TOP layer = a fine paper TOOTH (inline feTurbulence, desaturated + contrast-compressed),
     soft-light-blended into the cream so the board reads as cotton-rag MUSEUM MAT, not flat fill.
     BOTTOM layer = the gentle top-lit lighting gradient. Tile kept small → fine grain at any size. */
  background-image:
    url("data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20width%3D%27220%27%20height%3D%27220%27%3E%3Cfilter%20id%3D%27p%27%20x%3D%270%27%20y%3D%270%27%20width%3D%27100%25%27%20height%3D%27100%25%27%3E%3CfeTurbulence%20type%3D%27fractalNoise%27%20baseFrequency%3D%270.7%27%20numOctaves%3D%273%27%20seed%3D%277%27%20stitchTiles%3D%27stitch%27%2F%3E%3CfeColorMatrix%20type%3D%27saturate%27%20values%3D%270%27%2F%3E%3CfeComponentTransfer%3E%3CfeFuncR%20type%3D%27linear%27%20slope%3D%270.62%27%20intercept%3D%270.19%27%2F%3E%3CfeFuncG%20type%3D%27linear%27%20slope%3D%270.62%27%20intercept%3D%270.19%27%2F%3E%3CfeFuncB%20type%3D%27linear%27%20slope%3D%270.62%27%20intercept%3D%270.19%27%2F%3E%3C%2FfeComponentTransfer%3E%3C%2Ffilter%3E%3Crect%20width%3D%27100%25%27%20height%3D%27100%25%27%20filter%3D%27url%28%23p%29%27%2F%3E%3C%2Fsvg%3E"),
    linear-gradient(176deg,rgba(255,255,255,0.05),rgba(0,0,0,0.03) 62%,rgba(0,0,0,0.06));
  background-size:220px 220px,auto;
  background-blend-mode:soft-light,normal;
  /* seated in the wood rabbet: a top catch + a faint all-round inner vignette toward the wood */
  box-shadow:inset 0 1px 2px rgba(0,0,0,0.10),inset 0 0 26px -8px rgba(20,18,12,0.12);}
/* fills the mat box; the containing block for the opening. TRANSPARENT (the board shows through).
   (Legacy --mat-* vars may still be written here by applyMat() for old tooling; they are inert.) */
.g-mount{position:absolute;inset:0;z-index:1;background:transparent;}
/* the cut OPENING — placed by --win-l/r/t/b (% of the mat box), which gallery-frame.js derives from the
   mm mat widths. TRANSPARENT; overflow:hidden clips the image to the opening (the image runs on under
   the mat by the overlap). The opening carries a real bevel-cut MAT LIP (.g-window::before) so the cut
   edge reads as a raised board, not a flat sticker. */
.g-window{position:absolute;overflow:hidden;background:transparent;
  left:var(--win-l,0%);right:var(--win-r,0%);top:var(--win-t,0%);bottom:var(--win-b,0%);}
.g-frame.is-active .g-window{cursor:zoom-in;}
/* MAT LIP — the passe-partout's bevel-cut edge against the print (DESIGN.md "Mat Lip", kept a
   whisper). A real mat is ~1.4mm board cut at 45°, so it (a) reveals a fine LIGHTER core at the
   cut and (b) sits a hair PROUD of the print, casting a soft, top-weighted contact shadow onto it.
   Both are drawn as inset box-shadows on an overlay sitting OVER the art (z-index:2) — deliberately
   NOT a `filter:drop-shadow` (that follows the moulding's alpha and re-creates the old grey "band" on
   the mat) and NOT a flat outline (that read as a fake printed border). Hugs the opening exactly
   (inset:0 on .g-window); pointer-events:none so inspect zoom/pan still work; below the
   missing-label ::after (z-index:3). Tunable via the --lip-* vars. */
.g-window::before{content:"";position:absolute;inset:0;z-index:2;pointer-events:none;
  box-shadow:
    inset 0 0 0 1.5px var(--lip-core,rgba(251,250,247,0.55)),  /* bevel CORE reveal (lighter cut), reads on dark art */
    inset 0 3px 5px -2px var(--lip-cast,rgba(22,19,13,0.30)),   /* top cast shadow — mat sits proud, light from above */
    inset 2px 0 4px -2px rgba(22,19,13,0.16),                   /* lighter left cast (top-left key) */
    inset 0 0 9px rgba(22,19,13,0.10);}                         /* faint ambient occlusion around the opening */
/* artwork. UNFRAMED: fills the window; --art-fit/pos/zoom/ox/oy are a DATA-DRIVEN crop (the source
   file is never touched). TRANSPARENT background. NO drop-shadow: the artwork sits flush-mounted — a
   drop-shadow here cast a soft dark halo onto the mat (a second "middle layer" tint). */
.g-art{position:absolute;inset:0;width:100%;height:100%;object-fit:var(--art-fit,contain);
  object-position:var(--art-pos,center);display:block;background:transparent;z-index:1;
  transform:translate3d(calc(var(--art-ox,0%) + var(--pan-x,0px)),calc(var(--art-oy,0%) + var(--pan-y,0px)),0) scale(calc(var(--art-zoom,1) * var(--zoom,1)));
  transition:transform .26s var(--ease-out-expo);will-change:transform;user-select:none;-webkit-user-drag:none;}
/* FRAMED: the FULL image rect at --art-l/t/w/h (% of the opening) — it extends under the mat by the
   overlap, so left/top are ≤ 0 and w/h ≥ 100%. object-fit:cover so a stored imageMm whose aspect is a
   hair off the photo's crops a sliver rather than distorting or letterboxing. The crop vars are
   ignored here; only the inspect knobs (--pan-*, --zoom) transform it. max-width/height:none beats a
   host's global `img{max-width:100%}`, which would cap the rect at the opening and let cover crop it. */
.g-plate.is-framed .g-art{inset:auto;left:var(--art-l,0%);top:var(--art-t,0%);
  width:var(--art-w,100%);height:var(--art-h,100%);max-width:none;max-height:none;object-fit:cover;object-position:center;
  transform:translate3d(var(--pan-x,0px),var(--pan-y,0px),0) scale(var(--zoom,1));}
/* MOULDING — the photographed frame PNG as a CSS 9-SLICE border-image (no <img> stretching): the four
   corners are drawn 1:1-ish from the photo, the straight runs STRETCH along the grain (verified on both
   PNGs at outer aspects 0.6–1.4: the grain runs lengthwise, so stretching reads as a longer stick of
   the same wood, while `round` tiled visible seams/lighting repeats). border-width 0 + border-image-width
   in % (of this box = the plate) keeps it purely visual. NO drop-shadow (see header). */
.g-moulding{position:absolute;inset:0;z-index:3;pointer-events:none;box-sizing:border-box;
  border:0 solid transparent;
  border-image-source:var(--frame-src,none);
  border-image-slice:var(--frame-slice,40);
  border-image-width:var(--mould-t,4%) var(--mould-r,4%) var(--mould-b,4%) var(--mould-l,4%);
  border-image-repeat:stretch;}
/* the hidden loader for the frame PNG (gallery-frame.js hands its URL to --frame-src on load). */
.g-frame-img{display:none;}
/* UNFRAMED (gallery.frame:"none") — oil on canvas, digital prints: hung as-is, no frame, no mat.
   gallery-frame.js builds `.g-plate.is-unframed > .g-window > img.g-art` and sets the plate's own
   --frame-ar to the ARTWORK's aspect, so the window (inset 0) is the whole plate and the art fills
   it. Scaled a step below a framed plate: a framed plate's footprint includes wood + mat, so the
   same cap would make bare works read oversized next to their framed neighbours. The canvas sits a
   little closer to the wall than a frame → a slightly tighter cast shadow. The mat lip is replaced by
   a whisper of canvas edge: a hairline crisp edge + a faint top-lit falloff across the surface. */
.g-plate.is-unframed{
  --plate-w:calc(min(var(--pw-vw,46vw),calc(var(--pw-vh,80vh) * var(--frame-ar,0.8)),var(--pw-max,560px)) * var(--unframed-scale,0.86));
  box-shadow:0 28px 40px rgba(0,0,0,0.55),0 8px 14px rgba(0,0,0,0.45);}
.g-plate.is-unframed .g-window::before{
  box-shadow:inset 0 0 0 1px rgba(0,0,0,0.22),inset 0 1px 0 rgba(255,255,255,0.10);
  background:linear-gradient(172deg,rgba(255,255,255,0.045),transparent 36%,rgba(0,0,0,0.07));}
.g-dim{position:absolute;inset:0;z-index:4;pointer-events:none;background:rgba(9,7,4,0.6);opacity:1;transition:opacity .55s ease;}
.g-frame.is-active .g-dim{opacity:0;}
/* missing-image fallback: paint the message panel in the mat colour (was #ECD5BA) so
   it matches the now-continuous board instead of a different tan rectangle. */
.g-window.is-missing::after{content:attr(data-missing-label);position:absolute;inset:0;display:grid;place-items:center;padding:18px;z-index:3;
  text-align:center;font-family:var(--sans);font-size:12px;line-height:1.5;color:#6b6052;background:var(--mat-color,#EAD1B6);box-shadow:none;}
