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