diff --git a/.github/workflows/cdn.yml b/.github/workflows/cdn.yml new file mode 100644 index 0000000..19e645e --- /dev/null +++ b/.github/workflows/cdn.yml @@ -0,0 +1,37 @@ +name: Deploy CDN assets + +on: + push: + branches: [main] + paths: + - "public/cdn/**" + - ".github/workflows/cdn.yml" + workflow_dispatch: + +jobs: + sync: + runs-on: ubuntu-latest + env: + AWS_ACCESS_KEY_ID: ${{ secrets.S3_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.S3_SECRET_ACCESS_KEY }} + AWS_DEFAULT_REGION: ${{ secrets.S3_REGION || 'auto' }} + AWS_REQUEST_CHECKSUM_CALCULATION: when_required + AWS_RESPONSE_CHECKSUM_VALIDATION: when_required + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Ensure public/cdn is populated + run: | + if [ -z "$(find public/cdn -type f 2>/dev/null)" ]; then + echo "public/cdn is empty — refusing to sync (would delete web/static)." >&2 + exit 1 + fi + + - name: Sync public/cdn to S3-compatible storage (web/static) + run: | + aws s3 sync public/cdn "s3://${{ secrets.S3_BUCKET }}/web/static" \ + --endpoint-url "${{ secrets.S3_ENDPOINT }}" \ + --delete \ + --no-progress \ + --cache-control "public, max-age=604800" diff --git a/.migration/dialog.md b/.migration/dialog.md index 6113db3..170c9fe 100644 --- a/.migration/dialog.md +++ b/.migration/dialog.md @@ -37,7 +37,7 @@ Positioner); typecheck + full build clean. - Close button, header/footer/title/description classes, `showCloseButton` prop, `data-slot` attributes: UNCHANGED. - Leftover scan clean: `grep -n "radix-ui\|@radix-ui\|data-\[state=" - src/components/ui/dialog.tsx` → no matches. +src/components/ui/dialog.tsx` → no matches. - `src/components/CookieConsentModal.tsx` (consumer) - Removed the three Radix-only dismiss handlers from ``: diff --git a/bun.lock b/bun.lock index 630beca..a2d54b3 100644 --- a/bun.lock +++ b/bun.lock @@ -21,7 +21,7 @@ "cnfast": "^0.1.0", "daisyui": "^5.7.16", "elysia": "^1.4.29", - "gsap": "^3.15.0", + "gsap": "^3.13.0", "lucide-react": "^1.31.0", "maath": "^0.10.8", "motion": "^12.43.0", diff --git a/package.json b/package.json index b96e2ae..20f3f13 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,8 @@ "dev": "next dev", "lint": "next lint", "start": "next start", - "format": "oxfmt" + "format": "oxfmt", + "cdn:fetch": "bash scripts/fetch-cdn-assets.sh" }, "dependencies": { "@base-ui/react": "^1.7.0", @@ -26,7 +27,7 @@ "cnfast": "^0.1.0", "daisyui": "^5.7.16", "elysia": "^1.4.29", - "gsap": "^3.15.0", + "gsap": "^3.13.0", "lucide-react": "^1.31.0", "maath": "^0.10.8", "motion": "^12.43.0", diff --git a/public/cdn/airi.mp4 b/public/cdn/airi.mp4 new file mode 100644 index 0000000..395d35f Binary files /dev/null and b/public/cdn/airi.mp4 differ diff --git a/public/cdn/bar.glb b/public/cdn/bar.glb new file mode 100644 index 0000000..f62e2f2 Binary files /dev/null and b/public/cdn/bar.glb differ diff --git a/public/cdn/cube.glb b/public/cdn/cube.glb new file mode 100644 index 0000000..6fba306 Binary files /dev/null and b/public/cdn/cube.glb differ diff --git a/public/cdn/lens.glb b/public/cdn/lens.glb new file mode 100644 index 0000000..5a4a82d Binary files /dev/null and b/public/cdn/lens.glb differ diff --git a/public/cdn/vroid/hi.vrma b/public/cdn/vroid/hi.vrma new file mode 100644 index 0000000..b1e0645 Binary files /dev/null and b/public/cdn/vroid/hi.vrma differ diff --git a/public/cdn/vroid/horahora.vrma b/public/cdn/vroid/horahora.vrma new file mode 100644 index 0000000..633eae6 Binary files /dev/null and b/public/cdn/vroid/horahora.vrma differ diff --git a/public/cdn/vroid/mikan-web.vrm b/public/cdn/vroid/mikan-web.vrm new file mode 100644 index 0000000..31fa927 Binary files /dev/null and b/public/cdn/vroid/mikan-web.vrm differ diff --git a/public/cdn/vroid/shikanoko.vrma b/public/cdn/vroid/shikanoko.vrma new file mode 100644 index 0000000..ea260ca Binary files /dev/null and b/public/cdn/vroid/shikanoko.vrma differ diff --git a/public/cdn/vroid/soware.vrma b/public/cdn/vroid/soware.vrma new file mode 100644 index 0000000..9b4c8ea Binary files /dev/null and b/public/cdn/vroid/soware.vrma differ diff --git a/public/cdn/vroid/telepathy.vrma b/public/cdn/vroid/telepathy.vrma new file mode 100644 index 0000000..f52df95 Binary files /dev/null and b/public/cdn/vroid/telepathy.vrma differ diff --git a/public/cdn/vroid/tetoris.vrma b/public/cdn/vroid/tetoris.vrma new file mode 100644 index 0000000..a741dec Binary files /dev/null and b/public/cdn/vroid/tetoris.vrma differ diff --git a/public/cdn/vroid/uishig.vrma b/public/cdn/vroid/uishig.vrma new file mode 100644 index 0000000..9bc75f7 Binary files /dev/null and b/public/cdn/vroid/uishig.vrma differ diff --git a/public/vroid/hi.vrma b/public/vroid/hi.vrma deleted file mode 100644 index a4bbe4a..0000000 Binary files a/public/vroid/hi.vrma and /dev/null differ diff --git a/public/vroid/mikan.dev(long).vrm b/public/vroid/mikan.dev(long).vrm deleted file mode 100644 index f0fc436..0000000 Binary files a/public/vroid/mikan.dev(long).vrm and /dev/null differ diff --git a/public/vroid/mikan.dev(web).vrm b/public/vroid/mikan.dev(web).vrm deleted file mode 100644 index 942da81..0000000 Binary files a/public/vroid/mikan.dev(web).vrm and /dev/null differ diff --git a/public/vroid/mikan.dev_short.vrm b/public/vroid/mikan.dev_short.vrm deleted file mode 100644 index 2c73e06..0000000 Binary files a/public/vroid/mikan.dev_short.vrm and /dev/null differ diff --git a/public/vroid/shikanoko.vrma b/public/vroid/shikanoko.vrma deleted file mode 100644 index 4b71b64..0000000 Binary files a/public/vroid/shikanoko.vrma and /dev/null differ diff --git a/src/app/[locale]/(homepage)/page.tsx b/src/app/[locale]/(homepage)/page.tsx index 4a7f56a..2563359 100644 --- a/src/app/[locale]/(homepage)/page.tsx +++ b/src/app/[locale]/(homepage)/page.tsx @@ -11,19 +11,18 @@ import DecryptedText from "@/components/DecryptedText"; import CurvedLoop from "@/components/CurvedLoop"; import ScrollFloat from "@/components/ScrollFloat"; import SplitText from "@/components/SplitText"; -import { OSSProductCard } from "@/components/OSSProductCard"; +import { SolutionsFolder } from "@/components/SolutionsFolder"; import { VRM } from "@/components/vrm"; -import { IdCard } from "lucide-react"; import NeodyLogo from "@/assets/img/NeodyLogo.png"; import KuronekoLogo from "@/assets/img/kuroneko.png"; import RTLogo from "@/assets/img/rt.png"; import TakasumiLogo from "@/assets/img/takasumi.png"; -import KaraSnapIcon from "@/assets/img/karasnap.png"; export default function IndexPage() { const t = useTranslations("home"); const [showCircularText, setShowCircularText] = useState(false); + const [folderOpen, setFolderOpen] = useState(false); return (
@@ -100,21 +99,19 @@ export default function IndexPage() {

{t("OpenSourceBlurb")}

-
- - +
+ +
+ + + +
diff --git a/src/app/[locale]/(pages)/solutions/[solution]/page.tsx b/src/app/[locale]/(pages)/solutions/[solution]/page.tsx new file mode 100644 index 0000000..b6eb592 --- /dev/null +++ b/src/app/[locale]/(pages)/solutions/[solution]/page.tsx @@ -0,0 +1,33 @@ +import { use } from "react"; +import { notFound } from "next/navigation"; +import { useTranslations } from "next-intl"; +import { setRequestLocale } from "next-intl/server"; + +import { SolutionHero } from "@/components/SolutionHero"; +import { SolutionList } from "@/constants/solutions"; + +export function generateStaticParams() { + return SolutionList.map((solution) => ({ solution: solution.slug })); +} + +export default function SolutionPage({ + params, +}: { + params: Promise<{ locale: string; solution: string }>; +}) { + const { locale, solution: slug } = use(params); + setRequestLocale(locale); + const t = useTranslations(); + + const solution = SolutionList.find((entry) => entry.slug === slug); + if (!solution) notFound(); + + return ( + + ); +} diff --git a/src/app/[locale]/(pages)/solutions/page.tsx b/src/app/[locale]/(pages)/solutions/page.tsx new file mode 100644 index 0000000..ff27baf --- /dev/null +++ b/src/app/[locale]/(pages)/solutions/page.tsx @@ -0,0 +1,25 @@ +"use client"; + +import { useTranslations } from "next-intl"; + +import { SolutionCard } from "@/components/SolutionCard"; +import { SolutionList } from "@/constants/solutions"; + +export default function SolutionsPage() { + const t = useTranslations("solutions"); + + return ( +
+
+

{t("title")}

+

{t("subtitle")}

+
+ +
+ {SolutionList.map((solution) => ( + + ))} +
+
+ ); +} diff --git a/src/app/not-found.tsx b/src/app/not-found.tsx index f278a03..0b7ae37 100644 --- a/src/app/not-found.tsx +++ b/src/app/not-found.tsx @@ -1,6 +1,7 @@ import Link from "next/link"; import { Noto_Sans_JP } from "next/font/google"; import { Button } from "@/components/animate-ui/components/buttons/button"; +import { ConfusedCorner } from "@/components/ConfusedCorner"; import "./globals.css"; const notoSansJP = Noto_Sans_JP({ @@ -20,6 +21,7 @@ export default function NotFound() { + diff --git a/src/assets/img/confused.png b/src/assets/img/confused.png new file mode 100644 index 0000000..4529c79 Binary files /dev/null and b/src/assets/img/confused.png differ diff --git a/src/components/Aurora.tsx b/src/components/Aurora.tsx new file mode 100644 index 0000000..e6ce970 --- /dev/null +++ b/src/components/Aurora.tsx @@ -0,0 +1,193 @@ +import { useEffect, useRef } from 'react'; +import { Renderer, Program, Mesh, Color, Triangle } from 'ogl'; + +const VERT = `#version 300 es +in vec2 position; +void main() { + gl_Position = vec4(position, 0.0, 1.0); +} +`; + +const buildFragment = (stopCount: number) => `#version 300 es +precision highp float; + +uniform float uTime; +uniform float uAmplitude; +uniform vec3 uColorStops[${stopCount}]; +uniform vec2 uResolution; +uniform float uBlend; + +out vec4 fragColor; + +vec3 permute(vec3 x) { + return mod(((x * 34.0) + 1.0) * x, 289.0); +} + +float snoise(vec2 v){ + const vec4 C = vec4( + 0.211324865405187, 0.366025403784439, + -0.577350269189626, 0.024390243902439 + ); + vec2 i = floor(v + dot(v, C.yy)); + vec2 x0 = v - i + dot(i, C.xx); + vec2 i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0); + vec4 x12 = x0.xyxy + C.xxzz; + x12.xy -= i1; + i = mod(i, 289.0); + + vec3 p = permute( + permute(i.y + vec3(0.0, i1.y, 1.0)) + + i.x + vec3(0.0, i1.x, 1.0) + ); + + vec3 m = max( + 0.5 - vec3( + dot(x0, x0), + dot(x12.xy, x12.xy), + dot(x12.zw, x12.zw) + ), + 0.0 + ); + m = m * m; + m = m * m; + + vec3 x = 2.0 * fract(p * C.www) - 1.0; + vec3 h = abs(x) - 0.5; + vec3 ox = floor(x + 0.5); + vec3 a0 = x - ox; + m *= 1.79284291400159 - 0.85373472095314 * (a0*a0 + h*h); + + vec3 g; + g.x = a0.x * x0.x + h.x * x0.y; + g.yz = a0.yz * x12.xz + h.yz * x12.yw; + return 130.0 * dot(m, g); +} + +vec3 sampleRamp(float factor) { + float x = clamp(factor, 0.0, 1.0) * float(${stopCount - 1}); + int idx = int(floor(x)); + idx = min(idx, ${stopCount - 2}); + float f = x - float(idx); + return mix(uColorStops[idx], uColorStops[idx + 1], f); +} + +void main() { + vec2 uv = gl_FragCoord.xy / uResolution; + + vec3 rampColor = sampleRamp(uv.x); + + float height = snoise(vec2(uv.x * 2.0 + uTime * 0.1, uTime * 0.25)) * 0.5 * uAmplitude; + height = exp(height); + height = (uv.y * 2.0 - height + 0.2); + float intensity = 0.6 * height; + + float midPoint = 0.20; + float auroraAlpha = smoothstep(midPoint - uBlend * 0.5, midPoint + uBlend * 0.5, intensity); + + vec3 auroraColor = intensity * rampColor; + + fragColor = vec4(auroraColor * auroraAlpha, auroraAlpha); +} +`; + +const BRAND_COLOR_STOPS = ['#ff6a00', '#ff9900', '#ffc266', '#ffe0a8', '#ff7a1a']; + +interface AuroraProps { + colorStops?: string[]; + amplitude?: number; + blend?: number; + time?: number; + speed?: number; +} + +export default function Aurora(props: AuroraProps) { + const { colorStops = BRAND_COLOR_STOPS, amplitude = 1.0, blend = 0.5 } = props; + const propsRef = useRef(props); + propsRef.current = props; + + const ctnDom = useRef(null); + + useEffect(() => { + const ctn = ctnDom.current; + if (!ctn) return; + + const renderer = new Renderer({ + alpha: true, + premultipliedAlpha: true, + antialias: true + }); + const gl = renderer.gl; + gl.clearColor(0, 0, 0, 0); + gl.enable(gl.BLEND); + gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); + gl.canvas.style.backgroundColor = 'transparent'; + + let program: Program | undefined; + + function resize() { + if (!ctn) return; + const width = ctn.offsetWidth; + const height = ctn.offsetHeight; + renderer.setSize(width, height); + if (program) { + program.uniforms.uResolution.value = [width, height]; + } + } + window.addEventListener('resize', resize); + + const geometry = new Triangle(gl); + if (geometry.attributes.uv) { + delete geometry.attributes.uv; + } + + const stopCount = Math.max(2, colorStops.length); + const toRGB = (hex: string) => { + const c = new Color(hex); + return [c.r, c.g, c.b]; + }; + const colorStopsArray = colorStops.map(toRGB); + + program = new Program(gl, { + vertex: VERT, + fragment: buildFragment(stopCount), + uniforms: { + uTime: { value: 0 }, + uAmplitude: { value: amplitude }, + uColorStops: { value: colorStopsArray }, + uResolution: { value: [ctn.offsetWidth, ctn.offsetHeight] }, + uBlend: { value: blend } + } + }); + + const mesh = new Mesh(gl, { geometry, program }); + ctn.appendChild(gl.canvas); + + let animateId = 0; + const update = (t: number) => { + animateId = requestAnimationFrame(update); + const { time = t * 0.01, speed = 1.0 } = propsRef.current; + if (program) { + program.uniforms.uTime.value = time * speed * 0.1; + program.uniforms.uAmplitude.value = propsRef.current.amplitude ?? 1.0; + program.uniforms.uBlend.value = propsRef.current.blend ?? blend; + const stops = propsRef.current.colorStops ?? colorStops; + program.uniforms.uColorStops.value = (stops.length === stopCount ? stops : colorStops).map(toRGB); + renderer.render({ scene: mesh }); + } + }; + animateId = requestAnimationFrame(update); + + resize(); + + return () => { + cancelAnimationFrame(animateId); + window.removeEventListener('resize', resize); + if (ctn && gl.canvas.parentNode === ctn) { + ctn.removeChild(gl.canvas); + } + gl.getExtension('WEBGL_lose_context')?.loseContext(); + }; + }, [amplitude]); + + return
; +} diff --git a/src/components/BorderGlow.tsx b/src/components/BorderGlow.tsx new file mode 100644 index 0000000..7718508 --- /dev/null +++ b/src/components/BorderGlow.tsx @@ -0,0 +1,255 @@ +import { useRef, useCallback, useState, useEffect, type ReactNode } from 'react'; + +interface BorderGlowProps { + children?: ReactNode; + className?: string; + edgeSensitivity?: number; + glowColor?: string; + backgroundColor?: string; + borderRadius?: number; + glowRadius?: number; + glowIntensity?: number; + coneSpread?: number; + animated?: boolean; + colors?: string[]; + fillOpacity?: number; +} + +function parseHSL(hslStr: string): { h: number; s: number; l: number } { + const match = hslStr.match(/([\d.]+)\s*([\d.]+)%?\s*([\d.]+)%?/); + if (!match) return { h: 40, s: 80, l: 80 }; + return { h: parseFloat(match[1]), s: parseFloat(match[2]), l: parseFloat(match[3]) }; +} + +function buildBoxShadow(glowColor: string, intensity: number): string { + const { h, s, l } = parseHSL(glowColor); + const base = `${h}deg ${s}% ${l}%`; + const layers: [number, number, number, number, number, boolean][] = [ + [0, 0, 0, 1, 100, true], [0, 0, 1, 0, 60, true], [0, 0, 3, 0, 50, true], + [0, 0, 6, 0, 40, true], [0, 0, 15, 0, 30, true], [0, 0, 25, 2, 20, true], + [0, 0, 50, 2, 10, true], + [0, 0, 1, 0, 60, false], [0, 0, 3, 0, 50, false], [0, 0, 6, 0, 40, false], + [0, 0, 15, 0, 30, false], [0, 0, 25, 2, 20, false], [0, 0, 50, 2, 10, false], + ]; + return layers.map(([x, y, blur, spread, alpha, inset]) => { + const a = Math.min(alpha * intensity, 100); + return `${inset ? 'inset ' : ''}${x}px ${y}px ${blur}px ${spread}px hsl(${base} / ${a}%)`; + }).join(', '); +} + +function easeOutCubic(x: number) { return 1 - Math.pow(1 - x, 3); } +function easeInCubic(x: number) { return x * x * x; } + +interface AnimateOpts { + start?: number; end?: number; duration?: number; delay?: number; + ease?: (t: number) => number; onUpdate: (v: number) => void; onEnd?: () => void; +} + +function animateValue({ start = 0, end = 100, duration = 1000, delay = 0, ease = easeOutCubic, onUpdate, onEnd }: AnimateOpts) { + const t0 = performance.now() + delay; + function tick() { + const elapsed = performance.now() - t0; + const t = Math.min(elapsed / duration, 1); + onUpdate(start + (end - start) * ease(t)); + if (t < 1) requestAnimationFrame(tick); + else if (onEnd) onEnd(); + } + setTimeout(() => requestAnimationFrame(tick), delay); +} + +const GRADIENT_POSITIONS = ['80% 55%', '69% 34%', '8% 6%', '41% 38%', '86% 85%', '82% 18%', '51% 4%']; +const COLOR_MAP = [0, 1, 2, 0, 1, 2, 1]; + +function buildMeshGradients(colors: string[]): string[] { + const gradients: string[] = []; + for (let i = 0; i < 7; i++) { + const c = colors[Math.min(COLOR_MAP[i], colors.length - 1)]; + gradients.push(`radial-gradient(at ${GRADIENT_POSITIONS[i]}, ${c} 0px, transparent 50%)`); + } + gradients.push(`linear-gradient(${colors[0]} 0 100%)`); + return gradients; +} + +const BorderGlow: React.FC = ({ + children, + className = '', + edgeSensitivity = 30, + glowColor = '40 80 80', + backgroundColor = '#120F17', + borderRadius = 28, + glowRadius = 40, + glowIntensity = 1.0, + coneSpread = 25, + animated = false, + colors = ['#c084fc', '#f472b6', '#38bdf8'], + fillOpacity = 0.5, +}) => { + const cardRef = useRef(null); + const [isHovered, setIsHovered] = useState(false); + const [cursorAngle, setCursorAngle] = useState(45); + const [edgeProximity, setEdgeProximity] = useState(0); + const [sweepActive, setSweepActive] = useState(false); + + const getCenterOfElement = useCallback((el: HTMLElement) => { + const { width, height } = el.getBoundingClientRect(); + return [width / 2, height / 2]; + }, []); + + const getEdgeProximity = useCallback((el: HTMLElement, x: number, y: number) => { + const [cx, cy] = getCenterOfElement(el); + const dx = x - cx; + const dy = y - cy; + let kx = Infinity; + let ky = Infinity; + if (dx !== 0) kx = cx / Math.abs(dx); + if (dy !== 0) ky = cy / Math.abs(dy); + return Math.min(Math.max(1 / Math.min(kx, ky), 0), 1); + }, [getCenterOfElement]); + + const getCursorAngle = useCallback((el: HTMLElement, x: number, y: number) => { + const [cx, cy] = getCenterOfElement(el); + const dx = x - cx; + const dy = y - cy; + if (dx === 0 && dy === 0) return 0; + const radians = Math.atan2(dy, dx); + let degrees = radians * (180 / Math.PI) + 90; + if (degrees < 0) degrees += 360; + return degrees; + }, [getCenterOfElement]); + + const handlePointerMove = useCallback((e: React.PointerEvent) => { + const card = cardRef.current; + if (!card) return; + const rect = card.getBoundingClientRect(); + const x = e.clientX - rect.left; + const y = e.clientY - rect.top; + setEdgeProximity(getEdgeProximity(card, x, y)); + setCursorAngle(getCursorAngle(card, x, y)); + }, [getEdgeProximity, getCursorAngle]); + + useEffect(() => { + if (!animated) return; + const angleStart = 110; + const angleEnd = 465; + setSweepActive(true); + setCursorAngle(angleStart); + + animateValue({ duration: 500, onUpdate: v => setEdgeProximity(v / 100) }); + animateValue({ ease: easeInCubic, duration: 1500, end: 50, onUpdate: v => { + setCursorAngle((angleEnd - angleStart) * (v / 100) + angleStart); + }}); + animateValue({ ease: easeOutCubic, delay: 1500, duration: 2250, start: 50, end: 100, onUpdate: v => { + setCursorAngle((angleEnd - angleStart) * (v / 100) + angleStart); + }}); + animateValue({ ease: easeInCubic, delay: 2500, duration: 1500, start: 100, end: 0, + onUpdate: v => setEdgeProximity(v / 100), + onEnd: () => setSweepActive(false), + }); + }, [animated]); + + const colorSensitivity = edgeSensitivity + 20; + const isVisible = isHovered || sweepActive; + const borderOpacity = isVisible + ? Math.max(0, (edgeProximity * 100 - colorSensitivity) / (100 - colorSensitivity)) + : 0; + const glowOpacity = isVisible + ? Math.max(0, (edgeProximity * 100 - edgeSensitivity) / (100 - edgeSensitivity)) + : 0; + + const meshGradients = buildMeshGradients(colors); + const borderBg = meshGradients.map(g => `${g} border-box`); + const fillBg = meshGradients.map(g => `${g} padding-box`); + const angleDeg = `${cursorAngle.toFixed(3)}deg`; + + return ( +
setIsHovered(true)} + onPointerLeave={() => setIsHovered(false)} + className={`relative grid isolate border border-white/15 ${className}`} + style={{ + background: backgroundColor, + borderRadius: `${borderRadius}px`, + transform: 'translate3d(0, 0, 0.01px)', + boxShadow: 'rgba(0,0,0,0.1) 0 1px 2px, rgba(0,0,0,0.1) 0 2px 4px, rgba(0,0,0,0.1) 0 4px 8px, rgba(0,0,0,0.1) 0 8px 16px, rgba(0,0,0,0.1) 0 16px 32px, rgba(0,0,0,0.1) 0 32px 64px', + }} + > + {/* mesh gradient border */} +
+ + {/* mesh gradient fill near edges */} +
+ + {/* outer glow */} + + + + +
+ {children} +
+
+ ); +}; + +export default BorderGlow; diff --git a/src/components/ConfusedCorner.tsx b/src/components/ConfusedCorner.tsx new file mode 100644 index 0000000..d26db31 --- /dev/null +++ b/src/components/ConfusedCorner.tsx @@ -0,0 +1,24 @@ +"use client"; + +import { motion } from "motion/react"; +import Image from "next/image"; +import Confused from "@/assets/img/confused.png"; + +export function ConfusedCorner() { + return ( + + + + ); +} diff --git a/src/components/CookieConsentModal.tsx b/src/components/CookieConsentModal.tsx index cecd207..645435a 100644 --- a/src/components/CookieConsentModal.tsx +++ b/src/components/CookieConsentModal.tsx @@ -6,6 +6,7 @@ import { useTranslations } from "next-intl"; import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog"; import { useConsentStore } from "@/stores/consent"; +import { cdnUrl } from "@/lib/cdn-converter"; export function CookieConsentModal() { const t = useTranslations("cookieConsent"); @@ -28,7 +29,7 @@ export function CookieConsentModal() { className="block overflow-hidden rounded-md" >