/* ============================================================================
 * canvas-frame.css -- a photograph printed on canvas, wrapped round a wooden
 * stretcher bar, hanging on an evenly lit gallery wall.
 *
 * WHY THIS FILE EXISTS: it is the one and only definition of that look on this
 * site. Every page that shows a work this way LINKS this file (and, optionally,
 * canvas-frame.js) -- nobody copies the rules. Change a number here and every
 * wall, gallery and article picks it up. Tuned in tools/frame-lab/c4-final/
 * (rules: tools/frame-lab/BRIEF.md, sections 1-4, 9, 10). Variant B's taped
 * prints are the sibling module shared/print-tape.css.
 *
 * The look is "clean modern gallery": the photograph is the only thing with
 * colour. The canvas's white border is the wall's tone (+0-2 levels, a breath
 * warmer), so from a step back the work melts into the wall; up close it still
 * stands proud of it -- a lit top lip, a dark bottom lip, and the shadow a box
 * on a wall really throws under a row of wall-washers.
 *
 * ---------------------------------------------------------------------------
 * USAGE
 *
 *   <link rel="stylesheet" href="…/shared/canvas-frame.css">
 *   <script src="…/shared/canvas-frame.js"></script>        (optional: lamp-on fade)
 *
 *   <section class="cf-wall">              one lit wall (any element, any layout)
 *     …your own markup and layout…
 *       <div class="cf" style="width: 480px">        one canvas; its width is yours
 *         <img class="cf-print" src="…" alt="…">     the photograph
 *       </div>
 *   </section>
 *   <script>CanvasFrame.ready(document.querySelector(".cf-wall"));</script>
 *
 *   variable     set on              default                       meaning
 *   --cf-wall    .cf-wall / above    var(--wall, #f3f3f1)          the lit wall's colour (any colour,
 *                                                                  light or dark); every tone of the
 *                                                                  canvas and its shadow is computed
 *                                                                  from it (see TONES)
 *   --cf-depth   .cf-wall            clamp(4.5px, 0.55vw, 8px)     stretcher depth (px): the shadow's
 *                                                                  length and softness, one per wall
 *   --cf-reach   .cf-wall (READ)     2.4 x --cf-depth, in px       how far the shadow reaches below a
 *                                                                  canvas's bottom edge: leave this
 *                                                                  much room under the lowest row.
 *                                                                  Read it with CanvasFrame.reach(wall)
 *                                                                  (a px number in any browser);
 *                                                                  getComputedStyle gives px only where
 *                                                                  @property is supported (elsewhere the
 *                                                                  unresolved calc: parseFloat -> NaN)
 *   --cf-mat     .cf / above         6%                            white border, equal on all four
 *                                                                  sides; % = of the canvas's width.
 *                                                                  A length or a % (0 = no border);
 *                                                                  a value of any other type falls
 *                                                                  back to 6%
 *   --cf-ar      .cf / above         the photo's own ratio         display ratio (e.g. 1.5 or 3/2);
 *                                                                  the photo is cropped to it (cover).
 *                                                                  Unset, the img's width/height
 *                                                                  attributes do NOT reserve space:
 *                                                                  set it wherever the layout must
 *                                                                  know a height before the photo
 *                                                                  loads (masonry, loading=lazy)
 *
 *   A wall label plaque -- a printed label on thin white board beside a work:
 *       <div class="cf cf-plaque"><div style="padding: …">…label text…</div></div>
 *   Same wall, light, lips, contact shadow and lamp fade as a canvas; its own
 *   face (paper white), no cloth, a shallow board. Size it like a canvas; pad
 *   an inner element, not the .cf. The RULES below apply to it unchanged.
 *   --cf-paper         .cf-plaque / above  #fbfbf9                 the board's face: an absolute colour
 *                                                                  (white board, not the wall's tone)
 *   --cf-plaque-depth  .cf-plaque / above  2.5px                   the board's depth: its shadow
 *
 *   JS: CanvasFrame.ready(wall)   lamps come on once (0.7 s fade) -- call after
 *                                 building the wall; repeat calls are no-ops.
 *                                 Also audits the wall once (see RULES)
 *       CanvasFrame.relight(wall) lamps off at once, then on again (call after
 *                                 re-filtering / re-opening the wall)
 *       CanvasFrame.reach(wall)   --cf-reach in px, as a number, in any browser
 *                                 (0 if the wall has none)
 *   The dark state is .cf-wall.cf-dark, added only by canvas-frame.js. No JS,
 *   or prefers-reduced-motion -> the lamps are simply on.
 *   A .cf-wall inside another (a band in another colour) is on the outer
 *   wall's circuit: one room, one switch. ready / relight on the outer wall
 *   dim and light the band's canvases too; ready / relight on the band alone
 *   switch only the band.
 *
 * RULES FOR THE PAGE (what keeps this module working anywhere)
 *   - .cf sits somewhere inside a .cf-wall. .cf-wall sets isolation: isolate,
 *     so every shadow on the wall paints under every canvas on it. Neither a
 *     .cf nor anything between it and its wall may form a stacking context,
 *     or a shadow can land on a neighbouring canvas: no transform, translate,
 *     scale, rotate, perspective, opacity < 1, filter, backdrop-filter,
 *     isolation: isolate, mix-blend-mode, clip-path, mask, contain: paint /
 *     layout / strict / content, content-visibility: auto, container-type,
 *     position: fixed / sticky, z-index on a positioned element or on a flex /
 *     grid item, or will-change naming any of these. The wall itself, and
 *     anything above it, may do all of that.
 *   - The page paints the wall (its background); this file never does. It only
 *     reads --cf-wall to derive tones. Paint it on the .cf-wall element itself
 *     or on something above it -- NEVER on an element between the wall and a
 *     .cf: the shadows sit at the very bottom of the wall's stacking context,
 *     so any visible background, border or box-shadow in between (even one in
 *     the wall's own colour) covers them, silently. Fully transparent ones are
 *     harmless. A band in another colour is its own .cf-wall with --cf-wall
 *     set to that colour (on the outer wall's lamp circuit, see USAGE).
 *   - canvas-frame.js's ready() warns in the console ("canvas-frame: ...", one
 *     line per offending element) about every mistake in these rules: a
 *     stacking context on a .cf or between it and the wall, a visible
 *     background / border / box-shadow in between, and a box that clips the
 *     shadows (overflow other than visible on a .cf; overflow hidden / clip
 *     in between that cuts the shadow of a canvas it fully shows). It checks
 *     only the wall's own canvases: a nested .cf-wall is checked by its own
 *     ready().
 *   - The module owns .cf's box (position, display, padding, border-radius,
 *     background) and .cf-print's size and margin. Size the canvas by giving
 *     .cf (or its container) a width; do not pad it or set its background.
 *     The border is the photo's margin, so --cf-mat % is of the canvas's own
 *     width however the canvas is sized (no ancestor width variable, no cqw).
 *     Load this file after your own CSS if old rules still touch those.
 *   - Shadows reach --cf-reach below and ~1.6 x --cf-depth beside each canvas
 *     and are only boxes, never layout: leave room (e.g. margin-bottom:
 *     var(--cf-reach) under the last row) and do not clip them.
 *   - Nothing here depends on an ancestor's opacity or on blend modes: the
 *     wall itself and anything above it (overlays, the page) may fade freely.
 *     To take one work away use `hidden`, or fade the whole wall -- never fade
 *     a single .cf or anything between it and the wall (first rule).
 *   - The `hidden` attribute works on .cf and .cf-print as on any element
 *     (the module's own display rules would otherwise override the UA's).
 *     To hide a whole work, hide .cf (or its container). Hiding only the
 *     .cf-print collapses the canvas to 0 high; its shadow and veil are then
 *     dropped too, so no lone shadow strip is left on the wall.
 *
 * ---------------------------------------------------------------------------
 * TONES: all from --cf-wall, W = its sRGB channels (0-255)
 *
 *   A shadow is the wall lit by less light, a lit edge the wall lit by more:
 *   both are a FACTOR on the wall, not a fixed colour. So every tone is the
 *   wall's own channels times a factor (relative colour syntax), and every
 *   translucent layer (shadow, lips, weave, veil) is a darker version of the
 *   wall at an alpha -- its effect scales with the wall. Result: the same
 *   contrasts in % on #fff, #f3f3f1, warm #e6e2da, mid grey or charcoal
 *   (tools/frame-lab/c4-final/measure.py --walls checks five).
 *     shade  W x (.288, .300, .332)    fill-only tone: the wall, dark, a touch
 *                                      cool; shadow / bottom roll / side rolls
 *                                      / veil are this at an alpha, so the
 *                                      cast core is ~ -5 % on ANY wall
 *     face   W x (1.007, 1.004, 1) x --_cf-lift, max 255
 *                                      cotton: the wall's level, a breath
 *                                      warm; --_cf-lift undoes the weave's
 *                                      mean darkening (make-textures.py)
 *     lip    W x 1.02 + (7, 4.4, -1.8), max 255
 *                                      top roll, facing the light: a warm,
 *                                      brighter line; never darker than the
 *                                      wall (on #fff it is #fff: nothing can
 *                                      be lighter than paper white)
 *   Browsers without relative colours (Chrome < 119, Safari < 18, Firefox <
 *   128) get fixed mixes tuned for light walls (#ececea-#fafaf8).
 *   Keep the wall at or below ~#f8f8f6: on pure #fff the top lip has no
 *   headroom (nothing is lighter than paper white) and the canvas reads by
 *   its bottom lip and shadow alone.
 *
 * ---------------------------------------------------------------------------
 * PHYSICS (BRIEF.md 1-3, 9), d = --cf-depth
 *
 *   Object  a ~38 mm stretcher; the cloth rolls round its edges (r ~ 2-3 mm).
 *   Light   a row of wall-washers: wide across, ~30 deg off the wall from
 *           above, plus the white room's fill. The wall is lit evenly -- no
 *           per-work spot, so a dense salon wall has no overlapping pools.
 *           Lambert: wall and face cos60 = .50 (equal: border = wall), top roll
 *           cos30 = .87 (a warm bright lip, 1.5px), side rolls ~0 (fill only:
 *           a hair darker, 1px), bottom roll faces the floor (darkest, 1.25px,
 *           neutral-cool). Lip widths are CSS px: a 150px phone card shows
 *           the same lines as a 650px one.
 *   Shadow  for a line source parallel to the wall, every ray from a wall
 *           point climbs the same height per unit of depth, so the bottom
 *           edge casts a band of ONE length, L = d cot30 ~ 1.7 d, under every
 *           canvas on the wall whatever its size -- softened only by the
 *           fixtures' height. Across, the source is wide: a vertical edge
 *           blocks only the part of the row on its own side, so there is no
 *           hard side shadow, only a wide faint penumbra, and the bottom
 *           corners fade out softly. Nothing above the top edge: the light
 *           comes from above. The shadow is the wall lit by fill only, so it
 *           is clearly darker than the lit wall (~ -5 %), and near the contact
 *           the box also hides part of the room's fill (AO: ~ -9 %, within d).
 *           Tone: --_cf-shade, the wall's own colour darkened and a touch
 *           cooled (see TONES) -- grey, never black, never blue.
 *   Shape   alpha(x, y) = spread(x) * drop(y): one vertical gradient (the
 *           profile down the middle: contact, cast plateau, smooth tail; up
 *           the sides: the faint penumbra, rising to the contact value round
 *           the bottom corners) times one horizontal mask (the lateral
 *           profile, a smoothstep over 3.4 d: 0 at 1.6 d outside each side
 *           edge, ~.46 at the edge, 1 at 1.8 d inside). One layer, one mask:
 *           no tiles, so no seams; all translucent, so where neighbours'
 *           shadows overlap they simply deepen smoothly, with no edge. The
 *           stops are generated by
 *           tools/frame-lab/c4-final/make-textures.py (@gen blocks below):
 *           edit the constants there and re-run it, never the stops by hand.
 *   Cloth   a baked plain-weave tile (same script), 2 CSS px yarn pitch,
 *           black at low alpha only (so it scales with the face, never
 *           specks), its mean undone by --_cf-lift; painted on the border
 *           only (the photograph covers it).
 *
 * LAYERS (painted once; scrolling repaints nothing; no filter, no blur)
 *   z -1 in the wall   .cf::before  the shadow (gradient x mask)
 *   z auto, positioned .cf          face colour, weave, side / bottom / top lips
 *                      .cf-print    the photograph -- nothing is drawn over it
 *                      .cf::after   lamp-off veil (opacity 0 = not painted
 *                                   unless the wall is .cf-dark or fading)
 *   Lamp-on fade: only the lamp's own effects switch -- the shadow (opacity
 *   0 -> 1) and the veil (1 -> 0), both opacity-only. Only the lit state has a
 *   transition, so relight() snaps off and fades on.
 *
 * Do not hand-edit the @gen blocks (data URI, gradient stops): re-run
 *   python3 tools/frame-lab/c4-final/make-textures.py
 * ========================================================================== */

/* Registered so every canvas inherits the wall's resolved depth, and so
   getComputedStyle(wall) gives --cf-reach as plain px -- where @property is
   supported; CanvasFrame.reach(wall) gives it in any browser.
   --cf-mat is registered so a bare 0 means 0px, and a value of any other type
   falls back to 6%: unregistered, either one breaks the photo's width calc and
   the photo shows at its natural size, bursting out of the canvas. */
@property --cf-depth { syntax: "<length>"; inherits: true; initial-value: 8px; }
@property --cf-reach { syntax: "<length>"; inherits: true; initial-value: 0px; }
@property --cf-mat { syntax: "<length-percentage>"; inherits: true; initial-value: 6%; }

/* ---------- the wall ---------- */

:where(.cf-wall) { --cf-depth: clamp(4.5px, 0.55vw, 8px); }   /* :where -> any page rule overrides it */

.cf-wall {
  isolation: isolate;
  /* @gen:reach-begin (make-textures.py) */
  --cf-reach: calc(var(--cf-depth) * 2.4);
  /* @gen:reach-end */
}

/* ---------- the canvas ---------- */

.cf {
  --_cf-wall: var(--cf-wall, var(--wall, #f3f3f1));
  /* Fallback tones for browsers without relative colours: right for light
     walls only. The real ones (any wall) are in the @supports block below. */
  --_cf-shade: color-mix(in srgb, var(--_cf-wall) 25%, #0c101a);
  --_cf-face: color-mix(in srgb, var(--_cf-wall) 78%, #fffaf0);
  --_cf-lip: color-mix(in srgb, var(--_cf-wall) 20%, #fffcf4);
  --_cf-lip-w: 1.5px;
  --_cf-under: color-mix(in srgb, var(--_cf-shade) 19%, transparent);  /* bottom roll: faces the floor */
  --_cf-under-w: 1.25px;
  --_cf-roll: color-mix(in srgb, var(--_cf-shade) 3%, transparent);    /* side rolls: fill only */
  --_cf-veil: color-mix(in srgb, var(--_cf-shade) 2.5%, transparent);  /* lamp off: a breath, so an unlit work never reads as a grey card */
  --_cf-light: 0.7s;
  --_cf-ease: cubic-bezier(0.33, 0, 0.2, 1);
  /* @gen:profile-begin (make-textures.py) */
  /* REACH 2.4 d, cast 7.7 % to 0.96 d, contact +5.0 % over 1 d,
     sides 0.00 % at the edge, mask 1.6 d out / 1.8 d in */
  --_cf-reach: calc(var(--cf-depth) * 2.4);
  --_cf-side-top: calc(var(--cf-depth) * 1.4);
  --_cf-out: calc(var(--cf-depth) * 1.6);
  --_cf-drop:
    transparent calc(var(--cf-depth) * 0),
    color-mix(in srgb, var(--_cf-shade) 0.40%, transparent) calc(var(--cf-depth) * 0.2),
    color-mix(in srgb, var(--_cf-shade) 1.45%, transparent) calc(var(--cf-depth) * 0.4),
    color-mix(in srgb, var(--_cf-shade) 2.90%, transparent) calc(var(--cf-depth) * 0.6),
    color-mix(in srgb, var(--_cf-shade) 4.49%, transparent) calc(var(--cf-depth) * 0.8),
    color-mix(in srgb, var(--_cf-shade) 5.98%, transparent) calc(var(--cf-depth) * 1),
    color-mix(in srgb, var(--_cf-shade) 6.89%, transparent) calc(var(--cf-depth) * 1.15),
    color-mix(in srgb, var(--_cf-shade) 7.50%, transparent) calc(var(--cf-depth) * 1.3),
    color-mix(in srgb, var(--_cf-shade) 7.68%, transparent) calc(var(--cf-depth) * 1.4),
    color-mix(in srgb, var(--_cf-shade) 7.75%, transparent) calc(var(--cf-depth) * 1.5),
    color-mix(in srgb, var(--_cf-shade) 7.90%, transparent) calc(var(--cf-depth) * 1.6),
    color-mix(in srgb, var(--_cf-shade) 8.15%, transparent) calc(var(--cf-depth) * 1.7),
    color-mix(in srgb, var(--_cf-shade) 8.50%, transparent) calc(var(--cf-depth) * 1.8),
    color-mix(in srgb, var(--_cf-shade) 8.95%, transparent) calc(var(--cf-depth) * 1.9),
    color-mix(in srgb, var(--_cf-shade) 9.50%, transparent) calc(var(--cf-depth) * 2),
    color-mix(in srgb, var(--_cf-shade) 10.15%, transparent) calc(var(--cf-depth) * 2.1),
    color-mix(in srgb, var(--_cf-shade) 10.90%, transparent) calc(var(--cf-depth) * 2.2),
    color-mix(in srgb, var(--_cf-shade) 11.75%, transparent) calc(var(--cf-depth) * 2.3),
    color-mix(in srgb, var(--_cf-shade) 12.21%, transparent) calc(var(--cf-depth) * 2.35),
    color-mix(in srgb, var(--_cf-shade) 12.70%, transparent) calc(var(--cf-depth) * 2.4),
    color-mix(in srgb, var(--_cf-shade) 6.35%, transparent) calc(var(--cf-depth) * 2.65),
    transparent calc(var(--cf-depth) * 2.9),
    transparent calc(var(--cf-depth) * 3.15),
    transparent calc(var(--cf-depth) * 3.4),
    transparent calc(var(--cf-depth) * 3.65),
    transparent calc(var(--cf-depth) * 2.9),
    transparent calc(100% - var(--cf-depth) * 2),
    transparent calc(100% - var(--cf-depth) * 1.5),
    transparent calc(100% - var(--cf-depth) * 1),
    transparent calc(100% - var(--cf-depth) * 0.5),
    transparent calc(100% - var(--cf-depth) * 0);
  --_cf-spread:
    transparent calc(var(--cf-depth) * 0),
    rgb(0 0 0 / 0.0430) calc(var(--cf-depth) * 0.425),
    rgb(0 0 0 / 0.1562) calc(var(--cf-depth) * 0.85),
    rgb(0 0 0 / 0.3164) calc(var(--cf-depth) * 1.275),
    rgb(0 0 0 / 0.5000) calc(var(--cf-depth) * 1.7),
    rgb(0 0 0 / 0.6836) calc(var(--cf-depth) * 2.125),
    rgb(0 0 0 / 0.8438) calc(var(--cf-depth) * 2.55),
    rgb(0 0 0 / 0.9570) calc(var(--cf-depth) * 2.975),
    #000 calc(var(--cf-depth) * 3.4),
    #000 calc(100% - var(--cf-depth) * 3.4),
    rgb(0 0 0 / 0.9570) calc(100% - var(--cf-depth) * 2.975),
    rgb(0 0 0 / 0.8438) calc(100% - var(--cf-depth) * 2.55),
    rgb(0 0 0 / 0.6836) calc(100% - var(--cf-depth) * 2.125),
    rgb(0 0 0 / 0.5000) calc(100% - var(--cf-depth) * 1.7),
    rgb(0 0 0 / 0.3164) calc(100% - var(--cf-depth) * 1.275),
    rgb(0 0 0 / 0.1562) calc(100% - var(--cf-depth) * 0.85),
    rgb(0 0 0 / 0.0430) calc(100% - var(--cf-depth) * 0.425),
    transparent calc(100% - var(--cf-depth) * 0);
  /* @gen:profile-end */
  /* @gen:weave-begin (make-textures.py) */
  --_cf-lift: 1.0042;   /* 1 / (1 - mean weave alpha 0.421 %) */
  --_cf-weave: url("data:image/png;base64,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");
  /* @gen:weave-end */

  position: relative;
  display: flow-root;            /* keeps the photo's margin (the border) inside the canvas */
  box-sizing: border-box;
  padding: 0;
  border-radius: clamp(1px, calc(var(--cf-depth) * 0.18), 1.5px);   /* the cloth's roll round the bar */
  /* The lips are whole-box gradients with hard stops, not small tiles at
     100%: a tile's own pixel snapping can leave a 1 device px row of face
     colour under the bottom lip; a stop is measured from the snapped box. */
  background:
    /* top roll, then its light running 1px on into the face */
    linear-gradient(to bottom, var(--_cf-lip) var(--_cf-lip-w),
      color-mix(in srgb, var(--_cf-lip) 55%, transparent) var(--_cf-lip-w), transparent calc(var(--_cf-lip-w) + 1px)),
    /* bottom roll */
    linear-gradient(to top, var(--_cf-under) var(--_cf-under-w), transparent var(--_cf-under-w)),
    /* side rolls */
    linear-gradient(to right, var(--_cf-roll) 1px, transparent 1px,
      transparent calc(100% - 1px), var(--_cf-roll) calc(100% - 1px)),
    /* the cloth */
    var(--_cf-weave) 0 0 / 72px 72px,
    var(--_cf-face);
}

/* Tones derived from the wall (see TONES in the header). The test uses a
   calc on a channel: browsers whose channel keywords are not plain numbers
   (early Safari) fail it and keep the fallbacks above. */
@supports (color: rgb(from red calc(r + 1) g b)) {
  .cf {
    --_cf-shade: rgb(from var(--_cf-wall) calc(r * 0.288) calc(g * 0.300) calc(b * 0.332));
    --_cf-face: rgb(from var(--_cf-wall)
      min(255, r * 1.007 * var(--_cf-lift)) min(255, g * 1.004 * var(--_cf-lift)) min(255, b * var(--_cf-lift)));
    --_cf-lip: rgb(from var(--_cf-wall)
      min(255, r * 1.02 + 7) min(255, g * 1.02 + 4.4) min(255, b * 1.02 - 1.8));
  }
}

/* ---------- the wall label plaque (.cf.cf-plaque) ---------- */

/* A printed label on a thin board, hung beside a work: the same wall, the
   same wash of light, so the same lips, contact shadow and lamp fade -- only
   a different object. Paper white, not the wall's tone (a label is a sheet
   of white board; it never melts into the wall); smooth, so no cloth; ~5 mm
   thick, so its own shallow depth instead of the wall's stretcher depth. */
.cf.cf-plaque {
  --cf-depth: var(--cf-plaque-depth, 2.5px);
  --_cf-face: var(--cf-paper, #fbfbf9);
  --_cf-lip: color-mix(in srgb, var(--_cf-face) 35%, #fff);
  --_cf-weave: none;
  border-radius: 1px;            /* a cut board, not cloth rolled round a bar */
}
@supports (color: rgb(from red calc(r + 1) g b)) {
  /* The top edge faces the light: a breath brighter than the face itself. */
  .cf.cf-plaque {
    --_cf-lip: rgb(from var(--_cf-face) min(255, r * 1.02 + 3) min(255, g * 1.02 + 3) min(255, b * 1.02 + 3));
  }
}

.cf[hidden],
.cf-print[hidden] { display: none; }   /* `hidden` beats the module's display */

.cf-print {
  display: block;
  box-sizing: content-box;
  width: calc(100% - 2 * var(--cf-mat, 6%));
  max-width: 100%;               /* never binds (mat >= 0); caps the photo if the calc is invalid
                                    (a bad --cf-mat in a browser without @property) */
  height: auto;
  margin: var(--cf-mat, 6%);     /* % of the canvas's width on all four sides */
  padding: 0;
  border: 0;
  border-radius: 0;
  aspect-ratio: var(--cf-ar, auto);
  object-fit: cover;
}

/* ---------- the shadow ---------- */

/* Spans from --_cf-side-top below the top edge (nothing above the canvas) to
   --_cf-reach below the bottom edge, --_cf-out past each side. See PHYSICS. */
.cf::before {
  content: "";
  position: absolute;
  z-index: -1;
  top: var(--_cf-side-top);
  bottom: calc(var(--_cf-reach) * -1);
  left: calc(var(--_cf-out) * -1);
  right: calc(var(--_cf-out) * -1);
  pointer-events: none;
  background: linear-gradient(to top, var(--_cf-drop));
  -webkit-mask-image: linear-gradient(to right, var(--_cf-spread));
  mask-image: linear-gradient(to right, var(--_cf-spread));
  transition: opacity var(--_cf-light) var(--_cf-ease);
}

/* ---------- lamp off / on (canvas-frame.js) ---------- */

.cf::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  background: var(--_cf-veil);
  opacity: 0;
  transition: opacity var(--_cf-light) var(--_cf-ease);
}
/* Descendant, not child: a nested .cf-wall is on the outer wall's circuit. */
.cf-wall.cf-dark .cf::before { opacity: 0; transition: none; }
.cf-wall.cf-dark .cf::after { opacity: 1; transition: none; }

/* Only the photo hidden: the canvas is 0 high, so drop its shadow and veil
   rather than leave a lone strip on the wall (hide .cf to hide a work). */
.cf:has(> .cf-print[hidden])::before,
.cf:has(> .cf-print[hidden])::after { display: none; }

@media (prefers-reduced-motion: reduce) {
  .cf::before, .cf::after { transition: none; }
}
