Heading 2

Heading 3

CAPTIONS

Paragraph

Subheading

Instrument
Serif

Figtree

Heading 1

Case Study 1

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

EXAMPLE SECTION - CAPTIONS

What would I write here? - h2

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

Subheading

Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

Case Study 1

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

EXAMPLE SECTION - CAPTIONS

What would I write here? - h2

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

Something Title - h3

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

16px

64px

8px

16px

32px

8px

16px

64px

8px

16px

32px

8px

160px

How pretty!

PORTFOLIO COLOURS

Mid Grey
#737373
AA Normal // AA Large + Lightest Grey

Dark Grey
#525252
AAA Normal

Lightest Grey
#F5F5F5
Backgrounds

Light Grey
#ABA9A9
AA Large

Accent Small
AA Normal

Accent Large
AA Large

Off Black
#171717
AAA Normal

Paragraph Bold

8px

16px

32px

64px

128px

xs

sm

md

lg

xl

Password-protect coding

https://designying.io/blog/password-protect-page

160px

xxl

import React, { useState, useEffect, useRef } from "react"
import type { ComponentType } from "react"
import { addPropertyControls, ControlType } from "framer"
import {
    Eye,
    EyeSlash,
    LockSimple,
    LockSimpleOpen,
    ArrowUUpLeft,
} from "phosphor-react"

const SESSION_KEY = "framer_auth_session"

// ===== CUSTOMIZATION SECTION =====
// Edit this section to customize the password protection

// Add or remove passwords here (case-sensitive)
const ALLOWED_PASSWORDS = ["2025rekroFull"]

// Customize the text content here
const TEXT_CONTENT = {
    title: "Content Restricted",
    subtitle: "Enter passcode to continue (passcode: abcd)",
    errorMessage: "Incorrect passcode. Please try again.",
    buttonText: "Unlock Full Case Study",
    returnButtonText: "Return to Rekro Showcase",
}

// Change this URL to where you want users to return when clicking the link to return
const RETURN_URL = "https://christiefoo.com/casestudies/rekro"

// Customize the design here
const STYLE_TOKENS = {
    colors: {
        background: "#FFFFFF", // Background Color
        text: "#171717", // Main text color
        primary: "#106535", // Color for buttons and important elements
        secondaryText: "#737373", // Color for less important text
        buttonText: "#FFFFFF", // Color for the button text
        error: "#D12972", // Color for error messages and error input border
        inputBorder: "#ABA9A9", // Default border color for input fields
        inputBorderFocus: "#525252", // Border color when input is focused
        inputBorderError: "#D12972", // Border color when there's an error
        buttonHover: "#106535", // Button color on hover
        linkHover: "#106535", // Home link color on hover
    },
    fonts: {
        heading: "Instrument Serif, serif", // Font for headings
        body: "figtree, sans-serif", // Font for body text
        button: "figtree, sans-serif", // Font for button text
    },
    fontSizes: {
        title: "clamp(30px, 1.5625vw + 25px, 48px)", // Responsive title size
        paragraph: "16px",
        link: "16px",
        input: "16px",
        button: "16px",
        small: "14px",
    },
    fontWeights: {
        normal: "300",
        medium: "500",
        bold: "400",
    },
    spacing: {
        xs: "4px",
        sm: "8px",
        md: "16px",
        lg: "32px",
        xl: "64px",
        xxl: "128px",
        xxxl: "160px",
    },
    borderRadius: {
        small: "15px",
    },
    container: {
        maxWidth: "350px",
    },
}
// ===== END OF CUSTOMIZATION SECTION =====

const styles = {
    container: {
        display: "flex",
        flexDirection: "column",
        alignItems: "center",
        justifyContent: "center",
        minHeight: "100vh",
        backgroundColor: STYLE_TOKENS.colors.background,
        fontFamily: STYLE_TOKENS.fonts.body,
        color: STYLE_TOKENS.colors.text,
        padding: STYLE_TOKENS.spacing.md,
    },
    header: {
        marginBottom: STYLE_TOKENS.spacing.md,
        textAlign: "center",
    },
    title: {
        fontSize: STYLE_TOKENS.fontSizes.title,
        fontWeight: STYLE_TOKENS.fontWeights.bold,
        fontFamily: STYLE_TOKENS.fonts.heading,
        lineHeight: "120%",
        marginBottom: STYLE_TOKENS.spacing.md,
    },
    subtitle: {
        fontFamily: STYLE_TOKENS.fonts.body,
        fontWeight: STYLE_TOKENS.fontWeights.normal,
        fontSize: STYLE_TOKENS.fontSizes.paragraph,
        lineHeight: "125%",
        marginBottom: STYLE_TOKENS.spacing.sm,
        color: STYLE_TOKENS.colors.secondaryText,
    },
    link: {
        fontFamily: STYLE_TOKENS.fonts.body,
        fontWeight: STYLE_TOKENS.fontWeights.medium,
        fontSize: STYLE_TOKENS.fontSizes.link,
        lineHeight: "120%",
        margin: "0px",
        color: STYLE_TOKENS.colors.secondaryText,
    },
    form: {
        width: "100%",
        maxWidth: STYLE_TOKENS.container.maxWidth,
    },
    inputContainer: {
        position: "relative",
    },
    input: {
        width: "100%",
        fontFamily: STYLE_TOKENS.fonts.body,
        fontSize: STYLE_TOKENS.fontSizes.paragraph,
        fontWeight: STYLE_TOKENS.fontWeights.normal,
        paddingTop: STYLE_TOKENS.spacing.md,
        paddingBottom: STYLE_TOKENS.spacing.md,
        paddingLeft: STYLE_TOKENS.spacing.md,
        paddingRight: STYLE_TOKENS.spacing.xxxl,
        fontSize: STYLE_TOKENS.fontSizes.input,
        border: `1px solid ${STYLE_TOKENS.colors.inputBorder}`,
        borderRadius: STYLE_TOKENS.borderRadius.small,
        outline: "none",
        transition: "border-color 0.3s",
        WebkitAppearance: "none",
        MozAppearance: "none",
        appearance: "none",
    },
    inputError: {
        borderColor: STYLE_TOKENS.colors.inputBorderError,
    },
    showPasswordButton: {
        position: "absolute",
        right: STYLE_TOKENS.spacing.md,
        top: "50%",
        transform: "translateY(-50%)",
        background: "none",
        border: "none",
        cursor: "pointer",
        padding: "0",
    },
    button: {
        width: "100%",
        padding: STYLE_TOKENS.spacing.md,
        marginTop: STYLE_TOKENS.spacing.md,
        fontFamily: STYLE_TOKENS.fonts.button,
        fontSize: STYLE_TOKENS.fontSizes.button,
        fontWeight: STYLE_TOKENS.fontWeights.medium,
        color: STYLE_TOKENS.colors.buttonText,
        backgroundColor: STYLE_TOKENS.colors.primary,
        border: "none",
        borderRadius: STYLE_TOKENS.borderRadius.small,
        cursor: "pointer",
        transition: "background-color 0.3s",
        display: "flex",
        alignItems: "center",
        justifyContent: "center",
    },
    buttonHovered: {
        backgroundColor: STYLE_TOKENS.colors.buttonHover,
    },
    error: {
        color: STYLE_TOKENS.colors.error,
        marginTop: STYLE_TOKENS.spacing.md,
        fontSize: STYLE_TOKENS.fontSizes.small,
    },
    returnLink: {
        marginTop: STYLE_TOKENS.spacing.xl,
        color: STYLE_TOKENS.colors.text,
        textDecoration: "none",
        fontWeight: STYLE_TOKENS.fontWeights.medium, // Base weight
        fontSize: STYLE_TOKENS.fontSizes.small,
        display: "flex",
        alignItems: "center",
    },
    returnLinkHovered: {
        color: STYLE_TOKENS.colors.linkHover,
    },
}

export function requireAuth(Component): ComponentType {
    return (props) => {
        const [authenticated, setAuthenticated] = useState(false)
        const [showPassword, setShowPassword] = useState(false)
        const [errorMessage, setErrorMessage] = useState("")
        const [loading, setLoading] = useState(true)
        const [hovered, setHovered] = useState(false)
        const passwordRef = useRef(null)

        // Check for existing session on component mount
        useEffect(() => {
            const checkSession = () => {
                const session = localStorage.getItem(SESSION_KEY)
                if (session) {
                    try {
                        const sessionData = JSON.parse(session)
                        const isValid = Date.now() < sessionData.expiresAt

                        // Check if the stored password is still valid
                        const passwordStillValid = ALLOWED_PASSWORDS.includes(
                            sessionData.password
                        )

                        if (isValid && passwordStillValid) {
                            setAuthenticated(true)
                        } else {
                            // Clear invalid session
                            localStorage.removeItem(SESSION_KEY)
                            setAuthenticated(false)
                        }
                    } catch (e) {
                        localStorage.removeItem(SESSION_KEY)
                        setAuthenticated(false)
                    }
                }
                setLoading(false)
            }

            checkSession()
        }, [])

        useEffect(() => {
            if (passwordRef.current) {
                passwordRef.current.focus()
            }
        }, [])

        const createSession = (password) => {
            // Set session to expire in 24 hours
            const expiresAt = Date.now() + 24 * 60 * 60 * 1000
            localStorage.setItem(
                SESSION_KEY,
                JSON.stringify({
                    authenticated: true,
                    expiresAt,
                    password, // Store the password used for authentication
                })
            )
        }

        const validateAuth = (e) => {
            e.preventDefault()
            const inputPassword = e.target.elements.password.value

            if (ALLOWED_PASSWORDS.includes(inputPassword)) {
                setAuthenticated(true)
                createSession(inputPassword) // Pass the password to createSession
                setErrorMessage("")
            } else {
                setAuthenticated(false)
                setErrorMessage(TEXT_CONTENT.errorMessage)
            }

            e.target.elements.password.value = ""
        }

        if (loading) {
            return null // Or return a loading spinner
        }

        if (!authenticated) {
            return (
                <div style={styles.container}>
                    <div style={styles.header}>
                        <h1 style={styles.title}>{TEXT_CONTENT.title}</h1>
                        <p style={styles.subtitle}>{TEXT_CONTENT.subtitle}</p>
                    </div>
                    <form onSubmit={validateAuth} style={styles.form}>
                        <div style={styles.inputContainer}>
                            <input
                                type={showPassword ? "text" : "password"}
                                name="password"
                                ref={passwordRef}
                                style={{
                                    ...styles.input,
                                    ...(errorMessage ? styles.inputError : {}),
                                }}
                                onFocus={(e) =>
                                    (e.target.style.borderColor =
                                        STYLE_TOKENS.colors.inputBorderFocus)
                                }
                                onBlur={(e) =>
                                    (e.target.style.borderColor = errorMessage
                                        ? STYLE_TOKENS.colors.inputBorderError
                                        : STYLE_TOKENS.colors.inputBorder)
                                }
                                autoComplete="new-password"
                            />
                            <button
                                type="button"
                                onClick={() => setShowPassword(!showPassword)}
                                style={styles.showPasswordButton}
                            >
                                {showPassword ? (
                                    <EyeSlash
                                        size={20}
                                        color={
                                            STYLE_TOKENS.colors.secondaryText
                                        }
                                    />
                                ) : (
                                    <Eye
                                        size={20}
                                        color={
                                            STYLE_TOKENS.colors.secondaryText
                                        }
                                    />
                                )}
                            </button>
                        </div>
                        {errorMessage && (
                            <p style={styles.error}>{errorMessage}</p>
                        )}
                        <button
                            type="submit"
                            style={{
                                ...styles.button,
                                ...(hovered ? styles.buttonHovered : {}),
                            }}
                            onMouseEnter={() => setHovered(true)}
                            onMouseLeave={() => setHovered(false)}
                        >
                            {hovered ? (
                                <LockSimpleOpen size={20} />
                            ) : (
                                <LockSimple size={20} />
                            )}
                            <span
                                style={{
                                    marginLeft: STYLE_TOKENS.spacing.sm,
                                }}
                            >
                                {TEXT_CONTENT.buttonText}
                            </span>
                        </button>
                    </form>
                    <a
                        href={RETURN_URL}
                        style={styles.returnLink}
                        onMouseEnter={(e) =>
                            (e.currentTarget.style.color =
                                STYLE_TOKENS.colors.linkHover)
                        }
                        onMouseOut={(e) =>
                            (e.currentTarget.style.color =
                                STYLE_TOKENS.colors.text)
                        }
                    >
                        <ArrowUUpLeft
                            size={16}
                            style={{ marginRight: STYLE_TOKENS.spacing.sm }}
                        />
                        {TEXT_CONTENT.returnButtonText}
                    </a>
                </div>
            )
        }

        return <Component {...props} />
    }
}

// Add property controls (if needed)
addPropertyControls(requireAuth, {
    // Add your property controls here
})

Code block customised already