Developer Page Specifications
Details Page
/:lang/opportunity-radar/{slug} — 16-section breakdown.
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
- A · Breadcrumb
- B · Opportunity header (status badges, title, key meta)
- C · Apply via Official Source box
- D · Should You Apply (decision aid)
- E · What Is This Opportunity (full description)
- F · Who Is It For
- G · Why It Matters (editorial insight)
- H · Benefits
- I · Eligibility Criteria
- J · Application Requirements
- K · Timeline
- L · Organizer
- M · Related Opportunities
- N · Connected Signals (cross-module)
- O · Opportunity Signals subscription block
- 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.
