Skip to content

Commit 2fdb22c

Browse files
feat: image treatments — duotone, bw, polaroid, hard-frame, mask (#11)
1 parent ed137e3 commit 2fdb22c

3 files changed

Lines changed: 101 additions & 4 deletions

File tree

src/ir/parse.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -269,10 +269,22 @@ function inferTrend(options: Record<string, string>): Stat['trend'] {
269269
return undefined;
270270
}
271271

272+
const VALID_TREATMENTS = new Set([
273+
'plain',
274+
'frame',
275+
'bleed',
276+
'duotone',
277+
'bw',
278+
'polaroid',
279+
'hard-frame',
280+
'mask',
281+
]);
282+
272283
function buildImage(options: Record<string, string>): Image | null {
273284
if (!options.src) return null;
274-
const treatment =
275-
options.treatment === 'frame' || options.treatment === 'bleed' ? options.treatment : 'plain';
285+
const treatment = VALID_TREATMENTS.has(options.treatment)
286+
? (options.treatment as Image['treatment'])
287+
: 'plain';
276288
const img: Image = { type: 'image', src: options.src, treatment };
277289
if (options.alt) img.alt = options.alt;
278290
if (options.caption) img.caption = options.caption;

src/ir/schema.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,15 @@ export type Cell = {
9898
children: Block[];
9999
};
100100

101-
export type ImageTreatment = 'plain' | 'frame' | 'bleed';
101+
export type ImageTreatment =
102+
| 'plain'
103+
| 'frame'
104+
| 'bleed'
105+
| 'duotone'
106+
| 'bw'
107+
| 'polaroid'
108+
| 'hard-frame'
109+
| 'mask';
102110

103111
export type Image = {
104112
type: 'image';
@@ -285,7 +293,9 @@ const ImageSchema: z.ZodType<Image> = z.object({
285293
caption: z.string().optional(),
286294
aspectRatio: z.string().optional(),
287295
focal: z.string().optional(),
288-
treatment: z.enum(['plain', 'frame', 'bleed']).optional(),
296+
treatment: z
297+
.enum(['plain', 'frame', 'bleed', 'duotone', 'bw', 'polaroid', 'hard-frame', 'mask'])
298+
.optional(),
289299
annotations: z.array(ImageAnnotationSchema).optional(),
290300
});
291301

src/styles/blocks.css

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,81 @@
460460
border: none;
461461
}
462462

463+
/* B&W: pure desaturation */
464+
.block-image[data-treatment='bw'] .block-image__frame > img {
465+
filter: grayscale(1) contrast(1.05);
466+
}
467+
468+
/* Duotone: blend brand + dark using mix-blend-mode on the image */
469+
.block-image[data-treatment='duotone'] .block-image__frame {
470+
background: var(--color-brand);
471+
position: relative;
472+
}
473+
474+
.block-image[data-treatment='duotone'] .block-image__frame > img {
475+
filter: grayscale(1) contrast(1.1);
476+
mix-blend-mode: lighten;
477+
}
478+
479+
.block-image[data-treatment='duotone'] .block-image__frame::after {
480+
content: '';
481+
position: absolute;
482+
inset: 0;
483+
background: var(--color-text);
484+
mix-blend-mode: darken;
485+
pointer-events: none;
486+
}
487+
488+
/* Polaroid: thick white frame with extra-thick bottom; caption strip */
489+
.block-image[data-treatment='polaroid'] {
490+
background: #fff;
491+
padding: 14px 14px 56px;
492+
border-radius: 4px;
493+
box-shadow:
494+
0 4px 12px -2px rgba(0, 0, 0, 0.18),
495+
0 16px 32px -16px rgba(0, 0, 0, 0.25);
496+
transform: rotate(-1.5deg);
497+
}
498+
499+
.block-image[data-treatment='polaroid'] .block-image__frame {
500+
border-radius: 0;
501+
border: none;
502+
background: #f5f4ef;
503+
}
504+
505+
.block-image[data-treatment='polaroid'] > figcaption {
506+
position: absolute;
507+
bottom: 14px;
508+
left: 14px;
509+
right: 14px;
510+
text-align: center;
511+
font-family: var(--font-display, ui-serif);
512+
font-size: 13px;
513+
color: #1a1a1a;
514+
letter-spacing: 0;
515+
}
516+
517+
.block-image[data-treatment='polaroid'][data-has-caption=''] {
518+
position: relative;
519+
}
520+
521+
/* Hard-frame: thick brand-colored border, no shadow, sharp corners */
522+
.block-image[data-treatment='hard-frame'] .block-image__frame {
523+
border-radius: 0;
524+
border: 6px solid var(--color-brand);
525+
box-shadow: none;
526+
padding: 0;
527+
}
528+
529+
/* Mask: rounded blob mask */
530+
.block-image[data-treatment='mask'] .block-image__frame {
531+
border-radius: 0;
532+
border: none;
533+
background: transparent;
534+
-webkit-mask-image: radial-gradient(ellipse 50% 50% at 50% 50%, #000 60%, transparent 100%);
535+
mask-image: radial-gradient(ellipse 50% 50% at 50% 50%, #000 60%, transparent 100%);
536+
}
537+
463538
.block-image__frame > img {
464539
display: block;
465540
width: 100%;

0 commit comments

Comments
 (0)