chore: fix prettier formatting, regenerate phase9 manifest, add missing config files (.env.example, .node-version, .nvmrc, CI workflow)
CI / test (push) Failing after 28s
CI / test (push) Failing after 28s
This commit is contained in:
@@ -0,0 +1,10 @@
|
|||||||
|
# Production-safe example environment (Phase 14 integration defaults).
|
||||||
|
# All demo and analytics integrations are blocked by default.
|
||||||
|
NEXT_PUBLIC_DEMO_ENABLED=false
|
||||||
|
DEMO_SUBMISSION_MODE=blocked
|
||||||
|
DEMO_REQUEST_TIMEOUT_MS=4000
|
||||||
|
DEMO_IDEMPOTENCY_TTL_SECONDS=900
|
||||||
|
DEMO_TEST_SCENARIOS_ENABLED=false
|
||||||
|
DEMO_LOCAL_TEST_MODE=false
|
||||||
|
NEXT_PUBLIC_ANALYTICS_MODE=disabled
|
||||||
|
SITE_ORIGIN=http://127.0.0.1:3000
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
pull_request:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: pnpm/action-setup@v4
|
||||||
|
with:
|
||||||
|
version: 11.9.0
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 24.17.0
|
||||||
|
cache: pnpm
|
||||||
|
- run: pnpm install --frozen-lockfile
|
||||||
|
- run: pnpm test:integration
|
||||||
|
- run: pnpm test:security
|
||||||
|
- run: pnpm test:privacy
|
||||||
|
- run: pnpm test:browser
|
||||||
|
- run: pnpm test:a11y
|
||||||
|
- run: pnpm test:visual
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
24.17.0
|
||||||
@@ -37,11 +37,11 @@ The implementation must:
|
|||||||
|
|
||||||
Breakpoints are layout decisions, not device labels.
|
Breakpoints are layout decisions, not device labels.
|
||||||
|
|
||||||
| Mode | CSS range | Reference frames | Required behavior |
|
| Mode | CSS range | Reference frames | Required behavior |
|
||||||
|---|---:|---|---|
|
| ------- | ----------------: | ---------------- | ------------------------------------------------------------------------------------------------------- |
|
||||||
| Compact | 0–767 px | 320, 390 | One-column sections; mobile drawer; stacked CTAs; product preview simplified; no horizontal page scroll |
|
| Compact | 0–767 px | 320, 390 | One-column sections; mobile drawer; stacked CTAs; product preview simplified; no horizontal page scroll |
|
||||||
| Medium | 768–1119 px | 768, 1024 | Two-column where specified; mobile drawer remains; 4-column workflow allowed; balanced content widths |
|
| Medium | 768–1119 px | 768, 1024 | Two-column where specified; mobile drawer remains; 4-column workflow allowed; balanced content widths |
|
||||||
| Wide | 1120 px and above | 1280, 1440 | Desktop navigation and controls; 12-column composition; hero 7/5 split; max container 1280 px |
|
| Wide | 1120 px and above | 1280, 1440 | Desktop navigation and controls; 12-column composition; hero 7/5 split; max container 1280 px |
|
||||||
|
|
||||||
Do not infer new behavior between breakpoints. Follow `components/RESPONSIVE_BEHAVIOR_MATRIX_v1.0.csv`.
|
Do not infer new behavior between breakpoints. Follow `components/RESPONSIVE_BEHAVIOR_MATRIX_v1.0.csv`.
|
||||||
|
|
||||||
|
|||||||
@@ -3,164 +3,164 @@
|
|||||||
**Version:** 9.0
|
**Version:** 9.0
|
||||||
**Phases:** 1 — Design Foundation through 9 — Developer Handoff
|
**Phases:** 1 — Design Foundation through 9 — Developer Handoff
|
||||||
|
|
||||||
| ID | Decision | Status | Rationale | Consequence |
|
| ID | Decision | Status | Rationale | Consequence |
|
||||||
|---|---|---|---|---|
|
| ----- | ---------------------------------------------------------------------------------------------------------- | -------------------- | -------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
|
||||||
| D-001 | Use restrained operational blue as the main brand family | Approved | Supports trust, clarity, and product-led presentation | Blue carries navigation, links, selection, and routine primary actions |
|
| D-001 | Use restrained operational blue as the main brand family | Approved | Supports trust, clarity, and product-led presentation | Blue carries navigation, links, selection, and routine primary actions |
|
||||||
| D-002 | Reserve orange for demo conversion and tightly related emphasis | Approved | Preserves conversion salience and prevents visual noise | Orange is not a routine navigation or generic action color |
|
| D-002 | Reserve orange for demo conversion and tightly related emphasis | Approved | Preserves conversion salience and prevents visual noise | Orange is not a routine navigation or generic action color |
|
||||||
| D-003 | Use Inter Variable for English and French | Approved | Screen-oriented, broad Latin support, strong UI and marketing utility | One Latin family across website and product-facing UI |
|
| D-003 | Use Inter Variable for English and French | Approved | Screen-oriented, broad Latin support, strong UI and marketing utility | One Latin family across website and product-facing UI |
|
||||||
| D-004 | Use Noto Sans Arabic for Arabic | Approved | Broad Arabic-script support and suitable sans-serif structure | Arabic receives separate metrics and locale loading |
|
| D-004 | Use Noto Sans Arabic for Arabic | Approved | Broad Arabic-script support and suitable sans-serif structure | Arabic receives separate metrics and locale loading |
|
||||||
| D-005 | Use one token system with separate display and UI roles | Approved | Avoids two unrelated systems while respecting different density needs | Marketing headings and application labels do not share identical sizing |
|
| D-005 | Use one token system with separate display and UI roles | Approved | Avoids two unrelated systems while respecting different density needs | Marketing headings and application labels do not share identical sizing |
|
||||||
| D-006 | Adopt blue-600 `#1B5DD8` as brand anchor | Approved | Clear operational blue with useful lighter and darker neighbors | Accessible action background uses blue-700 |
|
| D-006 | Adopt blue-600 `#1B5DD8` as brand anchor | Approved | Clear operational blue with useful lighter and darker neighbors | Accessible action background uses blue-700 |
|
||||||
| D-007 | Adopt orange-600 `#EA580C` as accent anchor | Approved | Strong conversion accent without becoming the whole brand | White-text conversion CTA uses orange-700 |
|
| D-007 | Adopt orange-600 `#EA580C` as accent anchor | Approved | Strong conversion accent without becoming the whole brand | White-text conversion CTA uses orange-700 |
|
||||||
| D-008 | Ban orange-600 with white normal text | Approved restriction | 3.56:1 contrast fails normal-text AA | Use orange-700 in light theme or orange-400 with dark text in dark theme |
|
| D-008 | Ban orange-600 with white normal text | Approved restriction | 3.56:1 contrast fails normal-text AA | Use orange-700 in light theme or orange-400 with dark text in dark theme |
|
||||||
| D-009 | Use a 4 px spacing base | Approved | Predictable, scalable, compatible with compact and spacious layouts | Exceptions require explicit optical rationale |
|
| D-009 | Use a 4 px spacing base | Approved | Predictable, scalable, compatible with compact and spacious layouts | Exceptions require explicit optical rationale |
|
||||||
| D-010 | Use 4/8/12 column grids for mobile/tablet/desktop | Approved | Distinct responsive composition without device-specific hacks | Reference frames: 390, 1024, 1440 px |
|
| D-010 | Use 4/8/12 column grids for mobile/tablet/desktop | Approved | Distinct responsive composition without device-specific hacks | Reference frames: 390, 1024, 1440 px |
|
||||||
| D-011 | Cap primary container at 1280 px | Approved | Protects readability while allowing strong product visuals | Wider screens gain outer breathing room, not stretched text |
|
| D-011 | Cap primary container at 1280 px | Approved | Protects readability while allowing strong product visuals | Wider screens gain outer breathing room, not stretched text |
|
||||||
| D-012 | Use CSS logical properties | Approved | Required for real LTR/RTL parity | Physical left/right spacing tokens are rejected |
|
| D-012 | Use CSS logical properties | Approved | Required for real LTR/RTL parity | Physical left/right spacing tokens are rejected |
|
||||||
| D-013 | Use Lucide as the base icon family | Approved | Consistent 24 px grid and 2 px outline language | Mixing icon families requires a documented exception |
|
| D-013 | Use Lucide as the base icon family | Approved | Consistent 24 px grid and 2 px outline language | Mixing icon families requires a documented exception |
|
||||||
| D-014 | Prefer 44 × 44 px interactive targets | Approved | Exceeds the WCAG 2.2 minimum and improves usability | Compact exceptions must remain accessible |
|
| D-014 | Prefer 44 × 44 px interactive targets | Approved | Exceeds the WCAG 2.2 minimum and improves usability | Compact exceptions must remain accessible |
|
||||||
| D-015 | Require formal Modern Standard Arabic | Approved | Prevents inconsistent regional dialect and preserves professional tone | All Arabic product and marketing copy must be linguistically reviewed |
|
| D-015 | Require formal Modern Standard Arabic | Approved | Prevents inconsistent regional dialect and preserves professional tone | All Arabic product and marketing copy must be linguistically reviewed |
|
||||||
| D-016 | Avoid automotive racing clichés | Approved | Product is operational software, not performance-car entertainment | No speedometers, racing stripes, carbon fiber, or aggressive neon styling |
|
| D-016 | Avoid automotive racing clichés | Approved | Product is operational software, not performance-car entertainment | No speedometers, racing stripes, carbon fiber, or aggressive neon styling |
|
||||||
| R-001 | Use orange for every primary button | Rejected | Destroys conversion hierarchy | Routine primary actions remain blue |
|
| R-001 | Use orange for every primary button | Rejected | Destroys conversion hierarchy | Routine primary actions remain blue |
|
||||||
| R-002 | Force identical line-height across scripts | Rejected | Damages Arabic readability and vertical rhythm | Arabic uses script-specific line heights |
|
| R-002 | Force identical line-height across scripts | Rejected | Damages Arabic readability and vertical rhythm | Arabic uses script-specific line heights |
|
||||||
| R-003 | Use fixed-width translated buttons | Rejected | Breaks under French expansion and Arabic content | Buttons are content-driven |
|
| R-003 | Use fixed-width translated buttons | Rejected | Breaks under French expansion and Arabic content | Buttons are content-driven |
|
||||||
| R-004 | Mirror all icons in RTL | Rejected | Many icons are semantically direction-neutral | Mirror only directional meaning |
|
| R-004 | Mirror all icons in RTL | Rejected | Many icons are semantically direction-neutral | Mirror only directional meaning |
|
||||||
| R-005 | Create one type scale for every context | Rejected | Marketing and dense UI have different jobs | Shared foundations, separate role scales |
|
| R-005 | Create one type scale for every context | Rejected | Marketing and dense UI have different jobs | Shared foundations, separate role scales |
|
||||||
| D-017 | Use English as source, default, and fallback locale | Approved | Provides one controlled authoring source while all public locales remain independently addressable | Missing translations fail CI; production fallback is logged |
|
| D-017 | Use English as source, default, and fallback locale | Approved | Provides one controlled authoring source while all public locales remain independently addressable | Missing translations fail CI; production fallback is logged |
|
||||||
| D-018 | Use locale-prefixed public URLs for every indexable page | Approved | Makes language state shareable, crawlable, and deterministic | All navigation and SEO generation use a central route manifest |
|
| D-018 | Use locale-prefixed public URLs for every indexable page | Approved | Makes language state shareable, crawlable, and deterministic | All navigation and SEO generation use a central route manifest |
|
||||||
| D-019 | Limit automatic locale detection to root `/` | Approved | Respects explicit URLs and prevents redirect loops or crawler interference | Locale-prefixed pages are never overridden by browser language |
|
| D-019 | Limit automatic locale detection to root `/` | Approved | Respects explicit URLs and prevents redirect loops or crawler interference | Locale-prefixed pages are never overridden by browser language |
|
||||||
| D-020 | Persist explicit language selection in a first-party cookie | Approved | Allows SSR to honor user choice without relying on client-only state | Manual choice outranks Accept-Language |
|
| D-020 | Persist explicit language selection in a first-party cookie | Approved | Allows SSR to honor user choice without relying on client-only state | Manual choice outranks Accept-Language |
|
||||||
| D-021 | Use localized public slugs backed by stable route IDs | Approved | Improves localized readability while preventing routing logic from depending on translated strings | Switchers, sitemaps, and canonicals map by route ID |
|
| D-021 | Use localized public slugs backed by stable route IDs | Approved | Improves localized readability while preventing routing logic from depending on translated strings | Switchers, sitemaps, and canonicals map by route ID |
|
||||||
| D-022 | Use English/French LTR and Arabic document-level RTL | Approved | Correct browser bidi and layout behavior requires root direction | Arabic pages render `html lang=ar dir=rtl` |
|
| D-022 | Use English/French LTR and Arabic document-level RTL | Approved | Correct browser bidi and layout behavior requires root direction | Arabic pages render `html lang=ar dir=rtl` |
|
||||||
| D-023 | Use formal Modern Standard Arabic only | Approved | Maintains professional, region-neutral baseline | Dialect and colloquial copy fail linguistic review |
|
| D-023 | Use formal Modern Standard Arabic only | Approved | Maintains professional, region-neutral baseline | Dialect and colloquial copy fail linguistic review |
|
||||||
| D-024 | Use Latin digits and Gregorian dates in the language-only baseline | Approved | Keeps operational data and identifiers consistent before market-specific preferences exist | Revisit only after Arabic market research |
|
| D-024 | Use Latin digits and Gregorian dates in the language-only baseline | Approved | Keeps operational data and identifiers consistent before market-specific preferences exist | Revisit only after Arabic market research |
|
||||||
| D-025 | Use `فحص المركبة` for vehicle inspection | Approved | Clear operational term with broad formal comprehension | `معاينة المركبة` remains a documented secondary term |
|
| D-025 | Use `فحص المركبة` for vehicle inspection | Approved | Clear operational term with broad formal comprehension | `معاينة المركبة` remains a documented secondary term |
|
||||||
| D-026 | Use `عقد الإيجار` for rental agreement | Approved | Concise and standard legal-operational label | Jurisdictional legal review remains required |
|
| D-026 | Use `عقد الإيجار` for rental agreement | Approved | Concise and standard legal-operational label | Jurisdictional legal review remains required |
|
||||||
| D-027 | Use `مبلغ الضمان` for security deposit | Approved | Avoids confusion between deposit and insurance | Arabic `تأمين` alone is rejected |
|
| D-027 | Use `مبلغ الضمان` for security deposit | Approved | Avoids confusion between deposit and insurance | Arabic `تأمين` alone is rejected |
|
||||||
| D-028 | Use `معدل استخدام الأسطول` for fleet utilization rate | Approved | Describes use rather than occupancy | Occupancy language is rejected |
|
| D-028 | Use `معدل استخدام الأسطول` for fleet utilization rate | Approved | Describes use rather than occupancy | Occupancy language is rejected |
|
||||||
| D-029 | Keep currency, country, timezone, tax, and phone behavior independent from language | Approved | Language-only locales do not define business jurisdiction | Formatting consumes explicit data and context |
|
| D-029 | Keep currency, country, timezone, tax, and phone behavior independent from language | Approved | Language-only locales do not define business jurisdiction | Formatting consumes explicit data and context |
|
||||||
| D-030 | Use semantic namespaced translation keys | Approved | Supports reuse, context, QA, and refactoring | Source sentences, positions, and visual styles are not keys |
|
| D-030 | Use semantic namespaced translation keys | Approved | Supports reuse, context, QA, and refactoring | Source sentences, positions, and visual styles are not keys |
|
||||||
| D-031 | Fail missing translations in CI | Approved | Prevents silent partial localization | Production fallback is a logged safety net, not the workflow |
|
| D-031 | Fail missing translations in CI | Approved | Prevents silent partial localization | Production fallback is a logged safety net, not the workflow |
|
||||||
| D-032 | Use self-canonical locale pages and reciprocal language-only hreflang | Approved | Prevents English from swallowing localized indexing | `x-default` points to `/en` until a neutral selector exists |
|
| D-032 | Use self-canonical locale pages and reciprocal language-only hreflang | Approved | Prevents English from swallowing localized indexing | `x-default` points to `/en` until a neutral selector exists |
|
||||||
| D-033 | Omit regional Open Graph locale until target markets are approved | Approved | Avoids inventing country-region assumptions | Localized OG copy still ships |
|
| D-033 | Omit regional Open Graph locale until target markets are approved | Approved | Avoids inventing country-region assumptions | Localized OG copy still ships |
|
||||||
| R-006 | Use country flags as language controls | Rejected | Languages are not equivalent to countries | Display language names in their own language |
|
| R-006 | Use country flags as language controls | Rejected | Languages are not equivalent to countries | Display language names in their own language |
|
||||||
| R-007 | Redirect explicit locale URLs based on browser language | Rejected | Breaks shareability, intent, crawling, and testing | Detection is root-only |
|
| R-007 | Redirect explicit locale URLs based on browser language | Rejected | Breaks shareability, intent, crawling, and testing | Detection is root-only |
|
||||||
| R-008 | Use IP geolocation to choose language | Rejected | Country is not language and geolocation is unreliable | Use explicit preference and Accept-Language |
|
| R-008 | Use IP geolocation to choose language | Rejected | Country is not language and geolocation is unreliable | Use explicit preference and Accept-Language |
|
||||||
| R-009 | Use English source sentences as translation keys | Rejected | Copy edits become code changes and context is lost | Use semantic namespaced keys |
|
| R-009 | Use English source sentences as translation keys | Rejected | Copy edits become code changes and context is lost | Use semantic namespaced keys |
|
||||||
| R-010 | Simulate Arabic by right-aligning an LTR document | Rejected | Does not provide correct bidi or layout behavior | Set document-level RTL |
|
| R-010 | Simulate Arabic by right-aligning an LTR document | Rejected | Does not provide correct bidi or layout behavior | Set document-level RTL |
|
||||||
| R-011 | Translate check-in/check-out like hotel terminology | Rejected | Misstates rental operational processes | Use pickup, handover, and return terms by perspective |
|
| R-011 | Translate check-in/check-out like hotel terminology | Rejected | Misstates rental operational processes | Use pickup, handover, and return terms by perspective |
|
||||||
| R-012 | Infer currency or legal jurisdiction from locale | Rejected | English, French, and Arabic span multiple markets | Use explicit business data |
|
| R-012 | Infer currency or legal jurisdiction from locale | Rejected | English, French, and Arabic span multiple markets | Use explicit business data |
|
||||||
| R-013 | Publish placeholder logos, metrics, testimonials, or certifications | Rejected | Creates unsupported evidence | Hide evidence sections until approved sources exist |
|
| R-013 | Publish placeholder logos, metrics, testimonials, or certifications | Rejected | Creates unsupported evidence | Hide evidence sections until approved sources exist |
|
||||||
| D-034 | Organize the library into foundations, behavior primitives, and marketing composites | Approved | Prevents page-specific composition from masquerading as a reusable primitive | Component APIs and ownership follow explicit tiers |
|
| D-034 | Organize the library into foundations, behavior primitives, and marketing composites | Approved | Prevents page-specific composition from masquerading as a reusable primitive | Component APIs and ownership follow explicit tiers |
|
||||||
| D-035 | Share behavior primitives with the future product dashboard, not marketing composites by default | Approved | Marketing and operational interfaces have different density and interaction requirements | Dashboard reuse requires a later density specification |
|
| D-035 | Share behavior primitives with the future product dashboard, not marketing composites by default | Approved | Marketing and operational interfaces have different density and interaction requirements | Dashboard reuse requires a later density specification |
|
||||||
| D-036 | Use one Button component with primary-blue and conversion-orange intents | Approved | Preserves hierarchy and avoids duplicated behavior | Orange remains restricted to demo and related conversion |
|
| D-036 | Use one Button component with primary-blue and conversion-orange intents | Approved | Preserves hierarchy and avoids duplicated behavior | Orange remains restricted to demo and related conversion |
|
||||||
| D-037 | Require content-driven dimensions and prohibit truncation of important copy | Approved | French/Arabic expansion and zoom invalidate fixed text geometry | Components change structure before collision |
|
| D-037 | Require content-driven dimensions and prohibit truncation of important copy | Approved | French/Arabic expansion and zoom invalidate fixed text geometry | Components change structure before collision |
|
||||||
| D-038 | Require visible labels for all form fields | Approved | Placeholders disappear and are unreliable accessible context | Placeholder-only fields fail review |
|
| D-038 | Require visible labels for all form fields | Approved | Placeholders disappear and are unreliable accessible context | Placeholder-only fields fail review |
|
||||||
| D-039 | Use native HTML semantics as the baseline for controls, tables, disclosures, and dialogs | Approved | Native behavior reduces accessibility defects | Custom widgets require separate interaction contracts |
|
| D-039 | Use native HTML semantics as the baseline for controls, tables, disclosures, and dialogs | Approved | Native behavior reduces accessibility defects | Custom widgets require separate interaction contracts |
|
||||||
| D-040 | Use a native date input as the Phase 3 Gregorian baseline | Approved | Custom date pickers are complex and requirements are unresolved | Custom calendar work is deferred |
|
| D-040 | Use a native date input as the Phase 3 Gregorian baseline | Approved | Custom date pickers are complex and requirements are unresolved | Custom calendar work is deferred |
|
||||||
| D-041 | Use one culturally neutral full-name field in the demo form | Approved | First/last assumptions fail across naming systems | CRM mapping preserves the complete name |
|
| D-041 | Use one culturally neutral full-name field in the demo form | Approved | First/last assumptions fail across naming systems | CRM mapping preserves the complete name |
|
||||||
| D-042 | Keep the demo form minimally qualified | Approved | Extra fields increase abandonment and privacy burden | Required: full name, work email, company, fleet-size range |
|
| D-042 | Keep the demo form minimally qualified | Approved | Extra fields increase abandonment and privacy burden | Required: full name, work email, company, fleet-size range |
|
||||||
| D-043 | Evidence-gate metrics, testimonials, case studies, trust marks, and pricing | Approved | A visual component must not make unsupported content publishable | Missing or expired approval suppresses rendering |
|
| D-043 | Evidence-gate metrics, testimonials, case studies, trust marks, and pricing | Approved | A visual component must not make unsupported content publishable | Missing or expired approval suppresses rendering |
|
||||||
| D-044 | Approve pricing cards and pricing comparison as separate components | Approved | Plan orientation and detailed comparison are distinct tasks | Both may coexist; neither ships without approved pricing |
|
| D-044 | Approve pricing cards and pricing comparison as separate components | Approved | Plan orientation and detailed comparison are distinct tasks | Both may coexist; neither ships without approved pricing |
|
||||||
| D-045 | Open mobile navigation from inline-start | Approved | Aligns navigation origin with document direction | Drawer opens left in LTR and right in RTL |
|
| D-045 | Open mobile navigation from inline-start | Approved | Aligns navigation origin with document direction | Drawer opens left in LTR and right in RTL |
|
||||||
| D-046 | Require overlay focus containment, inert background, Escape policy, and focus return | Approved | Visual layering without focus management is inaccessible | Drawer/modal implementations need behavioral tests |
|
| D-046 | Require overlay focus containment, inert background, Escape policy, and focus return | Approved | Visual layering without focus management is inaccessible | Drawer/modal implementations need behavioral tests |
|
||||||
| D-047 | Place toasts at inline-end and keep critical errors persistent in context | Approved | Locale-aware placement and durable correction prevent missed failures | Error toasts cannot be the sole error record |
|
| D-047 | Place toasts at inline-end and keep critical errors persistent in context | Approved | Locale-aware placement and durable correction prevent missed failures | Error toasts cannot be the sole error record |
|
||||||
| D-048 | Use the same component tree for LTR and RTL | Approved | Direction-specific implementations drift | Direction is resolved through document state, logical CSS, and selective mirroring |
|
| D-048 | Use the same component tree for LTR and RTL | Approved | Direction-specific implementations drift | Direction is resolved through document state, logical CSS, and selective mirroring |
|
||||||
| D-049 | Respect reduced-motion preferences and keep motion non-essential | Approved | Motion should support continuity, not gate understanding | Shimmer and transforms are removed or minimized |
|
| D-049 | Respect reduced-motion preferences and keep motion non-essential | Approved | Motion should support continuity, not gate understanding | Shimmer and transforms are removed or minimized |
|
||||||
| D-050 | Use English light/LTR only for the initial specimen while requiring full locale/theme tests | Approved | Gives a concrete first specimen without pretending it proves conformance | Acceptance still requires en/fr/ar and light/dark |
|
| D-050 | Use English light/LTR only for the initial specimen while requiring full locale/theme tests | Approved | Gives a concrete first specimen without pretending it proves conformance | Acceptance still requires en/fr/ar and light/dark |
|
||||||
| R-014 | Create separate LTR and RTL component implementations | Rejected | Guarantees drift and mismatched behavior | Use one semantic component tree |
|
| R-014 | Create separate LTR and RTL component implementations | Rejected | Guarantees drift and mismatched behavior | Use one semantic component tree |
|
||||||
| R-015 | Use fixed-height cards, buttons, labels, or validation regions | Rejected | Breaks expansion, zoom, and Arabic metrics | Use content-driven block sizes |
|
| R-015 | Use fixed-height cards, buttons, labels, or validation regions | Rejected | Breaks expansion, zoom, and Arabic metrics | Use content-driven block sizes |
|
||||||
| R-016 | Publish placeholder metrics, logos, testimonials, certifications, or prices | Rejected | Converts missing evidence into deception | Omit until approval exists |
|
| R-016 | Publish placeholder metrics, logos, testimonials, certifications, or prices | Rejected | Converts missing evidence into deception | Omit until approval exists |
|
||||||
| R-017 | Open navigation or critical content on hover only | Rejected | Excludes keyboard and touch users | Use explicit triggers and focus behavior |
|
| R-017 | Open navigation or critical content on hover only | Rejected | Excludes keyboard and touch users | Use explicit triggers and focus behavior |
|
||||||
| R-018 | Use placeholder text instead of visible form labels | Rejected | Loses context after input and harms accessibility | Labels remain visible |
|
| R-018 | Use placeholder text instead of visible form labels | Rejected | Loses context after input and harms accessibility | Labels remain visible |
|
||||||
| R-019 | Replace native select/date controls for visual consistency alone | Rejected | Adds keyboard, screen-reader, mobile, and localization risk | Native controls are baseline |
|
| R-019 | Replace native select/date controls for visual consistency alone | Rejected | Adds keyboard, screen-reader, mobile, and localization risk | Native controls are baseline |
|
||||||
| R-020 | Force marketing composites into the future product dashboard | Rejected | Shared appearance is not shared information architecture | Share primitives; specify product compositions later |
|
| R-020 | Force marketing composites into the future product dashboard | Rejected | Shared appearance is not shared information architecture | Share primitives; specify product compositions later |
|
||||||
| R-021 | Combine pricing cards and comparison into one shape-shifting component | Rejected | Merges distinct tasks and creates brittle responsive behavior | Maintain separate components |
|
| R-021 | Combine pricing cards and comparison into one shape-shifting component | Rejected | Merges distinct tasks and creates brittle responsive behavior | Maintain separate components |
|
||||||
| R-022 | Auto-dismiss critical errors without a persistent correction path | Rejected | Users can miss failure and lose context | Keep errors in the owning form/page |
|
| R-022 | Auto-dismiss critical errors without a persistent correction path | Rejected | Users can miss failure and lose context | Keep errors in the owning form/page |
|
||||||
| D-051 | Expose Light, Dark, and System as explicit appearance choices | Approved | Users need deterministic control while retaining platform preference support | Theme selector and persistence support all three values |
|
| D-051 | Expose Light, Dark, and System as explicit appearance choices | Approved | Users need deterministic control while retaining platform preference support | Theme selector and persistence support all three values |
|
||||||
| D-052 | Persist System as an explicit choice | Approved | System is a user preference, not merely missing state | OS changes update resolved theme while the selector remains System |
|
| D-052 | Persist System as an explicit choice | Approved | System is a user preference, not merely missing state | OS changes update resolved theme while the selector remains System |
|
||||||
| D-053 | Use `hpc-theme` cookie as canonical preference and local storage as a mirror | Approved | SSR needs a readable value; client tabs need immediate synchronization | Cookie wins disagreement; both are written together |
|
| D-053 | Use `hpc-theme` cookie as canonical preference and local storage as a mirror | Approved | SSR needs a readable value; client tabs need immediate synchronization | Cookie wins disagreement; both are written together |
|
||||||
| D-054 | Separate theme preference from resolved theme | Approved | System can resolve to Light or Dark without losing the user choice | Root uses `data-theme-preference` and `data-theme` |
|
| D-054 | Separate theme preference from resolved theme | Approved | System can resolve to Light or Dark without losing the user choice | Root uses `data-theme-preference` and `data-theme` |
|
||||||
| D-055 | Apply theme before first paint with server state and a tiny inline bootstrap | Approved | Prevents incorrect-theme flash and hydration mismatch | Bootstrap runs before blocking CSS and receives a CSP nonce/hash |
|
| D-055 | Apply theme before first paint with server state and a tiny inline bootstrap | Approved | Prevents incorrect-theme flash and hydration mismatch | Bootstrap runs before blocking CSS and receives a CSP nonce/hash |
|
||||||
| D-056 | Use deep-navy dark surfaces rather than pure black or mechanical inversion | Approved | Preserves operational hierarchy and reduces glare without flattening the UI | Dark surface ladder uses canvas, surface, subtle, and raised roles |
|
| D-056 | Use deep-navy dark surfaces rather than pure black or mechanical inversion | Approved | Preserves operational hierarchy and reduces glare without flattening the UI | Dark surface ladder uses canvas, surface, subtle, and raised roles |
|
||||||
| D-057 | Use light blue with deep-navy text for routine primary actions in dark mode | Approved | Maintains brand recognition and high contrast without a luminous button wall | Dark primary action roles differ from light while preserving intent |
|
| D-057 | Use light blue with deep-navy text for routine primary actions in dark mode | Approved | Maintains brand recognition and high contrast without a luminous button wall | Dark primary action roles differ from light while preserving intent |
|
||||||
| D-058 | Use orange-400 with dark text for conversion actions in dark mode | Approved | Meets contrast and preserves controlled conversion salience | Orange remains restricted to demo conversion and related emphasis |
|
| D-058 | Use orange-400 with dark text for conversion actions in dark mode | Approved | Meets contrast and preserves controlled conversion salience | Orange remains restricted to demo conversion and related emphasis |
|
||||||
| D-059 | Use neutral-black dark shadows and prohibit colored glow elevation | Approved | Glow reduces precision and makes hierarchy noisy | Elevation uses borders, surface steps, and neutral shadows |
|
| D-059 | Use neutral-black dark shadows and prohibit colored glow elevation | Approved | Glow reduces precision and makes hierarchy noisy | Elevation uses borders, surface steps, and neutral shadows |
|
||||||
| D-060 | Permit semantically equivalent light/dark hero artwork variants | Approved | Dark presentation may need art direction without changing the product claim | Same content/composition; only presentation treatment changes |
|
| D-060 | Permit semantically equivalent light/dark hero artwork variants | Approved | Dark presentation may need art direction without changing the product claim | Same content/composition; only presentation treatment changes |
|
||||||
| D-061 | Do not make product screenshots automatically follow website theme | Approved | A screenshot is evidence of an actual product state | Store capture theme metadata; never invert or recolor captures |
|
| D-061 | Do not make product screenshots automatically follow website theme | Approved | A screenshot is evidence of an actual product state | Store capture theme metadata; never invert or recolor captures |
|
||||||
| D-062 | Allow paired screenshot variants only for equivalent approved product states | Approved | Theme matching is useful only when evidence remains truthful | Content model records capture theme, locale, direction, and approval |
|
| D-062 | Allow paired screenshot variants only for equivalent approved product states | Approved | Theme matching is useful only when evidence remains truthful | Content model records capture theme, locale, direction, and approval |
|
||||||
| D-063 | Use theme-specific chart tokens plus redundant non-color encoding | Approved | Color-only data is inaccessible and fragile across themes | Charts use labels, markers, dashes, patterns, or data tables |
|
| D-063 | Use theme-specific chart tokens plus redundant non-color encoding | Approved | Color-only data is inaccessible and fragile across themes | Charts use labels, markers, dashes, patterns, or data tables |
|
||||||
| D-064 | Keep theme changes immediate and non-essential motion reduced | Approved | Global cross-fades create flash, noise, and intermediate contrast failures | No global theme transition; reduced motion removes shimmer/rotation |
|
| D-064 | Keep theme changes immediate and non-essential motion reduced | Approved | Global cross-fades create flash, noise, and intermediate contrast failures | No global theme transition; reduced motion removes shimmer/rotation |
|
||||||
| D-065 | Update System mode live when the platform preference changes | Approved | System should continue following its stated source | Media-query changes update resolved theme only while preference is System |
|
| D-065 | Update System mode live when the platform preference changes | Approved | System should continue following its stated source | Media-query changes update resolved theme only while preference is System |
|
||||||
| D-066 | Set `color-scheme` from the resolved theme | Approved | Native controls and browser UI must match the rendered appearance | Selects, dates, autofill, scrollbars, and validation UI require browser testing |
|
| D-066 | Set `color-scheme` from the resolved theme | Approved | Native controls and browser UI must match the rendered appearance | Selects, dates, autofill, scrollbars, and validation UI require browser testing |
|
||||||
| R-023 | Create dark mode by inverting the light palette | Rejected | Numeric inversion destroys semantic hierarchy and brand intent | Map every role explicitly |
|
| R-023 | Create dark mode by inverting the light palette | Rejected | Numeric inversion destroys semantic hierarchy and brand intent | Map every role explicitly |
|
||||||
| R-024 | Use pure black canvas and pure white text everywhere | Rejected | Produces harsh contrast and flattens hierarchy | Use deep navy and off-white/blue-gray text roles |
|
| R-024 | Use pure black canvas and pure white text everywhere | Rejected | Produces harsh contrast and flattens hierarchy | Use deep navy and off-white/blue-gray text roles |
|
||||||
| R-025 | Add blue/orange glows to cards, charts, buttons, or dialogs | Rejected | Turns operational software into decorative noise | Use neutral elevation and restrained emphasis |
|
| R-025 | Add blue/orange glows to cards, charts, buttons, or dialogs | Rejected | Turns operational software into decorative noise | Use neutral elevation and restrained emphasis |
|
||||||
| R-026 | Store theme only in local storage and apply after hydration | Rejected | Causes incorrect-theme flash and prevents correct SSR | Use cookie, pre-paint bootstrap, and server state |
|
| R-026 | Store theme only in local storage and apply after hydration | Rejected | Causes incorrect-theme flash and prevents correct SSR | Use cookie, pre-paint bootstrap, and server state |
|
||||||
| R-027 | Treat System as an unpersisted default | Rejected | Loses the user’s explicit preference | Persist System exactly like Light and Dark |
|
| R-027 | Treat System as an unpersisted default | Rejected | Loses the user’s explicit preference | Persist System exactly like Light and Dark |
|
||||||
| R-028 | Recolor, invert, or mirror screenshots for dark mode | Rejected | Misrepresents the product and can corrupt direction/data meaning | Use approved captures or a neutral frame |
|
| R-028 | Recolor, invert, or mirror screenshots for dark mode | Rejected | Misrepresents the product and can corrupt direction/data meaning | Use approved captures or a neutral frame |
|
||||||
| R-029 | Encode chart series or status using color alone | Rejected | Fails accessibility and weakens print/forced-colors behavior | Add labels, markers, patterns, icons, or text |
|
| R-029 | Encode chart series or status using color alone | Rejected | Fails accessibility and weakens print/forced-colors behavior | Add labels, markers, patterns, icons, or text |
|
||||||
| D-067 | Use explicit bidi isolation for atomic mixed-direction values | Approved | Arabic labels frequently contain LTR operational identifiers whose punctuation otherwise reorders | Known technical values use `dir=ltr` and `bdi`/isolation at the smallest boundary |
|
| D-067 | Use explicit bidi isolation for atomic mixed-direction values | Approved | Arabic labels frequently contain LTR operational identifiers whose punctuation otherwise reorders | Known technical values use `dir=ltr` and `bdi`/isolation at the smallest boundary |
|
||||||
| D-068 | Use `dir=auto` only for genuinely unknown user-authored text | Approved | Known value classes have deterministic direction while free text may not | Translation resources and known identifiers never delegate direction to heuristics |
|
| D-068 | Use `dir=auto` only for genuinely unknown user-authored text | Approved | Known value classes have deterministic direction while free text may not | Translation resources and known identifiers never delegate direction to heuristics |
|
||||||
| D-069 | Mirror icons by semantic metadata rather than global RTL CSS | Approved | Global mirroring corrupts object, status, brand, and evidence assets | Icon instances declare directional, fixed, or contextual behavior |
|
| D-069 | Mirror icons by semantic metadata rather than global RTL CSS | Approved | Global mirroring corrupts object, status, brand, and evidence assets | Icon instances declare directional, fixed, or contextual behavior |
|
||||||
| D-070 | Keep DOM order aligned with Arabic visual and focus order | Approved | CSS reversal creates inaccessible mismatch and brittle responsive behavior | `row-reverse`, `column-reverse`, CSS `order`, and positive tabindex are prohibited for RTL repair |
|
| D-070 | Keep DOM order aligned with Arabic visual and focus order | Approved | CSS reversal creates inaccessible mismatch and brittle responsive behavior | `row-reverse`, `column-reverse`, CSS `order`, and positive tabindex are prohibited for RTL repair |
|
||||||
| D-071 | Make Arabic mobile navigation enter from inline-start | Approved | Navigation origin should follow document direction | Drawer enters from the right in Arabic and retains full focus management |
|
| D-071 | Make Arabic mobile navigation enter from inline-start | Approved | Navigation origin should follow document direction | Drawer enters from the right in Arabic and retains full focus management |
|
||||||
| D-072 | Let Arabic workflow progression run from right to left while preserving authored step order | Approved | Process reading should feel native without reversing data or source arrays | Natural RTL layout places step one on the right; mobile remains top-to-bottom |
|
| D-072 | Let Arabic workflow progression run from right to left while preserving authored step order | Approved | Process reading should feel native without reversing data or source arrays | Natural RTL layout places step one on the right; mobile remains top-to-bottom |
|
||||||
| D-073 | Keep chronological chart axes and trend meaning independent from RTL | Approved | Data chronology and increase/decrease meaning are domain semantics, not text direction | Charts are not automatically reversed and trend arrows are fixed |
|
| D-073 | Keep chronological chart axes and trend meaning independent from RTL | Approved | Data chronology and increase/decrease meaning are domain semantics, not text direction | Charts are not automatically reversed and trend arrows are fixed |
|
||||||
| D-074 | Preserve authoritative license-plate value and segment direction | Approved | Plate formats vary by country and may mix scripts | Latin, Arabic, and segmented plates use explicit direction; country templates remain market configuration |
|
| D-074 | Preserve authoritative license-plate value and segment direction | Approved | Plate formats vary by country and may mix scripts | Latin, Arabic, and segmented plates use explicit direction; country templates remain market configuration |
|
||||||
| D-075 | Require Arabic Light desktop and Arabic Dark mobile validation sheets | Approved | The riskiest direction/theme/responsive combinations need explicit evidence | 1440 Light desktop and 390 Dark mobile become release gates |
|
| D-075 | Require Arabic Light desktop and Arabic Dark mobile validation sheets | Approved | The riskiest direction/theme/responsive combinations need explicit evidence | 1440 Light desktop and 390 Dark mobile become release gates |
|
||||||
| D-076 | Confirm Latin digits and Gregorian dates as the Phase 5 language-only baseline | Approved | Phase 2 already separated language from market formatting | Arabic-Indic digits or alternate calendars require explicit market research and configuration |
|
| D-076 | Confirm Latin digits and Gregorian dates as the Phase 5 language-only baseline | Approved | Phase 2 already separated language from market formatting | Arabic-Indic digits or alternate calendars require explicit market research and configuration |
|
||||||
| D-077 | Use visual arrow-key movement for horizontal RTL tabs and similar composites | Approved | Keyboard commands should match spatial expectation while preserving authored sequence | ArrowLeft moves visually left and ArrowRight visually right; implementation maps to logical index |
|
| D-077 | Use visual arrow-key movement for horizontal RTL tabs and similar composites | Approved | Keyboard commands should match spatial expectation while preserving authored sequence | ArrowLeft moves visually left and ArrowRight visually right; implementation maps to logical index |
|
||||||
| R-030 | Mirror every SVG or image under `dir=rtl` | Rejected | Reverses logos, vehicles, screenshots, status icons, plates, and evidence | Mirror only explicitly directional icons |
|
| R-030 | Mirror every SVG or image under `dir=rtl` | Rejected | Reverses logos, vehicles, screenshots, status icons, plates, and evidence | Mirror only explicitly directional icons |
|
||||||
| R-031 | Reverse arrays or use CSS row-reverse to make Arabic look correct | Rejected | Breaks DOM/focus order and causes responsive drift | Preserve authored order and rely on document direction/logical layout |
|
| R-031 | Reverse arrays or use CSS row-reverse to make Arabic look correct | Rejected | Breaks DOM/focus order and causes responsive drift | Preserve authored order and rely on document direction/logical layout |
|
||||||
| R-032 | Insert Unicode bidi override characters into translation strings | Rejected | Hidden controls are fragile, difficult to audit, and can create spoofing risk | Use semantic markup and isolation wrappers |
|
| R-032 | Insert Unicode bidi override characters into translation strings | Rejected | Hidden controls are fragile, difficult to audit, and can create spoofing risk | Use semantic markup and isolation wrappers |
|
||||||
| R-033 | Reverse chronological charts because the page is RTL | Rejected | Text direction does not redefine time or data chronology | Chart order follows the chart specification |
|
| R-033 | Reverse chronological charts because the page is RTL | Rejected | Text direction does not redefine time or data chronology | Chart order follows the chart specification |
|
||||||
| R-034 | Infer license-plate format, currency, timezone, or country from Arabic language | Rejected | Arabic spans many jurisdictions | Consume explicit business and market configuration |
|
| R-034 | Infer license-plate format, currency, timezone, or country from Arabic language | Rejected | Arabic spans many jurisdictions | Consume explicit business and market configuration |
|
||||||
| R-035 | Leave technical fields RTL and compensate with right text alignment | Rejected | Caret, punctuation, selection, and copy behavior remain broken | Use explicit LTR direction and isolation for known technical values |
|
| R-035 | Leave technical fields RTL and compensate with right text alignment | Rejected | Caret, punctuation, selection, and copy behavior remain broken | Use explicit LTR direction and isolation for known technical values |
|
||||||
| D-078 | Use content-pressure breakpoints rather than device-name breakpoints | Approved | Translated copy, controls, and product evidence fail at different widths | Shared ranges coordinate layout, while intrinsic sizing and container queries control components |
|
| D-078 | Use content-pressure breakpoints rather than device-name breakpoints | Approved | Translated copy, controls, and product evidence fail at different widths | Shared ranges coordinate layout, while intrinsic sizing and container queries control components |
|
||||||
| D-079 | Validate 1440, 1024, and 390 px frames plus a 320 px reflow gate | Approved | Reference designs alone do not prove intermediate or zoom behavior | Automated tests include intermediate widths and 400% zoom equivalent |
|
| D-079 | Validate 1440, 1024, and 390 px frames plus a 320 px reflow gate | Approved | Reference designs alone do not prove intermediate or zoom behavior | Automated tests include intermediate widths and 400% zoom equivalent |
|
||||||
| D-080 | Collapse full navigation below 1120 px | Approved | French and Arabic header pressure makes 1024 too narrow for stable full navigation | The 1024 frame uses compact navigation and an inline-start drawer |
|
| D-080 | Collapse full navigation below 1120 px | Approved | French and Arabic header pressure makes 1024 too narrow for stable full navigation | The 1024 frame uses compact navigation and an inline-start drawer |
|
||||||
| D-081 | Keep the hero stacked below 1120 px | Approved | A forced 1024 split makes either copy or product preview less informative | Tablet uses claim, CTAs, then a full-width readable preview |
|
| D-081 | Keep the hero stacked below 1120 px | Approved | A forced 1024 split makes either copy or product preview less informative | Tablet uses claim, CTAs, then a full-width readable preview |
|
||||||
| D-082 | Keep the demo action reachable at every width | Approved | Primary conversion cannot disappear because the header becomes compact | Below 480 px the demo CTA is the first drawer action; above it may remain in the header when it fits |
|
| D-082 | Keep the demo action reachable at every width | Approved | Primary conversion cannot disappear because the header becomes compact | Below 480 px the demo CTA is the first drawer action; above it may remain in the header when it fits |
|
||||||
| D-083 | Use art-directed product previews instead of shrinking desktop screenshots below legibility | Approved | Tiny desktop UI ceases to provide product evidence | Mobile uses a focused crop, mobile capture, or coded responsive state |
|
| D-083 | Use art-directed product previews instead of shrinking desktop screenshots below legibility | Approved | Tiny desktop UI ceases to provide product evidence | Mobile uses a focused crop, mobile capture, or coded responsive state |
|
||||||
| D-084 | Prohibit swipe-only carousels for core modules, trust, testimonials, and case studies | Approved | Hidden slides reduce discoverability and create gesture/accessibility dependence | Core content uses grids or vertical stacks; optional carousels require a later explicit exception |
|
| D-084 | Prohibit swipe-only carousels for core modules, trust, testimonials, and case studies | Approved | Hidden slides reduce discoverability and create gesture/accessibility dependence | Core content uses grids or vertical stacks; optional carousels require a later explicit exception |
|
||||||
| D-085 | Prohibit page-level horizontal scrolling | Approved | Horizontal page scroll breaks reflow, focus visibility, and mobile use | Tables and atomic technical values may own bounded, labelled overflow |
|
| D-085 | Prohibit page-level horizontal scrolling | Approved | Horizontal page scroll breaks reflow, focus visibility, and mobile use | Tables and atomic technical values may own bounded, labelled overflow |
|
||||||
| D-086 | Preserve semantic pricing tables at constrained widths | Approved | Plan comparison is genuinely tabular and should not become an image or inaccessible card duplicate | Use internal horizontal scroll and a sticky inline-start feature column |
|
| D-086 | Preserve semantic pricing tables at constrained widths | Approved | Plan comparison is genuinely tabular and should not become an image or inaccessible card duplicate | Use internal horizontal scroll and a sticky inline-start feature column |
|
||||||
| D-087 | Use a 45rem container threshold for two-column demo-form fields | Approved | The form may sit in sections of different widths at the same viewport | Field layout responds to its own available space |
|
| D-087 | Use a 45rem container threshold for two-column demo-form fields | Approved | The form may sit in sections of different widths at the same viewport | Field layout responds to its own available space |
|
||||||
| D-088 | Require at least 10rem per step for a horizontal workflow | Approved | Breakpoint width alone does not protect French or Arabic step labels | Workflow stays vertical whenever actual content pressure fails the minimum |
|
| D-088 | Require at least 10rem per step for a horizontal workflow | Approved | Breakpoint width alone does not protect French or Arabic step labels | Workflow stays vertical whenever actual content pressure fails the minimum |
|
||||||
| D-089 | Use 18rem module cards, 20rem pricing cards, and 11rem metric cards as minimum readable widths | Approved | Fixed column counts create unreadable intermediate states | Grids use auto-fit/minmax and reduce column count before compressing content |
|
| D-089 | Use 18rem module cards, 20rem pricing cards, and 11rem metric cards as minimum readable widths | Approved | Fixed column counts create unreadable intermediate states | Grids use auto-fit/minmax and reduce column count before compressing content |
|
||||||
| D-090 | Never truncate critical headings, labels, buttons, errors, or evidence for responsive fit | Approved | Truncation hides meaning and disproportionately harms French and Arabic | Components wrap, stack, or change structure before reducing readability |
|
| D-090 | Never truncate critical headings, labels, buttons, errors, or evidence for responsive fit | Approved | Truncation hides meaning and disproportionately harms French and Arabic | Components wrap, stack, or change structure before reducing readability |
|
||||||
| D-091 | Keep 44 × 44 px minimum targets across all responsive states | Approved | Compact layouts increase, rather than reduce, touch accuracy needs | Headers, drawers, accordions, pagination, and form controls preserve target size |
|
| D-091 | Keep 44 × 44 px minimum targets across all responsive states | Approved | Compact layouts increase, rather than reduce, touch accuracy needs | Headers, drawers, accordions, pagination, and form controls preserve target size |
|
||||||
| D-092 | Remove evidence sections entirely when approved content is absent | Approved | Empty responsive slots invite placeholder or fabricated evidence | No reserved logo rails, metrics, or testimonial gaps are rendered |
|
| D-092 | Remove evidence sections entirely when approved content is absent | Approved | Empty responsive slots invite placeholder or fabricated evidence | No reserved logo rails, metrics, or testimonial gaps are rendered |
|
||||||
| R-036 | Use one global breakpoint set as the only adaptation mechanism | Rejected | Components fail at different content widths | Use shared coordination ranges plus intrinsic and container rules |
|
| R-036 | Use one global breakpoint set as the only adaptation mechanism | Rejected | Components fail at different content widths | Use shared coordination ranges plus intrinsic and container rules |
|
||||||
| R-037 | Keep desktop navigation at 1024 by shrinking gaps and labels | Rejected | Creates a fragile header that fails translation and zoom | Use compact navigation below 1120 |
|
| R-037 | Keep desktop navigation at 1024 by shrinking gaps and labels | Rejected | Creates a fragile header that fails translation and zoom | Use compact navigation below 1120 |
|
||||||
| R-038 | Hide secondary actions on mobile | Rejected | Removes valid user paths and can trap users in one conversion route | Stack or move actions without deleting them |
|
| R-038 | Hide secondary actions on mobile | Rejected | Removes valid user paths and can trap users in one conversion route | Stack or move actions without deleting them |
|
||||||
| R-039 | Scale a full desktop screenshot to fit mobile | Rejected | Makes product evidence unreadable | Use art-directed captures or coded responsive previews |
|
| R-039 | Scale a full desktop screenshot to fit mobile | Rejected | Makes product evidence unreadable | Use art-directed captures or coded responsive previews |
|
||||||
| R-040 | Convert core module sections into swipe-only carousels | Rejected | Reduces discoverability and accessibility | Use visible grids/stacks |
|
| R-040 | Convert core module sections into swipe-only carousels | Rejected | Reduces discoverability and accessibility | Use visible grids/stacks |
|
||||||
| R-041 | Allow body horizontal scrolling for comparison tables | Rejected | Breaks page navigation and reflow | Contain overflow inside the table region |
|
| R-041 | Allow body horizontal scrolling for comparison tables | Rejected | Breaks page navigation and reflow | Contain overflow inside the table region |
|
||||||
| R-042 | Ellipsize French or Arabic headings and button labels | Rejected | Hides meaning and creates unequal localization quality | Use content-driven size and structural change |
|
| R-042 | Ellipsize French or Arabic headings and button labels | Rejected | Hides meaning and creates unequal localization quality | Use content-driven size and structural change |
|
||||||
| R-043 | Use CSS order or DOM duplication to rearrange responsive content | Rejected | Creates focus, reading-order, maintenance, and analytics drift | Keep one semantic source order |
|
| R-043 | Use CSS order or DOM duplication to rearrange responsive content | Rejected | Creates focus, reading-order, maintenance, and analytics drift | Keep one semantic source order |
|
||||||
| R-044 | Use fixed heights to maintain card and section alignment | Rejected | Translation, zoom, validation, and dynamic data overflow | Use content-driven block sizing |
|
| R-044 | Use fixed heights to maintain card and section alignment | Rejected | Translation, zoom, validation, and dynamic data overflow | Use content-driven block sizing |
|
||||||
| D-093 | Use a workflow-first homepage narrative rather than a generic feature dump | Approved | Buyers need to understand the operating model before comparing modules | Hero, problem, workflow, roles, then modules establish the product story |
|
| D-093 | Use a workflow-first homepage narrative rather than a generic feature dump | Approved | Buyers need to understand the operating model before comparing modules | Hero, problem, workflow, roles, then modules establish the product story |
|
||||||
| D-094 | Use the reservation-to-return promise as the five-second hero message | Approved | It states category, scope, and operating value without unsupported superlatives | Hero copy stays concrete across en/fr/ar |
|
| D-094 | Use the reservation-to-return promise as the five-second hero message | Approved | It states category, scope, and operating value without unsupported superlatives | Hero copy stays concrete across en/fr/ar |
|
||||||
| D-095 | Render operational-coverage proof instead of unsupported customer-logo proof | Approved | Product scope can be shown without fabricating external trust | Lifecycle cards follow the hero; logos remain blocked |
|
| D-095 | Render operational-coverage proof instead of unsupported customer-logo proof | Approved | Product scope can be shown without fabricating external trust | Lifecycle cards follow the hero; logos remain blocked |
|
||||||
| D-096 | Show KPI definitions and measurement methodology instead of invented outcome numbers | Approved | Evaluation rigor is more credible than fictional percentages | Results section contains no claimed improvement values |
|
| D-096 | Show KPI definitions and measurement methodology instead of invented outcome numbers | Approved | Evaluation rigor is more credible than fictional percentages | Results section contains no claimed improvement values |
|
||||||
| D-097 | Omit the case-study section until an approved customer evidence package exists | Approved | Empty or anonymous proof violates the evidence policy | Conditional insertion point remains after Security and before Pricing |
|
| D-097 | Omit the case-study section until an approved customer evidence package exists | Approved | Empty or anonymous proof violates the evidence policy | Conditional insertion point remains after Security and before Pricing |
|
||||||
| D-098 | Present integration categories without naming partners until connector availability is verified | Approved | Named logos imply working production support | Discovery confirms market, connector, and data ownership |
|
| D-098 | Present integration categories without naming partners until connector availability is verified | Approved | Named logos imply working production support | Discovery confirms market, connector, and data ownership |
|
||||||
| D-099 | Present security as an evidence-request checklist until certifications and reliability claims are approved | Approved | Badges without scope are decoration, not assurance | No certification, uptime, RTO, or SLA claim renders |
|
| D-099 | Present security as an evidence-request checklist until certifications and reliability claims are approved | Approved | Badges without scope are decoration, not assurance | No certification, uptime, RTO, or SLA claim renders |
|
||||||
| D-100 | Use quote-oriented pricing preview until the commercial model is approved | Approved | Invented tiers or prices create false expectations | Page explains scope factors and routes to pricing request |
|
| D-100 | Use quote-oriented pricing preview until the commercial model is approved | Approved | Invented tiers or prices create false expectations | Page explains scope factors and routes to pricing request |
|
||||||
| D-101 | Use a coded, localized, explicitly illustrative product preview | Approved | It proves layout, theme, and RTL behavior without pretending to be production evidence | Replace or approve before launch |
|
| D-101 | Use a coded, localized, explicitly illustrative product preview | Approved | It proves layout, theme, and RTL behavior without pretending to be production evidence | Replace or approve before launch |
|
||||||
| D-102 | Repeat the demo conversion in header, hero, pricing, and final CTA while reserving orange for conversion | Approved | Repetition improves reach without flattening hierarchy | Routine navigation and product emphasis remain blue |
|
| D-102 | Repeat the demo conversion in header, hero, pricing, and final CTA while reserving orange for conversion | Approved | Repetition improves reach without flattening hierarchy | Routine navigation and product emphasis remain blue |
|
||||||
| D-103 | Use a native dialog for the demo form baseline | Approved | Provides a testable accessible interaction with visible labels and error state | Production integration must complete focus, submission, consent, and analytics testing |
|
| D-103 | Use a native dialog for the demo form baseline | Approved | Provides a testable accessible interaction with visible labels and error state | Production integration must complete focus, submission, consent, and analytics testing |
|
||||||
| D-104 | Validate six named production frames plus a 320 px reflow gate | Approved | These frames cover the highest-risk locale, direction, theme, and responsive combinations | Screenshots and automated checks are package deliverables |
|
| D-104 | Validate six named production frames plus a 320 px reflow gate | Approved | These frames cover the highest-risk locale, direction, theme, and responsive combinations | Screenshots and automated checks are package deliverables |
|
||||||
| R-045 | Fill the trust strip with fictional customer logos | Rejected | Placeholder logos quickly become accidental production claims | Omit logos until approved |
|
| R-045 | Fill the trust strip with fictional customer logos | Rejected | Placeholder logos quickly become accidental production claims | Omit logos until approved |
|
||||||
| R-046 | Publish “up to” performance percentages without methodology | Rejected | The qualifier does not rescue unsupported evidence | Publish only after baseline and source approval |
|
| R-046 | Publish “up to” performance percentages without methodology | Rejected | The qualifier does not rescue unsupported evidence | Publish only after baseline and source approval |
|
||||||
| R-047 | Add anonymous testimonial quotes | Rejected | Anonymous praise is not verifiable customer evidence | Use an attributable approved case study or omit |
|
| R-047 | Add anonymous testimonial quotes | Rejected | Anonymous praise is not verifiable customer evidence | Use an attributable approved case study or omit |
|
||||||
| R-048 | Display common integration logos as visual shorthand | Rejected | A familiar logo still implies a connector exists | Show neutral categories until verified |
|
| R-048 | Display common integration logos as visual shorthand | Rejected | A familiar logo still implies a connector exists | Show neutral categories until verified |
|
||||||
| R-049 | Add security badges with “pending” labels to the public page | Rejected | Public users should not see internal evidence placeholders | Keep status in the internal asset and copy registers |
|
| R-049 | Add security badges with “pending” labels to the public page | Rejected | Public users should not see internal evidence placeholders | Keep status in the internal asset and copy registers |
|
||||||
| R-050 | Invent three pricing tiers to make the page look complete | Rejected | Visual completeness is not worth commercial misinformation | Use a quote-oriented preview |
|
| R-050 | Invent three pricing tiers to make the page look complete | Rejected | Visual completeness is not worth commercial misinformation | Use a quote-oriented preview |
|
||||||
| R-051 | Hide unresolved evidence behind vague superlatives | Rejected | “Trusted,” “leading,” and “proven” are still claims | Use concrete scope, workflow, and evaluation language |
|
| R-051 | Hide unresolved evidence behind vague superlatives | Rejected | “Trusted,” “leading,” and “proven” are still claims | Use concrete scope, workflow, and evaluation language |
|
||||||
| R-052 | Make the Arabic frame by mirroring the English screenshot | Rejected | It violates true RTL and corrupts product evidence | Render localized coded UI in document-level RTL |
|
| R-052 | Make the Arabic frame by mirroring the English screenshot | Rejected | It violates true RTL and corrupts product evidence | Render localized coded UI in document-level RTL |
|
||||||
|
|
||||||
| D-105 | Treat Phase 8 as behavior validation, not aesthetic preference collection | Approved | Completion, comprehension, recovery, accessibility, and confidence are actionable; taste polling is not | Script uses task outcomes and neutral probes |
|
| D-105 | Treat Phase 8 as behavior validation, not aesthetic preference collection | Approved | Completion, comprehension, recovery, accessibility, and confidence are actionable; taste polling is not | Script uses task outcomes and neutral probes |
|
||||||
| D-106 | Use a coded local prototype with deterministic query states | Approved | Reproducible states support moderation, accessibility checks, and automation | Demo, tour, locale, theme, navigation, failure, and research modes are addressable |
|
| D-106 | Use a coded local prototype with deterministic query states | Approved | Reproducible states support moderation, accessibility checks, and automation | Demo, tour, locale, theme, navigation, failure, and research modes are addressable |
|
||||||
|
|||||||
@@ -8,13 +8,13 @@
|
|||||||
"files": [
|
"files": [
|
||||||
{
|
{
|
||||||
"path": "09_DEVELOPER_HANDOFF_v1.0.md",
|
"path": "09_DEVELOPER_HANDOFF_v1.0.md",
|
||||||
"bytes": 8091,
|
"bytes": 8378,
|
||||||
"sha256": "e82d486f1e566d1bf0263f1baa7dd8035ae62da5c36bb6bfa417e61331ec8d51"
|
"sha256": "6cb5a76ee42e48caadd672e852520aeed0a974a9e4642be924c6b0b3176e8b1f"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "DECISION_LOG_v9.0.md",
|
"path": "DECISION_LOG_v9.0.md",
|
||||||
"bytes": 38775,
|
"bytes": 63034,
|
||||||
"sha256": "85569a7418f8cad57d094ee9ec1d83b7727b5ab91a405cbda26de0271a8d76a1"
|
"sha256": "8e00d2cfa72b2bad65d855827a0fdfd09cce3e30e9885e46d8f8c6c7feb5f09f"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "DEFINITION_OF_DONE_v1.0.md",
|
"path": "DEFINITION_OF_DONE_v1.0.md",
|
||||||
@@ -108,8 +108,8 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "components/COMPONENT_CONTRACTS_v1.0.md",
|
"path": "components/COMPONENT_CONTRACTS_v1.0.md",
|
||||||
"bytes": 9800,
|
"bytes": 9698,
|
||||||
"sha256": "84b455fe725bff11b6ab54bb13373b1aa52991b062a36fad07db47a940187a53"
|
"sha256": "c36fe8a5221d0f76f0b74894d7227135adb7bae45eca0a7554a31b9433218052"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "components/COMPONENT_STATE_MATRIX_v1.0.csv",
|
"path": "components/COMPONENT_STATE_MATRIX_v1.0.csv",
|
||||||
@@ -138,23 +138,23 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "implementation/component-contracts_v1.0.ts",
|
"path": "implementation/component-contracts_v1.0.ts",
|
||||||
"bytes": 2844,
|
"bytes": 3041,
|
||||||
"sha256": "177dd2420b804290cf1296e43f41a7e759c8859b3253073fbdc523a81f6f94c0"
|
"sha256": "45fb0222d99308b2f28014572316b13c2d7308bc16b1bd02f63b6e2257148399"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "implementation/hreflang-generator_v1.0.ts",
|
"path": "implementation/hreflang-generator_v1.0.ts",
|
||||||
"bytes": 751,
|
"bytes": 799,
|
||||||
"sha256": "ab919a36a5d08a28c38ae51b4ddd0d84cef5d3ec3bed13649ebd26c3fb80ac8a"
|
"sha256": "aa9b5e2eb602b2f651b1195a2d0ff7c2a0e6157bf702d3b27ab25b5b86249ba8"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "implementation/localization-config_v1.0.ts",
|
"path": "implementation/localization-config_v1.0.ts",
|
||||||
"bytes": 772,
|
"bytes": 806,
|
||||||
"sha256": "bd8ca09f121bfe516ece817948a81d58136e9205fbd2c05dad2ce87f2d7a1782"
|
"sha256": "fd884ce4cd807ccdd196bd4c09e34128b3a26df73e0f267c120f270179c852d0"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "implementation/theme-config_v1.0.ts",
|
"path": "implementation/theme-config_v1.0.ts",
|
||||||
"bytes": 725,
|
"bytes": 752,
|
||||||
"sha256": "f56d37da4129d5830ba9ebd77b18cd6084a144e8514bfd8a8152387ecf76607c"
|
"sha256": "5ae9687c6ffa832ae12b70fa8272a61853960eea8a3cc18e3a68ef1f120046f8"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "interaction/ANALYTICS_EVENT_SPEC_v1.0.csv",
|
"path": "interaction/ANALYTICS_EVENT_SPEC_v1.0.csv",
|
||||||
@@ -168,28 +168,28 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "interaction/analytics-contract_v1.0.ts",
|
"path": "interaction/analytics-contract_v1.0.ts",
|
||||||
"bytes": 832,
|
"bytes": 918,
|
||||||
"sha256": "2716eb69cc98032005e068ab7bdc265a4c896a8e1a1bed682c7fdd4f45368123"
|
"sha256": "d79b32503a5f8346816c647b8a7f45a62468b96f4fc9525d6d5147b5d2f2ec45"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "interaction/demo-form-contract_v1.0.ts",
|
"path": "interaction/demo-form-contract_v1.0.ts",
|
||||||
"bytes": 632,
|
"bytes": 699,
|
||||||
"sha256": "194db77f7cfca417dbe35e6f2c2ea94cdd02806b83b1d0f1993fe509d8c10154"
|
"sha256": "073cf70224f93312d177f77abd7987704205628209e30c44824ae45f929ee59e"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "interaction/theme-bootstrap_v1.0.js",
|
"path": "interaction/theme-bootstrap_v1.0.js",
|
||||||
"bytes": 753,
|
"bytes": 757,
|
||||||
"sha256": "7b89a849bdcd72e1049714ebf76f0c0decb39e44261480f0cb08bb7dfe734e4a"
|
"sha256": "a90b31cc2b91fd085b8cd4442b85fce56127501602b9b7f90af75fd3c8f4c3b0"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "localization/LOCALIZATION_IMPLEMENTATION_v1.0.md",
|
"path": "localization/LOCALIZATION_IMPLEMENTATION_v1.0.md",
|
||||||
"bytes": 2062,
|
"bytes": 2069,
|
||||||
"sha256": "87a494d7ef7ed9f5012ed1699edf90e38995aa89a0efc3800db854e1d57e1024"
|
"sha256": "63773e2f4777a6a322570d07d8badac8867dc4d6eef2d71bf05cbf81dfd2c85b"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "localization/ROUTE_MANIFEST_v1.0.json",
|
"path": "localization/ROUTE_MANIFEST_v1.0.json",
|
||||||
"bytes": 1573,
|
"bytes": 1433,
|
||||||
"sha256": "fa29b8d31da1f36f87d216a1228fd1bbe1cde337636950b40b0024a10efaf49a"
|
"sha256": "e08ac94ba1f3899bb02dfa934695f530d1089f9f03b0fadaa0feabd466ad8ec4"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "localization/TRANSLATION_KEY_INVENTORY_v1.0.csv",
|
"path": "localization/TRANSLATION_KEY_INVENTORY_v1.0.csv",
|
||||||
@@ -198,18 +198,18 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "localization/locales/ar/homepage.json",
|
"path": "localization/locales/ar/homepage.json",
|
||||||
"bytes": 18615,
|
"bytes": 17419,
|
||||||
"sha256": "fe08aabd60203dbdebc718117f5e497b52a453a8a6fb671fe0bca28651bf0d47"
|
"sha256": "19c1b9d0c90c017da1679b2cde4f123a98625ab6dae8002f58354839309f0474"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "localization/locales/en/homepage.json",
|
"path": "localization/locales/en/homepage.json",
|
||||||
"bytes": 13869,
|
"bytes": 12769,
|
||||||
"sha256": "a5ed956c7f07fad016ca39b40b770c25e8beeae2799ff0fece82dbf087f18e51"
|
"sha256": "ba88daa108e3df63285327d0f7d7be6c0df3d971d800d48617e1e62c3414acfb"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "localization/locales/fr/homepage.json",
|
"path": "localization/locales/fr/homepage.json",
|
||||||
"bytes": 15512,
|
"bytes": 14564,
|
||||||
"sha256": "24079356bd47431ffeb66b7eb2981e108c03080205861b1bb42514b03b945a0a"
|
"sha256": "5a3dfe1dc43c99a30cfc275309b5084b5d9f583a33ea136d388babb0714de719"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "qa/ACCESSIBILITY_CHECKLIST_v1.0.csv",
|
"path": "qa/ACCESSIBILITY_CHECKLIST_v1.0.csv",
|
||||||
@@ -243,18 +243,18 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "qa/validate-phase9-package_v1.0.mjs",
|
"path": "qa/validate-phase9-package_v1.0.mjs",
|
||||||
"bytes": 2534,
|
"bytes": 2766,
|
||||||
"sha256": "631892318cf95c0530a605e5bf08def9b06c93af79b555205f3aeb8a2bcd87ed"
|
"sha256": "8bd42770f659b51e63d73b282d7d101d6d817650f8c9b8875cad1b0eb3eece4e"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "qa/visual-regression-scenarios_v1.0.json",
|
"path": "qa/visual-regression-scenarios_v1.0.json",
|
||||||
"bytes": 1401,
|
"bytes": 1209,
|
||||||
"sha256": "08a04f676cbb93e73b4c6bc112a55ebf287cb97be3dd3176ab80161e6de743c2"
|
"sha256": "27d961c33c32775485475f7037b8d3bbd13f634b65fe2f59d6eea898860edf5f"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "reference/FINDINGS_REPORT_v1.0.md",
|
"path": "reference/FINDINGS_REPORT_v1.0.md",
|
||||||
"bytes": 3062,
|
"bytes": 4135,
|
||||||
"sha256": "ba34afd535856d0eaa8ed7e174c23a7b5f0eae87344c8fcee5daafe00c068ce4"
|
"sha256": "20eaba0c9fafd166220a64b2f0ede3c36b3bd9fc6f32982c1f6c7745f9311965"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "reference/PHASE_8_HANDOFF_v1.0.md",
|
"path": "reference/PHASE_8_HANDOFF_v1.0.md",
|
||||||
@@ -288,13 +288,13 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "tokens/semantic-tokens_v1.0.css",
|
"path": "tokens/semantic-tokens_v1.0.css",
|
||||||
"bytes": 3553,
|
"bytes": 3589,
|
||||||
"sha256": "99912762e078bfc78b4878fc46b8c7d03d90c073e9c236942a58d83f6585a1b9"
|
"sha256": "f28b2f03139a6aa89b85d00574f4fe56aa90d4ca1e03e80a1fb6eb8e5d9fa621"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "tokens/tokens_v1.0.ts",
|
"path": "tokens/tokens_v1.0.ts",
|
||||||
"bytes": 442,
|
"bytes": 442,
|
||||||
"sha256": "ec265270251f8dd01d9166aeca3fcec9be2b5e534d825c9058fb6ed3f51e3224"
|
"sha256": "3a1a2bb1bd29e55bf024a468a7c95bf2ec29d3cdc479f580050e8503c19c40a7"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "validation/CONTENT_AND_STRUCTURE_VALIDATION_v1.0.json",
|
"path": "validation/CONTENT_AND_STRUCTURE_VALIDATION_v1.0.json",
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
# Component Contracts
|
# Component Contracts
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Every component below is implemented once for all locales, directions, and themes. Props are semantic; components do not fetch translations internally unless the architecture establishes a single typed content boundary.
|
Every component below is implemented once for all locales, directions, and themes. Props are semantic; components do not fetch translations internally unless the architecture establishes a single typed content boundary.
|
||||||
|
|
||||||
|
|
||||||
## SiteHeader
|
## SiteHeader
|
||||||
|
|
||||||
**Purpose:** Primary navigation, brand, locale/theme controls, login, demo CTA
|
**Purpose:** Primary navigation, brand, locale/theme controls, login, demo CTA
|
||||||
@@ -23,7 +20,6 @@ Every component below is implemented once for all locales, directions, and theme
|
|||||||
|
|
||||||
**Content rule:** Block size is content-driven. Unsupported evidence suppresses the component or uses the explicitly approved neutral variant.
|
**Content rule:** Block size is content-driven. Unsupported evidence suppresses the component or uses the explicitly approved neutral variant.
|
||||||
|
|
||||||
|
|
||||||
## MobileDrawer
|
## MobileDrawer
|
||||||
|
|
||||||
**Purpose:** Compact/medium navigation overlay
|
**Purpose:** Compact/medium navigation overlay
|
||||||
@@ -42,7 +38,6 @@ Every component below is implemented once for all locales, directions, and theme
|
|||||||
|
|
||||||
**Content rule:** Block size is content-driven. Unsupported evidence suppresses the component or uses the explicitly approved neutral variant.
|
**Content rule:** Block size is content-driven. Unsupported evidence suppresses the component or uses the explicitly approved neutral variant.
|
||||||
|
|
||||||
|
|
||||||
## LocaleSelector
|
## LocaleSelector
|
||||||
|
|
||||||
**Purpose:** Switch locale by stable route ID
|
**Purpose:** Switch locale by stable route ID
|
||||||
@@ -61,7 +56,6 @@ Every component below is implemented once for all locales, directions, and theme
|
|||||||
|
|
||||||
**Content rule:** Block size is content-driven. Unsupported evidence suppresses the component or uses the explicitly approved neutral variant.
|
**Content rule:** Block size is content-driven. Unsupported evidence suppresses the component or uses the explicitly approved neutral variant.
|
||||||
|
|
||||||
|
|
||||||
## ThemeSelector
|
## ThemeSelector
|
||||||
|
|
||||||
**Purpose:** Set Light/Dark/System preference
|
**Purpose:** Set Light/Dark/System preference
|
||||||
@@ -80,7 +74,6 @@ Every component below is implemented once for all locales, directions, and theme
|
|||||||
|
|
||||||
**Content rule:** Block size is content-driven. Unsupported evidence suppresses the component or uses the explicitly approved neutral variant.
|
**Content rule:** Block size is content-driven. Unsupported evidence suppresses the component or uses the explicitly approved neutral variant.
|
||||||
|
|
||||||
|
|
||||||
## HeroSection
|
## HeroSection
|
||||||
|
|
||||||
**Purpose:** Communicate category, promise, primary and secondary actions
|
**Purpose:** Communicate category, promise, primary and secondary actions
|
||||||
@@ -99,7 +92,6 @@ Every component below is implemented once for all locales, directions, and theme
|
|||||||
|
|
||||||
**Content rule:** Block size is content-driven. Unsupported evidence suppresses the component or uses the explicitly approved neutral variant.
|
**Content rule:** Block size is content-driven. Unsupported evidence suppresses the component or uses the explicitly approved neutral variant.
|
||||||
|
|
||||||
|
|
||||||
## ProductPreview
|
## ProductPreview
|
||||||
|
|
||||||
**Purpose:** Illustrative/evidence-governed product visual
|
**Purpose:** Illustrative/evidence-governed product visual
|
||||||
@@ -118,7 +110,6 @@ Every component below is implemented once for all locales, directions, and theme
|
|||||||
|
|
||||||
**Content rule:** Block size is content-driven. Unsupported evidence suppresses the component or uses the explicitly approved neutral variant.
|
**Content rule:** Block size is content-driven. Unsupported evidence suppresses the component or uses the explicitly approved neutral variant.
|
||||||
|
|
||||||
|
|
||||||
## LifecycleProof
|
## LifecycleProof
|
||||||
|
|
||||||
**Purpose:** Show operational coverage without customer logos
|
**Purpose:** Show operational coverage without customer logos
|
||||||
@@ -137,7 +128,6 @@ Every component below is implemented once for all locales, directions, and theme
|
|||||||
|
|
||||||
**Content rule:** Block size is content-driven. Unsupported evidence suppresses the component or uses the explicitly approved neutral variant.
|
**Content rule:** Block size is content-driven. Unsupported evidence suppresses the component or uses the explicitly approved neutral variant.
|
||||||
|
|
||||||
|
|
||||||
## ComparisonSection
|
## ComparisonSection
|
||||||
|
|
||||||
**Purpose:** Fragmented versus connected workflow
|
**Purpose:** Fragmented versus connected workflow
|
||||||
@@ -156,7 +146,6 @@ Every component below is implemented once for all locales, directions, and theme
|
|||||||
|
|
||||||
**Content rule:** Block size is content-driven. Unsupported evidence suppresses the component or uses the explicitly approved neutral variant.
|
**Content rule:** Block size is content-driven. Unsupported evidence suppresses the component or uses the explicitly approved neutral variant.
|
||||||
|
|
||||||
|
|
||||||
## WorkflowSteps
|
## WorkflowSteps
|
||||||
|
|
||||||
**Purpose:** Four-step rental flow and tour source
|
**Purpose:** Four-step rental flow and tour source
|
||||||
@@ -175,7 +164,6 @@ Every component below is implemented once for all locales, directions, and theme
|
|||||||
|
|
||||||
**Content rule:** Block size is content-driven. Unsupported evidence suppresses the component or uses the explicitly approved neutral variant.
|
**Content rule:** Block size is content-driven. Unsupported evidence suppresses the component or uses the explicitly approved neutral variant.
|
||||||
|
|
||||||
|
|
||||||
## RoleCards
|
## RoleCards
|
||||||
|
|
||||||
**Purpose:** Explain value by operating role
|
**Purpose:** Explain value by operating role
|
||||||
@@ -194,7 +182,6 @@ Every component below is implemented once for all locales, directions, and theme
|
|||||||
|
|
||||||
**Content rule:** Block size is content-driven. Unsupported evidence suppresses the component or uses the explicitly approved neutral variant.
|
**Content rule:** Block size is content-driven. Unsupported evidence suppresses the component or uses the explicitly approved neutral variant.
|
||||||
|
|
||||||
|
|
||||||
## ModuleGrid
|
## ModuleGrid
|
||||||
|
|
||||||
**Purpose:** Describe approved product modules
|
**Purpose:** Describe approved product modules
|
||||||
@@ -213,7 +200,6 @@ Every component below is implemented once for all locales, directions, and theme
|
|||||||
|
|
||||||
**Content rule:** Block size is content-driven. Unsupported evidence suppresses the component or uses the explicitly approved neutral variant.
|
**Content rule:** Block size is content-driven. Unsupported evidence suppresses the component or uses the explicitly approved neutral variant.
|
||||||
|
|
||||||
|
|
||||||
## MeasurementPlan
|
## MeasurementPlan
|
||||||
|
|
||||||
**Purpose:** Show KPI definitions without invented outcomes
|
**Purpose:** Show KPI definitions without invented outcomes
|
||||||
@@ -232,7 +218,6 @@ Every component below is implemented once for all locales, directions, and theme
|
|||||||
|
|
||||||
**Content rule:** Block size is content-driven. Unsupported evidence suppresses the component or uses the explicitly approved neutral variant.
|
**Content rule:** Block size is content-driven. Unsupported evidence suppresses the component or uses the explicitly approved neutral variant.
|
||||||
|
|
||||||
|
|
||||||
## IntegrationCategories
|
## IntegrationCategories
|
||||||
|
|
||||||
**Purpose:** Neutral category list
|
**Purpose:** Neutral category list
|
||||||
@@ -251,7 +236,6 @@ Every component below is implemented once for all locales, directions, and theme
|
|||||||
|
|
||||||
**Content rule:** Block size is content-driven. Unsupported evidence suppresses the component or uses the explicitly approved neutral variant.
|
**Content rule:** Block size is content-driven. Unsupported evidence suppresses the component or uses the explicitly approved neutral variant.
|
||||||
|
|
||||||
|
|
||||||
## SecurityChecklist
|
## SecurityChecklist
|
||||||
|
|
||||||
**Purpose:** Evidence-request checklist
|
**Purpose:** Evidence-request checklist
|
||||||
@@ -270,7 +254,6 @@ Every component below is implemented once for all locales, directions, and theme
|
|||||||
|
|
||||||
**Content rule:** Block size is content-driven. Unsupported evidence suppresses the component or uses the explicitly approved neutral variant.
|
**Content rule:** Block size is content-driven. Unsupported evidence suppresses the component or uses the explicitly approved neutral variant.
|
||||||
|
|
||||||
|
|
||||||
## PricingPreview
|
## PricingPreview
|
||||||
|
|
||||||
**Purpose:** Explain pricing factors and demo route
|
**Purpose:** Explain pricing factors and demo route
|
||||||
@@ -289,7 +272,6 @@ Every component below is implemented once for all locales, directions, and theme
|
|||||||
|
|
||||||
**Content rule:** Block size is content-driven. Unsupported evidence suppresses the component or uses the explicitly approved neutral variant.
|
**Content rule:** Block size is content-driven. Unsupported evidence suppresses the component or uses the explicitly approved neutral variant.
|
||||||
|
|
||||||
|
|
||||||
## FAQAccordion
|
## FAQAccordion
|
||||||
|
|
||||||
**Purpose:** Localized questions and answers
|
**Purpose:** Localized questions and answers
|
||||||
@@ -308,7 +290,6 @@ Every component below is implemented once for all locales, directions, and theme
|
|||||||
|
|
||||||
**Content rule:** Block size is content-driven. Unsupported evidence suppresses the component or uses the explicitly approved neutral variant.
|
**Content rule:** Block size is content-driven. Unsupported evidence suppresses the component or uses the explicitly approved neutral variant.
|
||||||
|
|
||||||
|
|
||||||
## FinalCTA
|
## FinalCTA
|
||||||
|
|
||||||
**Purpose:** Final conversion block and approved secondary contact
|
**Purpose:** Final conversion block and approved secondary contact
|
||||||
@@ -327,7 +308,6 @@ Every component below is implemented once for all locales, directions, and theme
|
|||||||
|
|
||||||
**Content rule:** Block size is content-driven. Unsupported evidence suppresses the component or uses the explicitly approved neutral variant.
|
**Content rule:** Block size is content-driven. Unsupported evidence suppresses the component or uses the explicitly approved neutral variant.
|
||||||
|
|
||||||
|
|
||||||
## SiteFooter
|
## SiteFooter
|
||||||
|
|
||||||
**Purpose:** Localized navigation, legal, evidence status internal only
|
**Purpose:** Localized navigation, legal, evidence status internal only
|
||||||
@@ -346,7 +326,6 @@ Every component below is implemented once for all locales, directions, and theme
|
|||||||
|
|
||||||
**Content rule:** Block size is content-driven. Unsupported evidence suppresses the component or uses the explicitly approved neutral variant.
|
**Content rule:** Block size is content-driven. Unsupported evidence suppresses the component or uses the explicitly approved neutral variant.
|
||||||
|
|
||||||
|
|
||||||
## DemoDialog
|
## DemoDialog
|
||||||
|
|
||||||
**Purpose:** Qualification form and submission states
|
**Purpose:** Qualification form and submission states
|
||||||
@@ -365,7 +344,6 @@ Every component below is implemented once for all locales, directions, and theme
|
|||||||
|
|
||||||
**Content rule:** Block size is content-driven. Unsupported evidence suppresses the component or uses the explicitly approved neutral variant.
|
**Content rule:** Block size is content-driven. Unsupported evidence suppresses the component or uses the explicitly approved neutral variant.
|
||||||
|
|
||||||
|
|
||||||
## ProductTourDialog
|
## ProductTourDialog
|
||||||
|
|
||||||
**Purpose:** Four-step guided overview
|
**Purpose:** Four-step guided overview
|
||||||
|
|||||||
@@ -1,26 +1,136 @@
|
|||||||
export type Locale = "en" | "fr" | "ar";
|
export type Locale = 'en' | 'fr' | 'ar';
|
||||||
export type Direction = "ltr" | "rtl";
|
export type Direction = 'ltr' | 'rtl';
|
||||||
export type EvidenceStatus = "approved" | "illustrative" | "pending" | "expired" | "rejected";
|
export type EvidenceStatus = 'approved' | 'illustrative' | 'pending' | 'expired' | 'rejected';
|
||||||
export type ThemePreference = "light" | "dark" | "system";
|
export type ThemePreference = 'light' | 'dark' | 'system';
|
||||||
|
|
||||||
export interface LinkModel { id: string; label: string; href: string; external?: boolean; }
|
export interface LinkModel {
|
||||||
export interface SiteHeaderProps { locale: Locale; direction: Direction; links: LinkModel[]; login?: LinkModel; onDemo(): void; }
|
id: string;
|
||||||
export interface DrawerProps extends SiteHeaderProps { open: boolean; onOpenChange(open: boolean): void; returnFocusRef?: HTMLElement | null; }
|
label: string;
|
||||||
export interface LocaleSelectorProps { locale: Locale; routeId: string; hash?: string; onLocaleChange(locale: Locale): void; }
|
href: string;
|
||||||
export interface ThemeSelectorProps { preference: ThemePreference; onPreferenceChange(value: ThemePreference): void; }
|
external?: boolean;
|
||||||
export interface HeroModel { eyebrow: string; title: string; body: string; primaryLabel: string; secondaryLabel: string; }
|
}
|
||||||
export interface ProductPreviewModel { status: EvidenceStatus; caption: string; locale: Locale; direction: Direction; theme: "light"|"dark"; assetId?: string; }
|
export interface SiteHeaderProps {
|
||||||
export interface LifecycleItem { id: string; title: string; description: string; icon: string; }
|
locale: Locale;
|
||||||
export interface ComparisonModel { title: string; body: string; before: { title: string; items: string[] }; after: { title: string; items: string[] }; }
|
direction: Direction;
|
||||||
export interface WorkflowStep { id: string; ordinal: string; title: string; description: string; }
|
links: LinkModel[];
|
||||||
export interface RoleCard { id: string; title: string; description: string; }
|
login?: LinkModel;
|
||||||
export interface ModuleCard { id: string; title: string; description: string; evidenceStatus: EvidenceStatus; }
|
onDemo(): void;
|
||||||
export interface MetricDefinition { id: string; title: string; definition: string; publishedValue?: never; }
|
}
|
||||||
export interface IntegrationCategory { id: string; label: string; verifiedPartners?: never; }
|
export interface DrawerProps extends SiteHeaderProps {
|
||||||
export interface SecurityCheck { id: string; title: string; description: string; certificationBadge?: never; }
|
open: boolean;
|
||||||
export interface PricingModel { mode: "quote-only" | "approved-pricing"; title: string; body: string; factors: string[]; note: string; }
|
onOpenChange(open: boolean): void;
|
||||||
export interface FaqItem { id: string; question: string; answer: string; }
|
returnFocusRef?: HTMLElement | null;
|
||||||
export interface DemoFormValues { fullName: string; workEmail: string; company: string; fleetSize: string; market?: string; preferredLanguage?: Locale; message?: string; }
|
}
|
||||||
export type DemoFormState = "idle"|"validating"|"submitting"|"success"|"recoverable_error"|"terminal_error";
|
export interface LocaleSelectorProps {
|
||||||
export interface DemoFormProps { state: DemoFormState; values: DemoFormValues; fieldErrors: Partial<Record<keyof DemoFormValues,string>>; formError?: string; onSubmit(values: DemoFormValues): Promise<void>; }
|
locale: Locale;
|
||||||
export interface TourProps { steps: WorkflowStep[]; activeIndex: number; open: boolean; onOpenChange(open:boolean): void; onStepChange(index:number): void; }
|
routeId: string;
|
||||||
|
hash?: string;
|
||||||
|
onLocaleChange(locale: Locale): void;
|
||||||
|
}
|
||||||
|
export interface ThemeSelectorProps {
|
||||||
|
preference: ThemePreference;
|
||||||
|
onPreferenceChange(value: ThemePreference): void;
|
||||||
|
}
|
||||||
|
export interface HeroModel {
|
||||||
|
eyebrow: string;
|
||||||
|
title: string;
|
||||||
|
body: string;
|
||||||
|
primaryLabel: string;
|
||||||
|
secondaryLabel: string;
|
||||||
|
}
|
||||||
|
export interface ProductPreviewModel {
|
||||||
|
status: EvidenceStatus;
|
||||||
|
caption: string;
|
||||||
|
locale: Locale;
|
||||||
|
direction: Direction;
|
||||||
|
theme: 'light' | 'dark';
|
||||||
|
assetId?: string;
|
||||||
|
}
|
||||||
|
export interface LifecycleItem {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
icon: string;
|
||||||
|
}
|
||||||
|
export interface ComparisonModel {
|
||||||
|
title: string;
|
||||||
|
body: string;
|
||||||
|
before: { title: string; items: string[] };
|
||||||
|
after: { title: string; items: string[] };
|
||||||
|
}
|
||||||
|
export interface WorkflowStep {
|
||||||
|
id: string;
|
||||||
|
ordinal: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
}
|
||||||
|
export interface RoleCard {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
}
|
||||||
|
export interface ModuleCard {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
evidenceStatus: EvidenceStatus;
|
||||||
|
}
|
||||||
|
export interface MetricDefinition {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
definition: string;
|
||||||
|
publishedValue?: never;
|
||||||
|
}
|
||||||
|
export interface IntegrationCategory {
|
||||||
|
id: string;
|
||||||
|
label: string;
|
||||||
|
verifiedPartners?: never;
|
||||||
|
}
|
||||||
|
export interface SecurityCheck {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
certificationBadge?: never;
|
||||||
|
}
|
||||||
|
export interface PricingModel {
|
||||||
|
mode: 'quote-only' | 'approved-pricing';
|
||||||
|
title: string;
|
||||||
|
body: string;
|
||||||
|
factors: string[];
|
||||||
|
note: string;
|
||||||
|
}
|
||||||
|
export interface FaqItem {
|
||||||
|
id: string;
|
||||||
|
question: string;
|
||||||
|
answer: string;
|
||||||
|
}
|
||||||
|
export interface DemoFormValues {
|
||||||
|
fullName: string;
|
||||||
|
workEmail: string;
|
||||||
|
company: string;
|
||||||
|
fleetSize: string;
|
||||||
|
market?: string;
|
||||||
|
preferredLanguage?: Locale;
|
||||||
|
message?: string;
|
||||||
|
}
|
||||||
|
export type DemoFormState =
|
||||||
|
| 'idle'
|
||||||
|
| 'validating'
|
||||||
|
| 'submitting'
|
||||||
|
| 'success'
|
||||||
|
| 'recoverable_error'
|
||||||
|
| 'terminal_error';
|
||||||
|
export interface DemoFormProps {
|
||||||
|
state: DemoFormState;
|
||||||
|
values: DemoFormValues;
|
||||||
|
fieldErrors: Partial<Record<keyof DemoFormValues, string>>;
|
||||||
|
formError?: string;
|
||||||
|
onSubmit(values: DemoFormValues): Promise<void>;
|
||||||
|
}
|
||||||
|
export interface TourProps {
|
||||||
|
steps: WorkflowStep[];
|
||||||
|
activeIndex: number;
|
||||||
|
open: boolean;
|
||||||
|
onOpenChange(open: boolean): void;
|
||||||
|
onStepChange(index: number): void;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,13 +1,21 @@
|
|||||||
import manifest from "../localization/ROUTE_MANIFEST_v1.0.json";
|
import manifest from '../localization/ROUTE_MANIFEST_v1.0.json';
|
||||||
type Locale = "en"|"fr"|"ar";
|
type Locale = 'en' | 'fr' | 'ar';
|
||||||
export function localizedPath(routeId:string, locale:Locale):string {
|
export function localizedPath(routeId: string, locale: Locale): string {
|
||||||
const route = manifest.routes.find((item) => item.id === routeId);
|
const route = manifest.routes.find((item) => item.id === routeId);
|
||||||
if (!route) throw new Error(`Unknown route: ${routeId}`);
|
if (!route) throw new Error(`Unknown route: ${routeId}`);
|
||||||
const slug = route.slugs[locale];
|
const slug = route.slugs[locale];
|
||||||
return slug ? `/${locale}/${slug}` : `/${locale}`;
|
return slug ? `/${locale}/${slug}` : `/${locale}`;
|
||||||
}
|
}
|
||||||
export function hreflangLinks(origin:string, routeId:string) {
|
export function hreflangLinks(origin: string, routeId: string) {
|
||||||
const alternates = manifest.locales.map((locale) => ({ rel:"alternate", hreflang:locale, href:new URL(localizedPath(routeId, locale as Locale),origin).href }));
|
const alternates = manifest.locales.map((locale) => ({
|
||||||
alternates.push({ rel:"alternate", hreflang:"x-default", href:new URL(localizedPath(routeId,"en"),origin).href });
|
rel: 'alternate',
|
||||||
|
hreflang: locale,
|
||||||
|
href: new URL(localizedPath(routeId, locale as Locale), origin).href,
|
||||||
|
}));
|
||||||
|
alternates.push({
|
||||||
|
rel: 'alternate',
|
||||||
|
hreflang: 'x-default',
|
||||||
|
href: new URL(localizedPath(routeId, 'en'), origin).href,
|
||||||
|
});
|
||||||
return alternates;
|
return alternates;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,20 @@
|
|||||||
export const locales = ["en", "fr", "ar"] as const;
|
export const locales = ['en', 'fr', 'ar'] as const;
|
||||||
export type Locale = typeof locales[number];
|
export type Locale = (typeof locales)[number];
|
||||||
export const defaultLocale: Locale = "en";
|
export const defaultLocale: Locale = 'en';
|
||||||
export const directionByLocale: Record<Locale,"ltr"|"rtl"> = { en:"ltr", fr:"ltr", ar:"rtl" };
|
export const directionByLocale: Record<Locale, 'ltr' | 'rtl'> = { en: 'ltr', fr: 'ltr', ar: 'rtl' };
|
||||||
export const localeCookie = "hpc-locale";
|
export const localeCookie = 'hpc-locale';
|
||||||
export const localeStorageKey = "hpc.locale";
|
export const localeStorageKey = 'hpc.locale';
|
||||||
export const campaignQueryAllowlist = ["utm_source","utm_medium","utm_campaign","utm_content","gclid"] as const;
|
export const campaignQueryAllowlist = [
|
||||||
export const rootDetectionOrder = ["cookie","accept-language","default-en"] as const;
|
'utm_source',
|
||||||
export function isLocale(value: unknown): value is Locale { return typeof value === "string" && (locales as readonly string[]).includes(value); }
|
'utm_medium',
|
||||||
export function isolateLtr(value: string): { dir:"ltr"; value:string } { return { dir:"ltr", value }; }
|
'utm_campaign',
|
||||||
|
'utm_content',
|
||||||
|
'gclid',
|
||||||
|
] as const;
|
||||||
|
export const rootDetectionOrder = ['cookie', 'accept-language', 'default-en'] as const;
|
||||||
|
export function isLocale(value: unknown): value is Locale {
|
||||||
|
return typeof value === 'string' && (locales as readonly string[]).includes(value);
|
||||||
|
}
|
||||||
|
export function isolateLtr(value: string): { dir: 'ltr'; value: string } {
|
||||||
|
return { dir: 'ltr', value };
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,8 +1,17 @@
|
|||||||
export const themeCookie = "hpc-theme";
|
export const themeCookie = 'hpc-theme';
|
||||||
export const themeStorageKey = "hpc.theme.preference";
|
export const themeStorageKey = 'hpc.theme.preference';
|
||||||
export const themePreferences = ["light","dark","system"] as const;
|
export const themePreferences = ['light', 'dark', 'system'] as const;
|
||||||
export type ThemePreference = typeof themePreferences[number];
|
export type ThemePreference = (typeof themePreferences)[number];
|
||||||
export type ResolvedTheme = "light"|"dark";
|
export type ResolvedTheme = 'light' | 'dark';
|
||||||
export function isThemePreference(v:unknown): v is ThemePreference { return typeof v === "string" && (themePreferences as readonly string[]).includes(v); }
|
export function isThemePreference(v: unknown): v is ThemePreference {
|
||||||
export function resolveTheme(preference:ThemePreference, systemDark:boolean):ResolvedTheme { return preference === "system" ? (systemDark ? "dark" : "light") : preference; }
|
return typeof v === 'string' && (themePreferences as readonly string[]).includes(v);
|
||||||
export const themeCookieAttributes = { path:"/", sameSite:"lax" as const, maxAgeSeconds:31536000, secureInProduction:true };
|
}
|
||||||
|
export function resolveTheme(preference: ThemePreference, systemDark: boolean): ResolvedTheme {
|
||||||
|
return preference === 'system' ? (systemDark ? 'dark' : 'light') : preference;
|
||||||
|
}
|
||||||
|
export const themeCookieAttributes = {
|
||||||
|
path: '/',
|
||||||
|
sameSite: 'lax' as const,
|
||||||
|
maxAgeSeconds: 31536000,
|
||||||
|
secureInProduction: true,
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,9 +1,41 @@
|
|||||||
export type AnalyticsEventName =
|
export type AnalyticsEventName =
|
||||||
| "page_view" | "section_view" | "navigation_click"
|
| 'page_view'
|
||||||
| "locale_change" | "theme_change" | "mobile_nav_open" | "mobile_nav_close"
|
| 'section_view'
|
||||||
| "product_tour_open" | "product_tour_step" | "product_tour_complete" | "product_tour_close"
|
| 'navigation_click'
|
||||||
| "demo_open" | "demo_submit_attempt" | "demo_validation_error" | "demo_submit_success" | "demo_submit_failure" | "demo_close"
|
| 'locale_change'
|
||||||
| "faq_open";
|
| 'theme_change'
|
||||||
export interface AnalyticsContext { routeId:string; locale:"en"|"fr"|"ar"; direction:"ltr"|"rtl"; resolvedTheme:"light"|"dark"; source?:string; step?:number; errorCode?:string; }
|
| 'mobile_nav_open'
|
||||||
export interface AnalyticsAdapter { consentGranted():boolean; track(name:AnalyticsEventName, context:AnalyticsContext):void; }
|
| 'mobile_nav_close'
|
||||||
export const forbiddenPayloadKeys = ["name","email","company","message","phone","fieldValue","serverResponse"] as const;
|
| 'product_tour_open'
|
||||||
|
| 'product_tour_step'
|
||||||
|
| 'product_tour_complete'
|
||||||
|
| 'product_tour_close'
|
||||||
|
| 'demo_open'
|
||||||
|
| 'demo_submit_attempt'
|
||||||
|
| 'demo_validation_error'
|
||||||
|
| 'demo_submit_success'
|
||||||
|
| 'demo_submit_failure'
|
||||||
|
| 'demo_close'
|
||||||
|
| 'faq_open';
|
||||||
|
export interface AnalyticsContext {
|
||||||
|
routeId: string;
|
||||||
|
locale: 'en' | 'fr' | 'ar';
|
||||||
|
direction: 'ltr' | 'rtl';
|
||||||
|
resolvedTheme: 'light' | 'dark';
|
||||||
|
source?: string;
|
||||||
|
step?: number;
|
||||||
|
errorCode?: string;
|
||||||
|
}
|
||||||
|
export interface AnalyticsAdapter {
|
||||||
|
consentGranted(): boolean;
|
||||||
|
track(name: AnalyticsEventName, context: AnalyticsContext): void;
|
||||||
|
}
|
||||||
|
export const forbiddenPayloadKeys = [
|
||||||
|
'name',
|
||||||
|
'email',
|
||||||
|
'company',
|
||||||
|
'message',
|
||||||
|
'phone',
|
||||||
|
'fieldValue',
|
||||||
|
'serverResponse',
|
||||||
|
] as const;
|
||||||
|
|||||||
@@ -1,5 +1,23 @@
|
|||||||
export const fleetSizeValues = ["1-9","10-24","25-49","50-99","100-249","250+"] as const;
|
export const fleetSizeValues = ['1-9', '10-24', '25-49', '50-99', '100-249', '250+'] as const;
|
||||||
export type FleetSize = typeof fleetSizeValues[number];
|
export type FleetSize = (typeof fleetSizeValues)[number];
|
||||||
export interface DemoRequest { fullName:string; workEmail:string; company:string; fleetSize:FleetSize; market?:string; preferredLanguage?:"en"|"fr"|"ar"; message?:string; idempotencyKey:string; source:string; }
|
export interface DemoRequest {
|
||||||
export interface DemoSuccess { requestId:string; acceptedAt:string; nextStepCode:"sales_followup"|"manual_review"; }
|
fullName: string;
|
||||||
export interface DemoFailure { code:"validation"|"rate_limited"|"duplicate"|"unavailable"|"unknown"; retryable:boolean; fieldErrors?:Record<string,string>; }
|
workEmail: string;
|
||||||
|
company: string;
|
||||||
|
fleetSize: FleetSize;
|
||||||
|
market?: string;
|
||||||
|
preferredLanguage?: 'en' | 'fr' | 'ar';
|
||||||
|
message?: string;
|
||||||
|
idempotencyKey: string;
|
||||||
|
source: string;
|
||||||
|
}
|
||||||
|
export interface DemoSuccess {
|
||||||
|
requestId: string;
|
||||||
|
acceptedAt: string;
|
||||||
|
nextStepCode: 'sales_followup' | 'manual_review';
|
||||||
|
}
|
||||||
|
export interface DemoFailure {
|
||||||
|
code: 'validation' | 'rate_limited' | 'duplicate' | 'unavailable' | 'unknown';
|
||||||
|
retryable: boolean;
|
||||||
|
fieldErrors?: Record<string, string>;
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,10 +3,12 @@
|
|||||||
var cookieMatch = document.cookie.match(/(?:^|; )hpc-theme=([^;]+)/);
|
var cookieMatch = document.cookie.match(/(?:^|; )hpc-theme=([^;]+)/);
|
||||||
var cookieValue = cookieMatch ? decodeURIComponent(cookieMatch[1]) : null;
|
var cookieValue = cookieMatch ? decodeURIComponent(cookieMatch[1]) : null;
|
||||||
var stored = null;
|
var stored = null;
|
||||||
try { stored = localStorage.getItem("hpc.theme.preference"); } catch (_) {}
|
try {
|
||||||
var preference = allowed[cookieValue] ? cookieValue : (allowed[stored] ? stored : "system");
|
stored = localStorage.getItem('hpc.theme.preference');
|
||||||
var dark = window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches;
|
} catch (_) {}
|
||||||
var resolved = preference === "system" ? (dark ? "dark" : "light") : preference;
|
var preference = allowed[cookieValue] ? cookieValue : allowed[stored] ? stored : 'system';
|
||||||
|
var dark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||||
|
var resolved = preference === 'system' ? (dark ? 'dark' : 'light') : preference;
|
||||||
var root = document.documentElement;
|
var root = document.documentElement;
|
||||||
root.dataset.themePreference = preference;
|
root.dataset.themePreference = preference;
|
||||||
root.dataset.theme = resolved;
|
root.dataset.theme = resolved;
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ English is the authoring source. French and Arabic are independent production re
|
|||||||
Set document attributes on the server:
|
Set document attributes on the server:
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<html lang="ar" dir="rtl" data-theme-preference="system" data-theme="dark">
|
<html lang="ar" dir="rtl" data-theme-preference="system" data-theme="dark"></html>
|
||||||
```
|
```
|
||||||
|
|
||||||
Do not wait for hydration to set language, direction, or resolved theme.
|
Do not wait for hydration to set language, direction, or resolved theme.
|
||||||
|
|||||||
@@ -1,11 +1,7 @@
|
|||||||
{
|
{
|
||||||
"version": "1.0",
|
"version": "1.0",
|
||||||
"defaultLocale": "en",
|
"defaultLocale": "en",
|
||||||
"locales": [
|
"locales": ["en", "fr", "ar"],
|
||||||
"en",
|
|
||||||
"fr",
|
|
||||||
"ar"
|
|
||||||
],
|
|
||||||
"rootDetection": true,
|
"rootDetection": true,
|
||||||
"routes": [
|
"routes": [
|
||||||
{
|
{
|
||||||
@@ -16,15 +12,7 @@
|
|||||||
"ar": ""
|
"ar": ""
|
||||||
},
|
},
|
||||||
"indexable": true,
|
"indexable": true,
|
||||||
"hashes": [
|
"hashes": ["top", "product", "workflow", "modules", "pricing", "faq", "contact"]
|
||||||
"top",
|
|
||||||
"product",
|
|
||||||
"workflow",
|
|
||||||
"modules",
|
|
||||||
"pricing",
|
|
||||||
"faq",
|
|
||||||
"contact"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "login",
|
"id": "login",
|
||||||
@@ -68,22 +56,12 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"switching": {
|
"switching": {
|
||||||
"preserveQueryAllowlist": [
|
"preserveQueryAllowlist": ["utm_source", "utm_medium", "utm_campaign", "utm_content", "gclid"],
|
||||||
"utm_source",
|
|
||||||
"utm_medium",
|
|
||||||
"utm_campaign",
|
|
||||||
"utm_content",
|
|
||||||
"gclid"
|
|
||||||
],
|
|
||||||
"preserveValidHash": true,
|
"preserveValidHash": true,
|
||||||
"dropUnknownQuery": true
|
"dropUnknownQuery": true
|
||||||
},
|
},
|
||||||
"hreflang": {
|
"hreflang": {
|
||||||
"values": [
|
"values": ["en", "fr", "ar"],
|
||||||
"en",
|
|
||||||
"fr",
|
|
||||||
"ar"
|
|
||||||
],
|
|
||||||
"xDefaultLocale": "en",
|
"xDefaultLocale": "en",
|
||||||
"selfCanonical": true
|
"selfCanonical": true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,26 +7,11 @@
|
|||||||
},
|
},
|
||||||
"header": {
|
"header": {
|
||||||
"nav": [
|
"nav": [
|
||||||
[
|
["product", "المنتج"],
|
||||||
"product",
|
["workflow", "سير العمل"],
|
||||||
"المنتج"
|
["modules", "الوحدات"],
|
||||||
],
|
["pricing", "التسعير"],
|
||||||
[
|
["faq", "الأسئلة الشائعة"]
|
||||||
"workflow",
|
|
||||||
"سير العمل"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"modules",
|
|
||||||
"الوحدات"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"pricing",
|
|
||||||
"التسعير"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"faq",
|
|
||||||
"الأسئلة الشائعة"
|
|
||||||
]
|
|
||||||
],
|
],
|
||||||
"login": "تسجيل الدخول",
|
"login": "تسجيل الدخول",
|
||||||
"demo": "احجز عرضًا توضيحيًا",
|
"demo": "احجز عرضًا توضيحيًا",
|
||||||
@@ -50,22 +35,10 @@
|
|||||||
"title": "سير عمل واحد يغطي دورة التأجير كاملة.",
|
"title": "سير عمل واحد يغطي دورة التأجير كاملة.",
|
||||||
"body": "حافظ على ترابط سياق العميل والمركبة والعقد والمهام مع تقدم عملية التأجير.",
|
"body": "حافظ على ترابط سياق العميل والمركبة والعقد والمهام مع تقدم عملية التأجير.",
|
||||||
"items": [
|
"items": [
|
||||||
[
|
["الحجز", "سجّل الطلب والبيانات المطلوبة."],
|
||||||
"الحجز",
|
["المركبة", "تابع التوافر والجاهزية."],
|
||||||
"سجّل الطلب والبيانات المطلوبة."
|
["العقد", "اجمع مهام التسليم والسجلات في موضع واحد."],
|
||||||
],
|
["الإرجاع", "سجّل الحالة وأغلق أعمال المتابعة."]
|
||||||
[
|
|
||||||
"المركبة",
|
|
||||||
"تابع التوافر والجاهزية."
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"العقد",
|
|
||||||
"اجمع مهام التسليم والسجلات في موضع واحد."
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"الإرجاع",
|
|
||||||
"سجّل الحالة وأغلق أعمال المتابعة."
|
|
||||||
]
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"comparison": {
|
"comparison": {
|
||||||
@@ -92,48 +65,20 @@
|
|||||||
"title": "حرّك عملية التأجير إلى الأمام من دون فقدان السياق.",
|
"title": "حرّك عملية التأجير إلى الأمام من دون فقدان السياق.",
|
||||||
"body": "توضح كل خطوة القرار والمسؤول والإجراء التالي بدل دفن التشغيل داخل قائمة عامة من الميزات.",
|
"body": "توضح كل خطوة القرار والمسؤول والإجراء التالي بدل دفن التشغيل داخل قائمة عامة من الميزات.",
|
||||||
"steps": [
|
"steps": [
|
||||||
[
|
["01", "تسجيل الحجز", "سجّل التواريخ وبيانات السائق والمتطلبات والمصدر."],
|
||||||
"01",
|
["02", "تخصيص مركبة جاهزة", "طابق التوافر مع الفئة والموقع وحالة الجاهزية."],
|
||||||
"تسجيل الحجز",
|
["03", "إكمال التسليم", "تحقق من المستندات والفحص والعقد ومهام تسليم المركبة."],
|
||||||
"سجّل التواريخ وبيانات السائق والمتطلبات والمصدر."
|
["04", "معالجة الإرجاع", "سجّل الحالة وأغلق الأعمال المفتوحة وأعد المركبة إلى التوافر."]
|
||||||
],
|
|
||||||
[
|
|
||||||
"02",
|
|
||||||
"تخصيص مركبة جاهزة",
|
|
||||||
"طابق التوافر مع الفئة والموقع وحالة الجاهزية."
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"03",
|
|
||||||
"إكمال التسليم",
|
|
||||||
"تحقق من المستندات والفحص والعقد ومهام تسليم المركبة."
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"04",
|
|
||||||
"معالجة الإرجاع",
|
|
||||||
"سجّل الحالة وأغلق الأعمال المفتوحة وأعد المركبة إلى التوافر."
|
|
||||||
]
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"roles": {
|
"roles": {
|
||||||
"eyebrow": "مصمم لفريق التشغيل",
|
"eyebrow": "مصمم لفريق التشغيل",
|
||||||
"title": "امنح كل دور السياق الذي يحتاجه من دون إنشاء نسخ متعددة من الحقيقة.",
|
"title": "امنح كل دور السياق الذي يحتاجه من دون إنشاء نسخ متعددة من الحقيقة.",
|
||||||
"roles": [
|
"roles": [
|
||||||
[
|
["المالك أو المدير", "اطّلع على عبء العمل والاستثناءات والمؤشرات التشغيلية عبر النشاط."],
|
||||||
"المالك أو المدير",
|
["فريق الحجوزات", "حوّل الطلبات إلى حجوزات مؤكدة مع عدد أقل من عمليات التسليم."],
|
||||||
"اطّلع على عبء العمل والاستثناءات والمؤشرات التشغيلية عبر النشاط."
|
["الأسطول والتشغيل", "اعرف المركبات المتاحة أو المحجوبة أو المستحقة أو التي تنتظر إجراءً."],
|
||||||
],
|
["مكتب الاستقبال", "حضّر مهام التسليم والإرجاع قبل انتظار العميل."]
|
||||||
[
|
|
||||||
"فريق الحجوزات",
|
|
||||||
"حوّل الطلبات إلى حجوزات مؤكدة مع عدد أقل من عمليات التسليم."
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"الأسطول والتشغيل",
|
|
||||||
"اعرف المركبات المتاحة أو المحجوبة أو المستحقة أو التي تنتظر إجراءً."
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"مكتب الاستقبال",
|
|
||||||
"حضّر مهام التسليم والإرجاع قبل انتظار العميل."
|
|
||||||
]
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"modules": {
|
"modules": {
|
||||||
@@ -141,30 +86,12 @@
|
|||||||
"title": "نواة مترابطة منظّمة حول أعمال التأجير الفعلية.",
|
"title": "نواة مترابطة منظّمة حول أعمال التأجير الفعلية.",
|
||||||
"body": "توضح حدود الوحدات المسؤوليات، بينما تمنع السجلات المشتركة فوضى النسخ واللصق المعتادة.",
|
"body": "توضح حدود الوحدات المسؤوليات، بينما تمنع السجلات المشتركة فوضى النسخ واللصق المعتادة.",
|
||||||
"items": [
|
"items": [
|
||||||
[
|
["الحجوزات", "التوافر والتواريخ ومتطلبات العميل والحالة ضمن سير واحد."],
|
||||||
"الحجوزات",
|
["جاهزية الأسطول", "توافر المركبات والحجوزات التشغيلية وحالة الخدمة وسياق الموقع."],
|
||||||
"التوافر والتواريخ ومتطلبات العميل والحالة ضمن سير واحد."
|
["عقود الإيجار", "متطلبات التسليم وحالة العقد والسجلات التشغيلية."],
|
||||||
],
|
["فحص المركبة", "فحوص منظمة عند التسليم والإرجاع مع مهام متابعة."],
|
||||||
[
|
["سجلات العملاء", "سجل التأجير وبيانات الاتصال المرتبطة بالعمل الجاري."],
|
||||||
"جاهزية الأسطول",
|
["التقارير التشغيلية", "تعريفات وعروض تشغيلية يمكن التحقق منها مقابل النشاط المصدر."]
|
||||||
"توافر المركبات والحجوزات التشغيلية وحالة الخدمة وسياق الموقع."
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"عقود الإيجار",
|
|
||||||
"متطلبات التسليم وحالة العقد والسجلات التشغيلية."
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"فحص المركبة",
|
|
||||||
"فحوص منظمة عند التسليم والإرجاع مع مهام متابعة."
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"سجلات العملاء",
|
|
||||||
"سجل التأجير وبيانات الاتصال المرتبطة بالعمل الجاري."
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"التقارير التشغيلية",
|
|
||||||
"تعريفات وعروض تشغيلية يمكن التحقق منها مقابل النشاط المصدر."
|
|
||||||
]
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"results": {
|
"results": {
|
||||||
@@ -172,22 +99,10 @@
|
|||||||
"title": "قِس التغيير، لا الخطاب التسويقي.",
|
"title": "قِس التغيير، لا الخطاب التسويقي.",
|
||||||
"body": "لا ينبغي نشر أي نتيجة قبل اعتماد خط أساس حقيقي لعميل وفترة مقارنة وتعريف واضح للبيانات. لذلك تعرض هذه النسخة منهجية القياس بدل نسب مئوية مختلقة.",
|
"body": "لا ينبغي نشر أي نتيجة قبل اعتماد خط أساس حقيقي لعميل وفترة مقارنة وتعريف واضح للبيانات. لذلك تعرض هذه النسخة منهجية القياس بدل نسب مئوية مختلقة.",
|
||||||
"metrics": [
|
"metrics": [
|
||||||
[
|
["زمن معالجة الحجز", "من استلام طلب صالح للعمل حتى تأكيد الحجز."],
|
||||||
"زمن معالجة الحجز",
|
["زمن إعادة المركبة إلى الجاهزية", "من تسجيل الإرجاع حتى حالة جاهز للتأجير."],
|
||||||
"من استلام طلب صالح للعمل حتى تأكيد الحجز."
|
["معدل استخدام الأسطول", "مقارنة أيام التوافر بأيام التأجير وفق تعريف واحد معتمد."],
|
||||||
],
|
["متابعة حالات التأخير", "عمليات التأجير المتأخرة المفتوحة مع مسؤول وإجراء تالٍ."]
|
||||||
[
|
|
||||||
"زمن إعادة المركبة إلى الجاهزية",
|
|
||||||
"من تسجيل الإرجاع حتى حالة جاهز للتأجير."
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"معدل استخدام الأسطول",
|
|
||||||
"مقارنة أيام التوافر بأيام التأجير وفق تعريف واحد معتمد."
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"متابعة حالات التأخير",
|
|
||||||
"عمليات التأجير المتأخرة المفتوحة مع مسؤول وإجراء تالٍ."
|
|
||||||
]
|
|
||||||
],
|
],
|
||||||
"methodTitle": "المنهجية المطلوبة قبل نشر أي نتيجة",
|
"methodTitle": "المنهجية المطلوبة قبل نشر أي نتيجة",
|
||||||
"method": [
|
"method": [
|
||||||
@@ -215,22 +130,10 @@
|
|||||||
"title": "قيّم المنصة بالأدلة، لا بالشعارات.",
|
"title": "قيّم المنصة بالأدلة، لا بالشعارات.",
|
||||||
"body": "لا تُنشر شهادات أو وعود بمستوى الخدمة قبل وجود وثائق معتمدة. أثناء التقييم، اطلب إجابات محددة حول الضوابط المهمة لنشاطك.",
|
"body": "لا تُنشر شهادات أو وعود بمستوى الخدمة قبل وجود وثائق معتمدة. أثناء التقييم، اطلب إجابات محددة حول الضوابط المهمة لنشاطك.",
|
||||||
"checks": [
|
"checks": [
|
||||||
[
|
["التحكم في الوصول", "الأدوار والصلاحيات وإجراءات المسؤول وإنهاء وصول المستخدم."],
|
||||||
"التحكم في الوصول",
|
["قابلية التدقيق", "التغييرات المسجلة والقابلة للبحث والتصدير."],
|
||||||
"الأدوار والصلاحيات وإجراءات المسؤول وإنهاء وصول المستخدم."
|
["معالجة البيانات", "الملكية والاحتفاظ والتصدير والحذف والجهات الفرعية."],
|
||||||
],
|
["استمرارية العمل", "النسخ الاحتياطي والاستعادة والتواصل عند الحوادث والدعم."]
|
||||||
[
|
|
||||||
"قابلية التدقيق",
|
|
||||||
"التغييرات المسجلة والقابلة للبحث والتصدير."
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"معالجة البيانات",
|
|
||||||
"الملكية والاحتفاظ والتصدير والحذف والجهات الفرعية."
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"استمرارية العمل",
|
|
||||||
"النسخ الاحتياطي والاستعادة والتواصل عند الحوادث والدعم."
|
|
||||||
]
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"pricing": {
|
"pricing": {
|
||||||
@@ -289,65 +192,32 @@
|
|||||||
[
|
[
|
||||||
"المنتج",
|
"المنتج",
|
||||||
[
|
[
|
||||||
[
|
["سير العمل", "#workflow"],
|
||||||
"سير العمل",
|
["الوحدات", "#modules"],
|
||||||
"#workflow"
|
["التسعير", "#pricing"]
|
||||||
],
|
|
||||||
[
|
|
||||||
"الوحدات",
|
|
||||||
"#modules"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"التسعير",
|
|
||||||
"#pricing"
|
|
||||||
]
|
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"الشركة",
|
"الشركة",
|
||||||
[
|
[
|
||||||
[
|
["تواصل مع المبيعات", "#contact"],
|
||||||
"تواصل مع المبيعات",
|
["احجز عرضًا توضيحيًا", "#demo"]
|
||||||
"#contact"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"احجز عرضًا توضيحيًا",
|
|
||||||
"#demo"
|
|
||||||
]
|
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"الحساب",
|
"الحساب",
|
||||||
[
|
[
|
||||||
[
|
["تسجيل الدخول", "#login"],
|
||||||
"تسجيل الدخول",
|
["اللغة", "#language"],
|
||||||
"#login"
|
["المظهر", "#theme"]
|
||||||
],
|
|
||||||
[
|
|
||||||
"اللغة",
|
|
||||||
"#language"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"المظهر",
|
|
||||||
"#theme"
|
|
||||||
]
|
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"قانوني",
|
"قانوني",
|
||||||
[
|
[
|
||||||
[
|
["الخصوصية", "#!"],
|
||||||
"الخصوصية",
|
["الشروط", "#!"],
|
||||||
"#!"
|
["إمكانية الوصول", "#!"]
|
||||||
],
|
|
||||||
[
|
|
||||||
"الشروط",
|
|
||||||
"#!"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"إمكانية الوصول",
|
|
||||||
"#!"
|
|
||||||
]
|
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
@@ -377,11 +247,7 @@
|
|||||||
"أكثر من 500 مركبة",
|
"أكثر من 500 مركبة",
|
||||||
"غير متأكد بعد"
|
"غير متأكد بعد"
|
||||||
],
|
],
|
||||||
"langOptions": [
|
"langOptions": ["English", "Français", "العربية"],
|
||||||
"English",
|
|
||||||
"Français",
|
|
||||||
"العربية"
|
|
||||||
],
|
|
||||||
"successTitle": "تم تسجيل طلب العرض",
|
"successTitle": "تم تسجيل طلب العرض",
|
||||||
"successBody": "اكتمل مسار الحجز في هذا النموذج الأولي. لم يُرسل أي طلب إلى نظام مبيعات فعلي.",
|
"successBody": "اكتمل مسار الحجز في هذا النموذج الأولي. لم يُرسل أي طلب إلى نظام مبيعات فعلي.",
|
||||||
"submitAnother": "إرسال طلب آخر",
|
"submitAnother": "إرسال طلب آخر",
|
||||||
|
|||||||
@@ -7,26 +7,11 @@
|
|||||||
},
|
},
|
||||||
"header": {
|
"header": {
|
||||||
"nav": [
|
"nav": [
|
||||||
[
|
["product", "Product"],
|
||||||
"product",
|
["workflow", "Workflow"],
|
||||||
"Product"
|
["modules", "Modules"],
|
||||||
],
|
["pricing", "Pricing"],
|
||||||
[
|
["faq", "FAQ"]
|
||||||
"workflow",
|
|
||||||
"Workflow"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"modules",
|
|
||||||
"Modules"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"pricing",
|
|
||||||
"Pricing"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"faq",
|
|
||||||
"FAQ"
|
|
||||||
]
|
|
||||||
],
|
],
|
||||||
"login": "Log in",
|
"login": "Log in",
|
||||||
"demo": "Book a demo",
|
"demo": "Book a demo",
|
||||||
@@ -50,22 +35,10 @@
|
|||||||
"title": "One workflow across the rental lifecycle.",
|
"title": "One workflow across the rental lifecycle.",
|
||||||
"body": "Keep the customer, vehicle, agreement, and task context connected as the rental moves forward.",
|
"body": "Keep the customer, vehicle, agreement, and task context connected as the rental moves forward.",
|
||||||
"items": [
|
"items": [
|
||||||
[
|
["Reservation", "Capture the request and required details."],
|
||||||
"Reservation",
|
["Vehicle", "Track availability and readiness."],
|
||||||
"Capture the request and required details."
|
["Agreement", "Keep pickup tasks and records together."],
|
||||||
],
|
["Return", "Record condition and close follow-up work."]
|
||||||
[
|
|
||||||
"Vehicle",
|
|
||||||
"Track availability and readiness."
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"Agreement",
|
|
||||||
"Keep pickup tasks and records together."
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"Return",
|
|
||||||
"Record condition and close follow-up work."
|
|
||||||
]
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"comparison": {
|
"comparison": {
|
||||||
@@ -92,48 +65,23 @@
|
|||||||
"title": "Move the rental forward without losing the thread.",
|
"title": "Move the rental forward without losing the thread.",
|
||||||
"body": "Each step exposes the decision, owner, and next action instead of burying operations inside a generic feature list.",
|
"body": "Each step exposes the decision, owner, and next action instead of burying operations inside a generic feature list.",
|
||||||
"steps": [
|
"steps": [
|
||||||
[
|
["01", "Capture the reservation", "Record dates, driver details, requirements, and source."],
|
||||||
"01",
|
["02", "Assign a ready vehicle", "Match availability with class, location, and readiness."],
|
||||||
"Capture the reservation",
|
["03", "Complete pickup", "Confirm documents, inspection, agreement, and handover tasks."],
|
||||||
"Record dates, driver details, requirements, and source."
|
["04", "Process the return", "Record condition, close open work, and release the vehicle."]
|
||||||
],
|
|
||||||
[
|
|
||||||
"02",
|
|
||||||
"Assign a ready vehicle",
|
|
||||||
"Match availability with class, location, and readiness."
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"03",
|
|
||||||
"Complete pickup",
|
|
||||||
"Confirm documents, inspection, agreement, and handover tasks."
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"04",
|
|
||||||
"Process the return",
|
|
||||||
"Record condition, close open work, and release the vehicle."
|
|
||||||
]
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"roles": {
|
"roles": {
|
||||||
"eyebrow": "Built for the operating team",
|
"eyebrow": "Built for the operating team",
|
||||||
"title": "Give each role the context it needs, without creating separate versions of the truth.",
|
"title": "Give each role the context it needs, without creating separate versions of the truth.",
|
||||||
"roles": [
|
"roles": [
|
||||||
[
|
["Owner or manager", "See workload, exceptions, and operating signals across the business."],
|
||||||
"Owner or manager",
|
["Reservations team", "Move requests from inquiry to confirmed rental with fewer handoffs."],
|
||||||
"See workload, exceptions, and operating signals across the business."
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"Reservations team",
|
|
||||||
"Move requests from inquiry to confirmed rental with fewer handoffs."
|
|
||||||
],
|
|
||||||
[
|
[
|
||||||
"Fleet and operations",
|
"Fleet and operations",
|
||||||
"Know which vehicles are available, blocked, due, or awaiting action."
|
"Know which vehicles are available, blocked, due, or awaiting action."
|
||||||
],
|
],
|
||||||
[
|
["Front desk", "Prepare pickup and return tasks before the customer is waiting."]
|
||||||
"Front desk",
|
|
||||||
"Prepare pickup and return tasks before the customer is waiting."
|
|
||||||
]
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"modules": {
|
"modules": {
|
||||||
@@ -141,26 +89,14 @@
|
|||||||
"title": "A connected core, organized around real rental work.",
|
"title": "A connected core, organized around real rental work.",
|
||||||
"body": "Module boundaries clarify responsibility while shared records prevent the usual copy-and-paste archaeology.",
|
"body": "Module boundaries clarify responsibility while shared records prevent the usual copy-and-paste archaeology.",
|
||||||
"items": [
|
"items": [
|
||||||
[
|
["Reservations", "Availability, dates, customer requirements, and status in one flow."],
|
||||||
"Reservations",
|
["Fleet readiness", "Vehicle availability, blocks, service state, and location context."],
|
||||||
"Availability, dates, customer requirements, and status in one flow."
|
["Rental agreements", "Pickup requirements, agreement status, and operational records."],
|
||||||
],
|
|
||||||
[
|
|
||||||
"Fleet readiness",
|
|
||||||
"Vehicle availability, blocks, service state, and location context."
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"Rental agreements",
|
|
||||||
"Pickup requirements, agreement status, and operational records."
|
|
||||||
],
|
|
||||||
[
|
[
|
||||||
"Vehicle inspections",
|
"Vehicle inspections",
|
||||||
"Structured pickup and return condition checks with follow-up tasks."
|
"Structured pickup and return condition checks with follow-up tasks."
|
||||||
],
|
],
|
||||||
[
|
["Customer records", "Rental history and contact context attached to active work."],
|
||||||
"Customer records",
|
|
||||||
"Rental history and contact context attached to active work."
|
|
||||||
],
|
|
||||||
[
|
[
|
||||||
"Operations reporting",
|
"Operations reporting",
|
||||||
"Definitions and operational views that can be validated against source activity."
|
"Definitions and operational views that can be validated against source activity."
|
||||||
@@ -172,22 +108,13 @@
|
|||||||
"title": "Measure the change, not the marketing.",
|
"title": "Measure the change, not the marketing.",
|
||||||
"body": "No outcome claim should appear until a real customer baseline, comparison period, and data definition are approved. The production page therefore shows the measurement plan instead of fictional percentages.",
|
"body": "No outcome claim should appear until a real customer baseline, comparison period, and data definition are approved. The production page therefore shows the measurement plan instead of fictional percentages.",
|
||||||
"metrics": [
|
"metrics": [
|
||||||
[
|
["Reservation handling time", "From usable request to confirmed reservation."],
|
||||||
"Reservation handling time",
|
["Vehicle turnaround time", "From return recorded to ready-for-rental status."],
|
||||||
"From usable request to confirmed reservation."
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"Vehicle turnaround time",
|
|
||||||
"From return recorded to ready-for-rental status."
|
|
||||||
],
|
|
||||||
[
|
[
|
||||||
"Fleet utilization",
|
"Fleet utilization",
|
||||||
"Available rental days compared with rented days, using one approved definition."
|
"Available rental days compared with rented days, using one approved definition."
|
||||||
],
|
],
|
||||||
[
|
["Overdue follow-up", "Open overdue rentals with an assigned owner and next action."]
|
||||||
"Overdue follow-up",
|
|
||||||
"Open overdue rentals with an assigned owner and next action."
|
|
||||||
]
|
|
||||||
],
|
],
|
||||||
"methodTitle": "Methodology required before publishing results",
|
"methodTitle": "Methodology required before publishing results",
|
||||||
"method": [
|
"method": [
|
||||||
@@ -215,22 +142,10 @@
|
|||||||
"title": "Evaluate the platform with evidence, not badges.",
|
"title": "Evaluate the platform with evidence, not badges.",
|
||||||
"body": "Certifications and service-level claims remain unpublished until approved documentation exists. During evaluation, request concrete answers for the controls that matter to your operation.",
|
"body": "Certifications and service-level claims remain unpublished until approved documentation exists. During evaluation, request concrete answers for the controls that matter to your operation.",
|
||||||
"checks": [
|
"checks": [
|
||||||
[
|
["Access control", "Roles, permissions, administrator actions, and offboarding."],
|
||||||
"Access control",
|
["Auditability", "Which changes are recorded, searchable, and exportable."],
|
||||||
"Roles, permissions, administrator actions, and offboarding."
|
["Data handling", "Ownership, retention, export, deletion, and subprocessors."],
|
||||||
],
|
["Continuity", "Backup, recovery, incident communication, and support process."]
|
||||||
[
|
|
||||||
"Auditability",
|
|
||||||
"Which changes are recorded, searchable, and exportable."
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"Data handling",
|
|
||||||
"Ownership, retention, export, deletion, and subprocessors."
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"Continuity",
|
|
||||||
"Backup, recovery, incident communication, and support process."
|
|
||||||
]
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"pricing": {
|
"pricing": {
|
||||||
@@ -289,65 +204,32 @@
|
|||||||
[
|
[
|
||||||
"Product",
|
"Product",
|
||||||
[
|
[
|
||||||
[
|
["Workflow", "#workflow"],
|
||||||
"Workflow",
|
["Modules", "#modules"],
|
||||||
"#workflow"
|
["Pricing", "#pricing"]
|
||||||
],
|
|
||||||
[
|
|
||||||
"Modules",
|
|
||||||
"#modules"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"Pricing",
|
|
||||||
"#pricing"
|
|
||||||
]
|
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"Company",
|
"Company",
|
||||||
[
|
[
|
||||||
[
|
["Contact sales", "#contact"],
|
||||||
"Contact sales",
|
["Book a demo", "#demo"]
|
||||||
"#contact"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"Book a demo",
|
|
||||||
"#demo"
|
|
||||||
]
|
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"Account",
|
"Account",
|
||||||
[
|
[
|
||||||
[
|
["Log in", "#login"],
|
||||||
"Log in",
|
["Language", "#language"],
|
||||||
"#login"
|
["Theme", "#theme"]
|
||||||
],
|
|
||||||
[
|
|
||||||
"Language",
|
|
||||||
"#language"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"Theme",
|
|
||||||
"#theme"
|
|
||||||
]
|
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"Legal",
|
"Legal",
|
||||||
[
|
[
|
||||||
[
|
["Privacy", "#!"],
|
||||||
"Privacy",
|
["Terms", "#!"],
|
||||||
"#!"
|
["Accessibility", "#!"]
|
||||||
],
|
|
||||||
[
|
|
||||||
"Terms",
|
|
||||||
"#!"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"Accessibility",
|
|
||||||
"#!"
|
|
||||||
]
|
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
@@ -377,11 +259,7 @@
|
|||||||
"More than 500 vehicles",
|
"More than 500 vehicles",
|
||||||
"Not sure yet"
|
"Not sure yet"
|
||||||
],
|
],
|
||||||
"langOptions": [
|
"langOptions": ["English", "Français", "العربية"],
|
||||||
"English",
|
|
||||||
"Français",
|
|
||||||
"العربية"
|
|
||||||
],
|
|
||||||
"successTitle": "Demo request recorded",
|
"successTitle": "Demo request recorded",
|
||||||
"successBody": "This prototype has completed the booking flow. No request was sent to a real sales system.",
|
"successBody": "This prototype has completed the booking flow. No request was sent to a real sales system.",
|
||||||
"submitAnother": "Submit another request",
|
"submitAnother": "Submit another request",
|
||||||
|
|||||||
@@ -7,26 +7,11 @@
|
|||||||
},
|
},
|
||||||
"header": {
|
"header": {
|
||||||
"nav": [
|
"nav": [
|
||||||
[
|
["product", "Produit"],
|
||||||
"product",
|
["workflow", "Parcours"],
|
||||||
"Produit"
|
["modules", "Modules"],
|
||||||
],
|
["pricing", "Tarifs"],
|
||||||
[
|
["faq", "FAQ"]
|
||||||
"workflow",
|
|
||||||
"Parcours"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"modules",
|
|
||||||
"Modules"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"pricing",
|
|
||||||
"Tarifs"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"faq",
|
|
||||||
"FAQ"
|
|
||||||
]
|
|
||||||
],
|
],
|
||||||
"login": "Se connecter",
|
"login": "Se connecter",
|
||||||
"demo": "Réserver une démo",
|
"demo": "Réserver une démo",
|
||||||
@@ -50,22 +35,10 @@
|
|||||||
"title": "Un même parcours pour tout le cycle de location.",
|
"title": "Un même parcours pour tout le cycle de location.",
|
||||||
"body": "Conservez le contexte du client, du véhicule, du contrat et des tâches au fil de la location.",
|
"body": "Conservez le contexte du client, du véhicule, du contrat et des tâches au fil de la location.",
|
||||||
"items": [
|
"items": [
|
||||||
[
|
["Réservation", "Saisir la demande et les informations nécessaires."],
|
||||||
"Réservation",
|
["Véhicule", "Suivre la disponibilité et l’état de préparation."],
|
||||||
"Saisir la demande et les informations nécessaires."
|
["Contrat", "Regrouper les tâches de départ et les documents."],
|
||||||
],
|
["Retour", "Enregistrer l’état et clôturer les actions de suivi."]
|
||||||
[
|
|
||||||
"Véhicule",
|
|
||||||
"Suivre la disponibilité et l’état de préparation."
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"Contrat",
|
|
||||||
"Regrouper les tâches de départ et les documents."
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"Retour",
|
|
||||||
"Enregistrer l’état et clôturer les actions de suivi."
|
|
||||||
]
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"comparison": {
|
"comparison": {
|
||||||
@@ -118,10 +91,7 @@
|
|||||||
"eyebrow": "Conçu pour l’équipe opérationnelle",
|
"eyebrow": "Conçu pour l’équipe opérationnelle",
|
||||||
"title": "Donnez à chaque rôle le contexte utile, sans multiplier les versions de la vérité.",
|
"title": "Donnez à chaque rôle le contexte utile, sans multiplier les versions de la vérité.",
|
||||||
"roles": [
|
"roles": [
|
||||||
[
|
["Direction ou responsable", "Voir la charge, les exceptions et les signaux d’exploitation."],
|
||||||
"Direction ou responsable",
|
|
||||||
"Voir la charge, les exceptions et les signaux d’exploitation."
|
|
||||||
],
|
|
||||||
[
|
[
|
||||||
"Équipe réservations",
|
"Équipe réservations",
|
||||||
"Faire passer les demandes vers une location confirmée avec moins de transmissions."
|
"Faire passer les demandes vers une location confirmée avec moins de transmissions."
|
||||||
@@ -130,10 +100,7 @@
|
|||||||
"Parc et opérations",
|
"Parc et opérations",
|
||||||
"Savoir quels véhicules sont disponibles, bloqués, attendus ou en attente d’action."
|
"Savoir quels véhicules sont disponibles, bloqués, attendus ou en attente d’action."
|
||||||
],
|
],
|
||||||
[
|
["Comptoir", "Préparer les tâches de départ et de retour avant que le client n’attende."]
|
||||||
"Comptoir",
|
|
||||||
"Préparer les tâches de départ et de retour avant que le client n’attende."
|
|
||||||
]
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"modules": {
|
"modules": {
|
||||||
@@ -141,30 +108,15 @@
|
|||||||
"title": "Un socle connecté, organisé autour du travail réel de location.",
|
"title": "Un socle connecté, organisé autour du travail réel de location.",
|
||||||
"body": "Les modules clarifient les responsabilités, tandis que les dossiers partagés évitent l’archéologie du copier-coller.",
|
"body": "Les modules clarifient les responsabilités, tandis que les dossiers partagés évitent l’archéologie du copier-coller.",
|
||||||
"items": [
|
"items": [
|
||||||
[
|
["Réservations", "Disponibilités, dates, besoins du client et statut dans un même flux."],
|
||||||
"Réservations",
|
["Préparation du parc", "Disponibilité, blocages, état de service et contexte d’agence."],
|
||||||
"Disponibilités, dates, besoins du client et statut dans un même flux."
|
["Contrats de location", "Exigences de départ, état du contrat et dossiers opérationnels."],
|
||||||
],
|
|
||||||
[
|
|
||||||
"Préparation du parc",
|
|
||||||
"Disponibilité, blocages, état de service et contexte d’agence."
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"Contrats de location",
|
|
||||||
"Exigences de départ, état du contrat et dossiers opérationnels."
|
|
||||||
],
|
|
||||||
[
|
[
|
||||||
"Inspections des véhicules",
|
"Inspections des véhicules",
|
||||||
"Contrôles structurés au départ et au retour avec actions de suivi."
|
"Contrôles structurés au départ et au retour avec actions de suivi."
|
||||||
],
|
],
|
||||||
[
|
["Dossiers clients", "Historique de location et coordonnées rattachés au travail en cours."],
|
||||||
"Dossiers clients",
|
["Rapports opérationnels", "Définitions et vues vérifiables par rapport à l’activité source."]
|
||||||
"Historique de location et coordonnées rattachés au travail en cours."
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"Rapports opérationnels",
|
|
||||||
"Définitions et vues vérifiables par rapport à l’activité source."
|
|
||||||
]
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"results": {
|
"results": {
|
||||||
@@ -176,18 +128,12 @@
|
|||||||
"Temps de traitement des réservations",
|
"Temps de traitement des réservations",
|
||||||
"De la demande exploitable à la réservation confirmée."
|
"De la demande exploitable à la réservation confirmée."
|
||||||
],
|
],
|
||||||
[
|
["Temps de remise en service", "Du retour enregistré au statut prêt à louer."],
|
||||||
"Temps de remise en service",
|
|
||||||
"Du retour enregistré au statut prêt à louer."
|
|
||||||
],
|
|
||||||
[
|
[
|
||||||
"Taux d’utilisation du parc",
|
"Taux d’utilisation du parc",
|
||||||
"Jours louables comparés aux jours loués, selon une définition approuvée."
|
"Jours louables comparés aux jours loués, selon une définition approuvée."
|
||||||
],
|
],
|
||||||
[
|
["Suivi des retards", "Locations en retard avec responsable et prochaine action."]
|
||||||
"Suivi des retards",
|
|
||||||
"Locations en retard avec responsable et prochaine action."
|
|
||||||
]
|
|
||||||
],
|
],
|
||||||
"methodTitle": "Méthode requise avant toute publication",
|
"methodTitle": "Méthode requise avant toute publication",
|
||||||
"method": [
|
"method": [
|
||||||
@@ -215,22 +161,10 @@
|
|||||||
"title": "Évaluez la plateforme avec des preuves, pas avec des badges.",
|
"title": "Évaluez la plateforme avec des preuves, pas avec des badges.",
|
||||||
"body": "Aucune certification ni promesse de niveau de service n’est publiée sans documentation approuvée. Pendant l’évaluation, demandez des réponses concrètes sur les contrôles utiles à votre activité.",
|
"body": "Aucune certification ni promesse de niveau de service n’est publiée sans documentation approuvée. Pendant l’évaluation, demandez des réponses concrètes sur les contrôles utiles à votre activité.",
|
||||||
"checks": [
|
"checks": [
|
||||||
[
|
["Contrôle des accès", "Rôles, autorisations, actions administrateur et départs."],
|
||||||
"Contrôle des accès",
|
["Traçabilité", "Modifications enregistrées, recherchables et exportables."],
|
||||||
"Rôles, autorisations, actions administrateur et départs."
|
["Gestion des données", "Propriété, conservation, export, suppression et sous-traitants."],
|
||||||
],
|
["Continuité", "Sauvegarde, reprise, communication d’incident et support."]
|
||||||
[
|
|
||||||
"Traçabilité",
|
|
||||||
"Modifications enregistrées, recherchables et exportables."
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"Gestion des données",
|
|
||||||
"Propriété, conservation, export, suppression et sous-traitants."
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"Continuité",
|
|
||||||
"Sauvegarde, reprise, communication d’incident et support."
|
|
||||||
]
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"pricing": {
|
"pricing": {
|
||||||
@@ -289,65 +223,32 @@
|
|||||||
[
|
[
|
||||||
"Produit",
|
"Produit",
|
||||||
[
|
[
|
||||||
[
|
["Parcours", "#workflow"],
|
||||||
"Parcours",
|
["Modules", "#modules"],
|
||||||
"#workflow"
|
["Tarifs", "#pricing"]
|
||||||
],
|
|
||||||
[
|
|
||||||
"Modules",
|
|
||||||
"#modules"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"Tarifs",
|
|
||||||
"#pricing"
|
|
||||||
]
|
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"Entreprise",
|
"Entreprise",
|
||||||
[
|
[
|
||||||
[
|
["Contacter le service commercial", "#contact"],
|
||||||
"Contacter le service commercial",
|
["Réserver une démo", "#demo"]
|
||||||
"#contact"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"Réserver une démo",
|
|
||||||
"#demo"
|
|
||||||
]
|
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"Compte",
|
"Compte",
|
||||||
[
|
[
|
||||||
[
|
["Se connecter", "#login"],
|
||||||
"Se connecter",
|
["Langue", "#language"],
|
||||||
"#login"
|
["Thème", "#theme"]
|
||||||
],
|
|
||||||
[
|
|
||||||
"Langue",
|
|
||||||
"#language"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"Thème",
|
|
||||||
"#theme"
|
|
||||||
]
|
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"Mentions légales",
|
"Mentions légales",
|
||||||
[
|
[
|
||||||
[
|
["Confidentialité", "#!"],
|
||||||
"Confidentialité",
|
["Conditions", "#!"],
|
||||||
"#!"
|
["Accessibilité", "#!"]
|
||||||
],
|
|
||||||
[
|
|
||||||
"Conditions",
|
|
||||||
"#!"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"Accessibilité",
|
|
||||||
"#!"
|
|
||||||
]
|
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
@@ -377,11 +278,7 @@
|
|||||||
"Plus de 500 véhicules",
|
"Plus de 500 véhicules",
|
||||||
"Je ne sais pas encore"
|
"Je ne sais pas encore"
|
||||||
],
|
],
|
||||||
"langOptions": [
|
"langOptions": ["English", "Français", "العربية"],
|
||||||
"English",
|
|
||||||
"Français",
|
|
||||||
"العربية"
|
|
||||||
],
|
|
||||||
"successTitle": "Demande de démonstration enregistrée",
|
"successTitle": "Demande de démonstration enregistrée",
|
||||||
"successBody": "Ce prototype a terminé le parcours de réservation. Aucune demande n’a été envoyée à un véritable système commercial.",
|
"successBody": "Ce prototype a terminé le parcours de réservation. Aucune demande n’a été envoyée à un véritable système commercial.",
|
||||||
"submitAnother": "Envoyer une autre demande",
|
"submitAnother": "Envoyer une autre demande",
|
||||||
|
|||||||
@@ -3,22 +3,77 @@ import path from 'node:path';
|
|||||||
import crypto from 'node:crypto';
|
import crypto from 'node:crypto';
|
||||||
const root = path.resolve(path.dirname(new URL(import.meta.url).pathname), '..');
|
const root = path.resolve(path.dirname(new URL(import.meta.url).pathname), '..');
|
||||||
const required = [
|
const required = [
|
||||||
'09_DEVELOPER_HANDOFF_v1.0.md','DECISION_LOG_v9.0.md','DEFINITION_OF_DONE_v1.0.md','OPEN_ISSUE_REGISTER_v1.0.csv',
|
'09_DEVELOPER_HANDOFF_v1.0.md',
|
||||||
'tokens/design-tokens_v1.0.json','tokens/semantic-tokens_v1.0.css','components/COMPONENT_CONTRACTS_v1.0.md',
|
'DECISION_LOG_v9.0.md',
|
||||||
'localization/ROUTE_MANIFEST_v1.0.json','localization/locales/en/homepage.json','localization/locales/fr/homepage.json','localization/locales/ar/homepage.json',
|
'DEFINITION_OF_DONE_v1.0.md',
|
||||||
'interaction/theme-bootstrap_v1.0.js','qa/QA_MATRIX_v1.0.csv','qa/ACCESSIBILITY_CHECKLIST_v1.0.csv','qa/PERFORMANCE_BUDGETS_v1.0.json'
|
'OPEN_ISSUE_REGISTER_v1.0.csv',
|
||||||
|
'tokens/design-tokens_v1.0.json',
|
||||||
|
'tokens/semantic-tokens_v1.0.css',
|
||||||
|
'components/COMPONENT_CONTRACTS_v1.0.md',
|
||||||
|
'localization/ROUTE_MANIFEST_v1.0.json',
|
||||||
|
'localization/locales/en/homepage.json',
|
||||||
|
'localization/locales/fr/homepage.json',
|
||||||
|
'localization/locales/ar/homepage.json',
|
||||||
|
'interaction/theme-bootstrap_v1.0.js',
|
||||||
|
'qa/QA_MATRIX_v1.0.csv',
|
||||||
|
'qa/ACCESSIBILITY_CHECKLIST_v1.0.csv',
|
||||||
|
'qa/PERFORMANCE_BUDGETS_v1.0.json',
|
||||||
];
|
];
|
||||||
const errors=[]; let checks=0;
|
const errors = [];
|
||||||
for (const file of required){ checks++; if(!fs.existsSync(path.join(root,file))) errors.push(`Missing ${file}`); }
|
let checks = 0;
|
||||||
const readJson=(f)=>JSON.parse(fs.readFileSync(path.join(root,f),'utf8'));
|
for (const file of required) {
|
||||||
for (const file of ['tokens/design-tokens_v1.0.json','localization/ROUTE_MANIFEST_v1.0.json','qa/PERFORMANCE_BUDGETS_v1.0.json','qa/visual-regression-scenarios_v1.0.json']) { try{readJson(file);checks++;}catch(e){errors.push(`${file}: ${e.message}`);} }
|
checks++;
|
||||||
function shape(value){ if(Array.isArray(value)) return value.map(shape); if(value && typeof value==='object') return Object.fromEntries(Object.keys(value).sort().map(k=>[k,shape(value[k])])); return typeof value; }
|
if (!fs.existsSync(path.join(root, file))) errors.push(`Missing ${file}`);
|
||||||
const locales=['en','fr','ar'].map(l=>readJson(`localization/locales/${l}/homepage.json`));
|
}
|
||||||
const base=JSON.stringify(shape(locales[0]));
|
const readJson = (f) => JSON.parse(fs.readFileSync(path.join(root, f), 'utf8'));
|
||||||
for (let i=1;i<locales.length;i++){ checks++; if(JSON.stringify(shape(locales[i]))!==base) errors.push(`Locale shape mismatch: ${['en','fr','ar'][i]}`); }
|
for (const file of [
|
||||||
const routes=readJson('localization/ROUTE_MANIFEST_v1.0.json');
|
'tokens/design-tokens_v1.0.json',
|
||||||
for (const route of routes.routes){ for (const locale of routes.locales){ checks++; if(!(locale in route.slugs)) errors.push(`Route ${route.id} missing ${locale}`); } }
|
'localization/ROUTE_MANIFEST_v1.0.json',
|
||||||
const issueCsv=fs.readFileSync(path.join(root,'OPEN_ISSUE_REGISTER_v1.0.csv'),'utf8'); checks++; if(/,(Critical|High),[^\n]*,,/.test(issueCsv)) errors.push('Critical/High issue missing ownership data');
|
'qa/PERFORMANCE_BUDGETS_v1.0.json',
|
||||||
const css=fs.readFileSync(path.join(root,'tokens/semantic-tokens_v1.0.css'),'utf8'); checks++; if(!css.includes('--color-action-conversion')) errors.push('Conversion semantic token missing');
|
'qa/visual-regression-scenarios_v1.0.json',
|
||||||
if(errors.length){ console.error(JSON.stringify({status:'FAILED',checks,errors},null,2)); process.exit(1); }
|
]) {
|
||||||
console.log(JSON.stringify({status:'PASSED',checks,files:fs.readdirSync(root).length},null,2));
|
try {
|
||||||
|
readJson(file);
|
||||||
|
checks++;
|
||||||
|
} catch (e) {
|
||||||
|
errors.push(`${file}: ${e.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function shape(value) {
|
||||||
|
if (Array.isArray(value)) return value.map(shape);
|
||||||
|
if (value && typeof value === 'object')
|
||||||
|
return Object.fromEntries(
|
||||||
|
Object.keys(value)
|
||||||
|
.sort()
|
||||||
|
.map((k) => [k, shape(value[k])]),
|
||||||
|
);
|
||||||
|
return typeof value;
|
||||||
|
}
|
||||||
|
const locales = ['en', 'fr', 'ar'].map((l) => readJson(`localization/locales/${l}/homepage.json`));
|
||||||
|
const base = JSON.stringify(shape(locales[0]));
|
||||||
|
for (let i = 1; i < locales.length; i++) {
|
||||||
|
checks++;
|
||||||
|
if (JSON.stringify(shape(locales[i])) !== base)
|
||||||
|
errors.push(`Locale shape mismatch: ${['en', 'fr', 'ar'][i]}`);
|
||||||
|
}
|
||||||
|
const routes = readJson('localization/ROUTE_MANIFEST_v1.0.json');
|
||||||
|
for (const route of routes.routes) {
|
||||||
|
for (const locale of routes.locales) {
|
||||||
|
checks++;
|
||||||
|
if (!(locale in route.slugs)) errors.push(`Route ${route.id} missing ${locale}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const issueCsv = fs.readFileSync(path.join(root, 'OPEN_ISSUE_REGISTER_v1.0.csv'), 'utf8');
|
||||||
|
checks++;
|
||||||
|
if (/,(Critical|High),[^\n]*,,/.test(issueCsv))
|
||||||
|
errors.push('Critical/High issue missing ownership data');
|
||||||
|
const css = fs.readFileSync(path.join(root, 'tokens/semantic-tokens_v1.0.css'), 'utf8');
|
||||||
|
checks++;
|
||||||
|
if (!css.includes('--color-action-conversion')) errors.push('Conversion semantic token missing');
|
||||||
|
if (errors.length) {
|
||||||
|
console.error(JSON.stringify({ status: 'FAILED', checks, errors }, null, 2));
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
console.log(
|
||||||
|
JSON.stringify({ status: 'PASSED', checks, files: fs.readdirSync(root).length }, null, 2),
|
||||||
|
);
|
||||||
|
|||||||
@@ -5,80 +5,56 @@
|
|||||||
{
|
{
|
||||||
"id": "VR-001",
|
"id": "VR-001",
|
||||||
"route": "/en",
|
"route": "/en",
|
||||||
"viewport": [
|
"viewport": [1440, 1000],
|
||||||
1440,
|
|
||||||
1000
|
|
||||||
],
|
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"state": "homepage-top"
|
"state": "homepage-top"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "VR-002",
|
"id": "VR-002",
|
||||||
"route": "/fr",
|
"route": "/fr",
|
||||||
"viewport": [
|
"viewport": [1024, 900],
|
||||||
1024,
|
|
||||||
900
|
|
||||||
],
|
|
||||||
"theme": "dark",
|
"theme": "dark",
|
||||||
"state": "homepage-top"
|
"state": "homepage-top"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "VR-003",
|
"id": "VR-003",
|
||||||
"route": "/ar",
|
"route": "/ar",
|
||||||
"viewport": [
|
"viewport": [390, 844],
|
||||||
390,
|
|
||||||
844
|
|
||||||
],
|
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"state": "homepage-top"
|
"state": "homepage-top"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "VR-004",
|
"id": "VR-004",
|
||||||
"route": "/ar",
|
"route": "/ar",
|
||||||
"viewport": [
|
"viewport": [390, 844],
|
||||||
390,
|
|
||||||
844
|
|
||||||
],
|
|
||||||
"theme": "dark",
|
"theme": "dark",
|
||||||
"state": "drawer-open"
|
"state": "drawer-open"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "VR-005",
|
"id": "VR-005",
|
||||||
"route": "/en",
|
"route": "/en",
|
||||||
"viewport": [
|
"viewport": [390, 844],
|
||||||
390,
|
|
||||||
844
|
|
||||||
],
|
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"state": "demo-invalid"
|
"state": "demo-invalid"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "VR-006",
|
"id": "VR-006",
|
||||||
"route": "/fr",
|
"route": "/fr",
|
||||||
"viewport": [
|
"viewport": [768, 1024],
|
||||||
768,
|
|
||||||
1024
|
|
||||||
],
|
|
||||||
"theme": "dark",
|
"theme": "dark",
|
||||||
"state": "tour-step-3"
|
"state": "tour-step-3"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "VR-007",
|
"id": "VR-007",
|
||||||
"route": "/ar",
|
"route": "/ar",
|
||||||
"viewport": [
|
"viewport": [320, 800],
|
||||||
320,
|
|
||||||
800
|
|
||||||
],
|
|
||||||
"theme": "dark",
|
"theme": "dark",
|
||||||
"state": "reflow-gate"
|
"state": "reflow-gate"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "VR-008",
|
"id": "VR-008",
|
||||||
"route": "/en",
|
"route": "/en",
|
||||||
"viewport": [
|
"viewport": [1440, 1000],
|
||||||
1440,
|
|
||||||
1000
|
|
||||||
],
|
|
||||||
"theme": "dark",
|
"theme": "dark",
|
||||||
"state": "pricing-and-final-cta"
|
"state": "pricing-and-final-cta"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,15 +12,15 @@ No claim is made that users understand the product, find the CTA, or complete th
|
|||||||
|
|
||||||
## Pre-test findings
|
## Pre-test findings
|
||||||
|
|
||||||
| ID | Evidence | Finding | Severity | Status |
|
| ID | Evidence | Finding | Severity | Status |
|
||||||
|---|---|---|---|---|
|
| ------ | ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------- | ----------------------------------------- |
|
||||||
| P8-001 | Expert + code review | Demo submission in Phase 7 prevented default and produced no success, failure, or recovery state. | S0/C | Fixed in prototype |
|
| P8-001 | Expert + code review | Demo submission in Phase 7 prevented default and produced no success, failure, or recovery state. | S0/C | Fixed in prototype |
|
||||||
| P8-002 | Expert + code review | “Watch product tour” navigated to a workflow section rather than opening a tour, creating an expectation mismatch. | S1/C | Fixed with four-step tour |
|
| P8-002 | Expert + code review | “Watch product tour” navigated to a workflow section rather than opening a tour, creating an expectation mismatch. | S1/C | Fixed with four-step tour |
|
||||||
| P8-003 | Expert + code review | Mobile drawer moved focus initially but did not contain focus or make background content inert. | S0/C | Fixed in prototype |
|
| P8-003 | Expert + code review | Mobile drawer moved focus initially but did not contain focus or make background content inert. | S0/C | Fixed in prototype |
|
||||||
| P8-004 | Expert + code review | Theme existed in URL state but did not persist reliably when revisiting without the query parameter. | S1/C | Fixed with local persistence |
|
| P8-004 | Expert + code review | Theme existed in URL state but did not persist reliably when revisiting without the query parameter. | S1/C | Fixed with local persistence |
|
||||||
| P8-005 | Expert review | Multi-location support is implied by branch/location copy but is not an approved capability claim. Users may over-infer. | S2/C | Test explicitly; content approval pending |
|
| P8-005 | Expert review | Multi-location support is implied by branch/location copy but is not an approved capability claim. Users may over-infer. | S2/C | Test explicitly; content approval pending |
|
||||||
| P8-006 | Expert review | Final legal consent, alternate failure contact, CRM routing, and spam controls are unresolved. | S1/C for production | Prototype limitation; open |
|
| P8-006 | Expert review | Final legal consent, alternate failure contact, CRM routing, and spam controls are unresolved. | S1/C for production | Prototype limitation; open |
|
||||||
| P8-007 | Automated browser test | EN/FR/AR direction, tour completion, form validation/success, locale hash preservation, theme persistence, and mobile focus return pass the scripted browser checks. | S4/A | Passed |
|
| P8-007 | Automated browser test | EN/FR/AR direction, tour completion, form validation/success, locale hash preservation, theme persistence, and mobile focus return pass the scripted browser checks. | S4/A | Passed |
|
||||||
|
|
||||||
## Hypotheses requiring participant evidence
|
## Hypotheses requiring participant evidence
|
||||||
|
|
||||||
|
|||||||
@@ -66,14 +66,14 @@
|
|||||||
--duration-standard: 160ms;
|
--duration-standard: 160ms;
|
||||||
--duration-overlay: 220ms;
|
--duration-overlay: 220ms;
|
||||||
--easing-standard: ease;
|
--easing-standard: ease;
|
||||||
--easing-productive: cubic-bezier(.2,0,0,1);
|
--easing-productive: cubic-bezier(0.2, 0, 0, 1);
|
||||||
--font-latin: Inter Variable, Inter, Arial, sans-serif;
|
--font-latin: Inter Variable, Inter, Arial, sans-serif;
|
||||||
--font-arabic: Noto Sans Arabic, Arial, sans-serif;
|
--font-arabic: Noto Sans Arabic, Arial, sans-serif;
|
||||||
--breakpoint-medium: 768px;
|
--breakpoint-medium: 768px;
|
||||||
--breakpoint-wide: 1120px;
|
--breakpoint-wide: 1120px;
|
||||||
}
|
}
|
||||||
|
|
||||||
html[data-theme="light"] {
|
html[data-theme='light'] {
|
||||||
--color-canvas: var(--color-gray-25);
|
--color-canvas: var(--color-gray-25);
|
||||||
--color-surface: #ffffff;
|
--color-surface: #ffffff;
|
||||||
--color-surface-raised: #ffffff;
|
--color-surface-raised: #ffffff;
|
||||||
@@ -91,10 +91,10 @@ html[data-theme="light"] {
|
|||||||
--color-on-inverse: #f8fafc;
|
--color-on-inverse: #f8fafc;
|
||||||
--color-soft-blue: #eef5ff;
|
--color-soft-blue: #eef5ff;
|
||||||
--color-soft-orange: #fff7ed;
|
--color-soft-orange: #fff7ed;
|
||||||
--shadow-color: rgba(8,20,38,.12);
|
--shadow-color: rgba(8, 20, 38, 0.12);
|
||||||
color-scheme: light;
|
color-scheme: light;
|
||||||
}
|
}
|
||||||
html[data-theme="dark"] {
|
html[data-theme='dark'] {
|
||||||
--color-canvas: var(--color-navy-950);
|
--color-canvas: var(--color-navy-950);
|
||||||
--color-surface: var(--color-navy-900);
|
--color-surface: var(--color-navy-900);
|
||||||
--color-surface-raised: var(--color-navy-850);
|
--color-surface-raised: var(--color-navy-850);
|
||||||
@@ -112,11 +112,19 @@ html[data-theme="dark"] {
|
|||||||
--color-on-inverse: var(--color-navy-950);
|
--color-on-inverse: var(--color-navy-950);
|
||||||
--color-soft-blue: #10284a;
|
--color-soft-blue: #10284a;
|
||||||
--color-soft-orange: #3a2015;
|
--color-soft-orange: #3a2015;
|
||||||
--shadow-color: rgba(0,0,0,.36);
|
--shadow-color: rgba(0, 0, 0, 0.36);
|
||||||
color-scheme: dark;
|
color-scheme: dark;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (prefers-reduced-motion: reduce) {
|
@media (prefers-reduced-motion: reduce) {
|
||||||
html { scroll-behavior: auto; }
|
html {
|
||||||
*, *::before, *::after { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; }
|
scroll-behavior: auto;
|
||||||
|
}
|
||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
animation-duration: 0.01ms !important;
|
||||||
|
animation-iteration-count: 1 !important;
|
||||||
|
transition-duration: 0.01ms !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
export const breakpoints = { medium: 768, wide: 1120 } as const;
|
export const breakpoints = { medium: 768, wide: 1120 } as const;
|
||||||
export const themePreferences = ["light", "dark", "system"] as const;
|
export const themePreferences = ['light', 'dark', 'system'] as const;
|
||||||
export type ThemePreference = (typeof themePreferences)[number];
|
export type ThemePreference = (typeof themePreferences)[number];
|
||||||
export type ResolvedTheme = Exclude<ThemePreference, "system">;
|
export type ResolvedTheme = Exclude<ThemePreference, 'system'>;
|
||||||
export const layout = { containerMaxPx: 1280, touchTargetMinPx: 44, spacingBasePx: 4 } as const;
|
export const layout = { containerMaxPx: 1280, touchTargetMinPx: 44, spacingBasePx: 4 } as const;
|
||||||
export const motion = { fastMs: 120, standardMs: 160, overlayMs: 220 } as const;
|
export const motion = { fastMs: 120, standardMs: 160, overlayMs: 220 } as const;
|
||||||
|
|||||||
@@ -9,19 +9,19 @@
|
|||||||
|
|
||||||
## Findings
|
## Findings
|
||||||
|
|
||||||
| Severity | Code | Path | Line | Finding |
|
| Severity | Code | Path | Line | Finding |
|
||||||
|---|---|---|---:|---|
|
| -------- | ------------------------------------ | -------------------------------------------------- | ---: | --------------------------------------------------------------- |
|
||||||
| High | `ci_integration_suite_omitted` | `.github/workflows/ci.yml` | | CI does not execute the demo-service integration suite. |
|
| High | `ci_integration_suite_omitted` | `.github/workflows/ci.yml` | | CI does not execute the demo-service integration suite. |
|
||||||
| High | `issue_register_overflow` | `docs/IMPLEMENTATION_ISSUE_REGISTER_v1.4.csv` | | One or more issue-register rows contain overflow columns. |
|
| High | `issue_register_overflow` | `docs/IMPLEMENTATION_ISSUE_REGISTER_v1.4.csv` | | One or more issue-register rows contain overflow columns. |
|
||||||
| High | `malformed_csv` | `docs/IMPLEMENTATION_ISSUE_REGISTER_v1.4.csv` | 43 | Expected 9 columns, found 11. |
|
| High | `malformed_csv` | `docs/IMPLEMENTATION_ISSUE_REGISTER_v1.4.csv` | 43 | Expected 9 columns, found 11. |
|
||||||
| High | `malformed_csv` | `docs/quality/PHASE_14_MANUAL_QA_RESULTS_v1.0.csv` | 11 | Expected 11 columns, found 10. |
|
| High | `malformed_csv` | `docs/quality/PHASE_14_MANUAL_QA_RESULTS_v1.0.csv` | 11 | Expected 11 columns, found 10. |
|
||||||
| Medium | `loose_content_type_match` | `src/` | | JSON API still uses a prefix media-type check. |
|
| Medium | `loose_content_type_match` | `src/` | | JSON API still uses a prefix media-type check. |
|
||||||
| Medium | `content_type_helper_not_used` | `src/app/api/demo/route.ts` | | Demo API does not use exact JSON media-type validation. |
|
| Medium | `content_type_helper_not_used` | `src/app/api/demo/route.ts` | | Demo API does not use exact JSON media-type validation. |
|
||||||
| Medium | `missing_exact_content_type_control` | `src/lib/security/content-type.ts` | | Exact application/json media-type helper is absent. |
|
| Medium | `missing_exact_content_type_control` | `src/lib/security/content-type.ts` | | Exact application/json media-type helper is absent. |
|
||||||
| Medium | `stale_theme_storage_key` | `tests/visual/component-system.visual.spec.ts` | | Visual test writes a storage key the application never reads. |
|
| Medium | `stale_theme_storage_key` | `tests/visual/component-system.visual.spec.ts` | | Visual test writes a storage key the application never reads. |
|
||||||
| Medium | `theme_storage_key_missing` | `tests/visual/component-system.visual.spec.ts` | | Visual test does not use the canonical theme storage key. |
|
| Medium | `theme_storage_key_missing` | `tests/visual/component-system.visual.spec.ts` | | Visual test does not use the canonical theme storage key. |
|
||||||
| Medium | `visual_project_scope` | `tests/visual/component-system.visual.spec.ts` | | Canonical snapshot test is not restricted to Chromium. |
|
| Medium | `visual_project_scope` | `tests/visual/component-system.visual.spec.ts` | | Canonical snapshot test is not restricted to Chromium. |
|
||||||
| Medium | `visual_project_scope` | `tests/visual/integrations.visual.spec.ts` | | Canonical integration snapshots are not restricted to Chromium. |
|
| Medium | `visual_project_scope` | `tests/visual/integrations.visual.spec.ts` | | Canonical integration snapshots are not restricted to Chromium. |
|
||||||
|
|
||||||
## Metrics
|
## Metrics
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1,6 @@
|
|||||||
{"command":"node scripts/run-validations.mjs","exit_code":0,"wall_seconds":1,"log":"docs/quality/phase15-command-logs/01-validate.log"}
|
{
|
||||||
|
"command": "node scripts/run-validations.mjs",
|
||||||
|
"exit_code": 0,
|
||||||
|
"wall_seconds": 1,
|
||||||
|
"log": "docs/quality/phase15-command-logs/01-validate.log"
|
||||||
|
}
|
||||||
|
|||||||
Vendored
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
/// <reference types="next" />
|
/// <reference types="next" />
|
||||||
/// <reference types="next/image-types/global" />
|
/// <reference types="next/image-types/global" />
|
||||||
import "./.next/dev/types/routes.d.ts";
|
import './.next/types/routes.d.ts';
|
||||||
|
|
||||||
// NOTE: This file should not be edited
|
// NOTE: This file should not be edited
|
||||||
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||||
|
|||||||
Generated
+2495
-899
File diff suppressed because it is too large
Load Diff
@@ -4,8 +4,20 @@ import path from 'node:path';
|
|||||||
|
|
||||||
const root = path.resolve(import.meta.dirname, '..');
|
const root = path.resolve(import.meta.dirname, '..');
|
||||||
const manifestPath = path.join(root, 'PHASE_16_PACKAGE_MANIFEST_v1.0.json');
|
const manifestPath = path.join(root, 'PHASE_16_PACKAGE_MANIFEST_v1.0.json');
|
||||||
const excludedDirectories = new Set(['.git', '.next', 'node_modules', 'coverage', 'playwright-report', 'test-results', '__pycache__']);
|
const excludedDirectories = new Set([
|
||||||
const excludedFiles = new Set(['PHASE_16_PACKAGE_MANIFEST_v1.0.json', 'package-lock.json', 'tsconfig.tsbuildinfo']);
|
'.git',
|
||||||
|
'.next',
|
||||||
|
'node_modules',
|
||||||
|
'coverage',
|
||||||
|
'playwright-report',
|
||||||
|
'test-results',
|
||||||
|
'__pycache__',
|
||||||
|
]);
|
||||||
|
const excludedFiles = new Set([
|
||||||
|
'PHASE_16_PACKAGE_MANIFEST_v1.0.json',
|
||||||
|
'package-lock.json',
|
||||||
|
'tsconfig.tsbuildinfo',
|
||||||
|
]);
|
||||||
|
|
||||||
async function walk(directory) {
|
async function walk(directory) {
|
||||||
const files = [];
|
const files = [];
|
||||||
@@ -26,7 +38,11 @@ const entries = [];
|
|||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
const contents = await readFile(file.fullPath);
|
const contents = await readFile(file.fullPath);
|
||||||
const details = await stat(file.fullPath);
|
const details = await stat(file.fullPath);
|
||||||
entries.push({ path: file.relative, bytes: details.size, sha256: createHash('sha256').update(contents).digest('hex') });
|
entries.push({
|
||||||
|
path: file.relative,
|
||||||
|
bytes: details.size,
|
||||||
|
sha256: createHash('sha256').update(contents).digest('hex'),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
const manifest = {
|
const manifest = {
|
||||||
schema_version: '1.0',
|
schema_version: '1.0',
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ const integrationVisual = await readFile(
|
|||||||
path.join(repositoryRoot, 'tests/visual/integrations.visual.spec.ts'),
|
path.join(repositoryRoot, 'tests/visual/integrations.visual.spec.ts'),
|
||||||
'utf8',
|
'utf8',
|
||||||
);
|
);
|
||||||
if (!componentVisual.includes("hpc.theme.preference")) {
|
if (!componentVisual.includes('hpc.theme.preference')) {
|
||||||
failures.push('Component visual test does not use the canonical theme storage key.');
|
failures.push('Component visual test does not use the canonical theme storage key.');
|
||||||
}
|
}
|
||||||
if (componentVisual.includes('rdg-theme-preference')) {
|
if (componentVisual.includes('rdg-theme-preference')) {
|
||||||
|
|||||||
@@ -3,7 +3,15 @@ import path from 'node:path';
|
|||||||
import { fail, pass, repositoryRoot, sha256 } from './lib.mjs';
|
import { fail, pass, repositoryRoot, sha256 } from './lib.mjs';
|
||||||
|
|
||||||
const manifestName = 'PHASE_16_PACKAGE_MANIFEST_v1.0.json';
|
const manifestName = 'PHASE_16_PACKAGE_MANIFEST_v1.0.json';
|
||||||
const excludedDirectories = new Set(['.git', '.next', 'node_modules', 'coverage', 'playwright-report', 'test-results', '__pycache__']);
|
const excludedDirectories = new Set([
|
||||||
|
'.git',
|
||||||
|
'.next',
|
||||||
|
'node_modules',
|
||||||
|
'coverage',
|
||||||
|
'playwright-report',
|
||||||
|
'test-results',
|
||||||
|
'__pycache__',
|
||||||
|
]);
|
||||||
const excludedFiles = new Set([manifestName, 'package-lock.json', 'tsconfig.tsbuildinfo']);
|
const excludedFiles = new Set([manifestName, 'package-lock.json', 'tsconfig.tsbuildinfo']);
|
||||||
|
|
||||||
async function walk(directory) {
|
async function walk(directory) {
|
||||||
@@ -22,25 +30,41 @@ async function walk(directory) {
|
|||||||
|
|
||||||
const manifest = JSON.parse(await readFile(path.join(repositoryRoot, manifestName), 'utf8'));
|
const manifest = JSON.parse(await readFile(path.join(repositoryRoot, manifestName), 'utf8'));
|
||||||
const failures = [];
|
const failures = [];
|
||||||
if (manifest.project !== 'RentalDriveGo' || manifest.phase !== 16) failures.push('Phase 16 manifest identity is invalid.');
|
if (manifest.project !== 'RentalDriveGo' || manifest.phase !== 16)
|
||||||
if (manifest.release_status !== 'Launch aborted before deployment') failures.push('Manifest release status is not the recorded Phase 16 decision.');
|
failures.push('Phase 16 manifest identity is invalid.');
|
||||||
if (!Array.isArray(manifest.files) || manifest.file_count !== manifest.files.length) failures.push('Manifest file count is invalid.');
|
if (manifest.release_status !== 'Launch aborted before deployment')
|
||||||
|
failures.push('Manifest release status is not the recorded Phase 16 decision.');
|
||||||
|
if (!Array.isArray(manifest.files) || manifest.file_count !== manifest.files.length)
|
||||||
|
failures.push('Manifest file count is invalid.');
|
||||||
const listed = new Set();
|
const listed = new Set();
|
||||||
for (const entry of manifest.files ?? []) {
|
for (const entry of manifest.files ?? []) {
|
||||||
if (typeof entry.path !== 'string' || path.isAbsolute(entry.path) || entry.path.split('/').includes('..')) {
|
if (
|
||||||
failures.push(`Unsafe manifest path: ${String(entry.path)}`); continue;
|
typeof entry.path !== 'string' ||
|
||||||
|
path.isAbsolute(entry.path) ||
|
||||||
|
entry.path.split('/').includes('..')
|
||||||
|
) {
|
||||||
|
failures.push(`Unsafe manifest path: ${String(entry.path)}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (listed.has(entry.path)) {
|
||||||
|
failures.push(`${entry.path}: duplicate manifest entry.`);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
if (listed.has(entry.path)) { failures.push(`${entry.path}: duplicate manifest entry.`); continue; }
|
|
||||||
listed.add(entry.path);
|
listed.add(entry.path);
|
||||||
try {
|
try {
|
||||||
const filePath = path.join(repositoryRoot, entry.path);
|
const filePath = path.join(repositoryRoot, entry.path);
|
||||||
const details = await stat(filePath);
|
const details = await stat(filePath);
|
||||||
if (!details.isFile() || details.size !== entry.bytes) failures.push(`${entry.path}: file metadata changed.`);
|
if (!details.isFile() || details.size !== entry.bytes)
|
||||||
|
failures.push(`${entry.path}: file metadata changed.`);
|
||||||
if ((await sha256(filePath)) !== entry.sha256) failures.push(`${entry.path}: SHA-256 changed.`);
|
if ((await sha256(filePath)) !== entry.sha256) failures.push(`${entry.path}: SHA-256 changed.`);
|
||||||
} catch { failures.push(`${entry.path}: file is missing.`); }
|
} catch {
|
||||||
|
failures.push(`${entry.path}: file is missing.`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const actual = new Set(await walk(repositoryRoot));
|
const actual = new Set(await walk(repositoryRoot));
|
||||||
for (const file of actual) if (!listed.has(file)) failures.push(`${file}: unlisted repository file.`);
|
for (const file of actual)
|
||||||
for (const file of listed) if (!actual.has(file)) failures.push(`${file}: listed but excluded or absent.`);
|
if (!listed.has(file)) failures.push(`${file}: unlisted repository file.`);
|
||||||
|
for (const file of listed)
|
||||||
|
if (!actual.has(file)) failures.push(`${file}: listed but excluded or absent.`);
|
||||||
if (failures.length) fail(failures);
|
if (failures.length) fail(failures);
|
||||||
else pass(`Phase 16 package manifest verifies ${listed.size} repository files with no extras.`);
|
else pass(`Phase 16 package manifest verifies ${listed.size} repository files with no extras.`);
|
||||||
|
|||||||
@@ -16,16 +16,51 @@ const required = [
|
|||||||
];
|
];
|
||||||
const failures = [];
|
const failures = [];
|
||||||
for (const file of required) {
|
for (const file of required) {
|
||||||
try { await access(path.join(repositoryRoot, file)); }
|
try {
|
||||||
catch { failures.push(`Missing required Phase 16 artifact: ${file}`); }
|
await access(path.join(repositoryRoot, file));
|
||||||
|
} catch {
|
||||||
|
failures.push(`Missing required Phase 16 artifact: ${file}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const status = await readFile(path.join(repositoryRoot, 'docs/phase16/FINAL_LAUNCH_STATUS_v1.0.md'), 'utf8');
|
const status = await readFile(
|
||||||
const auth = await readFile(path.join(repositoryRoot, 'docs/phase16/LAUNCH_AUTHORIZATION_RECORD_v1.0.md'), 'utf8');
|
path.join(repositoryRoot, 'docs/phase16/FINAL_LAUNCH_STATUS_v1.0.md'),
|
||||||
const flags = await readFile(path.join(repositoryRoot, 'docs/phase16/FEATURE_FLAG_RELEASE_STATE_v1.0.csv'), 'utf8');
|
'utf8',
|
||||||
const secrets = await readFile(path.join(repositoryRoot, 'docs/phase16/SECRET_PROVISIONING_RECORD_v1.0.csv'), 'utf8');
|
);
|
||||||
if (!status.includes('Launch aborted before deployment')) failures.push('Final launch status is missing or inconsistent.');
|
const auth = await readFile(
|
||||||
if (!auth.includes('**Rejected**')) failures.push('Launch authorization is not explicitly rejected.');
|
path.join(repositoryRoot, 'docs/phase16/LAUNCH_AUTHORIZATION_RECORD_v1.0.md'),
|
||||||
if (/\btrue\b/.test(flags.split('\n').slice(1).map((line) => line.split(',')[1]).join('\n'))) failures.push('A Phase 16 production feature flag is enabled.');
|
'utf8',
|
||||||
if (!secrets.includes('No production credentials exist') && !secrets.includes('No credential supplied')) failures.push('Secret provisioning record does not explicitly preserve missing credentials as blockers.');
|
);
|
||||||
|
const flags = await readFile(
|
||||||
|
path.join(repositoryRoot, 'docs/phase16/FEATURE_FLAG_RELEASE_STATE_v1.0.csv'),
|
||||||
|
'utf8',
|
||||||
|
);
|
||||||
|
const secrets = await readFile(
|
||||||
|
path.join(repositoryRoot, 'docs/phase16/SECRET_PROVISIONING_RECORD_v1.0.csv'),
|
||||||
|
'utf8',
|
||||||
|
);
|
||||||
|
if (!status.includes('Launch aborted before deployment'))
|
||||||
|
failures.push('Final launch status is missing or inconsistent.');
|
||||||
|
if (!auth.includes('**Rejected**'))
|
||||||
|
failures.push('Launch authorization is not explicitly rejected.');
|
||||||
|
if (
|
||||||
|
/\btrue\b/.test(
|
||||||
|
flags
|
||||||
|
.split('\n')
|
||||||
|
.slice(1)
|
||||||
|
.map((line) => line.split(',')[1])
|
||||||
|
.join('\n'),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
failures.push('A Phase 16 production feature flag is enabled.');
|
||||||
|
if (
|
||||||
|
!secrets.includes('No production credentials exist') &&
|
||||||
|
!secrets.includes('No credential supplied')
|
||||||
|
)
|
||||||
|
failures.push(
|
||||||
|
'Secret provisioning record does not explicitly preserve missing credentials as blockers.',
|
||||||
|
);
|
||||||
if (failures.length) fail(failures);
|
if (failures.length) fail(failures);
|
||||||
else pass('Phase 16 no-go governance, disabled feature state, blocked approvals, and evidence package are explicit and internally consistent.');
|
else
|
||||||
|
pass(
|
||||||
|
'Phase 16 no-go governance, disabled feature state, blocked approvals, and evidence package are explicit and internally consistent.',
|
||||||
|
);
|
||||||
|
|||||||
+15
-7
@@ -66,14 +66,14 @@
|
|||||||
--duration-standard: 160ms;
|
--duration-standard: 160ms;
|
||||||
--duration-overlay: 220ms;
|
--duration-overlay: 220ms;
|
||||||
--easing-standard: ease;
|
--easing-standard: ease;
|
||||||
--easing-productive: cubic-bezier(.2,0,0,1);
|
--easing-productive: cubic-bezier(0.2, 0, 0, 1);
|
||||||
--font-latin: Inter Variable, Inter, Arial, sans-serif;
|
--font-latin: Inter Variable, Inter, Arial, sans-serif;
|
||||||
--font-arabic: Noto Sans Arabic, Arial, sans-serif;
|
--font-arabic: Noto Sans Arabic, Arial, sans-serif;
|
||||||
--breakpoint-medium: 768px;
|
--breakpoint-medium: 768px;
|
||||||
--breakpoint-wide: 1120px;
|
--breakpoint-wide: 1120px;
|
||||||
}
|
}
|
||||||
|
|
||||||
html[data-theme="light"] {
|
html[data-theme='light'] {
|
||||||
--color-canvas: var(--color-gray-25);
|
--color-canvas: var(--color-gray-25);
|
||||||
--color-surface: #ffffff;
|
--color-surface: #ffffff;
|
||||||
--color-surface-raised: #ffffff;
|
--color-surface-raised: #ffffff;
|
||||||
@@ -91,10 +91,10 @@ html[data-theme="light"] {
|
|||||||
--color-on-inverse: #f8fafc;
|
--color-on-inverse: #f8fafc;
|
||||||
--color-soft-blue: #eef5ff;
|
--color-soft-blue: #eef5ff;
|
||||||
--color-soft-orange: #fff7ed;
|
--color-soft-orange: #fff7ed;
|
||||||
--shadow-color: rgba(8,20,38,.12);
|
--shadow-color: rgba(8, 20, 38, 0.12);
|
||||||
color-scheme: light;
|
color-scheme: light;
|
||||||
}
|
}
|
||||||
html[data-theme="dark"] {
|
html[data-theme='dark'] {
|
||||||
--color-canvas: var(--color-navy-950);
|
--color-canvas: var(--color-navy-950);
|
||||||
--color-surface: var(--color-navy-900);
|
--color-surface: var(--color-navy-900);
|
||||||
--color-surface-raised: var(--color-navy-850);
|
--color-surface-raised: var(--color-navy-850);
|
||||||
@@ -112,11 +112,19 @@ html[data-theme="dark"] {
|
|||||||
--color-on-inverse: var(--color-navy-950);
|
--color-on-inverse: var(--color-navy-950);
|
||||||
--color-soft-blue: #10284a;
|
--color-soft-blue: #10284a;
|
||||||
--color-soft-orange: #3a2015;
|
--color-soft-orange: #3a2015;
|
||||||
--shadow-color: rgba(0,0,0,.36);
|
--shadow-color: rgba(0, 0, 0, 0.36);
|
||||||
color-scheme: dark;
|
color-scheme: dark;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (prefers-reduced-motion: reduce) {
|
@media (prefers-reduced-motion: reduce) {
|
||||||
html { scroll-behavior: auto; }
|
html {
|
||||||
*, *::before, *::after { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; }
|
scroll-behavior: auto;
|
||||||
|
}
|
||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
animation-duration: 0.01ms !important;
|
||||||
|
animation-iteration-count: 1 !important;
|
||||||
|
transition-duration: 0.01ms !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,13 +10,10 @@ describe('demo API content type validation', () => {
|
|||||||
expect(isApplicationJson(value)).toBe(true);
|
expect(isApplicationJson(value)).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it.each([
|
it.each([null, '', 'text/plain', 'application/jsonp', 'application/json-patch+json'])(
|
||||||
null,
|
'rejects %s',
|
||||||
'',
|
(value) => {
|
||||||
'text/plain',
|
expect(isApplicationJson(value)).toBe(false);
|
||||||
'application/jsonp',
|
},
|
||||||
'application/json-patch+json',
|
);
|
||||||
])('rejects %s', (value) => {
|
|
||||||
expect(isApplicationJson(value)).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user