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