feat: add Jordan 3D topographic platform, Siro Map cinematic orbit studio, sun-shadow simulation, and sovereign intelligence security gating
@@ -13,6 +13,8 @@ import { MailModule } from './common/mail.module';
|
||||
import { WeatherModule } from './weather/weather.module';
|
||||
import { TacticalModule } from './tactical/tactical.module';
|
||||
import { TelemetryModule } from './telemetry/telemetry.module';
|
||||
import { HeritageModule } from './heritage/heritage.module';
|
||||
import { CommunityModule } from './community/community.module';
|
||||
import { UsageInterceptor } from './usage/usage.interceptor';
|
||||
|
||||
@Module({
|
||||
@@ -44,6 +46,8 @@ import { UsageInterceptor } from './usage/usage.interceptor';
|
||||
WeatherModule,
|
||||
TacticalModule,
|
||||
TelemetryModule,
|
||||
HeritageModule,
|
||||
CommunityModule,
|
||||
],
|
||||
controllers: [],
|
||||
providers: [
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import { Controller, Post, Get, Body, Param, Query, UseGuards } from '@nestjs/common';
|
||||
import { CommunityService } from './community.service';
|
||||
import { SubmitContributionDto } from './dto/submit-contribution.dto';
|
||||
import { ApiKeyGuard } from '../common/guards/api-key.guard';
|
||||
|
||||
@Controller('v1/community')
|
||||
@UseGuards(ApiKeyGuard)
|
||||
export class CommunityController {
|
||||
constructor(private readonly communityService: CommunityService) {}
|
||||
|
||||
@Post('contribute')
|
||||
async submitContribution(@Body() dto: SubmitContributionDto) {
|
||||
return this.communityService.submitContribution(dto);
|
||||
}
|
||||
|
||||
@Get('profile/:userId')
|
||||
async getProfile(@Param('userId') userId: string, @Query('name') displayName?: string) {
|
||||
return this.communityService.getOrCreateProfile(userId, displayName);
|
||||
}
|
||||
|
||||
@Get('leaderboard')
|
||||
async getLeaderboard(@Query('limit') limit?: string) {
|
||||
return this.communityService.getLeaderboard(limit ? parseInt(limit, 10) : 20);
|
||||
}
|
||||
|
||||
@Get('badges')
|
||||
async getBadges() {
|
||||
return this.communityService.getBadges();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { GuideProfile } from './entities/guide-profile.entity';
|
||||
import { Contribution } from './entities/contribution.entity';
|
||||
import { Badge } from './entities/badge.entity';
|
||||
import { GamificationService } from './gamification.service';
|
||||
import { CommunityService } from './community.service';
|
||||
import { CommunityController } from './community.controller';
|
||||
import { ModerationController } from './moderation.controller';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([GuideProfile, Contribution, Badge]),
|
||||
],
|
||||
controllers: [CommunityController, ModerationController],
|
||||
providers: [GamificationService, CommunityService],
|
||||
exports: [CommunityService, GamificationService],
|
||||
})
|
||||
export class CommunityModule {}
|
||||
@@ -0,0 +1,275 @@
|
||||
import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository, DataSource } from 'typeorm';
|
||||
import { GuideProfile } from './entities/guide-profile.entity';
|
||||
import { Contribution } from './entities/contribution.entity';
|
||||
import { Badge } from './entities/badge.entity';
|
||||
import { GamificationService } from './gamification.service';
|
||||
import { SubmitContributionDto } from './dto/submit-contribution.dto';
|
||||
import { ReviewContributionDto } from './dto/review-contribution.dto';
|
||||
|
||||
export { SubmitContributionDto, ReviewContributionDto };
|
||||
|
||||
@Injectable()
|
||||
export class CommunityService {
|
||||
constructor(
|
||||
@InjectRepository(GuideProfile)
|
||||
private readonly guideRepo: Repository<GuideProfile>,
|
||||
@InjectRepository(Contribution)
|
||||
private readonly contributionRepo: Repository<Contribution>,
|
||||
@InjectRepository(Badge)
|
||||
private readonly badgeRepo: Repository<Badge>,
|
||||
private readonly gamificationService: GamificationService,
|
||||
private readonly dataSource: DataSource,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Get or create a Local Guide profile by userId
|
||||
*/
|
||||
async getOrCreateProfile(userId: string, displayName?: string): Promise<GuideProfile> {
|
||||
let profile = await this.guideRepo.findOne({ where: { user_id: userId } });
|
||||
if (!profile) {
|
||||
profile = this.guideRepo.create({
|
||||
user_id: userId,
|
||||
display_name: displayName || `مرشد_${userId.substring(0, 6)}`,
|
||||
reputation_score: 10,
|
||||
total_points: 0,
|
||||
guide_level: 1,
|
||||
badge_title: 'مرشد مبتدئ',
|
||||
});
|
||||
await this.guideRepo.save(profile);
|
||||
}
|
||||
return profile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit a new contribution into the moderation queue
|
||||
*/
|
||||
async submitContribution(dto: SubmitContributionDto) {
|
||||
const guide = await this.getOrCreateProfile(dto.userId, dto.displayName);
|
||||
|
||||
const result = await this.dataSource.query(
|
||||
`INSERT INTO community.contributions
|
||||
(guide_id, contribution_type, target_table, target_id, place_name, suggested_data, suggested_geom, proof_image_url, notes, status)
|
||||
VALUES
|
||||
($1, $2, $3, $4, $5, $6, ST_SetSRID(ST_MakePoint($7, $8), 4326), $9, $10, 'PENDING')
|
||||
RETURNING id, status, created_at;`,
|
||||
[
|
||||
guide.id,
|
||||
dto.contributionType,
|
||||
dto.targetTable || 'heritage.landmarks',
|
||||
dto.targetId || null,
|
||||
dto.placeName || null,
|
||||
JSON.stringify(dto.suggestedData || {}),
|
||||
dto.lng,
|
||||
dto.lat,
|
||||
dto.proofImageUrl || null,
|
||||
dto.notes || null,
|
||||
],
|
||||
);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'تم إرسال المساهمة بنجاح وهي قيد المراجعة والتدقيق الإداري.',
|
||||
contributionId: result[0].id,
|
||||
status: result[0].status,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get pending contributions for manual review
|
||||
*/
|
||||
async getPendingContributions(limit = 50) {
|
||||
const rows = await this.dataSource.query(
|
||||
`SELECT
|
||||
c.id, c.contribution_type, c.target_table, c.target_id, c.place_name,
|
||||
c.suggested_data, c.proof_image_url, c.notes, c.status, c.created_at,
|
||||
ST_X(c.suggested_geom) AS lng,
|
||||
ST_Y(c.suggested_geom) AS lat,
|
||||
g.id AS guide_id, g.display_name AS guide_name, g.total_points AS guide_points, g.guide_level
|
||||
FROM community.contributions c
|
||||
LEFT JOIN community.guides_profiles g ON g.id = c.guide_id
|
||||
WHERE c.status = 'PENDING'
|
||||
ORDER BY c.created_at ASC
|
||||
LIMIT $1;`,
|
||||
[limit],
|
||||
);
|
||||
|
||||
return rows.map((r: any) => ({
|
||||
id: r.id,
|
||||
contribution_type: r.contribution_type,
|
||||
target_table: r.target_table,
|
||||
target_id: r.target_id,
|
||||
place_name: r.place_name,
|
||||
suggested_data: r.suggested_data,
|
||||
coordinates: { lat: parseFloat(r.lat), lng: parseFloat(r.lng) },
|
||||
proof_image_url: r.proof_image_url,
|
||||
notes: r.notes,
|
||||
created_at: r.created_at,
|
||||
guide: {
|
||||
id: r.guide_id,
|
||||
display_name: r.guide_name,
|
||||
total_points: r.guide_points,
|
||||
guide_level: r.guide_level,
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Approve or reject a contribution in the moderation queue.
|
||||
* Wrapped in a transaction for atomicity — all or nothing.
|
||||
*/
|
||||
async reviewContribution(dto: ReviewContributionDto) {
|
||||
const contribution = await this.contributionRepo.findOne({
|
||||
where: { id: dto.contributionId },
|
||||
relations: ['guide'],
|
||||
});
|
||||
|
||||
if (!contribution) {
|
||||
throw new NotFoundException(`Contribution #${dto.contributionId} not found.`);
|
||||
}
|
||||
|
||||
if (contribution.status !== 'PENDING') {
|
||||
throw new BadRequestException(`Contribution has already been reviewed (${contribution.status}).`);
|
||||
}
|
||||
|
||||
return this.dataSource.transaction(async (manager) => {
|
||||
if (dto.action === 'APPROVE') {
|
||||
const points = this.gamificationService.getPointsForContribution(contribution.contribution_type);
|
||||
|
||||
// 1. Update contribution status
|
||||
contribution.status = 'APPROVED';
|
||||
contribution.reviewed_by = dto.reviewerName;
|
||||
contribution.reviewer_notes = dto.reviewerNotes || 'تم الاعتماد بنجاح';
|
||||
contribution.points_awarded = points;
|
||||
contribution.reviewed_at = new Date();
|
||||
await manager.save(contribution);
|
||||
|
||||
// 2. Apply the change to heritage.landmarks based on contribution type
|
||||
if (contribution.target_table === 'heritage.landmarks') {
|
||||
if (contribution.contribution_type === 'CONFIRM_GATE' && contribution.target_id) {
|
||||
await manager.query(
|
||||
`UPDATE heritage.landmarks
|
||||
SET access_gate_geom = (SELECT suggested_geom FROM community.contributions WHERE id = $1),
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = $2;`,
|
||||
[contribution.id, contribution.target_id],
|
||||
);
|
||||
} else if (contribution.contribution_type === 'ADD_PARKING' && contribution.target_id) {
|
||||
await manager.query(
|
||||
`UPDATE heritage.landmarks
|
||||
SET parking_geom = (SELECT suggested_geom FROM community.contributions WHERE id = $1),
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = $2;`,
|
||||
[contribution.id, contribution.target_id],
|
||||
);
|
||||
} else if (contribution.contribution_type === 'ADD_PLACE') {
|
||||
// Insert a new landmark from the contribution data
|
||||
const data = contribution.suggested_data || {};
|
||||
await manager.query(
|
||||
`INSERT INTO heritage.landmarks
|
||||
(slug, name_ar, name_en, category, era, narrative_ar, centroid_geom, governorate, is_verified, metadata)
|
||||
VALUES
|
||||
($1, $2, $3, $4, $5, $6,
|
||||
(SELECT suggested_geom FROM community.contributions WHERE id = $7),
|
||||
$8, false, $9);`,
|
||||
[
|
||||
data.slug || `contrib-${contribution.id}`,
|
||||
contribution.place_name || data.name_ar || 'معلم بدون اسم',
|
||||
data.name_en || null,
|
||||
data.category || 'مساهمة_مجتمعية',
|
||||
data.era || null,
|
||||
data.narrative_ar || contribution.notes || null,
|
||||
contribution.id,
|
||||
data.governorate || null,
|
||||
JSON.stringify({ source: 'community', contribution_id: contribution.id }),
|
||||
],
|
||||
);
|
||||
} else if (contribution.contribution_type === 'ORAL_HISTORY' && contribution.target_id) {
|
||||
// Append oral history narrative to existing landmark
|
||||
const data = contribution.suggested_data || {};
|
||||
await manager.query(
|
||||
`UPDATE heritage.landmarks
|
||||
SET narrative_ar = COALESCE(narrative_ar, '') || E'\n---\n' || $1,
|
||||
audio_url = COALESCE($2, audio_url),
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = $3;`,
|
||||
[
|
||||
data.narrative_text || contribution.notes || '',
|
||||
data.audio_url || null,
|
||||
contribution.target_id,
|
||||
],
|
||||
);
|
||||
} else if (contribution.contribution_type === 'CORRECT_STREET') {
|
||||
// Street corrections are logged but not auto-applied to OSM data.
|
||||
// They feed the manual road refinement pipeline.
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Update guide points & level
|
||||
if (contribution.guide) {
|
||||
const guide = contribution.guide;
|
||||
guide.total_points += points;
|
||||
guide.approved_contributions_count += 1;
|
||||
const levelInfo = this.gamificationService.computeLevelInfo(guide.total_points);
|
||||
guide.guide_level = levelInfo.level;
|
||||
guide.badge_title = levelInfo.badgeTitle;
|
||||
await manager.save(guide);
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
action: 'APPROVED',
|
||||
pointsAwarded: points,
|
||||
message: `تم اعتماد المساهمة ومنح ${points} نقطة للمرشد بنجاح.`,
|
||||
};
|
||||
} else {
|
||||
// REJECT
|
||||
contribution.status = 'REJECTED';
|
||||
contribution.reviewed_by = dto.reviewerName;
|
||||
contribution.reviewer_notes = dto.reviewerNotes || 'المساهمة غير متوافقة مع معايير التدقيق';
|
||||
contribution.points_awarded = 0;
|
||||
contribution.reviewed_at = new Date();
|
||||
await manager.save(contribution);
|
||||
|
||||
if (contribution.guide) {
|
||||
const guide = contribution.guide;
|
||||
guide.rejected_contributions_count += 1;
|
||||
await manager.save(guide);
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
action: 'REJECTED',
|
||||
message: 'تم رفض المساهمة وتسجيل الملاحظات الإدارية.',
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Top 20 Guides Leaderboard
|
||||
*/
|
||||
async getLeaderboard(limit = 20) {
|
||||
return this.guideRepo.find({
|
||||
order: { total_points: 'DESC' },
|
||||
take: limit,
|
||||
select: [
|
||||
'id',
|
||||
'display_name',
|
||||
'avatar_url',
|
||||
'total_points',
|
||||
'guide_level',
|
||||
'badge_title',
|
||||
'approved_contributions_count',
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all badges
|
||||
*/
|
||||
async getBadges() {
|
||||
return this.badgeRepo.find({ order: { required_points: 'ASC' } });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { IsNumber, IsString, IsOptional, IsNotEmpty, IsIn } from 'class-validator';
|
||||
import { Type } from 'class-transformer';
|
||||
|
||||
export class ReviewContributionDto {
|
||||
@IsNumber()
|
||||
@IsNotEmpty()
|
||||
@Type(() => Number)
|
||||
contributionId: number;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@IsIn(['APPROVE', 'REJECT'])
|
||||
action: 'APPROVE' | 'REJECT';
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
reviewerName: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
reviewerNotes?: string;
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import { IsString, IsNumber, IsOptional, IsObject, IsNotEmpty, Min, Max, IsIn } from 'class-validator';
|
||||
import { Type } from 'class-transformer';
|
||||
|
||||
export class SubmitContributionDto {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
userId: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
displayName?: string;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@IsIn(['CONFIRM_GATE', 'ADD_PARKING', 'CORRECT_STREET', 'ADD_PLACE', 'ORAL_HISTORY'])
|
||||
contributionType: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
targetTable?: string;
|
||||
|
||||
@IsNumber()
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
targetId?: number;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
placeName?: string;
|
||||
|
||||
@IsNumber()
|
||||
@Min(-90)
|
||||
@Max(90)
|
||||
@Type(() => Number)
|
||||
lat: number;
|
||||
|
||||
@IsNumber()
|
||||
@Min(-180)
|
||||
@Max(180)
|
||||
@Type(() => Number)
|
||||
lng: number;
|
||||
|
||||
@IsObject()
|
||||
@IsOptional()
|
||||
suggestedData?: Record<string, any>;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
proofImageUrl?: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
notes?: string;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column, Index } from 'typeorm';
|
||||
|
||||
@Entity({ name: 'badges', schema: 'community' })
|
||||
export class Badge {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@Column({ unique: true })
|
||||
@Index()
|
||||
slug: string;
|
||||
|
||||
@Column()
|
||||
name_ar: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
name_en: string;
|
||||
|
||||
@Column({ type: 'text', nullable: true })
|
||||
description_ar: string;
|
||||
|
||||
@Column()
|
||||
required_points: number;
|
||||
|
||||
@Column({ default: 'award' })
|
||||
icon_name: string;
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, Index, ManyToOne, JoinColumn } from 'typeorm';
|
||||
import { GuideProfile } from './guide-profile.entity';
|
||||
|
||||
@Entity({ name: 'contributions', schema: 'community' })
|
||||
export class Contribution {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@Column({ nullable: true })
|
||||
guide_id: number;
|
||||
|
||||
@ManyToOne(() => GuideProfile, (g) => g.contributions, { onDelete: 'SET NULL' })
|
||||
@JoinColumn({ name: 'guide_id' })
|
||||
guide: GuideProfile;
|
||||
|
||||
@Column()
|
||||
@Index()
|
||||
contribution_type: string; // 'CONFIRM_GATE', 'ADD_PLACE', 'CORRECT_STREET', 'ADD_PARKING', 'ORAL_HISTORY'
|
||||
|
||||
@Column({ default: 'heritage.landmarks' })
|
||||
target_table: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
target_id: number;
|
||||
|
||||
@Column({ nullable: true })
|
||||
place_name: string;
|
||||
|
||||
@Column({ type: 'jsonb' })
|
||||
suggested_data: Record<string, any>;
|
||||
|
||||
@Column({ type: 'geometry', spatialFeatureType: 'Geometry', srid: 4326 })
|
||||
@Index({ spatial: true })
|
||||
suggested_geom: any;
|
||||
|
||||
@Column({ nullable: true })
|
||||
proof_image_url: string;
|
||||
|
||||
@Column({ type: 'text', nullable: true })
|
||||
notes: string;
|
||||
|
||||
@Column({ default: 'PENDING' })
|
||||
@Index()
|
||||
status: string; // 'PENDING', 'APPROVED', 'REJECTED'
|
||||
|
||||
@Column({ nullable: true })
|
||||
reviewed_by: string;
|
||||
|
||||
@Column({ type: 'text', nullable: true })
|
||||
reviewer_notes: string;
|
||||
|
||||
@Column({ default: 0 })
|
||||
points_awarded: number;
|
||||
|
||||
@CreateDateColumn()
|
||||
created_at: Date;
|
||||
|
||||
@Column({ type: 'timestamptz', nullable: true })
|
||||
reviewed_at: Date;
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, Index, OneToMany } from 'typeorm';
|
||||
import { Contribution } from './contribution.entity';
|
||||
|
||||
@Entity({ name: 'guides_profiles', schema: 'community' })
|
||||
export class GuideProfile {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@Column({ unique: true })
|
||||
@Index()
|
||||
user_id: string;
|
||||
|
||||
@Column()
|
||||
display_name: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
phone_number: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
email: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
avatar_url: string;
|
||||
|
||||
@Column({ default: 10 })
|
||||
reputation_score: number;
|
||||
|
||||
@Column({ default: 0 })
|
||||
@Index()
|
||||
total_points: number;
|
||||
|
||||
@Column({ default: 1 })
|
||||
guide_level: number;
|
||||
|
||||
@Column({ default: 'مرشد مبتدئ' })
|
||||
badge_title: string;
|
||||
|
||||
@Column({ default: 0 })
|
||||
approved_contributions_count: number;
|
||||
|
||||
@Column({ default: 0 })
|
||||
rejected_contributions_count: number;
|
||||
|
||||
@OneToMany(() => Contribution, (c) => c.guide)
|
||||
contributions: Contribution[];
|
||||
|
||||
@CreateDateColumn()
|
||||
created_at: Date;
|
||||
|
||||
@UpdateDateColumn()
|
||||
updated_at: Date;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
export interface LevelInfo {
|
||||
level: number;
|
||||
badgeTitle: string;
|
||||
nextLevelPoints: number;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class GamificationService {
|
||||
private readonly POINTS_MAP: Record<string, number> = {
|
||||
CONFIRM_GATE: 50,
|
||||
ADD_PARKING: 50,
|
||||
CORRECT_STREET: 75,
|
||||
ADD_PLACE: 100,
|
||||
ORAL_HISTORY: 150,
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculate awarded points for a verified contribution
|
||||
*/
|
||||
getPointsForContribution(type: string): number {
|
||||
return this.POINTS_MAP[type] || 25;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine guide level and rank title based on accumulated points
|
||||
*/
|
||||
computeLevelInfo(totalPoints: number): LevelInfo {
|
||||
if (totalPoints >= 1500) {
|
||||
return { level: 5, badgeTitle: 'مرشد سيادي رائد', nextLevelPoints: 3000 };
|
||||
}
|
||||
if (totalPoints >= 700) {
|
||||
return { level: 4, badgeTitle: 'مرشد معتمد', nextLevelPoints: 1500 };
|
||||
}
|
||||
if (totalPoints >= 300) {
|
||||
return { level: 3, badgeTitle: 'خبير محلي', nextLevelPoints: 700 };
|
||||
}
|
||||
if (totalPoints >= 100) {
|
||||
return { level: 2, badgeTitle: 'مستكشف نشط', nextLevelPoints: 300 };
|
||||
}
|
||||
return { level: 1, badgeTitle: 'مرشد مبتدئ', nextLevelPoints: 100 };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { Controller, Get, Post, Body, Query, UseGuards } from '@nestjs/common';
|
||||
import { CommunityService } from './community.service';
|
||||
import { ReviewContributionDto } from './dto/review-contribution.dto';
|
||||
import { ApiKeyGuard } from '../common/guards/api-key.guard';
|
||||
|
||||
@Controller('v1/admin/moderation')
|
||||
@UseGuards(ApiKeyGuard)
|
||||
export class ModerationController {
|
||||
constructor(private readonly communityService: CommunityService) {}
|
||||
|
||||
@Get('pending')
|
||||
async getPending(@Query('limit') limit?: string) {
|
||||
return this.communityService.getPendingContributions(limit ? parseInt(limit, 10) : 50);
|
||||
}
|
||||
|
||||
@Post('review')
|
||||
async review(@Body() dto: ReviewContributionDto) {
|
||||
return this.communityService.reviewContribution(dto);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, Index } from 'typeorm';
|
||||
|
||||
@Entity({ name: 'landmarks', schema: 'heritage' })
|
||||
export class HeritageLandmark {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@Column({ unique: true })
|
||||
@Index()
|
||||
slug: string;
|
||||
|
||||
@Column()
|
||||
@Index()
|
||||
name_ar: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
name_en: string;
|
||||
|
||||
@Column()
|
||||
@Index()
|
||||
category: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
era: string;
|
||||
|
||||
@Column({ type: 'text', nullable: true })
|
||||
narrative_ar: string;
|
||||
|
||||
@Column({ type: 'text', nullable: true })
|
||||
narrative_en: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
audio_url: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
image_url: string;
|
||||
|
||||
@Column({ type: 'geometry', spatialFeatureType: 'Point', srid: 4326 })
|
||||
@Index({ spatial: true })
|
||||
centroid_geom: any;
|
||||
|
||||
@Column({ type: 'geometry', spatialFeatureType: 'Point', srid: 4326, nullable: true })
|
||||
@Index({ spatial: true })
|
||||
access_gate_geom: any;
|
||||
|
||||
@Column({ type: 'geometry', spatialFeatureType: 'Point', srid: 4326, nullable: true })
|
||||
@Index({ spatial: true })
|
||||
parking_geom: any;
|
||||
|
||||
@Column({ type: 'geometry', spatialFeatureType: 'Point', srid: 4326, nullable: true })
|
||||
ticket_office_geom: any;
|
||||
|
||||
@Column({ nullable: true })
|
||||
@Index()
|
||||
governorate: string;
|
||||
|
||||
@Column({ default: true })
|
||||
is_verified: boolean;
|
||||
|
||||
@Column({ type: 'jsonb', default: {} })
|
||||
metadata: Record<string, any>;
|
||||
|
||||
@CreateDateColumn()
|
||||
created_at: Date;
|
||||
|
||||
@UpdateDateColumn()
|
||||
updated_at: Date;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, Index } from 'typeorm';
|
||||
|
||||
@Entity({ name: 'trails', schema: 'heritage' })
|
||||
export class HeritageTrail {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@Column({ unique: true })
|
||||
@Index()
|
||||
slug: string;
|
||||
|
||||
@Column()
|
||||
name_ar: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
name_en: string;
|
||||
|
||||
@Column()
|
||||
@Index()
|
||||
region: string;
|
||||
|
||||
@Column({ type: 'text', nullable: true })
|
||||
description_ar: string;
|
||||
|
||||
@Column({ type: 'geometry', spatialFeatureType: 'MultiLineString', srid: 4326, nullable: true })
|
||||
@Index({ spatial: true })
|
||||
trail_geom: any;
|
||||
|
||||
@Column({ type: 'decimal', precision: 6, scale: 2, default: 0.0 })
|
||||
total_distance_km: number;
|
||||
|
||||
@Column({ type: 'int', default: 0 })
|
||||
stops_count: number;
|
||||
|
||||
@Column({ default: true })
|
||||
is_active: boolean;
|
||||
|
||||
@CreateDateColumn()
|
||||
created_at: Date;
|
||||
}
|
||||
@@ -0,0 +1,218 @@
|
||||
import { Controller, Get, Param, Query, Header, UseGuards } from '@nestjs/common';
|
||||
import { HeritageService } from './heritage.service';
|
||||
import { ApiKeyGuard } from '../common/guards/api-key.guard';
|
||||
|
||||
@Controller('v1/heritage')
|
||||
@UseGuards(ApiKeyGuard)
|
||||
export class HeritageController {
|
||||
constructor(private readonly heritageService: HeritageService) {}
|
||||
|
||||
@Get('landmarks')
|
||||
async getLandmarks(
|
||||
@Query('category') category?: string,
|
||||
@Query('governorate') governorate?: string,
|
||||
@Query('lat') lat?: string,
|
||||
@Query('lng') lng?: string,
|
||||
@Query('radius') radius?: string,
|
||||
) {
|
||||
return this.heritageService.getLandmarks({
|
||||
category,
|
||||
governorate,
|
||||
lat: lat ? parseFloat(lat) : undefined,
|
||||
lng: lng ? parseFloat(lng) : undefined,
|
||||
radiusMeters: radius ? parseFloat(radius) : undefined,
|
||||
});
|
||||
}
|
||||
|
||||
@Get('landmarks/:slug')
|
||||
async getLandmarkBySlug(@Param('slug') slug: string) {
|
||||
return this.heritageService.getLandmarkBySlug(slug);
|
||||
}
|
||||
|
||||
@Get('trails')
|
||||
async getTrails(@Query('region') region?: string) {
|
||||
return this.heritageService.getTrails(region);
|
||||
}
|
||||
|
||||
@Get('offline-pack')
|
||||
async getOfflinePack(
|
||||
@Query('governorate') governorate?: string,
|
||||
@Query('region') region?: string,
|
||||
) {
|
||||
return this.heritageService.getOfflinePack(governorate, region);
|
||||
}
|
||||
|
||||
@Get('postman-collection')
|
||||
@Header('Content-Type', 'application/json')
|
||||
@Header('Content-Disposition', 'attachment; filename="siro-maps-postman-collection.json"')
|
||||
getPostmanCollection() {
|
||||
return {
|
||||
info: {
|
||||
name: "Siro Maps - Sovereign Heritage & Community Guides API",
|
||||
description: "Production Endpoints for National Heritage Landmarks, Precision Gate Navigation, and Moderated Local Guides Gamification.",
|
||||
schema: "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
|
||||
},
|
||||
variable: [
|
||||
{ key: "baseUrl", value: "https://map-saas.intaleqapp.com/api", type: "string" },
|
||||
{ key: "apiKey", value: "zP9vL5mK2nQ8xR7jT4wS1yB6hG3fV0cX", type: "string" }
|
||||
],
|
||||
item: [
|
||||
{
|
||||
name: "1. Heritage - List All Landmarks",
|
||||
request: {
|
||||
method: "GET",
|
||||
header: [{ key: "x-api-key", value: "{{apiKey}}" }],
|
||||
url: {
|
||||
raw: "{{baseUrl}}/v1/heritage/landmarks",
|
||||
host: ["{{baseUrl}}"],
|
||||
path: ["v1", "heritage", "landmarks"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "2. Heritage - Nearby Landmarks (Spatial Query)",
|
||||
request: {
|
||||
method: "GET",
|
||||
header: [{ key: "x-api-key", value: "{{apiKey}}" }],
|
||||
url: {
|
||||
raw: "{{baseUrl}}/v1/heritage/landmarks?lat=31.9539&lng=35.9350&radius=10000",
|
||||
host: ["{{baseUrl}}"],
|
||||
path: ["v1", "heritage", "landmarks"],
|
||||
query: [
|
||||
{ key: "lat", value: "31.9539" },
|
||||
{ key: "lng", value: "35.9350" },
|
||||
{ key: "radius", value: "10000" }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "3. Heritage - Single Landmark by Slug",
|
||||
request: {
|
||||
method: "GET",
|
||||
header: [{ key: "x-api-key", value: "{{apiKey}}" }],
|
||||
url: {
|
||||
raw: "{{baseUrl}}/v1/heritage/landmarks/amman-citadel",
|
||||
host: ["{{baseUrl}}"],
|
||||
path: ["v1", "heritage", "landmarks", "amman-citadel"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "4. Heritage - Offline Pack for Mobile",
|
||||
request: {
|
||||
method: "GET",
|
||||
header: [{ key: "x-api-key", value: "{{apiKey}}" }],
|
||||
url: {
|
||||
raw: "{{baseUrl}}/v1/heritage/offline-pack",
|
||||
host: ["{{baseUrl}}"],
|
||||
path: ["v1", "heritage", "offline-pack"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "5. Community - List Badges",
|
||||
request: {
|
||||
method: "GET",
|
||||
header: [{ key: "x-api-key", value: "{{apiKey}}" }],
|
||||
url: {
|
||||
raw: "{{baseUrl}}/v1/community/badges",
|
||||
host: ["{{baseUrl}}"],
|
||||
path: ["v1", "community", "badges"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "6. Community - Submit Contribution (Suggest Gate)",
|
||||
request: {
|
||||
method: "POST",
|
||||
header: [
|
||||
{ key: "Content-Type", value: "application/json" },
|
||||
{ key: "x-api-key", value: "{{apiKey}}" }
|
||||
],
|
||||
body: {
|
||||
mode: "raw",
|
||||
raw: JSON.stringify({
|
||||
userId: "guide_hamza_001",
|
||||
displayName: "حمزة عايد",
|
||||
contributionType: "CONFIRM_GATE",
|
||||
targetTable: "heritage.landmarks",
|
||||
targetId: 3,
|
||||
placeName: "مدينة جرش - البوابة الجنوبية السياحية",
|
||||
lat: 32.2731,
|
||||
lng: 35.8924,
|
||||
suggestedData: { gate_name: "South Main Gate" },
|
||||
notes: "تم تأكيد موقع بوابة التذاكر الميدانية"
|
||||
}, null, 2)
|
||||
},
|
||||
url: {
|
||||
raw: "{{baseUrl}}/v1/community/contribute",
|
||||
host: ["{{baseUrl}}"],
|
||||
path: ["v1", "community", "contribute"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "7. Community - Guide Profile & Points",
|
||||
request: {
|
||||
method: "GET",
|
||||
header: [{ key: "x-api-key", value: "{{apiKey}}" }],
|
||||
url: {
|
||||
raw: "{{baseUrl}}/v1/community/profile/guide_hamza_001",
|
||||
host: ["{{baseUrl}}"],
|
||||
path: ["v1", "community", "profile", "guide_hamza_001"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "8. Community - Leaderboard",
|
||||
request: {
|
||||
method: "GET",
|
||||
header: [{ key: "x-api-key", value: "{{apiKey}}" }],
|
||||
url: {
|
||||
raw: "{{baseUrl}}/v1/community/leaderboard",
|
||||
host: ["{{baseUrl}}"],
|
||||
path: ["v1", "community", "leaderboard"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "9. Admin - Moderation Pending Queue",
|
||||
request: {
|
||||
method: "GET",
|
||||
header: [{ key: "x-api-key", value: "{{apiKey}}" }],
|
||||
url: {
|
||||
raw: "{{baseUrl}}/v1/admin/moderation/pending",
|
||||
host: ["{{baseUrl}}"],
|
||||
path: ["v1", "admin", "moderation", "pending"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "10. Admin - Review Contribution (Approve)",
|
||||
request: {
|
||||
method: "POST",
|
||||
header: [
|
||||
{ key: "Content-Type", value: "application/json" },
|
||||
{ key: "x-api-key", value: "{{apiKey}}" }
|
||||
],
|
||||
body: {
|
||||
mode: "raw",
|
||||
raw: JSON.stringify({
|
||||
contributionId: 1,
|
||||
action: "APPROVE",
|
||||
reviewerName: "Hamza Ayed (Admin)",
|
||||
reviewerNotes: "معتمدة ومطابقة لصور الأقمار الصناعية"
|
||||
}, null, 2)
|
||||
},
|
||||
url: {
|
||||
raw: "{{baseUrl}}/v1/admin/moderation/review",
|
||||
host: ["{{baseUrl}}"],
|
||||
path: ["v1", "admin", "moderation", "review"]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { HeritageLandmark } from './entities/heritage-landmark.entity';
|
||||
import { HeritageTrail } from './entities/heritage-trail.entity';
|
||||
import { HeritageService } from './heritage.service';
|
||||
import { HeritageController } from './heritage.controller';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([HeritageLandmark, HeritageTrail]),
|
||||
],
|
||||
controllers: [HeritageController],
|
||||
providers: [HeritageService],
|
||||
exports: [HeritageService],
|
||||
})
|
||||
export class HeritageModule {}
|
||||
@@ -0,0 +1,206 @@
|
||||
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { HeritageLandmark } from './entities/heritage-landmark.entity';
|
||||
import { HeritageTrail } from './entities/heritage-trail.entity';
|
||||
|
||||
@Injectable()
|
||||
export class HeritageService {
|
||||
constructor(
|
||||
@InjectRepository(HeritageLandmark)
|
||||
private readonly landmarkRepo: Repository<HeritageLandmark>,
|
||||
@InjectRepository(HeritageTrail)
|
||||
private readonly trailRepo: Repository<HeritageTrail>,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Find landmarks with optional category, governorate, or spatial proximity filters.
|
||||
* Returns clean GeoJSON-compatible structure with precision gate & parking coordinates.
|
||||
*/
|
||||
async getLandmarks(options?: {
|
||||
category?: string;
|
||||
governorate?: string;
|
||||
lat?: number;
|
||||
lng?: number;
|
||||
radiusMeters?: number;
|
||||
}) {
|
||||
const qb = this.landmarkRepo
|
||||
.createQueryBuilder('l')
|
||||
.select([
|
||||
'l.id AS id',
|
||||
'l.slug AS slug',
|
||||
'l.name_ar AS name_ar',
|
||||
'l.name_en AS name_en',
|
||||
'l.category AS category',
|
||||
'l.era AS era',
|
||||
'l.narrative_ar AS narrative_ar',
|
||||
'l.audio_url AS audio_url',
|
||||
'l.image_url AS image_url',
|
||||
'l.governorate AS governorate',
|
||||
'l.is_verified AS is_verified',
|
||||
'l.metadata AS metadata',
|
||||
'ST_X(l.centroid_geom) AS centroid_lng',
|
||||
'ST_Y(l.centroid_geom) AS centroid_lat',
|
||||
'ST_X(l.access_gate_geom) AS gate_lng',
|
||||
'ST_Y(l.access_gate_geom) AS gate_lat',
|
||||
'ST_X(l.parking_geom) AS parking_lng',
|
||||
'ST_Y(l.parking_geom) AS parking_lat',
|
||||
'ST_X(l.ticket_office_geom) AS ticket_lng',
|
||||
'ST_Y(l.ticket_office_geom) AS ticket_lat',
|
||||
]);
|
||||
|
||||
if (options?.category) {
|
||||
qb.andWhere('l.category = :category', { category: options.category });
|
||||
}
|
||||
|
||||
if (options?.governorate) {
|
||||
qb.andWhere('l.governorate = :gov', { gov: options.governorate });
|
||||
}
|
||||
|
||||
if (options?.lat != null && options?.lng != null) {
|
||||
const radius = options.radiusMeters || 25000;
|
||||
qb.andWhere(
|
||||
'ST_DWithin(l.centroid_geom::geography, ST_SetSRID(ST_MakePoint(:lng, :lat), 4326)::geography, :radius)',
|
||||
{ lng: options.lng, lat: options.lat, radius },
|
||||
);
|
||||
qb.addSelect(
|
||||
'ST_Distance(l.centroid_geom::geography, ST_SetSRID(ST_MakePoint(:lng, :lat), 4326)::geography)',
|
||||
'distance_meters',
|
||||
);
|
||||
qb.orderBy('distance_meters', 'ASC');
|
||||
} else {
|
||||
qb.orderBy('l.id', 'ASC');
|
||||
}
|
||||
|
||||
const rawResults = await qb.getRawMany();
|
||||
|
||||
return rawResults.map((r) => ({
|
||||
id: r.id,
|
||||
slug: r.slug,
|
||||
name_ar: r.name_ar,
|
||||
name_en: r.name_en,
|
||||
category: r.category,
|
||||
era: r.era,
|
||||
narrative_ar: r.narrative_ar,
|
||||
audio_url: r.audio_url,
|
||||
image_url: r.image_url,
|
||||
governorate: r.governorate,
|
||||
is_verified: r.is_verified,
|
||||
metadata: r.metadata,
|
||||
coordinates: {
|
||||
centroid: { lat: parseFloat(r.centroid_lat), lng: parseFloat(r.centroid_lng) },
|
||||
access_gate: r.gate_lat ? { lat: parseFloat(r.gate_lat), lng: parseFloat(r.gate_lng) } : null,
|
||||
parking: r.parking_lat ? { lat: parseFloat(r.parking_lat), lng: parseFloat(r.parking_lng) } : null,
|
||||
ticket_office: r.ticket_lat ? { lat: parseFloat(r.ticket_lat), lng: parseFloat(r.ticket_lng) } : null,
|
||||
},
|
||||
distance_meters: r.distance_meters ? Math.round(parseFloat(r.distance_meters)) : undefined,
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a specific landmark by unique slug (direct query, no full-table scan)
|
||||
*/
|
||||
async getLandmarkBySlug(slug: string) {
|
||||
const qb = this.landmarkRepo
|
||||
.createQueryBuilder('l')
|
||||
.select([
|
||||
'l.id AS id',
|
||||
'l.slug AS slug',
|
||||
'l.name_ar AS name_ar',
|
||||
'l.name_en AS name_en',
|
||||
'l.category AS category',
|
||||
'l.era AS era',
|
||||
'l.narrative_ar AS narrative_ar',
|
||||
'l.audio_url AS audio_url',
|
||||
'l.image_url AS image_url',
|
||||
'l.governorate AS governorate',
|
||||
'l.is_verified AS is_verified',
|
||||
'l.metadata AS metadata',
|
||||
'ST_X(l.centroid_geom) AS centroid_lng',
|
||||
'ST_Y(l.centroid_geom) AS centroid_lat',
|
||||
'ST_X(l.access_gate_geom) AS gate_lng',
|
||||
'ST_Y(l.access_gate_geom) AS gate_lat',
|
||||
'ST_X(l.parking_geom) AS parking_lng',
|
||||
'ST_Y(l.parking_geom) AS parking_lat',
|
||||
'ST_X(l.ticket_office_geom) AS ticket_lng',
|
||||
'ST_Y(l.ticket_office_geom) AS ticket_lat',
|
||||
])
|
||||
.where('l.slug = :slug', { slug });
|
||||
|
||||
const r = await qb.getRawOne();
|
||||
if (!r) {
|
||||
throw new NotFoundException(`Heritage landmark with slug '${slug}' not found.`);
|
||||
}
|
||||
|
||||
return {
|
||||
id: r.id,
|
||||
slug: r.slug,
|
||||
name_ar: r.name_ar,
|
||||
name_en: r.name_en,
|
||||
category: r.category,
|
||||
era: r.era,
|
||||
narrative_ar: r.narrative_ar,
|
||||
audio_url: r.audio_url,
|
||||
image_url: r.image_url,
|
||||
governorate: r.governorate,
|
||||
is_verified: r.is_verified,
|
||||
metadata: r.metadata,
|
||||
coordinates: {
|
||||
centroid: { lat: parseFloat(r.centroid_lat), lng: parseFloat(r.centroid_lng) },
|
||||
access_gate: r.gate_lat ? { lat: parseFloat(r.gate_lat), lng: parseFloat(r.gate_lng) } : null,
|
||||
parking: r.parking_lat ? { lat: parseFloat(r.parking_lat), lng: parseFloat(r.parking_lng) } : null,
|
||||
ticket_office: r.ticket_lat ? { lat: parseFloat(r.ticket_lat), lng: parseFloat(r.ticket_lng) } : null,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch all active national cultural trails
|
||||
*/
|
||||
async getTrails(region?: string) {
|
||||
const qb = this.trailRepo
|
||||
.createQueryBuilder('t')
|
||||
.select([
|
||||
't.id AS id',
|
||||
't.slug AS slug',
|
||||
't.name_ar AS name_ar',
|
||||
't.name_en AS name_en',
|
||||
't.region AS region',
|
||||
't.description_ar AS description_ar',
|
||||
't.total_distance_km AS total_distance_km',
|
||||
't.stops_count AS stops_count',
|
||||
'ST_AsGeoJSON(t.trail_geom) AS geometry_geojson',
|
||||
])
|
||||
.where('t.is_active = true');
|
||||
|
||||
if (region) {
|
||||
qb.andWhere('t.region = :region', { region });
|
||||
}
|
||||
|
||||
const results = await qb.getRawMany();
|
||||
return results.map((t) => ({
|
||||
...t,
|
||||
geometry: t.geometry_geojson ? JSON.parse(t.geometry_geojson) : null,
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Export offline pack for Siro Maps mobile client (Cache-First)
|
||||
* Note: landmarks filter by `governorate`, trails filter by `region` — they differ.
|
||||
*/
|
||||
async getOfflinePack(governorate?: string, region?: string) {
|
||||
const landmarks = await this.getLandmarks({ governorate });
|
||||
const trails = await this.getTrails(region || governorate);
|
||||
|
||||
return {
|
||||
version: '1.0.0',
|
||||
timestamp: new Date().toISOString(),
|
||||
governorate: governorate || 'all',
|
||||
region: region || governorate || 'all',
|
||||
total_landmarks: landmarks.length,
|
||||
total_trails: trails.length,
|
||||
landmarks,
|
||||
trails,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
Copyright 2018 Boutros International. (http://www.boutrosfonts.com)
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
|
After Width: | Height: | Size: 4.0 MiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 1.0 MiB |
@@ -0,0 +1,501 @@
|
||||
<!doctype html>
|
||||
<html lang="ar" dir="rtl">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>المنظومة الوطنية للسيادة المكانية والملاحة التراثية | مذكرة الطرح الاستراتيجي</title>
|
||||
<meta name="description" content="مذكرة استراتيجية وهندسية موجهة لوزارة الثقافة ودائرة الآثار العامة لتحويل التراث الأردني إلى منظومة ملاحة مكانية سيادية تعمل بدون إنترنت.">
|
||||
|
||||
<!-- Fonts -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Tajawal:wght@400;500;700;800;900&family=Plus+Jakarta+Sans:wght@500;700;800&family=JetBrains+Mono:wght@500&display=swap" rel="stylesheet">
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--navy: #061d42;
|
||||
--navy-light: #0d2e61;
|
||||
--gold: #b59247;
|
||||
--gold-light: #e5cb8e;
|
||||
--gold-bg: rgba(181, 146, 71, 0.08);
|
||||
--ink: #102238;
|
||||
--muted: #596b82;
|
||||
--pale: #f4f7fb;
|
||||
--line: #dbe4ef;
|
||||
--white: #ffffff;
|
||||
--font: 'Tajawal', -apple-system, BlinkMacSystemFont, 'Segoe UI', Tahoma, sans-serif;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
html { scroll-behavior: smooth; }
|
||||
body {
|
||||
font-family: var(--font);
|
||||
background-color: var(--pale);
|
||||
color: var(--ink);
|
||||
line-height: 1.85;
|
||||
font-size: 17px;
|
||||
direction: rtl;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1080px;
|
||||
margin: 0 auto;
|
||||
padding: 40px 24px 80px;
|
||||
}
|
||||
|
||||
/* Header & Branding */
|
||||
header {
|
||||
background: var(--navy);
|
||||
color: var(--white);
|
||||
border-radius: 24px;
|
||||
padding: 50px 45px;
|
||||
margin-bottom: 40px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 20px 45px rgba(6, 29, 66, 0.15);
|
||||
}
|
||||
header::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -120px;
|
||||
left: -80px;
|
||||
width: 400px;
|
||||
height: 400px;
|
||||
background: radial-gradient(circle, rgba(181, 146, 71, 0.25), transparent 70%);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.tagline {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
background: rgba(181, 146, 71, 0.15);
|
||||
border: 1px solid rgba(181, 146, 71, 0.35);
|
||||
color: var(--gold-light);
|
||||
padding: 6px 16px;
|
||||
border-radius: 30px;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: clamp(28px, 4vw, 42px);
|
||||
font-weight: 900;
|
||||
line-height: 1.3;
|
||||
margin-bottom: 12px;
|
||||
color: var(--white);
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: clamp(18px, 2.2vw, 22px);
|
||||
color: #b8ccdf;
|
||||
font-weight: 500;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.meta-bar {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
||||
gap: 16px;
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.15);
|
||||
padding-top: 25px;
|
||||
font-size: 14px;
|
||||
}
|
||||
.meta-item b {
|
||||
color: var(--gold-light);
|
||||
display: block;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.meta-item span {
|
||||
color: #e2ebf4;
|
||||
}
|
||||
|
||||
/* Content Cards */
|
||||
.card {
|
||||
background: var(--white);
|
||||
border-radius: 20px;
|
||||
border: 1px solid var(--line);
|
||||
padding: 40px;
|
||||
margin-bottom: 35px;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.03);
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 26px;
|
||||
font-weight: 800;
|
||||
color: var(--navy);
|
||||
margin-bottom: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
h2::before {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
width: 6px;
|
||||
height: 26px;
|
||||
background: var(--gold);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
color: var(--navy-light);
|
||||
margin: 24px 0 12px;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: 16px;
|
||||
color: var(--ink);
|
||||
}
|
||||
|
||||
.highlight-box {
|
||||
background: var(--gold-bg);
|
||||
border-right: 4px solid var(--gold);
|
||||
padding: 20px 24px;
|
||||
border-radius: 12px;
|
||||
margin: 24px 0;
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
color: var(--navy);
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
/* Table Styling */
|
||||
.table-wrapper {
|
||||
overflow-x: auto;
|
||||
margin: 25px 0;
|
||||
border-radius: 14px;
|
||||
border: 1px solid var(--line);
|
||||
}
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
text-align: right;
|
||||
font-size: 15px;
|
||||
}
|
||||
th {
|
||||
background: var(--navy);
|
||||
color: var(--white);
|
||||
padding: 16px 20px;
|
||||
font-weight: 800;
|
||||
font-size: 16px;
|
||||
}
|
||||
td {
|
||||
padding: 16px 20px;
|
||||
border-bottom: 1px solid var(--line);
|
||||
color: var(--ink);
|
||||
}
|
||||
tr:nth-child(even) td {
|
||||
background: #fafbfd;
|
||||
}
|
||||
tr:hover td {
|
||||
background: #f1f6fb;
|
||||
}
|
||||
|
||||
/* Code & Architecture */
|
||||
pre {
|
||||
background: var(--navy);
|
||||
color: #d1e2f7;
|
||||
padding: 24px;
|
||||
border-radius: 14px;
|
||||
overflow-x: auto;
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 13.5px;
|
||||
direction: ltr;
|
||||
text-align: left;
|
||||
margin: 20px 0;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* List styles */
|
||||
ul, ol {
|
||||
margin: 16px 24px 24px 0;
|
||||
}
|
||||
li {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
/* Badges & Steps */
|
||||
.badge {
|
||||
display: inline-block;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
padding: 3px 10px;
|
||||
border-radius: 20px;
|
||||
background: #e9f0f9;
|
||||
color: #1a4f8b;
|
||||
}
|
||||
|
||||
.phase-card {
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 14px;
|
||||
padding: 24px;
|
||||
margin-bottom: 16px;
|
||||
background: #fafbfd;
|
||||
transition: all .2s;
|
||||
}
|
||||
.phase-card:hover {
|
||||
border-color: var(--gold);
|
||||
box-shadow: 0 8px 24px rgba(6, 29, 66, 0.06);
|
||||
}
|
||||
.phase-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.phase-header strong {
|
||||
font-size: 18px;
|
||||
color: var(--navy);
|
||||
}
|
||||
|
||||
/* Action Footer */
|
||||
.cta-footer {
|
||||
background: var(--navy);
|
||||
color: var(--white);
|
||||
border-radius: 20px;
|
||||
padding: 35px;
|
||||
text-align: center;
|
||||
margin-top: 50px;
|
||||
}
|
||||
.cta-footer h3 {
|
||||
color: var(--gold-light);
|
||||
font-size: 24px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.cta-footer p {
|
||||
color: #cfddec;
|
||||
max-width: 750px;
|
||||
margin: 0 auto 20px;
|
||||
}
|
||||
.btn-gold {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
background: var(--gold);
|
||||
color: var(--navy);
|
||||
font-weight: 800;
|
||||
padding: 14px 32px;
|
||||
border-radius: 10px;
|
||||
text-decoration: none;
|
||||
transition: all .2s;
|
||||
}
|
||||
.btn-gold:hover {
|
||||
background: var(--gold-light);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="container">
|
||||
|
||||
<!-- Header -->
|
||||
<header>
|
||||
<div class="tagline">مبادرة وطنية للسيادة المكانية والتراث الرقمي</div>
|
||||
<h1>المنظومة الوطنية للسيادة المكانية والملاحة التراثية</h1>
|
||||
<div class="subtitle">مذكرة الطرح الاستراتيجي والمعماري الموحد للشراكة الحكومية</div>
|
||||
|
||||
<div class="meta-bar">
|
||||
<div class="meta-item">
|
||||
<b>المُعدّ والخبير الاستراتيجي:</b>
|
||||
<span>حمزة عايد (Founding Tech Architect & Mobility Strategist)</span>
|
||||
</div>
|
||||
<div class="meta-item">
|
||||
<b>المرجع والتوثيق:</b>
|
||||
<span>التدقيق الاستقصائي المقارن لبيانات التراث الأردني (سبتمبر 2026)</span>
|
||||
</div>
|
||||
<div class="meta-item">
|
||||
<b>الجهات الشريكة المستهدفة:</b>
|
||||
<span>وزارة الثقافة · دائرة الآثار العامة · وزارة السياحة · هيئة النقل البري</span>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Section 1 -->
|
||||
<section class="card">
|
||||
<h2>1. الملخص التنفيذي وقراءة الفجوة الوطنية</h2>
|
||||
|
||||
<div class="highlight-box">
|
||||
"تمتلك المملكة الأردنية الهاشمية أثمن محتوى تراثي وأضخم قاعدة بيانات أثرية موثقة في المنطقة، لكنها تعاني من عجز ميداني كامل في نقل هذه البيانات من الرفوف الرقمية إلى حركة الملاحة الواقعية".
|
||||
</div>
|
||||
|
||||
<p>
|
||||
كشف التدقيق الاستقصائي المشترك للمنصات الرقمية الأردنية القائمة عن انفصال حاد بين ثلاث جزر مؤسسية تعمل كل منها بمعزل عن الأخرى:
|
||||
</p>
|
||||
|
||||
<ol>
|
||||
<li>
|
||||
<b>وزارة الثقافة (منصة تراثي ومشاريع التوثيق):</b>
|
||||
تمتلك رصيداً معرفياً هائلاً يشمل <b>75,000 مفردة</b> موثقة في 5 مجلدات علمية بـ "مكنز التراث الشعبي الأردني"، ومئات الساعات الصوتية النادرة لأشرطة الكاسيت المسجلة في السبعينيات، و6 مسارات تشمل 65 محطة، ومشروع السردية الأردنية.
|
||||
<br>
|
||||
<i>العجز الفعلي:</i> غياب تام لتطبيق هواتف ذكية رسمي (موقع ويب فقط)، انعدام الملاحة والتوجيه المنعطف بمنعطف، عجز كامل عن العمل دون اتصال بالإنترنت (Offline)، واعتماد دبابيس عشوائية تسقط في المركز الهندسي للمعلم بدلاً من بوابات الدخول ومواقف السيارات.
|
||||
</li>
|
||||
<li>
|
||||
<b>دائرة الآثار العامة (نظام ميجا الأردن - MEGA-Jordan):</b>
|
||||
تمتلك سجلاً مكانياً جغرافياً ضخماً يوثق أكثر من <b>15,000 موقع أثري و54,000 مكوّن موقعي</b>، مع مسار ترحيل جارٍ نحو منصة Arches v5 بالشراكة مع معهد جيتي وجامعة أكسفورد.
|
||||
<br>
|
||||
<i>العجز الفعلي:</i> نظام إداري ورقابي داخلي مغلق مخصص لحماية الآثار ومعاملات تراخيص الأبنية، واجهته العامة غير مستقرة، ومفصول كلياً عن خدمة الزوار أو تزويدهم بالملاحة.
|
||||
</li>
|
||||
<li>
|
||||
<b>قطاع السياحة والنقل والحركة اليومية:</b>
|
||||
جزر رقمية منفصلة (هيئة تنشيط السياحة، برنامج أردننا جنة، هيئة تنظيم النقل البري، الحرفيين في المحافظات) دون أي ترابط مع قواعد بيانات التراث.
|
||||
</li>
|
||||
</ol>
|
||||
</section>
|
||||
|
||||
<!-- Section 2 -->
|
||||
<section class="card">
|
||||
<h2>2. مصفوفة القيمة: تحويل العجز الحكومي إلى تفوق سيادي</h2>
|
||||
<p>مقارنة مباشرة بين الواقع التشغيلي الحالي وما تقدمه منصتنا السيادية كشريك تقني وطني:</p>
|
||||
|
||||
<div class="table-wrapper">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 25%;">المعيار التشغيلي الميداني</th>
|
||||
<th style="width: 35%;">الواقع الحكومي الحالي (تراثي / ميجا)</th>
|
||||
<th style="width: 40%;">حل منصة الخرائط السيادية (Map SaaS)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><b>الملاحة الميدانية ودقة الوصول</b></td>
|
||||
<td>إلقاء دبوس عشوائي في منتصف الموقع يقود السائح لجروف صخرية أو أسوار مغلقة.</td>
|
||||
<td><b>نظام التوجيه المزدوج:</b> ملاحة قيادة سيارات حتى موقف المركبات المعتمد، ثم ملاحة مشاة فورية نحو بوابة التذاكر والمسار الداخلي الآمن.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>العمل دون اتصال بالإنترنت</b></td>
|
||||
<td>منعدم كلياً؛ شلل كامل للخدمة في وادي رم، فينان، الكرك، البترا، وقلاع البادية عند انقطاع الشبكة الخلوية.</td>
|
||||
<td><b>حزم جغرافية محلية مسبقة التنزيل (Offline):</b> خرائط متجهات وتوجيه وتوثيق صوتي يعمل بنسبة 100% بنظام GPS بلا إنترنت.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>تكاليف التراخيص والسيادة الرقمية</b></td>
|
||||
<td>تبعية دولارية تصاعدية لواجهات خرائط جوجل (Google Maps APIs) ترهق موازنات الوزارات.</td>
|
||||
<td><b>بنية تحتية محلية مستقلة:</b> خوادم متجهات وطنية سيادية مستضافة محلياً تمنح الدولة استخداماً غير محدود بلا أي كلفة ترخيص أجنبية.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>تفعيل خريطة الحكايات</b></td>
|
||||
<td>تبويب نصي مكتبي ثابت يعتمد على قراءة المستخدم من شاشة الكمبيوتر.</td>
|
||||
<td><b>السياج الجغرافي الصوتي (Audio Geofencing):</b> تشغيل التسجيلات الصوتية النادرة لحكاية المكان تلقائياً بمجرد اقتراب السائح من المعلم دون لمس الشاشة.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>الربط بالنقل والتمكين الاقتصادي</b></td>
|
||||
<td>انفصال تام عن حركة المركبات والحافلات وأرزاق الحرفيين في القرى والمحافظات.</td>
|
||||
<td><b>تكامل حركي فوري:</b> زر مباشر لطلب وسيلة نقل نحو المعلم، وربط مشاغل الحرفيين بالدفع الرقمي المباشر.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Section 3 -->
|
||||
<section class="card">
|
||||
<h2>3. المعمارية الهندسية المتكاملة لربط المنظومات الوطنية</h2>
|
||||
<p>المخطط الهيكلي الذي يربط المحتوى التراثي لوزارة الثقافة مع سجلات الآثار ومنظومة الملاحة والنقل الذكي:</p>
|
||||
|
||||
<pre>
|
||||
┌──────────────────────────────────────┐ ┌─────────────────────────────────────┐
|
||||
│ وزارة الثقافة (منصة تراثي) │ │ دائرة الآثار العامة │
|
||||
│ المكنز (75 ألف مفردة) • السردية │ │ نظام MEGA-Jordan (15 ألف موقع) │
|
||||
│ أرشيف كاسيت السبعينيات النادر │ │ سجل Arches v5 │
|
||||
└──────────────────┬───────────────────┘ └──────────────────┬──────────────────┘
|
||||
│ │
|
||||
▼ ▼
|
||||
┌────────────────────────────────────────────────────────────┐
|
||||
│ الطبقة الجغرافية السيادية للتراث (Heritage Graph Engine) │
|
||||
│ محرك PostGIS • خادم بلاطات متجهة وطني • إحداثيات البوابات │
|
||||
│ معرف تراثي وطني موحد (Unified Heritage Place ID) │
|
||||
└────────────────────────────┬───────────────────────────────┘
|
||||
│
|
||||
┌───────────────┴───────────────┐
|
||||
▼ ▼
|
||||
┌─────────────────────────┐ ┌─────────────────────────┐
|
||||
│ تطبيق الهاتف الذكي السيادي│ │ بوابة النقل والتمكين │
|
||||
│ • توجيه مزدوج (سيارة+مشاة)│ │ • ربط حافلات وتطبيقات │
|
||||
│ • حزم تنزيل Offline │ │ • دعم الحرفيين والدفع │
|
||||
│ • سياج صوتي للحكايات │ │ • خفض تكلفة جوجل ($0) │
|
||||
└─────────────────────────┘ └─────────────────────────┘
|
||||
</pre>
|
||||
</section>
|
||||
|
||||
<!-- Section 4 -->
|
||||
<section class="card">
|
||||
<h2>4. نموذج هيكل البيانات المكاني المقترح (Data Schema)</h2>
|
||||
<p>هيكل بيانات موحد يربط أنظمة الدولة الثلاثة دون أي تضارب أو كشف للمواقع الأثرية الحساسة غير المفتوحة للزيارة:</p>
|
||||
|
||||
<pre>
|
||||
-- هيكل بيانات المعلم التراثي السيادي الموحد
|
||||
CREATE TABLE sovereign_heritage_asset (
|
||||
heritage_id VARCHAR(64) PRIMARY KEY, -- المعرف الوطني الموحد (مرتبط بـ MEGA و Turathi)
|
||||
site_name_ar VARCHAR(255) NOT NULL, -- الاسم الرسمي بالعربية
|
||||
site_name_en VARCHAR(255), -- الاسم بالإنجليزية
|
||||
governorate VARCHAR(64) NOT NULL, -- المحافظة (معان، مادبا، جرش، إلخ)
|
||||
category VARCHAR(64) NOT NULL, -- التصنيف (يونسكو، قلاع، متاحف، مسارات)
|
||||
|
||||
-- الفصل بين الإحداثيات الإدارية والملاحة الميدانية
|
||||
centroid_location GEOMETRY(Point, 4326), -- المركز الجغرافي العام
|
||||
parking_gate_location GEOMETRY(Point, 4326), -- موقع موقف السيارات ونقطة إنزال الركاب
|
||||
visitor_entrance_location GEOMETRY(Point, 4326), -- بوابة التذاكر ودخول المشاة الفعلي
|
||||
|
||||
-- الأصول الثقافية والصوتية الوطنية
|
||||
story_summary_60s TEXT, -- نبذة الـ 60 ثانية المركزة
|
||||
audio_narrative_url VARCHAR(512), -- تسجيلات أشرطة السبعينيات النادرة
|
||||
thesaurus_terms JSONB, -- مفردات المكنز التراثي الأردني المرتبطة بالمكان
|
||||
historical_era VARCHAR(128), -- الحقبة وفق مشروع السردية الأردنية
|
||||
|
||||
-- معايير السلامة والتشغيل
|
||||
is_offline_available BOOLEAN DEFAULT TRUE, -- هل المعلم مشمول بحزمة التنزيل المسبق
|
||||
requires_ticket BOOLEAN DEFAULT FALSE, -- هل يتطلب تذكرة دخول
|
||||
accessibility_level VARCHAR(32) -- مستوى المواءمة لذوي الإعاقة وكبار السن
|
||||
);
|
||||
</pre>
|
||||
</section>
|
||||
|
||||
<!-- Section 5 -->
|
||||
<section class="card">
|
||||
<h2>5. خطة العمل والشراكة الحكومية المقترحة</h2>
|
||||
|
||||
<div class="phase-card">
|
||||
<div class="phase-header">
|
||||
<strong>المرحلة الأولى: تقديم ورقة المفهوم المشتركة (Concept Note)</strong>
|
||||
<span class="badge">الشهر 1 - 2</span>
|
||||
</div>
|
||||
<p>عرض المذكرة التنفيذية على وزير الثقافة وأمين عام الوزارة ومدير عام الآثار العامة، والتأكيد على مبدأ التكامل السيادي: الوزارة توفر المحتوى وسردية الأرض والإنسان والاعتماد الرسمي، ونحن نوفر البنية المكانية ومحرك الخرائط المستقل.</p>
|
||||
</div>
|
||||
|
||||
<div class="phase-card">
|
||||
<div class="phase-header">
|
||||
<strong>المرحلة الثانية: إطلاق المسار التجريبي الريادي (Flagship PoC)</strong>
|
||||
<span class="badge">الشهر 3 - 5</span>
|
||||
</div>
|
||||
<p>اختيار مسار نموذجي مثل <b>مسار مادبا التراثي (10 محطات)</b> أو <b>مسار عجلون الزيتون (10 محطات)</b>، ورفع إحداثيات البوابات والمواقف الحقيقية، ودمج التسجيلات الصوتية الشعبية النادرة ومفردات المكنز داخل تطبيق الموبايل، وتشغيله أمام قيادات الوزارة وهيئة تنشيط السياحة بالكامل في وضع عدم الاتصال لإثبات الفارق التقني الميداني.</p>
|
||||
</div>
|
||||
|
||||
<div class="phase-card">
|
||||
<div class="phase-header">
|
||||
<strong>المرحلة الثالثة: التعميم الوطني والربط الاقتصادي</strong>
|
||||
<span class="badge">الشهر 6 - 12</span>
|
||||
</div>
|
||||
<p>شمول الـ 65 محطة المعتمدة في منصة تراثي، والتوسع تدريجياً ليشمل الـ 15 ألف موقع أثري المسجلة في نظام ميجا بصيغة سياحية آمنة، مع ربط خطوط النقل السياحي وتطبيقات التوصيل لإنعاش اقتصاد المحافظات ودعم الحرفيين المحليين.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- CTA Footer -->
|
||||
<div class="cta-footer">
|
||||
<h3>جاهزية المنظومة للتنفيذ الفوري</h3>
|
||||
<p>تمتلك منصتنا اليوم خوادم المتجهات الوطنية ومحرك التوجيه السيادي وتطبيق الموبايل الميداني الجاهز للتكامل مع قواعد بيانات الدولة الأردنية لخدمة أهداف التنمية والسيادة الرقمية.</p>
|
||||
<a href="https://map-saas.intaleqapp.com/sovereignty-brief.html" class="btn-gold">
|
||||
<span>معاينة العرض التنفيذي للسيادة المكانية</span>
|
||||
<span>←</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,501 @@
|
||||
<!doctype html>
|
||||
<html lang="ar" dir="rtl">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>المنظومة الوطنية للسيادة المكانية والملاحة التراثية | مذكرة الطرح الاستراتيجي</title>
|
||||
<meta name="description" content="مذكرة استراتيجية وهندسية موجهة لوزارة الثقافة ودائرة الآثار العامة لتحويل التراث الأردني إلى منظومة ملاحة مكانية سيادية تعمل بدون إنترنت.">
|
||||
|
||||
<!-- Fonts -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Tajawal:wght@400;500;700;800;900&family=Plus+Jakarta+Sans:wght@500;700;800&family=JetBrains+Mono:wght@500&display=swap" rel="stylesheet">
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--navy: #061d42;
|
||||
--navy-light: #0d2e61;
|
||||
--gold: #b59247;
|
||||
--gold-light: #e5cb8e;
|
||||
--gold-bg: rgba(181, 146, 71, 0.08);
|
||||
--ink: #102238;
|
||||
--muted: #596b82;
|
||||
--pale: #f4f7fb;
|
||||
--line: #dbe4ef;
|
||||
--white: #ffffff;
|
||||
--font: 'Tajawal', -apple-system, BlinkMacSystemFont, 'Segoe UI', Tahoma, sans-serif;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
html { scroll-behavior: smooth; }
|
||||
body {
|
||||
font-family: var(--font);
|
||||
background-color: var(--pale);
|
||||
color: var(--ink);
|
||||
line-height: 1.85;
|
||||
font-size: 17px;
|
||||
direction: rtl;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1080px;
|
||||
margin: 0 auto;
|
||||
padding: 40px 24px 80px;
|
||||
}
|
||||
|
||||
/* Header & Branding */
|
||||
header {
|
||||
background: var(--navy);
|
||||
color: var(--white);
|
||||
border-radius: 24px;
|
||||
padding: 50px 45px;
|
||||
margin-bottom: 40px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 20px 45px rgba(6, 29, 66, 0.15);
|
||||
}
|
||||
header::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -120px;
|
||||
left: -80px;
|
||||
width: 400px;
|
||||
height: 400px;
|
||||
background: radial-gradient(circle, rgba(181, 146, 71, 0.25), transparent 70%);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.tagline {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
background: rgba(181, 146, 71, 0.15);
|
||||
border: 1px solid rgba(181, 146, 71, 0.35);
|
||||
color: var(--gold-light);
|
||||
padding: 6px 16px;
|
||||
border-radius: 30px;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: clamp(28px, 4vw, 42px);
|
||||
font-weight: 900;
|
||||
line-height: 1.3;
|
||||
margin-bottom: 12px;
|
||||
color: var(--white);
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: clamp(18px, 2.2vw, 22px);
|
||||
color: #b8ccdf;
|
||||
font-weight: 500;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.meta-bar {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
||||
gap: 16px;
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.15);
|
||||
padding-top: 25px;
|
||||
font-size: 14px;
|
||||
}
|
||||
.meta-item b {
|
||||
color: var(--gold-light);
|
||||
display: block;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.meta-item span {
|
||||
color: #e2ebf4;
|
||||
}
|
||||
|
||||
/* Content Cards */
|
||||
.card {
|
||||
background: var(--white);
|
||||
border-radius: 20px;
|
||||
border: 1px solid var(--line);
|
||||
padding: 40px;
|
||||
margin-bottom: 35px;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.03);
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 26px;
|
||||
font-weight: 800;
|
||||
color: var(--navy);
|
||||
margin-bottom: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
h2::before {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
width: 6px;
|
||||
height: 26px;
|
||||
background: var(--gold);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
color: var(--navy-light);
|
||||
margin: 24px 0 12px;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: 16px;
|
||||
color: var(--ink);
|
||||
}
|
||||
|
||||
.highlight-box {
|
||||
background: var(--gold-bg);
|
||||
border-right: 4px solid var(--gold);
|
||||
padding: 20px 24px;
|
||||
border-radius: 12px;
|
||||
margin: 24px 0;
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
color: var(--navy);
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
/* Table Styling */
|
||||
.table-wrapper {
|
||||
overflow-x: auto;
|
||||
margin: 25px 0;
|
||||
border-radius: 14px;
|
||||
border: 1px solid var(--line);
|
||||
}
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
text-align: right;
|
||||
font-size: 15px;
|
||||
}
|
||||
th {
|
||||
background: var(--navy);
|
||||
color: var(--white);
|
||||
padding: 16px 20px;
|
||||
font-weight: 800;
|
||||
font-size: 16px;
|
||||
}
|
||||
td {
|
||||
padding: 16px 20px;
|
||||
border-bottom: 1px solid var(--line);
|
||||
color: var(--ink);
|
||||
}
|
||||
tr:nth-child(even) td {
|
||||
background: #fafbfd;
|
||||
}
|
||||
tr:hover td {
|
||||
background: #f1f6fb;
|
||||
}
|
||||
|
||||
/* Code & Architecture */
|
||||
pre {
|
||||
background: var(--navy);
|
||||
color: #d1e2f7;
|
||||
padding: 24px;
|
||||
border-radius: 14px;
|
||||
overflow-x: auto;
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 13.5px;
|
||||
direction: ltr;
|
||||
text-align: left;
|
||||
margin: 20px 0;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* List styles */
|
||||
ul, ol {
|
||||
margin: 16px 24px 24px 0;
|
||||
}
|
||||
li {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
/* Badges & Steps */
|
||||
.badge {
|
||||
display: inline-block;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
padding: 3px 10px;
|
||||
border-radius: 20px;
|
||||
background: #e9f0f9;
|
||||
color: #1a4f8b;
|
||||
}
|
||||
|
||||
.phase-card {
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 14px;
|
||||
padding: 24px;
|
||||
margin-bottom: 16px;
|
||||
background: #fafbfd;
|
||||
transition: all .2s;
|
||||
}
|
||||
.phase-card:hover {
|
||||
border-color: var(--gold);
|
||||
box-shadow: 0 8px 24px rgba(6, 29, 66, 0.06);
|
||||
}
|
||||
.phase-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.phase-header strong {
|
||||
font-size: 18px;
|
||||
color: var(--navy);
|
||||
}
|
||||
|
||||
/* Action Footer */
|
||||
.cta-footer {
|
||||
background: var(--navy);
|
||||
color: var(--white);
|
||||
border-radius: 20px;
|
||||
padding: 35px;
|
||||
text-align: center;
|
||||
margin-top: 50px;
|
||||
}
|
||||
.cta-footer h3 {
|
||||
color: var(--gold-light);
|
||||
font-size: 24px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.cta-footer p {
|
||||
color: #cfddec;
|
||||
max-width: 750px;
|
||||
margin: 0 auto 20px;
|
||||
}
|
||||
.btn-gold {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
background: var(--gold);
|
||||
color: var(--navy);
|
||||
font-weight: 800;
|
||||
padding: 14px 32px;
|
||||
border-radius: 10px;
|
||||
text-decoration: none;
|
||||
transition: all .2s;
|
||||
}
|
||||
.btn-gold:hover {
|
||||
background: var(--gold-light);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="container">
|
||||
|
||||
<!-- Header -->
|
||||
<header>
|
||||
<div class="tagline">مبادرة وطنية للسيادة المكانية والتراث الرقمي</div>
|
||||
<h1>المنظومة الوطنية للسيادة المكانية والملاحة التراثية</h1>
|
||||
<div class="subtitle">مذكرة الطرح الاستراتيجي والمعماري الموحد للشراكة الحكومية</div>
|
||||
|
||||
<div class="meta-bar">
|
||||
<div class="meta-item">
|
||||
<b>المُعدّ والخبير الاستراتيجي:</b>
|
||||
<span>حمزة عايد (Founding Tech Architect & Mobility Strategist)</span>
|
||||
</div>
|
||||
<div class="meta-item">
|
||||
<b>المرجع والتوثيق:</b>
|
||||
<span>التدقيق الاستقصائي المقارن لبيانات التراث الأردني (سبتمبر 2026)</span>
|
||||
</div>
|
||||
<div class="meta-item">
|
||||
<b>الجهات الشريكة المستهدفة:</b>
|
||||
<span>وزارة الثقافة · دائرة الآثار العامة · وزارة السياحة · هيئة النقل البري</span>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Section 1 -->
|
||||
<section class="card">
|
||||
<h2>1. الملخص التنفيذي وقراءة الفجوة الوطنية</h2>
|
||||
|
||||
<div class="highlight-box">
|
||||
"تمتلك المملكة الأردنية الهاشمية أثمن محتوى تراثي وأضخم قاعدة بيانات أثرية موثقة في المنطقة، لكنها تعاني من عجز ميداني كامل في نقل هذه البيانات من الرفوف الرقمية إلى حركة الملاحة الواقعية".
|
||||
</div>
|
||||
|
||||
<p>
|
||||
كشف التدقيق الاستقصائي المشترك للمنصات الرقمية الأردنية القائمة عن انفصال حاد بين ثلاث جزر مؤسسية تعمل كل منها بمعزل عن الأخرى:
|
||||
</p>
|
||||
|
||||
<ol>
|
||||
<li>
|
||||
<b>وزارة الثقافة (منصة تراثي ومشاريع التوثيق):</b>
|
||||
تمتلك رصيداً معرفياً هائلاً يشمل <b>75,000 مفردة</b> موثقة في 5 مجلدات علمية بـ "مكنز التراث الشعبي الأردني"، ومئات الساعات الصوتية النادرة لأشرطة الكاسيت المسجلة في السبعينيات، و6 مسارات تشمل 65 محطة، ومشروع السردية الأردنية.
|
||||
<br>
|
||||
<i>العجز الفعلي:</i> غياب تام لتطبيق هواتف ذكية رسمي (موقع ويب فقط)، انعدام الملاحة والتوجيه المنعطف بمنعطف، عجز كامل عن العمل دون اتصال بالإنترنت (Offline)، واعتماد دبابيس عشوائية تسقط في المركز الهندسي للمعلم بدلاً من بوابات الدخول ومواقف السيارات.
|
||||
</li>
|
||||
<li>
|
||||
<b>دائرة الآثار العامة (نظام ميجا الأردن - MEGA-Jordan):</b>
|
||||
تمتلك سجلاً مكانياً جغرافياً ضخماً يوثق أكثر من <b>15,000 موقع أثري و54,000 مكوّن موقعي</b>، مع مسار ترحيل جارٍ نحو منصة Arches v5 بالشراكة مع معهد جيتي وجامعة أكسفورد.
|
||||
<br>
|
||||
<i>العجز الفعلي:</i> نظام إداري ورقابي داخلي مغلق مخصص لحماية الآثار ومعاملات تراخيص الأبنية، واجهته العامة غير مستقرة، ومفصول كلياً عن خدمة الزوار أو تزويدهم بالملاحة.
|
||||
</li>
|
||||
<li>
|
||||
<b>قطاع السياحة والنقل والحركة اليومية:</b>
|
||||
جزر رقمية منفصلة (هيئة تنشيط السياحة، برنامج أردننا جنة، هيئة تنظيم النقل البري، الحرفيين في المحافظات) دون أي ترابط مع قواعد بيانات التراث.
|
||||
</li>
|
||||
</ol>
|
||||
</section>
|
||||
|
||||
<!-- Section 2 -->
|
||||
<section class="card">
|
||||
<h2>2. مصفوفة القيمة: تحويل العجز الحكومي إلى تفوق سيادي</h2>
|
||||
<p>مقارنة مباشرة بين الواقع التشغيلي الحالي وما تقدمه منصتنا السيادية كشريك تقني وطني:</p>
|
||||
|
||||
<div class="table-wrapper">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 25%;">المعيار التشغيلي الميداني</th>
|
||||
<th style="width: 35%;">الواقع الحكومي الحالي (تراثي / ميجا)</th>
|
||||
<th style="width: 40%;">حل منصة الخرائط السيادية (Map SaaS)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><b>الملاحة الميدانية ودقة الوصول</b></td>
|
||||
<td>إلقاء دبوس عشوائي في منتصف الموقع يقود السائح لجروف صخرية أو أسوار مغلقة.</td>
|
||||
<td><b>نظام التوجيه المزدوج:</b> ملاحة قيادة سيارات حتى موقف المركبات المعتمد، ثم ملاحة مشاة فورية نحو بوابة التذاكر والمسار الداخلي الآمن.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>العمل دون اتصال بالإنترنت</b></td>
|
||||
<td>منعدم كلياً؛ شلل كامل للخدمة في وادي رم، فينان، الكرك، البترا، وقلاع البادية عند انقطاع الشبكة الخلوية.</td>
|
||||
<td><b>حزم جغرافية محلية مسبقة التنزيل (Offline):</b> خرائط متجهات وتوجيه وتوثيق صوتي يعمل بنسبة 100% بنظام GPS بلا إنترنت.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>تكاليف التراخيص والسيادة الرقمية</b></td>
|
||||
<td>تبعية دولارية تصاعدية لواجهات خرائط جوجل (Google Maps APIs) ترهق موازنات الوزارات.</td>
|
||||
<td><b>بنية تحتية محلية مستقلة:</b> خوادم متجهات وطنية سيادية مستضافة محلياً تمنح الدولة استخداماً غير محدود بلا أي كلفة ترخيص أجنبية.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>تفعيل خريطة الحكايات</b></td>
|
||||
<td>تبويب نصي مكتبي ثابت يعتمد على قراءة المستخدم من شاشة الكمبيوتر.</td>
|
||||
<td><b>السياج الجغرافي الصوتي (Audio Geofencing):</b> تشغيل التسجيلات الصوتية النادرة لحكاية المكان تلقائياً بمجرد اقتراب السائح من المعلم دون لمس الشاشة.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>الربط بالنقل والتمكين الاقتصادي</b></td>
|
||||
<td>انفصال تام عن حركة المركبات والحافلات وأرزاق الحرفيين في القرى والمحافظات.</td>
|
||||
<td><b>تكامل حركي فوري:</b> زر مباشر لطلب وسيلة نقل نحو المعلم، وربط مشاغل الحرفيين بالدفع الرقمي المباشر.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Section 3 -->
|
||||
<section class="card">
|
||||
<h2>3. المعمارية الهندسية المتكاملة لربط المنظومات الوطنية</h2>
|
||||
<p>المخطط الهيكلي الذي يربط المحتوى التراثي لوزارة الثقافة مع سجلات الآثار ومنظومة الملاحة والنقل الذكي:</p>
|
||||
|
||||
<pre>
|
||||
┌──────────────────────────────────────┐ ┌─────────────────────────────────────┐
|
||||
│ وزارة الثقافة (منصة تراثي) │ │ دائرة الآثار العامة │
|
||||
│ المكنز (75 ألف مفردة) • السردية │ │ نظام MEGA-Jordan (15 ألف موقع) │
|
||||
│ أرشيف كاسيت السبعينيات النادر │ │ سجل Arches v5 │
|
||||
└──────────────────┬───────────────────┘ └──────────────────┬──────────────────┘
|
||||
│ │
|
||||
▼ ▼
|
||||
┌────────────────────────────────────────────────────────────┐
|
||||
│ الطبقة الجغرافية السيادية للتراث (Heritage Graph Engine) │
|
||||
│ محرك PostGIS • خادم بلاطات متجهة وطني • إحداثيات البوابات │
|
||||
│ معرف تراثي وطني موحد (Unified Heritage Place ID) │
|
||||
└────────────────────────────┬───────────────────────────────┘
|
||||
│
|
||||
┌───────────────┴───────────────┐
|
||||
▼ ▼
|
||||
┌─────────────────────────┐ ┌─────────────────────────┐
|
||||
│ تطبيق الهاتف الذكي السيادي│ │ بوابة النقل والتمكين │
|
||||
│ • توجيه مزدوج (سيارة+مشاة)│ │ • ربط حافلات وتطبيقات │
|
||||
│ • حزم تنزيل Offline │ │ • دعم الحرفيين والدفع │
|
||||
│ • سياج صوتي للحكايات │ │ • خفض تكلفة جوجل ($0) │
|
||||
└─────────────────────────┘ └─────────────────────────┘
|
||||
</pre>
|
||||
</section>
|
||||
|
||||
<!-- Section 4 -->
|
||||
<section class="card">
|
||||
<h2>4. نموذج هيكل البيانات المكاني المقترح (Data Schema)</h2>
|
||||
<p>هيكل بيانات موحد يربط أنظمة الدولة الثلاثة دون أي تضارب أو كشف للمواقع الأثرية الحساسة غير المفتوحة للزيارة:</p>
|
||||
|
||||
<pre>
|
||||
-- هيكل بيانات المعلم التراثي السيادي الموحد
|
||||
CREATE TABLE sovereign_heritage_asset (
|
||||
heritage_id VARCHAR(64) PRIMARY KEY, -- المعرف الوطني الموحد (مرتبط بـ MEGA و Turathi)
|
||||
site_name_ar VARCHAR(255) NOT NULL, -- الاسم الرسمي بالعربية
|
||||
site_name_en VARCHAR(255), -- الاسم بالإنجليزية
|
||||
governorate VARCHAR(64) NOT NULL, -- المحافظة (معان، مادبا، جرش، إلخ)
|
||||
category VARCHAR(64) NOT NULL, -- التصنيف (يونسكو، قلاع، متاحف، مسارات)
|
||||
|
||||
-- الفصل بين الإحداثيات الإدارية والملاحة الميدانية
|
||||
centroid_location GEOMETRY(Point, 4326), -- المركز الجغرافي العام
|
||||
parking_gate_location GEOMETRY(Point, 4326), -- موقع موقف السيارات ونقطة إنزال الركاب
|
||||
visitor_entrance_location GEOMETRY(Point, 4326), -- بوابة التذاكر ودخول المشاة الفعلي
|
||||
|
||||
-- الأصول الثقافية والصوتية الوطنية
|
||||
story_summary_60s TEXT, -- نبذة الـ 60 ثانية المركزة
|
||||
audio_narrative_url VARCHAR(512), -- تسجيلات أشرطة السبعينيات النادرة
|
||||
thesaurus_terms JSONB, -- مفردات المكنز التراثي الأردني المرتبطة بالمكان
|
||||
historical_era VARCHAR(128), -- الحقبة وفق مشروع السردية الأردنية
|
||||
|
||||
-- معايير السلامة والتشغيل
|
||||
is_offline_available BOOLEAN DEFAULT TRUE, -- هل المعلم مشمول بحزمة التنزيل المسبق
|
||||
requires_ticket BOOLEAN DEFAULT FALSE, -- هل يتطلب تذكرة دخول
|
||||
accessibility_level VARCHAR(32) -- مستوى المواءمة لذوي الإعاقة وكبار السن
|
||||
);
|
||||
</pre>
|
||||
</section>
|
||||
|
||||
<!-- Section 5 -->
|
||||
<section class="card">
|
||||
<h2>5. خطة العمل والشراكة الحكومية المقترحة</h2>
|
||||
|
||||
<div class="phase-card">
|
||||
<div class="phase-header">
|
||||
<strong>المرحلة الأولى: تقديم ورقة المفهوم المشتركة (Concept Note)</strong>
|
||||
<span class="badge">الشهر 1 - 2</span>
|
||||
</div>
|
||||
<p>عرض المذكرة التنفيذية على وزير الثقافة وأمين عام الوزارة ومدير عام الآثار العامة، والتأكيد على مبدأ التكامل السيادي: الوزارة توفر المحتوى وسردية الأرض والإنسان والاعتماد الرسمي، ونحن نوفر البنية المكانية ومحرك الخرائط المستقل.</p>
|
||||
</div>
|
||||
|
||||
<div class="phase-card">
|
||||
<div class="phase-header">
|
||||
<strong>المرحلة الثانية: إطلاق المسار التجريبي الريادي (Flagship PoC)</strong>
|
||||
<span class="badge">الشهر 3 - 5</span>
|
||||
</div>
|
||||
<p>اختيار مسار نموذجي مثل <b>مسار مادبا التراثي (10 محطات)</b> أو <b>مسار عجلون الزيتون (10 محطات)</b>، ورفع إحداثيات البوابات والمواقف الحقيقية، ودمج التسجيلات الصوتية الشعبية النادرة ومفردات المكنز داخل تطبيق الموبايل، وتشغيله أمام قيادات الوزارة وهيئة تنشيط السياحة بالكامل في وضع عدم الاتصال لإثبات الفارق التقني الميداني.</p>
|
||||
</div>
|
||||
|
||||
<div class="phase-card">
|
||||
<div class="phase-header">
|
||||
<strong>المرحلة الثالثة: التعميم الوطني والربط الاقتصادي</strong>
|
||||
<span class="badge">الشهر 6 - 12</span>
|
||||
</div>
|
||||
<p>شمول الـ 65 محطة المعتمدة في منصة تراثي، والتوسع تدريجياً ليشمل الـ 15 ألف موقع أثري المسجلة في نظام ميجا بصيغة سياحية آمنة، مع ربط خطوط النقل السياحي وتطبيقات التوصيل لإنعاش اقتصاد المحافظات ودعم الحرفيين المحليين.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- CTA Footer -->
|
||||
<div class="cta-footer">
|
||||
<h3>جاهزية المنظومة للتنفيذ الفوري</h3>
|
||||
<p>تمتلك منصتنا اليوم خوادم المتجهات الوطنية ومحرك التوجيه السيادي وتطبيق الموبايل الميداني الجاهز للتكامل مع قواعد بيانات الدولة الأردنية لخدمة أهداف التنمية والسيادة الرقمية.</p>
|
||||
<a href="https://map-saas.intaleqapp.com/sovereignty-brief.html" class="btn-gold">
|
||||
<span>معاينة العرض التنفيذي للسيادة المكانية</span>
|
||||
<span>←</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,675 @@
|
||||
{
|
||||
"version": 8,
|
||||
"name": "Intaleq Sovereign 3D Topographic & Terrain Style (خريطة التضاريس ثلاثية الأبعاد السيادية)",
|
||||
"metadata": {
|
||||
"brand": "Intaleq",
|
||||
"version": "3.5.0-terrain-3d",
|
||||
"description": "High-fidelity Sovereign 3D Topographic Style with DEM terrain displacement, dynamic hillshading, contour lines, and 3D architectural extrusions"
|
||||
},
|
||||
"center": [
|
||||
35.4337,
|
||||
29.5763
|
||||
],
|
||||
"zoom": 12.5,
|
||||
"pitch": 65,
|
||||
"bearing": -35,
|
||||
"glyphs": "https://cdn.protomaps.com/fonts/pbf/{fontstack}/{range}.pbf",
|
||||
"sources": {
|
||||
"terrain-dem": {
|
||||
"type": "raster-dem",
|
||||
"tiles": [
|
||||
"https://s3.amazonaws.com/elevation-tiles-prod/terrarium/{z}/{x}/{y}.png",
|
||||
"https://tiles.intaleqapp.com/raster_dem/{z}/{x}/{y}.png"
|
||||
],
|
||||
"encoding": "terrarium",
|
||||
"tileSize": 256,
|
||||
"maxzoom": 15
|
||||
},
|
||||
"esri-satellite": {
|
||||
"type": "raster",
|
||||
"tiles": [
|
||||
"https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}"
|
||||
],
|
||||
"tileSize": 256,
|
||||
"maxzoom": 18,
|
||||
"attribution": "© Esri, DigitalGlobe, GeoEye, Earthstar Geographics"
|
||||
},
|
||||
"opentopo-contours": {
|
||||
"type": "raster",
|
||||
"tiles": [
|
||||
"https://a.tile.opentopomap.org/{z}/{x}/{y}.png",
|
||||
"https://b.tile.opentopomap.org/{z}/{x}/{y}.png",
|
||||
"https://c.tile.opentopomap.org/{z}/{x}/{y}.png"
|
||||
],
|
||||
"tileSize": 256,
|
||||
"maxzoom": 17
|
||||
},
|
||||
"jordan_contours": {
|
||||
"type": "vector",
|
||||
"tiles": [
|
||||
"https://tiles.intaleqapp.com/jordan_contours/{z}/{x}/{y}"
|
||||
],
|
||||
"minzoom": 8,
|
||||
"maxzoom": 16
|
||||
},
|
||||
"local-osm-polygons": {
|
||||
"type": "vector",
|
||||
"tiles": [
|
||||
"https://tiles.intaleqapp.com/planet_osm_polygon/{z}/{x}/{y}"
|
||||
],
|
||||
"maxzoom": 14,
|
||||
"attribution": "© Intaleq | © OpenStreetMap contributors"
|
||||
},
|
||||
"local-osm-lines": {
|
||||
"type": "vector",
|
||||
"tiles": [
|
||||
"https://tiles.intaleqapp.com/planet_osm_line/{z}/{x}/{y}"
|
||||
],
|
||||
"maxzoom": 14
|
||||
},
|
||||
"local-osm-points": {
|
||||
"type": "vector",
|
||||
"tiles": [
|
||||
"https://tiles.intaleqapp.com/planet_osm_point/{z}/{x}/{y}"
|
||||
],
|
||||
"maxzoom": 14
|
||||
},
|
||||
"places_jordan": {
|
||||
"type": "vector",
|
||||
"tiles": [
|
||||
"https://tiles.intaleqapp.com/places_jordan/{z}/{x}/{y}"
|
||||
],
|
||||
"maxzoom": 14
|
||||
},
|
||||
"overture_buildings": {
|
||||
"type": "vector",
|
||||
"tiles": [
|
||||
"https://tiles.intaleqapp.com/overture_building/{z}/{x}/{y}"
|
||||
],
|
||||
"maxzoom": 16
|
||||
},
|
||||
"approved_roads": {
|
||||
"type": "vector",
|
||||
"tiles": [
|
||||
"https://tiles.intaleqapp.com/approved_roads/{z}/{x}/{y}"
|
||||
],
|
||||
"minzoom": 8,
|
||||
"maxzoom": 18
|
||||
}
|
||||
},
|
||||
"layers": [
|
||||
{
|
||||
"id": "topo-background",
|
||||
"type": "background",
|
||||
"paint": {
|
||||
"background-color": "#EFECE6"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "satellite-base-layer",
|
||||
"type": "raster",
|
||||
"source": "esri-satellite",
|
||||
"layout": {
|
||||
"visibility": "none"
|
||||
},
|
||||
"paint": {
|
||||
"raster-opacity": 0.95,
|
||||
"raster-saturation": 0.1
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "terrain-3d-hillshading",
|
||||
"type": "hillshade",
|
||||
"source": "terrain-dem",
|
||||
"layout": {
|
||||
"visibility": "visible"
|
||||
},
|
||||
"paint": {
|
||||
"hillshade-illumination-direction": 315,
|
||||
"hillshade-illumination-anchor": "viewport",
|
||||
"hillshade-shadow-color": "#261d15",
|
||||
"hillshade-highlight-color": "#fffbf2",
|
||||
"hillshade-accent-color": "#784a28",
|
||||
"hillshade-exaggeration": 0.75
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "natural-water-poly",
|
||||
"type": "fill",
|
||||
"source": "local-osm-polygons",
|
||||
"source-layer": "planet_osm_polygon",
|
||||
"filter": [
|
||||
"any",
|
||||
["==", "natural", "water"],
|
||||
["==", "water", "lake"],
|
||||
["==", "waterway", "riverbank"]
|
||||
],
|
||||
"paint": {
|
||||
"fill-color": "#0284c7",
|
||||
"fill-opacity": 0.85
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "natural-wood-forest",
|
||||
"type": "fill",
|
||||
"source": "local-osm-polygons",
|
||||
"source-layer": "planet_osm_polygon",
|
||||
"filter": [
|
||||
"any",
|
||||
["==", "natural", "wood"],
|
||||
["==", "landuse", "forest"],
|
||||
["==", "leisure", "nature_reserve"]
|
||||
],
|
||||
"paint": {
|
||||
"fill-color": "#4d7c0f",
|
||||
"fill-opacity": 0.25
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "natural-sand-dune",
|
||||
"type": "fill",
|
||||
"source": "local-osm-polygons",
|
||||
"source-layer": "planet_osm_polygon",
|
||||
"filter": [
|
||||
"any",
|
||||
["==", "natural", "sand"],
|
||||
["==", "natural", "desert"],
|
||||
["==", "natural", "scree"]
|
||||
],
|
||||
"paint": {
|
||||
"fill-color": "#ea580c",
|
||||
"fill-opacity": 0.12
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "wadis-waterways",
|
||||
"type": "line",
|
||||
"source": "local-osm-lines",
|
||||
"source-layer": "planet_osm_line",
|
||||
"filter": [
|
||||
"in",
|
||||
"waterway",
|
||||
"river",
|
||||
"stream",
|
||||
"canal",
|
||||
"wadi"
|
||||
],
|
||||
"paint": {
|
||||
"line-color": "#0284c7",
|
||||
"line-width": [
|
||||
"interpolate",
|
||||
["linear"],
|
||||
["zoom"],
|
||||
8, 1,
|
||||
14, 3.5
|
||||
],
|
||||
"line-opacity": 0.7
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "contours-raster-overlay",
|
||||
"type": "raster",
|
||||
"source": "opentopo-contours",
|
||||
"minzoom": 8,
|
||||
"maxzoom": 18,
|
||||
"layout": {
|
||||
"visibility": "visible"
|
||||
},
|
||||
"paint": {
|
||||
"raster-opacity": 0.55,
|
||||
"raster-contrast": 0.2
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "contour-minor-lines",
|
||||
"type": "line",
|
||||
"source": "jordan_contours",
|
||||
"source-layer": "jordan_contours",
|
||||
"minzoom": 11,
|
||||
"layout": {
|
||||
"visibility": "visible",
|
||||
"line-join": "round",
|
||||
"line-cap": "round"
|
||||
},
|
||||
"paint": {
|
||||
"line-color": "#a88358",
|
||||
"line-width": 0.75,
|
||||
"line-opacity": 0.5
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "contour-major-lines",
|
||||
"type": "line",
|
||||
"source": "jordan_contours",
|
||||
"source-layer": "jordan_contours",
|
||||
"minzoom": 9,
|
||||
"filter": [
|
||||
"==",
|
||||
"type",
|
||||
"index"
|
||||
],
|
||||
"layout": {
|
||||
"visibility": "visible",
|
||||
"line-join": "round",
|
||||
"line-cap": "round"
|
||||
},
|
||||
"paint": {
|
||||
"line-color": "#784a28",
|
||||
"line-width": 1.4,
|
||||
"line-opacity": 0.75
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "roads-casing",
|
||||
"type": "line",
|
||||
"source": "local-osm-lines",
|
||||
"source-layer": "planet_osm_line",
|
||||
"filter": [
|
||||
"in",
|
||||
"highway",
|
||||
"motorway",
|
||||
"trunk",
|
||||
"primary",
|
||||
"secondary"
|
||||
],
|
||||
"paint": {
|
||||
"line-color": "#ffffff",
|
||||
"line-width": [
|
||||
"interpolate",
|
||||
["linear"],
|
||||
["zoom"],
|
||||
8, 2,
|
||||
14, 6
|
||||
],
|
||||
"line-opacity": 0.9
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "roads-core-highways",
|
||||
"type": "line",
|
||||
"source": "local-osm-lines",
|
||||
"source-layer": "planet_osm_line",
|
||||
"filter": [
|
||||
"in",
|
||||
"highway",
|
||||
"motorway",
|
||||
"trunk"
|
||||
],
|
||||
"paint": {
|
||||
"line-color": "#d97706",
|
||||
"line-width": [
|
||||
"interpolate",
|
||||
["linear"],
|
||||
["zoom"],
|
||||
8, 1.2,
|
||||
14, 4.5
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "roads-core-primary",
|
||||
"type": "line",
|
||||
"source": "local-osm-lines",
|
||||
"source-layer": "planet_osm_line",
|
||||
"filter": [
|
||||
"in",
|
||||
"highway",
|
||||
"primary",
|
||||
"secondary"
|
||||
],
|
||||
"paint": {
|
||||
"line-color": "#f59e0b",
|
||||
"line-width": [
|
||||
"interpolate",
|
||||
["linear"],
|
||||
["zoom"],
|
||||
9, 1,
|
||||
14, 3.2
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "roads-core-minor",
|
||||
"type": "line",
|
||||
"source": "local-osm-lines",
|
||||
"source-layer": "planet_osm_line",
|
||||
"filter": [
|
||||
"in",
|
||||
"highway",
|
||||
"residential",
|
||||
"service",
|
||||
"unclassified",
|
||||
"living_street"
|
||||
],
|
||||
"minzoom": 13.5,
|
||||
"paint": {
|
||||
"line-color": "#ffffff",
|
||||
"line-width": [
|
||||
"interpolate",
|
||||
["linear"],
|
||||
["zoom"],
|
||||
13.5, 0.9,
|
||||
16, 2.5
|
||||
],
|
||||
"line-opacity": 0.8
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "road-labels-highways",
|
||||
"type": "symbol",
|
||||
"source": "local-osm-lines",
|
||||
"source-layer": "planet_osm_line",
|
||||
"filter": [
|
||||
"in",
|
||||
"highway",
|
||||
"motorway",
|
||||
"trunk",
|
||||
"primary"
|
||||
],
|
||||
"minzoom": 12,
|
||||
"layout": {
|
||||
"text-field": [
|
||||
"coalesce",
|
||||
["get", "name:ar"],
|
||||
["get", "name"],
|
||||
""
|
||||
],
|
||||
"text-font": [
|
||||
"Noto Sans Arabic Bold",
|
||||
"Open Sans Bold"
|
||||
],
|
||||
"text-size": [
|
||||
"interpolate",
|
||||
["linear"],
|
||||
["zoom"],
|
||||
12, 10,
|
||||
15, 12,
|
||||
18, 14
|
||||
],
|
||||
"symbol-placement": "line",
|
||||
"text-rotation-alignment": "map",
|
||||
"text-pitch-alignment": "map",
|
||||
"text-keep-upright": true,
|
||||
"text-letter-spacing": 0.05,
|
||||
"text-padding": 24,
|
||||
"symbol-spacing": 700,
|
||||
"text-max-angle": 20,
|
||||
"text-allow-overlap": false,
|
||||
"text-ignore-placement": false
|
||||
},
|
||||
"paint": {
|
||||
"text-color": "#1e293b",
|
||||
"text-halo-color": "rgba(255, 255, 255, 0.95)",
|
||||
"text-halo-width": 2.2
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "road-labels-major",
|
||||
"type": "symbol",
|
||||
"source": "local-osm-lines",
|
||||
"source-layer": "planet_osm_line",
|
||||
"filter": [
|
||||
"in",
|
||||
"highway",
|
||||
"secondary",
|
||||
"tertiary"
|
||||
],
|
||||
"minzoom": 13.5,
|
||||
"layout": {
|
||||
"text-field": [
|
||||
"coalesce",
|
||||
["get", "name:ar"],
|
||||
["get", "name"],
|
||||
""
|
||||
],
|
||||
"text-font": [
|
||||
"Noto Sans Arabic Bold",
|
||||
"Open Sans Bold"
|
||||
],
|
||||
"text-size": [
|
||||
"interpolate",
|
||||
["linear"],
|
||||
["zoom"],
|
||||
13.5, 9.5,
|
||||
16, 11,
|
||||
18, 13
|
||||
],
|
||||
"symbol-placement": "line",
|
||||
"text-rotation-alignment": "map",
|
||||
"text-pitch-alignment": "map",
|
||||
"text-keep-upright": true,
|
||||
"text-letter-spacing": 0.04,
|
||||
"text-padding": 20,
|
||||
"symbol-spacing": 600,
|
||||
"text-max-angle": 20,
|
||||
"text-allow-overlap": false,
|
||||
"text-ignore-placement": false
|
||||
},
|
||||
"paint": {
|
||||
"text-color": "#334155",
|
||||
"text-halo-color": "rgba(255, 255, 255, 0.9)",
|
||||
"text-halo-width": 2.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "road-labels-minor",
|
||||
"type": "symbol",
|
||||
"source": "local-osm-lines",
|
||||
"source-layer": "planet_osm_line",
|
||||
"filter": [
|
||||
"in",
|
||||
"highway",
|
||||
"residential",
|
||||
"unclassified"
|
||||
],
|
||||
"minzoom": 16,
|
||||
"layout": {
|
||||
"text-field": [
|
||||
"coalesce",
|
||||
["get", "name:ar"],
|
||||
["get", "name"],
|
||||
""
|
||||
],
|
||||
"text-font": [
|
||||
"Noto Sans Arabic Regular",
|
||||
"Open Sans Regular"
|
||||
],
|
||||
"text-size": [
|
||||
"interpolate",
|
||||
["linear"],
|
||||
["zoom"],
|
||||
16, 9.5,
|
||||
18, 11.5
|
||||
],
|
||||
"symbol-placement": "line",
|
||||
"text-rotation-alignment": "map",
|
||||
"text-pitch-alignment": "map",
|
||||
"text-keep-upright": true,
|
||||
"text-letter-spacing": 0.04,
|
||||
"text-padding": 20,
|
||||
"symbol-spacing": 600,
|
||||
"text-max-angle": 18,
|
||||
"text-allow-overlap": false,
|
||||
"text-ignore-placement": false
|
||||
},
|
||||
"paint": {
|
||||
"text-color": "#475569",
|
||||
"text-halo-color": "rgba(255, 255, 255, 0.9)",
|
||||
"text-halo-width": 1.8
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "approved-road-labels",
|
||||
"type": "symbol",
|
||||
"source": "approved_roads",
|
||||
"source-layer": "approved_roads",
|
||||
"minzoom": 16.5,
|
||||
"layout": {
|
||||
"text-field": [
|
||||
"coalesce",
|
||||
["get", "name:ar"],
|
||||
["get", "name"],
|
||||
""
|
||||
],
|
||||
"text-font": [
|
||||
"Noto Sans Arabic Bold",
|
||||
"Open Sans Bold"
|
||||
],
|
||||
"text-size": [
|
||||
"interpolate",
|
||||
["linear"],
|
||||
["zoom"],
|
||||
16.5, 9,
|
||||
18, 11.5
|
||||
],
|
||||
"symbol-placement": "line",
|
||||
"text-rotation-alignment": "map",
|
||||
"text-pitch-alignment": "map",
|
||||
"text-keep-upright": true,
|
||||
"text-letter-spacing": 0.05,
|
||||
"text-padding": 20,
|
||||
"symbol-spacing": 650,
|
||||
"text-max-angle": 20,
|
||||
"text-allow-overlap": false,
|
||||
"text-ignore-placement": false
|
||||
},
|
||||
"paint": {
|
||||
"text-color": "#1e293b",
|
||||
"text-halo-color": "rgba(255, 255, 255, 0.95)",
|
||||
"text-halo-width": 2.2
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "3d-buildings-ground-shadows",
|
||||
"type": "fill",
|
||||
"source": "overture_buildings",
|
||||
"source-layer": "overture_building",
|
||||
"minzoom": 13.5,
|
||||
"layout": {
|
||||
"visibility": "visible"
|
||||
},
|
||||
"paint": {
|
||||
"fill-color": "#0f172a",
|
||||
"fill-opacity": 0.45,
|
||||
"fill-translate": [12, 4],
|
||||
"fill-translate-anchor": "map"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "3d-buildings-extrusion",
|
||||
"type": "fill-extrusion",
|
||||
"source": "overture_buildings",
|
||||
"source-layer": "overture_building",
|
||||
"minzoom": 13.5,
|
||||
"layout": {
|
||||
"visibility": "visible"
|
||||
},
|
||||
"paint": {
|
||||
"fill-extrusion-color": [
|
||||
"interpolate",
|
||||
["linear"],
|
||||
["coalesce", ["get", "height"], 12],
|
||||
0, "#e7e5e4",
|
||||
30, "#d6d3d1",
|
||||
70, "#a8a29e",
|
||||
150, "#78716c"
|
||||
],
|
||||
"fill-extrusion-height": [
|
||||
"coalesce",
|
||||
["get", "height"],
|
||||
["*", ["coalesce", ["get", "num_floors"], 3], 3.5]
|
||||
],
|
||||
"fill-extrusion-base": 0,
|
||||
"fill-extrusion-opacity": 0.88
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "mountain-peaks-points",
|
||||
"type": "circle",
|
||||
"source": "local-osm-points",
|
||||
"source-layer": "planet_osm_point",
|
||||
"filter": [
|
||||
"==",
|
||||
"natural",
|
||||
"peak"
|
||||
],
|
||||
"minzoom": 10,
|
||||
"paint": {
|
||||
"circle-radius": 4.5,
|
||||
"circle-color": "#991b1b",
|
||||
"circle-stroke-width": 1.5,
|
||||
"circle-stroke-color": "#ffffff"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "mountain-peaks-labels",
|
||||
"type": "symbol",
|
||||
"source": "local-osm-points",
|
||||
"source-layer": "planet_osm_point",
|
||||
"filter": [
|
||||
"==",
|
||||
"natural",
|
||||
"peak"
|
||||
],
|
||||
"minzoom": 10,
|
||||
"layout": {
|
||||
"text-field": [
|
||||
"format",
|
||||
["coalesce", ["get", "name:ar"], ["get", "name"], "قمة جبلية"],
|
||||
{},
|
||||
"\n▲ ",
|
||||
{ "font-scale": 0.8 },
|
||||
["concat", ["coalesce", ["get", "ele"], ""], " م"],
|
||||
{ "font-scale": 0.75 }
|
||||
],
|
||||
"text-font": ["Noto Sans Arabic Regular", "Open Sans Regular"],
|
||||
"text-size": 11.5,
|
||||
"text-offset": [0, 1.2],
|
||||
"text-anchor": "top",
|
||||
"text-max-width": 10
|
||||
},
|
||||
"paint": {
|
||||
"text-color": "#451a03",
|
||||
"text-halo-color": "#ffffff",
|
||||
"text-halo-width": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "jordan-city-labels",
|
||||
"type": "symbol",
|
||||
"source": "places_jordan",
|
||||
"source-layer": "places_jordan",
|
||||
"filter": [
|
||||
"any",
|
||||
["==", "category", "city"],
|
||||
["==", "category", "town"],
|
||||
["==", "category", "governorate"],
|
||||
["==", "category", "capital"]
|
||||
],
|
||||
"minzoom": 6,
|
||||
"maxzoom": 11,
|
||||
"layout": {
|
||||
"text-field": ["coalesce", ["get", "name_ar"], ["get", "name"], ""],
|
||||
"text-font": ["Noto Sans Arabic Bold", "Open Sans Bold"],
|
||||
"text-size": [
|
||||
"interpolate",
|
||||
["linear"],
|
||||
["zoom"],
|
||||
6, 10,
|
||||
9, 12,
|
||||
11, 14
|
||||
],
|
||||
"text-padding": 24,
|
||||
"text-allow-overlap": false,
|
||||
"text-ignore-placement": false,
|
||||
"text-transform": "uppercase",
|
||||
"text-letter-spacing": 0.05
|
||||
},
|
||||
"paint": {
|
||||
"text-color": "#1c1917",
|
||||
"text-halo-color": "#fafaf9",
|
||||
"text-halo-width": 2.2
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,457 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ar" dir="rtl">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>خطة العمل التنفيذية والهندسية | سيرو ماب ونظام المرشدين المحليين</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Tajawal:wght@400;500;700;800;900&family=JetBrains+Mono:wght@400;600;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
:root {
|
||||
--bg-base: #0a0f1d;
|
||||
--bg-surface: #111827;
|
||||
--bg-card: #1e293b;
|
||||
--border-subtle: rgba(255, 255, 255, 0.08);
|
||||
--border-accent: rgba(56, 189, 248, 0.3);
|
||||
--text-primary: #f8fafc;
|
||||
--text-secondary: #94a3b8;
|
||||
--accent-cyan: #38bdf8;
|
||||
--accent-emerald: #10b981;
|
||||
--accent-gold: #f59e0b;
|
||||
--accent-purple: #a855f7;
|
||||
--accent-rose: #f43f5e;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--bg-base);
|
||||
color: var(--text-primary);
|
||||
font-family: 'Tajawal', sans-serif;
|
||||
line-height: 1.8;
|
||||
padding: 2.5rem 1.25rem;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1180px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
header {
|
||||
text-align: center;
|
||||
margin-bottom: 3rem;
|
||||
padding-bottom: 2rem;
|
||||
border-bottom: 1px solid var(--border-subtle);
|
||||
}
|
||||
|
||||
.badge {
|
||||
display: inline-block;
|
||||
padding: 0.35rem 1.2rem;
|
||||
background: rgba(56, 189, 248, 0.1);
|
||||
color: var(--accent-cyan);
|
||||
border: 1px solid rgba(56, 189, 248, 0.3);
|
||||
border-radius: 9999px;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 800;
|
||||
margin-bottom: 1rem;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2.3rem;
|
||||
font-weight: 900;
|
||||
color: #ffffff;
|
||||
margin-bottom: 0.75rem;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
color: var(--text-secondary);
|
||||
font-size: 1.1rem;
|
||||
max-width: 850px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.pillar-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
|
||||
gap: 1.5rem;
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
|
||||
.pillar-card {
|
||||
background: var(--bg-surface);
|
||||
border: 1px solid var(--border-subtle);
|
||||
border-radius: 14px;
|
||||
padding: 1.75rem;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.pillar-card.highlight {
|
||||
border-color: var(--accent-cyan);
|
||||
box-shadow: 0 10px 30px -10px rgba(56, 189, 248, 0.15);
|
||||
}
|
||||
|
||||
.pillar-icon {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
border-radius: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 1rem;
|
||||
font-weight: 900;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.pillar-title {
|
||||
font-size: 1.2rem;
|
||||
font-weight: 800;
|
||||
color: #ffffff;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.pillar-desc {
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 1.6rem;
|
||||
font-weight: 900;
|
||||
color: #ffffff;
|
||||
margin-bottom: 1.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
border-right: 4px solid var(--accent-cyan);
|
||||
padding-right: 0.75rem;
|
||||
}
|
||||
|
||||
.phase-box {
|
||||
background: var(--bg-surface);
|
||||
border: 1px solid var(--border-subtle);
|
||||
border-radius: 14px;
|
||||
padding: 2rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.phase-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 1.25rem;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.phase-num {
|
||||
background: rgba(16, 185, 129, 0.15);
|
||||
color: var(--accent-emerald);
|
||||
border: 1px solid rgba(16, 185, 129, 0.3);
|
||||
padding: 0.25rem 0.75rem;
|
||||
border-radius: 6px;
|
||||
font-weight: 800;
|
||||
font-size: 0.85rem;
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
}
|
||||
|
||||
.phase-name {
|
||||
font-size: 1.35rem;
|
||||
font-weight: 800;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.code-block {
|
||||
direction: ltr;
|
||||
text-align: left;
|
||||
background: #070b14;
|
||||
border: 1px solid var(--border-subtle);
|
||||
border-radius: 8px;
|
||||
padding: 1.25rem;
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 0.85rem;
|
||||
color: #38bdf8;
|
||||
overflow-x: auto;
|
||||
margin: 1.25rem 0;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 1.5rem 0;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
th, td {
|
||||
padding: 0.9rem 1rem;
|
||||
text-align: right;
|
||||
border-bottom: 1px solid var(--border-subtle);
|
||||
}
|
||||
|
||||
th {
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
color: var(--accent-cyan);
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
td {
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
td strong {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.badge-point {
|
||||
display: inline-block;
|
||||
padding: 0.2rem 0.6rem;
|
||||
background: rgba(245, 158, 11, 0.15);
|
||||
color: var(--accent-gold);
|
||||
border-radius: 6px;
|
||||
font-weight: 800;
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 1rem;
|
||||
margin-top: 3rem;
|
||||
}
|
||||
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.85rem 1.75rem;
|
||||
border-radius: 10px;
|
||||
font-weight: 800;
|
||||
text-decoration: none;
|
||||
font-size: 1rem;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: #0284c7;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background: #0369a1;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: var(--bg-card);
|
||||
color: var(--text-primary);
|
||||
border: 1px solid var(--border-subtle);
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: #334155;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="container">
|
||||
<header>
|
||||
<div class="badge">SIRO MAPS MASTER ROADMAP & ARCHITECTURE</div>
|
||||
<h1>خطة العمل التنفيذية والهندسية: منظومة سيرو ماب</h1>
|
||||
<p class="subtitle">
|
||||
بناء المسارات التراثية ونظام المرشدين المحليين (Local Guides) مع التدقيق اليدوي ومحرك التلعيب، مع ضمان العزل التام للخدمات التجارية القائمة.
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<!-- Operating Principles -->
|
||||
<div class="pillar-grid">
|
||||
<div class="pillar-card highlight">
|
||||
<div class="pillar-icon" style="background: rgba(56, 189, 248, 0.15); color: var(--accent-cyan);">1</div>
|
||||
<div class="pillar-title">الكتابة على الماك والبناء على السيرفر</div>
|
||||
<div class="pillar-desc">
|
||||
تطوير الكود واختباره محلياً في بيئة العمل الصارمة، ثم المزامنة التكتيكية مع خادم الإنتاج عبر سكريبت المزامنة الجاهز.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pillar-card highlight">
|
||||
<div class="pillar-icon" style="background: rgba(16, 185, 129, 0.15); color: var(--accent-emerald);">2</div>
|
||||
<div class="pillar-title">عزل تام لخدمات الإنتاج (Zero Risk)</div>
|
||||
<div class="pillar-desc">
|
||||
محرك الملاحة GraphHopper، والبحث الجغرافي B2B، وتتبع السائقين، والفوترة تبقى معزولة تماماً بنسبة 100% دون أي تعديل.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pillar-card highlight">
|
||||
<div class="pillar-icon" style="background: rgba(245, 158, 11, 0.15); color: var(--accent-gold);">3</div>
|
||||
<div class="pillar-title">التلعيب والتدقيق اليدوي الصارم</div>
|
||||
<div class="pillar-desc">
|
||||
نظام مرشدين وطني يمنح نقاطاً وأوسمة للمساهمات، ولكن لا يُنشر أي تعديل للخريطة إلا بعد موافقة المشرف من لوحة التحكم.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Phase 1 -->
|
||||
<div class="phase-box">
|
||||
<div class="phase-header">
|
||||
<div class="phase-name">المرحلة 1: البنية التحتية والمخططات المعزولة في PostGIS</div>
|
||||
<div class="phase-num">PHASE 01 : DATABASE</div>
|
||||
</div>
|
||||
<p style="color: var(--text-secondary); margin-bottom: 1rem;">
|
||||
إنشاء مخططي <code>heritage</code> و <code>community</code> داخل نفس قاعدة البيانات لتجنب استهلاك الذاكرة، مع ضمان سرعة الربط المكاني اللحظي عبر خادم مارتن للبلاطات الشعاعية.
|
||||
</p>
|
||||
|
||||
<div class="code-block">
|
||||
CREATE SCHEMA IF NOT EXISTS heritage;
|
||||
CREATE SCHEMA IF NOT EXISTS community;
|
||||
|
||||
-- جدول المعالم والبوابات الدقيقة
|
||||
CREATE TABLE heritage.landmarks (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name_ar VARCHAR(255) NOT NULL,
|
||||
category VARCHAR(100) NOT NULL,
|
||||
centroid_geom geometry(Point, 4326) NOT NULL,
|
||||
access_gate_geom geometry(Point, 4326),
|
||||
parking_geom geometry(Point, 4326),
|
||||
narrative_ar TEXT,
|
||||
audio_url VARCHAR(500)
|
||||
);
|
||||
|
||||
-- جدول مساهمات المرشدين الخاضعة للمراجعة
|
||||
CREATE TABLE community.contributions (
|
||||
id SERIAL PRIMARY KEY,
|
||||
guide_id INT,
|
||||
contribution_type VARCHAR(50), -- ADD_PLACE, CORRECT_STREET, CONFIRM_GATE
|
||||
suggested_data JSONB,
|
||||
suggested_geom geometry(Geometry, 4326),
|
||||
status VARCHAR(30) DEFAULT 'PENDING'
|
||||
);
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Phase 2 -->
|
||||
<div class="phase-box">
|
||||
<div class="phase-header">
|
||||
<div class="phase-name">المرحلة 2: الوحدات المستقلة في خادم التطبيقات (NestJS Modules)</div>
|
||||
<div class="phase-num">PHASE 02 : BACKEND API</div>
|
||||
</div>
|
||||
<p style="color: var(--text-secondary); margin-bottom: 1rem;">
|
||||
بناء موديولين معزولين تماماً داخل <code>apps/api</code>:
|
||||
</p>
|
||||
<ul style="color: var(--text-secondary); padding-right: 1.5rem; margin-bottom: 1.25rem;">
|
||||
<li><code>HeritageModule</code>: لخدمة المعالم، المسارات الستة، والملفات الصوتية.</li>
|
||||
<li><code>CommunityModule</code>: لإدارة نقاط المرشدين، لوحة المتصدرين، وتلقي المساهمات.</li>
|
||||
</ul>
|
||||
|
||||
<h4 style="color: var(--accent-gold); margin-top: 1.5rem; margin-bottom: 0.5rem;">جدول نقاط التلعيب المعتمد للمرشدين (Gamification Scorecard):</h4>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>نوع المساهمة الميدانية</th>
|
||||
<th>النقاط الممنوحة</th>
|
||||
<th>طريقة التدقيق</th>
|
||||
<th>الأثر الميداني</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><strong>تأكيد بوابة أو موقف سيارات</strong></td>
|
||||
<td><span class="badge-point">+50 PTS</span></td>
|
||||
<td>فحص جوي سريع بالأقمار الصناعية</td>
|
||||
<td>توجيه السيارات مباشرة للمدخل الحقيقي</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>إضافة معلم محلي أو تراثي جديد</strong></td>
|
||||
<td><span class="badge-point">+100 PTS</span></td>
|
||||
<td>تدقيق يدوي عبر لوحة المشرف</td>
|
||||
<td>إثراء الخريطة السيادية بمعالم غير متوفرة تجارياً</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>تصحيح اسم شارع أو اتجاه</strong></td>
|
||||
<td><span class="badge-point">+75 PTS</span></td>
|
||||
<td>فحص الاتصال بشبكة الطرق</td>
|
||||
<td>رفع جودة التوجيه الملاحي في القرى والبوادي</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>توثيق حكاية أو تسجيل صوتي تراثي</strong></td>
|
||||
<td><span class="badge-point">+150 PTS</span></td>
|
||||
<td>مراجعة محتوى وتوافق تاريخي</td>
|
||||
<td>ربط المكان بالسردية الوطنية الأردنية</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Phase 3 -->
|
||||
<div class="phase-box">
|
||||
<div class="phase-header">
|
||||
<div class="phase-name">المرحلة 3: لوحة التدقيق الإدارية الخفيفة (Moderation Console)</div>
|
||||
<div class="phase-num">PHASE 03 : ADMIN CONSOLE</div>
|
||||
</div>
|
||||
<p style="color: var(--text-secondary); margin-bottom: 1rem;">
|
||||
شاشة خفيفة وسريعة جداً مبنية داخل لوحة التحكم الحالية (Vanilla JS بدون مكتبات ثقيلة)، تمكن المشرف من:
|
||||
</p>
|
||||
<ul style="color: var(--text-secondary); padding-right: 1.5rem;">
|
||||
<li>استعراض المساهمات المعلقة ومقارنتها فوراً مع صور الأقمار الصناعية.</li>
|
||||
<li>الموافقة بضغطة زر واحدة (Approve) فتنزل النقطة فوراً في خريطة الإنتاج وتُصرف النقاط للمرشد.</li>
|
||||
<li>الرفض مع كتابة سبب تنبيهي للمرشد (Reject with Feedback) لحماية نقاوة البيانات.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Phase 4 -->
|
||||
<div class="phase-box">
|
||||
<div class="phase-header">
|
||||
<div class="phase-name">المرحلة 4: تكامل تطبيق الموبايل (Flutter - Siro Maps)</div>
|
||||
<div class="phase-num">PHASE 04 : MOBILE APP</div>
|
||||
</div>
|
||||
<p style="color: var(--text-secondary); margin-bottom: 1rem;">
|
||||
تفعيل الميزات التالية داخل كود <code>apps/siro_maps</code>:
|
||||
</p>
|
||||
<ul style="color: var(--text-secondary); padding-right: 1.5rem;">
|
||||
<li>مفتاح تفعيل طبقة المعالم والمسارات التراثية التكتيكية.</li>
|
||||
<li>واجهة "اقترح تعديلاً أو أضف مكاناً" مع التقاط الإحداثية الحالية للمستخدم.</li>
|
||||
<li>شاشة ملف المرشد الشخصي (رصيد النقاط، الرتبة، والأوسمة المكتسبة).</li>
|
||||
<li>دعم التخزين المحلي Offline Cache لضمان العمل في المناطق النائية.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Phase 5 -->
|
||||
<div class="phase-box">
|
||||
<div class="phase-header">
|
||||
<div class="phase-name">المرحلة 5: المزامنة، النشر والتحقق (Deployment & Testing)</div>
|
||||
<div class="phase-num">PHASE 05 : VERIFICATION</div>
|
||||
</div>
|
||||
<p style="color: var(--text-secondary); margin-bottom: 1rem;">
|
||||
دورة النشر الآمنة:
|
||||
</p>
|
||||
<div class="code-block">
|
||||
1. Run local integration tests on Mac
|
||||
2. bash sync_to_server.sh
|
||||
3. Execute database migration on map-db container
|
||||
4. Rebuild API container safely: docker compose up -d --build api
|
||||
5. Smoke test commercial B2B endpoints (Route, Geocode) -> Must return 200 OK
|
||||
6. Verify new Heritage & Community endpoints -> Must return 200 OK
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<a href="/architecture-diagram.html" class="btn btn-secondary">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M19 12H5M12 19l-7-7 7-7"/></svg>
|
||||
معاينة المخطط المعماري
|
||||
</a>
|
||||
<a href="/siro-maps.html" class="btn btn-primary">
|
||||
بوابة سيرو ماب التفاعلية
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M5 12h14M12 5l7 7-7 7"/></svg>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,457 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ar" dir="rtl">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>خطة العمل التنفيذية والهندسية | سيرو ماب ونظام المرشدين المحليين</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Tajawal:wght@400;500;700;800;900&family=JetBrains+Mono:wght@400;600;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
:root {
|
||||
--bg-base: #0a0f1d;
|
||||
--bg-surface: #111827;
|
||||
--bg-card: #1e293b;
|
||||
--border-subtle: rgba(255, 255, 255, 0.08);
|
||||
--border-accent: rgba(56, 189, 248, 0.3);
|
||||
--text-primary: #f8fafc;
|
||||
--text-secondary: #94a3b8;
|
||||
--accent-cyan: #38bdf8;
|
||||
--accent-emerald: #10b981;
|
||||
--accent-gold: #f59e0b;
|
||||
--accent-purple: #a855f7;
|
||||
--accent-rose: #f43f5e;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--bg-base);
|
||||
color: var(--text-primary);
|
||||
font-family: 'Tajawal', sans-serif;
|
||||
line-height: 1.8;
|
||||
padding: 2.5rem 1.25rem;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1180px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
header {
|
||||
text-align: center;
|
||||
margin-bottom: 3rem;
|
||||
padding-bottom: 2rem;
|
||||
border-bottom: 1px solid var(--border-subtle);
|
||||
}
|
||||
|
||||
.badge {
|
||||
display: inline-block;
|
||||
padding: 0.35rem 1.2rem;
|
||||
background: rgba(56, 189, 248, 0.1);
|
||||
color: var(--accent-cyan);
|
||||
border: 1px solid rgba(56, 189, 248, 0.3);
|
||||
border-radius: 9999px;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 800;
|
||||
margin-bottom: 1rem;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2.3rem;
|
||||
font-weight: 900;
|
||||
color: #ffffff;
|
||||
margin-bottom: 0.75rem;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
color: var(--text-secondary);
|
||||
font-size: 1.1rem;
|
||||
max-width: 850px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.pillar-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
|
||||
gap: 1.5rem;
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
|
||||
.pillar-card {
|
||||
background: var(--bg-surface);
|
||||
border: 1px solid var(--border-subtle);
|
||||
border-radius: 14px;
|
||||
padding: 1.75rem;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.pillar-card.highlight {
|
||||
border-color: var(--accent-cyan);
|
||||
box-shadow: 0 10px 30px -10px rgba(56, 189, 248, 0.15);
|
||||
}
|
||||
|
||||
.pillar-icon {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
border-radius: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 1rem;
|
||||
font-weight: 900;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.pillar-title {
|
||||
font-size: 1.2rem;
|
||||
font-weight: 800;
|
||||
color: #ffffff;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.pillar-desc {
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 1.6rem;
|
||||
font-weight: 900;
|
||||
color: #ffffff;
|
||||
margin-bottom: 1.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
border-right: 4px solid var(--accent-cyan);
|
||||
padding-right: 0.75rem;
|
||||
}
|
||||
|
||||
.phase-box {
|
||||
background: var(--bg-surface);
|
||||
border: 1px solid var(--border-subtle);
|
||||
border-radius: 14px;
|
||||
padding: 2rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.phase-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 1.25rem;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.phase-num {
|
||||
background: rgba(16, 185, 129, 0.15);
|
||||
color: var(--accent-emerald);
|
||||
border: 1px solid rgba(16, 185, 129, 0.3);
|
||||
padding: 0.25rem 0.75rem;
|
||||
border-radius: 6px;
|
||||
font-weight: 800;
|
||||
font-size: 0.85rem;
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
}
|
||||
|
||||
.phase-name {
|
||||
font-size: 1.35rem;
|
||||
font-weight: 800;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.code-block {
|
||||
direction: ltr;
|
||||
text-align: left;
|
||||
background: #070b14;
|
||||
border: 1px solid var(--border-subtle);
|
||||
border-radius: 8px;
|
||||
padding: 1.25rem;
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 0.85rem;
|
||||
color: #38bdf8;
|
||||
overflow-x: auto;
|
||||
margin: 1.25rem 0;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 1.5rem 0;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
th, td {
|
||||
padding: 0.9rem 1rem;
|
||||
text-align: right;
|
||||
border-bottom: 1px solid var(--border-subtle);
|
||||
}
|
||||
|
||||
th {
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
color: var(--accent-cyan);
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
td {
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
td strong {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.badge-point {
|
||||
display: inline-block;
|
||||
padding: 0.2rem 0.6rem;
|
||||
background: rgba(245, 158, 11, 0.15);
|
||||
color: var(--accent-gold);
|
||||
border-radius: 6px;
|
||||
font-weight: 800;
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 1rem;
|
||||
margin-top: 3rem;
|
||||
}
|
||||
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.85rem 1.75rem;
|
||||
border-radius: 10px;
|
||||
font-weight: 800;
|
||||
text-decoration: none;
|
||||
font-size: 1rem;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: #0284c7;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background: #0369a1;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: var(--bg-card);
|
||||
color: var(--text-primary);
|
||||
border: 1px solid var(--border-subtle);
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: #334155;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="container">
|
||||
<header>
|
||||
<div class="badge">SIRO MAPS MASTER ROADMAP & ARCHITECTURE</div>
|
||||
<h1>خطة العمل التنفيذية والهندسية: منظومة سيرو ماب</h1>
|
||||
<p class="subtitle">
|
||||
بناء المسارات التراثية ونظام المرشدين المحليين (Local Guides) مع التدقيق اليدوي ومحرك التلعيب، مع ضمان العزل التام للخدمات التجارية القائمة.
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<!-- Operating Principles -->
|
||||
<div class="pillar-grid">
|
||||
<div class="pillar-card highlight">
|
||||
<div class="pillar-icon" style="background: rgba(56, 189, 248, 0.15); color: var(--accent-cyan);">1</div>
|
||||
<div class="pillar-title">الكتابة على الماك والبناء على السيرفر</div>
|
||||
<div class="pillar-desc">
|
||||
تطوير الكود واختباره محلياً في بيئة العمل الصارمة، ثم المزامنة التكتيكية مع خادم الإنتاج عبر سكريبت المزامنة الجاهز.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pillar-card highlight">
|
||||
<div class="pillar-icon" style="background: rgba(16, 185, 129, 0.15); color: var(--accent-emerald);">2</div>
|
||||
<div class="pillar-title">عزل تام لخدمات الإنتاج (Zero Risk)</div>
|
||||
<div class="pillar-desc">
|
||||
محرك الملاحة GraphHopper، والبحث الجغرافي B2B، وتتبع السائقين، والفوترة تبقى معزولة تماماً بنسبة 100% دون أي تعديل.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pillar-card highlight">
|
||||
<div class="pillar-icon" style="background: rgba(245, 158, 11, 0.15); color: var(--accent-gold);">3</div>
|
||||
<div class="pillar-title">التلعيب والتدقيق اليدوي الصارم</div>
|
||||
<div class="pillar-desc">
|
||||
نظام مرشدين وطني يمنح نقاطاً وأوسمة للمساهمات، ولكن لا يُنشر أي تعديل للخريطة إلا بعد موافقة المشرف من لوحة التحكم.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Phase 1 -->
|
||||
<div class="phase-box">
|
||||
<div class="phase-header">
|
||||
<div class="phase-name">المرحلة 1: البنية التحتية والمخططات المعزولة في PostGIS</div>
|
||||
<div class="phase-num">PHASE 01 : DATABASE</div>
|
||||
</div>
|
||||
<p style="color: var(--text-secondary); margin-bottom: 1rem;">
|
||||
إنشاء مخططي <code>heritage</code> و <code>community</code> داخل نفس قاعدة البيانات لتجنب استهلاك الذاكرة، مع ضمان سرعة الربط المكاني اللحظي عبر خادم مارتن للبلاطات الشعاعية.
|
||||
</p>
|
||||
|
||||
<div class="code-block">
|
||||
CREATE SCHEMA IF NOT EXISTS heritage;
|
||||
CREATE SCHEMA IF NOT EXISTS community;
|
||||
|
||||
-- جدول المعالم والبوابات الدقيقة
|
||||
CREATE TABLE heritage.landmarks (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name_ar VARCHAR(255) NOT NULL,
|
||||
category VARCHAR(100) NOT NULL,
|
||||
centroid_geom geometry(Point, 4326) NOT NULL,
|
||||
access_gate_geom geometry(Point, 4326),
|
||||
parking_geom geometry(Point, 4326),
|
||||
narrative_ar TEXT,
|
||||
audio_url VARCHAR(500)
|
||||
);
|
||||
|
||||
-- جدول مساهمات المرشدين الخاضعة للمراجعة
|
||||
CREATE TABLE community.contributions (
|
||||
id SERIAL PRIMARY KEY,
|
||||
guide_id INT,
|
||||
contribution_type VARCHAR(50), -- ADD_PLACE, CORRECT_STREET, CONFIRM_GATE
|
||||
suggested_data JSONB,
|
||||
suggested_geom geometry(Geometry, 4326),
|
||||
status VARCHAR(30) DEFAULT 'PENDING'
|
||||
);
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Phase 2 -->
|
||||
<div class="phase-box">
|
||||
<div class="phase-header">
|
||||
<div class="phase-name">المرحلة 2: الوحدات المستقلة في خادم التطبيقات (NestJS Modules)</div>
|
||||
<div class="phase-num">PHASE 02 : BACKEND API</div>
|
||||
</div>
|
||||
<p style="color: var(--text-secondary); margin-bottom: 1rem;">
|
||||
بناء موديولين معزولين تماماً داخل <code>apps/api</code>:
|
||||
</p>
|
||||
<ul style="color: var(--text-secondary); padding-right: 1.5rem; margin-bottom: 1.25rem;">
|
||||
<li><code>HeritageModule</code>: لخدمة المعالم، المسارات الستة، والملفات الصوتية.</li>
|
||||
<li><code>CommunityModule</code>: لإدارة نقاط المرشدين، لوحة المتصدرين، وتلقي المساهمات.</li>
|
||||
</ul>
|
||||
|
||||
<h4 style="color: var(--accent-gold); margin-top: 1.5rem; margin-bottom: 0.5rem;">جدول نقاط التلعيب المعتمد للمرشدين (Gamification Scorecard):</h4>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>نوع المساهمة الميدانية</th>
|
||||
<th>النقاط الممنوحة</th>
|
||||
<th>طريقة التدقيق</th>
|
||||
<th>الأثر الميداني</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><strong>تأكيد بوابة أو موقف سيارات</strong></td>
|
||||
<td><span class="badge-point">+50 PTS</span></td>
|
||||
<td>فحص جوي سريع بالأقمار الصناعية</td>
|
||||
<td>توجيه السيارات مباشرة للمدخل الحقيقي</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>إضافة معلم محلي أو تراثي جديد</strong></td>
|
||||
<td><span class="badge-point">+100 PTS</span></td>
|
||||
<td>تدقيق يدوي عبر لوحة المشرف</td>
|
||||
<td>إثراء الخريطة السيادية بمعالم غير متوفرة تجارياً</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>تصحيح اسم شارع أو اتجاه</strong></td>
|
||||
<td><span class="badge-point">+75 PTS</span></td>
|
||||
<td>فحص الاتصال بشبكة الطرق</td>
|
||||
<td>رفع جودة التوجيه الملاحي في القرى والبوادي</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>توثيق حكاية أو تسجيل صوتي تراثي</strong></td>
|
||||
<td><span class="badge-point">+150 PTS</span></td>
|
||||
<td>مراجعة محتوى وتوافق تاريخي</td>
|
||||
<td>ربط المكان بالسردية الوطنية الأردنية</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Phase 3 -->
|
||||
<div class="phase-box">
|
||||
<div class="phase-header">
|
||||
<div class="phase-name">المرحلة 3: لوحة التدقيق الإدارية الخفيفة (Moderation Console)</div>
|
||||
<div class="phase-num">PHASE 03 : ADMIN CONSOLE</div>
|
||||
</div>
|
||||
<p style="color: var(--text-secondary); margin-bottom: 1rem;">
|
||||
شاشة خفيفة وسريعة جداً مبنية داخل لوحة التحكم الحالية (Vanilla JS بدون مكتبات ثقيلة)، تمكن المشرف من:
|
||||
</p>
|
||||
<ul style="color: var(--text-secondary); padding-right: 1.5rem;">
|
||||
<li>استعراض المساهمات المعلقة ومقارنتها فوراً مع صور الأقمار الصناعية.</li>
|
||||
<li>الموافقة بضغطة زر واحدة (Approve) فتنزل النقطة فوراً في خريطة الإنتاج وتُصرف النقاط للمرشد.</li>
|
||||
<li>الرفض مع كتابة سبب تنبيهي للمرشد (Reject with Feedback) لحماية نقاوة البيانات.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Phase 4 -->
|
||||
<div class="phase-box">
|
||||
<div class="phase-header">
|
||||
<div class="phase-name">المرحلة 4: تكامل تطبيق الموبايل (Flutter - Siro Maps)</div>
|
||||
<div class="phase-num">PHASE 04 : MOBILE APP</div>
|
||||
</div>
|
||||
<p style="color: var(--text-secondary); margin-bottom: 1rem;">
|
||||
تفعيل الميزات التالية داخل كود <code>apps/siro_maps</code>:
|
||||
</p>
|
||||
<ul style="color: var(--text-secondary); padding-right: 1.5rem;">
|
||||
<li>مفتاح تفعيل طبقة المعالم والمسارات التراثية التكتيكية.</li>
|
||||
<li>واجهة "اقترح تعديلاً أو أضف مكاناً" مع التقاط الإحداثية الحالية للمستخدم.</li>
|
||||
<li>شاشة ملف المرشد الشخصي (رصيد النقاط، الرتبة، والأوسمة المكتسبة).</li>
|
||||
<li>دعم التخزين المحلي Offline Cache لضمان العمل في المناطق النائية.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Phase 5 -->
|
||||
<div class="phase-box">
|
||||
<div class="phase-header">
|
||||
<div class="phase-name">المرحلة 5: المزامنة، النشر والتحقق (Deployment & Testing)</div>
|
||||
<div class="phase-num">PHASE 05 : VERIFICATION</div>
|
||||
</div>
|
||||
<p style="color: var(--text-secondary); margin-bottom: 1rem;">
|
||||
دورة النشر الآمنة:
|
||||
</p>
|
||||
<div class="code-block">
|
||||
1. Run local integration tests on Mac
|
||||
2. bash sync_to_server.sh
|
||||
3. Execute database migration on map-db container
|
||||
4. Rebuild API container safely: docker compose up -d --build api
|
||||
5. Smoke test commercial B2B endpoints (Route, Geocode) -> Must return 200 OK
|
||||
6. Verify new Heritage & Community endpoints -> Must return 200 OK
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<a href="/architecture-diagram.html" class="btn btn-secondary">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M19 12H5M12 19l-7-7 7-7"/></svg>
|
||||
معاينة المخطط المعماري
|
||||
</a>
|
||||
<a href="/siro-maps.html" class="btn btn-primary">
|
||||
بوابة سيرو ماب التفاعلية
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M5 12h14M12 5l7 7-7 7"/></svg>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,80 @@
|
||||
<!doctype html>
|
||||
<html lang="ar" dir="rtl">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="theme-color" content="#061d42">
|
||||
<meta name="description" content="سيرو ماب: خرائط سهلة بلا تسجيل للعراق والأردن وسوريا ومصر، ورؤية لدليل سياحي يبدأ من معالم العراق.">
|
||||
<title>سيرو ماب — كل طريق، بداية حكاية</title>
|
||||
<link rel="icon" type="image/png" href="assets/siro-maps.png">
|
||||
<style>
|
||||
@font-face {
|
||||
font-family: 'Tajawal';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(assets/tajawal-400.ttf) format('truetype');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Tajawal';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
src: url(assets/tajawal-500.ttf) format('truetype');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Tajawal';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url(assets/tajawal-700.ttf) format('truetype');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Tajawal';
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
font-display: swap;
|
||||
src: url(assets/tajawal-800.ttf) format('truetype');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Tajawal';
|
||||
font-style: normal;
|
||||
font-weight: 900;
|
||||
font-display: swap;
|
||||
src: url(assets/tajawal-900.ttf) format('truetype');
|
||||
}
|
||||
|
||||
:root{--navy:#061d42;--blue:#124f9f;--gold:#b59247;--pale:#edf3f9;--ink:#132844;--muted:#5c6c80;--line:#dce3ec;--white:#fff;--font:'Tajawal','Geeza Pro',Tahoma,sans-serif}
|
||||
*{box-sizing:border-box}html{scroll-behavior:smooth;scroll-padding-top:100px}body{margin:0;background:#fff;color:var(--ink);font-family:var(--font);font-size:18px;line-height:1.85}a{color:inherit;text-decoration:none}button{font:inherit}button,a{-webkit-tap-highlight-color:transparent}button:focus-visible,a:focus-visible{outline:3px solid #c6a057;outline-offset:6px}button{cursor:pointer}img{max-width:100%;height:auto;display:block}h1,h2,h3,p{margin:0}h1,h2,h3{line-height:1.35}h2{font-size:clamp(32px,3.5vw,49px);font-weight:800;letter-spacing:-.6px}h3{font-size:25px}::selection{background:#d6bb7b;color:var(--navy)}.wrap{width:min(1190px,calc(100% - 88px));margin:auto}.eyebrow{font-size:14px;letter-spacing:1.8px;font-weight:700;color:var(--gold);display:flex;align-items:center;gap:12px;margin-bottom:22px}.eyebrow:before{content:'';width:28px;height:2px;background:currentColor}.latin{font-family:Arial,sans-serif;letter-spacing:2.5px;font-size:12px;direction:ltr;display:inline-block}.muted{color:var(--muted)}
|
||||
header{height:100px;background:rgba(255,255,255,.97);border-bottom:1px solid var(--line);position:relative;z-index:5}.nav{height:100%;display:flex;align-items:center;justify-content:space-between;gap:24px}.brand{display:flex;gap:13px;align-items:center}.brand img{width:62px;height:62px;border-radius:15px}.brand strong{font-size:23px;display:block;line-height:1.3}.brand small{color:var(--muted);font-size:11px;letter-spacing:2.2px;display:block;direction:ltr}.links{display:flex;align-items:center;gap:30px;font-size:16px;font-weight:600}.links a{transition:color .2s}.links a:hover{color:var(--blue)}.nav-tag{font-size:14px;color:var(--blue);border:1px solid #c9d8ed;padding:7px 17px;border-radius:30px;white-space:nowrap}
|
||||
.hero{position:relative;overflow:hidden;background:var(--navy);color:white;padding:75px 0 0}.hero:before{content:'';position:absolute;width:740px;height:740px;left:-140px;top:-220px;background:radial-gradient(circle,rgba(39,105,180,.38),transparent 67%);pointer-events:none}.hero-grid{display:grid;grid-template-columns:1.12fr 1fr;align-items:center;gap:65px;position:relative;min-height:470px;padding-bottom:65px}.hero h1{font-size:clamp(44px,5.7vw,76px);font-weight:800;line-height:1.24;letter-spacing:-1px}.hero h1 span{color:#dbbc77}.hero p{font-size:20px;color:#c0ccdc;max-width:530px;margin-top:27px;line-height:1.9}.hero .eyebrow{color:#ddbd78}.hero-actions{display:flex;gap:14px;align-items:center;flex-wrap:wrap;margin-top:33px}.btn{display:inline-flex;align-items:center;justify-content:center;gap:20px;padding:11px 23px;border-radius:7px;font-size:17px;font-weight:700;border:1px solid transparent;min-height:50px}.btn-gold{background:#dbbc77;color:#0b2547}.btn-gold:hover{background:#efd49b}.btn-outline{border-color:#526580;color:#e3eaf3}.btn-outline:hover{background:#ffffff0c}.arrow{font-family:Arial;font-size:23px;font-weight:400}.hero-status{display:block;font-size:14px;color:#acbbcf;margin-top:17px}.brand-stage{position:relative;display:flex;justify-content:center;align-items:center;min-height:420px;padding:15px}.brand-stage:before,.brand-stage:after{content:'';position:absolute;border:1px solid #759ed12b;border-radius:50%;width:410px;height:410px;pointer-events:none}.brand-stage:after{width:480px;height:480px;border-style:dashed;opacity:.6}.hero-icon{width:330px;border-radius:70px;transform:rotate(-6deg);box-shadow:0 30px 60px #0004;z-index:1}.float-label{position:absolute;z-index:2;border:1px solid #6986af6b;background:#0b2b55eb;box-shadow:0 10px 35px #0002;backdrop-filter:blur(15px);padding:10px 17px;border-radius:10px;font-size:16px;display:flex;align-items:center;gap:12px}.float-label svg{width:22px;height:22px;stroke:#e0bf7d;fill:none;stroke-width:1.6}.label-one{left:-5px;top:60px}.label-two{right:4px;bottom:43px}.hero-facts{position:relative;border-top:1px solid #ffffff20;display:grid;grid-template-columns:repeat(3,1fr);padding:24px 0;gap:30px}.fact{display:flex;gap:17px;align-items:center}.fact:not(:last-child){border-left:1px solid #ffffff20}.fact b{font-size:35px;color:#dfc28a;font-weight:500;line-height:1}.fact span{display:block;font-size:17px}.fact small{display:block;color:#a8b8cd;font-size:14px}
|
||||
.story{padding:88px 0;display:grid;grid-template-columns:.9fr 1.1fr;gap:95px;align-items:center}.story p{color:var(--muted);font-size:20px}.story .accent{color:var(--ink);font-weight:700}.story .eyebrow{margin-bottom:16px}.section-intro{display:flex;align-items:flex-end;justify-content:space-between;gap:40px;margin-bottom:37px}.section-intro p{max-width:430px;color:var(--muted)}
|
||||
.coverage{background:var(--pale);padding:72px 0 78px}.country-tabs{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;margin-bottom:24px}.country-tab{display:flex;align-items:center;justify-content:space-between;padding:17px 22px;background:transparent;border:1px solid #cdd8e6;color:var(--ink);border-radius:8px;transition:background .2s;min-height:83px}.country-tab strong{font-size:23px}.country-tab small{display:block;font-size:12px;letter-spacing:2px;line-height:1.2;color:var(--muted);direction:ltr}.country-tab .number{font-family:Arial;font-size:14px;color:#8b9db3}.country-tab[aria-selected=true]{background:var(--navy);border-color:var(--navy);color:white}.country-tab[aria-selected=true] small,.country-tab[aria-selected=true] .number{color:#d8bd81}.country-panel{background:#fff;border-radius:12px;padding:27px 32px;display:grid;grid-template-columns:1fr 1.4fr;gap:35px;align-items:center;border:1px solid #e2e9f2}.country-panel h3{font-size:28px;margin-bottom:5px}.country-panel p{font-size:17px;color:var(--muted)}.panel-note{border-right:2px solid #c4a266;padding-right:24px}.pill{display:inline-block;font-size:13px;border:1px solid #d9e1ec;background:#f7f9fc;padding:2px 10px;border-radius:30px;color:#49617f;margin-bottom:9px}
|
||||
.tourism{padding:90px 0}.tourism-feature{display:grid;grid-template-columns:1.15fr 1fr;background:var(--navy);color:white;border-radius:18px;overflow:hidden;min-height:415px}.tourism-photo{position:relative;background:#15395e;min-height:380px;overflow:hidden}.tourism-photo img{width:100%;height:100%;object-fit:cover;position:absolute}.photo-caption{position:absolute;bottom:0;right:0;left:0;color:white;padding:45px 28px 22px;background:linear-gradient(transparent,#041a35e6);font-size:16px}.photo-caption small{display:block;opacity:.8;font-size:12px;direction:ltr;text-align:right}.tourism-copy{padding:42px 40px;align-self:center}.tourism-copy h3{font-size:35px;margin:12px 0 15px}.tourism-copy p{color:#beccdd}.tourism-copy .pill{background:#ffffff0c;border-color:#597087;color:#e2c388}.tourism-list{padding:0;list-style:none;display:grid;grid-template-columns:1fr 1fr;gap:13px;margin:25px 0 0;font-size:16px;color:#e7edf6}.tourism-list li:before{content:'+';color:#d8b975;padding-left:10px}.tourism-foot{display:flex;justify-content:space-between;gap:25px;margin-top:20px;font-size:15px;color:var(--muted)}.tourism-foot a{color:var(--blue);text-decoration:underline;text-underline-offset:4px}
|
||||
.ideas{background:#f7f9fc;padding:75px 0 82px}.ideas-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:0;border-top:1px solid var(--line);border-bottom:1px solid var(--line)}.idea{padding:32px 29px 30px}.idea:not(:last-child){border-left:1px solid var(--line)}.idea-index{font-size:13px;direction:ltr;display:block;text-align:right;color:#977837;letter-spacing:2px;margin-bottom:22px}.idea svg{width:34px;height:34px;fill:none;stroke:var(--blue);stroke-width:1.5;margin-bottom:15px}.idea h3{margin-bottom:13px;font-size:25px}.idea p{font-size:17px;color:var(--muted)}.benefit{margin-top:24px;padding-top:17px;border-top:1px solid var(--line);font-size:15px;color:var(--blue)}.ideas-note{font-size:14px;color:var(--muted);margin-top:20px}
|
||||
.roadmap{padding:90px 0}.steps{display:grid;grid-template-columns:repeat(3,1fr);gap:32px;margin-top:42px}.step{border-top:2px solid #dce3ec;padding-top:22px}.step.current{border-top-color:var(--gold)}.step-top{display:flex;align-items:center;justify-content:space-between;margin-bottom:14px}.step-top span{font-size:14px;color:#728096}.step-top b{font-size:28px;font-family:Arial;font-weight:400;color:#a7b4c4}.step.current .step-top span{color:#907132}.step h3{font-size:25px;margin-bottom:12px}.step p{color:var(--muted);font-size:17px}
|
||||
.partnership{border:1px solid var(--line);border-radius:15px;display:grid;grid-template-columns:160px 1fr;gap:40px;padding:32px 40px;align-items:center;margin-bottom:70px}.prize-logo{width:150px}.partnership .eyebrow{font-size:13px;margin-bottom:11px}.partnership h3{font-size:29px;margin-bottom:9px}.partnership p{font-size:18px;color:var(--muted);max-width:820px}.closing{background:var(--navy);color:white;text-align:center;padding:65px 24px}.closing .latin{color:#dbbc77;margin-bottom:15px}.closing h2{font-size:clamp(29px,4vw,46px)}.closing p{color:#acbed4;margin-top:15px;font-size:18px}.footer{padding:23px 0;display:flex;justify-content:space-between;align-items:center;gap:20px;font-size:14px;color:var(--muted)}.footer a{color:var(--blue)}.footer .latin{font-size:11px;letter-spacing:1px}
|
||||
@media(min-width:1500px){.hero-grid{min-height:530px}}
|
||||
@media(max-width:1050px){.wrap{width:calc(100% - 48px)}.links{gap:18px}.nav-tag{display:none}.hero-grid{gap:25px}.hero-icon{width:280px;border-radius:58px}.brand-stage:before{width:340px;height:340px}.brand-stage:after{width:390px;height:390px}.label-one{left:0;top:45px}.label-two{right:0;bottom:45px}.story{gap:45px}.tourism-copy{padding:30px}.idea{padding:28px 20px}.country-tab{padding:15px}.hero h1{font-size:58px}}
|
||||
@media(max-width:760px){body{font-size:17px}.wrap{width:calc(100% - 36px)}header{height:auto;min-height:86px}.nav{padding:13px 0;flex-wrap:wrap;gap:10px}.brand img{width:48px;height:48px}.brand strong{font-size:20px}.brand small{font-size:10px}.links{gap:19px;font-size:14px}.links a:last-child{display:none}.hero{padding-top:44px}.hero-grid{grid-template-columns:1fr;gap:23px;padding-bottom:35px}.hero h1{font-size:clamp(44px,10vw,62px);line-height:1.3}.hero p{font-size:18px;margin-top:20px}.hero-actions{margin-top:25px}.btn{font-size:16px;padding:10px 19px}.hero-status{font-size:13px}.brand-stage{min-height:325px;width:min(100%,390px);margin:auto}.hero-icon{width:240px;border-radius:52px}.brand-stage:before{width:285px;height:285px}.brand-stage:after{width:320px;height:320px}.float-label{font-size:14px;padding:8px 12px}.label-one{top:39px;left:0}.label-two{bottom:23px;right:0}.hero-facts{gap:15px;padding:20px 0}.fact{gap:8px;display:block}.fact b{font-size:27px}.fact span{font-size:14px}.fact small{font-size:12px}.story{grid-template-columns:1fr;gap:22px;padding:53px 0}.story p{font-size:18px}.eyebrow{font-size:13px;margin-bottom:15px}.section-intro{display:block;margin-bottom:27px}.section-intro p{margin-top:17px;font-size:17px}.coverage{padding:48px 0}.country-tabs{grid-template-columns:repeat(2,1fr);gap:9px}.country-tab{min-height:78px}.country-tab strong{font-size:22px}.country-panel{grid-template-columns:1fr;gap:18px;padding:24px}.panel-note{padding-right:16px}.tourism{padding:55px 0}.tourism-feature{grid-template-columns:1fr}.tourism-photo{min-height:280px}.tourism-copy{padding:29px 25px}.tourism-copy h3{font-size:30px}.tourism-foot{display:block;font-size:14px}.tourism-foot a{display:inline-block;margin-top:7px}.ideas{padding:50px 0}.ideas-grid{grid-template-columns:1fr}.idea{padding:28px 4px}.idea:not(:last-child){border-left:0;border-bottom:1px solid var(--line)}.idea-index{margin-bottom:14px}.idea svg{float:left}.benefit{margin-top:17px}.roadmap{padding:53px 0}.steps{grid-template-columns:1fr;gap:28px;margin-top:25px}.step{padding-top:14px}.step-top{margin-bottom:8px}.partnership{grid-template-columns:1fr;gap:18px;padding:26px;margin-bottom:45px}.prize-logo{width:120px}.partnership h3{font-size:25px}.partnership p{font-size:17px}.closing{padding:45px 22px}.footer{flex-direction:column;align-items:flex-start;gap:8px}.tourism-list{font-size:15px}}
|
||||
@media(prefers-reduced-motion:reduce){html{scroll-behavior:auto}*{transition:none!important}}
|
||||
@media print{header,.hero-actions,.hero-status,.country-tabs,.footer a{display:none}body{font-size:12pt}.hero,.closing,.tourism-feature{print-color-adjust:exact;-webkit-print-color-adjust:exact}.wrap{width:92%}.hero{padding-top:30px}.hero-grid{min-height:0;gap:25px}.hero h1{font-size:38px}.hero p{font-size:16px}.brand-stage{min-height:250px}.hero-icon{width:200px}.brand-stage:before,.brand-stage:after,.float-label{display:none}.story,.tourism,.ideas,.roadmap,.coverage{padding:25px 0}.section-intro h2,h2{font-size:28px}.story p{font-size:16px}.tourism-feature{break-inside:avoid}.idea,.step,.partnership{break-inside:avoid}.partnership{margin-bottom:25px}.closing{padding:25px}}
|
||||
</style>
|
||||
</head>
|
||||
<body id="top">
|
||||
<header><nav class="wrap nav" aria-label="التنقل الرئيسي"><a class="brand" href="#top" aria-label="سيرو ماب، بداية الصفحة"><img src="assets/siro-maps.png" alt="شعار سيرو ماب" width="62" height="62"><span><strong>سيرو ماب</strong><small>SIRO MAP</small></span></a><div class="links"><a href="#coverage">الانطلاقة</a><a href="#tourism">اكتشف العراق</a><a href="#ideas">آفاق سيرو ماب</a><a href="#journey">رحلتنا</a></div><span class="nav-tag">من سيرو ماب، إلى كل وجهة</span></nav></header>
|
||||
<main>
|
||||
<section class="hero" aria-labelledby="hero-title"><div class="wrap"><div class="hero-grid"><div><div class="eyebrow">الانطلاقة الأولى · سيرو ماب</div><h1 id="hero-title">كل طريق،<br>بداية <span>حكاية.</span></h1><p>خرائط تقرّب المسافات، وحكايات تقرّبنا من المكان.<br>نبدأ بأربع دول، ونفتح من العراق بابًا لاكتشاف الثقافة والمعالم والوجهات.</p><div class="hero-actions"><a class="btn btn-gold" href="#coverage">اكتشف الانطلاقة <span class="arrow" aria-hidden="true">←</span></a><a class="btn btn-outline" href="#ideas">ما الذي نبنيه معًا؟</a></div><small class="hero-status">نسخة التطبيق قيد المراجعة على Google Play</small></div><div class="brand-stage"><img class="hero-icon" src="assets/siro-maps.png" alt="شعار سيرو ماب الذكي للملاحة والتنقل" width="512" height="512"><span class="float-label label-one"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M20 10c0 6-8 11-8 11S4 16 4 10a8 8 0 1 1 16 0Z"/><circle cx="12" cy="10" r="2.5"/></svg>وجهتك أقرب</span><span class="float-label label-two"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="m3 5 6-2 6 2 6-2v16l-6 2-6-2-6 2Z M9 3v16 M15 5v16"/></svg>اكتشف حكاية المكان</span></div></div><div class="hero-facts"><div class="fact"><b>04</b><div><span>دول في الانطلاقة</span><small>العراق · الأردن · سوريا · مصر</small></div></div><div class="fact"><b>01</b><div><span>بداية للدليل السياحي</span><small>معالم العراق أولًا</small></div></div><div class="fact"><b>ببساطة</b><div><span>خرائط بلا تسجيل</span><small>افتح التطبيق، وابدأ رحلتك</small></div></div></div></div></section>
|
||||
<section class="wrap story" aria-labelledby="story-title"><div><div class="eyebrow">فكرة تجمعنا</div><h2 id="story-title">من اسم يحمل حضارة،<br>إلى خدمة ترافق الناس.</h2></div><p>تعتمد سيرو ماب على الابتكار والسيادة الرقمية وتوفير التكاليف. واليوم، نمنح هذا المشروع حضورًا قويًا في الحياة اليومية: خرائط سهلة ومستقلة، ودليل يكشف ما حولنا من وجهات ومعالم.<br><span class="accent">خطوة أولى لشراكتنا، وأساس ننمو عليه معًا.</span></p></section>
|
||||
<section class="coverage" id="coverage" aria-labelledby="coverage-title"><div class="wrap"><div class="section-intro"><div><div class="eyebrow">أربع دول · تجربة واحدة</div><h2 id="coverage-title">بداية قريبة منّا.</h2></div><p>خرائط للاستخدام دون إنشاء حساب. ننطلق بهذا النطاق، ونواصل تحسين البيانات والتجربة قبل التوسع إلى مزيد من الدول العربية.</p></div><div class="country-tabs" role="tablist" aria-label="دول الانطلاقة"><button id="tab-iq" class="country-tab" role="tab" aria-selected="true" aria-controls="country-panel" data-country="iq"><span><strong>العراق</strong><small>IRAQ</small></span><span class="number">01</span></button><button id="tab-jo" class="country-tab" role="tab" aria-selected="false" aria-controls="country-panel" tabindex="-1" data-country="jo"><span><strong>الأردن</strong><small>JORDAN</small></span><span class="number">02</span></button><button id="tab-sy" class="country-tab" role="tab" aria-selected="false" aria-controls="country-panel" tabindex="-1" data-country="sy"><span><strong>سوريا</strong><small>SYRIA</small></span><span class="number">03</span></button><button id="tab-eg" class="country-tab" role="tab" aria-selected="false" aria-controls="country-panel" tabindex="-1" data-country="eg"><span><strong>مصر</strong><small>EGYPT</small></span><span class="number">04</span></button></div><div id="country-panel" class="country-panel" role="tabpanel" tabindex="0" aria-labelledby="tab-iq"><div><span class="pill">ضمن نطاق الانطلاقة</span><h3 id="country-title">العراق، هنا تبدأ الحكاية.</h3><p id="country-description">الخرائط أولًا، ومنها نبدأ بناء الدليل السياحي.</p></div><p id="country-note" class="panel-note">نبدأ بإثراء المعالم السياحية والأثرية العراقية: قصة المكان، موقعه، ومعلومات تساعد على زيارته.</p></div></div></section>
|
||||
<section class="wrap tourism" id="tourism" aria-labelledby="tourism-title"><div class="section-intro"><div><div class="eyebrow">الخطوة التالية · الدليل السياحي</div><h2 id="tourism-title">للمكان أكثر من عنوان.</h2></div><p>من معالم العراق نبدأ. نريد أن تتحول النقطة على الخريطة إلى حكاية تعرفها، ووجهة تستطيع التخطيط لزيارتها.</p></div><div class="tourism-feature"><div class="tourism-photo" id="tourism-photo"><img src="assets/erbil-citadel.jpg" alt="قلعة أربيل التاريخية والأسواق المحيطة بها" width="4032" height="3024" loading="lazy"><div class="photo-caption">قلعة أربيل · العراق<small>الصورة: <a href="https://commons.wikimedia.org/wiki/File:Citadel_of_Erbil.jpg" target="_blank" rel="noopener">Sarbast.T.Hameed / Wikimedia Commons</a> · <a href="https://creativecommons.org/licenses/by-sa/4.0/" target="_blank" rel="noopener">CC BY-SA 4.0</a> · عرض مقتصّ</small></div></div><div class="tourism-copy"><span class="pill">تصور قادم داخل التطبيق</span><h3>اكتشف ما حولك،<br>واعرف ما وراءه.</h3><p>دليل قريب منك للمعالم والمواقع الأثرية، بمعلومات موثقة تُراجع وتُحدّث مع الشركاء المحليين.</p><ul class="tourism-list"><li>قصة موجزة لكل معلم</li><li>صور وتعريف بالمكان</li><li>المداخل ومعلومات الزيارة</li><li>وجهات قريبة منك</li></ul></div></div><div class="tourism-foot"><span>أمثلة للإثراء: قلعة أربيل، بابل، وآثار ومدن العراق. تُراجع إتاحة الزيارة لكل موقع قبل إدراجه.</span><a href="https://whc.unesco.org/en/statesparties/iq" target="_blank" rel="noopener">تراث العراق لدى اليونسكو ↗</a></div></section>
|
||||
<section class="ideas" id="ideas" aria-labelledby="ideas-title"><div class="wrap"><div class="section-intro"><div><div class="eyebrow">آفاق سيرو ماب</div><h2 id="ideas-title">كل إضافة، قيمة جديدة.</h2></div><p>أفكار نطوّرها تدريجيًا؛ تخدم المستخدم، وتمنح شركاء سيرو ماب حضورًا يصل إلى الناس.</p></div><div class="ideas-grid"><article class="idea"><span class="idea-index">01 / الشركاء</span><svg viewBox="0 0 32 32" aria-hidden="true"><rect x="3" y="7" width="26" height="19" rx="3"/><path d="M3 13h26 M8 20h6 M21 18v6 M18 21h6"/></svg><h3>مزايا أقرب إليك</h3><p>دليل للمستشفيات والفنادق ومراكز الدورات المتعاقدة مع شبكة سيرو، يعرض موقع كل جهة ومزايا البطاقة وشروطها المعتمدة.</p><div class="benefit">الفائدة: يجد حامل البطاقة أين يستفيد منها.</div></article><article class="idea"><span class="idea-index">02 / الفعاليات</span><svg viewBox="0 0 32 32" aria-hidden="true"><rect x="5" y="6" width="22" height="23" rx="3"/><path d="M10 3v7 M22 3v7 M5 14h22 M10 20h4 M18 20h4 M10 24h4"/></svg><h3>من الدعوة إلى الوجهة</h3><p>خريطة للمؤتمرات والفعاليات الكبرى: موقع القاعة، المداخل، المواقف والوجهات المحيطة، تُشارك برابط أو رمز QR.</p><div class="benefit">الفائدة: كل مناسبة فرصة حقيقية لتجربة التطبيق.</div></article><article class="idea"><span class="idea-index">03 / جودة البيانات</span><svg viewBox="0 0 32 32" aria-hidden="true"><path d="M25 14c0 7-9 14-9 14S7 21 7 14a9 9 0 1 1 18 0Z"/><path d="m12 14 3 3 6-6"/></svg><h3>معًا، نصحّح الخريطة</h3><p>اقتراح مكان، تصحيح عنوان أو تحديد مدخل. مساهمات تُراجع، وبيانات استخدام اختيارية تساعد على تحسين التجربة.</p><div class="benefit">الفائدة: عناوين أدق، وأساس أفضل للنقل لاحقًا.</div></article></div><p class="ideas-note">هذه إضافات مقترحة للتطوير؛ ظهور الجهات والمزايا مرتبط بتأكيد بياناتها واتفاقاتها.</p></div></section>
|
||||
<section class="wrap roadmap" id="journey" aria-labelledby="journey-title"><div class="eyebrow">رحلة تنمو بخطوات واضحة</div><h2 id="journey-title">نبدأ بما ينفع اليوم.<br>ونبني ما نحتاجه غدًا.</h2><div class="steps"><article class="step current"><div class="step-top"><span>الانطلاقة الأولى</span><b>01</b></div><h3>خرائط بين يدي الناس</h3><p>أربع دول، بلا تسجيل. تعريف بسيرو ماب، وتجارب فعلية تُحسّن المنتج وتبني حضور الاسم.</p></article><article class="step"><div class="step-top"><span>الإثراء التدريجي</span><b>02</b></div><h3>دليل يستحق العودة</h3><p>معالم العراق أولًا، ثم الشركاء والفعاليات؛ أسباب متجددة لفتح التطبيق والاستفادة منه.</p></article><article class="step"><div class="step-top"><span>الأفق القادم</span><b>03</b></div><h3>نحو سيرو للنقل الذكي</h3><p>ربط اكتشاف الوجهة بالوصول إليها عند إطلاق خدمة النقل، والتوسع إلى دول عربية أخرى تدريجيًا.</p></article></div></section>
|
||||
|
||||
<section class="closing"><span class="latin">SIRO MAP · THE FIRST JOURNEY</span><h2>من هنا تبدأ حكايتنا على الخريطة.</h2><p>العراق · الأردن · سوريا · مصر</p></section>
|
||||
</main><footer class="wrap footer"><span>سيرو ماب © 2026 <span aria-hidden="true"> — </span> الانطلاقة الأولى ورؤية التطوير</span><a href="#top">العودة إلى البداية ↑</a></footer>
|
||||
<script>
|
||||
const countries={iq:{title:'العراق، هنا تبدأ الحكاية.',description:'الخرائط أولًا، ومنها نبدأ بناء الدليل السياحي.',note:'نبدأ بإثراء المعالم السياحية والأثرية العراقية: قصة المكان، موقعه، ومعلومات تساعد على زيارته.'},jo:{title:'الأردن، ضمن أولى وجهاتنا.',description:'استخدام الخرائط دون الحاجة إلى إنشاء حساب.',note:'الأردن ضمن نطاق الخرائط في الانطلاقة الأولى. يبدأ إثراء المحتوى السياحي من العراق، ثم يتوسع تدريجيًا.'},sy:{title:'سوريا، أقرب على الخريطة.',description:'تجربة خرائط سهلة ضمن الدول الأربع الأولى.',note:'نطوّر جودة البيانات بالاختبار والمراجعة ومساهمات المستخدمين؛ لتصبح معلومات الأماكن أكثر دقة وفائدة.'},eg:{title:'مصر، امتداد لانطلاقتنا.',description:'خرائط بلا تسجيل ضمن تجربة سيرو ماب.',note:'مصر ضمن نطاق الانطلاقة. نبدأ بخدمة الخرائط، ونضيف الدليل والوجهات والشراكات على مراحل واضحة.'}};
|
||||
const tabs=[...document.querySelectorAll('[role=tab]')];function choose(tab){tabs.forEach(t=>{const selected=t===tab;t.setAttribute('aria-selected',String(selected));t.tabIndex=selected?0:-1});const item=countries[tab.dataset.country];document.getElementById('country-title').textContent=item.title;document.getElementById('country-description').textContent=item.description;document.getElementById('country-note').textContent=item.note;document.getElementById('country-panel').setAttribute('aria-labelledby',tab.id)}tabs.forEach((tab,index)=>{tab.addEventListener('click',()=>choose(tab));tab.addEventListener('keydown',event=>{let next;if(event.key==='ArrowLeft')next=(index+1)%tabs.length;if(event.key==='ArrowRight')next=(index-1+tabs.length)%tabs.length;if(event.key==='Home')next=0;if(event.key==='End')next=tabs.length-1;if(next!==undefined){event.preventDefault();choose(tabs[next]);tabs[next].focus()}})});
|
||||
</script>
|
||||
</body></html>
|
||||
@@ -0,0 +1,161 @@
|
||||
{
|
||||
"info": {
|
||||
"name": "Siro Maps - Sovereign Heritage & Community Guides API",
|
||||
"description": "Production Endpoints for National Heritage Landmarks, Precision Gate Navigation, and Moderated Local Guides Gamification.",
|
||||
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
|
||||
},
|
||||
"variable": [
|
||||
{
|
||||
"key": "baseUrl",
|
||||
"value": "https://map-saas.intaleqapp.com/api",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"key": "apiKey",
|
||||
"value": "zP9vL5mK2nQ8xR7jT4wS1yB6hG3fV0cX",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"item": [
|
||||
{
|
||||
"name": "1. Heritage - List All Landmarks",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{baseUrl}}/v1/heritage/landmarks",
|
||||
"host": ["{{baseUrl}}"],
|
||||
"path": ["v1", "heritage", "landmarks"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "2. Heritage - Nearby Landmarks (Spatial Query)",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{baseUrl}}/v1/heritage/landmarks?lat=31.9539&lng=35.9350&radius=10000",
|
||||
"host": ["{{baseUrl}}"],
|
||||
"path": ["v1", "heritage", "landmarks"],
|
||||
"query": [
|
||||
{ "key": "lat", "value": "31.9539" },
|
||||
{ "key": "lng", "value": "35.9350" },
|
||||
{ "key": "radius", "value": "10000" }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "3. Heritage - Single Landmark by Slug",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{baseUrl}}/v1/heritage/landmarks/amman-citadel",
|
||||
"host": ["{{baseUrl}}"],
|
||||
"path": ["v1", "heritage", "landmarks", "amman-citadel"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "4. Heritage - Offline Pack for Mobile",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{baseUrl}}/v1/heritage/offline-pack",
|
||||
"host": ["{{baseUrl}}"],
|
||||
"path": ["v1", "heritage", "offline-pack"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "5. Community - List Badges",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{baseUrl}}/v1/community/badges",
|
||||
"host": ["{{baseUrl}}"],
|
||||
"path": ["v1", "community", "badges"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "6. Community - Submit Contribution (Suggest Gate)",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [
|
||||
{ "key": "Content-Type", "value": "application/json" }
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"userId\": \"guide_hamza_001\",\n \"displayName\": \"حمزة عايد\",\n \"contributionType\": \"CONFIRM_GATE\",\n \"targetTable\": \"heritage.landmarks\",\n \"targetId\": 3,\n \"placeName\": \"جرش - البوابة الجنوبية السياحية\",\n \"lat\": 32.2735,\n \"lng\": 35.8928,\n \"suggestedData\": { \"gate_name\": \"South Main Gate\" },\n \"notes\": \"تم التحقق من موقع بوابة التذاكر الميدانية\"\n}"
|
||||
},
|
||||
"url": {
|
||||
"raw": "{{baseUrl}}/v1/community/contribute",
|
||||
"host": ["{{baseUrl}}"],
|
||||
"path": ["v1", "community", "contribute"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "7. Community - Guide Profile & Points",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{baseUrl}}/v1/community/profile/guide_hamza_001",
|
||||
"host": ["{{baseUrl}}"],
|
||||
"path": ["v1", "community", "profile", "guide_hamza_001"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "8. Community - Leaderboard",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{baseUrl}}/v1/community/leaderboard",
|
||||
"host": ["{{baseUrl}}"],
|
||||
"path": ["v1", "community", "leaderboard"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "9. Admin - Moderation Pending Queue",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [
|
||||
{ "key": "x-api-key", "value": "{{apiKey}}" }
|
||||
],
|
||||
"url": {
|
||||
"raw": "{{baseUrl}}/v1/admin/moderation/pending",
|
||||
"host": ["{{baseUrl}}"],
|
||||
"path": ["v1", "admin", "moderation", "pending"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "10. Admin - Review Contribution (Approve)",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [
|
||||
{ "key": "Content-Type", "value": "application/json" },
|
||||
{ "key": "x-api-key", "value": "{{apiKey}}" }
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"contributionId\": 2,\n \"action\": \"APPROVE\",\n \"reviewerName\": \"Hamza Ayed (Admin)\",\n \"reviewerNotes\": \"معتمدة ومطابقة لصور الأقمار الصناعية\"\n}"
|
||||
},
|
||||
"url": {
|
||||
"raw": "{{baseUrl}}/v1/admin/moderation/review",
|
||||
"host": ["{{baseUrl}}"],
|
||||
"path": ["v1", "admin", "moderation", "review"]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
<!doctype html>
|
||||
<html lang="ar" dir="rtl">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="theme-color" content="#061d42">
|
||||
<meta name="description" content="سيرو ماب: خرائط سهلة بلا تسجيل للعراق والأردن وسوريا ومصر، ورؤية لدليل سياحي يبدأ من معالم العراق.">
|
||||
<title>سيرو ماب — كل طريق، بداية حكاية</title>
|
||||
<link rel="icon" type="image/png" href="assets/siro-maps.png">
|
||||
<style>
|
||||
@font-face {
|
||||
font-family: 'Tajawal';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(assets/tajawal-400.ttf) format('truetype');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Tajawal';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
src: url(assets/tajawal-500.ttf) format('truetype');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Tajawal';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url(assets/tajawal-700.ttf) format('truetype');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Tajawal';
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
font-display: swap;
|
||||
src: url(assets/tajawal-800.ttf) format('truetype');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Tajawal';
|
||||
font-style: normal;
|
||||
font-weight: 900;
|
||||
font-display: swap;
|
||||
src: url(assets/tajawal-900.ttf) format('truetype');
|
||||
}
|
||||
|
||||
:root{--navy:#061d42;--blue:#124f9f;--gold:#b59247;--pale:#edf3f9;--ink:#132844;--muted:#5c6c80;--line:#dce3ec;--white:#fff;--font:'Tajawal','Geeza Pro',Tahoma,sans-serif}
|
||||
*{box-sizing:border-box}html{scroll-behavior:smooth;scroll-padding-top:100px}body{margin:0;background:#fff;color:var(--ink);font-family:var(--font);font-size:18px;line-height:1.85}a{color:inherit;text-decoration:none}button{font:inherit}button,a{-webkit-tap-highlight-color:transparent}button:focus-visible,a:focus-visible{outline:3px solid #c6a057;outline-offset:6px}button{cursor:pointer}img{max-width:100%;height:auto;display:block}h1,h2,h3,p{margin:0}h1,h2,h3{line-height:1.35}h2{font-size:clamp(32px,3.5vw,49px);font-weight:800;letter-spacing:-.6px}h3{font-size:25px}::selection{background:#d6bb7b;color:var(--navy)}.wrap{width:min(1190px,calc(100% - 88px));margin:auto}.eyebrow{font-size:14px;letter-spacing:1.8px;font-weight:700;color:var(--gold);display:flex;align-items:center;gap:12px;margin-bottom:22px}.eyebrow:before{content:'';width:28px;height:2px;background:currentColor}.latin{font-family:Arial,sans-serif;letter-spacing:2.5px;font-size:12px;direction:ltr;display:inline-block}.muted{color:var(--muted)}
|
||||
header{height:100px;background:rgba(255,255,255,.97);border-bottom:1px solid var(--line);position:relative;z-index:5}.nav{height:100%;display:flex;align-items:center;justify-content:space-between;gap:24px}.brand{display:flex;gap:13px;align-items:center}.brand img{width:62px;height:62px;border-radius:15px}.brand strong{font-size:23px;display:block;line-height:1.3}.brand small{color:var(--muted);font-size:11px;letter-spacing:2.2px;display:block;direction:ltr}.links{display:flex;align-items:center;gap:30px;font-size:16px;font-weight:600}.links a{transition:color .2s}.links a:hover{color:var(--blue)}.nav-tag{font-size:14px;color:var(--blue);border:1px solid #c9d8ed;padding:7px 17px;border-radius:30px;white-space:nowrap}
|
||||
.hero{position:relative;overflow:hidden;background:var(--navy);color:white;padding:75px 0 0}.hero:before{content:'';position:absolute;width:740px;height:740px;left:-140px;top:-220px;background:radial-gradient(circle,rgba(39,105,180,.38),transparent 67%);pointer-events:none}.hero-grid{display:grid;grid-template-columns:1.12fr 1fr;align-items:center;gap:65px;position:relative;min-height:470px;padding-bottom:65px}.hero h1{font-size:clamp(44px,5.7vw,76px);font-weight:800;line-height:1.24;letter-spacing:-1px}.hero h1 span{color:#dbbc77}.hero p{font-size:20px;color:#c0ccdc;max-width:530px;margin-top:27px;line-height:1.9}.hero .eyebrow{color:#ddbd78}.hero-actions{display:flex;gap:14px;align-items:center;flex-wrap:wrap;margin-top:33px}.btn{display:inline-flex;align-items:center;justify-content:center;gap:20px;padding:11px 23px;border-radius:7px;font-size:17px;font-weight:700;border:1px solid transparent;min-height:50px}.btn-gold{background:#dbbc77;color:#0b2547}.btn-gold:hover{background:#efd49b}.btn-outline{border-color:#526580;color:#e3eaf3}.btn-outline:hover{background:#ffffff0c}.arrow{font-family:Arial;font-size:23px;font-weight:400}.hero-status{display:block;font-size:14px;color:#acbbcf;margin-top:17px}.brand-stage{position:relative;display:flex;justify-content:center;align-items:center;min-height:420px;padding:15px}.brand-stage:before,.brand-stage:after{content:'';position:absolute;border:1px solid #759ed12b;border-radius:50%;width:410px;height:410px;pointer-events:none}.brand-stage:after{width:480px;height:480px;border-style:dashed;opacity:.6}.hero-icon{width:330px;border-radius:70px;transform:rotate(-6deg);box-shadow:0 30px 60px #0004;z-index:1}.float-label{position:absolute;z-index:2;border:1px solid #6986af6b;background:#0b2b55eb;box-shadow:0 10px 35px #0002;backdrop-filter:blur(15px);padding:10px 17px;border-radius:10px;font-size:16px;display:flex;align-items:center;gap:12px}.float-label svg{width:22px;height:22px;stroke:#e0bf7d;fill:none;stroke-width:1.6}.label-one{left:-5px;top:60px}.label-two{right:4px;bottom:43px}.hero-facts{position:relative;border-top:1px solid #ffffff20;display:grid;grid-template-columns:repeat(3,1fr);padding:24px 0;gap:30px}.fact{display:flex;gap:17px;align-items:center}.fact:not(:last-child){border-left:1px solid #ffffff20}.fact b{font-size:35px;color:#dfc28a;font-weight:500;line-height:1}.fact span{display:block;font-size:17px}.fact small{display:block;color:#a8b8cd;font-size:14px}
|
||||
.story{padding:88px 0;display:grid;grid-template-columns:.9fr 1.1fr;gap:95px;align-items:center}.story p{color:var(--muted);font-size:20px}.story .accent{color:var(--ink);font-weight:700}.story .eyebrow{margin-bottom:16px}.section-intro{display:flex;align-items:flex-end;justify-content:space-between;gap:40px;margin-bottom:37px}.section-intro p{max-width:430px;color:var(--muted)}
|
||||
.coverage{background:var(--pale);padding:72px 0 78px}.country-tabs{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;margin-bottom:24px}.country-tab{display:flex;align-items:center;justify-content:space-between;padding:17px 22px;background:transparent;border:1px solid #cdd8e6;color:var(--ink);border-radius:8px;transition:background .2s;min-height:83px}.country-tab strong{font-size:23px}.country-tab small{display:block;font-size:12px;letter-spacing:2px;line-height:1.2;color:var(--muted);direction:ltr}.country-tab .number{font-family:Arial;font-size:14px;color:#8b9db3}.country-tab[aria-selected=true]{background:var(--navy);border-color:var(--navy);color:white}.country-tab[aria-selected=true] small,.country-tab[aria-selected=true] .number{color:#d8bd81}.country-panel{background:#fff;border-radius:12px;padding:27px 32px;display:grid;grid-template-columns:1fr 1.4fr;gap:35px;align-items:center;border:1px solid #e2e9f2}.country-panel h3{font-size:28px;margin-bottom:5px}.country-panel p{font-size:17px;color:var(--muted)}.panel-note{border-right:2px solid #c4a266;padding-right:24px}.pill{display:inline-block;font-size:13px;border:1px solid #d9e1ec;background:#f7f9fc;padding:2px 10px;border-radius:30px;color:#49617f;margin-bottom:9px}
|
||||
.tourism{padding:90px 0}.tourism-feature{display:grid;grid-template-columns:1.15fr 1fr;background:var(--navy);color:white;border-radius:18px;overflow:hidden;min-height:415px}.tourism-photo{position:relative;background:#15395e;min-height:380px;overflow:hidden}.tourism-photo img{width:100%;height:100%;object-fit:cover;position:absolute}.photo-caption{position:absolute;bottom:0;right:0;left:0;color:white;padding:45px 28px 22px;background:linear-gradient(transparent,#041a35e6);font-size:16px}.photo-caption small{display:block;opacity:.8;font-size:12px;direction:ltr;text-align:right}.tourism-copy{padding:42px 40px;align-self:center}.tourism-copy h3{font-size:35px;margin:12px 0 15px}.tourism-copy p{color:#beccdd}.tourism-copy .pill{background:#ffffff0c;border-color:#597087;color:#e2c388}.tourism-list{padding:0;list-style:none;display:grid;grid-template-columns:1fr 1fr;gap:13px;margin:25px 0 0;font-size:16px;color:#e7edf6}.tourism-list li:before{content:'+';color:#d8b975;padding-left:10px}.tourism-foot{display:flex;justify-content:space-between;gap:25px;margin-top:20px;font-size:15px;color:var(--muted)}.tourism-foot a{color:var(--blue);text-decoration:underline;text-underline-offset:4px}
|
||||
.pillars-section{padding:80px 0}.pillars-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:24px;margin-top:35px}.pillar-card{background:var(--pale);border:1px solid #dce5f0;border-radius:16px;padding:32px;display:flex;flex-direction:column;justify-content:space-between;transition:transform .2s,box-shadow .2s}.pillar-card:hover{transform:translateY(-4px);box-shadow:0 12px 30px rgba(6,29,66,.08);border-color:#b59247}.pillar-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:18px}.pillar-num{font-size:32px;font-weight:900;color:var(--gold);line-height:1}.pillar-tag{font-size:13px;font-weight:700;color:var(--blue);background:rgba(18,79,159,.08);padding:4px 12px;border-radius:20px}.pillar-card h3{font-size:24px;margin-bottom:12px;color:var(--navy)}.pillar-card p{color:var(--muted);font-size:16px;line-height:1.7;margin-bottom:20px}.pillar-badge{font-size:13px;font-weight:600;color:var(--gold);border-top:1px solid #dce5f0;padding-top:14px}
|
||||
.ideas{background:#f7f9fc;padding:75px 0 82px}.ideas-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:0;border-top:1px solid var(--line);border-bottom:1px solid var(--line)}.idea{padding:32px 29px 30px}.idea:not(:last-child){border-left:1px solid var(--line)}.idea-index{font-size:13px;direction:ltr;display:block;text-align:right;color:#977837;letter-spacing:2px;margin-bottom:22px}.idea svg{width:34px;height:34px;fill:none;stroke:var(--blue);stroke-width:1.5;margin-bottom:15px}.idea h3{margin-bottom:13px;font-size:25px}.idea p{font-size:17px;color:var(--muted)}.benefit{margin-top:24px;padding-top:17px;border-top:1px solid var(--line);font-size:15px;color:var(--blue)}.ideas-note{font-size:14px;color:var(--muted);margin-top:20px}
|
||||
.roadmap{padding:90px 0}.steps{display:grid;grid-template-columns:repeat(3,1fr);gap:32px;margin-top:42px}.step{border-top:2px solid #dce3ec;padding-top:22px}.step.current{border-top-color:var(--gold)}.step-top{display:flex;align-items:center;justify-content:space-between;margin-bottom:14px}.step-top span{font-size:14px;color:#728096}.step-top b{font-size:28px;font-family:Arial;font-weight:400;color:#a7b4c4}.step.current .step-top span{color:#907132}.step h3{font-size:25px;margin-bottom:12px}.step p{color:var(--muted);font-size:17px}
|
||||
.partnership{border:1px solid var(--line);border-radius:15px;display:grid;grid-template-columns:160px 1fr;gap:40px;padding:32px 40px;align-items:center;margin-bottom:70px}.prize-logo{width:150px}.partnership .eyebrow{font-size:13px;margin-bottom:11px}.partnership h3{font-size:29px;margin-bottom:9px}.partnership p{font-size:18px;color:var(--muted);max-width:820px}.closing{background:var(--navy);color:white;text-align:center;padding:65px 24px}.closing .latin{color:#dbbc77;margin-bottom:15px}.closing h2{font-size:clamp(29px,4vw,46px)}.closing p{color:#acbed4;margin-top:15px;font-size:18px}.footer{padding:23px 0;display:flex;justify-content:space-between;align-items:center;gap:20px;font-size:14px;color:var(--muted)}.footer a{color:var(--blue)}.footer .latin{font-size:11px;letter-spacing:1px}
|
||||
@media(min-width:1500px){.hero-grid{min-height:530px}}
|
||||
@media(max-width:1050px){.wrap{width:calc(100% - 48px)}.links{gap:18px}.nav-tag{display:none}.hero-grid{gap:25px}.hero-icon{width:280px;border-radius:58px}.brand-stage:before{width:340px;height:340px}.brand-stage:after{width:390px;height:390px}.label-one{left:0;top:45px}.label-two{right:0;bottom:45px}.story{gap:45px}.tourism-copy{padding:30px}.idea{padding:28px 20px}.country-tab{padding:15px}.hero h1{font-size:58px}}
|
||||
@media(max-width:760px){body{font-size:17px}.wrap{width:calc(100% - 36px)}header{height:auto;min-height:86px}.nav{padding:13px 0;flex-wrap:wrap;gap:10px}.brand img{width:48px;height:48px}.brand strong{font-size:20px}.brand small{font-size:10px}.links{gap:19px;font-size:14px}.links a:last-child{display:none}.hero{padding-top:44px}.hero-grid{grid-template-columns:1fr;gap:23px;padding-bottom:35px}.hero h1{font-size:clamp(44px,10vw,62px);line-height:1.3}.hero p{font-size:18px;margin-top:20px}.hero-actions{margin-top:25px}.btn{font-size:16px;padding:10px 19px}.hero-status{font-size:13px}.brand-stage{min-height:325px;width:min(100%,390px);margin:auto}.hero-icon{width:240px;border-radius:52px}.brand-stage:before{width:285px;height:285px}.brand-stage:after{width:320px;height:320px}.float-label{font-size:14px;padding:8px 12px}.label-one{top:39px;left:0}.label-two{bottom:23px;right:0}.hero-facts{gap:15px;padding:20px 0}.fact{gap:8px;display:block}.fact b{font-size:27px}.fact span{font-size:14px}.fact small{font-size:12px}.story{grid-template-columns:1fr;gap:22px;padding:53px 0}.story p{font-size:18px}.eyebrow{font-size:13px;margin-bottom:15px}.section-intro{display:block;margin-bottom:27px}.section-intro p{margin-top:17px;font-size:17px}.coverage{padding:48px 0}.country-tabs{grid-template-columns:repeat(2,1fr);gap:9px}.country-tab{min-height:78px}.country-tab strong{font-size:22px}.country-panel{grid-template-columns:1fr;gap:18px;padding:24px}.panel-note{padding-right:16px}.tourism{padding:55px 0}.tourism-feature{grid-template-columns:1fr}.tourism-photo{min-height:280px}.tourism-copy{padding:29px 25px}.tourism-copy h3{font-size:30px}.tourism-foot{display:block;font-size:14px}.tourism-foot a{display:inline-block;margin-top:7px}.ideas{padding:50px 0}.ideas-grid{grid-template-columns:1fr}.idea{padding:28px 4px}.idea:not(:last-child){border-left:0;border-bottom:1px solid var(--line)}.idea-index{margin-bottom:14px}.idea svg{float:left}.benefit{margin-top:17px}.roadmap{padding:53px 0}.steps{grid-template-columns:1fr;gap:28px;margin-top:25px}.step{padding-top:14px}.step-top{margin-bottom:8px}.partnership{grid-template-columns:1fr;gap:18px;padding:26px;margin-bottom:45px}.prize-logo{width:120px}.partnership h3{font-size:25px}.partnership p{font-size:17px}.closing{padding:45px 22px}.footer{flex-direction:column;align-items:flex-start;gap:8px}.tourism-list{font-size:15px}}
|
||||
@media(prefers-reduced-motion:reduce){html{scroll-behavior:auto}*{transition:none!important}}
|
||||
@media print{header,.hero-actions,.hero-status,.country-tabs,.footer a{display:none}body{font-size:12pt}.hero,.closing,.tourism-feature{print-color-adjust:exact;-webkit-print-color-adjust:exact}.wrap{width:92%}.hero{padding-top:30px}.hero-grid{min-height:0;gap:25px}.hero h1{font-size:38px}.hero p{font-size:16px}.brand-stage{min-height:250px}.hero-icon{width:200px}.brand-stage:before,.brand-stage:after,.float-label{display:none}.story,.tourism,.ideas,.roadmap,.coverage{padding:25px 0}.section-intro h2,h2{font-size:28px}.story p{font-size:16px}.tourism-feature{break-inside:avoid}.idea,.step,.partnership{break-inside:avoid}.partnership{margin-bottom:25px}.closing{padding:25px}}
|
||||
</style>
|
||||
</head>
|
||||
<body id="top">
|
||||
<header><nav class="wrap nav" aria-label="التنقل الرئيسي"><a class="brand" href="#top" aria-label="سيرو ماب، بداية الصفحة"><img src="assets/siro-maps.png" alt="شعار سيرو ماب" width="62" height="62"><span><strong>سيرو ماب</strong><small>SIRO MAP</small></span></a><div class="links"><a href="#coverage">الانطلاقة</a><a href="#tourism">اكتشف العراق</a><a href="#pillars">ركائز الدليل</a><a href="#ideas">آفاق سيرو ماب</a><a href="#journey">رحلتنا</a></div><span class="nav-tag">من سيرو ماب، إلى كل وجهة</span></nav></header>
|
||||
<main>
|
||||
<section class="hero" aria-labelledby="hero-title"><div class="wrap"><div class="hero-grid"><div><div class="eyebrow">الانطلاقة الأولى · سيرو ماب</div><h1 id="hero-title">كل طريق،<br>بداية <span>حكاية.</span></h1><p>خرائط تقرّب المسافات، وحكايات تقرّبنا من المكان.<br>نبدأ بأربع دول، ونفتح من العراق بابًا لاكتشاف الثقافة والمعالم والوجهات.</p><div class="hero-actions"><a class="btn btn-gold" href="#coverage">اكتشف الانطلاقة <span class="arrow" aria-hidden="true">←</span></a><a class="btn btn-outline" href="#ideas">ما الذي نبنيه معًا؟</a></div><small class="hero-status">نسخة التطبيق قيد المراجعة على Google Play</small></div><div class="brand-stage"><img class="hero-icon" src="assets/siro-maps.png" alt="شعار سيرو ماب الذكي للملاحة والتنقل" width="512" height="512"><span class="float-label label-one"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M20 10c0 6-8 11-8 11S4 16 4 10a8 8 0 1 1 16 0Z"/><circle cx="12" cy="10" r="2.5"/></svg>وجهتك أقرب</span><span class="float-label label-two"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="m3 5 6-2 6 2 6-2v16l-6 2-6-2-6 2Z M9 3v16 M15 5v16"/></svg>اكتشف حكاية المكان</span></div></div><div class="hero-facts"><div class="fact"><b>04</b><div><span>دول في الانطلاقة</span><small>العراق · الأردن · سوريا · مصر</small></div></div><div class="fact"><b>01</b><div><span>بداية للدليل السياحي</span><small>معالم العراق أولًا</small></div></div><div class="fact"><b>ببساطة</b><div><span>خرائط بلا تسجيل</span><small>افتح التطبيق، وابدأ رحلتك</small></div></div></div></div></section>
|
||||
<section class="wrap story" aria-labelledby="story-title"><div><div class="eyebrow">فكرة تجمعنا</div><h2 id="story-title">من اسم يحمل حضارة،<br>إلى خدمة ترافق الناس.</h2></div><p>تعتمد سيرو ماب على الابتكار والسيادة الرقمية وتوفير التكاليف. واليوم، نمنح هذا المشروع حضورًا قويًا في الحياة اليومية: خرائط سهلة ومستقلة، ودليل يكشف ما حولنا من وجهات ومعالم.<br><span class="accent">خطوة أولى لشراكتنا، وأساس ننمو عليه معًا.</span></p></section>
|
||||
<section class="coverage" id="coverage" aria-labelledby="coverage-title"><div class="wrap"><div class="section-intro"><div><div class="eyebrow">أربع دول · تجربة واحدة</div><h2 id="coverage-title">بداية قريبة منّا.</h2></div><p>خرائط للاستخدام دون إنشاء حساب. ننطلق بهذا النطاق، ونواصل تحسين البيانات والتجربة قبل التوسع إلى مزيد من الدول العربية.</p></div><div class="country-tabs" role="tablist" aria-label="دول الانطلاقة"><button id="tab-iq" class="country-tab" role="tab" aria-selected="true" aria-controls="country-panel" data-country="iq"><span><strong>العراق</strong><small>IRAQ</small></span><span class="number">01</span></button><button id="tab-jo" class="country-tab" role="tab" aria-selected="false" aria-controls="country-panel" tabindex="-1" data-country="jo"><span><strong>الأردن</strong><small>JORDAN</small></span><span class="number">02</span></button><button id="tab-sy" class="country-tab" role="tab" aria-selected="false" aria-controls="country-panel" tabindex="-1" data-country="sy"><span><strong>سوريا</strong><small>SYRIA</small></span><span class="number">03</span></button><button id="tab-eg" class="country-tab" role="tab" aria-selected="false" aria-controls="country-panel" tabindex="-1" data-country="eg"><span><strong>مصر</strong><small>EGYPT</small></span><span class="number">04</span></button></div><div id="country-panel" class="country-panel" role="tabpanel" tabindex="0" aria-labelledby="tab-iq"><div><span class="pill">ضمن نطاق الانطلاقة</span><h3 id="country-title">العراق، هنا تبدأ الحكاية.</h3><p id="country-description">الخرائط أولًا، ومنها نبدأ بناء الدليل السياحي.</p></div><p id="country-note" class="panel-note">نبدأ بإثراء المعالم السياحية والأثرية العراقية: قصة المكان، موقعه، ومعلومات تساعد على زيارته.</p></div></div></section>
|
||||
<section class="wrap tourism" id="tourism" aria-labelledby="tourism-title"><div class="section-intro"><div><div class="eyebrow">الخطوة التالية · الدليل السياحي</div><h2 id="tourism-title">للمكان أكثر من عنوان.</h2></div><p>من معالم العراق نبدأ. نريد أن تتحول النقطة على الخريطة إلى حكاية تعرفها، ووجهة تستطيع التخطيط لزيارتها.</p></div><div class="tourism-feature"><div class="tourism-photo" id="tourism-photo"><img src="assets/erbil-citadel.jpg" alt="قلعة أربيل التاريخية والأسواق المحيطة بها" width="4032" height="3024" loading="lazy"><div class="photo-caption">قلعة أربيل · العراق<small>الصورة: <a href="https://commons.wikimedia.org/wiki/File:Citadel_of_Erbil.jpg" target="_blank" rel="noopener">Sarbast.T.Hameed / Wikimedia Commons</a> · <a href="https://creativecommons.org/licenses/by-sa/4.0/" target="_blank" rel="noopener">CC BY-SA 4.0</a> · عرض مقتصّ</small></div></div><div class="tourism-copy"><span class="pill">تصور قادم داخل التطبيق</span><h3>اكتشف ما حولك،<br>واعرف ما وراءه.</h3><p>دليل قريب منك للمعالم والمواقع الأثرية، بمعلومات موثقة تُراجع وتُحدّث مع الشركاء المحليين.</p><ul class="tourism-list"><li>قصة موجزة لكل معلم</li><li>صور وتعريف بالمكان</li><li>المداخل ومعلومات الزيارة</li><li>وجهات قريبة منك</li></ul></div></div><div class="tourism-foot"><span>أمثلة للإثراء: قلعة أربيل، بابل، وآثار ومدن العراق. تُراجع إتاحة الزيارة لكل موقع قبل إدراجه.</span><a href="https://whc.unesco.org/en/statesparties/iq" target="_blank" rel="noopener">تراث العراق لدى اليونسكو ↗</a></div></section>
|
||||
<section class="wrap pillars-section" id="pillars" aria-labelledby="pillars-title"><div class="section-intro"><div><div class="eyebrow">منهجية العمل · الركائز الأربع للدليل</div><h2 id="pillars-title">كيف نُعيد صياغة تجربة المعالم؟</h2></div><p>لا نكتفي بوضع نقطة صامتة على الخارطة؛ بل نمزج دقة الملاحة الميدانية بسردية التاريخ والسيادة التقنية.</p></div><div class="pillars-grid"><div class="pillar-card"><div class="pillar-header"><span class="pillar-num">01</span><span class="pillar-tag">الملاحة الواقعية</span></div><h3>التوجيه للبوابات الفعلية</h3><p>توجيه السائقين والزوار مباشرة إلى نقاط الدخول الرسمية ومواقف السيارات بدلاً من التوجيه الخاطئ إلى منتصف المواقع الشاسعة أو الأسوار المغلقة.</p><div class="pillar-badge">دقة الوصول الميداني</div></div><div class="pillar-card"><div class="pillar-header"><span class="pillar-num">02</span><span class="pillar-tag">التوثيق الحضاري</span></div><h3>سردية المكان في 60 ثانية</h3><p>بطاقة ذكية ومكثفة لكل معلم: الحقبة التاريخية، من بنى المكان، وأهميته التراثية العالمية، بصور موثقة بدعم المبادرات الثقافية الوطنية.</p><div class="pillar-badge">معرفة سريعة وموثوقة</div></div><div class="pillar-card"><div class="pillar-header"><span class="pillar-num">03</span><span class="pillar-tag">السيادة التقنية</span></div><h3>استكشاف كامل بدون إنترنت</h3><p>حزم محلية خفيفة تحفظ قصص المعالم ومساراتها على الهاتف، لتعمل بكفاءة وسرعة فائقة حتى في المناطق الصحراوية والنائية.</p><div class="pillar-badge">استمرارية في الميدان</div></div><div class="pillar-card"><div class="pillar-header"><span class="pillar-num">04</span><span class="pillar-tag">منظومة النقل</span></div><h3>جسر مباشر بين الاستكشاف والوصول</h3><p>ربط قرار الزيارة فوراً بطلب وسيلة مواصلات ذكية أو بدء مسار الملاحة، لتحويل الاكتشاف الثقافي إلى حركة يومية واقتصاد سياحي نابض.</p><div class="pillar-badge">تكامل ذكي مع الحركة</div></div></div></section>
|
||||
<section class="ideas" id="ideas" aria-labelledby="ideas-title"><div class="wrap"><div class="section-intro"><div><div class="eyebrow">آفاق سيرو ماب</div><h2 id="ideas-title">كل إضافة، قيمة جديدة.</h2></div><p>أفكار نطوّرها تدريجيًا؛ تخدم المستخدم، وتمنح شركاء سيرو ماب حضورًا يصل إلى الناس.</p></div><div class="ideas-grid"><article class="idea"><span class="idea-index">01 / الشركاء</span><svg viewBox="0 0 32 32" aria-hidden="true"><rect x="3" y="7" width="26" height="19" rx="3"/><path d="M3 13h26 M8 20h6 M21 18v6 M18 21h6"/></svg><h3>مزايا أقرب إليك</h3><p>دليل للمستشفيات والفنادق ومراكز الدورات المتعاقدة مع شبكة سيرو، يعرض موقع كل جهة ومزايا البطاقة وشروطها المعتمدة.</p><div class="benefit">الفائدة: يجد حامل البطاقة أين يستفيد منها.</div></article><article class="idea"><span class="idea-index">02 / الفعاليات</span><svg viewBox="0 0 32 32" aria-hidden="true"><rect x="5" y="6" width="22" height="23" rx="3"/><path d="M10 3v7 M22 3v7 M5 14h22 M10 20h4 M18 20h4 M10 24h4"/></svg><h3>من الدعوة إلى الوجهة</h3><p>خريطة للمؤتمرات والفعاليات الكبرى: موقع القاعة، المداخل، المواقف والوجهات المحيطة، تُشارك برابط أو رمز QR.</p><div class="benefit">الفائدة: كل مناسبة فرصة حقيقية لتجربة التطبيق.</div></article><article class="idea"><span class="idea-index">03 / جودة البيانات</span><svg viewBox="0 0 32 32" aria-hidden="true"><path d="M25 14c0 7-9 14-9 14S7 21 7 14a9 9 0 1 1 18 0Z"/><path d="m12 14 3 3 6-6"/></svg><h3>معًا، نصحّح الخريطة</h3><p>اقتراح مكان، تصحيح عنوان أو تحديد مدخل. مساهمات تُراجع، وبيانات استخدام اختيارية تساعد على تحسين التجربة.</p><div class="benefit">الفائدة: عناوين أدق، وأساس أفضل للنقل لاحقًا.</div></article></div><p class="ideas-note">هذه إضافات مقترحة للتطوير؛ ظهور الجهات والمزايا مرتبط بتأكيد بياناتها واتفاقاتها.</p></div></section>
|
||||
<section class="wrap roadmap" id="journey" aria-labelledby="journey-title"><div class="eyebrow">رحلة تنمو بخطوات واضحة</div><h2 id="journey-title">نبدأ بما ينفع اليوم.<br>ونبني ما نحتاجه غدًا.</h2><div class="steps"><article class="step current"><div class="step-top"><span>الانطلاقة الأولى</span><b>01</b></div><h3>خرائط بين يدي الناس</h3><p>أربع دول، بلا تسجيل. تعريف بسيرو ماب، وتجارب فعلية تُحسّن المنتج وتبني حضور الاسم.</p></article><article class="step"><div class="step-top"><span>الإثراء التدريجي</span><b>02</b></div><h3>دليل يستحق العودة</h3><p>معالم العراق أولًا، ثم الشركاء والفعاليات؛ أسباب متجددة لفتح التطبيق والاستفادة منه.</p></article><article class="step"><div class="step-top"><span>الأفق القادم</span><b>03</b></div><h3>نحو سيرو للنقل الذكي</h3><p>ربط اكتشاف الوجهة بالوصول إليها عند إطلاق خدمة النقل، والتوسع إلى دول عربية أخرى تدريجيًا.</p></article></div></section>
|
||||
|
||||
<section class="closing"><span class="latin">SIRO MAP · THE FIRST JOURNEY</span><h2>من هنا تبدأ حكايتنا على الخريطة.</h2><p>العراق · الأردن · سوريا · مصر</p></section>
|
||||
</main><footer class="wrap footer"><span>سيرو ماب © 2026 <span aria-hidden="true"> — </span> الانطلاقة الأولى ورؤية التطوير</span><a href="#top">العودة إلى البداية ↑</a></footer>
|
||||
<script>
|
||||
const countries={iq:{title:'العراق، هنا تبدأ الحكاية.',description:'الخرائط أولًا، ومنها نبدأ بناء الدليل السياحي.',note:'نبدأ بإثراء المعالم السياحية والأثرية العراقية: قصة المكان، موقعه، ومعلومات تساعد على زيارته.'},jo:{title:'الأردن، ضمن أولى وجهاتنا.',description:'استخدام الخرائط دون الحاجة إلى إنشاء حساب.',note:'الأردن ضمن نطاق الخرائط في الانطلاقة الأولى. يبدأ إثراء المحتوى السياحي من العراق، ثم يتوسع تدريجيًا.'},sy:{title:'سوريا، أقرب على الخريطة.',description:'تجربة خرائط سهلة ضمن الدول الأربع الأولى.',note:'نطوّر جودة البيانات بالاختبار والمراجعة ومساهمات المستخدمين؛ لتصبح معلومات الأماكن أكثر دقة وفائدة.'},eg:{title:'مصر، امتداد لانطلاقتنا.',description:'خرائط بلا تسجيل ضمن تجربة سيرو ماب.',note:'مصر ضمن نطاق الانطلاقة. نبدأ بخدمة الخرائط، ونضيف الدليل والوجهات والشراكات على مراحل واضحة.'}};
|
||||
const tabs=[...document.querySelectorAll('[role=tab]')];function choose(tab){tabs.forEach(t=>{const selected=t===tab;t.setAttribute('aria-selected',String(selected));t.tabIndex=selected?0:-1});const item=countries[tab.dataset.country];document.getElementById('country-title').textContent=item.title;document.getElementById('country-description').textContent=item.description;document.getElementById('country-note').textContent=item.note;document.getElementById('country-panel').setAttribute('aria-labelledby',tab.id)}tabs.forEach((tab,index)=>{tab.addEventListener('click',()=>choose(tab));tab.addEventListener('keydown',event=>{let next;if(event.key==='ArrowLeft')next=(index+1)%tabs.length;if(event.key==='ArrowRight')next=(index-1+tabs.length)%tabs.length;if(event.key==='Home')next=0;if(event.key==='End')next=tabs.length-1;if(next!==undefined){event.preventDefault();choose(tabs[next]);tabs[next].focus()}})});
|
||||
</script>
|
||||
</body></html>
|
||||
@@ -0,0 +1,93 @@
|
||||
Copyright 2018 Boutros International. (http://www.boutrosfonts.com)
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
|
After Width: | Height: | Size: 4.0 MiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 1.0 MiB |
@@ -0,0 +1,82 @@
|
||||
<!doctype html>
|
||||
<html lang="ar" dir="rtl">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="theme-color" content="#061d42">
|
||||
<meta name="description" content="سيرو ماب: خرائط سهلة بلا تسجيل للعراق والأردن وسوريا ومصر، ورؤية لدليل سياحي يبدأ من معالم العراق.">
|
||||
<title>سيرو ماب — كل طريق، بداية حكاية</title>
|
||||
<link rel="icon" type="image/png" href="assets/siro-maps.png">
|
||||
<style>
|
||||
@font-face {
|
||||
font-family: 'Tajawal';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(assets/tajawal-400.ttf) format('truetype');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Tajawal';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
src: url(assets/tajawal-500.ttf) format('truetype');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Tajawal';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url(assets/tajawal-700.ttf) format('truetype');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Tajawal';
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
font-display: swap;
|
||||
src: url(assets/tajawal-800.ttf) format('truetype');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Tajawal';
|
||||
font-style: normal;
|
||||
font-weight: 900;
|
||||
font-display: swap;
|
||||
src: url(assets/tajawal-900.ttf) format('truetype');
|
||||
}
|
||||
|
||||
:root{--navy:#061d42;--blue:#124f9f;--gold:#b59247;--pale:#edf3f9;--ink:#132844;--muted:#5c6c80;--line:#dce3ec;--white:#fff;--font:'Tajawal','Geeza Pro',Tahoma,sans-serif}
|
||||
*{box-sizing:border-box}html{scroll-behavior:smooth;scroll-padding-top:100px}body{margin:0;background:#fff;color:var(--ink);font-family:var(--font);font-size:18px;line-height:1.85}a{color:inherit;text-decoration:none}button{font:inherit}button,a{-webkit-tap-highlight-color:transparent}button:focus-visible,a:focus-visible{outline:3px solid #c6a057;outline-offset:6px}button{cursor:pointer}img{max-width:100%;height:auto;display:block}h1,h2,h3,p{margin:0}h1,h2,h3{line-height:1.35}h2{font-size:clamp(32px,3.5vw,49px);font-weight:800;letter-spacing:-.6px}h3{font-size:25px}::selection{background:#d6bb7b;color:var(--navy)}.wrap{width:min(1190px,calc(100% - 88px));margin:auto}.eyebrow{font-size:14px;letter-spacing:1.8px;font-weight:700;color:var(--gold);display:flex;align-items:center;gap:12px;margin-bottom:22px}.eyebrow:before{content:'';width:28px;height:2px;background:currentColor}.latin{font-family:Arial,sans-serif;letter-spacing:2.5px;font-size:12px;direction:ltr;display:inline-block}.muted{color:var(--muted)}
|
||||
header{height:100px;background:rgba(255,255,255,.97);border-bottom:1px solid var(--line);position:relative;z-index:5}.nav{height:100%;display:flex;align-items:center;justify-content:space-between;gap:24px}.brand{display:flex;gap:13px;align-items:center}.brand img{width:62px;height:62px;border-radius:15px}.brand strong{font-size:23px;display:block;line-height:1.3}.brand small{color:var(--muted);font-size:11px;letter-spacing:2.2px;display:block;direction:ltr}.links{display:flex;align-items:center;gap:30px;font-size:16px;font-weight:600}.links a{transition:color .2s}.links a:hover{color:var(--blue)}.nav-tag{font-size:14px;color:var(--blue);border:1px solid #c9d8ed;padding:7px 17px;border-radius:30px;white-space:nowrap}
|
||||
.hero{position:relative;overflow:hidden;background:var(--navy);color:white;padding:75px 0 0}.hero:before{content:'';position:absolute;width:740px;height:740px;left:-140px;top:-220px;background:radial-gradient(circle,rgba(39,105,180,.38),transparent 67%);pointer-events:none}.hero-grid{display:grid;grid-template-columns:1.12fr 1fr;align-items:center;gap:65px;position:relative;min-height:470px;padding-bottom:65px}.hero h1{font-size:clamp(44px,5.7vw,76px);font-weight:800;line-height:1.24;letter-spacing:-1px}.hero h1 span{color:#dbbc77}.hero p{font-size:20px;color:#c0ccdc;max-width:530px;margin-top:27px;line-height:1.9}.hero .eyebrow{color:#ddbd78}.hero-actions{display:flex;gap:14px;align-items:center;flex-wrap:wrap;margin-top:33px}.btn{display:inline-flex;align-items:center;justify-content:center;gap:20px;padding:11px 23px;border-radius:7px;font-size:17px;font-weight:700;border:1px solid transparent;min-height:50px}.btn-gold{background:#dbbc77;color:#0b2547}.btn-gold:hover{background:#efd49b}.btn-outline{border-color:#526580;color:#e3eaf3}.btn-outline:hover{background:#ffffff0c}.arrow{font-family:Arial;font-size:23px;font-weight:400}.hero-status{display:block;font-size:14px;color:#acbbcf;margin-top:17px}.brand-stage{position:relative;display:flex;justify-content:center;align-items:center;min-height:420px;padding:15px}.brand-stage:before,.brand-stage:after{content:'';position:absolute;border:1px solid #759ed12b;border-radius:50%;width:410px;height:410px;pointer-events:none}.brand-stage:after{width:480px;height:480px;border-style:dashed;opacity:.6}.hero-icon{width:330px;border-radius:70px;transform:rotate(-6deg);box-shadow:0 30px 60px #0004;z-index:1}.float-label{position:absolute;z-index:2;border:1px solid #6986af6b;background:#0b2b55eb;box-shadow:0 10px 35px #0002;backdrop-filter:blur(15px);padding:10px 17px;border-radius:10px;font-size:16px;display:flex;align-items:center;gap:12px}.float-label svg{width:22px;height:22px;stroke:#e0bf7d;fill:none;stroke-width:1.6}.label-one{left:-5px;top:60px}.label-two{right:4px;bottom:43px}.hero-facts{position:relative;border-top:1px solid #ffffff20;display:grid;grid-template-columns:repeat(3,1fr);padding:24px 0;gap:30px}.fact{display:flex;gap:17px;align-items:center}.fact:not(:last-child){border-left:1px solid #ffffff20}.fact b{font-size:35px;color:#dfc28a;font-weight:500;line-height:1}.fact span{display:block;font-size:17px}.fact small{display:block;color:#a8b8cd;font-size:14px}
|
||||
.story{padding:88px 0;display:grid;grid-template-columns:.9fr 1.1fr;gap:95px;align-items:center}.story p{color:var(--muted);font-size:20px}.story .accent{color:var(--ink);font-weight:700}.story .eyebrow{margin-bottom:16px}.section-intro{display:flex;align-items:flex-end;justify-content:space-between;gap:40px;margin-bottom:37px}.section-intro p{max-width:430px;color:var(--muted)}
|
||||
.coverage{background:var(--pale);padding:72px 0 78px}.country-tabs{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;margin-bottom:24px}.country-tab{display:flex;align-items:center;justify-content:space-between;padding:17px 22px;background:transparent;border:1px solid #cdd8e6;color:var(--ink);border-radius:8px;transition:background .2s;min-height:83px}.country-tab strong{font-size:23px}.country-tab small{display:block;font-size:12px;letter-spacing:2px;line-height:1.2;color:var(--muted);direction:ltr}.country-tab .number{font-family:Arial;font-size:14px;color:#8b9db3}.country-tab[aria-selected=true]{background:var(--navy);border-color:var(--navy);color:white}.country-tab[aria-selected=true] small,.country-tab[aria-selected=true] .number{color:#d8bd81}.country-panel{background:#fff;border-radius:12px;padding:27px 32px;display:grid;grid-template-columns:1fr 1.4fr;gap:35px;align-items:center;border:1px solid #e2e9f2}.country-panel h3{font-size:28px;margin-bottom:5px}.country-panel p{font-size:17px;color:var(--muted)}.panel-note{border-right:2px solid #c4a266;padding-right:24px}.pill{display:inline-block;font-size:13px;border:1px solid #d9e1ec;background:#f7f9fc;padding:2px 10px;border-radius:30px;color:#49617f;margin-bottom:9px}
|
||||
.tourism{padding:90px 0}.tourism-feature{display:grid;grid-template-columns:1.15fr 1fr;background:var(--navy);color:white;border-radius:18px;overflow:hidden;min-height:415px}.tourism-photo{position:relative;background:#15395e;min-height:380px;overflow:hidden}.tourism-photo img{width:100%;height:100%;object-fit:cover;position:absolute}.photo-caption{position:absolute;bottom:0;right:0;left:0;color:white;padding:45px 28px 22px;background:linear-gradient(transparent,#041a35e6);font-size:16px}.photo-caption small{display:block;opacity:.8;font-size:12px;direction:ltr;text-align:right}.tourism-copy{padding:42px 40px;align-self:center}.tourism-copy h3{font-size:35px;margin:12px 0 15px}.tourism-copy p{color:#beccdd}.tourism-copy .pill{background:#ffffff0c;border-color:#597087;color:#e2c388}.tourism-list{padding:0;list-style:none;display:grid;grid-template-columns:1fr 1fr;gap:13px;margin:25px 0 0;font-size:16px;color:#e7edf6}.tourism-list li:before{content:'+';color:#d8b975;padding-left:10px}.tourism-foot{display:flex;justify-content:space-between;gap:25px;margin-top:20px;font-size:15px;color:var(--muted)}.tourism-foot a{color:var(--blue);text-decoration:underline;text-underline-offset:4px}
|
||||
.pillars-section{padding:80px 0}.pillars-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:24px;margin-top:35px}.pillar-card{background:var(--pale);border:1px solid #dce5f0;border-radius:16px;padding:32px;display:flex;flex-direction:column;justify-content:space-between;transition:transform .2s,box-shadow .2s}.pillar-card:hover{transform:translateY(-4px);box-shadow:0 12px 30px rgba(6,29,66,.08);border-color:#b59247}.pillar-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:18px}.pillar-num{font-size:32px;font-weight:900;color:var(--gold);line-height:1}.pillar-tag{font-size:13px;font-weight:700;color:var(--blue);background:rgba(18,79,159,.08);padding:4px 12px;border-radius:20px}.pillar-card h3{font-size:24px;margin-bottom:12px;color:var(--navy)}.pillar-card p{color:var(--muted);font-size:16px;line-height:1.7;margin-bottom:20px}.pillar-badge{font-size:13px;font-weight:600;color:var(--gold);border-top:1px solid #dce5f0;padding-top:14px}
|
||||
.ideas{background:#f7f9fc;padding:75px 0 82px}.ideas-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:0;border-top:1px solid var(--line);border-bottom:1px solid var(--line)}.idea{padding:32px 29px 30px}.idea:not(:last-child){border-left:1px solid var(--line)}.idea-index{font-size:13px;direction:ltr;display:block;text-align:right;color:#977837;letter-spacing:2px;margin-bottom:22px}.idea svg{width:34px;height:34px;fill:none;stroke:var(--blue);stroke-width:1.5;margin-bottom:15px}.idea h3{margin-bottom:13px;font-size:25px}.idea p{font-size:17px;color:var(--muted)}.benefit{margin-top:24px;padding-top:17px;border-top:1px solid var(--line);font-size:15px;color:var(--blue)}.ideas-note{font-size:14px;color:var(--muted);margin-top:20px}
|
||||
.roadmap{padding:90px 0}.steps{display:grid;grid-template-columns:repeat(3,1fr);gap:32px;margin-top:42px}.step{border-top:2px solid #dce3ec;padding-top:22px}.step.current{border-top-color:var(--gold)}.step-top{display:flex;align-items:center;justify-content:space-between;margin-bottom:14px}.step-top span{font-size:14px;color:#728096}.step-top b{font-size:28px;font-family:Arial;font-weight:400;color:#a7b4c4}.step.current .step-top span{color:#907132}.step h3{font-size:25px;margin-bottom:12px}.step p{color:var(--muted);font-size:17px}
|
||||
.partnership{border:1px solid var(--line);border-radius:15px;display:grid;grid-template-columns:160px 1fr;gap:40px;padding:32px 40px;align-items:center;margin-bottom:70px}.prize-logo{width:150px}.partnership .eyebrow{font-size:13px;margin-bottom:11px}.partnership h3{font-size:29px;margin-bottom:9px}.partnership p{font-size:18px;color:var(--muted);max-width:820px}.closing{background:var(--navy);color:white;text-align:center;padding:65px 24px}.closing .latin{color:#dbbc77;margin-bottom:15px}.closing h2{font-size:clamp(29px,4vw,46px)}.closing p{color:#acbed4;margin-top:15px;font-size:18px}.footer{padding:23px 0;display:flex;justify-content:space-between;align-items:center;gap:20px;font-size:14px;color:var(--muted)}.footer a{color:var(--blue)}.footer .latin{font-size:11px;letter-spacing:1px}
|
||||
@media(min-width:1500px){.hero-grid{min-height:530px}}
|
||||
@media(max-width:1050px){.wrap{width:calc(100% - 48px)}.links{gap:18px}.nav-tag{display:none}.hero-grid{gap:25px}.hero-icon{width:280px;border-radius:58px}.brand-stage:before{width:340px;height:340px}.brand-stage:after{width:390px;height:390px}.label-one{left:0;top:45px}.label-two{right:0;bottom:45px}.story{gap:45px}.tourism-copy{padding:30px}.idea{padding:28px 20px}.country-tab{padding:15px}.hero h1{font-size:58px}}
|
||||
@media(max-width:760px){body{font-size:17px}.wrap{width:calc(100% - 36px)}header{height:auto;min-height:86px}.nav{padding:13px 0;flex-wrap:wrap;gap:10px}.brand img{width:48px;height:48px}.brand strong{font-size:20px}.brand small{font-size:10px}.links{gap:19px;font-size:14px}.links a:last-child{display:none}.hero{padding-top:44px}.hero-grid{grid-template-columns:1fr;gap:23px;padding-bottom:35px}.hero h1{font-size:clamp(44px,10vw,62px);line-height:1.3}.hero p{font-size:18px;margin-top:20px}.hero-actions{margin-top:25px}.btn{font-size:16px;padding:10px 19px}.hero-status{font-size:13px}.brand-stage{min-height:325px;width:min(100%,390px);margin:auto}.hero-icon{width:240px;border-radius:52px}.brand-stage:before{width:285px;height:285px}.brand-stage:after{width:320px;height:320px}.float-label{font-size:14px;padding:8px 12px}.label-one{top:39px;left:0}.label-two{bottom:23px;right:0}.hero-facts{gap:15px;padding:20px 0}.fact{gap:8px;display:block}.fact b{font-size:27px}.fact span{font-size:14px}.fact small{font-size:12px}.story{grid-template-columns:1fr;gap:22px;padding:53px 0}.story p{font-size:18px}.eyebrow{font-size:13px;margin-bottom:15px}.section-intro{display:block;margin-bottom:27px}.section-intro p{margin-top:17px;font-size:17px}.coverage{padding:48px 0}.country-tabs{grid-template-columns:repeat(2,1fr);gap:9px}.country-tab{min-height:78px}.country-tab strong{font-size:22px}.country-panel{grid-template-columns:1fr;gap:18px;padding:24px}.panel-note{padding-right:16px}.tourism{padding:55px 0}.tourism-feature{grid-template-columns:1fr}.tourism-photo{min-height:280px}.tourism-copy{padding:29px 25px}.tourism-copy h3{font-size:30px}.tourism-foot{display:block;font-size:14px}.tourism-foot a{display:inline-block;margin-top:7px}.ideas{padding:50px 0}.ideas-grid{grid-template-columns:1fr}.idea{padding:28px 4px}.idea:not(:last-child){border-left:0;border-bottom:1px solid var(--line)}.idea-index{margin-bottom:14px}.idea svg{float:left}.benefit{margin-top:17px}.roadmap{padding:53px 0}.steps{grid-template-columns:1fr;gap:28px;margin-top:25px}.step{padding-top:14px}.step-top{margin-bottom:8px}.partnership{grid-template-columns:1fr;gap:18px;padding:26px;margin-bottom:45px}.prize-logo{width:120px}.partnership h3{font-size:25px}.partnership p{font-size:17px}.closing{padding:45px 22px}.footer{flex-direction:column;align-items:flex-start;gap:8px}.tourism-list{font-size:15px}}
|
||||
@media(prefers-reduced-motion:reduce){html{scroll-behavior:auto}*{transition:none!important}}
|
||||
@media print{header,.hero-actions,.hero-status,.country-tabs,.footer a{display:none}body{font-size:12pt}.hero,.closing,.tourism-feature{print-color-adjust:exact;-webkit-print-color-adjust:exact}.wrap{width:92%}.hero{padding-top:30px}.hero-grid{min-height:0;gap:25px}.hero h1{font-size:38px}.hero p{font-size:16px}.brand-stage{min-height:250px}.hero-icon{width:200px}.brand-stage:before,.brand-stage:after,.float-label{display:none}.story,.tourism,.ideas,.roadmap,.coverage{padding:25px 0}.section-intro h2,h2{font-size:28px}.story p{font-size:16px}.tourism-feature{break-inside:avoid}.idea,.step,.partnership{break-inside:avoid}.partnership{margin-bottom:25px}.closing{padding:25px}}
|
||||
</style>
|
||||
</head>
|
||||
<body id="top">
|
||||
<header><nav class="wrap nav" aria-label="التنقل الرئيسي"><a class="brand" href="#top" aria-label="سيرو ماب، بداية الصفحة"><img src="assets/siro-maps.png" alt="شعار سيرو ماب" width="62" height="62"><span><strong>سيرو ماب</strong><small>SIRO MAP</small></span></a><div class="links"><a href="#coverage">الانطلاقة</a><a href="#tourism">اكتشف العراق</a><a href="#pillars">ركائز الدليل</a><a href="#ideas">آفاق سيرو ماب</a><a href="#journey">رحلتنا</a></div><span class="nav-tag">من سيرو ماب، إلى كل وجهة</span></nav></header>
|
||||
<main>
|
||||
<section class="hero" aria-labelledby="hero-title"><div class="wrap"><div class="hero-grid"><div><div class="eyebrow">الانطلاقة الأولى · سيرو ماب</div><h1 id="hero-title">كل طريق،<br>بداية <span>حكاية.</span></h1><p>خرائط تقرّب المسافات، وحكايات تقرّبنا من المكان.<br>نبدأ بأربع دول، ونفتح من العراق بابًا لاكتشاف الثقافة والمعالم والوجهات.</p><div class="hero-actions"><a class="btn btn-gold" href="#coverage">اكتشف الانطلاقة <span class="arrow" aria-hidden="true">←</span></a><a class="btn btn-outline" href="#ideas">ما الذي نبنيه معًا؟</a></div><small class="hero-status">نسخة التطبيق قيد المراجعة على Google Play</small></div><div class="brand-stage"><img class="hero-icon" src="assets/siro-maps.png" alt="شعار سيرو ماب الذكي للملاحة والتنقل" width="512" height="512"><span class="float-label label-one"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M20 10c0 6-8 11-8 11S4 16 4 10a8 8 0 1 1 16 0Z"/><circle cx="12" cy="10" r="2.5"/></svg>وجهتك أقرب</span><span class="float-label label-two"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="m3 5 6-2 6 2 6-2v16l-6 2-6-2-6 2Z M9 3v16 M15 5v16"/></svg>اكتشف حكاية المكان</span></div></div><div class="hero-facts"><div class="fact"><b>04</b><div><span>دول في الانطلاقة</span><small>العراق · الأردن · سوريا · مصر</small></div></div><div class="fact"><b>01</b><div><span>بداية للدليل السياحي</span><small>معالم العراق أولًا</small></div></div><div class="fact"><b>ببساطة</b><div><span>خرائط بلا تسجيل</span><small>افتح التطبيق، وابدأ رحلتك</small></div></div></div></div></section>
|
||||
<section class="wrap story" aria-labelledby="story-title"><div><div class="eyebrow">فكرة تجمعنا</div><h2 id="story-title">من اسم يحمل حضارة،<br>إلى خدمة ترافق الناس.</h2></div><p>تعتمد سيرو ماب على الابتكار والسيادة الرقمية وتوفير التكاليف. واليوم، نمنح هذا المشروع حضورًا قويًا في الحياة اليومية: خرائط سهلة ومستقلة، ودليل يكشف ما حولنا من وجهات ومعالم.<br><span class="accent">خطوة أولى لشراكتنا، وأساس ننمو عليه معًا.</span></p></section>
|
||||
<section class="coverage" id="coverage" aria-labelledby="coverage-title"><div class="wrap"><div class="section-intro"><div><div class="eyebrow">أربع دول · تجربة واحدة</div><h2 id="coverage-title">بداية قريبة منّا.</h2></div><p>خرائط للاستخدام دون إنشاء حساب. ننطلق بهذا النطاق، ونواصل تحسين البيانات والتجربة قبل التوسع إلى مزيد من الدول العربية.</p></div><div class="country-tabs" role="tablist" aria-label="دول الانطلاقة"><button id="tab-iq" class="country-tab" role="tab" aria-selected="true" aria-controls="country-panel" data-country="iq"><span><strong>العراق</strong><small>IRAQ</small></span><span class="number">01</span></button><button id="tab-jo" class="country-tab" role="tab" aria-selected="false" aria-controls="country-panel" tabindex="-1" data-country="jo"><span><strong>الأردن</strong><small>JORDAN</small></span><span class="number">02</span></button><button id="tab-sy" class="country-tab" role="tab" aria-selected="false" aria-controls="country-panel" tabindex="-1" data-country="sy"><span><strong>سوريا</strong><small>SYRIA</small></span><span class="number">03</span></button><button id="tab-eg" class="country-tab" role="tab" aria-selected="false" aria-controls="country-panel" tabindex="-1" data-country="eg"><span><strong>مصر</strong><small>EGYPT</small></span><span class="number">04</span></button></div><div id="country-panel" class="country-panel" role="tabpanel" tabindex="0" aria-labelledby="tab-iq"><div><span class="pill">ضمن نطاق الانطلاقة</span><h3 id="country-title">العراق، هنا تبدأ الحكاية.</h3><p id="country-description">الخرائط أولًا، ومنها نبدأ بناء الدليل السياحي.</p></div><p id="country-note" class="panel-note">نبدأ بإثراء المعالم السياحية والأثرية العراقية: قصة المكان، موقعه، ومعلومات تساعد على زيارته.</p></div></div></section>
|
||||
<section class="wrap tourism" id="tourism" aria-labelledby="tourism-title"><div class="section-intro"><div><div class="eyebrow">الخطوة التالية · الدليل السياحي</div><h2 id="tourism-title">للمكان أكثر من عنوان.</h2></div><p>من معالم العراق نبدأ. نريد أن تتحول النقطة على الخريطة إلى حكاية تعرفها، ووجهة تستطيع التخطيط لزيارتها.</p></div><div class="tourism-feature"><div class="tourism-photo" id="tourism-photo"><img src="assets/erbil-citadel.jpg" alt="قلعة أربيل التاريخية والأسواق المحيطة بها" width="4032" height="3024" loading="lazy"><div class="photo-caption">قلعة أربيل · العراق<small>الصورة: <a href="https://commons.wikimedia.org/wiki/File:Citadel_of_Erbil.jpg" target="_blank" rel="noopener">Sarbast.T.Hameed / Wikimedia Commons</a> · <a href="https://creativecommons.org/licenses/by-sa/4.0/" target="_blank" rel="noopener">CC BY-SA 4.0</a> · عرض مقتصّ</small></div></div><div class="tourism-copy"><span class="pill">تصور قادم داخل التطبيق</span><h3>اكتشف ما حولك،<br>واعرف ما وراءه.</h3><p>دليل قريب منك للمعالم والمواقع الأثرية، بمعلومات موثقة تُراجع وتُحدّث مع الشركاء المحليين.</p><ul class="tourism-list"><li>قصة موجزة لكل معلم</li><li>صور وتعريف بالمكان</li><li>المداخل ومعلومات الزيارة</li><li>وجهات قريبة منك</li></ul></div></div><div class="tourism-foot"><span>أمثلة للإثراء: قلعة أربيل، بابل، وآثار ومدن العراق. تُراجع إتاحة الزيارة لكل موقع قبل إدراجه.</span><a href="https://whc.unesco.org/en/statesparties/iq" target="_blank" rel="noopener">تراث العراق لدى اليونسكو ↗</a></div></section>
|
||||
<section class="wrap pillars-section" id="pillars" aria-labelledby="pillars-title"><div class="section-intro"><div><div class="eyebrow">منهجية العمل · الركائز الأربع للدليل</div><h2 id="pillars-title">كيف نُعيد صياغة تجربة المعالم؟</h2></div><p>لا نكتفي بوضع نقطة صامتة على الخارطة؛ بل نمزج دقة الملاحة الميدانية بسردية التاريخ والسيادة التقنية.</p></div><div class="pillars-grid"><div class="pillar-card"><div class="pillar-header"><span class="pillar-num">01</span><span class="pillar-tag">الملاحة الواقعية</span></div><h3>التوجيه للبوابات الفعلية</h3><p>توجيه السائقين والزوار مباشرة إلى نقاط الدخول الرسمية ومواقف السيارات بدلاً من التوجيه الخاطئ إلى منتصف المواقع الشاسعة أو الأسوار المغلقة.</p><div class="pillar-badge">دقة الوصول الميداني</div></div><div class="pillar-card"><div class="pillar-header"><span class="pillar-num">02</span><span class="pillar-tag">التوثيق الحضاري</span></div><h3>سردية المكان في 60 ثانية</h3><p>بطاقة ذكية ومكثفة لكل معلم: الحقبة التاريخية، من بنى المكان، وأهميته التراثية العالمية، بصور موثقة بدعم المبادرات الثقافية الوطنية.</p><div class="pillar-badge">معرفة سريعة وموثوقة</div></div><div class="pillar-card"><div class="pillar-header"><span class="pillar-num">03</span><span class="pillar-tag">السيادة التقنية</span></div><h3>استكشاف كامل بدون إنترنت</h3><p>حزم محلية خفيفة تحفظ قصص المعالم ومساراتها على الهاتف، لتعمل بكفاءة وسرعة فائقة حتى في المناطق الصحراوية والنائية.</p><div class="pillar-badge">استمرارية في الميدان</div></div><div class="pillar-card"><div class="pillar-header"><span class="pillar-num">04</span><span class="pillar-tag">منظومة النقل</span></div><h3>جسر مباشر بين الاستكشاف والوصول</h3><p>ربط قرار الزيارة فوراً بطلب وسيلة مواصلات ذكية أو بدء مسار الملاحة، لتحويل الاكتشاف الثقافي إلى حركة يومية واقتصاد سياحي نابض.</p><div class="pillar-badge">تكامل ذكي مع الحركة</div></div></div></section>
|
||||
<section class="ideas" id="ideas" aria-labelledby="ideas-title"><div class="wrap"><div class="section-intro"><div><div class="eyebrow">آفاق سيرو ماب</div><h2 id="ideas-title">كل إضافة، قيمة جديدة.</h2></div><p>أفكار نطوّرها تدريجيًا؛ تخدم المستخدم، وتمنح شركاء سيرو ماب حضورًا يصل إلى الناس.</p></div><div class="ideas-grid"><article class="idea"><span class="idea-index">01 / الشركاء</span><svg viewBox="0 0 32 32" aria-hidden="true"><rect x="3" y="7" width="26" height="19" rx="3"/><path d="M3 13h26 M8 20h6 M21 18v6 M18 21h6"/></svg><h3>مزايا أقرب إليك</h3><p>دليل للمستشفيات والفنادق ومراكز الدورات المتعاقدة مع شبكة سيرو، يعرض موقع كل جهة ومزايا البطاقة وشروطها المعتمدة.</p><div class="benefit">الفائدة: يجد حامل البطاقة أين يستفيد منها.</div></article><article class="idea"><span class="idea-index">02 / الفعاليات</span><svg viewBox="0 0 32 32" aria-hidden="true"><rect x="5" y="6" width="22" height="23" rx="3"/><path d="M10 3v7 M22 3v7 M5 14h22 M10 20h4 M18 20h4 M10 24h4"/></svg><h3>من الدعوة إلى الوجهة</h3><p>خريطة للمؤتمرات والفعاليات الكبرى: موقع القاعة، المداخل، المواقف والوجهات المحيطة، تُشارك برابط أو رمز QR.</p><div class="benefit">الفائدة: كل مناسبة فرصة حقيقية لتجربة التطبيق.</div></article><article class="idea"><span class="idea-index">03 / جودة البيانات</span><svg viewBox="0 0 32 32" aria-hidden="true"><path d="M25 14c0 7-9 14-9 14S7 21 7 14a9 9 0 1 1 18 0Z"/><path d="m12 14 3 3 6-6"/></svg><h3>معًا، نصحّح الخريطة</h3><p>اقتراح مكان، تصحيح عنوان أو تحديد مدخل. مساهمات تُراجع، وبيانات استخدام اختيارية تساعد على تحسين التجربة.</p><div class="benefit">الفائدة: عناوين أدق، وأساس أفضل للنقل لاحقًا.</div></article></div><p class="ideas-note">هذه إضافات مقترحة للتطوير؛ ظهور الجهات والمزايا مرتبط بتأكيد بياناتها واتفاقاتها.</p></div></section>
|
||||
<section class="wrap roadmap" id="journey" aria-labelledby="journey-title"><div class="eyebrow">رحلة تنمو بخطوات واضحة</div><h2 id="journey-title">نبدأ بما ينفع اليوم.<br>ونبني ما نحتاجه غدًا.</h2><div class="steps"><article class="step current"><div class="step-top"><span>الانطلاقة الأولى</span><b>01</b></div><h3>خرائط بين يدي الناس</h3><p>أربع دول، بلا تسجيل. تعريف بسيرو ماب، وتجارب فعلية تُحسّن المنتج وتبني حضور الاسم.</p></article><article class="step"><div class="step-top"><span>الإثراء التدريجي</span><b>02</b></div><h3>دليل يستحق العودة</h3><p>معالم العراق أولًا، ثم الشركاء والفعاليات؛ أسباب متجددة لفتح التطبيق والاستفادة منه.</p></article><article class="step"><div class="step-top"><span>الأفق القادم</span><b>03</b></div><h3>نحو سيرو للنقل الذكي</h3><p>ربط اكتشاف الوجهة بالوصول إليها عند إطلاق خدمة النقل، والتوسع إلى دول عربية أخرى تدريجيًا.</p></article></div></section>
|
||||
|
||||
<section class="closing"><span class="latin">SIRO MAP · THE FIRST JOURNEY</span><h2>من هنا تبدأ حكايتنا على الخريطة.</h2><p>العراق · الأردن · سوريا · مصر</p></section>
|
||||
</main><footer class="wrap footer"><span>سيرو ماب © 2026 <span aria-hidden="true"> — </span> الانطلاقة الأولى ورؤية التطوير</span><a href="#top">العودة إلى البداية ↑</a></footer>
|
||||
<script>
|
||||
const countries={iq:{title:'العراق، هنا تبدأ الحكاية.',description:'الخرائط أولًا، ومنها نبدأ بناء الدليل السياحي.',note:'نبدأ بإثراء المعالم السياحية والأثرية العراقية: قصة المكان، موقعه، ومعلومات تساعد على زيارته.'},jo:{title:'الأردن، ضمن أولى وجهاتنا.',description:'استخدام الخرائط دون الحاجة إلى إنشاء حساب.',note:'الأردن ضمن نطاق الخرائط في الانطلاقة الأولى. يبدأ إثراء المحتوى السياحي من العراق، ثم يتوسع تدريجيًا.'},sy:{title:'سوريا، أقرب على الخريطة.',description:'تجربة خرائط سهلة ضمن الدول الأربع الأولى.',note:'نطوّر جودة البيانات بالاختبار والمراجعة ومساهمات المستخدمين؛ لتصبح معلومات الأماكن أكثر دقة وفائدة.'},eg:{title:'مصر، امتداد لانطلاقتنا.',description:'خرائط بلا تسجيل ضمن تجربة سيرو ماب.',note:'مصر ضمن نطاق الانطلاقة. نبدأ بخدمة الخرائط، ونضيف الدليل والوجهات والشراكات على مراحل واضحة.'}};
|
||||
const tabs=[...document.querySelectorAll('[role=tab]')];function choose(tab){tabs.forEach(t=>{const selected=t===tab;t.setAttribute('aria-selected',String(selected));t.tabIndex=selected?0:-1});const item=countries[tab.dataset.country];document.getElementById('country-title').textContent=item.title;document.getElementById('country-description').textContent=item.description;document.getElementById('country-note').textContent=item.note;document.getElementById('country-panel').setAttribute('aria-labelledby',tab.id)}tabs.forEach((tab,index)=>{tab.addEventListener('click',()=>choose(tab));tab.addEventListener('keydown',event=>{let next;if(event.key==='ArrowLeft')next=(index+1)%tabs.length;if(event.key==='ArrowRight')next=(index-1+tabs.length)%tabs.length;if(event.key==='Home')next=0;if(event.key==='End')next=tabs.length-1;if(next!==undefined){event.preventDefault();choose(tabs[next]);tabs[next].focus()}})});
|
||||
</script>
|
||||
</body></html>
|
||||
@@ -12,7 +12,7 @@
|
||||
IMPORTANT NOTICE FOR APP REVIEWERS:
|
||||
|
||||
1. Operational Geographic Coverage:
|
||||
Uruk Map is engineered on an independent, self-hosted regional spatial vector engine. Our active vector map tiles and routing servers are currently provisioned specifically for three operational countries in the MENA region:
|
||||
Siro Map is engineered on an independent, self-hosted regional spatial vector engine. Our active vector map tiles and routing servers are currently provisioned specifically for three operational countries in the MENA region:
|
||||
- Jordan (الأردن)
|
||||
- Iraq (العراق)
|
||||
- Egypt (مصر)
|
||||
@@ -51,7 +51,7 @@ For any technical inquiries during review, please contact: support@intaleqapp.co
|
||||
ملاحظة هامة لفاحصي ومراجعي التطبيق:
|
||||
|
||||
1. نطاق التغطية الجغرافية التشغيلية:
|
||||
تعتمد «خرائط أوروك» على منظومة خرائط وملاحة فيكتورية مستقلة ومستضافة ذاتياً، وتغطي خوادم الخرائط والبيانات المكانية الحالية ثلاث دول تشغيلية رئيسية في منطقة الشرق الأوسط وشمال أفريقيا:
|
||||
تعتمد «خرائط سيرو» على منظومة خرائط وملاحة فيكتورية مستقلة ومستضافة ذاتياً، وتغطي خوادم الخرائط والبيانات المكانية الحالية ثلاث دول تشغيلية رئيسية في منطقة الشرق الأوسط وشمال أفريقيا:
|
||||
- الأردن
|
||||
- العراق
|
||||
- مصر
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# ملف بيانات ونصوص النشر على Google Play Console | خرائط أوروك (Uruk Map)
|
||||
# ملف بيانات ونصوص النشر على Google Play Console | خرائط سيرو (Siro Map)
|
||||
================================================================================
|
||||
|
||||
هذا الملف يحتوي على كافة النصوص والبيانات المطلوبة لرفع وتعبئة تطبيق **Uruk Map** على **Google Play Console** باللغتين العربية والإنجليزية، وفق معايير خوارزميات تحسين متجر التطبيقات (ASO) وتوافقاً مع الحدود الرسمية لعدد الحروف.
|
||||
هذا الملف يحتوي على كافة النصوص والبيانات المطلوبة لرفع وتعبئة تطبيق **Siro Map** على **Google Play Console** باللغتين العربية والإنجليزية، وفق معايير خوارزميات تحسين متجر التطبيقات (ASO) وتوافقاً مع الحدود الرسمية لعدد الحروف، مع الحفاظ على معرف الحزمة التقني المعتمد على المتجر: `com.urukmap.app`.
|
||||
|
||||
---
|
||||
|
||||
@@ -10,9 +10,9 @@
|
||||
### باللغة العربية (Arabic - Default)
|
||||
* **اسم التطبيق (App name - بحد أقصى 30 حرفاً)**:
|
||||
```text
|
||||
خرائط أوروك: ملاحة وتوجيه ذكي
|
||||
خرائط سيرو: ملاحة وتوجيه ذكي
|
||||
```
|
||||
*(بديل مقترح: `خرائط أوروك - Uruk Map`)*
|
||||
*(بديل مقترح: `خرائط سيرو - Siro Map`)*
|
||||
|
||||
* **الوصف القصير (Short description - بحد أقصى 80 حرفاً)**:
|
||||
```text
|
||||
@@ -21,13 +21,11 @@
|
||||
|
||||
* **الوصف الكامل (Full description - بحد أقصى 4000 حرف)**:
|
||||
```text
|
||||
«خرائط أوروك - Uruk Map» هي المنظومة المتقدمة للملاحة والخرائط الذكية المصممة خصيصاً لتلبية احتياجات العالم العربي ومنطقة الشرق الأوسط وشمال أفريقيا، بموثوقية فائقة وتقنيات تشغيل مستقلة.
|
||||
«خرائط سيرو - Siro Map» هي المنظومة المتقدمة للملاحة والخرائط الذكية المصممة خصيصاً لتلبية احتياجات العالم العربي ومنطقة الشرق الأوسط وشمال أفريقيا، بموثوقية فائقة وتقنيات تشغيل مستقلة.
|
||||
|
||||
🏆 مشروع يحظى برعاية ودعم برنامج جائزة أوروك الدولية للابتكار وتطوير الخدمات التقنية.
|
||||
سواء كنت تتنقل داخل المدن المزدحمة، أو تقود عبر الطرق السريعة في المناطق التي تعاني من ضعف شبكات التغطية أو قيود خدمات الخرائط العالمية (في العراق، السودان، اليمن، سوريا، والمنطقة)، تمنحك خرائط سيرو تجربة تنقل دقيقة، سريعة، واقتصادية في استهلاك البيانات.
|
||||
|
||||
سواء كنت تتنقل داخل المدن المزدحمة، أو تقود عبر الطرق السريعة في المناطق التي تعاني من ضعف شبكات التغطية أو قيود خدمات الخرائط العالمية (في العراق، السودان، اليمن، سوريا، والمنطقة)، تمنحك خرائط أوروك تجربة تنقل دقيقة، سريعة، واقتصادية في استهلاك البيانات.
|
||||
|
||||
لماذا تختار خرائط أوروك؟
|
||||
لماذا تختار خرائط سيرو؟
|
||||
|
||||
🧠 1. كاش ذكي واستدعاء فوري من الذاكرة
|
||||
نظام متقدم لحفظ بلاطات الخريطة في ذاكرة الجهاز (Memory Cache) لاستدعائها فوراً دون تأخير، مما يوفر سرعة استجابة فائقة ويقلل استهلاك باقة الإنترنت.
|
||||
@@ -56,7 +54,7 @@
|
||||
🛡️ 9. حماية الخصوصية وتوفير البطارية
|
||||
تصميم برمجي حديث ومحسّن لا يستهلك طاقة البطارية ولا يجمع بياناتك الخاصة بدون إذن.
|
||||
|
||||
حمّل «خرائط أوروك» اليوم واستمتع بتجربة ملاحة حرة، ذكية، ومستقلة 100%!
|
||||
حمّل «خرائط سيرو» اليوم واستمتع بتجربة ملاحة حرة، ذكية، ومستقلة 100%!
|
||||
```
|
||||
|
||||
---
|
||||
@@ -64,9 +62,9 @@
|
||||
### باللغة الإنجليزية (English - United States)
|
||||
* **App Name (Max 30 characters)**:
|
||||
```text
|
||||
Uruk Map: GPS Navigation
|
||||
Siro Map: GPS Navigation
|
||||
```
|
||||
*(Alternative: `Uruk Map - Smart Navigation`)*
|
||||
*(Alternative: `Siro Map - Smart Navigation`)*
|
||||
|
||||
* **Short Description (Max 80 characters)**:
|
||||
```text
|
||||
@@ -75,11 +73,9 @@
|
||||
|
||||
* **Full Description (Max 4000 characters)**:
|
||||
```text
|
||||
Uruk Map is an advanced GPS navigation and digital mapping platform engineered specifically for the Middle East, North Africa, and regional markets requiring dependable offline capabilities.
|
||||
Siro Map is an advanced GPS navigation and digital mapping platform engineered specifically for the Middle East, North Africa, and regional markets requiring dependable offline capabilities.
|
||||
|
||||
🏆 Backed and supported by the Uruk International Prize program for technological innovation and digital service infrastructure.
|
||||
|
||||
Whether commuting through bustling metropolitan hubs or navigating transit routes across Iraq, Sudan, Yemen, Syria, and the broader MENA region, Uruk Map delivers unmatched precision, high-speed memory caching, and independent offline reliability.
|
||||
Whether commuting through bustling metropolitan hubs or navigating transit routes across Iraq, Sudan, Yemen, Syria, and the broader MENA region, Siro Map delivers unmatched precision, high-speed memory caching, and independent offline reliability.
|
||||
|
||||
KEY FEATURES:
|
||||
|
||||
@@ -110,7 +106,7 @@
|
||||
🔋 9. Battery & Data Optimized
|
||||
Engineered with high-performance native rendering (MapLibre vector graphics) that maximizes battery endurance and minimizes background data.
|
||||
|
||||
Download Uruk Map today and experience fast, smart, and independent navigation!
|
||||
Download Siro Map today and experience fast, smart, and independent navigation!
|
||||
```
|
||||
|
||||
---
|
||||
@@ -135,14 +131,12 @@
|
||||
1. **أيقونة التطبيق (App Icon)**:
|
||||
- القياس: `512 x 512` بكسل
|
||||
- الصيغة: `PNG 32-bit`
|
||||
- المسار الجاهز: `assets/images/uruk_map_playstore_512.png`
|
||||
- *(ملف تم إنشاؤه مسبقاً ومطابق لمعايير جوجل بلاي)*.
|
||||
- المسار الجاهز: `assets/images/siro_map_playstore_512.png`
|
||||
|
||||
2. **الرسم المميز / البانر (Feature Graphic)**:
|
||||
- القياس: `1024 x 500` بكسل
|
||||
- الصيغة: `JPEG / PNG` (بدون شفافية)
|
||||
- المسار الجاهز: `assets/images/uruk_playstore_feature_graphic_1024x500.jpg`
|
||||
- *(بانر ملكي فاخر يدمج تمثال أوروك الذهبي وشبكة الطرق المتوهجة)*.
|
||||
- المسار الجاهز: `assets/images/siro_playstore_feature_graphic_1024x500.png`
|
||||
|
||||
3. **لقطات الشاشة (Screenshots)**:
|
||||
- الحد الأدنى: 2 لقطات (المستحسن من 4 إلى 8 لقطات).
|
||||
@@ -151,7 +145,7 @@
|
||||
1. شاشة الخريطة الرئيسية مع شريط البحث والملاحة الفيكتورية.
|
||||
2. شاشة التوجيه الفعلي Turn-by-Turn مع بطاقة PiP ولوحة السرعة.
|
||||
3. شاشة طبقات الخريطة وتخصيص أيقونة المركبة.
|
||||
4. شاشة الاعتماد وجائزة أوروك الدولية للسيادة المكانية.
|
||||
4. شاشة بطاقة التعريف ومنظومة سيرو ماب للسيادة المكانية.
|
||||
|
||||
---
|
||||
|
||||
@@ -196,7 +190,7 @@
|
||||
IMPORTANT NOTICE FOR APP REVIEWERS:
|
||||
|
||||
1. Operational Geographic Coverage:
|
||||
Uruk Map operates on an independent, self-hosted regional spatial vector engine. Our vector map tiles and routing servers are currently live specifically for three operational countries in the MENA region:
|
||||
Siro Map operates on an independent, self-hosted regional spatial vector engine. Our vector map tiles and routing servers are currently live specifically for three operational countries in the MENA region:
|
||||
- Jordan (الأردن)
|
||||
- Iraq (العراق)
|
||||
- Egypt (مصر)
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<uses-permission android:name="androidx.car.app.ACCESS_SURFACE"/>
|
||||
|
||||
<application
|
||||
android:label="Uruk Map"
|
||||
android:label="Siro Map"
|
||||
android:name="${applicationName}"
|
||||
android:icon="@mipmap/ic_launcher">
|
||||
<activity
|
||||
|
||||
|
Before Width: | Height: | Size: 9.7 KiB After Width: | Height: | Size: 7.1 KiB |
|
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 276 KiB |
|
After Width: | Height: | Size: 157 KiB |
|
After Width: | Height: | Size: 800 KiB |
|
Before Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 333 KiB |
|
Before Width: | Height: | Size: 139 KiB |
|
Before Width: | Height: | Size: 139 KiB |
|
Before Width: | Height: | Size: 392 KiB |
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.0 MiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 5.4 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 9.7 KiB |
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 8.4 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 7.8 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 30 KiB |
@@ -7,7 +7,7 @@
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Uruk Map</string>
|
||||
<string>Siro Map</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
@@ -15,7 +15,7 @@
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>uruk_map</string>
|
||||
<string>siro_maps</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
@@ -27,9 +27,9 @@
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>NSLocationWhenInUseUsageDescription</key>
|
||||
<string>يستخدم تطبيق خرائط أوروك (Uruk Map) موقعك لعرض خريطة تفاعلية وتوفير الملاحة الحية الدقيقة.</string>
|
||||
<string>يستخدم تطبيق خرائط سيرو (Siro Map) موقعك لعرض خريطة تفاعلية وتوفير الملاحة الحية الدقيقة.</string>
|
||||
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
|
||||
<string>يستخدم تطبيق خرائط أوروك (Uruk Map) موقعك في الخلفية لتقديم التوجيهات الصوتية الحية وتنبيهات الطريق أثناء القيادة.</string>
|
||||
<string>يستخدم تطبيق خرائط سيرو (Siro Map) موقعك في الخلفية لتقديم التوجيهات الصوتية الحية وتنبيهات الطريق أثناء القيادة.</string>
|
||||
<key>UIBackgroundModes</key>
|
||||
<array>
|
||||
<string>location</string>
|
||||
|
||||
@@ -20,10 +20,10 @@ class AppColors {
|
||||
static const Color tacticalNavy = Color(0xFF0B192C);
|
||||
static const Color tacticalEmerald = Color(0xFF059669);
|
||||
static const Color sovereignGold = Color(0xFFD97706);
|
||||
static const Color urukGold = Color(0xFFD4AF37);
|
||||
static const Color urukGoldLight = Color(0xFFFFF9E6);
|
||||
static const Color urukGoldDark = Color(0xFF8C6B1C);
|
||||
static const Color urukGoldBorder = Color(0x40D4AF37);
|
||||
static const Color siroGold = Color(0xFFD4AF37);
|
||||
static const Color siroGoldLight = Color(0xFFFFF9E6);
|
||||
static const Color siroGoldDark = Color(0xFF8C6B1C);
|
||||
static const Color siroGoldBorder = Color(0x40D4AF37);
|
||||
static const Color coralDanger = Color(0xFFDC2626);
|
||||
|
||||
// Borders & Dividers
|
||||
|
||||
@@ -55,7 +55,7 @@ class SiroMapsApp extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
title: 'خرائط أوروك - Uruk Map',
|
||||
title: 'خرائط سيرو - Siro Map',
|
||||
debugShowCheckedModeBanner: false,
|
||||
theme: AppTheme.lightTheme,
|
||||
locale: const Locale('ar', 'JO'),
|
||||
|
||||
@@ -21,6 +21,7 @@ import 'widgets/report_hazard_sheet.dart';
|
||||
import 'widgets/add_place_sheet.dart';
|
||||
import 'widgets/vehicle_customizer_sheet.dart';
|
||||
import 'widgets/about_awards_sheet.dart';
|
||||
import 'widgets/cinematic_3d_studio_sheet.dart';
|
||||
|
||||
class MapView extends StatefulWidget {
|
||||
const MapView({super.key});
|
||||
@@ -116,6 +117,18 @@ class _MapViewState extends State<MapView> {
|
||||
);
|
||||
}
|
||||
|
||||
void _showCinematic3DStudio(BuildContext context, NavigationCubit cubit, NavigationState state) {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
backgroundColor: Colors.transparent,
|
||||
builder: (_) => Cinematic3DStudioSheet(
|
||||
cubit: cubit,
|
||||
state: state,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showLayerSelector(BuildContext context, NavigationCubit cubit, MapThemeType current) {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
@@ -126,6 +139,10 @@ class _MapViewState extends State<MapView> {
|
||||
cubit.setMapTheme(theme);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
onOpenCinematic3DStudio: () {
|
||||
Navigator.of(context).pop();
|
||||
_showCinematic3DStudio(context, cubit, cubit.state);
|
||||
},
|
||||
onOpenVehicleCustomizer: () {
|
||||
Navigator.of(context).pop();
|
||||
_showVehicleCustomizer(context, cubit, cubit.state);
|
||||
@@ -1251,6 +1268,14 @@ class _MapViewState extends State<MapView> {
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// 3D Cinematic Studio Button
|
||||
_buildFloatingCircle(
|
||||
icon: Icons.view_in_ar_rounded,
|
||||
color: const Color(0xFFEA580C),
|
||||
tooltip: 'استوديو التضاريس والسينما 3D',
|
||||
onTap: () => _showCinematic3DStudio(context, cubit, state),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
// Layer Selector Button
|
||||
_buildFloatingCircle(
|
||||
icon: Icons.layers_rounded,
|
||||
@@ -1259,11 +1284,11 @@ class _MapViewState extends State<MapView> {
|
||||
onTap: () => _showLayerSelector(context, cubit, state.mapTheme),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
// Uruk International Prize & Platform Architecture Button
|
||||
// Siro Map Platform Architecture & Info Button
|
||||
_buildFloatingCircle(
|
||||
icon: Icons.workspace_premium_rounded,
|
||||
color: AppColors.urukGoldDark,
|
||||
tooltip: 'عن جائزة أوروك والمنظومة التقنية',
|
||||
icon: Icons.info_outline_rounded,
|
||||
color: AppColors.appleBlue,
|
||||
tooltip: 'عن سيرو ماب والمنظومة التقنية',
|
||||
onTap: () => _showAboutAwardsSheet(context),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
@@ -56,12 +56,12 @@ class AboutAwardsSheet extends StatelessWidget {
|
||||
shape: BoxShape.circle,
|
||||
color: AppColors.pureWhite,
|
||||
border: Border.all(
|
||||
color: AppColors.urukGold.withValues(alpha: 0.45),
|
||||
color: AppColors.appleBlue.withValues(alpha: 0.35),
|
||||
width: 2,
|
||||
),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: AppColors.urukGold.withValues(alpha: 0.2),
|
||||
color: AppColors.appleBlue.withValues(alpha: 0.15),
|
||||
blurRadius: 26,
|
||||
offset: const Offset(0, 8),
|
||||
),
|
||||
@@ -70,14 +70,14 @@ class AboutAwardsSheet extends StatelessWidget {
|
||||
padding: const EdgeInsets.all(6),
|
||||
child: ClipOval(
|
||||
child: Image.asset(
|
||||
'assets/images/uruk_prize_logo.png',
|
||||
'assets/images/siro_maps_logo.png',
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 14),
|
||||
Text(
|
||||
'جائزة أوروك الدولية',
|
||||
'خرائط سيرو',
|
||||
style: GoogleFonts.alexandria(
|
||||
fontSize: 22,
|
||||
fontWeight: FontWeight.w800,
|
||||
@@ -86,11 +86,11 @@ class AboutAwardsSheet extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
'URUK INTERNATIONAL PRIZE',
|
||||
'SIRO MAP',
|
||||
style: GoogleFonts.alexandria(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColors.urukGoldDark,
|
||||
color: AppColors.appleBlue,
|
||||
letterSpacing: 1.5,
|
||||
),
|
||||
),
|
||||
@@ -98,25 +98,25 @@ class AboutAwardsSheet extends StatelessWidget {
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 6),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.urukGoldLight,
|
||||
color: AppColors.surfaceMuted,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(color: AppColors.urukGoldBorder),
|
||||
border: Border.all(color: AppColors.borderSubtle),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.workspace_premium_rounded,
|
||||
Icons.verified_rounded,
|
||||
size: 16,
|
||||
color: AppColors.urukGoldDark,
|
||||
color: AppColors.appleBlue,
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
'رعاية ودعم الابتكار والخدمات التقنية',
|
||||
'منظومة الملاحة والخرائط الذكية المستقلة',
|
||||
style: GoogleFonts.alexandria(
|
||||
fontSize: 11.5,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColors.urukGoldDark,
|
||||
color: AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -128,16 +128,16 @@ class AboutAwardsSheet extends StatelessWidget {
|
||||
|
||||
const SizedBox(height: 22),
|
||||
|
||||
// ── 2. URUK PRIZE MISSION & ROLE ──
|
||||
// ── 2. SIRO MAP MISSION & VISION ──
|
||||
_buildSectionCard(
|
||||
icon: Icons.lightbulb_rounded,
|
||||
iconColor: AppColors.urukGoldDark,
|
||||
title: 'دور جائزة أوروك ورسالتها التقنية',
|
||||
icon: Icons.explore_rounded,
|
||||
iconColor: AppColors.appleBlue,
|
||||
title: 'عن سيرو ماب والرؤية التقنية',
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'تستلهم جائزة أوروك الدولية رمزيتها من حضارة أوروك العريقة — مهد التنظيم المدني والقياسات الأولى في تاريخ الإنسانية — لتبني وتدعم المشاريع والخدمات التقنية المبتكرة في العالم العربي والشرق الأوسط.',
|
||||
'تعتمد خرائط سيرو (Siro Map) على منظومة خرائط وملاحة فيكتورية متقدمة ومستضافة ذاتياً، لتقديم حلول مكانية متطورة تخدم التنقل الذكي وسلاسل الإمداد في العالم العربي ومنطقة الشرق الأوسط وشمال أفريقيا.',
|
||||
style: GoogleFonts.alexandria(
|
||||
fontSize: 12.5,
|
||||
height: 1.65,
|
||||
@@ -147,7 +147,7 @@ class AboutAwardsSheet extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Text(
|
||||
'تعمل الجائزة وبرامجها على تشجيع التوسع في البنى التحتية الرقمية، وتمكين الحلول الذكية المستقلة التي تسد الفجوات التقنية وتخدم المجتمعات وقطاعات النقل والحركة بكفاءة عالية.',
|
||||
'تهدف المنظومة إلى تمكين الاستقلالية الرقمية الجغرافية الكاملة، وتقديم خرائط عالية السرعة والدقة مع خفض جذري في تكاليف واستهلاك البيانات مقارنة بالخدمات العالمية التقليدية.',
|
||||
style: GoogleFonts.alexandria(
|
||||
fontSize: 12.5,
|
||||
height: 1.65,
|
||||
@@ -191,7 +191,7 @@ class AboutAwardsSheet extends StatelessWidget {
|
||||
const Divider(height: 18, color: AppColors.borderSubtle),
|
||||
_buildTechRow(
|
||||
icon: Icons.public_rounded,
|
||||
iconColor: AppColors.urukGoldDark,
|
||||
iconColor: AppColors.appleBlue,
|
||||
title: 'تغطية الأسواق الإقليمية الحيوية',
|
||||
desc: 'تركيز استراتيجي على توفير بيانات طرق تفصيلية ومحدثة للأسواق التي تعاني من نقص أو قيود في الخرائط العالمية (العراق، السودان، اليمن، سوريا، والمنطقة).',
|
||||
),
|
||||
|
||||
@@ -55,7 +55,7 @@ class _AddPlaceSheetState extends State<AddPlaceSheet> {
|
||||
),
|
||||
const SizedBox(height: 18),
|
||||
const Text(
|
||||
'إضافة مكان جديد إلى خرائط أوروك',
|
||||
'إضافة مكان جديد إلى خرائط سيرو',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w700),
|
||||
),
|
||||
|
||||
@@ -0,0 +1,756 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../../../core/constants/app_colors.dart';
|
||||
import '../../../../logic/cubits/navigation/navigation_cubit.dart';
|
||||
import '../../../../logic/cubits/navigation/navigation_state.dart';
|
||||
|
||||
class Cinematic3DStudioSheet extends StatefulWidget {
|
||||
final NavigationCubit cubit;
|
||||
final NavigationState state;
|
||||
|
||||
const Cinematic3DStudioSheet({
|
||||
super.key,
|
||||
required this.cubit,
|
||||
required this.state,
|
||||
});
|
||||
|
||||
@override
|
||||
State<Cinematic3DStudioSheet> createState() => _Cinematic3DStudioSheetState();
|
||||
}
|
||||
|
||||
class _Cinematic3DStudioSheetState extends State<Cinematic3DStudioSheet> {
|
||||
// Camera Tilt State
|
||||
double _tilt = 55.0;
|
||||
|
||||
// Orbit State
|
||||
bool _isOrbiting = false;
|
||||
double _orbitSpeed = 1.0;
|
||||
Timer? _orbitTimer;
|
||||
|
||||
// Sun & Shadow Simulation State
|
||||
// Range: 330 minutes (05:30 AM) to 1260 minutes (09:00 PM)
|
||||
int _sunTimeMinutes = 1110; // Default: 18:30 (Golden Hour Sunset)
|
||||
bool _isSunPlaying = false;
|
||||
Timer? _sunTimer;
|
||||
|
||||
// Layer Toggles
|
||||
bool _showBuildings = true;
|
||||
bool _showContours = true;
|
||||
bool _showHillshading = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
// Get initial camera tilt if available
|
||||
final initialPos = widget.cubit.mapController?.cameraPosition;
|
||||
if (initialPos != null && initialPos.tilt > 0) {
|
||||
_tilt = initialPos.tilt;
|
||||
} else {
|
||||
// Set to 55 degrees for instant 3D perspective
|
||||
widget.cubit.mapController?.setTilt(55.0);
|
||||
}
|
||||
|
||||
// Apply initial golden hour lighting
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_applySunLighting(_sunTimeMinutes);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_orbitTimer?.cancel();
|
||||
_sunTimer?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
// ── Orbit Logic ──
|
||||
void _toggleOrbit() {
|
||||
setState(() {
|
||||
_isOrbiting = !_isOrbiting;
|
||||
});
|
||||
|
||||
_orbitTimer?.cancel();
|
||||
if (_isOrbiting) {
|
||||
_orbitTimer = Timer.periodic(const Duration(milliseconds: 60), (_) {
|
||||
if (!mounted || !_isOrbiting) return;
|
||||
widget.cubit.mapController?.orbitStep(
|
||||
_orbitSpeed,
|
||||
duration: const Duration(milliseconds: 60),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ── Sun & Shadows Simulation Logic ──
|
||||
void _toggleSunAutoPlay() {
|
||||
setState(() {
|
||||
_isSunPlaying = !_isSunPlaying;
|
||||
});
|
||||
|
||||
_sunTimer?.cancel();
|
||||
if (_isSunPlaying) {
|
||||
_sunTimer = Timer.periodic(const Duration(milliseconds: 130), (_) {
|
||||
if (!mounted || !_isSunPlaying) return;
|
||||
setState(() {
|
||||
_sunTimeMinutes += 12;
|
||||
if (_sunTimeMinutes > 1260) {
|
||||
_sunTimeMinutes = 330; // Loop back to 05:30 AM
|
||||
}
|
||||
});
|
||||
_applySunLighting(_sunTimeMinutes);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void _onSunSliderChanged(double value) {
|
||||
setState(() {
|
||||
_sunTimeMinutes = value.toInt();
|
||||
});
|
||||
_applySunLighting(_sunTimeMinutes);
|
||||
}
|
||||
|
||||
void _setPresetTime(int minutes) {
|
||||
setState(() {
|
||||
_sunTimeMinutes = minutes;
|
||||
});
|
||||
_applySunLighting(minutes);
|
||||
}
|
||||
|
||||
void _applySunLighting(int minutes) {
|
||||
// Fraction of daylight cycle: 0.0 (05:30) to 1.0 (21:00)
|
||||
final double fraction = ((minutes - 330) / (1260 - 330)).clamp(0.0, 1.0);
|
||||
|
||||
// Azimuth sweeps from 75° (East - Sunrise) to 180° (South - Noon) to 290° (West - Sunset)
|
||||
final double sunAzimuth = 75.0 + (fraction * 215.0);
|
||||
|
||||
String shadowColor;
|
||||
String highlightColor;
|
||||
String buildingColor;
|
||||
double opacity = 0.88;
|
||||
|
||||
if (minutes < 420) {
|
||||
// Dawn (05:30 - 07:00)
|
||||
shadowColor = '#4c1d95'; // Deep violet dawn shadow
|
||||
highlightColor = '#fed7aa'; // Peach highlight
|
||||
buildingColor = '#fbcfe8'; // Morning rose tint
|
||||
opacity = 0.85;
|
||||
} else if (minutes < 660) {
|
||||
// Morning (07:00 - 11:00)
|
||||
shadowColor = '#334155';
|
||||
highlightColor = '#ffffff';
|
||||
buildingColor = '#fef08a'; // Golden morning sun
|
||||
opacity = 0.88;
|
||||
} else if (minutes < 840) {
|
||||
// Noon (11:00 - 14:00)
|
||||
shadowColor = '#1e293b'; // Crisp direct shadow
|
||||
highlightColor = '#ffffff';
|
||||
buildingColor = '#f8fafc'; // Crisp bright daylight
|
||||
opacity = 0.90;
|
||||
} else if (minutes < 1050) {
|
||||
// Afternoon (14:00 - 17:30)
|
||||
shadowColor = '#374151';
|
||||
highlightColor = '#ffedd5';
|
||||
buildingColor = '#fed7aa'; // Warm afternoon sun
|
||||
opacity = 0.88;
|
||||
} else if (minutes < 1170) {
|
||||
// Sunset / Golden Hour (17:30 - 19:30)
|
||||
shadowColor = '#3b0764'; // Dramatic deep purple-amber shadow
|
||||
highlightColor = '#ffedd5';
|
||||
buildingColor = '#ea580c'; // Glowing warm sunset orange
|
||||
opacity = 0.92;
|
||||
} else {
|
||||
// Night / Twilight (19:30 - 21:00)
|
||||
shadowColor = '#020617';
|
||||
highlightColor = '#60a5fa'; // Cool blue moonlight
|
||||
buildingColor = '#1e293b'; // Dark sapphire buildings
|
||||
opacity = 0.80;
|
||||
}
|
||||
|
||||
widget.cubit.mapController?.updateSunSimulation(
|
||||
sunAzimuth: sunAzimuth,
|
||||
shadowColorHex: shadowColor,
|
||||
highlightColorHex: highlightColor,
|
||||
buildingColorHex: buildingColor,
|
||||
buildingOpacity: opacity,
|
||||
);
|
||||
}
|
||||
|
||||
String _formatSunTime(int minutes) {
|
||||
final int hours = minutes ~/ 60;
|
||||
final int mins = minutes % 60;
|
||||
final String minsStr = mins.toString().padLeft(2, '0');
|
||||
final String period = hours >= 12 ? 'م' : 'ص';
|
||||
final int displayHour = hours > 12 ? hours - 12 : (hours == 0 ? 12 : hours);
|
||||
|
||||
String label = '';
|
||||
if (minutes < 420) {
|
||||
label = 'الفجر والشروق 🌅';
|
||||
} else if (minutes < 660) {
|
||||
label = 'الصباح الباكر ☀️';
|
||||
} else if (minutes < 840) {
|
||||
label = 'الظهيرة الساطعة 🌤️';
|
||||
} else if (minutes < 1050) {
|
||||
label = 'العصر ⛅';
|
||||
} else if (minutes < 1170) {
|
||||
label = 'الغروب الذهبي 🌇';
|
||||
} else {
|
||||
label = 'الليل التكتيكي 🌙';
|
||||
}
|
||||
|
||||
return '$displayHour:$minsStr $period • $label';
|
||||
}
|
||||
|
||||
// ── Tilt Logic ──
|
||||
void _setTilt(double newTilt) {
|
||||
setState(() => _tilt = newTilt);
|
||||
widget.cubit.mapController?.setTilt(newTilt);
|
||||
}
|
||||
|
||||
// ── Layer Toggles ──
|
||||
void _toggleBuildings() {
|
||||
setState(() => _showBuildings = !_showBuildings);
|
||||
widget.cubit.mapController?.setLayerVisibility('building-3d', _showBuildings);
|
||||
widget.cubit.mapController?.setLayerVisibility('building-3d-osm', _showBuildings);
|
||||
}
|
||||
|
||||
void _toggleContours() {
|
||||
setState(() => _showContours = !_showContours);
|
||||
widget.cubit.mapController?.setLayerVisibility('tactical-contour-minor', _showContours);
|
||||
widget.cubit.mapController?.setLayerVisibility('tactical-contour-major', _showContours);
|
||||
widget.cubit.mapController?.setLayerVisibility('opentopo-contour-lines', _showContours);
|
||||
}
|
||||
|
||||
void _toggleHillshading() {
|
||||
setState(() => _showHillshading = !_showHillshading);
|
||||
widget.cubit.mapController?.setLayerVisibility('hillshading', _showHillshading);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isSatellite = widget.state.mapTheme == MapThemeType.satellite;
|
||||
final double fraction = ((_sunTimeMinutes - 330) / (1260 - 330)).clamp(0.0, 1.0);
|
||||
final double sunAzimuth = 75.0 + (fraction * 215.0);
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.fromLTRB(20, 16, 20, 36),
|
||||
decoration: const BoxDecoration(
|
||||
color: AppColors.pureWhite,
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(28)),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Color(0x28000000),
|
||||
blurRadius: 28,
|
||||
offset: Offset(0, -8),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
// Drag Handle
|
||||
Center(
|
||||
child: Container(
|
||||
width: 42,
|
||||
height: 4.5,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.borderGlass,
|
||||
borderRadius: BorderRadius.circular(3),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 14),
|
||||
|
||||
// Header
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 38,
|
||||
height: 38,
|
||||
decoration: BoxDecoration(
|
||||
gradient: const LinearGradient(
|
||||
colors: [Color(0xFFEA580C), Color(0xFFF59E0B)],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(11),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: const Color(0xFFEA580C).withValues(alpha: 0.35),
|
||||
blurRadius: 10,
|
||||
),
|
||||
],
|
||||
),
|
||||
child: const Icon(Icons.view_in_ar_rounded, color: Colors.white, size: 22),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
const Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'استوديو التضاريس والسينما 3D',
|
||||
style: TextStyle(
|
||||
fontSize: 15.5,
|
||||
fontWeight: FontWeight.w800,
|
||||
color: AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'دوران سينمائي • محاكاة حركة الشمس والظلال',
|
||||
style: TextStyle(fontSize: 11, color: AppColors.textSecondary),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
icon: const Icon(Icons.close_rounded, color: AppColors.textMuted),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: 18),
|
||||
const Divider(height: 1, color: AppColors.borderSubtle),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// ── 1. DAY-TO-NIGHT SUN & SHADOWS SIMULATION (Stationary Location) ──
|
||||
Container(
|
||||
padding: const EdgeInsets.all(14),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF0F172A),
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
border: Border.all(color: const Color(0xFF334155)),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(6),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFEA580C).withValues(alpha: 0.2),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: const Icon(Icons.wb_sunny_rounded, color: Color(0xFFFB923C), size: 16),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
const Text(
|
||||
'محاكاة حركة الشمس وسقوط الظلال',
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
// Auto-Play Toggle Button
|
||||
InkWell(
|
||||
onTap: _toggleSunAutoPlay,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
|
||||
decoration: BoxDecoration(
|
||||
color: _isSunPlaying
|
||||
? const Color(0xFFEA580C)
|
||||
: Colors.white.withValues(alpha: 0.1),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(
|
||||
color: _isSunPlaying
|
||||
? const Color(0xFFEA580C)
|
||||
: Colors.white.withValues(alpha: 0.2),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
_isSunPlaying ? Icons.pause_rounded : Icons.play_arrow_rounded,
|
||||
size: 14,
|
||||
color: Colors.white,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
_isSunPlaying ? 'إيقاف الدورة' : 'دورة تلقائية',
|
||||
style: const TextStyle(
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
const Text(
|
||||
'المكان ثابت: وحركة الضوء تحاكي زاوية الشمس لرؤية تشكل وظلال المباني والجبال',
|
||||
style: TextStyle(fontSize: 10.5, color: Color(0xFF94A3B8)),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// Current Time Display & Azimuth
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
_formatSunTime(_sunTimeMinutes),
|
||||
style: const TextStyle(
|
||||
fontSize: 12.5,
|
||||
fontWeight: FontWeight.w800,
|
||||
color: Color(0xFFFDBA74),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 7, vertical: 2),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withValues(alpha: 0.08),
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
child: Text(
|
||||
'زاوية الإسقاط: ${sunAzimuth.toStringAsFixed(0)}°',
|
||||
style: const TextStyle(fontSize: 10, color: Color(0xFFCBD5E1)),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
|
||||
// Slider
|
||||
SliderTheme(
|
||||
data: SliderTheme.of(context).copyWith(
|
||||
activeTrackColor: const Color(0xFFEA580C),
|
||||
inactiveTrackColor: Colors.white.withValues(alpha: 0.15),
|
||||
thumbColor: const Color(0xFFFB923C),
|
||||
overlayColor: const Color(0xFFEA580C).withValues(alpha: 0.2),
|
||||
trackHeight: 4,
|
||||
),
|
||||
child: Slider(
|
||||
min: 330.0,
|
||||
max: 1260.0,
|
||||
value: _sunTimeMinutes.toDouble(),
|
||||
onChanged: _onSunSliderChanged,
|
||||
),
|
||||
),
|
||||
|
||||
// Quick Sun Period Buttons
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
_buildSunPeriodChip('06:00 ص', 'الفجر', 360),
|
||||
_buildSunPeriodChip('09:00 ص', 'الصباح', 540),
|
||||
_buildSunPeriodChip('12:00 م', 'الظهيرة', 720),
|
||||
_buildSunPeriodChip('06:30 م', 'الغروب', 1110),
|
||||
_buildSunPeriodChip('08:30 م', 'الليل', 1230),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// ── 2. CINEMATIC 360° ORBIT & CAMERA TILT ──
|
||||
Row(
|
||||
children: [
|
||||
// Orbit Button
|
||||
Expanded(
|
||||
child: InkWell(
|
||||
onTap: _toggleOrbit,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 13, horizontal: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: _isOrbiting
|
||||
? AppColors.appleBlue.withValues(alpha: 0.1)
|
||||
: AppColors.surfaceMuted,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(
|
||||
color: _isOrbiting ? AppColors.appleBlue : AppColors.borderSubtle,
|
||||
width: _isOrbiting ? 2 : 1,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.rotate_right_rounded,
|
||||
color: _isOrbiting ? AppColors.appleBlue : AppColors.textPrimary,
|
||||
size: 20,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
_isOrbiting ? 'الدوران نشط 360°' : 'دوران سينمائي',
|
||||
style: TextStyle(
|
||||
fontSize: 12.5,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: _isOrbiting ? AppColors.appleBlue : AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
_isOrbiting ? 'انقر للإيقاف' : 'التفاف حول الموقع',
|
||||
style: const TextStyle(fontSize: 10, color: AppColors.textMuted),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
|
||||
// Orbit Speed Selector (When active)
|
||||
if (_isOrbiting)
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.surfaceMuted,
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
border: Border.all(color: AppColors.borderSubtle),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
_buildSpeedChip(0.6, 'بطيء'),
|
||||
_buildSpeedChip(1.2, 'سلس'),
|
||||
_buildSpeedChip(2.4, 'سريع'),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: 14),
|
||||
|
||||
// ── 3. CAMERA TILT CONTROLS ──
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.surfaceMuted,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(color: AppColors.borderSubtle),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Text(
|
||||
'إمالة الكاميرا للأفق (Camera Tilt):',
|
||||
style: TextStyle(
|
||||
fontSize: 12.5,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'${_tilt.toStringAsFixed(0)}°',
|
||||
style: const TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w800,
|
||||
color: AppColors.appleBlue,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
children: [
|
||||
_buildTiltPresetChip(0.0, '2D مسطح (0°)'),
|
||||
const SizedBox(width: 8),
|
||||
_buildTiltPresetChip(45.0, '3D كتل (45°)'),
|
||||
const SizedBox(width: 8),
|
||||
_buildTiltPresetChip(60.0, 'أفق سينمائي (60°)'),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 14),
|
||||
|
||||
// ── 4. 3D LAYERS TOGGLES ──
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
_buildFeatureToggle(
|
||||
title: 'المباني 3D',
|
||||
icon: Icons.apartment_rounded,
|
||||
active: _showBuildings,
|
||||
onTap: _toggleBuildings,
|
||||
),
|
||||
_buildFeatureToggle(
|
||||
title: 'خطوط الكنتور',
|
||||
icon: Icons.layers_outlined,
|
||||
active: _showContours,
|
||||
onTap: _toggleContours,
|
||||
),
|
||||
_buildFeatureToggle(
|
||||
title: 'ظلال التضاريس',
|
||||
icon: Icons.landscape_rounded,
|
||||
active: _showHillshading,
|
||||
onTap: _toggleHillshading,
|
||||
),
|
||||
_buildFeatureToggle(
|
||||
title: 'أقمار صناعية',
|
||||
icon: Icons.satellite_alt_rounded,
|
||||
active: isSatellite,
|
||||
onTap: () {
|
||||
widget.cubit.setMapTheme(
|
||||
isSatellite ? MapThemeType.vectorLight : MapThemeType.satellite,
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSunPeriodChip(String time, String label, int minutes) {
|
||||
final bool isSelected = (_sunTimeMinutes - minutes).abs() < 40;
|
||||
return InkWell(
|
||||
onTap: () => _setPresetTime(minutes),
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 3),
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected
|
||||
? const Color(0xFFEA580C)
|
||||
: Colors.white.withValues(alpha: 0.08),
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
fontSize: 9.5,
|
||||
fontWeight: isSelected ? FontWeight.w800 : FontWeight.w500,
|
||||
color: isSelected ? Colors.white : const Color(0xFFCBD5E1),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
time,
|
||||
style: TextStyle(
|
||||
fontSize: 8.5,
|
||||
color: isSelected ? Colors.white.withValues(alpha: 0.9) : const Color(0xFF64748B),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSpeedChip(double speed, String label) {
|
||||
final bool isSelected = (_orbitSpeed - speed).abs() < 0.1;
|
||||
return InkWell(
|
||||
onTap: () => setState(() => _orbitSpeed = speed),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 7, vertical: 5),
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected ? AppColors.appleBlue : Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
fontSize: 10.5,
|
||||
fontWeight: isSelected ? FontWeight.w700 : FontWeight.w500,
|
||||
color: isSelected ? Colors.white : AppColors.textSecondary,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTiltPresetChip(double tiltValue, String label) {
|
||||
final bool isSelected = (_tilt - tiltValue).abs() < 4.0;
|
||||
return Expanded(
|
||||
child: InkWell(
|
||||
onTap: () => _setTilt(tiltValue),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected ? AppColors.appleBlue : Colors.white,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
border: Border.all(
|
||||
color: isSelected ? AppColors.appleBlue : AppColors.borderSubtle,
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
label,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
fontWeight: isSelected ? FontWeight.w700 : FontWeight.w600,
|
||||
color: isSelected ? Colors.white : AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildFeatureToggle({
|
||||
required String title,
|
||||
required IconData icon,
|
||||
required bool active,
|
||||
required VoidCallback onTap,
|
||||
}) {
|
||||
return InkWell(
|
||||
onTap: onTap,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: Container(
|
||||
width: 76,
|
||||
padding: const EdgeInsets.symmetric(vertical: 9),
|
||||
decoration: BoxDecoration(
|
||||
color: active
|
||||
? AppColors.appleBlue.withValues(alpha: 0.08)
|
||||
: AppColors.surfaceMuted,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(
|
||||
color: active ? AppColors.appleBlue : AppColors.borderSubtle,
|
||||
width: active ? 1.5 : 1,
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Icon(
|
||||
icon,
|
||||
size: 20,
|
||||
color: active ? AppColors.appleBlue : AppColors.textMuted,
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
fontWeight: active ? FontWeight.w700 : FontWeight.w500,
|
||||
color: active ? AppColors.appleBlue : AppColors.textSecondary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ class LayerSelectorSheet extends StatelessWidget {
|
||||
final ValueChanged<MapThemeType> onThemeChanged;
|
||||
final VoidCallback? onOpenVehicleCustomizer;
|
||||
final VoidCallback? onOpenAboutAwards;
|
||||
final VoidCallback? onOpenCinematic3DStudio;
|
||||
|
||||
const LayerSelectorSheet({
|
||||
super.key,
|
||||
@@ -14,6 +15,7 @@ class LayerSelectorSheet extends StatelessWidget {
|
||||
required this.onThemeChanged,
|
||||
this.onOpenVehicleCustomizer,
|
||||
this.onOpenAboutAwards,
|
||||
this.onOpenCinematic3DStudio,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -68,6 +70,43 @@ class LayerSelectorSheet extends StatelessWidget {
|
||||
),
|
||||
],
|
||||
),
|
||||
if (onOpenCinematic3DStudio != null) ...[
|
||||
const SizedBox(height: 16),
|
||||
ListTile(
|
||||
onTap: onOpenCinematic3DStudio,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
side: const BorderSide(color: Color(0x33EA580C)),
|
||||
),
|
||||
tileColor: const Color(0xFFEA580C).withValues(alpha: 0.08),
|
||||
leading: Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
gradient: const LinearGradient(
|
||||
colors: [Color(0xFFEA580C), Color(0xFFF59E0B)],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: const Icon(Icons.view_in_ar_rounded, color: Colors.white, size: 22),
|
||||
),
|
||||
title: const Text(
|
||||
'استوديو التضاريس والسينما 3D',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w800,
|
||||
color: AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
subtitle: const Text(
|
||||
'دوران سينمائي 360° • محاكاة حركة الشمس وسقوط الظلال',
|
||||
style: TextStyle(fontSize: 11, color: Color(0xFFEA580C), fontWeight: FontWeight.w600),
|
||||
),
|
||||
trailing: const Icon(Icons.arrow_forward_ios_rounded, size: 14, color: Color(0xFFEA580C)),
|
||||
),
|
||||
],
|
||||
if (onOpenVehicleCustomizer != null) ...[
|
||||
const SizedBox(height: 18),
|
||||
const Divider(height: 1, color: AppColors.borderSubtle),
|
||||
@@ -106,37 +145,37 @@ class LayerSelectorSheet extends StatelessWidget {
|
||||
onTap: onOpenAboutAwards,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
side: const BorderSide(color: AppColors.urukGoldBorder),
|
||||
side: const BorderSide(color: AppColors.borderSubtle),
|
||||
),
|
||||
tileColor: AppColors.urukGoldLight,
|
||||
tileColor: AppColors.surfaceMuted,
|
||||
leading: Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.pureWhite,
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(color: AppColors.urukGold.withValues(alpha: 0.4)),
|
||||
border: Border.all(color: AppColors.borderSubtle),
|
||||
),
|
||||
child: ClipOval(
|
||||
child: Image.asset(
|
||||
'assets/images/uruk_prize_logo.png',
|
||||
'assets/images/siro_maps_logo.png',
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
),
|
||||
),
|
||||
title: const Text(
|
||||
'عن منظومة أوروك والتقنيات المفعلة',
|
||||
'عن منظومة سيرو ماب والتقنيات المفعلة',
|
||||
style: TextStyle(
|
||||
fontSize: 13.5,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColors.urukGoldDark,
|
||||
color: AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
subtitle: const Text(
|
||||
'الكاش الذكي، مفاتيح الأجهزة، والملاحة دون اتصال',
|
||||
style: TextStyle(fontSize: 11, color: AppColors.textSecondary),
|
||||
),
|
||||
trailing: const Icon(Icons.workspace_premium_rounded, size: 20, color: AppColors.urukGoldDark),
|
||||
trailing: const Icon(Icons.info_outline_rounded, size: 20, color: AppColors.appleBlue),
|
||||
),
|
||||
],
|
||||
],
|
||||
|
||||
@@ -75,20 +75,20 @@ class _SplashViewState extends State<SplashView> with SingleTickerProviderStateM
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
// Luxury Uruk Emblem Container
|
||||
// Luxury Siro Maps Emblem Container
|
||||
Container(
|
||||
width: 114,
|
||||
height: 114,
|
||||
width: 116,
|
||||
height: 116,
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF131722),
|
||||
borderRadius: BorderRadius.circular(32),
|
||||
border: Border.all(
|
||||
color: AppColors.urukGold.withValues(alpha: 0.35),
|
||||
color: AppColors.appleBlue.withValues(alpha: 0.35),
|
||||
width: 1.5,
|
||||
),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: AppColors.urukGold.withValues(alpha: 0.22),
|
||||
color: AppColors.appleBlue.withValues(alpha: 0.18),
|
||||
blurRadius: 36,
|
||||
offset: const Offset(0, 14),
|
||||
),
|
||||
@@ -102,7 +102,7 @@ class _SplashViewState extends State<SplashView> with SingleTickerProviderStateM
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
child: Image.asset(
|
||||
'assets/images/siro_uruk_logo.png',
|
||||
'assets/images/siro_maps_logo.png',
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
@@ -110,7 +110,7 @@ class _SplashViewState extends State<SplashView> with SingleTickerProviderStateM
|
||||
const SizedBox(height: 24),
|
||||
// App Title
|
||||
Text(
|
||||
'خرائط أوروك',
|
||||
'خرائط سيرو',
|
||||
style: GoogleFonts.alexandria(
|
||||
fontSize: 28,
|
||||
fontWeight: FontWeight.w800,
|
||||
@@ -121,104 +121,13 @@ class _SplashViewState extends State<SplashView> with SingleTickerProviderStateM
|
||||
const SizedBox(height: 6),
|
||||
// Subtitle
|
||||
Text(
|
||||
'Uruk Map • منظومة الخرائط والملاحة الذكية',
|
||||
'Siro Map • منظومة الخرائط والملاحة الذكية',
|
||||
style: GoogleFonts.alexandria(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.textSecondary,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 26),
|
||||
|
||||
// Official Uruk International Prize Award Badge
|
||||
Container(
|
||||
constraints: const BoxConstraints(maxWidth: 340),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 11),
|
||||
decoration: BoxDecoration(
|
||||
gradient: const LinearGradient(
|
||||
colors: [
|
||||
Color(0xFFFFFDF7),
|
||||
Color(0xFFFFF6D8),
|
||||
],
|
||||
begin: Alignment.topRight,
|
||||
end: Alignment.bottomLeft,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
border: Border.all(color: AppColors.urukGoldBorder, width: 1.2),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: AppColors.urukGold.withValues(alpha: 0.14),
|
||||
blurRadius: 22,
|
||||
offset: const Offset(0, 8),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
width: 44,
|
||||
height: 44,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: AppColors.pureWhite,
|
||||
border: Border.all(
|
||||
color: AppColors.urukGold.withValues(alpha: 0.4),
|
||||
width: 1.2,
|
||||
),
|
||||
),
|
||||
child: ClipOval(
|
||||
child: Image.asset(
|
||||
'assets/images/uruk_prize_logo.png',
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Flexible(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.workspace_premium_rounded,
|
||||
size: 16,
|
||||
color: AppColors.urukGoldDark,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Flexible(
|
||||
child: Text(
|
||||
'الحائز على جائزة أوروك الدولية',
|
||||
style: GoogleFonts.alexandria(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColors.urukGoldDark,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
'مشروع منبثق عن برنامج جوائز أوروك للسيادة الرقمية',
|
||||
style: GoogleFonts.alexandria(
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: AppColors.textSecondary,
|
||||
),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 28),
|
||||
// Regional Sovereignty Pill Badge
|
||||
Container(
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
// 'flutter create' template.
|
||||
|
||||
// The application's name. By default this is also the title of the Flutter window.
|
||||
PRODUCT_NAME = uruk_map
|
||||
PRODUCT_NAME = siro_maps
|
||||
|
||||
// The application's bundle identifier
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.urukmap.app
|
||||
|
||||
// The copyright displayed in application information
|
||||
PRODUCT_COPYRIGHT = Copyright © 2026 com.urukmap. All rights reserved.
|
||||
PRODUCT_COPYRIGHT = Copyright © 2026 Siro Map. All rights reserved.
|
||||
|
||||
@@ -627,10 +627,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: matcher
|
||||
sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6"
|
||||
sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.12.18"
|
||||
version: "0.12.19"
|
||||
material_color_utilities:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -643,10 +643,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
|
||||
sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.17.0"
|
||||
version: "1.18.0"
|
||||
mime:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1032,10 +1032,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636"
|
||||
sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.9"
|
||||
version: "0.7.11"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -27,7 +27,7 @@ void main() {
|
||||
),
|
||||
);
|
||||
|
||||
expect(find.text('خرائط أوروك'), findsOneWidget);
|
||||
expect(find.text('خرائط سيرو'), findsOneWidget);
|
||||
expect(find.textContaining('v2.4 PRO'), findsOneWidget);
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 3000));
|
||||
|
||||
@@ -23,11 +23,11 @@
|
||||
"@eslint/js": "^9.39.4",
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@vitejs/plugin-react": "^6.0.1",
|
||||
"@vitejs/plugin-react": "^4.7.0",
|
||||
"eslint": "^9.39.4",
|
||||
"eslint-plugin-react-hooks": "^7.0.1",
|
||||
"eslint-plugin-react-refresh": "^0.5.2",
|
||||
"globals": "^17.4.0",
|
||||
"vite": "^8.0.1"
|
||||
"vite": "^6.4.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,514 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ar" dir="rtl">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>انطلق | الدليل الهندسي التعليمي الشامل</title>
|
||||
<meta name="description" content="دليل تعليمي شامل لمنصة انطلق - تعلم NestJS و PostGIS و Docker من خلال مشروع حقيقي">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans+Arabic:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
*{margin:0;padding:0;box-sizing:border-box}
|
||||
:root{
|
||||
--bg:#0b0f1a;--bg2:#111827;--bg3:#1e293b;
|
||||
--fg:#f1f5f9;--muted:#94a3b8;--dim:#475569;
|
||||
--teal:#00f5d4;--gold:#fee440;--blue:#818cf8;
|
||||
--pink:#f472b6;--red:#fb7185;--green:#34d399;
|
||||
--card:#151e2e;--border:#1e3a5f;--code-bg:#0d1117;
|
||||
}
|
||||
body{font-family:'IBM Plex Sans Arabic',system-ui,sans-serif;background:var(--bg);color:var(--fg);line-height:1.9;overflow-x:hidden}
|
||||
::-webkit-scrollbar{width:5px}::-webkit-scrollbar-track{background:var(--bg)}::-webkit-scrollbar-thumb{background:var(--teal);border-radius:10px}
|
||||
|
||||
/* Layout */
|
||||
.sidebar{position:fixed;top:0;right:0;width:300px;height:100vh;background:var(--bg2);border-left:1px solid var(--border);padding:1.5rem;z-index:100;overflow-y:auto;display:flex;flex-direction:column}
|
||||
.main{margin-right:300px;min-height:100vh}
|
||||
@media(max-width:1024px){.sidebar{display:none}.main{margin-right:0}}
|
||||
|
||||
/* Sidebar */
|
||||
.logo{display:flex;align-items:center;gap:.75rem;padding-bottom:1.5rem;border-bottom:1px solid var(--border);margin-bottom:1.5rem}
|
||||
.logo-icon{width:42px;height:42px;background:linear-gradient(135deg,var(--teal),var(--blue));border-radius:12px;display:flex;align-items:center;justify-content:center;font-size:1.2rem;font-weight:900;color:var(--bg)}
|
||||
.logo h1{font-size:1.3rem;font-weight:700}.logo span{font-size:.65rem;color:var(--muted);display:block}
|
||||
.nav-group{margin-bottom:1.5rem}
|
||||
.nav-group-title{font-size:.65rem;text-transform:uppercase;letter-spacing:3px;color:var(--dim);font-weight:700;margin-bottom:.5rem;padding-right:.5rem}
|
||||
.nav-link{display:flex;align-items:center;gap:.6rem;padding:.6rem .75rem;border-radius:10px;color:var(--muted);text-decoration:none;font-size:.85rem;font-weight:500;transition:all .2s;border:1px solid transparent}
|
||||
.nav-link:hover,.nav-link.active{background:rgba(0,245,212,.06);color:var(--teal);border-color:rgba(0,245,212,.15)}
|
||||
.nav-link .num{font-family:'JetBrains Mono',monospace;font-size:.7rem;color:var(--dim);min-width:20px}
|
||||
|
||||
/* Hero */
|
||||
.hero{padding:5rem 4rem;background:linear-gradient(180deg,rgba(0,245,212,.03) 0%,transparent 60%);border-bottom:1px solid var(--border)}
|
||||
.hero .badge{display:inline-flex;align-items:center;gap:.5rem;background:rgba(0,245,212,.08);border:1px solid rgba(0,245,212,.2);border-radius:999px;padding:.35rem 1rem;font-size:.75rem;color:var(--teal);margin-bottom:1.5rem}
|
||||
.hero h1{font-size:3.2rem;font-weight:800;line-height:1.3;margin-bottom:1rem;max-width:700px}
|
||||
.hero h1 em{font-style:normal;background:linear-gradient(135deg,var(--teal),var(--blue));background-clip:text;-webkit-background-clip:text;-webkit-text-fill-color:transparent}
|
||||
.hero p{font-size:1.1rem;color:var(--muted);max-width:600px;line-height:1.8}
|
||||
.hero-stats{display:flex;gap:2.5rem;margin-top:2.5rem;flex-wrap:wrap}
|
||||
.hero-stat{text-align:center}.hero-stat .val{font-size:2rem;font-weight:800;color:var(--teal)}.hero-stat .lbl{font-size:.75rem;color:var(--muted)}
|
||||
|
||||
/* Sections */
|
||||
.chapter{padding:4rem;border-bottom:1px solid var(--border);scroll-margin-top:2rem}
|
||||
.chapter-header{display:flex;align-items:center;gap:1rem;margin-bottom:2rem}
|
||||
.chapter-num{font-family:'JetBrains Mono',monospace;font-size:.75rem;color:var(--bg);background:var(--teal);width:32px;height:32px;border-radius:8px;display:flex;align-items:center;justify-content:center;font-weight:700}
|
||||
.chapter h2{font-size:1.8rem;font-weight:800}
|
||||
.chapter p,.chapter li{color:var(--muted);font-size:.95rem}
|
||||
.chapter ul,.chapter ol{padding-right:1.5rem}
|
||||
.chapter li{margin-bottom:.5rem}
|
||||
|
||||
/* Code Blocks */
|
||||
.code-card{background:var(--code-bg);border:1px solid var(--border);border-radius:16px;overflow:hidden;margin:1.5rem 0}
|
||||
.code-header{display:flex;align-items:center;justify-content:space-between;padding:.6rem 1rem;background:rgba(255,255,255,.03);border-bottom:1px solid var(--border);font-size:.75rem}
|
||||
.code-header .file{color:var(--muted);font-family:'JetBrains Mono',monospace;display:flex;align-items:center;gap:.5rem}
|
||||
.code-header .file .dot{width:8px;height:8px;border-radius:50%}
|
||||
.code-header .lang{color:var(--teal);font-family:'JetBrains Mono',monospace;font-weight:600}
|
||||
pre.code{padding:1.25rem;font-family:'JetBrains Mono',monospace;font-size:.8rem;line-height:1.8;color:#c9d1d9;direction:ltr;text-align:left;overflow-x:auto;margin:0}
|
||||
.hl-key{color:#ff7b72}.hl-str{color:#a5d6ff}.hl-fn{color:#d2a8ff}.hl-cmt{color:#8b949e;font-style:italic}.hl-dec{color:#79c0ff}.hl-type{color:#ffa657}.hl-num{color:var(--teal)}
|
||||
|
||||
/* Info boxes */
|
||||
.callout{padding:1.25rem 1.5rem;border-radius:12px;margin:1.5rem 0;border-right:4px solid;font-size:.9rem}
|
||||
.callout.info{background:rgba(129,140,248,.06);border-color:var(--blue);color:var(--blue)}
|
||||
.callout.tip{background:rgba(0,245,212,.06);border-color:var(--teal)}
|
||||
.callout.warn{background:rgba(254,228,64,.06);border-color:var(--gold);color:var(--gold)}
|
||||
.callout strong{display:block;margin-bottom:.3rem}
|
||||
|
||||
/* Grid Cards */
|
||||
.card-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:1.25rem;margin:1.5rem 0}
|
||||
.card{background:var(--card);border:1px solid var(--border);border-radius:16px;padding:1.5rem;transition:all .3s}
|
||||
.card:hover{border-color:var(--teal);transform:translateY(-4px);box-shadow:0 12px 40px rgba(0,0,0,.4)}
|
||||
.card .icon{font-size:1.5rem;margin-bottom:.75rem;display:block}
|
||||
.card h4{font-size:1rem;font-weight:700;margin-bottom:.5rem}
|
||||
.card p{font-size:.85rem;color:var(--muted)}
|
||||
|
||||
/* Diagram */
|
||||
.diagram{background:var(--card);border:1px solid var(--border);border-radius:16px;padding:2rem;margin:1.5rem 0;text-align:center}
|
||||
.diagram-flow{display:flex;align-items:center;justify-content:center;gap:.5rem;flex-wrap:wrap;margin:1rem 0}
|
||||
.diagram-node{background:var(--bg);border:1px solid var(--border);border-radius:12px;padding:.75rem 1.25rem;font-size:.8rem;font-weight:600;min-width:100px}
|
||||
.diagram-node.active{border-color:var(--teal);color:var(--teal);box-shadow:0 0 20px rgba(0,245,212,.15)}
|
||||
.diagram-arrow{color:var(--dim);font-size:1.2rem}
|
||||
|
||||
/* API Table */
|
||||
.api-table{width:100%;border-collapse:collapse;margin:1.5rem 0;font-size:.85rem}
|
||||
.api-table th{text-align:right;padding:.75rem 1rem;background:var(--card);color:var(--teal);font-size:.7rem;text-transform:uppercase;letter-spacing:1px;border-bottom:2px solid var(--border)}
|
||||
.api-table td{padding:.75rem 1rem;border-bottom:1px solid rgba(255,255,255,.05);color:var(--muted);vertical-align:top}
|
||||
.api-table tr:hover td{background:rgba(255,255,255,.02)}
|
||||
.method{font-family:'JetBrains Mono',monospace;font-size:.75rem;font-weight:700;padding:.15rem .5rem;border-radius:4px}
|
||||
.method.get{color:#34d399;background:rgba(52,211,153,.1)}.method.post{color:#818cf8;background:rgba(129,140,248,.1)}.method.patch{color:#fbbf24;background:rgba(251,191,36,.1)}.method.delete{color:#fb7185;background:rgba(251,113,133,.1)}
|
||||
.endpoint{font-family:'JetBrains Mono',monospace;font-size:.8rem;color:var(--fg);direction:ltr;display:inline-block}
|
||||
|
||||
/* Footer */
|
||||
footer{padding:3rem 4rem;text-align:center;border-top:1px solid var(--border);color:var(--dim);font-size:.8rem}
|
||||
|
||||
/* Animations */
|
||||
.fade-in{opacity:0;transform:translateY(25px);transition:all .7s cubic-bezier(.16,1,.3,1)}
|
||||
.fade-in.visible{opacity:1;transform:translateY(0)}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- Sidebar Navigation -->
|
||||
<aside class="sidebar">
|
||||
<div class="logo">
|
||||
<div class="logo-icon">🧭</div>
|
||||
<div><h1>انطلق</h1><span>الدليل الهندسي التعليمي v3.0</span></div>
|
||||
</div>
|
||||
<nav>
|
||||
<div class="nav-group">
|
||||
<div class="nav-group-title">الأساسيات</div>
|
||||
<a href="#hero" class="nav-link active"><span class="num">00</span> نظرة عامة</a>
|
||||
<a href="#nestjs-paradigm" class="nav-link"><span class="num">01</span> فلسفة NestJS</a>
|
||||
<a href="#modules" class="nav-link"><span class="num">02</span> الوحدات والتبعيات</a>
|
||||
</div>
|
||||
<div class="nav-group">
|
||||
<div class="nav-group-title">قاعدة البيانات</div>
|
||||
<a href="#entities" class="nav-link"><span class="num">03</span> الكيانات و TypeORM</a>
|
||||
<a href="#geocoding" class="nav-link"><span class="num">04</span> نظام البحث المكاني</a>
|
||||
</div>
|
||||
<div class="nav-group">
|
||||
<div class="nav-group-title">الذكاء المكاني</div>
|
||||
<a href="#telemetry" class="nav-link"><span class="num">05</span> تتبع السائقين</a>
|
||||
<a href="#intelligence" class="nav-link"><span class="num">06</span> دورة الـ 10 أيام</a>
|
||||
</div>
|
||||
<div class="nav-group">
|
||||
<div class="nav-group-title">البنية التحتية</div>
|
||||
<a href="#api-reference" class="nav-link"><span class="num">07</span> مرجع الـ API</a>
|
||||
<a href="#docker" class="nav-link"><span class="num">08</span> بيئة Docker</a>
|
||||
<a href="#nginx" class="nav-link"><span class="num">09</span> إعدادات NGINX</a>
|
||||
</div>
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="main">
|
||||
|
||||
<!-- Hero -->
|
||||
<header class="hero fade-in" id="hero">
|
||||
<div class="badge">📘 دليل تعليمي تفاعلي — من الصفر إلى الإنتاج</div>
|
||||
<h1>تعلّم بناء <em>منصة خرائط ذكية</em> بتقنيات حقيقية.</h1>
|
||||
<p>هذا الدليل يأخذك في رحلة عملية داخل كود منصة "انطلق" — من تصميم قاعدة البيانات المكانية إلى نشر الحاويات. كل سطر كود هنا مأخوذ من المشروع الفعلي.</p>
|
||||
<div class="hero-stats">
|
||||
<div class="hero-stat"><div class="val">4</div><div class="lbl">وحدات NestJS</div></div>
|
||||
<div class="hero-stat"><div class="val">6</div><div class="lbl">حاويات Docker</div></div>
|
||||
<div class="hero-stat"><div class="val">3</div><div class="lbl">دول مدعومة</div></div>
|
||||
<div class="hero-stat"><div class="val">∞</div><div class="lbl">نقاط تتبع</div></div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Chapter 01: NestJS Paradigm -->
|
||||
<section class="chapter fade-in" id="nestjs-paradigm">
|
||||
<div class="chapter-header"><div class="chapter-num">01</div><h2>فلسفة NestJS — لماذا هذا الإطار؟</h2></div>
|
||||
<p>NestJS يقوم على ثلاثة مبادئ أساسية: <strong>الوحدات (Modules)</strong> لتنظيم الكود، <strong>المتحكمات (Controllers)</strong> لاستقبال الطلبات، و<strong>الخدمات (Services)</strong> لمنطق العمل. كل شيء مربوط بـ <strong>حقن التبعيات (Dependency Injection)</strong>.</p>
|
||||
|
||||
<div class="callout info"><strong>💡 المبدأ الأساسي</strong> كل وحدة (Module) هي صندوق مستقل يحتوي على متحكماته وخدماته. الوحدة لا تعرف شيئاً عن الوحدات الأخرى إلا إذا تم "تصديرها" (Export) و"استيرادها" (Import) بشكل صريح.</div>
|
||||
|
||||
<p style="margin-top:1rem">نقطة البداية هي ملف <code style="color:var(--teal)">main.ts</code> — هنا يبدأ كل شيء:</p>
|
||||
<div class="code-card">
|
||||
<div class="code-header"><div class="file"><span class="dot" style="background:var(--teal)"></span> main.ts</div><div class="lang">TypeScript</div></div>
|
||||
<pre class="code"><span class="hl-key">import</span> { NestFactory } <span class="hl-key">from</span> <span class="hl-str">'@nestjs/core'</span>;
|
||||
<span class="hl-key">import</span> { DocumentBuilder, SwaggerModule } <span class="hl-key">from</span> <span class="hl-str">'@nestjs/swagger'</span>;
|
||||
<span class="hl-key">import</span> { AppModule } <span class="hl-key">from</span> <span class="hl-str">'./app.module'</span>;
|
||||
|
||||
<span class="hl-key">async function</span> <span class="hl-fn">bootstrap</span>() {
|
||||
<span class="hl-key">const</span> app = <span class="hl-key">await</span> NestFactory.<span class="hl-fn">create</span>(AppModule);
|
||||
|
||||
<span class="hl-cmt">// تفعيل تبادل الموارد مع الواجهة الأمامية</span>
|
||||
app.<span class="hl-fn">enableCors</span>();
|
||||
app.<span class="hl-fn">setGlobalPrefix</span>(<span class="hl-str">'api'</span>); <span class="hl-cmt">// كل المسارات تبدأ بـ /api</span>
|
||||
|
||||
<span class="hl-cmt">// إعداد وثائق Swagger التلقائية</span>
|
||||
<span class="hl-key">const</span> config = <span class="hl-key">new</span> <span class="hl-fn">DocumentBuilder</span>()
|
||||
.<span class="hl-fn">setTitle</span>(<span class="hl-str">'Jordan Map Platform API'</span>)
|
||||
.<span class="hl-fn">setVersion</span>(<span class="hl-str">'1.0'</span>).<span class="hl-fn">build</span>();
|
||||
|
||||
<span class="hl-key">const</span> port = process.env.API_PORT || <span class="hl-num">3000</span>;
|
||||
<span class="hl-key">await</span> app.<span class="hl-fn">listen</span>(port);
|
||||
console.<span class="hl-fn">log</span>(<span class="hl-str">`🚀 API is running on port ${port}`</span>);
|
||||
}
|
||||
<span class="hl-fn">bootstrap</span>();</pre>
|
||||
</div>
|
||||
|
||||
<div class="callout tip"><strong>🔗 العلاقة:</strong> <code>main.ts</code> → يُنشئ التطبيق من <code>AppModule</code> → الذي يستورد <code>TelemetryModule</code> + <code>MapsModule</code> + <code>GeocodingModule</code>.</div>
|
||||
</section>
|
||||
|
||||
<!-- Chapter 02: Modules -->
|
||||
<section class="chapter fade-in" id="modules">
|
||||
<div class="chapter-header"><div class="chapter-num">02</div><h2>الوحدات والتبعيات — كيف يُنظَّم الكود؟</h2></div>
|
||||
<p>المشروع مقسم لـ 4 وحدات رئيسية. كل وحدة تعريفها في ملف <code style="color:var(--teal)">*.module.ts</code>:</p>
|
||||
|
||||
<div class="diagram">
|
||||
<div style="color:var(--muted);font-size:.8rem;margin-bottom:1rem">🏗️ خريطة الوحدات</div>
|
||||
<div class="diagram-flow">
|
||||
<div class="diagram-node active">AppModule</div>
|
||||
<div class="diagram-arrow">←</div>
|
||||
<div class="diagram-node">TelemetryModule</div>
|
||||
<div class="diagram-arrow">←</div>
|
||||
<div class="diagram-node">MapsModule</div>
|
||||
<div class="diagram-arrow">←</div>
|
||||
<div class="diagram-node">GeocodingModule</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="code-card">
|
||||
<div class="code-header"><div class="file"><span class="dot" style="background:var(--blue)"></span> app.module.ts</div><div class="lang">TypeScript</div></div>
|
||||
<pre class="code"><span class="hl-key">@Module</span>({
|
||||
<span class="hl-type">imports</span>: [
|
||||
ConfigModule.<span class="hl-fn">forRoot</span>({ isGlobal: <span class="hl-num">true</span> }),
|
||||
ScheduleModule.<span class="hl-fn">forRoot</span>(), <span class="hl-cmt">// لتشغيل المهام الدورية (Cron)</span>
|
||||
TypeOrmModule.<span class="hl-fn">forRootAsync</span>({ <span class="hl-cmt">// اتصال قاعدة البيانات</span>
|
||||
<span class="hl-type">useFactory</span>: (config) => ({
|
||||
type: <span class="hl-str">'postgres'</span>,
|
||||
url: config.<span class="hl-fn">get</span>(<span class="hl-str">'DATABASE_URL'</span>),
|
||||
autoLoadEntities: <span class="hl-num">true</span>,
|
||||
}),
|
||||
}),
|
||||
TelemetryModule, <span class="hl-cmt">// وحدة تتبع السائقين</span>
|
||||
MapsModule, <span class="hl-cmt">// وحدة التوجيه والخرائط</span>
|
||||
GeocodingModule, <span class="hl-cmt">// وحدة البحث المكاني</span>
|
||||
],
|
||||
})
|
||||
<span class="hl-key">export class</span> <span class="hl-type">AppModule</span> {}</pre>
|
||||
</div>
|
||||
|
||||
<div class="code-card">
|
||||
<div class="code-header"><div class="file"><span class="dot" style="background:var(--gold)"></span> telemetry.module.ts</div><div class="lang">TypeScript</div></div>
|
||||
<pre class="code"><span class="hl-key">@Module</span>({
|
||||
<span class="hl-type">imports</span>: [
|
||||
TypeOrmModule.<span class="hl-fn">forFeature</span>([ <span class="hl-cmt">// ← تسجيل الكيانات</span>
|
||||
TelemetryLog,
|
||||
RoadSegmentStat,
|
||||
CandidateRoad
|
||||
]),
|
||||
RedisModule, <span class="hl-cmt">// ← استيراد خدمة Redis</span>
|
||||
],
|
||||
<span class="hl-type">controllers</span>: [TelemetryController, MapRefinementController],
|
||||
<span class="hl-type">providers</span>: [TelemetryService, TelemetryAnalyzerService,
|
||||
RedisService, ExternalTelemetryService],
|
||||
<span class="hl-type">exports</span>: [TelemetryService, TelemetryAnalyzerService],
|
||||
})
|
||||
<span class="hl-key">export class</span> <span class="hl-type">TelemetryModule</span> {}</pre>
|
||||
</div>
|
||||
|
||||
<div class="callout warn"><strong>⚠️ قاعدة ذهبية:</strong> أي خدمة (Service) يجب أن تكون مُسجلة في <code>providers</code> وأي خدمة تريد مشاركتها يجب وضعها في <code>exports</code>.</div>
|
||||
</section>
|
||||
|
||||
|
||||
<!-- Chapter 03: Entities -->
|
||||
<section class="chapter fade-in" id="entities">
|
||||
<div class="chapter-header"><div class="chapter-num">03</div><h2>الكيانات و TypeORM — تصميم قاعدة البيانات</h2></div>
|
||||
<p>كل جدول في PostgreSQL يُمثَّل بكيان (Entity). الكيان هو كلاس TypeScript مزيّن بـ <code style="color:var(--teal)">@Entity()</code> يُعرِّف الأعمدة والفهارس:</p>
|
||||
|
||||
<div class="card-grid">
|
||||
<div class="card"><div class="icon">📍</div><h4>TelemetryLog</h4><p>يخزن نقاط GPS من السائقين. يحتوي عمود <code>location</code> من نوع PostGIS <code>geography(Point)</code> مع فهرس مكاني.</p></div>
|
||||
<div class="card"><div class="icon">🛣️</div><h4>RoadSegmentStat</h4><p>إحصائيات السرعة لكل مقطع طريق. يحتوي <code>congestionFactor</code> و<code>geometry</code> من نوع <code>LineString</code>.</p></div>
|
||||
<div class="card"><div class="icon">🔍</div><h4>CandidateRoad</h4><p>طرق مُكتشفة من تحليل DBSCAN. تحتوي <code>confidence</code> (0-1) و<code>status</code> (pending/approved/rejected).</p></div>
|
||||
<div class="card"><div class="icon">🌍</div><h4>BasePlace</h4><p>الكيان الأب لجداول <code>places_syria</code>, <code>places_jordan</code>, <code>places_egypt</code>. يُعرّف الأعمدة المشتركة.</p></div>
|
||||
</div>
|
||||
|
||||
<div class="code-card">
|
||||
<div class="code-header"><div class="file"><span class="dot" style="background:var(--pink)"></span> telemetry.entity.ts</div><div class="lang">TypeScript</div></div>
|
||||
<pre class="code"><span class="hl-key">@Entity</span>(<span class="hl-str">'telemetry_logs'</span>)
|
||||
<span class="hl-key">export class</span> <span class="hl-type">TelemetryLog</span> {
|
||||
<span class="hl-key">@PrimaryGeneratedColumn</span>()
|
||||
id: <span class="hl-type">number</span>;
|
||||
|
||||
<span class="hl-key">@Column</span>() <span class="hl-key">@Index</span>()
|
||||
driverId: <span class="hl-type">string</span>;
|
||||
|
||||
<span class="hl-key">@Column</span>(<span class="hl-str">'decimal'</span>, { precision: <span class="hl-num">10</span>, scale: <span class="hl-num">7</span> })
|
||||
latitude: <span class="hl-type">number</span>;
|
||||
|
||||
<span class="hl-key">@Column</span>(<span class="hl-str">'float'</span>)
|
||||
speed: <span class="hl-type">number</span>;
|
||||
|
||||
<span class="hl-cmt">// نقطة جغرافية PostGIS للبحث المكاني السريع</span>
|
||||
<span class="hl-key">@Column</span>({ type: <span class="hl-str">'geography'</span>, spatialFeatureType: <span class="hl-str">'Point'</span>, srid: <span class="hl-num">4326</span> })
|
||||
<span class="hl-key">@Index</span>({ spatial: <span class="hl-num">true</span> }) <span class="hl-cmt">// ← فهرس GIST للبحث بالمسافة</span>
|
||||
location: <span class="hl-type">any</span>;
|
||||
}</pre>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Chapter 04: Geocoding -->
|
||||
<section class="chapter fade-in" id="geocoding">
|
||||
<div class="chapter-header"><div class="chapter-num">04</div><h2>نظام البحث المكاني — Forward & Reverse Geocoding</h2></div>
|
||||
<p>الخدمة تبحث في 3 جداول إقليمية + جدول OSM العالمي. المفتاح: استخدام <code style="color:var(--teal)">::float</code> في استعلامات SQL لتجنب خطأ "النتائج الفارغة":</p>
|
||||
|
||||
<div class="code-card">
|
||||
<div class="code-header"><div class="file"><span class="dot" style="background:var(--green)"></span> geocoding.service.ts — searchPlaces()</div><div class="lang">SQL + TypeScript</div></div>
|
||||
<pre class="code"><span class="hl-cmt">// تحديد الجدول المناسب بناءً على الإحداثيات</span>
|
||||
<span class="hl-key">private</span> <span class="hl-fn">getRepositoryForCoords</span>(lat, lng) {
|
||||
<span class="hl-key">if</span> (lat >= <span class="hl-num">29</span> && lat <= <span class="hl-num">37.5</span> && lng >= <span class="hl-num">34.5</span>) {
|
||||
<span class="hl-key">if</span> (lat > <span class="hl-num">32.5</span> && lng > <span class="hl-num">35.8</span>) <span class="hl-key">return</span> placesSyriaRepo;
|
||||
<span class="hl-key">return</span> placesJordanRepo;
|
||||
}
|
||||
<span class="hl-key">if</span> (lat >= <span class="hl-num">22</span> && lat <= <span class="hl-num">32</span>) <span class="hl-key">return</span> placesEgyptRepo;
|
||||
}
|
||||
|
||||
<span class="hl-cmt">// الاستعلام المكاني — لاحظ ::float في كل مكان</span>
|
||||
<span class="hl-key">const</span> query = <span class="hl-str">`
|
||||
SELECT id, name_ar, latitude, longitude,
|
||||
ST_DistanceSphere(
|
||||
location,
|
||||
ST_SetSRID(ST_MakePoint($3<span class="hl-dec">::float</span>, $2<span class="hl-dec">::float</span>), 4326)
|
||||
) as distance
|
||||
FROM ${tableName}
|
||||
WHERE name_ar % $1 -- بحث تشابهي (Trigram)
|
||||
ORDER BY distance ASC LIMIT 15
|
||||
`</span>;</pre>
|
||||
</div>
|
||||
<div class="callout tip"><strong>🐛 الخطأ الذي حللناه:</strong> PostGIS كان يقرأ الإحداثيات كـ <code>text</code> بدلاً من <code>float</code>، مما يُنتج نتائج فارغة. الحل: إضافة <code>::float</code> لكل مُعامل رقمي.</div>
|
||||
</section>
|
||||
|
||||
<!-- Chapter 05: Telemetry -->
|
||||
<section class="chapter fade-in" id="telemetry">
|
||||
<div class="chapter-header"><div class="chapter-num">05</div><h2>تتبع السائقين — من GPS إلى قاعدة البيانات</h2></div>
|
||||
<p>كل 3 ثوانٍ، يرسل تطبيق السائق بيانات الموقع. المتحكم يستقبلها والخدمة تحفظها:</p>
|
||||
|
||||
<div class="code-card">
|
||||
<div class="code-header"><div class="file"><span class="dot" style="background:var(--gold)"></span> telemetry.service.ts — ingest()</div><div class="lang">TypeScript</div></div>
|
||||
<pre class="code"><span class="hl-key">async</span> <span class="hl-fn">ingest</span>(data: DriverTelemetryDto) {
|
||||
<span class="hl-key">const</span> log = <span class="hl-key">this</span>.telemetryRepo.<span class="hl-fn">create</span>({
|
||||
driverId: data.driver_id,
|
||||
latitude: data.latitude, longitude: data.longitude,
|
||||
speed: data.speed, heading: data.heading,
|
||||
location: {
|
||||
type: <span class="hl-str">'Point'</span>,
|
||||
coordinates: [data.longitude, data.latitude], <span class="hl-cmt">// GeoJSON: [lng, lat]</span>
|
||||
},
|
||||
});
|
||||
<span class="hl-key">await this</span>.telemetryRepo.<span class="hl-fn">save</span>(log);
|
||||
<span class="hl-key">return</span> { success: <span class="hl-num">true</span>, timestamp: <span class="hl-key">new</span> Date() };
|
||||
}</pre>
|
||||
</div>
|
||||
|
||||
<div class="callout info"><strong>🔗 سلسلة الاستدعاء:</strong> <code>Flutter App</code> → <code>POST /api/telemetry</code> → <code>TelemetryController.ingest()</code> → <code>TelemetryService.ingest()</code> → <code>PostgreSQL + PostGIS</code></div>
|
||||
</section>
|
||||
|
||||
<!-- Chapter 06: Intelligence Cycle -->
|
||||
<section class="chapter fade-in" id="intelligence">
|
||||
<div class="chapter-header"><div class="chapter-num">06</div><h2>دورة الذكاء المكاني — كل 10 أيام</h2></div>
|
||||
<p>هذا هو قلب المنصة. الخدمة <code style="color:var(--teal)">TelemetryAnalyzerService</code> تعمل تلقائياً عبر <code>@Cron</code> كل فجر لتحليل البيانات:</p>
|
||||
|
||||
<div class="diagram">
|
||||
<div style="color:var(--muted);font-size:.8rem;margin-bottom:1rem">🧠 دورة المعالجة الذكية</div>
|
||||
<div class="diagram-flow">
|
||||
<div class="diagram-node">1. مزامنة البيانات</div>
|
||||
<div class="diagram-arrow">→</div>
|
||||
<div class="diagram-node active">2. تحليل السرعات</div>
|
||||
<div class="diagram-arrow">→</div>
|
||||
<div class="diagram-node">3. اكتشاف الطرق</div>
|
||||
<div class="diagram-arrow">→</div>
|
||||
<div class="diagram-node">4. تحديث Redis</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="code-card">
|
||||
<div class="code-header"><div class="file"><span class="dot" style="background:var(--red)"></span> telemetry-analyzer.service.ts — discoverNewRoads()</div><div class="lang">PostGIS SQL</div></div>
|
||||
<pre class="code"><span class="hl-cmt">-- الخطوة 1: إيجاد نقاط بعيدة عن أي طريق معروف (> 15 متر)</span>
|
||||
<span class="hl-key">WITH</span> off_road_points <span class="hl-key">AS</span> (
|
||||
<span class="hl-key">SELECT</span> t.id, t.location, t.timestamp, t."driverId"
|
||||
<span class="hl-key">FROM</span> telemetry_logs t
|
||||
<span class="hl-key">WHERE</span> t.speed > <span class="hl-num">5</span> <span class="hl-cmt">-- متحرك وليس متوقف</span>
|
||||
<span class="hl-key">AND NOT EXISTS</span> (
|
||||
<span class="hl-key">SELECT</span> <span class="hl-num">1</span> <span class="hl-key">FROM</span> planet_osm_line l
|
||||
<span class="hl-key">WHERE</span> <span class="hl-fn">ST_DWithin</span>(t.location::geography,
|
||||
<span class="hl-fn">ST_Transform</span>(l.way, <span class="hl-num">4326</span>)::geography, <span class="hl-num">15</span>)
|
||||
)
|
||||
),
|
||||
<span class="hl-cmt">-- الخطوة 2: تجميع النقاط القريبة باستخدام DBSCAN</span>
|
||||
clustered <span class="hl-key">AS</span> (
|
||||
<span class="hl-key">SELECT</span> *,
|
||||
<span class="hl-fn">ST_ClusterDBSCAN</span>(location::geometry,
|
||||
eps := <span class="hl-num">0.0003</span>, minpoints := <span class="hl-num">5</span>) <span class="hl-key">OVER</span> () <span class="hl-key">AS</span> cluster_id
|
||||
<span class="hl-key">FROM</span> off_road_points
|
||||
)
|
||||
<span class="hl-cmt">-- الخطوة 3: تحويل كل تجمع إلى خط طريق مرشح</span>
|
||||
<span class="hl-key">SELECT</span> cluster_id,
|
||||
<span class="hl-fn">COUNT</span>(<span class="hl-key">DISTINCT</span> "driverId") <span class="hl-key">AS</span> unique_drivers,
|
||||
<span class="hl-fn">ST_AsGeoJSON</span>(<span class="hl-fn">ST_MakeLine</span>(location <span class="hl-key">ORDER BY</span> timestamp)) <span class="hl-key">AS</span> geojson
|
||||
<span class="hl-key">FROM</span> clustered <span class="hl-key">WHERE</span> cluster_id <span class="hl-key">IS NOT NULL</span>
|
||||
<span class="hl-key">GROUP BY</span> cluster_id
|
||||
<span class="hl-key">HAVING COUNT</span>(<span class="hl-key">DISTINCT</span> "driverId") >= <span class="hl-num">2</span></pre>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Chapter 07: API Reference -->
|
||||
<section class="chapter fade-in" id="api-reference">
|
||||
<div class="chapter-header"><div class="chapter-num">07</div><h2>مرجع الـ API — كل نقاط النهاية</h2></div>
|
||||
<p>جميع المسارات محمية بـ <code style="color:var(--teal)">ApiKeyGuard</code> عبر الترويسة <code>x-api-key</code>.</p>
|
||||
|
||||
<table class="api-table">
|
||||
<thead><tr><th>الطريقة</th><th>المسار</th><th>الوصف</th><th>الملف المصدر</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td><span class="method post">POST</span></td><td><span class="endpoint">/api/telemetry</span></td><td>استقبال بيانات تتبع السائقين كل 3 ثوانٍ</td><td style="color:var(--dim)">telemetry.controller.ts</td></tr>
|
||||
<tr><td><span class="method post">POST</span></td><td><span class="endpoint">/api/telemetry/sync</span></td><td>مزامنة يدوية من الخادم الخارجي</td><td style="color:var(--dim)">telemetry.controller.ts</td></tr>
|
||||
<tr><td><span class="method post">POST</span></td><td><span class="endpoint">/api/telemetry/process-intelligence</span></td><td>🚀 تنفيذ دورة الذكاء كاملة (مزامنة + تحليل)</td><td style="color:var(--dim)">telemetry.controller.ts</td></tr>
|
||||
<tr><td><span class="method get">GET</span></td><td><span class="endpoint">/api/maps/route?fromLat&fromLng&toLat&toLng</span></td><td>حساب أقصر مسار عبر GraphHopper</td><td style="color:var(--dim)">maps.controller.ts</td></tr>
|
||||
<tr><td><span class="method get">GET</span></td><td><span class="endpoint">/api/geocoding/search?q&lat&lng&country</span></td><td>بحث مكاني متعدد الأقاليم</td><td style="color:var(--dim)">geocoding.controller.ts</td></tr>
|
||||
<tr><td><span class="method get">GET</span></td><td><span class="endpoint">/api/geocoding/reverse?lat&lng</span></td><td>تحويل إحداثيات → عنوان</td><td style="color:var(--dim)">geocoding.controller.ts</td></tr>
|
||||
<tr><td><span class="method post">POST</span></td><td><span class="endpoint">/api/geocoding/upsert-batch</span></td><td>إدخال أماكن بالجملة (Scraper)</td><td style="color:var(--dim)">geocoding.controller.ts</td></tr>
|
||||
<tr><td><span class="method post">POST</span></td><td><span class="endpoint">/api/map-refinement/analyze-speeds</span></td><td>تحليل سرعات الطرق</td><td style="color:var(--dim)">map-refinement.controller.ts</td></tr>
|
||||
<tr><td><span class="method post">POST</span></td><td><span class="endpoint">/api/map-refinement/discover-roads</span></td><td>اكتشاف طرق جديدة (DBSCAN)</td><td style="color:var(--dim)">map-refinement.controller.ts</td></tr>
|
||||
<tr><td><span class="method get">GET</span></td><td><span class="endpoint">/api/map-refinement/congestion?north&south&east&west</span></td><td>بيانات الازدحام للخريطة الحرارية</td><td style="color:var(--dim)">map-refinement.controller.ts</td></tr>
|
||||
<tr><td><span class="method patch">PATCH</span></td><td><span class="endpoint">/api/map-refinement/candidates/:id/approve</span></td><td>الموافقة على طريق مكتشف</td><td style="color:var(--dim)">map-refinement.controller.ts</td></tr>
|
||||
<tr><td><span class="method delete">DELETE</span></td><td><span class="endpoint">/api/geocoding/places?country&name</span></td><td>حذف مكان حسب الاسم أو المعرّف</td><td style="color:var(--dim)">geocoding.controller.ts</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<!-- Chapter 08: Docker -->
|
||||
<section class="chapter fade-in" id="docker">
|
||||
<div class="chapter-header"><div class="chapter-num">08</div><h2>بيئة Docker — حاويات مترابطة</h2></div>
|
||||
<p>كل خدمة تعمل في حاوية معزولة. ملف <code style="color:var(--teal)">docker-compose.yml</code> يربطها عبر شبكة داخلية:</p>
|
||||
|
||||
<div class="card-grid">
|
||||
<div class="card"><div class="icon">🐘</div><h4>db (PostGIS)</h4><p>postgis/postgis:15-3.3 — القلب. تخزين كل البيانات المكانية مع فهارس GIST.</p></div>
|
||||
<div class="card"><div class="icon">🚀</div><h4>api (NestJS)</h4><p>node:20-alpine — العقل المدبر. يعالج الطلبات وينفذ التحليلات.</p></div>
|
||||
<div class="card"><div class="icon">🗺️</div><h4>martin (Tiles)</h4><p>maplibre/martin — يولّد Vector Tiles مباشرة من PostGIS بسرعة فائقة.</p></div>
|
||||
<div class="card"><div class="icon">🧭</div><h4>routing (GraphHopper)</h4><p>يحسب أقصر المسارات باستخدام ملف OSM محدث + بيانات الازدحام.</p></div>
|
||||
<div class="card"><div class="icon">⚡</div><h4>redis (Cache)</h4><p>redis:7-alpine — تخزين مؤقت لحالة الازدحام اللحظية.</p></div>
|
||||
<div class="card"><div class="icon">🌐</div><h4>web (React)</h4><p>لوحة تحكم الخرائط التفاعلية مع عرض البيانات.</p></div>
|
||||
</div>
|
||||
|
||||
<div class="code-card">
|
||||
<div class="code-header"><div class="file"><span class="dot" style="background:var(--blue)"></span> docker-compose.yml (مقتطف)</div><div class="lang">YAML</div></div>
|
||||
<pre class="code"><span class="hl-type">services</span>:
|
||||
<span class="hl-fn">db</span>:
|
||||
image: <span class="hl-str">postgis/postgis:15-3.3</span>
|
||||
volumes:
|
||||
- <span class="hl-str">postgres_data:/var/lib/postgresql/data</span>
|
||||
healthcheck:
|
||||
test: [<span class="hl-str">"CMD-SHELL"</span>, <span class="hl-str">"pg_isready"</span>]
|
||||
|
||||
<span class="hl-fn">api</span>:
|
||||
build: ./infrastructure/docker/api/Dockerfile
|
||||
ports: [<span class="hl-str">"3200:3200"</span>]
|
||||
environment:
|
||||
- <span class="hl-str">DATABASE_URL=postgresql://user:pass@db:5432/mapdb</span>
|
||||
- <span class="hl-str">REDIS_URL=redis://redis:6379</span>
|
||||
depends_on:
|
||||
db: { condition: <span class="hl-str">service_healthy</span> }
|
||||
redis: { condition: <span class="hl-str">service_healthy</span> }
|
||||
|
||||
<span class="hl-fn">martin</span>:
|
||||
image: <span class="hl-str">maplibre/martin:latest</span>
|
||||
ports: [<span class="hl-str">"3202:3000"</span>]
|
||||
command: <span class="hl-str">postgresql://user:pass@db:5432/mapdb</span></pre>
|
||||
</div>
|
||||
|
||||
<div class="callout info"><strong>💻 أوامر التشغيل الأساسية:</strong><br>
|
||||
<code style="color:var(--teal)">$ docker-compose up -d --build</code> — بناء وتشغيل كل الحاويات<br>
|
||||
<code style="color:var(--teal)">$ docker-compose ps</code> — عرض حالة الحاويات<br>
|
||||
<code style="color:var(--teal)">$ docker-compose logs -f api</code> — مراقبة سجلات الـ API
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Chapter 09: NGINX -->
|
||||
<section class="chapter fade-in" id="nginx">
|
||||
<div class="chapter-header"><div class="chapter-num">09</div><h2>إعدادات NGINX — البوابة الأمامية</h2></div>
|
||||
<p>NGINX يعمل كـ <strong>Reverse Proxy</strong> — يستقبل كل الطلبات على المنفذ 80 ويوجهها للحاوية المناسبة:</p>
|
||||
|
||||
<div class="code-card">
|
||||
<div class="code-header"><div class="file"><span class="dot" style="background:var(--green)"></span> nginx.conf</div><div class="lang">NGINX</div></div>
|
||||
<pre class="code"><span class="hl-key">server</span> {
|
||||
listen <span class="hl-num">80</span>;
|
||||
server_name portal.intaleq.map;
|
||||
|
||||
<span class="hl-cmt"># توجيه طلبات الـ API إلى حاوية NestJS</span>
|
||||
<span class="hl-key">location</span> /api {
|
||||
proxy_pass <span class="hl-str">http://api:3200</span>;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
}
|
||||
|
||||
<span class="hl-cmt"># توجيه طلبات بلاط الخرائط إلى Martin</span>
|
||||
<span class="hl-key">location</span> /tiles {
|
||||
proxy_pass <span class="hl-str">http://martin:3000</span>;
|
||||
}
|
||||
|
||||
<span class="hl-cmt"># توجيه طلبات التوجيه إلى GraphHopper</span>
|
||||
<span class="hl-key">location</span> /routing {
|
||||
proxy_pass <span class="hl-str">http://routing:8989</span>;
|
||||
}
|
||||
}</pre>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<footer>
|
||||
<p>نظام "انطلق" — الدليل الهندسي التعليمي الشامل © 2026</p>
|
||||
<p style="margin-top:.5rem">تم بناء هذا الدليل من الكود المصدري الفعلي للمنصة</p>
|
||||
</footer>
|
||||
|
||||
|
||||
<script>
|
||||
const obs = new IntersectionObserver(e => e.forEach(el => {if(el.isIntersecting)el.target.classList.add('visible')}),{threshold:.1});
|
||||
document.querySelectorAll('.fade-in').forEach(r => obs.observe(r));
|
||||
|
||||
// Active nav link tracking
|
||||
const sections = document.querySelectorAll('.chapter, .hero');
|
||||
const navLinks = document.querySelectorAll('.nav-link');
|
||||
window.addEventListener('scroll', () => {
|
||||
let current = '';
|
||||
sections.forEach(s => { if(scrollY >= s.offsetTop - 200) current = s.id; });
|
||||
navLinks.forEach(l => { l.classList.toggle('active', l.getAttribute('href') === '#' + current); });
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,479 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ar" dir="rtl">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>المخطط المعماري الهندسي | منظومة المعالم التراثية والسيادية Siro Maps</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Tajawal:wght@400;500;700;800;900&family=JetBrains+Mono:wght@400;600;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
:root {
|
||||
--bg-base: #0a0f1d;
|
||||
--bg-surface: #111827;
|
||||
--bg-card: #1e293b;
|
||||
--border-subtle: rgba(255, 255, 255, 0.08);
|
||||
--border-accent: rgba(56, 189, 248, 0.3);
|
||||
--text-primary: #f8fafc;
|
||||
--text-secondary: #94a3b8;
|
||||
--accent-cyan: #38bdf8;
|
||||
--accent-emerald: #10b981;
|
||||
--accent-gold: #f59e0b;
|
||||
--accent-purple: #a855f7;
|
||||
--accent-rose: #f43f5e;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--bg-base);
|
||||
color: var(--text-primary);
|
||||
font-family: 'Tajawal', sans-serif;
|
||||
line-height: 1.7;
|
||||
padding: 2rem 1rem;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1240px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
header {
|
||||
text-align: center;
|
||||
margin-bottom: 2.5rem;
|
||||
padding-bottom: 1.5rem;
|
||||
border-bottom: 1px solid var(--border-subtle);
|
||||
}
|
||||
|
||||
.badge {
|
||||
display: inline-block;
|
||||
padding: 0.35rem 1rem;
|
||||
background: rgba(56, 189, 248, 0.1);
|
||||
color: var(--accent-cyan);
|
||||
border: 1px solid rgba(56, 189, 248, 0.3);
|
||||
border-radius: 9999px;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 0.75rem;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2.2rem;
|
||||
font-weight: 900;
|
||||
color: #ffffff;
|
||||
margin-bottom: 0.5rem;
|
||||
background: linear-gradient(135deg, #ffffff 0%, #cbd5e1 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
color: var(--text-secondary);
|
||||
font-size: 1.05rem;
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.image-showcase {
|
||||
background: var(--bg-surface);
|
||||
border: 1px solid var(--border-subtle);
|
||||
border-radius: 16px;
|
||||
padding: 1.5rem;
|
||||
margin-bottom: 3rem;
|
||||
box-shadow: 0 20px 40px -15px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.image-showcase h2 {
|
||||
font-size: 1.3rem;
|
||||
color: var(--accent-cyan);
|
||||
margin-bottom: 1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.image-frame {
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--border-accent);
|
||||
background: #000;
|
||||
}
|
||||
|
||||
.image-frame img {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.diagram-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 1.5rem;
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.diagram-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.card {
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border-subtle);
|
||||
border-radius: 14px;
|
||||
padding: 1.75rem;
|
||||
position: relative;
|
||||
transition: border-color 0.2s ease;
|
||||
}
|
||||
|
||||
.card.recommended {
|
||||
border-color: var(--accent-emerald);
|
||||
box-shadow: 0 0 25px -5px rgba(16, 185, 129, 0.15);
|
||||
}
|
||||
|
||||
.card-tag {
|
||||
position: absolute;
|
||||
top: 1rem;
|
||||
left: 1rem;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 800;
|
||||
padding: 0.2rem 0.6rem;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.tag-alt {
|
||||
background: rgba(148, 163, 184, 0.15);
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
.tag-rec {
|
||||
background: rgba(16, 185, 129, 0.2);
|
||||
color: var(--accent-emerald);
|
||||
border: 1px solid rgba(16, 185, 129, 0.4);
|
||||
}
|
||||
|
||||
.card h3 {
|
||||
font-size: 1.25rem;
|
||||
margin-bottom: 0.5rem;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.card p {
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.95rem;
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
|
||||
.metric-list {
|
||||
list-style: none;
|
||||
margin-top: 1rem;
|
||||
border-top: 1px solid var(--border-subtle);
|
||||
padding-top: 1rem;
|
||||
}
|
||||
|
||||
.metric-list li {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 0.4rem 0;
|
||||
font-size: 0.9rem;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.metric-list li span:last-child {
|
||||
font-weight: 700;
|
||||
color: #ffffff;
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
.code-box {
|
||||
direction: ltr;
|
||||
text-align: left;
|
||||
background: #0a0f1d;
|
||||
border: 1px solid var(--border-subtle);
|
||||
border-radius: 8px;
|
||||
padding: 1rem;
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 0.85rem;
|
||||
color: #38bdf8;
|
||||
overflow-x: auto;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.svg-section {
|
||||
background: var(--bg-surface);
|
||||
border: 1px solid var(--border-subtle);
|
||||
border-radius: 16px;
|
||||
padding: 2rem;
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
|
||||
.svg-container {
|
||||
width: 100%;
|
||||
overflow-x: auto;
|
||||
padding: 1rem 0;
|
||||
}
|
||||
|
||||
svg {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.action-bar {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 1rem;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.75rem 1.5rem;
|
||||
border-radius: 8px;
|
||||
font-weight: 700;
|
||||
text-decoration: none;
|
||||
font-size: 0.95rem;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: #0284c7;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background: #0369a1;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: var(--bg-card);
|
||||
color: var(--text-primary);
|
||||
border: 1px solid var(--border-subtle);
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: #334155;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="container">
|
||||
<header>
|
||||
<div class="badge">ARCHITECTURAL BLUEPRINT & SYSTEM DESIGN</div>
|
||||
<h1>المخطط المعماري لدمج منظومة المعالم والمسارات التراثية</h1>
|
||||
<p class="subtitle">
|
||||
تحليل المفاضلة الهندسية بين العزل في حاوية مستقلة والدمج المعياري في قاعدة البيانات، مع رسم بياني شامل لتدفق البيانات وتطبيق الموبايل.
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<!-- Visual Architecture Render -->
|
||||
<div class="image-showcase">
|
||||
<h2>
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<rect x="3" y="3" width="18" height="18" rx="2"/>
|
||||
<circle cx="8.5" cy="8.5" r="1.5"/>
|
||||
<path d="M21 15l-5-5L5 21"/>
|
||||
</svg>
|
||||
الرسم البياني التنفيذي للمعمارية (Executive System Architecture)
|
||||
</h2>
|
||||
<div class="image-frame">
|
||||
<img src="/heritage-architecture.jpg" alt="Architecture Diagram: Dedicated Docker vs Modular Stack">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Comparative Grid -->
|
||||
<div class="diagram-grid">
|
||||
<!-- Option A -->
|
||||
<div class="card">
|
||||
<span class="card-tag tag-alt">خيار بديل | Option A</span>
|
||||
<h3>حاوية ميكروسيرفيس مستقلة (Dedicated Docker)</h3>
|
||||
<p>
|
||||
فصل المعالم التراثية ووزارة الثقافة في حاوية خاصة تماماً على منفذ شبكي مخصص مع قاعدة بيانات أو استعلامات مفصولة.
|
||||
</p>
|
||||
|
||||
<div class="code-box">
|
||||
services:
|
||||
map-heritage:
|
||||
image: node:20-alpine
|
||||
container_name: map-heritage
|
||||
ports: ["3205:3205"]
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 300m
|
||||
</div>
|
||||
|
||||
<ul class="metric-list">
|
||||
<li>
|
||||
<span>استهلاك الذاكرة الإضافي (Server RAM):</span>
|
||||
<span style="color: var(--accent-rose);">+250MB to 350MB</span>
|
||||
</li>
|
||||
<li>
|
||||
<span>العزل التشغيلي (Fault Isolation):</span>
|
||||
<span style="color: var(--accent-emerald);">مطلق (100%)</span>
|
||||
</li>
|
||||
<li>
|
||||
<span>الربط المكاني بشبكة الطرق (Spatial Joins):</span>
|
||||
<span style="color: var(--accent-gold);">عبر واجهة برمجة (أبطأ)</span>
|
||||
</li>
|
||||
<li>
|
||||
<span>جاهزية التسليم للوزارة (On-Premise Handover):</span>
|
||||
<span style="color: var(--accent-emerald);">فورية بحاوية جاهزة</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Option B (Recommended) -->
|
||||
<div class="card recommended">
|
||||
<span class="card-tag tag-rec">الخيار الموصى به هندسياً | Option B</span>
|
||||
<h3>المخطط المعزول والوحدة المعيارية (Modular Schema)</h3>
|
||||
<p>
|
||||
إنشاء مخطط مستقل داخل قاعدة البيانات الحالية، مع وحدة معزولة بالكامل داخل الخادم، والاستفادة المباشرة من خادم البلاطات.
|
||||
</p>
|
||||
|
||||
<div class="code-box">
|
||||
PostGIS:
|
||||
Schema: "heritage" (Landmarks, Trails, Gates)
|
||||
Schema: "public" (OSM Roads, Buildings)
|
||||
Martin:
|
||||
Exposes "heritage.*" directly as MVT tiles
|
||||
NestJS:
|
||||
Imports: [HeritageModule] (/api/v1/heritage/*)
|
||||
</div>
|
||||
|
||||
<ul class="metric-list">
|
||||
<li>
|
||||
<span>استهلاك الذاكرة الإضافي (Server RAM):</span>
|
||||
<span style="color: var(--accent-emerald);">~ 0 MB (نفس العمليات الحالية)</span>
|
||||
</li>
|
||||
<li>
|
||||
<span>العزل التشغيلي (Fault Isolation):</span>
|
||||
<span style="color: var(--accent-emerald);">معزول منطقياً بمسارات مستقلة</span>
|
||||
</li>
|
||||
<li>
|
||||
<span>الربط المكاني بشبكة الطرق (Spatial Joins):</span>
|
||||
<span style="color: var(--accent-emerald);">فائق السرعة (Sub-millisecond)</span>
|
||||
</li>
|
||||
<li>
|
||||
<span>بث البلاطات للتطبيق (Tile Server):</span>
|
||||
<span style="color: var(--accent-emerald);">مباشر عبر خادم مارتن دون كود إضافي</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Interactive SVG Architecture Vector -->
|
||||
<div class="svg-section">
|
||||
<h2 style="font-size: 1.3rem; color: var(--accent-cyan); margin-bottom: 1.5rem;">
|
||||
مخطط تدفق البيانات والمكونات المكانية (Detailed Component Data Flow)
|
||||
</h2>
|
||||
|
||||
<div class="svg-container">
|
||||
<svg width="1050" height="520" viewBox="0 0 1050 520" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<!-- Background Grid Pattern -->
|
||||
<rect width="1050" height="520" rx="12" fill="#0c1322"/>
|
||||
<path d="M0 60 H1050 M0 120 H1050 M0 180 H1050 M0 240 H1050 M0 300 H1050 M0 360 H1050 M0 420 H1050 M0 480 H1050" stroke="#1e293b" stroke-width="0.75" stroke-dasharray="3 3"/>
|
||||
<path d="M70 0 V520 M140 0 V520 M210 0 V520 M280 0 V520 M350 0 V520 M420 0 V520 M490 0 V520 M560 0 V520 M630 0 V520 M700 0 V520 M770 0 V520 M840 0 V520 M910 0 V520 M980 0 V520" stroke="#1e293b" stroke-width="0.75" stroke-dasharray="3 3"/>
|
||||
|
||||
<!-- Client Layer (Flutter Mobile App) -->
|
||||
<rect x="50" y="80" width="220" height="360" rx="10" fill="#172033" stroke="#38bdf8" stroke-width="2"/>
|
||||
<text x="160" y="115" fill="#38bdf8" font-size="14" font-weight="bold" font-family="'JetBrains Mono', monospace" text-anchor="middle">CLIENT APP (FLUTTER)</text>
|
||||
<text x="160" y="135" fill="#94a3b8" font-size="12" text-anchor="middle">Siro Maps / Siro Maps</text>
|
||||
|
||||
<rect x="70" y="160" width="180" height="48" rx="6" fill="#0f172a" stroke="#334155" stroke-width="1"/>
|
||||
<text x="160" y="188" fill="#f8fafc" font-size="12" font-weight="600" text-anchor="middle">Tactical Heritage Layer</text>
|
||||
|
||||
<rect x="70" y="225" width="180" height="48" rx="6" fill="#0f172a" stroke="#334155" stroke-width="1"/>
|
||||
<text x="160" y="253" fill="#f8fafc" font-size="12" font-weight="600" text-anchor="middle">Audio Geofence Trigger</text>
|
||||
|
||||
<rect x="70" y="290" width="180" height="48" rx="6" fill="#0f172a" stroke="#334155" stroke-width="1"/>
|
||||
<text x="160" y="318" fill="#f8fafc" font-size="12" font-weight="600" text-anchor="middle">Gate / Parking Nav</text>
|
||||
|
||||
<rect x="70" y="355" width="180" height="60" rx="6" fill="#10b981" fill-opacity="0.1" stroke="#10b981" stroke-width="1"/>
|
||||
<text x="160" y="380" fill="#10b981" font-size="11" font-weight="bold" text-anchor="middle">Offline Heritage Store</text>
|
||||
<text x="160" y="400" fill="#94a3b8" font-size="10" font-family="'JetBrains Mono', monospace" text-anchor="middle">Cache-First (Hive/SQLite)</text>
|
||||
|
||||
<!-- Middle Layer: Martin & NestJS -->
|
||||
<!-- Martin Tile Server -->
|
||||
<rect x="360" y="80" width="280" height="150" rx="10" fill="#172033" stroke="#a855f7" stroke-width="2"/>
|
||||
<text x="500" y="115" fill="#a855f7" font-size="14" font-weight="bold" font-family="'JetBrains Mono', monospace" text-anchor="middle">MARTIN TILE SERVER (:3202)</text>
|
||||
<text x="500" y="135" fill="#94a3b8" font-size="11" text-anchor="middle">Ultra-Fast Rust Vector Engine (MVT)</text>
|
||||
<rect x="380" y="150" width="240" height="60" rx="6" fill="#0f172a" stroke="#334155" stroke-width="1"/>
|
||||
<text x="500" y="175" fill="#e2e8f0" font-size="12" font-family="'JetBrains Mono', monospace" text-anchor="middle">/heritage.landmarks/{z}/{x}/{y}</text>
|
||||
<text x="500" y="195" fill="#10b981" font-size="11" text-anchor="middle">Direct Streaming Without Node.js</text>
|
||||
|
||||
<!-- NestJS API Gateway -->
|
||||
<rect x="360" y="270" width="280" height="170" rx="10" fill="#172033" stroke="#10b981" stroke-width="2"/>
|
||||
<text x="500" y="305" fill="#10b981" font-size="14" font-weight="bold" font-family="'JetBrains Mono', monospace" text-anchor="middle">NESTJS BACKEND (:3200)</text>
|
||||
<text x="500" y="325" fill="#94a3b8" font-size="11" text-anchor="middle">apps/api with Clean Modular Architecture</text>
|
||||
<rect x="380" y="340" width="240" height="40" rx="6" fill="#1e293b" stroke="#334155" stroke-width="1"/>
|
||||
<text x="500" y="365" fill="#38bdf8" font-size="12" font-family="'JetBrains Mono', monospace" text-anchor="middle">MapsModule & GraphHopper (Core)</text>
|
||||
<rect x="380" y="388" width="240" height="40" rx="6" fill="#10b981" fill-opacity="0.15" stroke="#10b981" stroke-width="1"/>
|
||||
<text x="500" y="413" fill="#10b981" font-size="12" font-family="'JetBrains Mono', monospace" text-anchor="middle">HeritageModule (/api/v1/heritage/*)</text>
|
||||
|
||||
<!-- Database Layer: PostGIS -->
|
||||
<rect x="730" y="80" width="270" height="360" rx="10" fill="#172033" stroke="#f59e0b" stroke-width="2"/>
|
||||
<text x="865" y="115" fill="#f59e0b" font-size="14" font-weight="bold" font-family="'JetBrains Mono', monospace" text-anchor="middle">POSTGIS DATABASE (:5432)</text>
|
||||
<text x="865" y="135" fill="#94a3b8" font-size="11" text-anchor="middle">Container: map-db</text>
|
||||
|
||||
<!-- Isolated Schema: heritage -->
|
||||
<rect x="750" y="160" width="230" height="110" rx="8" fill="#10b981" fill-opacity="0.1" stroke="#10b981" stroke-width="1.5"/>
|
||||
<text x="865" y="185" fill="#10b981" font-size="12" font-weight="bold" font-family="'JetBrains Mono', monospace" text-anchor="middle">SCHEMA: heritage</text>
|
||||
<text x="865" y="210" fill="#e2e8f0" font-size="11" text-anchor="middle">heritage.landmarks (Centroid, Gate, Parking)</text>
|
||||
<text x="865" y="230" fill="#e2e8f0" font-size="11" text-anchor="middle">heritage.trails (MultiLineString)</text>
|
||||
<text x="865" y="250" fill="#e2e8f0" font-size="11" text-anchor="middle">heritage.thesaurus (75K Terms)</text>
|
||||
|
||||
<!-- Public Schema -->
|
||||
<rect x="750" y="290" width="230" height="130" rx="8" fill="#0f172a" stroke="#334155" stroke-width="1"/>
|
||||
<text x="865" y="315" fill="#cbd5e1" font-size="12" font-weight="bold" font-family="'JetBrains Mono', monospace" text-anchor="middle">SCHEMA: public (CORE)</text>
|
||||
<text x="865" y="340" fill="#94a3b8" font-size="11" text-anchor="middle">planet_osm_roads (Routing)</text>
|
||||
<text x="865" y="360" fill="#94a3b8" font-size="11" text-anchor="middle">places_jordan (Geocoding)</text>
|
||||
<text x="865" y="380" fill="#94a3b8" font-size="11" text-anchor="middle">admin_boundaries (Districts)</text>
|
||||
<text x="865" y="402" fill="#38bdf8" font-size="10" font-weight="bold" text-anchor="middle">Spatial Query: ST_DWithin(roads, gate)</text>
|
||||
|
||||
<!-- Connecting Arrows -->
|
||||
<!-- Flutter to Martin (Vector Tiles) -->
|
||||
<path d="M270 184 L360 155" stroke="#a855f7" stroke-width="2" stroke-dasharray="4 4"/>
|
||||
<!-- Flutter to NestJS (API & Metadata) -->
|
||||
<path d="M270 314 L360 380" stroke="#10b981" stroke-width="2"/>
|
||||
<!-- Martin to PostGIS -->
|
||||
<path d="M640 155 L750 190" stroke="#a855f7" stroke-width="2"/>
|
||||
<!-- NestJS HeritageModule to PostGIS heritage schema -->
|
||||
<path d="M640 408 L750 240" stroke="#10b981" stroke-width="2"/>
|
||||
<!-- NestJS Core to PostGIS public schema -->
|
||||
<path d="M640 360 L750 340" stroke="#38bdf8" stroke-width="2"/>
|
||||
|
||||
<!-- Footer Status Indicator -->
|
||||
<circle cx="80" cy="485" r="5" fill="#10b981"/>
|
||||
<text x="95" y="489" fill="#10b981" font-size="11" font-weight="bold">ISOLATION VERIFIED: Core Routing Never Blocked by Heritage Operations</text>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Navigation & Links -->
|
||||
<div class="action-bar">
|
||||
<a href="/siro-maps.html" class="btn btn-secondary">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M19 12H5M12 19l-7-7 7-7"/></svg>
|
||||
العودة إلى بوابة Siro Maps
|
||||
</a>
|
||||
<a href="/heritage-memo.html" class="btn btn-primary">
|
||||
المذكرة الاستراتيجية والتنفيذية
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M5 12h14M12 5l7 7-7 7"/></svg>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,93 @@
|
||||
Copyright 2018 Boutros International. (http://www.boutrosfonts.com)
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||