Skip to main content

Developer Page Specifications

Details Page

/:lang/opportunity-radar/{slug} — 16-section breakdown.

Complete

Route: /:lang/opportunity-radar/{slug}. Renders one opportunity in full — header, decision-aid, body, organizer, related, connected signals. 16 sections in canonical order. Every section is conditional: if the underlying data is missing, the section hides rather than rendering empty.

Section order

  1. A · Breadcrumb
  2. B · Opportunity header (status badges, title, key meta)
  3. C · Apply via Official Source box
  4. D · Should You Apply (decision aid)
  5. E · What Is This Opportunity (full description)
  6. F · Who Is It For
  7. G · Why It Matters (editorial insight)
  8. H · Benefits
  9. I · Eligibility Criteria
  10. J · Application Requirements
  11. K · Timeline
  12. L · Organizer
  13. M · Related Opportunities
  14. N · Connected Signals (cross-module)
  15. O · Opportunity Signals subscription block
  16. P · Footer (global)

A · Breadcrumb

  • Trail: Home → Opportunity Radar → {current title}. The current title is non-clickable.
  • Source: route segments + OpportunityLang.Title for current locale.
  • Emits BreadcrumbList JSON-LD (see SEO sub-topic).

B · Opportunity header — fields

Module label
Static: "Opportunity Radar". Eyebrow font.
Opportunity number
Source: Opportunity.DisplayOrder or editorial ranking. Display: "#1" / "#42" etc. Optional.
Status badges row
Render in order: { Status, Type, Sponsored (if true), Verified (if true) }. Each is a pill chip.
Title (H1)
Source: OpportunityLang.Title. Only H1 on the page.
Short description
Source: OpportunityLang.ShortDescription. ≤ 200 chars, rendered as the lede paragraph.
Deadline pill
Source: Opportunity.DeadlineDate (or IsRolling). Same computed label as listing card (see Listing E).
Country pill
Source: Country.Code + Country.NameLang. Flag emoji + name.
Stage pill(s)
Source: OpportunityStage join. All stages displayed, no overflow truncation on details page (unlike card).
Value signal pill
Source: Opportunity.ValueSignal. Examples: "Funding available" / "Equity-free" / "Up to $100K". Optional.

C · Apply via Official Source box

Box title
Static: "Apply via Official Source".
Disclaimer text
Static: "StartupHub does not collect applications. You apply directly with the organizer." Translated per locale.
Official Apply button
Primary CTA. Source: Opportunity.OfficialUrl. Behavior: window.open(url, '_blank', 'noopener,noreferrer'). State: disabled when OfficialUrl is null OR Status === Closed.
Share button
Tries navigator.share first (mobile), falls back to copy-to-clipboard. Fires opportunity_share_click.
Save button
Currently disabled (login flow not yet wired). When enabled: POST /api/users/:userId/saved-items { type: 'opportunity', id }.
Last verified note
Footer of box. "Last verified {N} days ago by {VerifiedBy.Name}". Source: Opportunity.LastVerifiedAt + Opportunity.VerifiedByUserId → User.DisplayName.

D · Should You Apply — decision aid

Urgency
Computed from DeadlineDate + Status. Values: "Apply today" / "Apply this week" / "Plan ahead" / "No deadline (rolling)" / "Closed".
Best for
Source: Opportunity.BestForStages (free-text bilingual). Display: 1-2 line summary. Example: "Pre-seed B2B SaaS founders in MENA".
Time required
Source: Opportunity.TimeRequired. Examples: "15 minutes" / "2 hours" / "1 day (full application)". Optional but strongly preferred.
Value signal
Same field as header section B. Repeated here in the decision-aid context.
Application form requirements
Source: Opportunity.ApplicationRequirements (bilingual free-text). Examples: "Pitch deck, team CV, demo video".
Eligibility criteria count
Aggregate: SELECT COUNT(*) FROM OpportunityEligibilityCriteria WHERE OpportunityId = :id. Display as "{N} eligibility criteria — see section I below".

E · What Is This Opportunity

  • Full description — Source: OpportunityLang.FullDescription. Rendered as pre-sanitised HTML (markdown converted server-side at edit time). Required: yes.
  • Program structure — Source: OpportunityLang.ProgramStructure. Optional. Renders as a sub-heading + structured list when present. Hide section when absent.

F · Who Is It For

Target stages
Source: OpportunityStage join. Display as labelled chip group.
Target sectors
Source: OpportunitySector join. Same chip-group display.
Founder / team profile
Source: OpportunityLang.FounderProfile (free-text). Example: "Solo or 2-person teams, MENA-resident".
Country eligibility
Source: OpportunityCountry join (multi-country support — some opportunities span 16 MENA countries, others are single-country).
Minimum requirements
Source: OpportunityLang.MinimumRequirements (free-text). Examples: "Incorporated entity" / "Working prototype" / "Revenue stage".

G · Why It Matters — editorial insight

Source: OpportunityLang.WhyItMatters. Editorially-authored short paragraph (2-4 sentences) explaining the strategic significance for the ecosystem — NOT a marketing description, NOT a re-statement of the program. Examples: "Numu Demo Day is the first GCC demo day to publicly disclose term sheet outcomes." Optional but strongly preferred for editorialPriority='featured' or 'spotlight'. Hide section entirely when null.

H · Benefits

  • Per benefit: { title, description, type, displayOrder, iconKey }.
  • Source: OpportunityBenefits join table. Display as grid of cards (2 cols desktop, 1 mobile).
  • Common types: funding · mentorship · investor-access · workspace · corporate-pilots · legal-support · cloud-credits · network-access. type drives iconKey lookup.
  • Sorted by displayOrder ASC.

I · Eligibility Criteria

  • Per criterion: { title, isRequired, notes, displayOrder }.
  • Source: OpportunityEligibilityCriteria table.
  • Display: vertical list. Required criteria get a red dot prefix, optional get a gray dot. Notes (when present) render as small muted text below the title.
  • Sorted by displayOrder ASC; required-first when displayOrder is equal.

J · Application Requirements

Required documents[]
Source: OpportunityApplicationRequirements.RequiredDocs (string array). Display: bulleted list.
Optional documents[]
Source: OpportunityApplicationRequirements.OptionalDocs (string array). Display: separate "Optional" sub-list.
Pitch deck required (bool)
Source: OpportunityApplicationRequirements.PitchDeckRequired. Renders as inline ✓ icon + label.
Founder profile required (bool)
Source: OpportunityApplicationRequirements.FounderProfileRequired. Same inline display.
Product demo required (bool)
Source: OpportunityApplicationRequirements.ProductDemoRequired. Same inline display.
Application form link
Source: OpportunityApplicationRequirements.FormUrl. If different from OfficialUrl, render as secondary button. Otherwise hide (deduped).

K · Timeline

  • Per item: { date (ISO 8601), label, description, status, displayOrder }.
  • Source: OpportunityTimeline table.
  • Display: vertical timeline with date column (left LTR, right RTL) + label + description.
  • Sorted by date ASC, fallback to displayOrder when dates equal.
  • Item status (past / current / upcoming) computed at render time from item.date vs NOW(). Past items use desaturated tint.
  • Examples per item: { date: "2026-04-15", label: "Applications open", status: "past" } / { date: "2026-05-31", label: "Deadline" } / { date: "2026-07-01", label: "Program start" } / { date: "2026-12-15", label: "Demo day" }.

L · Organizer

Organizer name + logo
Source: Organizer.Name + Organizer.LogoUrl. Logo: 80×80 with object-contain.
Organizer type
Source: Organizer.Type. Values: Accelerator / Incubator / VC / Corporate / Government / University / Community / Media / Other. Mirrors §9.6 OrganizerType.
Organizer country
Source: Organizer.CountryCode + Country.NameLang.
Organizer description
Source: Organizer.DescriptionLang. 2-4 lines max. Hide if null.
Verified organizer badge
Source: Organizer.IsVerified. Renders as small ✓ chip next to name.
Organizer website link
Source: Organizer.WebsiteUrl. target="_blank" rel="noopener". External-link icon inline.

M · Related Opportunities

  • Same <app-opportunity-card> component, compact variant.
  • Up to 6 cards displayed. 2-column desktop, 1-column mobile.
  • Sourcing order: (1) Manual links (OpportunityRelatedOpportunity table) sorted by displayOrder; (2) Auto-fill from same Country + same Sector + Status ∈ { Open, ClosingSoon, Rolling }, sorted by recency.
  • Manual links always come first. Section hides entirely when both sources return zero results.

N · Connected Signals — the cross-module rail

Most strategically important section on the page. Surfaces every entity across Founder Files, Editorial, Startup Showcase, Founder Profiles, Events Coverage, and MENA Calendar that connects to this opportunity. Source: EcosystemEntityLinks table queried by entityType='opportunity' AND entityId=:id.

Per connected signal
Fields: { sourceEntityType, sourceEntityId, sourceTitle, sourceCoverUrl, reasonLang, sourceRoute, relationType, displayOrder }. reasonLang is the short editorial explanation of why the link exists — "Read this before applying" / "Adds market context" / "Startup in the same country".
relationType values
recommended-reading · context · related-entity · companion-event · companion-coverage · founder-mention. Drives the chip prefix on each card.
Display
Horizontal scroll-snap rail on mobile; 3-col grid on desktop. Each card renders sourceCoverUrl (or generated SVG fallback), sourceTitle, reasonLang chip, and routes via sourceRoute on click.

O · Opportunity Signals subscription block

  • Same component as listing F. Context-aware overrides: source='details', opportunity_id=current.
  • Pre-selects Opportunity Radar interest checkbox.
  • Tracks source_page=opportunity_details + target_opportunity_id in the submit event.

P · Footer

Global <app-site-footer> only. No per-page footer.