{"id":5476,"date":"2026-08-28T11:49:18","date_gmt":"2026-08-28T11:49:18","guid":{"rendered":"https:\/\/anasalassi.com\/projects\/meeting-room-booking\/"},"modified":"2026-08-28T11:49:18","modified_gmt":"2026-08-28T11:49:18","slug":"meeting-room-booking","status":"publish","type":"page","link":"https:\/\/anasalassi.com\/ar\/projects\/meeting-room-booking\/","title":{"rendered":"RoomFlow \u2014 \u0646\u0638\u0627\u0645 \u062d\u062c\u0632 \u063a\u0631\u0641 \u0627\u0644\u0627\u062c\u062a\u0645\u0627\u0639\u0627\u062a"},"content":{"rendered":"\t\t<div data-elementor-type=\"wp-page\" data-elementor-id=\"5476\" class=\"elementor elementor-5476\" data-elementor-post-type=\"page\">\n\t\t\t\t<div class=\"elementor-element elementor-element-7057b6f aa-cs-hero e-flex e-con-boxed e-con e-parent\" data-id=\"7057b6f\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-8783d8f elementor-widget elementor-widget-html\" data-id=\"8783d8f\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t\t<a class=\"aa-cs-back\" href=\"https:\/\/anasalassi.com\/projects\/\"><svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" aria-hidden=\"true\"><path d=\"M15 18l-6-6 6-6\"\/><\/svg>All projects<\/a>\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-eabfb93 elementor-widget elementor-widget-html\" data-id=\"eabfb93\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t\t<span class=\"aa-cs-status aa-cs-status--live\"><span class=\"aa-cs-status__dot\" aria-hidden=\"true\"><\/span>Live Demo<\/span>\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-60f4225 aa-cs-h1 elementor-widget elementor-widget-heading\" data-id=\"60f4225\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t\t<h1 class=\"elementor-heading-title elementor-size-default\">RoomFlow \u2014 Meeting Room Booking<\/h1>\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-0e570ee elementor-widget elementor-widget-text-editor\" data-id=\"0e570ee\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<p>RoomFlow simplifies meeting-room discovery, real-time availability, conflict-free booking, approvals, check-ins and utilisation management in one bilingual workspace.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-bc00502 elementor-widget elementor-widget-html\" data-id=\"bc00502\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t\t<dl class=\"aa-cs-facts\"><div class=\"aa-cs-fact\"><dt>Role<\/dt><dd>Product design and full-stack delivery<\/dd><\/div><div class=\"aa-cs-fact\"><dt>Category<\/dt><dd>Workplace scheduling<\/dd><\/div><div class=\"aa-cs-fact\"><dt>Status<\/dt><dd>Live public demo<\/dd><\/div><div class=\"aa-cs-fact\"><dt>Stack<\/dt><dd>PHP 8.3 &middot; MySQL 8 &middot; no framework<\/dd><\/div><\/dl>\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-bd3f713 aa-cs-cta-row e-con-full e-flex e-con e-parent\" data-id=\"bd3f713\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-8fd6d5c aa-el-btn aa-el-btn--primary elementor-widget elementor-widget-button\" data-id=\"8fd6d5c\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"button.default\">\n\t\t\t\t\t\t\t\t\t\t<a class=\"elementor-button elementor-button-link elementor-size-sm\" href=\"https:\/\/meeting.3dot.tech\" target=\"_blank\" rel=\"noopener noreferrer\" aria-label=\"Try Live Demo (opens in a new tab)\">\n\t\t\t\t\t\t<span class=\"elementor-button-content-wrapper\">\n\t\t\t\t\t\t\t\t\t<span class=\"elementor-button-text\">Try Live Demo<\/span>\n\t\t\t\t\t<\/span>\n\t\t\t\t\t<\/a>\n\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-a261682 aa-el-btn aa-el-btn--ghost elementor-widget elementor-widget-button\" data-id=\"a261682\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"button.default\">\n\t\t\t\t\t\t\t\t\t\t<a class=\"elementor-button elementor-button-link elementor-size-sm\" href=\"https:\/\/anasalassi.com\/#contact\">\n\t\t\t\t\t\t<span class=\"elementor-button-content-wrapper\">\n\t\t\t\t\t\t\t\t\t<span class=\"elementor-button-text\">Book a Call<\/span>\n\t\t\t\t\t<\/span>\n\t\t\t\t\t<\/a>\n\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-5cca1cd e-flex e-con-boxed e-con e-parent\" data-id=\"5cca1cd\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-8e427ac elementor-widget elementor-widget-html\" data-id=\"8e427ac\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t\t<figure class=\"aa-cs-shot\"><img loading=\"lazy\" src=\"https:\/\/anasalassi.com\/wp-content\/uploads\/2026\/09\/roomflow-dashboard-en.jpg\" alt=\"RoomFlow dashboard: a Check availability form with date, start time, duration and attendee count, above upcoming bookings and a list of rooms free right now\" width=\"1440\" height=\"743\" loading=\"lazy\" decoding=\"async\" class=\"aa-cs-shotimg\"><figcaption class=\"aa-cs-cap\">The dashboard opens on the one question most people arrive with: is there a room free?<\/figcaption><\/figure>\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-61390a5 e-flex e-con-boxed e-con e-parent\" data-id=\"61390a5\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-f63cac1 aa-cs-h2 elementor-widget elementor-widget-heading\" data-id=\"f63cac1\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t\t<h2 class=\"elementor-heading-title elementor-size-default\">Overview<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-f951ede elementor-widget elementor-widget-text-editor\" data-id=\"f951ede\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<p>RoomFlow is a bilingual meeting-room booking system. Someone who needs a room answers three questions \u2014 when, how many people, and what they need in the room \u2014 and gets a shortlist of rooms that actually fit, each one explaining why it was suggested. Behind that simple surface sit a day\/week\/agenda calendar, approvals, QR check-in, no-show release, a waitlist, maintenance windows and utilisation reporting for the people who run the space.<\/p><p>It runs as a public demo with fictional data. Every account, room, meeting and audit record you see in it was invented for the demo.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-f1861ed aa-cs-two e-con-full e-flex e-con e-parent\" data-id=\"f1861ed\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t<div class=\"elementor-element elementor-element-2df1ab0 e-flex e-con-boxed e-con e-parent\" data-id=\"2df1ab0\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-d04a7d9 aa-cs-h3 elementor-widget elementor-widget-heading\" data-id=\"d04a7d9\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t\t<h3 class=\"elementor-heading-title elementor-size-default\">The room-booking problem<\/h3>\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-3c042da elementor-widget elementor-widget-html\" data-id=\"3c042da\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t\t<ul class=\"aa-cs-list\"><li>Two people book the same room for the same hour and both believe it is theirs.<\/li><li>Finding a free room means opening every room in turn and reading its calendar.<\/li><li>A room is booked, nobody turns up, and it stays blocked for an hour.<\/li><li>The largest room gets taken for a two-person call because it was the first one free.<\/li><li>Nobody can say which rooms are actually used and which are not.<\/li><\/ul>\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-aee78ab e-flex e-con-boxed e-con e-parent\" data-id=\"aee78ab\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-270c88f aa-cs-h3 elementor-widget elementor-widget-heading\" data-id=\"270c88f\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t\t<h3 class=\"elementor-heading-title elementor-size-default\">What RoomFlow does about it<\/h3>\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-ff4bc79 elementor-widget elementor-widget-html\" data-id=\"ff4bc79\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t\t<ul class=\"aa-cs-list aa-cs-list--check\"><li>Overlaps are rejected by the database inside a transaction, not by JavaScript.<\/li><li>One form answers 'what is free' across every room at once.<\/li><li>A booking nobody checks into is released automatically and offered to the waitlist.<\/li><li>Rooms are ranked by how well they fit, and the reason is shown on the card.<\/li><li>Utilisation is reported from the bookings themselves.<\/li><\/ul>\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-b5c64ee e-flex e-con-boxed e-con e-parent\" data-id=\"b5c64ee\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-ad61c2f aa-cs-h2 elementor-widget elementor-widget-heading\" data-id=\"ad61c2f\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t\t<h2 class=\"elementor-heading-title elementor-size-default\">My role<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-2a2417b elementor-widget elementor-widget-text-editor\" data-id=\"2a2417b\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<p class='aa-cs-contrib'>Everything: product decisions, information architecture, interface and interaction design, the Arabic and English content, the PHP and MySQL implementation, the automated test suite, the server configuration, and the deployment.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-8fa6379 e-flex e-con-boxed e-con e-parent\" data-id=\"8fa6379\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-b86f3dd aa-cs-h2 elementor-widget elementor-widget-heading\" data-id=\"b86f3dd\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t\t<h2 class=\"elementor-heading-title elementor-size-default\">Product and UX decisions<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-0cc0a8d elementor-widget elementor-widget-text-editor\" data-id=\"0cc0a8d\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<p>The first build was complete and hard to use. Everything a room administrator might ever need sat on the same screens as the one thing an ordinary employee wants. These were the decisions that fixed it.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-32a4b87 elementor-widget elementor-widget-html\" data-id=\"32a4b87\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t\t<ul class=\"aa-cs-list aa-cs-list--check\"><li><strong>Three actions, not eleven.<\/strong> Find a room, Available now, My bookings sit at the top of the navigation. Every administrative tool moved below a labelled divider.<\/li><li><strong>The dashboard asks a question instead of reporting numbers.<\/strong> An employee sees a date, a start time, a duration and a headcount, and a button. Analytics moved below that.<\/li><li><strong>Optional inputs are collapsed.<\/strong> Equipment filters and step-free access live under 'More filters', which opens by itself when a filter is already applied.<\/li><li><strong>Defaults you can accept.<\/strong> The form opens on the next half hour rather than a fixed 09:00 that is usually already in the past. Nothing is created until the booking is confirmed.<\/li><li><strong>Durations are written the way people say them.<\/strong> '1 hour', not '60 minutes'.<\/li><li><strong>Plain words instead of system vocabulary.<\/strong> No record ids, no status codes, no database language anywhere a person reads.<\/li><\/ul>\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-6dde7d8 e-flex e-con-boxed e-con e-parent\" data-id=\"6dde7d8\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-53f30ea aa-cs-h2 elementor-widget elementor-widget-heading\" data-id=\"53f30ea\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t\t<h2 class=\"elementor-heading-title elementor-size-default\">The booking journey<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-e47b475 elementor-widget elementor-widget-text-editor\" data-id=\"e47b475\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<p>Five steps, each asking for only what it needs to ask for.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-47931f8 elementor-widget elementor-widget-html\" data-id=\"47931f8\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t\t<ol class=\"aa-cs-steps\"><li><strong>When<\/strong><span>Date, start time and a duration. Sensible defaults, nothing created yet.<\/span><\/li><li><strong>What you need<\/strong><span>How many people, and any equipment. Optional filters stay collapsed.<\/span><\/li><li><strong>Choose a room<\/strong><span>Rooms that fit, best match first, each explaining why, with capacity, location, floor, equipment, step-free access and how long it stays free.<\/span><\/li><li><strong>Meeting details<\/strong><span>A title, and optionally a purpose, notes or a repeat rule.<\/span><\/li><li><strong>Confirm<\/strong><span>One action. The booking is created inside a transaction, or refused with alternatives.<\/span><\/li><\/ol>\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-e1b5963 elementor-widget elementor-widget-html\" data-id=\"e1b5963\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t\t<figure class=\"aa-cs-shot\"><img loading=\"lazy\" src=\"https:\/\/anasalassi.com\/wp-content\/uploads\/2026\/09\/roomflow-find-a-room-en.jpg\" alt=\"Room results in RoomFlow: Cedar Room flagged Best match, showing type, capacity, wing, floor, step-free access, how long it is free, equipment tags and the reason it was suggested\" width=\"1440\" height=\"743\" loading=\"lazy\" decoding=\"async\" class=\"aa-cs-shotimg\"><figcaption class=\"aa-cs-cap\">Every suggestion states its reason. The boardroom shows its approval requirement as a caveat, not a selling point.<\/figcaption><\/figure>\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-4213011 e-flex e-con-boxed e-con e-parent\" data-id=\"4213011\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-7c6156a aa-cs-h2 elementor-widget elementor-widget-heading\" data-id=\"7c6156a\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t\t<h2 class=\"elementor-heading-title elementor-size-default\">The mobile navigation, and why it was invisible<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-e3a6ebc elementor-widget elementor-widget-text-editor\" data-id=\"e3a6ebc\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<p>On a phone the menu button was there, correctly sized and correctly labelled \u2014 and rendered as an empty box. Two separate faults, either of which alone would have hidden the menu.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-cfa5bde elementor-widget elementor-widget-html\" data-id=\"cfa5bde\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t\t<ul class=\"aa-cs-list\"><li><strong>The icon had no glyph.<\/strong> When the sidebar moved to an inline SVG sprite, the <code>.nav-ic<\/code> rule lost the solid square that had been standing in for an icon. The menu button still used the old markup \u2014 an empty <code>&lt;span&gt;<\/code> \u2014 and no <code>#ic-menu<\/code> symbol existed. A 44&times;44 button with a 1px border and nothing inside it.<\/li><li><strong>The drawer could not open in Arabic.<\/strong> <code>html[dir=\"rtl\"] .sidebar<\/code> scores 0,2,1 in CSS specificity; <code>.sidebar.is-open<\/code> scores 0,2,0. The closed state outranked the open state, so in Arabic \u2014 the default language \u2014 the panel stayed parked off the right edge even when the button reported it as expanded.<\/li><\/ul>\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-8040450 elementor-widget elementor-widget-text-editor\" data-id=\"8040450\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<p>Both are fixed, and the drawer was rebuilt as a proper dialog: it slides in from the edge the button sits on, traps focus while open, closes on Escape, on the backdrop and on its own close button, returns focus to the menu button every time, locks background scrolling, and leaves the tab order entirely when closed.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-8e8a478 elementor-widget elementor-widget-html\" data-id=\"8e8a478\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t\t<figure class=\"aa-cs-shot\"><img loading=\"lazy\" src=\"https:\/\/anasalassi.com\/wp-content\/uploads\/2026\/09\/roomflow-mobile-menu-ar.png\" alt=\"RoomFlow Arabic mobile navigation drawer open from the right, showing Find a room, Available now and My bookings above a labelled Manage group, with a close button and a dimmed backdrop\" width=\"780\" height=\"1480\" loading=\"lazy\" decoding=\"async\" class=\"aa-cs-shotimg\"><figcaption class=\"aa-cs-cap\">The Arabic drawer opens from the right, under the thumb that opened it.<\/figcaption><\/figure>\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-25e513a e-flex e-con-boxed e-con e-parent\" data-id=\"25e513a\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-b0e5700 aa-cs-h2 elementor-widget elementor-widget-heading\" data-id=\"b0e5700\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t\t<h2 class=\"elementor-heading-title elementor-size-default\">Knowing what is free<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-6b47acc elementor-widget elementor-widget-text-editor\" data-id=\"6b47acc\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<p>The availability board answers the question without a search. Free rooms sort first, and every room states its condition in words next to an icon \u2014 never colour alone, which is unreadable to a colour-blind visitor and to anyone outdoors.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-a51a56d elementor-widget elementor-widget-html\" data-id=\"a51a56d\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t\t<ul class=\"aa-cs-list aa-cs-list--check\"><li>Free all day, or free until a specific time<\/li><li>Occupied until a specific time<\/li><li>Starting soon, with the time it starts<\/li><li>Under maintenance<\/li><\/ul>\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-e220305 elementor-widget elementor-widget-text-editor\" data-id=\"e220305\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<p>One subtlety cost a bug: a &#8216;free until 09:00&#8217; label that was quoting tomorrow morning. Every &#8216;next booking&#8217; lookup is now bounded to the same day, so a time shown to a reader always belongs to the day they are looking at.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-4b7fe08 elementor-widget elementor-widget-html\" data-id=\"4b7fe08\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t\t<figure class=\"aa-cs-shot\"><img loading=\"lazy\" src=\"https:\/\/anasalassi.com\/wp-content\/uploads\/2026\/09\/roomflow-available-now-en.jpg\" alt=\"RoomFlow live availability board showing seven rooms, free ones first, each with a written status badge such as Free all day, and one room marked Maintenance\" width=\"1520\" height=\"784\" loading=\"lazy\" decoding=\"async\" class=\"aa-cs-shotimg\"><figcaption class=\"aa-cs-cap\">Status is written out, with an icon. Colour only reinforces it.<\/figcaption><\/figure>\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-556e515 e-flex e-con-boxed e-con e-parent\" data-id=\"556e515\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-d509871 aa-cs-h2 elementor-widget elementor-widget-heading\" data-id=\"d509871\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t\t<h2 class=\"elementor-heading-title elementor-size-default\">Seeing the whole schedule<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-32aa0bd elementor-widget elementor-widget-text-editor\" data-id=\"32aa0bd\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<p>Availability answers &ldquo;is anything free now&rdquo;. A calendar answers the other question: what does the day actually look like, and where are the gaps. Three presentations, chosen because they are genuinely different jobs rather than one layout at three widths.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-82e974a elementor-widget elementor-widget-html\" data-id=\"82e974a\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t\t<ul class=\"aa-cs-list aa-cs-list--check\"><li><strong>Day<\/strong> \u2014 one column per room, so you can compare rooms at a glance and see which is free at 14:00.<\/li><li><strong>Week<\/strong> \u2014 one column per day across a working week, for spotting the pattern rather than the moment.<\/li><li><strong>Agenda<\/strong> \u2014 a list grouped by day, with the free gaps written out when a single room is selected. This is the default on a phone.<\/li><\/ul>\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-1527559 elementor-widget elementor-widget-text-editor\" data-id=\"1527559\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<p>Blocks are positioned from their real start and end times, computed on the server, so the grid is correct before any JavaScript runs. Overlapping bookings split into lanes side by side \u2014 and the lane count is scoped to each cluster of overlaps, so a booking that clashes with nothing keeps the full column rather than shrinking because two others collided earlier that morning.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-9af7e0e elementor-widget elementor-widget-text-editor\" data-id=\"9af7e0e\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<p>Every state is distinguishable without relying on colour: confirmed, awaiting approval, completed, no-show, cancelled and maintenance each have their own treatment, a legend, and words in the accessible name. A red line marks the current time, and only ever on today.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-977b381 elementor-widget elementor-widget-html\" data-id=\"977b381\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t\t<figure class=\"aa-cs-shot\"><img loading=\"lazy\" src=\"https:\/\/anasalassi.com\/wp-content\/uploads\/2026\/09\/roomflow-calendar-week-en.jpg\" alt=\"RoomFlow week calendar: seven day columns, bookings positioned by time, today tinted with a red current-time line, maintenance shown as a hatched block and one booking marked awaiting approval\" width=\"1456\" height=\"829\" loading=\"lazy\" decoding=\"async\" class=\"aa-cs-shotimg\"><figcaption class=\"aa-cs-cap\">Week view. Today is tinted, the red line is the current time, and the hatched columns are maintenance.<\/figcaption><\/figure>\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-050dbdb elementor-widget elementor-widget-text-editor\" data-id=\"050dbdb\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<p><strong>Occupancy is shared; content is not.<\/strong> An employee can see that a room is taken from 13:00 to 14:00 \u2014 otherwise the calendar could not help them find a gap \u2014 but another person&rsquo;s meeting shows only as <em>Busy<\/em>, with no title, no organiser and no link. A coordinator sees the detail their role requires. The rule is enforced server-side, in the same place that governs opening a booking directly.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-3807aa8 aa-cs-two e-con-full e-flex e-con e-parent\" data-id=\"3807aa8\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-7c40cf5 elementor-widget elementor-widget-html\" data-id=\"7c40cf5\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t\t<figure class=\"aa-cs-shot\"><img loading=\"lazy\" src=\"https:\/\/anasalassi.com\/wp-content\/uploads\/2026\/09\/roomflow-calendar-day-ar.jpg\" alt=\"RoomFlow Arabic day calendar, right to left, with a column per room and the current-time indicator across all columns\" width=\"1456\" height=\"829\" loading=\"lazy\" decoding=\"async\" class=\"aa-cs-shotimg\"><figcaption class=\"aa-cs-cap\">Day view in Arabic. The grid mirrors; the time axis moves to the right.<\/figcaption><\/figure>\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-8b30a58 elementor-widget elementor-widget-html\" data-id=\"8b30a58\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t\t<figure class=\"aa-cs-shot\"><img loading=\"lazy\" src=\"https:\/\/anasalassi.com\/wp-content\/uploads\/2026\/09\/roomflow-calendar-agenda-mobile.png\" alt=\"RoomFlow mobile agenda listing bookings by day with times, room, organiser and status badges\" width=\"745\" height=\"1567\" loading=\"lazy\" decoding=\"async\" class=\"aa-cs-shotimg\"><figcaption class=\"aa-cs-cap\">On a phone the agenda replaces the grid \u2014 not the grid squeezed.<\/figcaption><\/figure>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-92f276d elementor-widget elementor-widget-text-editor\" data-id=\"92f276d\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<p>Every block is a real link with an accessible name that carries the title, the room, the status and both times \u2014 because a coloured bar at a position means nothing to a screen reader. Blocks a viewer may not open are not links at all.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-bc22a21 e-flex e-con-boxed e-con e-parent\" data-id=\"bc22a21\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-a47c3ca aa-cs-h2 elementor-widget elementor-widget-heading\" data-id=\"a47c3ca\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t\t<h2 class=\"elementor-heading-title elementor-size-default\">Room information<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-194dc69 elementor-widget elementor-widget-text-editor\" data-id=\"194dc69\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<p>Rooms follow one naming system so a reader can guess what a room is for before opening it: purpose-named at the small and large ends, neutral names in between. Each room carries an explicit type alongside its code.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-21055a3 elementor-widget elementor-widget-html\" data-id=\"21055a3\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t\t<div class=\"aa-cs-tablewrap\"><table class=\"aa-cs-table\"><thead><tr><th>Room<\/th><th>Arabic<\/th><th>Type<\/th><th>Seats<\/th><\/tr><\/thead><tbody><tr><td>Focus Room<\/td><td dir=\"rtl\">\u0642\u0627\u0639\u0629 \u0627\u0644\u062a\u0631\u0643\u064a\u0632<\/td><td>Focus room<\/td><td>2<\/td><\/tr><tr><td>Jasmine Room<\/td><td dir=\"rtl\">\u0642\u0627\u0639\u0629 \u0627\u0644\u064a\u0627\u0633\u0645\u064a\u0646<\/td><td>Meeting room<\/td><td>4<\/td><\/tr><tr><td>Cedar Room<\/td><td dir=\"rtl\">\u0642\u0627\u0639\u0629 \u0627\u0644\u0623\u0631\u0632<\/td><td>Meeting room<\/td><td>6<\/td><\/tr><tr><td>Collaboration Room<\/td><td dir=\"rtl\">\u0642\u0627\u0639\u0629 \u0627\u0644\u062a\u0639\u0627\u0648\u0646<\/td><td>Collaboration room<\/td><td>6<\/td><\/tr><tr><td>Olive Room<\/td><td dir=\"rtl\">\u0642\u0627\u0639\u0629 \u0627\u0644\u0632\u064a\u062a\u0648\u0646<\/td><td>Meeting room<\/td><td>8<\/td><\/tr><tr><td>Horizon Room<\/td><td dir=\"rtl\">\u0642\u0627\u0639\u0629 \u0627\u0644\u0623\u0641\u0642<\/td><td>Collaboration room<\/td><td>12<\/td><\/tr><tr><td>Executive Boardroom<\/td><td dir=\"rtl\">\u0642\u0627\u0639\u0629 \u0627\u0644\u0627\u062c\u062a\u0645\u0627\u0639\u0627\u062a \u0627\u0644\u062a\u0646\u0641\u064a\u0630\u064a\u0629<\/td><td>Boardroom<\/td><td>20<\/td><\/tr><\/tbody><\/table><\/div>\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-66a1921 elementor-widget elementor-widget-text-editor\" data-id=\"66a1921\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<p>A result card shows name, code, type, capacity, wing, floor, step-free access, how long the room stays free, and its equipment. Anything beyond that lives on the room page rather than crowding the card. All seven rooms are fictional.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-5b1e2fa e-flex e-con-boxed e-con e-parent\" data-id=\"5b1e2fa\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-e666099 aa-cs-h2 elementor-widget elementor-widget-heading\" data-id=\"e666099\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t\t<h2 class=\"elementor-heading-title elementor-size-default\">Conflict prevention<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-84d1855 elementor-widget elementor-widget-text-editor\" data-id=\"84d1855\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<p>This is the part that has to be right. Two people pressing Confirm at the same moment must not both get the room, and no amount of client-side validation can guarantee that.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-85549ac elementor-widget elementor-widget-text-editor\" data-id=\"85549ac\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<p>Every booking is created inside a database transaction. The candidate rows are locked with <code>SELECT \u2026 FOR UPDATE<\/code> before the overlap test runs, so a second request queues behind the first rather than reading a stale answer. The overlap test itself is buffer-aware: a room with a 15-minute turnaround rejects a booking that starts 10 minutes after the previous one ends.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-2c2d28b elementor-widget elementor-widget-html\" data-id=\"2c2d28b\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t\t<pre class=\"aa-cs-code\"><code>newStart &lt; existingEnd + buffer\nnewEnd   &gt; existingStart - buffer<\/code><\/pre>\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-9554205 elementor-widget elementor-widget-text-editor\" data-id=\"9554205\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<p>It is verified by racing twelve real operating-system processes at the same slot, five times over. Exactly one wins each round and the database ends with exactly one row \u2014 60 concurrent attempts, zero double bookings. To prove the test is not vacuous, the same harness was pointed at a naive check-then-insert with no row lock: it produced twelve overlapping bookings out of twelve.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-f949218 e-flex e-con-boxed e-con e-parent\" data-id=\"f949218\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-d2afb66 aa-cs-h2 elementor-widget elementor-widget-heading\" data-id=\"d2afb66\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t\t<h2 class=\"elementor-heading-title elementor-size-default\">How rooms are ranked<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-2a8ccdd elementor-widget elementor-widget-text-editor\" data-id=\"2a8ccdd\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<p>There is no machine learning here and no claim of any. The ranking is a small, deterministic score that a person can read and predict:<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-7d6116f elementor-widget elementor-widget-html\" data-id=\"7d6116f\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t\t<ul class=\"aa-cs-list\"><li>Capacity efficiency \u2014 a room that fits the party with no more than two spare seats scores best, and a room far larger than needed is pushed down.<\/li><li>Every requested piece of equipment present.<\/li><li>Step-free access when it was asked for.<\/li><li>Video conferencing as a mild preference.<\/li><li>An approval requirement counts against a room, because it delays confirmation.<\/li><\/ul>\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-d0a932b elementor-widget elementor-widget-text-editor\" data-id=\"d0a932b\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<p>The reasons shown on each card are the same values the score was computed from, so the explanation cannot drift from the ranking. Reasons and caveats are separated deliberately: an approval requirement is a cost, and listing it under a heading that reads &#8216;Why this room&#8217; would sell a drawback as a benefit.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-56e0d10 elementor-widget elementor-widget-text-editor\" data-id=\"56e0d10\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<p>When nothing fits, the same logic produces alternatives \u2014 the next free slot in each suitable room \u2014 rather than an empty result.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-9e5df60 aa-cs-roles e-con-full e-flex e-con e-parent\" data-id=\"9e5df60\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-fa7b260 aa-cs-h2 elementor-widget elementor-widget-heading\" data-id=\"fa7b260\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t\t<h2 class=\"elementor-heading-title elementor-size-default\">Roles and permissions<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-c488c3a elementor-widget elementor-widget-text-editor\" data-id=\"c488c3a\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<p>Three roles, each with a different first screen. Every route, every controller action and every direct object URL is checked on the server; hiding a link is presentation, not protection.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-c66be8d e-flex e-con-boxed e-con e-parent\" data-id=\"c66be8d\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-d75d233 elementor-widget elementor-widget-html\" data-id=\"d75d233\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t\t<h3 class=\"aa-role__h\">Employee<\/h3><p class=\"aa-role__p\">Find a room, book it, see and cancel their own bookings, check in by QR. Cannot open anyone else's booking, by URL or otherwise.<\/p>\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-0be9f3a e-flex e-con-boxed e-con e-parent\" data-id=\"0be9f3a\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-945b548 elementor-widget elementor-widget-html\" data-id=\"945b548\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t\t<h3 class=\"aa-role__h\">Coordinator<\/h3><p class=\"aa-role__p\">Everything an employee can do, plus today's schedule across all rooms, check-ins due, no-shows, approvals and maintenance windows.<\/p>\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-f2376fd e-flex e-con-boxed e-con e-parent\" data-id=\"f2376fd\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-d75a35d elementor-widget elementor-widget-html\" data-id=\"d75a35d\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t\t<h3 class=\"aa-role__h\">Administrator<\/h3><p class=\"aa-role__p\">Everything a coordinator can do, plus users and roles, utilisation reporting and the audit trail.<\/p>\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-ae4b981 e-flex e-con-boxed e-con e-parent\" data-id=\"ae4b981\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-ca84b3b elementor-widget elementor-widget-text-editor\" data-id=\"ca84b3b\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<p>Approving a booking is a coordinator duty, not a user-administration privilege. That distinction was wrong in an earlier build \u2014 coordinators could manage maintenance but not approve anything, which is the one thing the role exists for \u2014 and the automated role tests now assert it.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-fcafc83 e-flex e-con-boxed e-con e-parent\" data-id=\"fcafc83\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-86c4e6c aa-cs-h2 elementor-widget elementor-widget-heading\" data-id=\"86c4e6c\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t\t<h2 class=\"elementor-heading-title elementor-size-default\">Check-in and no-shows<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-5aa68a7 elementor-widget elementor-widget-text-editor\" data-id=\"5aa68a7\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<p>Each booking carries a 128-bit random check-in token, generated independently of the booking id so it cannot be guessed or walked. The QR code is rendered by a Reed\u2013Solomon encoder written for this project rather than pulled from a third-party service, so no booking data leaves the server to have a code drawn.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-98c9d9e elementor-widget elementor-widget-html\" data-id=\"98c9d9e\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t\t<ul class=\"aa-cs-list aa-cs-list--check\"><li>Check-in opens 15 minutes before the meeting and closes 15 minutes after it starts.<\/li><li>Scanning outside that window is refused with the reason, not a generic error.<\/li><li>A booking nobody checks into is released automatically and stops holding the room.<\/li><li>The freed slot is offered to the longest-waiting person on the waitlist who fits, and the offer expires if it is not taken.<\/li><li>A booking that was attended is closed out when it ends, so utilisation separates 'used' from 'booked'.<\/li><\/ul>\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-caa96a0 e-flex e-con-boxed e-con e-parent\" data-id=\"caa96a0\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-0bfc512 aa-cs-h2 elementor-widget elementor-widget-heading\" data-id=\"0bfc512\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t\t<h2 class=\"elementor-heading-title elementor-size-default\">Keeping a public demo safe<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-f0036c4 elementor-widget elementor-widget-text-editor\" data-id=\"f0036c4\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<p>The demo is a real, fully interactive instance. Anyone can sign in and book a room. The guards are about protecting the shared dataset, not about disabling the product.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-474acab elementor-widget elementor-widget-html\" data-id=\"474acab\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t\t<ul class=\"aa-cs-list aa-cs-list--check\"><li>Booking, cancelling your own booking, joining the waitlist and checking in all genuinely work \u2014 they are the journey the demo exists to show.<\/li><li>Cancelling someone else's booking, cancelling a whole series, toggling users and editing rooms are blocked, and each blocked attempt is written to the audit trail as <code>demo_blocked<\/code>.<\/li><li>The three demo accounts are listed on the sign-in page with a click-to-fill button. It fills the form and stops; nothing is submitted for the visitor, and the password field stays a password field.<\/li><li>A nightly job restores the dataset. It runs under its own low-privilege MySQL account that can only read and write this one schema \u2014 verified unable to DROP, to reach another database, to create a database, or to grant itself anything \u2014 and it holds a lock so two resets cannot interleave.<\/li><li>The application's own database credentials are never read by that job.<\/li><\/ul>\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-44d3ef4 e-flex e-con-boxed e-con e-parent\" data-id=\"44d3ef4\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-f5405f6 aa-cs-h2 elementor-widget elementor-widget-heading\" data-id=\"f5405f6\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t\t<h2 class=\"elementor-heading-title elementor-size-default\">Arabic and English<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-f2d411c elementor-widget elementor-widget-text-editor\" data-id=\"f2d411c\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<p>Arabic is the default and is a first-class language, not a translation layer. The interface mirrors with CSS logical properties, so the drawer, the navigation, the card layouts and the form grids all flip without a second stylesheet.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-d6bd927 elementor-widget elementor-widget-html\" data-id=\"d6bd927\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t\t<ul class=\"aa-cs-list aa-cs-list--check\"><li>Arabic-Indic numerals throughout the Arabic interface.<\/li><li>Arabic month names, assembled from parts rather than pushed through a format string \u2014 an earlier version leaked a placeholder token into every date.<\/li><li>Real Arabic plurals, including the dual: <span dir=\"rtl\">\u063a\u0631\u0641\u0629 \u0648\u0627\u062d\u062f\u0629 \u0645\u062a\u0627\u062d\u0629<\/span> for one, <span dir=\"rtl\">\u063a\u0631\u0641\u062a\u0627\u0646 \u0645\u062a\u0627\u062d\u062a\u0627\u0646<\/span> for two, and a different form again above ten. A single ':n rooms' string gets three of those four wrong.<\/li><li>An en dash separates inline facts in Arabic instead of a middle dot, because Arabic-Indic zero (<span dir=\"rtl\">\u0660<\/span>) is itself a raised dot and the two are indistinguishable at reading size.<\/li><li>Both language files are asserted to have matching keys and complete plural families on every test run.<\/li><\/ul>\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-9b73f02 aa-cs-two e-con-full e-flex e-con e-parent\" data-id=\"9b73f02\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-904f583 elementor-widget elementor-widget-html\" data-id=\"904f583\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t\t<figure class=\"aa-cs-shot\"><img loading=\"lazy\" src=\"https:\/\/anasalassi.com\/wp-content\/uploads\/2026\/09\/roomflow-mobile-dashboard-ar.png\" alt=\"RoomFlow Arabic mobile dashboard, right-to-left, with the menu button top right and the availability form in a single column\" width=\"780\" height=\"1480\" loading=\"lazy\" decoding=\"async\" class=\"aa-cs-shotimg\"><figcaption class=\"aa-cs-cap\">Arabic, RTL.<\/figcaption><\/figure>\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-3060d5e elementor-widget elementor-widget-html\" data-id=\"3060d5e\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t\t<figure class=\"aa-cs-shot\"><img loading=\"lazy\" src=\"https:\/\/anasalassi.com\/wp-content\/uploads\/2026\/09\/roomflow-mobile-dashboard-en.png\" alt=\"RoomFlow English mobile dashboard, left-to-right, with the menu button top left and the same availability form\" width=\"780\" height=\"1480\" loading=\"lazy\" decoding=\"async\" class=\"aa-cs-shotimg\"><figcaption class=\"aa-cs-cap\">English, LTR. Same layout, mirrored.<\/figcaption><\/figure>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-d8f8583 e-flex e-con-boxed e-con e-parent\" data-id=\"d8f8583\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-fb62a67 aa-cs-h2 elementor-widget elementor-widget-heading\" data-id=\"fb62a67\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t\t<h2 class=\"elementor-heading-title elementor-size-default\">Accessibility<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-fb2319f elementor-widget elementor-widget-html\" data-id=\"fb2319f\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t\t<ul class=\"aa-cs-list aa-cs-list--check\"><li>Every interactive control meets 44&times;44 CSS pixels on a coarse pointer or a viewport up to 900px; denser sizes are used only where a mouse is driving.<\/li><li>A single visible focus ring on everything focusable, using <code>:focus-visible<\/code> so a mouse click does not leave a ring behind.<\/li><li>The mobile drawer is a real dialog: focus moves in, is trapped while open, and returns to the menu button on every close path.<\/li><li>The closed drawer leaves the tab order entirely, rather than sitting off-screen still focusable.<\/li><li>Status is always words plus an icon, never colour alone.<\/li><li>A skip link, landmark regions, <code>aria-current<\/code> on the active navigation item, and labelled form controls throughout.<\/li><li><code>prefers-reduced-motion<\/code> is honoured \u2014 the drawer, the chart bars and the button transitions all stop.<\/li><li>Verified at 320, 360, 390, 430, 768, 1024, 1440 and 1920 pixels in both languages: no horizontal overflow and no undersized target anywhere.<\/li><\/ul>\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-7caaafc e-flex e-con-boxed e-con e-parent\" data-id=\"7caaafc\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-a59bcc4 aa-cs-h2 elementor-widget elementor-widget-heading\" data-id=\"a59bcc4\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t\t<h2 class=\"elementor-heading-title elementor-size-default\">Security<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-2e464fa elementor-widget elementor-widget-html\" data-id=\"2e464fa\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t\t<ul class=\"aa-cs-list aa-cs-list--check\"><li>Every statement is a prepared statement; no string-built SQL anywhere.<\/li><li>Output is escaped at the point of rendering, and helper functions that return markup are the exception rather than the rule.<\/li><li>CSRF tokens on every state-changing request, verified server-side.<\/li><li>Session id regenerated on login; cookies <code>HttpOnly<\/code>, <code>Secure<\/code> and <code>SameSite=Lax<\/code>.<\/li><li>Login throttling per email address and per IP, with a lockout that expires rather than sticking.<\/li><li>Authorisation checked on every route, controller action and direct object URL \u2014 opening another person's booking, or their calendar file, returns 403.<\/li><li>Content-Security-Policy, HSTS, X-Content-Type-Options, X-Frame-Options, Referrer-Policy and Permissions-Policy, each sent exactly once.<\/li><li>No server version disclosed, and no <code>X-Powered-By<\/code>.<\/li><li>Nothing outside the public directory is reachable over HTTP.<\/li><li>The service worker caches the static shell only \u2014 never an authenticated page, never a POST, never a per-booking token.<\/li><\/ul>\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-57d3cb4 e-flex e-con-boxed e-con e-parent\" data-id=\"57d3cb4\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-b356cc0 aa-cs-h2 elementor-widget elementor-widget-heading\" data-id=\"b356cc0\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t\t<h2 class=\"elementor-heading-title elementor-size-default\">Technical architecture<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-456484c elementor-widget elementor-widget-text-editor\" data-id=\"456484c\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<p>PHP 8.3 and MySQL 8 with no framework. A front controller with a regex route table, PSR-style autoloading, thin controllers, and the booking rules in one place rather than scattered across views.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-9c711e8 elementor-widget elementor-widget-html\" data-id=\"9c711e8\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t\t<ul class=\"aa-cs-list aa-cs-list--check\"><li><strong>14 tables, 10 foreign keys.<\/strong> Rooms, locations, equipment, bookings, series, attendees, waitlist, maintenance windows, notifications, audit log, login attempts, settings, users.<\/li><li><strong>One booking engine.<\/strong> Overlap detection, buffers, policy validation, recurrence expansion, ranking and alternative-slot search all live in a single class the controllers call.<\/li><li><strong>Timezone pinned per connection.<\/strong> The MySQL session offset is set to the application timezone on connect, so <code>NOW()<\/code> and PHP's <code>date()<\/code> cannot disagree \u2014 the class of bug that silently breaks conflict windows and lockout expiry.<\/li><li><strong>Recurrence bounded by occurrences.<\/strong> Capping the day-walk instead lets a daily rule produce 400 bookings while limiting a weekly one to about 57.<\/li><li><strong>Assets versioned by file modification time,<\/strong> so a CSS fix cannot be invisible behind a cache for an hour.<\/li><li><strong>RFC 5545 calendar export<\/strong> written directly, with correct octet-based line folding and escaping that a hostile meeting title cannot break out of.<\/li><\/ul>\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-7c512e3 e-flex e-con-boxed e-con e-parent\" data-id=\"7c512e3\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-9c2ebbe aa-cs-h2 elementor-widget elementor-widget-heading\" data-id=\"9c2ebbe\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t\t<h2 class=\"elementor-heading-title elementor-size-default\">Deployment<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-731f4a6 elementor-widget elementor-widget-html\" data-id=\"731f4a6\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t\t<ul class=\"aa-cs-list aa-cs-list--check\"><li>Its own directory, system user and PHP-FPM pool, with <code>open_basedir<\/code> confining it.<\/li><li>Its own MySQL database and application account, holding only SELECT, INSERT, UPDATE and DELETE on that one schema \u2014 cross-database access verified denied.<\/li><li>A separate, even more limited account for the nightly demo reset.<\/li><li>nginx virtual host with HTTPS, HTTP permanently redirected, and static-asset caching.<\/li><li>Configuration validated before every reload; the co-hosted applications on the same server were checked before and after each change and were unaffected.<\/li><li>Log rotation, a nightly dataset reset, and a five-minute housekeeping job that releases no-shows and expires stale waitlist offers.<\/li><\/ul>\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-9cd6400 e-flex e-con-boxed e-con e-parent\" data-id=\"9cd6400\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-a0aacae aa-cs-h2 elementor-widget elementor-widget-heading\" data-id=\"a0aacae\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t\t<h2 class=\"elementor-heading-title elementor-size-default\">Testing<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-461d448 elementor-widget elementor-widget-text-editor\" data-id=\"461d448\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<p>Six suites run against the deployed application, not a mock of it.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-7b96990 elementor-widget elementor-widget-html\" data-id=\"7b96990\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t\t<div class=\"aa-cs-tablewrap\"><table class=\"aa-cs-table\"><thead><tr><th>Suite<\/th><th>What it covers<\/th><th>Assertions<\/th><\/tr><\/thead><tbody><tr><td>Unit and integration<\/td><td>Overlap and buffer logic, policy validation, approvals, maintenance, recurrence, ranking, alternatives, ICS, QR, bilingual coverage and plurals, timezone agreement, housekeeping<\/td><td>143<\/td><\/tr><tr><td>Concurrency<\/td><td>12 real processes racing one slot, 5 rounds<\/td><td>60 attempts, 0 double bookings<\/td><\/tr><tr><td>Live HTTP<\/td><td>Authentication, role enforcement, CSRF, direct object access, security headers, transport, demo guards, navigation contract, availability wording<\/td><td>124<\/td><\/tr><tr><td>Calendar<\/td><td>Three views, block positioning, overlap lanes, filters, states, current-time indicator, empty states, keyboard names, role-based privacy, escaping, bilingual<\/td><td>56<\/td><\/tr><tr><td>End-to-end journey<\/td><td>Sign in, search, select, confirm, view, conflict, export, QR check-in, cancel<\/td><td>23<\/td><\/tr><tr><td>Login throttling<\/td><td>Lockout, expiry, isolation between accounts<\/td><td>5<\/td><\/tr><\/tbody><\/table><\/div>\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-7e9a68b elementor-widget elementor-widget-text-editor\" data-id=\"7e9a68b\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<p>Two of those tests exist because they caught me. The concurrency harness was checked against a deliberately unlocked implementation to prove it could detect the failure it claims to prevent. And an assertion that every state-changing action was demo-guarded was passing vacuously \u2014 it split methods on <code>public function<\/code> while the controllers declare <code>public static function<\/code>, so it never parsed a single method. Rewritten against the route table, it immediately found a real gap.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-2395bcd e-flex e-con-boxed e-con e-parent\" data-id=\"2395bcd\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-2e61583 aa-cs-h2 elementor-widget elementor-widget-heading\" data-id=\"2e61583\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t\t<h2 class=\"elementor-heading-title elementor-size-default\">What was measured<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-e695357 elementor-widget elementor-widget-text-editor\" data-id=\"e695357\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<p>These are figures from the build itself. There are no user numbers, adoption figures or savings claims here, because RoomFlow is a portfolio demo and has none.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-82c5c1c elementor-widget elementor-widget-html\" data-id=\"82c5c1c\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t\t<ul class=\"aa-cs-metrics\"><li><strong>351<\/strong><span>automated assertions passing<\/span><\/li><li><strong>60<\/strong><span>concurrent booking attempts, zero double bookings<\/span><\/li><li><strong>236<\/strong><span>page &times; width checks with no horizontal overflow<\/span><\/li><li><strong>0<\/strong><span>undersized touch targets at mobile and tablet widths<\/span><\/li><li><strong>8<\/strong><span>viewport widths verified in both languages<\/span><\/li><li><strong>14<\/strong><span>tables, 10 foreign keys<\/span><\/li><\/ul>\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-d26c55e e-flex e-con-boxed e-con e-parent\" data-id=\"d26c55e\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-da28652 aa-cs-h2 elementor-widget elementor-widget-heading\" data-id=\"da28652\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t\t<h2 class=\"elementor-heading-title elementor-size-default\">Technology<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-b59f274 elementor-widget elementor-widget-html\" data-id=\"b59f274\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t\t<ul class=\"aa-cs-chips\"><li>PHP 8.3<\/li><li>MySQL 8<\/li><li>nginx<\/li><li>PHP-FPM<\/li><li>No framework<\/li><li>Vanilla JavaScript<\/li><li>CSS logical properties<\/li><li>Progressive Web App<\/li><li>Service worker<\/li><li>RFC 5545 iCalendar<\/li><li>Reed\u2013Solomon QR encoder<\/li><li>Let&rsquo;s Encrypt<\/li><li>Bilingual AR \/ EN<\/li><\/ul>\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-4b4e179 aa-cs-final e-flex e-con-boxed e-con e-parent\" data-id=\"4b4e179\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-9970d3c aa-cs-h2 elementor-widget elementor-widget-heading\" data-id=\"9970d3c\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t\t<h2 class=\"elementor-heading-title elementor-size-default\">Try it<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-0927c8b elementor-widget elementor-widget-text-editor\" data-id=\"0927c8b\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<p>The demo is live with fictional data. Sign in as an employee, a coordinator or an administrator from the buttons on the sign-in page \u2014 each role opens on a different first screen.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-bf41f39 aa-cs-cta-row e-con-full e-flex e-con e-parent\" data-id=\"bf41f39\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-caaeea6 aa-el-btn aa-el-btn--primary elementor-widget elementor-widget-button\" data-id=\"caaeea6\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"button.default\">\n\t\t\t\t\t\t\t\t\t\t<a class=\"elementor-button elementor-button-link elementor-size-sm\" href=\"https:\/\/meeting.3dot.tech\" target=\"_blank\" rel=\"noopener noreferrer\" aria-label=\"Try Live Demo (opens in a new tab)\">\n\t\t\t\t\t\t<span class=\"elementor-button-content-wrapper\">\n\t\t\t\t\t\t\t\t\t<span class=\"elementor-button-text\">Try Live Demo<\/span>\n\t\t\t\t\t<\/span>\n\t\t\t\t\t<\/a>\n\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-33af1bd aa-el-btn aa-el-btn--ghost elementor-widget elementor-widget-button\" data-id=\"33af1bd\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"button.default\">\n\t\t\t\t\t\t\t\t\t\t<a class=\"elementor-button elementor-button-link elementor-size-sm\" href=\"https:\/\/anasalassi.com\/projects\/\">\n\t\t\t\t\t\t<span class=\"elementor-button-content-wrapper\">\n\t\t\t\t\t\t\t\t\t<span class=\"elementor-button-text\">All projects<\/span>\n\t\t\t\t\t<\/span>\n\t\t\t\t\t<\/a>\n\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t","protected":false},"excerpt":{"rendered":"<p>\u064a\u0628\u0633\u0637 RoomFlow \u0627\u0644\u0628\u062d\u062b \u0639\u0646 \u063a\u0631\u0641 \u0627\u0644\u0627\u062c\u062a\u0645\u0627\u0639\u0627\u062a\u060c \u0648\u0645\u0639\u0631\u0641\u0629 \u0627\u0644\u062a\u0648\u0627\u0641\u0631 \u0627\u0644\u0641\u0648\u0631\u064a\u060c \u0648\u0627\u0644\u062d\u062c\u0632 \u062f\u0648\u0646 \u062a\u0639\u0627\u0631\u0636\u060c \u0648\u0627\u0644\u0645\u0648\u0627\u0641\u0642\u0627\u062a\u060c \u0648\u062a\u0633\u062c\u064a\u0644 \u0627\u0644\u062d\u0636\u0648\u0631\u060c \u0648\u0625\u062f\u0627\u0631\u0629 \u0627\u0644\u0627\u0633\u062a\u062e\u062f\u0627\u0645 \u0636\u0645\u0646 \u0645\u0646\u0635\u0629 \u062b\u0646\u0627\u0626\u064a\u0629 \u0627\u0644\u0644\u063a\u0629.<\/p>","protected":false},"author":0,"featured_media":5618,"parent":5422,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"elementor_header_footer","meta":{"_acf_changed":false,"footnotes":""},"class_list":["post-5476","page","type-page","status-publish","has-post-thumbnail","hentry"],"acf":[],"_links":{"self":[{"href":"https:\/\/anasalassi.com\/ar\/wp-json\/wp\/v2\/pages\/5476","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/anasalassi.com\/ar\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/anasalassi.com\/ar\/wp-json\/wp\/v2\/types\/page"}],"replies":[{"embeddable":true,"href":"https:\/\/anasalassi.com\/ar\/wp-json\/wp\/v2\/comments?post=5476"}],"version-history":[{"count":0,"href":"https:\/\/anasalassi.com\/ar\/wp-json\/wp\/v2\/pages\/5476\/revisions"}],"up":[{"embeddable":true,"href":"https:\/\/anasalassi.com\/ar\/wp-json\/wp\/v2\/pages\/5422"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/anasalassi.com\/ar\/wp-json\/wp\/v2\/media\/5618"}],"wp:attachment":[{"href":"https:\/\/anasalassi.com\/ar\/wp-json\/wp\/v2\/media?parent=5476"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}