Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
243 changes: 125 additions & 118 deletions package-lock.json

Large diffs are not rendered by default.

57 changes: 36 additions & 21 deletions src/components/MitigationCard.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@ import styles from "./MitigationCard.module.css";

export default function MitigationCard({ group, active, accent, t }) {
const [open, setOpen] = useState(false);

return (
<div
className={`${styles.card} ${active ? "" : styles.cardInactive}`}
style={{
border: `2px solid ${active ? accent : "var(--border-subtle)"}`,
background: active ? `${accent}10` : "var(--bg-main)",
border: `1px solid ${active ? accent : "var(--border-subtle)"}`,
background: active ? `${accent}10` : "transparent",
}}
>
<div
Expand All @@ -18,34 +19,48 @@ export default function MitigationCard({ group, active, accent, t }) {
style={{ cursor: active ? "pointer" : "default" }}
>
<div className={styles.headerInfo}>
<span className={styles.icon}>{group.icon}</span>
<span className={styles.icon} style={{ opacity: active ? 1 : 0.35 }}>
{active ? group.icon : "🔒"}
</span>
<div>
<div className={styles.title} style={{ color: active ? "var(--text-heading)" : "var(--text-secondary)" }}>
<div
className={styles.title}
style={{
color: active ? "var(--text-heading)" : "var(--text-secondary)",
opacity: active ? 1 : 0.5,
}}
>
{group.title}
</div>
<div className={styles.subtitle}>
{group.measures.length} {group.measures.length !== 1 ? t.measures : t.measure}
<div className={styles.subtitle} style={{ opacity: active ? 1 : 0.4 }}>
{active
? `${group.measures.length} ${group.measures.length !== 1 ? t.measures : t.measure}`
: `Unlocks at Tier ${group.tier}`}
</div>
</div>
</div>
{active && <span className={`${styles.chevron} ${open ? styles.chevronOpen : ""}`}>▾</span>}
</div>
{active && open && (
<div className={styles.measures}>
{group.measures.map((m, i) => {
const tc = TYPE_COLORS[m.type];
return (
<div key={i} className={styles.measure} style={{ borderLeft: `3px solid ${tc.color}` }}>
<div className={styles.measureHeader}>
<span className={styles.measureName}>{m.name}</span>
<span className={styles.typeBadge} style={{ color: tc.color, background: tc.bg }}>
{t.typeBadges[m.type]}
</span>

{/* Always rendered — grid-template-rows animates open/close smoothly */}
{active && (
<div className={`${styles.measuresWrapper} ${open ? styles.measuresWrapperOpen : ""}`}>
<div className={styles.measures}>
{group.measures.map((m, i) => {
const tc = TYPE_COLORS[m.type];
return (
<div key={i} className={styles.measure} style={{ borderLeft: `3px solid ${tc.color}` }}>
<div className={styles.measureHeader}>
<span className={styles.measureName}>{m.name}</span>
<span className={styles.typeBadge} style={{ color: tc.color, background: tc.bg }}>
{t.typeBadges[m.type]}
</span>
</div>
<div className={styles.measureDesc}>{m.desc}</div>
</div>
<div className={styles.measureDesc}>{m.desc}</div>
</div>
);
})}
);
})}
</div>
</div>
)}
</div>
Expand Down
71 changes: 59 additions & 12 deletions src/components/MitigationCard.module.css
Original file line number Diff line number Diff line change
@@ -1,11 +1,23 @@
.card {
border-radius: 12px;
padding: 12px 14px;
transition: all 0.3s;
border-radius: 10px;
padding: 10px 14px;
transition:
border-color 0.25s ease,
background 0.25s ease,
box-shadow 0.25s ease,
transform 0.25s cubic-bezier(0.34, 1.2, 0.64, 1);
}

.card:not(.cardInactive):hover {
transform: translateY(-2px) scale(1.01);
box-shadow: 0 6px 24px -4px rgba(0, 0, 0, 0.25);
border-width: 2px !important;
filter: brightness(1.08);
}

.cardInactive {
opacity: 0.5;
padding: 7px 14px;
border-radius: 8px;
}

.header {
Expand All @@ -21,40 +33,75 @@
}

.icon {
font-size: 24px;
font-size: 20px;
transition: opacity 0.3s ease;
}

.title {
font-weight: 700;
font-size: 18px;
font-weight: 600;
font-size: 16px;
transition:
color 0.3s ease,
opacity 0.3s ease;
}

.subtitle {
font-size: 13px;
font-size: 12px;
color: var(--text-secondary);
transition: opacity 0.3s ease;
}

.chevron {
font-size: 22px;
font-size: 20px;
color: var(--text-secondary);
transition: transform 0.2s;
transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
}

.chevronOpen {
transform: rotate(180deg);
}

/* ── Smooth expand/collapse ──────────────────────────────────────────────── */
/*
grid-template-rows trick: animating from 0fr → 1fr smoothly collapses
and expands content of any height without needing to know it in advance.
The inner div needs overflow:hidden to clip during animation.
*/

.measuresWrapper {
max-height: 0;
overflow: hidden;
transition: max-height 0.6s cubic-bezier(0.4, 0, 0.2, 1);
}

.measuresWrapperOpen {
max-height: 600px;
transition: max-height 0.8s cubic-bezier(0.4, 0, 0.2, 1);
}

.measures {
margin-top: 10px;
display: flex;
flex-direction: column;
gap: 6px;
padding-bottom: 10px;
}

.measure {
background: var(--bg-card);
border-radius: 8px;
padding: 8px 10px;
transition:
box-shadow 0.2s ease,
transform 0.2s cubic-bezier(0.34, 1.2, 0.64, 1),
background 0.2s ease;
}

.measure:hover {
transform: translateX(3px);
box-shadow: 0 2px 12px -2px rgba(0, 0, 0, 0.2);
background: var(--bg-sidebar);
filter: brightness(1.1);
}

.measureHeader {
Expand All @@ -67,7 +114,7 @@

.measureName {
font-weight: 600;
font-size: 16px;
font-size: 15px;
color: var(--text-primary);
}

Expand All @@ -81,7 +128,7 @@
}

.measureDesc {
font-size: 15px;
font-size: 14px;
color: var(--text-secondary);
margin-top: 3px;
line-height: 1.4;
Expand Down
Loading
Loading