All modules

GymSphere — Your GymNOT OPEN YET

Gym discovery already runs on the server and is live on the web: distance, amenities, opening hours, campaigns and live occupancy in one list. The in-app door is not open yet — the app screen stands in its place and shows the real list rendered with sample data.

GymSphere — Your Gym — Your Gym

Your Gym · 1/3

WHERE YOU ARE

The top of the GymSphere display case. You get here by tapping the sphere in the middle of the navigation bar at the bottom of the home screen; the bar’s two wings lead to the trainer and dietitian display cases, and the sphere in the middle to this screen.

WHAT YOU DO

You read how the list will look once gym discovery opens, rendered with sample data, and vote on the launch order at the bottom.

WHERE IT GOES
  • Back — the home screen; this screen’s back button returns straight to the Dashboard
  • Down — the "Why here" and "What you get" panels, with the vote button at the bottom
  • There is NO EXIT to anywhere else from this screen: the demo list is deliberately drawn unpressable
What is on this screen 12

Say you press the sphere on the home screen and the first thing you meet is a grey box: "This package is not open yet".

That box sits at the very top of the screen and tells you the area is closed before you begin reading anything.

The order is deliberate: with the same note at the bottom of the page you would see the list first, conclude "so it works", and discover your mistake on the way down. Building a wrong mental model and correcting it later costs more than never building one — you stop trusting not the product itself but what the product says.

The list on this screen is a promise, not a service. If you want to search for a gym, the path that works today is on the web.

The system calls this a "closed-area display case".

Say a blue-framed box opens under the note, reading "SAMPLE VIEW" in its top right corner, with three gym cards inside.

That frame is an unremovable honesty marker: everything inside it is sample data and none of the cards can be pressed.

A button that looks pressable but does nothing makes the display case a liar. So demo content is drawn closed to touch and does not enter the screen-reader tree as "there is a button here" either. The badge is not added as text but embedded in the frame itself — if the frame is there, so is the badge; one cannot survive while the other is forgotten.

Do not try tapping the cards; the job here is to see, not to choose. When the real list opens you will see the same layout, tappable.

The system calls this the "demo frame".

Say the first card in the list is "Sample Gym C" with a grey "AD" label on its right; the two cards below carry no such label.

Paid placements are never mixed into the organic list; they sit in a separate, labelled block of at most three slots.

There are two reasons, both written in the code. The first is legal: ranking results based on an advertising agreement must be labelled as advertising, and a separate block makes that beyond dispute. The second is technical: ad slots do not shift during pagination, so you never see the same gym again on page two. If a gym would appear in both the ad block and the organic list, it is dropped from the organic one.

The top card does not mean "the best gym"; it means "the gym that paid". Make your comparison across the unlabelled cards.

The system calls this the "sponsored slot block".

Say the ad card’s border is grey while the two organic cards below have blue borders, and you take that for favouritism.

It is the opposite: the ad card deliberately carries a NEUTRAL border, and the brand colour belongs to the organic cards only.

An emphasised border would make the ad look stronger than the organic results and undo the label’s work. The distinction is kept in colour, not in weight. That the borders are visible at all is a measurement result rather than a preference: previously the card edges fell below the UI-element threshold (3.0) and the cards looked glued together; now the neutral edge carries 3.2640 and the brand edge 6.5562 contrast.

You can tell which is an ad from the border colour, but the real signal is the written label.

In interface terms this is the rule "distinguish by colour, not by weight".

Say the "Sample Gym A" card reads "Moda · 0.8 km" and you wonder where that distance comes from.

The distance is the straight-line distance between your current coordinates and the gym’s pinned coordinates, recomputed on every request.

The calculation has two steps. First a coarse bounding box is drawn — the cheap step where the database index can be used, eliminating distant records outside the radius from the start. Then records left in the box’s corners but outside the radius are dropped one by one using the great-circle formula. Done in one step it would be either slow, or gyms in the corners would leak into the list.

Narrowing the radius does not speed the list up, only shortens it; the speed already comes from the pre-filter. If a gym is missing from the list it is either outside the radius or has no pin entered.

The system calls this "bounding-box pre-filter plus Haversine".

Say there is a small green dot and the word "Open" next to the distance, and the time is 21:40.

That label is computed on the spot from the opening hours the gym entered; the business does not press an "I am open" button.

The decision looks at three things: which day of the week it is, which minute of the day you are in, and the opening and closing minutes entered for that day. The time zone is resolved by a fixed offset because Turkey has been on UTC+3 since 2016 with no daylight saving. With a manually toggled "open" flag, one forgotten tap at closing time would leave the gym showing as open all night.

If a gym does not show as open there are two reasons, and both sit on one line of code: either it really is closed at that moment, or it entered no hours for that day.

The system calls this "openness derived from opening hours".

Say you see blue chips reading "Free weights", "Pool", "Sauna" and ask whether the gym writes those itself.

No — amenities are not free text; they are chosen from a closed list of 21 keys.

Left free, ten thousand gyms would write "shower", "showers", "has a shower", and the amenity filter would find nothing. Fixed keys plus translation in the interface keep the filter both working and multilingual. An unknown key sent to search is silently discarded; an invented filter query never reaches the database.

If you do not see the amenity you want as a chip, that amenity is not on the list yet — the gym simply not having entered it is a separate case.

In data terms this is called a "controlled vocabulary".

Say the "Sample Gym A" card reads "Quiet right now · 28%" with a thin blue bar beneath it, while "Sample Gym C" shows no occupancy at all.

Occupancy appears only for gyms that share their capacity; with no capacity, no percentage is invented and the card draws no occupancy.

A percentage needs a denominator. If the gym did not enter its capacity there is a numerator but no denominator — a percentage produced with an invented denominator is a wrong number that looks right, and you decide whether to go to the gym by it. The words "quiet / busy" also come not from the raw percentage but from the band it falls into; the band boundaries are fixed on the server: below 35% quiet, below 70% moderate, below 95% busy, above that full.

A card without occupancy does not mean "an empty gym"; it means "a gym that has not shared its capacity". Compare among the cards that do show occupancy.

The system calls this the "occupancy level".

Say the ad card reads "Opening special: first month half price" with a tag icon, and you assume we wrote that text.

The business writes the campaign headline from its own panel; the discovery list carries only what is live at that moment.

A campaign has a start and an end; if the moment the list is built is not inside that window, the headline never reaches the card. Leaving an expired campaign on the card means walking to the door and being told "that campaign is over" — which is exactly the most complained-about thing on listing sites, and precisely what having no listing site in between is meant to avoid.

The campaign you see on the card is live at that moment. Even so, the price itself is not written here; the price is on the gym’s page.

The system calls this a "campaign with a publication window".

Say at the end of the demo list you read "The organic ranking is built from distance, profile completeness and freshness; the criteria are always written on screen."

That sentence is not a marketing line but the abbreviated form of the server’s ranking disclosure, whose full text can be read from an endpoint on the web.

Disclosing ranking criteria is an obligation, and the disclosure text is written INSIDE the file where the weights live. So when a weight changes the disclosure changes with it; the two cannot drift apart and turn the disclosure into a lie. Kept elsewhere, the distance weight would one day drop while the old ratio still stood on screen.

If you wonder why a gym is at the top you do not have to guess the criteria; the "How is the ranking done?" link on the web discovery page pulls the list from the server.

The system calls this "ranking disclosure".

Say at the bottom of the demo list, with a phone icon, it reads "your number goes to that gym only with your consent", and you wonder how binding that is.

Two things are required together for your number to reach a gym: you must be signed in and you must tick the consent box. Both are separately checked on the server.

If the consent box were checked only in the interface, someone crafting the request by hand could skip it — leaving a transfer with no defence. The exact consent text shown on screen at that moment is also written into the record: even if the text changes later, the old record carries its own copy. Because the burden of proof is ours, that copy stays in the record.

Telling a gym you are interested is a reversible decision; when you delete the record it is deleted entirely — there is no "I withdrew but you still have it" state.

The system calls these the "lead" and the "consent text snapshot".

Say you see no price anywhere in the demo list and take it for an omission.

There is deliberately no price on this screen.

Two reasons stack. First: showing a price in an area that is not open is making a promise that cannot be kept today — you remember the figure, and when the area opens the figure is different. Second: no purchase path is opened from inside the app, and a behaviour guard actively looks for currency symbols on this screen. The real gym page on the web does carry package prices, shown there as "starting from the lowest package".

If you want to compare prices, the right place today is web discovery; the app screen shows which information lands on the card, not the figure.

In product terms this is the rule "never promise what you cannot keep".

Behind the scenes — how the system decides 8

The organic ranking score (distance 60% · profile completeness 30% · freshness 10%)

Say the nearest gym is 300 metres away but run down; 1.3 kilometres further there is a newly opened gym that has filled in its profile, and you do not know it exists.

The organic order is not pure distance; it comes from a single three-component score, of which distance is the heaviest.

Under pure distance ranking the second gym would never appear. The consequence is not merely a poor experience: a new gym that is never seen does not join the system, and if supply does not grow there is nothing left to offer you. So 30% of the score measures "a gym that explains itself well" — photographs are the heaviest part, because they influence your decision most — and 10% grants temporary visibility to newly published gyms: full support for the first 30 days, zero by day 90. Distance itself is not linear either: 0 km scores 1.0, 2 km scores 0.5, 10 km scores 0.17. The first kilometres matter a great deal and the rest matters little — that is how people actually behave.

The gym at the top of the list is not always the nearest. Reading the distance next to it tells you more than reading the position.

In marketplace terms this is called "cold-start support".

Where the distance weight goes when there is no location

Say you did not grant location permission and searched by selecting a province and district; the list still came back ordered.

The distance component is switched off and its weight is redistributed to quality and freshness.

Giving distance a fixed value with no location was the easiest route but would have been the most wrong: every gym would score the same on distance, the three-component score would collapse to one component, and the ordering would silently degrade to "insertion order". Redistributing the weight keeps the ordering meaningful — the gym that filled its profile and the newly opened one stay in front.

Withholding location does not drop you to a second-class list; only the "nearest to you" axis closes. Selecting province and district is an equivalent path.

The system calls this "weight redistribution".

Separating advertising from the organic list

Say a gym paid to be promoted and that same gym already ranks high in the organic list.

The gym is shown in the ad block and dropped from the organic list; you never see it twice.

The ad block holds at most three slots and appears only on the first page. Scope is mandatory too: a nationwide ad shows in every search, a provincial ad only in that province, a district ad only in that district. An unscoped ad would show an athlete in Istanbul a gym in Van — and the gym paying for it loses as well. Impressions and clicks are counted separately, because an impression alone does not measure interest.

On the second page the ad block disappears; everything from there on is entirely organic.

The system calls this "scoped promotion".

Live occupancy is derived on read, not held in a counter

Say you checked into the gym by QR, finished your evening session and forgot to scan out.

Four hours later your record no longer counts as "inside" and the occupancy corrects itself.

Held in a counter column, "N people inside right now" would be permanently inflated by everyone who forgot to check out, and the number would never recover. Derived instead, stale open records fall outside the time bound and the number stays correct even if the cleanup job stalls. Counting is also per unique person: even if a race condition creates two open records for one person, the count rises by one. If the gym is closed no occupancy is shown at all — writing "0 people" for a closed gym reads as "empty, go now" and is wrong.

The occupancy percentage is a usable signal for choosing your training hour, not an exact headcount.

In data terms this is called "derive on read".

The location gate on QR check-in

Say someone photographed the gym’s QR code and tried to scan it from home.

The server measures the distance and rejects a check-in beyond 250 metres.

For the occupancy number to mean anything, "inside" has to mean actually inside. The threshold could not be zero: urban location error sits in the 50–100 metre band, and a near-zero threshold would reject people genuinely at the gym. Requiring location is also the source of the occupancy data — the percentages in the discovery list are born from these check-ins.

Behind a card showing occupancy are people who were genuinely at the gym; the number is not a prediction engine.

The system calls this a "geofenced check-in".

The candidate cap and pagination

Say you run an unfiltered search and there are thousands of gyms in the area.

The server takes at most 500 candidates into memory, scores them, sorts them and hands them to you page by page.

Writing the three-component score into the database query would produce an unreadable expression, so the sorting happens in memory. Work done in memory must have a ceiling — otherwise a single malicious or unfiltered request chokes the server. Since the interface makes you choose either a location or a province and district, the candidate set is small in practice; the cap is a defence, not a limit. At most 50 results are returned per page.

On a very broad search, narrowing the radius or the district gives a better result than scrolling to the end of the list.

The system calls this the "candidate cap".

Which fields leave the building on a gym page

Say you press a gym card and arrive on the public gym page: photographs, packages, campaigns, opening hours, address, phone.

The public page lives in a separate service and carries only publishable fields.

The view the gym owner sees in their panel also includes draft fields. With both in the same file, someone would one day add the wrong field to the list and an unpublished profile would leak out. The separation is kept at file level. The owner’s phone is deliberately exposed — an athlete needs to be able to call — but email and account details are never exposed under any condition. The page returns only published gyms; a profile under review does not open even if its address is known.

Every field you see on a gym page is one the gym chose to publish; information you find missing was not hidden but never entered.

The system calls this "publication-surface separation".

The source of the launch order

Say there are five closed areas — the athlete store, the nutrition basket, trainer, dietitian, GymSphere — and the question of which opens first is on the table.

The order is built not by guesswork but from a demand count broken down by area and country.

With five separate record tables for five areas, "which is most wanted" could not be answered: comparing two differently shaped tables is trying to add two different units. One table, one shape, one unit. Country is a separate breakdown because the launch order may come out differently in Turkey and in Europe; a single total would misrepresent both markets.

Your vote feeds not only the choice of area but also which market that area opens in first.

The system calls this the "area × country demand summary".

What your data is for 8

Location permission (only while searching)

If you provide it Gyms are ordered nearest first, the distance appears on the card and the radius filter works. Your location is not stored on the server — it is used only to compute distance for that request and forgotten with the response; on the web it is kept in the browser only until the tab closes.

If you skip it The distance axis closes: no distance appears on the cards and the ordering is built from quality and freshness. You can keep searching by province and district; that is an equivalent path, not a restricted mode.

The amenity filter (pool, sauna, parking, women-only hours…)

If you provide it The list shows only gyms carrying ALL the amenities you selected; pick two and only those offering both remain.

If you skip it No filter is applied and every published gym in the area stays in the list. You end up reading the chips on each card one by one to find what you want.

Your phone number (only at the "I am interested" step)

If you provide it The gym can call you. The number is prefilled from your profile but copied into the request: even if you change your profile later, the number the gym holds does not change, so they do not call the wrong person.

If you skip it No request is created — the server rejects numbers shorter than 10 or longer than 20 characters. A request without a phone number is useless to the gym: it would mean "someone is interested but you cannot reach them".

The consent box (passing your number to the gym)

If you provide it The exact consent text shown on screen at that moment is written into the record and the request is created. Even if the text changes later, your record carries its own copy.

If you skip it No record is created. A request arriving without the box ticked is rejected on the server — trusting the interface alone is not defensible here. No record is created if the consent text arrives empty or shorter than 20 characters either.

The gym’s capacity (entered by the business, not by you)

If you provide it The occupancy percentage and level appear on the card, so you can decide what time to go.

If you skip it The card shows no occupancy at all. A percentage produced with an invented denominator would be a wrong number that looks right, and you would go to the gym on the strength of it.

The gym’s opening hours (entered by the business)

If you provide it The card says "Open now", and the gym page shows the weekly hours table and the closing time. The "Open now" filter also runs off this data.

If you skip it The card shows no open state and the gym drops out of the "Open now" filter. Occupancy is not shown either, because occupancy is meaningless without knowing whether the gym is open.

The country on your account (for the vote)

If you provide it Your vote feeds the area and country breakdown; the launch plan sees the two markets separately.

If you skip it No vote record is written at all. Rows whose country cannot be resolved pile up in an "unknown" bucket, and as that bucket grows the ordering becomes untrustworthy — so rather than creating an incomplete record, none is created.

How complete the gym’s profile is (photos, packages, amenities, hours, description)

If you provide it The gym scores on the quality component of the organic ranking and appears higher. Photographs are the heaviest part of that component.

If you skip it The quality score drops and the gym falls below competitors at the same distance. This score is not a claim of "a good gym"; it measures "a gym that explains itself well" — that is the honest signal available.