diff --git a/README.md b/README.md index bc5e770..20ab0ce 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,6 @@ My architectural design portfolio website built with Next.js and Tailwind CSS, showcasing a range of projects and skills in a clean, minimalistic manor. It is fully responsive across all devices. -

πŸ”Ή Report Bug     @@ -20,11 +19,10 @@ My architectural design portfolio website built with Next.js and Tailwind CSS, s Request Feature

- # 🌟 Features - **Responsive Design**: Works smoothly on mobile, tablet, and desktop. -- **Customizable Content**: Easy to modify content to display your own projects and profile. (See [Managing Content](#managing-content). *This feature is still in development*) +- **Customizable Content**: Easy to modify content to display your own projects and profile. (See [Managing Content](#managing-content). _This feature is still in development_) - **SEO Optimized**: Includes metadata setup for SEO. # πŸ“‹ Using This Repository @@ -70,13 +68,13 @@ npm run start # πŸ›  Managing Content -Currently the content is stored in [`src/data`](./src/data/) folder. You can modify the content in the following files: +Currently the content is stored in [`./data`](./data/) folder. You can modify the content in the following files: -| filename | description | -| ------------------------------------------- | ----------------------------- | -| [`projects.json`](./src/data/projects.json) | Featured projects information | -| [`profile.json`](./src/data/profile.json) | Personal profile information | -| [`navbar.json`](./src/data/navbar.json) | Navbar links | +| filename | description | +| --------------------------------------- | ----------------------------- | +| [`projects.json`](./data/projects.json) | Featured projects information | +| [`profile.json`](./data/profile.json) | Personal profile information | +| [`manifest.json`](./data/manifest.json) | Site manifest information | ## Site Metadata @@ -129,132 +127,124 @@ export const metadata = { ## Managing Projects -The [`projects.json`](./src/data/projects.json) file located in the [`src/data`](./src/data/) directory plays a key role in managing the projects showcased on your portfolio website. Here's an in-depth guide on how to effectively update and manage this file. +The [`projects.json`](./data/projects.json) file located in the [`./data`](./data/) directory plays a key role in managing the projects showcased on your portfolio website. Here's an in-depth guide on how to effectively update and manage this file. -### Structure of [`projects.json`](./src/data/projects.json) +### Structure of [`projects.json`](./data/projects.json) The JSON file is structured as an array of objects, each representing a project. Below is a detailed description of each field within a project object: - -```json -{ - "name": "The name of the project", - "type": "The type of project (e.g., Design Studio, Research) - must be one of predefined enum values", - "year": "The year the project was completed or published", - "description": "A short description of the project", - "longDescription": "A more detailed description of the project", - "href": "A URL link to more details about the project or an external site", - "video": { - "src": "URL to a video showcasing the project", - "alt": "Alternative text for the video" - }, - "location": { - "name": "Name of the location relevant to the project", - "url": "Optional URL providing more details about the location" - }, - "group": ["Array of strings used to tag or categorize the project"], - "awards": [ - { - "name": "Name of the award", - "url": "URL to the award description", - "img": "URL to an image of the award" - } - ], - "publications": [ - { - "name": "Name of the publication", - "url": "URL to the publication", - "img": "URL to an image of the publication cover" - } - ], - "bentoAttributes": { - "className": "CSS class for additional styling attributes" - }, - "tutors": [ - { - "name": "Name of a tutor or mentor involved in the project", - "url": "URL to tutor's professional profile" - } - ], - "images": [ - { - "src": "URL to an image associated with the project", - "alt": "Alternative text for the image", - "className": "CSS class for styling the image", - "href": "Link to the source of the image", - "caption": "Caption for the image", - "credit": { - "text": "Text crediting the source of the image", - "url": "URL to the source of the image" - }, - "isHero": "Boolean indicating if this is the main image for the project", - "isAdaptive": "Boolean indicating if the image is adaptive", - "isCarousel": "Boolean indicating if the image should be included in a carousel", - "isExternal": "Boolean indicating if the image is from an external source", - "isVideo": "Boolean indicating if the image is actually a video thumbnail" - } +```yaml +- projects (array of objects, required) + [ + - id (string, required) + - name (string, required) + - type (string, required) + - Enum: ["Design Studio", "International Competition", "Design Studio / Research", "Research", "Research Assistanship"] + - year (string, required) + - description (string, required) + - longDescription (string, required) + - location (object, required) + - name (string, required) + - url (string, optional) + - group (array of strings, optional) + - awards (array of objects, optional) + [ + - name (string, required) + - url (string, optional) + - img (string, optional) + ] + - publications (array of objects, optional) + [ + - name (string, required) + - url (string, required) + - img (string, optional) + ] + - gallery (object, required) + - className (string, required) + - tutors (array of objects, optional) + [ + - name (string, required) + - url (string, required) + ] + - mediaContainer (object, required) + - className (string, optional) + - media (array of objects, optional) + [ + - src (string, optional) + - alt (string, optional) + - className (string, optional) + - credit (object, optional) + - text (string, optional) + - url (string, optional) + - isButton (boolean, optional) + - sizes (string, optional) + - caption (object, optional) + - text (string, optional) + - isExpose (boolean, optional) + - blurDataURL (string, optional) + - isHero (boolean, optional) + - isAdaptive (boolean, optional) + - isCarousel (boolean, optional) + - isExternal (boolean, optional) + - isInverted (boolean, optional) + - isVideo (boolean, optional) + ] ] -} ``` +### Notes + +- The schema uses draft-07 of JSON Schema. +- Additional properties are not allowed in the project objects. +- The `location`, `gallery`, and `mediaContainer` objects are required for each project. + ### Adding a New Project -To add a new project, simply append a new object to the array in [`projects.json`](./src/data/projects.json) using the schema provided above. Ensure all required fields are included to maintain site functionality. +To add a new project, simply append a new object to the array in [`projects.json`](./data/projects.json) using the schema provided above. Ensure all required fields are included to maintain site functionality. ## Managing Profile Information -Profile information is managed through the [`profile.json`](./src/data/profile.json) file located in the [`src/data`](./src/data/) directory. This file is structured as a single JSON object representing your personal or professional profile. +Profile information is managed through the [`profile.json`](./data/profile.json) file located in the [`./data`](./data/) directory. This file is structured as a single JSON object representing your personal or professional profile. -### Structure of [`profile.json`](./src/data/profile.json) +### Structure of [`profile.json`](./data/profile.json) Here’s what each field should contain: -```json -{ - "name": "Your full name", - "about": "A brief introduction or biography", - "image": { - "src": "Path to your profile picture", - "alt": "Alternative text for the profile picture" - }, - "keywords": [ - "Relevant keywords to your profession or skills (This will be displayed as rotating text on the homepage)" - ], - "slogan": "A catchy or meaningful slogan (This will be displayed as a hero text on the homepage)", - "social": { - "instagram": "URL to your Instagram profile", - "github": "URL to your GitHub profile", - "youtube": "URL to your YouTube channel" - }, - "contact": { - "email": "Your email address", - "phone": "Your phone number" - } -} -``` - -## Navbar - -If for reasons you want to change the navbar links, you can do so by modifying the [`navbar.json`](./src/data/navbar.json) file located in the [`src/data`](./src/data/) directory. The file is structured as an array of objects, each representing a link in the navbar. - -```json -[ - { - "title": "Section 1", - "url": "/#section-1", - "id": "section-1" - }, - { - "title": "Section 2", - "url": "/#section-2", - "id": "section-2" - } -] +## πŸ—οΈ Structure +```yaml +- Profile object + - name (string, required) + - Description: A brief description of yourself. + - about (string, required) + - Description: A brief description of yourself. + - image (object, optional) + - src (string, required) + - alt (string, optional) + - keywords (array of strings, required) + - slogan (string, required) + - social (object, required) + - instagram (string, optional) + - Format: URI + - github (string, optional) + - Format: URI + - youtube (string, optional) + - Format: URI + - contact (object, required) + - email (string, optional) + - Format: email + - phone (string, optional) ``` +### Notes +- The schema defines a single object with multiple properties. +- Required fields: `name`, `about`, `social`, `contact`, `keywords`, and `slogan`. +- The image object is optional, but if present, it must have a 'src' property. +- Social media links and contact information are structured as nested objects. +- All social media links should be valid URIs. +- The email field, if provided, should be in a valid email format. ## Images -Images are stored in the [`public`](./public/) folder. You can replace the images with your own images. Make sure to update the image paths in the [`projects.json`](./src/data/projects.json) file. +Images are stored in the [`public`](./public/) folder. You can replace the images with your own images. Make sure to update the image paths in the [`projects.json`](./data/projects.json) file. # 🀝 Contributing diff --git a/data/manifest.json b/data/manifest.json index f86660f..2856e38 100644 --- a/data/manifest.json +++ b/data/manifest.json @@ -1,6 +1,6 @@ { "name": "Zeke Zhang Portfolio", - "version": "1.5.4", + "version": "1.5.5", "copyright": { "name": "zekezhang.com" }, diff --git a/data/projects.json b/data/projects.json index 1477306..ad863b8 100644 --- a/data/projects.json +++ b/data/projects.json @@ -27,20 +27,17 @@ "awards": [ { "name": "ARM ARCHITECTURE PRIZE", - "url": "https://www.instagram.com/armarchitecture/p/CWVFOyvFK-9/?hl=zh-cn&ref=159", - "img": "/awards/arm.webp" + "url": "https://www.instagram.com/armarchitecture/p/CWVFOyvFK-9/?hl=zh-cn&ref=159" }, { "name": "RASCOL Students Choice Award", - "url": "https://www.instagram.com/p/CREE_m0lMBI/?img_index=1", - "img": "/awards/rascol.webp" + "url": "https://www.instagram.com/p/CREE_m0lMBI/?img_index=1" } ], "publications": [ { "name": "Architecture students embrace life-changing learning on Country", - "url": "https://www.rmit.edu.au/news/all-news/2021/may/architectural-students-learning-on-country", - "img": "/awards/rmit_publication.webp" + "url": "https://www.rmit.edu.au/news/all-news/2021/may/architectural-students-learning-on-country" }, { "name": "On Country: Framlingham, Portfolio 2021", @@ -253,7 +250,6 @@ "text": "The aggregation process is driven by a series of atomic discrete units that self-assemble into larger structures. This process is informed by the principles of swarm intelligence, where individual agents interact with their environment and each other to achieve a collective goal. The aggregation algorithm is designed to optimize the distribution of units, ensuring structural stability and adaptability to changing environmental conditions." }, "className": "md:col-span-3 col-span-6 md:h-auto", - "sizes": "(min-width: 865x) 25vw, 100vw", "isVideo": true }, { diff --git a/data/schema/projects-schema.json b/data/schema/projects-schema.json index 2201f96..0470e39 100644 --- a/data/schema/projects-schema.json +++ b/data/schema/projects-schema.json @@ -2,18 +2,22 @@ "$schema": "http://json-schema.org/draft-07/schema#", "title": "Project", "type": "array", + "uniqueItems": true, "items": { "type": "object", "additionalProperties": false, "properties": { "id": { - "type": "string" + "type": "string", + "description": "Unique identifier for the project (this will be part of the URL)" }, "name": { - "type": "string" + "type": "string", + "description": "Name of the project" }, "type": { "type": "string", + "description": "Type of the project", "enum": [ "Design Studio", "International Competition", @@ -23,45 +27,59 @@ ] }, "year": { - "type": "string" + "type": "string", + "description": "Year of the project" }, "description": { - "type": "string" + "type": "string", + "description": "Short description of the project", + "maxLength": 120 }, "longDescription": { - "type": "string" + "type": "string", + "description": "Full description of the project" }, "location": { "type": "object", + "description": "Location of the project", + "additionalProperties": false, "properties": { "name": { - "type": "string" + "type": "string", + "description": "Name of the location" }, "url": { - "type": "string" + "type": "string", + "description": "Location URL. (e.g. Google Maps)", + "format": "uri" } }, "required": ["name"] }, "group": { "type": "array", + "description": "Group members of the project", + "uniqueItems": true, "items": { "type": "string" } }, "awards": { "type": "array", + "description": "Awards, honourable mentions, shortlists of the project", + "uniqueItems": true, "items": { "type": "object", + "additionalProperties": false, "properties": { "name": { - "type": "string" + "type": "string", + "description": "Name of the award" }, "url": { - "type": "string" - }, - "img": { - "type": "string" + "type": "string", + "format": "uri", + "description": "Award URL source if any (e.g. award website)" } }, "required": ["name"] @@ -69,17 +87,18 @@ }, "publications": { "type": "array", + "description": "Publications, articles, interviews of the project", + "uniqueItems": true, "items": { "type": "object", + "additionalProperties": false, "properties": { "name": { "type": "string" }, "url": { - "type": "string" - }, - "img": { - "type": "string" + "type": "string", + "format": "uri" } }, "required": ["name", "url"] @@ -87,23 +106,32 @@ }, "gallery": { "type": "object", + "description": "Homepage project gallery properties", + "additionalProperties": false, "properties": { "className": { - "type": "string" + "type": "string", + "description": "Gallery container tailwindCSS className (e.g. lg:col-span-3)" } }, "required": ["className"] }, "tutors": { "type": "array", + "description": "Supervisors, tutors of the project if any", + "uniqueItems": true, "items": { "type": "object", + "additionalProperties": false, "properties": { "name": { - "type": "string" + "type": "string", + "description": "Name of the tutor" }, "url": { - "type": "string" + "type": "string", + "format": "uri", + "description": "Tutor URL if any (e.g. personal website, linkedin etc.)" } }, "required": ["name", "url"] @@ -111,88 +139,178 @@ }, "mediaContainer": { "type": "object", + "description": "Media container properties", "properties": { "className": { - "type": "string" + "type": "string", + "description": "Media container tailwindCSS className (e.g. grid-cols-3)" }, "media": { "type": "array", + "description": "Media items of the project", + "uniqueItems": true, "items": { "type": "object", "additionalProperties": false, "properties": { "src": { - "type": "string" + "type": "string", + "description": "Media source file path. `./public/` folder as root. (e.g. /images/project.jpg)" }, "alt": { - "type": "string" + "type": "string", + "description": "Media alt text for accessibility" }, "className": { - "type": "string" + "type": "string", + "description": "Media item tailwindCSS className (e.g. col-span-2)" }, "credit": { "type": "object", + "description": "Media credit reference properties", "additionalProperties": false, "properties": { "text": { - "type": "string" + "type": "string", + "description": "Credit text" }, "url": { - "type": "string" + "type": "string", + "format": "uri", + "description": "Credit source URL" }, "isButton": { "type": "boolean", + "description": "Credit as button inside lightbox?", "default": true } } }, "sizes": { - "type": "string" + "type": "string", + "description": "Media sizes attribute for responsive images (e.g. '(min-width: 640px) 50vw, 100vw')" }, "caption": { "type": "object", "additionalProperties": false, "properties": { "text": { - "type": "string" + "type": "string", + "description": "Detail image description text" }, "isExpose": { "type": "boolean", + "description": "Expose caption outside of lightbox?", "default": true } - } - }, - "blurDataURL": { - "type": "string" + }, + "allOf": [ + { + "if": { + "properties": { + "isExpose": { + "enum": [true] + } + }, + "required": ["isExpose"] + }, + "then": { + "properties": { + "text": { + "description": "Detail image description text (max 120 characters)", + "maxLength": 120 + } + } + } + } + ] }, "isHero": { "type": "boolean", + "description": "Image as hero image in project detail page? Gallery will show hero image. (one hero image per project must be set)", "default": true }, "isAdaptive": { "type": "boolean", + "description": "Media as adaptive image?", "default": true }, "isCarousel": { "type": "boolean", + "description": "Media inside carousel?", "default": true }, "isExternal": { "type": "boolean", + "description": "Media sourced from external URL?", "default": true }, "isInverted": { "type": "boolean", + "description": "Media as inverted image in dark mode?", "default": true }, "isVideo": { "type": "boolean", + "description": "Media is video?", "default": true } - } + }, + "required": ["src", "alt"], + "allOf": [ + { + "if": { + "properties": { + "isVideo": { + "enum": [true] + } + }, + "required": ["isVideo"] + }, + "then": { + "properties": { + "src": { + "description": "Video source URL (local video is not supported yet)", + "format": "uri" + }, + "isHero": { + "description": "Video as hero video in project detail page? Gallery will still show hero image." + }, + "isInverted": { + "const": false + }, + "isAdaptive": { + "const": false + } + }, + "not": { + "required": ["sizes"] + } + } + }, + { + "if": { + "properties": { + "isExternal": { + "enum": [true] + } + }, + "required": ["isExternal"] + }, + "then": { + "properties": { + "src": { + "description": "External media source URL (e.g. https://www.example.com/image.jpg)", + "format": "uri" + } + } + } + } + ] } } - } + }, + "required": ["media"] } }, "required": [ diff --git a/todo.md b/todo.md index 2bc1bbd..52ad43d 100644 --- a/todo.md +++ b/todo.md @@ -6,7 +6,7 @@ - [ ] Complete image descriptions - [x] Refactor `project.json` data structure -- [ ] Add description for each items in `projects-schema.json` +- [x] Add description for each items in `projects-schema.json` - [x] Implement separate `blurhash.json` for image placeholders - [ ] Rename image files