diff --git a/.gitignore b/.gitignore index 3555062..c34085d 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ build/ !**/src/main/** !**/src/test/** *.DS_STORE +local.properties ### STS ### .apt_generated diff --git a/src/commonMain/kotlin/codes/miley/model/Experience.kt b/src/commonMain/kotlin/codes/miley/model/Experience.kt index 9a6bc5a..ddcaf8e 100644 --- a/src/commonMain/kotlin/codes/miley/model/Experience.kt +++ b/src/commonMain/kotlin/codes/miley/model/Experience.kt @@ -19,14 +19,51 @@ data class Experience( companion object } +val ADYEN = Experience( + id = 123, + timespans = listOf(2025 to null), + name = "Adyen", + title = "Android Software Engineer II", + route = "/adyen", + summary = """ + • Working on the in-person payments Android SDK, enabling merchants to accept + payments through Android devices. + """.trimIndent(), + category = Category.JOB, + skills = setOf( + Skill.ANDROID, + Skill.COMPOSE, + Skill.JNI, + Skill.KOTLIN, + Skill.PROTOBUF, + Skill.SDK, + ), + topics = setOf( + Topic.PAYMENTS, + Topic.UI, + Topic.UX, + ), + iconUri = "/experiences/job/adyen/icon.png", + media = listOf( + Media.Image( + uri = "/job/adyen/sdk.png", + ), + ), +) + val GRINDR = Experience( id = 123, - timespans = listOf(2023 to null), + timespans = listOf(2023 to 2024), name = "Grindr", title = "Android Developer", route = "/grindr", - summary = "Enhancing customer satisfaction and retention by boosting engagement through IAP-based user experiences. " + - "Focusing on map driven user interactions, which includes using Google Maps APIs for location tagging and custom overlay drawing.", + summary = """ + • Enhancing customer satisfaction and retention by boosting engagement through + IAP-based user experiences. + + • Focusing on map driven user interactions, which includes using Google Maps APIs + for location tagging and custom overlay drawing. + """.trimIndent(), category = Category.FREELANCE, skills = setOf( Skill.ANDROID, @@ -54,13 +91,20 @@ val GRINDR = Experience( val WALMART = Experience( id = 123, - timespans = listOf(2022 to null), + timespans = listOf(2022 to 2024), name = "Walmart", title = "Senior Mobile Solutions Engineer", route = "/walmart", - summary = "Android Platform Team; designing Gradle build tools to boost developer productivity and ensure tech debt is properly managed. " + - "W+ Engagement and Retention Team; building features to improve engagement by enhancing communication of membership benefits. " + - "W+ Account Management Team; improving user satisfaction by developing features to enhance the membership settings experience.", + summary = """ + • Android Platform Team; designing Gradle build tools to boost developer productivity + and ensure tech debt is properly managed. + + • W+ Engagement and Retention Team; building features to improve engagement by + enhancing communication of membership benefits. + + • W+ Account Management Team; improving user satisfaction by developing features + to enhance the membership settings experience. + """.trimIndent(), category = Category.JOB, skills = setOf( Skill.ANDROID, @@ -89,11 +133,17 @@ val SOCKET = Experience( id = 12345, timespans = listOf(2021 to null), name = "Socket", - title = "Kotlin Multiplatform", + title = "Founder & Independent Developer", route = "/socket", - summary = "Developing a cross-platform automation platform capable of extensible control of all of your devices. " + - "Creating native applications for Android, iOS, Web, and Desktop by leveraging Kotlin Multiplatform, Compose Multiplatform, and SwiftUI. " + - "Prioritizing a \"write once, run everywhere\" architecture for rapid feature development across all client apps simultaneously.", + summary = """ + • Developing a cross-platform automation platform capable of extensible control of all of your devices. + + • Creating native applications for Android, iOS, Web, and Desktop by leveraging Kotlin Multiplatform, + Compose Multiplatform, and SwiftUI. + + • Prioritizing a "write once, run everywhere" architecture for rapid feature development across all + client apps simultaneously. + """.trimIndent(), category = Category.PROJECT, skills = setOf( Skill.COMPOSE, @@ -125,8 +175,14 @@ val STOCK_X = Experience( name = "StockX", title = "Android Software Engineer II", route = "/stockx", - summary = "Worked in a legacy Java codebase to migrate features into Kotlin using Clean Architecture. " + - "Involved in Checkout, Payment, and Internationalization feature work. Responsible for the initial adoption of Jetpack Compose while performing a complete rewrite of the checkout flow.", + summary = """ + • Worked in a legacy Java codebase to migrate features into Kotlin using Clean Architecture. + + • Involved in Checkout, Payment, and Internationalization feature work. + + • Responsible for the initial adoption of Jetpack Compose while performing a complete rewrite + of the checkout flow. + """.trimIndent(), category = Category.JOB, skills = setOf( Skill.ANDROID, @@ -162,9 +218,15 @@ val SHARPEN = Experience( name = "Sharpen", title = "Android Developer", route = "/sharpen", - summary = "Architected a native Android app using Jetpack Compose, Coroutines, MVU, and GraphQL. " + - "Delivered a minimum viable direct-to-consumer product in 2022 for McGraw Hill -- the world's leading ed-tech company. " + - "Rejoined the project in 2023 to work on a cross-platform analytics library implementation for launching v2.", + summary = """ + • Architected a native Android app using Jetpack Compose, Coroutines, MVU, and GraphQL. + + • Delivered a minimum viable direct-to-consumer product in 2022 for McGraw Hill -- the world's + leading ed-tech company. + + • Rejoined the project in 2023 to work on a cross-platform analytics library implementation + for launching v2. + """.trimIndent(), category = Category.FREELANCE, skills = setOf( Skill.ANDROID, @@ -200,8 +262,12 @@ val TROVE = Experience( name = "Trove", title = "Android Engineering Intern", route = "/trove", - summary = "Developed a native Android application in Kotlin using RxJava with a Redux architecture. " + - "Focused on UI/UX implementation of features including reusable components, animations, and development of a platform style guide.", + summary = """ + • Developed a native Android application in Kotlin using RxJava with a Redux architecture. + + • Focused on UI/UX implementation of features including reusable components, animations, and + development of a platform style guide. + """.trimIndent(), category = Category.JOB, skills = setOf( Skill.ANDROID, @@ -237,8 +303,13 @@ val HANDOTATE = Experience( name = "handotate", title = "Native Android & Design", route = "/handotate", - summary = "Used the mvRx library from AirBnb to facilitate rapid iteration of a prototype used for annotating ML training datasets. " + - "Included the ability to keyframe multiple bounding boxes on videos, and then interpolate for labelling a moving object.", + summary = """ + • Used the mvRx library from AirBnb to facilitate rapid iteration of a prototype used for + annotating ML training datasets. + + • Included the ability to keyframe multiple bounding boxes on videos, and then interpolate + for labelling a moving object. + """.trimIndent(), category = Category.PROJECT, skills = setOf( Skill.ANDROID, @@ -267,7 +338,10 @@ val MEET_UP = Experience( name = "Meet^", title = "React Native & Design", route = "/meet_up", - summary = "Used React Native and Firebase to build a realtime chat app for making spontaneous plans with friends.", + summary = """ + • Used React Native and Firebase to build a realtime chat app for making spontaneous plans + with friends. + """.trimIndent(), category = Category.PROJECT, skills = setOf( Skill.REACT_NATIVE, @@ -294,7 +368,10 @@ val LINELEAP = Experience( name = "LineLeap", title = "React Web & Design", route = "/lineleap", - summary = "Integrated with Squarespace orders API to allow event staff to validate a user's digital ticket purchase.", + summary = """ + • Integrated with Squarespace orders API to allow event staff to validate a user's digital + ticket purchase. + """.trimIndent(), category = Category.FREELANCE, skills = setOf( Skill.REACT, @@ -319,8 +396,11 @@ val TASKTRACKER = Experience( name = "TaskTracker", title = "React Web & Design", route = "/task_tracker", - summary = "Focused on the product design of a Kanban style homework tracking web app. " + - "Built out the proper infrastructure to allow for user testing to validate MVP.", + summary = """ + • Focused on the product design of a Kanban style homework tracking web app. + + • Built out the proper infrastructure to allow for user testing to validate MVP. + """.trimIndent(), category = Category.PROJECT, skills = setOf( Skill.REACT, @@ -345,8 +425,12 @@ val TOTEM = Experience( name = "Totem", title = "Native Android & Design", route = "/totem", - summary = "Developed a native Android application in Kotlin using RxJava and Dagger. " + - "Also responsible for the UI design, API design, and overall technology management of the company.", + summary = """ + • Developed a native Android application in Kotlin using RxJava and Dagger. + + • Also responsible for the UI design, API design, and overall technology management of + the company. + """.trimIndent(), category = Category.PROJECT, skills = setOf( Skill.ANDROID, @@ -372,7 +456,10 @@ val PRESENT = Experience( name = "Present VR", title = "Unity & node.js", route = "/present", - summary = "Created an SDK for Unity to allow for dynamic insertion of gamified ad experiences within any given VR experience.", + summary = """ + • Created an SDK for Unity to allow for dynamic insertion of gamified ad experiences within + any given VR experience. + """.trimIndent(), category = Category.PROJECT, skills = setOf( Skill.UNITY, @@ -396,7 +483,10 @@ val ARTABLETOP = Experience( name = "ARTabletop", title = "Unity & Android ARCore", route = "/artabletop", - summary = "Built the backend to allow for realtime remote D&D gameplay through a shared interactive AR game board.", + summary = """ + • Built the backend to allow for realtime remote D&D gameplay through a shared interactive + AR game board. + """.trimIndent(), category = Category.PROJECT, skills = setOf( Skill.UNITY, @@ -422,7 +512,10 @@ val MFRAME = Experience( name = "mFrame", title = "A-Frame WebVR", route = "/mframe", - summary = "Built the backend for a web-based VR social network, where users could customize a virtual space to express themselves.", + summary = """ + • Built the backend for a web-based VR social network, where users could customize a virtual + space to express themselves. + """.trimIndent(), category = Category.PROJECT, skills = setOf( Skill.HTML, @@ -447,7 +540,10 @@ val GBHS = Experience( name = "GBHS", title = "Native Android & iOS", route = "/gbhs", - summary = "Commissioned by the school to build a self-serve school resource app that scraped data from the official website.", + summary = """ + • Commissioned by the school to build a self-serve school resource app that scraped data from + the official website. + """.trimIndent(), category = Category.PROJECT, skills = setOf( Skill.ANDROID, @@ -468,6 +564,7 @@ val GBHS = Experience( ) val ALL_EXPERIENCES = listOf( + ADYEN, GRINDR, WALMART, SOCKET, @@ -477,16 +574,16 @@ val ALL_EXPERIENCES = listOf( HANDOTATE, MEET_UP, LINELEAP, - TASKTRACKER, +// TASKTRACKER, TOTEM, - PRESENT, +// PRESENT, ARTABLETOP, - MFRAME, +// MFRAME, GBHS, ) val FEATURED_EXPERIENCES = listOf( + ADYEN, SOCKET, - WALMART, - SHARPEN, + STOCK_X, ) diff --git a/src/commonMain/kotlin/codes/miley/model/Skill.kt b/src/commonMain/kotlin/codes/miley/model/Skill.kt index 95a10d1..9581187 100644 --- a/src/commonMain/kotlin/codes/miley/model/Skill.kt +++ b/src/commonMain/kotlin/codes/miley/model/Skill.kt @@ -11,12 +11,15 @@ enum class Skill(val displayName: String) { IOS("iOS"), JAVA("Java"), JAVASCRIPT("Javascript"), + JNI("JNI"), KOTLIN("Kotlin"), KOTLIN_MULTIPLATFORM("Kotlin Multiplatform"), NODEJS("node.js"), OBJECTIVE_C("Objective-C"), + PROTOBUF("Protobuf"), REACT("React"), REACT_NATIVE("React Native"), + SDK("SDK"), SQL("SQL"), SWIFT_UI("SwiftUI"), UNITY("Unity"), diff --git a/src/commonMain/kotlin/codes/miley/model/Topic.kt b/src/commonMain/kotlin/codes/miley/model/Topic.kt index f16c52c..2f97ce9 100644 --- a/src/commonMain/kotlin/codes/miley/model/Topic.kt +++ b/src/commonMain/kotlin/codes/miley/model/Topic.kt @@ -5,6 +5,7 @@ enum class Topic { DATING, E_COMMERCE, ED_TECH, + PAYMENTS, UI, UX, } diff --git a/src/commonMain/resources/experiences/job/adyen/icon.png b/src/commonMain/resources/experiences/job/adyen/icon.png new file mode 100644 index 0000000..a5998d0 Binary files /dev/null and b/src/commonMain/resources/experiences/job/adyen/icon.png differ diff --git a/src/commonMain/resources/experiences/project/socket/icon.png b/src/commonMain/resources/experiences/project/socket/icon.png index 7151827..4317e7b 100644 Binary files a/src/commonMain/resources/experiences/project/socket/icon.png and b/src/commonMain/resources/experiences/project/socket/icon.png differ diff --git a/src/commonMain/resources/gallery.css b/src/commonMain/resources/gallery.css index 9fb3d8e..d2e6f8a 100644 --- a/src/commonMain/resources/gallery.css +++ b/src/commonMain/resources/gallery.css @@ -15,6 +15,7 @@ justify-content: center; gap: 16px; padding-bottom: 8px; + box-sizing: border-box; } .thumbnail { @@ -24,6 +25,7 @@ border-radius: 12px; box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.1), 0 5px 5px 0 rgba(0, 0, 0, 0.05); + box-sizing: border-box; } .thumbnail .tag { diff --git a/src/commonMain/resources/index.css b/src/commonMain/resources/index.css index 35fa2f2..589bad3 100644 --- a/src/commonMain/resources/index.css +++ b/src/commonMain/resources/index.css @@ -4,7 +4,7 @@ html, body { margin: 0; - padding: 0; + padding: 0 20px; font: 16px 'Karla', 'Helvetica Neue', Helvetica, Arial, sans-serif; background: #f5f5f5; color: #575757; @@ -88,7 +88,7 @@ button { } .categories { - background: rgba(255, 255, 255, 0.85); + background: rgba(247, 250, 253, 0.5); display: block; position: fixed; left: 0; @@ -96,8 +96,12 @@ button { padding-bottom: 24px; width: 100%; z-index: 100; - box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), - 0 25px 50px 0 rgba(0, 0, 0, 0.1); + box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), + 0 20px 40px 0 rgba(0, 0, 0, 0.05); + backdrop-filter: blur(6px); + -webkit-backdrop-filter: blur(6px); + background-image: linear-gradient(135deg, rgba(236, 244, 253, 0.2) 15%, rgba(247, 250, 253, 0.6) 50%, rgba(236, 244, 253, 0.2) 85%); + border-bottom: 1px solid rgba(210, 230, 250, 0.3); } .categories ul { @@ -143,7 +147,7 @@ button { } .experience-section { - padding-top: 132px; + padding-top: 40px; display: inline-flex; flex-direction: column; } @@ -152,12 +156,16 @@ button { padding-top: 24px; display: inline-flex; flex-wrap: wrap; + width: 100%; } .experience-cell { display: inline-flex; flex-direction: column; flex-wrap: wrap; + padding: 0.75em 0 0 0.5em; + width: 100%; + box-sizing: border-box; } .experience-content { @@ -165,12 +173,16 @@ button { flex-direction: column; flex-wrap: wrap; flex: 1; - padding: 24px 0 32px 0; + padding: 8px 16px 32px 16px; margin: .5em 0 0 .5em; background: white; border-radius: 12px; box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.1), 0 25px 50px 0 rgba(0, 0, 0, 0.05); + height: 100%; + width: calc(100% - 1em); + box-sizing: border-box; + position: relative; } .experience-icon { @@ -178,6 +190,7 @@ button { height: 52px; border-radius: 64px; object-fit: contain; + margin-right: 16px; } .divider { @@ -191,43 +204,60 @@ button { line-height: 1.1; } +.experience-header { + margin-bottom: 0; +} + .experience-header.row { display: inline-flex; flex-direction: row; - padding-left: 16px; - padding-right: 16px; - padding-bottom: 8px; - justify-content: space-around; + padding: 16px 16px 16px 16px; + justify-content: flex-start; + align-items: flex-start; + position: relative; } .experience-header.col { display: inline-flex; flex: 1; flex-direction: column; - margin-left: 12px; + align-self: flex-start; + position: relative; } .experience-header.title-row { display: inline-flex; flex-direction: row; - justify-content: flex-end; - align-items: center; - margin-top: 2px; + justify-content: space-between; + align-items: flex-start; + margin: 0; + padding: 0; } .experience-header.title { - margin: 0 auto 0 0; + margin: 0; + padding: 0; align-self: flex-start; + font-size: 20px; + line-height: 1; +} + +.experience-header h5 { + margin-top: 0px; + padding: 0; + line-height: 1; } .experience-gallery { display: inline-flex; flex-wrap: wrap; - padding-top: 24px; - max-width: 90%; + padding: 24px 16px; + max-width: 100%; overflow-y: clip; overflow-x: auto; - margin: auto; + margin: 0 0 0 0; + gap: 16px; + z-index: 1; } .experience-gallery * { @@ -237,9 +267,10 @@ button { } .experience-info { - padding-left: 20px; - padding-top: 24px; - padding-right: 20px; + padding: 0 16px; + margin-top: auto; + white-space: pre-line; + line-height: 1.6; } .date-range { @@ -256,41 +287,178 @@ button { } .tags { - display: inline-flex; + position: relative; + display: flex; flex-direction: row; flex-wrap: wrap; - gap: 4px; - margin-top: 12px; + gap: 6px; + z-index: 10; + margin-top: 0; + padding-left: 16px; + margin-bottom: 6px; + width: 100%; + box-sizing: border-box; + align-items: flex-start; + justify-content: flex-start; } .tags h6 { - padding: 2px 12px; + padding: 6px 12px; border-radius: 8px; + font-size: 13px; + line-height: 1.2; + display: flex; + align-items: center; + cursor: pointer; + transition: all 0.2s ease; + position: relative; + overflow: hidden; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); + font-weight: 600; + border: 1px solid rgba(0, 0, 0, 0.1); +} + +.tags h6:hover { + backdrop-filter: blur(4px); + -webkit-backdrop-filter: blur(4px); + box-shadow: 0 4px 10px rgba(0, 0, 0, 0.08); + transform: scale(1.05); +} + +.tags h6:active { + transform: scale(1.05); +} + +.tags h6::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: radial-gradient(circle at center, rgba(0, 0, 0, 0.22) 0%, rgba(0, 0, 0, 0.08) 100%); + opacity: 0; + transition: opacity 0.05s ease-in; + z-index: 0; +} + +.tags h6:active::before { + opacity: 1; + transition: none; +} + +.tags h6::after { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: radial-gradient(ellipse at center, rgba(0, 0, 0, 0.17) 0%, rgba(0, 0, 0, 0.05) 40%, rgba(0, 0, 0, 0.03) 70%, rgba(0, 0, 0, 0.02) 100%); + opacity: 0; + transform: scale(0); + z-index: 1; +} + +.tags h6:active::after { + animation: ripple-effect 0.7s cubic-bezier(0.2, 0.6, 0.3, 1) forwards; +} + +@keyframes ripple-effect { + 0% { + transform: scale(1.2); + opacity: 0; + } + 20% { + opacity: 0.6; + } + 60% { + opacity: 0.4; + } + 80% { + opacity: 0.25; + } + 100% { + transform: scale(3); + opacity: 0; + } +} + +.tags h6 span { + position: relative; + z-index: 2; +} + +.tags.job h6::before { + background: radial-gradient(circle at center, rgba(233, 79, 55, 0.15) 0%, rgba(242, 220, 217, 0.9) 100%); } .tags.job h6 { background: #F2DCD9; color: #E94F37; + border: 1px solid rgba(233, 79, 55, 0.3); + box-shadow: 0 2px 4px rgba(233, 79, 55, 0.08); +} + +.tags.job h6:hover, .tags.job h6:active { + background-color: rgba(242, 220, 217, 0.8); + border: 1px solid rgba(233, 79, 55, 0.4); + box-shadow: 0 3px 6px rgba(233, 79, 55, 0.12); +} + +.tags.freelance h6::before { + background: radial-gradient(circle at center, rgba(84, 56, 220, 0.15) 0%, rgba(218, 212, 247, 0.9) 100%); } .tags.freelance h6 { background: #DAD4F7; color: #5438DC; + border: 1px solid rgba(84, 56, 220, 0.3); + box-shadow: 0 2px 4px rgba(84, 56, 220, 0.08); +} + +.tags.freelance h6:hover, .tags.freelance h6:active { + background-color: rgba(218, 212, 247, 0.8); + border: 1px solid rgba(84, 56, 220, 0.4); + box-shadow: 0 3px 6px rgba(84, 56, 220, 0.12); +} + +.tags.project h6::before { + background: radial-gradient(circle at center, rgba(32, 166, 223, 0.15) 0%, rgba(221, 244, 253, 0.9) 100%); } .tags.project h6 { background: #DDF4FD; color: #20A6DF; + border: 1px solid rgba(32, 166, 223, 0.3); + box-shadow: 0 2px 4px rgba(32, 166, 223, 0.08); +} + +.tags.project h6:hover, .tags.project h6:active { + background-color: rgba(221, 244, 253, 0.8); + border: 1px solid rgba(32, 166, 223, 0.4); + box-shadow: 0 3px 6px rgba(32, 166, 223, 0.12); +} + +.tags .skill h6::before { + background: radial-gradient(circle at center, rgba(153, 153, 153, 0.15) 0%, rgba(255, 255, 255, 0.9) 100%); } .tags .skill h6 { background: #FFF; color: #999999; - border: 1px solid rgba(0, 0, 0, 0.15); + border: 1px solid rgba(0, 0, 0, 0.2); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08); +} + +.tags .skill h6:hover, .tags .skill h6:active { + background-color: rgba(255, 255, 255, 0.8); + border: 1px solid rgba(153, 153, 153, 0.4); + box-shadow: 0 3px 6px rgba(0, 0, 0, 0.12); } .content { - padding: 0 2vw; + padding: 0 20px; display: flex; flex-direction: column; } @@ -343,7 +511,9 @@ button { /* With gutters */ .Grid--gutters { - margin: -.5em 0 0 -.5em; + margin: -1em 0 0 -1em; + width: calc(100% + 1em); + box-sizing: border-box; } /*.Grid--gutters > .Grid-cell {*/ /* padding: .5em 0 0 .5em;*/ @@ -469,3 +639,10 @@ button { bottom: 10px; } } + +.title-description { + display: flex; + flex-direction: column; + width: 100%; + margin-top: 4px; +} diff --git a/src/jsMain/kotlin/codes/miley/frontend/widget/ExperienceCell.kt b/src/jsMain/kotlin/codes/miley/frontend/widget/ExperienceCell.kt index 00f184b..1eecd4f 100644 --- a/src/jsMain/kotlin/codes/miley/frontend/widget/ExperienceCell.kt +++ b/src/jsMain/kotlin/codes/miley/frontend/widget/ExperienceCell.kt @@ -31,21 +31,29 @@ fun RenderContext.experienceCell( } div("experience-header col") { - div("experience-header title-row") { - h1("experience-header title") { +name } - dateRanges.forEach { range -> - h6("date-range $isActive") { +range } + div("title-description") { + div("experience-header title-row") { + h1("experience-header title") { +name } + dateRanges.forEach { range -> + h6("date-range $isActive") { +range } + } } + h5 { +title } } + } + } - h5 { +title } - - div("tags ${category.name.lowercase()}") { - h6 { +category.displayName } - - skills.forEach { skill -> - div("skill") { - h6 { +skill.displayName } + div("tags ${category.name.lowercase()}") { + h6 { + span { + +category.displayName + } + } + skills.forEach { skill -> + div("skill") { + h6 { + span { + +skill.displayName } } }