bookcover-core 0.18.0 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/data_file.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // Serialise all computed cover data into a Typst _data.typ file
2
- import { mm_to_typst } from './utils.js';
2
+ import { mm_to_typst, text_dir } from './utils.js';
3
3
  import { escape_typst_str } from 'typst-utils';
4
4
  import { SCHEMA_DEFAULTS } from './defaults.js';
5
5
  /** Helper: emit a Typst #let binding for a millimetre value */
@@ -30,6 +30,10 @@ function color(name, hsl_str) {
30
30
  function content_raw(name, value) {
31
31
  return `#let ${name} = [${value}]`;
32
32
  }
33
+ /** Helper: emit a Typst #let binding for a text's base direction (ltr/rtl) */
34
+ function dir(name, text) {
35
+ return `#let ${name} = ${text_dir(text)}`;
36
+ }
33
37
  /** Helper: emit a Typst #let binding for a number */
34
38
  function num(name, value) {
35
39
  return `#let ${name} = ${value}`;
@@ -90,6 +94,19 @@ export function build_data_file(schema, dims, colors, font_body_family, font_tit
90
94
  lines.push(bool('has_author', !!(schema.author)));
91
95
  lines.push(bool('has_blurb', !!(schema.blurb)));
92
96
  lines.push('');
97
+ // Base text direction per field (first-strong rule), so RTL text gets RTL punctuation and
98
+ // number placement. The blurb sets its own per paragraph (see form_schema.ts build_blurb).
99
+ // The spine's title + author pair is laid out in the title's direction (author if no title).
100
+ lines.push('// Text direction');
101
+ lines.push(dir('title1_dir', schema.title1 ?? ''));
102
+ lines.push(dir('title2_dir', schema.title2 ?? ''));
103
+ lines.push(dir('title3_dir', schema.title3 ?? ''));
104
+ lines.push(`#let subtitle_dirs = (${subtitle_lines.map(l => `${text_dir(l)}, `).join('')})`);
105
+ lines.push(dir('author_dir', schema.author ?? ''));
106
+ lines.push(dir('spine_title_dir', schema.spine_title ?? ''));
107
+ lines.push(dir('spine_author_dir', schema.spine_author ?? ''));
108
+ lines.push(dir('spine_dir', schema.spine_title || schema.spine_author || ''));
109
+ lines.push('');
93
110
  // Per-text sizing (relative multipliers, default 1.0)
94
111
  lines.push('// Per-text sizing');
95
112
  lines.push(num('title1_size', schema.title1_size ?? SCHEMA_DEFAULTS.title1_size));
@@ -154,12 +171,13 @@ export function build_data_file(schema, dims, colors, font_body_family, font_tit
154
171
  lines.push(str('title_position', schema.title_position));
155
172
  lines.push(str('subtitle_position', schema.subtitle_position));
156
173
  lines.push(str('author_position', schema.author_position));
157
- // Alignment emitted as raw Typst values (left/center/right)
174
+ // Alignment emitted as raw Typst values (left/center/right). Justified text's last line sits
175
+ // at the logical start, which Typst resolves per paragraph — left for LTR, right for RTL
158
176
  lines.push(`#let title_alignment = ${schema.title_alignment ?? SCHEMA_DEFAULTS.title_alignment}`);
159
177
  lines.push(`#let subtitle_alignment = ${schema.subtitle_alignment ?? SCHEMA_DEFAULTS.subtitle_alignment}`);
160
178
  lines.push(`#let author_alignment = ${schema.author_alignment ?? SCHEMA_DEFAULTS.author_alignment}`);
161
179
  const blurb_align = schema.blurb_alignment ?? SCHEMA_DEFAULTS.blurb_alignment;
162
- lines.push(`#let blurb_alignment = ${blurb_align === 'justified' ? 'left' : blurb_align}`);
180
+ lines.push(`#let blurb_alignment = ${blurb_align === 'justified' ? 'start' : blurb_align}`);
163
181
  lines.push(bool('blurb_justify', blurb_align === 'justified'));
164
182
  lines.push('');
165
183
  // Panel margins (all 4 sides of each panel, resolved to mm)
@@ -7,7 +7,7 @@ export declare const SCHEMA_VERSION = 1;
7
7
  * the bundled pattern/vector/icon data. Hosts that re-render a frozen record later should
8
8
  * record this at freeze time (alongside the typst version their wrapper reports) so a drifted
9
9
  * reprint is detectable instead of silent. */
10
- export declare const RENDER_VERSION = 2;
10
+ export declare const RENDER_VERSION = 3;
11
11
  /** What an omitted CoverSchema field resolves to at render time. Derived fields are absent by
12
12
  * design — see the note at the top of this file. */
13
13
  export declare const SCHEMA_DEFAULTS: {
package/dist/defaults.js CHANGED
@@ -19,7 +19,7 @@ export const SCHEMA_VERSION = 1;
19
19
  * the bundled pattern/vector/icon data. Hosts that re-render a frozen record later should
20
20
  * record this at freeze time (alongside the typst version their wrapper reports) so a drifted
21
21
  * reprint is detectable instead of silent. */
22
- export const RENDER_VERSION = 2;
22
+ export const RENDER_VERSION = 3;
23
23
  /** What an omitted CoverSchema field resolves to at render time. Derived fields are absent by
24
24
  * design — see the note at the top of this file. */
25
25
  export const SCHEMA_DEFAULTS = {
@@ -165,6 +165,7 @@ export const FORM_DEFAULTS = {
165
165
  home_print_margin: false,
166
166
  // BACKGROUND — white, no image, no icon, no pattern
167
167
  bg_image: null,
168
+ bg_image_builtin: null,
168
169
  bg_image_coverage: 'full',
169
170
  bg_color: null,
170
171
  bg_color_gradient: false,
@@ -2,7 +2,7 @@
2
2
  import { pm_to_typst } from 'pm-to-typst';
3
3
  import { hex_override_to_hsl } from './colors.js';
4
4
  import { find_pattern } from './patterns.js';
5
- import { warn_unknown } from './utils.js';
5
+ import { warn_unknown, text_dir } from './utils.js';
6
6
  /** Replace straight quotes with typographic curly quotes */
7
7
  export function curly_quotes(text) {
8
8
  return text
@@ -15,14 +15,28 @@ export function curly_quotes(text) {
15
15
  // Remaining single quotes become closing (also covers apostrophes)
16
16
  .replace(/'/g, '’');
17
17
  }
18
+ // Collect the raw text of a ProseMirror node and its descendants
19
+ function pm_text(node) {
20
+ return node.type === 'text' ? node.text ?? '' : (node.content ?? []).map(pm_text).join('');
21
+ }
18
22
  /**
19
23
  * Render the blurb document to Typst markup. pm_to_typst escapes the text and the blurb is
20
24
  * emitted as a Typst content block (see data_file.ts), so Typst's own smartquote curls the
21
25
  * quotes — no manual curling needed here, unlike the plain-string title fields below.
26
+ * Each top-level block (paragraph, heading, whole list, quote) takes its base direction from
27
+ * its own first strong letter, so an RTL paragraph gets RTL punctuation, list markers and
28
+ * justified last line while its LTR neighbours are untouched. Blocks nested inside a list or
29
+ * quote follow the enclosing block's direction.
22
30
  * Returns undefined when the blurb is empty.
23
31
  */
24
32
  function build_blurb(doc) {
25
- const typst = pm_to_typst(doc);
33
+ const typst = pm_to_typst(doc, { nodes: {
34
+ doc: (node, ctx) => (node.content ?? []).map(child => {
35
+ const rendered = ctx.node(child);
36
+ return text_dir(pm_text(child)) === 'rtl'
37
+ ? `#[#set text(dir: rtl)\n${rendered}\n]` : rendered;
38
+ }).join('\n\n'),
39
+ } });
26
40
  return typst.trim() ? typst : undefined;
27
41
  }
28
42
  /** Parse a font family name from user input. Accepts:
@@ -1,13 +1,14 @@
1
1
  import type { CjkVariant } from 'typst-fonts';
2
2
  import type { PmDoc } from 'pm-to-typst';
3
- /** JSON-safe form values — every form field except the bg_image binary. This is the shape
3
+ /** JSON-safe form values — every form field except the background image (bg_image's binary, or
4
+ * the bg_image_builtin reference that stands in for one). This is the shape
4
5
  * hosts store and round-trip (e.g. via the widget's embed protocol).
5
6
  *
6
7
  * Two invariants hosts can rely on, and must preserve:
7
8
  * - NO BINARIES. The background image and custom font bytes are never in this record; they
8
- * are referenced out-of-band by the host, which owns their storage and identity. The widget
9
- * reports which built-in background the user picked as an advisory protocol field, never as
10
- * a field in here (see generator-web's embed_types.ts).
9
+ * are referenced out-of-band by the host, which owns their storage and identity. A built-in
10
+ * background travels as its ID in the protocol's bg_image_builtin field, never as a field in
11
+ * here (see generator-web's embed_types.ts).
11
12
  * - ABSENCE MEANS NOTHING. Every field is always present and explicitly valued. Where a value
12
13
  * is meant to be derived at render time, it says so with an explicit sentinel ('auto'/null),
13
14
  * never by omitting the key — so a record's meaning can't shift when a default changes. */
@@ -109,23 +110,30 @@ export interface EmbedFormState {
109
110
  isbn: string;
110
111
  cjk_variant: CjkVariant | 'auto';
111
112
  }
112
- /** Live form values — adds the background image File, which is held only in the browser and
113
- * never serialized with the rest of the form */
113
+ /** Live form values — adds the background image, which is held only in the browser and never
114
+ * serialized with the rest of the form. At most one of bg_image and bg_image_builtin is set:
115
+ * a built-in background is held by ID alone, an upload by its bytes */
114
116
  export interface FormState extends EmbedFormState {
115
- /** The background image, held byte-for-byte.
117
+ /** An uploaded background image (a user upload or a host-supplied one), held byte-for-byte.
118
+ * Always null for a built-in background — see bg_image_builtin.
116
119
  *
117
- * BYTES ROUND-TRIP UNMODIFIED. A File put in this slot — whether the user uploaded it, a
118
- * host handed it over the embed protocol, or it was fetched from the built-in backgrounds
119
- * — is the exact File handed back out again: never re-encoded, resized, recompressed or
120
- * stripped of metadata. So any host may use the bytes themselves as the image's identity
121
- * (hashing them to recognise an image it already stores), and that identity survives a
122
- * round trip through the editor.
120
+ * BYTES ROUND-TRIP UNMODIFIED. A File put in this slot — whether the user uploaded it or a
121
+ * host handed it over the embed protocol — is the exact File handed back out again: never
122
+ * re-encoded, resized, recompressed or stripped of metadata. So any host may use the bytes
123
+ * themselves as the image's identity (hashing them to recognise an image it already
124
+ * stores), and that identity survives a round trip through the editor.
123
125
  *
124
126
  * Everything the editor derives FROM the pixels — crop/coverage choices, sampled colors,
125
127
  * region analysis — lives in the form values or is recomputed at render time, never by
126
128
  * rewriting this file. Downscaling for preview rendering is fine, but the result is a
127
129
  * separate Blob that must not be written back here. */
128
130
  bg_image: File | null;
131
+ /** Filename of the built-in background in use (e.g. 'beach.jpg') — its ID, and the whole of
132
+ * its identity: no bytes are held for it. Renderers fetch whichever copy they need by this
133
+ * name (the preview-sized one in BG_PREVIEW_DIR, or the original under backgrounds/), and
134
+ * its colors come from the baked table by the same name (get_builtin_bg_regions). Null
135
+ * for an upload or no image. */
136
+ bg_image_builtin: string | null;
129
137
  }
130
138
  /** Plain object with blank/empty values — no demo content, white background, no icon or
131
139
  * pattern. Values come from FORM_DEFAULTS; the blurb document is cloned so each form gets its
@@ -1,15 +1,19 @@
1
1
  import type { ImageRegions } from '../design.js';
2
+ /** One built-in background photo's baked metadata */
3
+ export interface BuiltinBg {
4
+ width: number;
5
+ height: number;
6
+ regions: ImageRegions;
7
+ }
2
8
  /** Precomputed ImageRegions for every builtin background photo (assets/backgrounds/<key>),
3
9
  * sampled against a fixed reference size — a 6in x 9in trim, 250-page Lulu paperback — rather
4
10
  * than the actual cover being generated, since the source photo's dominant colors barely shift
5
11
  * across trim sizes/spine widths and doing this once at build time skips a real image decode
6
12
  * (measured 100-350ms for this app's own background photos) for the common case of a builtin
7
- * pick. `size` is the source file's byte length, checked alongside the filename key so a
8
- * same-named user upload doesn't collide with a builtin entry. Callers should fall back to a
9
- * live analyze_pixel_regions() when get_builtin_bg_regions() returns null (custom uploads, or
10
- * any image not in this table). */
11
- export declare const BUILTIN_BG_REGIONS: Record<string, {
12
- size: number;
13
- regions: ImageRegions;
14
- }>;
13
+ * pick. The filename key is the built-in's ID, so get_builtin_bg_regions() looks entries up by
14
+ * it alone — the regions hold for the preview-sized copy, or any visually identical
15
+ * re-encode, too: they are a rough colour read, not a fingerprint. `width`/`height` are the
16
+ * original's pixel size, for resolution checks without downloading it. Callers fall back to a
17
+ * live analyze_pixel_regions() for anything not in this table. */
18
+ export declare const BUILTIN_BG_REGIONS: Record<string, BuiltinBg>;
15
19
  //# sourceMappingURL=builtin_bg_regions.d.ts.map
@@ -5,69 +5,70 @@
5
5
  * than the actual cover being generated, since the source photo's dominant colors barely shift
6
6
  * across trim sizes/spine widths and doing this once at build time skips a real image decode
7
7
  * (measured 100-350ms for this app's own background photos) for the common case of a builtin
8
- * pick. `size` is the source file's byte length, checked alongside the filename key so a
9
- * same-named user upload doesn't collide with a builtin entry. Callers should fall back to a
10
- * live analyze_pixel_regions() when get_builtin_bg_regions() returns null (custom uploads, or
11
- * any image not in this table). */
8
+ * pick. The filename key is the built-in's ID, so get_builtin_bg_regions() looks entries up by
9
+ * it alone — the regions hold for the preview-sized copy, or any visually identical
10
+ * re-encode, too: they are a rough colour read, not a fingerprint. `width`/`height` are the
11
+ * original's pixel size, for resolution checks without downloading it. Callers fall back to a
12
+ * live analyze_pixel_regions() for anything not in this table. */
12
13
  export const BUILTIN_BG_REGIONS = {
13
- "adventure.jpg": { size: 1043669, regions: { "front_top": { "hue": 53.853, "saturation": 0.177, "lightness": 0.492, "lightness_spread": 0.065 }, "front_bottom": { "hue": 59.163, "saturation": 0.411, "lightness": 0.148, "lightness_spread": 0.085 }, "front_top_full": { "hue": 42.53, "saturation": 0.248, "lightness": 0.523, "lightness_spread": 0.046 }, "front_bottom_full": { "hue": 62.659, "saturation": 0.277, "lightness": 0.114, "lightness_spread": 0.089 }, "back": { "hue": 65.834, "saturation": 0.203, "lightness": 0.366, "lightness_spread": 0.13 }, "spine": { "hue": 51.015, "saturation": 0.19, "lightness": 0.415, "lightness_spread": 0.149 } } },
14
- "awe.jpg": { size: 1654140, regions: { "front_top": { "hue": 197.469, "saturation": 0.412, "lightness": 0.632, "lightness_spread": 0.1 }, "front_bottom": { "hue": 40.53, "saturation": 0.586, "lightness": 0.247, "lightness_spread": 0.219 }, "front_top_full": { "hue": 197.375, "saturation": 0.414, "lightness": 0.626, "lightness_spread": 0.1 }, "front_bottom_full": { "hue": 35.006, "saturation": 0.581, "lightness": 0.264, "lightness_spread": 0.191 }, "back": { "hue": 100.052, "saturation": 0.427, "lightness": 0.558, "lightness_spread": 0.307 }, "spine": { "hue": 55.877, "saturation": 0.445, "lightness": 0.797, "lightness_spread": 0.168 } } },
15
- "beach.jpg": { size: 1432324, regions: { "front_top": { "hue": 156.52, "saturation": 0.203, "lightness": 0.736, "lightness_spread": 0.087 }, "front_bottom": { "hue": 20.308, "saturation": 0.253, "lightness": 0.731, "lightness_spread": 0.056 }, "front_top_full": { "hue": 185.142, "saturation": 0.208, "lightness": 0.694, "lightness_spread": 0.079 }, "front_bottom_full": { "hue": 14.105, "saturation": 0.141, "lightness": 0.699, "lightness_spread": 0.053 }, "back": { "hue": 27.684, "saturation": 0.32, "lightness": 0.771, "lightness_spread": 0.081 }, "spine": { "hue": 73.634, "saturation": 0.126, "lightness": 0.691, "lightness_spread": 0.084 } } },
16
- "bible.jpg": { size: 2441840, regions: { "front_top": { "hue": 31.57, "saturation": 0.34, "lightness": 0.353, "lightness_spread": 0.128 }, "front_bottom": { "hue": 30.561, "saturation": 0.2, "lightness": 0.224, "lightness_spread": 0.201 }, "front_top_full": { "hue": 30.385, "saturation": 0.267, "lightness": 0.283, "lightness_spread": 0.088 }, "front_bottom_full": { "hue": 37.048, "saturation": 0.175, "lightness": 0.263, "lightness_spread": 0.224 }, "back": { "hue": 29.602, "saturation": 0.315, "lightness": 0.383, "lightness_spread": 0.216 }, "spine": { "hue": 24.906, "saturation": 0.374, "lightness": 0.469, "lightness_spread": 0.211 } } },
17
- "bird.jpg": { size: 5029850, regions: { "front_top": { "hue": 214.405, "saturation": 0.44, "lightness": 0.592, "lightness_spread": 0.087 }, "front_bottom": { "hue": 213.722, "saturation": 0.456, "lightness": 0.595, "lightness_spread": 0.012 }, "front_top_full": { "hue": 214.183, "saturation": 0.446, "lightness": 0.627, "lightness_spread": 0.016 }, "front_bottom_full": { "hue": 213.969, "saturation": 0.456, "lightness": 0.602, "lightness_spread": 0.012 }, "back": { "hue": 215.121, "saturation": 0.432, "lightness": 0.542, "lightness_spread": 0.143 }, "spine": { "hue": 216.804, "saturation": 0.407, "lightness": 0.504, "lightness_spread": 0.175 } } },
18
- "books.jpg": { size: 3452573, regions: { "front_top": { "hue": 102.789, "saturation": 0.114, "lightness": 0.936, "lightness_spread": 0.034 }, "front_bottom": { "hue": 163.276, "saturation": 0.14, "lightness": 0.717, "lightness_spread": 0.207 }, "front_top_full": { "hue": 114.719, "saturation": 0.103, "lightness": 0.931, "lightness_spread": 0.038 }, "front_bottom_full": { "hue": 177.717, "saturation": 0.153, "lightness": 0.743, "lightness_spread": 0.189 }, "back": { "hue": 119.388, "saturation": 0.133, "lightness": 0.776, "lightness_spread": 0.22 }, "spine": { "hue": 115.939, "saturation": 0.142, "lightness": 0.761, "lightness_spread": 0.202 } } },
19
- "burning.jpg": { size: 1235840, regions: { "front_top": { "hue": 2.3, "saturation": 0.725, "lightness": 0.042, "lightness_spread": 0.06 }, "front_bottom": { "hue": 6.292, "saturation": 0.907, "lightness": 0.095, "lightness_spread": 0.101 }, "front_top_full": { "hue": 1.512, "saturation": 0.982, "lightness": 0.05, "lightness_spread": 0.032 }, "front_bottom_full": { "hue": 10.809, "saturation": 0.951, "lightness": 0.155, "lightness_spread": 0.124 }, "back": { "hue": 2.963, "saturation": 0.668, "lightness": 0.044, "lightness_spread": 0.071 }, "spine": { "hue": 10.518, "saturation": 0.968, "lightness": 0.158, "lightness_spread": 0.129 } } },
20
- "church.jpg": { size: 2698112, regions: { "front_top": { "hue": 7.129, "saturation": 0.218, "lightness": 0.446, "lightness_spread": 0.101 }, "front_bottom": { "hue": 30.511, "saturation": 0.515, "lightness": 0.111, "lightness_spread": 0.055 }, "front_top_full": { "hue": 12.033, "saturation": 0.252, "lightness": 0.462, "lightness_spread": 0.101 }, "front_bottom_full": { "hue": 30.714, "saturation": 0.498, "lightness": 0.115, "lightness_spread": 0.05 }, "back": { "hue": 31.991, "saturation": 0.486, "lightness": 0.412, "lightness_spread": 0.246 }, "spine": { "hue": 32.696, "saturation": 0.46, "lightness": 0.449, "lightness_spread": 0.275 } } },
21
- "city.jpg": { size: 1096868, regions: { "front_top": { "hue": 213.093, "saturation": 0.237, "lightness": 0.824, "lightness_spread": 0.028 }, "front_bottom": { "hue": 222.764, "saturation": 0.417, "lightness": 0.184, "lightness_spread": 0.109 }, "front_top_full": { "hue": 213.017, "saturation": 0.246, "lightness": 0.818, "lightness_spread": 0.025 }, "front_bottom_full": { "hue": 222.134, "saturation": 0.406, "lightness": 0.196, "lightness_spread": 0.113 }, "back": { "hue": 249.993, "saturation": 0.313, "lightness": 0.598, "lightness_spread": 0.298 }, "spine": { "hue": 247.817, "saturation": 0.257, "lightness": 0.612, "lightness_spread": 0.267 } } },
22
- "city_sunset.jpg": { size: 842748, regions: { "front_top": { "hue": 205.058, "saturation": 0.671, "lightness": 0.644, "lightness_spread": 0.069 }, "front_bottom": { "hue": 201.446, "saturation": 0.557, "lightness": 0.29, "lightness_spread": 0.099 }, "front_top_full": { "hue": 204.963, "saturation": 0.672, "lightness": 0.638, "lightness_spread": 0.069 }, "front_bottom_full": { "hue": 201.759, "saturation": 0.511, "lightness": 0.29, "lightness_spread": 0.103 }, "back": { "hue": 207.517, "saturation": 0.534, "lightness": 0.549, "lightness_spread": 0.196 }, "spine": { "hue": 210.585, "saturation": 0.497, "lightness": 0.585, "lightness_spread": 0.202 } } },
23
- "countryside.jpg": { size: 4917994, regions: { "front_top": { "hue": 229.906, "saturation": 0.282, "lightness": 0.921, "lightness_spread": 0.12 }, "front_bottom": { "hue": 74.204, "saturation": 0.633, "lightness": 0.312, "lightness_spread": 0.056 }, "front_top_full": { "hue": 227.19, "saturation": 0.249, "lightness": 0.968, "lightness_spread": 0.037 }, "front_bottom_full": { "hue": 73.87, "saturation": 0.64, "lightness": 0.314, "lightness_spread": 0.062 }, "back": { "hue": 95.017, "saturation": 0.36, "lightness": 0.561, "lightness_spread": 0.275 }, "spine": { "hue": 101.947, "saturation": 0.327, "lightness": 0.667, "lightness_spread": 0.277 } } },
24
- "crops.jpg": { size: 1600104, regions: { "front_top": { "hue": 192.799, "saturation": 0.341, "lightness": 0.606, "lightness_spread": 0.152 }, "front_bottom": { "hue": 35.026, "saturation": 0.663, "lightness": 0.449, "lightness_spread": 0.112 }, "front_top_full": { "hue": 192.145, "saturation": 0.415, "lightness": 0.564, "lightness_spread": 0.166 }, "front_bottom_full": { "hue": 34.742, "saturation": 0.707, "lightness": 0.42, "lightness_spread": 0.109 }, "back": { "hue": 46.564, "saturation": 0.419, "lightness": 0.579, "lightness_spread": 0.135 }, "spine": { "hue": 49.05, "saturation": 0.456, "lightness": 0.572, "lightness_spread": 0.152 } } },
25
- "cross.jpg": { size: 463154, regions: { "front_top": { "hue": 65.291, "saturation": 0.002, "lightness": 0.004, "lightness_spread": 0 }, "front_bottom": { "hue": 21.86, "saturation": 0.736, "lightness": 0.041, "lightness_spread": 0.073 }, "front_top_full": { "hue": 60, "saturation": 0.004, "lightness": 0.004, "lightness_spread": 0 }, "front_bottom_full": { "hue": 6.788, "saturation": 0.827, "lightness": 0.071, "lightness_spread": 0.099 }, "back": { "hue": 57.399, "saturation": 0.252, "lightness": 0.012, "lightness_spread": 0.031 }, "spine": { "hue": 31.63, "saturation": 0.396, "lightness": 0.024, "lightness_spread": 0.041 } } },
26
- "cross_sun.jpg": { size: 3259091, regions: { "front_top": { "hue": 215.261, "saturation": 0.2, "lightness": 0.323, "lightness_spread": 0.037 }, "front_bottom": { "hue": 29.6, "saturation": 0.717, "lightness": 0.276, "lightness_spread": 0.221 }, "front_top_full": { "hue": 214.434, "saturation": 0.231, "lightness": 0.315, "lightness_spread": 0.035 }, "front_bottom_full": { "hue": 25.524, "saturation": 0.617, "lightness": 0.241, "lightness_spread": 0.202 }, "back": { "hue": 36.336, "saturation": 0.462, "lightness": 0.396, "lightness_spread": 0.175 }, "spine": { "hue": 43.042, "saturation": 0.473, "lightness": 0.431, "lightness_spread": 0.226 } } },
27
- "crown.jpg": { size: 4518272, regions: { "front_top": { "hue": 184.268, "saturation": 0.78, "lightness": 0.067, "lightness_spread": 0.071 }, "front_bottom": { "hue": 187.149, "saturation": 0.541, "lightness": 0.395, "lightness_spread": 0.241 }, "front_top_full": { "hue": 185.243, "saturation": 0.852, "lightness": 0.111, "lightness_spread": 0.075 }, "front_bottom_full": { "hue": 184.884, "saturation": 0.407, "lightness": 0.586, "lightness_spread": 0.195 }, "back": { "hue": 185.917, "saturation": 0.707, "lightness": 0.109, "lightness_spread": 0.109 }, "spine": { "hue": 184.658, "saturation": 0.623, "lightness": 0.191, "lightness_spread": 0.13 } } },
28
- "desert.jpg": { size: 825742, regions: { "front_top": { "hue": 28.124, "saturation": 0.774, "lightness": 0.957, "lightness_spread": 0.054 }, "front_bottom": { "hue": 18.297, "saturation": 0.832, "lightness": 0.693, "lightness_spread": 0.062 }, "front_top_full": { "hue": 30.431, "saturation": 0.8, "lightness": 0.974, "lightness_spread": 0.005 }, "front_bottom_full": { "hue": 18.449, "saturation": 0.903, "lightness": 0.739, "lightness_spread": 0.043 }, "back": { "hue": 21.289, "saturation": 0.831, "lightness": 0.814, "lightness_spread": 0.126 }, "spine": { "hue": 22.343, "saturation": 0.845, "lightness": 0.848, "lightness_spread": 0.119 } } },
29
- "desert_sunset.jpg": { size: 1779067, regions: { "front_top": { "hue": 1.343, "saturation": 0.954, "lightness": 0.791, "lightness_spread": 0.019 }, "front_bottom": { "hue": 209.341, "saturation": 0.282, "lightness": 0.601, "lightness_spread": 0.107 }, "front_top_full": { "hue": 0.408, "saturation": 0.948, "lightness": 0.788, "lightness_spread": 0.019 }, "front_bottom_full": { "hue": 211.086, "saturation": 0.262, "lightness": 0.624, "lightness_spread": 0.049 }, "back": { "hue": 325.997, "saturation": 0.65, "lightness": 0.641, "lightness_spread": 0.165 }, "spine": { "hue": 311.993, "saturation": 0.75, "lightness": 0.653, "lightness_spread": 0.151 } } },
30
- "earth.jpg": { size: 5037401, regions: { "front_top": { "hue": 216.02, "saturation": 0.575, "lightness": 0.508, "lightness_spread": 0.313 }, "front_bottom": { "hue": 209.702, "saturation": 0.289, "lightness": 0.554, "lightness_spread": 0.135 }, "front_top_full": { "hue": 216.514, "saturation": 0.579, "lightness": 0.534, "lightness_spread": 0.327 }, "front_bottom_full": { "hue": 207.85, "saturation": 0.254, "lightness": 0.552, "lightness_spread": 0.112 }, "back": { "hue": 213.127, "saturation": 0.425, "lightness": 0.556, "lightness_spread": 0.186 }, "spine": { "hue": 210.071, "saturation": 0.468, "lightness": 0.569, "lightness_spread": 0.166 } } },
31
- "earth_whole.jpg": { size: 1339552, regions: { "front_top": { "hue": 0, "saturation": 0, "lightness": 0, "lightness_spread": 0 }, "front_bottom": { "hue": 217.681, "saturation": 0.036, "lightness": 0.048, "lightness_spread": 0.15 }, "front_top_full": { "hue": 0, "saturation": 0, "lightness": 0, "lightness_spread": 0 }, "front_bottom_full": { "hue": 217.675, "saturation": 0.034, "lightness": 0.049, "lightness_spread": 0.15 }, "back": { "hue": 221.204, "saturation": 0.046, "lightness": 0.1, "lightness_spread": 0.213 }, "spine": { "hue": 222.062, "saturation": 0.112, "lightness": 0.256, "lightness_spread": 0.281 } } },
32
- "fire.jpg": { size: 1567935, regions: { "front_top": { "hue": 336.921, "saturation": 0.854, "lightness": 0.018, "lightness_spread": 0.035 }, "front_bottom": { "hue": 354.143, "saturation": 0.911, "lightness": 0.103, "lightness_spread": 0.18 }, "front_top_full": { "hue": 325.737, "saturation": 0.855, "lightness": 0.013, "lightness_spread": 0.019 }, "front_bottom_full": { "hue": 352.679, "saturation": 0.914, "lightness": 0.127, "lightness_spread": 0.203 }, "back": { "hue": 352.717, "saturation": 0.883, "lightness": 0.041, "lightness_spread": 0.084 }, "spine": { "hue": 14.079, "saturation": 0.948, "lightness": 0.295, "lightness_spread": 0.24 } } },
33
- "flowers.jpg": { size: 6013283, regions: { "front_top": { "hue": 359.525, "saturation": 0.257, "lightness": 0.857, "lightness_spread": 0.059 }, "front_bottom": { "hue": 11.121, "saturation": 0.213, "lightness": 0.632, "lightness_spread": 0.118 }, "front_top_full": { "hue": 10.868, "saturation": 0.302, "lightness": 0.836, "lightness_spread": 0.033 }, "front_bottom_full": { "hue": 29.7, "saturation": 0.192, "lightness": 0.656, "lightness_spread": 0.121 }, "back": { "hue": 2.179, "saturation": 0.241, "lightness": 0.738, "lightness_spread": 0.141 }, "spine": { "hue": 352.672, "saturation": 0.305, "lightness": 0.784, "lightness_spread": 0.111 } } },
34
- "flowers_field.jpg": { size: 3901495, regions: { "front_top": { "hue": 22.369, "saturation": 0.653, "lightness": 0.774, "lightness_spread": 0.14 }, "front_bottom": { "hue": 272.415, "saturation": 0.606, "lightness": 0.192, "lightness_spread": 0.206 }, "front_top_full": { "hue": 27.782, "saturation": 0.772, "lightness": 0.743, "lightness_spread": 0.2 }, "front_bottom_full": { "hue": 277.885, "saturation": 0.565, "lightness": 0.21, "lightness_spread": 0.204 }, "back": { "hue": 331.502, "saturation": 0.616, "lightness": 0.465, "lightness_spread": 0.309 }, "spine": { "hue": 334.484, "saturation": 0.672, "lightness": 0.456, "lightness_spread": 0.298 } } },
35
- "flowers_red.jpg": { size: 2441949, regions: { "front_top": { "hue": 214.211, "saturation": 0.394, "lightness": 0.646, "lightness_spread": 0.077 }, "front_bottom": { "hue": 79.975, "saturation": 0.552, "lightness": 0.244, "lightness_spread": 0.187 }, "front_top_full": { "hue": 208.432, "saturation": 0.381, "lightness": 0.708, "lightness_spread": 0.033 }, "front_bottom_full": { "hue": 83.209, "saturation": 0.566, "lightness": 0.201, "lightness_spread": 0.143 }, "back": { "hue": 20.111, "saturation": 0.542, "lightness": 0.501, "lightness_spread": 0.196 }, "spine": { "hue": 22.569, "saturation": 0.549, "lightness": 0.532, "lightness_spread": 0.213 } } },
36
- "funding.jpg": { size: 1822830, regions: { "front_top": { "hue": 90.48, "saturation": 0.049, "lightness": 0.54, "lightness_spread": 0.068 }, "front_bottom": { "hue": 314.715, "saturation": 0.263, "lightness": 0.806, "lightness_spread": 0.223 }, "front_top_full": { "hue": 89.426, "saturation": 0.093, "lightness": 0.549, "lightness_spread": 0.097 }, "front_bottom_full": { "hue": 11.233, "saturation": 0.334, "lightness": 0.727, "lightness_spread": 0.267 }, "back": { "hue": 78.106, "saturation": 0.093, "lightness": 0.594, "lightness_spread": 0.18 }, "spine": { "hue": 19.89, "saturation": 0.128, "lightness": 0.568, "lightness_spread": 0.195 } } },
37
- "grass.jpg": { size: 1913684, regions: { "front_top": { "hue": 84.038, "saturation": 0.364, "lightness": 0.62, "lightness_spread": 0.108 }, "front_bottom": { "hue": 101.932, "saturation": 0.802, "lightness": 0.214, "lightness_spread": 0.113 }, "front_top_full": { "hue": 85.783, "saturation": 0.308, "lightness": 0.561, "lightness_spread": 0.114 }, "front_bottom_full": { "hue": 106.376, "saturation": 0.9, "lightness": 0.164, "lightness_spread": 0.091 }, "back": { "hue": 89.967, "saturation": 0.509, "lightness": 0.466, "lightness_spread": 0.184 }, "spine": { "hue": 93.954, "saturation": 0.649, "lightness": 0.431, "lightness_spread": 0.218 } } },
38
- "green.jpg": { size: 6361662, regions: { "front_top": { "hue": 88.154, "saturation": 0.371, "lightness": 0.682, "lightness_spread": 0.125 }, "front_bottom": { "hue": 70.958, "saturation": 0.75, "lightness": 0.139, "lightness_spread": 0.126 }, "front_top_full": { "hue": 37.851, "saturation": 0.386, "lightness": 0.73, "lightness_spread": 0.133 }, "front_bottom_full": { "hue": 76.589, "saturation": 0.81, "lightness": 0.099, "lightness_spread": 0.046 }, "back": { "hue": 72.64, "saturation": 0.582, "lightness": 0.35, "lightness_spread": 0.253 }, "spine": { "hue": 76.817, "saturation": 0.611, "lightness": 0.329, "lightness_spread": 0.259 } } },
39
- "growing.jpg": { size: 1442096, regions: { "front_top": { "hue": 28.178, "saturation": 0.058, "lightness": 0.36, "lightness_spread": 0.071 }, "front_bottom": { "hue": 33.518, "saturation": 0.103, "lightness": 0.234, "lightness_spread": 0.07 }, "front_top_full": { "hue": 27.718, "saturation": 0.062, "lightness": 0.345, "lightness_spread": 0.074 }, "front_bottom_full": { "hue": 43.211, "saturation": 0.132, "lightness": 0.226, "lightness_spread": 0.068 }, "back": { "hue": 33.722, "saturation": 0.076, "lightness": 0.32, "lightness_spread": 0.094 }, "spine": { "hue": 41.412, "saturation": 0.105, "lightness": 0.319, "lightness_spread": 0.112 } } },
40
- "hills.jpg": { size: 1606652, regions: { "front_top": { "hue": 192.459, "saturation": 0.283, "lightness": 0.823, "lightness_spread": 0.022 }, "front_bottom": { "hue": 193.529, "saturation": 0.185, "lightness": 0.694, "lightness_spread": 0.094 }, "front_top_full": { "hue": 192.98, "saturation": 0.285, "lightness": 0.812, "lightness_spread": 0.016 }, "front_bottom_full": { "hue": 195.772, "saturation": 0.194, "lightness": 0.638, "lightness_spread": 0.084 }, "back": { "hue": 187.953, "saturation": 0.211, "lightness": 0.809, "lightness_spread": 0.065 }, "spine": { "hue": 189.125, "saturation": 0.214, "lightness": 0.786, "lightness_spread": 0.059 } } },
41
- "hills_trees.jpg": { size: 3625779, regions: { "front_top": { "hue": 26.64, "saturation": 0.682, "lightness": 0.647, "lightness_spread": 0.094 }, "front_bottom": { "hue": 29.738, "saturation": 0.521, "lightness": 0.21, "lightness_spread": 0.187 }, "front_top_full": { "hue": 28.288, "saturation": 0.709, "lightness": 0.635, "lightness_spread": 0.047 }, "front_bottom_full": { "hue": 28.319, "saturation": 0.501, "lightness": 0.29, "lightness_spread": 0.185 }, "back": { "hue": 28.031, "saturation": 0.541, "lightness": 0.422, "lightness_spread": 0.253 }, "spine": { "hue": 28.831, "saturation": 0.506, "lightness": 0.517, "lightness_spread": 0.153 } } },
42
- "island.jpg": { size: 4886053, regions: { "front_top": { "hue": 183.004, "saturation": 0.739, "lightness": 0.598, "lightness_spread": 0.27 }, "front_bottom": { "hue": 29.886, "saturation": 0.359, "lightness": 0.753, "lightness_spread": 0.075 }, "front_top_full": { "hue": 205.803, "saturation": 0.896, "lightness": 0.81, "lightness_spread": 0.042 }, "front_bottom_full": { "hue": 25.859, "saturation": 0.219, "lightness": 0.732, "lightness_spread": 0.079 }, "back": { "hue": 59.35, "saturation": 0.527, "lightness": 0.441, "lightness_spread": 0.269 }, "spine": { "hue": 115.095, "saturation": 0.528, "lightness": 0.558, "lightness_spread": 0.267 } } },
43
- "israel.jpg": { size: 3350836, regions: { "front_top": { "hue": 212.339, "saturation": 0.53, "lightness": 0.636, "lightness_spread": 0.087 }, "front_bottom": { "hue": 37.962, "saturation": 0.788, "lightness": 0.747, "lightness_spread": 0.191 }, "front_top_full": { "hue": 212.782, "saturation": 0.511, "lightness": 0.652, "lightness_spread": 0.081 }, "front_bottom_full": { "hue": 43.884, "saturation": 0.771, "lightness": 0.691, "lightness_spread": 0.239 }, "back": { "hue": 221.786, "saturation": 0.471, "lightness": 0.684, "lightness_spread": 0.123 }, "spine": { "hue": 228.331, "saturation": 0.48, "lightness": 0.715, "lightness_spread": 0.123 } } },
44
- "israel_lake.jpg": { size: 3707360, regions: { "front_top": { "hue": 200.86, "saturation": 0.787, "lightness": 0.428, "lightness_spread": 0.08 }, "front_bottom": { "hue": 236.092, "saturation": 0.485, "lightness": 0.191, "lightness_spread": 0.065 }, "front_top_full": { "hue": 199.559, "saturation": 0.789, "lightness": 0.438, "lightness_spread": 0.086 }, "front_bottom_full": { "hue": 243.249, "saturation": 0.485, "lightness": 0.195, "lightness_spread": 0.074 }, "back": { "hue": 207.865, "saturation": 0.476, "lightness": 0.432, "lightness_spread": 0.18 }, "spine": { "hue": 204.557, "saturation": 0.535, "lightness": 0.429, "lightness_spread": 0.177 } } },
45
- "lake.jpg": { size: 2459213, regions: { "front_top": { "hue": 216.275, "saturation": 0.671, "lightness": 0.704, "lightness_spread": 0.131 }, "front_bottom": { "hue": 178.136, "saturation": 0.131, "lightness": 0.521, "lightness_spread": 0.096 }, "front_top_full": { "hue": 216.746, "saturation": 0.682, "lightness": 0.713, "lightness_spread": 0.043 }, "front_bottom_full": { "hue": 191.587, "saturation": 0.163, "lightness": 0.517, "lightness_spread": 0.075 }, "back": { "hue": 207.78, "saturation": 0.357, "lightness": 0.489, "lightness_spread": 0.261 }, "spine": { "hue": 208.761, "saturation": 0.378, "lightness": 0.557, "lightness_spread": 0.203 } } },
46
- "lake_tree.jpg": { size: 2947645, regions: { "front_top": { "hue": 214.387, "saturation": 0.247, "lightness": 0.773, "lightness_spread": 0.07 }, "front_bottom": { "hue": 204.49, "saturation": 0.12, "lightness": 0.538, "lightness_spread": 0.102 }, "front_top_full": { "hue": 216.486, "saturation": 0.197, "lightness": 0.781, "lightness_spread": 0.061 }, "front_bottom_full": { "hue": 196.376, "saturation": 0.086, "lightness": 0.507, "lightness_spread": 0.131 }, "back": { "hue": 212.441, "saturation": 0.238, "lightness": 0.626, "lightness_spread": 0.17 }, "spine": { "hue": 200.983, "saturation": 0.223, "lightness": 0.655, "lightness_spread": 0.196 } } },
47
- "lion.jpg": { size: 5221103, regions: { "front_top": { "hue": 40.978, "saturation": 0.737, "lightness": 0.67, "lightness_spread": 0.02 }, "front_bottom": { "hue": 26.342, "saturation": 0.54, "lightness": 0.269, "lightness_spread": 0.091 }, "front_top_full": { "hue": 41.05, "saturation": 0.771, "lightness": 0.663, "lightness_spread": 0.02 }, "front_bottom_full": { "hue": 25.125, "saturation": 0.544, "lightness": 0.287, "lightness_spread": 0.085 }, "back": { "hue": 32.332, "saturation": 0.635, "lightness": 0.485, "lightness_spread": 0.185 }, "spine": { "hue": 33.272, "saturation": 0.67, "lightness": 0.56, "lightness_spread": 0.127 } } },
48
- "lost_sheep.jpg": { size: 3394050, regions: { "front_top": { "hue": 204.631, "saturation": 0.338, "lightness": 0.297, "lightness_spread": 0.068 }, "front_bottom": { "hue": 27.535, "saturation": 0.675, "lightness": 0.294, "lightness_spread": 0.118 }, "front_top_full": { "hue": 204.723, "saturation": 0.341, "lightness": 0.292, "lightness_spread": 0.064 }, "front_bottom_full": { "hue": 27.238, "saturation": 0.635, "lightness": 0.322, "lightness_spread": 0.12 }, "back": { "hue": 28.646, "saturation": 0.496, "lightness": 0.341, "lightness_spread": 0.15 }, "spine": { "hue": 29.267, "saturation": 0.496, "lightness": 0.373, "lightness_spread": 0.138 } } },
49
- "mist.jpg": { size: 670830, regions: { "front_top": { "hue": 26.249, "saturation": 0.702, "lightness": 0.967, "lightness_spread": 0.024 }, "front_bottom": { "hue": 209.76, "saturation": 0.359, "lightness": 0.136, "lightness_spread": 0.061 }, "front_top_full": { "hue": 26.513, "saturation": 0.661, "lightness": 0.958, "lightness_spread": 0.033 }, "front_bottom_full": { "hue": 205.366, "saturation": 0.364, "lightness": 0.142, "lightness_spread": 0.07 }, "back": { "hue": 29.044, "saturation": 0.46, "lightness": 0.571, "lightness_spread": 0.378 }, "spine": { "hue": 42.01, "saturation": 0.4, "lightness": 0.546, "lightness_spread": 0.36 } } },
50
- "mountains.jpg": { size: 1665711, regions: { "front_top": { "hue": 57.715, "saturation": 0.745, "lightness": 0.917, "lightness_spread": 0.171 }, "front_bottom": { "hue": 215.393, "saturation": 0.365, "lightness": 0.131, "lightness_spread": 0.131 }, "front_top_full": { "hue": 53.684, "saturation": 0.757, "lightness": 0.923, "lightness_spread": 0.156 }, "front_bottom_full": { "hue": 216.876, "saturation": 0.301, "lightness": 0.168, "lightness_spread": 0.148 }, "back": { "hue": 84.473, "saturation": 0.434, "lightness": 0.519, "lightness_spread": 0.352 }, "spine": { "hue": 153.543, "saturation": 0.321, "lightness": 0.435, "lightness_spread": 0.299 } } },
51
- "opening.jpg": { size: 1044074, regions: { "front_top": { "hue": 30.056, "saturation": 0.05, "lightness": 0.073, "lightness_spread": 0.116 }, "front_bottom": { "hue": 33.193, "saturation": 0.12, "lightness": 0.025, "lightness_spread": 0.043 }, "front_top_full": { "hue": 43.213, "saturation": 0.054, "lightness": 0.119, "lightness_spread": 0.099 }, "front_bottom_full": { "hue": 33.204, "saturation": 0.132, "lightness": 0.032, "lightness_spread": 0.047 }, "back": { "hue": 26.211, "saturation": 0.128, "lightness": 0.069, "lightness_spread": 0.175 }, "spine": { "hue": 33.939, "saturation": 0.218, "lightness": 0.461, "lightness_spread": 0.432 } } },
52
- "plane.jpg": { size: 1808726, regions: { "front_top": { "hue": 195.996, "saturation": 0.304, "lightness": 0.706, "lightness_spread": 0.113 }, "front_bottom": { "hue": 261.078, "saturation": 0.333, "lightness": 0.527, "lightness_spread": 0.195 }, "front_top_full": { "hue": 27.271, "saturation": 0.317, "lightness": 0.778, "lightness_spread": 0.085 }, "front_bottom_full": { "hue": 22.135, "saturation": 0.328, "lightness": 0.673, "lightness_spread": 0.143 }, "back": { "hue": 206.021, "saturation": 0.322, "lightness": 0.581, "lightness_spread": 0.16 }, "spine": { "hue": 31.419, "saturation": 0.405, "lightness": 0.725, "lightness_spread": 0.185 } } },
53
- "plant.jpg": { size: 2315603, regions: { "front_top": { "hue": 78.493, "saturation": 0.103, "lightness": 0.018, "lightness_spread": 0.075 }, "front_bottom": { "hue": 19.299, "saturation": 0.562, "lightness": 0.057, "lightness_spread": 0.062 }, "front_top_full": { "hue": 61.914, "saturation": 0.138, "lightness": 0.02, "lightness_spread": 0.084 }, "front_bottom_full": { "hue": 16.765, "saturation": 0.628, "lightness": 0.09, "lightness_spread": 0.076 }, "back": { "hue": 40.407, "saturation": 0.257, "lightness": 0.026, "lightness_spread": 0.075 }, "spine": { "hue": 102.277, "saturation": 0.448, "lightness": 0.075, "lightness_spread": 0.139 } } },
54
- "plant_table.jpg": { size: 2862801, regions: { "front_top": { "hue": 198.339, "saturation": 0.773, "lightness": 0.049, "lightness_spread": 0.082 }, "front_bottom": { "hue": 149.308, "saturation": 0.6, "lightness": 0.21, "lightness_spread": 0.202 }, "front_top_full": { "hue": 200.271, "saturation": 0.715, "lightness": 0.072, "lightness_spread": 0.12 }, "front_bottom_full": { "hue": 22.69, "saturation": 0.348, "lightness": 0.249, "lightness_spread": 0.175 }, "back": { "hue": 193.39, "saturation": 0.818, "lightness": 0.062, "lightness_spread": 0.116 }, "spine": { "hue": 183.36, "saturation": 0.615, "lightness": 0.234, "lightness_spread": 0.217 } } },
55
- "reflecting.jpg": { size: 2360242, regions: { "front_top": { "hue": 32.362, "saturation": 0.601, "lightness": 0.746, "lightness_spread": 0.06 }, "front_bottom": { "hue": 30.088, "saturation": 0.653, "lightness": 0.086, "lightness_spread": 0.051 }, "front_top_full": { "hue": 31.463, "saturation": 0.75, "lightness": 0.782, "lightness_spread": 0.018 }, "front_bottom_full": { "hue": 28.549, "saturation": 0.732, "lightness": 0.121, "lightness_spread": 0.051 }, "back": { "hue": 30.002, "saturation": 0.493, "lightness": 0.381, "lightness_spread": 0.299 }, "spine": { "hue": 27.784, "saturation": 0.604, "lightness": 0.383, "lightness_spread": 0.334 } } },
56
- "rocket.jpg": { size: 11421758, regions: { "front_top": { "hue": 219.973, "saturation": 0.117, "lightness": 0.426, "lightness_spread": 0.026 }, "front_bottom": { "hue": 33.981, "saturation": 0.387, "lightness": 0.268, "lightness_spread": 0.239 }, "front_top_full": { "hue": 222.115, "saturation": 0.107, "lightness": 0.437, "lightness_spread": 0.027 }, "front_bottom_full": { "hue": 34.049, "saturation": 0.416, "lightness": 0.316, "lightness_spread": 0.276 }, "back": { "hue": 6.734, "saturation": 0.181, "lightness": 0.379, "lightness_spread": 0.131 }, "spine": { "hue": 16.293, "saturation": 0.184, "lightness": 0.396, "lightness_spread": 0.131 } } },
57
- "sea.jpg": { size: 5778889, regions: { "front_top": { "hue": 187.102, "saturation": 0.188, "lightness": 0.844, "lightness_spread": 0.025 }, "front_bottom": { "hue": 190.313, "saturation": 0.395, "lightness": 0.306, "lightness_spread": 0.137 }, "front_top_full": { "hue": 183.78, "saturation": 0.184, "lightness": 0.864, "lightness_spread": 0.016 }, "front_bottom_full": { "hue": 190.437, "saturation": 0.308, "lightness": 0.379, "lightness_spread": 0.156 }, "back": { "hue": 190.777, "saturation": 0.367, "lightness": 0.497, "lightness_spread": 0.281 }, "spine": { "hue": 188.845, "saturation": 0.291, "lightness": 0.562, "lightness_spread": 0.268 } } },
58
- "sheep.jpg": { size: 3299614, regions: { "front_top": { "hue": 206.693, "saturation": 0.505, "lightness": 0.726, "lightness_spread": 0.247 }, "front_bottom": { "hue": 19.526, "saturation": 0.296, "lightness": 0.309, "lightness_spread": 0.179 }, "front_top_full": { "hue": 204.645, "saturation": 0.507, "lightness": 0.843, "lightness_spread": 0.111 }, "front_bottom_full": { "hue": 20.808, "saturation": 0.24, "lightness": 0.332, "lightness_spread": 0.207 }, "back": { "hue": 15.911, "saturation": 0.387, "lightness": 0.478, "lightness_spread": 0.246 }, "spine": { "hue": 213.3, "saturation": 0.361, "lightness": 0.617, "lightness_spread": 0.236 } } },
59
- "snow.jpg": { size: 1513769, regions: { "front_top": { "hue": 205.584, "saturation": 0.109, "lightness": 0.838, "lightness_spread": 0.091 }, "front_bottom": { "hue": 201.878, "saturation": 0.137, "lightness": 0.553, "lightness_spread": 0.248 }, "front_top_full": { "hue": 207.511, "saturation": 0.102, "lightness": 0.848, "lightness_spread": 0.009 }, "front_bottom_full": { "hue": 202.758, "saturation": 0.127, "lightness": 0.553, "lightness_spread": 0.221 }, "back": { "hue": 202.002, "saturation": 0.122, "lightness": 0.644, "lightness_spread": 0.24 }, "spine": { "hue": 205.215, "saturation": 0.095, "lightness": 0.74, "lightness_spread": 0.147 } } },
60
- "snow_trees.jpg": { size: 2229250, regions: { "front_top": { "hue": 14.593, "saturation": 0.868, "lightness": 0.841, "lightness_spread": 0.005 }, "front_bottom": { "hue": 242.436, "saturation": 0.25, "lightness": 0.711, "lightness_spread": 0.094 }, "front_top_full": { "hue": 11.62, "saturation": 0.794, "lightness": 0.845, "lightness_spread": 0.004 }, "front_bottom_full": { "hue": 247.651, "saturation": 0.225, "lightness": 0.729, "lightness_spread": 0.089 }, "back": { "hue": 8.932, "saturation": 0.604, "lightness": 0.776, "lightness_spread": 0.093 }, "spine": { "hue": 6.137, "saturation": 0.554, "lightness": 0.796, "lightness_spread": 0.075 } } },
61
- "stars.jpg": { size: 5697480, regions: { "front_top": { "hue": 189.927, "saturation": 0.443, "lightness": 0.179, "lightness_spread": 0.027 }, "front_bottom": { "hue": 31.853, "saturation": 0.196, "lightness": 0.124, "lightness_spread": 0.045 }, "front_top_full": { "hue": 190.529, "saturation": 0.44, "lightness": 0.192, "lightness_spread": 0.025 }, "front_bottom_full": { "hue": 30.242, "saturation": 0.223, "lightness": 0.131, "lightness_spread": 0.05 }, "back": { "hue": 183.075, "saturation": 0.361, "lightness": 0.189, "lightness_spread": 0.072 }, "spine": { "hue": 176.444, "saturation": 0.333, "lightness": 0.246, "lightness_spread": 0.109 } } },
62
- "sunset.jpg": { size: 1267112, regions: { "front_top": { "hue": 274.427, "saturation": 0.16, "lightness": 0.301, "lightness_spread": 0.13 }, "front_bottom": { "hue": 8.075, "saturation": 0.244, "lightness": 0.16, "lightness_spread": 0.195 }, "front_top_full": { "hue": 252.675, "saturation": 0.156, "lightness": 0.331, "lightness_spread": 0.126 }, "front_bottom_full": { "hue": 16.226, "saturation": 0.243, "lightness": 0.183, "lightness_spread": 0.209 }, "back": { "hue": 354.746, "saturation": 0.287, "lightness": 0.282, "lightness_spread": 0.227 }, "spine": { "hue": 16.92, "saturation": 0.393, "lightness": 0.448, "lightness_spread": 0.258 } } },
63
- "sunset_tree.jpg": { size: 2194495, regions: { "front_top": { "hue": 196.994, "saturation": 0.954, "lightness": 0.165, "lightness_spread": 0.048 }, "front_bottom": { "hue": 180.153, "saturation": 0.33, "lightness": 0.176, "lightness_spread": 0.13 }, "front_top_full": { "hue": 195.813, "saturation": 0.977, "lightness": 0.188, "lightness_spread": 0.04 }, "front_bottom_full": { "hue": 167.16, "saturation": 0.31, "lightness": 0.263, "lightness_spread": 0.141 }, "back": { "hue": 197.466, "saturation": 0.551, "lightness": 0.151, "lightness_spread": 0.101 }, "spine": { "hue": 194.694, "saturation": 0.504, "lightness": 0.25, "lightness_spread": 0.147 } } },
64
- "surge.jpg": { size: 3542895, regions: { "front_top": { "hue": 203.871, "saturation": 0.247, "lightness": 0.642, "lightness_spread": 0.069 }, "front_bottom": { "hue": 171.049, "saturation": 0.162, "lightness": 0.514, "lightness_spread": 0.208 }, "front_top_full": { "hue": 203.103, "saturation": 0.248, "lightness": 0.655, "lightness_spread": 0.049 }, "front_bottom_full": { "hue": 162.989, "saturation": 0.145, "lightness": 0.529, "lightness_spread": 0.189 }, "back": { "hue": 195.534, "saturation": 0.249, "lightness": 0.59, "lightness_spread": 0.181 }, "spine": { "hue": 198.382, "saturation": 0.229, "lightness": 0.623, "lightness_spread": 0.174 } } },
65
- "sword.jpg": { size: 1843024, regions: { "front_top": { "hue": 207.422, "saturation": 0.322, "lightness": 0.66, "lightness_spread": 0.204 }, "front_bottom": { "hue": 207.147, "saturation": 0.337, "lightness": 0.515, "lightness_spread": 0.203 }, "front_top_full": { "hue": 207.493, "saturation": 0.317, "lightness": 0.586, "lightness_spread": 0.251 }, "front_bottom_full": { "hue": 206.121, "saturation": 0.38, "lightness": 0.563, "lightness_spread": 0.217 }, "back": { "hue": 208.648, "saturation": 0.298, "lightness": 0.583, "lightness_spread": 0.19 }, "spine": { "hue": 207.453, "saturation": 0.327, "lightness": 0.656, "lightness_spread": 0.15 } } },
66
- "tomb.jpg": { size: 3267225, regions: { "front_top": { "hue": 29.481, "saturation": 0.207, "lightness": 0.442, "lightness_spread": 0.223 }, "front_bottom": { "hue": 27.291, "saturation": 0.253, "lightness": 0.291, "lightness_spread": 0.195 }, "front_top_full": { "hue": 29.453, "saturation": 0.179, "lightness": 0.466, "lightness_spread": 0.234 }, "front_bottom_full": { "hue": 27.081, "saturation": 0.307, "lightness": 0.218, "lightness_spread": 0.073 }, "back": { "hue": 28.492, "saturation": 0.213, "lightness": 0.381, "lightness_spread": 0.246 }, "spine": { "hue": 27.539, "saturation": 0.281, "lightness": 0.281, "lightness_spread": 0.106 } } },
67
- "tropical.jpg": { size: 1237585, regions: { "front_top": { "hue": 184.914, "saturation": 0.683, "lightness": 0.465, "lightness_spread": 0.213 }, "front_bottom": { "hue": 147.063, "saturation": 0.477, "lightness": 0.24, "lightness_spread": 0.146 }, "front_top_full": { "hue": 190.393, "saturation": 0.768, "lightness": 0.509, "lightness_spread": 0.064 }, "front_bottom_full": { "hue": 164.906, "saturation": 0.488, "lightness": 0.287, "lightness_spread": 0.153 }, "back": { "hue": 152.551, "saturation": 0.502, "lightness": 0.273, "lightness_spread": 0.219 }, "spine": { "hue": 166.024, "saturation": 0.519, "lightness": 0.421, "lightness_spread": 0.212 } } },
68
- "vineyard.jpg": { size: 4813194, regions: { "front_top": { "hue": 212.221, "saturation": 0.438, "lightness": 0.741, "lightness_spread": 0.102 }, "front_bottom": { "hue": 62.602, "saturation": 0.623, "lightness": 0.197, "lightness_spread": 0.086 }, "front_top_full": { "hue": 210.808, "saturation": 0.413, "lightness": 0.808, "lightness_spread": 0.091 }, "front_bottom_full": { "hue": 63.029, "saturation": 0.628, "lightness": 0.19, "lightness_spread": 0.079 }, "back": { "hue": 82.509, "saturation": 0.527, "lightness": 0.391, "lightness_spread": 0.239 }, "spine": { "hue": 76.283, "saturation": 0.491, "lightness": 0.406, "lightness_spread": 0.264 } } },
69
- "wasteland.jpg": { size: 3540567, regions: { "front_top": { "hue": 214.698, "saturation": 0.276, "lightness": 0.268, "lightness_spread": 0.14 }, "front_bottom": { "hue": 26.97, "saturation": 0.2, "lightness": 0.161, "lightness_spread": 0.065 }, "front_top_full": { "hue": 213.884, "saturation": 0.299, "lightness": 0.237, "lightness_spread": 0.122 }, "front_bottom_full": { "hue": 25.05, "saturation": 0.231, "lightness": 0.167, "lightness_spread": 0.053 }, "back": { "hue": 210.06, "saturation": 0.186, "lightness": 0.254, "lightness_spread": 0.15 }, "spine": { "hue": 339.732, "saturation": 0.204, "lightness": 0.283, "lightness_spread": 0.148 } } },
70
- "wilderness.jpg": { size: 3286289, regions: { "front_top": { "hue": 215.436, "saturation": 0.233, "lightness": 0.719, "lightness_spread": 0.101 }, "front_bottom": { "hue": 31.604, "saturation": 0.489, "lightness": 0.502, "lightness_spread": 0.141 }, "front_top_full": { "hue": 215.056, "saturation": 0.25, "lightness": 0.761, "lightness_spread": 0.044 }, "front_bottom_full": { "hue": 31.914, "saturation": 0.46, "lightness": 0.521, "lightness_spread": 0.121 }, "back": { "hue": 32.365, "saturation": 0.358, "lightness": 0.582, "lightness_spread": 0.185 }, "spine": { "hue": 30.638, "saturation": 0.33, "lightness": 0.611, "lightness_spread": 0.158 } } },
71
- "work.jpg": { size: 1164813, regions: { "front_top": { "hue": 201.939, "saturation": 0.574, "lightness": 0.963, "lightness_spread": 0.06 }, "front_bottom": { "hue": 204.081, "saturation": 0.266, "lightness": 0.569, "lightness_spread": 0.315 }, "front_top_full": { "hue": 207.197, "saturation": 0.595, "lightness": 0.983, "lightness_spread": 0.014 }, "front_bottom_full": { "hue": 236.915, "saturation": 0.249, "lightness": 0.667, "lightness_spread": 0.228 }, "back": { "hue": 206.291, "saturation": 0.448, "lightness": 0.818, "lightness_spread": 0.281 }, "spine": { "hue": 214.079, "saturation": 0.455, "lightness": 0.857, "lightness_spread": 0.271 } } },
14
+ "adventure.jpg": { width: 3506, height: 2329, regions: { "front_top": { "hue": 53.853, "saturation": 0.177, "lightness": 0.492, "lightness_spread": 0.065 }, "front_bottom": { "hue": 59.163, "saturation": 0.411, "lightness": 0.148, "lightness_spread": 0.085 }, "front_top_full": { "hue": 42.53, "saturation": 0.248, "lightness": 0.523, "lightness_spread": 0.046 }, "front_bottom_full": { "hue": 62.659, "saturation": 0.277, "lightness": 0.114, "lightness_spread": 0.089 }, "back": { "hue": 65.834, "saturation": 0.203, "lightness": 0.366, "lightness_spread": 0.13 }, "spine": { "hue": 51.015, "saturation": 0.19, "lightness": 0.415, "lightness_spread": 0.149 } } },
15
+ "awe.jpg": { width: 5000, height: 3333, regions: { "front_top": { "hue": 197.469, "saturation": 0.412, "lightness": 0.632, "lightness_spread": 0.1 }, "front_bottom": { "hue": 40.53, "saturation": 0.586, "lightness": 0.247, "lightness_spread": 0.219 }, "front_top_full": { "hue": 197.375, "saturation": 0.414, "lightness": 0.626, "lightness_spread": 0.1 }, "front_bottom_full": { "hue": 35.006, "saturation": 0.581, "lightness": 0.264, "lightness_spread": 0.191 }, "back": { "hue": 100.052, "saturation": 0.427, "lightness": 0.558, "lightness_spread": 0.307 }, "spine": { "hue": 55.877, "saturation": 0.445, "lightness": 0.797, "lightness_spread": 0.168 } } },
16
+ "beach.jpg": { width: 4621, height: 3072, regions: { "front_top": { "hue": 156.52, "saturation": 0.203, "lightness": 0.736, "lightness_spread": 0.087 }, "front_bottom": { "hue": 20.308, "saturation": 0.253, "lightness": 0.731, "lightness_spread": 0.056 }, "front_top_full": { "hue": 185.142, "saturation": 0.208, "lightness": 0.694, "lightness_spread": 0.079 }, "front_bottom_full": { "hue": 14.105, "saturation": 0.141, "lightness": 0.699, "lightness_spread": 0.053 }, "back": { "hue": 27.684, "saturation": 0.32, "lightness": 0.771, "lightness_spread": 0.081 }, "spine": { "hue": 73.634, "saturation": 0.126, "lightness": 0.691, "lightness_spread": 0.084 } } },
17
+ "bible.jpg": { width: 4608, height: 3456, regions: { "front_top": { "hue": 31.57, "saturation": 0.34, "lightness": 0.353, "lightness_spread": 0.128 }, "front_bottom": { "hue": 30.561, "saturation": 0.2, "lightness": 0.224, "lightness_spread": 0.201 }, "front_top_full": { "hue": 30.385, "saturation": 0.267, "lightness": 0.283, "lightness_spread": 0.088 }, "front_bottom_full": { "hue": 37.048, "saturation": 0.175, "lightness": 0.263, "lightness_spread": 0.224 }, "back": { "hue": 29.602, "saturation": 0.315, "lightness": 0.383, "lightness_spread": 0.216 }, "spine": { "hue": 24.906, "saturation": 0.374, "lightness": 0.469, "lightness_spread": 0.211 } } },
18
+ "bird.jpg": { width: 4036, height: 2691, regions: { "front_top": { "hue": 214.411, "saturation": 0.44, "lightness": 0.592, "lightness_spread": 0.087 }, "front_bottom": { "hue": 213.727, "saturation": 0.456, "lightness": 0.595, "lightness_spread": 0.012 }, "front_top_full": { "hue": 214.182, "saturation": 0.446, "lightness": 0.627, "lightness_spread": 0.016 }, "front_bottom_full": { "hue": 213.974, "saturation": 0.456, "lightness": 0.602, "lightness_spread": 0.012 }, "back": { "hue": 215.141, "saturation": 0.432, "lightness": 0.542, "lightness_spread": 0.143 }, "spine": { "hue": 216.838, "saturation": 0.407, "lightness": 0.504, "lightness_spread": 0.174 } } },
19
+ "books.jpg": { width: 4480, height: 3360, regions: { "front_top": { "hue": 102.789, "saturation": 0.114, "lightness": 0.936, "lightness_spread": 0.034 }, "front_bottom": { "hue": 163.276, "saturation": 0.14, "lightness": 0.717, "lightness_spread": 0.207 }, "front_top_full": { "hue": 114.719, "saturation": 0.103, "lightness": 0.931, "lightness_spread": 0.038 }, "front_bottom_full": { "hue": 177.717, "saturation": 0.153, "lightness": 0.743, "lightness_spread": 0.189 }, "back": { "hue": 119.388, "saturation": 0.133, "lightness": 0.776, "lightness_spread": 0.22 }, "spine": { "hue": 115.939, "saturation": 0.142, "lightness": 0.761, "lightness_spread": 0.202 } } },
20
+ "burning.jpg": { width: 4032, height: 3024, regions: { "front_top": { "hue": 2.3, "saturation": 0.725, "lightness": 0.042, "lightness_spread": 0.06 }, "front_bottom": { "hue": 6.292, "saturation": 0.907, "lightness": 0.095, "lightness_spread": 0.101 }, "front_top_full": { "hue": 1.512, "saturation": 0.982, "lightness": 0.05, "lightness_spread": 0.032 }, "front_bottom_full": { "hue": 10.809, "saturation": 0.951, "lightness": 0.155, "lightness_spread": 0.124 }, "back": { "hue": 2.963, "saturation": 0.668, "lightness": 0.044, "lightness_spread": 0.071 }, "spine": { "hue": 10.518, "saturation": 0.968, "lightness": 0.158, "lightness_spread": 0.129 } } },
21
+ "church.jpg": { width: 6240, height: 3510, regions: { "front_top": { "hue": 7.129, "saturation": 0.218, "lightness": 0.446, "lightness_spread": 0.101 }, "front_bottom": { "hue": 30.511, "saturation": 0.515, "lightness": 0.111, "lightness_spread": 0.055 }, "front_top_full": { "hue": 12.033, "saturation": 0.252, "lightness": 0.462, "lightness_spread": 0.101 }, "front_bottom_full": { "hue": 30.714, "saturation": 0.498, "lightness": 0.115, "lightness_spread": 0.05 }, "back": { "hue": 31.991, "saturation": 0.486, "lightness": 0.412, "lightness_spread": 0.246 }, "spine": { "hue": 32.696, "saturation": 0.46, "lightness": 0.449, "lightness_spread": 0.275 } } },
22
+ "city.jpg": { width: 3520, height: 1980, regions: { "front_top": { "hue": 213.093, "saturation": 0.237, "lightness": 0.824, "lightness_spread": 0.028 }, "front_bottom": { "hue": 222.764, "saturation": 0.417, "lightness": 0.184, "lightness_spread": 0.109 }, "front_top_full": { "hue": 213.017, "saturation": 0.246, "lightness": 0.818, "lightness_spread": 0.025 }, "front_bottom_full": { "hue": 222.134, "saturation": 0.406, "lightness": 0.196, "lightness_spread": 0.113 }, "back": { "hue": 249.993, "saturation": 0.313, "lightness": 0.598, "lightness_spread": 0.298 }, "spine": { "hue": 247.817, "saturation": 0.257, "lightness": 0.612, "lightness_spread": 0.267 } } },
23
+ "city_sunset.jpg": { width: 2532, height: 1537, regions: { "front_top": { "hue": 205.058, "saturation": 0.671, "lightness": 0.644, "lightness_spread": 0.069 }, "front_bottom": { "hue": 201.446, "saturation": 0.557, "lightness": 0.29, "lightness_spread": 0.099 }, "front_top_full": { "hue": 204.963, "saturation": 0.672, "lightness": 0.638, "lightness_spread": 0.069 }, "front_bottom_full": { "hue": 201.759, "saturation": 0.511, "lightness": 0.29, "lightness_spread": 0.103 }, "back": { "hue": 207.517, "saturation": 0.534, "lightness": 0.549, "lightness_spread": 0.196 }, "spine": { "hue": 210.585, "saturation": 0.497, "lightness": 0.585, "lightness_spread": 0.202 } } },
24
+ "countryside.jpg": { width: 4957, height: 3305, regions: { "front_top": { "hue": 229.906, "saturation": 0.282, "lightness": 0.921, "lightness_spread": 0.12 }, "front_bottom": { "hue": 74.204, "saturation": 0.633, "lightness": 0.312, "lightness_spread": 0.056 }, "front_top_full": { "hue": 227.19, "saturation": 0.249, "lightness": 0.968, "lightness_spread": 0.037 }, "front_bottom_full": { "hue": 73.87, "saturation": 0.64, "lightness": 0.314, "lightness_spread": 0.062 }, "back": { "hue": 95.017, "saturation": 0.36, "lightness": 0.561, "lightness_spread": 0.275 }, "spine": { "hue": 101.947, "saturation": 0.327, "lightness": 0.667, "lightness_spread": 0.277 } } },
25
+ "crops.jpg": { width: 3339, height: 1876, regions: { "front_top": { "hue": 192.799, "saturation": 0.341, "lightness": 0.606, "lightness_spread": 0.152 }, "front_bottom": { "hue": 35.026, "saturation": 0.663, "lightness": 0.449, "lightness_spread": 0.112 }, "front_top_full": { "hue": 192.145, "saturation": 0.415, "lightness": 0.564, "lightness_spread": 0.166 }, "front_bottom_full": { "hue": 34.742, "saturation": 0.707, "lightness": 0.42, "lightness_spread": 0.109 }, "back": { "hue": 46.564, "saturation": 0.419, "lightness": 0.579, "lightness_spread": 0.135 }, "spine": { "hue": 49.05, "saturation": 0.456, "lightness": 0.572, "lightness_spread": 0.152 } } },
26
+ "cross.jpg": { width: 4032, height: 3024, regions: { "front_top": { "hue": 65.291, "saturation": 0.002, "lightness": 0.004, "lightness_spread": 0 }, "front_bottom": { "hue": 21.86, "saturation": 0.736, "lightness": 0.041, "lightness_spread": 0.073 }, "front_top_full": { "hue": 60, "saturation": 0.004, "lightness": 0.004, "lightness_spread": 0 }, "front_bottom_full": { "hue": 6.788, "saturation": 0.827, "lightness": 0.071, "lightness_spread": 0.099 }, "back": { "hue": 57.399, "saturation": 0.252, "lightness": 0.012, "lightness_spread": 0.031 }, "spine": { "hue": 31.63, "saturation": 0.396, "lightness": 0.024, "lightness_spread": 0.041 } } },
27
+ "cross_sun.jpg": { width: 6000, height: 4000, regions: { "front_top": { "hue": 215.261, "saturation": 0.2, "lightness": 0.323, "lightness_spread": 0.037 }, "front_bottom": { "hue": 29.6, "saturation": 0.717, "lightness": 0.276, "lightness_spread": 0.221 }, "front_top_full": { "hue": 214.434, "saturation": 0.231, "lightness": 0.315, "lightness_spread": 0.035 }, "front_bottom_full": { "hue": 25.524, "saturation": 0.617, "lightness": 0.241, "lightness_spread": 0.202 }, "back": { "hue": 36.336, "saturation": 0.462, "lightness": 0.396, "lightness_spread": 0.175 }, "spine": { "hue": 43.042, "saturation": 0.473, "lightness": 0.431, "lightness_spread": 0.226 } } },
28
+ "crown.jpg": { width: 6000, height: 4000, regions: { "front_top": { "hue": 184.268, "saturation": 0.78, "lightness": 0.067, "lightness_spread": 0.071 }, "front_bottom": { "hue": 187.149, "saturation": 0.541, "lightness": 0.395, "lightness_spread": 0.241 }, "front_top_full": { "hue": 185.243, "saturation": 0.852, "lightness": 0.111, "lightness_spread": 0.075 }, "front_bottom_full": { "hue": 184.884, "saturation": 0.407, "lightness": 0.586, "lightness_spread": 0.195 }, "back": { "hue": 185.917, "saturation": 0.707, "lightness": 0.109, "lightness_spread": 0.109 }, "spine": { "hue": 184.658, "saturation": 0.623, "lightness": 0.191, "lightness_spread": 0.13 } } },
29
+ "desert.jpg": { width: 3872, height: 2178, regions: { "front_top": { "hue": 28.124, "saturation": 0.774, "lightness": 0.957, "lightness_spread": 0.054 }, "front_bottom": { "hue": 18.297, "saturation": 0.832, "lightness": 0.693, "lightness_spread": 0.062 }, "front_top_full": { "hue": 30.431, "saturation": 0.8, "lightness": 0.974, "lightness_spread": 0.005 }, "front_bottom_full": { "hue": 18.449, "saturation": 0.903, "lightness": 0.739, "lightness_spread": 0.043 }, "back": { "hue": 21.289, "saturation": 0.831, "lightness": 0.814, "lightness_spread": 0.126 }, "spine": { "hue": 22.343, "saturation": 0.845, "lightness": 0.848, "lightness_spread": 0.119 } } },
30
+ "desert_sunset.jpg": { width: 4263, height: 2822, regions: { "front_top": { "hue": 1.343, "saturation": 0.954, "lightness": 0.791, "lightness_spread": 0.019 }, "front_bottom": { "hue": 209.341, "saturation": 0.282, "lightness": 0.601, "lightness_spread": 0.107 }, "front_top_full": { "hue": 0.408, "saturation": 0.948, "lightness": 0.788, "lightness_spread": 0.019 }, "front_bottom_full": { "hue": 211.086, "saturation": 0.262, "lightness": 0.624, "lightness_spread": 0.049 }, "back": { "hue": 325.997, "saturation": 0.65, "lightness": 0.641, "lightness_spread": 0.165 }, "spine": { "hue": 311.993, "saturation": 0.75, "lightness": 0.653, "lightness_spread": 0.151 } } },
31
+ "earth.jpg": { width: 8256, height: 5504, regions: { "front_top": { "hue": 216.02, "saturation": 0.575, "lightness": 0.508, "lightness_spread": 0.313 }, "front_bottom": { "hue": 209.702, "saturation": 0.289, "lightness": 0.554, "lightness_spread": 0.135 }, "front_top_full": { "hue": 216.514, "saturation": 0.579, "lightness": 0.534, "lightness_spread": 0.327 }, "front_bottom_full": { "hue": 207.85, "saturation": 0.254, "lightness": 0.552, "lightness_spread": 0.112 }, "back": { "hue": 213.127, "saturation": 0.425, "lightness": 0.556, "lightness_spread": 0.186 }, "spine": { "hue": 210.071, "saturation": 0.468, "lightness": 0.569, "lightness_spread": 0.166 } } },
32
+ "earth_whole.jpg": { width: 3840, height: 4004, regions: { "front_top": { "hue": 0, "saturation": 0, "lightness": 0, "lightness_spread": 0 }, "front_bottom": { "hue": 217.681, "saturation": 0.036, "lightness": 0.048, "lightness_spread": 0.15 }, "front_top_full": { "hue": 0, "saturation": 0, "lightness": 0, "lightness_spread": 0 }, "front_bottom_full": { "hue": 217.675, "saturation": 0.034, "lightness": 0.049, "lightness_spread": 0.15 }, "back": { "hue": 221.204, "saturation": 0.046, "lightness": 0.1, "lightness_spread": 0.213 }, "spine": { "hue": 222.062, "saturation": 0.112, "lightness": 0.256, "lightness_spread": 0.281 } } },
33
+ "fire.jpg": { width: 4928, height: 3264, regions: { "front_top": { "hue": 336.921, "saturation": 0.854, "lightness": 0.018, "lightness_spread": 0.035 }, "front_bottom": { "hue": 354.143, "saturation": 0.911, "lightness": 0.103, "lightness_spread": 0.18 }, "front_top_full": { "hue": 325.737, "saturation": 0.855, "lightness": 0.013, "lightness_spread": 0.019 }, "front_bottom_full": { "hue": 352.679, "saturation": 0.914, "lightness": 0.127, "lightness_spread": 0.203 }, "back": { "hue": 352.717, "saturation": 0.883, "lightness": 0.041, "lightness_spread": 0.084 }, "spine": { "hue": 14.079, "saturation": 0.948, "lightness": 0.295, "lightness_spread": 0.24 } } },
34
+ "flowers.jpg": { width: 6000, height: 4000, regions: { "front_top": { "hue": 359.525, "saturation": 0.257, "lightness": 0.857, "lightness_spread": 0.059 }, "front_bottom": { "hue": 11.121, "saturation": 0.213, "lightness": 0.632, "lightness_spread": 0.118 }, "front_top_full": { "hue": 10.868, "saturation": 0.302, "lightness": 0.836, "lightness_spread": 0.033 }, "front_bottom_full": { "hue": 29.7, "saturation": 0.192, "lightness": 0.656, "lightness_spread": 0.121 }, "back": { "hue": 2.179, "saturation": 0.241, "lightness": 0.738, "lightness_spread": 0.141 }, "spine": { "hue": 352.672, "saturation": 0.305, "lightness": 0.784, "lightness_spread": 0.111 } } },
35
+ "flowers_field.jpg": { width: 6000, height: 4000, regions: { "front_top": { "hue": 22.369, "saturation": 0.653, "lightness": 0.774, "lightness_spread": 0.14 }, "front_bottom": { "hue": 272.415, "saturation": 0.606, "lightness": 0.192, "lightness_spread": 0.206 }, "front_top_full": { "hue": 27.782, "saturation": 0.772, "lightness": 0.743, "lightness_spread": 0.2 }, "front_bottom_full": { "hue": 277.885, "saturation": 0.565, "lightness": 0.21, "lightness_spread": 0.204 }, "back": { "hue": 331.502, "saturation": 0.616, "lightness": 0.465, "lightness_spread": 0.309 }, "spine": { "hue": 334.484, "saturation": 0.672, "lightness": 0.456, "lightness_spread": 0.298 } } },
36
+ "flowers_red.jpg": { width: 6000, height: 4000, regions: { "front_top": { "hue": 214.211, "saturation": 0.394, "lightness": 0.646, "lightness_spread": 0.077 }, "front_bottom": { "hue": 79.975, "saturation": 0.552, "lightness": 0.244, "lightness_spread": 0.187 }, "front_top_full": { "hue": 208.432, "saturation": 0.381, "lightness": 0.708, "lightness_spread": 0.033 }, "front_bottom_full": { "hue": 83.209, "saturation": 0.566, "lightness": 0.201, "lightness_spread": 0.143 }, "back": { "hue": 20.111, "saturation": 0.542, "lightness": 0.501, "lightness_spread": 0.196 }, "spine": { "hue": 22.569, "saturation": 0.549, "lightness": 0.532, "lightness_spread": 0.213 } } },
37
+ "funding.jpg": { width: 4979, height: 3319, regions: { "front_top": { "hue": 90.48, "saturation": 0.049, "lightness": 0.54, "lightness_spread": 0.068 }, "front_bottom": { "hue": 314.715, "saturation": 0.263, "lightness": 0.806, "lightness_spread": 0.223 }, "front_top_full": { "hue": 89.426, "saturation": 0.093, "lightness": 0.549, "lightness_spread": 0.097 }, "front_bottom_full": { "hue": 11.233, "saturation": 0.334, "lightness": 0.727, "lightness_spread": 0.267 }, "back": { "hue": 78.106, "saturation": 0.093, "lightness": 0.594, "lightness_spread": 0.18 }, "spine": { "hue": 19.89, "saturation": 0.128, "lightness": 0.568, "lightness_spread": 0.195 } } },
38
+ "grass.jpg": { width: 5500, height: 3667, regions: { "front_top": { "hue": 84.038, "saturation": 0.364, "lightness": 0.62, "lightness_spread": 0.108 }, "front_bottom": { "hue": 101.932, "saturation": 0.802, "lightness": 0.214, "lightness_spread": 0.113 }, "front_top_full": { "hue": 85.783, "saturation": 0.308, "lightness": 0.561, "lightness_spread": 0.114 }, "front_bottom_full": { "hue": 106.376, "saturation": 0.9, "lightness": 0.164, "lightness_spread": 0.091 }, "back": { "hue": 89.967, "saturation": 0.509, "lightness": 0.466, "lightness_spread": 0.184 }, "spine": { "hue": 93.954, "saturation": 0.649, "lightness": 0.431, "lightness_spread": 0.218 } } },
39
+ "green.jpg": { width: 5464, height: 3640, regions: { "front_top": { "hue": 88.154, "saturation": 0.371, "lightness": 0.682, "lightness_spread": 0.125 }, "front_bottom": { "hue": 70.958, "saturation": 0.75, "lightness": 0.139, "lightness_spread": 0.126 }, "front_top_full": { "hue": 37.851, "saturation": 0.386, "lightness": 0.73, "lightness_spread": 0.133 }, "front_bottom_full": { "hue": 76.589, "saturation": 0.81, "lightness": 0.099, "lightness_spread": 0.046 }, "back": { "hue": 72.64, "saturation": 0.582, "lightness": 0.35, "lightness_spread": 0.253 }, "spine": { "hue": 76.817, "saturation": 0.611, "lightness": 0.329, "lightness_spread": 0.259 } } },
40
+ "growing.jpg": { width: 3472, height: 1954, regions: { "front_top": { "hue": 28.178, "saturation": 0.058, "lightness": 0.36, "lightness_spread": 0.071 }, "front_bottom": { "hue": 33.518, "saturation": 0.103, "lightness": 0.234, "lightness_spread": 0.07 }, "front_top_full": { "hue": 27.718, "saturation": 0.062, "lightness": 0.345, "lightness_spread": 0.074 }, "front_bottom_full": { "hue": 43.211, "saturation": 0.132, "lightness": 0.226, "lightness_spread": 0.068 }, "back": { "hue": 33.722, "saturation": 0.076, "lightness": 0.32, "lightness_spread": 0.094 }, "spine": { "hue": 41.412, "saturation": 0.105, "lightness": 0.319, "lightness_spread": 0.112 } } },
41
+ "hills.jpg": { width: 5832, height: 3893, regions: { "front_top": { "hue": 192.459, "saturation": 0.283, "lightness": 0.823, "lightness_spread": 0.022 }, "front_bottom": { "hue": 193.529, "saturation": 0.185, "lightness": 0.694, "lightness_spread": 0.094 }, "front_top_full": { "hue": 192.98, "saturation": 0.285, "lightness": 0.812, "lightness_spread": 0.016 }, "front_bottom_full": { "hue": 195.772, "saturation": 0.194, "lightness": 0.638, "lightness_spread": 0.084 }, "back": { "hue": 187.953, "saturation": 0.211, "lightness": 0.809, "lightness_spread": 0.065 }, "spine": { "hue": 189.125, "saturation": 0.214, "lightness": 0.786, "lightness_spread": 0.059 } } },
42
+ "hills_trees.jpg": { width: 5577, height: 3718, regions: { "front_top": { "hue": 26.64, "saturation": 0.682, "lightness": 0.647, "lightness_spread": 0.094 }, "front_bottom": { "hue": 29.738, "saturation": 0.521, "lightness": 0.21, "lightness_spread": 0.187 }, "front_top_full": { "hue": 28.288, "saturation": 0.709, "lightness": 0.635, "lightness_spread": 0.047 }, "front_bottom_full": { "hue": 28.319, "saturation": 0.501, "lightness": 0.29, "lightness_spread": 0.185 }, "back": { "hue": 28.031, "saturation": 0.541, "lightness": 0.422, "lightness_spread": 0.253 }, "spine": { "hue": 28.831, "saturation": 0.506, "lightness": 0.517, "lightness_spread": 0.153 } } },
43
+ "island.jpg": { width: 5480, height: 3654, regions: { "front_top": { "hue": 183.004, "saturation": 0.739, "lightness": 0.598, "lightness_spread": 0.27 }, "front_bottom": { "hue": 29.886, "saturation": 0.359, "lightness": 0.753, "lightness_spread": 0.075 }, "front_top_full": { "hue": 205.803, "saturation": 0.896, "lightness": 0.81, "lightness_spread": 0.042 }, "front_bottom_full": { "hue": 25.859, "saturation": 0.219, "lightness": 0.732, "lightness_spread": 0.079 }, "back": { "hue": 59.35, "saturation": 0.527, "lightness": 0.441, "lightness_spread": 0.269 }, "spine": { "hue": 115.095, "saturation": 0.528, "lightness": 0.558, "lightness_spread": 0.267 } } },
44
+ "israel.jpg": { width: 5019, height: 3346, regions: { "front_top": { "hue": 212.339, "saturation": 0.53, "lightness": 0.636, "lightness_spread": 0.087 }, "front_bottom": { "hue": 37.962, "saturation": 0.788, "lightness": 0.747, "lightness_spread": 0.191 }, "front_top_full": { "hue": 212.782, "saturation": 0.511, "lightness": 0.652, "lightness_spread": 0.081 }, "front_bottom_full": { "hue": 43.884, "saturation": 0.771, "lightness": 0.691, "lightness_spread": 0.239 }, "back": { "hue": 221.786, "saturation": 0.471, "lightness": 0.684, "lightness_spread": 0.123 }, "spine": { "hue": 228.331, "saturation": 0.48, "lightness": 0.715, "lightness_spread": 0.123 } } },
45
+ "israel_lake.jpg": { width: 5184, height: 3456, regions: { "front_top": { "hue": 200.86, "saturation": 0.787, "lightness": 0.428, "lightness_spread": 0.08 }, "front_bottom": { "hue": 236.092, "saturation": 0.485, "lightness": 0.191, "lightness_spread": 0.065 }, "front_top_full": { "hue": 199.559, "saturation": 0.789, "lightness": 0.438, "lightness_spread": 0.086 }, "front_bottom_full": { "hue": 243.249, "saturation": 0.485, "lightness": 0.195, "lightness_spread": 0.074 }, "back": { "hue": 207.865, "saturation": 0.476, "lightness": 0.432, "lightness_spread": 0.18 }, "spine": { "hue": 204.557, "saturation": 0.535, "lightness": 0.429, "lightness_spread": 0.177 } } },
46
+ "lake.jpg": { width: 4000, height: 3000, regions: { "front_top": { "hue": 216.275, "saturation": 0.671, "lightness": 0.704, "lightness_spread": 0.131 }, "front_bottom": { "hue": 178.136, "saturation": 0.131, "lightness": 0.521, "lightness_spread": 0.096 }, "front_top_full": { "hue": 216.746, "saturation": 0.682, "lightness": 0.713, "lightness_spread": 0.043 }, "front_bottom_full": { "hue": 191.587, "saturation": 0.163, "lightness": 0.517, "lightness_spread": 0.075 }, "back": { "hue": 207.78, "saturation": 0.357, "lightness": 0.489, "lightness_spread": 0.261 }, "spine": { "hue": 208.761, "saturation": 0.378, "lightness": 0.557, "lightness_spread": 0.203 } } },
47
+ "lake_tree.jpg": { width: 5472, height: 3648, regions: { "front_top": { "hue": 214.387, "saturation": 0.247, "lightness": 0.773, "lightness_spread": 0.07 }, "front_bottom": { "hue": 204.49, "saturation": 0.12, "lightness": 0.538, "lightness_spread": 0.102 }, "front_top_full": { "hue": 216.486, "saturation": 0.197, "lightness": 0.781, "lightness_spread": 0.061 }, "front_bottom_full": { "hue": 196.376, "saturation": 0.086, "lightness": 0.507, "lightness_spread": 0.131 }, "back": { "hue": 212.441, "saturation": 0.238, "lightness": 0.626, "lightness_spread": 0.17 }, "spine": { "hue": 200.983, "saturation": 0.223, "lightness": 0.655, "lightness_spread": 0.196 } } },
48
+ "lion.jpg": { width: 5472, height: 3648, regions: { "front_top": { "hue": 40.978, "saturation": 0.737, "lightness": 0.67, "lightness_spread": 0.02 }, "front_bottom": { "hue": 26.342, "saturation": 0.54, "lightness": 0.269, "lightness_spread": 0.091 }, "front_top_full": { "hue": 41.05, "saturation": 0.771, "lightness": 0.663, "lightness_spread": 0.02 }, "front_bottom_full": { "hue": 25.125, "saturation": 0.544, "lightness": 0.287, "lightness_spread": 0.085 }, "back": { "hue": 32.332, "saturation": 0.635, "lightness": 0.485, "lightness_spread": 0.185 }, "spine": { "hue": 33.272, "saturation": 0.67, "lightness": 0.56, "lightness_spread": 0.127 } } },
49
+ "lost_sheep.jpg": { width: 5931, height: 3336, regions: { "front_top": { "hue": 204.631, "saturation": 0.338, "lightness": 0.297, "lightness_spread": 0.068 }, "front_bottom": { "hue": 27.535, "saturation": 0.675, "lightness": 0.294, "lightness_spread": 0.118 }, "front_top_full": { "hue": 204.723, "saturation": 0.341, "lightness": 0.292, "lightness_spread": 0.064 }, "front_bottom_full": { "hue": 27.238, "saturation": 0.635, "lightness": 0.322, "lightness_spread": 0.12 }, "back": { "hue": 28.646, "saturation": 0.496, "lightness": 0.341, "lightness_spread": 0.15 }, "spine": { "hue": 29.267, "saturation": 0.496, "lightness": 0.373, "lightness_spread": 0.138 } } },
50
+ "mist.jpg": { width: 2200, height: 1467, regions: { "front_top": { "hue": 26.249, "saturation": 0.702, "lightness": 0.967, "lightness_spread": 0.024 }, "front_bottom": { "hue": 209.76, "saturation": 0.359, "lightness": 0.136, "lightness_spread": 0.061 }, "front_top_full": { "hue": 26.513, "saturation": 0.661, "lightness": 0.958, "lightness_spread": 0.033 }, "front_bottom_full": { "hue": 205.366, "saturation": 0.364, "lightness": 0.142, "lightness_spread": 0.07 }, "back": { "hue": 29.044, "saturation": 0.46, "lightness": 0.571, "lightness_spread": 0.378 }, "spine": { "hue": 42.01, "saturation": 0.4, "lightness": 0.546, "lightness_spread": 0.36 } } },
51
+ "mountains.jpg": { width: 6316, height: 4211, regions: { "front_top": { "hue": 57.715, "saturation": 0.745, "lightness": 0.917, "lightness_spread": 0.171 }, "front_bottom": { "hue": 215.393, "saturation": 0.365, "lightness": 0.131, "lightness_spread": 0.131 }, "front_top_full": { "hue": 53.684, "saturation": 0.757, "lightness": 0.923, "lightness_spread": 0.156 }, "front_bottom_full": { "hue": 216.876, "saturation": 0.301, "lightness": 0.168, "lightness_spread": 0.148 }, "back": { "hue": 84.473, "saturation": 0.434, "lightness": 0.519, "lightness_spread": 0.352 }, "spine": { "hue": 153.543, "saturation": 0.321, "lightness": 0.435, "lightness_spread": 0.299 } } },
52
+ "opening.jpg": { width: 4906, height: 3258, regions: { "front_top": { "hue": 30.056, "saturation": 0.05, "lightness": 0.073, "lightness_spread": 0.116 }, "front_bottom": { "hue": 33.193, "saturation": 0.12, "lightness": 0.025, "lightness_spread": 0.043 }, "front_top_full": { "hue": 43.213, "saturation": 0.054, "lightness": 0.119, "lightness_spread": 0.099 }, "front_bottom_full": { "hue": 33.204, "saturation": 0.132, "lightness": 0.032, "lightness_spread": 0.047 }, "back": { "hue": 26.211, "saturation": 0.128, "lightness": 0.069, "lightness_spread": 0.175 }, "spine": { "hue": 33.939, "saturation": 0.218, "lightness": 0.461, "lightness_spread": 0.432 } } },
53
+ "plane.jpg": { width: 4288, height: 2848, regions: { "front_top": { "hue": 195.996, "saturation": 0.304, "lightness": 0.706, "lightness_spread": 0.113 }, "front_bottom": { "hue": 261.078, "saturation": 0.333, "lightness": 0.527, "lightness_spread": 0.195 }, "front_top_full": { "hue": 27.271, "saturation": 0.317, "lightness": 0.778, "lightness_spread": 0.085 }, "front_bottom_full": { "hue": 22.135, "saturation": 0.328, "lightness": 0.673, "lightness_spread": 0.143 }, "back": { "hue": 206.021, "saturation": 0.322, "lightness": 0.581, "lightness_spread": 0.16 }, "spine": { "hue": 31.419, "saturation": 0.405, "lightness": 0.725, "lightness_spread": 0.185 } } },
54
+ "plant.jpg": { width: 6000, height: 4000, regions: { "front_top": { "hue": 78.493, "saturation": 0.103, "lightness": 0.018, "lightness_spread": 0.075 }, "front_bottom": { "hue": 19.299, "saturation": 0.562, "lightness": 0.057, "lightness_spread": 0.062 }, "front_top_full": { "hue": 61.914, "saturation": 0.138, "lightness": 0.02, "lightness_spread": 0.084 }, "front_bottom_full": { "hue": 16.765, "saturation": 0.628, "lightness": 0.09, "lightness_spread": 0.076 }, "back": { "hue": 40.407, "saturation": 0.257, "lightness": 0.026, "lightness_spread": 0.075 }, "spine": { "hue": 102.277, "saturation": 0.448, "lightness": 0.075, "lightness_spread": 0.139 } } },
55
+ "plant_table.jpg": { width: 7351, height: 4903, regions: { "front_top": { "hue": 198.339, "saturation": 0.773, "lightness": 0.049, "lightness_spread": 0.082 }, "front_bottom": { "hue": 149.308, "saturation": 0.6, "lightness": 0.21, "lightness_spread": 0.202 }, "front_top_full": { "hue": 200.271, "saturation": 0.715, "lightness": 0.072, "lightness_spread": 0.12 }, "front_bottom_full": { "hue": 22.69, "saturation": 0.348, "lightness": 0.249, "lightness_spread": 0.175 }, "back": { "hue": 193.39, "saturation": 0.818, "lightness": 0.062, "lightness_spread": 0.116 }, "spine": { "hue": 183.36, "saturation": 0.615, "lightness": 0.234, "lightness_spread": 0.217 } } },
56
+ "reflecting.jpg": { width: 6000, height: 4000, regions: { "front_top": { "hue": 32.362, "saturation": 0.601, "lightness": 0.746, "lightness_spread": 0.06 }, "front_bottom": { "hue": 30.088, "saturation": 0.653, "lightness": 0.086, "lightness_spread": 0.051 }, "front_top_full": { "hue": 31.463, "saturation": 0.75, "lightness": 0.782, "lightness_spread": 0.018 }, "front_bottom_full": { "hue": 28.549, "saturation": 0.732, "lightness": 0.121, "lightness_spread": 0.051 }, "back": { "hue": 30.002, "saturation": 0.493, "lightness": 0.381, "lightness_spread": 0.299 }, "spine": { "hue": 27.784, "saturation": 0.604, "lightness": 0.383, "lightness_spread": 0.334 } } },
57
+ "rocket.jpg": { width: 6437, height: 4291, regions: { "front_top": { "hue": 220.291, "saturation": 0.117, "lightness": 0.427, "lightness_spread": 0.026 }, "front_bottom": { "hue": 33.292, "saturation": 0.381, "lightness": 0.269, "lightness_spread": 0.239 }, "front_top_full": { "hue": 222.485, "saturation": 0.107, "lightness": 0.438, "lightness_spread": 0.027 }, "front_bottom_full": { "hue": 33.391, "saturation": 0.411, "lightness": 0.317, "lightness_spread": 0.276 }, "back": { "hue": 4.266, "saturation": 0.18, "lightness": 0.38, "lightness_spread": 0.131 }, "spine": { "hue": 14.518, "saturation": 0.182, "lightness": 0.396, "lightness_spread": 0.131 } } },
58
+ "sea.jpg": { width: 6000, height: 4000, regions: { "front_top": { "hue": 187.102, "saturation": 0.188, "lightness": 0.844, "lightness_spread": 0.025 }, "front_bottom": { "hue": 190.313, "saturation": 0.395, "lightness": 0.306, "lightness_spread": 0.137 }, "front_top_full": { "hue": 183.78, "saturation": 0.184, "lightness": 0.864, "lightness_spread": 0.016 }, "front_bottom_full": { "hue": 190.437, "saturation": 0.308, "lightness": 0.379, "lightness_spread": 0.156 }, "back": { "hue": 190.777, "saturation": 0.367, "lightness": 0.497, "lightness_spread": 0.281 }, "spine": { "hue": 188.845, "saturation": 0.291, "lightness": 0.562, "lightness_spread": 0.268 } } },
59
+ "sheep.jpg": { width: 5184, height: 3456, regions: { "front_top": { "hue": 206.693, "saturation": 0.505, "lightness": 0.726, "lightness_spread": 0.247 }, "front_bottom": { "hue": 19.526, "saturation": 0.296, "lightness": 0.309, "lightness_spread": 0.179 }, "front_top_full": { "hue": 204.645, "saturation": 0.507, "lightness": 0.843, "lightness_spread": 0.111 }, "front_bottom_full": { "hue": 20.808, "saturation": 0.24, "lightness": 0.332, "lightness_spread": 0.207 }, "back": { "hue": 15.911, "saturation": 0.387, "lightness": 0.478, "lightness_spread": 0.246 }, "spine": { "hue": 213.3, "saturation": 0.361, "lightness": 0.617, "lightness_spread": 0.236 } } },
60
+ "snow.jpg": { width: 4163, height: 2769, regions: { "front_top": { "hue": 205.584, "saturation": 0.109, "lightness": 0.838, "lightness_spread": 0.091 }, "front_bottom": { "hue": 201.878, "saturation": 0.137, "lightness": 0.553, "lightness_spread": 0.248 }, "front_top_full": { "hue": 207.511, "saturation": 0.102, "lightness": 0.848, "lightness_spread": 0.009 }, "front_bottom_full": { "hue": 202.758, "saturation": 0.127, "lightness": 0.553, "lightness_spread": 0.221 }, "back": { "hue": 202.002, "saturation": 0.122, "lightness": 0.644, "lightness_spread": 0.24 }, "spine": { "hue": 205.215, "saturation": 0.095, "lightness": 0.74, "lightness_spread": 0.147 } } },
61
+ "snow_trees.jpg": { width: 4199, height: 2362, regions: { "front_top": { "hue": 14.593, "saturation": 0.868, "lightness": 0.841, "lightness_spread": 0.005 }, "front_bottom": { "hue": 242.436, "saturation": 0.25, "lightness": 0.711, "lightness_spread": 0.094 }, "front_top_full": { "hue": 11.62, "saturation": 0.794, "lightness": 0.845, "lightness_spread": 0.004 }, "front_bottom_full": { "hue": 247.651, "saturation": 0.225, "lightness": 0.729, "lightness_spread": 0.089 }, "back": { "hue": 8.932, "saturation": 0.604, "lightness": 0.776, "lightness_spread": 0.093 }, "spine": { "hue": 6.137, "saturation": 0.554, "lightness": 0.796, "lightness_spread": 0.075 } } },
62
+ "stars.jpg": { width: 6000, height: 4000, regions: { "front_top": { "hue": 189.927, "saturation": 0.443, "lightness": 0.179, "lightness_spread": 0.027 }, "front_bottom": { "hue": 31.853, "saturation": 0.196, "lightness": 0.124, "lightness_spread": 0.045 }, "front_top_full": { "hue": 190.529, "saturation": 0.44, "lightness": 0.192, "lightness_spread": 0.025 }, "front_bottom_full": { "hue": 30.242, "saturation": 0.223, "lightness": 0.131, "lightness_spread": 0.05 }, "back": { "hue": 183.075, "saturation": 0.361, "lightness": 0.189, "lightness_spread": 0.072 }, "spine": { "hue": 176.444, "saturation": 0.333, "lightness": 0.246, "lightness_spread": 0.109 } } },
63
+ "sunset.jpg": { width: 6000, height: 4000, regions: { "front_top": { "hue": 274.427, "saturation": 0.16, "lightness": 0.301, "lightness_spread": 0.13 }, "front_bottom": { "hue": 8.075, "saturation": 0.244, "lightness": 0.16, "lightness_spread": 0.195 }, "front_top_full": { "hue": 252.675, "saturation": 0.156, "lightness": 0.331, "lightness_spread": 0.126 }, "front_bottom_full": { "hue": 16.226, "saturation": 0.243, "lightness": 0.183, "lightness_spread": 0.209 }, "back": { "hue": 354.746, "saturation": 0.287, "lightness": 0.282, "lightness_spread": 0.227 }, "spine": { "hue": 16.92, "saturation": 0.393, "lightness": 0.448, "lightness_spread": 0.258 } } },
64
+ "sunset_tree.jpg": { width: 5762, height: 3841, regions: { "front_top": { "hue": 196.994, "saturation": 0.954, "lightness": 0.165, "lightness_spread": 0.048 }, "front_bottom": { "hue": 180.153, "saturation": 0.33, "lightness": 0.176, "lightness_spread": 0.13 }, "front_top_full": { "hue": 195.813, "saturation": 0.977, "lightness": 0.188, "lightness_spread": 0.04 }, "front_bottom_full": { "hue": 167.16, "saturation": 0.31, "lightness": 0.263, "lightness_spread": 0.141 }, "back": { "hue": 197.466, "saturation": 0.551, "lightness": 0.151, "lightness_spread": 0.101 }, "spine": { "hue": 194.694, "saturation": 0.504, "lightness": 0.25, "lightness_spread": 0.147 } } },
65
+ "surge.jpg": { width: 6000, height: 4000, regions: { "front_top": { "hue": 203.871, "saturation": 0.247, "lightness": 0.642, "lightness_spread": 0.069 }, "front_bottom": { "hue": 171.049, "saturation": 0.162, "lightness": 0.514, "lightness_spread": 0.208 }, "front_top_full": { "hue": 203.103, "saturation": 0.248, "lightness": 0.655, "lightness_spread": 0.049 }, "front_bottom_full": { "hue": 162.989, "saturation": 0.145, "lightness": 0.529, "lightness_spread": 0.189 }, "back": { "hue": 195.534, "saturation": 0.249, "lightness": 0.59, "lightness_spread": 0.181 }, "spine": { "hue": 198.382, "saturation": 0.229, "lightness": 0.623, "lightness_spread": 0.174 } } },
66
+ "sword.jpg": { width: 6264, height: 4176, regions: { "front_top": { "hue": 207.422, "saturation": 0.322, "lightness": 0.66, "lightness_spread": 0.204 }, "front_bottom": { "hue": 207.147, "saturation": 0.337, "lightness": 0.515, "lightness_spread": 0.203 }, "front_top_full": { "hue": 207.493, "saturation": 0.317, "lightness": 0.586, "lightness_spread": 0.251 }, "front_bottom_full": { "hue": 206.121, "saturation": 0.38, "lightness": 0.563, "lightness_spread": 0.217 }, "back": { "hue": 208.648, "saturation": 0.298, "lightness": 0.583, "lightness_spread": 0.19 }, "spine": { "hue": 207.453, "saturation": 0.327, "lightness": 0.656, "lightness_spread": 0.15 } } },
67
+ "tomb.jpg": { width: 4928, height: 3264, regions: { "front_top": { "hue": 29.481, "saturation": 0.207, "lightness": 0.442, "lightness_spread": 0.223 }, "front_bottom": { "hue": 27.291, "saturation": 0.253, "lightness": 0.291, "lightness_spread": 0.195 }, "front_top_full": { "hue": 29.453, "saturation": 0.179, "lightness": 0.466, "lightness_spread": 0.234 }, "front_bottom_full": { "hue": 27.081, "saturation": 0.307, "lightness": 0.218, "lightness_spread": 0.073 }, "back": { "hue": 28.492, "saturation": 0.213, "lightness": 0.381, "lightness_spread": 0.246 }, "spine": { "hue": 27.539, "saturation": 0.281, "lightness": 0.281, "lightness_spread": 0.106 } } },
68
+ "tropical.jpg": { width: 2560, height: 1440, regions: { "front_top": { "hue": 184.914, "saturation": 0.683, "lightness": 0.465, "lightness_spread": 0.213 }, "front_bottom": { "hue": 147.063, "saturation": 0.477, "lightness": 0.24, "lightness_spread": 0.146 }, "front_top_full": { "hue": 190.393, "saturation": 0.768, "lightness": 0.509, "lightness_spread": 0.064 }, "front_bottom_full": { "hue": 164.906, "saturation": 0.488, "lightness": 0.287, "lightness_spread": 0.153 }, "back": { "hue": 152.551, "saturation": 0.502, "lightness": 0.273, "lightness_spread": 0.219 }, "spine": { "hue": 166.024, "saturation": 0.519, "lightness": 0.421, "lightness_spread": 0.212 } } },
69
+ "vineyard.jpg": { width: 5272, height: 3948, regions: { "front_top": { "hue": 212.221, "saturation": 0.438, "lightness": 0.741, "lightness_spread": 0.102 }, "front_bottom": { "hue": 62.602, "saturation": 0.623, "lightness": 0.197, "lightness_spread": 0.086 }, "front_top_full": { "hue": 210.808, "saturation": 0.413, "lightness": 0.808, "lightness_spread": 0.091 }, "front_bottom_full": { "hue": 63.029, "saturation": 0.628, "lightness": 0.19, "lightness_spread": 0.079 }, "back": { "hue": 82.509, "saturation": 0.527, "lightness": 0.391, "lightness_spread": 0.239 }, "spine": { "hue": 76.283, "saturation": 0.491, "lightness": 0.406, "lightness_spread": 0.264 } } },
70
+ "wasteland.jpg": { width: 5474, height: 3649, regions: { "front_top": { "hue": 214.698, "saturation": 0.276, "lightness": 0.268, "lightness_spread": 0.14 }, "front_bottom": { "hue": 26.97, "saturation": 0.2, "lightness": 0.161, "lightness_spread": 0.065 }, "front_top_full": { "hue": 213.884, "saturation": 0.299, "lightness": 0.237, "lightness_spread": 0.122 }, "front_bottom_full": { "hue": 25.05, "saturation": 0.231, "lightness": 0.167, "lightness_spread": 0.053 }, "back": { "hue": 210.06, "saturation": 0.186, "lightness": 0.254, "lightness_spread": 0.15 }, "spine": { "hue": 339.732, "saturation": 0.204, "lightness": 0.283, "lightness_spread": 0.148 } } },
71
+ "wilderness.jpg": { width: 4032, height: 3024, regions: { "front_top": { "hue": 215.436, "saturation": 0.233, "lightness": 0.719, "lightness_spread": 0.101 }, "front_bottom": { "hue": 31.604, "saturation": 0.489, "lightness": 0.502, "lightness_spread": 0.141 }, "front_top_full": { "hue": 215.056, "saturation": 0.25, "lightness": 0.761, "lightness_spread": 0.044 }, "front_bottom_full": { "hue": 31.914, "saturation": 0.46, "lightness": 0.521, "lightness_spread": 0.121 }, "back": { "hue": 32.365, "saturation": 0.358, "lightness": 0.582, "lightness_spread": 0.185 }, "spine": { "hue": 30.638, "saturation": 0.33, "lightness": 0.611, "lightness_spread": 0.158 } } },
72
+ "work.jpg": { width: 5184, height: 3456, regions: { "front_top": { "hue": 201.939, "saturation": 0.574, "lightness": 0.963, "lightness_spread": 0.06 }, "front_bottom": { "hue": 204.081, "saturation": 0.266, "lightness": 0.569, "lightness_spread": 0.315 }, "front_top_full": { "hue": 207.197, "saturation": 0.595, "lightness": 0.983, "lightness_spread": 0.014 }, "front_bottom_full": { "hue": 236.915, "saturation": 0.249, "lightness": 0.667, "lightness_spread": 0.228 }, "back": { "hue": 206.291, "saturation": 0.448, "lightness": 0.818, "lightness_spread": 0.281 }, "spine": { "hue": 214.079, "saturation": 0.455, "lightness": 0.857, "lightness_spread": 0.271 } } },
72
73
  };
73
74
  //# sourceMappingURL=builtin_bg_regions.js.map
@@ -2,7 +2,7 @@
2
2
  // Edit the source .typ files instead, then rerun the generator build
3
3
  /** The cover.typ / _helpers.typ templates baked in at this package version */
4
4
  export const DEFAULT_TEMPLATES = {
5
- cover: "\n// Book cover template — receives all variables from _data.typ\n#import \"_data.typ\": *\n\n// -- Page setup --\n#set page(\n width: total_width,\n height: total_height,\n margin: 0mm,\n)\n\n#set text(font: font_body_family)\n#set quote(block: true)\n#show quote: set pad(x: 3em)\n\n// ============================================================\n// Helper functions\n// ============================================================\n\n#import \"_helpers.typ\": *\n\n\n// ============================================================\n// Layer 1 — Background fills\n// ============================================================\n\n#place(\n top + left,\n rect(\n width: total_width,\n height: total_height,\n fill: if has_front_gradient {\n gradient.linear(\n (color_front_gradient_start, 0%),\n (color_front_bg, 30%),\n (color_front_bg, 45%),\n (color_front_gradient_end, 100%),\n angle: -40deg,\n )\n } else {\n color_front_bg\n },\n ),\n)\n\n\n#if has_pattern {\n let tiled = pattern(\n size: (pattern_tile_w, pattern_tile_h),\n image(\"pattern.svg\", width: pattern_tile_w, height: pattern_tile_h),\n )\n place(\n top + left,\n rect(width: total_width, height: total_height, fill: tiled),\n )\n}\n\n\n// ============================================================\n// Layer 2 — Background image\n// ============================================================\n\n#if has_image {\n if image_coverage == \"full\" {\n // Full spread: image covers entire canvas (back + spine + front)\n place(\n top + left,\n dx: 0mm,\n dy: 0mm,\n image(\n image_filename,\n width: total_width,\n height: total_height,\n fit: if image_is_vector { \"stretch\" } else { \"cover\" },\n ),\n )\n } else if image_coverage == \"painted\" {\n // Painted: image inset on front cover — 15mm margin horizontally and from bottom, 50% trim height\n let painted_w = face_width - 30mm\n let painted_h = face_height * 0.5 // sync: index.ts painted_h = face_height * 0.5\n place(\n top + left,\n dx: front_x + 15mm,\n dy: bleed + face_height * (1/3),\n image(\n image_filename,\n width: painted_w,\n height: painted_h,\n fit: \"stretch\", // Should be exact but just in case, ensures full frame visible\n ),\n )\n } else if image_coverage == \"feature\" {\n // Feature: same size and position as painted but with rounded corners via box clip\n let painted_w = face_width - 30mm\n let painted_h = face_height * 0.5\n place(\n top + left,\n dx: front_x + 15mm,\n dy: bleed + face_height * (1/3),\n box(\n width: painted_w,\n height: painted_h,\n radius: 6mm,\n clip: true,\n image(\n image_filename,\n width: painted_w,\n height: painted_h,\n fit: \"cover\",\n ),\n ),\n )\n } else if image_coverage == \"front_partial\" {\n // Lower 2/3: image covers the full front face width, bottom 2/3 of height, flush to the bottom trim edge\n let w = face_width\n let h = face_height * (2 / 3)\n place(\n top + left,\n dx: front_x,\n dy: bleed + face_height - h,\n image(\n image_filename,\n width: w,\n height: h,\n fit: \"cover\",\n ),\n )\n } else {\n // Front only: image covers the front panel including all bleed margins\n place(\n top + left,\n dx: front_x,\n dy: 0mm,\n image(\n image_filename,\n width: face_width + bleed,\n height: total_height,\n fit: if image_is_vector { \"stretch\" } else { \"cover\" },\n ),\n )\n }\n}\n\n\n// ============================================================\n// Layer 2b — Icon on front cover\n// ============================================================\n\n#if has_icon {\n let icon_size = calc.min(face_height * 0.4, face_width * 0.8)\n if icon_mode != \"center\" {\n icon_size = calc.min(face_height * 0.5, face_width * 0.8)\n }\n // Apply user-controlled size multiplier (1.0 = default)\n icon_size = icon_size * icon_size_mod\n let offset_icon_dx = front_x + (face_width - icon_size) + (icon_size * 0.15)\n let offset_icon_dy = bleed * -1 + (icon_size * 0.15)\n\n // Place ghost icons first (if any) so below main one\n if icon_mode == \"echo\" {\n place(\n bottom + left,\n dx: offset_icon_dx + icon_size * 0.1,\n dy: offset_icon_dy - icon_size * 0.15,\n image(\"icon_ghost.svg\", width: icon_size, height: icon_size),\n )\n place(\n bottom + left,\n dx: offset_icon_dx - icon_size * 0.1,\n dy: offset_icon_dy + icon_size * 0.1,\n image(\"icon_ghost2.svg\", width: icon_size, height: icon_size),\n )\n }\n\n if icon_mode == \"center\" {\n // Center — large icon centered horizontally, positioned in the lower/mid section\n place(\n bottom + left,\n dx: front_x + (face_width - icon_size) / 2,\n dy: (face_height * 0.2 + bleed) * -1,\n image(\"icon_main.svg\", width: icon_size, height: icon_size),\n )\n } else {\n // Offset/echo: large icon offset towards bottom-right, slightly overflowing the trim edge\n place(\n bottom + left,\n dx: offset_icon_dx,\n dy: offset_icon_dy,\n image(\"icon_main.svg\", width: icon_size, height: icon_size),\n )\n }\n}\n\n\n// ============================================================\n// Layer 2c — Spine background\n// ============================================================\n\n// Spine background (only when spread has a spine) — drawn after pattern so it covers it\n#if has_spine and color_spine_bg != none {\n place(\n top + left,\n dx: spine_x,\n dy: 0mm,\n rect(width: spine_width, height: total_height, fill: color_spine_bg),\n )\n}\n\n\n// ============================================================\n// Layer 3 — Back panel content\n// ============================================================\n\n// back_margin, back_content_w, spiral_margin imported from _data.typ\n\n// Blurb container box with background color and 1cm padding around the text\n// Content is scaled down if it would overflow the available back-panel height\n#if has_blurb {\n let blurb_content = {\n set par(justify: true) if blurb_justify\n set par(leading: 1em * blurb_spacing)\n // Compact heading styles for a small blurb box — size and leading are hardcoded\n // independent of blurb_spacing, since headings rarely wrap and need tight leading\n show heading.where(level: 1): it => {\n set block(above: 2em, below: 1em)\n set text(size: 1.3em)\n set par(leading: 0.5em)\n it\n }\n show heading.where(level: 2): it => {\n set block(above: 2em, below: 1em)\n set text(size: 1.15em)\n set par(leading: 0.5em)\n it\n }\n align(blurb_alignment, text(\n font: font_blurb_family,\n size: fs_back_blurb * blurb_size,\n fill: color_blurb,\n blurb,\n ))\n }\n let blurb_max_h = back_content_h - blurb_padding * 2\n place(\n top + left,\n // Centered within the safe area, not the full face — spiral_margin is 0 for other bindings\n dx: back_x + (face_width - spiral_margin - blurb_width) / 2,\n dy: bleed + back_margin,\n rect(\n width: blurb_width,\n fill: color_blurb_bg,\n stroke: none,\n radius: 1.5mm,\n inset: blurb_padding,\n context {\n let avail_w = blurb_width - blurb_padding * 2\n let natural = measure(blurb_content, width: avail_w)\n if natural.height > blurb_max_h {\n let ratio = blurb_max_h / natural.height\n scale(x: ratio * 100%, y: ratio * 100%, reflow: true, blurb_content)\n } else {\n blurb_content\n }\n },\n ),\n )\n}\n\n// ISBN barcode — position and size from printing-services cover_region_barcode\n#if has_barcode {\n place(\n bottom + left,\n dx: barcode_x,\n dy: barcode_y * -1,\n image(\"barcode.svg\", width: barcode_w, height: barcode_h),\n )\n}\n\n// ============================================================\n// Layer 4 — Spine content\n// ============================================================\n\n#if has_spine and has_spine_text and fs_spine_title > 0mm {\n // Title rotated along spine (bottom-to-top reading direction).\n // place() anchors the unrotated bounding box top-left, so we subtract\n // half the unrotated dimensions to keep the visual center on the spine.\n place(\n top + left,\n dx: spine_x + spine_width * 0.5 - face_height * 0.4,\n dy: bleed + face_height * 0.5 - spine_width * 0.5,\n rotate(\n 90deg,\n origin: center,\n box(\n width: face_height * 0.8,\n height: spine_width,\n align(\n center + horizon,\n // Shrink title + author together as one unit if they overflow the spine length\n shrink-to-width(\n stack(\n dir: ltr,\n spacing: spine_width * 0.3,\n text(\n font: font_spine_title_family,\n size: fs_spine_title * spine_title_size_mod,\n fill: color_spine_title,\n weight: spine_title_weight,\n style: if spine_title_italic { \"italic\" } else { \"normal\" },\n spine_title,\n ),\n if has_author and fs_spine_author > 0mm {\n text(\n font: font_spine_author_family,\n size: fs_spine_author * spine_author_size_mod,\n fill: color_spine_author,\n weight: spine_author_weight,\n style: if spine_author_italic { \"italic\" } else { \"normal\" },\n spine_author,\n )\n },\n ),\n ),\n ),\n ),\n ),\n )\n}\n\n// Small icon at the bottom of the spine (when spine is wide enough for text)\n#if has_spine_icon {\n let spine_icon_size = spine_width * 0.7\n let spine_icon_side_margin = (spine_width - spine_icon_size) / 2\n // Bottom margin is twice the side margin\n let spine_icon_bottom_margin = spine_icon_side_margin * 2\n place(\n top + left,\n dx: spine_x + spine_icon_side_margin,\n dy: bleed + face_height - spine_icon_size - spine_icon_bottom_margin,\n image(\"icon_spine.svg\", width: spine_icon_size, height: spine_icon_size),\n )\n}\n\n// ============================================================\n// Layer 5 — Front panel content\n// ============================================================\n\n// front_margin, front_content_w, spiral_margin imported from _data.typ\n\n// Base font size — proportional to trim height, multiplied by per-title size modifier\n#let fs_title_base = face_height * 0.05\n#let fs_subtitle = face_height * 0.025 * subtitle_size\n#let fs_author = face_height * 0.035 * author_size\n\n// Render a single title line with its per-title styling, shrunk to fit if too wide\n#let render_title(txt, font_family, size_mod, weight, is_italic, color) = {\n if txt != \"\" {\n box(\n width: front_content_w,\n shrink-to-width(\n align(title_alignment, text(\n font: font_family,\n size: fs_title_base * size_mod,\n fill: color,\n weight: weight,\n style: if is_italic { \"italic\" } else { \"normal\" },\n txt,\n )),\n ),\n )\n }\n}\n\n// Render each title line, then filter out empty ones so they don't create stack spacing gaps\n#let title_lines_rendered = stack(\n dir: ttb,\n spacing: face_height * title_spacing / 100,\n ..(\n render_title(title1, font_title1_family, title1_size, title1_weight, title1_italic, color_front_title1),\n render_title(title2, font_title2_family, title2_size, title2_weight, title2_italic, color_front_title2),\n render_title(title3, font_title3_family, title3_size, title3_weight, title3_italic, color_front_title3),\n ).filter(x => x != none),\n)\n\n// All subtitle lines in one container — shrink-to-width scales them all together uniformly\n// text() wraps shrink-to-width so inner content inherits the scaled size (no explicit size inside)\n#let subtitle_lines_rendered = text(\n font: font_subtitle_family,\n size: fs_subtitle,\n weight: subtitle_weight,\n style: if subtitle_italic { \"italic\" } else { \"normal\" },\n fill: color_front_subtitle,\n box(\n width: front_content_w,\n shrink-to-width(\n stack(\n dir: ttb,\n spacing: face_height * subtitle_spacing / 100,\n ..subtitle_lines.map(line => align(subtitle_alignment, line)),\n )\n ),\n ),\n)\n\n// Author styled text — single line, shrunk to fit width\n#let author_rendered = box(\n width: front_content_w,\n shrink-to-width(\n align(author_alignment, text(\n font: font_author_family,\n size: fs_author,\n fill: color_front_author,\n weight: author_weight,\n style: if author_italic { \"italic\" } else { \"normal\" },\n author,\n )),\n ),\n)\n\n// Title group: stacked title lines\n#let title_group = stack(\n dir: ttb,\n spacing: face_height * title_spacing / 100,\n title_lines_rendered,\n)\n\n// Check which elements belong to each position box\n#let top_has_title = title_position == \"top\"\n#let top_has_subtitle = has_subtitle and subtitle_position == \"top\"\n#let top_has_author = has_author and author_position == \"top\"\n#let top_has_any = top_has_title or top_has_subtitle or top_has_author\n\n#let mid_has_title = title_position == \"middle\"\n#let mid_has_subtitle = has_subtitle and subtitle_position == \"middle\"\n#let mid_has_author = has_author and author_position == \"middle\"\n#let mid_has_any = mid_has_title or mid_has_subtitle or mid_has_author\n\n#let bot_has_title = title_position == \"bottom\"\n#let bot_has_subtitle = has_subtitle and subtitle_position == \"bottom\"\n#let bot_has_author = has_author and author_position == \"bottom\"\n#let bot_has_any = bot_has_title or bot_has_subtitle or bot_has_author\n\n// Build a content group for a position box (order within box: title | subtitle | author)\n// Each element gets its own vertical margins inside the box\n#let make_pos_box(has_t, has_s, has_a) = box(\n width: front_content_w,\n stack(\n dir: ttb,\n if has_t { v(face_height * title_margin_top / 100) },\n if has_t { title_group },\n if has_t { v(face_height * title_margin_bottom / 100) },\n if has_s { v(face_height * subtitle_margin_top / 100) },\n if has_s { subtitle_lines_rendered },\n if has_s { v(face_height * subtitle_margin_bottom / 100) },\n if has_a { v(face_height * author_margin_top / 100) },\n if has_a { author_rendered },\n if has_a { v(face_height * author_margin_bottom / 100) },\n ),\n)\n\n// Place each non-empty position box on the front panel (within panel margin on all sides)\n#if top_has_any {\n place(\n top + left,\n dx: front_x + front_margin + spiral_margin,\n dy: bleed + front_margin,\n make_pos_box(top_has_title, top_has_subtitle, top_has_author),\n )\n}\n#if mid_has_any {\n place(\n horizon + left,\n dx: front_x + front_margin + spiral_margin,\n make_pos_box(mid_has_title, mid_has_subtitle, mid_has_author),\n )\n}\n#if bot_has_any {\n place(\n bottom + left,\n dx: front_x + front_margin + spiral_margin,\n dy: (bleed + front_margin) * -1,\n make_pos_box(bot_has_title, bot_has_subtitle, bot_has_author),\n )\n}\n\n\n// ============================================================\n// Layer 6 — Home-print margin (white rounded borders for home inkjet printers)\n// ============================================================\n\n#if home_print {\n // Render the back and front as two separate rounded cards, each with a white\n // border, so a home printer that can't reach the paper edge still prints\n // cleanly. One white matte covers the spread with two rounded windows punched\n // out — one per trim face, each inset by `home_margin` on all sides with\n // `home_corner`-rounded corners. Windows track the trim faces (not a spine-\n // centre split), so the bleed and spine become white gutter and every side —\n // including the inner/gutter edge — gets a real margin. A single matte (vs.\n // two abutting ones) avoids a hairline seam down the gutter.\n let m = home_margin\n let win_w = face_width - 2 * m\n let win_h = face_height - 2 * m\n let win_y = bleed + m\n let back_win = (bleed + m, win_y, win_w, win_h)\n let front_win = (total_width - bleed - face_width + m, win_y, win_w, win_h)\n home-print-matte(total_width, total_height, (back_win, front_win), home_corner)\n}\n",
5
+ cover: "\n// Book cover template — receives all variables from _data.typ\n#import \"_data.typ\": *\n\n// -- Page setup --\n#set page(\n width: total_width,\n height: total_height,\n margin: 0mm,\n)\n\n#set text(font: font_body_family)\n#set quote(block: true)\n#show quote: set pad(x: 3em)\n\n// ============================================================\n// Helper functions\n// ============================================================\n\n#import \"_helpers.typ\": *\n\n\n// ============================================================\n// Layer 1 — Background fills\n// ============================================================\n\n#place(\n top + left,\n rect(\n width: total_width,\n height: total_height,\n fill: if has_front_gradient {\n gradient.linear(\n (color_front_gradient_start, 0%),\n (color_front_bg, 30%),\n (color_front_bg, 45%),\n (color_front_gradient_end, 100%),\n angle: -40deg,\n )\n } else {\n color_front_bg\n },\n ),\n)\n\n\n#if has_pattern {\n let tiled = pattern(\n size: (pattern_tile_w, pattern_tile_h),\n image(\"pattern.svg\", width: pattern_tile_w, height: pattern_tile_h),\n )\n place(\n top + left,\n rect(width: total_width, height: total_height, fill: tiled),\n )\n}\n\n\n// ============================================================\n// Layer 2 — Background image\n// ============================================================\n\n#if has_image {\n if image_coverage == \"full\" {\n // Full spread: image covers entire canvas (back + spine + front)\n place(\n top + left,\n dx: 0mm,\n dy: 0mm,\n image(\n image_filename,\n width: total_width,\n height: total_height,\n fit: if image_is_vector { \"stretch\" } else { \"cover\" },\n ),\n )\n } else if image_coverage == \"painted\" {\n // Painted: image inset on front cover — 15mm margin horizontally and from bottom, 50% trim height\n let painted_w = face_width - 30mm\n let painted_h = face_height * 0.5 // sync: index.ts painted_h = face_height * 0.5\n place(\n top + left,\n dx: front_x + 15mm,\n dy: bleed + face_height * (1/3),\n image(\n image_filename,\n width: painted_w,\n height: painted_h,\n fit: \"stretch\", // Should be exact but just in case, ensures full frame visible\n ),\n )\n } else if image_coverage == \"feature\" {\n // Feature: same size and position as painted but with rounded corners via box clip\n let painted_w = face_width - 30mm\n let painted_h = face_height * 0.5\n place(\n top + left,\n dx: front_x + 15mm,\n dy: bleed + face_height * (1/3),\n box(\n width: painted_w,\n height: painted_h,\n radius: 6mm,\n clip: true,\n image(\n image_filename,\n width: painted_w,\n height: painted_h,\n fit: \"cover\",\n ),\n ),\n )\n } else if image_coverage == \"front_partial\" {\n // Lower 2/3: image covers the full front face width, bottom 2/3 of height, flush to the bottom trim edge\n let w = face_width\n let h = face_height * (2 / 3)\n place(\n top + left,\n dx: front_x,\n dy: bleed + face_height - h,\n image(\n image_filename,\n width: w,\n height: h,\n fit: \"cover\",\n ),\n )\n } else {\n // Front only: image covers the front panel including all bleed margins\n place(\n top + left,\n dx: front_x,\n dy: 0mm,\n image(\n image_filename,\n width: face_width + bleed,\n height: total_height,\n fit: if image_is_vector { \"stretch\" } else { \"cover\" },\n ),\n )\n }\n}\n\n\n// ============================================================\n// Layer 2b — Icon on front cover\n// ============================================================\n\n#if has_icon {\n let icon_size = calc.min(face_height * 0.4, face_width * 0.8)\n if icon_mode != \"center\" {\n icon_size = calc.min(face_height * 0.5, face_width * 0.8)\n }\n // Apply user-controlled size multiplier (1.0 = default)\n icon_size = icon_size * icon_size_mod\n let offset_icon_dx = front_x + (face_width - icon_size) + (icon_size * 0.15)\n let offset_icon_dy = bleed * -1 + (icon_size * 0.15)\n\n // Place ghost icons first (if any) so below main one\n if icon_mode == \"echo\" {\n place(\n bottom + left,\n dx: offset_icon_dx + icon_size * 0.1,\n dy: offset_icon_dy - icon_size * 0.15,\n image(\"icon_ghost.svg\", width: icon_size, height: icon_size),\n )\n place(\n bottom + left,\n dx: offset_icon_dx - icon_size * 0.1,\n dy: offset_icon_dy + icon_size * 0.1,\n image(\"icon_ghost2.svg\", width: icon_size, height: icon_size),\n )\n }\n\n if icon_mode == \"center\" {\n // Center — large icon centered horizontally, positioned in the lower/mid section\n place(\n bottom + left,\n dx: front_x + (face_width - icon_size) / 2,\n dy: (face_height * 0.2 + bleed) * -1,\n image(\"icon_main.svg\", width: icon_size, height: icon_size),\n )\n } else {\n // Offset/echo: large icon offset towards bottom-right, slightly overflowing the trim edge\n place(\n bottom + left,\n dx: offset_icon_dx,\n dy: offset_icon_dy,\n image(\"icon_main.svg\", width: icon_size, height: icon_size),\n )\n }\n}\n\n\n// ============================================================\n// Layer 2c — Spine background\n// ============================================================\n\n// Spine background (only when spread has a spine) — drawn after pattern so it covers it\n#if has_spine and color_spine_bg != none {\n place(\n top + left,\n dx: spine_x,\n dy: 0mm,\n rect(width: spine_width, height: total_height, fill: color_spine_bg),\n )\n}\n\n\n// ============================================================\n// Layer 3 — Back panel content\n// ============================================================\n\n// back_margin, back_content_w, spiral_margin imported from _data.typ\n\n// Blurb container box with background color and 1cm padding around the text\n// Content is scaled down if it would overflow the available back-panel height\n#if has_blurb {\n let blurb_content = {\n set par(justify: true) if blurb_justify\n set par(leading: 1em * blurb_spacing)\n // Compact heading styles for a small blurb box — size and leading are hardcoded\n // independent of blurb_spacing, since headings rarely wrap and need tight leading\n show heading.where(level: 1): it => {\n set block(above: 2em, below: 1em)\n set text(size: 1.3em)\n set par(leading: 0.5em)\n it\n }\n show heading.where(level: 2): it => {\n set block(above: 2em, below: 1em)\n set text(size: 1.15em)\n set par(leading: 0.5em)\n it\n }\n align(blurb_alignment, text(\n font: font_blurb_family,\n size: fs_back_blurb * blurb_size,\n fill: color_blurb,\n blurb,\n ))\n }\n let blurb_max_h = back_content_h - blurb_padding * 2\n place(\n top + left,\n // Centered within the safe area, not the full face — spiral_margin is 0 for other bindings\n dx: back_x + (face_width - spiral_margin - blurb_width) / 2,\n dy: bleed + back_margin,\n rect(\n width: blurb_width,\n fill: color_blurb_bg,\n stroke: none,\n radius: 1.5mm,\n inset: blurb_padding,\n context {\n let avail_w = blurb_width - blurb_padding * 2\n let natural = measure(blurb_content, width: avail_w)\n if natural.height > blurb_max_h {\n let ratio = blurb_max_h / natural.height\n scale(x: ratio * 100%, y: ratio * 100%, reflow: true, blurb_content)\n } else {\n blurb_content\n }\n },\n ),\n )\n}\n\n// ISBN barcode — position and size from printing-services cover_region_barcode\n#if has_barcode {\n place(\n bottom + left,\n dx: barcode_x,\n dy: barcode_y * -1,\n image(\"barcode.svg\", width: barcode_w, height: barcode_h),\n )\n}\n\n// ============================================================\n// Layer 4 — Spine content\n// ============================================================\n\n#if has_spine and has_spine_text and fs_spine_title > 0mm {\n // Title rotated along spine (bottom-to-top reading direction).\n // place() anchors the unrotated bounding box top-left, so we subtract\n // half the unrotated dimensions to keep the visual center on the spine.\n // The title + author pair runs in the title's direction, so for RTL the title\n // comes first reading right-to-left (bottom-to-top once rotated).\n place(\n top + left,\n dx: spine_x + spine_width * 0.5 - face_height * 0.4,\n dy: bleed + face_height * 0.5 - spine_width * 0.5,\n rotate(\n 90deg,\n origin: center,\n box(\n width: face_height * 0.8,\n height: spine_width,\n align(\n center + horizon,\n // Shrink title + author together as one unit if they overflow the spine length\n shrink-to-width(\n stack(\n dir: spine_dir,\n spacing: spine_width * 0.3,\n text(\n dir: spine_title_dir,\n font: font_spine_title_family,\n size: fs_spine_title * spine_title_size_mod,\n fill: color_spine_title,\n weight: spine_title_weight,\n style: if spine_title_italic { \"italic\" } else { \"normal\" },\n spine_title,\n ),\n if has_author and fs_spine_author > 0mm {\n text(\n dir: spine_author_dir,\n font: font_spine_author_family,\n size: fs_spine_author * spine_author_size_mod,\n fill: color_spine_author,\n weight: spine_author_weight,\n style: if spine_author_italic { \"italic\" } else { \"normal\" },\n spine_author,\n )\n },\n ),\n ),\n ),\n ),\n ),\n )\n}\n\n// Small icon at the bottom of the spine (when spine is wide enough for text)\n#if has_spine_icon {\n let spine_icon_size = spine_width * 0.7\n let spine_icon_side_margin = (spine_width - spine_icon_size) / 2\n // Bottom margin is twice the side margin\n let spine_icon_bottom_margin = spine_icon_side_margin * 2\n place(\n top + left,\n dx: spine_x + spine_icon_side_margin,\n dy: bleed + face_height - spine_icon_size - spine_icon_bottom_margin,\n image(\"icon_spine.svg\", width: spine_icon_size, height: spine_icon_size),\n )\n}\n\n// ============================================================\n// Layer 5 — Front panel content\n// ============================================================\n\n// front_margin, front_content_w, spiral_margin imported from _data.typ\n\n// Base font size — proportional to trim height, multiplied by per-title size modifier\n#let fs_title_base = face_height * 0.05\n#let fs_subtitle = face_height * 0.025 * subtitle_size\n#let fs_author = face_height * 0.035 * author_size\n\n// Render a single title line with its per-title styling, shrunk to fit if too wide\n#let render_title(txt, font_family, size_mod, weight, is_italic, color, txt_dir) = {\n if txt != \"\" {\n box(\n width: front_content_w,\n shrink-to-width(\n align(title_alignment, text(\n dir: txt_dir,\n font: font_family,\n size: fs_title_base * size_mod,\n fill: color,\n weight: weight,\n style: if is_italic { \"italic\" } else { \"normal\" },\n txt,\n )),\n ),\n )\n }\n}\n\n// Render each title line, then filter out empty ones so they don't create stack spacing gaps\n#let title_lines_rendered = stack(\n dir: ttb,\n spacing: face_height * title_spacing / 100,\n ..(\n render_title(title1, font_title1_family, title1_size, title1_weight, title1_italic, color_front_title1, title1_dir),\n render_title(title2, font_title2_family, title2_size, title2_weight, title2_italic, color_front_title2, title2_dir),\n render_title(title3, font_title3_family, title3_size, title3_weight, title3_italic, color_front_title3, title3_dir),\n ).filter(x => x != none),\n)\n\n// All subtitle lines in one container — shrink-to-width scales them all together uniformly\n// text() wraps shrink-to-width so inner content inherits the scaled size (no explicit size inside)\n#let subtitle_lines_rendered = text(\n font: font_subtitle_family,\n size: fs_subtitle,\n weight: subtitle_weight,\n style: if subtitle_italic { \"italic\" } else { \"normal\" },\n fill: color_front_subtitle,\n box(\n width: front_content_w,\n shrink-to-width(\n stack(\n dir: ttb,\n spacing: face_height * subtitle_spacing / 100,\n ..subtitle_lines.zip(subtitle_dirs).map(((line, line_dir)) => align(\n subtitle_alignment, text(dir: line_dir, line),\n )),\n )\n ),\n ),\n)\n\n// Author styled text — single line, shrunk to fit width\n#let author_rendered = box(\n width: front_content_w,\n shrink-to-width(\n align(author_alignment, text(\n dir: author_dir,\n font: font_author_family,\n size: fs_author,\n fill: color_front_author,\n weight: author_weight,\n style: if author_italic { \"italic\" } else { \"normal\" },\n author,\n )),\n ),\n)\n\n// Title group: stacked title lines\n#let title_group = stack(\n dir: ttb,\n spacing: face_height * title_spacing / 100,\n title_lines_rendered,\n)\n\n// Check which elements belong to each position box\n#let top_has_title = title_position == \"top\"\n#let top_has_subtitle = has_subtitle and subtitle_position == \"top\"\n#let top_has_author = has_author and author_position == \"top\"\n#let top_has_any = top_has_title or top_has_subtitle or top_has_author\n\n#let mid_has_title = title_position == \"middle\"\n#let mid_has_subtitle = has_subtitle and subtitle_position == \"middle\"\n#let mid_has_author = has_author and author_position == \"middle\"\n#let mid_has_any = mid_has_title or mid_has_subtitle or mid_has_author\n\n#let bot_has_title = title_position == \"bottom\"\n#let bot_has_subtitle = has_subtitle and subtitle_position == \"bottom\"\n#let bot_has_author = has_author and author_position == \"bottom\"\n#let bot_has_any = bot_has_title or bot_has_subtitle or bot_has_author\n\n// Build a content group for a position box (order within box: title | subtitle | author)\n// Each element gets its own vertical margins inside the box\n#let make_pos_box(has_t, has_s, has_a) = box(\n width: front_content_w,\n stack(\n dir: ttb,\n if has_t { v(face_height * title_margin_top / 100) },\n if has_t { title_group },\n if has_t { v(face_height * title_margin_bottom / 100) },\n if has_s { v(face_height * subtitle_margin_top / 100) },\n if has_s { subtitle_lines_rendered },\n if has_s { v(face_height * subtitle_margin_bottom / 100) },\n if has_a { v(face_height * author_margin_top / 100) },\n if has_a { author_rendered },\n if has_a { v(face_height * author_margin_bottom / 100) },\n ),\n)\n\n// Place each non-empty position box on the front panel (within panel margin on all sides)\n#if top_has_any {\n place(\n top + left,\n dx: front_x + front_margin + spiral_margin,\n dy: bleed + front_margin,\n make_pos_box(top_has_title, top_has_subtitle, top_has_author),\n )\n}\n#if mid_has_any {\n place(\n horizon + left,\n dx: front_x + front_margin + spiral_margin,\n make_pos_box(mid_has_title, mid_has_subtitle, mid_has_author),\n )\n}\n#if bot_has_any {\n place(\n bottom + left,\n dx: front_x + front_margin + spiral_margin,\n dy: (bleed + front_margin) * -1,\n make_pos_box(bot_has_title, bot_has_subtitle, bot_has_author),\n )\n}\n\n\n// ============================================================\n// Layer 6 — Home-print margin (white rounded borders for home inkjet printers)\n// ============================================================\n\n#if home_print {\n // Render the back and front as two separate rounded cards, each with a white\n // border, so a home printer that can't reach the paper edge still prints\n // cleanly. One white matte covers the spread with two rounded windows punched\n // out — one per trim face, each inset by `home_margin` on all sides with\n // `home_corner`-rounded corners. Windows track the trim faces (not a spine-\n // centre split), so the bleed and spine become white gutter and every side —\n // including the inner/gutter edge — gets a real margin. A single matte (vs.\n // two abutting ones) avoids a hairline seam down the gutter.\n let m = home_margin\n let win_w = face_width - 2 * m\n let win_h = face_height - 2 * m\n let win_y = bleed + m\n let back_win = (bleed + m, win_y, win_w, win_h)\n let front_win = (total_width - bleed - face_width + m, win_y, win_w, win_h)\n home-print-matte(total_width, total_height, (back_win, front_win), home_corner)\n}\n",
6
6
  helpers: "\n// Shared helper functions for the cover template\n\n// Scale content so it fits within the container width using geometric scaling.\n// Uses scale() rather than text(size: ...) so inner explicit font sizes are also scaled.\n#let fit-to-width(max-text-size: auto, min-text-size: 2mm, it) = context {\n let effective-max = if max-text-size == auto { text.size * 3 } else { max-text-size }\n let effective-min = if min-text-size == auto { text.size * 0.33 } else { min-text-size }\n // Express size limits as dimensionless ratios relative to current text size\n let max-ratio = effective-max / text.size\n let min-ratio = effective-min / text.size\n let contentsize = measure(it)\n layout(size => {\n if contentsize.width > 0mm {\n let ratio = calc.min(size.width / contentsize.width, size.height / contentsize.height)\n let clamped = calc.max(calc.min(ratio, max-ratio), min-ratio)\n if clamped != 1.0 {\n scale(x: clamped * 100%, y: clamped * 100%, reflow: true, it)\n } else {\n it\n }\n }\n })\n}\n\n// Variant that only shrinks (never grows beyond current text size)\n#let shrink-to-width(min-text-size: auto, it) = context {\n fit-to-width(max-text-size: text.size, min-text-size: min-text-size, it)\n}\n\n// Curve segments tracing one rounded rectangle (absolute coords), for compositing\n// into a larger path. Corners use a cubic-bezier handle (0.5523r) to approximate arcs.\n#let rounded-subpath(x, y, w, h, r) = {\n let k = 0.5523 * r\n (\n curve.move((x + r, y), relative: false),\n curve.line((x + w - r, y), relative: false),\n curve.cubic((x + w - r + k, y), (x + w, y + r - k), (x + w, y + r), relative: false),\n curve.line((x + w, y + h - r), relative: false),\n curve.cubic((x + w, y + h - r + k), (x + w - r + k, y + h), (x + w - r, y + h), relative: false),\n curve.line((x + r, y + h), relative: false),\n curve.cubic((x + r - k, y + h), (x, y + h - r + k), (x, y + h - r), relative: false),\n curve.line((x, y + r), relative: false),\n curve.cubic((x, y + r - k), (x + r - k, y), (x + r, y), relative: false),\n curve.close(),\n )\n}\n\n// White \"matte\" covering the whole canvas (cw × ch) with rounded-rect windows\n// punched out via even-odd fill, so the artwork shows through each window as a\n// rounded card surrounded by a white border. Used for home-print margins on\n// edge-to-edge-incapable printers. `windows` is an array of (x, y, w, h) tuples.\n#let home-print-matte(cw, ch, windows, r) = {\n let segs = (\n curve.move((0mm, 0mm), relative: false),\n curve.line((cw, 0mm), relative: false),\n curve.line((cw, ch), relative: false),\n curve.line((0mm, ch), relative: false),\n curve.close(),\n )\n for win in windows {\n segs = segs + rounded-subpath(win.at(0), win.at(1), win.at(2), win.at(3), r)\n }\n place(top + left, curve(fill: white, fill-rule: \"even-odd\", ..segs))\n}\n",
7
7
  };
8
8
  //# sourceMappingURL=templates_data.js.map
@@ -1,5 +1,6 @@
1
1
  import type { GetDimensionsResult } from './dimensions.js';
2
2
  import type { RegionStats, ImageRegions } from './design.js';
3
+ import type { BuiltinBg } from './generated/builtin_bg_regions.js';
3
4
  /** Average a rectangle of pixel data into a RegionStats — saturation-weighted circular mean
4
5
  * for hue (biases toward colorful pixels over a muddy gray average), plain mean for
5
6
  * saturation/lightness, plus the lightness standard deviation (lightness_spread) — a region
@@ -19,10 +20,19 @@ export declare function analyze_rect(data: Uint8ClampedArray, img_w: number, img
19
20
  * pass null to skip that (front_top_full/front_bottom_full/back/spine all come back null).
20
21
  */
21
22
  export declare function analyze_pixel_regions(data: Uint8ClampedArray, width: number, height: number, dims: GetDimensionsResult | null): ImageRegions;
22
- /** Look up precomputed ImageRegions for a builtin background image (assets/backgrounds/<filename>)
23
- * — see builtin_bg_regions.ts for how/why these are baked in. `byte_length` is checked against
24
- * the source file's known size so a same-named non-builtin upload doesn't collide with an
25
- * entry. Returns null for anything not in the table, in which case callers should fall back to
26
- * analyze_pixel_regions() against the actual decoded image. */
27
- export declare function get_builtin_bg_regions(filename: string, byte_length: number): ImageRegions | null;
23
+ /** Path (relative to the assets root) of the preview-sized copies of the builtin backgrounds —
24
+ * 2700px wide, ~380KB, for rendering previews without downloading the original. Join a
25
+ * builtin's filename onto it; the filename is the same ID as the original's. Generated by
26
+ * .bin/gen_bg_thumbnails, whose header explains the size */
27
+ export declare const BG_PREVIEW_DIR = "backgrounds/previews_2700/";
28
+ /** Baked metadata for a builtin background (assets/backgrounds/<filename>) — its original's pixel
29
+ * size and its precomputed ImageRegions (see builtin_bg_regions.ts for how/why these are baked
30
+ * in). Returns null for anything not in the table */
31
+ export declare function get_builtin_bg(filename: string): BuiltinBg | null;
32
+ /** Precomputed ImageRegions for a builtin background, looked up by its filename (its ID) alone —
33
+ * so they apply whatever copy of it is being rendered, the original or the preview-sized one in
34
+ * BG_PREVIEW_DIR. They include the full-wrap regions (sampled against a 6x9 reference cover).
35
+ * Returns null for anything not in the table, in which case callers fall back to
36
+ * analyze_pixel_regions() against the actual decoded image */
37
+ export declare function get_builtin_bg_regions(filename: string): ImageRegions | null;
28
38
  //# sourceMappingURL=image_regions.d.ts.map
@@ -123,13 +123,23 @@ export function analyze_pixel_regions(data, width, height, dims) {
123
123
  }
124
124
  return { front_top, front_bottom, front_top_full, front_bottom_full, back, spine };
125
125
  }
126
- /** Look up precomputed ImageRegions for a builtin background image (assets/backgrounds/<filename>)
127
- * — see builtin_bg_regions.ts for how/why these are baked in. `byte_length` is checked against
128
- * the source file's known size so a same-named non-builtin upload doesn't collide with an
129
- * entry. Returns null for anything not in the table, in which case callers should fall back to
130
- * analyze_pixel_regions() against the actual decoded image. */
131
- export function get_builtin_bg_regions(filename, byte_length) {
132
- const entry = BUILTIN_BG_REGIONS[filename];
133
- return (entry && entry.size === byte_length) ? entry.regions : null;
126
+ /** Path (relative to the assets root) of the preview-sized copies of the builtin backgrounds —
127
+ * 2700px wide, ~380KB, for rendering previews without downloading the original. Join a
128
+ * builtin's filename onto it; the filename is the same ID as the original's. Generated by
129
+ * .bin/gen_bg_thumbnails, whose header explains the size */
130
+ export const BG_PREVIEW_DIR = 'backgrounds/previews_2700/';
131
+ /** Baked metadata for a builtin background (assets/backgrounds/<filename>) — its original's pixel
132
+ * size and its precomputed ImageRegions (see builtin_bg_regions.ts for how/why these are baked
133
+ * in). Returns null for anything not in the table */
134
+ export function get_builtin_bg(filename) {
135
+ return Object.hasOwn(BUILTIN_BG_REGIONS, filename) ? BUILTIN_BG_REGIONS[filename] : null;
136
+ }
137
+ /** Precomputed ImageRegions for a builtin background, looked up by its filename (its ID) alone —
138
+ * so they apply whatever copy of it is being rendered, the original or the preview-sized one in
139
+ * BG_PREVIEW_DIR. They include the full-wrap regions (sampled against a 6x9 reference cover).
140
+ * Returns null for anything not in the table, in which case callers fall back to
141
+ * analyze_pixel_regions() against the actual decoded image */
142
+ export function get_builtin_bg_regions(filename) {
143
+ return get_builtin_bg(filename)?.regions ?? null;
134
144
  }
135
145
  //# sourceMappingURL=image_regions.js.map
package/dist/index.d.ts CHANGED
@@ -28,7 +28,8 @@ export { SCHEMA_VERSION, RENDER_VERSION, SCHEMA_DEFAULTS, FORM_DEFAULTS } from '
28
28
  export { warn_unknown } from './utils.js';
29
29
  export { build_schema, curly_quotes, parse_font_family, normalize_font_family, font_families_in_form } from './form_schema.js';
30
30
  export type { CustomFontStyle } from './form_schema.js';
31
- export { analyze_pixel_regions, get_builtin_bg_regions } from './image_regions.js';
31
+ export { analyze_pixel_regions, get_builtin_bg, get_builtin_bg_regions, BG_PREVIEW_DIR } from './image_regions.js';
32
+ export type { BuiltinBg } from './generated/builtin_bg_regions.js';
32
33
  export { derive_colors, hex_override_to_hsl, hex_to_hsl, is_dark_color } from './colors.js';
33
34
  export type { DerivedColors } from './colors.js';
34
35
  export type { FontSizes } from './font_sizes.js';
@@ -61,7 +62,7 @@ export { frame_image, frame_asset_path } from './frame.js';
61
62
  * image_regions, when provided, is passed straight through to resolve_colors() to fill in any
62
63
  * color fields the schema left unset (see design.ts) — this function does no image decoding
63
64
  * itself (stays pure/no I/O); generator-node/generator-web compute image_regions (via
64
- * get_builtin_bg_regions or a live analyze_pixel_regions decode) and pass it in.
65
+ * the builtin lookups or a live analyze_pixel_regions decode) and pass it in.
65
66
  */
66
67
  export declare function build(schema: CoverSchema, image?: {
67
68
  data: Uint8Array;
package/dist/index.js CHANGED
@@ -31,7 +31,7 @@ export { make_blank_form_values } from './form_state.js';
31
31
  export { SCHEMA_VERSION, RENDER_VERSION, SCHEMA_DEFAULTS, FORM_DEFAULTS } from './defaults.js';
32
32
  export { warn_unknown } from './utils.js';
33
33
  export { build_schema, curly_quotes, parse_font_family, normalize_font_family, font_families_in_form } from './form_schema.js';
34
- export { analyze_pixel_regions, get_builtin_bg_regions } from './image_regions.js';
34
+ export { analyze_pixel_regions, get_builtin_bg, get_builtin_bg_regions, BG_PREVIEW_DIR } from './image_regions.js';
35
35
  export { derive_colors, hex_override_to_hsl, hex_to_hsl, is_dark_color } from './colors.js';
36
36
  // Re-export lower-level functions needed by generator-node and generator-web
37
37
  export { cover_schema, calculate_font_sizes, resolve_colors, darken_hsl };
@@ -82,7 +82,7 @@ function wrap_blurb_cjk(markup, han_variant, style, chain) {
82
82
  * image_regions, when provided, is passed straight through to resolve_colors() to fill in any
83
83
  * color fields the schema left unset (see design.ts) — this function does no image decoding
84
84
  * itself (stays pure/no I/O); generator-node/generator-web compute image_regions (via
85
- * get_builtin_bg_regions or a live analyze_pixel_regions decode) and pass it in.
85
+ * the builtin lookups or a live analyze_pixel_regions decode) and pass it in.
86
86
  */
87
87
  export async function build(schema, image, frame_fn, frame_data, templates = DEFAULT_TEMPLATES, image_regions) {
88
88
  // Resolve spine text — undefined means derive from titles/author; '' means explicitly empty
package/dist/utils.d.ts CHANGED
@@ -4,6 +4,10 @@ export declare function mm_to_typst(mm: number): string;
4
4
  export declare function clamp(value: number, min: number, max: number): number;
5
5
  /** Derive the default spine title by joining all the title lines */
6
6
  export declare function default_spine_title(title1?: string, title2?: string, title3?: string): string;
7
+ /** A text's base direction by the Unicode first-strong rule (as HTML's dir="auto"): the first
8
+ * letter decides, so a Hebrew sentence quoting an English word is still RTL. Text with no
9
+ * letters at all (digits, punctuation) is LTR. */
10
+ export declare function text_dir(text: string): 'ltr' | 'rtl';
7
11
  /** Report a value naming something this package version doesn't have (a pruned or renamed
8
12
  * pattern, vector background, or background image). The feature is dropped rather than failing
9
13
  * the whole render, but never silently — a missing ID means a stored record can no longer be
package/dist/utils.js CHANGED
@@ -15,6 +15,34 @@ export function default_spine_title(title1, title2, title3) {
15
15
  .trim()
16
16
  .replace(/ +/g, ' ');
17
17
  }
18
+ // Unicode scripts whose letters are strong right-to-left (bidi class R or AL). Built from names
19
+ // so any the JS engine's Unicode database doesn't know yet are skipped rather than throwing
20
+ const RTL_SCRIPTS = [
21
+ 'Hebrew', 'Arabic', 'Syriac', 'Thaana', 'Nko', 'Samaritan', 'Mandaic', 'Adlam',
22
+ 'Hanifi_Rohingya', 'Yezidi', 'Mende_Kikakui', 'Garay', 'Imperial_Aramaic', 'Phoenician',
23
+ 'Kharoshthi', 'Old_South_Arabian', 'Old_North_Arabian', 'Avestan', 'Inscriptional_Parthian',
24
+ 'Inscriptional_Pahlavi', 'Psalter_Pahlavi', 'Old_Turkic', 'Old_Hungarian', 'Nabataean',
25
+ 'Palmyrene', 'Hatran', 'Manichaean', 'Elymaic', 'Sogdian', 'Old_Sogdian', 'Old_Uyghur',
26
+ 'Chorasmian', 'Lydian', 'Meroitic_Cursive', 'Meroitic_Hieroglyphs', 'Cypriot',
27
+ ].filter(script => {
28
+ try {
29
+ new RegExp(`\\p{Script=${script}}`, 'u');
30
+ return true;
31
+ }
32
+ catch {
33
+ return false;
34
+ }
35
+ });
36
+ const RTL_REGEX = new RegExp(`[${RTL_SCRIPTS.map(s => `\\p{Script=${s}}`).join('')}\\u200F\\u061C]`, 'u');
37
+ // Strong directional characters: letters, plus the LRM/RLM/ALM marks typed to force a direction
38
+ const STRONG_REGEX = /[\p{L}‎‏؜]/u;
39
+ /** A text's base direction by the Unicode first-strong rule (as HTML's dir="auto"): the first
40
+ * letter decides, so a Hebrew sentence quoting an English word is still RTL. Text with no
41
+ * letters at all (digits, punctuation) is LTR. */
42
+ export function text_dir(text) {
43
+ const strong = STRONG_REGEX.exec(text);
44
+ return strong && RTL_REGEX.test(strong[0]) ? 'rtl' : 'ltr';
45
+ }
18
46
  /** Report a value naming something this package version doesn't have (a pruned or renamed
19
47
  * pattern, vector background, or background image). The feature is dropped rather than failing
20
48
  * the whole render, but never silently — a missing ID means a stored record can no longer be
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bookcover-core",
3
- "version": "0.18.0",
3
+ "version": "0.20.0",
4
4
  "license": "MIT-0",
5
5
  "repository": {
6
6
  "type": "git",