Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 | 59x 177x 177x 280x 280x 280x 55x 40x 71x 59x 55x 70x 70x 70x 70x 70x 70x 70x 70x 280x 70x | import { useMemo } from "react";
import { Box, Chip } from "@mui/material";
import { alpha, useTheme } from "@mui/material/styles";
import { generateClassCardShapes } from "./generateClassCardShapes";
function trianglePoints(cx, cy, r) {
return [-90, 30, 150]
.map((deg) => {
const rad = (deg * Math.PI) / 180;
return `${cx + r * Math.cos(rad)},${cy + r * Math.sin(rad)}`;
})
.join(" ");
}
function BannerShape({ shape, color, fillOpacity, strokeOpacity }) {
const { type, cx, cy, r, rotate } = shape;
const transform = `rotate(${rotate} ${cx} ${cy})`;
switch (type) {
case "ring":
return (
<circle
cx={cx}
cy={cy}
r={r}
fill="none"
stroke={color}
strokeOpacity={strokeOpacity}
strokeWidth={1.5}
/>
);
case "square":
return (
<rect
x={cx - r * 0.75}
y={cy - r * 0.75}
width={r * 1.5}
height={r * 1.5}
fill={color}
opacity={fillOpacity}
transform={transform}
/>
);
case "squareOutline":
return (
<rect
x={cx - r * 0.75}
y={cy - r * 0.75}
width={r * 1.5}
height={r * 1.5}
fill="none"
stroke={color}
strokeOpacity={strokeOpacity}
strokeWidth={1.5}
transform={transform}
/>
);
case "triangle":
return (
<polygon points={trianglePoints(cx, cy, r)} fill={color} opacity={fillOpacity} transform={transform} />
);
case "circle":
default:
return <circle cx={cx} cy={cy} r={r} fill={color} opacity={fillOpacity} />;
}
}
// No per-class cover image exists in the class model, so this banner is
// generated instead: a flat accent-color fill plus a crisp (no blur, no
// gradient) scatter of shapes computed continuously from `seed` — the same
// seed always renders the same layout, and distinct seeds render distinct
// layouts. Shared by the teacher and student class card grids.
export default function ClassCardBanner({ seed, accentMain, label, labelId, ariaLabel, height = 120 }) {
const theme = useTheme();
const isLight = theme.palette.mode !== "dark";
const shapes = useMemo(() => generateClassCardShapes(seed), [seed]);
// Light mode: a mostly-white banner — a ~20% tint of the accent color
// (the rest reads as white, since that's what's behind this Box) instead
// of a fully-saturated flat fill. The shape scatter switches from white
// to the accent color itself so it stays visible against the now-pale
// background. Dark mode is unaffected — a saturated fill with white
// shapes already reads correctly there.
const bannerBg = isLight ? alpha(accentMain, 0.2) : accentMain;
const shapeColor = isLight ? accentMain : "#fff";
const fillOpacity = isLight ? 0.4 : 0.16;
const strokeOpacity = isLight ? 0.6 : 0.35;
return (
<Box role="img" aria-label={ariaLabel} sx={{ position: "relative", height, bgcolor: bannerBg }}>
<Box
component="svg"
viewBox="0 0 100 100"
preserveAspectRatio="none"
sx={{ width: "100%", height: "100%" }}
>
{shapes.map((s, i) => (
<BannerShape
key={i}
shape={s}
color={shapeColor}
fillOpacity={fillOpacity}
strokeOpacity={strokeOpacity}
/>
))}
</Box>
<Chip
label={label}
size="small"
id={labelId}
sx={{
position: "absolute",
top: 12,
right: 12,
bgcolor: (t) => alpha(t.palette.background.paper, 0.92),
color: "text.primary",
fontWeight: 700,
fontSize: "0.64rem",
height: 22,
backdropFilter: "blur(4px)",
}}
/>
</Box>
);
}
|