diff --git a/.gitignore b/.gitignore index 3139398..cf5a2ef 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,4 @@ CLAUDE.md node_modules/ dist/ npm-debug.log* +.superpowers/ diff --git a/docs/superpowers/specs/2026-06-12-terminal-hero-design.md b/docs/superpowers/specs/2026-06-12-terminal-hero-design.md new file mode 100644 index 0000000..70dfa76 --- /dev/null +++ b/docs/superpowers/specs/2026-06-12-terminal-hero-design.md @@ -0,0 +1,48 @@ +# Terminal Hero — Design + +## Goal + +Replace the cropped square photo hero on the home page with a macOS-style +terminal window that presents the portrait photo at its natural 2:3 ratio and +absorbs the `about/` section, so the home page is a single coherent element. + +## Concept + +The home page becomes one terminal session with two commands: + +1. `~ $ cd davidbingmann/.` is typed (reusing the existing typing-animation + idea), then the "output" fades in: photo (left, natural 2:3, ~220px wide) + and a neofetch-style fact sheet (right): name, role, `study`/`uni`/`work` + key-value lines (keys in accent yellow), social icon links. +2. `~/davidbingmann $ cat about.txt` is typed, then the existing about prose + fades in as command output, including the mail link. +3. A final empty prompt `~/davidbingmann $` with a blinking block cursor. + +The separate `about/` section below the hero is removed — its facts live in +the fact sheet, its prose in the `cat about.txt` output. + +## Visuals + +- Whole page stays in the existing dark theme (`#0b0b0b`, already hardcoded + via `data-theme="dark"`). +- Terminal window: slightly lighter surface (`#141414`), 1px border + (`#2a2c27`), 12px radius, title bar with macOS traffic lights and + `david@bingmann.de — zsh` title. On dark backgrounds the window separates + via surface + border (shadows don't read). +- Colors via `--term-*` CSS variables with a light-theme fallback so the + light palette keeps working if the theme is ever switched back. +- Prompt cwd in green, `$` muted, fact keys in yellow (`#febc2e`). + +## Behavior + +- Animation sequence: type cmd 1 → profile output fades in → type cmd 2 → + about output fades in → final blinking prompt. ~90ms per character, + short pauses between stages. +- On small screens (≤720px) and `prefers-reduced-motion`: everything renders + immediately (same policy as the current hero). +- Mobile layout: photo and fact sheet stack vertically inside the terminal. + +## Out of scope + +- Nav, footer, projects/resume/impressum pages stay unchanged. +- No new dependencies. diff --git a/src/data/projects.js b/src/data/projects.js index 0e26a77..b47ea61 100644 --- a/src/data/projects.js +++ b/src/data/projects.js @@ -1,30 +1,13 @@ export const projects = [ - { - slug: 'paper-humanoid-robots-industry-5', - title: 'Paper', - listTitle: 'Paper', - headline: 'What potential do humanoid robots offer in Industry 5.0?', - tags: ['industry-5.0', 'humanoid-robots', 'ai', 'paper'], - repo: { - prefix: 'PDF', - label: 'davidbingmann/paper', - href: '/papers/paper.pdf', - }, - summary: - 'My paper looks at whether humanoid robots can support the shift from Industry 4.0 to Industry 5.0: more resilient, more human-centered, and better suited to real factories.', - body: [ - 'As part of a seminar at the University of Trier, I wrote this paper on humanoid robots in the context of Industry 5.0: not as a replacement for humans, but as a possible way to make industrial work more resilient, flexible, and human-centered.', - 'The paper discusses why current automation still struggles in human-designed factories, what recent robot learning research makes possible, and where the technical, economic, and regulatory limits still are. You can read the public PDF version above.', - ], - }, { slug: 'echotype', title: 'EchoType', + type: 'software', headline: 'Speak your prompts instead of typing.', tags: ['agents', 'ai', 'productivity', 'voice'], repo: { - label: 'davidbingmann/EchoType', href: 'https://github.com/davidbingmann/EchoType', + linkText: 'github.com/davidbingmann/EchoType', }, summary: 'I was looking for a faster and more intuitive way to write prompts for AI agents like Codex or Claude Code, especially longer prompts that are tedious to type.', @@ -36,4 +19,21 @@ export const projects = [ 'If you want to install EchoType, you can find the installation instructions in the GitHub repository.', ], }, + { + slug: 'paper-humanoid-robots-industry-5', + title: 'Humanoid Robots in Industry 5.0', + type: 'paper', + headline: 'What potential do humanoid robots offer in Industry 5.0?', + tags: ['industry-5.0', 'humanoid-robots', 'ai', 'paper'], + repo: { + href: '/papers/paper.pdf', + linkText: 'paper.pdf', + }, + summary: + 'My paper looks at whether humanoid robots can support the shift from Industry 4.0 to Industry 5.0: more resilient, more human-centered, and better suited to real factories.', + body: [ + 'As part of a seminar at the University of Trier, I wrote this paper on humanoid robots in the context of Industry 5.0: not as a replacement for humans, but as a possible way to make industrial work more resilient, flexible, and human-centered.', + 'The paper discusses why current automation still struggles in human-designed factories, what recent robot learning research makes possible, and where the technical, economic, and regulatory limits still are. You can read the public PDF version above.', + ], + }, ]; diff --git a/src/data/timeline.js b/src/data/timeline.js index 85ae512..8ff5d23 100644 --- a/src/data/timeline.js +++ b/src/data/timeline.js @@ -1,32 +1,34 @@ export const timelineItems = [ { - year: 'October 2025 → Present', - title: 'Switch to University of Trier', - description: - 'Switch to the University of Trier to study Business Informatics and Artificial Intelligence', + period: 'Oct 2025 → present', + title: 'University of Trier', + description: 'B.Sc. Business Informatics & Artificial Intelligence', + category: 'education', + current: true, }, { - year: 'October 2024 → Present', - title: 'Research Assistant at DFKI', + period: 'Oct 2024 → present', + title: 'Research Assistant — DFKI', description: - 'Start of my part-time job at the German Research Center for Artificial Intelligence (DFKI) alongside my studies', + 'German Research Center for Artificial Intelligence, part-time alongside my studies', + category: 'work', + current: true, }, { - year: 'October 2024', - title: "Start of my Bachelor's degree", - description: - "Start of my Bachelor's degree: Artificial Intelligence and Data Science at the University of Applied Sciences in Trier", + period: 'Oct 2024 → Sep 2025', + title: 'University of Applied Sciences Trier', + description: 'B.Sc. Artificial Intelligence & Data Science', + category: 'education', }, { - year: 'May 2024', - title: 'Internship at Tesla Automation', - description: - 'Internship from 27/05/2024 until 07/06/2024 in Controls Engineering', + period: 'May → Jun 2024', + title: 'Intern — Tesla Automation', + description: 'Controls Engineering', + category: 'internship', }, { - year: 'March 2024', - title: 'Abitur', - description: - 'Finished my Abitur at the Staatliches Eifelgymnasium in Neuerburg', + period: 'Mar 2024', + title: 'Abitur — Staatliches Eifelgymnasium Neuerburg', + category: 'education', }, ]; diff --git a/src/pages/Home.jsx b/src/pages/Home.jsx index 7fc5b96..b03f03a 100644 --- a/src/pages/Home.jsx +++ b/src/pages/Home.jsx @@ -1,155 +1,210 @@ -import { useEffect, useRef, useState } from 'react'; +import { useEffect, useState } from 'react'; +import { FaEnvelope, FaGithub, FaLinkedinIn } from 'react-icons/fa'; +import { FaXTwitter } from 'react-icons/fa6'; import profilePicture800 from '../assets/profile_picture_800.jpeg'; import profilePicture1200 from '../assets/profile_picture_1200.jpeg'; import profilePictureFull from '../assets/profile_picture.jpeg'; -export default function Home() { - const fullPrompt = '~ cd'; - const fullPath = 'davidbingmann/.'; - const totalLength = fullPrompt.length + fullPath.length; - - const [reduceMotion, setReduceMotion] = useState(() => - typeof window !== 'undefined' && - window.matchMedia('(prefers-reduced-motion: reduce)').matches - ); - const [isSmallScreen, setIsSmallScreen] = useState(() => - typeof window !== 'undefined' && window.matchMedia('(max-width: 720px)').matches - ); - const disableHeroAnimation = reduceMotion || isSmallScreen; - - const [typedCount, setTypedCount] = useState(() => - disableHeroAnimation ? totalLength : 0 +const COMMAND_CD = 'cd davidbingmann/.'; +const COMMAND_CAT = 'cat about.txt'; +const TYPE_INTERVAL = 90; + +const STAGE = { + TYPING_CD: 0, + PROFILE: 1, + TYPING_CAT: 2, + ABOUT: 3, + DONE: 4, +}; + +// Module-level so the intro only plays on the first visit per page load, +// not every time the user navigates back to home via the tabs. +let introPlayed = false; + +const socialLinks = [ + { + label: 'LinkedIn', + href: 'https://www.linkedin.com/in/david-bingmann-13b897293/', + Icon: FaLinkedinIn, + }, + { label: 'Mail', href: 'mailto:contact@davidbingmann.de', Icon: FaEnvelope }, + { label: 'GitHub', href: 'https://github.com/davidbingmann', Icon: FaGithub }, + { label: 'X', href: 'https://x.com/dxv1d04', Icon: FaXTwitter }, +]; + +function useMediaQuery(query) { + const [matches, setMatches] = useState( + () => typeof window !== 'undefined' && window.matchMedia(query).matches ); - const [hidePrompt, setHidePrompt] = useState(() => disableHeroAnimation); - const promptRef = useRef(null); - const [promptWidth, setPromptWidth] = useState(0); - - const promptText = fullPrompt.slice(0, Math.min(typedCount, fullPrompt.length)); - const pathText = - typedCount > fullPrompt.length - ? fullPath.slice(0, typedCount - fullPrompt.length) - : ''; useEffect(() => { - document.title = 'David Bingmann'; - }, []); + const mediaQuery = window.matchMedia(query); + const handleChange = (event) => setMatches(event.matches); - useEffect(() => { - const motionQuery = window.matchMedia('(prefers-reduced-motion: reduce)'); - const handleMotionChange = (event) => setReduceMotion(event.matches); + setMatches(mediaQuery.matches); + mediaQuery.addEventListener('change', handleChange); + return () => mediaQuery.removeEventListener('change', handleChange); + }, [query]); - setReduceMotion(motionQuery.matches); - if (motionQuery.addEventListener) { - motionQuery.addEventListener('change', handleMotionChange); - return () => motionQuery.removeEventListener('change', handleMotionChange); - } + return matches; +} - motionQuery.addListener(handleMotionChange); - return () => motionQuery.removeListener(handleMotionChange); - }, []); +function useTerminalTimeline(instant) { + const [stage, setStage] = useState(instant ? STAGE.DONE : STAGE.TYPING_CD); + const [typedCd, setTypedCd] = useState(instant ? COMMAND_CD.length : 0); + const [typedCat, setTypedCat] = useState(instant ? COMMAND_CAT.length : 0); useEffect(() => { - const screenQuery = window.matchMedia('(max-width: 720px)'); - const handleScreenChange = (event) => setIsSmallScreen(event.matches); - - setIsSmallScreen(screenQuery.matches); - if (screenQuery.addEventListener) { - screenQuery.addEventListener('change', handleScreenChange); - return () => screenQuery.removeEventListener('change', handleScreenChange); + if (instant) { + setStage(STAGE.DONE); + setTypedCd(COMMAND_CD.length); + setTypedCat(COMMAND_CAT.length); + return undefined; } - screenQuery.addListener(handleScreenChange); - return () => screenQuery.removeListener(handleScreenChange); - }, []); - - useEffect(() => { - if (disableHeroAnimation) { - setTypedCount(totalLength); - setHidePrompt(true); + let delay; + let action; + + if (stage === STAGE.TYPING_CD && typedCd < COMMAND_CD.length) { + delay = TYPE_INTERVAL; + action = () => setTypedCd((count) => count + 1); + } else if (stage === STAGE.TYPING_CD) { + delay = 400; + action = () => setStage(STAGE.PROFILE); + } else if (stage === STAGE.PROFILE) { + delay = 900; + action = () => setStage(STAGE.TYPING_CAT); + } else if (stage === STAGE.TYPING_CAT && typedCat < COMMAND_CAT.length) { + delay = TYPE_INTERVAL; + action = () => setTypedCat((count) => count + 1); + } else if (stage === STAGE.TYPING_CAT) { + delay = 400; + action = () => setStage(STAGE.ABOUT); + } else if (stage === STAGE.ABOUT) { + delay = 500; + action = () => setStage(STAGE.DONE); + } else { return undefined; } - const timer = setInterval(() => { - setTypedCount((current) => { - if (current >= totalLength) { - return current; - } - return current + 1; - }); - }, 150); + const timer = setTimeout(action, delay); + return () => clearTimeout(timer); + }, [instant, stage, typedCd, typedCat]); - return () => { - clearInterval(timer); - }; - }, [disableHeroAnimation, totalLength]); + return { stage, typedCd, typedCat }; +} - useEffect(() => { - if (disableHeroAnimation) { - return undefined; - } +function Reveal({ open, children }) { + return ( +
+
{children}
+
+ ); +} - if (typedCount === totalLength) { - const timer = setTimeout(() => setHidePrompt(true), 500); - return () => clearTimeout(timer); - } - return undefined; - }, [disableHeroAnimation, typedCount, totalLength]); +export default function Home() { + const reduceMotion = useMediaQuery('(prefers-reduced-motion: reduce)'); + const [skipIntro] = useState(() => introPlayed); + const { stage, typedCd, typedCat } = useTerminalTimeline( + reduceMotion || skipIntro + ); useEffect(() => { - if (disableHeroAnimation) { - return undefined; - } - - if (promptText.length === fullPrompt.length && promptRef.current) { - setPromptWidth(promptRef.current.getBoundingClientRect().width); - } - return undefined; - }, [disableHeroAnimation, promptText, fullPrompt.length]); + introPlayed = true; + document.title = 'David Bingmann'; + }, []); return (
-
- Portrait of David Bingmann -
-
-
- - {promptText} - - {pathText} -
-
Bachelor’s student
+
+
+ + + + david@bingmann.de — zsh
-
- -
-

about/

-
-

- My name is David Bingmann and I am currently studying Business - Informatics & Artificial Intelligence at the University of Trier. -

-

- In addition to my studies, I work as a research assistant at the - German Research Center for Artificial Intelligence (DFKI). -

-

- Feel free to check out my social media channels or write me a{' '} - mail. I look forward to - connecting! +

+

+ ~{' '} + ${' '} + {COMMAND_CD.slice(0, typedCd)} + {stage === STAGE.TYPING_CD && }

+ + = STAGE.PROFILE}> +
+ Portrait of David Bingmann +
+

David Bingmann

+

Bachelor’s student

+
+

+ Study + Business Informatics & AI +

+

+ Uni + University of Trier +

+

+ Work + Research assistant @ DFKI +

+
+ {socialLinks.map(({ label, href, Icon }) => ( + + + ))} +
+
+
+
+ + = STAGE.TYPING_CAT}> +

+ ~/davidbingmann{' '} + ${' '} + {COMMAND_CAT.slice(0, typedCat)} + {stage === STAGE.TYPING_CAT && ( + + )} +

+
+ + = STAGE.ABOUT}> +

+ My name is David Bingmann and I am currently studying Business + Informatics & Artificial Intelligence at the University of + Trier. In addition to my studies, I work as a research assistant + at the German Research Center for Artificial Intelligence (DFKI). + Feel free to check out my social media channels or write me a{' '} + mail — I look + forward to connecting! +

+
+ + = STAGE.DONE}> +

+ ~/davidbingmann{' '} + ${' '} + +

+
diff --git a/src/pages/Impressum.jsx b/src/pages/Impressum.jsx index 735ca1b..7675d69 100644 --- a/src/pages/Impressum.jsx +++ b/src/pages/Impressum.jsx @@ -7,7 +7,10 @@ export default function Impressum() { return (
-

imprint/

+
+

imprint/

+ legal +
{project.title}
-

{project.title}

+
+

+ {project.title} +

+ {project.type} +
-
+

{project.headline}

-

- - {project.repo.prefix ?? 'GitHub'}: {project.repo.label} +

+ + →{' '} + {project.repo.linkText}

- {project.body.map((paragraph) => ( -

{paragraph}

+ {project.body.map((paragraph, index) => ( +

+ {paragraph} +

))}
diff --git a/src/pages/Projects.jsx b/src/pages/Projects.jsx index 2c4af12..c0d905b 100644 --- a/src/pages/Projects.jsx +++ b/src/pages/Projects.jsx @@ -15,21 +15,31 @@ export default function Projects() { ))}
diff --git a/src/pages/Resume.jsx b/src/pages/Resume.jsx index 8787c03..a7412ff 100644 --- a/src/pages/Resume.jsx +++ b/src/pages/Resume.jsx @@ -9,16 +9,31 @@ export default function Resume() { return (

resume/

-
+
{timelineItems.map((item, index) => (
-

{item.year}

-

{item.title}

-

{item.description}

+ +
+ + {item.category} + +
+

+ {item.title} + {item.current && current} +

+ {item.period} +
+ {item.description && ( +

{item.description}

+ )} +
))}
diff --git a/src/styles.css b/src/styles.css index baa07e7..1ef9558 100644 --- a/src/styles.css +++ b/src/styles.css @@ -10,6 +10,15 @@ --accent: #2f2f2f; --shadow: rgba(21, 20, 18, 0.05); --hover: rgba(47, 47, 47, 0.08); + --term-bg: #ffffff; + --term-bar: #ecebe5; + --term-border: #e1dfd8; + --term-text: #1c1c1c; + --term-muted: #5f5f5f; + --term-key: #b07d10; + --term-green: #1f9e4b; + --term-blue: #2563a8; + --term-purple: #7a4fb5; } :root[data-theme='dark'] { @@ -23,6 +32,24 @@ --accent: #f5f3ee; --shadow: rgba(0, 0, 0, 0.35); --hover: rgba(245, 243, 238, 0.08); + --term-bg: #141414; + --term-bar: #1d1f1b; + --term-border: #2a2c27; + --term-text: #f5f3ee; + --term-muted: #b7b2a8; + --term-key: #febc2e; + --term-green: #28c840; + --term-blue: #7eb8f2; + --term-purple: #c89bf5; +} + +/* Space Mono draws § and parentheses as small raised glyphs that read as + broken superscripts; serve just these characters from a local mono font. */ +@font-face { + font-family: 'Space Mono Glyph Fix'; + src: local('Menlo'), local('Consolas'), local('DejaVu Sans Mono'), + local('Courier New'); + unicode-range: U+00A7, U+0028-0029; } * { @@ -37,7 +64,7 @@ body { margin: 0; min-height: 100vh; min-height: 100svh; - font-family: 'Space Mono', monospace; + font-family: 'Space Mono Glyph Fix', 'Space Mono', monospace; color: var(--text); background: var(--bg); overflow-x: hidden; @@ -115,88 +142,179 @@ p { padding-right: calc(var(--page-gutter) + env(safe-area-inset-right, 0px)); } -.hero { - width: auto; - max-width: min(100%, 760px); - aspect-ratio: 1 / 1; - container-type: inline-size; +.terminal { margin: 0 auto; - height: 70vh; - height: 70svh; - min-height: 420px; - background: #0b0b0b; - position: relative; + border: 1px solid var(--term-border); + border-radius: 12px; + background: var(--term-bg); + overflow: hidden; +} + +.terminal-bar { display: flex; - align-items: flex-end; - justify-content: center; - color: #ffffff; - border-radius: 18px; + align-items: center; + gap: 7px; + padding: 10px 14px; + background: var(--term-bar); + border-bottom: 1px solid var(--term-border); +} + +.terminal-dot { + width: 12px; + height: 12px; + border-radius: 50%; +} + +.terminal-dot--close { + background: #ff5f57; +} + +.terminal-dot--minimize { + background: #febc2e; +} + +.terminal-dot--zoom { + background: #28c840; +} + +.terminal-title { + margin: 0 auto; + font-size: 0.8rem; + color: var(--term-muted); +} + +.terminal-body { + padding: 1.8rem 1.9rem 1.9rem; + font-size: 1.15rem; + line-height: 1.75; + color: var(--term-text); +} + +.terminal-line { + margin: 0; + color: var(--term-text); +} + +.terminal-cwd { + color: var(--term-green); +} + +.terminal-dollar { + color: var(--term-muted); +} + +.terminal-cursor { + display: inline-block; + width: 0.6em; + height: 1.05em; + margin-left: 2px; + background: var(--term-text); + vertical-align: text-bottom; +} + +.terminal-cursor--blink { + animation: blink 1.1s steps(1) infinite; +} + +.terminal-reveal { + display: grid; + grid-template-rows: 0fr; + transition: grid-template-rows 0.5s ease; +} + +.terminal-reveal--open { + grid-template-rows: 1fr; +} + +.terminal-reveal-inner { + min-height: 0; overflow: hidden; + opacity: 0; + transition: opacity 0.45s ease 0.15s; } -.hero-image { - position: absolute; - inset: 0; - width: 100%; - height: 100%; - object-fit: cover; - object-position: 50% 20%; - pointer-events: none; +.terminal-reveal--open .terminal-reveal-inner { + opacity: 1; } -.hero-overlay { - position: absolute; - inset: 0; - background: linear-gradient(180deg, rgba(0, 0, 0, 0.15) 0%, rgba(0, 0, 0, 0.55) 70%); - z-index: 1; +.terminal-profile { + display: flex; + gap: 1.4rem; + margin: 1.1rem 0 1.3rem; } -.hero-content { - position: relative; - z-index: 2; - text-align: center; - padding: 0 1.5rem 2.5rem; - animation: fadeUp 0.8s ease forwards; +.terminal-photo { + width: min(290px, 40%); + aspect-ratio: 2 / 3; + object-fit: cover; + border-radius: 8px; + flex-shrink: 0; } -.hero-command { - display: inline-flex; - align-items: center; - font-size: clamp(2.2rem, 5vw, 3rem); - /* sized so the longest animation state "~ cd davidbingmann/." (20ch) never exceeds the hero */ - font-size: clamp(1.4rem, calc((100cqw - 3.5rem) / 12), 3rem); +.terminal-facts { + display: flex; + flex-direction: column; + min-width: 0; +} + +.terminal-name { + margin: 0; + font-size: 1.8rem; font-weight: 700; - line-height: 1.1; - text-shadow: 0 2px 12px rgba(0, 0, 0, 0.5); + color: var(--term-text); + line-height: 1.3; } -.hero-tagline { - margin-top: 0.85rem; - font-size: clamp(1.05rem, 2.2vw, 1.4rem); - font-weight: 600; - letter-spacing: 0.06em; - opacity: 0.9; - text-shadow: 0 2px 12px rgba(0, 0, 0, 0.5); +.terminal-role { + margin: 0.1rem 0 0; + color: var(--term-muted); +} + +.terminal-rule { + width: 100%; + margin: 0.8rem 0; + border: 0; + border-top: 1px solid var(--term-border); +} + +.terminal-fact { + margin: 0 0 0.35rem; + color: var(--term-text); } -.hero-command .prompt { +.terminal-key { display: inline-block; - overflow: hidden; - white-space: nowrap; - max-width: var(--prompt-width, 6ch); - margin-right: 1ch; - opacity: 0.9; - transition: max-width 0.35s ease, margin-right 0.35s ease, opacity 0.35s ease; + min-width: 7ch; + color: var(--term-key); + font-weight: 700; } -.hero-command .prompt--hidden { - max-width: 0; - margin-right: 0; - opacity: 0; +.terminal-socials { + display: flex; + gap: 1.1rem; + margin-top: auto; + padding-top: 1rem; } -.hero-command .path { - white-space: nowrap; +.terminal-socials a { + display: inline-flex; + color: var(--term-muted); + transition: color 0.2s ease; +} + +.terminal-social-icon { + width: 21px; + height: 21px; +} + +.terminal-about { + margin: 0.4rem 0 1.2rem; + color: var(--term-muted); +} + +.terminal-about a { + color: var(--term-text); + border-bottom: 1px solid var(--term-text); } .section { @@ -216,11 +334,14 @@ p { } .section-body { - background: var(--surface); - font-size: 1.2rem; - padding: 2rem; border: 1px solid var(--line); - box-shadow: 0 20px 40px var(--shadow); + border-radius: 10px; + background: var(--term-bg); + font-size: 1.05rem; + padding: 1.5rem 1.6rem; + opacity: 0; + transform: translateY(14px); + animation: fadeIn 0.5s ease forwards; } .section-body h2 { @@ -279,53 +400,44 @@ p { transform: translateY(0); } -.social-links { - display: flex; - flex-wrap: wrap; - gap: 1.5rem; - font-size: 0.95rem; - text-transform: lowercase; -} - -.social-links a { - color: var(--muted); - transition: color 0.2s ease; -} - .empty-note { padding: 2rem; border: 1px dashed var(--line); + border-radius: 10px; color: var(--muted); - background: var(--surface); + background: var(--term-bg); } .project-list { display: grid; - gap: 1.25rem; + gap: 1rem; } .project-card { position: relative; - font-size: 1.2rem; - padding: 1.7rem; border: 1px solid var(--line); - background: var(--surface); - box-shadow: 0 20px 40px var(--shadow); - display: grid; - gap: 0.9rem; + border-radius: 10px; + background: var(--term-bg); + padding: 1.15rem 1.3rem; + cursor: pointer; opacity: 0; - transform: translateY(12px); + transform: translateY(14px); animation: fadeIn 0.5s ease forwards; animation-delay: var(--delay); - cursor: pointer; transition: border-color 0.2s ease; } .project-card-title { - font-size: 1.35rem; + margin: 0; + font-size: 1.25rem; letter-spacing: 0.02em; } +.project-card-arrow { + color: var(--muted); + font-weight: 400; +} + .project-title-link::after { content: ''; position: absolute; @@ -333,27 +445,81 @@ p { } .project-card-headline { - margin: 0; + margin: 0.5rem 0 0; + font-size: 1rem; color: var(--text); font-weight: 700; } .project-card-summary { - margin: 0; + margin: 0.45rem 0 0; + font-size: 0.95rem; + line-height: 1.65; } -.project-card-meta { - margin: 0; - font-size: 0.9rem; - color: var(--muted); +.project-card-foot { + display: flex; + flex-wrap: wrap; + justify-content: space-between; + align-items: baseline; + gap: 0.35rem 1rem; + margin-top: 0.9rem; } -.project-card-meta a { +.project-card-link { position: relative; z-index: 1; - color: var(--muted); + font-size: 0.85rem; + color: var(--term-green); border-bottom: 1px solid transparent; - transition: color 0.2s ease, border-color 0.2s ease; + transition: border-color 0.2s ease; +} + +.project-card-link-arrow { + color: var(--muted); +} + +.project-card-tags { + font-size: 0.8rem; + color: var(--muted); + opacity: 0.75; +} + +.project-head { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 0.9rem; +} + +.project-head .tag { + margin-bottom: 0; +} + +.project-detail { + border: 1px solid var(--line); + border-radius: 10px; + background: var(--term-bg); + padding: 1.5rem 1.6rem; + font-size: 1.05rem; + opacity: 0; + transform: translateY(14px); + animation: fadeIn 0.5s ease forwards; +} + +.project-detail-link { + margin: 0 0 1.2rem; +} + +.project-detail-paragraph { + opacity: 0; + transform: translateY(12px); + animation: fadeIn 0.5s ease forwards; + animation-delay: var(--delay); +} + +.project-detail-paragraph:last-child { + margin-bottom: 0; } .project-breadcrumb { @@ -372,50 +538,129 @@ p { } .project-headline { - margin-top: 0; + margin: 0 0 0.9rem; color: var(--text); font-weight: 700; } -.timeline { +.resume-timeline { + position: relative; margin-top: 0.5rem; - padding-left: 1.5rem; - border-left: 1px solid var(--line); + padding-left: 1.6rem; display: grid; - gap: 1.8rem; + gap: 1rem; } -.timeline-item { +.resume-timeline::before { + content: ''; + position: absolute; + top: 0; + bottom: 0; + left: 0; + width: 1.5px; + background: var(--line); + transform: scaleY(0); + transform-origin: top; + animation: drawLine 1s ease forwards; +} + +.resume-item { position: relative; - padding-left: 1rem; opacity: 0; - transform: translateY(12px); + transform: translateY(14px); animation: fadeIn 0.5s ease forwards; animation-delay: var(--delay); } -.timeline-item::before { - content: ''; +.resume-dot { position: absolute; - left: -1.1rem; - top: 0.35rem; + left: calc(-1.6rem - 4px); + top: 1.35rem; width: 10px; height: 10px; border-radius: 50%; - background: var(--accent); + background: var(--muted); } -.timeline-year { - margin: 0 0 0.4rem; +.resume-dot--current { + background: var(--term-green); + animation: pulse 2s ease-out infinite; +} + +.resume-card { + border: 1px solid var(--line); + border-radius: 10px; + background: var(--term-bg); + padding: 1.15rem 1.3rem; +} + +.tag { + display: inline-block; + margin-bottom: 0.55rem; + padding: 0.1rem 0.6rem; + border: 1px solid var(--line); + border-radius: 999px; + font-size: 0.68rem; + letter-spacing: 0.1em; text-transform: uppercase; - letter-spacing: 0.15em; - font-size: 0.75rem; color: var(--muted); } -.timeline-item h3 { - margin-bottom: 0.4rem; - font-size: 1.3rem; +.tag--education, +.tag--paper { + color: var(--term-key); + border-color: color-mix(in srgb, var(--term-key) 40%, transparent); +} + +.tag--work, +.tag--software { + color: var(--term-blue); + border-color: color-mix(in srgb, var(--term-blue) 40%, transparent); +} + +.tag--internship { + color: var(--term-purple); + border-color: color-mix(in srgb, var(--term-purple) 40%, transparent); +} + +.resume-card-row { + display: flex; + flex-wrap: wrap; + justify-content: space-between; + align-items: baseline; + gap: 0.25rem 1rem; +} + +.resume-card-title { + margin: 0; + font-size: 1.15rem; + line-height: 1.5; +} + +.resume-badge { + display: inline-block; + margin-left: 0.6rem; + padding: 0.05rem 0.5rem; + border: 1px solid color-mix(in srgb, var(--term-green) 40%, transparent); + border-radius: 999px; + font-size: 0.68rem; + font-weight: 400; + color: var(--term-green); + vertical-align: 2px; + white-space: nowrap; +} + +.resume-period { + flex-shrink: 0; + color: var(--muted); + font-size: 0.85rem; + white-space: nowrap; +} + +.resume-card-description { + margin: 0.5rem 0 0; + font-size: 0.95rem; + line-height: 1.6; } .footer { @@ -482,14 +727,24 @@ p { border: 0; } -@keyframes fadeUp { - from { +@keyframes blink { + 50% { opacity: 0; - transform: translateY(20px); } +} + +@keyframes drawLine { to { - opacity: 1; - transform: translateY(0); + transform: scaleY(1); + } +} + +@keyframes pulse { + from { + box-shadow: 0 0 0 0 color-mix(in srgb, var(--term-green) 50%, transparent); + } + to { + box-shadow: 0 0 0 9px transparent; } } @@ -514,18 +769,17 @@ p { border-color: var(--text); } - .project-card-meta a:hover { - color: var(--text); - border-color: var(--text); + .project-card-link:hover { + border-color: var(--term-green); } - .project-breadcrumb-link:hover { - color: var(--text); - border-color: var(--text); + .terminal-socials a:hover { + color: var(--term-text); } - .social-links a:hover { + .project-breadcrumb-link:hover { color: var(--text); + border-color: var(--text); } .footer-impressum:hover { @@ -552,59 +806,80 @@ p { gap: 1.25rem 1.75rem; } - .hero { - height: 58vh; - height: 58svh; - min-height: 320px; - border-radius: 14px; + .terminal-body { + padding: 1.1rem 1rem 1.2rem; + font-size: 0.92rem; } - .hero-tagline { - margin-top: 0.75rem; - font-size: clamp(0.95rem, 4vw, 1.2rem); + .terminal-profile { + flex-direction: column; + align-items: center; + gap: 1rem; } - .hero-content { - animation: none; - padding: 0 1.25rem 2rem; + .terminal-photo { + width: min(240px, 64%); } - .hero-command .prompt { - transition: none; + .terminal-facts { + width: 100%; + } + + .terminal-name, + .terminal-role { + text-align: center; + } + + .terminal-socials { + justify-content: center; + } + + .terminal-name { + font-size: 1.3rem; } .section-body { font-size: 1rem; - padding: 1.5rem; + padding: 1.2rem 1.25rem; } .section-body h2 { font-size: 1.05rem; } - .timeline { - padding-left: 1rem; + .resume-timeline { + padding-left: 1.2rem; } - .timeline-item h3 { - font-size: 1.1rem; + .resume-dot { + left: calc(-1.2rem - 4px); } - .timeline-item { - opacity: 1; - transform: none; - animation: none; + .resume-card { + padding: 1rem 1.1rem; + } + + .resume-card-title { + font-size: 1.05rem; + } + + .resume-card-row { + flex-direction: column; + align-items: flex-start; + gap: 0.15rem; } .project-card { - font-size: 1rem; - opacity: 1; - transform: none; - animation: none; + padding: 1rem 1.1rem; } .project-card-title { - font-size: 1.2rem; + font-size: 1.15rem; + } + + .project-detail { + padding: 1.2rem 1.25rem; + font-size: 1rem; } .footer-impressum { @@ -618,17 +893,8 @@ p { --page-gutter: 0.95rem; } - .hero-tagline { - margin-top: 0.7rem; - } - - .hero-content { - padding-left: 1rem; - padding-right: 1rem; - } - - .hero-command .path { - white-space: nowrap; + .terminal-body { + font-size: 0.85rem; } .section {