diff --git a/README.md b/README.md index b17da5d2..24a88c78 100644 --- a/README.md +++ b/README.md @@ -226,6 +226,36 @@ The sponsor summary is defined in a separate partial `themes/pybcn_theme/layouts About the creation of new sponsors, the `themes/pybcn_theme/archetypes/sponsors.md` file contains the template Hugo will use when the editor runs the command `hugo new sponsors/my-new-sponsor.md`. +### Person photos + +Every person photo is a square, cut once in the repository rather than on each +build. `bin/square-photos` does the cutting: it finds the face with OpenCV, +crops the largest square it can centre on that face, and resizes to at most +800px, which is what the 2x variant of a 400px grid slot needs. It never +enlarges, because a 200px photo blown up to 800 is the same picture in four +times the bytes. + +``` +pip install opencv-python pillow +bin/square-photos --dry-run # list what would change, change nothing +bin/square-photos # rewrite the photos in place +``` + +It is run by hand when a photo is added. The build does not run it and neither +does CI, so a contributor without OpenCV can still build the site. + +Review the result before committing. Face detection is crude: it misses a face +in profile or in shadow, and it finds faces that are not there. When it misses, +the crop falls back to the upper middle of the picture, which is where a head +usually is. When the crop is wrong, the usual cause is the photo rather than +the crop: a full-length shot on a beach stays a full-length shot on a beach, +because the tool does not zoom in. + +`bin/check-content` rejects a photo that is not square, one over 1200px, and +one over 1000 KB, and it reports the photos under 400px, which the build has +to enlarge and which look soft in the grid. Replacing the file is the only fix +for those. + ### People pages implementation details: organizers, speakers... There are three pages displaying people: PyBCN Organizers, PyLadiesBCN Organizers and PyLadiesBCN speakers. More can be added to display, for example, collaborators of other events, etc. diff --git a/bin/check-content b/bin/check-content index 3d996ac9..f6048491 100755 --- a/bin/check-content +++ b/bin/check-content @@ -153,19 +153,20 @@ def image_size(path): def check_photos(people): - # A photo that is not square is cropped by the build rather than squashed, - # so it is not an error. It is reported once, with a count, because there - # are dozens of them and a line each would bury everything else. - cropped = [] + """Every person photo has to be a square the site can serve as it is. + + The square is cut once, in the repository, by bin/square-photos, so the + build only resizes. A photo that is not square would be cropped by the + build instead, by a rule that does not know where the face is, and nobody + would see the result before it was live. + + The size limits are here for the pull request nobody reads closely: a + photo straight off a phone is several megabytes and tens of megapixels, + and it costs the repository that much for ever. + """ small = [] for page_id, path in people.items(): data = front_matter(path) or {} - # A remote photo loads from a third party on every page that lists - # the person and tells that party who viewed it. The template no - # longer reads the field, so a value here would be ignored in silence. - if data.get("photo_url"): - error(path, "photo_url is not supported: put the file under " - "themes/pybcn_theme/assets/images/people/ and use 'photo'") photo = data.get("photo") if photo and not bare_file_name(path, "photo", photo): continue @@ -173,17 +174,35 @@ def check_photos(people): if photo and not found: where = ", ".join(str(d.relative_to(ROOT)) for d in PHOTO_DIRS) error(path, f"photo '{photo}' not found in any of: {where}") - elif found: - size = image_size(found) - if size and size[0] != size[1]: - cropped.append((photo, size)) - if size and min(size) < PHOTO_SIDE: - small.append((photo, size)) - report_cropped(cropped) + continue + if not found: + continue + + size = found.stat().st_size + if size > PHOTO_MAX_BYTES: + error(path, f"photo '{photo}' is {size // 1024} KB, over the " + f"{PHOTO_MAX_BYTES // 1024} KB limit. Run " + f"bin/square-photos, or save it smaller.") + + dimensions = image_size(found) + if not dimensions: + continue + width, height = dimensions + if width != height: + error(path, f"photo '{photo}' is {width}x{height}, not square. " + f"Run bin/square-photos, which crops it on the face.") + elif width > PHOTO_MAX_SIDE: + error(path, f"photo '{photo}' is {width}px square, over the " + f"{PHOTO_MAX_SIDE}px limit. The site never serves more " + f"than 800. Run bin/square-photos.") + if min(width, height) < PHOTO_SIDE: + small.append((photo, (width, height))) report_small(small) PHOTO_SIDE = 400 +PHOTO_MAX_SIDE = 1200 +PHOTO_MAX_BYTES = 1_000_000 def report_small(small): @@ -192,18 +211,7 @@ def report_small(small): shown = ", ".join(f"{name} ({w}x{h})" for name, (w, h) in sorted(small)[:3]) note(f"{len(small)} of the person photos are smaller than {PHOTO_SIDE}px on " f"their short side, so the build enlarges them and they look soft in " - f"the grid. Smallest: {shown}.") - - -def report_cropped(cropped): - if not cropped: - return - worst = sorted(cropped, key=lambda item: abs(item[1][0] / item[1][1] - 1), reverse=True)[:3] - shown = ", ".join(f"{name} ({w}x{h})" for name, (w, h) in worst) - note(f"{len(cropped)} of the person photos are not square, so the build " - f"crops them to fit the grid and part of each picture is lost. " - f"Furthest from square: {shown}. Crop one yourself to choose which " - f"part survives.") + f"the grid. Smallest: {shown}. Replacing the file is the only fix.") def check_logos(sponsors): diff --git a/bin/square-photos b/bin/square-photos new file mode 100755 index 00000000..62abbb91 --- /dev/null +++ b/bin/square-photos @@ -0,0 +1,208 @@ +#!/usr/bin/env python3 +"""Crop the person photos to a square, centred on the face. + +The site serves every person photo as a square. Until now the square was cut +at build time, which has two costs: the crop is chosen by a heuristic that +does not know where the face is, and the repository carries sources far larger +than anything the site ever serves, 38 MB of them. + +This cuts the square once, in the repository, so the crop is reviewable in a +pull request like any other change, and the build only has to resize. + +The crop is centred on the face when OpenCV finds one, and on the upper middle +when it does not, because a portrait that is taller than it is wide almost +always has the head in the top half. + +Usage: + bin/square-photos --dry-run list what would change, change nothing + bin/square-photos rewrite the photos in place + bin/square-photos --side 800 pick the target side, default 800 + +Needs opencv-python and pillow, which are not needed to build the site. This +is run by hand when a photo is added, not by the build and not by CI. +""" + +import argparse +import pathlib +import sys + +try: + import cv2 + from PIL import Image, ImageOps +except ImportError: + sys.exit("needs opencv-python and pillow: pip install opencv-python pillow") + +ROOT = pathlib.Path(__file__).resolve().parent.parent +PHOTOS = ROOT / "themes" / "pybcn_theme" / "assets" / "images" / "people" +SUFFIXES = {".jpg", ".jpeg", ".png", ".webp"} + +# 800px is what the 2x variant of a 400px grid slot needs. A source larger than +# that is never served at full size, so the extra pixels are weight and nothing +# else. +DEFAULT_SIDE = 800 + + +def load(path): + """Open a photo with its EXIF rotation applied. + + One photo in the set carries Orientation 5. A browser honours that tag and + shows the picture upright, so the site looked right while every tool that + reads the pixels directly saw it on its side. Applying the rotation here + and dropping the tag leaves one truth instead of two. + """ + image = Image.open(path) + return ImageOps.exif_transpose(image) + + +def find_face(path): + """Return (x, y, w, h) of the face, or None. + + A Haar cascade returns several boxes for one portrait and most of them are + wrong. Neither obvious rule picks the right one on its own: + + toni_espadas real face 953px at 5.40, a 59px box scored 6.85 + ferran-jovell real face 2062px at 3.84, a 154px box scored 5.92 + juan-bernardo-lince real face 170px at 0.18, an 88px box scored 1.69 + alicia-morales real face 932px at 7.22, a 2091px box scored 1.72 + + adrian-garcia-riber real face 854px at 7.27, a 51px box scored 0.20 + + Taking the most confident box fails on the first three. Taking the largest + fails on alicia-morales. Dropping every box wider than 60% of the short + side fixes her and breaks adrian-garcia-riber, whose face really is 64% of + it, because the photo is a close selfie. + + Width alone cannot tell a close selfie from a box over a torso. The score + can: the selfie scored 7.27 and the torso 1.72. So a wide box survives when + it is confident, and the largest of the survivors wins. + + The cascade still misses a face in profile or in shadow, which is why the + caller has a fallback rather than an error. + """ + with load(path) as upright: + rgb = upright.convert("RGB") + import numpy + image = cv2.cvtColor(numpy.array(rgb), cv2.COLOR_RGB2BGR) + grey = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) + grey = cv2.equalizeHist(grey) + cascade = cv2.CascadeClassifier( + cv2.data.haarcascades + "haarcascade_frontalface_default.xml") + faces, _levels, weights = cascade.detectMultiScale3( + grey, scaleFactor=1.1, minNeighbors=5, minSize=(40, 40), + outputRejectLevels=True) + if len(faces) == 0: + return None + limit = MAX_FACE_FRACTION * min(grey.shape[0], grey.shape[1]) + plausible = [face for face, weight in zip(faces, weights) + if face[2] <= limit or weight >= MIN_WIDE_FACE_WEIGHT] + return max(plausible or list(faces), key=lambda f: f[2] * f[3]) + + +# A square centred on a face near the edge does not fit, so the square has to +# shrink. It does not shrink below this, because a tight crop of a head with no +# shoulders looks like a mugshot next to the others in the grid. +MIN_FACE_MULTIPLE = 3.5 + +# A detection this wide, as a fraction of the short side, is suspect: a face +# that big is a close selfie, which happens, but so does a box over a whole +# torso, which is not a face at all. +MAX_FACE_FRACTION = 0.6 + +# What tells those two apart is the score. The selfie scored 7.27 and the torso +# scored 1.72, so a wide box is kept when it is confident and dropped when it +# is not. +MIN_WIDE_FACE_WEIGHT = 3.0 + + +def crop_box(width, height, face): + """Return the square box to keep, as (left, top, right, bottom). + + The square is the largest one that fits, unless the face sits closer to an + edge than half of that: then it shrinks, so the face stays in the middle + rather than drifting to a corner. One photo in the set, a 2731x4096 portrait + with the head high in the frame, came out with the face a third of the way + down before this. + """ + side = min(width, height) + if face is not None: + fx, fy, fw, fh = face + centre_x = fx + fw / 2 + centre_y = fy + fh / 2 + fits_centred = 2 * min(centre_x, width - centre_x, + centre_y, height - centre_y) + floor = min(side, max(DEFAULT_SIDE, round(MIN_FACE_MULTIPLE * fh))) + side = max(min(side, round(fits_centred)), floor) + side = min(side, width, height) + else: + centre_x = width / 2 + # The upper middle, not the middle: a portrait taller than it is wide + # has the head in the top half, and a centred crop cuts the forehead. + centre_y = height / 2 if width >= height else height * 0.4 + left = round(centre_x - side / 2) + top = round(centre_y - side / 2) + left = max(0, min(left, width - side)) + top = max(0, min(top, height - side)) + return (left, top, left + side, top + side) + + +def process(path, side, dry_run): + with load(path) as image: + width, height = image.size + already_square = width == height + face = None if already_square and width <= side else find_face(path) + box = crop_box(width, height, face) + cropped_side = box[2] - box[0] + + # Never enlarge. A 200px photo blown up to 800 is the same picture in + # four times the bytes, and the build already enlarges when it has to. + target = min(side, cropped_side) + + rotated = image.getexif().get(274, 1) not in (1, None) + if already_square and width == target and not rotated: + return None + + where = "face" if face is not None else "fallback" + note = (f"{width}x{height} -> {target}x{target}" + f" crop {cropped_side}px at {box[0]},{box[1]} ({where})") + if dry_run: + return note + + out = image.convert("RGB") if image.mode in ("P", "RGBA", "LA") else image + out = out.crop(box) + if cropped_side != target: + out = out.resize((target, target), Image.LANCZOS) + if path.suffix.lower() in (".jpg", ".jpeg"): + out.save(path, "JPEG", quality=88, optimize=True, progressive=True) + elif path.suffix.lower() == ".png": + out.save(path, "PNG", optimize=True) + else: + out.save(path) + return note + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--dry-run", action="store_true") + parser.add_argument("--side", type=int, default=DEFAULT_SIDE) + args = parser.parse_args() + + changed = 0 + before = after = 0 + for path in sorted(PHOTOS.iterdir()): + if path.suffix.lower() not in SUFFIXES: + continue + before += path.stat().st_size + note = process(path, args.side, args.dry_run) + after += path.stat().st_size + if note: + changed += 1 + print(f"{path.name}: {note}") + + print(f"\n{changed} photos {'would change' if args.dry_run else 'changed'}.") + if not args.dry_run: + print(f"{before / 1048576:.1f} MB -> {after / 1048576:.1f} MB") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/config.toml b/config.toml index 4959eda2..4d2a197a 100644 --- a/config.toml +++ b/config.toml @@ -38,8 +38,8 @@ unsafe=true [params.social_items] [params.social_items.twitter] - name = "Twitter" - icon = "fab fa-twitter-square fa-2x" + name = "X" + icon = "icon_x fa-2x" link = "https://twitter.com/PyBCN" [params.social_items.youtube] name = "YouTube" @@ -64,8 +64,8 @@ unsafe=true [params.social_items_pyladies] [params.social_items_pyladies.twitter] - name = "Twitter" - icon = "fab fa-twitter-square fa-2x" + name = "X" + icon = "icon_x fa-2x" link = "https://twitter.com/PyLadiesBCN" [params.social_items_pyladies.meetup] name = "Meetup" diff --git a/content/pybcn_association/organizers.md b/content/pybcn_association/organizers.md index 6913bb54..6ca787e7 100644 --- a/content/pybcn_association/organizers.md +++ b/content/pybcn_association/organizers.md @@ -10,16 +10,16 @@ layout: people heroBackground: /images/photos/reg-desk-pyday-2019.jpg people_levels: - - people_per_line: 3 + - people_per_line: 5 people: [jordi-bosch, daniel-mesejo, yago-tafalla, mauricio-volcanes, sergi-ramirez] - name: The Management Board - - people_per_line: 4 + name: Management Board (2026) + - people_per_line: 5 people: [alberto, david, ifosch, loris, natalia, rberenguel, xavi, alicia-morales, ferran-jovell] - name: The Permanent Committee - - people_per_line: 6 + name: Permanent Committee + - people_per_line: 5 people: [elisabeth-ortega-carrasco, josep, lpmayos, nuria, jose-riera] - name: Our collaborators + name: Collaborators - people_per_line: 5 people: [lpmayos-president, mireia, natalia-secretary] - name: Management Board 2018 - 2025 + name: Management Board (2018 - 2025) --- diff --git a/themes/pybcn_theme/assets/images/people/JavierSabariego.jpg b/themes/pybcn_theme/assets/images/people/JavierSabariego.jpg index c8c22363..fc17c7e0 100644 Binary files a/themes/pybcn_theme/assets/images/people/JavierSabariego.jpg and b/themes/pybcn_theme/assets/images/people/JavierSabariego.jpg differ diff --git a/themes/pybcn_theme/assets/images/people/VyronVasileiadis.jpg b/themes/pybcn_theme/assets/images/people/VyronVasileiadis.jpg index ecba7314..ffa38611 100644 Binary files a/themes/pybcn_theme/assets/images/people/VyronVasileiadis.jpg and b/themes/pybcn_theme/assets/images/people/VyronVasileiadis.jpg differ diff --git a/themes/pybcn_theme/assets/images/people/adria-blanco.jpg b/themes/pybcn_theme/assets/images/people/adria-blanco.jpg index 2f3608ed..6c72c69e 100644 Binary files a/themes/pybcn_theme/assets/images/people/adria-blanco.jpg and b/themes/pybcn_theme/assets/images/people/adria-blanco.jpg differ diff --git a/themes/pybcn_theme/assets/images/people/adrian-garcia-riber.jpeg b/themes/pybcn_theme/assets/images/people/adrian-garcia-riber.jpeg index 5246b091..5362cde3 100644 Binary files a/themes/pybcn_theme/assets/images/people/adrian-garcia-riber.jpeg and b/themes/pybcn_theme/assets/images/people/adrian-garcia-riber.jpeg differ diff --git a/themes/pybcn_theme/assets/images/people/afra-llado.jpg b/themes/pybcn_theme/assets/images/people/afra-llado.jpg index 10ace9e4..6b456e41 100644 Binary files a/themes/pybcn_theme/assets/images/people/afra-llado.jpg and b/themes/pybcn_theme/assets/images/people/afra-llado.jpg differ diff --git a/themes/pybcn_theme/assets/images/people/albert-franzi.jpeg b/themes/pybcn_theme/assets/images/people/albert-franzi.jpeg index c659305c..e81e8b63 100644 Binary files a/themes/pybcn_theme/assets/images/people/albert-franzi.jpeg and b/themes/pybcn_theme/assets/images/people/albert-franzi.jpeg differ diff --git a/themes/pybcn_theme/assets/images/people/alberto-labarga.jpg b/themes/pybcn_theme/assets/images/people/alberto-labarga.jpg index ab333b62..0030eb32 100644 Binary files a/themes/pybcn_theme/assets/images/people/alberto-labarga.jpg and b/themes/pybcn_theme/assets/images/people/alberto-labarga.jpg differ diff --git a/themes/pybcn_theme/assets/images/people/alejandro-nicolas.jpg b/themes/pybcn_theme/assets/images/people/alejandro-nicolas.jpg index b59085c2..b6b85cdb 100644 Binary files a/themes/pybcn_theme/assets/images/people/alejandro-nicolas.jpg and b/themes/pybcn_theme/assets/images/people/alejandro-nicolas.jpg differ diff --git a/themes/pybcn_theme/assets/images/people/alex-molas.png b/themes/pybcn_theme/assets/images/people/alex-molas.png index 3bf442ac..87d1251e 100644 Binary files a/themes/pybcn_theme/assets/images/people/alex-molas.png and b/themes/pybcn_theme/assets/images/people/alex-molas.png differ diff --git a/themes/pybcn_theme/assets/images/people/alexandre-savio.jpeg b/themes/pybcn_theme/assets/images/people/alexandre-savio.jpeg index e02b33d0..29525cc7 100644 Binary files a/themes/pybcn_theme/assets/images/people/alexandre-savio.jpeg and b/themes/pybcn_theme/assets/images/people/alexandre-savio.jpeg differ diff --git a/themes/pybcn_theme/assets/images/people/alicia-morales.jpeg b/themes/pybcn_theme/assets/images/people/alicia-morales.jpeg index 4ee04153..8691e57d 100644 Binary files a/themes/pybcn_theme/assets/images/people/alicia-morales.jpeg and b/themes/pybcn_theme/assets/images/people/alicia-morales.jpeg differ diff --git a/themes/pybcn_theme/assets/images/people/alvaro-duran-barata.jpeg b/themes/pybcn_theme/assets/images/people/alvaro-duran-barata.jpeg index 8104038a..c4a7ff5c 100644 Binary files a/themes/pybcn_theme/assets/images/people/alvaro-duran-barata.jpeg and b/themes/pybcn_theme/assets/images/people/alvaro-duran-barata.jpeg differ diff --git a/themes/pybcn_theme/assets/images/people/alvaro_duran.jpg b/themes/pybcn_theme/assets/images/people/alvaro_duran.jpg index 2432a3e8..74b6790c 100644 Binary files a/themes/pybcn_theme/assets/images/people/alvaro_duran.jpg and b/themes/pybcn_theme/assets/images/people/alvaro_duran.jpg differ diff --git a/themes/pybcn_theme/assets/images/people/angel.jpg b/themes/pybcn_theme/assets/images/people/angel.jpg index 4b4ca0ae..397d8694 100644 Binary files a/themes/pybcn_theme/assets/images/people/angel.jpg and b/themes/pybcn_theme/assets/images/people/angel.jpg differ diff --git a/themes/pybcn_theme/assets/images/people/anton-caceres.jpg b/themes/pybcn_theme/assets/images/people/anton-caceres.jpg index c6c5581f..8864f069 100644 Binary files a/themes/pybcn_theme/assets/images/people/anton-caceres.jpg and b/themes/pybcn_theme/assets/images/people/anton-caceres.jpg differ diff --git a/themes/pybcn_theme/assets/images/people/ariadna_fernandez.jpg b/themes/pybcn_theme/assets/images/people/ariadna_fernandez.jpg index 6bfa74e0..e3075ae7 100644 Binary files a/themes/pybcn_theme/assets/images/people/ariadna_fernandez.jpg and b/themes/pybcn_theme/assets/images/people/ariadna_fernandez.jpg differ diff --git a/themes/pybcn_theme/assets/images/people/biel-stela-ballester.jpeg b/themes/pybcn_theme/assets/images/people/biel-stela-ballester.jpeg index cca6b222..67894098 100644 Binary files a/themes/pybcn_theme/assets/images/people/biel-stela-ballester.jpeg and b/themes/pybcn_theme/assets/images/people/biel-stela-ballester.jpeg differ diff --git a/themes/pybcn_theme/assets/images/people/camilo-chacon.jpg b/themes/pybcn_theme/assets/images/people/camilo-chacon.jpg index 9abfc746..bd17ef26 100644 Binary files a/themes/pybcn_theme/assets/images/people/camilo-chacon.jpg and b/themes/pybcn_theme/assets/images/people/camilo-chacon.jpg differ diff --git a/themes/pybcn_theme/assets/images/people/cheuk-ting-ho.jpg b/themes/pybcn_theme/assets/images/people/cheuk-ting-ho.jpg index e6a781cd..30d7fbf6 100644 Binary files a/themes/pybcn_theme/assets/images/people/cheuk-ting-ho.jpg and b/themes/pybcn_theme/assets/images/people/cheuk-ting-ho.jpg differ diff --git a/themes/pybcn_theme/assets/images/people/dani-mesejo.jpeg b/themes/pybcn_theme/assets/images/people/dani-mesejo.jpeg index b0c57d31..ab21e051 100644 Binary files a/themes/pybcn_theme/assets/images/people/dani-mesejo.jpeg and b/themes/pybcn_theme/assets/images/people/dani-mesejo.jpeg differ diff --git a/themes/pybcn_theme/assets/images/people/daniel_mesejo.jpg b/themes/pybcn_theme/assets/images/people/daniel_mesejo.jpg index 814991a8..24dc697a 100644 Binary files a/themes/pybcn_theme/assets/images/people/daniel_mesejo.jpg and b/themes/pybcn_theme/assets/images/people/daniel_mesejo.jpg differ diff --git a/themes/pybcn_theme/assets/images/people/david.jpeg b/themes/pybcn_theme/assets/images/people/david.jpeg index 88dd699a..d143c830 100644 Binary files a/themes/pybcn_theme/assets/images/people/david.jpeg and b/themes/pybcn_theme/assets/images/people/david.jpeg differ diff --git a/themes/pybcn_theme/assets/images/people/diego-giaquinta.png b/themes/pybcn_theme/assets/images/people/diego-giaquinta.png index 1ee86cc5..67b03990 100644 Binary files a/themes/pybcn_theme/assets/images/people/diego-giaquinta.png and b/themes/pybcn_theme/assets/images/people/diego-giaquinta.png differ diff --git a/themes/pybcn_theme/assets/images/people/diego-gonzalez.jpeg b/themes/pybcn_theme/assets/images/people/diego-gonzalez.jpeg index 79311b8f..94e3a764 100644 Binary files a/themes/pybcn_theme/assets/images/people/diego-gonzalez.jpeg and b/themes/pybcn_theme/assets/images/people/diego-gonzalez.jpeg differ diff --git a/themes/pybcn_theme/assets/images/people/edgar-riba.jpg b/themes/pybcn_theme/assets/images/people/edgar-riba.jpg index e7f8cce0..3972a212 100644 Binary files a/themes/pybcn_theme/assets/images/people/edgar-riba.jpg and b/themes/pybcn_theme/assets/images/people/edgar-riba.jpg differ diff --git a/themes/pybcn_theme/assets/images/people/elisabeth-ortega-carrasco.jpg b/themes/pybcn_theme/assets/images/people/elisabeth-ortega-carrasco.jpg index 5fa966b6..cc0896a0 100644 Binary files a/themes/pybcn_theme/assets/images/people/elisabeth-ortega-carrasco.jpg and b/themes/pybcn_theme/assets/images/people/elisabeth-ortega-carrasco.jpg differ diff --git a/themes/pybcn_theme/assets/images/people/elisabeth-ortega-carrasco.png b/themes/pybcn_theme/assets/images/people/elisabeth-ortega-carrasco.png index 6849c91f..22e4d396 100644 Binary files a/themes/pybcn_theme/assets/images/people/elisabeth-ortega-carrasco.png and b/themes/pybcn_theme/assets/images/people/elisabeth-ortega-carrasco.png differ diff --git a/themes/pybcn_theme/assets/images/people/eloi.jpg b/themes/pybcn_theme/assets/images/people/eloi.jpg index 259d1332..3fadad8f 100644 Binary files a/themes/pybcn_theme/assets/images/people/eloi.jpg and b/themes/pybcn_theme/assets/images/people/eloi.jpg differ diff --git a/themes/pybcn_theme/assets/images/people/esperanza-buitrago.jpeg b/themes/pybcn_theme/assets/images/people/esperanza-buitrago.jpeg index 3b1f5a56..fbfaeed0 100644 Binary files a/themes/pybcn_theme/assets/images/people/esperanza-buitrago.jpeg and b/themes/pybcn_theme/assets/images/people/esperanza-buitrago.jpeg differ diff --git a/themes/pybcn_theme/assets/images/people/ferran-jovell.jpg b/themes/pybcn_theme/assets/images/people/ferran-jovell.jpg index 0c995341..f3281c0b 100644 Binary files a/themes/pybcn_theme/assets/images/people/ferran-jovell.jpg and b/themes/pybcn_theme/assets/images/people/ferran-jovell.jpg differ diff --git a/themes/pybcn_theme/assets/images/people/ferran_fabregas.png b/themes/pybcn_theme/assets/images/people/ferran_fabregas.png index a7a55831..d21d2f45 100644 Binary files a/themes/pybcn_theme/assets/images/people/ferran_fabregas.png and b/themes/pybcn_theme/assets/images/people/ferran_fabregas.png differ diff --git a/themes/pybcn_theme/assets/images/people/fiorella-piriz-sapio.jpeg b/themes/pybcn_theme/assets/images/people/fiorella-piriz-sapio.jpeg index dfb8c1ae..d18fc8d0 100644 Binary files a/themes/pybcn_theme/assets/images/people/fiorella-piriz-sapio.jpeg and b/themes/pybcn_theme/assets/images/people/fiorella-piriz-sapio.jpeg differ diff --git a/themes/pybcn_theme/assets/images/people/francesco-faraone.png b/themes/pybcn_theme/assets/images/people/francesco-faraone.png index 78981e5e..ac54549f 100644 Binary files a/themes/pybcn_theme/assets/images/people/francesco-faraone.png and b/themes/pybcn_theme/assets/images/people/francesco-faraone.png differ diff --git a/themes/pybcn_theme/assets/images/people/gabriel-de-maeztu.jpg b/themes/pybcn_theme/assets/images/people/gabriel-de-maeztu.jpg index 15003930..5ace33f5 100644 Binary files a/themes/pybcn_theme/assets/images/people/gabriel-de-maeztu.jpg and b/themes/pybcn_theme/assets/images/people/gabriel-de-maeztu.jpg differ diff --git a/themes/pybcn_theme/assets/images/people/guillermo_abad.jpg b/themes/pybcn_theme/assets/images/people/guillermo_abad.jpg index d1cb9f14..38dbadcd 100644 Binary files a/themes/pybcn_theme/assets/images/people/guillermo_abad.jpg and b/themes/pybcn_theme/assets/images/people/guillermo_abad.jpg differ diff --git a/themes/pybcn_theme/assets/images/people/jimena-escobar.jpg b/themes/pybcn_theme/assets/images/people/jimena-escobar.jpg index 37bb690b..38e4bc3e 100644 Binary files a/themes/pybcn_theme/assets/images/people/jimena-escobar.jpg and b/themes/pybcn_theme/assets/images/people/jimena-escobar.jpg differ diff --git a/themes/pybcn_theme/assets/images/people/joan-fontanals-martinez.jpg b/themes/pybcn_theme/assets/images/people/joan-fontanals-martinez.jpg index 84f6cb8e..babbac67 100644 Binary files a/themes/pybcn_theme/assets/images/people/joan-fontanals-martinez.jpg and b/themes/pybcn_theme/assets/images/people/joan-fontanals-martinez.jpg differ diff --git a/themes/pybcn_theme/assets/images/people/jordi-mur.jpg b/themes/pybcn_theme/assets/images/people/jordi-mur.jpg index 517516d4..79defd20 100644 Binary files a/themes/pybcn_theme/assets/images/people/jordi-mur.jpg and b/themes/pybcn_theme/assets/images/people/jordi-mur.jpg differ diff --git a/themes/pybcn_theme/assets/images/people/jordi.jpg b/themes/pybcn_theme/assets/images/people/jordi.jpg index 38b60d4f..46fd303c 100644 Binary files a/themes/pybcn_theme/assets/images/people/jordi.jpg and b/themes/pybcn_theme/assets/images/people/jordi.jpg differ diff --git a/themes/pybcn_theme/assets/images/people/jose_haro.png b/themes/pybcn_theme/assets/images/people/jose_haro.png index dd245183..7cbfbcc4 100644 Binary files a/themes/pybcn_theme/assets/images/people/jose_haro.png and b/themes/pybcn_theme/assets/images/people/jose_haro.png differ diff --git a/themes/pybcn_theme/assets/images/people/josep.jpg b/themes/pybcn_theme/assets/images/people/josep.jpg index dd31fef4..96c5eda2 100644 Binary files a/themes/pybcn_theme/assets/images/people/josep.jpg and b/themes/pybcn_theme/assets/images/people/josep.jpg differ diff --git a/themes/pybcn_theme/assets/images/people/juan-bernardo-lince.jpeg b/themes/pybcn_theme/assets/images/people/juan-bernardo-lince.jpeg index e0d0a01f..0a3f83d9 100644 Binary files a/themes/pybcn_theme/assets/images/people/juan-bernardo-lince.jpeg and b/themes/pybcn_theme/assets/images/people/juan-bernardo-lince.jpeg differ diff --git a/themes/pybcn_theme/assets/images/people/juan-luis-cano.jpeg b/themes/pybcn_theme/assets/images/people/juan-luis-cano.jpeg index a1a8ae35..0750f524 100644 Binary files a/themes/pybcn_theme/assets/images/people/juan-luis-cano.jpeg and b/themes/pybcn_theme/assets/images/people/juan-luis-cano.jpeg differ diff --git a/themes/pybcn_theme/assets/images/people/julio-martinez.jpg b/themes/pybcn_theme/assets/images/people/julio-martinez.jpg index 1d25223d..069f035f 100644 Binary files a/themes/pybcn_theme/assets/images/people/julio-martinez.jpg and b/themes/pybcn_theme/assets/images/people/julio-martinez.jpg differ diff --git a/themes/pybcn_theme/assets/images/people/kevin-albes.jpg b/themes/pybcn_theme/assets/images/people/kevin-albes.jpg index 46156fbd..48b0e707 100644 Binary files a/themes/pybcn_theme/assets/images/people/kevin-albes.jpg and b/themes/pybcn_theme/assets/images/people/kevin-albes.jpg differ diff --git a/themes/pybcn_theme/assets/images/people/loris.jpg b/themes/pybcn_theme/assets/images/people/loris.jpg index 2feed09a..bdbfd288 100644 Binary files a/themes/pybcn_theme/assets/images/people/loris.jpg and b/themes/pybcn_theme/assets/images/people/loris.jpg differ diff --git a/themes/pybcn_theme/assets/images/people/lpmayos.jpeg b/themes/pybcn_theme/assets/images/people/lpmayos.jpeg index c7eba54d..f6e0b09a 100644 Binary files a/themes/pybcn_theme/assets/images/people/lpmayos.jpeg and b/themes/pybcn_theme/assets/images/people/lpmayos.jpeg differ diff --git a/themes/pybcn_theme/assets/images/people/manuel-gijon.jpg b/themes/pybcn_theme/assets/images/people/manuel-gijon.jpg index 6c654080..4640f408 100644 Binary files a/themes/pybcn_theme/assets/images/people/manuel-gijon.jpg and b/themes/pybcn_theme/assets/images/people/manuel-gijon.jpg differ diff --git a/themes/pybcn_theme/assets/images/people/marc-ramirez.jpeg b/themes/pybcn_theme/assets/images/people/marc-ramirez.jpeg index 22b51910..1dc90795 100644 Binary files a/themes/pybcn_theme/assets/images/people/marc-ramirez.jpeg and b/themes/pybcn_theme/assets/images/people/marc-ramirez.jpeg differ diff --git a/themes/pybcn_theme/assets/images/people/maria_teresa_grifa.jpg b/themes/pybcn_theme/assets/images/people/maria_teresa_grifa.jpg index d57bdd8a..749cdc96 100644 Binary files a/themes/pybcn_theme/assets/images/people/maria_teresa_grifa.jpg and b/themes/pybcn_theme/assets/images/people/maria_teresa_grifa.jpg differ diff --git a/themes/pybcn_theme/assets/images/people/marina-palma.jpg b/themes/pybcn_theme/assets/images/people/marina-palma.jpg index 957747a8..cab9ae48 100644 Binary files a/themes/pybcn_theme/assets/images/people/marina-palma.jpg and b/themes/pybcn_theme/assets/images/people/marina-palma.jpg differ diff --git a/themes/pybcn_theme/assets/images/people/miquel-sarrias.jpeg b/themes/pybcn_theme/assets/images/people/miquel-sarrias.jpeg index 3034a8e9..db832b5f 100644 Binary files a/themes/pybcn_theme/assets/images/people/miquel-sarrias.jpeg and b/themes/pybcn_theme/assets/images/people/miquel-sarrias.jpeg differ diff --git a/themes/pybcn_theme/assets/images/people/noe-casas.png b/themes/pybcn_theme/assets/images/people/noe-casas.png index c407cb6a..41df0c51 100644 Binary files a/themes/pybcn_theme/assets/images/people/noe-casas.png and b/themes/pybcn_theme/assets/images/people/noe-casas.png differ diff --git a/themes/pybcn_theme/assets/images/people/pavel-sulimov.png b/themes/pybcn_theme/assets/images/people/pavel-sulimov.png index c7e002e8..fc34b634 100644 Binary files a/themes/pybcn_theme/assets/images/people/pavel-sulimov.png and b/themes/pybcn_theme/assets/images/people/pavel-sulimov.png differ diff --git a/themes/pybcn_theme/assets/images/people/pere-miquel-brull.jpeg b/themes/pybcn_theme/assets/images/people/pere-miquel-brull.jpeg index cf4dc5fd..ebba5812 100644 Binary files a/themes/pybcn_theme/assets/images/people/pere-miquel-brull.jpeg and b/themes/pybcn_theme/assets/images/people/pere-miquel-brull.jpeg differ diff --git a/themes/pybcn_theme/assets/images/people/peter_krupa.jpeg b/themes/pybcn_theme/assets/images/people/peter_krupa.jpeg index 19342fd7..19f867d5 100644 Binary files a/themes/pybcn_theme/assets/images/people/peter_krupa.jpeg and b/themes/pybcn_theme/assets/images/people/peter_krupa.jpeg differ diff --git a/themes/pybcn_theme/assets/images/people/rajdeep-pal.jpeg b/themes/pybcn_theme/assets/images/people/rajdeep-pal.jpeg index fcd4fe4c..102467e2 100644 Binary files a/themes/pybcn_theme/assets/images/people/rajdeep-pal.jpeg and b/themes/pybcn_theme/assets/images/people/rajdeep-pal.jpeg differ diff --git a/themes/pybcn_theme/assets/images/people/ricardo-ander-egg.jpeg b/themes/pybcn_theme/assets/images/people/ricardo-ander-egg.jpeg index 13b9e1a2..15483cb2 100644 Binary files a/themes/pybcn_theme/assets/images/people/ricardo-ander-egg.jpeg and b/themes/pybcn_theme/assets/images/people/ricardo-ander-egg.jpeg differ diff --git a/themes/pybcn_theme/assets/images/people/ruben-afonso.png b/themes/pybcn_theme/assets/images/people/ruben-afonso.png index bde6133c..dcd6990f 100644 Binary files a/themes/pybcn_theme/assets/images/people/ruben-afonso.png and b/themes/pybcn_theme/assets/images/people/ruben-afonso.png differ diff --git a/themes/pybcn_theme/assets/images/people/selen-ozkan.jpg b/themes/pybcn_theme/assets/images/people/selen-ozkan.jpg index 72e239ff..3bd38e6c 100644 Binary files a/themes/pybcn_theme/assets/images/people/selen-ozkan.jpg and b/themes/pybcn_theme/assets/images/people/selen-ozkan.jpg differ diff --git a/themes/pybcn_theme/assets/images/people/sergi-ramirez.jpeg b/themes/pybcn_theme/assets/images/people/sergi-ramirez.jpeg index 4f470a2b..9180dafc 100644 Binary files a/themes/pybcn_theme/assets/images/people/sergi-ramirez.jpeg and b/themes/pybcn_theme/assets/images/people/sergi-ramirez.jpeg differ diff --git a/themes/pybcn_theme/assets/images/people/sonia-estrade.jpeg b/themes/pybcn_theme/assets/images/people/sonia-estrade.jpeg index 6607b371..e3cbc4b8 100644 Binary files a/themes/pybcn_theme/assets/images/people/sonia-estrade.jpeg and b/themes/pybcn_theme/assets/images/people/sonia-estrade.jpeg differ diff --git a/themes/pybcn_theme/assets/images/people/toni_espadas.jpg b/themes/pybcn_theme/assets/images/people/toni_espadas.jpg index 3249177a..14370afa 100644 Binary files a/themes/pybcn_theme/assets/images/people/toni_espadas.jpg and b/themes/pybcn_theme/assets/images/people/toni_espadas.jpg differ diff --git a/themes/pybcn_theme/assets/images/people/vincent-choubard.jpeg b/themes/pybcn_theme/assets/images/people/vincent-choubard.jpeg index 98de5ba0..1b4f9613 100644 Binary files a/themes/pybcn_theme/assets/images/people/vincent-choubard.jpeg and b/themes/pybcn_theme/assets/images/people/vincent-choubard.jpeg differ diff --git a/themes/pybcn_theme/assets/images/people/vivek-sharma.jpg b/themes/pybcn_theme/assets/images/people/vivek-sharma.jpg index 9920cd14..ab3f13a0 100644 Binary files a/themes/pybcn_theme/assets/images/people/vivek-sharma.jpg and b/themes/pybcn_theme/assets/images/people/vivek-sharma.jpg differ diff --git a/themes/pybcn_theme/assets/images/people/xiang-xu.jpg b/themes/pybcn_theme/assets/images/people/xiang-xu.jpg index 0567a7ac..9ba9f840 100644 Binary files a/themes/pybcn_theme/assets/images/people/xiang-xu.jpg and b/themes/pybcn_theme/assets/images/people/xiang-xu.jpg differ diff --git a/themes/pybcn_theme/assets/scss/main.scss b/themes/pybcn_theme/assets/scss/main.scss index 3de978d9..bfef0d21 100644 --- a/themes/pybcn_theme/assets/scss/main.scss +++ b/themes/pybcn_theme/assets/scss/main.scss @@ -8,6 +8,26 @@ $focus-ring: #0b0c0c; $primary: $blue-pybcn; +// Font Awesome 5.8.2 is older than the Twitter rename, so it carries no X +// mark. This draws the mark from the Font Awesome 6 Free path (CC BY 4.0) as +// a CSS mask, which keeps one vendored icon set instead of two. The mask takes +// its colour from `currentColor`, so the icon follows the same colour and +// hover rules as every Font Awesome glyph next to it. +.icon_x { + display: inline-block; + width: 1em; + height: 1em; + // Font Awesome drops its glyphs this far below the baseline. Matching it + // keeps the X level with the icons on each side. + vertical-align: -0.125em; + background-color: currentColor; + $x-mark: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E%3Cpath d='M389.2 48h70.6L305.6 224.2 487 464H345L233.7 318.6 106.5 464H35.8L196.2 280.2 22 48h148.1l108.6 143.5zm-24.8 373.8h39.1L151.1 88h-42z'/%3E%3C/svg%3E"); + -webkit-mask: $x-mark no-repeat center; + mask: $x-mark no-repeat center; + -webkit-mask-size: contain; + mask-size: contain; +} + .semitransparent { background-color: rgba(0, 0, 0, 0.5); padding: 10px; diff --git a/themes/pybcn_theme/assets/scss/people.scss b/themes/pybcn_theme/assets/scss/people.scss index f4db29cd..0dc69305 100644 --- a/themes/pybcn_theme/assets/scss/people.scss +++ b/themes/pybcn_theme/assets/scss/people.scss @@ -2,9 +2,45 @@ // people section, so there is no single id to target. .people_grid { + // One row of N cards. Bootstrap's twelve columns cannot express five, so + // the width is a fraction of the row instead. Below the medium breakpoint + // every card takes the full width, as the Bootstrap classes used to do. + // + // The count steps up with the viewport. A row of five on a 768 px screen + // gives each card 114 px, which is too narrow for the name and pushes the + // social icons onto a second line. Three fit at 768 px, four at 992 px, + // and the full count from 1200 px, where a card is about 200 px wide. + @for $n from 1 through 8 { + $md: if($n > 3, 3, $n); + $lg: if($n > 4, 4, $n); + + .per_line_#{$n} { + flex: 0 0 100%; + max-width: 100%; + + @media (min-width: 768px) { + flex: 0 0 percentage(1 / $md); + max-width: percentage(1 / $md); + } + + @media (min-width: 992px) { + flex: 0 0 percentage(1 / $lg); + max-width: percentage(1 / $lg); + } + + @media (min-width: 1200px) { + flex: 0 0 percentage(1 / $n); + max-width: percentage(1 / $n); + } + } + } + .person_wrapper { + display: flex; margin-bottom: 20px; + padding-right: 15px; + padding-left: 15px; .person_item { @@ -73,6 +109,22 @@ margin-top: auto; padding-top: 10px; + // Flex, not text-align: inline-block icons are separated by the + // whitespace between the tags, about 4 px each, which is enough + // to push the fourth icon of a 5-per-row card onto a second + // line. Flex drops those text nodes and leaves only the gap set + // here, so the spacing is the same whatever the markup does. + display: flex; + flex-wrap: wrap; + justify-content: center; + + // A small gap still widens the target each icon offers to a + // pointer, which the 2x size alone does not. + a { + display: block; + margin: 0 3px; + } + i { color: black; &:hover { @@ -81,6 +133,13 @@ } } + // The border follows the hover and the focus, so the card that + // the pointer or the keyboard is on is obvious at a glance. + &:hover, + &:focus-within { + border-color: $blue-pybcn; + } + // Keyboard focus shows the colour photo too, because the card holds // two focusable triggers. &:hover, diff --git a/themes/pybcn_theme/layouts/partials/footer.html b/themes/pybcn_theme/layouts/partials/footer.html index bb1cf966..69d4a513 100644 --- a/themes/pybcn_theme/layouts/partials/footer.html +++ b/themes/pybcn_theme/layouts/partials/footer.html @@ -11,7 +11,7 @@
diff --git a/themes/pybcn_theme/layouts/partials/people-grid.html b/themes/pybcn_theme/layouts/partials/people-grid.html index e3fe9235..5b3555dd 100644 --- a/themes/pybcn_theme/layouts/partials/people-grid.html +++ b/themes/pybcn_theme/layouts/partials/people-grid.html @@ -10,6 +10,11 @@ person can sit in more than one of them. The key goes into every id the grid emits, so the row ids and the modal ids stay unique on the page. Styles hook on the people_grid class, not on an id. + + The column width comes from a per_line_N class rather than from Bootstrap's + col-md-N. Twelve columns divide by 1, 2, 3, 4, 6 and 12, so people_per_line + was silently rounded for any other value: 5 gave col-md-2, which is six + cards to a row. */ -}} {{- $key := default "people" .key -}}