diff --git a/about.html b/about.html new file mode 100644 index 0000000..52c07d4 --- /dev/null +++ b/about.html @@ -0,0 +1,233 @@ + + + + + + + + + About - Yaohan Xu + + + + + + + + + + + + +
+ + + + +
+
+
+
+

About Me

+ +
+

I specialize in city and transportation planning, with a focus on smart cities and data-driven solutions. Combining expertise in product and interaction design, I create intuitive digital platforms to enhance urban living and connectivity.

+
+ Yaohan Xu Photo +
+ +
+
    +
  • +

    EDUCATION

    +
      +
    • +
      +

      UNIVERSITY OF PENNSYLVANIA | Weitzman School of Design

      + 2023 - 2025 +
      + Master of City Planning with a Transportation Planning Concentration +

      + 2-year professional degree | Philadelphia, USA +

      +

      + GPA: 3.88/4.00 +

      +
    • +
    • +
      +

      SOUTHEAST UNIVERSITY | School of Architecture

      + 2017 - 2022 +
      + Bachelor of Engineering in Landscape Architecture +

      + 5-year professional degree | Nanjing, CN +

      +

      + GPA: 3.92/4.00, 89.64/100 (ranking 1/28) +

      +
    • +
    +
  • +
  • +

    EMPLOYMENT

    +
      +
    • +
      +

      SHURONG DATA TECHNOLOGY CO., LTD. | Shanghai Office

      + Jun.- Aug. 2024 +
      + Product Design and Data Analysis Intern +

      + UI design and data indicator organization and analysis for the Yangpu District Comprehensive Transportation Information Platform. +

      +

      + Yangpu District High Passenger Flow Project: Demand Forecasting Based on the LSTM Model. +

      +
    • +
    • +
      +

      SWA GROUP | Shanghai Office

      + Jan.- Apr. 2022 +
      + Landscape Design Intern +

      + Led design competition for Suzhou creek and supported Guangzhou shipyard (Built). +

      +

      + Researched, designed and produced drawings for Hangzhou Xiaoshan Technology City and Zhuguang Lijiao #1 Park. +

      +
    • +
    +
  • +
  • +

    AWARDS

    +
      +
    • +
      +

      China National Scholarship | Ministry of Education of China PRC

      + Dec. 2019 +
      +

      + Awarded to 0.2% of undergraduates, merit-based. +

      +
    • +
    • +
      +

      Gold Smart Scholarship | Education Foundation of SEU

      + Jun. 2021 +
      +

      + Awarded to one student per year, merit-based. +

      +
    • +
    • +
      +

      Minyu Alumni Award Fund | Education Foundation of SEU

      + Jun. 2019 +
      +

      + Awarded to five students per year, merit-based. +

      +
    • +
    +
  • +
  • +

    SOFTWARES

    +
      +
    • +
      +

      Programming Language

      + 2023-2025 +
      + Python | R Language | Java Script +

      + Proficient in using Python and R for data cleaning and analysis, with expertise in various geospatial analyses and modeling. +

      +

      + Familiar with spatial optimization models and have a solid understanding of machine learning. +

      +

      + Experienced with frontend frameworks. +

      +
    • +
    • +
      +

      Graphic and Product Design

      + 2017-2025 +
      + Adobe Creative Suite | Figma | MockingBot +

      + Proficient in tools like Adobe Creative Suite (Photoshop, Illustrator, InDesign) for graphic design and Figma for user interface design. Experienced in creating wireframes, prototypes, and user flows for digital products. Familiar with front-end design frameworks and spatial data visualization tools to integrate design with technical functionalities effectively. +

      +
    • +
    • +
      +

      Other Types

      + 2017-2025 +
      + Microsoft Office Suite | ArGIS | Rhinoceros | VISUM | VISSIM | CAD +

      + Proficient in Microsoft Office Suite (Word, Excel, PowerPoint) for documentation, data analysis, and presentations. Skilled in ArcGIS for spatial data analysis and mapping. Experienced with Rhinoceros for 3D modeling and design, and CAD for technical drafting. Familiar with transportation modeling tools like VISUM and VISSIM for traffic flow analysis and simulation. +

      +
    • +
    +
  • +
  • +

    LANGUAGE

    +
      +
    • +
      +

      College English Test Band 6

      + Mar. 2018 +
      +

      + 600 +

      +
    • +
    • +
      +

      TOEFL (Test of English as a Foreign Language)

      + Apr. 2021 +
      +

      + 101 (R 26 L 28 S 23 W 24) +

      +
    • +
    +
  • +
+
+
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/assets/project_1/Description_1.txt b/assets/project_1/Description_1.txt new file mode 100644 index 0000000..7fafc79 --- /dev/null +++ b/assets/project_1/Description_1.txt @@ -0,0 +1 @@ +The project introduces a participatory planting framework that actively involves local residents in the ecological restoration process, providing guidelines for selecting native plant species that are resilient to mountain conditions while fostering community ownership and long-term stewardship of the restored landscapes. \ No newline at end of file diff --git a/assets/project_1/Description_2.txt b/assets/project_1/Description_2.txt new file mode 100644 index 0000000..06d326f --- /dev/null +++ b/assets/project_1/Description_2.txt @@ -0,0 +1 @@ +Temporary but essential canopy bridges, made of lightweight, eco-friendly materials like ropes and suspended platforms, are installed to connect isolated forest patches, providing sloths with immediate, safe passageways while natural vegetation gradually regenerates to create long-term solutions. \ No newline at end of file diff --git a/assets/project_1/Description_3.txt b/assets/project_1/Description_3.txt new file mode 100644 index 0000000..d4838c7 --- /dev/null +++ b/assets/project_1/Description_3.txt @@ -0,0 +1 @@ +By revitalizing riparian zones with native vegetation along riverbanks, the project creates natural corridors that serve as pathways for sloths, simultaneously enhancing biodiversity, protecting water resources, and allowing gradual rewilding of degraded areas into thriving ecosystems. \ No newline at end of file diff --git a/assets/project_1/Description_4.txt b/assets/project_1/Description_4.txt new file mode 100644 index 0000000..2766e7c --- /dev/null +++ b/assets/project_1/Description_4.txt @@ -0,0 +1 @@ +To mitigate the impact of human activities on sloth habitats, eco-engineered solutions such as insulated power lines, tree-lined road crossings, and strategically placed warning signs are implemented, reducing mortality rates while allowing for safe coexistence between urban development and wildlife movement. \ No newline at end of file diff --git a/assets/project_1/Description_5.txt b/assets/project_1/Description_5.txt new file mode 100644 index 0000000..e6c8005 --- /dev/null +++ b/assets/project_1/Description_5.txt @@ -0,0 +1 @@ +Local communities play a critical role in designing, maintaining, and monitoring habitat corridors, fostering stewardship while facilitating the transition from human-created pathways to self-sustaining, naturalized forests that support sloth mobility across diverse landscapes. \ No newline at end of file diff --git a/assets/project_1/Description_6.txt b/assets/project_1/Description_6.txt new file mode 100644 index 0000000..cc8f370 --- /dev/null +++ b/assets/project_1/Description_6.txt @@ -0,0 +1 @@ +In the project's final phase, previously fragmented habitats are seamlessly reconnected into continuous, self-sustaining ecosystems, where mature canopies and dense vegetation enable sloths to traverse naturally and freely without reliance on human intervention, restoring ecological harmony in key habitat zones. \ No newline at end of file diff --git a/assets/project_1/Image_1.jpg b/assets/project_1/Image_1.jpg new file mode 100644 index 0000000..f4b12aa Binary files /dev/null and b/assets/project_1/Image_1.jpg differ diff --git a/assets/project_1/Image_2.jpg b/assets/project_1/Image_2.jpg new file mode 100644 index 0000000..6e00c2d Binary files /dev/null and b/assets/project_1/Image_2.jpg differ diff --git a/assets/project_1/Image_3.jpg b/assets/project_1/Image_3.jpg new file mode 100644 index 0000000..531132f Binary files /dev/null and b/assets/project_1/Image_3.jpg differ diff --git a/assets/project_1/Image_4.jpg b/assets/project_1/Image_4.jpg new file mode 100644 index 0000000..d748505 Binary files /dev/null and b/assets/project_1/Image_4.jpg differ diff --git a/assets/project_1/Image_5.jpg b/assets/project_1/Image_5.jpg new file mode 100644 index 0000000..7689dd6 Binary files /dev/null and b/assets/project_1/Image_5.jpg differ diff --git a/assets/project_1/Image_6.jpg b/assets/project_1/Image_6.jpg new file mode 100644 index 0000000..6363ae3 Binary files /dev/null and b/assets/project_1/Image_6.jpg differ diff --git a/assets/project_1/cover.jpg b/assets/project_1/cover.jpg new file mode 100644 index 0000000..8a0e2bc Binary files /dev/null and b/assets/project_1/cover.jpg differ diff --git a/assets/project_10/cover.jpg b/assets/project_10/cover.jpg new file mode 100644 index 0000000..7d93d65 Binary files /dev/null and b/assets/project_10/cover.jpg differ diff --git a/assets/project_11/cover.jpg b/assets/project_11/cover.jpg new file mode 100644 index 0000000..6d9aab8 Binary files /dev/null and b/assets/project_11/cover.jpg differ diff --git a/assets/project_12/cover.jpg b/assets/project_12/cover.jpg new file mode 100644 index 0000000..eadb5e9 Binary files /dev/null and b/assets/project_12/cover.jpg differ diff --git a/assets/project_13/cover.jpg b/assets/project_13/cover.jpg new file mode 100644 index 0000000..5727b07 Binary files /dev/null and b/assets/project_13/cover.jpg differ diff --git a/assets/project_14/cover.jpg b/assets/project_14/cover.jpg new file mode 100644 index 0000000..723470f Binary files /dev/null and b/assets/project_14/cover.jpg differ diff --git a/assets/project_15/cover.jpg b/assets/project_15/cover.jpg new file mode 100644 index 0000000..abc8d44 Binary files /dev/null and b/assets/project_15/cover.jpg differ diff --git a/assets/project_16/cover.jpg b/assets/project_16/cover.jpg new file mode 100644 index 0000000..e0bb874 Binary files /dev/null and b/assets/project_16/cover.jpg differ diff --git a/assets/project_17/cover.jpg b/assets/project_17/cover.jpg new file mode 100644 index 0000000..d06b0c8 Binary files /dev/null and b/assets/project_17/cover.jpg differ diff --git a/assets/project_18/cover.jpg b/assets/project_18/cover.jpg new file mode 100644 index 0000000..caede39 Binary files /dev/null and b/assets/project_18/cover.jpg differ diff --git a/assets/project_2/Description_1.txt b/assets/project_2/Description_1.txt new file mode 100644 index 0000000..a1dcc6e --- /dev/null +++ b/assets/project_2/Description_1.txt @@ -0,0 +1 @@ +In the early stages of the project, carefully planned reforestation efforts involve planting native tree species in linear arrangements to create artificial corridors, ensuring sloths can traverse safely between fragmented habitats while reducing exposure to ground-level threats such as predators and human infrastructure. \ No newline at end of file diff --git a/assets/project_2/Description_2.txt b/assets/project_2/Description_2.txt new file mode 100644 index 0000000..5eb4e56 --- /dev/null +++ b/assets/project_2/Description_2.txt @@ -0,0 +1 @@ +By integrating scientific planting guidelines with the traditional knowledge of local communities, the project transforms abandoned mine sites into thriving ecosystems, where native plants not only restore soil health and biodiversity but also reconnect residents to the land through hands-on engagement. \ No newline at end of file diff --git a/assets/project_2/Description_3.txt b/assets/project_2/Description_3.txt new file mode 100644 index 0000000..ec2644c --- /dev/null +++ b/assets/project_2/Description_3.txt @@ -0,0 +1 @@ +A flexible planting strategy is developed to address the unique environmental challenges of mountain architecture, replacing conventional methods with site-specific approaches that prioritize erosion control, native vegetation recovery, and sustainable land management through active community participation. \ No newline at end of file diff --git a/assets/project_2/Description_4.txt b/assets/project_2/Description_4.txt new file mode 100644 index 0000000..05607b9 --- /dev/null +++ b/assets/project_2/Description_4.txt @@ -0,0 +1 @@ +The project reimagines post-mine landscapes as opportunities for ecological and social renewal, creating a blueprint where degraded mountain areas are restored through collaborative planting activities, resulting in a greener, more resilient environment that benefits both local ecosystems and the community. \ No newline at end of file diff --git a/assets/project_2/Image_1.jpg b/assets/project_2/Image_1.jpg new file mode 100644 index 0000000..fa86762 Binary files /dev/null and b/assets/project_2/Image_1.jpg differ diff --git a/assets/project_2/Image_2.jpg b/assets/project_2/Image_2.jpg new file mode 100644 index 0000000..dfee368 Binary files /dev/null and b/assets/project_2/Image_2.jpg differ diff --git a/assets/project_2/Image_3.jpg b/assets/project_2/Image_3.jpg new file mode 100644 index 0000000..855fbb5 Binary files /dev/null and b/assets/project_2/Image_3.jpg differ diff --git a/assets/project_2/Image_4.jpg b/assets/project_2/Image_4.jpg new file mode 100644 index 0000000..4e5139d Binary files /dev/null and b/assets/project_2/Image_4.jpg differ diff --git a/assets/project_2/cover.jpg b/assets/project_2/cover.jpg new file mode 100644 index 0000000..b20e150 Binary files /dev/null and b/assets/project_2/cover.jpg differ diff --git a/assets/project_3/Description_1.txt b/assets/project_3/Description_1.txt new file mode 100644 index 0000000..07a774f --- /dev/null +++ b/assets/project_3/Description_1.txt @@ -0,0 +1 @@ +The project reimagines the traditional prison layout by breaking physical and social barriers, creating shared spaces where inmates, the community, and the Neighborhood Association can interact in structured and meaningful ways, fostering mutual understanding and cooperation. \ No newline at end of file diff --git a/assets/project_3/Description_2.txt b/assets/project_3/Description_2.txt new file mode 100644 index 0000000..f5cc3eb --- /dev/null +++ b/assets/project_3/Description_2.txt @@ -0,0 +1 @@ +The design prioritizes open yet secure architectural elements, replacing isolation with connection, where educational programs, workshops, and shared green spaces act as bridges between the prison and the community, redefining boundaries to achieve social harmony and reintegration. \ No newline at end of file diff --git a/assets/project_3/Description_3.txt b/assets/project_3/Description_3.txt new file mode 100644 index 0000000..c29189a --- /dev/null +++ b/assets/project_3/Description_3.txt @@ -0,0 +1 @@ +By establishing a coexistence model rooted in transparency and collaboration, the project encourages the integration of correctional facilities into the urban fabric, promoting shared responsibility, social acceptance, and opportunities for rehabilitated individuals to reintegrate into society. \ No newline at end of file diff --git a/assets/project_3/Description_4.txt b/assets/project_3/Description_4.txt new file mode 100644 index 0000000..014e8df --- /dev/null +++ b/assets/project_3/Description_4.txt @@ -0,0 +1 @@ +Through the innovative design of multipurpose areas, the prison becomes a platform for dialogue, enabling diverse groups—including inmates, local residents, and community organizations—to share ideas, host activities, and contribute to a more inclusive and rehabilitative environment. \ No newline at end of file diff --git a/assets/project_3/Image_1.jpg b/assets/project_3/Image_1.jpg new file mode 100644 index 0000000..d1632ea Binary files /dev/null and b/assets/project_3/Image_1.jpg differ diff --git a/assets/project_3/Image_2.jpg b/assets/project_3/Image_2.jpg new file mode 100644 index 0000000..6ee2786 Binary files /dev/null and b/assets/project_3/Image_2.jpg differ diff --git a/assets/project_3/Image_3.jpg b/assets/project_3/Image_3.jpg new file mode 100644 index 0000000..47c0d53 Binary files /dev/null and b/assets/project_3/Image_3.jpg differ diff --git a/assets/project_3/Image_4.jpg b/assets/project_3/Image_4.jpg new file mode 100644 index 0000000..4eb5476 Binary files /dev/null and b/assets/project_3/Image_4.jpg differ diff --git a/assets/project_3/cover.jpg b/assets/project_3/cover.jpg new file mode 100644 index 0000000..929d2fb Binary files /dev/null and b/assets/project_3/cover.jpg differ diff --git a/assets/project_4/Description_1.txt b/assets/project_4/Description_1.txt new file mode 100644 index 0000000..4489eb3 --- /dev/null +++ b/assets/project_4/Description_1.txt @@ -0,0 +1 @@ +The project reshapes farmland into a dynamic landscape where natural processes like flooding are embraced and managed, allowing diverse user activities to coexist harmoniously with ecological systems while safeguarding the land for future environmental changes. \ No newline at end of file diff --git a/assets/project_4/Description_2.txt b/assets/project_4/Description_2.txt new file mode 100644 index 0000000..e1e92f4 --- /dev/null +++ b/assets/project_4/Description_2.txt @@ -0,0 +1 @@ +Through innovative topography and sustainable land management practices, the design creates a flood-resilient environment that not only mitigates water-related risks but also enhances biodiversity, offering users interactive and adaptable spaces for work and recreation. \ No newline at end of file diff --git a/assets/project_4/Description_3.txt b/assets/project_4/Description_3.txt new file mode 100644 index 0000000..80d1340 --- /dev/null +++ b/assets/project_4/Description_3.txt @@ -0,0 +1 @@ +By integrating varied terrains and flexible design elements, the project allows users to freely engage in activities such as farming, leisure, and community gatherings, while ensuring the land remains functional and productive during both dry and flood-prone seasons. \ No newline at end of file diff --git a/assets/project_4/Description_4.txt b/assets/project_4/Description_4.txt new file mode 100644 index 0000000..21470ae --- /dev/null +++ b/assets/project_4/Description_4.txt @@ -0,0 +1 @@ +The project transforms traditional farmland into diverse, multi-functional waterfront terrains that blend natural beauty with resilience, providing spaces for recreation, agriculture, and ecological preservation while preparing for future flooding through adaptive design strategies. \ No newline at end of file diff --git a/assets/project_4/Image_1.jpg b/assets/project_4/Image_1.jpg new file mode 100644 index 0000000..1978f40 Binary files /dev/null and b/assets/project_4/Image_1.jpg differ diff --git a/assets/project_4/Image_2.jpg b/assets/project_4/Image_2.jpg new file mode 100644 index 0000000..ed20fb9 Binary files /dev/null and b/assets/project_4/Image_2.jpg differ diff --git a/assets/project_4/Image_3.jpg b/assets/project_4/Image_3.jpg new file mode 100644 index 0000000..21487f5 Binary files /dev/null and b/assets/project_4/Image_3.jpg differ diff --git a/assets/project_4/Image_4.jpg b/assets/project_4/Image_4.jpg new file mode 100644 index 0000000..1d86c95 Binary files /dev/null and b/assets/project_4/Image_4.jpg differ diff --git a/assets/project_4/cover.jpg b/assets/project_4/cover.jpg new file mode 100644 index 0000000..ca6f250 Binary files /dev/null and b/assets/project_4/cover.jpg differ diff --git a/assets/project_5/cover.jpg b/assets/project_5/cover.jpg new file mode 100644 index 0000000..7bf801d Binary files /dev/null and b/assets/project_5/cover.jpg differ diff --git a/assets/project_6/cover.jpg b/assets/project_6/cover.jpg new file mode 100644 index 0000000..a7dc008 Binary files /dev/null and b/assets/project_6/cover.jpg differ diff --git a/assets/project_7/cover.jpg b/assets/project_7/cover.jpg new file mode 100644 index 0000000..27c9855 Binary files /dev/null and b/assets/project_7/cover.jpg differ diff --git a/assets/project_8/cover.jpg b/assets/project_8/cover.jpg new file mode 100644 index 0000000..7af92f2 Binary files /dev/null and b/assets/project_8/cover.jpg differ diff --git a/assets/project_9/cover.jpg b/assets/project_9/cover.jpg new file mode 100644 index 0000000..644447b Binary files /dev/null and b/assets/project_9/cover.jpg differ diff --git a/comments.html b/comments.html new file mode 100644 index 0000000..0075d77 --- /dev/null +++ b/comments.html @@ -0,0 +1,147 @@ + + + + + + + + + Comments - Yaohan Xu + + + + + + + + + + + + + + + + +
+ + + + +
+ +
+

Comments

+

This website is not just a platform for showing my personal projects but also a welcoming community for interaction and idea exchange. Here, you can explore various works related to design, interaction, data analysis, and visualization. If something catches your interest, I warmly invite you to leave a comment and share your thoughts and insights. Whether it's suggestions for design details, improvements in interaction experience, or unique perspectives on data analysis and visualization, I am eager to hear from you. Your valuable feedback will help me continuously improve and create better content.

+
+ + +
+
+
+

View — All Projects

+ Project Type Selector +
+ + +
+ Show +
+ Hide +
+
+ +
    +
  • +
  • +
  • +
  • +
  • +
+
+ + + +
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/css/about.css b/css/about.css new file mode 100644 index 0000000..141648d --- /dev/null +++ b/css/about.css @@ -0,0 +1,435 @@ +/* +// for all elements +*/ +* { + box-sizing: border-box; +} + +html { + font-size: 16px; + font-family: Roboto, sans-serif; +} + +body { + height: auto; + width: 100%; + max-width: 1000px; + + margin: 0 auto; + padding: 0; + + opacity: 0; + transition: opacity 0.75s ease-in-out; /* for fade-in effect */ +} + +body.loaded { + opacity: 1; /* for fade-in effect */ +} + + +/* +// for body elements +*/ + +/* main */ +main { + height: auto; + width: 100%; + + display: flex; + flex-direction: column; +} + +/* footer */ +.contact-information { + height: auto; + width: 100%; + + margin: 0; + margin-bottom: 2rem; + padding: 0; + + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; +} + +.contact-information span{ + flex: 1; + text-align: center; + + font-size: 1rem; + + position: relative; +} + +.contact-information span:not(:last-child)::after { + content: ""; + position: absolute; + top: 50%; + right: 0; + transform: translateY(-50%); + width: 1.5px; + height: 75%; + background-color: #5d5d5d; +} + + +/* +// for main elements +*/ + +/* header */ +.page-header { + height: auto; + width: 100%; + + margin-top: 3rem; + + display: flex; + flex-direction: row; + align-items: center; +} + +/* section */ +.main-content { + height: auto; + width: 100%; + + margin: 0; + margin-bottom: 5rem; + + display: flex; + flex-direction: column; +} + + +/* +// for header elements +*/ + +/* for page header */ +.page-header h1 { + height: auto; + width: auto; + + margin: 0; + + font-size: 1.8rem; + font-family: Merriweather, serif; +} + +/* for page navigator */ +.page-navigator { + height: auto; + flex-grow: 1; +} + +.page-name-list { + height: auto; + width: 100%; + + margin: 0; + padding: 0; + + display: flex; + flex-direction: row; + justify-content: flex-end; + gap: 1.8rem; +} + +.page-name { + height: auto; + width: auto; + + font-size: 1.2rem; + font-family: Merriweather, serif; + + list-style: none; +} + +.page-name a { + text-decoration: none; + color: black; +} + + +/* +// for section elements +*/ +.section-header { + height: auto; + width: 100%; + + margin: 0; + + display: flex; + flex-direction: row; + align-items: flex-end; + gap: 6rem; +} + +.personal-experience { + height: auto; + width: 100%; + + margin: 0; + margin-top: 3.6rem; + padding: 0; + + display: flex; + flex-direction: column; +} + + +/* +// for section header +*/ + +.introduction-part { + height: auto; + width: 100%; + + margin: 0; + margin-top: 5rem; + + display: flex; + flex-direction: column; +} + +.photo-part { + height: auto; + width: 200px; + + margin: 0; +} + +/* for introduction part */ +.title-part { + height: auto; + width: 100%; + + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; +} + +.title-part h2 { + height: auto; + width: auto; + + margin: 0; + + font-size: 2.8rem; + font-weight: 400; +} + +.view-resume-button { + height: 100%; + width: auto; + + display: flex; + flex-direction: row; + align-items: center; + gap: 0.65rem; + + margin: 0; + margin-left: 4rem; + padding: 0.5rem 0.8rem; + + /* font-style: italic; */ + font-size: 0.9rem; + font-weight: 400; + color: #383838; + + background-color: white; + border: 1.2px dashed #5d5d5d; + border-radius: 8px; + + cursor: pointer; +} + +.resume-icon { + height: 0.85rem; + width: auto; +} + +.introduction-part p { + height: auto; + width: auto; + + margin: 0; + margin-top: 2rem; + + font-size: 1.4rem; + font-weight: 400; + + line-height: 1.4; +} + + +/* +// for personal experience +*/ + +/* for experience-type */ +.experience-type { + height: auto; + width: 100%; + + display: flex; + flex-direction: column; + gap: 3.6rem; + + margin: 0; + padding: 0; + + list-style: none; +} + +.experience-type li { + height: auto; + width: 100%; + + margin: 0; + padding: 0; + + display: flex; + flex-direction: column; + gap: 1.5rem; +} + +.experience-type h3 { + height: auto; + width: auto; + + margin: 0; + + font-size: 2rem; + font-weight: 500; + + text-decoration: underline; + text-decoration-thickness: 2px; + text-underline-offset: 8px; +} + +.experience-item { + height: auto; + width: 100%; + + display: flex; + flex-direction: column; + gap: 1.2rem; + + padding: 0; + + list-style: none; +} + +.experience-item li { + height: auto; + width: 100%; + + margin: 0; + padding: 0; + + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.experience-title { + height: auto; + width: auto; + + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; +} + +.experience-title h4 { + height: auto; + width: auto; + + margin: 0; + margin-bottom: 0.2rem; + + font-size: 1.6rem; + font-weight: 400; +} + +.experience-time { + height: auto; + width: auto; + + margin: 0; + + font-size: 0.9rem; + font-weight: 400; + font-style: italic; + color: gray; +} + +.experience-subtitle { + height: auto; + width: auto; + + margin: 0; + + font-size: 1.2rem; + font-weight: 400; + +} + +.experience-description { + height: auto; + width: auto; + + margin: 0; + + font-size: 1.2rem; + font-weight: 400; + color: gray; + + line-height: 1.4; +} + +/* for popup resume */ +.popup-overlay { + height: 100%; + width: 100%; + + position: fixed; + top: 0; + left: 0; + + display: none; /* Set to hide by default */ + align-items: center; + justify-content: center; + + background: rgba(0 0 0 / 80%); + z-index: 1000; +} + +.popup-content { + height: 90%; + width: auto; + max-width: 1000px; + + display: flex; + align-items: center; + justify-content: center; + + position: relative; +} + +.popup-image { + height: 100%; + width: auto; + + object-fit: contain; /* to fit the image */ + box-shadow: 0 0 20px rgba(0 0 0 / 60%); +} \ No newline at end of file diff --git a/css/comments.css b/css/comments.css new file mode 100644 index 0000000..90ebcfd --- /dev/null +++ b/css/comments.css @@ -0,0 +1,468 @@ +/* +// for section elements +*/ + +/* for type selector */ +.main-project-type-filter { + height: auto; + width: 100%; + + margin-top: 3.6rem; + + display: flex; + flex-direction: column; +} + +.control-bar { + height: auto; + width: 100%; + + display: flex; + flex-direction: row; + align-items: center; +} + +.main-project-type-selected { + height: auto; + width: auto; + + display: flex; + flex-direction: row; + align-items: center; + gap: 0.75rem; + + cursor: pointer; +} + +.main-project-type-selected p { + height: auto; + width: auto; + + margin: 0; + + font-size: 1.2rem; + font-weight: 400; +} + +.select-icon { + height: 1.2rem; + width: auto; +} + +.toggle-switch { + height: auto; + flex-grow: 1; + + display: flex; + flex-direction: row; + align-items: center; + justify-content: flex-end; + gap: 1.2rem; + + font-size: 1.2rem; + font-weight: 400; +} + +.divider { + height: 1.2rem; + width: 2px; + + background-color: #5d5d5d; +} + +.comments-show:hover { + color: gray; + cursor: pointer; +} + +.comments-hide:hover { + color: gray; + cursor: pointer; +} + +/* for project type list */ +.main-project-type-list { + height: 0; + width: auto; + + margin: 0; + padding: 0; + + overflow: hidden; + opacity: 0; + transform: scaleY(0); + transform-origin: top; + transition: transform 0.2s ease, height 0.2s ease, margin-top 0.2s ease; /* for smooth transition of type selector list */ + + display: flex; + flex-direction: row; + gap: 2rem; + + list-style: none; +} + +.main-project-type-list.visible { + height: auto; + width: auto; + + opacity: 1; + transform: scaleY(1); /* for smooth transition of type selector list */ + margin-top: 1rem; +} + +.main-project-type { + height: auto; + width: auto; +} + +.main-project-type-list button { + height: auto; + width: 100%; + + background-color: white; + border: none; + + font-size: 1rem; + + opacity: 0; + transition: opacity 0.4s ease; /* for smooth transition of button */ + + cursor: pointer; +} + +.main-project-type-list.visible button { + opacity: 1; + transition-delay: 0.2s; /* for smooth transition of button */ +} + +.main-project-type-list button:hover { + color: gray; +} + +/* for project list */ +.projects-list { + height: auto; + width: 100%; + + margin: 0; + margin-top: 2rem; + margin-bottom: 5rem; + padding: 0; + + display: flex; + flex-direction: column; + gap: 3.6rem; +} + +.project-item { + height: auto; + width: 100%; + + display: flex; + flex-direction: row; + align-items: center; + gap: 2rem; +} + +.project-part { + height: auto; + width: 60%; + + display: flex; + flex-direction: column; + gap: 1rem; +} + +.comments-part { + height: auto; + width: 40%; + + display: flex; + flex-direction: column; + gap: 0.5rem; +} + + +/* +// for project list +*/ + +/* for project part */ +.project-cover-container { + height: auto; + width: 100%; + + display: flex; + flex-direction: row; + border: 1px solid #a2a2a2; + border-radius: 20px; + + margin: 0; + padding: 0.5rem; + + position: relative; + cursor: pointer; + overflow: visible; +} + +.project-cover-container::before { + content: ''; + position: absolute; + top: -1.5px; + left: -1.5px; + right: -1.5px; + bottom: -1.5px; + border-radius: 20px; + border: 1px solid transparent; + pointer-events: none; +} /* create a pseudo element to show border on hover */ + +.project-cover-container:hover::before { + border: 3px solid #383838; +} /* add border style to pseudo element on hover */ + +.projects-list.no-hover .project-cover-container:hover::before { + border: none; +} /* remove border style from pseudo element on hover when the toggle switch is set to show comments */ + +.project-cover { + height: auto; + width: 100%; + + margin: 0; + padding: 0; + + border: 1px solid #d6d6d6; + border-radius: 15px; +} + +.project-title-container { + height: auto; + width: 100%; + + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; +} + +.project-info { + height: auto; + width: auto; + + display: flex; + flex-direction: row; + align-items: center; + gap: 1rem; + + font-size: 1.8rem; + font-family: Merriweather, serif; + font-weight: 500; +} + +.project-comment-button { + height: auto; + width: auto; + + display: flex; + flex-direction: row; + align-items: center; + gap: 0.65rem; + + margin: 0; + margin-left: 2rem; + padding: 0.5rem 0.8rem; + + /* font-style: italic; */ + font-size: 0.9rem; + font-weight: 400; + color: #383838; + + background-color: white; + border: 1.2px dashed #5d5d5d; + border-radius: 8px; + + cursor: pointer; +} + +.comment-icon { + height: 0.8rem; + width: auto; +} + +/* for comments part */ +.comments-list { + height: auto; + width: 100%; + + margin: 0; + padding: 0; + padding-right: 1rem; + + display: flex; + flex-direction: column; + + list-style: none; +} + +.comment-item { + height: auto; + width: 100%; + + margin-bottom: 1rem; + padding-bottom: 0.4rem; + + display: flex; + flex-direction: column; + gap: 0.5rem; + + border-bottom: 1px solid #d6d6d6; +} + +.comment-item:last-child { + margin-bottom: 0; + padding-bottom: 0; + border-bottom: none; +} + +.line-one { + height: auto; + width: 100%; + + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; +} + +.line-two { + height: auto; + width: 100%; + + display: flex; + flex-direction: row; +} + +.comment-name { + height: auto; + width: auto; + + font-size: 1rem; + font-weight: 500; +} + +.comment-time { + height: auto; + width: auto; + + font-size: 0.8rem; + font-weight: 400; +} + +.comment-content { + height: auto; + width: 100%; + + font-size: 1rem; + font-weight: 400; + line-height: 1.4; +} + + +/* +// for comment modal +*/ +.modal.hidden { + display: none; +} + +.modal { + width: 100%; + height: 100%; + + position: fixed; + top: 0; + left: 0; + + display: flex; + justify-content: center; + align-items: center; + + background: rgba(0 0 0 / 50%); + z-index: 1000; +} + +.modal-content { + width: 400px; + + padding: 20px; + border-radius: 8px; + + background: white; +} + +.modal-content h2 { + margin-top: 5px; + margin-bottom: 20px; +} + +.modal-content label { + display: block; + margin: 10px 0 8px; +} + +.modal-content input, +.modal-content textarea { + width: 100%; + + margin-bottom: 15px; + padding: 10px; + + border: 1px solid #ccc; + border-radius: 4px; + + font-family: inherit; + font-size: 0.85rem; +} + +.modal-content input:focus, +.modal-content textarea:focus { + outline: 1.6px solid #696969; +} + +.modal-buttons { + display: flex; + justify-content: space-around; +} + +#submit-comment { + padding: 5px 10px; + + border: none; + border-radius: 4px; + + font-size: 0.9rem; + background: white; + + cursor: pointer; +} + +#close-modal { + padding: 5px 10px; + + border: none; + border-radius: 4px; + + font-size: 0.9rem; + background: white; + + cursor: pointer; +} + +#submit-comment:hover { + color: gray; +} + +#close-modal:hover { + color: gray; +} \ No newline at end of file diff --git a/css/project_template.css b/css/project_template.css new file mode 100644 index 0000000..e455223 --- /dev/null +++ b/css/project_template.css @@ -0,0 +1,494 @@ +/* +// for all elements +*/ +* { + box-sizing: border-box; +} + +html { + font-size: 16px; + font-family: Roboto, sans-serif; +} + +body { + height: auto; + width: 100%; + max-width: 1000px; + + margin: 0 auto; + padding: 0; + + opacity: 0; + transition: opacity 0.75s ease-in-out; /* for fade-in effect */ +} + +body.loaded { + opacity: 1; /* for fade-in effect */ +} + + +/* +// for body elements +*/ + +/* main */ +main { + height: auto; + width: 100%; + + display: flex; + flex-direction: column; +} + +/* footer */ +.contact-information { + height: auto; + width: 100%; + + margin: 0; + margin-bottom: 2rem; + padding: 0; + + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; +} + +.contact-information span{ + flex: 1; + text-align: center; + + font-size: 1rem; + + position: relative; +} + +.contact-information span:not(:last-child)::after { + content: ""; + position: absolute; + top: 50%; + right: 0; + transform: translateY(-50%); + width: 1.5px; + height: 75%; + background-color: #5d5d5d; +} + + +/* +// for main elements +*/ + +/* header */ +.page-header { + height: auto; + width: 100%; + + margin-top: 3rem; + + display: flex; + flex-direction: row; + align-items: center; +} + +/* section */ +.main-content { + height: auto; + width: 100%; + + display: flex; + flex-direction: column; +} + + +/* +// for header elements +*/ + +/* for page header */ +.page-header h1 { + height: auto; + width: auto; + + margin: 0; + + font-size: 1.8rem; + font-family: Merriweather, serif; +} + +/* for page navigator */ +.page-navigator { + height: auto; + flex-grow: 1; +} + +.page-name-list { + height: auto; + width: 100%; + + margin: 0; + padding: 0; + + display: flex; + flex-direction: row; + justify-content: flex-end; + gap: 1.8rem; +} + +.page-name { + height: auto; + width: auto; + + font-size: 1.2rem; + font-family: Merriweather, serif; + + list-style: none; +} + +.page-name a { + text-decoration: none; + color: black; +} + + +/* +// for section elements +*/ + +/* for section header */ +.section-header { + height: auto; + width: 100%; +} + +.section-header h2 { + height: auto; + width: auto; + + margin: 0; + margin-top: 5rem; + + font-size: 2.8rem; + font-weight: 400; +} + +.section-header p { + height: auto; + width: auto; + + margin: 0; + margin-top: 2rem; + + font-size: 1.4rem; + font-weight: 400; + + line-height: 1.4; +} + +/* for project contents */ +.project-contents { + height: auto; + width: 100%; + + margin-top: 5rem; + margin-bottom: 5rem; +} + +.project-content-list { + height: auto; + width: 100%; + + margin: 0; + padding: 0; + + display: flex; + flex-direction: column; + gap: 5rem; + + list-style: none; +} + +.project-content-list li { + height: auto; + width: 100%; + + display: flex; + flex-direction: column; + gap: 2rem; +} + +.project-image-container { + height: auto; + width: 100%; + + margin: 0; + padding: 0.5rem; + + display: flex; + flex-direction: row; + align-items: center; + + border: 1px solid #a2a2a2; + border-radius: 20px; +} + +.project-image { + height: auto; + width: 100%; + + margin: 0; + padding: 0; + + border: 1px solid #d6d6d6; + border-radius: 15px; +} + +.project-content-list p { + height: auto; + width: 100%; + + margin: 0; + + font-size: 1.4rem; + font-weight: 400; + + line-height: 1.4; +} + +/* for project comments */ +.project-comments { + height: auto; + width: 100%; + + margin: 0; + + display: flex; + flex-direction: column; +} + +.comments-header { + height: auto; + width: 100%; + + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; +} + +.project-comments h3 { + height: auto; + width: auto; + + margin: 0; + + font-size: 2.4rem; + font-weight: 400; +} + +.project-comment-button { + height: auto; + width: auto; + + display: flex; + flex-direction: row; + align-items: center; + gap: 0.65rem; + + margin: 0; + padding: 0.5rem 0.8rem; + + /* font-style: italic; */ + font-size: 0.9rem; + font-weight: 400; + color: #383838; + + background-color: white; + border: 1.2px dashed #5d5d5d; + border-radius: 8px; + + cursor: pointer; +} + +.comment-icon { + height: 0.8rem; + width: auto; +} + +.comments-list { + height: auto; + width: 100%; + + margin: 0; + margin-top: 1.5rem; + margin-bottom: 4rem; + padding: 0; + + display: flex; + flex-direction: column; + + list-style: none; +} + +.comment-item { + height: auto; + width: 100%; + + margin-bottom: 1.4rem; + padding-bottom: 0.6rem; + + display: flex; + flex-direction: column; + gap: 0.6rem; + + border-bottom: 1px solid #d6d6d6; +} + +.comment-item:last-child { + margin-bottom: 0; + padding-bottom: 0; + border-bottom: none; +} + +.line-one { + height: auto; + width: 100%; + + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; +} + +.line-two { + height: auto; + width: 100%; + + display: flex; + flex-direction: row; +} + +.comment-name { + height: auto; + width: auto; + + font-size: 1.2rem; + font-weight: 500; +} + +.comment-time { + height: auto; + width: auto; + + font-size: 0.8rem; + font-weight: 400; +} + +.comment-content { + height: auto; + width: 100%; + + font-size: 1rem; + font-weight: 400; + line-height: 1.4; +} + + +/* +// for comment modal +*/ +.modal.hidden { + display: none; +} + +.modal { + width: 100%; + height: 100%; + + position: fixed; + top: 0; + left: 0; + + display: flex; + justify-content: center; + align-items: center; + + background: rgba(0 0 0 / 50%); + z-index: 1000; +} + +.modal-content { + width: 400px; + + padding: 20px; + border-radius: 8px; + + background: white; +} + +.modal-content h2 { + margin-top: 5px; + margin-bottom: 20px; +} + +.modal-content label { + display: block; + margin: 10px 0 8px; +} + +.modal-content input, +.modal-content textarea { + width: 100%; + + margin-bottom: 15px; + padding: 10px; + + border: 1px solid #ccc; + border-radius: 4px; + + font-family: inherit; + font-size: 0.85rem; +} + +.modal-content input:focus, +.modal-content textarea:focus { + outline: 1.6px solid #696969; +} + +.modal-buttons { + display: flex; + justify-content: space-around; +} + +#submit-comment { + padding: 5px 10px; + + border: none; + border-radius: 4px; + + font-size: 0.9rem; + background: white; + + cursor: pointer; +} + +#close-modal { + padding: 5px 10px; + + border: none; + border-radius: 4px; + + font-size: 0.9rem; + background: white; + + cursor: pointer; +} + +#submit-comment:hover { + color: gray; +} + +#close-modal:hover { + color: gray; +} \ No newline at end of file diff --git a/css/projects.css b/css/projects.css new file mode 100644 index 0000000..0cc9454 --- /dev/null +++ b/css/projects.css @@ -0,0 +1,199 @@ +/* +// for section elements +*/ + +/* for type selector */ +.main-project-type-filter { + height: auto; + width: 100%; + + margin-top: 3.6rem; + + display: flex; + flex-direction: column; +} + +.main-project-type-selected { + height: auto; + width: 100%; + + display: flex; + flex-direction: row; + align-items: center; + gap: 0.75rem; + + cursor: pointer; +} + +.main-project-type-selected p { + height: auto; + width: auto; + + margin: 0; + + font-size: 1.2rem; + font-weight: 400; +} + +.select-icon { + height: 1.2rem; + width: auto; +} + +.main-project-type-list { + height: 0; + width: auto; + + margin: 0; + padding: 0; + + overflow: hidden; + opacity: 0; + transform: scaleY(0); + transform-origin: top; + transition: transform 0.2s ease, height 0.2s ease, margin-top 0.2s ease; /* for smooth transition of type selector list */ + + display: flex; + flex-direction: row; + gap: 2rem; + + list-style: none; +} + +.main-project-type-list.visible { + height: auto; + width: auto; + + opacity: 1; + transform: scaleY(1); /* for smooth transition of type selector list */ + margin-top: 1rem; +} + +.main-project-type { + height: auto; + width: auto; +} + +.main-project-type-list button { + height: auto; + width: 100%; + + background-color: white; + border: none; + + font-size: 1rem; + + opacity: 0; + transition: opacity 0.4s ease; /* for smooth transition of button */ + + cursor: pointer; +} + +.main-project-type-list.visible button { + opacity: 1; + transition-delay: 0.2s; /* for smooth transition of button */ +} + +.main-project-type-list button:hover { + color: gray; +} + +/* for project list */ +.projects-list { + height: auto; + width: 100%; + + margin: 0; + margin-top: 2rem; + margin-bottom: 5rem; + padding: 0; + + display: flex; + flex-direction: column; + gap: 3.2rem; +} + +.project-item { + height: auto; + width: 100%; + + display: flex; + flex-direction: row; + align-items: center; + gap: 2rem; +} + +.project-cover-container { + height: auto; + width: 60%; + + margin: 0; + padding: 0.5rem; + + display: flex; + flex-direction: row; + + border: 1px solid #a2a2a2; + border-radius: 20px; + cursor: pointer; +} + +.project-cover { + height: auto; + width: 100%; + + margin: 0; + padding: 0; + + border: 1px solid #d6d6d6; + border-radius: 15px; +} + +.project-details { + height: auto; + width: 40%; + + display: flex; + flex-direction: column; + gap: 0.8rem; + + cursor: pointer; +} + +/* for project details */ +.line-one { + height: auto; + width: 100%; + + display: flex; + flex-direction: row; + align-items: center; + gap: 1rem; + + font-size: 1.8rem; + font-family: Merriweather, serif; + font-weight: 700; +} + +.line-two { + height:auto; + width: 100%; + + display: flex; + flex-direction: row; + align-items: center; + gap: 1rem; + + font-size: 0.8rem; + font-style: italic; + color: #383838; +} + +.line-three { + height: auto; + width: 100%; + + font-size: 1rem; + font-weight: 400; + line-height: 1.4; +} \ No newline at end of file diff --git a/css/style.css b/css/style.css new file mode 100644 index 0000000..3a0a217 --- /dev/null +++ b/css/style.css @@ -0,0 +1,251 @@ +/* +// for all elements +*/ +* { + box-sizing: border-box; +} + +html { + font-size: 16px; + font-family: Roboto, sans-serif; +} + +body { + height: auto; + width: 100%; + max-width: 1000px; + + margin: 0 auto; + padding: 0; + + opacity: 0; + transition: opacity 0.75s ease-in-out; /* for fade-in effect */ +} + +body.loaded { + opacity: 1; /* for fade-in effect */ +} + + +/* +// for body elements +*/ + +/* aside */ +aside { + height: auto; + width: 250px; + + position: fixed; + bottom: 60px; + margin-left: -310px; +} + +@media (width < 1400px) { + aside { + display: none; + } +} + +/* main */ +main { + height: auto; + width: 100%; + + display: flex; + flex-direction: column; +} + +/* footer */ +.contact-information { + height: auto; + width: 100%; + + margin: 0; + margin-bottom: 2rem; + padding: 0; + + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; +} + +.contact-information span{ + flex: 1; + text-align: center; + + font-size: 1rem; + + position: relative; +} + +.contact-information span:not(:last-child)::after { + content: ""; + position: absolute; + top: 50%; + right: 0; + transform: translateY(-50%); + width: 1.5px; + height: 75%; + background-color: #5d5d5d; +} + + +/* +// for aside elements +*/ +.aside-project-type-list { + height: auto; + width: 100%; + + margin: 0; + padding: 0; + + display: flex; + flex-direction: column; + + list-style: none; +} + +.aside-project-type { + height: auto; + width: 100%; + + margin-bottom: 35px; +} + +.aside-project-type:last-child { + margin-bottom: 0; +} + +.aside-project-type button { + height: auto; + width: 100%; + + text-align: right; + background-color: white; + border: none; + + font-size: 1rem; + + cursor: pointer; +} + +.aside-project-type .active { + font-weight: bold; + text-decoration: underline; + text-decoration-thickness: 2px; + text-underline-offset: 4px; +} + + +/* +// for main elements +*/ + +/* header */ +.page-header { + height: auto; + width: 100%; + + margin-top: 3rem; + + display: flex; + flex-direction: row; + align-items: center; +} + +/* section */ +.main-content { + height: auto; + width: 100%; + + display: flex; + flex-direction: column; +} + + +/* +// for header elements +*/ + +/* for page header */ +.page-header h1 { + height: auto; + width: auto; + + margin: 0; + + font-size: 1.8rem; + font-family: Merriweather, serif; +} + +/* for page navigator */ +.page-navigator { + height: auto; + flex-grow: 1; +} + +.page-name-list { + height: auto; + width: 100%; + + margin: 0; + padding: 0; + + display: flex; + flex-direction: row; + justify-content: flex-end; + gap: 1.8rem; +} + +.page-name { + height: auto; + width: auto; + + font-size: 1.2rem; + font-family: Merriweather, serif; + + list-style: none; +} + +.page-name a { + text-decoration: none; + color: black; +} + + +/* +// for section elements +*/ + +/* for section header */ +.section-header { + height: auto; + width: 100%; +} + +.section-header h2 { + height: auto; + width: auto; + + margin: 0; + margin-top: 5rem; + + font-size: 2.8rem; + font-weight: 400; +} + +.section-header p { + height: auto; + width: auto; + + margin: 0; + margin-top: 2rem; + + font-size: 1.4rem; + font-weight: 400; + + line-height: 1.4; +} \ No newline at end of file diff --git a/data/projects.json b/data/projects.json new file mode 100644 index 0000000..158d388 --- /dev/null +++ b/data/projects.json @@ -0,0 +1,200 @@ +[ + { + "id": "project_1", + "number": "01", + "title": "Dynamic Lifeline", + "type": "Landscape", + "keyword": "Ecological Planning | Low-intervention", + "time": "2022-06", + "description": "This project aims to create various types of corridors to ensure the safe traversal of sloths across three major habitat types in Costa Rica. The development model transitions from a more human-intervened approach to one that is increasingly adapted to natural conditions.", + "folder": "assets/project_1", + "url": "project_template.html" + }, + { + "id": "project_2", + "number": "02", + "title": "Mufu New Life", + "type": "Landscape", + "keyword": "Mine Restoration | Participatory Planting", + "time": "2021-07", + "description": "During the design of mountain architecture, the project provides planting guidelines required for the ecological restoration of the mine and replaces traditional planting methods with a participatory approach that engages residents.", + "folder": "assets/project_2", + "url": "project_template.html" + }, + { + "id": "project_3", + "number": "03", + "title": "Break the Wall", + "type": "Landscape", + "keyword": "Prison Renovation | Self-governing", + "time": "2021-10", + "description": "I design a new prison layout and established a model for beneficial coexistence with the community and Neighborhood Association, providing a platform for different groups to share and express their ideas.", + "folder": "assets/project_3", + "url": "project_template.html" + }, + { + "id": "project_4", + "number": "04", + "title": "Reshape the Farmland", + "type": "Landscape", + "keyword": "Waterfront Landform | Resilient System", + "time": "2021-02", + "description": "This design creates diverse waterfront terrains that enable users to freely choose various activities while accommodating potential future flooding.", + "folder": "assets/project_4", + "url": "project_template.html" + }, + { + "id": "project_5", + "number": "05", + "title": "The World of Algae", + "type": "Landscape", + "keyword": "Concept Design | Industry Chain | Future City", + "time": "2020-06", + "description": "I design a comprehensive chain for the production, processing, and utilization of algae products. This approach allows residents to envision diverse futures for the city, shaped by their level of acceptance and engagement with these innovations.", + "folder": "assets/project_5", + "url": "project_template.html" + }, + { + "id": "project_6", + "number": "06", + "title": "Renew Suzhou Creek", + "type": "Landscape", + "keyword": "Waterfront Renovation | Practical Project", + "time": "2022-02", + "description": "During my internship at SWA, I joined the project team during the schematic design phase and was responsible for the detailed design and modification of specific nodes. The project aimed to preserve and celebrate the historical heritage of Suzhou Creek while revitalizing its waterfront as a vibrant urban public space.", + "folder": "assets/project_6", + "url": "project_template.html" + }, + { + "id": "project_7", + "number": "07", + "title": "Explore National Parks", + "type": "Web Design", + "keyword": "Story Map | Visitor Guideline", + "time": "2024-10", + "description": "This story map introduces some of the most famous national parks, offering a resource for learning about them. It also lays the groundwork for future additions, such as detailed park information, tools for visit planning, options for sharing experiences, and even comparisons of different parks.", + "folder": "assets/project_7", + "url": "project_template.html" + }, + { + "id": "project_8", + "number": "08", + "title": "Yellowstone POI Guide", + "type": "Web Design", + "keyword": "Interactive Dashboard | Visitor Information", + "time": "2024-12", + "description": "This project is a dashboard designed for visitors to Yellowstone National Park. It provides essential details about each Point of Interest (POI), including its location, name, type, subcategory, and estimated travel time. Additionally, it enables visitors to select their preferred POIs and plan their trips efficiently.", + "folder": "assets/project_8", + "url": "project_template.html" + }, + { + "id": "project_9", + "number": "09", + "title": "Transport Info Hub", + "type": "Web Design", + "keyword": "Integrated Information Platform", + "time": "2024-08", + "description": "This project aims to develop an Integrated Transportation Information Platform for the Yangpu District Government as a key component of Shanghai's 'One Network Management' initiative. The platform integrates basic information and commonly used analyses across various transportation modes, along with impact evaluation models for major engineering projects.", + "folder": "assets/project_9", + "url": "project_template.html" + }, + { + "id": "project_10", + "number": "10", + "title": "Manage Shared Bikes", + "type": "Web Design", + "keyword": "Data Display Screen | Bluetooth Sniffing", + "time": "2024-06", + "description": "The platform uses Bluetooth sniffing and video monitoring to detect shared bike misuse and automatically assigns tasks to companies for timely resolution.", + "folder": "assets/project_10", + "url": "project_template.html" + }, + { + "id": "project_11", + "number": "11", + "title": "30-Day Map Challenge", + "type": "Data Visualization", + "keyword": "Vector Mapping | Raster Analysis", + "time": "2023-11", + "description": "This project involves exploring maps across various scales, utilizing diverse datasets, and focusing on different topics. Each topic follows a structured process, including brainstorming, identifying data sources, data processing, analysis, and creating final graphics.", + "folder": "assets/project_11", + "url": "project_template.html" + }, + { + "id": "project_12", + "number": "12", + "title": "Locate Bike Stations", + "type": "Data Visualization", + "keyword": "Cycling Trends | Multi-Demand", + "time": "2023-10", + "description": "This project analyzes IndeGo bike-sharing data in Philadelphia, focusing on the distribution and usage patterns of existing bike stations. Using ArcGIS for comprehensive analysis, it incorporates diverse demand characteristics to identify optimal locations for new IndeGo stations.", + "folder": "assets/project_12", + "url": "project_template.html" + }, + { + "id": "project_13", + "number": "13", + "title": "Empower Chinatown", + "type": "Data Visualization", + "keyword": "Public Engagement | Policy Research", + "time": "2024-05", + "description": "This project aims to protect Chinatown's cultural heritage while fostering community resilience, accessibility, and safety. It strengthens local businesses and institutions, connecting Chinatown to Philadelphia's broader Asian American communities.", + "folder": "assets/project_13", + "url": "project_template.html" + }, + { + "id": "project_14", + "number": "14", + "title": "Lac Rose Plan", + "type": "Data Visualization", + "keyword": "Tourist Town | Forest Conservation", + "time": "2024-11", + "description": "This project begins with organizing and visualizing data resources from the Dakar region in Senegal. Combining insights from field research and community meetings, it provides strategic recommendations for urban development around Lac Rose, balancing nature conservation with urban growth while addressing the needs of both local residents and tourists.", + "folder": "assets/project_14", + "url": "project_template.html" + }, + { + "id": "project_15", + "number": "15", + "title": "Police Patrol Locations", + "type": "Spatial Analysis", + "keyword": "Optimization | MCLP | P-Median", + "time": "2024-05", + "description": "This study optimizes police patrol locations in Philadelphia by analyzing spatial-temporal crime severity patterns. Using MCLP and P-Median models, it identifies strategies to balance coverage and response time, improving public safety.", + "folder": "assets/project_15", + "url": "project_template.html" + }, + { + "id": "project_16", + "number": "16", + "title": "Bike & Subway Usage", + "type": "Spatial Analysis", + "keyword": "Correlation Analysis | Time Series", + "time": "2024-04", + "description": "This study investigates the relationship between bike-sharing and subway ridership in the MBTA core area, revealing a positive correlation that varies across different times of the day.", + "folder": "assets/project_16", + "url": "project_template.html" + }, + { + "id": "project_17", + "number": "17", + "title": "Predict Bike Ridership", + "type": "Spatial Analysis", + "keyword": "Machine Learning | Time Lag", + "time": "2024-05", + "description": "This project analyzes Philadelphia's Indego bike-sharing system to explore how time, location, and weather affect trip patterns. It focuses on strategies to balance bike supply and demand, comparing efficient truck-based redistribution with cost-effective rider incentives to enhance system sustainability and accessibility.", + "folder": "assets/project_17", + "url": "project_template.html" + }, + { + "id": "project_18", + "number": "18", + "title": "Predict Gentrification", + "type": "Spatial Analysis", + "keyword": "K-Means | Logistic Regression", + "time": "2024-06", + "description": "This project uses logistic regression to predict gentrification in Chicago, focusing on census tract data. It identifies risks in minority areas with rising incomes and education, supporting targeted policies and resource allocation.", + "folder": "assets/project_18", + "url": "project_template.html" + } +] \ No newline at end of file diff --git a/img/comment-icon.svg b/img/comment-icon.svg new file mode 100644 index 0000000..6b4905f --- /dev/null +++ b/img/comment-icon.svg @@ -0,0 +1,15 @@ + + + + + + + + \ No newline at end of file diff --git a/img/favicon.svg b/img/favicon.svg new file mode 100644 index 0000000..0aadfa2 --- /dev/null +++ b/img/favicon.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/img/photo.jpg b/img/photo.jpg new file mode 100644 index 0000000..ff1bf83 Binary files /dev/null and b/img/photo.jpg differ diff --git a/img/resume-icon.svg b/img/resume-icon.svg new file mode 100644 index 0000000..8386507 --- /dev/null +++ b/img/resume-icon.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/img/resume.jpg b/img/resume.jpg new file mode 100644 index 0000000..523928d Binary files /dev/null and b/img/resume.jpg differ diff --git a/img/select-icon.svg b/img/select-icon.svg new file mode 100644 index 0000000..173c3e9 --- /dev/null +++ b/img/select-icon.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..7bd6e89 --- /dev/null +++ b/index.html @@ -0,0 +1,108 @@ + + + + + + + + + Projects - Yaohan Xu + + + + + + + + + + + + + + + + +
+ + + + +
+ +
+

Projects

+

Welcome to my Projects page! This page shows selected works from my undergraduate and graduate studies, highlighting my skills, creativity, and passion for diverse fields. The projects are categorized into four areas: data visualization, landscape and urban design, spatial analysis, and web design. Most are drawn from academic assignments and professional internships. Each project reflects my journey of exploring new ideas, overcoming challenges, and delivering impactful solutions — demonstrating both technical expertise and a commitment to continuous learning and innovation. Feel free to explore and discover more!

+
+ + +
+
+

View — All Projects

+ Project Type Selector +
+ +
    +
  • +
  • +
  • +
  • +
  • +
+
+ + + +
+
+ + + + + + + + \ No newline at end of file diff --git a/js/about.js b/js/about.js new file mode 100644 index 0000000..9cb74ad --- /dev/null +++ b/js/about.js @@ -0,0 +1,19 @@ +// Add a click event to show the resume when the button is clicked +const button = document.querySelector('.view-resume-button'); +const popupOverlay = document.getElementById('popup-overlay'); + +button.addEventListener('click', () => { + popupOverlay.style.display = 'flex'; +}); + +// Hide the resume when clicking outside the image +popupOverlay.addEventListener('click', (event) => { + if (event.target === popupOverlay) { + popupOverlay.style.display = 'none'; + } +}); + +// Display the page after it is loaded +window.addEventListener('load', () => { + document.body.classList.add('loaded'); // Add the loaded class to the body +}); diff --git a/js/comments.js b/js/comments.js new file mode 100644 index 0000000..1b3baf7 --- /dev/null +++ b/js/comments.js @@ -0,0 +1,69 @@ +// This is the main script for the comments page. +import { loadProjectsData } from './projects_data.js'; +import { initProjectsSelect } from './projects_select.js'; +import { loadComments } from './comments_load.js'; +import { commentsControl } from './comments_control.js'; +import { submitComments } from './comments_submit.js'; +import { setupProjectButtons } from './project_button.js'; + +// Load projects data +const projectsData = await loadProjectsData(); + +// Create project list items +const projectsListItems = {}; +function initListItems() { + for (const project of projectsData) { + const listItem = document.createElement('li'); + listItem.className = 'project-item'; + listItem.setAttribute('data-project-id', project.id); + + // Create the list item content + listItem.innerHTML = ` +
+
+ ${project.title} Cover +
+
+
+ ${project.number} + ${project.title} +
+ +
+
+ +
+ +
+ `; + + const commentsList = listItem.querySelector('.comments-list'); + loadComments(project.id, commentsList); + + // Add the list item to the list + projectsListItems[project.id] = listItem; + } +} +initListItems(); +console.log(projectsListItems); // Print the list items + +// Initialize the selected projects list +const projectsListEl = document.querySelector('#projects-list'); +initProjectsSelect(projectsListEl, projectsListItems, projectsData); + +// Initialize the control of showing/hiding comments +commentsControl(); + +// Initialize the comments submit function +submitComments(); + +// Add a event listener to load the project page when a project is clicked +setupProjectButtons('.project-cover-container', 'data-project-id', 'project_template.html'); + +// Display the page after it is loaded +window.addEventListener('load', () => { + document.body.classList.add('loaded'); // Add the loaded class to the body +}); diff --git a/js/comments_control.js b/js/comments_control.js new file mode 100644 index 0000000..f61d0b2 --- /dev/null +++ b/js/comments_control.js @@ -0,0 +1,107 @@ +// Control the show/hide comments switch +// When the toggle switch is set to `show`, all comments will be loaded and displayed. +// When the toggle switch is set to `hide`, all comments will be cleared. +// However, when the toggle switch is set to `hide`, the comments will be loaded and displayed when the mouse hovers over the project cover. +import { loadComments } from './comments_load.js'; + +function commentsControl() { + const toggleSwitch = document.querySelector('.toggle-switch'); + const allCommentsParts = document.querySelectorAll('.comments-part'); + const projectCoverContainers = document.querySelectorAll('.project-cover-container'); + + let isShowActive = true; // Initialize the state of toggle switch to `show` + + // A function to handle mouse enter event + function handleMouseEnter(event) { + if (isShowActive) return; // Ensure the mouse logic is disabled in `show` state + + const projectId = event.currentTarget.closest('.project-item').dataset.projectId; + const relatedComments = document.querySelector(`#comments-${projectId}`); + if (relatedComments) { + const commentsList = relatedComments.querySelector('.comments-list'); + loadComments(projectId, commentsList); // Add comments to the project item + } + } + + // A function to handle mouse leave event + function handleMouseLeave(event) { + if (isShowActive) return; // Ensure the mouse logic is disabled in `show` state + + const projectId = event.currentTarget.closest('.project-item').dataset.projectId; + const relatedComments = document.querySelector(`#comments-${projectId}`); + if (relatedComments) { + const commentsList = relatedComments.querySelector('.comments-list'); + commentsList.innerHTML = ''; // Clear the comments of the project item + } + } + + // Update mouse event listeners status + function updateMouseEventListeners() { + if (isShowActive) { + projectCoverContainers.forEach((coverContainer) => { + coverContainer.removeEventListener('mouseenter', handleMouseEnter); + coverContainer.removeEventListener('mouseleave', handleMouseLeave); + }); + } else { + projectCoverContainers.forEach((coverContainer) => { + coverContainer.addEventListener('mouseenter', handleMouseEnter); + coverContainer.addEventListener('mouseleave', handleMouseLeave); + }); + } + } + + // Initialize mouse event listeners + projectCoverContainers.forEach((coverContainer) => { + coverContainer.addEventListener('mouseenter', handleMouseEnter); + coverContainer.addEventListener('mouseleave', handleMouseLeave); + }); + + // Make sure the hover effect is disabled when the page is first loaded + document.querySelector('.projects-list').classList.add('no-hover'); + + // Load and clear all comments based on the toggle switch state + toggleSwitch.addEventListener('click', (event) => { + if (event.target.classList.contains('comments-show')) { + toggleSwitch.querySelector('.comments-show').setAttribute('active', true); + toggleSwitch.querySelector('.comments-hide').removeAttribute('active'); + + // Load all comments to the project items + const promises = []; + + allCommentsParts.forEach((commentsPart) => { + const projectId = commentsPart.closest('.project-item').dataset.projectId; + const commentsList = commentsPart.querySelector('.comments-list'); + + promises.push(loadComments(projectId, commentsList)); // Add comments dynamically + }); + + Promise.all(promises).then(() => { // Wait for all comments to be loaded + console.log('All comments are loaded'); + }).catch((error) => { + console.error('Failed to load comments:', error); + }); // Set for debugging + + document.querySelector('.projects-list').classList.add('no-hover'); // Remove the hover effect + + isShowActive = true; // Set the toggle switch to `show` state + } else if (event.target.classList.contains('comments-hide')) { + toggleSwitch.querySelector('.comments-hide').setAttribute('active', true); + toggleSwitch.querySelector('.comments-show').removeAttribute('active'); + + // Clear all comments from the project items + allCommentsParts.forEach((commentsPart) => { + const commentsList = commentsPart.querySelector('.comments-list'); + commentsList.innerHTML = ''; // Remove comments dynamically + }); + + document.querySelector('.projects-list').classList.remove('no-hover'); // Restore the hover effect + + isShowActive = false; // Set the toggle switch to `hide` state + } + }); + + // Update the mouse event listeners status when the toggle switch is clicked + toggleSwitch.addEventListener('click', updateMouseEventListeners); +} + +export { commentsControl }; diff --git a/js/comments_load.js b/js/comments_load.js new file mode 100644 index 0000000..9351c4f --- /dev/null +++ b/js/comments_load.js @@ -0,0 +1,56 @@ +import { getProjectComments } from './firebase.js'; + +// Define a function to load comments for a specific project +async function loadComments(projectId, commentsList) { + // Download comments for a specific project from Firebase + const comments = await getProjectComments(projectId); + + // Clear the commentsList + commentsList.innerHTML = ''; + + // Fullfill the commentsList with comments + for (const comment of comments) { + const commentItem = document.createElement('li'); + commentItem.className = 'comment-item'; + commentItem.innerHTML = ` +
+ ${comment.name} + ${comment.time} +
+
+ ${comment.content} +
+ `; + commentsList.appendChild(commentItem); + } + + // Adjust the height of commentsList after the comments are loaded + // This needs to be run every time after the comments are loaded because the comments data are dynamic + adjustCommentsHeight(projectId); +} + +// Define a function to adjust the height of comments part based on the height of project part +function adjustCommentsHeight(projectId) { + const listItem = document.querySelector(`[data-project-id="${projectId}"]`); + if (!listItem) return; + + const projectPart = listItem.querySelector('.project-part'); + const commentsPart = listItem.querySelector('.comments-part'); + + if (!projectPart || !commentsPart) return; + + // Get the height of the project and comments parts + const projectHeight = projectPart.offsetHeight; + const commentsHeight = commentsPart.scrollHeight; + + // Set the max height of the comments part based on the height of the project part + if (commentsHeight > projectHeight) { + commentsPart.style.maxHeight = `${projectHeight}px`; // Limit the max height + commentsPart.style.overflowY = 'auto'; // Enable the scroll bar + } else { + commentsPart.style.maxHeight = 'none'; // Set back to default + commentsPart.style.overflowY = 'hidden'; // Disable the scroll bar + } +} + +export { loadComments }; diff --git a/js/comments_load_template.js b/js/comments_load_template.js new file mode 100644 index 0000000..9bf72c6 --- /dev/null +++ b/js/comments_load_template.js @@ -0,0 +1,28 @@ +import { getProjectComments } from './firebase.js'; + +// Define a function to load project comments dynamically +async function loadTemplateComments(projectId, commentsList) { + // Download comments for a specific project from Firebase + const comments = await getProjectComments(projectId); + + // Clear the commentsList + commentsList.innerHTML = ''; + + // Fullfill the commentsList with comments + for (const comment of comments) { + const commentItem = document.createElement('li'); + commentItem.className = 'comment-item'; + commentItem.innerHTML = ` +
+ ${comment.name} + ${comment.time} +
+
+ ${comment.content} +
+ `; + commentsList.appendChild(commentItem); + } +} + +export { loadTemplateComments }; diff --git a/js/comments_submit.js b/js/comments_submit.js new file mode 100644 index 0000000..b1d75bd --- /dev/null +++ b/js/comments_submit.js @@ -0,0 +1,68 @@ +import { addProjectComment } from './firebase.js'; +import { loadComments } from './comments_load.js'; + +// Define a function to submit comments using the comment modal +function submitComments() { + const modal = document.getElementById('comment-modal'); + const nameInput = document.getElementById('name-input'); + const contentInput = document.getElementById('content-input'); + const submitButton = document.getElementById('submit-comment'); + const closeButton = document.getElementById('close-modal'); + let currentProjectId = null; // Store the current project ID + + // Add click event listeners to all comment buttons + document.querySelectorAll('.project-comment-button').forEach((button) => { + button.addEventListener('click', () => { + currentProjectId = button.id.split('-').pop(); // Extract the project ID from the button ID + modal.classList.remove('hidden'); // Show the comment modal + }); + }); + + // Add click event listener to the submit button + submitButton.addEventListener('click', async () => { + const name = nameInput.value.trim(); + const content = contentInput.value.trim(); + + if (!name || !content) { + alert('Please fill in both name and content!'); + return; + } + + try { + // Add the comment to the current project in firebase + await addProjectComment(currentProjectId, name, content); + + // Get the comments list of the current project + const listItem = document.querySelector(`#add-comment-${currentProjectId}`).closest('li'); + const commentsList = listItem.querySelector('.comments-list'); + + // Check the current state of the toggle switch + const toggleSwitch = document.querySelector('.toggle-switch'); + const commentsShow = toggleSwitch.querySelector('.comments-show'); + + if (commentsShow.hasAttribute('active')) { + // if comments are shown, reload the comments, if not, do nothing + await loadComments(currentProjectId, commentsList); + } + } catch (error) { + console.error('Error adding comment:', error); + alert('Failed to add comment. Please try again.'); + } finally { + // Hide the modal and clear the input fields + modal.classList.add('hidden'); + nameInput.value = ''; + contentInput.value = ''; + currentProjectId = null; // Reset the project ID to null + } + }); + + // Add click event listener to the close button + closeButton.addEventListener('click', () => { + modal.classList.add('hidden'); // Hide the modal + nameInput.value = ''; + contentInput.value = ''; + currentProjectId = null; // Reset the project ID to null + }); +} + +export { submitComments }; diff --git a/js/firebase.js b/js/firebase.js new file mode 100644 index 0000000..24dce78 --- /dev/null +++ b/js/firebase.js @@ -0,0 +1,62 @@ +// Import the functions you need from the SDKs you need +import { initializeApp } from 'https://www.gstatic.com/firebasejs/11.1.0/firebase-app.js'; +import { getFirestore, collection, query, where, addDoc, getDocs } from 'https://www.gstatic.com/firebasejs/11.1.0/firebase-firestore.js'; +import { getAnalytics } from 'https://www.gstatic.com/firebasejs/11.1.0/firebase-analytics.js'; +// TODO: Add SDKs for Firebase products that you want to use +// https://firebase.google.com/docs/web/setup#available-libraries + +// Your web app's Firebase configuration +// For Firebase JS SDK v7.20.0 and later, measurementId is optional +const firebaseConfig = { + apiKey: 'AIzaSyCKlDER4TqrzQHdbXRDStC906GorFBq_bE', + authDomain: 'personal-website-2f4e6.firebaseapp.com', + projectId: 'personal-website-2f4e6', + storageBucket: 'personal-website-2f4e6.firebasestorage.app', + messagingSenderId: '265752195974', + appId: '1:265752195974:web:d974b9321c1e4fb6aab207', + measurementId: 'G-P8JEBF4JQP', +}; + +// Initialize Firebase +const app = initializeApp(firebaseConfig); +const analytics = getAnalytics(app); +const db = getFirestore(app); + +// Define a function to download comments for a specific project from firebase +async function getProjectComments(projectId) { + const commentsCollection = collection(db, 'project_comments'); + const commentsQuery = query(commentsCollection, where('projectId', '==', projectId)); + const querySnapshot = await getDocs(commentsQuery); + + const comments = querySnapshot.docs.map((doc) => { + const data = doc.data(); + const time = data.time?.toDate(); + const formattedTime = time ? `${time.getFullYear()}-${String(time.getMonth() + 1).padStart(2, '0')}` : null; + + return { + id: doc.id, + ...data, + time: formattedTime, + originalTime: time, // Keep the original time stamp for sorting + }; + }); + + // Sort comments by original time stamp in descending order + comments.sort((a, b) => (b.originalTime - a.originalTime)); + + // Remove originalTime from the final result + return comments.map(({ originalTime, ...rest }) => rest); +} + +// Define a function to add a comment for a specific project to firebase +async function addProjectComment(projectId, name, content) { + const commentsCollection = collection(db, 'project_comments'); + await addDoc(commentsCollection, { + projectId, + name, + content, + time: new Date(), + }); +} + +export { app, analytics, db, getProjectComments, addProjectComment}; diff --git a/js/project_button.js b/js/project_button.js new file mode 100644 index 0000000..0e9dd7f --- /dev/null +++ b/js/project_button.js @@ -0,0 +1,34 @@ +import { loadProjectsData } from './projects_data.js'; + +async function setupProjectButtons(containerSelector, projectIdAttribute = 'data-project-id', defaultPage = 'project_template.html') { + const containers = document.querySelectorAll(containerSelector); + + if (containers.length === 0) { + console.warn(`No containers found for selector: ${containerSelector}`); + return; + } + + // Load projects data + const projects = await loadProjectsData(); + + // Add click event listener to the project containers + containers.forEach((container) => { + container.addEventListener('click', (event) => { + const target = event.target.closest(`[${projectIdAttribute}]`); + + if (target) { + const projectId = target.getAttribute(projectIdAttribute); + const project = projects.find((p) => p.id === projectId); + + if (project) { + const projectUrl = project.url || defaultPage; + window.location.href = `${projectUrl}?id=${projectId}`; + } else { + console.error('Project not found for ID:', projectId); + } + } + }); + }); +} + +export { setupProjectButtons }; diff --git a/js/project_page.js b/js/project_page.js new file mode 100644 index 0000000..8b3e023a --- /dev/null +++ b/js/project_page.js @@ -0,0 +1,110 @@ +// Define a function to load project page dynamically +import { loadProjectsData } from './projects_data.js'; +import { getProjectComments } from './firebase.js'; + +async function loadProjectPage() { + // Load projects data + const projectsData = await loadProjectsData(); + + const params = new URLSearchParams(window.location.search); + const projectId = params.get('id'); + + const project = projectsData.find((project) => project.id === projectId); + + if (project) { + // Load project header dynamically + loadProjectHeader(project); + console.log(project); + + // Load project images and descriptions dynamically + const contentList = document.getElementById('project-content-list'); + await loadProjectContent(project, contentList); + + // Load project comments dynamically + const commentsList = document.getElementById('project-comments-list'); + await loadProjectComments(project.id, commentsList); + } else { + document.body.innerHTML = '

Project not found

'; + } +} + +// Define a function to load project header dynamically +function loadProjectHeader(project) { + const titleElement = document.getElementById('project-title'); + const descriptionsElement = document.getElementById('project-description'); + + titleElement.textContent = project.title; + descriptionsElement.textContent = project.description; +} + +// Define a function to load project images and descriptions dynamically +async function loadProjectContent(project, contentList) { + contentList.innerHTML = ''; + + const folder = project.folder; + const maxImages = 10; + const imagePromises = []; + const descriptionPromises = []; + + for (let i = 1; i <= maxImages; i++) { + const imagePath = `${folder}/Image_${i}.jpg`; + const descriptionPath = `${folder}/Description_${i}.txt`; + + const imagePromise = fetch(imagePath) + .then((response) => (response.ok ? imagePath : null)) + .catch(() => null); + + const descriptionPromise = fetch(descriptionPath) + .then((response) => (response.ok ? response.text() : null)) + .catch(() => null); + + imagePromises.push(imagePromise); + descriptionPromises.push(descriptionPromise); + } + + const images = await Promise.all(imagePromises); + const descriptions = await Promise.all(descriptionPromises); + + images.forEach((image, index) => { + if (image) { + const description = descriptions[index] || 'No description available'; + const listItem = document.createElement('li'); + listItem.classList.add('project-content-item'); + + listItem.innerHTML = ` +
+ Image ${index + 1} +
+

${description}

+ `; + contentList.appendChild(listItem); + } + }); +} + +// Define a function to load project comments dynamically +async function loadProjectComments(projectId, commentsList) { + // Download comments for a specific project from Firebase + const comments = await getProjectComments(projectId); + + // Clear the commentsList + commentsList.innerHTML = ''; + + // Fullfill the commentsList with comments + for (const comment of comments) { + const commentItem = document.createElement('li'); + commentItem.className = 'comment-item'; + commentItem.innerHTML = ` +
+ ${comment.name} + ${comment.time} +
+
+ ${comment.content} +
+ `; + commentsList.appendChild(commentItem); + } +} + +export { loadProjectPage }; diff --git a/js/project_template.js b/js/project_template.js new file mode 100644 index 0000000..0fde1f7 --- /dev/null +++ b/js/project_template.js @@ -0,0 +1,62 @@ +import { loadProjectPage } from './project_page.js'; +import { addProjectComment } from './firebase.js'; +import { loadTemplateComments } from './comments_load_template.js'; + +// The function to dynamically load the project page contents +loadProjectPage(); + +// Define a function to submit comments using the comment modal +function submitTemplateComments() { + const button = document.getElementById('comment-button'); + const modal = document.getElementById('comment-modal'); + const nameInput = document.getElementById('name-input'); + const contentInput = document.getElementById('content-input'); + const submitButton = document.getElementById('submit-comment'); + const closeButton = document.getElementById('close-modal'); + const commentsList = document.getElementById('project-comments-list'); + + const params = new URLSearchParams(window.location.search); + const currentProjectId = params.get('id'); + + button.addEventListener('click', () => { + modal.classList.remove('hidden'); // Show the comment modal + }); + + // Add click event listener to the submit button + submitButton.addEventListener('click', async () => { + const name = nameInput.value.trim(); + const content = contentInput.value.trim(); + + if (!name || !content) { + alert('Please fill in both name and content!'); + return; + } + + try { + // Add the comment to the current project in firebase + await addProjectComment(currentProjectId, name, content); + await loadTemplateComments(currentProjectId, commentsList); + } catch (error) { + console.error('Error adding comment:', error); + alert('Failed to add comment. Please try again.'); + } finally { + // Hide the modal and clear the input fields + modal.classList.add('hidden'); + nameInput.value = ''; + contentInput.value = ''; + } + }); + + // Add click event listener to the close button + closeButton.addEventListener('click', () => { + modal.classList.add('hidden'); // Hide the modal + nameInput.value = ''; + contentInput.value = ''; + }); +} +submitTemplateComments(); + +// Display the page after it is loaded +window.addEventListener('load', () => { + document.body.classList.add('loaded'); // Add the loaded class to the body +}); diff --git a/js/projects.js b/js/projects.js new file mode 100644 index 0000000..46b4d2b --- /dev/null +++ b/js/projects.js @@ -0,0 +1,86 @@ +// This is the main script for the projects page. +import { loadProjectsData } from './projects_data.js'; +import { initProjectsSelect } from './projects_select.js'; +import { setupProjectButtons } from './project_button.js'; + +// Load projects data +const projectsData = await loadProjectsData(); + +// Create project list items +const projectsListItems = {}; +function initListItems() { + for (const project of projectsData) { + const listItem = document.createElement('li'); + listItem.className = 'project-item'; + listItem.setAttribute('data-project-id', project.id); + + // Create the list item content + listItem.innerHTML = ` +
+ ${project.title} Cover +
+ +
+
+ ${project.number} + ${project.title} +
+
+ ${project.type} + ${project.keyword} + ${project.time} +
+
+ ${project.description} +
+
+ `; + + // Add the list item to the list + projectsListItems[project.id] = listItem; + } +} +initListItems(); +console.log(projectsListItems); // Print the list items + +// Initialize the selected projects list +const projectsListEl = document.querySelector('#projects-list'); +initProjectsSelect(projectsListEl, projectsListItems, projectsData); + +// Add a event listener to load the project page when a project is clicked +setupProjectButtons('.project-cover-container, .project-details', 'data-project-id', 'project_template.html'); + +// Control the maximum height of the project details manually after the page is loaded +// This only needs to be run once after the page is loaded because the project details are static +window.addEventListener('load', () => { + const projectItems = document.querySelectorAll('.project-item'); + + projectItems.forEach((item, index) => { + const coverPart = item.querySelector('.project-cover-container'); + const detailsPart = item.querySelector('.project-details'); + + if (!coverPart || !detailsPart) { + console.warn(`Missing elements for index ${index}`); + return; + } + + // Get the height of the cover and details parts + const coverHeight = coverPart.offsetHeight; + const detailsHeight = detailsPart.scrollHeight; + + console.log(`Cover Part ${index} - Height: ${coverHeight}`); + console.log(`Details Part ${index} - Height: ${detailsHeight}`); + + // Set the max height of the details part depending on the cover part + if (detailsHeight > coverHeight) { + detailsPart.style.maxHeight = `${coverHeight}px`; // Limit the max height + detailsPart.style.overflowY = 'auto'; // Enable the scroll bar + } else { + detailsPart.style.maxHeight = 'none'; // Set back to default + detailsPart.style.overflowY = 'hidden'; // Disable the scroll bar + } + }); + + // Display the page after it is loaded + document.body.classList.add('loaded'); // Add the loaded class to the body +}); diff --git a/js/projects_data.js b/js/projects_data.js new file mode 100644 index 0000000..aaca5bf --- /dev/null +++ b/js/projects_data.js @@ -0,0 +1,16 @@ +// Load the projects data from the projects.json file +async function loadProjectsData() { + try { + const projectsResponse = await fetch('data/projects.json'); + if (!projectsResponse.ok) { + throw new Error('Failed to load projects data'); + } + const projectsData = await projectsResponse.json(); + return projectsData; + } catch (error) { + console.error('Error fetching or parsing the projects.json file:', error); + return []; + } +} + +export { loadProjectsData }; diff --git a/js/projects_select.js b/js/projects_select.js new file mode 100644 index 0000000..6aa3843 --- /dev/null +++ b/js/projects_select.js @@ -0,0 +1,71 @@ +// Initialize the projects type filter and list display +function initProjectsSelect(projectsListEl, projectsListItems, projectsData) { + // Create variables for the filter elements + const asideFilter = document.querySelector('.aside-project-type-filter'); + const mainFilter = document.querySelector('.main-project-type-filter'); + const mainSelectedText = document.querySelector('.main-project-type-selected p'); + const mainTypeList = document.querySelector('.main-project-type-list'); + + // Create a function to populate the list + function populateList(projects) { + projectsListEl.innerHTML = ''; + + projects.forEach((project, index) => { + const projectItem = projectsListItems[project.id]; + projectItem.querySelector('.project-number').textContent = String(index + 1).padStart(2, '0'); + projectsListEl.append(projectItem); + }); + } + populateList(projectsData); // Populate the list with all projects + + // Create a function to set the active button + function setActiveButton(filterContainer, type) { + const buttons = filterContainer.querySelectorAll('button'); + buttons.forEach((button) => { + if (button.dataset.type === type) { + button.classList.add('active'); + } else { + button.classList.remove('active'); + } + }); + } + + // Create a function to handle filter clicks + function handleFilterClick(type) { + setActiveButton(asideFilter, type); + setActiveButton(mainFilter, type); + mainSelectedText.textContent = `View — ${type.split(' ').map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(' ')}`; + + const filteredProjects = type === 'all projects' ? projectsData : projectsData.filter((project) => project.type.toLowerCase() === type); + populateList(filteredProjects); + } + + // Add event listener to the aside filter elements + asideFilter.addEventListener('click', (event) => { + if (event.target.tagName === 'BUTTON') { + const type = event.target.dataset.type; + handleFilterClick(type); + } + }); + + // Add event listener to the main filter elements + mainFilter.addEventListener('click', (event) => { + if (event.target.tagName === 'BUTTON') { + const type = event.target.dataset.type; + handleFilterClick(type); + // Ensure the list folds back after a selection + mainTypeList.classList.remove('visible'); + } else if (event.target.closest('.main-project-type-selected')) { + // Toggle visibility and ensure reset display + if (mainTypeList.classList.contains('visible')) { + mainTypeList.classList.remove('visible'); + } else { + setTimeout(() => { + mainTypeList.classList.add('visible'); + }, 10); // Delay the visibility to ensure the click event is handled + } + } + }); +} + +export { initProjectsSelect }; diff --git a/package-lock.json b/package-lock.json index 9f99c26..50b120a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -517,10 +517,11 @@ } }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, + "license": "MIT", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -1581,9 +1582,9 @@ "dev": true }, "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", "dev": true, "funding": [ { @@ -1591,6 +1592,7 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -2855,9 +2857,9 @@ } }, "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "requires": { "path-key": "^3.1.0", @@ -3649,9 +3651,9 @@ "dev": true }, "nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", "dev": true }, "natural-compare": { diff --git a/project_template.html b/project_template.html new file mode 100644 index 0000000..74a3d36 --- /dev/null +++ b/project_template.html @@ -0,0 +1,188 @@ + + + + + + + + + Project Page + + + + + + + + + + + + +
+ + + + +
+ +
+

Dynamic Lifeline

+

Costa Rica's tropical rainforests have faced severe destruction due to deforestation, agriculture, ranching, and urbanization, causing significant habitat loss for wildlife. Sloths, as slow-moving arboreal animals, are particularly at risk due to their reliance on a continuous canopy. This project aims to create safe migration paths for sloths across cities, farmland, and fragmented rainforests by proposing tailored strategies that address specific threats while harmonizing with the environment. These interventions gradually reduce human impact, supporting sloths in safely migrating and returning to nature.

+
+ + +
+ +
    +
  • +
    + Image One +
    +

    + The project is a design proposal for sloths to cross three types of land: city, farmland, and + fragmented rainforest. The design strategies are proposed to meet sloths' specific needs when + crossing three types of sites. The project aims to create safe environments for sloths to migrate + as well as help them return to nature. +

    +
  • +
  • +
    + Image Two +
    +

    + The project is a design proposal for sloths to cross three types of land: city, farmland, and + fragmented rainforest. The design strategies are proposed to meet sloths' specific needs when + crossing three types of sites. The project aims to create safe environments for sloths to migrate + as well as help them return to nature. +

    +
  • +
  • +
    + Image Three +
    +

    + The project is a design proposal for sloths to cross three types of land: city, farmland, and + fragmented rainforest. The design strategies are proposed to meet sloths' specific needs when + crossing three types of sites. The project aims to create safe environments for sloths to migrate + as well as help them return to nature. +

    +
  • +
  • +
    + Image Four +
    +

    + The project is a design proposal for sloths to cross three types of land: city, farmland, and + fragmented rainforest. The design strategies are proposed to meet sloths' specific needs when + crossing three types of sites. The project aims to create safe environments for sloths to migrate + as well as help them return to nature. +

    +
  • +
  • +
    + Image Five +
    +

    + The project is a design proposal for sloths to cross three types of land: city, farmland, and + fragmented rainforest. The design strategies are proposed to meet sloths' specific needs when + crossing three types of sites. The project aims to create safe environments for sloths to migrate + as well as help them return to nature. +

    +
  • +
  • +
    + Image Six +
    +

    + The project is a design proposal for sloths to cross three types of land: city, farmland, and + fragmented rainforest. The design strategies are proposed to meet sloths' specific needs when + crossing three types of sites. The project aims to create safe environments for sloths to migrate + as well as help them return to nature. +

    +
  • +
+
+ + +
+
+

Comments

+ +
+ + +
    +
  • +
    + Ethan Cooper + 2024-07 +
    +
    + This project proves that great design starts with understanding the environment, crafting a space where innovation and tradition come together in perfect harmony. +
    +
  • +
  • +
    + Mason Perez + 2024-09 +
    +
    + A fine example of how design can work with, rather than against, the environment, fostering a deeper appreciation for the interconnectedness of all things. +
    +
  • +
  • +
    + Ava Walker + 2024-11 +
    +
    + It's refreshing to see such a holistic approach to design, where every component is in harmony with the larger ecosystem, inspiring a new standard in architectural thinking. +
    +
  • +
+
+
+
+ + + + + + + + + + + \ No newline at end of file