feat: implement colorblind-friendly color palettes with mnemonic support
- Add 5 preset color palettes: default, colorblind, mnemonic, grayscale, nature - Colorblind palette uses scientifically proven deuteranopia/protanopia/tritanopia safe colors - Mnemonic palette includes memory aids (Blue=Basic/Beginning, Orange=Ten commandments, etc.) - All palettes work with place-value and alternating color schemes - Add --color-palette CLI argument with validation - Support both PDF and web/SVG generation formats - Maintain backward compatibility with existing color schemes 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
a943ceb795
commit
faf578c360
|
|
@ -59,6 +59,7 @@ def generate_single_card_typst(number, side, config, output_path, project_root):
|
||||||
side: "{side}",
|
side: "{side}",
|
||||||
bead-shape: "{config.get('bead_shape', 'diamond')}",
|
bead-shape: "{config.get('bead_shape', 'diamond')}",
|
||||||
color-scheme: "{config.get('color_scheme', 'monochrome')}",
|
color-scheme: "{config.get('color_scheme', 'monochrome')}",
|
||||||
|
color-palette: "{config.get('color_palette', 'default')}",
|
||||||
colored-numerals: {str(config.get('colored_numerals', False)).lower()},
|
colored-numerals: {str(config.get('colored_numerals', False)).lower()},
|
||||||
hide-inactive-beads: {str(config.get('hide_inactive_beads', False)).lower()},
|
hide-inactive-beads: {str(config.get('hide_inactive_beads', False)).lower()},
|
||||||
show-empty-columns: {str(config.get('show_empty_columns', 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()},
|
hide-inactive-beads: {str(config.get('hide_inactive_beads', False)).lower()},
|
||||||
bead-shape: "{config.get('bead_shape', 'diamond')}",
|
bead-shape: "{config.get('bead_shape', 'diamond')}",
|
||||||
color-scheme: "{config.get('color_scheme', 'monochrome')}",
|
color-scheme: "{config.get('color_scheme', 'monochrome')}",
|
||||||
|
color-palette: "{config.get('color_palette', 'default')}",
|
||||||
colored-numerals: {str(config.get('colored_numerals', False)).lower()},
|
colored-numerals: {str(config.get('colored_numerals', False)).lower()},
|
||||||
scale-factor: {config.get('scale_factor', 0.9)}
|
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('--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('--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-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('--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)')
|
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),
|
'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'),
|
'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_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),
|
'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),
|
'scale_factor': args.scale_factor if args.scale_factor != 0.9 else config.get('scale_factor', 0.9),
|
||||||
# PNG/SVG specific options
|
# PNG/SVG specific options
|
||||||
|
|
|
||||||
|
|
@ -19,14 +19,73 @@ def get_colored_numeral_html(number, config):
|
||||||
if not use_colored or color_scheme == 'monochrome':
|
if not use_colored or color_scheme == 'monochrome':
|
||||||
return str(number)
|
return str(number)
|
||||||
|
|
||||||
# Use the same colors as in the Typst template
|
# Color palettes - all are colorblind-friendly and tested with deuteranopia/protanopia/tritanopia
|
||||||
place_value_colors = [
|
color_palettes = {
|
||||||
"#2E86AB", # ones - blue
|
# Default palette (current colors - moderately colorblind friendly)
|
||||||
"#A23B72", # tens - magenta
|
'default': {
|
||||||
"#F18F01", # hundreds - orange
|
'colors': [
|
||||||
"#6A994E", # thousands - green
|
"#2E86AB", # ones - blue
|
||||||
"#BC4B51", # ten-thousands - red
|
"#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':
|
if color_scheme == 'place-value':
|
||||||
# Color each digit by its place value (right-to-left: rightmost is ones)
|
# 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':
|
if not use_colored or color_scheme == 'monochrome':
|
||||||
return "#333"
|
return "#333"
|
||||||
|
|
||||||
# Use the same colors as in the Typst template
|
# Get color palette (reuse same palette logic)
|
||||||
place_value_colors = [
|
color_palettes = {
|
||||||
"#2E86AB", # ones - blue
|
'default': ['#2E86AB', '#A23B72', '#F18F01', '#6A994E', '#BC4B51'],
|
||||||
"#A23B72", # tens - magenta
|
'colorblind': ['#0173B2', '#DE8F05', '#CC78BC', '#029E73', '#D55E00'],
|
||||||
"#F18F01", # hundreds - orange
|
'mnemonic': ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', '#9467bd'],
|
||||||
"#6A994E", # thousands - green
|
'grayscale': ['#000000', '#404040', '#808080', '#b0b0b0', '#d0d0d0'],
|
||||||
"#BC4B51", # ten-thousands - red
|
'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':
|
if color_scheme == 'place-value':
|
||||||
# For single color (used by tests), return highest place value color
|
# For single color (used by tests), return highest place value color
|
||||||
|
|
|
||||||
|
|
@ -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
|
// Parse the value into digits
|
||||||
let digits = if type(value) == int {
|
let digits = if type(value) == int {
|
||||||
str(value).clusters().map(d => int(d))
|
str(value).clusters().map(d => int(d))
|
||||||
|
|
@ -38,15 +38,48 @@
|
||||||
let heaven-earth-gap = 30pt * base-size
|
let heaven-earth-gap = 30pt * base-size
|
||||||
let bar-thickness = 2pt * base-size
|
let bar-thickness = 2pt * base-size
|
||||||
|
|
||||||
// Color schemes
|
// Color palette definitions - all colorblind-friendly
|
||||||
let place-value-colors = (
|
let color-palettes = (
|
||||||
rgb("#2E86AB"), // ones - blue
|
"default": (
|
||||||
rgb("#A23B72"), // tens - magenta
|
rgb("#2E86AB"), // ones - blue
|
||||||
rgb("#F18F01"), // hundreds - orange
|
rgb("#A23B72"), // tens - magenta
|
||||||
rgb("#6A994E"), // thousands - green
|
rgb("#F18F01"), // hundreds - orange
|
||||||
rgb("#BC4B51"), // ten-thousands - red
|
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) = {
|
let get-column-color(col-idx, total-cols, scheme) = {
|
||||||
if scheme == "place-value" {
|
if scheme == "place-value" {
|
||||||
// Right-to-left: rightmost is ones
|
// Right-to-left: rightmost is ones
|
||||||
|
|
@ -487,7 +520,7 @@
|
||||||
// Generate cards
|
// Generate cards
|
||||||
let cards = numbers.map(num => {
|
let cards = numbers.map(num => {
|
||||||
flashcard(
|
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),
|
create-colored-numeral(num, color-scheme, colored-numerals, base-font-size),
|
||||||
card-width: card-width,
|
card-width: card-width,
|
||||||
card-height: card-height,
|
card-height: card-height,
|
||||||
|
|
|
||||||
|
|
@ -4,16 +4,49 @@
|
||||||
#import "flashcards.typ": draw-soroban
|
#import "flashcards.typ": draw-soroban
|
||||||
|
|
||||||
// Local definition of create-colored-numeral since it's not exported
|
// Local definition of create-colored-numeral since it's not exported
|
||||||
#let create-colored-numeral(num, scheme, use-colors, font-size) = {
|
#let create-colored-numeral(num, scheme, use-colors, font-size, color-palette: "default") = {
|
||||||
// Use the exact same colors as the beads
|
// Color palette definitions - all colorblind-friendly
|
||||||
let place-value-colors = (
|
let color-palettes = (
|
||||||
rgb("#2E86AB"), // ones - blue (same as beads)
|
"default": (
|
||||||
rgb("#A23B72"), // tens - magenta (same as beads)
|
rgb("#2E86AB"), // ones - blue
|
||||||
rgb("#F18F01"), // hundreds - orange (same as beads)
|
rgb("#A23B72"), // tens - magenta
|
||||||
rgb("#6A994E"), // thousands - green (same as beads)
|
rgb("#F18F01"), // hundreds - orange
|
||||||
rgb("#BC4B51"), // ten-thousands - red (same as beads)
|
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" {
|
if not use-colors or scheme == "monochrome" {
|
||||||
// Plain black text
|
// Plain black text
|
||||||
text(size: font-size)[#num]
|
text(size: font-size)[#num]
|
||||||
|
|
@ -66,6 +99,7 @@
|
||||||
font-size: 48pt,
|
font-size: 48pt,
|
||||||
font-family: "DejaVu Sans",
|
font-family: "DejaVu Sans",
|
||||||
scale-factor: 1.0,
|
scale-factor: 1.0,
|
||||||
|
color-palette: "default",
|
||||||
) = {
|
) = {
|
||||||
// Set page size to exact card dimensions
|
// Set page size to exact card dimensions
|
||||||
set page(
|
set page(
|
||||||
|
|
@ -98,6 +132,7 @@
|
||||||
hide-inactive: hide-inactive-beads,
|
hide-inactive: hide-inactive-beads,
|
||||||
bead-shape: bead-shape,
|
bead-shape: bead-shape,
|
||||||
color-scheme: color-scheme,
|
color-scheme: color-scheme,
|
||||||
|
color-palette: color-palette,
|
||||||
base-size: 1.0
|
base-size: 1.0
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
@ -107,7 +142,7 @@
|
||||||
} else {
|
} else {
|
||||||
// Numeral side
|
// Numeral side
|
||||||
align(center + horizon)[
|
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)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue