1use dioxus::prelude::*;
16#[cfg(all(feature = "split", target_arch = "wasm32"))]
17use dioxus::wasm_split;
18use crate::ui::components::main_nav::{MainNav, ActivePage};
19use crate::ui::components::footer::Footer;
20use crate::ui::components::icon::{Icon, IconVariant, IconSize};
21use crate::ui::pages::roadmap_data::{get_milestones, Milestone, Status};
22use crate::ui::pages::roadmap_history::{get_history, news_slug_for, Release};
23use crate::ui::router::Route;
24use crate::ui::seo::{JsonLdMultiple, PageHead, organization_schema, roadmap_schema, breadcrumb_schema, BreadcrumbItem, pages as seo_pages};
25
26const ROADMAP_STYLE: &str = r#"
27.roadmap-container {
28 min-height: 100vh;
29 background: linear-gradient(135deg, #070a12 0%, #0b1022 50%, #070a12 100%);
30 color: #e5e7eb;
31 font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
32}
33
34.roadmap-nav {
35 position: sticky;
36 top: 0;
37 z-index: 50;
38 backdrop-filter: blur(18px);
39 background: linear-gradient(180deg, rgba(7,10,18,0.72), rgba(7,10,18,0.44));
40 border-bottom: 1px solid rgba(255,255,255,0.06);
41 padding: 16px 20px;
42}
43
44.roadmap-nav-inner {
45 max-width: 1000px;
46 margin: 0 auto;
47 display: flex;
48 align-items: center;
49 justify-content: space-between;
50}
51
52.roadmap-brand {
53 display: flex;
54 align-items: center;
55 gap: 12px;
56 text-decoration: none;
57 color: #e5e7eb;
58}
59
60.roadmap-logo {
61 width: 36px;
62 height: 36px;
63 border-radius: 12px;
64 background:
65 radial-gradient(circle at 30% 30%, rgba(96,165,250,0.85), transparent 55%),
66 radial-gradient(circle at 65% 60%, rgba(167,139,250,0.85), transparent 55%),
67 rgba(255,255,255,0.06);
68 border: 1px solid rgba(255,255,255,0.10);
69}
70
71.roadmap-brand-name {
72 font-weight: 800;
73 font-size: 14px;
74 letter-spacing: -0.5px;
75}
76
77.roadmap-back {
78 color: #a78bfa;
79 text-decoration: none;
80 font-size: 14px;
81 padding: 8px 16px;
82 border-radius: 8px;
83 border: 1px solid rgba(167,139,250,0.3);
84 transition: all 0.2s ease;
85}
86
87.roadmap-back:hover {
88 background: rgba(167,139,250,0.1);
89 border-color: rgba(167,139,250,0.5);
90}
91
92.roadmap-hero {
93 text-align: center;
94 padding: 60px 20px 40px;
95 max-width: 800px;
96 margin: 0 auto;
97}
98
99.roadmap-hero h1 {
100 font-size: 42px;
101 font-weight: 800;
102 letter-spacing: -1px;
103 background: linear-gradient(180deg, #ffffff 0%, rgba(229,231,235,0.78) 100%);
104 -webkit-background-clip: text;
105 -webkit-text-fill-color: transparent;
106 margin-bottom: 12px;
107}
108
109.roadmap-hero .version {
110 display: inline-block;
111 font-size: 14px;
112 padding: 6px 14px;
113 border-radius: 20px;
114 background: rgba(167,139,250,0.15);
115 border: 1px solid rgba(167,139,250,0.3);
116 color: #a78bfa;
117 margin-bottom: 16px;
118}
119
120.roadmap-hero p {
121 color: rgba(229,231,235,0.72);
122 font-size: 18px;
123 line-height: 1.6;
124}
125
126.timeline {
127 max-width: 700px;
128 margin: 0 auto;
129 padding: 0 20px 80px;
130 position: relative;
131}
132
133.timeline::before {
134 content: "";
135 position: absolute;
136 left: 28px;
137 top: 0;
138 bottom: 80px;
139 width: 3px;
140 background: linear-gradient(
141 180deg,
142 #22c55e 0%,
143 #22c55e 86%,
144 #a78bfa 90%,
145 #a78bfa 100%
146 );
147 border-radius: 2px;
148}
149
150.milestone {
151 position: relative;
152 padding-left: 70px;
153 margin-bottom: 40px;
154}
155
156.milestone-dot {
157 position: absolute;
158 left: 16px;
159 top: 4px;
160 width: 24px;
161 height: 24px;
162 border-radius: 50%;
163 display: flex;
164 align-items: center;
165 justify-content: center;
166 font-size: 12px;
167 font-weight: 600;
168}
169
170.milestone-dot.done {
171 background: linear-gradient(135deg, #22c55e, #16a34a);
172 box-shadow: 0 0 20px rgba(34,197,94,0.4);
173}
174
175.milestone-dot.progress {
176 background: linear-gradient(135deg, #a78bfa, #8b5cf6);
177 box-shadow: 0 0 20px rgba(167,139,250,0.4);
178 animation: pulse 2s ease-in-out infinite;
179}
180
181.milestone-dot.planned {
182 background: rgba(255,255,255,0.1);
183 border: 2px solid rgba(255,255,255,0.2);
184}
185
186@keyframes pulse {
187 0%, 100% { transform: scale(1); }
188 50% { transform: scale(1.1); }
189}
190
191.milestone-content {
192 background: rgba(255,255,255,0.03);
193 border: 1px solid rgba(255,255,255,0.08);
194 border-radius: 16px;
195 padding: 24px;
196 transition: all 0.2s ease;
197}
198
199.milestone-content:hover {
200 background: rgba(255,255,255,0.05);
201 border-color: rgba(255,255,255,0.12);
202}
203
204.milestone-header {
205 display: flex;
206 align-items: center;
207 gap: 12px;
208 margin-bottom: 12px;
209}
210
211.milestone-title {
212 font-size: 20px;
213 font-weight: 700;
214 color: #fff;
215}
216
217.milestone-badge {
218 font-size: 11px;
219 font-weight: 600;
220 padding: 4px 10px;
221 border-radius: 12px;
222 text-transform: uppercase;
223 letter-spacing: 0.5px;
224}
225
226.milestone-badge.done {
227 background: rgba(34,197,94,0.15);
228 color: #22c55e;
229 border: 1px solid rgba(34,197,94,0.3);
230}
231
232.milestone-badge.progress {
233 background: rgba(167,139,250,0.15);
234 color: #a78bfa;
235 border: 1px solid rgba(167,139,250,0.3);
236}
237
238.milestone-badge.planned {
239 background: rgba(255,255,255,0.05);
240 color: rgba(255,255,255,0.5);
241 border: 1px solid rgba(255,255,255,0.1);
242}
243
244.milestone-desc {
245 color: rgba(229,231,235,0.72);
246 font-size: 14px;
247 line-height: 1.6;
248 margin-bottom: 16px;
249}
250
251.milestone-features {
252 display: flex;
253 flex-wrap: wrap;
254 gap: 8px;
255}
256
257.feature-tag {
258 font-size: 12px;
259 padding: 6px 12px;
260 border-radius: 8px;
261 background: rgba(255,255,255,0.04);
262 border: 1px solid rgba(255,255,255,0.08);
263 color: rgba(229,231,235,0.8);
264}
265
266.feature-tag.done {
267 background: rgba(34,197,94,0.08);
268 border-color: rgba(34,197,94,0.2);
269 color: #86efac;
270}
271
272.roadmap-footer {
273 border-top: 1px solid rgba(255,255,255,0.06);
274 padding: 24px 20px;
275 text-align: center;
276 color: rgba(229,231,235,0.56);
277 font-size: 13px;
278}
279
280.roadmap-footer a {
281 color: rgba(229,231,235,0.72);
282 text-decoration: none;
283 margin: 0 8px;
284}
285
286.roadmap-footer a:hover {
287 color: #a78bfa;
288}
289
290.roadmap-nav-links {
291 display: flex;
292 align-items: center;
293 gap: 12px;
294}
295
296.roadmap-github {
297 display: flex;
298 align-items: center;
299 justify-content: center;
300 width: 36px;
301 height: 36px;
302 border-radius: 8px;
303 border: 1px solid rgba(255,255,255,0.1);
304 background: rgba(255,255,255,0.03);
305 color: rgba(229,231,235,0.72);
306 transition: all 0.2s ease;
307}
308
309.roadmap-github:hover {
310 background: rgba(255,255,255,0.08);
311 color: #e5e7eb;
312 border-color: rgba(255,255,255,0.2);
313}
314
315.roadmap-github svg {
316 width: 18px;
317 height: 18px;
318 fill: currentColor;
319}
320
321.github-link {
322 display: inline-flex;
323 align-items: center;
324 gap: 6px;
325}
326
327@media (max-width: 600px) {
328 .timeline::before {
329 left: 18px;
330 }
331 .milestone {
332 padding-left: 50px;
333 }
334 .milestone-dot {
335 left: 6px;
336 width: 20px;
337 height: 20px;
338 }
339 .milestone-title {
340 font-size: 18px;
341 }
342}
343
344.milestone-examples {
345 margin-top: 16px;
346 border-top: 1px solid rgba(255,255,255,0.06);
347 padding-top: 16px;
348}
349
350.milestone-tabs {
351 display: flex;
352 gap: 6px;
353 margin-bottom: 12px;
354 flex-wrap: wrap;
355}
356
357.milestone-tab {
358 padding: 6px 12px;
359 border-radius: 6px;
360 border: 1px solid rgba(255,255,255,0.1);
361 background: rgba(255,255,255,0.03);
362 color: #94a3b8;
363 cursor: pointer;
364 font-size: 12px;
365 font-weight: 500;
366 transition: all 0.2s ease;
367}
368
369.milestone-tab:hover {
370 background: rgba(255,255,255,0.08);
371 color: #e8e8e8;
372}
373
374.milestone-tab.active {
375 background: linear-gradient(135deg, #667eea, #764ba2);
376 color: white;
377 border-color: transparent;
378}
379
380.milestone-code {
381 background: rgba(0,0,0,0.25);
382 border-radius: 8px;
383 padding: 16px;
384 font-family: 'SF Mono', 'Fira Code', 'Consolas', monospace;
385 font-size: 13px;
386}
387
388.milestone-english {
389 color: #e8e8e8;
390 font-style: italic;
391 margin-bottom: 8px;
392 white-space: pre-wrap;
393 line-height: 1.5;
394}
395
396.milestone-english.source {
397 font-family: 'SF Mono', 'Fira Code', 'Consolas', monospace;
398 font-style: normal;
399 font-size: 12px;
400 color: #cbd5e1;
401}
402
403.milestone-arrow {
404 color: #667eea;
405 margin: 8px 0;
406 font-size: 16px;
407}
408
409.milestone-output {
410 color: #98c379;
411 white-space: pre-wrap;
412 line-height: 1.4;
413}
414
415.format-toggle {
416 display: flex;
417 gap: 4px;
418 margin-bottom: 8px;
419}
420
421.format-btn {
422 padding: 3px 8px;
423 border-radius: 4px;
424 border: 1px solid rgba(255,255,255,0.1);
425 background: rgba(255,255,255,0.03);
426 color: #64748b;
427 cursor: pointer;
428 font-size: 10px;
429 font-weight: 500;
430 text-transform: uppercase;
431 letter-spacing: 0.5px;
432 transition: all 0.15s ease;
433}
434
435.format-btn:hover {
436 background: rgba(255,255,255,0.06);
437 color: #94a3b8;
438}
439
440.format-btn.active {
441 background: rgba(102,126,234,0.2);
442 border-color: rgba(102,126,234,0.4);
443 color: #a5b4fc;
444}
445"#;
446
447#[component]
448fn MilestoneExamples(examples: &'static [crate::ui::pages::roadmap_data::Example]) -> Element {
449 use crate::ui::pages::roadmap_data::Output;
450 let mut active = use_signal(|| 0usize);
451 let mut use_unicode = use_signal(|| false);
452
453 let ex = examples[active()];
454
455 rsx! {
456 div { class: "milestone-examples",
457 div { class: "milestone-tabs",
458 for (i, e) in examples.iter().enumerate() {
459 button {
460 key: "{i}",
461 class: if active() == i { "milestone-tab active" } else { "milestone-tab" },
462 onclick: move |_| active.set(i),
463 "{e.label}"
464 }
465 }
466 }
467 div { class: "milestone-code",
468 {match ex.output {
469 Output::Fol { .. } => rsx! {
470 div { class: "milestone-english", "\"{ex.english}\"" }
471 },
472 _ => rsx! {
473 div { class: "milestone-english source", "{ex.english}" }
474 },
475 }}
476 div { class: "milestone-arrow", "↓" }
477 {match ex.output {
478 Output::Fol { simple, unicode } => rsx! {
479 div { class: "format-toggle",
480 button {
481 class: if !use_unicode() { "format-btn active" } else { "format-btn" },
482 onclick: move |_| use_unicode.set(false),
483 "Simple"
484 }
485 button {
486 class: if use_unicode() { "format-btn active" } else { "format-btn" },
487 onclick: move |_| use_unicode.set(true),
488 "Unicode"
489 }
490 }
491 div { class: "milestone-output",
492 if use_unicode() { "{unicode}" } else { "{simple}" }
493 }
494 },
495 Output::Rust(code) => rsx! {
496 div { class: "format-toggle",
497 span { class: "format-btn active", "Rust" }
498 }
499 div { class: "milestone-output", "{code}" }
500 },
501 Output::Sva(code) => rsx! {
502 div { class: "format-toggle",
503 span { class: "format-btn active", "SVA" }
504 }
505 div { class: "milestone-output", "{code}" }
506 },
507 }}
508 }
509 }
510 }
511}
512
513fn milestone_dot_icon(status: Status) -> IconVariant {
514 match status {
515 Status::Complete => IconVariant::Check,
516 Status::InProgress => IconVariant::Clock,
517 Status::Planned => IconVariant::Sparkles,
518 }
519}
520
521#[component]
522fn MilestoneItem(milestone: &'static Milestone) -> Element {
523 let status = milestone.status;
524 let dot_class = format!("milestone-dot {}", status.css_class());
525 let badge_class = format!("milestone-badge {}", status.css_class());
526 let tag_class = match status {
527 Status::Complete => "feature-tag done",
528 _ => "feature-tag",
529 };
530
531 rsx! {
532 div { class: "milestone",
533 div { class: "{dot_class}",
534 Icon { variant: milestone_dot_icon(status), size: IconSize::Small, color: "#fff" }
535 }
536 div { class: "milestone-content",
537 div { class: "milestone-header",
538 span { class: "milestone-title", "{milestone.title}" }
539 span { class: "{badge_class}", "{status.label()}" }
540 }
541 p { class: "milestone-desc", "{milestone.description}" }
542 div { class: "milestone-features",
543 for (i, feature) in milestone.features.iter().enumerate() {
544 span { key: "{i}", class: "{tag_class}", "{feature}" }
545 }
546 }
547 if !milestone.examples.is_empty() {
548 MilestoneExamples { examples: milestone.examples }
549 }
550 }
551 }
552 }
553}
554
555const RELEASE_HISTORY_STYLE: &str = r#"
556.roadmap-section-title {
557 max-width: 700px;
558 margin: 8px auto 18px;
559 padding: 0 20px;
560 font-size: 13px;
561 font-weight: 600;
562 text-transform: uppercase;
563 letter-spacing: 1px;
564 color: rgba(229,231,235,0.5);
565}
566
567.release-history {
568 max-width: 700px;
569 margin: 0 auto;
570 padding: 0 20px 80px;
571}
572
573.release-list {
574 display: flex;
575 flex-direction: column;
576 gap: 2px;
577}
578
579.release-row {
580 display: flex;
581 align-items: baseline;
582 gap: 14px;
583 padding: 10px 14px;
584 border-radius: 10px;
585 border: 1px solid transparent;
586 text-decoration: none;
587 color: inherit;
588 transition: background 0.15s ease, border-color 0.15s ease;
589}
590
591.release-row.linked {
592 cursor: pointer;
593}
594
595.release-row.linked:hover {
596 background: rgba(255,255,255,0.04);
597 border-color: rgba(255,255,255,0.08);
598}
599
600.release-version {
601 font-family: 'SF Mono', 'Fira Code', 'Consolas', monospace;
602 font-weight: 700;
603 font-size: 13px;
604 color: #a78bfa;
605 min-width: 64px;
606}
607
608.release-date {
609 color: rgba(229,231,235,0.45);
610 font-size: 12px;
611 min-width: 92px;
612 font-variant-numeric: tabular-nums;
613}
614
615.release-title {
616 flex: 1;
617 color: rgba(229,231,235,0.85);
618 font-size: 14px;
619}
620
621.release-unreleased {
622 font-size: 10px;
623 font-weight: 600;
624 text-transform: uppercase;
625 letter-spacing: 0.5px;
626 color: #a78bfa;
627 border: 1px solid rgba(167,139,250,0.3);
628 border-radius: 10px;
629 padding: 2px 8px;
630}
631
632.release-arrow {
633 color: rgba(229,231,235,0.25);
634 font-size: 14px;
635 transition: color 0.15s ease;
636}
637
638.release-row.linked:hover .release-arrow {
639 color: #a78bfa;
640}
641
642.release-row.maintenance {
643 opacity: 0.5;
644}
645
646.release-row.maintenance .release-version {
647 color: rgba(167,139,250,0.6);
648}
649
650.maint-toggle {
651 display: inline-block;
652 margin: 0 0 14px;
653 padding: 6px 12px;
654 border-radius: 8px;
655 border: 1px solid rgba(255,255,255,0.1);
656 background: rgba(255,255,255,0.03);
657 color: rgba(229,231,235,0.6);
658 cursor: pointer;
659 font-size: 12px;
660 font-weight: 500;
661 transition: background 0.15s ease, color 0.15s ease;
662}
663
664.maint-toggle:hover {
665 background: rgba(255,255,255,0.06);
666 color: #e5e7eb;
667}
668
669@media (max-width: 600px) {
670 .release-date { display: none; }
671}
672"#;
673
674#[component]
675fn ReleaseRowBody(release: &'static Release, unreleased: bool) -> Element {
676 rsx! {
677 span { class: "release-version", "v{release.version}" }
678 span { class: "release-date", "{release.date}" }
679 span { class: "release-title", "{release.title}" }
680 if unreleased {
681 span { class: "release-unreleased", "Unreleased" }
682 }
683 }
684}
685
686#[component]
687fn ReleaseRow(release: &'static Release, unreleased: bool) -> Element {
688 let maint = if release.maintenance { " maintenance" } else { "" };
689 match news_slug_for(&release.version) {
690 Some(slug) => rsx! {
691 Link {
692 to: Route::NewsArticle { slug: slug.to_string() },
693 class: "release-row linked{maint}",
694 ReleaseRowBody { release, unreleased }
695 span { class: "release-arrow", "→" }
696 }
697 },
698 None => rsx! {
699 div { class: "release-row{maint}",
700 ReleaseRowBody { release, unreleased }
701 }
702 },
703 }
704}
705
706#[component(lazy)]
707pub fn Roadmap() -> Element {
708 let breadcrumbs = vec![
709 BreadcrumbItem { name: "Home", path: "/" },
710 BreadcrumbItem { name: "Roadmap", path: "/roadmap" },
711 ];
712 let schemas = vec![
713 organization_schema(),
714 roadmap_schema(),
715 breadcrumb_schema(&breadcrumbs),
716 ];
717
718 let history = get_history();
722 let first_tagged = history.iter().position(|r| r.tagged).unwrap_or(history.len());
723 let maint_count = history.iter().filter(|r| r.maintenance).count();
724 let mut show_maint = use_signal(|| false);
725
726 rsx! {
727 PageHead {
728 title: seo_pages::ROADMAP.title,
729 description: seo_pages::ROADMAP.description,
730 canonical_path: seo_pages::ROADMAP.canonical_path,
731 }
732 style { "{ROADMAP_STYLE}" }
733 style { "{RELEASE_HISTORY_STYLE}" }
734 JsonLdMultiple { schemas }
735
736 div { class: "roadmap-container",
737 MainNav { active: ActivePage::Roadmap, subtitle: Some("Where we're headed") }
738
739 section { class: "roadmap-hero",
740 h1 { "LOGOS Roadmap" }
741 span { class: "version", "v0.10.0" }
742 p { "From English sentences to a verified compilation stack: first-order logic, a native execution tier, hardware model checking, and a kernel-certified proof core." }
743 }
744
745 h2 { class: "roadmap-section-title", "Featured milestones" }
746 div { class: "timeline",
747 for (i, milestone) in get_milestones().iter().enumerate() {
748 MilestoneItem { key: "{i}", milestone }
749 }
750 }
751
752 h2 { class: "roadmap-section-title", "Release history" }
753 section { class: "release-history",
754 if maint_count > 0 {
755 button {
756 class: "maint-toggle",
757 onclick: move |_| { let on = show_maint(); show_maint.set(!on); },
758 if show_maint() {
759 "Hide maintenance releases"
760 } else {
761 "Show {maint_count} maintenance releases (CI, benchmarks, tooling)"
762 }
763 }
764 }
765 div { class: "release-list",
766 for (i, release) in history.iter().enumerate() {
767 if !release.maintenance || show_maint() {
768 ReleaseRow { key: "{i}", release, unreleased: i < first_tagged }
769 }
770 }
771 }
772 }
773
774 Footer {}
775 }
776 }
777}