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
9 changes: 9 additions & 0 deletions .coderabbit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,15 @@ reviews:
### Nuxt Config
- Module-driven architecture. Check module docs before modifying module options.

- path: "app/components/OgImage/**/*.vue"
instructions: |
OG image components are rendered by Satori (not a browser). Special rules:
- Use inline styles only (no Tailwind, no `<style>` blocks). Hardcode hex colors.
- Use `<img>` for photos, inline `<svg>` for icons (never `<Icon>`, `<NuxtImg>`, or data URI `<img>`).
- Set icon `width`/`height` both as HTML attributes AND in `:style`.
- CRITICAL: nuxt-og-image's flex plugin overrides `flexDirection` to `column` on any `<div>` whose children include block elements (div, p, ul, ol, li, blockquote, pre, hr, table, dl). To preserve `flexDirection: 'row'`, add `class="flex-row"` (any class containing "flex-") to the container. Use `<span>` instead of `<div>` for text inside row containers.
- The flex plugin also forces `flexWrap: 'wrap'` and `gap: '0.2em'` on elements without a class containing "flex-".

- path: "**/*.vue"
instructions: |
Verify: SFC block order, `<NuxtLink>` instead of `<a>`, `<NuxtImg>` instead of `<img>`, no nested links (stretched-link pattern).
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ _(excluding those listed in `./package.json`)_
**Assets & Materials Used:**<br>
_(including images & 3D models; mostly only those requiring attribution)_

- Image `avatar-thorsten-seyschab.webp` by [Franziska Kestel](https://franziskakestel.de/).
- Image `avatar-thorsten-seyschab.jpg` by [Franziska Kestel](https://franziskakestel.de/).

## License

Expand Down
43 changes: 6 additions & 37 deletions app/components/OgImage/OgImageHome.vue
Original file line number Diff line number Diff line change
Expand Up @@ -29,31 +29,8 @@ withDefaults(defineProps<{
overflow: 'hidden',
}"
>
<!-- Radial accent glow (top-right, behind avatar) -->
<div
:style="{
position: 'absolute',
top: '-80px',
right: '100px',
width: '700px',
height: '700px',
borderRadius: '50%',
background: 'radial-gradient(circle, rgba(0,220,130,0.25) 0%, transparent 65%)',
}"
/>

<!-- Secondary glow (bottom-left) -->
<div
:style="{
position: 'absolute',
bottom: '-200px',
left: '-100px',
width: '500px',
height: '500px',
borderRadius: '50%',
background: 'radial-gradient(circle, rgba(0,220,130,0.18) 0%, transparent 65%)',
}"
/>
<!-- Ambient glow effects -->
<OgImageGlow />

<!-- Subtle horizontal rule -->
<div
Expand Down Expand Up @@ -111,11 +88,12 @@ withDefaults(defineProps<{
borderRadius: '50%',
background: 'linear-gradient(135deg, #00dc82, #00c474)',
padding: '5px',
flexWrap: 'nowrap',
}"
>
<img
alt="Thorsten Seyschab"
src="/avatar-thorsten-seyschab.webp"
src="/avatar-thorsten-seyschab.jpg"
:style="{
width: '340px',
height: '340px',
Expand All @@ -125,16 +103,7 @@ withDefaults(defineProps<{
>
</div>

<!-- Footer (absolute to stay at bottom despite centered content) -->
<div
:style="{
position: 'absolute',
bottom: '60px',
left: '60px',
right: '60px',
}"
>
<OgImageFooter />
</div>
<!-- Footer -->
<OgImageFooter />
</div>
</template>
159 changes: 159 additions & 0 deletions app/components/OgImage/OgImageTalk.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
<script setup lang="ts">
/**
* OG image component for talk pages.
* Rendered by Satori (not a browser) - must use inline styles and hardcoded colors.
*/
defineProps<{
title?: string
event?: string
date?: string
location?: string
}>()

const iconColor = '#71717a'
</script>

<template>
<div
:style="{
width: '1200px',
height: '630px',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
backgroundColor: '#0a0a0b',
fontFamily: 'Inter, system-ui, sans-serif',
padding: '60px 80px',
position: 'relative',
overflow: 'hidden',
}"
Comment thread
toddeTV marked this conversation as resolved.
>
<!-- Ambient glow effects -->
<OgImageGlow />

<!-- Avatar - top right -->
<OgImageAvatar />

<!-- Main content -->
<div
:style="{
display: 'flex',
flexDirection: 'column',
flexWrap: 'nowrap',
maxWidth: '960px',
}"
>
<!-- Category label -->
<div
:style="{
fontSize: '16px',
fontWeight: 600,
color: '#00dc82',
letterSpacing: '0.08em',
textTransform: 'uppercase',
marginBottom: '20px',
}"
>
Talk
</div>

<!-- Title -->
<div
:style="{
fontSize: '64px',
fontWeight: 800,
color: '#fafafa',
lineHeight: 1.05,
letterSpacing: '-0.03em',
marginBottom: '24px',
}"
>
{{ title || 'Conference Talk' }}
</div>

<!-- Event name -->
<div
v-if="event"
:style="{
fontSize: '26px',
fontWeight: 500,
color: '#a1a1aa',
lineHeight: 1.3,
}"
>
{{ event }}
</div>

<!-- Date and location row -->
<!--
class="flex-row" prevents nuxt-og-image's flex plugin from overriding
flexDirection to 'column' (it does so for any div containing div children
unless the parent has a class containing "flex-").
Text nodes use <span> to avoid being classified as block elements.
-->
<div
v-if="date || location"
class="flex-row"
:style="{
display: 'flex',
flexDirection: 'row',
flexWrap: 'nowrap',
alignItems: 'center',
marginTop: '20px',
}"
>
<!-- Phosphor calendar-blank icon (regular weight) -->
<svg
v-if="date"
height="18"
:style="{ width: '18px', height: '18px', marginRight: '8px' }"
viewBox="0 0 256 256"
width="18"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M208 32h-24v-8a8 8 0 0 0-16 0v8H88v-8a8 8 0 0 0-16 0v8H48a16 16 0 0 0-16 16v160a16 16 0 0 0 16 16h160a16 16 0 0 0 16-16V48a16 16 0 0 0-16-16M72 48v8a8 8 0 0 0 16 0v-8h80v8a8 8 0 0 0 16 0v-8h24v32H48V48Zm136 160H48V96h160z"
:fill="iconColor"
/>
</svg>
<span
v-if="date"
:style="{
fontSize: '18px',
color: '#71717a',
fontFamily: 'monospace',
marginRight: location ? '32px' : '0',
}"
>
{{ date }}
</span>
<!-- Phosphor map-pin icon (regular weight) -->
<svg
v-if="location"
height="18"
:style="{ width: '18px', height: '18px', marginRight: '8px' }"
viewBox="0 0 256 256"
width="18"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M128 64a40 40 0 1 0 40 40a40 40 0 0 0-40-40m0 64a24 24 0 1 1 24-24a24 24 0 0 1-24 24m0-112a88.1 88.1 0 0 0-88 88c0 31.4 14.51 64.68 42 96.25a254.2 254.2 0 0 0 41.45 38.3a8 8 0 0 0 9.18 0a254.2 254.2 0 0 0 41.37-38.3c27.45-31.57 42-64.85 42-96.25a88.1 88.1 0 0 0-88-88m0 206c-16.53-13-72-60.75-72-118a72 72 0 0 1 144 0c0 57.23-55.47 105-72 118"
:fill="iconColor"
/>
</svg>
<span
v-if="location"
:style="{
fontSize: '18px',
color: '#71717a',
}"
>
{{ location }}
</span>
</div>
</div>

<!-- Footer -->
<OgImageFooter />
</div>
</template>
34 changes: 34 additions & 0 deletions app/components/OgImage/components/OgImageAvatar.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<script setup lang="ts">
/**
* Shared avatar for non-Home OG image components.
* Renders a circular avatar with accent gradient border in the top-right corner.
* Satori constraints: inline styles only, no Tailwind, hardcoded colors.
*/
</script>

<template>
<div
:style="{
position: 'absolute',
top: '48px',
right: '80px',
width: '130px',
height: '130px',
borderRadius: '50%',
background: 'linear-gradient(135deg, #00dc82, #27272a)',
padding: '4px',
flexWrap: 'nowrap',
}"
>
<img
alt="Thorsten Seyschab"
src="/avatar-thorsten-seyschab.jpg"
:style="{
width: '120px',
height: '120px',
borderRadius: '50%',
objectFit: 'cover',
}"
>
</div>
</template>
65 changes: 38 additions & 27 deletions app/components/OgImage/components/OgImageFooter.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,44 +7,55 @@
</script>

<template>
<!-- Author name and site URL -->
<div
:style="{
display: 'flex',
flexWrap: 'nowrap',
justifyContent: 'space-between',
alignItems: 'flex-end',
position: 'absolute',
bottom: '60px',
left: '60px',
right: '60px',
}"
Comment thread
toddeTV marked this conversation as resolved.
>
<!-- Author name and site URL -->
<div
class="flex-row"
:style="{
fontSize: '24px',
fontWeight: 600,
color: '#00dc82',
display: 'flex',
flexDirection: 'row',
flexWrap: 'nowrap',
justifyContent: 'space-between',
alignItems: 'flex-end',
}"
>
Thorsten Seyschab
<span
:style="{
fontSize: '24px',
fontWeight: 600,
color: '#00dc82',
}"
>
Thorsten Seyschab
</span>
<span
:style="{
fontSize: '22px',
fontWeight: 400,
color: '#71717a',
}"
>
todde.tv
</span>
</div>
Comment thread
coderabbitai[bot] marked this conversation as resolved.

<!-- Bottom gradient accent line -->
<div
:style="{
fontSize: '22px',
fontWeight: 400,
color: '#71717a',
position: 'absolute',
bottom: '0',
left: '0',
right: '0',
height: '4px',
background: 'linear-gradient(90deg, #00dc82, #00c474, transparent)',
}"
>
todde.tv
</div>
/>
</div>

<!-- Bottom gradient accent line -->
<div
:style="{
position: 'absolute',
bottom: '0',
left: '0',
right: '0',
height: '4px',
background: 'linear-gradient(90deg, #00dc82, #00c474, transparent)',
}"
/>
</template>
Loading