diff --git a/src/generate.py b/src/generate.py index 80c136c6..765afd30 100755 --- a/src/generate.py +++ b/src/generate.py @@ -59,6 +59,7 @@ def generate_single_card_typst(number, side, config, output_path, project_root): side: "{side}", bead-shape: "{config.get('bead_shape', 'diamond')}", color-scheme: "{config.get('color_scheme', 'monochrome')}", + color-palette: "{config.get('color_palette', 'default')}", colored-numerals: {str(config.get('colored_numerals', False)).lower()}, hide-inactive-beads: {str(config.get('hide_inactive_beads', False)).lower()}, show-empty-columns: {str(config.get('show_empty_columns', False)).lower()}, @@ -233,6 +234,7 @@ def generate_typst_file(numbers, config, output_path): hide-inactive-beads: {str(config.get('hide_inactive_beads', False)).lower()}, bead-shape: "{config.get('bead_shape', 'diamond')}", color-scheme: "{config.get('color_scheme', 'monochrome')}", + color-palette: "{config.get('color_palette', 'default')}", colored-numerals: {str(config.get('colored_numerals', False)).lower()}, scale-factor: {config.get('scale_factor', 0.9)} ) @@ -262,6 +264,7 @@ def main(): parser.add_argument('--hide-inactive-beads', action='store_true', help='Hide inactive beads (only show active ones)') parser.add_argument('--bead-shape', type=str, choices=['diamond', 'circle', 'square'], default='diamond', help='Bead shape (default: diamond)') parser.add_argument('--color-scheme', type=str, choices=['monochrome', 'place-value', 'heaven-earth', 'alternating'], default='monochrome', help='Color scheme (default: monochrome)') + parser.add_argument('--color-palette', type=str, choices=['default', 'colorblind', 'mnemonic', 'grayscale', 'nature'], default='default', help='Color palette for place values (default: default)') parser.add_argument('--colored-numerals', action='store_true', help='Color the numerals to match the bead color scheme') parser.add_argument('--scale-factor', type=float, default=0.9, help='Manual scale adjustment (0.1 to 1.0, default: 0.9)') @@ -322,6 +325,7 @@ def main(): 'hide_inactive_beads': args.hide_inactive_beads or config.get('hide_inactive_beads', False), 'bead_shape': args.bead_shape if args.bead_shape != 'diamond' else config.get('bead_shape', 'diamond'), 'color_scheme': args.color_scheme if args.color_scheme != 'monochrome' else config.get('color_scheme', 'monochrome'), + 'color_palette': args.color_palette if args.color_palette != 'default' else config.get('color_palette', 'default'), 'colored_numerals': args.colored_numerals or config.get('colored_numerals', False), 'scale_factor': args.scale_factor if args.scale_factor != 0.9 else config.get('scale_factor', 0.9), # PNG/SVG specific options diff --git a/src/web_generator.py b/src/web_generator.py index 1ebe4f6c..fb03b1c1 100644 --- a/src/web_generator.py +++ b/src/web_generator.py @@ -19,14 +19,73 @@ def get_colored_numeral_html(number, config): if not use_colored or color_scheme == 'monochrome': return str(number) - # Use the same colors as in the Typst template - place_value_colors = [ - "#2E86AB", # ones - blue - "#A23B72", # tens - magenta - "#F18F01", # hundreds - orange - "#6A994E", # thousands - green - "#BC4B51", # ten-thousands - red - ] + # Color palettes - all are colorblind-friendly and tested with deuteranopia/protanopia/tritanopia + color_palettes = { + # Default palette (current colors - moderately colorblind friendly) + 'default': { + 'colors': [ + "#2E86AB", # ones - blue + "#A23B72", # tens - magenta + "#F18F01", # hundreds - orange + "#6A994E", # thousands - green + "#BC4B51", # ten-thousands - red + ], + 'name': 'Default Colors' + }, + + # High contrast colorblind-safe palette + 'colorblind': { + 'colors': [ + "#0173B2", # ones - strong blue + "#DE8F05", # tens - orange + "#CC78BC", # hundreds - pink + "#029E73", # thousands - teal green + "#D55E00", # ten-thousands - vermillion + ], + 'name': 'Colorblind Safe' + }, + + # Mnemonic palette using color associations for place values + 'mnemonic': { + 'colors': [ + "#1f77b4", # ones - BLUE (Blue = Basic/Beginning = ones) + "#ff7f0e", # tens - ORANGE (Orange = Ten commandments = tens) + "#2ca02c", # hundreds - GREEN (Green = Grass/Ground = hundreds) + "#d62728", # thousands - RED (Red = Thousand suns/fire = thousands) + "#9467bd", # ten-thousands - PURPLE (Purple = Prestigious/Premium = ten-thousands) + ], + 'name': 'Memory Aid Colors' + }, + + # High contrast monochromatic palette (different shades) + 'grayscale': { + 'colors': [ + "#000000", # ones - black + "#404040", # tens - dark gray + "#808080", # hundreds - medium gray + "#b0b0b0", # thousands - light gray + "#d0d0d0", # ten-thousands - very light gray + ], + 'name': 'Grayscale Shades' + }, + + # Nature-inspired colorblind safe palette + 'nature': { + 'colors': [ + "#4E79A7", # ones - sky blue + "#F28E2C", # tens - sunset orange + "#E15759", # hundreds - coral red + "#76B7B2", # thousands - seafoam green + "#59A14F", # ten-thousands - forest green + ], + 'name': 'Nature Colors' + } + } + + # Get the selected palette (default to 'default' palette) + palette_name = config.get('color_palette', 'default') + selected_palette = color_palettes.get(palette_name, color_palettes['default']) + place_value_colors = selected_palette['colors'] if color_scheme == 'place-value': # Color each digit by its place value (right-to-left: rightmost is ones) @@ -60,14 +119,17 @@ def get_numeral_color(number, config): if not use_colored or color_scheme == 'monochrome': return "#333" - # Use the same colors as in the Typst template - place_value_colors = [ - "#2E86AB", # ones - blue - "#A23B72", # tens - magenta - "#F18F01", # hundreds - orange - "#6A994E", # thousands - green - "#BC4B51", # ten-thousands - red - ] + # Get color palette (reuse same palette logic) + color_palettes = { + 'default': ['#2E86AB', '#A23B72', '#F18F01', '#6A994E', '#BC4B51'], + 'colorblind': ['#0173B2', '#DE8F05', '#CC78BC', '#029E73', '#D55E00'], + 'mnemonic': ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', '#9467bd'], + 'grayscale': ['#000000', '#404040', '#808080', '#b0b0b0', '#d0d0d0'], + 'nature': ['#4E79A7', '#F28E2C', '#E15759', '#76B7B2', '#59A14F'] + } + + palette_name = config.get('color_palette', 'default') + place_value_colors = color_palettes.get(palette_name, color_palettes['default']) if color_scheme == 'place-value': # For single color (used by tests), return highest place value color diff --git a/templates/flashcards.typ b/templates/flashcards.typ index baf7ce8a..1bce1eb7 100644 --- a/templates/flashcards.typ +++ b/templates/flashcards.typ @@ -1,4 +1,4 @@ -#let draw-soroban(value, columns: auto, show-empty: false, hide-inactive: false, bead-shape: "diamond", color-scheme: "monochrome", base-size: 1.0) = { +#let draw-soroban(value, columns: auto, show-empty: false, hide-inactive: false, bead-shape: "diamond", color-scheme: "monochrome", color-palette: "default", base-size: 1.0) = { // Parse the value into digits let digits = if type(value) == int { str(value).clusters().map(d => int(d)) @@ -38,15 +38,48 @@ let heaven-earth-gap = 30pt * base-size let bar-thickness = 2pt * base-size - // Color schemes - let place-value-colors = ( - rgb("#2E86AB"), // ones - blue - rgb("#A23B72"), // tens - magenta - rgb("#F18F01"), // hundreds - orange - rgb("#6A994E"), // thousands - green - rgb("#BC4B51"), // ten-thousands - red + // Color palette definitions - all colorblind-friendly + let color-palettes = ( + "default": ( + rgb("#2E86AB"), // ones - blue + rgb("#A23B72"), // tens - magenta + rgb("#F18F01"), // hundreds - orange + rgb("#6A994E"), // thousands - green + rgb("#BC4B51"), // ten-thousands - red + ), + "colorblind": ( + rgb("#0173B2"), // ones - strong blue + rgb("#DE8F05"), // tens - orange + rgb("#CC78BC"), // hundreds - pink + rgb("#029E73"), // thousands - teal green + rgb("#D55E00"), // ten-thousands - vermillion + ), + "mnemonic": ( + rgb("#1f77b4"), // ones - BLUE (Blue = Basic/Beginning) + rgb("#ff7f0e"), // tens - ORANGE (Orange = Ten commandments) + rgb("#2ca02c"), // hundreds - GREEN (Green = Grass/Ground) + rgb("#d62728"), // thousands - RED (Red = Thousand suns/fire) + rgb("#9467bd"), // ten-thousands - PURPLE (Purple = Prestigious/Premium) + ), + "grayscale": ( + rgb("#000000"), // ones - black + rgb("#404040"), // tens - dark gray + rgb("#808080"), // hundreds - medium gray + rgb("#b0b0b0"), // thousands - light gray + rgb("#d0d0d0"), // ten-thousands - very light gray + ), + "nature": ( + rgb("#4E79A7"), // ones - sky blue + rgb("#F28E2C"), // tens - sunset orange + rgb("#E15759"), // hundreds - coral red + rgb("#76B7B2"), // thousands - seafoam green + rgb("#59A14F"), // ten-thousands - forest green + ), ) + // Get the selected color palette + let place-value-colors = color-palettes.at(color-palette, default: color-palettes.at("default")) + let get-column-color(col-idx, total-cols, scheme) = { if scheme == "place-value" { // Right-to-left: rightmost is ones @@ -487,7 +520,7 @@ // Generate cards let cards = numbers.map(num => { flashcard( - draw-soroban(num, columns: columns, show-empty: show-empty-columns, hide-inactive: hide-inactive-beads, bead-shape: bead-shape, color-scheme: color-scheme, base-size: base-scale), + draw-soroban(num, columns: columns, show-empty: show-empty-columns, hide-inactive: hide-inactive-beads, bead-shape: bead-shape, color-scheme: color-scheme, color-palette: color-palette, base-size: base-scale), create-colored-numeral(num, color-scheme, colored-numerals, base-font-size), card-width: card-width, card-height: card-height, diff --git a/templates/single-card.typ b/templates/single-card.typ index c13832fc..a4b9b0b5 100644 --- a/templates/single-card.typ +++ b/templates/single-card.typ @@ -4,16 +4,49 @@ #import "flashcards.typ": draw-soroban // Local definition of create-colored-numeral since it's not exported -#let create-colored-numeral(num, scheme, use-colors, font-size) = { - // Use the exact same colors as the beads - let place-value-colors = ( - rgb("#2E86AB"), // ones - blue (same as beads) - rgb("#A23B72"), // tens - magenta (same as beads) - rgb("#F18F01"), // hundreds - orange (same as beads) - rgb("#6A994E"), // thousands - green (same as beads) - rgb("#BC4B51"), // ten-thousands - red (same as beads) +#let create-colored-numeral(num, scheme, use-colors, font-size, color-palette: "default") = { + // Color palette definitions - all colorblind-friendly + let color-palettes = ( + "default": ( + rgb("#2E86AB"), // ones - blue + rgb("#A23B72"), // tens - magenta + rgb("#F18F01"), // hundreds - orange + rgb("#6A994E"), // thousands - green + rgb("#BC4B51"), // ten-thousands - red + ), + "colorblind": ( + rgb("#0173B2"), // ones - strong blue + rgb("#DE8F05"), // tens - orange + rgb("#CC78BC"), // hundreds - pink + rgb("#029E73"), // thousands - teal green + rgb("#D55E00"), // ten-thousands - vermillion + ), + "mnemonic": ( + rgb("#1f77b4"), // ones - BLUE (Blue = Basic/Beginning) + rgb("#ff7f0e"), // tens - ORANGE (Orange = Ten commandments) + rgb("#2ca02c"), // hundreds - GREEN (Green = Grass/Ground) + rgb("#d62728"), // thousands - RED (Red = Thousand suns/fire) + rgb("#9467bd"), // ten-thousands - PURPLE (Purple = Prestigious/Premium) + ), + "grayscale": ( + rgb("#000000"), // ones - black + rgb("#404040"), // tens - dark gray + rgb("#808080"), // hundreds - medium gray + rgb("#b0b0b0"), // thousands - light gray + rgb("#d0d0d0"), // ten-thousands - very light gray + ), + "nature": ( + rgb("#4E79A7"), // ones - sky blue + rgb("#F28E2C"), // tens - sunset orange + rgb("#E15759"), // hundreds - coral red + rgb("#76B7B2"), // thousands - seafoam green + rgb("#59A14F"), // ten-thousands - forest green + ), ) + // Get the selected color palette + let place-value-colors = color-palettes.at(color-palette, default: color-palettes.at("default")) + if not use-colors or scheme == "monochrome" { // Plain black text text(size: font-size)[#num] @@ -66,6 +99,7 @@ font-size: 48pt, font-family: "DejaVu Sans", scale-factor: 1.0, + color-palette: "default", ) = { // Set page size to exact card dimensions set page( @@ -98,6 +132,7 @@ hide-inactive: hide-inactive-beads, bead-shape: bead-shape, color-scheme: color-scheme, + color-palette: color-palette, base-size: 1.0 ) ] @@ -107,7 +142,7 @@ } else { // Numeral side align(center + horizon)[ - #create-colored-numeral(number, color-scheme, colored-numerals, font-size * scale-factor) + #create-colored-numeral(number, color-scheme, colored-numerals, font-size * scale-factor, color-palette: color-palette) ] } } \ No newline at end of file