/* ============================================================
 * Photo Effects — CSS-only recreations of a twelve-pack of
 * Photoshop photo-effect actions. Loaded only by the
 * /labs/photo-effects/ page (linked from the photo_effects
 * shortcode), NOT part of the site-wide Tailwind bundle.
 *
 * Structure of one plate, emitted identically for every effect
 * by templates/shortcodes/photo_effects.html:
 *
 *   .pe-box                      <- container-query context
 *     .pe-stage[data-effect]     <- 4:3 frame, blend isolation
 *       .pe-mix                  <- blend group; a filter here
 *         .pe-l1 … .pe-l6           applies to the composite
 *       .pe-o1, .pe-o2           <- overlays, above that filter
 *
 * Every layer already carries the photograph as its background
 * image; every layer but .pe-l1 starts hidden. An effect block
 * switches on the layers it needs and styles them. All effect
 * blocks below are scoped to their own [data-effect="…"] so the
 * twelve cannot interfere with each other.
 *
 * Distances, blur radii and pattern sizes are expressed in
 * container units (1cqw = 1% of the plate width) so a plate
 * renders as the same effect in the ~300px grid thumbnail and
 * in the ~1100px viewer.
 *
 * Use cqw only — never cqh/cqb. The container is typed
 * inline-size (it has to be: the plate's height comes from its
 * own aspect-ratio, so size containment would collapse it), and
 * block-axis container units silently fall back to the viewport
 * there. The frame is 4:3, so 1cqh = 0.75cqw.
 * ============================================================ */

.pe-box {
  container-type: inline-size;
}

.pe-stage {
  display: block; /* the markup uses <span> so a plate can live in a <button> */
  position: relative;
  aspect-ratio: 4 / 3;
  overflow: hidden;
  isolation: isolate;
  background: #000;
  /* Halftone cell size, shared by the screen-based effects. */
  --pe-cell: 1.1cqw;
  /* Fractal-noise tile used for film grain and paper fibre. */
  --pe-noise: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}

.pe-mix {
  position: absolute;
  inset: 0;
  isolation: isolate;
}

.pe-l,
.pe-over {
  position: absolute;
  inset: 0;
  display: none;
  background-image: var(--pe-img);
  /* Tiling by default: the photograph is drawn with background-size:
   * cover so repetition never shows, while a halftone or grain tile
   * that overrides background-size gets the repeat it needs without
   * having to remember to ask for it. */
  background-repeat: repeat;
  background-position: var(--pe-pos, 50% 50%);
  background-size: cover;
}

.pe-stage .pe-l1 {
  display: block;
}

/* ── gallery chrome ──────────────────────────────────────────
 * Deliberately plain: the plates are the only loud thing on the
 * page. Colours come from the site's token custom properties, so
 * the chrome follows the light/dark toggle for free.
 * ─────────────────────────────────────────────────────────── */

.pe-src,
.pe-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 8px 14px;
  border: 1px solid color-mix(in srgb, var(--color-ink) 20%, transparent);
  border-radius: 100px;
  background: transparent;
  color: var(--color-ink);
  font-family: "IBM Plex Mono", monospace;
  font-size: 12px;
  letter-spacing: 0.0975em;
  text-transform: uppercase;
  cursor: pointer;
  transition: background-color 150ms ease, color 150ms ease, border-color 150ms ease;
}

.pe-src:hover,
.pe-btn:hover {
  border-color: var(--color-primary);
  color: var(--color-primary);
}

.pe-src.is-active {
  background: var(--color-primary);
  border-color: var(--color-primary);
  color: var(--color-on-primary);
}

.pe-src--file {
  border-style: dashed;
}

#pe-file:focus-visible + .pe-src--file {
  outline: 2px solid var(--color-primary);
  outline-offset: 2px;
}

/* The gallery lives inside the article's .prose block, whose element
 * rules (ul, li, p, h2, pre) are plain descendant selectors that
 * Tailwind's .not-prose does not neutralise. Every chrome rule that
 * targets one of those elements is therefore written as
 * `.pe-root <selector>` so it outweighs `.prose <element>`. */

.pe-root .pe-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: 20px;
  margin: 0;
  padding: 0;
  list-style: none;
}

/* Twelve plates of stacked blurs, blends and contrast thresholds is a
 * lot of compositing. Skip the ones nobody is looking at. */
.pe-root .pe-grid > li {
  margin: 0;
  content-visibility: auto;
  contain-intrinsic-size: auto 420px;
}

.pe-tile {
  display: block;
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  text-align: left;
  cursor: zoom-in;
}

.pe-tile .pe-stage {
  display: block;
  border-radius: 10px;
}

/* The hover lift is the only motion in the chrome, and it is pure
 * decoration — the focus ring below is what actually communicates
 * state, so this can go away entirely. */
@media (prefers-reduced-motion: no-preference) {
  .pe-tile .pe-stage {
    transition: transform 200ms cubic-bezier(0.33, 1, 0.68, 1);
  }

  .pe-tile:hover .pe-stage,
  .pe-tile:focus-visible .pe-stage {
    transform: translateY(-2px) scale(1.01);
  }
}

.pe-tile:focus-visible {
  outline: none;
}

.pe-tile:focus-visible .pe-stage {
  outline: 2px solid var(--color-primary);
  outline-offset: 3px;
}

.pe-tile__meta {
  display: block;
  margin-top: 10px;
}

.pe-tile__num {
  color: var(--color-primary);
  margin-right: 8px;
}

/* The blue accent is 6.6:1 on the light canvas but only 2.8:1 on the
 * dark one, and these numbers are 12px — so on dark they step down to
 * a dimmed inverse ink instead. */
.dark .pe-tile__num,
.dark .pe-root .pe-viewer__num {
  color: color-mix(in srgb, var(--color-inverse-ink) 78%, transparent);
}

.pe-tile__title {
  font-weight: 500;
  font-size: 1rem;
  color: var(--color-ink);
}

.pe-tile__note {
  display: block;
  margin-top: 4px;
  font-size: 0.8rem;
  line-height: 1.45;
  color: var(--color-ink-muted);
  opacity: 0.85;
}

/* ── fullscreen viewer ─────────────────────────────────────── */

.pe-viewer {
  width: min(94vw, 1100px);
  max-width: none;
  max-height: 92vh;
  overflow: auto;
  padding: 20px;
  /* The dialog sits on a dimmed copy of the page, which on the dark
   * theme is the same near-black — the rim is what separates them. */
  border: 1px solid color-mix(in srgb, var(--color-ink) 22%, transparent);
  border-radius: 20px;
  background: var(--color-background);
  color: var(--color-ink);
  box-shadow: 0 24px 64px rgb(0 0 0 / 0.45);
}

.pe-viewer::backdrop {
  background: rgb(0 0 0 / 0.82);
}

.pe-viewer__head {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: start;
  gap: 8px 16px;
  margin-bottom: 16px;
}

.pe-root .pe-viewer__num {
  grid-column: 1;
  color: var(--color-primary);
  margin: 0;
}

.pe-root .pe-viewer__title {
  grid-column: 1;
  margin: 0;
  font-size: clamp(1.5rem, 1.1rem + 1.6vw, 2.25rem);
  font-weight: 500;
  letter-spacing: -0.01em;
  line-height: 1.1;
}

.pe-viewer__close {
  grid-column: 2;
  grid-row: 1 / span 2;
}

.pe-viewer__frame .pe-stage {
  border-radius: 15px;
}

.pe-viewer__foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  margin-top: 16px;
}

.pe-root .pe-viewer__note {
  flex: 1 1 320px;
  margin: 0;
  font-size: 0.95rem;
  line-height: 1.5;
  color: var(--color-ink-muted);
}

.pe-viewer__nav {
  display: flex;
  gap: 8px;
}

.pe-viewer__code {
  margin-top: 20px;
  border-top: 1px solid color-mix(in srgb, var(--color-ink) 12%, transparent);
  padding-top: 12px;
}

.pe-viewer__code summary {
  cursor: pointer;
  color: var(--color-ink-muted);
}

.pe-root .pe-viewer__code pre {
  margin: 12px 0 0;
  max-height: 40vh;
  overflow: auto;
  padding: 16px;
  border-radius: 10px;
  background: #282828;
  color: #f0f1fa;
  font-size: 12px;
  line-height: 1.6;
  white-space: pre;
}

/* No JavaScript: the plates still render, so drop the affordances
 * that would do nothing. */
.pe-root:not(.pe-ready) .pe-bar,
.pe-root:not(.pe-ready) .pe-viewer {
  display: none;
}

.pe-root:not(.pe-ready) .pe-tile {
  cursor: default;
}

/* ── the twelve effects ──────────────────────────────────────
 * Each block below is introduced by a `==== plate: <slug> ====`
 * marker. That marker is load-bearing: photo-effects.js fetches
 * this stylesheet and splits on it to show an effect's own rules
 * in the viewer, so the listing can never drift from what the
 * browser applies. Keep the marker format if you add an effect.
 * ─────────────────────────────────────────────────────────── */




/* ==== plate: scanline ==== */

/* 01 · Scanline Halftone — hard-light blended repeating-linear-gradient line tile pushed through a contrast() threshold to make line thickness track photo luminance */

/* base photo layer: pre-flatten to grayscale so the blend below reads pure luminance */
[data-effect="scanline"] .pe-l1 {
  filter: grayscale(1) brightness(1.08);
}

/* line-screen tile: a SMOOTH vertical ramp (not a hard-edged stripe) so that
   hard-light blending lets the photo's local luminance slide the ramp's
   crossing point up or down within each cell — that shifted crossing point
   is what the later contrast() threshold reads as line thickness */
[data-effect="scanline"] .pe-l2 {
  display: block;
  background-image: repeating-linear-gradient(
    to bottom,
    #000 0,
    #fff 50%,
    #000 100%
  );
  background-size: 100% var(--pe-cell);
  mix-blend-mode: hard-light;
}

/* the threshold: contrast collapses the ramp to either pure white (highlights
   swell the line to near-solid) or pure black (shadows thin it to nothing) —
   tuned so the pattern still tracks tone instead of collapsing to solid
   blobs (too high) or staying a muddy grey (too low) */
[data-effect="scanline"] .pe-mix {
  filter: grayscale(1) contrast(9) brightness(0.98);
}

/* grain, layered above the filtered result so it isn't crushed by contrast().
   overlay blend is naturally strongest in the midtones and near-neutral at
   pure black/white, so grain automatically pools where the halftone itself
   is midtone-dense — no extra masking needed */
[data-effect="scanline"] .pe-o1 {
  display: block;
  background-image: var(--pe-noise);
  background-size: 9cqw 9cqw;
  background-repeat: repeat;
  mix-blend-mode: overlay;
  opacity: 0.9;
}

/* second, coarser grain pass + a soft vignette to keep the frame edges dense
   and inky, like a scanned wirephoto print */
[data-effect="scanline"] .pe-o2 {
  display: block;
  background-image:
    radial-gradient(circle at 50% 50%, transparent 45%, rgba(0, 0, 0, 0.6) 100%),
    var(--pe-noise);
  background-size: 100% 100%, 9cqw 9cqw;
  background-repeat: no-repeat, repeat;
  mix-blend-mode: multiply;
  opacity: 0.7;
}

/* subtle CRT-style flicker; the static frame above is already a complete image */
@media (prefers-reduced-motion: no-preference) {
  [data-effect="scanline"] .pe-o1 {
    animation: pe-scanline-flicker 2.4s steps(6) infinite;
  }
  @keyframes pe-scanline-flicker {
    0%, 100% { opacity: 0.5; }
    50% { opacity: 0.62; }
    75% { opacity: 0.46; }
  }
}

/* ==== plate: vector-glitch ==== */

/* 02 · Vector Glitch — sepia/hue-rotate + multiply silhouette over a radial
   gradient, with stepped border-only shards (skew + translate + clip-path)
   as escaping vector echoes */

[data-effect="vector-glitch"] .pe-mix {
  background: radial-gradient(135% 135% at 42% 36%, #ffe066 0%, #ff8c00 45%, #c81c0d 100%);
  /* threshold the whole composite: snaps the multiplied silhouette from a
     graduated "thermal" tone into two flat fields (hot ground / dark shape) */
  filter: saturate(1.2) contrast(1.6) brightness(1.05);
}

[data-effect="vector-glitch"] .pe-l1 {
  /* light blur fuses fine texture (fur, railings) before the threshold so the
     shadow reads as a shape's outline, not a mottled photo; grayscale ->
     contrast pushes luminance toward near-binary. The invert/brightness/invert
     trio raises the black point off literal 0 so the colorize step afterward
     (sepia -> hue-rotate) has something to tint dark crimson instead of black */
  filter: blur(0.6cqw) grayscale(1) brightness(1.35) contrast(3.4) invert(1) brightness(0.82) invert(1) sepia(1) hue-rotate(-50deg) saturate(6) brightness(0.95);
  mix-blend-mode: multiply;
}

[data-effect="vector-glitch"] .pe-l2,
[data-effect="vector-glitch"] .pe-l3,
[data-effect="vector-glitch"] .pe-l4 {
  display: block;
  background: none;               /* drop the photo, these are outline shards */
  top: 28%;
  left: 52%;
  width: 30%;
  height: 34%;
  border: 2px solid #fff6da;      /* pale near-white reads on both orange and crimson */
  transform-origin: 0% 50%;
  mix-blend-mode: screen;
}

[data-effect="vector-glitch"] .pe-l2 {
  transform: skew(-14deg, -6deg) translate(6%, 4%) rotate(-4deg);
  border-style: dashed;
  opacity: 0.95;
}

[data-effect="vector-glitch"] .pe-l3 {
  top: 40%;
  left: 62%;
  width: 24%;
  height: 26%;
  border-style: dashed;
  transform: skew(-14deg, -6deg) translate(20%, 14%) rotate(-4deg);
  opacity: 0.85;
}

[data-effect="vector-glitch"] .pe-l4 {
  top: 50%;
  left: 74%;
  width: 16%;
  height: 17%;
  border-style: solid;
  clip-path: polygon(0 30%, 55% 0, 100% 22%, 100% 100%, 45% 100%, 0 68%);
  -webkit-clip-path: polygon(0 30%, 55% 0, 100% 22%, 100% 100%, 45% 100%, 0 68%);
  transform: skew(-14deg, -6deg) translate(34%, 22%) rotate(-4deg);
  opacity: 0.8;
}

[data-effect="vector-glitch"] .pe-o1 {
  display: block;
  background-image: var(--pe-noise);
  background-size: 22cqw 22cqw;
  background-repeat: repeat;
  mix-blend-mode: overlay;
  opacity: 0.18;
}

@media (prefers-reduced-motion: no-preference) {
  [data-effect="vector-glitch"] .pe-l3,
  [data-effect="vector-glitch"] .pe-l4 {
    animation: pe-vg-flicker 5.5s ease-in-out infinite;
  }
  @keyframes pe-vg-flicker {
    0%, 100% { opacity: 0.6; }
    50% { opacity: 0.35; }
  }
}

/* ==== plate: prism ==== */

/* 03 · Prism Slice — repeating-linear-gradient masks slice mirrored/translated
   photo copies into diagonal bands, screen/lighten-blended into ghost repeats,
   with a hard-light rainbow gradient riding the band edges over a cyan wash */

/* punch up the composited stack before it hits the glassy overlays */
[data-effect="prism"] .pe-mix { filter: saturate(1.3) contrast(1.1) brightness(1.06); }

[data-effect="prism"] .pe-l1 { display: block; }

/* ghost #2: shifted a full quarter-frame so the subject visibly re-appears in a
   different band instead of just ghosting near its own outline; mirrored so the
   duplicate reads as a distinct refraction, not a blurred copy of itself */
[data-effect="prism"] .pe-l2 {
  display: block;
  transform: scaleX(-1) translateX(38cqw) translateY(-2.25cqw);
  filter: hue-rotate(18deg) saturate(1.25);
  -webkit-mask-image: repeating-linear-gradient(65deg, #000 0 13cqw, transparent 13cqw 26cqw);
  mask-image: repeating-linear-gradient(65deg, #000 0 13cqw, transparent 13cqw 26cqw);
  mix-blend-mode: screen;
  opacity: 0.75;
}

/* ghost #3: mirrored + shifted the opposite way by a comparably large offset,
   occupies the complementary bands so two clearly displaced repeats interleave */
[data-effect="prism"] .pe-l3 {
  display: block;
  transform: scaleX(-1) translateX(-44cqw) translateY(3cqw);
  filter: hue-rotate(-22deg) saturate(1.2);
  -webkit-mask-image: repeating-linear-gradient(65deg, transparent 0 13cqw, #000 13cqw 26cqw);
  mask-image: repeating-linear-gradient(65deg, transparent 0 13cqw, #000 13cqw 26cqw);
  mix-blend-mode: lighten;
  opacity: 0.75;
}

/* rainbow spill: a compact spectral ramp placed only where each band seam falls
   (every 13cqw), fading to transparent well before the next seam, so it reads
   as light spilling off an edge rather than a solid stripe of flat CMY tape */
[data-effect="prism"] .pe-o1 {
  display: block;
  background-image: repeating-linear-gradient(65deg,
    transparent 0, rgba(150,60,255,0.55) 0.4cqw,
    rgba(70,110,255,0.5) 1.2cqw, rgba(60,210,255,0.45) 2cqw,
    rgba(110,255,150,0.4) 2.8cqw, rgba(255,235,90,0.4) 3.6cqw,
    rgba(255,140,60,0.45) 4.4cqw, rgba(255,60,90,0.5) 5.2cqw,
    transparent 6.2cqw, transparent 13cqw);
  mix-blend-mode: hard-light;
  opacity: 0.85;
}

/* bright glassy cyan lighting sitting over the whole composite */
[data-effect="prism"] .pe-o2 {
  display: block;
  background-image: radial-gradient(120% 100% at 50% 40%, rgba(180,255,255,0.4), rgba(0,200,220,0.12) 60%, rgba(0,20,30,0.35) 100%);
  mix-blend-mode: soft-light;
  box-shadow: inset 0 0 18cqw rgba(0,255,255,0.25);
}

/* subtle drift keeps the slabs feeling lit rather than static, no full-frame flash */
@media (prefers-reduced-motion: no-preference) {
  [data-effect="prism"] .pe-o1 { animation: prism-shimmer 6s ease-in-out infinite; }
  @keyframes prism-shimmer {
    0%, 100% { opacity: 0.9; }
    50% { opacity: 0.6; }
  }
}

/* ==== plate: smear ==== */

/* 04 · Smear Pull — a thin horizontal slice of the photo is stretched
   vertically until it fills the frame (melted drag), with NO column comb
   so the smear stays continuous edge-to-edge; tonal variation comes only
   from the slice's own horizontal detail. One band-shaped mask keeps a
   legible strip of the source photo; a corner vignette (not a comb)
   supplies genuine black. */

[data-effect="smear"] .pe-mix { background: #000; filter: contrast(1.7) brightness(0.68) saturate(1.4); }

/* readable band: photo shows through, full width, tall enough to read */
[data-effect="smear"] .pe-l1 {
  -webkit-mask-image: linear-gradient(to bottom, transparent 0%, transparent 32%, #000 38%, #000 62%, transparent 68%, transparent 100%);
  mask-image: linear-gradient(to bottom, transparent 0%, transparent 32%, #000 38%, #000 62%, transparent 68%, transparent 100%);
  filter: brightness(0.95) contrast(1.05);
}

/* same band cut OUT of every smear layer so it is not painted over */
[data-effect="smear"] .pe-l2, [data-effect="smear"] .pe-l3, [data-effect="smear"] .pe-l4, [data-effect="smear"] .pe-l5 {
  -webkit-mask-image: linear-gradient(to bottom, #000 0%, #000 32%, transparent 38%, transparent 62%, #000 68%, #000 100%);
  mask-image: linear-gradient(to bottom, #000 0%, #000 32%, transparent 38%, transparent 62%, #000 68%, #000 100%);
}

/* primary smear: a single photo row zoomed hugely on Y via background-size
   (not transform, so the band mask above stays frame-relative and does not
   get stretched along with it) — continuous, full width, no comb */
[data-effect="smear"] .pe-l2 {
  display: block;
  background-size: 100% 1800%;
  background-position: 50% 46%;
  filter: grayscale(1) contrast(1.3) brightness(1.05);
  mix-blend-mode: screen;
}

/* second smear: different row + zoom, adds depth, still full width */
[data-effect="smear"] .pe-l3 {
  display: block;
  background-size: 100% 2400%;
  background-position: 50% 60%;
  filter: grayscale(1) contrast(1.2) brightness(0.85);
  mix-blend-mode: screen;
  opacity: 0.75;
}

/* incandescent tint: forces red/orange hue onto the grayscale streaks */
[data-effect="smear"] .pe-l5 {
  display: block;
  background-image: linear-gradient(180deg, #1a0300 0%, #ff5500 22%, #ffdf9c 50%, #ff5500 78%, #1a0300 100%);
  mix-blend-mode: color;
}

/* fine scratchy striations riding the streaks, full frame, no comb */
[data-effect="smear"] .pe-l4 {
  display: block;
  background-image: repeating-linear-gradient(90deg, rgba(255,190,120,0.8) 0, rgba(255,190,120,0.8) 0.1cqw, transparent 0.1cqw, transparent 0.55cqw);
  mix-blend-mode: color-dodge;
  opacity: 0.28;
}

/* corner vignette: real black from falloff, not a mask comb */
[data-effect="smear"] .pe-l6 {
  display: block;
  background-image: radial-gradient(64% 64% at 50% 50%, transparent 30%, rgba(0,0,0,0.55) 68%, rgba(0,0,0,0.97) 100%);
  mix-blend-mode: multiply;
}

/* soft incandescent bloom behind the readable band */
[data-effect="smear"] .pe-o1 {
  display: block;
  background: radial-gradient(40cqw 14cqw at 50% 50%, rgba(255,80,20,0.35), transparent 70%);
  mix-blend-mode: screen;
  filter: blur(1.6cqw);
}

/* faint grain to keep the glow from looking flat */
[data-effect="smear"] .pe-o2 {
  display: block;
  background-image: var(--pe-noise);
  background-size: 22cqw 22cqw;
  mix-blend-mode: overlay;
  opacity: 0.12;
}

@media (prefers-reduced-motion: no-preference) {
  [data-effect="smear"] .pe-l3 { animation: smear-drift 7s ease-in-out infinite; }
  @keyframes smear-drift {
    0%, 100% { background-position: 50% 60%; }
    50% { background-position: 50% 63%; }
  }
}

/* ==== plate: vhs ==== */

/* 05 · VHS Double Exposure — two dimmed, screen-blended offset photo copies inside .pe-mix
   for the ghost; grayscale+contrast(punch back the midtones)+brightness on .pe-mix; fine
   scanlines; two normal-blend masked layers (l5, l6) torn in opposite directions replace
   thin bands of the base with a shifted copy = obvious tracking-tear glitches; noise on top */

[data-effect="vhs"] .pe-mix {
  filter: grayscale(1) contrast(1.35) brightness(0.95);
}

/* base copy */
[data-effect="vhs"] .pe-l1 { display: block; }

/* ghost copy: offset + screen blend = double exposure, kept dark enough to survive the contrast punch */
[data-effect="vhs"] .pe-l2 {
  display: block;
  transform: translateX(4cqw);
  filter: brightness(0.8);
  mix-blend-mode: screen;
  opacity: 0.65;
}

/* second ghost, opposite direction, fainter: reads as a true double exposure not a single shadow */
[data-effect="vhs"] .pe-l3 {
  display: block;
  transform: translateX(-3cqw) translateY(0.75cqw);
  filter: brightness(0.75) blur(0.2cqw);
  mix-blend-mode: screen;
  opacity: 0.35;
}

/* fine horizontal scanlines, generated (not a photo layer). NOTE: the stage only contains
   the inline axis (container-type: inline-size), so cqh/cqb are NOT sized to this element —
   they'd fall back to the viewport and drift with viewport height. Everything here is cqw,
   using the fixed 4:3 aspect-ratio (height = 0.75 * width) to convert intended "vertical %"
   figures into cqw so the pattern truly scales with the stage at any embed size. */
[data-effect="vhs"] .pe-l4 {
  display: block;
  background-image: repeating-linear-gradient(
    to bottom,
    rgba(0, 0, 0, 0.5) 0,
    rgba(0, 0, 0, 0.5) 0.09cqw,
    transparent 0.09cqw,
    transparent 0.34cqw
  );
  mix-blend-mode: multiply;
  opacity: 0.5;
}

/* tracking tear #1: single band torn hard left. normal blend (not screen) so the band
   cleanly REPLACES the base instead of just brightening it. Mask stops in cqw (see l4 note). */
[data-effect="vhs"] .pe-l5 {
  display: block;
  transform: translateX(-7.5cqw);
  filter: brightness(1.02) contrast(1.1) blur(0.2cqw);
  -webkit-mask-image: repeating-linear-gradient(
    to bottom, transparent 0, transparent 9.8cqw,
    #000 9.8cqw, #000 12.4cqw, transparent 12.4cqw, transparent 100%
  );
  mask-image: repeating-linear-gradient(
    to bottom, transparent 0, transparent 9.8cqw,
    #000 9.8cqw, #000 12.4cqw, transparent 12.4cqw, transparent 100%
  );
  mix-blend-mode: normal;
  opacity: 1;
}

/* tracking tears #2/#3: two more bands torn the OPPOSITE way, so the tape damage
   doesn't read as one repeated stripe but as distinct independent glitches */
[data-effect="vhs"] .pe-l6 {
  display: block;
  transform: translateX(8cqw);
  /* partial invert makes these two bands read as a stark glitch even where the
     underlying photo is already light, so they stay obvious on the pale source too */
  filter: contrast(1.3) invert(0.8) blur(0.15cqw);
  -webkit-mask-image: repeating-linear-gradient(
    to bottom,
    transparent 0, transparent 33cqw, #000 33cqw, #000 35.6cqw,
    transparent 35.6cqw, transparent 54.8cqw, #000 54.8cqw, #000 56.6cqw,
    transparent 56.6cqw, transparent 100%
  );
  mask-image: repeating-linear-gradient(
    to bottom,
    transparent 0, transparent 33cqw, #000 33cqw, #000 35.6cqw,
    transparent 35.6cqw, transparent 54.8cqw, #000 54.8cqw, #000 56.6cqw,
    transparent 56.6cqw, transparent 100%
  );
  mix-blend-mode: normal;
  opacity: 1;
}

/* faint bloom only near center, no longer a full-frame white wash */
[data-effect="vhs"] .pe-o1 {
  display: block;
  background-image: radial-gradient(
    ellipse at 50% 45%,
    rgba(255, 255, 255, 0.22) 0%,
    rgba(255, 255, 255, 0) 65%
  );
  mix-blend-mode: screen;
}

/* grain over everything to sell the tape damage */
[data-effect="vhs"] .pe-o2 {
  display: block;
  background-image: var(--pe-noise);
  background-size: 45cqw 45cqw;
  background-repeat: repeat;
  mix-blend-mode: overlay;
  opacity: 0.3;
}

@media (prefers-reduced-motion: no-preference) {
  [data-effect="vhs"] .pe-l5 {
    animation: vhs-tracking-drift-a 9s ease-in-out infinite;
  }
  [data-effect="vhs"] .pe-l6 {
    animation: vhs-tracking-drift-b 6.5s ease-in-out infinite;
  }
  @keyframes vhs-tracking-drift-a {
    0%, 100% { transform: translateX(-7.5cqw); }
    50% { transform: translateX(-5cqw); }
  }
  @keyframes vhs-tracking-drift-b {
    0%, 100% { transform: translateX(8cqw); }
    50% { transform: translateX(6cqw); }
  }
}

/* ==== plate: shatter ==== */

/* 06 · Shattered Glass — a dim, unclipped base pane (pe-l1) plus 9 broken
   fragments fanned around one off-centre impact point (62%,36%), each an
   irregular clip-path shard with its OWN large rotate + scale + translate
   about that shared point. Because l1 always fully covers the frame,
   wherever a fragment's transform pulls its content away from "home" the
   dim intact base shows through instead of a black hole — so the shards
   can move A LOT (clear mismatch) without ever tearing open the photo.
   pe-l3/l4/l5/l6 each grow a ::after twin (photo re-declared) so 9
   fragments come from 5 layers. drop-shadow() on each clipped fragment
   traces its own silhouette: dark to one side for the crack, light to
   the other for the specular edge. */

[data-effect="shatter"] .pe-mix { filter: contrast(1.05) saturate(1.05); }
[data-effect="shatter"] .pe-l1 { filter: brightness(.6) saturate(.85); } /* intact base pane, seen dimly through/behind every crack */

[data-effect="shatter"] .pe-l2, [data-effect="shatter"] .pe-l3, [data-effect="shatter"] .pe-l4,
[data-effect="shatter"] .pe-l5, [data-effect="shatter"] .pe-l6,
[data-effect="shatter"] .pe-l3::after, [data-effect="shatter"] .pe-l4::after,
[data-effect="shatter"] .pe-l5::after, [data-effect="shatter"] .pe-l6::after {
  display: block;
  transform-origin: 62% 36%; /* every fragment pivots on the same impact point */
  filter: drop-shadow(.55cqw .48cqw .22cqw rgba(0,0,0,.9)) drop-shadow(-.32cqw -.32cqw .24cqw rgba(255,255,255,.65));
}
[data-effect="shatter"] .pe-l3::after, [data-effect="shatter"] .pe-l4::after,
[data-effect="shatter"] .pe-l5::after, [data-effect="shatter"] .pe-l6::after {
  content: ""; position: absolute; inset: 0; background: var(--pe-img) 50% 50% / cover no-repeat;
}

[data-effect="shatter"] .pe-l2        { clip-path: polygon(62% 36%, 25% -12%, 46% -22%, 66% -14%, 85% -4%, 60% 22%); transform: rotate(11deg) scale(1.16) translate(1.6cqw,-0.975cqw); filter: brightness(1.12) contrast(1.06); }
[data-effect="shatter"] .pe-l3        { clip-path: polygon(62% 36%, 85% -4%, 100% 8%, 112% 22%, 78% 30%); transform: rotate(-13deg) scale(1.2) translate(-1.4cqw,-0.75cqw); filter: brightness(.88); }
[data-effect="shatter"] .pe-l3::after { clip-path: polygon(62% 36%, 112% 22%, 115% 38%, 115% 55%, 80% 45%); transform: rotate(9deg) scale(1.14) translate(1.8cqw,0.375cqw); filter: brightness(1.08) contrast(1.05); }
[data-effect="shatter"] .pe-l4        { clip-path: polygon(62% 36%, 115% 55%, 108% 70%, 97% 86%, 68% 55%); transform: rotate(-10deg) scale(1.22) translate(1.2cqw,1.2cqw); filter: brightness(.9); }
[data-effect="shatter"] .pe-l4::after { clip-path: polygon(62% 36%, 97% 86%, 80% 98%, 62% 110%, 55% 65%); transform: rotate(14deg) scale(1.18) translate(-.8cqw,1.35cqw); filter: brightness(1.1); }
[data-effect="shatter"] .pe-l5        { clip-path: polygon(62% 36%, 62% 110%, 45% 108%, 25% 108%, 48% 60%); transform: rotate(-12deg) scale(1.16) translate(-1.6cqw,0.75cqw); filter: brightness(.92) contrast(1.06); }
[data-effect="shatter"] .pe-l5::after { clip-path: polygon(62% 36%, 25% 108%, 10% 95%, -6% 80%, 40% 55%); transform: rotate(10deg) scale(1.24) translate(-1.8cqw,-0.375cqw); filter: brightness(1.06); }
[data-effect="shatter"] .pe-l6        { clip-path: polygon(62% 36%, -6% 80%, -12% 60%, -18% 35%, 42% 40%); transform: rotate(-15deg) scale(1.15) translate(-1.4cqw,-0.9cqw); filter: brightness(1.1) contrast(1.08); }
[data-effect="shatter"] .pe-l6::after { clip-path: polygon(62% 36%, -18% 35%, -8% 15%, 6% -2%, 25% -12%, 48% 18%); transform: rotate(8deg) scale(1.2) translate(.8cqw,-1.2cqw); filter: brightness(.9); }

/* soft dark pool at the impact, plus a small crazed-glass glint cluster right at the point */
[data-effect="shatter"] .pe-o1 {
  display: block;
  background-image: radial-gradient(circle at 62% 36%, transparent 0, transparent 16cqw, rgba(0,0,0,.35) 55cqw, rgba(0,0,0,.55) 100cqw);
  mix-blend-mode: multiply;
}
[data-effect="shatter"] .pe-o2 {
  display: block;
  background-image:
    radial-gradient(circle at 62% 36%, rgba(255,255,255,.95) 0, rgba(255,255,255,.5) 1.2cqw, transparent 5cqw),
    repeating-conic-gradient(from 6deg at 62% 36%, rgba(255,255,255,.55) 0deg 3deg, transparent 3deg 36deg);
  mask-image: radial-gradient(circle at 62% 36%, #000 0, #000 5cqw, transparent 10cqw);
  -webkit-mask-image: radial-gradient(circle at 62% 36%, #000 0, #000 5cqw, transparent 10cqw);
  mix-blend-mode: screen;
}

@media (prefers-reduced-motion: no-preference) {
  [data-effect="shatter"] .pe-o2 { animation: shatter-glint 5s ease-in-out infinite; }
  @keyframes shatter-glint { 0%, 100% { opacity: .8; } 50% { opacity: 1; } }
}

/* ==== plate: chrome ==== */

/* 07 · Chrome Solarize — rebuilt from scratch (the difference-of-negatives
   edge map from the last two passes read as an X-ray: it only lit actual
   contours, so any textured photo (fur, foliage) filled the frame with dark
   speckle and nothing looked "lit"). New approach: a normal (non-inverted)
   high-contrast tone map carries the subject's own lights as the bright
   metal, a heavily blurred+brightened copy screens over it for real halation
   bloom (the light source the brief asks for), then a color-blend tint
   imposes hue without adding channel noise, same trick as before. */

[data-effect="chrome"] .pe-l1,
[data-effect="chrome"] .pe-l2,
[data-effect="chrome"] .pe-l3,
[data-effect="chrome"] .pe-l4 { display: block; }

[data-effect="chrome"] .pe-l1 {
  /* base tone map: the photo's own highlights become the "metal", not an
     edge artifact, so soft light-on-light subjects still read bright.
     Brightness pulled slightly DOWN so the contrast pivot has room — only
     genuine highlights reach white, everything below mid-gray crushes. */
  filter: grayscale(1) brightness(0.85) contrast(2.3);
}

[data-effect="chrome"] .pe-l2 {
  /* bloom: a big soft blur of the tone map, screened on top at modest
     opacity — reads as halation around the highlights without flattening
     the whole frame to white; smooth by construction so it adds no speckle */
  filter: grayscale(1) blur(4.5cqw) brightness(1.15) contrast(1.2);
  mix-blend-mode: screen;
  opacity: 0.45;
}

[data-effect="chrome"] .pe-l3 {
  /* electric blue-white key fading to deep ink-navy; "color" blend imposes
     only hue/saturation, leaving the tone map + bloom luminance untouched */
  background: radial-gradient(125% 135% at 50% 40%,
    hsl(195 90% 95%) 0%, hsl(205 100% 66%) 34%, hsl(228 95% 32%) 74%, hsl(240 85% 8%) 100%);
  mix-blend-mode: color;
}

[data-effect="chrome"] .pe-l4 {
  /* crush the corners to near-black for a plated, glossy vignette — starts
     closer in than before so the surround reads properly black, not grey */
  background: radial-gradient(140% 140% at 50% 50%, transparent 32%, #00030d 100%);
  mix-blend-mode: multiply;
}

[data-effect="chrome"] .pe-mix {
  /* gentle final punch — l1's contrast + l2's bloom already did the
     exposure work, this just snaps saturation and edges a touch further */
  filter: brightness(1.05) contrast(1.25) saturate(1.6);
}

[data-effect="chrome"] .pe-o1 {
  /* keep the frame edges black so the glow reads against void */
  display: block;
  background: radial-gradient(120% 120% at 50% 50%, transparent 55%, #000 100%);
}

[data-effect="chrome"] .pe-o2 {
  /* wet plastic sheen streak, noise kept faint and coarse so it reads as
     sheen texture rather than visible speckle */
  display: block;
  background-image:
    linear-gradient(115deg, transparent 30%, rgba(220, 244, 255, 0.55) 48%, transparent 62%),
    var(--pe-noise);
  background-size: 220% 220%, 34cqw 34cqw;
  background-repeat: no-repeat, repeat;
  mix-blend-mode: screen;
  opacity: 0.3;
}

@media (prefers-reduced-motion: no-preference) {
  [data-effect="chrome"] .pe-o2 {
    animation: chrome-sheen-drift 9s ease-in-out infinite;
  }
  @keyframes chrome-sheen-drift {
    0%, 100% { background-position: 0% 0%, 0 0; }
    50% { background-position: 100% 100%, 3cqw 3cqw; }
  }
}

/* ==== plate: dither ==== */

/* 08 · Dither Threshold — 1-bit AM-screen stipple. A single radial-gradient
   dot per cell, ramped smoothly from black centre to white edge, is
   hard-light blended against the (later-grayscaled) photo so each dot's
   surviving radius tracks local luminance. An extreme contrast() threshold
   then snaps every pixel to pure black or pure white — dot density, not
   grey, carries the tone. */
[data-effect="dither"] {
  --pe-cell: 2cqw; /* bigger than the shared default so dots read clearly at 800px */
}

/* l2: the dot screen. 68% radius means neighbouring dots just kiss at the
   cell corners, leaving a visible lavender gap between them once thresholded. */
[data-effect="dither"] .pe-l2 {
  display: block;
  background-image: radial-gradient(circle at 50% 50%, #000 0%, #fff 68%);
  background-size: var(--pe-cell) var(--pe-cell);
  mix-blend-mode: hard-light;
}

/* Composite l1 (photo) + l2 (dot tile), then threshold to 1-bit. contrast(7)
   is tuned by eye: high enough that dot edges stay crisp, low enough that
   individual dots survive instead of fusing into solid blobs. */
[data-effect="dither"] .pe-mix {
  filter: grayscale(1) contrast(7) brightness(1.08);
}

/* .pe-over sits above the .pe-mix filter, so it is immune to the contrast
   crush: multiply(black, anything) stays black, multiply(white, lavender) =
   lavender. Recolors the thresholded field to pale lavender without
   touching the black dots. */
[data-effect="dither"] .pe-o1 {
  display: block;
  background-image: none;
  background-color: #b9b7f7;
  mix-blend-mode: multiply;
}

/* Faint riso-paper grain on top, independent of the threshold. */
[data-effect="dither"] .pe-o2 {
  display: block;
  background-image: var(--pe-noise);
  background-size: 22cqw 22cqw;
  mix-blend-mode: overlay;
  opacity: 0.3;
}

@media (prefers-reduced-motion: no-preference) {
  /* Slow drift of the dot tile so the stipple reads as hand-set, not printed. */
  [data-effect="dither"] .pe-l2 {
    animation: pe-dither-drift 9s ease-in-out infinite;
  }
  @keyframes pe-dither-drift {
    0%, 100% { background-position: 0 0; }
    50% { background-position: 6% 4%; }
  }
}

/* ==== plate: echo ==== */

/* 09 · Motion Echo — long-exposure smear. Key fix: BLUR happens before
   the per-layer threshold in the filter chain, not after. Blurring first
   averages away fine photo texture (fur, railings) into a smooth ramp, so
   the contrast pass that follows steepens that ramp into a soft-edged
   glow instead of thresholding raw high-frequency detail into a jagged
   binary stipple. The leading copy skips blur entirely and stays crisp.
   A darken/lighten duotone pair then recolors the whole stack to flat
   chrome-yellow ground / hot-magenta subject. */

/* leading copy: no blur — thresholded straight, so it stays a sharp,
   recognisable silhouette (this one is allowed to be near-binary) */
[data-effect="echo"] .pe-l1 { filter: grayscale(1) brightness(2) contrast(4.5); }

/* trailing copies: blur FIRST, threshold SECOND — rising blur + real
   offset gaps (9/18/28cqw) + falling opacity read as soft, fading strides */
[data-effect="echo"] .pe-l2,
[data-effect="echo"] .pe-l3,
[data-effect="echo"] .pe-l4 {
  display: block;
  mix-blend-mode: multiply;
}
[data-effect="echo"] .pe-l2 {
  filter: grayscale(1) brightness(2) blur(2.4cqw) contrast(4);
  transform: translate(9cqw, 1.6cqw);
  opacity: 0.6;
}
[data-effect="echo"] .pe-l3 {
  filter: grayscale(1) brightness(2) blur(4.2cqw) contrast(4);
  transform: translate(18cqw, 3.4cqw);
  opacity: 0.4;
}
[data-effect="echo"] .pe-l4 {
  filter: grayscale(1) brightness(2) blur(6cqw) contrast(4);
  transform: translate(28cqw, 5.4cqw);
  opacity: 0.24;
}

/* group-level polish only — separation now comes from the blur-then-
   threshold order above, this just adds punch to the final colors */
[data-effect="echo"] .pe-mix { filter: contrast(1.3) saturate(2.2); }

/* duotone: darken lays flat yellow into the bright ground; lighten paints
   magenta into the dark subject/trail. */
[data-effect="echo"] .pe-l5,
[data-effect="echo"] .pe-l6 {
  display: block;
  background-image: none;
}
[data-effect="echo"] .pe-l5 {
  background-color: #ffd23c;
  mix-blend-mode: darken;
}
[data-effect="echo"] .pe-l6 {
  background-color: #ff0aad;
  mix-blend-mode: lighten;
}

/* gentle vignette on top of everything, sits above the pe-mix filter */
[data-effect="echo"] .pe-o1 {
  display: block;
  background-image: none;
  background: radial-gradient(120% 100% at 50% 50%, transparent 55%, #000 115%);
  mix-blend-mode: multiply;
  opacity: 0.4;
}

/* subtle drift on the faintest echo only, never a strobe risk */
@media (prefers-reduced-motion: no-preference) {
  [data-effect="echo"] .pe-l4 {
    animation: pe-echo-drift 5s ease-in-out infinite alternate;
  }
  @keyframes pe-echo-drift {
    from { transform: translate(26cqw, 5cqw); }
    to   { transform: translate(30cqw, 5.8cqw); }
  }
}

/* ==== plate: aberration ==== */

/* 10 · Chromatic Aberration — RGB channel-split (isolate + multiply tint + screen recombine).
   Each channel layer is scaled from the CENTRE ORIGIN only (no translate): scale(1) at the
   middle pixel means the centre never moves, while pixels further out get pushed further —
   this is what keeps the subject registered while the fringe widens toward the frame edge,
   instead of the whole frame sliding sideways (which reads as blur). A thin, lightly-blurred
   diagonal streak (screen blend, low opacity) adds the prismatic flare without washing the image. */

[data-effect="aberration"] .pe-mix { filter: contrast(1.15) saturate(1.25); }

[data-effect="aberration"] .pe-l1,
[data-effect="aberration"] .pe-l2,
[data-effect="aberration"] .pe-l3 {
  display: block;
  isolation: isolate;
  mix-blend-mode: screen;
}
[data-effect="aberration"] .pe-l1::after,
[data-effect="aberration"] .pe-l2::after,
[data-effect="aberration"] .pe-l3::after { content: ""; position: absolute; inset: 0; mix-blend-mode: multiply; }

/* red channel pushed outward, green stays put as the registration anchor,
   blue pulled inward — mostly a radial scale (zero displacement at centre,
   growing toward the corners) with a small diagonal translate layered on
   top so the pull reads as raking along one diagonal, not a plain iris */
[data-effect="aberration"] .pe-l1 { transform: translate(-.45cqw, -.4cqw) scale(1.05); }
[data-effect="aberration"] .pe-l1::after { background: #ff1030; }

[data-effect="aberration"] .pe-l2::after { background: #14ff6e; opacity: .8; }

[data-effect="aberration"] .pe-l3 { transform: translate(.45cqw, .4cqw) scale(.955); }
[data-effect="aberration"] .pe-l3::after { background: #1050ff; }

/* prismatic flare: thin diagonal spectral streak, barely blurred so it stays a crisp
   rake rather than a fog, screen-blended so it only brightens (blows out highlights) */
[data-effect="aberration"] .pe-l4 {
  display: block;
  background-image: repeating-linear-gradient(115deg,
    transparent 0 7cqw, rgba(255,70,80,.4) 7.4cqw, rgba(255,235,90,.35) 8.1cqw,
    rgba(90,255,190,.35) 8.8cqw, rgba(80,150,255,.4) 9.5cqw, transparent 10cqw 23cqw);
  filter: blur(.35cqw);
  mix-blend-mode: screen;
  opacity: .55;
}

[data-effect="aberration"] .pe-o1 {
  display: block;
  background: radial-gradient(circle at 50% 50%, transparent 32%, #000 88%);
  mix-blend-mode: multiply;
}

@media (prefers-reduced-motion: no-preference) {
  [data-effect="aberration"] .pe-l1 { animation: pe-aberration-drift-r 7s ease-in-out infinite; }
  [data-effect="aberration"] .pe-l3 { animation: pe-aberration-drift-b 7s ease-in-out infinite; }
  @keyframes pe-aberration-drift-r { 50% { transform: translate(-.6cqw, -.55cqw) scale(1.065); } }
  @keyframes pe-aberration-drift-b { 50% { transform: translate(.6cqw, .55cqw) scale(.94); } }
}

/* ==== plate: print-split ==== */

/* 11 · Print Split — hard-light halftone dots thresholded via contrast()/grayscale() on .pe-mix, then screen-blended cyan recolors black dots while paper stays white */

/* right half: build the halftone inside the isolated mix group */
[data-effect="print-split"] .pe-mix {
  clip-path: inset(0 0 0 50%);
  -webkit-clip-path: inset(0 0 0 50%);
  filter: grayscale(1) contrast(13) brightness(1.05);
}

[data-effect="print-split"] .pe-l1 {
  display: block; /* base tone that the dot tile reads against */
}

[data-effect="print-split"] .pe-l2 {
  display: block;
  background-image: radial-gradient(circle at 50% 50%, #000 42%, transparent 43%);
  background-size: var(--pe-cell) var(--pe-cell);
  background-repeat: repeat;
  mix-blend-mode: hard-light; /* dot local-luminance follows photo tone */
  transform: translate(0.35cqw, 0.25cqw); /* print registration offset */
}

/* cyan plate: screen(black-dot, cyan) = cyan, screen(white-paper, cyan) = white */
[data-effect="print-split"] .pe-o1 {
  display: block;
  clip-path: inset(0 0 0 50%);
  -webkit-clip-path: inset(0 0 0 50%);
  background-image: var(--pe-noise), linear-gradient(0deg, #00e5ff, #00e5ff);
  background-size: 18cqw 18cqw, cover;
  background-repeat: repeat, no-repeat;
  background-blend-mode: overlay; /* paper grain worked into the cyan fill */
  mix-blend-mode: screen;
  opacity: 0.94;
}

/* left half: untouched photograph, sharp on top, plus a paper-white seam hairline */
[data-effect="print-split"] .pe-o2 {
  display: block;
  clip-path: inset(0 50% 0 0);
  -webkit-clip-path: inset(0 50% 0 0);
  background-image:
    linear-gradient(to right, transparent calc(50% - 2px), #fff calc(50% - 2px), #fff 50%, transparent 50%),
    var(--pe-img);
  background-size: 100% 100%, cover;
  background-repeat: no-repeat, no-repeat;
}

/* ==== plate: newsprint ==== */

/* 12 · Newsprint Collage — smooth radial-ramp dot tile blended hard-light so
   dot size tracks local photo tone, thresholded by contrast() into a coarse
   halftone screen; sepia + warm paper stock age it into 1950s print, and two
   oversized clipped rings sweep across as vintage orbit-diagram arcs. */

/* smooth black-centre-to-white-edge dot ramp (NOT a hard-edged tile): the
   dark centre hard-light-multiplies toward an ink dot, the light edge
   hard-light-screens toward paper, and the ramp's crossing point shifts with
   the photo's local luminance, so dot size grows/shrinks with tone instead
   of a flat 1-bit split */
[data-effect="newsprint"] .pe-l2 {
  display: block;
  background-image: radial-gradient(circle at 50% 50%, #000 0%, #fff 100%);
  background-size: 1.8cqw 1.8cqw;
  mix-blend-mode: hard-light;
}

/* threshold the blended pair into round black dots on white, then warm the
   result toward print-ink sepia; contrast kept moderate so dots stay round
   instead of swallowing into solid blobs */
[data-effect="newsprint"] .pe-mix {
  filter: grayscale(1) contrast(4) sepia(0.45) saturate(1.3) brightness(1.05);
}

/* warm cream paper stock + fibre grain, multiplied over the dot screen */
[data-effect="newsprint"] .pe-o1 {
  display: block;
  background-image:
    var(--pe-noise),
    radial-gradient(ellipse at 50% 50%, transparent 42%, rgba(32, 17, 4, 0.45) 100%),
    linear-gradient(#efdcb2, #efdcb2);
  background-size: 16cqw 16cqw, cover, cover;
  mix-blend-mode: multiply;
  opacity: 0.78;
}

/* orbit-ring arcs: two thick circles mostly off-frame, clipped by pe-stage's
   overflow:hidden into sweeping curved segments across the picture. The
   layer itself must switch on for its ::before/::after to paint, but it
   must not show the base rule's inherited photo background — only the ring
   pseudo-elements should draw here. */
[data-effect="newsprint"] .pe-o2 {
  display: block;
  background-image: none;
}
[data-effect="newsprint"] .pe-o2::before,
[data-effect="newsprint"] .pe-o2::after {
  content: "";
  position: absolute;
  border-radius: 50%;
  border-style: solid;
  mix-blend-mode: multiply;
}
[data-effect="newsprint"] .pe-o2::before {
  width: 88cqw;
  height: 88cqw;
  top: -42cqw;
  left: 32cqw;
  border-width: 0.6cqw;
  border-color: rgba(38, 20, 6, 0.8);
}
[data-effect="newsprint"] .pe-o2::after {
  width: 72cqw;
  height: 72cqw;
  bottom: -52cqw;
  left: -18cqw;
  border-width: 0.5cqw;
  border-color: rgba(38, 20, 6, 0.65);
}

/* pasted-down cut-out shadow at the frame edge */
[data-effect="newsprint"].pe-stage {
  box-shadow: inset 0 0 6cqw rgba(20, 10, 2, 0.5), inset 0 0.6cqw 1.2cqw rgba(0, 0, 0, 0.3);
}
