Skip to the article
Aniq-UI

3D LandingMake it your own

Make it your own

Change the text, the flavours and their 3D cans, the colours, the images, the fonts and the page title.

Where everything is

WhatWhere
Every text, in both languagesmessages/en.json and messages/ar.json
Page title and descriptionthe metadata keys in the same two files
The flavours and their 3D canssrc/features/carousel/constants/juiceCans.ts, models in public/assets/3d/can/
The colours of each flavoursrc/features/product-showcase/constants/themes.ts
Lighting on the canspublic/assets/hdri/forest_slope_1k.hdr, named in src/features/carousel/constants/environment.ts
Fruit imagespublic/assets/images/fruit/, listed in src/features/floating-fruits/constants/fruitImages.ts
Ice cubes, leaves, water droppublic/assets/images/ice/, public/assets/images/leaf/, public/assets/images/drop.png
Cart sample itemssrc/features/navigation/constants/sampleCartItems.ts, images in public/assets/images/can/
Bottle sizessrc/features/product-showcase/constants/sizes.ts
Contact linkssrc/features/product-showcase/constants/contactLinks.ts
Menu itemssrc/features/navigation/constants/navItems.ts
Fontssrc/app/[locale]/layout.tsx and src/app/globals.css
Faviconsrc/app/favicon.ico

Run yarn dev while you work: the page reloads on every save.

Text

Every word on the page comes from messages/en.json and messages/ar.json. Change the value, never the key, and keep the same keys in both files.

KeyWhat it is
brand.nameThe name at the start of the menu bar
brand.wordmarkThe giant word behind the cans
juices.<id>.name, juices.<id>.descriptionThe title and the text of each flavour
nav.itemsThe menu labels
cart.itemsThe product names in the cart panel
scroll.firstLine, scroll.secondLineThe two words on the round button at the bottom, which opens the contact menu
contactThe contact menu's title and the labels of its links
metadataThe page title and description

The giant word is drawn in the Thunder font, in capitals, and is JUICY in both languages. Keep it in Latin letters, or change its font too.

Swap a flavour

A flavour is an id that ties together a 3D can, a colour theme, a fruit image and its text. The steps below replace Mango Burst with a new flavour, Peach Fizz, with the id peachFizz. Do the same for any of the four.

The wheel holds exactly four cans: four positions in src/features/carousel/constants/positionConfigs.ts, and a quarter turn per step. Replace a flavour rather than add a fifth.

  1. Rename the id

    In src/types/juice.ts, replace the old id with the new one. From here on, TypeScript points at every file that still uses the old id.

    src/types/juice.ts
    export type JuiceId =
      | "lemonGinger"
      | "blueberryAcai"
      | "peachFizz"
      | "raspberryRose";
  2. Make the can model

    Each can is a .glb file with one mesh and two materials: M_Aluminum, the metal body, and M_Cola, the label. The label is one image wrapped around the can, 2048 by 958 pixels, stored inside the .glb. To make your can, open an existing one in Blender (File > Import > glTF 2.0), replace the image in the M_Cola material with your own label at the same size, and export it with File > Export > glTF 2.0, format glTF Binary (.glb). Keep the model's size and position so it sits on the wheel like the others.

    Save it as public/assets/3d/can/peach.glb. The code makes every material shiny metal when it loads the model (metalness and roughness in src/features/carousel/hooks/useCanModel.ts), so change the finish there rather than in Blender.

  3. Point the wheel at the model

    In src/features/carousel/constants/juiceCans.ts, change the entry. The order of this list is the order on the wheel, and the first can is the one the page opens on.

    src/features/carousel/constants/juiceCans.ts
    { id: "peachFizz", model: "/assets/3d/can/peach.glb" },
  4. Give it its colours

    In src/features/product-showcase/constants/themes.ts, rename the entry and set its colours. What each one paints is in Colours.

    src/features/product-showcase/constants/themes.ts
    peachFizz: {
      mainBgColor: "#F4A27B",
      blurColor: "#FFE1D2",
      textColor: "white",
      accentColor: "rgba(255, 255, 255, 0.9)",
      buttonBgColor: "white",
      buttonTextColor: "#F4A27B",
    },
  5. Add its fruit image

    Put a PNG with a transparent background in public/assets/images/fruit/, at 400 by 600 pixels like the others, and name it in src/features/floating-fruits/constants/fruitImages.ts. The page shows it four times around the can, at different sizes and angles.

    src/features/floating-fruits/constants/fruitImages.ts
    peachFizz: "peach.png",
  6. Write its text

    In both messages/en.json and messages/ar.json, rename the flavour's keys under juices and cart.items and write the new text.

    messages/en.json
    "juices": {
      "peachFizz": {
        "name": "Peach Fizz",
        "description": "Ripe peaches and a light sparkle, cold from the can."
      }
    }
  7. Update the cart, if the flavour is in it

    The cart panel shows the items in src/features/navigation/constants/sampleCartItems.ts: Lemon Ginger and Blueberry Açai. If yours is one of them, change its juiceId, its price and its image, a picture of the can in public/assets/images/can/.

  8. Check

    Terminalin juicy
    yarn typecheck
    yarn dev

    Expected result: yarn typecheck reports no errors, and the new can turns into place with its colours, its fruit and its text.

    Having trouble?

When you replace the first flavour, Lemon Ginger, also change DEFAULT_JUICE_ID at the end of themes.ts, and the background of body in src/app/globals.css to its mainBgColor. That colour shows for a moment before the page has loaded.

Colours

Each flavour has a theme in src/features/product-showcase/constants/themes.ts. The page eases from one theme to the next as the wheel turns.

FieldWhat it paints
mainBgColorThe page background, the size buttons that are not selected, and the buttons in the cart, account and contact panels
blurColorThe soft glow behind the can
buttonBgColorThe fill of the See More button and of the selected size
buttonTextColorThe text of the See More button

textColor and accentColor are part of each theme but no component reads them. The text on the page is white.

While the page runs, useBodyBackground in src/features/landing/hooks/ paints the page background in the active flavour's mainBgColor. The body background in src/app/globals.css, #82AF38, only covers the moment before the page loads, so change it together with the first flavour.

The light reflected on the cans comes from public/assets/hdri/forest_slope_1k.hdr, Poly Haven's Forest Slope image (CC0). To change it, put another .hdr file in that folder and name it in src/features/carousel/constants/environment.ts.

Images

Replace a file with your own under the same name, or change the name where the table says.

ImageFileNamed in
Fruitspublic/assets/images/fruit/*.pngsrc/features/floating-fruits/constants/fruitImages.ts
Ice cubepublic/assets/images/ice/cube.webpsrc/features/ice-cubes/utils/getImagePath.ts
Leavespublic/assets/images/leaf/1.png, 2.pngsrc/features/ice-cubes/utils/getImagePath.ts
Water ripplepublic/assets/images/drop.pngsrc/features/landing/constants/stage.ts
Cans in the cartpublic/assets/images/can/*.webpsrc/features/navigation/constants/sampleCartItems.ts

The number of ice cubes and leaves, on phones and on larger screens, is set in src/features/ice-cubes/constants/layout.ts.

Fonts

  • Thunder, the giant word: its files are in src/app/fonts/ and src/app/[locale]/layout.tsx loads them with localFont. To use another font, put its files there and change the src paths.
  • Cairo, the Arabic text: loaded from Google Fonts in the same file with next/font/google. Replace Cairo with another Google font that has the arabic subset.
  • The Latin text uses Arial, set on body in src/app/globals.css.

Parts of the page

The page is one file, src/app/[locale]/page.tsx, which places each part. To remove one, delete its element there.

ElementWhat it is
<IceCubes>The ice cubes and leaves at the sides
<NavBar>The menu bar, with the language switch, the account and the cart
<WaterWaveStage>The water ripple under the pointer, around everything else
<ProductLogo>The giant word behind the cans
<JuiceCarousel>The 3D wheel, its arrows and the floating fruits
<SizeSelector>The round size buttons
<ProductInfo>The flavour's title, text and See More button
<ScrollDownButton>The round button at the bottom that opens the contact menu

The contact menu's links are in src/features/product-showcase/constants/contactLinks.ts. Each has an id, a url and an icon from react-icons, and its label is under contact.links.<id> in the message files. Replace the two sample links with your own.

src/features/product-showcase/constants/contactLinks.ts
import { FaInstagram } from "react-icons/fa6";

{ id: "instagram", url: "https://www.instagram.com/your-brand", icon: FaInstagram },
  • Menu items are listed by id in src/features/navigation/constants/navItems.ts, with their labels under nav.items. They are labels only and lead nowhere yet: to link one, render an <a> with your address in src/features/navigation/components/NavLinks.tsx.
  • See More, Checkout and View Cart have no action yet. Add an onClick or turn them into links where they are drawn: src/features/product-showcase/components/ProductInfo.tsx and src/features/navigation/components/CartSummary.tsx.
  • The sign-in form checks its fields and then clears them. To sign people in, send the values from onSubmit in src/features/navigation/hooks/useSignInForm.ts to your own back-end.

Bottle sizes

The round size buttons come from src/features/product-showcase/constants/sizes.ts. Pressing one selects it; selected: true marks the one chosen when the page opens. The unit's label is under units in the message files.

src/features/product-showcase/constants/sizes.ts
{ value: "355", unit: "ml", selected: true },

Page title, description and search

The browser tab's title and the description search engines show are metadata.title and metadata.description in each message file, so each language has its own. The tab icon is src/app/favicon.ico: replace it with your own under the same name.

The template sets no site address, social sharing image, sitemap or robots.txt. Once you know your domain, add them in generateMetadata in src/app/[locale]/layout.tsx, for example:

src/app/[locale]/layout.tsx
return {
  metadataBase: new URL("https://www.your-domain.com"),
  title: t("title"),
  description: t("description"),
  openGraph: { images: "/og.png" },
};

Put the sharing image at public/og.png, 1200 by 630 pixels.

Stuck on a step?

Find a fix before you start over.

Troubleshooting

Cookie Preferences

We use cookies to enhance your browsing experience, analyze site traffic, and personalize content. By clicking "Accept All", you consent to our use of cookies for analytics and personalized advertising.