Skip to content

This page zooms: scrolling moves you closer, from a name into a sentence, into a project, into its code. Use the arrow keys to step between depths, or read the flat version at /all.

Your system asks for reduced motion, so this page is laid out flat.

Vättern Visingsö E4 40 26 47 32 Huskvarna Gränna Habo Mullsjö Bankeryd Tranås Aneby Tidaholm Taberg Tenhult Bottnaryd Skärstad Jönköping 0 10 20 km N 57°47′ N · 14°10′ E map data © OpenStreetMap

Ismail Safwat

I build software for the whole stack: web applications, the services behind them, the firmware inside industrial machines, and once, for the fun of it, a game engine.

I build software for the whole stack: web applications, the services behind them, the firmware inside industrial machines, and once, for the fun of it, a game engine.

this website · 2026 · personal

ismailsafwat.com — this website

This site: one page that zooms from a name down to a piece of code, built on Astro and served from Cloudflare's edge.

Astro · TypeScript · CSS · Cloudflare Pages · Cloudflare Workers

interface · application · services · data

Ismail Safwat 1× Ismail Safwat 4× Wexiödisk, 2023 → 20× Wexiödisk · 2023 → Mjukvaruingenjör a piece of the code ↓ 80× s = target.w / next.w k = 1 / s MAGNIFICATION AGAINST SCROLL · log 1× 10× 100× 1000× 0 1 2 3 4 · depth ≈ 330× out: translate((cx−tx)·p, (cy−ty)·p) scale(k^p) in: translate((tx−cx)·(1−p), …) scale(s^(1−p)) surroundings fade 0.03–0.28 · target 0.28–0.50 · next 0.44–0.66 prefers-reduced-motion → same document, flat 1 2 3 4 5
interface
application
services
data
firmware
a piece of the code ↓

Wexiödisk · 2023 → · work

Software Engineer at Wexiödisk

I write the control software and the Qt/QML touch interface inside Wexiödisk's industrial dishwashers, and the telemetry that reports every machine back to the cloud. Firmware that has to be right in a kitchen at noon, and screens that work with wet hands.

C++ · QML · Linux · Rhapsody · IoT · Embedded Systems

interface · application · services · data · firmware

GND POUR U1 ARM · 480 MHz LINUX · C++ Y1 · 25 MHz J1 · SENSORS T1 temp L1 level S1 door P1 press. K1 HEATER 16 A · 230 V K2 PUMP 8 A K3 DOOR LOCK M1 Wi‑Fi · BLE U2 PMIC F1 · 2 A D1 D2 D3 TP1–3 J3 · FPC 40p J2 RS‑485 ECO 65° 04:12 PROGRAM 2 · WASH WD‑TOUCH · 7" · QML MQTT · TLS 1.3 temp 64.8 · cycle 2 temp 64.5 · cycle 2 door closed · 04:12 120.00 84 A 1 2 3 4 5 6 7 8
interface
application
services
data
firmware
a piece of the code ↓

Blixtvakt · 2022 · work

Blixtvakt — lightning alerts on your phone

At Zenon AB I rebuilt Blixtvakt, an Ionic app that watches SMHI's lightning data and warns you when strikes come close, on a map drawn from OpenStreetMap, with alert settings you tune yourself.

Ionic · TypeScript · OpenStreetMap · SMHI API · Push notifications

interface · application · services

14:02 Vättern 25 km 3 strikes within 25 km RADIUS 25 km QUIET 23–06 · SOUND ON IONIC · TYPESCRIPT SMHI lightning strikes · API every 5 min lat · lon · kA · t OpenStreetMap map tiles · z/x/y cached on device the app poll → filter → notify poll dist push haversine against my position tiles strikes → app → phone ALERTS TODAY 11:40 13:15 13:52 14:02 quiet 23–06: no sound, banner only 10 km 1 2 3 4 5
interface
application
services
data
firmware
a piece of the code ↓

a distributed enterprise system · 2022 · university

Distributed Enterprise System

A university project where I built a system the enterprise way: several services, each owning its own data, talking over REST. Most of what I know about the boundaries between services I learned by getting them wrong here first.

SOA · Microservices · Service Architecture · Enterprise Patterns

application · services · data

SERVICE ZONE · PRIVATE NETWORK PUBLIC EDGE client browser · JSON gateway auth · JWT rate limit route · retry REST · HTTP/1.1 account :8081 ctrl svc repo accounts person :8082 ctrl svc repo persons bank :8083 ctrl svc repo banks ONE STORE PER SERVICE GET /persons/{id} ONE REQUEST, TIMELINE · req 7f3a client gateway account person t=0 · POST /transfer +4 ms · auth ok +11 ms · owner? +23 ms · 200 +31 ms · 201 created +33 ms timeout 800 ms · 2 retries 1 2 3 4 5 6
interface
application
services
data
firmware
a piece of the code ↓

Kundvagn · 2021 · university

Kundvagn - Shopping Cart Android App

A university project: an Android shopping-cart app in Kotlin. The part I cared about was sync: two phones, one cart, and Firestore keeping them honest in real time.

Kotlin · Android SDK · Firebase · Firestore · Mobile Development

interface · application · data

09:41 09:41 kundvagn uid 3f…A mjölk×2 · 34 kr bröd×1 · 42 kr kaffe×1 · 69 kr pay · 145 kr PHONE A · ADDS kundvagn uid 3f…B mjölk×2 · 34 kr bröd×1 · 42 kr kaffe×1 · 69 kr pay · 145 kr PHONE B · LISTENS Firestore europe‑west1 carts/ {uid}/ items/ k7f2 { name:"kaffe", qty:1 } a91c { name:"mjölk", qty:2 } c04e { name:"bröd", qty:1 } rules: auth.uid == uid index: items.addedAt ↑ offline: writes queued set() onSnapshot onSnapshot set() NO CONNECTION set() waits locally → replayed on reconnect ~200 ms write to visible change, Jönköping → europe‑west1 → Jönköping 1 2 3 4 5
interface
application
services
data
firmware
a piece of the code ↓

Shape Shooters · 2020 · university

Shape Shooters - C++ Game Engine

A university project: a multiplayer arcade shooter and the small engine under it, written from scratch in C++ with SFML. My first real lesson in game loops, timing, and why collision detection is never quite finished.

C++ · SFML · OOP · UML · Game Development

interface · application

SCORE 0420 LIVES 3 · WAVE 4 grid 80 px · 18 cells AABB 56² Ship · p(234,248) v(0,42) v(‑26,36) v(‑40,0) hit · +20 CLASSES Entity pos · vel · box Ship Enemy Bullet entities 14 collision tests 31 / 91 grid saves 66 % frame time · 60 Hz ONE FRAME · 16.7 ms input 0.4 · update 2.1 collide 1.3 render 6.8 idle 6.1 · vsync dt = clock.restart() · position += velocity · dt 1 2 3 4 5
interface
application
services
data
firmware
a piece of the code ↓
// Between depth i and i+1, p runs from 0 to 1.
// The outgoing layer grows around its target;
// the incoming layer shrinks onto that same target.
const s = target.width / incoming.width; // < 1
const k = 1 / s;

out.style.transform =
  `translate(${(cx - tx) * p}px, ${(cy - ty) * p}px) scale(${k ** p})`;

inc.style.transform =
  `translate(${(tx - cx) * (1 - p)}px, ${(ty - cy) * (1 - p)}px) scale(${s ** (1 - p)})`;
enum class State { Idle, Fill, Heat, Wash, Drain, Fault };

State next(State s, const Sensors& in) {
  switch (s) {
    case State::Idle:  return in.doorClosed && in.start ? State::Fill  : s;
    case State::Fill:  return in.level >= target.level  ? State::Heat  : s;
    case State::Heat:  return in.temp  >= target.temp   ? State::Wash  : s;
    case State::Wash:  return timer.expired()           ? State::Drain : s;
    case State::Drain: return in.level <= 0             ? State::Idle  : s;
    case State::Fault: return s;
  }
  return State::Fault;
}
const EARTH_KM = 6371;
function distanceKm(a: LatLng, b: LatLng) {
  const dLat = rad(b.lat - a.lat), dLon = rad(b.lon - a.lon);
  const h = Math.sin(dLat / 2) ** 2 + Math.cos(rad(a.lat)) * Math.cos(rad(b.lat)) * Math.sin(dLon / 2) ** 2;
  return 2 * EARTH_KM * Math.asin(Math.sqrt(h));
}

for (const strike of await smhi.latestStrikes()) {
  if (distanceKm(strike, here) <= settings.radiusKm && !settings.quietHoursNow()) {
    notify(`Blixt ${Math.round(distanceKm(strike, here))} km bort`);
  }
}
@GET
@Path("/accounts/{id}/balance")
public Response balance(@PathParam("id") String id) {
    Account account = accounts.find(id)
        .orElseThrow(NotFoundException::new);
    Person owner = personService.get(account.ownerId()); // another service, over REST
    return Response.ok(new BalanceDto(owner.name(), account.balance())).build();
}
cartRef.addSnapshotListener { snapshot, error ->
    if (error != null) return@addSnapshotListener
    val items = snapshot?.documents?.mapNotNull { it.toObject<CartItem>() }.orEmpty()
    _cart.value = items.sortedBy { it.addedAt }
}
while (window.isOpen()) {
    float dt = clock.restart().asSeconds();
    handleInput(window);
    for (auto& entity : entities) entity->update(dt, world);
    resolveCollisions(entities);
    window.clear();
    for (auto& entity : entities) entity->draw(window);
    window.display();
}
London Oslo Stockholm Helsingfors Köpenhamn Warszawa Amsterdam Frankfurt you ~ 12 ms the page sits here, and at every other point NO SERVER OF ITS OWN · ONE BUILD, COPIED TO THE EDGE
$ ./start-all gateway :8080 account :8081 person :8082 account-db :5432 person-db :5433 five processes · one machine · localhost the code runs here all five of it ONE LAPTOP · LINKÖPING UNIVERSITY · 2022
kundvagn mjölk ×2 bröd ×1 kaffe ×1 145 kr kundvagn mjölk ×2 bröd ×1 kaffe ×1 145 kr the same cart the code runs here twice A · mine B · borrowed A TABLE IN THE LAB · LINKÖPING UNIVERSITY · 2021