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}",
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
]
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue