diff --git a/src/app.rs b/src/app.rs
index ce345a7..93c3f7b 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -945,7 +945,16 @@ fn AlternativeCard(
let name = feature.name.clone();
move || !name.is_empty()
}>
-
{feature.name.clone()}
+
+ {match feature.link.clone() {
+ Some(href) => view! {
+
+ {feature.name.clone()}
+
+ }.into_any(),
+ None => feature.name.clone().into_any(),
+ }}
+
,
+ /// An https URL the card's name links to - an announcement's
+ /// programme page, a venue. Plain-text descriptions can't carry
+ /// links, so this is the one place a card points elsewhere.
+ #[serde(default)]
+ pub link: Option,
#[serde(default)]
pub requirements: Vec,
/// Live data this feature pulls in. Read-only unless `transitions`
@@ -1017,6 +1022,16 @@ pub fn validate_questions(
}
}
for alternative in &question.alternatives {
+ for feature in &alternative.features {
+ if let Some(link) = &feature.link {
+ if !link.starts_with("https://") {
+ anyhow::bail!(
+ "question {:?} feature {:?}: link {:?} must be an https:// URL",
+ question.id, feature.name, link
+ );
+ }
+ }
+ }
// A dangling action is a literal dead end: the submit
// button navigates to "Nothing here".
if let Some(action) = &alternative.action {
diff --git a/style/main.css b/style/main.css
index bca5bdd..3f79551 100644
--- a/style/main.css
+++ b/style/main.css
@@ -448,6 +448,17 @@ main.not-found {
margin-bottom: 0.15em;
}
+.feature h3 a {
+ color: inherit;
+ text-decoration: none;
+ border-bottom: 0.06rem solid var(--line);
+}
+
+.feature h3 a:hover {
+ color: var(--accent);
+ border-bottom-color: var(--accent);
+}
+
.feature p {
color: var(--ink-dim);
font-size: 0.95rem;