guy landing page.

guy

software landing page

star icon. front-end

js

html

sass

the goal

build a marketing page for a mobile app companion using Sass for styling, the IntersectionObserver API to dynamically apply styles, and local storage save themes.

guy's inspiration

guy is a mobile app companion that actively listens to your conversations and responds to you via phone notification. This concept draws from Friend, an AI pendant that garnered a lot of attention for its potential to replace human connection.


Friend states that “your friend is always listening and forming their own internal thoughts,” which sparked conversation regarding the ethical concerns of the pendant.

due to the mixed reactions to Friend, i thought it would be fun to not only create a website inspired by its modern branding, but attempt to market an unsettling product to the average person using a SaaS-style approach.

using Sass

Sass was essential for organizing the project’s styling, particularly when implementing features like dark mode and animations. by isolating color properties in a separate .scss file, i could easily manage and adjust light/dark mode without cluttering other files.

.about {
    .notif_container {
        .notif {
            background-color: #ffffff99;
            border: 0.25px $grey-100 solid;
        }
    }
    .about_content {
        background-color: $white;
        box-shadow: 0px 0px 12px 1px #9191914D;
    }
}

.pricing {
    .cards_container {
        .card {
            background-color: $white;
            box-shadow: 0px 0px 12px 1px #9191914D;
            span {
                color: $grey-700;
            }
            .card_title {
                ion-icon {
                    color: $grey-800;
                }
            }
            button {
                background-color: $grey-100;
                border: $grey-500 solid 1px;
                color: $grey-900;
            }
        }
        > div:nth-child(3) {
            border: $orange-200 2px solid;
            box-shadow: 0px 0px 12px 1px #ff59004d;
            button {
                background-color: $orange-200;
                color: $white;
                border: $orange-100 solid 1px;
            }
        }
    }
}
.about {
    > img {
        filter: brightness(0.75);
    }
    .notif_container {
        .notif {
            background-color: #2c2c2c99;
            border: 0.25px $grey-800 solid;
        }
    }
    .about_content {
        background-color: $grey-800;
        box-shadow: 0px 0px 12px 1px #0000004D;
    }
}

.pricing {
    .cards_container {
        .card {
            background-color: $grey-700;
            box-shadow: 0px 0px 12px 1px #0000004D;
            span {
                color: $grey-200;
            }
            .card_title {
                ion-icon {
                    color: $grey-100;
                }
            }
            button {
                background-color: $grey-800;
                border: $grey-700 solid 1px;
                color: $white;
            }
        }
        > div:nth-child(3) {
            button {
                background-color: #b45a29;
                color: $white;
                border: #ca8762 solid 1px;
            }
        }
    }
}

leveraging localStorage

localStorage allows for data to be stored in a user’s browser, allowing for certain settings to be stored across different sessions. for guy, i stored the user’s dark/light mode preference so that it can be saved every time they visit the website or refresh the page, resulting in a pleasant user experience.

// moon -> toggles dark mode; default
let toggleoff = document.querySelector('#dark');
// sun -> toggles light mode
let toggleon = document.querySelector('#light');
// gets all notifications to change image
let notifs = document.querySelectorAll('.notif');
// get mode from local storage
let mode = localStorage.getItem("mode");

// updates the theme of the website
function updateMode(mode) {
    // if the mode is dark mode
    if (mode === 'darkmode') {
        // set mode to dark in local storage
        localStorage.setItem('mode', 'darkmode');
        // show the light button
        toggleoff.style.display = 'none';
        toggleon.style.display = 'block';
        // add the dark class to the body
        document.body.classList.add("darkmode");
        // remove the light class from the body
        document.body.classList.remove('lightmode');
        // change the notifications to the dark version
        notifs[0].src = '../media/hero-notif-dark.png';
        notifs[1].src = '../media/friends-notif-dark.png';
    } else {
        // set mode to light in local storage
        localStorage.setItem('mode', 'lightmode');
        // show the dark button
        toggleoff.style.display = 'block';
        toggleon.style.display = 'none';
        // remove the dark class from the body
        document.body.classList.remove("darkmode");
        // add the light class to the body
        document.body.classList.add('lightmode');
        // change the notifications to the light version
        notifs[0].src = '../media/hero-notif-light.png';
        notifs[1].src = '../media/friends-notif-light.png';
    }
}

// updates the mode when either toggle button is clicked
toggleon.addEventListener('click', function () {
    updateMode('lightmode');
});

toggleoff.addEventListener('click', function () {
    updateMode('darkmode');
});

// updates the mode if the mode is in local storage
if (mode === 'darkmode') {
    updateMode('darkmode');
} else if (mode === 'lightmode'){
    updateMode('lightmode');
}else(updateMode('lightmode'))

working with IntersectionObserver API

we were provided a script.js file to integrate the IntersectionObserver API. i customized it to trigger fade and slide animations as elements entered the viewport, which enhanced the site’s modern feel and visually demonstrated how guy’s notifications would appear on a phone. i also used the ::after pseudo-element to add a dynamic section indicator to the navbar, improving the site’s navigation and user experience.

nav {
    display: flex;
    align-items: center;
    flex-direction: row;
    width: 100%;
    justify-content: space-between;
    a {
        font-family: $serif;
        font-size: 2.8rem;
        transition: color 0.25s ease;
    }
    a::after {
        display: block;
        content: '';
        position: absolute;
        left: 0;
        height: 0.4rem;
        width: 0.4rem;
        border-radius: 0.4rem;
        margin-top: 0.2rem;
        transform: translateX(calc(var(--markerLeft) + var(--markerWidth)  / 2 - 0.2rem));
        transition: transform ease 0.25s;
        will-change: width;
        -webkit-animation: dotAppear ease 0.5s;
                animation: dotAppear ease 0.5s;
    }
    a:hover {
        color: $orange-200 !important;
    }
}

FAQ accordions

for the FAQ section, i implemented animated accordions using the max-height property. this kept the content organized and easy to access, with smooth transitions when opening or closing each question.

// gets all FAQ items
const FAQS = document.querySelectorAll('.faq-item');
// gets all FAQ buttons
const FAQBtns = document.querySelectorAll('.faq-btn');

// toggles the visibility of the FAQ item
function toggleFAQ() {

    // iterates over all FAQ items and hide the ones that are not the parent element of the button that was clicked
    FAQS.forEach(el => {
        if (el !== this.parentElement) {
            el.classList.remove('expand');
        }
    })

    // toggles the visibility of the parent element of the button that was clicked
    this.parentElement.classList.toggle('expand');

}

// adds a click event listener to each FAQ button
// toggles the visibility of the corresponding FAQ item
FAQBtns.forEach(FAQBtn => FAQBtn.addEventListener('click', toggleFAQ));
.faq {
    height: fit-content;
    > div {
        visibility: hidden;
        height: 100%;
        display: grid;
        place-items: center;
        width: 100%;
        > div {
            height: -webkit-fit-content;
            height: -moz-fit-content;
            height: fit-content;
            border-radius: 16px;
            overflow: hidden;
            .faq-item {
                max-height: 6.6rem;
                overflow: hidden;
                transition: max-height 0.5s ease;
                width: 100%;
                button {
                    text-align: left;
                    border: 0;
                    padding: 2rem;
                    width: 100%;
                    font-weight: 600;
                    display: flex;
                    align-items: center;
                    justify-content: space-between;
                    ion-icon {
                        font-size: 2.4rem;
                        transition: transform 0.5s ease;
                        min-width: 2.4rem;
                    }
                }
                button:hover {
                    cursor: pointer;
                }
                .faq-content {
                    padding: 1.6rem;
                    border: 0;
                    border-top: unset;
                }
            }
            .expand {
                max-height: 20rem;
                button {
                    ion-icon {
                        transform: rotate(180deg);
                    }
                }
            }
        }
    }
}

bringing it together

the marketing page became a successful blend of modern design, interactive features, and smooth user experience. by leveraging Sass for organized styling, the IntersectionObserver API added an interactive layer, and localStorage integration i was able to explore the intriguing challenge of marketing a product through a sleek, SaaS-style website.