feat(ui): add /cv route, site-wide lowercase, no-cookies footer
reproduces the legacy cv (previously at grenade.github.io/cv) as a react-router /cv route, fetched at runtime from the same gist. moves the lowercase aesthetic from per-element overrides to a single body- level rule so a future toggle can flip it from one place. adds a small site-wide footer noting why no cookie consent banner is shown. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
66
ui/src/components/cv/CvSection.tsx
Normal file
66
ui/src/components/cv/CvSection.tsx
Normal file
@@ -0,0 +1,66 @@
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import Card from 'react-bootstrap/Card';
|
||||
import { type CvSectionConfig, type GistFile } from '../../api/cv';
|
||||
import { entryAnchorId } from '../../lib/cvDates';
|
||||
|
||||
interface Props {
|
||||
section: CvSectionConfig;
|
||||
files: GistFile[];
|
||||
}
|
||||
|
||||
// Pipe-delimited fields (e.g. "email | phone | github, linkedin" in the
|
||||
// contact section) become one paragraph per field, so each lands on its own
|
||||
// line with a paragraph gap. Within each pipe-segment, comma-separated values
|
||||
// are stacked with a soft line break (markdown ` \n` -> `<br/>`) so multiple
|
||||
// emails / phones / urls each get their own line at a tighter spacing.
|
||||
function splitPipes(content: string): string {
|
||||
return content
|
||||
.split('\n')
|
||||
.map((line) => {
|
||||
if (!line.includes(' | ')) return line;
|
||||
return line
|
||||
.split(' | ')
|
||||
.map((segment) =>
|
||||
segment.includes(', ') ? segment.split(', ').join(' \n') : segment,
|
||||
)
|
||||
.join('\n\n');
|
||||
})
|
||||
.join('\n');
|
||||
}
|
||||
|
||||
// Renders a single section. Each .md file becomes its own block. When
|
||||
// `show_section_name` is true (e.g. experience, education) the entries are
|
||||
// wrapped in cards and given anchor ids so the timeline sidebar can deep-link
|
||||
// to them; otherwise (e.g. summary, contact) they render as flat markdown.
|
||||
export function CvSection({ section, files }: Props) {
|
||||
return (
|
||||
<section id={section.name} className="cv-section mb-4">
|
||||
{section.show_section_name && <h2 className="cv-section-name">{section.name}</h2>}
|
||||
{files.map((file) => {
|
||||
const content = section.show_section_name
|
||||
? file.content
|
||||
: splitPipes(file.content);
|
||||
if (section.show_section_name) {
|
||||
return (
|
||||
<div
|
||||
key={file.filename}
|
||||
id={entryAnchorId(section.name, file.content)}
|
||||
className="cv-entry mb-3"
|
||||
>
|
||||
<Card className="cv-card">
|
||||
<Card.Body>
|
||||
<ReactMarkdown>{content}</ReactMarkdown>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div key={file.filename} className="cv-entry">
|
||||
<ReactMarkdown>{content}</ReactMarkdown>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user