Compare commits

...

4 Commits

Author SHA1 Message Date
03048b30b1 Add deployment to documentation 2025-11-02 20:43:07 -05:00
820a83feef Add docker support 2025-11-02 20:08:31 -05:00
fbb210f083 Add desktop support 2025-11-02 19:57:59 -05:00
7abf14e9ae Add more sections 2025-11-02 19:43:39 -05:00
12 changed files with 274 additions and 29 deletions

1
.env.example Normal file
View File

@ -0,0 +1 @@
LOCAL_PORT=3000

2
.gitignore vendored
View File

@ -22,3 +22,5 @@ dist-ssr
*.njsproj *.njsproj
*.sln *.sln
*.sw? *.sw?
.env

12
Dockerfile Normal file
View File

@ -0,0 +1,12 @@
FROM node:alpine AS build
WORKDIR /app
COPY package.json ./
RUN npm install
COPY . .
RUN npm run build
FROM nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/html
COPY --from=build /app/nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD [ "nginx", "-g", "daemon off;" ]

View File

@ -1,6 +1,8 @@
# BCDigital Code Challenge # BCDigital Code Challenge
My submission to the code challenge from [BCDigial](https://www.bc-digital.ca/) See the website at https://bcdigital-challenge.thingsnstuff.xyz/
My submission to the code challenge from [BC Digital](https://www.bc-digital.ca/) for the Frontend Developer position
Landing page for hypthetical client **Great Music LLM**'s event and employee management software Landing page for hypthetical client **Great Music LLM**'s event and employee management software
@ -8,7 +10,8 @@ Landing page for hypthetical client **Great Music LLM**'s event and employee man
- [`vite`](https://vite.dev/) build tooling - [`vite`](https://vite.dev/) build tooling
- [`tailwindcss`](https://tailwindcss.com/) styling - [`tailwindcss`](https://tailwindcss.com/) styling
- [Magic UI](https://magicui.design/) component library - [Magic UI](https://magicui.design/) component library (see [`src/components/ui`](https://git.thingsnstuff.xyz/ethanglide/bcdigital-challenge/src/branch/main/src/components/ui))
- [`prettier`](https://prettier.io/) code formatting - [`prettier`](https://prettier.io/) code formatting
- [`eslint`](https://eslint.org/) code linting - [`eslint`](https://eslint.org/) code linting
- [`husky`](https://typicode.github.io/husky/) and [`lint-staged`](https://github.com/lint-staged/lint-staged) pre-commit hooks for testing/formatting/linting - [`husky`](https://typicode.github.io/husky/) and [`lint-staged`](https://github.com/lint-staged/lint-staged) pre-commit hooks for testing/formatting/linting
- [Docker](https://www.docker.com/) containerization

6
docker-compose.yml Normal file
View File

@ -0,0 +1,6 @@
services:
nginx:
build: .
ports:
- "${LOCAL_PORT}:80"
restart: always

7
nginx.conf Normal file
View File

@ -0,0 +1,7 @@
server {
location / {
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
}

View File

@ -1,7 +1,9 @@
import { StripedPattern } from "./components/magicui/striped-pattern";
import { Navbar } from "./components/navbar/navbar"; import { Navbar } from "./components/navbar/navbar";
import { PartnersMarquee } from "./components/partners-marquee/partners-marquee"; import { PartnersMarquee } from "./components/partners-marquee/partners-marquee";
import { AuroraText } from "./components/ui/aurora-text"; import { AuroraText } from "./components/ui/aurora-text";
import { DotPattern } from "./components/ui/dot-pattern"; import { DotPattern } from "./components/ui/dot-pattern";
import { GridPattern } from "./components/ui/grid-pattern";
import { RainbowButton } from "./components/ui/rainbow-button"; import { RainbowButton } from "./components/ui/rainbow-button";
import { TextAnimate } from "./components/ui/text-animate"; import { TextAnimate } from "./components/ui/text-animate";
@ -18,14 +20,14 @@ export function App() {
<div className="h-full text-white flex items-center justify-center backdrop-blur-[3px] backdrop-grayscale-50"> <div className="h-full text-white flex items-center justify-center backdrop-blur-[3px] backdrop-grayscale-50">
<div className="flex flex-col gap-16 items-center text-center"> <div className="flex flex-col gap-16 items-center text-center">
<div className="flex flex-col gap-4 items-center"> <div className="flex flex-col gap-4 items-center">
<h2 className="text-4xl font-bold"> <h2 className="text-4xl font-bold sm:text-6xl">
<AuroraText>Orchestral</AuroraText> Event Management <AuroraText>Orchestral</AuroraText> Event Management
</h2> </h2>
<TextAnimate <TextAnimate
animation="blurInUp" animation="blurInUp"
by="character" by="character"
as="h3" as="h3"
className="text-xl" className="text-xl sm:text-3xl"
> >
Without The Hassle Without The Hassle
</TextAnimate> </TextAnimate>
@ -34,37 +36,127 @@ export function App() {
</div> </div>
</div> </div>
</div> </div>
<div className="relative overflow-hidden py-5 flex flex-col gap-5"> <div className="relative overflow-hidden py-5 flex flex-col gap-5 sm:py-10">
<TextAnimate <TextAnimate
animation="blurInUp" animation="blurInUp"
by="character" by="character"
as="h3" as="h3"
className="text-xl font-bold text-center" className="text-xl font-bold text-center sm:text-3xl"
> >
Our Partners Our Partners
</TextAnimate> </TextAnimate>
<PartnersMarquee /> <PartnersMarquee />
<span></span> {/* Spacer for DotPattern */} <span></span> {/* Spacer for DotPattern */}
<DotPattern className="-z-10 mask-[radial-gradient(300px_circle_at_center,white,transparent)]" /> <DotPattern className="-z-10" />
</div> </div>
<div className="flex flex-col gap-5 p-6 bg-stone-200 text-center"> <div className="bg-[url(https://cdn.toontrack.com/app/uploads/product/upright-ebx/jpg/upright-ebx-top-mobile.jpg)] bg-cover bg-center text-white">
<div className="flex flex-col gap-5 p-6 text-center sm:px-[100px] md:px-[200px] lg:px-[300px] xl:px-[400px] backdrop-blur-[5px] backdrop-grayscale-25 sm:py-10">
<TextAnimate
animation="blurInUp"
by="character"
as="h3"
className="text-xl font-bold text-center sm:text-3xl"
>
About Us
</TextAnimate>
<p className="sm:text-lg">
We are dedicated to simplifying orchestral event management
through innovative solutions tailored to the unique needs of
orchestras and their audiences.
</p>
<p className="sm:text-lg">
Our platform streamlines the planning, coordination, and execution
of orchestral events, allowing musicians and organizers to focus
on what they do best: creating unforgettable musical experiences.
</p>
</div>
</div>
<div className="relative flex flex-col gap-5 p-6 text-center sm:px-[100px] md:px-[200px] lg:px-[300px] xl:px-[400px] backdrop-blur-[6px] sm:py-10">
<TextAnimate <TextAnimate
animation="blurInUp" animation="blurInUp"
by="character" by="character"
as="h3" as="h3"
className="text-xl font-bold text-center" className="text-xl font-bold text-center sm:text-3xl"
> >
About Us Events
</TextAnimate> </TextAnimate>
<p> <p className="sm:text-lg">
We are dedicated to simplifying orchestral event management through Are you an event organizer looking to host an orchestral
innovative solutions tailored to the unique needs of orchestras and performance? Our platform connects you with talented orchestras and
their audiences. provides the tools you need to plan and execute a successful event.
</p> </p>
<p> <div className="flex justify-center">
Our platform streamlines the planning, coordination, and execution <RainbowButton variant="outline" className="w-fit">
of orchestral events, allowing musicians and organizers to focus on Events Services
what they do best: creating unforgettable musical experiences. </RainbowButton>
</div>
<GridPattern strokeDasharray="4 2" className="-z-10" />
</div>
<div className="bg-[url(https://images.stockcake.com/public/1/3/a/13a0d24a-a714-43b5-b462-636017c01d60_large/grand-piano-interior-stockcake.jpg)] bg-cover bg-center text-white">
<div className="flex flex-col gap-5 p-6 text-center sm:px-[100px] md:px-[200px] lg:px-[300px] xl:px-[400px] backdrop-blur-[3px] backdrop-grayscale-50 sm:py-10">
<TextAnimate
animation="blurInUp"
by="character"
as="h3"
className="text-xl font-bold text-center sm:text-3xl"
>
Employee Management
</TextAnimate>
<p className="sm:text-lg">
Are you an orchestra looking to manage your musicians and staff
more effectively? Our platform offers comprehensive employee
management solutions designed specifically for orchestras.
</p>
<div className="flex justify-center">
<RainbowButton variant="outline" className="w-fit">
Employee Management Services
</RainbowButton>
</div>
</div>
</div>
<div className="relative flex flex-col gap-4 p-6 sm:px-[100px] md:px-[200px] lg:px-[300px] xl:px-[400px] sm:py-10 sm:flex-row sm:justify-around">
<h3 className="font-alex-brush text-2xl sm:text-3xl">
Great Music LLM
</h3>
<div className="flex flex-col gap-2">
<h4 className="font-bold text-lg sm:text-xl">Services</h4>
<div className="flex flex-col gap-1">
<a
href="#"
className="text-gray-500 hover:text-gray-600 hover:underline sm:text-lg"
>
Events
</a>
<a
href="#"
className="text-gray-500 hover:text-gray-600 hover:underline sm:text-lg"
>
Employee Management
</a>
</div>
</div>
<div className="flex flex-col gap-2">
<h4 className="font-bold text-lg sm:text-xl">Company</h4>
<div className="flex flex-col gap-1">
<a
href="#"
className="text-gray-500 hover:text-gray-600 hover:underline sm:text-lg"
>
Terms &amp; Conditions
</a>
<a
href="#"
className="text-gray-500 hover:text-gray-600 hover:underline sm:text-lg"
>
Privacy Policy
</a>
</div>
</div>
<StripedPattern className="-z-10 text-gray-300" />
</div>
<div className="p-2 border-t">
<p className="text-center text-xs">
© 2025 Great Music LLM | All rights reserved.
</p> </p>
</div> </div>
</main> </main>

View File

@ -0,0 +1,50 @@
import React, { useId } from "react";
import { cn } from "@/lib/utils";
interface StripedPatternProps extends React.SVGProps<SVGSVGElement> {
direction?: "left" | "right";
}
export function StripedPattern({
direction = "left",
className,
width = 10,
height = 10,
...props
}: StripedPatternProps) {
const id = useId();
const w = Number(width);
const h = Number(height);
return (
<svg
aria-hidden="true"
className={cn(
"pointer-events-none absolute inset-0 z-10 h-full w-full stroke-[0.5]",
className,
)}
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<defs>
<pattern id={id} width={w} height={h} patternUnits="userSpaceOnUse">
{direction === "left" ? (
<>
<line x1="0" y1={h} x2={w} y2="0" stroke="currentColor" />
<line x1={-w} y1={h} x2="0" y2="0" stroke="currentColor" />
<line x1={w} y1={h} x2={w * 2} y2="0" stroke="currentColor" />
</>
) : (
<>
<line x1="0" y1="0" x2={w} y2={h} stroke="currentColor" />
<line x1={-w} y1="0" x2="0" y2={h} stroke="currentColor" />
<line x1={w} y1="0" x2={w * 2} y2={h} stroke="currentColor" />
</>
)}
</pattern>
</defs>
<rect width="100%" height="100%" fill={`url(#${id})`} />
</svg>
);
}

View File

@ -1,14 +1,16 @@
import { RainbowButton } from "../ui/rainbow-button";
export function Navbar() { export function Navbar() {
return ( return (
<nav className="flex justify-between items-center p-4 px-6 shadow-md"> <nav className="flex justify-between items-center p-4 px-6 shadow-md">
<h1 className="text-2xl font-bold font-alex-brush">Great Music LLM</h1> <h1 className="text-2xl font-bold font-alex-brush">Great Music LLM</h1>
<div className="hidden sm:flex gap-2"> <div className="hidden sm:flex gap-2">
<button className="bg-blue-500 text-white px-4 py-2 rounded"> <RainbowButton variant="outline" className="w-fit">
Button 1 Events Services
</button> </RainbowButton>
<button className="bg-green-500 text-white px-4 py-2 rounded"> <RainbowButton variant="outline" className="w-fit">
Button 2 Employee Management Services
</button> </RainbowButton>
</div> </div>
</nav> </nav>
); );

View File

@ -122,7 +122,7 @@ export function DotPattern({
<stop offset="100%" stopColor="currentColor" stopOpacity="0" /> <stop offset="100%" stopColor="currentColor" stopOpacity="0" />
</radialGradient> </radialGradient>
</defs> </defs>
{dots.map((dot, index) => ( {dots.map((dot) => (
<motion.circle <motion.circle
key={`${dot.x}-${dot.y}`} key={`${dot.x}-${dot.y}`}
cx={dot.x} cx={dot.x}

View File

@ -0,0 +1,70 @@
import { useId } from "react";
import { cn } from "@/lib/utils";
interface GridPatternProps extends React.SVGProps<SVGSVGElement> {
width?: number;
height?: number;
x?: number;
y?: number;
squares?: Array<[x: number, y: number]>;
strokeDasharray?: string;
className?: string;
[key: string]: unknown;
}
export function GridPattern({
width = 40,
height = 40,
x = -1,
y = -1,
strokeDasharray = "0",
squares,
className,
...props
}: GridPatternProps) {
const id = useId();
return (
<svg
aria-hidden="true"
className={cn(
"pointer-events-none absolute inset-0 h-full w-full fill-gray-400/30 stroke-gray-400/30",
className,
)}
{...props}
>
<defs>
<pattern
id={id}
width={width}
height={height}
patternUnits="userSpaceOnUse"
x={x}
y={y}
>
<path
d={`M.5 ${height}V.5H${width}`}
fill="none"
strokeDasharray={strokeDasharray}
/>
</pattern>
</defs>
<rect width="100%" height="100%" strokeWidth={0} fill={`url(#${id})`} />
{squares && (
<svg x={x} y={y} className="overflow-visible">
{squares.map(([x, y]) => (
<rect
strokeWidth="0"
key={`${x}-${y}`}
width={width - 1}
height={height - 1}
x={x * width + 1}
y={y * height + 1}
/>
))}
</svg>
)}
</svg>
);
}

View File

@ -46,7 +46,7 @@ export function Marquee({
<div <div
{...props} {...props}
className={cn( className={cn(
"group flex [gap:var(--gap)] overflow-hidden p-2 [--duration:40s] [--gap:1rem]", "group flex gap-(--gap) overflow-hidden p-2 [--duration:40s] [--gap:1rem]",
{ {
"flex-row": !vertical, "flex-row": !vertical,
"flex-col": vertical, "flex-col": vertical,
@ -59,11 +59,11 @@ export function Marquee({
.map((_, i) => ( .map((_, i) => (
<div <div
key={i} key={i}
className={cn("flex shrink-0 justify-around [gap:var(--gap)]", { className={cn("flex shrink-0 justify-around gap-(--gap)", {
"animate-marquee flex-row": !vertical, "animate-marquee flex-row": !vertical,
"animate-marquee-vertical flex-col": vertical, "animate-marquee-vertical flex-col": vertical,
"group-hover:[animation-play-state:paused]": pauseOnHover, "group-hover:paused": pauseOnHover,
"[animation-direction:reverse]": reverse, "direction-[reverse]": reverse,
})} })}
> >
{children} {children}