Portfolio/src/components/ds/LaurelDivider.vue
Matthew L McPeak f03f34f12e
Some checks failed
ci / build (push) Successful in 38s
ci / publish (push) Failing after 8s
Initial Portfolio (#1)
Reviewed-on: #1
Co-authored-by: Matthew L McPeak <m.mcpeak98@icloud.com>
Co-committed-by: Matthew L McPeak <m.mcpeak98@icloud.com>
2026-06-11 16:21:00 -04:00

152 lines
4.4 KiB
Vue

<script setup lang="ts">
import { computed, onMounted, onBeforeUnmount, ref } from 'vue'
const props = withDefaults(
defineProps<{
vertical?: boolean
label?: string
height?: number
size?: number
}>(),
{
vertical: false,
height: 14,
size: 18,
},
)
// 16-leaf radial wreath, used as the vertical divider's center ornament
const radialLeaves = Array.from({ length: 16 }, (_, i) => {
const angle = i * 22.5
const isB = i % 2 === 1
return isB
? {
path: 'M340,72 C350,80 352,97 343,113 C339,108 332,94 335,77 Z',
line: { x1: 340, y1: 75, x2: 343, y2: 111 },
transform: `rotate(${angle},340,180)`,
}
: {
path: 'M340,70 C350,78 352,96 343,112 C339,107 332,93 335,76 Z',
line: { x1: 340, y1: 73, x2: 343, y2: 110 },
transform: `rotate(${angle},340,180)`,
}
})
// Running laurel: the loader's Type-A leaf rotated 90° CW and tiled, fitting
// only whole leaves across the container width (no clipped edge leaves).
const wrapRef = ref<HTMLElement | null>(null)
const uid = `ld${Math.random().toString(36).slice(2, 8)}`
const svgW = ref(800)
let ro: ResizeObserver | null = null
onMounted(() => {
if (!wrapRef.value) return
svgW.value = Math.floor(wrapRef.value.getBoundingClientRect().width)
if (typeof ResizeObserver === 'undefined') return
ro = new ResizeObserver(([entry]) => {
if (entry) svgW.value = Math.floor(entry.contentRect.width)
})
ro.observe(wrapRef.value)
})
onBeforeUnmount(() => {
ro?.disconnect()
})
const h = computed(() => props.height)
const k = computed(() => (h.value * 0.7) / 20)
const tileW = computed(() => Math.round(42 * k.value))
const cx = computed(() => tileW.value / 2)
const cy = computed(() => h.value * 0.55)
const tf = (x: number, y: number): [number, number] => [
cx.value + (y - 91) * k.value,
cy.value - (x - 340) * k.value,
]
const rawPts: Array<[number, number]> = [
[340, 70],
[350, 78],
[352, 96],
[343, 112],
[339, 107],
[332, 93],
[335, 76],
]
const f = (n: number) => n.toFixed(1)
const pts = computed(() => rawPts.map(([x, y]) => tf(x, y)))
const leafPath = computed(() => {
const p = pts.value
return [
`M${f(p[0]![0])} ${f(p[0]![1])}`,
`C${f(p[1]![0])} ${f(p[1]![1])} ${f(p[2]![0])} ${f(p[2]![1])} ${f(p[3]![0])} ${f(p[3]![1])}`,
`C${f(p[4]![0])} ${f(p[4]![1])} ${f(p[5]![0])} ${f(p[5]![1])} ${f(p[6]![0])} ${f(p[6]![1])}`,
'Z',
].join(' ')
})
const numTiles = computed(() => Math.max(1, Math.floor(svgW.value / tileW.value)))
const usedW = computed(() => numTiles.value * tileW.value)
const offsetX = computed(() => (svgW.value - usedW.value) / 2)
</script>
<template>
<div v-if="vertical" class="nych-divider--vertical" role="separator">
<svg
viewBox="226 66 228 228"
:width="size"
:height="size"
fill="currentColor"
stroke="currentColor"
aria-hidden="true"
style="flex-shrink: 0"
>
<g v-for="(leaf, i) in radialLeaves" :key="i" :transform="leaf.transform">
<path :d="leaf.path" />
<line
:x1="leaf.line.x1"
:y1="leaf.line.y1"
:x2="leaf.line.x2"
:y2="leaf.line.y2"
stroke-width="0.8"
stroke-linecap="round"
/>
</g>
</svg>
</div>
<div v-else class="nych-divider" role="separator" :data-p="label ? 'label' : undefined">
<span v-if="label" class="nych-divider-label">{{ label }}</span>
<div v-else ref="wrapRef" style="width: 100%; height: 100%">
<svg :width="svgW" :height="h" aria-hidden="true">
<defs>
<pattern
:id="uid"
:x="offsetX"
y="0"
:width="tileW"
:height="h"
patternUnits="userSpaceOnUse"
>
<path :d="leafPath" fill="currentColor" opacity="0.85" />
<line
:x1="f(pts[0]![0])"
:y1="f(pts[0]![1])"
:x2="f(pts[3]![0])"
:y2="f(pts[3]![1])"
stroke="currentColor"
stroke-width="0.5"
stroke-linecap="round"
opacity="0.4"
/>
</pattern>
<clipPath :id="`${uid}c`">
<rect :x="offsetX" y="0" :width="usedW" :height="h" />
</clipPath>
</defs>
<rect :width="svgW" :height="h" :fill="`url(#${uid})`" :clip-path="`url(#${uid}c)`" />
</svg>
</div>
</div>
</template>