Skip to content

Demijuls/kid-web

Repository files navigation

New KidCollective website with portfolio

Check out the demo here

Official website for Kid Collective, an independent creative agency based in Stockholm, Sweden.

Built with React + Vite + TypeScript, animated with Framer Motion, styled with Tailwind CSS v4.


Table of Contents


Getting Started

Prerequisites

  • Node.js v18 or higher
  • npm v9 or higher

Project Structure

src/
├── animated-components/     # Scroll and hover animation wrappers
│   ├── AnimatedCaseCard.tsx          # Per-card parallax wrapper for cases grid
│   ├── AnimatedFeaturedCaseCard.tsx  # Per-card parallax wrapper for featured cases
│   ├── AnimatedFooter.tsx
│   └── StickyGridAnimation.tsx       # Scroll-driven two-column parallax grid
├── assets/                  # Images, fonts, icons
│   ├── awards/
│   ├── cases/
│   ├── fonts/
│   └── icons/
├── components/              # Reusable UI components
├── data/                    # Hardcoded case data (temporary)
│   ├── AddedCasesData.ts
│   └── FeaturedCasesData.ts
├── hooks/                   # Custom React hooks
│   └── useIsDesktop.ts      # Returns true when viewport is >= 1024px
├── pages/                   # Page-level components
├── styles/                  # Layout wrappers, global CSS, design tokens
│   ├── CasesGrid.tsx        # Infinite scroll wrapper, renders StickyGridAnimation
│   ├── global.css
│   └── variables.css
└── types/
    └── Types.ts             # Shared TypeScript types

Tech Stack

Category Library Version
Framework React 19
Language TypeScript 5.9
Build tool Vite 8
Routing React Router DOM 7
Animation Framer Motion 12
Lottie animation @lottiefiles/dotlottie-react 0.18
Styling Tailwind CSS 4
Component dev Storybook 10
Testing Vitest + Playwright

Development

Available scripts

npm run dev            # Start dev server
npm run build          # Type-check and build for production
npm run preview        # Preview the production build locally
npm run lint           # Run ESLint
npm run storybook      # Start Storybook on port 6006
npm run build-storybook # Build static Storybook

Branch structure

Branch Purpose
main Production-ready code
styling Styling and UI work
origin/styling/animation Animation work
develop Integration branch

Routing

This is a Single Page Application — all routing is handled client-side by React Router. The netlify.toml at the project root contains the redirect rule needed for page refreshes and direct URL access:

[[redirects]]
  from = "/*"
  to = "/index.html"
  status = 200

This file must be present when deploying to any static host.


Adding New Cases

Cases are currently stored as static TypeScript arrays. There are two data files:

File Used by
src/data/AddedCasesData.ts Projects/Work page — all cases
src/data/FeaturedCasesData.ts Home page — highlighted cases

Case data shape

type Project = {
  id: number; // Must be unique across all cases
  title: string; // Case title
  client: string; // Client name
  category: string[]; // One or more category slugs (see below)
  imageUrl: string; // Card image
  hoverImageUrl?: string; // Optional — second image for hover reveal effect
  text: string; // Short description
  overlayColor?: string; // Optional — CSS variable e.g. "var(--brand-red)"
};

Available category slugs

award-winning   pr              design
tech-and-innovation  growth     content-x-some
communication   strategy        audio
film            digital

Steps to add a new case

  1. Add the case image(s) to src/assets/ (create a subfolder if needed)
  2. Open src/data/AddedCasesData.ts
  3. Import the image at the top of the file:
    import myNewCasePic from "../assets/my-new-case.png";
  4. Add a new entry to the addedCases array:
    {
      id: 13, // increment from the last id
      title: "Campaign Title",
      client: "Client Name",
      category: ["design", "pr"],
      imageUrl: myNewCasePic,
      text: "Short description of the campaign",
      overlayColor: "var(--brand-green)",
    }
  5. If the case should appear on the Home page, add it to FeaturedCasesData.ts as well

Featured cases limit

FeaturedCasesData.ts can hold more than 4 entries, but FeaturedCases only renders cards that have a matching entry in layoutFeaturedCases (currently 4 slots). Any project beyond index 3 is silently skipped. To show more featured cases, add a corresponding layout entry in FeaturedCases.tsx.

Adding a new category

Categories are defined in src/types/Types.ts:

export const categories: Category[] = [
  { label: "Award winning", slug: "award-winning" },
  // add new entry here
];

Both label (shown in the filter UI) and slug (used in URLs and data) are required.


Animation System

Scroll-driven parallax

Both the Featured Cases section (Home page) and the All Cases grid (Cases page) use a two-column scroll-driven parallax effect. Left and right columns move in opposite vertical directions as the user scrolls, creating a depth effect.

The effect is desktop-only (>= 1024px). On smaller screens, cards render statically at full opacity with no transforms applied.

How it works

Each grid has a useScroll target ref. scrollYProgress (0 → 1) drives two useTransform values — one per column — via Framer Motion. Cards in the left column drift in one direction, cards in the right column drift the opposite way.

scrollYProgress 0 → 1
  leftColumn:  "0px" → "60px"   (drifts down)
  rightColumn: "0px" → "-60px"  (drifts up)

Key files

File Role
StickyGridAnimation.tsx Creates scrollYProgress, leftColumn, rightColumn; maps each card to its column
AnimatedCaseCard.tsx Applies columnY transform and fade-in per card
FeaturedCases.tsx Same scroll setup for the featured grid
AnimatedFeaturedCaseCard.tsx Applies columnY transform per featured card

Column assignment

Each layout entry has an explicit column: "left" | "right" field defined in the LayoutCase type. This is the single source of truth — no class name parsing or index-based guessing.

// src/types/Types.ts
export type LayoutCase = {
  className?: string;
  height?: string;
  column: "left" | "right";
};

Desktop detection

// src/hooks/useIsDesktop.ts
export const useIsDesktop = () => { ... }

Uses window.matchMedia("(min-width: 1024px)") and a change listener. Returns a boolean. Both AnimatedCaseCard and AnimatedFeaturedCaseCard receive isDesktop as a prop and skip all style transforms when it is false.

Infinite scroll

The Cases page loads 8 cards at a time. An IntersectionObserver on a sentinel div at the bottom of the list triggers the next batch when it enters the viewport. The observer resets when the active filter changes.

This is handled entirely in CasesGrid.tsxStickyGridAnimation only receives the currently visible slice and is unaware of pagination.

Hover effects

CaseCard has two interactive effects:

  • Scale — the main image scales to 1.04 on hover via a Framer Motion animate prop
  • Clip-path reveal — a second hover image is revealed by an expanding inset() clip-path driven by mouse position. The reveal shape grows from the cursor outward using a spring-animated RADIUS motion value

A shared cardHover MotionValue<number> (0 or 1) is passed from the page level down to each card and also to CursorOnHover, which shows a "View project →" pill that follows the cursor and fades in when any card is hovered.


Deployment

Current setup

Deployed on Netlify as a test environment.

Setting Value
Build command npm run build
Publish directory dist
Node version 18+

Production deployment (planned)

The production site will be hosted on a custom .com domain. Steps:

  1. Purchase domain via Namecheap or Cloudflare Registrar
  2. In Netlify: Site settings → Domain management → Add custom domain
  3. Update DNS at the registrar to point to Netlify's nameservers
  4. SSL certificate is provisioned automatically by Netlify

Build requirements

The build script runs TypeScript type-checking before bundling (tsc -b && vite build). The build will fail if there are any TypeScript errors. Fix all TS errors before deploying.


Environment Variables

No environment variables are required for the current static setup.

When a backend or CMS is added in future, create a .env file at the project root:

VITE_API_URL=https://your-api-url.com

Vite only exposes variables prefixed with VITE_ to the frontend. Never put secrets or private keys in .env files in this project.


Design Tokens

Brand colors and typography are defined in src/styles/variables.css and available as CSS custom properties:

--brand-beige: #eee8e2 /* page background */ --brand-red: #fd4e00
  --brand-green: #05bd00 --brand-lime: #e9ff69 --brand-blue: #9ec0e8
  --brand-purple: #681637 --brand-brown: #91724c;

Typography:

  • font-primary — Familjen Grotesk (loaded from Google Fonts)
  • font-accent — KidSpaghetti (custom font, loaded from src/assets/fonts/)

Known Issues

Must fix before production launch

  • Contact form posts to a test URL (http://httpbin.org/anything) — replace with a real endpoint before going live, suggestions: Nodemailer + Gmail SMTP (requires small backend)
  • Case detail pages use hardcoded placeholder images and copy — needs to be data-driven per case

Planned pages (not yet built)

These routes exist in the router but the pages are empty placeholders:

  • /services
  • /our-people
  • /creative-principles
  • /create-like-kid
  • About page submenu and dropdown navigation

Future — CMS Integration

The current hardcoded data approach is temporary. Next step is to connect a headless CMS so cases can be managed without code changes.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors