From b84647c749dabdc011f62d8e8213743fd03f565b Mon Sep 17 00:00:00 2001 From: Paul Buetow Date: Mon, 22 Jun 2026 10:15:42 +0300 Subject: Add large 2x2 feature tiles and a CSS-grid overview Allow a single photo to be blown up into a large "feature" tile that spans a 2x2 block of the album overview, controlled by a new THUMB_FEATURE_PERCENT (0-100, default 10; 0 disables). Each tile rolls for a feature first, then for a subdivision, otherwise stays a normal square. To pack mixed-size tiles (normal 1x1, subdivided 1x1, feature 2x2) without gaps, the overview is now a real CSS grid with grid-auto-flow: dense, so smaller tiles backfill the holes a 2x2 feature would leave. Tile spacing moved from per-image padding to the grid gap. Feature tiles reuse the img.thumb class and its dramatic hover. THUMB_FEATURE_PERCENT is wired through the same layers as THUMB_SUBDIVIDE_PERCENT: config defaults, 0..100 validation, --print-config, the --feature CLI flag, the usage text, and the shuriken.json / --dry-run metadata, with docs and tests updated. Generated HTML and CSS pass the W3C Nu HTML checker and CSS validator. Setting both percentages to 0 reproduces the previous all-1x1 grid. Co-Authored-By: Claude Opus 4.8 --- bin/shuriken | 64 +++++++++++++++++++++------- docs/configuration.md | 6 ++- docs/generation.md | 4 +- docs/usage.md | 1 + share/templates/default/header.tmpl | 71 ++++++++++++++++++++++---------- share/templates/default/previewpage.tmpl | 2 + src/lib/album-metadata.source.sh | 5 +++ src/lib/album-render.source.sh | 50 +++++++++++++++------- src/lib/bootstrap.source.sh | 1 + src/lib/config.print.source.sh | 1 + src/lib/config.source.sh | 4 +- src/lib/config.validate.source.sh | 1 + src/shuriken.default.conf | 5 +++ src/shuriken.sh | 2 + tests/cli.sh | 9 ++++ 15 files changed, 169 insertions(+), 57 deletions(-) diff --git a/bin/shuriken b/bin/shuriken index 7727cde..fb2e4df 100755 --- a/bin/shuriken +++ b/bin/shuriken @@ -38,6 +38,7 @@ declare -ra CLI_CONFIG_OVERRIDE_TARGETS=( THUMBHEIGHT MAXPREVIEWS THUMB_SUBDIVIDE_PERCENT + THUMB_FEATURE_PERCENT IMAGE_JOBS RANDOM_SEED SHUFFLE @@ -59,6 +60,7 @@ declare -Ar CLI_OPTION_SPEC=( [--thumbheight]='kind=value config=THUMBHEIGHT' [--maxpreviews]='kind=value config=MAXPREVIEWS' [--subdivide]='kind=value config=THUMB_SUBDIVIDE_PERCENT' + [--feature]='kind=value config=THUMB_FEATURE_PERCENT' [--image-jobs]='kind=value config=IMAGE_JOBS' [--random-seed]='kind=value config=RANDOM_SEED' [--shuffle]='kind=flag value=yes config=SHUFFLE' @@ -154,6 +156,7 @@ usage() { --thumbheight VALUE --maxpreviews N --subdivide PERCENT + --feature PERCENT --image-jobs N --random-seed VALUE --splash @@ -2203,6 +2206,7 @@ _collect_generation_metadata() { _GENERATION_METADATA["settings_thumbheight"]="$THUMBHEIGHT" _GENERATION_METADATA["settings_maxpreviews"]="$MAXPREVIEWS" _GENERATION_METADATA["settings_subdivide_percent"]="$THUMB_SUBDIVIDE_PERCENT" + _GENERATION_METADATA["settings_feature_percent"]="$THUMB_FEATURE_PERCENT" _GENERATION_METADATA["settings_image_jobs"]="$IMAGE_JOBS" _GENERATION_METADATA["settings_random_seed"]="$RANDOM_SEED" _GENERATION_METADATA["settings_shuffle"]="$SHUFFLE" @@ -2260,6 +2264,8 @@ _generation_metadata_json() { "$(_json_string "${_GENERATION_METADATA["settings_maxpreviews"]}")" printf ' "subdivide_percent": %s,\n' \ "$(_json_string "${_GENERATION_METADATA["settings_subdivide_percent"]}")" + printf ' "feature_percent": %s,\n' \ + "$(_json_string "${_GENERATION_METADATA["settings_feature_percent"]}")" printf ' "image_jobs": %s,\n' \ "$(_json_string "${_GENERATION_METADATA["settings_image_jobs"]}")" printf ' "random_seed": %s,\n' \ @@ -2342,6 +2348,7 @@ collect_dry_run_plan() { plan_ref["thumbheight"]="$THUMBHEIGHT" plan_ref["maxpreviews"]="$MAXPREVIEWS" plan_ref["subdivide_percent"]="$THUMB_SUBDIVIDE_PERCENT" + plan_ref["feature_percent"]="$THUMB_FEATURE_PERCENT" plan_ref["image_jobs"]="$IMAGE_JOBS" plan_ref["random_seed"]="$RANDOM_SEED" plan_ref["shuffle"]="$SHUFFLE" @@ -2373,6 +2380,7 @@ print_dry_run_plan() { printf 'Thumb height: %s\n' "${plan_ref["thumbheight"]}" printf 'Max previews per page: %s\n' "${plan_ref["maxpreviews"]}" printf 'Subdivide percent: %s\n' "${plan_ref["subdivide_percent"]}" + printf 'Feature percent: %s\n' "${plan_ref["feature_percent"]}" printf 'Image jobs: %s\n' "${plan_ref["image_jobs"]}" printf 'Random seed: %s\n' "${plan_ref["random_seed"]}" printf 'Shuffle: %s\n' "${plan_ref["shuffle"]}" @@ -2649,11 +2657,13 @@ append_preview_grid() { done } -# Decide the layout for the next tile, printing " ". A tile -# is subdivided with THUMB_SUBDIVIDE_PERCENT probability, but only into a layout -# that fits the photos still remaining on the page; otherwise it is a single -# square thumbnail. Randomness reuses the seeded random_index, so builds are -# reproducible when RANDOM_SEED is set (and varied otherwise). +# Decide the layout for the next tile, printing " ". Each +# tile rolls first for a "feature" (one photo blown up to a 2x2 hero tile) with +# THUMB_FEATURE_PERCENT probability, then for a subdivision with +# THUMB_SUBDIVIDE_PERCENT probability (only into a layout that fits the photos +# still remaining on the page); otherwise it is a single square thumbnail. The +# feature and subdivide rolls use independent seeded random_index namespaces, so +# builds stay reproducible when RANDOM_SEED is set (and varied otherwise). tile_layout_for() { local -ri remaining="$1"; shift local -r context="$1"; shift @@ -2661,6 +2671,14 @@ tile_layout_for() { local -a counts=(2) local -i roll choice + # A feature tile always fits (it consumes a single photo), so roll for it + # first. THUMB_FEATURE_PERCENT == 0 disables it (the roll can never be < 0). + roll=$(random_index "feature:$context" 100) + if (( roll < THUMB_FEATURE_PERCENT )); then + printf 'feature 1\n' + return + fi + # A single tile when subdivision is disabled, too few photos remain to fill # even the smallest subdivided layout (two_wide needs 2), or the roll misses. if (( remaining < 2 || THUMB_SUBDIVIDE_PERCENT == 0 )); then @@ -2688,9 +2706,11 @@ tile_layout_for() { } # Render one tile's markup (no trailing newline). A "single" tile is the plain -# square thumbnail, byte-identical to the previous per-thumbnail output; any -# other layout is a subdivided tile. The tile's photos are the trailing args and -# their preview numbers run from start_preview upward. +# square thumbnail, byte-identical to the previous per-thumbnail output. A +# "feature" tile is the same single thumbnail but with the 'feature' anchor class +# that makes CSS span it across a 2x2 block. Any other layout is a subdivided +# tile. The tile's photos are the trailing args and their preview numbers run +# from start_preview upward. build_tile_block() { local -r thumbs_dir="$1"; shift local -r backhref="$1"; shift @@ -2700,13 +2720,21 @@ build_tile_block() { local -a photos=("$@") local animation_class - if [ "$layout" = single ]; then - animation_class=$(random_animation_css_class slow "${photos[0]}") - build_preview_thumbnail \ - "$thumbs_dir" "$backhref" "$page_num" "$start_preview" \ - "${photos[0]}" "$animation_class" - return - fi + case "$layout" in + single|feature) + animation_class=$(random_animation_css_class slow "${photos[0]}") + # 'single' -> no anchor class (legacy output); 'feature' -> the + # 'feature' anchor class CSS spans across a 2x2 grid block. + local anchor_class='' + if [ "$layout" = feature ]; then + anchor_class='feature' + fi + build_preview_thumbnail \ + "$thumbs_dir" "$backhref" "$page_num" "$start_preview" \ + "${photos[0]}" "$animation_class" thumb "$anchor_class" + return + ;; + esac build_subdivided_tile \ "$thumbs_dir" "$backhref" "$page_num" "$layout" "$start_preview" \ "${photos[@]}" @@ -4846,8 +4874,10 @@ apply_config_defaults() { SPLASH_PAGE="${SPLASH_PAGE:-yes}" STATS_PAGE="${STATS_PAGE:-no}" # Optional with a default (unlike the required THUMBHEIGHT): the percent - # chance a preview tile is subdivided into smaller thumbnails. 0 disables it. + # chance a preview tile is subdivided into smaller thumbnails, and the + # percent chance it becomes a large 2x2 "feature" tile. 0 disables either. THUMB_SUBDIVIDE_PERCENT="${THUMB_SUBDIVIDE_PERCENT:-30}" + THUMB_FEATURE_PERCENT="${THUMB_FEATURE_PERCENT:-10}" SYNC_DELETE="${SYNC_DELETE:-yes}" TARBALL_INCLUDE="${TARBALL_INCLUDE:-no}" TARBALL_SUFFIX="${TARBALL_SUFFIX:-.tar}" @@ -4897,6 +4927,7 @@ print_config() { print_shell_assignment THUMBHEIGHT "$THUMBHEIGHT" print_shell_assignment MAXPREVIEWS "$MAXPREVIEWS" print_shell_assignment THUMB_SUBDIVIDE_PERCENT "$THUMB_SUBDIVIDE_PERCENT" + print_shell_assignment THUMB_FEATURE_PERCENT "$THUMB_FEATURE_PERCENT" print_shell_assignment IMAGE_JOBS "$IMAGE_JOBS" print_shell_assignment IMAGEMAGICK_TIMEOUT "$IMAGEMAGICK_TIMEOUT" print_shell_assignment RANDOM_SEED "$RANDOM_SEED" @@ -5486,6 +5517,7 @@ validate_common_config() { validate_positive_integer_config_var THUMBHEIGHT || return validate_positive_integer_config_var MAXPREVIEWS || return validate_percentage_config_var THUMB_SUBDIVIDE_PERCENT || return + validate_percentage_config_var THUMB_FEATURE_PERCENT || return validate_positive_integer_config_var IMAGE_JOBS || return validate_positive_integer_config_var IMAGEMAGICK_TIMEOUT || return validate_positive_integer_config_var TAR_TIMEOUT || return diff --git a/docs/configuration.md b/docs/configuration.md index 5d61a27..69a8835 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -14,6 +14,7 @@ values for the current run. | `THUMBHEIGHT` | `300` | Thumbnail height in pixels. Positive integer. | | `MAXPREVIEWS` | `40` | Maximum previews per page. Positive integer. | | `THUMB_SUBDIVIDE_PERCENT` | `30` | Percent chance (0-100) that a preview tile is subdivided into smaller thumbnails (2x2 quad, two stacked wide strips, or two squares plus one wide strip on top/bottom). Each sub-thumbnail is its own clickable photo. `0` disables it. | +| `THUMB_FEATURE_PERCENT` | `10` | Percent chance (0-100) that a preview tile is a large "feature" tile: a single photo spanning a 2x2 block of the overview grid. Rolled before the subdivision chance. `0` disables it. | | `IMAGE_JOBS` | `3` | Parallel jobs for image processing and HTML template rendering. Positive integer. | | `IMAGEMAGICK_TIMEOUT` | `60` | Per-ImageMagick-command timeout in seconds. Positive integer. | | `TAR_TIMEOUT` | `120` | Tarball creation timeout in seconds. Positive integer. | @@ -54,7 +55,8 @@ The checks (details in `src/lib/config.validate.source.sh`): `IMAGE_JOBS`, `INCOMING_DIR`, `DIST_DIR`, `TEMPLATE_DIR`. * **Positive integers**: `THUMBHEIGHT`, `MAXPREVIEWS`, `IMAGE_JOBS`, `IMAGEMAGICK_TIMEOUT`, `TAR_TIMEOUT`; `HEIGHT` is an optional positive integer. -* **Percentage (0-100 integer)**: `THUMB_SUBDIVIDE_PERCENT`. +* **Percentage (0-100 integer)**: `THUMB_SUBDIVIDE_PERCENT`, + `THUMB_FEATURE_PERCENT`. * **`yes`/`no` settings**: `SHUFFLE`, `SPLASH_PAGE`, `STATS_PAGE`, `TARBALL_INCLUDE`, `SYNC_DELETE` (where applicable). * **Readable input**: `INCOMING_DIR` must be a readable directory; `TEMPLATE_DIR` @@ -77,7 +79,7 @@ Generation stops before writing album output when validation fails. `CONFIG_SOURCE`, `INCOMING_DIR`, `DIST_DIR`, `TEMPLATE_DIR`, `FAVICON`, `SOURCE_URL`, `TITLE`, `HEIGHT`, `THUMBHEIGHT`, `MAXPREVIEWS`, -`THUMB_SUBDIVIDE_PERCENT`, `IMAGE_JOBS`, +`THUMB_SUBDIVIDE_PERCENT`, `THUMB_FEATURE_PERCENT`, `IMAGE_JOBS`, `IMAGEMAGICK_TIMEOUT`, `RANDOM_SEED`, `SHUFFLE`, `SPLASH_PAGE`, `STATS_PAGE`, `TARBALL_INCLUDE`, `TARBALL_SUFFIX`, `TAR_TIMEOUT`, `TAR_OPTS`, `SYNC_DELETE`, `SYNC_DESTINATIONS`, `ORIGINAL_BASEPATH`. diff --git a/docs/generation.md b/docs/generation.md index b89cafc..4d29840 100644 --- a/docs/generation.md +++ b/docs/generation.md @@ -97,8 +97,8 @@ metadata records: * generated photo, thumbnail, and HTML file counts; * tarball status (included + file); * effective settings (title, height, thumbheight, maxpreviews, subdivide - percent, image jobs, random seed, shuffle, splash page, stats page, original - basepath) useful for debugging a published album. + percent, feature percent, image jobs, random seed, shuffle, splash page, stats + page, original basepath) useful for debugging a published album. ## Favicon diff --git a/docs/usage.md b/docs/usage.md index f4c8d27..3f85471 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -69,6 +69,7 @@ config variable documented in [configuration.md](configuration.md). | `--thumbheight VALUE` | `THUMBHEIGHT` | | `--maxpreviews N` | `MAXPREVIEWS` | | `--subdivide PERCENT` | `THUMB_SUBDIVIDE_PERCENT` | +| `--feature PERCENT` | `THUMB_FEATURE_PERCENT` | | `--image-jobs N` | `IMAGE_JOBS` | | `--random-seed VALUE` | `RANDOM_SEED` | | `--shuffle` | `SHUFFLE=yes` | diff --git a/share/templates/default/header.tmpl b/share/templates/default/header.tmpl index 50a33a7..cf78513 100644 --- a/share/templates/default/header.tmpl +++ b/share/templates/default/header.tmpl @@ -189,21 +189,51 @@ cat < ${render_preview_thumbs_html} + END diff --git a/src/lib/album-metadata.source.sh b/src/lib/album-metadata.source.sh index 3d38cbf..b74bf8f 100644 --- a/src/lib/album-metadata.source.sh +++ b/src/lib/album-metadata.source.sh @@ -210,6 +210,7 @@ _collect_generation_metadata() { _GENERATION_METADATA["settings_thumbheight"]="$THUMBHEIGHT" _GENERATION_METADATA["settings_maxpreviews"]="$MAXPREVIEWS" _GENERATION_METADATA["settings_subdivide_percent"]="$THUMB_SUBDIVIDE_PERCENT" + _GENERATION_METADATA["settings_feature_percent"]="$THUMB_FEATURE_PERCENT" _GENERATION_METADATA["settings_image_jobs"]="$IMAGE_JOBS" _GENERATION_METADATA["settings_random_seed"]="$RANDOM_SEED" _GENERATION_METADATA["settings_shuffle"]="$SHUFFLE" @@ -267,6 +268,8 @@ _generation_metadata_json() { "$(_json_string "${_GENERATION_METADATA["settings_maxpreviews"]}")" printf ' "subdivide_percent": %s,\n' \ "$(_json_string "${_GENERATION_METADATA["settings_subdivide_percent"]}")" + printf ' "feature_percent": %s,\n' \ + "$(_json_string "${_GENERATION_METADATA["settings_feature_percent"]}")" printf ' "image_jobs": %s,\n' \ "$(_json_string "${_GENERATION_METADATA["settings_image_jobs"]}")" printf ' "random_seed": %s,\n' \ @@ -349,6 +352,7 @@ collect_dry_run_plan() { plan_ref["thumbheight"]="$THUMBHEIGHT" plan_ref["maxpreviews"]="$MAXPREVIEWS" plan_ref["subdivide_percent"]="$THUMB_SUBDIVIDE_PERCENT" + plan_ref["feature_percent"]="$THUMB_FEATURE_PERCENT" plan_ref["image_jobs"]="$IMAGE_JOBS" plan_ref["random_seed"]="$RANDOM_SEED" plan_ref["shuffle"]="$SHUFFLE" @@ -380,6 +384,7 @@ print_dry_run_plan() { printf 'Thumb height: %s\n' "${plan_ref["thumbheight"]}" printf 'Max previews per page: %s\n' "${plan_ref["maxpreviews"]}" printf 'Subdivide percent: %s\n' "${plan_ref["subdivide_percent"]}" + printf 'Feature percent: %s\n' "${plan_ref["feature_percent"]}" printf 'Image jobs: %s\n' "${plan_ref["image_jobs"]}" printf 'Random seed: %s\n' "${plan_ref["random_seed"]}" printf 'Shuffle: %s\n' "${plan_ref["shuffle"]}" diff --git a/src/lib/album-render.source.sh b/src/lib/album-render.source.sh index 63c0c57..d97d05f 100644 --- a/src/lib/album-render.source.sh +++ b/src/lib/album-render.source.sh @@ -220,11 +220,13 @@ append_preview_grid() { done } -# Decide the layout for the next tile, printing " ". A tile -# is subdivided with THUMB_SUBDIVIDE_PERCENT probability, but only into a layout -# that fits the photos still remaining on the page; otherwise it is a single -# square thumbnail. Randomness reuses the seeded random_index, so builds are -# reproducible when RANDOM_SEED is set (and varied otherwise). +# Decide the layout for the next tile, printing " ". Each +# tile rolls first for a "feature" (one photo blown up to a 2x2 hero tile) with +# THUMB_FEATURE_PERCENT probability, then for a subdivision with +# THUMB_SUBDIVIDE_PERCENT probability (only into a layout that fits the photos +# still remaining on the page); otherwise it is a single square thumbnail. The +# feature and subdivide rolls use independent seeded random_index namespaces, so +# builds stay reproducible when RANDOM_SEED is set (and varied otherwise). tile_layout_for() { local -ri remaining="$1"; shift local -r context="$1"; shift @@ -232,6 +234,14 @@ tile_layout_for() { local -a counts=(2) local -i roll choice + # A feature tile always fits (it consumes a single photo), so roll for it + # first. THUMB_FEATURE_PERCENT == 0 disables it (the roll can never be < 0). + roll=$(random_index "feature:$context" 100) + if (( roll < THUMB_FEATURE_PERCENT )); then + printf 'feature 1\n' + return + fi + # A single tile when subdivision is disabled, too few photos remain to fill # even the smallest subdivided layout (two_wide needs 2), or the roll misses. if (( remaining < 2 || THUMB_SUBDIVIDE_PERCENT == 0 )); then @@ -259,9 +269,11 @@ tile_layout_for() { } # Render one tile's markup (no trailing newline). A "single" tile is the plain -# square thumbnail, byte-identical to the previous per-thumbnail output; any -# other layout is a subdivided tile. The tile's photos are the trailing args and -# their preview numbers run from start_preview upward. +# square thumbnail, byte-identical to the previous per-thumbnail output. A +# "feature" tile is the same single thumbnail but with the 'feature' anchor class +# that makes CSS span it across a 2x2 block. Any other layout is a subdivided +# tile. The tile's photos are the trailing args and their preview numbers run +# from start_preview upward. build_tile_block() { local -r thumbs_dir="$1"; shift local -r backhref="$1"; shift @@ -271,13 +283,21 @@ build_tile_block() { local -a photos=("$@") local animation_class - if [ "$layout" = single ]; then - animation_class=$(random_animation_css_class slow "${photos[0]}") - build_preview_thumbnail \ - "$thumbs_dir" "$backhref" "$page_num" "$start_preview" \ - "${photos[0]}" "$animation_class" - return - fi + case "$layout" in + single|feature) + animation_class=$(random_animation_css_class slow "${photos[0]}") + # 'single' -> no anchor class (legacy output); 'feature' -> the + # 'feature' anchor class CSS spans across a 2x2 grid block. + local anchor_class='' + if [ "$layout" = feature ]; then + anchor_class='feature' + fi + build_preview_thumbnail \ + "$thumbs_dir" "$backhref" "$page_num" "$start_preview" \ + "${photos[0]}" "$animation_class" thumb "$anchor_class" + return + ;; + esac build_subdivided_tile \ "$thumbs_dir" "$backhref" "$page_num" "$layout" "$start_preview" \ "${photos[@]}" diff --git a/src/lib/bootstrap.source.sh b/src/lib/bootstrap.source.sh index 7fa5ea3..66193e3 100644 --- a/src/lib/bootstrap.source.sh +++ b/src/lib/bootstrap.source.sh @@ -29,6 +29,7 @@ usage() { --thumbheight VALUE --maxpreviews N --subdivide PERCENT + --feature PERCENT --image-jobs N --random-seed VALUE --splash diff --git a/src/lib/config.print.source.sh b/src/lib/config.print.source.sh index 401d84c..62ff145 100644 --- a/src/lib/config.print.source.sh +++ b/src/lib/config.print.source.sh @@ -34,6 +34,7 @@ print_config() { print_shell_assignment THUMBHEIGHT "$THUMBHEIGHT" print_shell_assignment MAXPREVIEWS "$MAXPREVIEWS" print_shell_assignment THUMB_SUBDIVIDE_PERCENT "$THUMB_SUBDIVIDE_PERCENT" + print_shell_assignment THUMB_FEATURE_PERCENT "$THUMB_FEATURE_PERCENT" print_shell_assignment IMAGE_JOBS "$IMAGE_JOBS" print_shell_assignment IMAGEMAGICK_TIMEOUT "$IMAGEMAGICK_TIMEOUT" print_shell_assignment RANDOM_SEED "$RANDOM_SEED" diff --git a/src/lib/config.source.sh b/src/lib/config.source.sh index 538724f..67ddade 100644 --- a/src/lib/config.source.sh +++ b/src/lib/config.source.sh @@ -47,8 +47,10 @@ apply_config_defaults() { SPLASH_PAGE="${SPLASH_PAGE:-yes}" STATS_PAGE="${STATS_PAGE:-no}" # Optional with a default (unlike the required THUMBHEIGHT): the percent - # chance a preview tile is subdivided into smaller thumbnails. 0 disables it. + # chance a preview tile is subdivided into smaller thumbnails, and the + # percent chance it becomes a large 2x2 "feature" tile. 0 disables either. THUMB_SUBDIVIDE_PERCENT="${THUMB_SUBDIVIDE_PERCENT:-30}" + THUMB_FEATURE_PERCENT="${THUMB_FEATURE_PERCENT:-10}" SYNC_DELETE="${SYNC_DELETE:-yes}" TARBALL_INCLUDE="${TARBALL_INCLUDE:-no}" TARBALL_SUFFIX="${TARBALL_SUFFIX:-.tar}" diff --git a/src/lib/config.validate.source.sh b/src/lib/config.validate.source.sh index e3c55e7..7b1421b 100644 --- a/src/lib/config.validate.source.sh +++ b/src/lib/config.validate.source.sh @@ -287,6 +287,7 @@ validate_common_config() { validate_positive_integer_config_var THUMBHEIGHT || return validate_positive_integer_config_var MAXPREVIEWS || return validate_percentage_config_var THUMB_SUBDIVIDE_PERCENT || return + validate_percentage_config_var THUMB_FEATURE_PERCENT || return validate_positive_integer_config_var IMAGE_JOBS || return validate_positive_integer_config_var IMAGEMAGICK_TIMEOUT || return validate_positive_integer_config_var TAR_TIMEOUT || return diff --git a/src/shuriken.default.conf b/src/shuriken.default.conf index 737eb26..28da780 100644 --- a/src/shuriken.default.conf +++ b/src/shuriken.default.conf @@ -12,6 +12,11 @@ MAXPREVIEWS=40 # strip with the strip on top or bottom). Each sub-thumbnail is still its own # clickable photo. 0 disables subdivision; 100 always subdivides. THUMB_SUBDIVIDE_PERCENT=30 +# Percent chance (0-100) that a thumbnail tile is a large "feature" tile: a +# single photo blown up to span a 2x2 block of the overview grid. Rolled before +# the subdivision chance. 0 disables feature tiles; 100 makes every tile a +# feature. +THUMB_FEATURE_PERCENT=10 # Parallel jobs for image processing and HTML template rendering. IMAGE_JOBS=3 # Timeout in seconds for each ImageMagick command. diff --git a/src/shuriken.sh b/src/shuriken.sh index 11f503a..418dfed 100755 --- a/src/shuriken.sh +++ b/src/shuriken.sh @@ -38,6 +38,7 @@ declare -ra CLI_CONFIG_OVERRIDE_TARGETS=( THUMBHEIGHT MAXPREVIEWS THUMB_SUBDIVIDE_PERCENT + THUMB_FEATURE_PERCENT IMAGE_JOBS RANDOM_SEED SHUFFLE @@ -59,6 +60,7 @@ declare -Ar CLI_OPTION_SPEC=( [--thumbheight]='kind=value config=THUMBHEIGHT' [--maxpreviews]='kind=value config=MAXPREVIEWS' [--subdivide]='kind=value config=THUMB_SUBDIVIDE_PERCENT' + [--feature]='kind=value config=THUMB_FEATURE_PERCENT' [--image-jobs]='kind=value config=IMAGE_JOBS' [--random-seed]='kind=value config=RANDOM_SEED' [--shuffle]='kind=flag value=yes config=SHUFFLE' diff --git a/tests/cli.sh b/tests/cli.sh index 3181746..cc8b925 100755 --- a/tests/cli.sh +++ b/tests/cli.sh @@ -140,6 +140,7 @@ assert metadata["settings"]["height"] == "120" assert metadata["settings"]["thumbheight"] == "30" assert metadata["settings"]["maxpreviews"] == maxpreviews assert metadata["settings"]["subdivide_percent"] == "30" +assert metadata["settings"]["feature_percent"] == "10" assert metadata["settings"]["image_jobs"] == "3" assert metadata["settings"]["shuffle"] is False assert isinstance(metadata["settings"]["splash_page"], bool) @@ -1414,6 +1415,7 @@ HEIGHT=1200 THUMBHEIGHT=300 MAXPREVIEWS=40 THUMB_SUBDIVIDE_PERCENT=30 +THUMB_FEATURE_PERCENT=10 IMAGE_JOBS=3 IMAGEMAGICK_TIMEOUT=60 RANDOM_SEED='' @@ -1464,6 +1466,7 @@ HEIGHT='' THUMBHEIGHT=30 MAXPREVIEWS=40 THUMB_SUBDIVIDE_PERCENT=30 +THUMB_FEATURE_PERCENT=10 IMAGE_JOBS=3 IMAGEMAGICK_TIMEOUT=60 RANDOM_SEED='' @@ -1607,6 +1610,7 @@ HEIGHT=120 THUMBHEIGHT=30 MAXPREVIEWS=7 THUMB_SUBDIVIDE_PERCENT=30 +THUMB_FEATURE_PERCENT=10 IMAGE_JOBS=3 IMAGEMAGICK_TIMEOUT=60 RANDOM_SEED='' @@ -1652,6 +1656,7 @@ HEIGHT=120 THUMBHEIGHT=30 MAXPREVIEWS=8 THUMB_SUBDIVIDE_PERCENT=30 +THUMB_FEATURE_PERCENT=10 IMAGE_JOBS=3 IMAGEMAGICK_TIMEOUT=60 RANDOM_SEED='' @@ -1705,6 +1710,7 @@ test_print_config_applies_cli_overrides_without_writes() { --thumbheight 45 \ --maxpreviews 9 \ --subdivide 55 \ + --feature 35 \ --image-jobs 2 \ --random-seed cli-seed \ --shuffle \ @@ -1723,6 +1729,7 @@ HEIGHT=456 THUMBHEIGHT=45 MAXPREVIEWS=9 THUMB_SUBDIVIDE_PERCENT=55 +THUMB_FEATURE_PERCENT=35 IMAGE_JOBS=2 IMAGEMAGICK_TIMEOUT=60 RANDOM_SEED=cli-seed @@ -1940,6 +1947,7 @@ test_dry_run_reports_cli_overrides_without_writes() { --thumbheight 45 \ --maxpreviews 2 \ --subdivide 25 \ + --feature 15 \ --image-jobs 2 \ --random-seed dry-seed \ --shuffle \ @@ -1960,6 +1968,7 @@ test_dry_run_reports_cli_overrides_without_writes() { test::assert_contains 'Thumb height: 45' "$output" test::assert_contains 'Max previews per page: 2' "$output" test::assert_contains 'Subdivide percent: 25' "$output" + test::assert_contains 'Feature percent: 15' "$output" test::assert_contains 'Image jobs: 2' "$output" test::assert_contains 'Random seed: dry-seed' "$output" test::assert_contains 'Shuffle: yes' "$output" -- cgit v1.2.3