hero
This commit is contained in:
parent
230e54fcba
commit
d7626a050b
5 changed files with 121 additions and 122 deletions
|
|
@ -8,6 +8,7 @@ import { ContentBlock } from '@/blocks/Content/Component'
|
||||||
import { FormBlock } from '@/blocks/Form/Component'
|
import { FormBlock } from '@/blocks/Form/Component'
|
||||||
import { MediaBlock } from '@/blocks/MediaBlock/Component'
|
import { MediaBlock } from '@/blocks/MediaBlock/Component'
|
||||||
import { SkillsBlock } from '@/blocks/Skills/Component'
|
import { SkillsBlock } from '@/blocks/Skills/Component'
|
||||||
|
import { SkillsMarqueeBlock } from '@/blocks/SkillsMarquee/Component'
|
||||||
|
|
||||||
const blockComponents = {
|
const blockComponents = {
|
||||||
archive: ArchiveBlock,
|
archive: ArchiveBlock,
|
||||||
|
|
@ -16,6 +17,7 @@ const blockComponents = {
|
||||||
formBlock: FormBlock,
|
formBlock: FormBlock,
|
||||||
mediaBlock: MediaBlock,
|
mediaBlock: MediaBlock,
|
||||||
skills: SkillsBlock,
|
skills: SkillsBlock,
|
||||||
|
skillsMarquee: SkillsMarqueeBlock,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const RenderBlocks: React.FC<{
|
export const RenderBlocks: React.FC<{
|
||||||
|
|
|
||||||
96
src/blocks/SkillsMarquee/SkillsMarquee-Component.tsx
Normal file
96
src/blocks/SkillsMarquee/SkillsMarquee-Component.tsx
Normal file
|
|
@ -0,0 +1,96 @@
|
||||||
|
'use client'
|
||||||
|
|
||||||
|
import React from 'react'
|
||||||
|
import { cn } from '@/utilities/ui'
|
||||||
|
import { InfiniteSlider } from '@/components/ui/infinite-slider'
|
||||||
|
import { useThemeMode } from '@/hooks/useThemeMode'
|
||||||
|
|
||||||
|
const LOGOS = [
|
||||||
|
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/aftereffects-plain.svg', alt: 'aftereffects' },
|
||||||
|
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/almalinux-original.svg', alt: 'almalinux' },
|
||||||
|
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/amazonwebservices-original-wordmark.svg', alt: 'amazonwebservices' },
|
||||||
|
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/android-plain-wordmark.svg', alt: 'android' },
|
||||||
|
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/angularjs-plain-wordmark.svg', alt: 'angularjs' },
|
||||||
|
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/apache-line-wordmark.svg', alt: 'apache' },
|
||||||
|
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/apple-original.svg', alt: 'apple' },
|
||||||
|
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/azure-original-wordmark.svg', alt: 'azure' },
|
||||||
|
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/blender-original.svg', alt: 'blender' },
|
||||||
|
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/bootstrap-plain-wordmark.svg', alt: 'bootstrap' },
|
||||||
|
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/cloudflare-original-wordmark.svg', alt: 'cloudflare' },
|
||||||
|
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/cpanel-original-wordmark.svg', alt: 'cpanel' },
|
||||||
|
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/docker-plain-wordmark.svg', alt: 'docker' },
|
||||||
|
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/drupal-plain-wordmark.svg', alt: 'drupal' },
|
||||||
|
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/eslint-plain-wordmark.svg', alt: 'eslint' },
|
||||||
|
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/expo-original-wordmark.svg', alt: 'expo' },
|
||||||
|
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/figma-original.svg', alt: 'figma' },
|
||||||
|
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/flutter-original.svg', alt: 'flutter' },
|
||||||
|
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/forgejo-original.svg', alt: 'forgejo' },
|
||||||
|
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/fusion-original.svg', alt: 'fusion' },
|
||||||
|
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/gimp-original.svg', alt: 'gimp' },
|
||||||
|
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/github-original-wordmark.svg', alt: 'github' },
|
||||||
|
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/gitlab-plain-wordmark.svg', alt: 'gitlab' },
|
||||||
|
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/googlecloud-original-wordmark.svg', alt: 'googlecloud' },
|
||||||
|
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/html5-original-wordmark.svg', alt: 'html5' },
|
||||||
|
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/illustrator-original.svg', alt: 'illustrator' },
|
||||||
|
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/inkscape-plain-wordmark.svg', alt: 'inkscape' },
|
||||||
|
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/javascript-original.svg', alt: 'javascript' },
|
||||||
|
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/jira-original-wordmark.svg', alt: 'jira' },
|
||||||
|
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/jquery-plain-wordmark.svg', alt: 'jquery' },
|
||||||
|
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/kalilinux-original-wordmark.svg', alt: 'kalilinux' },
|
||||||
|
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/linux-original.svg', alt: 'linux' },
|
||||||
|
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/magento-plain-wordmark.svg', alt: 'magento' },
|
||||||
|
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/mongodb-original-wordmark.svg', alt: 'mongodb' },
|
||||||
|
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/nextjs-original-wordmark.svg', alt: 'nextjs' },
|
||||||
|
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/nginx-original.svg', alt: 'nginx' },
|
||||||
|
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/nodejs-original-wordmark.svg', alt: 'nodejs' },
|
||||||
|
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/opencl-original.svg', alt: 'opencl' },
|
||||||
|
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/photoshop-original.svg', alt: 'photoshop' },
|
||||||
|
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/php-original.svg', alt: 'php' },
|
||||||
|
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/premierepro-original.svg', alt: 'premierepro' },
|
||||||
|
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/prometheus-plain-wordmark.svg', alt: 'prometheus' },
|
||||||
|
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/proxmox-original-wordmark.svg', alt: 'proxmox' },
|
||||||
|
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/python-original-wordmark.svg', alt: 'python' },
|
||||||
|
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/rails-plain-wordmark.svg', alt: 'rails' },
|
||||||
|
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/reactnative-original-wordmark.svg', alt: 'reactnative' },
|
||||||
|
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/reactnavigation-original.svg', alt: 'reactnavigation' },
|
||||||
|
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/slack-original-wordmark.svg', alt: 'slack' },
|
||||||
|
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/swift-original-wordmark.svg', alt: 'swift' },
|
||||||
|
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/typescript-original.svg', alt: 'typescript' },
|
||||||
|
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/web3js-original.svg', alt: 'web3js' },
|
||||||
|
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/wordpress-original.svg', alt: 'wordpress' },
|
||||||
|
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/xcode-original.svg', alt: 'xcode' },
|
||||||
|
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/zend-original-wordmark.svg', alt: 'zend' },
|
||||||
|
]
|
||||||
|
|
||||||
|
type SkillsMarqueeBlockProps = {
|
||||||
|
heading?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SkillsMarqueeBlock({ heading }: SkillsMarqueeBlockProps) {
|
||||||
|
const { isDark } = useThemeMode()
|
||||||
|
const text = isDark ? 'text-white/40' : 'text-black/40'
|
||||||
|
const border = isDark ? 'border-white/10' : 'border-black/10'
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className={cn('relative border-t py-8', border)}>
|
||||||
|
<p className={cn('mb-6 text-center text-xs tracking-widest uppercase px-6', text)}>
|
||||||
|
{heading || 'Skills'}
|
||||||
|
</p>
|
||||||
|
<div className="[mask-image:linear-gradient(to_right,transparent,black,transparent)]">
|
||||||
|
<InfiniteSlider gap={42} duration={35}>
|
||||||
|
{LOGOS.map((l) => (
|
||||||
|
<img
|
||||||
|
key={l.alt}
|
||||||
|
src={l.src}
|
||||||
|
alt={l.alt}
|
||||||
|
className={cn(
|
||||||
|
'h-5 w-auto flex-shrink-0 pointer-events-none select-none',
|
||||||
|
isDark ? 'brightness-0 invert opacity-30' : 'brightness-0 opacity-25'
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</InfiniteSlider>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
16
src/blocks/SkillsMarquee/SkillsMarquee-config.ts
Normal file
16
src/blocks/SkillsMarquee/SkillsMarquee-config.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
import type { Block } from 'payload'
|
||||||
|
|
||||||
|
export const SkillsMarqueeBlock: Block = {
|
||||||
|
slug: 'skillsMarquee',
|
||||||
|
labels: {
|
||||||
|
singular: 'Skills Marquee',
|
||||||
|
plural: 'Skills Marquees',
|
||||||
|
},
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'heading',
|
||||||
|
type: 'text',
|
||||||
|
defaultValue: 'Skills',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
@ -8,6 +8,7 @@ import { Content } from '../../blocks/Content/config'
|
||||||
import { FormBlock } from '../../blocks/Form/config'
|
import { FormBlock } from '../../blocks/Form/config'
|
||||||
import { MediaBlock } from '../../blocks/MediaBlock/config'
|
import { MediaBlock } from '../../blocks/MediaBlock/config'
|
||||||
import { SkillsBlock } from '../../blocks/Skills/config'
|
import { SkillsBlock } from '../../blocks/Skills/config'
|
||||||
|
import { SkillsMarqueeBlock } from '../../blocks/SkillsMarquee/config'
|
||||||
import { hero } from '@/heros/config'
|
import { hero } from '@/heros/config'
|
||||||
import { slugField } from 'payload'
|
import { slugField } from 'payload'
|
||||||
import { populatePublishedAt } from '../../hooks/populatePublishedAt'
|
import { populatePublishedAt } from '../../hooks/populatePublishedAt'
|
||||||
|
|
@ -30,9 +31,6 @@ export const Pages: CollectionConfig<'pages'> = {
|
||||||
read: authenticatedOrPublished,
|
read: authenticatedOrPublished,
|
||||||
update: authenticated,
|
update: authenticated,
|
||||||
},
|
},
|
||||||
// This config controls what's populated by default when a page is referenced
|
|
||||||
// https://payloadcms.com/docs/queries/select#defaultpopulate-collection-config-property
|
|
||||||
// Type safe if the collection slug generic is passed to `CollectionConfig` - `CollectionConfig<'pages'>
|
|
||||||
defaultPopulate: {
|
defaultPopulate: {
|
||||||
title: true,
|
title: true,
|
||||||
slug: true,
|
slug: true,
|
||||||
|
|
@ -73,7 +71,7 @@ export const Pages: CollectionConfig<'pages'> = {
|
||||||
{
|
{
|
||||||
name: 'layout',
|
name: 'layout',
|
||||||
type: 'blocks',
|
type: 'blocks',
|
||||||
blocks: [CallToAction, Content, MediaBlock, Archive, FormBlock, SkillsBlock],
|
blocks: [CallToAction, Content, MediaBlock, Archive, FormBlock, SkillsBlock, SkillsMarqueeBlock],
|
||||||
required: true,
|
required: true,
|
||||||
admin: {
|
admin: {
|
||||||
initCollapsed: true,
|
initCollapsed: true,
|
||||||
|
|
@ -97,13 +95,9 @@ export const Pages: CollectionConfig<'pages'> = {
|
||||||
MetaImageField({
|
MetaImageField({
|
||||||
relationTo: 'media',
|
relationTo: 'media',
|
||||||
}),
|
}),
|
||||||
|
|
||||||
MetaDescriptionField({}),
|
MetaDescriptionField({}),
|
||||||
PreviewField({
|
PreviewField({
|
||||||
// if the `generateUrl` function is configured
|
|
||||||
hasGenerateFn: true,
|
hasGenerateFn: true,
|
||||||
|
|
||||||
// field paths to match the target field for data
|
|
||||||
titlePath: 'meta.title',
|
titlePath: 'meta.title',
|
||||||
descriptionPath: 'meta.description',
|
descriptionPath: 'meta.description',
|
||||||
}),
|
}),
|
||||||
|
|
@ -128,7 +122,7 @@ export const Pages: CollectionConfig<'pages'> = {
|
||||||
versions: {
|
versions: {
|
||||||
drafts: {
|
drafts: {
|
||||||
autosave: {
|
autosave: {
|
||||||
interval: 100, // We set this interval for optimal live preview
|
interval: 100,
|
||||||
},
|
},
|
||||||
schedulePublish: true,
|
schedulePublish: true,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,11 @@
|
||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import React, { useEffect, useRef, useState } from 'react'
|
import React, { useEffect, useRef } from 'react'
|
||||||
import { cn } from '@/utilities/ui'
|
import { cn } from '@/utilities/ui'
|
||||||
import { InfiniteSlider } from '@/components/ui/infinite-slider'
|
|
||||||
import { ArrowRightIcon, PhoneCallIcon } from 'lucide-react'
|
|
||||||
import { createPortal } from 'react-dom'
|
|
||||||
import { MenuToggleIcon } from '@/components/ui/menu-toggle-icon'
|
|
||||||
import { useScroll } from '@/components/ui/use-scroll'
|
|
||||||
import { useThemeMode } from '@/hooks/useThemeMode'
|
import { useThemeMode } from '@/hooks/useThemeMode'
|
||||||
import { CMSLink } from '@/components/Link'
|
import { CMSLink } from '@/components/Link'
|
||||||
import RichText from '@/components/RichText'
|
import RichText from '@/components/RichText'
|
||||||
import type { Page } from '@/payload-types'
|
import type { Page } from '@/payload-types'
|
||||||
import { Button } from '@/components/ui/button'
|
|
||||||
|
|
||||||
// ─── Types ────────────────────────────────────────────────────────────────────
|
// ─── Types ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
@ -171,30 +165,23 @@ function Background({ isDark }: { isDark: boolean }) {
|
||||||
type HeroProps = Pick<Page['hero'], 'richText' | 'links'>
|
type HeroProps = Pick<Page['hero'], 'richText' | 'links'>
|
||||||
|
|
||||||
function HeroSection({ isDark, richText, links }: HeroProps & { isDark: boolean }) {
|
function HeroSection({ isDark, richText, links }: HeroProps & { isDark: boolean }) {
|
||||||
const muted = isDark ? 'text-white/55' : 'text-black/55'
|
|
||||||
const borderLine = isDark ? 'bg-white/10' : 'bg-black/10'
|
const borderLine = isDark ? 'bg-white/10' : 'bg-black/10'
|
||||||
const borderLineFaint = isDark ? 'bg-white/5' : 'bg-black/5'
|
const borderLineFaint = isDark ? 'bg-white/5' : 'bg-black/5'
|
||||||
const richTextColor = isDark ? 'text-white' : 'text-black'
|
const richTextColor = isDark ? 'text-white' : 'text-black'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="relative mx-auto w-full max-w-5xl">
|
<section className="relative mx-auto w-full max-w-5xl">
|
||||||
{/* Vertical border lines */}
|
|
||||||
<div aria-hidden="true" className="absolute inset-0 mx-auto hidden min-h-screen w-full max-w-5xl lg:block pointer-events-none">
|
<div aria-hidden="true" className="absolute inset-0 mx-auto hidden min-h-screen w-full max-w-5xl lg:block pointer-events-none">
|
||||||
<div className={cn('mask-y-from-80% mask-y-to-100% absolute inset-y-0 left-0 z-10 h-full w-px', borderLine)} />
|
<div className={cn('mask-y-from-80% mask-y-to-100% absolute inset-y-0 left-0 z-10 h-full w-px', borderLine)} />
|
||||||
<div className={cn('mask-y-from-80% mask-y-to-100% absolute inset-y-0 right-0 z-10 h-full w-px', borderLine)} />
|
<div className={cn('mask-y-from-80% mask-y-to-100% absolute inset-y-0 right-0 z-10 h-full w-px', borderLine)} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Inner vertical border lines */}
|
|
||||||
<div aria-hidden="true" className="absolute inset-0 -z-[1] size-full overflow-hidden pointer-events-none">
|
<div aria-hidden="true" className="absolute inset-0 -z-[1] size-full overflow-hidden pointer-events-none">
|
||||||
<div className={cn('absolute inset-y-0 left-4 w-px bg-linear-to-b from-transparent via-border to-border md:left-8', borderLine)} />
|
<div className={cn('absolute inset-y-0 left-4 w-px bg-linear-to-b from-transparent via-border to-border md:left-8', borderLine)} />
|
||||||
<div className={cn('absolute inset-y-0 right-4 w-px bg-linear-to-b from-transparent via-border to-border md:right-8', borderLine)} />
|
<div className={cn('absolute inset-y-0 right-4 w-px bg-linear-to-b from-transparent via-border to-border md:right-8', borderLine)} />
|
||||||
<div className={cn('absolute inset-y-0 left-8 w-px bg-linear-to-b from-transparent via-border/50 to-border/50 md:left-12', borderLineFaint)} />
|
<div className={cn('absolute inset-y-0 left-8 w-px bg-linear-to-b from-transparent via-border/50 to-border/50 md:left-12', borderLineFaint)} />
|
||||||
<div className={cn('absolute inset-y-0 right-8 w-px bg-linear-to-b from-transparent via-border/50 to-border/50 md:right-12', borderLineFaint)} />
|
<div className={cn('absolute inset-y-0 right-8 w-px bg-linear-to-b from-transparent via-border/50 to-border/50 md:right-12', borderLineFaint)} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="relative flex flex-col items-center justify-center gap-5 pt-32 pb-28 px-6 text-center">
|
<div className="relative flex flex-col items-center justify-center gap-5 pt-32 pb-28 px-6 text-center">
|
||||||
|
|
||||||
{/* Rich text from Payload */}
|
|
||||||
{richText && (
|
{richText && (
|
||||||
<RichText
|
<RichText
|
||||||
className={cn(
|
className={cn(
|
||||||
|
|
@ -206,8 +193,6 @@ function HeroSection({ isDark, richText, links }: HeroProps & { isDark: boolean
|
||||||
enableGutter={false}
|
enableGutter={false}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Links from Payload */}
|
|
||||||
{Array.isArray(links) && links.length > 0 && (
|
{Array.isArray(links) && links.length > 0 && (
|
||||||
<div className="fade-in slide-in-from-bottom-10 animate-in fill-mode-backwards delay-300 duration-500 ease-out flex flex-wrap items-center justify-center gap-3 pt-2">
|
<div className="fade-in slide-in-from-bottom-10 animate-in fill-mode-backwards delay-300 duration-500 ease-out flex flex-wrap items-center justify-center gap-3 pt-2">
|
||||||
{links.map(({ link }, i) => (
|
{links.map(({ link }, i) => (
|
||||||
|
|
@ -228,94 +213,6 @@ function HeroSection({ isDark, richText, links }: HeroProps & { isDark: boolean
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ─── Logo cloud ───────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
const LOGOS = [
|
|
||||||
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/aftereffects-plain.svg', alt: 'aftereffects' },
|
|
||||||
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/almalinux-original.svg', alt: 'almalinux' },
|
|
||||||
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/amazonwebservices-original-wordmark.svg', alt: 'amazonwebservices' },
|
|
||||||
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/android-plain-wordmark.svg', alt: 'android' },
|
|
||||||
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/angularjs-plain-wordmark.svg', alt: 'angularjs' },
|
|
||||||
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/apache-line-wordmark.svg', alt: 'apache' },
|
|
||||||
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/apple-original.svg', alt: 'apple' },
|
|
||||||
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/azure-original-wordmark.svg', alt: 'azure' },
|
|
||||||
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/blender-original.svg', alt: 'blender' },
|
|
||||||
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/bootstrap-plain-wordmark.svg', alt: 'bootstrap' },
|
|
||||||
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/cloudflare-original-wordmark.svg', alt: 'cloudflare' },
|
|
||||||
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/cpanel-original-wordmark.svg', alt: 'cpanel' },
|
|
||||||
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/docker-plain-wordmark.svg', alt: 'docker' },
|
|
||||||
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/drupal-plain-wordmark.svg', alt: 'drupal' },
|
|
||||||
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/eslint-plain-wordmark.svg', alt: 'eslint' },
|
|
||||||
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/expo-original-wordmark.svg', alt: 'expo' },
|
|
||||||
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/figma-original.svg', alt: 'figma' },
|
|
||||||
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/flutter-original.svg', alt: 'flutter' },
|
|
||||||
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/forgejo-original.svg', alt: 'forgejo' },
|
|
||||||
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/fusion-original.svg', alt: 'fusion' },
|
|
||||||
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/gimp-original.svg', alt: 'gimp' },
|
|
||||||
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/github-original-wordmark.svg', alt: 'github' },
|
|
||||||
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/gitlab-plain-wordmark.svg', alt: 'gitlab' },
|
|
||||||
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/googlecloud-original-wordmark.svg', alt: 'googlecloud' },
|
|
||||||
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/html5-original-wordmark.svg', alt: 'html5' },
|
|
||||||
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/illustrator-original.svg', alt: 'illustrator' },
|
|
||||||
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/inkscape-plain-wordmark.svg', alt: 'inkscape' },
|
|
||||||
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/javascript-original.svg', alt: 'javascript' },
|
|
||||||
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/jira-original-wordmark.svg', alt: 'jira' },
|
|
||||||
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/jquery-plain-wordmark.svg', alt: 'jquery' },
|
|
||||||
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/kalilinux-original-wordmark.svg', alt: 'kalilinux' },
|
|
||||||
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/linux-original.svg', alt: 'linux' },
|
|
||||||
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/magento-plain-wordmark.svg', alt: 'magento' },
|
|
||||||
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/mongodb-original-wordmark.svg', alt: 'mongodb' },
|
|
||||||
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/nextjs-original-wordmark.svg', alt: 'nextjs' },
|
|
||||||
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/nginx-original.svg', alt: 'nginx' },
|
|
||||||
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/nodejs-original-wordmark.svg', alt: 'nodejs' },
|
|
||||||
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/opencl-original.svg', alt: 'opencl' },
|
|
||||||
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/photoshop-original.svg', alt: 'photoshop' },
|
|
||||||
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/php-original.svg', alt: 'php' },
|
|
||||||
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/premierepro-original.svg', alt: 'premierepro' },
|
|
||||||
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/prometheus-plain-wordmark.svg', alt: 'prometheus' },
|
|
||||||
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/proxmox-original-wordmark.svg', alt: 'proxmox' },
|
|
||||||
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/python-original-wordmark.svg', alt: 'python' },
|
|
||||||
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/rails-plain-wordmark.svg', alt: 'rails' },
|
|
||||||
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/reactnative-original-wordmark.svg', alt: 'reactnative' },
|
|
||||||
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/reactnavigation-original.svg', alt: 'reactnavigation' },
|
|
||||||
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/slack-original-wordmark.svg', alt: 'slack' },
|
|
||||||
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/swift-original-wordmark.svg', alt: 'swift' },
|
|
||||||
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/typescript-original.svg', alt: 'typescript' },
|
|
||||||
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/web3js-original.svg', alt: 'web3js' },
|
|
||||||
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/wordpress-original.svg', alt: 'wordpress' },
|
|
||||||
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/xcode-original.svg', alt: 'xcode' },
|
|
||||||
{ src: 'https://portfolio-media.s3web.bymackie.com/Skill_logo/zend-original-wordmark.svg', alt: 'zend' },
|
|
||||||
]
|
|
||||||
|
|
||||||
function LogosSection({ isDark }: { isDark: boolean }) {
|
|
||||||
const text = isDark ? 'text-white/40' : 'text-black/40'
|
|
||||||
const border = isDark ? 'border-white/10' : 'border-black/10'
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section className={cn('relative border-t py-8', border)}>
|
|
||||||
<p className={cn('mb-6 text-center text-xs tracking-widest uppercase px-6', text)}>
|
|
||||||
Skills
|
|
||||||
</p>
|
|
||||||
<div className="[mask-image:linear-gradient(to_right,transparent,black,transparent)]">
|
|
||||||
<InfiniteSlider gap={42} duration={35}>
|
|
||||||
{LOGOS.map((l) => (
|
|
||||||
<img
|
|
||||||
key={l.alt}
|
|
||||||
src={l.src}
|
|
||||||
alt={l.alt}
|
|
||||||
className={cn(
|
|
||||||
'h-5 w-auto flex-shrink-0 pointer-events-none select-none',
|
|
||||||
isDark ? 'brightness-0 invert opacity-30' : 'brightness-0 opacity-25'
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</InfiniteSlider>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
)
|
)
|
||||||
|
|
@ -330,29 +227,23 @@ interface HeroPageProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function HeroPage({ richText, links, children }: HeroPageProps) {
|
export default function HeroPage({ richText, links, children }: HeroPageProps) {
|
||||||
const { isDark, toggle } = useThemeMode()
|
const { isDark } = useThemeMode()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ position: 'relative', minHeight: '100vh' }}>
|
<div style={{ position: 'relative', minHeight: '100vh' }}>
|
||||||
|
|
||||||
{/* Canvas background — fixed behind everything */}
|
|
||||||
<div style={{ position: 'fixed', inset: 0, zIndex: -1 }}>
|
<div style={{ position: 'fixed', inset: 0, zIndex: -1 }}>
|
||||||
<Background isDark={isDark} />
|
<Background isDark={isDark} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Page content */}
|
|
||||||
<div style={{ position: 'relative', zIndex: 1 }}>
|
<div style={{ position: 'relative', zIndex: 1 }}>
|
||||||
<main>
|
<main>
|
||||||
<HeroSection isDark={isDark} richText={richText} links={links} />
|
<HeroSection isDark={isDark} richText={richText} links={links} />
|
||||||
<LogosSection isDark={isDark} />
|
|
||||||
{children && (
|
{children && (
|
||||||
<div id="skills">
|
<div id="content">
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue