Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
208 changes: 98 additions & 110 deletions scripts/generate_share_images.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
LANG_DIR = os.path.join(DATA_DIR, "languages")
OUTPUT_DIR = os.path.join(ROOT, "webapp", "static", "images", "share")
DEFAULT_CONFIG_PATH = os.path.join(DATA_DIR, "default_language_config.json")
OG_IMAGE_PATH = os.path.join(ROOT, "webapp", "static", "images", "og-image.png")

# Image dimensions (standard OG image)
WIDTH, HEIGHT = 1200, 630
Expand All @@ -33,24 +34,24 @@
YELLOW = (234, 179, 8) # #eab308
GRAY = (82, 82, 82) # #525252
WHITE = (255, 255, 255)
LIGHT_GRAY = (163, 163, 163) # #a3a3a3

# Wordle tile pattern for the logo (G=green, Y=yellow, X=gray)
TILE_PATTERN = ["G", "Y", "X", "G", "Y", "G"]
TILE_LETTERS = ["W", "O", "R", "D", "L", "E"]
TILE_COLORS = {"G": GREEN, "Y": YELLOW, "X": GRAY}

# Font paths
FONT_BASE = "/usr/share/fonts/truetype/noto"
BOLD_FONT = os.path.join(FONT_BASE, "NotoSans-Bold.ttf")
# DejaVu Sans covers Latin, Cyrillic, Greek, Arabic, Hebrew, Georgian, Armenian,
# Devanagari, Korean, and more — perfect for mixed-script challenge text
CHALLENGE_FONT = "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"
LATIN_FONT = os.path.join(FONT_BASE, "NotoSans-Regular.ttf")
FONT_DEJAVU = "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"
FONT_DEJAVU_BOLD = "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf"
FONT_CJK = "/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc"
FONT_CJK_BOLD = "/usr/share/fonts/opentype/noto/NotoSansCJK-Bold.ttc"
SCORE_FONT = "/usr/share/fonts/truetype/noto/NotoSans-Bold.ttf"
Comment on lines +39 to +43
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Avoid hard-coded system font paths without fallback.

These absolute paths will fail on environments where Noto fonts are not installed, causing generation to crash.

Suggested fix
+def pick_font_path(*candidates):
+    for candidate in candidates:
+        if os.path.exists(candidate):
+            return candidate
+    raise FileNotFoundError(f"No usable font found in: {candidates}")
+
-FONT_DEJAVU = "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"
-FONT_DEJAVU_BOLD = "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf"
-FONT_CJK = "/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc"
-FONT_CJK_BOLD = "/usr/share/fonts/opentype/noto/NotoSansCJK-Bold.ttc"
-SCORE_FONT = "/usr/share/fonts/truetype/noto/NotoSans-Bold.ttf"
+FONT_DEJAVU = pick_font_path("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf")
+FONT_DEJAVU_BOLD = pick_font_path("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf")
+FONT_CJK = pick_font_path(
+    "/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc",
+    FONT_DEJAVU,
+)
+FONT_CJK_BOLD = pick_font_path(
+    "/usr/share/fonts/opentype/noto/NotoSansCJK-Bold.ttc",
+    FONT_DEJAVU_BOLD,
+)
+SCORE_FONT = pick_font_path(
+    "/usr/share/fonts/truetype/noto/NotoSans-Bold.ttf",
+    FONT_DEJAVU_BOLD,
+)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/generate_share_images.py` around lines 39 - 43, Replace the
hard-coded absolute font paths by implementing resilient font resolution in
scripts/generate_share_images.py: for each symbol (FONT_DEJAVU,
FONT_DEJAVU_BOLD, FONT_CJK, FONT_CJK_BOLD, SCORE_FONT) attempt to load from a
list of candidate paths and fall back to a bundled font or PIL’s default if none
exist, and log a warning when falling back; ensure functions that call
ImageFont.truetype (or equivalent) use the resolved path or fallback object so
font loading won’t crash when the specific system fonts are missing.


# Languages needing CJK font (DejaVu doesn't cover Hangul glyphs)
CJK_LANGS = {"ko"}

# Pre-load fonts (avoid repeated truetype() calls in hot loop)
FONTS = {}

# Header strip cropped from the real og-image.png (pixel-perfect branding)
HEADER_STRIP = None
HEADER_HEIGHT = 90


def get_font(path, size):
"""Get a cached font instance."""
Expand All @@ -60,49 +61,31 @@ def get_font(path, size):
return FONTS[key]


def load_header():
"""Crop and scale the WORDLE tiles + globe from og-image.png."""
global HEADER_STRIP
og = Image.open(OG_IMAGE_PATH)
# Crop the tiles+globe band (y=185..335 in the 1200x630 original)
strip = og.crop((0, 185, 1200, 335))
HEADER_STRIP = strip.resize(
(int(strip.width * HEADER_HEIGHT / strip.height), HEADER_HEIGHT),
Image.LANCZOS,
)
Comment on lines +64 to +73
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Handle missing og-image.png and close file handles safely.

Image.open() should be wrapped in a context manager, and missing file errors should be explicit for faster diagnosis.

Suggested fix
 def load_header():
     """Crop and scale the WORDLE tiles + globe from og-image.png."""
     global HEADER_STRIP
-    og = Image.open(OG_IMAGE_PATH)
-    # Crop the tiles+globe band (y=185..335 in the 1200x630 original)
-    strip = og.crop((0, 185, 1200, 335))
+    if not os.path.exists(OG_IMAGE_PATH):
+        raise FileNotFoundError(f"Required image not found: {OG_IMAGE_PATH}")
+    with Image.open(OG_IMAGE_PATH) as og:
+        # Crop the tiles+globe band (y=185..335 in the 1200x630 original)
+        strip = og.crop((0, 185, 1200, 335))
     HEADER_STRIP = strip.resize(
         (int(strip.width * HEADER_HEIGHT / strip.height), HEADER_HEIGHT),
         Image.LANCZOS,
     )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/generate_share_images.py` around lines 64 - 73, The load_header()
function currently opens OG_IMAGE_PATH without a context manager and doesn't
handle missing files; change Image.open(OG_IMAGE_PATH) to use a with-statement
(e.g., with Image.open(OG_IMAGE_PATH) as og:) so the file handle is closed
safely, and wrap that block in a try/except catching FileNotFoundError/OSError
to raise or log a clear error that mentions OG_IMAGE_PATH before attempting to
crop/resize and assign HEADER_STRIP.



def prepare_bidi_text(text, is_rtl):
"""Reshape and reorder RTL text for correct Pillow rendering."""
if not is_rtl:
return text
# Arabic script needs reshaping (letter joining) — apply to all RTL
# since it's a no-op for Hebrew script
text = arabic_reshaper.reshape(text)
return get_display(text)


def draw_wordle_tiles(draw, y_center):
"""Draw the WORDLE letter tiles across the top."""
tile_size = 56
gap = 8
total_width = len(TILE_LETTERS) * tile_size + (len(TILE_LETTERS) - 1) * gap
start_x = (WIDTH - total_width) // 2
font = get_font(BOLD_FONT, 34)

for i, (letter, pattern) in enumerate(zip(TILE_LETTERS, TILE_PATTERN)):
x = start_x + i * (tile_size + gap)
color = TILE_COLORS[pattern]
draw.rounded_rectangle(
[x, y_center - tile_size // 2, x + tile_size, y_center + tile_size // 2],
radius=6,
fill=color,
)
bbox = draw.textbbox((0, 0), letter, font=font)
tw = bbox[2] - bbox[0]
th = bbox[3] - bbox[1]
draw.text(
(x + (tile_size - tw) // 2, y_center - th // 2 - 2),
letter,
fill=WHITE,
font=font,
)


def wrap_text(text, font, draw, max_width):
"""Wrap text to fit within max_width pixels."""
words = text.split()
lines = []
current_line = ""

for word in words:
test_line = f"{current_line} {word}".strip() if current_line else word
bbox = draw.textbbox((0, 0), test_line, font=font)
Expand All @@ -112,90 +95,96 @@ def wrap_text(text, font, draw, max_width):
if current_line:
lines.append(current_line)
current_line = word

if current_line:
lines.append(current_line)

return lines


def draw_mini_tiles(draw, result, score_y):
"""Draw decorative mini tile grids flanking the score."""
mini_size = 16
mini_gap = 4
n_rows = int(result) if result != "x" else 6
solved_row = int(result) - 1 if result != "x" else -1

for side in ("left", "right"):
for row in range(n_rows):
for col in range(5):
if side == "left":
mx = 80 + col * (mini_size + mini_gap)
else:
mx = WIDTH - 80 - (4 - col) * (mini_size + mini_gap) - mini_size
my = score_y + 20 + row * (mini_size + mini_gap)

if row == solved_row:
c = GREEN
else:
offset = 0 if side == "left" else 1
v = (row + col + offset) % 3
c = GREEN if v == 0 else YELLOW if v == 1 else GRAY
draw.rectangle([mx, my, mx + mini_size, my + mini_size], fill=c)
def draw_mini_grid(draw, cx, cy, n_rows, solved_row, tile_size=24, gap=5):
"""Draw a mini tile grid centered at (cx, cy)."""
cols = 5
grid_w = cols * tile_size + (cols - 1) * gap
grid_h = n_rows * tile_size + (n_rows - 1) * gap
x0 = cx - grid_w // 2
y0 = cy - grid_h // 2
for row in range(n_rows):
for col in range(cols):
mx = x0 + col * (tile_size + gap)
my = y0 + row * (tile_size + gap)
if row == solved_row:
c = GREEN
else:
v = (row + col) % 3
c = GREEN if v == 0 else YELLOW if v == 1 else GRAY
draw.rectangle([mx, my, mx + tile_size, my + tile_size], fill=c)


def generate_image(lang_code, result, challenge_text, is_rtl):
"""Generate a single share preview image."""
img = Image.new("RGB", (WIDTH, HEIGHT), BG_COLOR)
draw = ImageDraw.Draw(img)

# 1. WORDLE tiles at top
draw_wordle_tiles(draw, y_center=70)
# 1. Header — pixel-perfect from og-image.png
paste_x = (WIDTH - HEADER_STRIP.width) // 2
img.paste(HEADER_STRIP, (paste_x, 15))

# 2. "WORDLE GLOBAL" text under tiles
font_title = get_font(BOLD_FONT, 22)
bbox = draw.textbbox((0, 0), "WORDLE GLOBAL", font=font_title)
tw = bbox[2] - bbox[0]
draw.text(((WIDTH - tw) // 2, 108), "WORDLE GLOBAL", fill=LIGHT_GRAY, font=font_title)
# 2. Score at 1/3, mini grid at 2/3, vertically aligned
score_text = f"{result}/6" if result != "x" else "X/6"
score_color = GRAY if result == "x" else (GREEN if int(result) <= 3 else YELLOW)

# 3. Big score in the center
if result == "x":
score_text = "X/6"
score_color = GRAY
else:
score_text = f"{result}/6"
score_color = GREEN if int(result) <= 3 else YELLOW
sf = get_font(SCORE_FONT, 160)
bbox = draw.textbbox((0, 0), score_text, font=sf)
stw = bbox[2] - bbox[0]
ascent, _ = sf.getmetrics()

font_score = get_font(BOLD_FONT, 140)
bbox = draw.textbbox((0, 0), score_text, font=font_score)
tw = bbox[2] - bbox[0]
th = bbox[3] - bbox[1]
score_y = 180
draw.text(((WIDTH - tw) // 2, score_y), score_text, fill=score_color, font=font_score)
mid_y = 265 # vertical center of score/grid band
score_x = WIDTH // 3 - stw // 2
score_y = mid_y - ascent // 2 - 5
draw.text((score_x, score_y), score_text, fill=score_color, font=sf)

# 4. Decorative mini tiles flanking the score
draw_mini_tiles(draw, result, score_y)
# Grid centered at 2/3 mark, vertically aligned to score
n_rows = int(result) if result != "x" else 6
solved = int(result) - 1 if result != "x" else -1
score_visual_cy = score_y + ascent // 2 + 10
draw_mini_grid(draw, 2 * WIDTH // 3, score_visual_cy, n_rows, solved)

# 5. Challenge text (apply bidi reordering for RTL languages)
# 3. Challenge text — pick font based on language script
use_cjk = lang_code in CJK_LANGS
font_reg = FONT_CJK if use_cjk else FONT_DEJAVU
font_bold = FONT_CJK_BOLD if use_cjk else FONT_DEJAVU_BOLD

# Auto-size: start at 44px, shrink if text overflows
display_text = prepare_bidi_text(challenge_text, is_rtl)
font_challenge = get_font(CHALLENGE_FONT, 32)
lines = wrap_text(display_text, font_challenge, draw, WIDTH - 160)
text_y = 460
for line in lines[:2]: # Max 2 lines
bbox = draw.textbbox((0, 0), line, font=font_challenge)
tw = bbox[2] - bbox[0]
x = (WIDTH - tw) // 2
draw.text((x, text_y), line, fill=WHITE, font=font_challenge)
text_y += 44

# 6. URL at bottom
font_url = get_font(LATIN_FONT, 20)
url_text = f"wordle.global/{lang_code}"
bbox = draw.textbbox((0, 0), url_text, font=font_url)
tw = bbox[2] - bbox[0]
draw.text(((WIDTH - tw) // 2, HEIGHT - 50), url_text, fill=LIGHT_GRAY, font=font_url)

return img
max_w = WIDTH - 200
for size in (44, 38, 32, 26):
font_main = get_font(font_reg, size)
font_cta = get_font(font_bold, size + 4)
lines = wrap_text(display_text, font_main, draw, max_w)
if len(lines) <= 2:
break

if not lines:
return

if len(lines) >= 2:
line1 = lines[0]
line2 = " ".join(lines[1:])
Comment on lines +159 to +168
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Two-line cap is not guaranteed at minimum font size.

If text still wraps to >2 lines at size 26, the current branch joins all remaining lines into line2, which can overflow the width and break the layout objective.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/generate_share_images.py` around lines 159 - 168, The loop that
reduces font size using get_font and wrap_text may still yield >2 lines at the
minimum size (size 26), but the code then joins all remaining lines into line2
which can overflow; change the post-loop handling in the block that uses lines
to enforce a hard two-line cap: keep line1 = lines[0], construct line2 from the
remaining text but truncate it with an ellipsis so its rendered width (measured
with draw.textlength or font_cta metrics) does not exceed max_w, ensuring you
measure with the chosen font (font_main/font_cta) and trim words/characters
until the width fits; update the logic around variables lines, line1, line2 and
reuse wrap_text/measurement utilities rather than allowing an unbounded join.

bbox1 = draw.textbbox((0, 0), line1, font=font_main)
draw.text(((WIDTH - (bbox1[2] - bbox1[0])) // 2, 430), line1, fill=WHITE, font=font_main)
bbox2 = draw.textbbox((0, 0), line2, font=font_cta)
draw.text(
((WIDTH - (bbox2[2] - bbox2[0])) // 2, 430 + size + 12),
line2,
fill=GREEN,
font=font_cta,
)
else:
line = lines[0]
bbox = draw.textbbox((0, 0), line, font=font_cta)
draw.text(((WIDTH - (bbox[2] - bbox[0])) // 2, 460), line, fill=GREEN, font=font_cta)
Comment on lines +170 to +181
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Run Ruff formatting to enforce max line length.

A few updated draw.text(...) lines exceed the 100-character limit.

As per coding guidelines, "Use Ruff formatter and linter with 100 character line length for Python code".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/generate_share_images.py` around lines 170 - 181, Several
draw.text(...) calls (around the block using bbox1, bbox2, WIDTH, font_main,
font_cta and the else branch using bbox) exceed the 100-char line length; run
the Ruff formatter (or manually wrap the long draw.text argument lists) so each
line is <=100 characters, breaking long argument lists across multiple lines and
aligning subsequent args, and reformat the tuple calculations ((WIDTH - (bbox[2]
- bbox[0])) // 2, ...) and the draw.text(...) calls for both the two-line branch
(line1/line2 using bbox1/bbox2) and the single-line else branch (line using
bbox) to satisfy the linter.


# Convert to palette mode for smaller file size (~6 distinct colors)
return img.convert("P", palette=Image.ADAPTIVE, colors=64)


def load_language_configs():
Expand All @@ -210,7 +199,6 @@ def load_language_configs():
continue
with open(config_path) as f:
lang_config = json.load(f)
# Merge defaults for text section
merged_text = {**defaults.get("text", {}), **lang_config.get("text", {})}
configs[lang_code] = {
"name_native": lang_config.get("name_native", lang_config.get("name", lang_code)),
Expand All @@ -228,9 +216,9 @@ def load_language_configs():


def main():
load_header()
configs = load_language_configs()

# Filter languages if args provided
if len(sys.argv) > 1:
target_langs = sys.argv[1:]
else:
Expand Down
8 changes: 6 additions & 2 deletions webapp/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -612,8 +612,12 @@ def load_languages():
)
min_lang = min(language_codes_5words, key=lambda k: len(language_codes_5words[k]))
max_lang = max(language_codes_5words, key=lambda k: len(language_codes_5words[k]))
print(f"- The language with least words is {min_lang}, with {len(language_codes_5words[min_lang])} words")
print(f"- The language with most words is {max_lang}, with {len(language_codes_5words[max_lang])} words")
print(
f"- The language with least words is {min_lang}, with {len(language_codes_5words[min_lang])} words"
)
print(
f"- The language with most words is {max_lang}, with {len(language_codes_5words[max_lang])} words"
)
print(
f"- Average number of words per language is {sum(len(language_codes_5words[l_code]) for l_code in language_codes) / len(language_codes):.2f}"
)
Expand Down
Binary file modified webapp/static/images/share/ar_1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified webapp/static/images/share/ar_2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified webapp/static/images/share/ar_3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified webapp/static/images/share/ar_4.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified webapp/static/images/share/ar_5.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified webapp/static/images/share/ar_6.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified webapp/static/images/share/ar_x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified webapp/static/images/share/az_1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified webapp/static/images/share/az_2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified webapp/static/images/share/az_3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified webapp/static/images/share/az_4.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified webapp/static/images/share/az_5.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified webapp/static/images/share/az_6.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified webapp/static/images/share/az_x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified webapp/static/images/share/bg_1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified webapp/static/images/share/bg_2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified webapp/static/images/share/bg_3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified webapp/static/images/share/bg_4.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified webapp/static/images/share/bg_5.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified webapp/static/images/share/bg_6.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified webapp/static/images/share/bg_x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified webapp/static/images/share/br_1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified webapp/static/images/share/br_2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified webapp/static/images/share/br_3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified webapp/static/images/share/br_4.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified webapp/static/images/share/br_5.png
Binary file modified webapp/static/images/share/br_6.png
Binary file modified webapp/static/images/share/br_x.png
Binary file modified webapp/static/images/share/ca_1.png
Binary file modified webapp/static/images/share/ca_2.png
Binary file modified webapp/static/images/share/ca_3.png
Binary file modified webapp/static/images/share/ca_4.png
Binary file modified webapp/static/images/share/ca_5.png
Binary file modified webapp/static/images/share/ca_6.png
Binary file modified webapp/static/images/share/ca_x.png
Binary file modified webapp/static/images/share/ckb_1.png
Binary file modified webapp/static/images/share/ckb_2.png
Binary file modified webapp/static/images/share/ckb_3.png
Binary file modified webapp/static/images/share/ckb_4.png
Binary file modified webapp/static/images/share/ckb_5.png
Binary file modified webapp/static/images/share/ckb_6.png
Binary file modified webapp/static/images/share/ckb_x.png
Binary file modified webapp/static/images/share/cs_1.png
Binary file modified webapp/static/images/share/cs_2.png
Binary file modified webapp/static/images/share/cs_3.png
Binary file modified webapp/static/images/share/cs_4.png
Binary file modified webapp/static/images/share/cs_5.png
Binary file modified webapp/static/images/share/cs_6.png
Binary file modified webapp/static/images/share/cs_x.png
Binary file modified webapp/static/images/share/da_1.png
Binary file modified webapp/static/images/share/da_2.png
Binary file modified webapp/static/images/share/da_3.png
Binary file modified webapp/static/images/share/da_4.png
Binary file modified webapp/static/images/share/da_5.png
Binary file modified webapp/static/images/share/da_6.png
Binary file modified webapp/static/images/share/da_x.png
Binary file modified webapp/static/images/share/de_1.png
Binary file modified webapp/static/images/share/de_2.png
Binary file modified webapp/static/images/share/de_3.png
Binary file modified webapp/static/images/share/de_4.png
Binary file modified webapp/static/images/share/de_5.png
Binary file modified webapp/static/images/share/de_6.png
Binary file modified webapp/static/images/share/de_x.png
Binary file modified webapp/static/images/share/el_1.png
Binary file modified webapp/static/images/share/el_2.png
Binary file modified webapp/static/images/share/el_3.png
Binary file modified webapp/static/images/share/el_4.png
Binary file modified webapp/static/images/share/el_5.png
Binary file modified webapp/static/images/share/el_6.png
Binary file modified webapp/static/images/share/el_x.png
Binary file modified webapp/static/images/share/en_1.png
Binary file modified webapp/static/images/share/en_2.png
Binary file modified webapp/static/images/share/en_3.png
Binary file modified webapp/static/images/share/en_4.png
Binary file modified webapp/static/images/share/en_5.png
Binary file modified webapp/static/images/share/en_6.png
Binary file modified webapp/static/images/share/en_x.png
Binary file modified webapp/static/images/share/eo_1.png
Binary file modified webapp/static/images/share/eo_2.png
Binary file modified webapp/static/images/share/eo_3.png
Binary file modified webapp/static/images/share/eo_4.png
Binary file modified webapp/static/images/share/eo_5.png
Binary file modified webapp/static/images/share/eo_6.png
Binary file modified webapp/static/images/share/eo_x.png
Binary file modified webapp/static/images/share/es_1.png
Binary file modified webapp/static/images/share/es_2.png
Binary file modified webapp/static/images/share/es_3.png
Binary file modified webapp/static/images/share/es_4.png
Binary file modified webapp/static/images/share/es_5.png
Binary file modified webapp/static/images/share/es_6.png
Binary file modified webapp/static/images/share/es_x.png
Binary file modified webapp/static/images/share/et_1.png
Binary file modified webapp/static/images/share/et_2.png
Binary file modified webapp/static/images/share/et_3.png
Binary file modified webapp/static/images/share/et_4.png
Binary file modified webapp/static/images/share/et_5.png
Binary file modified webapp/static/images/share/et_6.png
Binary file modified webapp/static/images/share/et_x.png
Binary file modified webapp/static/images/share/eu_1.png
Binary file modified webapp/static/images/share/eu_2.png
Binary file modified webapp/static/images/share/eu_3.png
Binary file modified webapp/static/images/share/eu_4.png
Binary file modified webapp/static/images/share/eu_5.png
Binary file modified webapp/static/images/share/eu_6.png
Binary file modified webapp/static/images/share/eu_x.png
Binary file modified webapp/static/images/share/fa_1.png
Binary file modified webapp/static/images/share/fa_2.png
Binary file modified webapp/static/images/share/fa_3.png
Binary file modified webapp/static/images/share/fa_4.png
Binary file modified webapp/static/images/share/fa_5.png
Binary file modified webapp/static/images/share/fa_6.png
Binary file modified webapp/static/images/share/fa_x.png
Binary file modified webapp/static/images/share/fi_1.png
Binary file modified webapp/static/images/share/fi_2.png
Binary file modified webapp/static/images/share/fi_3.png
Binary file modified webapp/static/images/share/fi_4.png
Binary file modified webapp/static/images/share/fi_5.png
Binary file modified webapp/static/images/share/fi_6.png
Binary file modified webapp/static/images/share/fi_x.png
Binary file modified webapp/static/images/share/fo_1.png
Binary file modified webapp/static/images/share/fo_2.png
Binary file modified webapp/static/images/share/fo_3.png
Binary file modified webapp/static/images/share/fo_4.png
Binary file modified webapp/static/images/share/fo_5.png
Binary file modified webapp/static/images/share/fo_6.png
Binary file modified webapp/static/images/share/fo_x.png
Binary file modified webapp/static/images/share/fr_1.png
Binary file modified webapp/static/images/share/fr_2.png
Binary file modified webapp/static/images/share/fr_3.png
Binary file modified webapp/static/images/share/fr_4.png
Binary file modified webapp/static/images/share/fr_5.png
Binary file modified webapp/static/images/share/fr_6.png
Binary file modified webapp/static/images/share/fr_x.png
Binary file modified webapp/static/images/share/fur_1.png
Binary file modified webapp/static/images/share/fur_2.png
Binary file modified webapp/static/images/share/fur_3.png
Binary file modified webapp/static/images/share/fur_4.png
Binary file modified webapp/static/images/share/fur_5.png
Binary file modified webapp/static/images/share/fur_6.png
Binary file modified webapp/static/images/share/fur_x.png
Binary file modified webapp/static/images/share/fy_1.png
Binary file modified webapp/static/images/share/fy_2.png
Binary file modified webapp/static/images/share/fy_3.png
Binary file modified webapp/static/images/share/fy_4.png
Binary file modified webapp/static/images/share/fy_5.png
Binary file modified webapp/static/images/share/fy_6.png
Binary file modified webapp/static/images/share/fy_x.png
Binary file modified webapp/static/images/share/ga_1.png
Binary file modified webapp/static/images/share/ga_2.png
Binary file modified webapp/static/images/share/ga_3.png
Binary file modified webapp/static/images/share/ga_4.png
Binary file modified webapp/static/images/share/ga_5.png
Binary file modified webapp/static/images/share/ga_6.png
Binary file modified webapp/static/images/share/ga_x.png
Binary file modified webapp/static/images/share/gd_1.png
Binary file modified webapp/static/images/share/gd_2.png
Binary file modified webapp/static/images/share/gd_3.png
Binary file modified webapp/static/images/share/gd_4.png
Binary file modified webapp/static/images/share/gd_5.png
Binary file modified webapp/static/images/share/gd_6.png
Binary file modified webapp/static/images/share/gd_x.png
Binary file modified webapp/static/images/share/gl_1.png
Binary file modified webapp/static/images/share/gl_2.png
Binary file modified webapp/static/images/share/gl_3.png
Binary file modified webapp/static/images/share/gl_4.png
Binary file modified webapp/static/images/share/gl_5.png
Binary file modified webapp/static/images/share/gl_6.png
Binary file modified webapp/static/images/share/gl_x.png
Binary file modified webapp/static/images/share/he_1.png
Binary file modified webapp/static/images/share/he_2.png
Binary file modified webapp/static/images/share/he_3.png
Binary file modified webapp/static/images/share/he_4.png
Binary file modified webapp/static/images/share/he_5.png
Binary file modified webapp/static/images/share/he_6.png
Binary file modified webapp/static/images/share/he_x.png
Binary file modified webapp/static/images/share/hr_1.png
Binary file modified webapp/static/images/share/hr_2.png
Binary file modified webapp/static/images/share/hr_3.png
Binary file modified webapp/static/images/share/hr_4.png
Binary file modified webapp/static/images/share/hr_5.png
Binary file modified webapp/static/images/share/hr_6.png
Binary file modified webapp/static/images/share/hr_x.png
Binary file modified webapp/static/images/share/hu_1.png
Binary file modified webapp/static/images/share/hu_2.png
Binary file modified webapp/static/images/share/hu_3.png
Binary file modified webapp/static/images/share/hu_4.png
Binary file modified webapp/static/images/share/hu_5.png
Binary file modified webapp/static/images/share/hu_6.png
Binary file modified webapp/static/images/share/hu_x.png
Binary file modified webapp/static/images/share/hy_1.png
Binary file modified webapp/static/images/share/hy_2.png
Binary file modified webapp/static/images/share/hy_3.png
Binary file modified webapp/static/images/share/hy_4.png
Binary file modified webapp/static/images/share/hy_5.png
Binary file modified webapp/static/images/share/hy_6.png
Binary file modified webapp/static/images/share/hy_x.png
Binary file modified webapp/static/images/share/hyw_1.png
Binary file modified webapp/static/images/share/hyw_2.png
Binary file modified webapp/static/images/share/hyw_3.png
Binary file modified webapp/static/images/share/hyw_4.png
Binary file modified webapp/static/images/share/hyw_5.png
Binary file modified webapp/static/images/share/hyw_6.png
Binary file modified webapp/static/images/share/hyw_x.png
Binary file modified webapp/static/images/share/ia_1.png
Binary file modified webapp/static/images/share/ia_2.png
Binary file modified webapp/static/images/share/ia_3.png
Binary file modified webapp/static/images/share/ia_4.png
Binary file modified webapp/static/images/share/ia_5.png
Binary file modified webapp/static/images/share/ia_6.png
Binary file modified webapp/static/images/share/ia_x.png
Binary file modified webapp/static/images/share/ie_1.png
Binary file modified webapp/static/images/share/ie_2.png
Binary file modified webapp/static/images/share/ie_3.png
Binary file modified webapp/static/images/share/ie_4.png
Binary file modified webapp/static/images/share/ie_5.png
Binary file modified webapp/static/images/share/ie_6.png
Binary file modified webapp/static/images/share/ie_x.png
Binary file modified webapp/static/images/share/is_1.png
Binary file modified webapp/static/images/share/is_2.png
Binary file modified webapp/static/images/share/is_3.png
Binary file modified webapp/static/images/share/is_4.png
Binary file modified webapp/static/images/share/is_5.png
Binary file modified webapp/static/images/share/is_6.png
Binary file modified webapp/static/images/share/is_x.png
Binary file modified webapp/static/images/share/it_1.png
Binary file modified webapp/static/images/share/it_2.png
Binary file modified webapp/static/images/share/it_3.png
Binary file modified webapp/static/images/share/it_4.png
Binary file modified webapp/static/images/share/it_5.png
Binary file modified webapp/static/images/share/it_6.png
Binary file modified webapp/static/images/share/it_x.png
Binary file modified webapp/static/images/share/ka_1.png
Binary file modified webapp/static/images/share/ka_2.png
Binary file modified webapp/static/images/share/ka_3.png
Binary file modified webapp/static/images/share/ka_4.png
Binary file modified webapp/static/images/share/ka_5.png
Binary file modified webapp/static/images/share/ka_6.png
Binary file modified webapp/static/images/share/ka_x.png
Binary file modified webapp/static/images/share/ko_1.png
Binary file modified webapp/static/images/share/ko_2.png
Binary file modified webapp/static/images/share/ko_3.png
Binary file modified webapp/static/images/share/ko_4.png
Binary file modified webapp/static/images/share/ko_5.png
Binary file modified webapp/static/images/share/ko_6.png
Binary file modified webapp/static/images/share/ko_x.png
Binary file modified webapp/static/images/share/la_1.png
Binary file modified webapp/static/images/share/la_2.png
Binary file modified webapp/static/images/share/la_3.png
Binary file modified webapp/static/images/share/la_4.png
Binary file modified webapp/static/images/share/la_5.png
Binary file modified webapp/static/images/share/la_6.png
Binary file modified webapp/static/images/share/la_x.png
Binary file modified webapp/static/images/share/lb_1.png
Binary file modified webapp/static/images/share/lb_2.png
Binary file modified webapp/static/images/share/lb_3.png
Binary file modified webapp/static/images/share/lb_4.png
Binary file modified webapp/static/images/share/lb_5.png
Binary file modified webapp/static/images/share/lb_6.png
Binary file modified webapp/static/images/share/lb_x.png
Binary file modified webapp/static/images/share/lt_1.png
Binary file modified webapp/static/images/share/lt_2.png
Binary file modified webapp/static/images/share/lt_3.png
Binary file modified webapp/static/images/share/lt_4.png
Binary file modified webapp/static/images/share/lt_5.png
Binary file modified webapp/static/images/share/lt_6.png
Binary file modified webapp/static/images/share/lt_x.png
Binary file modified webapp/static/images/share/ltg_1.png
Binary file modified webapp/static/images/share/ltg_2.png
Binary file modified webapp/static/images/share/ltg_3.png
Binary file modified webapp/static/images/share/ltg_4.png
Binary file modified webapp/static/images/share/ltg_5.png
Binary file modified webapp/static/images/share/ltg_6.png
Binary file modified webapp/static/images/share/ltg_x.png
Binary file modified webapp/static/images/share/lv_1.png
Binary file modified webapp/static/images/share/lv_2.png
Binary file modified webapp/static/images/share/lv_3.png
Binary file modified webapp/static/images/share/lv_4.png
Binary file modified webapp/static/images/share/lv_5.png
Binary file modified webapp/static/images/share/lv_6.png
Binary file modified webapp/static/images/share/lv_x.png
Binary file modified webapp/static/images/share/mi_1.png
Binary file modified webapp/static/images/share/mi_2.png
Binary file modified webapp/static/images/share/mi_3.png
Binary file modified webapp/static/images/share/mi_4.png
Binary file modified webapp/static/images/share/mi_5.png
Binary file modified webapp/static/images/share/mi_6.png
Binary file modified webapp/static/images/share/mi_x.png
Binary file modified webapp/static/images/share/mk_1.png
Binary file modified webapp/static/images/share/mk_2.png
Binary file modified webapp/static/images/share/mk_3.png
Binary file modified webapp/static/images/share/mk_4.png
Binary file modified webapp/static/images/share/mk_5.png
Binary file modified webapp/static/images/share/mk_6.png
Binary file modified webapp/static/images/share/mk_x.png
Binary file modified webapp/static/images/share/mn_1.png
Binary file modified webapp/static/images/share/mn_2.png
Binary file modified webapp/static/images/share/mn_3.png
Binary file modified webapp/static/images/share/mn_4.png
Loading
Loading