Redesign portfolio as a personal studio with product spotlights

This commit is contained in:
2026-09-22 12:26:40 -04:00
parent 52a2177f43
commit b72108e103
27 changed files with 1716 additions and 979 deletions
+4
View File
@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="120" height="120" viewBox="0 0 120 120" fill="#20252f">
<path d="M35.296 81.846Q33.416 81.846 31.113 81.47Q28.810000000000002 81.094 26.789 80.53Q24.768 79.966 23.64 79.308Q22.982 78.932 22.653 78.509Q22.323999999999998 78.086 22.323999999999998 77.052L21.854 62.294Q21.854 60.414 23.076 60.414Q24.11 60.414 24.486 62.106L25.332 65.678Q28.528 79.12 37.269999999999996 79.12Q41.97 79.12 44.743 75.971Q47.516000000000005 72.822 47.516000000000005 67.088Q47.516000000000005 61.542 44.649 56.748000000000005Q41.782 51.954 35.484 47.254Q28.528 42.272 25.332 37.619Q22.136 32.966 22.136 27.701999999999998Q22.136 20.746000000000002 26.976999999999997 16.563Q31.817999999999998 12.379999999999995 39.338 12.379999999999995Q42.158 12.379999999999995 44.79 13.037999999999997Q47.422 13.695999999999998 49.114000000000004 14.918000000000006Q49.678 15.293999999999997 50.007 15.763999999999996Q50.336 16.233999999999995 50.336 16.986000000000004L50.618 31.18Q50.618 32.778 49.396 32.778Q48.55 32.778 48.174 31.462000000000003L47.516000000000005 29.112000000000002Q45.448 21.874000000000002 43.239000000000004 18.537000000000003Q41.03 15.200000000000003 36.894 15.200000000000003Q32.852000000000004 15.200000000000003 30.455000000000002 17.832Q28.058 20.464 28.058 25.258000000000003Q28.058 29.299999999999997 30.69 32.965999999999994Q33.322 36.632 39.902 41.896Q46.952 47.63 50.195 52.941Q53.438 58.251999999999995 53.438 64.268Q53.438 69.532 51.088 73.47999999999999Q48.738 77.428 44.649 79.637Q40.56 81.846 35.296 81.846Z"/>
<path d="M72.38 99.846Q67.868 99.846 63.92 97.167Q59.972 94.488 57.010999999999996 89.788Q54.05 85.088 52.405 78.743Q50.76 72.398 50.76 65.066Q50.76 57.734 52.405 51.436Q54.05 45.138 57.010999999999996 40.391Q59.972 35.644 63.92 33.012Q67.868 30.379999999999995 72.38 30.379999999999995Q76.892 30.379999999999995 80.793 33.012Q84.694 35.644 87.702 40.391Q90.71000000000001 45.138 92.355 51.436Q94.0 57.734 94.0 65.066Q94.0 72.398 92.355 78.743Q90.71000000000001 85.088 87.702 89.788Q84.694 94.488 80.793 97.167Q76.892 99.846 72.38 99.846ZM72.38 97.308Q79.054 97.308 82.814 89.31800000000001Q86.574 81.328 86.574 65.066Q86.574 48.804 82.814 40.861000000000004Q79.054 32.918000000000006 72.38 32.918000000000006Q65.8 32.918000000000006 61.992999999999995 40.861000000000004Q58.186 48.804 58.186 65.066Q58.186 81.328 61.992999999999995 89.31800000000001Q65.8 97.308 72.38 97.308Z"/>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

+1
View File
@@ -0,0 +1 @@
Manyangles portfolio card uses the existing study-abroad group dinner photo at /trips/engr290/insta290.jpg, also shown on the travel page. This is illustrative portfolio imagery, not a claim that the trip used Manyangles.
Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 521 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

@@ -0,0 +1,93 @@
Copyright 2020 The Geologica Project Authors (https://github.com/googlefonts/geologica)
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
Binary file not shown.
+78
View File
@@ -0,0 +1,78 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 1487 343" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g transform="matrix(1,0,0,1,-120.524,-84.8906)">
<g id="slant-round" serif:id="slant/round" transform="matrix(0.661785,0,0,0.661785,24.3307,-229.931)">
<g transform="matrix(1.20551,0,0,1.20551,-840.208,509.804)">
<g transform="matrix(233.753,0,0,233.753,1308.09,362.817)">
<path d="M0.213,-0C0.171,-0 0.142,-0.007 0.125,-0.022C0.108,-0.036 0.099,-0.057 0.099,-0.085C0.099,-0.101 0.101,-0.119 0.105,-0.14C0.109,-0.161 0.113,-0.181 0.117,-0.201L0.215,-0.657L0.321,-0.657L0.222,-0.192C0.219,-0.178 0.216,-0.165 0.214,-0.153C0.212,-0.14 0.21,-0.129 0.21,-0.119C0.21,-0.108 0.214,-0.1 0.221,-0.095C0.228,-0.089 0.239,-0.087 0.253,-0.087L0.34,-0.087L0.322,-0L0.213,-0ZM0.089,-0.445L0.107,-0.532L0.435,-0.532L0.416,-0.445L0.089,-0.445Z" style="fill:white;fill-rule:nonzero;"/>
</g>
<g transform="matrix(233.753,0,0,233.753,1399.35,362.817)">
<path d="M0.232,0.012C0.169,0.012 0.12,-0.005 0.086,-0.04C0.052,-0.075 0.035,-0.123 0.035,-0.184C0.035,-0.234 0.042,-0.281 0.056,-0.325C0.07,-0.369 0.091,-0.407 0.117,-0.44C0.144,-0.473 0.177,-0.499 0.215,-0.517C0.254,-0.535 0.298,-0.544 0.346,-0.544C0.386,-0.544 0.42,-0.537 0.448,-0.523C0.476,-0.509 0.497,-0.487 0.512,-0.459C0.526,-0.431 0.533,-0.396 0.533,-0.354C0.533,-0.334 0.532,-0.313 0.529,-0.293C0.526,-0.272 0.522,-0.253 0.517,-0.235L0.129,-0.235L0.146,-0.315L0.422,-0.315C0.423,-0.321 0.424,-0.328 0.425,-0.335C0.425,-0.342 0.426,-0.351 0.426,-0.362C0.426,-0.392 0.417,-0.415 0.401,-0.431C0.384,-0.446 0.36,-0.454 0.33,-0.454C0.303,-0.454 0.277,-0.447 0.254,-0.434C0.231,-0.421 0.212,-0.403 0.196,-0.38C0.18,-0.356 0.167,-0.329 0.158,-0.297C0.149,-0.266 0.145,-0.231 0.145,-0.194C0.145,-0.154 0.154,-0.124 0.172,-0.106C0.19,-0.088 0.216,-0.079 0.251,-0.079C0.283,-0.079 0.31,-0.086 0.334,-0.1C0.357,-0.114 0.373,-0.134 0.383,-0.16L0.492,-0.153C0.47,-0.103 0.439,-0.063 0.397,-0.033C0.355,-0.003 0.3,0.012 0.232,0.012Z" style="fill:white;fill-rule:nonzero;"/>
</g>
<g transform="matrix(233.753,0,0,233.753,1529.27,362.817)">
<path d="M0.222,0.012C0.159,0.012 0.112,-0.005 0.081,-0.038C0.05,-0.071 0.035,-0.117 0.035,-0.177C0.035,-0.252 0.047,-0.316 0.073,-0.371C0.098,-0.426 0.134,-0.468 0.18,-0.499C0.225,-0.529 0.279,-0.544 0.34,-0.544C0.381,-0.544 0.416,-0.536 0.443,-0.521C0.471,-0.505 0.492,-0.483 0.507,-0.454C0.521,-0.425 0.528,-0.391 0.528,-0.351L0.418,-0.345C0.418,-0.383 0.41,-0.411 0.394,-0.427C0.379,-0.444 0.354,-0.452 0.321,-0.452C0.287,-0.452 0.256,-0.441 0.23,-0.418C0.203,-0.395 0.183,-0.364 0.168,-0.326C0.153,-0.288 0.145,-0.247 0.145,-0.201C0.145,-0.156 0.153,-0.125 0.168,-0.107C0.183,-0.089 0.207,-0.08 0.242,-0.08C0.278,-0.08 0.309,-0.089 0.334,-0.109C0.359,-0.128 0.379,-0.157 0.393,-0.195L0.501,-0.189C0.487,-0.148 0.467,-0.113 0.441,-0.083C0.415,-0.053 0.383,-0.029 0.347,-0.013C0.31,0.004 0.269,0.012 0.222,0.012Z" style="fill:white;fill-rule:nonzero;"/>
</g>
<g transform="matrix(233.753,0,0,233.753,1656.24,362.817)">
<path d="M0.02,-0L0.17,-0.71L0.276,-0.71L0.213,-0.412L0.198,-0.415C0.218,-0.458 0.245,-0.491 0.278,-0.512C0.311,-0.533 0.347,-0.544 0.386,-0.544C0.434,-0.544 0.469,-0.531 0.493,-0.506C0.517,-0.481 0.529,-0.448 0.529,-0.407C0.529,-0.39 0.527,-0.371 0.524,-0.351C0.52,-0.331 0.517,-0.313 0.513,-0.299L0.45,-0L0.344,-0L0.41,-0.309C0.413,-0.321 0.415,-0.333 0.417,-0.345C0.419,-0.358 0.42,-0.371 0.42,-0.384C0.42,-0.406 0.413,-0.424 0.4,-0.436C0.387,-0.449 0.368,-0.455 0.341,-0.455C0.318,-0.455 0.296,-0.45 0.276,-0.439C0.256,-0.428 0.239,-0.412 0.224,-0.391C0.21,-0.37 0.199,-0.344 0.192,-0.312L0.126,-0L0.02,-0Z" style="fill:white;fill-rule:nonzero;"/>
</g>
<g transform="matrix(233.753,0,0,233.753,1787.71,362.817)">
<path d="M0.019,-0L0.132,-0.532L0.229,-0.532L0.202,-0.39L0.191,-0.397C0.205,-0.431 0.222,-0.459 0.242,-0.481C0.262,-0.502 0.285,-0.518 0.309,-0.529C0.334,-0.539 0.359,-0.544 0.386,-0.544C0.434,-0.544 0.469,-0.531 0.493,-0.506C0.517,-0.481 0.528,-0.449 0.528,-0.409C0.528,-0.391 0.527,-0.372 0.523,-0.351C0.52,-0.331 0.517,-0.313 0.513,-0.299L0.45,-0L0.344,-0L0.41,-0.309C0.413,-0.321 0.415,-0.333 0.417,-0.346C0.419,-0.359 0.42,-0.373 0.42,-0.387C0.42,-0.409 0.413,-0.425 0.4,-0.437C0.387,-0.449 0.367,-0.455 0.341,-0.455C0.318,-0.455 0.296,-0.449 0.276,-0.438C0.256,-0.427 0.238,-0.41 0.224,-0.389C0.209,-0.367 0.198,-0.341 0.191,-0.31L0.125,-0L0.019,-0Z" style="fill:white;fill-rule:nonzero;"/>
</g>
<g transform="matrix(233.753,0,0,233.753,1920.76,362.817)">
<path d="M0.23,0.012C0.166,0.012 0.118,-0.005 0.084,-0.04C0.051,-0.075 0.034,-0.121 0.034,-0.177C0.034,-0.225 0.04,-0.271 0.053,-0.315C0.065,-0.359 0.084,-0.398 0.109,-0.432C0.134,-0.467 0.165,-0.494 0.203,-0.514C0.24,-0.534 0.283,-0.544 0.333,-0.544C0.396,-0.544 0.445,-0.527 0.478,-0.492C0.511,-0.458 0.527,-0.412 0.527,-0.355C0.527,-0.307 0.521,-0.261 0.509,-0.217C0.496,-0.173 0.478,-0.134 0.453,-0.1C0.428,-0.065 0.397,-0.038 0.36,-0.018C0.323,0.002 0.279,0.012 0.23,0.012ZM0.244,-0.08C0.279,-0.08 0.309,-0.091 0.335,-0.114C0.361,-0.137 0.381,-0.169 0.395,-0.208C0.41,-0.247 0.417,-0.291 0.417,-0.339C0.417,-0.378 0.409,-0.407 0.393,-0.425C0.377,-0.443 0.352,-0.452 0.318,-0.452C0.284,-0.452 0.253,-0.441 0.227,-0.418C0.201,-0.394 0.181,-0.363 0.166,-0.324C0.152,-0.285 0.144,-0.241 0.144,-0.193C0.144,-0.153 0.153,-0.124 0.17,-0.107C0.186,-0.089 0.211,-0.08 0.244,-0.08Z" style="fill:white;fill-rule:nonzero;"/>
</g>
<g transform="matrix(233.753,0,0,233.753,2051.05,362.817)">
<path d="M0.132,-0C0.102,-0 0.079,-0.006 0.063,-0.017C0.047,-0.029 0.039,-0.047 0.039,-0.072C0.039,-0.088 0.041,-0.105 0.046,-0.123C0.05,-0.142 0.053,-0.159 0.057,-0.175L0.17,-0.71L0.276,-0.71L0.161,-0.167C0.158,-0.154 0.156,-0.143 0.154,-0.132C0.151,-0.121 0.15,-0.113 0.15,-0.106C0.15,-0.1 0.153,-0.095 0.157,-0.092C0.162,-0.088 0.169,-0.087 0.178,-0.087L0.223,-0.087L0.205,-0L0.132,-0Z" style="fill:white;fill-rule:nonzero;"/>
</g>
<g transform="matrix(233.753,0,0,233.753,2113.05,362.817)">
<path d="M0.23,0.012C0.166,0.012 0.118,-0.005 0.084,-0.04C0.051,-0.075 0.034,-0.121 0.034,-0.177C0.034,-0.225 0.04,-0.271 0.053,-0.315C0.065,-0.359 0.084,-0.398 0.109,-0.432C0.134,-0.467 0.165,-0.494 0.203,-0.514C0.24,-0.534 0.283,-0.544 0.333,-0.544C0.396,-0.544 0.445,-0.527 0.478,-0.492C0.511,-0.458 0.527,-0.412 0.527,-0.355C0.527,-0.307 0.521,-0.261 0.509,-0.217C0.496,-0.173 0.478,-0.134 0.453,-0.1C0.428,-0.065 0.397,-0.038 0.36,-0.018C0.323,0.002 0.279,0.012 0.23,0.012ZM0.244,-0.08C0.279,-0.08 0.309,-0.091 0.335,-0.114C0.361,-0.137 0.381,-0.169 0.395,-0.208C0.41,-0.247 0.417,-0.291 0.417,-0.339C0.417,-0.378 0.409,-0.407 0.393,-0.425C0.377,-0.443 0.352,-0.452 0.318,-0.452C0.284,-0.452 0.253,-0.441 0.227,-0.418C0.201,-0.394 0.181,-0.363 0.166,-0.324C0.152,-0.285 0.144,-0.241 0.144,-0.193C0.144,-0.153 0.153,-0.124 0.17,-0.107C0.186,-0.089 0.211,-0.08 0.244,-0.08Z" style="fill:white;fill-rule:nonzero;"/>
</g>
<g transform="matrix(233.753,0,0,233.753,2242.17,362.817)">
<path d="M0.192,0.162C0.158,0.162 0.128,0.159 0.104,0.152C0.08,0.145 0.06,0.134 0.045,0.121C0.029,0.108 0.018,0.092 0.011,0.073C0.004,0.054 0.001,0.033 0.001,0.009L0.111,0.002C0.111,0.025 0.118,0.043 0.132,0.056C0.145,0.068 0.171,0.074 0.209,0.074C0.255,0.074 0.289,0.065 0.313,0.048C0.337,0.032 0.353,0.003 0.361,-0.038L0.378,-0.116C0.359,-0.089 0.336,-0.068 0.308,-0.052C0.28,-0.035 0.247,-0.027 0.21,-0.027C0.156,-0.027 0.114,-0.044 0.085,-0.077C0.055,-0.11 0.041,-0.156 0.041,-0.216C0.041,-0.26 0.047,-0.302 0.06,-0.341C0.073,-0.381 0.091,-0.415 0.115,-0.446C0.139,-0.476 0.167,-0.5 0.201,-0.518C0.234,-0.535 0.272,-0.544 0.314,-0.544C0.355,-0.544 0.386,-0.535 0.408,-0.518C0.431,-0.5 0.445,-0.477 0.451,-0.448L0.469,-0.532L0.571,-0.532L0.467,-0.04C0.457,0.011 0.44,0.051 0.416,0.08C0.393,0.11 0.363,0.131 0.326,0.143C0.289,0.156 0.244,0.162 0.192,0.162ZM0.243,-0.117C0.272,-0.117 0.297,-0.124 0.319,-0.138C0.342,-0.151 0.36,-0.169 0.375,-0.192C0.39,-0.214 0.402,-0.238 0.409,-0.265C0.417,-0.291 0.42,-0.318 0.42,-0.344C0.42,-0.378 0.411,-0.404 0.393,-0.424C0.375,-0.444 0.35,-0.454 0.317,-0.454C0.292,-0.454 0.27,-0.448 0.25,-0.437C0.23,-0.425 0.212,-0.409 0.198,-0.389C0.183,-0.368 0.171,-0.345 0.164,-0.317C0.156,-0.29 0.152,-0.261 0.152,-0.229C0.152,-0.191 0.158,-0.164 0.172,-0.145C0.185,-0.126 0.209,-0.117 0.243,-0.117Z" style="fill:white;fill-rule:nonzero;"/>
</g>
<g transform="matrix(233.753,0,0,233.753,2377.23,362.817)">
<path d="M0.019,-0L0.131,-0.532L0.237,-0.532L0.125,-0L0.019,-0ZM0.146,-0.609L0.167,-0.715L0.278,-0.715L0.256,-0.609L0.146,-0.609Z" style="fill:white;fill-rule:nonzero;"/>
</g>
<g transform="matrix(233.753,0,0,233.753,2435.77,362.817)">
<path d="M0.232,0.012C0.169,0.012 0.12,-0.005 0.086,-0.04C0.052,-0.075 0.035,-0.123 0.035,-0.184C0.035,-0.234 0.042,-0.281 0.056,-0.325C0.07,-0.369 0.091,-0.407 0.117,-0.44C0.144,-0.473 0.177,-0.499 0.215,-0.517C0.254,-0.535 0.298,-0.544 0.346,-0.544C0.386,-0.544 0.42,-0.537 0.448,-0.523C0.476,-0.509 0.497,-0.487 0.512,-0.459C0.526,-0.431 0.533,-0.396 0.533,-0.354C0.533,-0.334 0.532,-0.313 0.529,-0.293C0.526,-0.272 0.522,-0.253 0.517,-0.235L0.129,-0.235L0.146,-0.315L0.422,-0.315C0.423,-0.321 0.424,-0.328 0.425,-0.335C0.425,-0.342 0.426,-0.351 0.426,-0.362C0.426,-0.392 0.417,-0.415 0.401,-0.431C0.384,-0.446 0.36,-0.454 0.33,-0.454C0.303,-0.454 0.277,-0.447 0.254,-0.434C0.231,-0.421 0.212,-0.403 0.196,-0.38C0.18,-0.356 0.167,-0.329 0.158,-0.297C0.149,-0.266 0.145,-0.231 0.145,-0.194C0.145,-0.154 0.154,-0.124 0.172,-0.106C0.19,-0.088 0.216,-0.079 0.251,-0.079C0.283,-0.079 0.31,-0.086 0.334,-0.1C0.357,-0.114 0.373,-0.134 0.383,-0.16L0.492,-0.153C0.47,-0.103 0.439,-0.063 0.397,-0.033C0.355,-0.003 0.3,0.012 0.232,0.012Z" style="fill:white;fill-rule:nonzero;"/>
</g>
<g transform="matrix(233.753,0,0,233.753,2565.69,362.817)">
<path d="M0.236,0.012C0.172,0.012 0.122,-0.004 0.085,-0.036C0.048,-0.068 0.027,-0.112 0.022,-0.168L0.131,-0.173C0.135,-0.14 0.147,-0.115 0.166,-0.1C0.185,-0.084 0.212,-0.077 0.247,-0.077C0.277,-0.077 0.302,-0.082 0.324,-0.094C0.345,-0.105 0.356,-0.123 0.356,-0.148C0.356,-0.161 0.353,-0.171 0.348,-0.18C0.342,-0.188 0.332,-0.196 0.316,-0.202C0.3,-0.207 0.277,-0.213 0.245,-0.218C0.188,-0.228 0.144,-0.245 0.115,-0.268C0.085,-0.292 0.07,-0.326 0.07,-0.371C0.07,-0.4 0.078,-0.427 0.092,-0.454C0.106,-0.48 0.129,-0.501 0.16,-0.519C0.192,-0.536 0.234,-0.544 0.286,-0.544C0.349,-0.544 0.398,-0.529 0.433,-0.498C0.468,-0.468 0.488,-0.425 0.493,-0.369L0.383,-0.363C0.381,-0.395 0.371,-0.419 0.354,-0.433C0.337,-0.448 0.312,-0.455 0.281,-0.455C0.246,-0.455 0.22,-0.447 0.204,-0.432C0.188,-0.416 0.181,-0.397 0.181,-0.375C0.181,-0.361 0.184,-0.35 0.191,-0.341C0.198,-0.331 0.209,-0.324 0.224,-0.318C0.24,-0.312 0.26,-0.307 0.286,-0.302C0.332,-0.294 0.369,-0.283 0.395,-0.271C0.421,-0.258 0.44,-0.242 0.45,-0.224C0.461,-0.205 0.466,-0.182 0.466,-0.156C0.466,-0.12 0.456,-0.09 0.436,-0.065C0.416,-0.039 0.388,-0.02 0.354,-0.007C0.319,0.006 0.28,0.012 0.236,0.012Z" style="fill:white;fill-rule:nonzero;"/>
</g>
<g transform="matrix(233.753,0,0,233.753,2690.79,362.817)">
</g>
</g>
<g transform="matrix(1.20551,0,0,1.20551,-797.429,238.41)">
<g transform="matrix(233.753,0,0,233.753,1308.09,362.817)">
<path d="M0.01,-0L0.16,-0.71L0.31,-0.71L0.246,-0.411L0.227,-0.412C0.247,-0.459 0.274,-0.493 0.307,-0.515C0.34,-0.537 0.377,-0.548 0.418,-0.548C0.465,-0.548 0.501,-0.536 0.525,-0.511C0.55,-0.486 0.562,-0.453 0.562,-0.41C0.562,-0.39 0.56,-0.368 0.556,-0.345C0.552,-0.322 0.548,-0.301 0.544,-0.284L0.483,-0L0.333,-0L0.397,-0.294C0.4,-0.306 0.402,-0.319 0.404,-0.332C0.406,-0.345 0.407,-0.358 0.407,-0.371C0.407,-0.39 0.402,-0.404 0.392,-0.414C0.382,-0.424 0.366,-0.429 0.344,-0.429C0.325,-0.429 0.307,-0.425 0.291,-0.416C0.275,-0.407 0.262,-0.394 0.251,-0.375C0.239,-0.357 0.231,-0.334 0.225,-0.305L0.16,-0L0.01,-0Z" style="fill:white;fill-rule:nonzero;"/>
</g>
<g transform="matrix(233.753,0,0,233.753,1446.75,362.817)">
<path d="M0.381,-0.244L0.242,-0.214C0.217,-0.209 0.198,-0.201 0.186,-0.189C0.173,-0.178 0.167,-0.161 0.167,-0.139C0.167,-0.123 0.172,-0.111 0.18,-0.103C0.189,-0.095 0.202,-0.091 0.22,-0.091C0.242,-0.091 0.261,-0.095 0.279,-0.102C0.297,-0.11 0.312,-0.121 0.325,-0.138C0.337,-0.154 0.346,-0.174 0.352,-0.199C0.354,-0.21 0.356,-0.224 0.36,-0.24C0.363,-0.257 0.367,-0.274 0.37,-0.292C0.374,-0.311 0.377,-0.327 0.379,-0.343C0.381,-0.358 0.382,-0.371 0.382,-0.379C0.382,-0.399 0.378,-0.414 0.369,-0.424C0.359,-0.434 0.343,-0.439 0.318,-0.439C0.291,-0.439 0.27,-0.433 0.254,-0.421C0.238,-0.408 0.225,-0.389 0.216,-0.362L0.067,-0.369C0.087,-0.43 0.118,-0.475 0.16,-0.504C0.203,-0.533 0.263,-0.548 0.341,-0.548C0.406,-0.548 0.454,-0.535 0.486,-0.508C0.517,-0.482 0.533,-0.442 0.533,-0.391C0.533,-0.377 0.531,-0.361 0.528,-0.341C0.525,-0.32 0.521,-0.299 0.516,-0.277C0.511,-0.254 0.506,-0.233 0.501,-0.212C0.496,-0.191 0.492,-0.172 0.489,-0.156C0.486,-0.141 0.484,-0.13 0.484,-0.124C0.484,-0.116 0.486,-0.11 0.49,-0.107C0.495,-0.104 0.501,-0.103 0.509,-0.103L0.524,-0.103L0.503,-0C0.497,0.001 0.489,0.003 0.479,0.004C0.468,0.005 0.457,0.006 0.447,0.006C0.414,0.006 0.389,-0.001 0.373,-0.014C0.356,-0.028 0.348,-0.046 0.348,-0.069C0.348,-0.077 0.349,-0.084 0.35,-0.092C0.351,-0.1 0.353,-0.108 0.356,-0.116L0.365,-0.108C0.354,-0.084 0.339,-0.063 0.32,-0.045C0.301,-0.027 0.279,-0.013 0.253,-0.003C0.227,0.007 0.197,0.012 0.164,0.012C0.116,0.012 0.079,0.002 0.052,-0.019C0.025,-0.04 0.012,-0.07 0.012,-0.11C0.012,-0.163 0.029,-0.204 0.063,-0.235C0.097,-0.265 0.146,-0.286 0.211,-0.299L0.401,-0.337L0.381,-0.244Z" style="fill:white;fill-rule:nonzero;"/>
</g>
<g transform="matrix(233.753,0,0,233.753,1577.98,362.817)">
<path d="M0.19,0.012C0.136,0.012 0.096,-0.005 0.07,-0.04C0.043,-0.075 0.03,-0.122 0.03,-0.182C0.03,-0.233 0.037,-0.281 0.051,-0.325C0.065,-0.369 0.084,-0.408 0.11,-0.442C0.135,-0.475 0.165,-0.501 0.199,-0.52C0.233,-0.539 0.27,-0.548 0.311,-0.548C0.345,-0.548 0.373,-0.54 0.396,-0.524C0.418,-0.508 0.434,-0.487 0.442,-0.46L0.495,-0.71L0.645,-0.71L0.494,-0L0.351,-0L0.373,-0.122L0.383,-0.11C0.362,-0.072 0.336,-0.043 0.304,-0.021C0.273,0.001 0.235,0.012 0.19,0.012ZM0.259,-0.103C0.28,-0.103 0.3,-0.109 0.318,-0.121C0.336,-0.133 0.352,-0.149 0.365,-0.17C0.378,-0.191 0.389,-0.216 0.397,-0.244C0.405,-0.272 0.408,-0.303 0.408,-0.336C0.408,-0.367 0.401,-0.391 0.388,-0.408C0.374,-0.424 0.355,-0.433 0.331,-0.433C0.31,-0.433 0.291,-0.427 0.274,-0.415C0.256,-0.403 0.241,-0.386 0.227,-0.366C0.214,-0.345 0.203,-0.321 0.196,-0.294C0.188,-0.267 0.185,-0.238 0.185,-0.207C0.185,-0.173 0.191,-0.148 0.203,-0.13C0.214,-0.112 0.233,-0.103 0.259,-0.103Z" style="fill:white;fill-rule:nonzero;"/>
</g>
<g transform="matrix(233.753,0,0,233.753,1720.76,362.817)">
<path d="M0.143,-0C0.107,-0 0.079,-0.007 0.061,-0.021C0.043,-0.035 0.034,-0.057 0.034,-0.088C0.034,-0.105 0.036,-0.124 0.041,-0.146C0.045,-0.168 0.16,-0.71 0.16,-0.71L0.31,-0.71L0.202,-0.202C0.2,-0.189 0.197,-0.176 0.195,-0.164C0.192,-0.152 0.191,-0.142 0.191,-0.135C0.191,-0.127 0.194,-0.121 0.198,-0.118C0.203,-0.114 0.211,-0.112 0.222,-0.112L0.262,-0.112L0.239,-0L0.143,-0Z" style="fill:white;fill-rule:nonzero;"/>
</g>
<g transform="matrix(233.753,0,0,233.753,1789.86,362.817)">
<path d="M0.245,0.012C0.175,0.012 0.122,-0.005 0.085,-0.04C0.049,-0.075 0.031,-0.123 0.031,-0.184C0.031,-0.233 0.037,-0.279 0.05,-0.323C0.062,-0.367 0.082,-0.405 0.108,-0.439C0.135,-0.473 0.168,-0.5 0.208,-0.519C0.248,-0.538 0.296,-0.548 0.35,-0.548C0.421,-0.548 0.474,-0.531 0.51,-0.496C0.546,-0.461 0.564,-0.413 0.564,-0.352C0.564,-0.303 0.558,-0.257 0.545,-0.213C0.532,-0.169 0.513,-0.13 0.487,-0.097C0.46,-0.063 0.427,-0.036 0.387,-0.017C0.347,0.002 0.3,0.012 0.245,0.012ZM0.265,-0.103C0.294,-0.103 0.319,-0.114 0.34,-0.134C0.362,-0.155 0.379,-0.182 0.391,-0.217C0.403,-0.252 0.41,-0.29 0.41,-0.333C0.41,-0.367 0.403,-0.392 0.39,-0.408C0.376,-0.424 0.357,-0.433 0.33,-0.433C0.301,-0.433 0.276,-0.422 0.254,-0.402C0.232,-0.381 0.215,-0.353 0.203,-0.318C0.191,-0.284 0.185,-0.245 0.185,-0.202C0.185,-0.169 0.192,-0.144 0.206,-0.128C0.22,-0.111 0.239,-0.103 0.265,-0.103Z" style="fill:white;fill-rule:nonzero;"/>
</g>
<g transform="matrix(233.753,0,0,233.753,1926.65,362.817)">
<path d="M0.238,0.012C0.169,0.012 0.117,-0.005 0.082,-0.038C0.048,-0.071 0.03,-0.12 0.03,-0.184C0.03,-0.259 0.044,-0.324 0.071,-0.378C0.099,-0.432 0.137,-0.474 0.186,-0.504C0.234,-0.533 0.291,-0.548 0.355,-0.548C0.401,-0.548 0.44,-0.54 0.471,-0.524C0.502,-0.507 0.525,-0.483 0.541,-0.452C0.556,-0.421 0.564,-0.383 0.564,-0.338L0.409,-0.331C0.409,-0.367 0.403,-0.393 0.391,-0.409C0.379,-0.424 0.359,-0.432 0.33,-0.432C0.302,-0.432 0.277,-0.422 0.255,-0.402C0.234,-0.382 0.216,-0.355 0.204,-0.321C0.191,-0.287 0.185,-0.249 0.185,-0.206C0.185,-0.17 0.191,-0.143 0.204,-0.127C0.216,-0.111 0.236,-0.103 0.263,-0.103C0.293,-0.103 0.319,-0.112 0.34,-0.13C0.361,-0.147 0.378,-0.174 0.39,-0.21L0.543,-0.204C0.529,-0.16 0.508,-0.122 0.479,-0.089C0.45,-0.057 0.415,-0.032 0.374,-0.014C0.334,0.003 0.288,0.012 0.238,0.012Z" style="fill:white;fill-rule:nonzero;"/>
</g>
<g transform="matrix(233.753,0,0,233.753,2061.9,362.817)">
<path d="M0.16,-0.131L0.192,-0.306L0.45,-0.536L0.637,-0.536L0.16,-0.131ZM0.01,-0L0.16,-0.71L0.31,-0.71L0.16,-0L0.01,-0ZM0.368,-0L0.264,-0.248L0.379,-0.333L0.533,-0L0.368,-0Z" style="fill:white;fill-rule:nonzero;"/>
</g>
<g transform="matrix(233.753,0,0,233.753,2204.53,362.817)">
</g>
<g transform="matrix(233.753,0,0,233.753,2257.92,362.817)">
</g>
</g>
<g transform="matrix(0.96583,0,0,0.984036,-9.32219,21.63)">
<path d="M311.416,461.453L281.645,600.375C289.122,590.297 297.573,582.144 306.999,575.916C323.911,564.741 342.767,559.153 363.566,559.153C387.706,559.153 405.998,565.418 418.443,577.948C430.888,590.478 437.111,607.649 437.111,629.46C437.111,639.413 436.088,650.35 434.042,662.271C431.997,674.193 430.02,684.453 428.113,693.051L427.462,696.076C423.753,695.216 420.018,694.752 416.268,694.75C406.179,694.744 395.48,698.167 384.836,704.403C372.642,711.548 360.183,722.586 348.622,733.745C346.354,735.934 343.856,738.282 341.291,740.656L352.618,688.384C354.19,682.014 355.504,675.476 356.562,668.772C357.619,662.068 358.148,655.533 358.148,649.167C358.148,639.479 355.506,632.129 350.221,627.118C344.936,622.107 336.803,619.602 325.823,619.602C315.935,619.602 306.916,621.837 298.768,626.309C290.619,630.78 283.68,637.62 277.95,646.83C272.22,656.039 267.786,667.925 264.649,682.487L251.522,744.09C238.026,749.008 223.339,759.127 209.691,775.266C195.858,791.625 174.257,805.089 160.149,812.821L222.685,520.219C248.05,495.448 277.874,475.362 311.416,461.453ZM167.579,829.767C182.86,821.112 206.229,805.095 222.641,785.686C237.826,767.728 254.036,758.385 267.374,757.899C293.757,756.936 302.691,778.88 318.086,779.007C336.919,779.162 379.548,712.1 414.23,712.121C451.598,712.144 483.219,775.838 507.785,775.779C524.374,775.74 533.917,757.587 553.577,757.745C563.589,757.825 577.104,768.353 591.118,779.64C602.675,788.947 640.35,820.705 666.264,842.607C619.483,926.992 528.796,985.05 423.739,986.884C310.019,988.869 213.48,924.048 167.579,829.767ZM278.49,839.21C278.49,839.21 304.093,836.136 326.031,822.275C348.428,808.124 369.348,792.845 375.016,792.822C384.512,792.783 389.858,806.521 399.148,806.075C412.642,805.428 428.651,781.005 442.132,780.304C453.181,779.73 462.606,790.088 473.411,793.844C485.308,797.98 493.412,795.767 493.412,795.767C493.412,795.767 485.653,791.267 474.788,781.04C468.553,775.172 455.548,760.741 448.613,753.468C439.024,743.412 431.096,732.407 414.23,732.443C396.454,732.481 378.823,753.996 363.705,769.996C361.805,772.006 346.912,787.343 326.504,804.966C305.719,822.914 278.49,839.21 278.49,839.21ZM191.013,835.528C191.013,835.528 198.753,834.654 212.863,828.48C234.834,818.867 248.25,808.244 253.605,808.284C263.1,808.356 269.553,814.837 277.343,814.911C289.97,815.032 304.884,797.976 304.884,797.976C304.884,797.976 300.044,793.491 292.116,788.043C284.572,782.858 276.793,777.654 270.195,778.095C262.994,778.577 254.156,783.666 233.748,801.289C212.963,819.238 191.013,835.528 191.013,835.528ZM444.972,847.714C444.972,847.714 460.408,849.651 480.379,840.466C501.271,830.857 510.121,817.267 521.837,817.219C531.333,817.18 531.587,823.077 543.15,823.2C549.857,823.271 564.23,810.962 570.599,808.858C576.701,806.843 587.107,812.929 593.443,813.343C601.094,813.844 609.904,813.566 609.904,813.566C609.904,813.566 594.304,800.885 580.945,790.381C565.983,778.617 562.447,772.447 552.368,772.483C534.593,772.547 524.153,792.804 507.527,805.859C502.985,809.424 491.579,822.338 467.899,834.348C444.737,846.095 444.972,847.714 444.972,847.714ZM215.601,885.767C215.791,895.51 391.383,897.206 391.75,900.77C392.145,904.604 279.347,904.07 279.388,911.079C279.421,916.747 388.247,915.497 388.209,918.468C388.157,922.422 307.862,929.231 307.906,933.11C307.947,936.729 417.063,938.623 417.252,942.432C417.439,946.187 374.935,948.552 374.932,952.509C374.93,955.439 393.626,958.15 436.923,958.622C462.095,958.896 485.516,954.565 485.422,951.428C485.303,947.447 449.314,945.119 449.219,942.655C449.124,940.205 531.133,936.516 531.261,931.696C531.368,927.625 442.356,921.757 442.276,919.178C442.212,917.13 558.319,915.343 558.409,911.079C558.473,908.009 443,903.452 443.03,900.77C443.055,898.507 621.947,890.989 621.84,886.225C621.789,883.919 521.675,884.701 416.688,884.351C318.799,884.024 215.535,882.359 215.601,885.767ZM681.616,619.738L595.583,619.766L574.301,719.027C572.647,726.603 571.143,733.885 569.788,740.872C564.39,739.115 559.011,738.144 553.734,738.101C541.83,738.006 532.257,741.958 523.591,747.203C520.143,749.29 516.882,751.593 513.572,753.607C511.888,754.632 510.324,755.863 508.379,756.096C507.433,755.535 503.282,753.035 500.771,750.897C498.037,748.569 495.24,745.913 492.378,743.066C494.161,734.289 496.032,725.328 497.991,716.185L518.753,619.766L474.568,619.766L486.739,563.071L531.413,563.071L550.495,471.901C575.034,484.681 597.215,501.04 616.762,520.085L608.019,563.071L652.508,563.071C664.182,580.704 674.281,599.613 681.616,619.738Z" style="fill:white;"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 307 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 473 KiB

+15
View File
@@ -0,0 +1,15 @@
<svg xmlns="http://www.w3.org/2000/svg" width="900" height="608" viewBox="0 0 900 608">
<rect width="900" height="608" fill="#e9edfc"/>
<g fill="none" stroke="#cad3f3" stroke-width="2">
<rect x="211" y="93" width="260" height="325" rx="12" transform="rotate(-16 341 255)"/>
<rect x="427" y="142" width="260" height="325" rx="12" transform="rotate(16 557 305)"/>
</g>
<rect x="335" y="141" width="230" height="230" rx="32" fill="#3156c8"/>
<g transform="translate(387 193) scale(4)" fill="none" stroke="#fff" stroke-width="2.3" stroke-linecap="round" stroke-linejoin="round">
<path d="M14 4H8a4 4 0 0 0-4 4v6M18 4h6a4 4 0 0 1 4 4v6M28 18v6a4 4 0 0 1-4 4h-6M14 28H8a4 4 0 0 1-4-4v-6"/>
<circle cx="21" cy="11" r="2" fill="#fff" stroke="none"/>
<path d="m8.5 22 5-6.2a1.7 1.7 0 0 1 2.6-.1l2.6 2.9 1.7-1.8a1.7 1.7 0 0 1 2.5 0l1.6 1.8"/>
</g>
<text x="450" y="461" text-anchor="middle" font-family="Arial, sans-serif" font-size="48" font-weight="600" letter-spacing="-2" fill="#233463">Manyangles</text>
<text x="450" y="502" text-anchor="middle" font-family="Arial, sans-serif" font-size="18" fill="#53638b">Event photo sharing</text>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

+25
View File
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 1614 304" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g transform="matrix(1,0,0,1,-148.166,-388.36)">
<g id="Wordmark" transform="matrix(1,0,0,1,247.728,-1.13687e-13)">
<g transform="matrix(0.640412,0,0,0.640412,67.9128,191.461)">
<path d="M577.412,694.848L537.594,694.848C531.036,694.848 527.757,691.568 527.757,685.01L527.757,403.473C527.757,396.914 531.036,393.635 537.594,393.635L649.085,393.635C673.444,393.635 692.495,400.35 706.236,413.778C719.977,427.207 726.847,446.101 726.847,470.461L726.847,507C726.847,542.289 712.95,565.712 685.156,577.267L685.156,579.141L735.28,683.605C738.09,691.1 735.436,694.848 727.316,694.848L689.372,694.848C683.75,694.848 679.69,694.223 677.192,692.974C674.694,691.725 672.664,689.07 671.102,685.01L624.257,583.825L592.871,583.825C589.124,583.825 587.25,585.543 587.25,588.978L587.25,685.01C587.25,691.568 583.971,694.848 577.412,694.848ZM592.403,536.044L638.311,536.044C648.304,536.044 655.643,533.935 660.328,529.719C665.012,525.503 667.354,518.711 667.354,509.342L667.354,472.803C667.354,463.122 665.012,456.095 660.328,451.723C655.643,447.351 648.304,445.165 638.311,445.165L592.403,445.165C588.967,445.165 587.25,446.882 587.25,450.317L587.25,530.891C587.25,534.326 588.967,536.044 592.403,536.044Z" style="fill:white;fill-rule:nonzero;"/>
<path d="M815.384,694.848L770.413,694.848C764.792,694.848 762.918,691.568 764.792,685.01L838.807,403.473C840.993,396.914 845.677,393.635 852.86,393.635L915.164,393.635C922.971,393.635 927.656,396.914 929.217,403.473L1003.23,685.01C1005.11,691.568 1003.23,694.848 997.611,694.848L952.64,694.848C949.204,694.848 946.862,694.145 945.613,692.74C944.364,691.334 943.427,688.758 942.802,685.01L929.686,633.481L838.338,633.481L825.222,685.01C824.597,688.758 823.582,691.334 822.177,692.74C820.771,694.145 818.507,694.848 815.384,694.848ZM883.309,450.317L849.581,585.699L918.911,585.699L885.183,450.317L883.309,450.317Z" style="fill:white;fill-rule:nonzero;"/>
<path d="M1168.59,694.848L1117.53,694.848C1092.86,694.848 1073.81,688.289 1060.38,675.173C1046.95,662.056 1040.24,643.162 1040.24,618.49L1040.24,469.992C1040.24,445.321 1046.95,426.427 1060.38,413.31C1073.81,400.193 1092.86,393.635 1117.53,393.635L1168.59,393.635C1192.95,393.635 1211.93,400.272 1225.51,413.544C1239.1,426.817 1245.89,445.633 1245.89,469.992L1245.89,492.009C1245.89,498.88 1242.45,502.315 1235.58,502.315L1196.23,502.315C1189.67,502.315 1186.39,498.88 1186.39,492.009L1186.39,472.803C1186.39,462.809 1184.29,455.705 1180.07,451.489C1175.86,447.273 1168.75,445.165 1158.76,445.165L1126.9,445.165C1117.22,445.165 1110.27,447.273 1106.06,451.489C1101.84,455.705 1099.73,462.809 1099.73,472.803L1099.73,615.68C1099.73,625.673 1101.84,632.778 1106.06,636.994C1110.27,641.21 1117.22,643.318 1126.9,643.318L1158.76,643.318C1168.75,643.318 1175.86,641.21 1180.07,636.994C1184.29,632.778 1186.39,625.673 1186.39,615.68L1186.39,596.473C1186.39,589.603 1189.67,586.168 1196.23,586.168L1235.58,586.168C1242.45,586.168 1245.89,589.603 1245.89,596.473L1245.89,618.49C1245.89,642.85 1239.1,661.666 1225.51,674.938C1211.93,688.211 1192.95,694.848 1168.59,694.848Z" style="fill:white;fill-rule:nonzero;"/>
<path d="M1459.97,694.848L1306.79,694.848C1300.23,694.848 1296.95,691.568 1296.95,685.01L1296.95,403.473C1296.95,396.914 1300.23,393.635 1306.79,393.635L1459.97,393.635C1466.21,393.635 1469.34,396.914 1469.34,403.473L1469.34,435.327C1469.34,441.885 1466.21,445.165 1459.97,445.165L1362.06,445.165C1358.32,445.165 1356.44,446.882 1356.44,450.317L1356.44,511.216C1356.44,514.651 1358.32,516.369 1362.06,516.369L1442.17,516.369C1448.73,516.369 1452.01,519.648 1452.01,526.206L1452.01,558.061C1452.01,564.619 1448.73,567.898 1442.17,567.898L1362.06,567.898C1358.32,567.898 1356.44,569.616 1356.44,573.051L1356.44,638.165C1356.44,641.601 1358.32,643.318 1362.06,643.318L1459.97,643.318C1466.21,643.318 1469.34,646.597 1469.34,653.156L1469.34,685.01C1469.34,691.568 1466.21,694.848 1459.97,694.848Z" style="fill:white;fill-rule:nonzero;"/>
</g>
<g transform="matrix(0.640412,0,0,0.640412,827.59,191.461)">
<path d="M620.978,694.848L581.628,694.848C574.758,694.848 571.323,691.568 571.323,685.01L571.323,450.317C571.323,446.882 569.605,445.165 566.17,445.165L512.766,445.165C506.208,445.165 502.929,441.885 502.929,435.327L502.929,403.473C502.929,396.914 506.208,393.635 512.766,393.635L689.84,393.635C696.398,393.635 699.677,396.914 699.677,403.473L699.677,435.327C699.677,441.885 696.398,445.165 689.84,445.165L636.437,445.165C632.689,445.165 630.816,446.882 630.816,450.317L630.816,685.01C630.816,691.568 627.536,694.848 620.978,694.848Z" style="fill:white;fill-rule:nonzero;"/>
<path d="M797.115,403.473L797.115,685.01C797.115,691.568 793.836,694.848 787.277,694.848L747.459,694.848C740.901,694.848 737.622,691.568 737.622,685.01L737.622,403.473C737.622,396.914 740.901,393.635 747.459,393.635L787.277,393.635C793.836,393.635 797.115,396.914 797.115,403.473Z" style="fill:white;fill-rule:nonzero;"/>
<path d="M894.552,694.848L848.176,694.848C845.053,694.848 842.867,693.833 841.617,691.803C840.368,689.773 840.368,687.352 841.617,684.542L914.227,540.728L843.96,403.941C842.71,401.443 842.71,399.1 843.96,396.914C845.209,394.728 847.395,393.635 850.518,393.635L895.489,393.635C903.296,393.635 908.449,396.914 910.948,403.473L955.45,504.189L957.324,504.189L1001.36,403.473C1004.48,396.914 1009.48,393.635 1016.35,393.635L1062.26,393.635C1065.38,393.635 1067.64,394.806 1069.05,397.149C1070.45,399.491 1070.53,401.911 1069.28,404.409L998.079,540.728L1070.69,684.073C1072.25,687.196 1072.41,689.773 1071.16,691.803C1069.91,693.833 1067.57,694.848 1064.13,694.848L1017.29,694.848C1010.73,694.848 1006.2,691.725 1003.7,685.479L957.324,584.294L955.45,584.294L908.605,685.479C905.795,691.725 901.11,694.848 894.552,694.848Z" style="fill:white;fill-rule:nonzero;"/>
</g>
<g transform="matrix(1.00002,-0.00623216,-0.00623216,3.44642,230.747,-470.029)">
<path d="M830.429,252.46L870.429,252.562L870.39,267.797L830.39,267.695L830.429,252.46ZM830.376,273.024L870.376,273.126L870.328,291.99L830.328,291.888L830.376,273.024ZM830.315,297.217L870.314,297.319L870.266,316.183L830.267,316.081L830.315,297.217ZM830.253,321.41L870.253,321.512L870.214,336.747L830.214,336.645L830.253,321.41Z" style="fill:white;"/>
</g>
</g>
<g id="Icon" transform="matrix(1.4649,0,0,1.4649,15.7961,195.857)">
<path d="M220.75,322.191C204.864,317.449 188.523,313.355 169.574,313.355C151.106,313.355 133.16,317.776 118.354,326.063C118.354,326.063 109.407,331.834 100.226,328.324C94.298,326.058 90.462,317.42 90.44,313.998C90.263,286.476 90.44,172.21 90.44,172.21L91.657,164.147L94.852,157.325L99.303,152.049C100.837,150.629 102.588,149.324 104.546,148.193C123.308,137.361 146.121,131.41 169.574,131.41C189.374,131.41 206.667,135.211 222.716,139.971C221.468,142.065 220.75,144.511 220.75,147.124L220.75,155.584C220.75,163.311 227.024,169.584 234.75,169.584C242.477,169.584 248.75,163.311 248.75,155.584L248.75,148.381C262.221,152.786 275.241,156.496 289.059,156.496C307.987,156.496 322.568,154.02 332.664,148.193C337.327,145.501 342.981,143.953 348.81,143.953C366.61,143.953 379.061,158.508 379.061,172.21L379.061,297.641L377.844,305.704L374.649,312.525L370.198,317.802C368.663,319.222 366.913,320.527 364.955,321.658C346.192,332.49 323.38,338.441 299.927,338.441C281.142,338.441 264.624,335.011 248.731,330.591C248.744,330.346 248.75,330.1 248.75,329.852L248.75,321.392C248.75,313.666 242.477,307.392 234.75,307.392C227.024,307.392 220.75,313.666 220.75,321.392L220.75,322.191ZM145.495,275.563C147.604,275.563 148.658,274.508 148.658,272.399L148.658,241.517C148.658,240.412 149.261,239.86 150.466,239.86L160.559,239.86L175.624,272.399C176.126,273.705 176.779,274.558 177.582,274.96C178.386,275.362 179.691,275.563 181.499,275.563L193.701,275.563C196.312,275.563 197.166,274.357 196.262,271.947L180.143,238.353L180.143,237.751C189.081,234.035 193.551,226.502 193.551,215.154L193.551,203.403C193.551,195.57 191.341,189.494 186.922,185.175C182.503,180.857 176.377,178.697 168.543,178.697L132.69,178.697C130.581,178.697 129.526,179.752 129.526,181.861L129.526,272.399C129.526,274.508 130.581,275.563 132.69,275.563L145.495,275.563ZM150.315,224.494C149.21,224.494 148.658,223.941 148.658,222.837L148.658,196.926C148.658,195.821 149.21,195.268 150.315,195.268L165.079,195.268C168.292,195.268 170.652,195.971 172.159,197.377C173.665,198.784 174.419,201.043 174.419,204.157L174.419,215.907C174.419,218.92 173.665,221.104 172.159,222.46C170.652,223.816 168.292,224.494 165.079,224.494L150.315,224.494ZM220.75,260.483L220.75,277.402C220.75,285.129 227.024,291.402 234.75,291.402C242.477,291.402 248.75,285.129 248.75,277.402L248.75,260.483C248.75,252.756 242.477,246.483 234.75,246.483C227.024,246.483 220.75,252.756 220.75,260.483ZM220.75,199.574L220.75,216.493C220.75,224.22 227.024,230.493 234.75,230.493C242.477,230.493 248.75,224.22 248.75,216.493L248.75,199.574C248.75,191.847 242.477,185.574 234.75,185.574C227.024,185.574 220.75,191.847 220.75,199.574ZM314.666,293.403C316.775,293.403 317.83,292.349 317.83,290.24L317.83,214.766C317.83,213.661 318.432,213.109 319.637,213.109L336.811,213.109C338.92,213.109 339.975,212.055 339.975,209.945L339.975,199.702C339.975,197.593 338.92,196.538 336.811,196.538L279.867,196.538C277.758,196.538 276.703,197.593 276.703,199.702L276.703,209.945C276.703,212.055 277.758,213.109 279.867,213.109L297.04,213.109C298.145,213.109 298.698,213.661 298.698,214.766L298.698,290.24C298.698,292.349 299.802,293.403 302.012,293.403L314.666,293.403Z" style="fill:white;"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 930 KiB

+4 -13
View File
@@ -127,23 +127,14 @@ export default function ExperiencePage() {
</div>
</div>
<p className="mt-2 text-lg text-muted-foreground">
My comprehensive experience across research, teaching, professional
development, and leadership roles.
Building products, running systems, and helping people use them.
My path through business, engineering, teaching, and research.
</p>
</section>
{/* Research Experience */}
{renderExperienceSection("Research Experience", researchExperience, 1)}
{/* Teaching Experience */}
{renderExperienceSection("Professional Experience", professionalExperience, 1)}
{renderExperienceSection("Teaching Experience", teachingExperience, 2)}
{/* Professional Experience */}
{renderExperienceSection(
"Professional Experience",
professionalExperience,
3,
)}
{renderExperienceSection("Research Experience", researchExperience, 3)}
{/* Leadership & Activities */}
{renderExperienceSection(
+6 -13
View File
@@ -6,32 +6,28 @@ import { Navigation } from "~/components/Navigation";
import { SiteFrame } from "~/components/SiteFrame";
import { BreadcrumbProvider } from "~/context/BreadcrumbContext";
import { playfair } from "~/lib/fonts";
import { manrope, instrument } from "~/lib/fonts";
import { description, name } from "~/lib/data";
import "~/styles/globals.css";
import { Inter } from "next/font/google";
import { cn } from "~/lib/utils";
const inter = Inter({subsets:['latin'],variable:'--font-sans'});
export const metadata: Metadata = {
title: `${name[0]?.first} ${name[0]?.last}`,
description: description,
icons: [{ rel: "icon", url: "/favicon.ico" }],
icons: [{ rel: "icon", url: "/monogram.svg", type: "image/svg+xml" }],
};
export default function RootLayout({ children }: React.PropsWithChildren) {
return (
<html
lang="en"
className={cn(inter.variable, playfair.variable, "font-sans", inter.variable)}
className={cn(manrope.variable, instrument.variable, "font-sans")}
suppressHydrationWarning
>
<body
className="flex min-h-screen flex-col bg-background font-sans text-foreground"
suppressHydrationWarning
>
{env.NEXT_PUBLIC_UMAMI_WEBSITE_ID && (
<Script
defer
@@ -43,12 +39,9 @@ export default function RootLayout({ children }: React.PropsWithChildren) {
/>
)}
{/* Living background */}
<div className="pointer-events-none fixed inset-0 -z-10 overflow-hidden">
<div className="absolute inset-0 bg-[linear-gradient(to_right,#80808010_1px,transparent_1px),linear-gradient(to_bottom,#80808010_1px,transparent_1px)] bg-[size:24px_24px]" />
<div className="animate-blob absolute left-[45%] top-[30%] h-[700px] w-[700px] rounded-full bg-primary/5 blur-3xl" />
<div className="animate-blob absolute right-[20%] top-[60%] h-[500px] w-[500px] rounded-full bg-primary/4 blur-3xl [animation-delay:3s]" />
</div>
<a href="#main-content" className="skip-link">
Skip to content
</a>
<BreadcrumbProvider>
<Navigation />
+221 -445
View File
@@ -1,470 +1,246 @@
import {
ArrowUpRight,
Award,
BookOpen,
Code,
ExternalLink,
FileText,
Globe,
GraduationCap,
Mail,
MapPin,
Smartphone,
} from "lucide-react";
import Image from "next/image";
import { ManyanglesPreview } from "~/components/ManyanglesPreview";
import { BeenvoicePreview } from "~/components/BeenvoicePreview";
import Link from "next/link";
import { Badge } from "~/components/ui/badge";
import { Button } from "~/components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "~/components/ui/card";
import { awards, educationList } from "~/lib/data";
import { ArrowUpRight } from "lucide-react";
function SectionLabel({ children }: { children: React.ReactNode }) {
return (
<h2 className="text-xs font-semibold uppercase tracking-widest text-muted-foreground">
{children}
</h2>
);
}
const work = [
{
name: "Racetix",
category: "Ticketing platform",
image: "/portfolio/racetix.webp",
alt: "Stock cars at Riverhead Raceway",
tone: "racetix",
href: "https://racetix.us",
description:
"A cash-only track needed advance ticket sales. I built a multi-tenant platform covering payments, mobile wallet tickets, and QR admission scanning.",
stack: "Multi-tenant / Stripe Connect / Mobile ticketing",
},
{
name: "beenvoice",
category: "Business software",
image: "/portfolio/beenvoice-dashboard.webp",
alt: "beenvoices mobile invoicing dashboard",
tone: "beenvoice",
href: "https://beenvoice.app",
description:
"The business side of independent work. Clients, time tracking, recurring invoices, and payments across web and mobile, with iOS widgets to keep things close.",
stack: "Next.js / Expo / PostgreSQL",
},
{
name: "Puter",
category: "Native macOS app",
image: "/images/puter.jpg",
alt: "Puters macOS performance dashboard",
tone: "puter",
href: "https://git.soconnor.dev/soconnor/Puter",
description:
"A system monitor for the details Activity Monitor leaves out. Live process controls, hardware telemetry, and diagnostics, built in SwiftUI.",
stack: "Swift / SwiftUI / macOS",
},
{
name: "Manyangles",
category: "Event photo sharing",
image: "/portfolio/manyangles.svg",
alt: "Manyangles event photo sharing",
tone: "manyangles",
href: "https://ma.hadlock.tech",
description:
"One shared link for everyones photos. Guests upload from their phones; hosts review submissions and bring the event together in a shared gallery.",
stack: "Guest uploads / Host moderation / Image processing",
},
];
export default function HomePage() {
return (
<div className="space-y-16">
{/* Hero */}
<section className="animate-fade-in-up space-y-5 pt-2">
<div className="space-y-4">
<h1 className="animate-fade-in-up-delay-1 text-4xl font-bold tracking-tight">
Sean O&apos;Connor
<div className="studio-home">
<section className="studio-intro">
<div className="intro-topline">
<span>Software engineer & founder</span>
<span>Boston, MA</span>
</div>
<div className="intro-main">
<div>
<h1>
Sean OConnor<span>.</span>
</h1>
<p className="animate-fade-in-up-delay-2 max-w-lg text-lg text-muted-foreground">
Researcher & builder. HRI at BU, motorsports software at Riverhead.
<p className="intro-statement">
I build software and run{" "}
<a href="https://hadlock.tech">Hadlock Technologies</a>.
</p>
<div className="animate-fade-in-up-delay-3 flex flex-wrap gap-4 text-sm text-muted-foreground">
<div className="flex items-center gap-1.5">
<Mail className="h-4 w-4" />
<a
href="mailto:sean@soconnor.dev"
className="transition-colors hover:text-foreground"
>
sean@soconnor.dev
<p className="intro-detail">
Web products, native apps, and the systems behind them. Currently
finishing my masters in Computer Engineering at Boston
University.
</p>
<div className="intro-links">
<a href="mailto:sean@soconnor.dev">
Say hello <ArrowUpRight size={16} />
</a>
<a href="https://github.com/soconnor0919">
GitHub <ArrowUpRight size={16} />
</a>
</div>
<div className="flex items-center gap-1.5">
<GraduationCap className="h-4 w-4" />
Boston University
</div>
<div className="flex items-center gap-1.5">
<MapPin className="h-4 w-4" />
Boston, MA
</div>
</div>
<div className="animate-fade-in-up-delay-4 flex gap-3">
<Button variant="outline" asChild>
<Link href="/cv">
<ExternalLink className="mr-2 h-4 w-4" />
CV
Résumé <ArrowUpRight size={16} />
</Link>
</Button>
<Button variant="outline" asChild>
<Link href="/publications">
<BookOpen className="mr-2 h-4 w-4" />
Publications
</Link>
</Button>
</div>
</div>
<Image
className="intro-portrait"
src="/headshot.webp"
alt="Sean OConnor"
width={220}
height={260}
priority
/>
</div>
</section>
{/* Currently Building */}
<section className="animate-fade-in-up space-y-4">
<SectionLabel>Currently building</SectionLabel>
<div className="grid gap-4 md:grid-cols-2">
<Card className="card-hover card-full-height">
<CardHeader>
<div className="flex items-start gap-3">
<div className="rounded-xl bg-primary/10 p-2.5">
<Smartphone className="h-5 w-5 text-primary" />
</div>
<div className="min-w-0">
<CardTitle>Riverhead Raceway Mobile App</CardTitle>
<CardDescription>racehub-app · React Native</CardDescription>
</div>
</div>
</CardHeader>
<CardContent className="card-content-stretch">
<div className="flex h-full flex-col gap-3">
<p className="text-sm leading-relaxed text-muted-foreground">
Native mobile companion for Riverhead Raceway race-day
results, live standings, and event info for fans and
competitors on the track.
</p>
<div className="flex flex-wrap gap-1.5">
{["React Native", "TypeScript", "tRPC"].map((t) => (
<Badge key={t} variant="secondary">
{t}
</Badge>
))}
</div>
</div>
</CardContent>
</Card>
<Card className="card-hover card-full-height">
<CardHeader>
<div className="flex items-start gap-3">
<div className="rounded-xl bg-primary/10 p-2.5">
<Code className="h-5 w-5 text-primary" />
</div>
<div className="min-w-0">
<CardTitle>beenCMS</CardTitle>
<CardDescription>
White-label CMS engine · Next.js
</CardDescription>
</div>
</div>
</CardHeader>
<CardContent className="card-content-stretch">
<div className="flex h-full flex-col gap-3">
<p className="text-sm leading-relaxed text-muted-foreground">
Fork-once CMS engine for powering multiple client sites.
Block-based page editor, runtime theming, role-based auth,
and a full admin interface all on SQLite with zero ops.
</p>
<div className="flex flex-wrap gap-1.5">
{["Next.js", "tRPC", "Drizzle", "SQLite"].map((t) => (
<Badge key={t} variant="secondary">
{t}
</Badge>
))}
</div>
</div>
</CardContent>
</Card>
</div>
</section>
{/* Featured Work */}
<section className="animate-fade-in-up space-y-4">
<SectionLabel>Featured work</SectionLabel>
<div className="grid-equal-height grid gap-4 md:grid-cols-2">
<Card className="card-hover card-full-height">
<CardHeader>
<div className="flex items-start justify-between gap-2">
<div className="min-w-0">
<CardTitle>Honors Thesis</CardTitle>
<CardDescription>
Bucknell University · 2026
</CardDescription>
</div>
<FileText className="h-5 w-5 flex-shrink-0 text-muted-foreground" />
</div>
</CardHeader>
<CardContent className="card-content-stretch">
<div className="flex h-full flex-col gap-3">
<p className="text-sm leading-relaxed text-muted-foreground">
A web-based Wizard-of-Oz platform for accessible, reproducible
HRI research. Pilot study showed higher design fidelity and
usability than Choregraphe across all six sessions.
</p>
<div className="flex flex-wrap gap-1.5">
{["HRI", "Wizard-of-Oz", "React", "ROS2"].map((t) => (
<Badge key={t} variant="outline">
{t}
</Badge>
))}
</div>
<Button
variant="outline"
size="sm"
asChild
className="mt-auto w-full"
>
<Link href="/publications">
Read thesis
<ArrowUpRight className="ml-1.5 h-3.5 w-3.5" />
</Link>
</Button>
</div>
</CardContent>
</Card>
<Card className="card-hover card-full-height">
<CardHeader>
<div className="flex items-start justify-between gap-2">
<div className="min-w-0">
<CardTitle>IEEE RO-MAN 2025</CardTitle>
<CardDescription>
Eindhoven · First author
</CardDescription>
</div>
<BookOpen className="h-5 w-5 flex-shrink-0 text-muted-foreground" />
</div>
</CardHeader>
<CardContent className="card-content-stretch">
<div className="flex h-full flex-col gap-3">
<p className="text-sm leading-relaxed text-muted-foreground">
Collaborative and Reproducible HRI Research Through a
Web-Based Wizard-of-Oz Platform. 34th IEEE International
Conference on Robot and Human Interactive Communication.
</p>
<div className="flex flex-wrap gap-1.5">
{["IEEE", "HRI", "Research"].map((t) => (
<Badge key={t} variant="outline">
{t}
</Badge>
))}
</div>
<Button
variant="outline"
size="sm"
asChild
className="mt-auto w-full"
>
<Link href="/publications">
View paper
<ArrowUpRight className="ml-1.5 h-3.5 w-3.5" />
</Link>
</Button>
</div>
</CardContent>
</Card>
<Card className="card-hover card-full-height">
<CardHeader>
<div className="flex items-start justify-between gap-2">
<div className="min-w-0">
<CardTitle>Riverhead Raceway</CardTitle>
<CardDescription>
Production · 250k+ monthly users
</CardDescription>
</div>
<Globe className="h-5 w-5 flex-shrink-0 text-muted-foreground" />
</div>
</CardHeader>
<CardContent className="card-content-stretch">
<div className="flex h-full flex-col gap-3">
<p className="text-sm leading-relaxed text-muted-foreground">
Official website and CMS for Riverhead Raceway, NY.
Real-time race data, event management, standings, and a full
admin interface with 97+ granular permissions.
</p>
<div className="flex flex-wrap gap-1.5">
{["Next.js", "PostgreSQL", "tRPC"].map((t) => (
<Badge key={t} variant="outline">
{t}
</Badge>
))}
</div>
<Button
variant="outline"
size="sm"
asChild
className="mt-auto w-full"
>
<a
href="https://riverheadraceway.com"
target="_blank"
rel="noopener noreferrer"
>
Visit site
<ArrowUpRight className="ml-1.5 h-3.5 w-3.5" />
</a>
</Button>
</div>
</CardContent>
</Card>
<Card className="card-hover card-full-height">
<CardHeader>
<div className="flex items-start justify-between gap-2">
<div className="min-w-0">
<CardTitle>beenvoice</CardTitle>
<CardDescription>Invoicing for freelancers</CardDescription>
</div>
<Code className="h-5 w-5 flex-shrink-0 text-muted-foreground" />
</div>
</CardHeader>
<CardContent className="card-content-stretch">
<div className="flex h-full flex-col gap-3">
<p className="text-sm leading-relaxed text-muted-foreground">
Professional invoicing: client management, PDF generation,
email delivery, timesheet view, and CSV import. Built with
tRPC and Drizzle ORM on Next.js 16.
</p>
<div className="flex flex-wrap gap-1.5">
{["Next.js", "tRPC", "Drizzle"].map((t) => (
<Badge key={t} variant="outline">
{t}
</Badge>
))}
</div>
<Button
variant="outline"
size="sm"
asChild
className="mt-auto w-full"
>
<a
href="https://beenvoice.soconnor.dev"
target="_blank"
rel="noopener noreferrer"
>
Try it
<ArrowUpRight className="ml-1.5 h-3.5 w-3.5" />
</a>
</Button>
</div>
</CardContent>
</Card>
</div>
</section>
{/* Education */}
<section className="animate-fade-in-up space-y-4">
<SectionLabel>Education</SectionLabel>
<div className="grid gap-4 md:grid-cols-2">
{educationList.map((edu, i) => (
<Card key={i} className="card-hover">
<CardHeader>
<CardTitle>{edu.institution}</CardTitle>
<CardDescription>{edu.degree}</CardDescription>
</CardHeader>
<CardContent>
<p className="text-sm text-muted-foreground">
{edu.graduated ? "" : "Expected "}
{edu.expectedGraduation} · {edu.location}
</p>
{edu.engineeringGpa && (
<div className="mt-2 flex flex-wrap gap-1.5">
<Badge variant="secondary">
Eng. GPA {edu.engineeringGpa}
</Badge>
<Badge variant="outline">Overall {edu.gpa}</Badge>
</div>
)}
</CardContent>
</Card>
))}
</div>
</section>
{/* Recognition */}
<section className="animate-fade-in-up space-y-4">
<SectionLabel>Recognition</SectionLabel>
<div className="grid gap-4 md:grid-cols-3">
{awards.map((award, i) => (
<Card key={i} className="card-hover">
<CardHeader>
<div className="flex items-start gap-3">
<Award className="mt-0.5 h-4 w-4 flex-shrink-0 text-primary" />
<div className="min-w-0">
<CardTitle className="text-sm leading-snug">
{award.title}
</CardTitle>
{award.organization && (
<CardDescription className="mt-1 text-xs">
{award.organization} · {award.year}
</CardDescription>
)}
</div>
</div>
</CardHeader>
{award.description && (
<CardContent>
<p className="text-xs leading-relaxed text-muted-foreground">
{award.description}
</p>
</CardContent>
)}
</Card>
))}
</div>
</section>
{/* Explore */}
<section className="animate-fade-in-up space-y-4">
<SectionLabel>Explore</SectionLabel>
<div className="grid-equal-height grid gap-4 md:grid-cols-3">
<Card className="card-hover card-full-height">
<CardHeader>
<div className="flex items-center gap-2">
<Code className="h-5 w-5 flex-shrink-0" />
<CardTitle>Projects</CardTitle>
</div>
</CardHeader>
<CardContent className="card-content-stretch">
<div className="flex h-full flex-col gap-3">
<p className="text-sm leading-relaxed text-muted-foreground">
HRIStudio, Riverhead Raceway, machine learning, embedded
systems, and more.
</p>
<Button variant="outline" asChild className="mt-auto w-full">
<section id="work" className="studio-work">
<div className="studio-section-heading">
<h2>
Selected projects <span>04</span>
</h2>
<Link href="/projects">
View projects
<ArrowUpRight className="ml-2 h-4 w-4" />
All projects <ArrowUpRight size={16} />
</Link>
</Button>
</div>
</CardContent>
</Card>
<Card className="card-hover card-full-height">
<CardHeader>
<div className="flex items-center gap-2">
<BookOpen className="h-5 w-5 flex-shrink-0" />
<CardTitle>Publications</CardTitle>
<div className="studio-grid">
{work.map((project, index) => (
<article
key={project.name}
id={`project-${project.name.toLowerCase()}`}
className={`studio-project ${project.tone}`}
>
<a
href={project.href}
className="studio-project-image"
aria-label={`Visit ${project.name}`}
>
{project.name === "beenvoice" ? (
<BeenvoicePreview priority />
) : project.name === "Manyangles" ? (
<ManyanglesPreview />
) : (
<Image
src={project.image}
alt={project.alt}
fill
priority={index < 2}
sizes="(max-width: 700px) 100vw, 50vw"
/>
)}
{project.name === "Racetix" && (
<div className="studio-racetix-logo">
<Image
src="/portfolio/racetix-logo.svg"
alt=""
width={230}
height={44}
/>
</div>
</CardHeader>
<CardContent className="card-content-stretch">
<div className="flex h-full flex-col gap-3">
<p className="text-sm leading-relaxed text-muted-foreground">
Peer-reviewed work in human-robot interaction research.
</p>
<Button variant="outline" asChild className="mt-auto w-full">
<Link href="/publications">
View publications
<ArrowUpRight className="ml-2 h-4 w-4" />
</Link>
</Button>
)}
</a>
<div className="studio-project-title">
<h3>
<a href={project.href}>
{project.name} <ArrowUpRight size={20} />
</a>
</h3>
<span>{project.category}</span>
</div>
</CardContent>
</Card>
<Card className="card-hover card-full-height">
<CardHeader>
<div className="flex items-center gap-2">
<ExternalLink className="h-5 w-5 flex-shrink-0" />
<CardTitle>CV</CardTitle>
</div>
</CardHeader>
<CardContent className="card-content-stretch">
<div className="flex h-full flex-col gap-3">
<p className="text-sm leading-relaxed text-muted-foreground">
Complete academic and professional curriculum vitae.
</p>
<Button variant="outline" asChild className="mt-auto w-full">
<Link href="/cv">
View CV
<ArrowUpRight className="ml-2 h-4 w-4" />
</Link>
</Button>
</div>
</CardContent>
</Card>
<p>{project.description}</p>
<div className="studio-stack">{project.stack}</div>
</article>
))}
</div>
</section>
<section className="studio-hadlock">
<div>
<p className="studio-label">My company</p>
<h2>Hadlock Technologies</h2>
<p>
I started Hadlock in 2026 to bring my software and infrastructure
work together. I work directly with businesses, from figuring out
what they need to building it and keeping it running.
</p>
<div className="studio-cms">
<h3>hadlockCMS</h3>
<p>
Im also building a publishing platform for multiple sites: visual
page editing, revisions, scheduled publishing, and custom domains,
without maintaining a separate codebase for each client.
</p>
<Link className="studio-link" href="/projects#hadlockcms">
About the platform <ArrowUpRight size={16} />
</Link>
</div>
<a className="studio-link" href="https://hadlock.tech">
Visit Hadlock <ArrowUpRight size={16} />
</a>
</div>
<Image
src="/portfolio/hadlock-pond.jpg"
alt="Hadlock Pond, the Adirondack lake that gave the company its name"
width={520}
height={390}
sizes="(max-width: 700px) 100vw, 35vw"
/>
</section>
<section id="about" className="studio-about">
<div>
<p className="studio-label">A bit more about me</p>
<h2>
I like knowing how
<br />
the whole thing works.
</h2>
</div>
<div>
<p>
I started working on technology at Riverhead Raceway in 2020. Over
time that grew from IT into building the website, live race systems,
and software used around the track.
</p>
<p>
At Bucknell, I studied Computer Science and Engineering, built tools
for robotics research, and helped other students learn to build
things. Now Im at BU, aiming to finish my masters in a year while
continuing my own projects.
</p>
<p>
Outside of software, Im into specialty coffee, Formula One, and
travel. I cofounded Bucknells Coffee Societyand, naturally, made a
<a
className="coffee-link"
href="https://lewisburgcoffee.soconnor.dev"
>
website for finding coffee in Lewisburg
</a>
.
</p>
<div className="intro-links">
<Link href="/experience">
Experience <ArrowUpRight size={16} />
</Link>
<Link href="/travel">
Trips & interests <ArrowUpRight size={16} />
</Link>
</div>
</div>
</section>
<section className="studio-contact">
<div>
<p className="studio-label">Get in touch</p>
<h2>Always happy to talk.</h2>
</div>
<a href="mailto:sean@soconnor.dev">
sean@soconnor.dev <ArrowUpRight size={23} />
</a>
</section>
</div>
);
}
+71 -315
View File
@@ -1,346 +1,102 @@
"use client";
import {
Card,
CardHeader,
CardTitle,
CardDescription,
CardContent,
} from "~/components/ui/card";
import { Badge } from "~/components/ui/badge";
import { Button } from "~/components/ui/button";
import Image from "next/image";
import { ManyanglesPreview } from "~/components/ManyanglesPreview";
import { BeenvoicePreview } from "~/components/BeenvoicePreview";
import Link from "next/link";
import { ArrowUpRight, Play, BookOpen, FolderGit2, Presentation } from "lucide-react";
import { GithubIcon } from "~/components/BrandIcons";
const Github = GithubIcon;
import { ArrowUpRight } from "lucide-react";
import type { Metadata } from "next";
import { projects } from "~/lib/data";
import { ImageWithSkeleton } from "~/components/ui/image-with-skeleton";
export const metadata: Metadata = {
title: "Work — Sean OConnor",
description:
"Products, client work, and experiments by Sean OConnor. From ticketing and business software to native Mac apps and robotics tools.",
};
const order = [
"Racetix",
"beenvoice",
"Puter",
"Manyangles",
"hadlockCMS",
"Lewisburg, PA Coffee Map",
"Real-time Racing Statistics Platform",
"HRIStudio",
];
const orderedProjects = [...projects].sort((a, b) => {
const rank = (title: string) => {
const i = order.indexOf(title);
return i === -1 ? order.length : i;
};
return rank(a.title) - rank(b.title);
});
export default function ProjectsPage() {
const featuredProjects = projects.filter((p) => p.featured);
const otherProjects = projects.filter((p) => !p.featured);
return (
<div className="space-y-8">
<section className="prose prose-zinc max-w-none dark:prose-invert">
<div className="flex items-start gap-3">
<FolderGit2 className="h-8 w-8 text-primary" />
<div>
<h1 className="mb-2 text-2xl font-bold">Projects</h1>
</div>
</div>
<p className="mt-2 text-lg text-muted-foreground">
A collection of my academic research, professional work, and personal
projects spanning robotics, web development, and embedded systems.
<header className="archive-header">
<p className="eyebrow">The portfolio</p>
<h1>
Things Ive built<span className="accent-text">.</span>
</h1>
<p>
Products, client work, and a few explorations. From the interface to
the infrastructure, I like taking an idea all the way through.
</p>
</section>
{/* Featured Projects */}
<section className="animate-fade-in-up space-y-6">
<h2 className="text-2xl font-bold">Featured Work</h2>
<div className="space-y-8">
{featuredProjects.map((project, index) => (
<div
key={index}
className={`animate-fade-in-up-delay-${Math.min(index + 1, 4)} card-hover`}
>
<Card className="overflow-hidden">
<div className="flex flex-col lg:flex-row">
{/* Project Image */}
</header>
<div className="archive-grid">
{orderedProjects.map((project, index) => (
<article className="archive-card" id={project.id} key={project.title}>
{project.image && (
<div className="lg:w-1/3">
<div className="flex items-center justify-center p-4 lg:h-full">
<ImageWithSkeleton
src={project.image}
alt={project.imageAlt ?? project.title}
width={400}
height={300}
className="h-auto w-full object-contain"
containerClassName="w-full rounded-xl shadow-md overflow-hidden"
priority={index === 0}
/>
</div>
</div>
)}
{/* Project Content */}
<div className="card-content-stretch flex flex-1 flex-col p-6">
<div className="flex-1 space-y-4">
<div>
<CardTitle className="break-words text-xl leading-tight">
{project.title}
</CardTitle>
<CardDescription className="mt-2 break-words text-base leading-relaxed">
{project.description}
</CardDescription>
</div>
<p className="break-words leading-relaxed text-muted-foreground">
{project.longDescription}
</p>
</div>
<div className="mt-6 flex flex-col gap-4 sm:flex-row sm:items-end sm:justify-between">
<div className="flex flex-wrap gap-2">
{project.tags.map((tag) => (
<Badge
key={tag}
variant="secondary"
className="break-words"
>
{tag}
</Badge>
))}
</div>
<div className="flex gap-2 sm:flex-shrink-0">
{project.link && project.link.startsWith("/") && (
<Button
variant="outline"
asChild
className="button-hover"
>
<Link href={project.link}>
{project.title ===
"LaTeX Introduction Tutorial" ? (
<>
<Play className="mr-2 h-4 w-4" />
Watch Tutorial
</>
<div className="archive-image">
{project.title === "beenvoice" ? (
<BeenvoicePreview priority={index < 2} />
) : project.title === "Manyangles" ? (
<ManyanglesPreview />
) : (
<>
<BookOpen className="mr-2 h-4 w-4" />
Learn More
</>
)}
</Link>
</Button>
)}
{project.websiteLink && (
<Button
variant="outline"
asChild
className="button-hover"
>
<Link
href={project.websiteLink}
target="_blank"
rel="noopener noreferrer"
>
<ArrowUpRight className="mr-2 h-4 w-4" />
Visit Site
</Link>
</Button>
)}
{project.gitLink && (
<Button
variant="outline"
asChild
className="button-hover"
>
<Link
href={project.gitLink}
target="_blank"
rel="noopener noreferrer"
>
<Github className="mr-2 h-4 w-4" />
View Code
</Link>
</Button>
)}
{project.slidesUrl && (
<Button
variant="outline"
asChild
className="button-hover"
>
<Link
href={project.slidesUrl}
target="_blank"
rel="noopener noreferrer"
>
<Presentation className="mr-2 h-4 w-4" />
View Slides
</Link>
</Button>
)}
{project.link &&
!project.link.startsWith("/") &&
!project.websiteLink &&
!project.gitLink && (
<Button
variant="outline"
asChild
className="button-hover"
>
<Link
href={project.link}
target="_blank"
rel="noopener noreferrer"
>
<ArrowUpRight className="mr-2 h-4 w-4" />
View Project
</Link>
</Button>
)}
</div>
</div>
</div>
</div>
</Card>
</div>
))}
</div>
</section>
{/* Other Projects */}
{otherProjects.length > 0 && (
<section className="animate-fade-in-up space-y-6">
<h2 className="text-2xl font-bold">Additional Projects</h2>
<div className="grid-equal-height grid gap-6 md:grid-cols-2">
{otherProjects.map((project, index) => (
<div
key={index}
className={`animate-fade-in-up-delay-${Math.min(index + 1, 4)} card-hover`}
>
<Card className="card-full-height flex flex-col">
{project.image && (
<div className="flex h-48 items-center justify-center p-4">
<ImageWithSkeleton
<Image
src={project.image}
alt={project.imageAlt ?? project.title}
width={400}
height={250}
className="h-auto max-h-full w-full object-contain"
containerClassName="w-full h-full flex items-center justify-center rounded-xl shadow-md overflow-hidden"
fill
sizes="(max-width: 760px) 100vw, 50vw"
priority={index < 2}
/>
)}
</div>
)}
<div className="flex flex-1 flex-col p-6">
<div className="flex flex-1 flex-col">
<CardHeader className="p-0">
<div>
<CardTitle className="break-words text-lg leading-tight">
{project.title}
</CardTitle>
</div>
<CardDescription className="mt-2 break-words leading-relaxed">
{project.description}
</CardDescription>
</CardHeader>
<CardContent className="flex flex-1 flex-col p-0 pt-4">
<div className="flex flex-wrap gap-2">
<div className="archive-body">
<h2>{project.title}</h2>
<p>{project.longDescription ?? project.description}</p>
<div className="project-tags">
{project.tags.map((tag) => (
<Badge
key={tag}
variant="secondary"
className="break-words text-xs"
>
{tag}
</Badge>
<span key={tag}>{tag}</span>
))}
</div>
<div className="mt-auto flex gap-2 pt-4">
{project.link && project.link.startsWith("/") && (
<Button
variant="outline"
asChild
className="button-hover sm:flex-shrink-0"
>
<Link href={project.link}>
<BookOpen className="mr-2 h-4 w-4" />
Learn More
</Link>
</Button>
)}
<div className="archive-links">
{project.websiteLink && (
<Button
variant="outline"
asChild
className="button-hover sm:flex-shrink-0"
>
<Link
href={project.websiteLink}
target="_blank"
rel="noopener noreferrer"
>
<ArrowUpRight className="mr-2 h-4 w-4" />
Visit Site
</Link>
</Button>
<a className="text-link" href={project.websiteLink}>
Visit site <ArrowUpRight size={16} />
</a>
)}
{project.gitLink && (
<Button
variant="outline"
asChild
className="button-hover sm:flex-shrink-0"
>
<Link
href={project.gitLink}
target="_blank"
rel="noopener noreferrer"
>
<Github className="mr-2 h-4 w-4" />
View Code
<a className="text-link" href={project.gitLink}>
View code <ArrowUpRight size={16} />
</a>
)}
{project.link && (
<Link className="text-link" href={project.link}>
Read more <ArrowUpRight size={16} />
</Link>
</Button>
)}
{project.slidesUrl && (
<Button
variant="outline"
asChild
className="button-hover sm:flex-shrink-0"
>
<Link
href={project.slidesUrl}
target="_blank"
rel="noopener noreferrer"
>
<Presentation className="mr-2 h-4 w-4" />
View Slides
</Link>
</Button>
)}
{project.link &&
!project.link.startsWith("/") &&
!project.websiteLink &&
!project.gitLink && (
<Button
variant="outline"
asChild
className="button-hover sm:flex-shrink-0"
>
<Link
href={project.link}
target="_blank"
rel="noopener noreferrer"
>
<ArrowUpRight className="mr-2 h-4 w-4" />
View Project
</Link>
</Button>
<a className="text-link" href={project.slidesUrl}>
View slides <ArrowUpRight size={16} />
</a>
)}
</div>
</CardContent>
</div>
</div>
</Card>
</div>
</article>
))}
</div>
</section>
)}
</div>
);
}
+44
View File
@@ -0,0 +1,44 @@
import Image from "next/image";
export function BeenvoicePreview({ priority = false }: { priority?: boolean }) {
return (
<div className="beenvoice-devices">
<div className="product-device product-ipad">
<Image
className="device-screen"
src="/portfolio/beenvoice-ipad.png"
alt="beenvoices iPad dashboard"
fill
sizes="(max-width: 700px) 65vw, 30vw"
priority={priority}
/>
<Image
className="device-bezel"
src="/portfolio/ipad-pro-bezel.png"
alt=""
fill
sizes="(max-width: 700px) 65vw, 30vw"
priority={priority}
/>
</div>
<div className="product-device product-iphone">
<Image
className="device-screen"
src="/portfolio/beenvoice-timer.webp"
alt="beenvoices iPhone time tracker"
fill
sizes="(max-width: 700px) 25vw, 15vw"
priority={priority}
/>
<Image
className="device-bezel"
src="/portfolio/iphone-17-pro-bezel.png"
alt=""
fill
sizes="(max-width: 700px) 25vw, 15vw"
priority={priority}
/>
</div>
</div>
);
}
+20 -8
View File
@@ -1,14 +1,26 @@
import React from "react";
import { name } from "~/lib/data";
import Link from "next/link";
import { Monogram } from "~/components/Monogram";
export function Footer() {
return (
<footer className="bg-background py-2 text-foreground lg:hidden">
<div className="mx-auto max-w-screen-xl px-4 sm:px-6 lg:px-8">
<p className="text-sm text-muted-foreground">
&copy; {new Date().getFullYear()} {name[0]?.first}&nbsp;
{name[0]?.last}. All rights reserved.
</p>
<footer className="site-footer">
<div className="footer-top">
<Link href="/" className="wordmark">
<Monogram />
<span className="wordmark-name">Sean OConnor</span>
</Link>
<div className="footer-links">
<Link href="/experience">Experience</Link>
<Link href="/publications">Research</Link>
<Link href="/articles">In the press</Link>
<Link href="/travel">Elsewhere</Link>
<a href="https://github.com/soconnor0919">GitHub</a>
<a href="https://linkedin.com/in/bu-soconnor">LinkedIn</a>
</div>
</div>
<div className="footer-bottom">
<span>© {new Date().getFullYear()} Sean OConnor</span>
<span>Built with care. From Boston, MA.</span>
</div>
</footer>
);
+40
View File
@@ -0,0 +1,40 @@
import Image from "next/image";
import localFont from "next/font/local";
const geologica = localFont({
src: "../../public/portfolio/fonts/geologica.woff2",
display: "swap",
weight: "100 900",
});
export function ManyanglesPreview() {
return (
<div className="manyangles-preview">
<Image
src="/trips/engr290/insta290.jpg"
alt="Seans study-abroad group gathered for dinner in Paris"
fill
sizes="(max-width: 700px) 100vw, 50vw"
/>
<div className="manyangles-lockup">
<svg viewBox="0 0 32 32" fill="none" aria-hidden="true">
<path
d="M14 4H8a4 4 0 0 0-4 4v6M18 4h6a4 4 0 0 1 4 4v6M28 18v6a4 4 0 0 1-4 4h-6M14 28H8a4 4 0 0 1-4-4v-6"
stroke="currentColor"
strokeWidth="3"
strokeLinecap="round"
/>
<circle cx="21" cy="11" r="2.25" fill="currentColor" />
<path
d="m8.5 22 5-6.2a1.7 1.7 0 0 1 2.6-.1l2.6 2.9 1.7-1.8a1.7 1.7 0 0 1 2.5 0l1.6 1.8"
stroke="currentColor"
strokeWidth="3"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
<span className={geologica.className}>Manyangles</span>
</div>
</div>
);
}
+14
View File
@@ -0,0 +1,14 @@
import Image from "next/image";
export function Monogram() {
return (
<Image
className="monogram"
src="/monogram.svg"
alt=""
width={120}
height={120}
aria-hidden="true"
/>
);
}
+52 -81
View File
@@ -1,112 +1,83 @@
"use client";
import {
Book,
BookOpenText,
Briefcase,
FileText,
FolderGit2,
Home,
Menu,
Newspaper,
Plane,
X,
} from "lucide-react";
import { ArrowUpRight, Menu, X } from "lucide-react";
import { usePathname } from "next/navigation";
import Link from "next/link";
import { useState } from "react";
import { Monogram } from "~/components/Monogram";
import { useRef, useState } from "react";
const navItems = [
{ href: "/", label: "About", icon: Home },
{ href: "/blog", label: "Blog", icon: Book },
{ href: "/articles", label: "Articles", icon: Newspaper },
{ href: "/projects", label: "Projects", icon: FolderGit2 },
{ href: "/publications", label: "Publications", icon: BookOpenText },
{ href: "/experience", label: "Experience", icon: Briefcase },
{ href: "/travel", label: "Travel", icon: Plane },
{ href: "/cv", label: "CV", icon: FileText },
const items = [
{ href: "/projects", label: "Work" },
{ href: "/#about", label: "About" },
{ href: "/blog", label: "Writing" },
{ href: "/cv", label: "Résumé" },
];
export function Navigation() {
const pathname = usePathname();
const [isOpen, setIsOpen] = useState(false);
const [open, setOpen] = useState(false);
const toggle = useRef<HTMLButtonElement>(null);
return (
<>
<nav
className="fixed left-0 right-0 top-0 z-[51] border-b border-border/50 bg-background/80 backdrop-blur-md"
<header
className="site-header"
onKeyDown={(event) => {
if (event.key === "Escape" && open) {
setOpen(false);
toggle.current?.focus();
}
}}
>
<div className="relative px-8">
<div className="flex h-16 items-center justify-between">
<Link href="/" className="flex items-center py-2">
<span className="font-heading text-xl font-semibold tracking-tight transition-colors hover:text-primary">
Sean O&apos;Connor
</span>
<nav className="nav-inner" aria-label="Main navigation">
<Link
href="/"
className="wordmark"
onClick={() => setOpen(false)}
aria-label="Sean O'Connor home"
>
<Monogram />
<span className="wordmark-name">Sean OConnor</span>
</Link>
<div className="flex items-center space-x-4">
<div className="hidden lg:flex lg:justify-end lg:space-x-4">
{navItems.map((item) => {
const isActive = pathname === item.href;
return (
<div className="desktop-nav">
{items.map((item) => (
<Link
key={item.href}
href={item.href}
className={`text-sm font-medium transition-colors ${isActive ? "text-primary" : "text-muted-foreground"} flex items-center gap-2 hover:text-primary`}
aria-current={pathname === item.href ? "page" : undefined}
>
<item.icon size={16} />
{item.label}
</Link>
);
})}
))}
<a className="nav-contact" href="mailto:sean@soconnor.dev">
Get in touch <ArrowUpRight size={16} />
</a>
</div>
<button
onClick={() => setIsOpen(!isOpen)}
className="relative h-6 w-6 text-gray-500 hover:text-primary focus:outline-none lg:hidden"
aria-label={isOpen ? "Close menu" : "Open menu"}
ref={toggle}
className="mobile-toggle"
aria-label={open ? "Close menu" : "Open menu"}
aria-expanded={open}
aria-controls="mobile-menu"
onClick={() => setOpen(!open)}
>
<span
className={`absolute inset-0 transition-opacity duration-300 ${isOpen ? "opacity-0" : "opacity-100"}`}
>
<Menu size={24} />
</span>
<span
className={`absolute inset-0 transition-opacity duration-300 ${isOpen ? "opacity-100" : "opacity-0"}`}
>
<X size={24} />
</span>
{open ? <X /> : <Menu />}
</button>
</div>
</div>
</div>
</nav>
<div
className={`fixed inset-0 z-40 bg-background/80 backdrop-blur-sm transition-opacity duration-200 ${isOpen ? "opacity-100" : "pointer-events-none opacity-0"
}`}
onClick={() => setIsOpen(false)}
aria-hidden="true"
/>
<div
className={`fixed left-0 right-0 top-16 z-50 overflow-hidden border-t border-border/50 bg-background/80 backdrop-blur-md transition-all duration-300 lg:hidden ${isOpen ? "max-h-[calc(100vh-8rem)] opacity-100" : "max-h-0 opacity-0"
}`}
>
<div className="flex flex-col space-y-2 p-4">
{navItems.map((item) => {
const isActive = pathname === item.href;
return (
{open && (
<div id="mobile-menu" className="mobile-menu">
{items.map((item) => (
<Link
key={item.href}
href={item.href}
className={`flex items-center text-sm font-medium transition-colors ${isActive ? "text-primary" : "text-muted-foreground"
} gap-2 hover:text-primary`}
onClick={() => setIsOpen(false)}
onClick={() => setOpen(false)}
>
<item.icon size={16} />
<span>{item.label}</span>
{item.label}
</Link>
);
})}
))}
<a href="mailto:sean@soconnor.dev">
Get in touch <ArrowUpRight size={18} />
</a>
</div>
</div>
</>
)}
</header>
);
}
+5 -13
View File
@@ -2,24 +2,16 @@
import { usePathname } from "next/navigation";
import { BreadcrumbWrapper } from "~/components/BreadcrumbWrapper";
import { Sidebar } from "~/components/Sidebar";
import { cn } from "~/lib/utils";
export function SiteFrame({ children }: React.PropsWithChildren) {
const pathname = usePathname();
const showSidebar = pathname !== "/october";
return (
<div className="flex flex-1 flex-col pt-16 lg:flex-row">
{showSidebar ? <Sidebar /> : null}
<div className={cn("min-w-0 flex-1", showSidebar && "lg:pl-80")}>
<div className="mx-auto max-w-screen-xl px-4 sm:px-6 lg:pl-0 lg:pr-6">
<main className="p-4 pb-6 pt-6 sm:p-6 sm:pb-8 sm:pt-8">
<BreadcrumbWrapper />
<main
id="main-content"
className={pathname === "/" ? "site-main" : "site-main interior-page"}
>
{pathname !== "/" && <BreadcrumbWrapper />}
{children}
</main>
</div>
</div>
</div>
);
}
+75 -23
View File
@@ -8,6 +8,7 @@ const Linkedin = LinkedInIcon;
// TypeScript interfaces
export interface Project {
title: string;
id?: string;
description: string;
longDescription?: string;
tags: string[];
@@ -69,7 +70,7 @@ export const name = [
];
export const description =
"Computer Science and Engineering graduate pursuing a Master's at Boston University. Research interests in human-robot interaction and developing technologies that make robots better collaborators with humans.";
"Sean O'Connor is a software engineer and founder of Hadlock Technologies. Explore full-stack products, native applications, and the infrastructure behind them.";
export const researchInterests =
"My research focuses on the intersection of human-robot interaction and research infrastructure. At Bucknell, I developed HRIStudio—a web-based platform that addresses the accessibility and reproducibility challenges endemic to Wizard-of-Oz HRI studies—resulting in two first-author publications at IEEE RO-MAN and a completed honors thesis. Continuing at Boston University, I'm interested in how principled software architecture and rigorous experimental methodology can accelerate our understanding of human-robot trust, collaboration, and the design of social robots that work reliably outside the lab.";
@@ -100,7 +101,15 @@ export const educationList = [
graduated: true,
gpa: "3.71",
engineeringGpa: "3.92",
deansListSemesters: ["Fall 2022", "Fall 2023", "Spring 2024", "Fall 2024", "Spring 2025", "Fall 2025", "Spring 2026"],
deansListSemesters: [
"Fall 2022",
"Fall 2023",
"Spring 2024",
"Fall 2024",
"Spring 2025",
"Fall 2025",
"Spring 2026",
],
},
];
@@ -176,15 +185,16 @@ export const articles = [
export const experiences: Experience[] = [
{
title: "Graduate Research Assistant",
organization: "Boston University",
location: "Boston, MA",
period: "Sep 2026 May 2027",
title: "Founder & Software Engineer",
organization: "Hadlock Technologies LLC",
location: "Long Island, NY / Boston, MA",
period: "Jul 2026 Present",
description: [
"Pursuing Master's degree in Computer Engineering at Boston University",
"Research interests in human-robot interaction and robotics",
"Founded Hadlock to design, build, and operate technology for businesses, from networking and hosting to customer-facing software.",
"Built Racetix, a multi-tenant ticketing platform with Stripe Connect payments, mobile wallet tickets, and QR admission scanning.",
"Develop beenvoice to bring client management, time tracking, invoicing, and payment tracking into one workflow.",
],
type: "research",
type: "professional",
},
{
title: "Senior Honors Thesis - Extending HRIStudio to Social Robotics",
@@ -357,7 +367,8 @@ export const awards: Award[] = [
title: "Dean's List (7 semesters)",
organization: "Bucknell University",
year: 2026,
description: "Fall 2022, Fall 2023, Spring 2024, Fall 2024, Spring 2025, Fall 2025, Spring 2026",
description:
"Fall 2022, Fall 2023, Spring 2024, Fall 2024, Spring 2025, Fall 2025, Spring 2026",
},
{
title: "AIChE Mid-Atlantic Chem-E-Car Competition",
@@ -462,6 +473,40 @@ export const relevantCoursework = [
];
export const projects: Project[] = [
{
title: "Manyangles",
description:
"Shared event photo albums, from guest uploads to a moderated gallery.",
longDescription:
"Guests contribute photos through a shared link, and hosts review submissions before publishing them to the gallery. Direct-to-storage uploads, background image processing, and HEIC support handle the media pipeline; display copies strip location metadata while original files are retained.",
tags: ["Next.js", "TypeScript", "S3", "Image processing"],
websiteLink: "https://ma.hadlock.tech",
image: "/portfolio/manyangles.svg",
imageAlt: "Manyangles event photo sharing",
featured: true,
},
{
title: "hadlockCMS",
id: "hadlockcms",
description:
"A publishing platform for multiple sites, with one shared codebase.",
longDescription:
"Visual page editing, revisions, draft previews, scheduled publishing, and custom domains in a multi-tenant platform. Includes a media library, themes, forms, and roles, with atomic publishing for changes that need to go live together.",
tags: ["Multi-tenant", "Next.js", "TypeScript", "Drizzle", "Publishing"],
featured: false,
},
{
title: "Racetix",
description: "Multi-tenant ticketing, from online checkout to the gate.",
longDescription:
"Built to help a cash-only raceway sell tickets in advance for an event with upfront costs. Each track has its own catalog, with Stripe Connect payments and refunds, Apple and Google Wallet tickets, and QR admission scanning.",
tags: ["Multi-tenant", "Stripe Connect", "Mobile ticketing"],
websiteLink: "https://racetix.us",
image: "/portfolio/racetix.webp",
imageAlt: "Modified stock cars racing at Riverhead Raceway",
featured: true,
},
{
title: "Puter",
description:
@@ -496,10 +541,19 @@ export const projects: Project[] = [
"Removes the F1 Halo safety arch from onboard visor-cam footage using classical CV mask detection and neural inpainting. Final project for Image Processing and Analysis.",
longDescription:
"The Halo is a mandatory titanium arch on all F1 cars. It saves lives but cuts through the most interesting part of onboard footage. This project removes it cleanly from visor-cam video using two stages. Stage one: classical CV mask detection — Sobel-Y gradient detection for the arch edge, a robust probe-and-fit keel detector with outlier rejection and temporal jump guards, and explicit geometry construction to avoid over-masking. Stage two: two inpainting methods compared side by side — LaMa (Fast Fourier Convolution network, per-frame spatial inpainting) and RAFT optical flow with backward warp and distance-transform blending for temporal coherence across 300 frames at 60fps.",
tags: ["Python", "OpenCV", "Computer Vision", "LaMa", "RAFT", "Jupyter", "Inpainting"],
tags: [
"Python",
"OpenCV",
"Computer Vision",
"LaMa",
"RAFT",
"Jupyter",
"Inpainting",
],
gitLink: "https://github.com/soconnor0919/f1-halo-removal",
image: "/images/f1-halo-removal.webp",
imageAlt: "Side-by-side comparison of original F1 footage, LaMa spatial inpainting, and RAFT temporal inpainting across three frames",
imageAlt:
"Side-by-side comparison of original F1 footage, LaMa spatial inpainting, and RAFT temporal inpainting across three frames",
slidesUrl: "/publications/f1-halo-removal.pdf",
featured: true,
},
@@ -625,25 +679,23 @@ export const projects: Project[] = [
featured: true,
},
{
title: "BeenVoice - Professional Invoicing Platform",
title: "beenvoice",
description:
"A comprehensive business invoicing application built with the T3 stack for reliable invoice and client management.",
"Invoicing and time tracking for independent work, on web and mobile.",
longDescription:
"Developed a full-stack invoicing platform prioritizing reliability, security, and professional user experience. Features include multi-step invoice creation with flexible line items, automated PDF generation, client management with complete contact details, business profile management with branding support, and comprehensive status tracking (draft → sent → paid/overdue). The application implements proper authentication, input validation, and error handling throughout, with a mobile-first responsive design using shadcn/ui components.",
"Manage clients, track time, send recurring invoices, and follow payments across a web app and mobile companion. The Expo app includes iOS widgets, while shared domain logic and a background job worker support the product across platforms.",
tags: [
"Next.js",
"Expo",
"TypeScript",
"tRPC",
"Drizzle ORM",
"NextAuth.js",
"LibSQL",
"Tailwind CSS",
"shadcn/ui",
"PostgreSQL",
"Drizzle",
"iOS widgets",
],
gitLink: "https://github.com/soconnor0919/beenvoice",
websiteLink: "https://beenvoice.soconnor.dev",
websiteLink: "https://beenvoice.app",
image: "/images/beenvoice.webp",
imageAlt: "Screenshot of BeenVoice",
imageAlt: "beenvoice web dashboard",
featured: true,
},
{
+5 -3
View File
@@ -1,13 +1,15 @@
import { Inter, Playfair_Display } from "next/font/google";
import { Instrument_Serif, Manrope } from "next/font/google";
export const inter = Inter({
export const manrope = Manrope({
subsets: ["latin"],
variable: "--font-sans",
display: "swap",
});
export const playfair = Playfair_Display({
export const instrument = Instrument_Serif({
subsets: ["latin"],
weight: "400",
style: ["normal", "italic"],
variable: "--font-heading",
display: "swap",
});
+906 -28
View File
@@ -3,44 +3,76 @@
@tailwind utilities;
@keyframes accordion-down {
from { height: 0; }
to { height: var(--radix-accordion-content-height); }
from {
height: 0;
}
to {
height: var(--radix-accordion-content-height);
}
}
@keyframes accordion-up {
from { height: var(--radix-accordion-content-height); }
to { height: 0; }
from {
height: var(--radix-accordion-content-height);
}
to {
height: 0;
}
}
@keyframes collapsible-down {
from { height: 0; }
to { height: var(--radix-collapsible-content-height); }
from {
height: 0;
}
to {
height: var(--radix-collapsible-content-height);
}
}
@keyframes collapsible-up {
from { height: var(--radix-collapsible-content-height); }
to { height: 0; }
from {
height: var(--radix-collapsible-content-height);
}
to {
height: 0;
}
}
@keyframes slide-in-from-left {
from { transform: translateX(-100%); }
from {
transform: translateX(-100%);
}
}
@keyframes slide-in-from-right {
from { transform: translateX(100%); }
from {
transform: translateX(100%);
}
}
@keyframes slide-in-from-top {
from { transform: translateY(-100%); }
from {
transform: translateY(-100%);
}
}
@keyframes slide-in-from-bottom {
from { transform: translateY(100%); }
from {
transform: translateY(100%);
}
}
@keyframes slide-out-to-left {
to { transform: translateX(-100%); }
to {
transform: translateX(-100%);
}
}
@keyframes slide-out-to-right {
to { transform: translateX(100%); }
to {
transform: translateX(100%);
}
}
@keyframes slide-out-to-top {
to { transform: translateY(-100%); }
to {
transform: translateY(-100%);
}
}
@keyframes slide-out-to-bottom {
to { transform: translateY(100%); }
to {
transform: translateY(100%);
}
}
@layer base {
@@ -135,21 +167,867 @@
}
@keyframes fade-in-up {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.animate-fade-in-up { animation: fade-in-up 0.5s ease-out both; }
.animate-fade-in-up-delay-1 { animation: fade-in-up 0.5s ease-out 0.1s both; }
.animate-fade-in-up-delay-2 { animation: fade-in-up 0.5s ease-out 0.2s both; }
.animate-fade-in-up-delay-3 { animation: fade-in-up 0.5s ease-out 0.3s both; }
.animate-fade-in-up-delay-4 { animation: fade-in-up 0.5s ease-out 0.4s both; }
.animate-fade-in-up {
animation: fade-in-up 0.5s ease-out both;
}
.animate-fade-in-up-delay-1 {
animation: fade-in-up 0.5s ease-out 0.1s both;
}
.animate-fade-in-up-delay-2 {
animation: fade-in-up 0.5s ease-out 0.2s both;
}
.animate-fade-in-up-delay-3 {
animation: fade-in-up 0.5s ease-out 0.3s both;
}
.animate-fade-in-up-delay-4 {
animation: fade-in-up 0.5s ease-out 0.4s both;
}
@keyframes blob {
0%, 100% { transform: scale(1) translate(0px, 0px); }
25% { transform: scale(1.08) translate(40px, -30px); }
50% { transform: scale(0.94) translate(-25px, 50px); }
75% { transform: scale(1.04) translate(35px, 30px); }
0%,
100% {
transform: scale(1) translate(0px, 0px);
}
25% {
transform: scale(1.08) translate(40px, -30px);
}
50% {
transform: scale(0.94) translate(-25px, 50px);
}
75% {
transform: scale(1.04) translate(35px, 30px);
}
}
.animate-blob { animation: blob 9s ease-in-out infinite; }
.animate-blob {
animation: blob 9s ease-in-out infinite;
}
/* Personal studio */
:root {
color-scheme: light;
--background: 40 20% 98%;
--foreground: 225 15% 15%;
--card: 0 0% 100%;
--card-foreground: 225 15% 15%;
--popover: 0 0% 100%;
--popover-foreground: 225 15% 15%;
--primary: 229 76% 48%;
--primary-foreground: 0 0% 100%;
--secondary: 220 13% 94%;
--secondary-foreground: 225 15% 15%;
--muted: 220 13% 94%;
--muted-foreground: 220 7% 42%;
--accent: 225 35% 94%;
--accent-foreground: 225 15% 15%;
--border: 220 10% 86%;
--input: 220 10% 86%;
--ring: 229 76% 48%;
--studio-blue: #2849d4;
--studio-muted: #62666e;
--studio-rule: #dedfe2;
}
html {
scroll-behavior: smooth;
scroll-padding-top: 110px;
}
body {
-webkit-font-smoothing: antialiased;
}
::selection {
background: #dce3ff;
color: #142675;
}
:focus-visible {
outline: 2px solid var(--studio-blue);
outline-offset: 5px;
}
a,
button {
-webkit-tap-highlight-color: transparent;
}
.skip-link {
position: fixed;
top: 10px;
left: 10px;
transform: translateY(-160%);
background: white;
color: var(--studio-blue);
padding: 12px 20px;
z-index: 100;
}
.skip-link:focus {
transform: none;
}
.site-header {
position: sticky;
top: 0;
z-index: 60;
background: #faf9f7ed;
backdrop-filter: blur(18px);
}
.nav-inner {
max-width: 1160px;
padding: 0 40px;
margin: auto;
height: 90px;
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 1px solid var(--studio-rule);
}
.wordmark {
display: inline-flex;
align-items: center;
gap: 15px;
font-size: 17px;
font-weight: 550;
letter-spacing: -0.035em;
}
.monogram {
width: 52px;
height: 52px;
flex-shrink: 0;
}
.accent-text {
color: var(--studio-blue);
}
.wordmark-name {
font-size: 14px;
}
.desktop-nav {
display: flex;
align-items: center;
gap: 27px;
font-size: 13px;
color: var(--studio-muted);
}
.desktop-nav a:hover,
.desktop-nav a[aria-current] {
color: var(--studio-blue);
}
.nav-contact {
display: flex;
gap: 8px;
align-items: center;
color: var(--studio-blue);
}
.mobile-toggle {
display: none;
padding: 12px;
}
.mobile-menu {
padding: 10px 24px 24px;
border-bottom: 1px solid var(--studio-rule);
}
.mobile-menu a {
display: flex;
align-items: center;
gap: 10px;
padding: 13px 0;
}
.site-main {
flex: 1;
min-width: 0;
}
.studio-home {
max-width: 1160px;
margin: auto;
padding: 0 40px;
}
.studio-intro {
padding: 68px 0 77px;
}
.intro-topline {
display: flex;
justify-content: space-between;
font-size: 12px;
color: var(--studio-muted);
}
.intro-main {
display: grid;
grid-template-columns: 1fr 180px;
align-items: center;
gap: 60px;
margin-top: 32px;
}
.intro-main h1 {
font-size: clamp(48px, 6.4vw, 79px);
font-weight: 500;
letter-spacing: -0.075em;
line-height: 1.1;
margin-bottom: 27px;
}
.intro-main h1 span {
color: var(--studio-blue);
}
.intro-statement {
font-size: clamp(21px, 2.4vw, 28px);
line-height: 1.5;
letter-spacing: -0.04em;
max-width: 650px;
}
.intro-statement a {
color: var(--studio-blue);
text-decoration: underline;
text-decoration-color: #bcc7f2;
text-underline-offset: 5px;
text-decoration-thickness: 1px;
}
.intro-detail {
font-size: 16px;
line-height: 1.8;
color: var(--studio-muted);
max-width: 550px;
margin-top: 15px;
}
.intro-portrait {
width: 180px;
height: 215px;
object-fit: cover;
object-position: center;
border-radius: 8px;
transform: rotate(3deg);
box-shadow:
0 0 0 7px white,
0 8px 30px #20284010;
}
.intro-links {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 25px;
margin-top: 27px;
font-size: 13px;
}
.intro-links a,
.studio-link {
display: inline-flex;
align-items: center;
gap: 7px;
}
.intro-links a:hover,
.studio-link:hover {
color: var(--studio-blue);
}
.studio-section-heading {
display: flex;
align-items: center;
justify-content: space-between;
border-top: 1px solid var(--studio-rule);
padding: 25px 0 30px;
gap: 20px;
}
.studio-section-heading h2 {
font-size: 18px;
font-weight: 550;
letter-spacing: -0.035em;
}
.studio-section-heading h2 span {
font-size: 11px;
color: var(--studio-muted);
vertical-align: super;
margin-left: 5px;
font-weight: 400;
}
.studio-section-heading > a {
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 13px;
color: var(--studio-muted);
}
.studio-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 48px 30px;
}
.studio-project {
min-width: 0;
scroll-margin-top: 115px;
}
.studio-project-image {
position: relative;
display: block;
aspect-ratio: 1.48;
overflow: hidden;
border-radius: 7px;
background: #e8ecf5;
}
.studio-project-image > img {
object-fit: contain;
width: 88% !important;
height: 86% !important;
margin: auto;
transition: transform 0.4s ease;
}
.studio-project-image:hover > img {
transform: scale(1.035);
}
.puter .studio-project-image {
background: #e1e7f2;
}
.beenvoice .studio-project-image {
background: #e8eddf;
}
.hristudio .studio-project-image {
background: #e3edea;
}
.racetix .studio-project-image > img {
width: 100% !important;
height: 100% !important;
object-fit: cover;
}
.studio-racetix-logo {
position: absolute;
inset: 0;
display: grid;
place-items: center;
background: #0004;
}
.studio-racetix-logo img {
width: 43%;
height: auto;
}
.studio-project-title {
display: flex;
align-items: center;
justify-content: space-between;
gap: 15px;
margin: 20px 0 11px;
}
.studio-project-title h3 {
font-size: 23px;
font-weight: 550;
letter-spacing: -0.045em;
}
.studio-project-title h3 a {
display: inline-flex;
align-items: center;
gap: 8px;
}
.studio-project-title h3 svg {
color: var(--studio-muted);
}
.studio-project-title > span {
font-size: 11px;
color: var(--studio-muted);
}
.studio-project > p {
font-size: 14px;
line-height: 1.8;
color: var(--studio-muted);
max-width: 480px;
}
.studio-stack {
font-size: 11px;
color: #737780;
margin-top: 17px;
}
.studio-hadlock {
display: grid;
grid-template-columns: 1.25fr 1fr;
gap: 55px;
align-items: center;
background: #edf0f7;
padding: 38px;
border-radius: 8px;
margin: 76px 0;
}
.studio-label {
font-size: 11px;
color: var(--studio-muted);
letter-spacing: 0.08em;
text-transform: uppercase;
}
.studio-hadlock h2 {
font-size: 28px;
font-weight: 500;
letter-spacing: -0.05em;
margin: 15px 0;
}
.studio-hadlock p:not(.studio-label) {
color: var(--studio-muted);
font-size: 14px;
line-height: 1.9;
}
.studio-hadlock > img {
width: 100%;
height: auto;
aspect-ratio: 1.35;
object-fit: cover;
border-radius: 5px;
}
.studio-link {
font-size: 13px;
color: var(--studio-blue);
margin-top: 20px;
}
.studio-about {
display: grid;
grid-template-columns: 0.85fr 1.35fr;
gap: 75px;
padding: 0 0 65px;
}
.studio-about h2 {
font-size: 32px;
letter-spacing: -0.045em;
line-height: 1.3;
margin-top: 18px;
font-weight: 450;
}
.studio-about > div > p:not(.studio-label) {
color: var(--studio-muted);
font-size: 15px;
line-height: 1.9;
margin-bottom: 17px;
}
.studio-contact {
display: flex;
align-items: center;
justify-content: space-between;
gap: 30px;
border-top: 1px solid var(--studio-rule);
padding: 40px 0 60px;
}
.studio-contact h2 {
font-size: 29px;
letter-spacing: -0.05em;
margin-top: 13px;
}
.studio-contact > a {
display: inline-flex;
align-items: center;
gap: 12px;
color: var(--studio-blue);
font-size: 20px;
letter-spacing: -0.03em;
}
.site-footer {
width: 100%;
max-width: 1160px;
margin: auto;
padding: 0 40px 25px;
}
.footer-top {
border-top: 1px solid var(--studio-rule);
padding: 25px 0;
display: flex;
align-items: center;
justify-content: space-between;
gap: 25px;
}
.footer-links {
display: flex;
flex-wrap: wrap;
gap: 17px;
font-size: 11px;
color: var(--studio-muted);
}
.footer-bottom {
display: flex;
justify-content: space-between;
gap: 20px;
font-size: 10px;
color: var(--studio-muted);
}
.interior-page {
max-width: 1160px;
width: 100%;
margin: auto;
padding: 40px 40px 80px;
}
.archive-header {
padding: 25px 0 40px;
}
.eyebrow {
color: var(--studio-muted);
font-size: 12px;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.archive-header h1 {
font-size: clamp(40px, 5vw, 64px);
line-height: 1.1;
letter-spacing: -0.06em;
margin: 20px 0;
}
.archive-header > p:last-child {
color: var(--studio-muted);
max-width: 620px;
font-size: 16px;
line-height: 1.8;
}
.archive-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 25px;
}
.archive-card {
border: 1px solid var(--studio-rule);
border-radius: 8px;
overflow: hidden;
background: white;
display: flex;
flex-direction: column;
}
.archive-image {
aspect-ratio: 1.7;
position: relative;
overflow: hidden;
background: #e8ecf1;
}
.archive-image img {
object-fit: cover;
object-position: top;
}
.archive-body {
padding: 26px;
display: flex;
flex-direction: column;
flex: 1;
}
.archive-body h2 {
font-size: 23px;
letter-spacing: -0.04em;
line-height: 1.2;
}
.archive-body p {
color: var(--studio-muted);
font-size: 14px;
line-height: 1.85;
margin-top: 16px;
}
.project-tags {
display: flex;
flex-wrap: wrap;
gap: 6px;
margin-top: 18px;
}
.project-tags span {
font-size: 11px;
border: 1px solid var(--studio-rule);
border-radius: 4px;
padding: 4px 8px;
}
.archive-links {
display: flex;
flex-wrap: wrap;
gap: 18px;
margin-top: auto;
padding-top: 23px;
}
.text-link {
display: inline-flex;
align-items: center;
gap: 7px;
font-size: 13px;
color: var(--studio-blue);
}
@media (max-width: 900px) {
.intro-main {
grid-template-columns: 1fr 140px;
gap: 30px;
}
.intro-portrait {
width: 140px;
height: 180px;
}
.desktop-nav {
gap: 18px;
}
.studio-hadlock {
gap: 30px;
padding: 28px;
}
.studio-about {
gap: 40px;
}
}
@media (max-width: 700px) {
.studio-home,
.site-footer {
padding-left: 23px;
padding-right: 23px;
}
.nav-inner {
height: 75px;
padding: 0 23px;
}
.desktop-nav {
display: none;
}
.mobile-toggle {
display: block;
}
.studio-intro {
padding: 37px 0 44px;
}
.intro-topline {
font-size: 10px;
}
.intro-main {
display: block;
margin-top: 28px;
position: relative;
}
.intro-main h1 {
font-size: clamp(43px, 10vw, 65px);
margin-bottom: 24px;
}
.intro-statement {
font-size: 24px;
max-width: 450px;
}
.intro-detail {
font-size: 15px;
}
.intro-portrait {
width: 72px;
height: 84px;
margin-top: 29px;
border-radius: 5px;
}
.intro-links {
gap: 22px;
font-size: 13px;
}
.studio-grid,
.archive-grid {
grid-template-columns: 1fr;
gap: 36px;
}
.studio-project > p {
font-size: 15px;
}
.studio-project-title > span {
font-size: 10px;
}
.studio-hadlock {
grid-template-columns: 1fr;
margin: 48px 0;
padding: 26px;
gap: 28px;
}
.studio-hadlock > img {
aspect-ratio: 1.7;
}
.studio-about {
grid-template-columns: 1fr;
gap: 25px;
padding-bottom: 35px;
}
.studio-about h2 {
font-size: 30px;
}
.studio-contact {
align-items: flex-start;
flex-direction: column;
padding: 32px 0 40px;
gap: 22px;
}
.studio-contact > a {
font-size: 19px;
}
.footer-top {
align-items: flex-start;
flex-direction: column;
gap: 20px;
}
.footer-links {
gap: 14px;
}
.footer-bottom {
flex-wrap: wrap;
font-size: 9px;
gap: 9px;
}
.interior-page {
padding: 30px 23px 60px;
}
}
@media (prefers-reduced-motion: reduce) {
html {
scroll-behavior: auto;
}
*,
*::before,
*::after {
animation: none !important;
transition: none !important;
}
}
/* Product spotlights and personal experiments */
.beenvoice .studio-project-image {
background: #dce5df;
}
.manyangles .studio-project-image > img {
width: 100% !important;
height: 100% !important;
object-fit: cover;
}
.studio-cms {
border-top: 1px solid #cdd3e0;
margin-top: 24px;
padding-top: 20px;
}
.studio-cms h3 {
font-size: 18px;
font-weight: 550;
margin-bottom: 8px;
letter-spacing: -0.035em;
}
.studio-cms .studio-link {
margin-top: 12px;
}
.archive-card[id] {
scroll-margin-top: 115px;
}
/* Studio identity: expressive display type, quiet working typography. */
.wordmark-name {
font-size: 13px;
font-weight: 600;
letter-spacing: -0.025em;
}
.intro-main h1,
.archive-header h1,
.studio-about h2,
.studio-contact h2 {
font-family: var(--font-heading), Georgia, serif;
font-weight: 400;
letter-spacing: -0.035em;
}
.intro-main h1 {
font-size: clamp(62px, 7.4vw, 96px);
line-height: 1.02;
}
.intro-statement {
font-size: clamp(20px, 2.1vw, 25px);
letter-spacing: -0.035em;
}
.studio-about h2 {
font-size: 43px;
line-height: 1.12;
}
.studio-contact h2 {
font-size: 42px;
}
.studio-project-title h3,
.studio-hadlock h2 {
letter-spacing: -0.045em;
font-weight: 600;
}
.coffee-link {
color: var(--studio-blue);
text-decoration: underline;
text-decoration-color: #bcc7f2;
text-underline-offset: 4px;
}
.coffee-link:hover {
text-decoration-color: currentColor;
}
@media (max-width: 700px) {
.intro-main h1 {
font-size: clamp(47px, 12vw, 76px);
letter-spacing: -0.04em;
}
.studio-about h2,
.studio-contact h2 {
font-size: 36px;
}
.monogram {
width: 46px;
height: 46px;
}
}
/* Apple product bezels: screenshots sit beneath the transparent screen apertures. */
.beenvoice-devices {
position: absolute;
inset: 0;
overflow: hidden;
background: #dce5df;
}
.product-device {
position: absolute;
filter: drop-shadow(0 10px 12px #182b2526);
}
.product-ipad {
width: 63%;
aspect-ratio: 2300 / 3000;
top: 9%;
left: 9%;
transform: rotate(-6deg);
}
.product-iphone {
width: 28%;
aspect-ratio: 1350 / 2760;
top: 22%;
right: 8%;
transform: rotate(7deg);
}
.product-device .device-screen {
object-fit: fill;
margin: 0;
}
.product-ipad .device-screen {
left: 5.13% !important;
top: 4.13% !important;
width: 89.74% !important;
height: 91.74% !important;
border-radius: 4% / 3%;
}
.product-iphone .device-screen {
left: 5.33% !important;
top: 2.5% !important;
width: 89.34% !important;
height: 95% !important;
border-radius: 13% / 6%;
}
.product-device .device-bezel {
z-index: 1;
object-fit: contain;
}
.manyangles-preview {
position: absolute;
inset: 0;
overflow: hidden;
}
.manyangles-preview > img {
object-fit: cover;
object-position: 50% 40%;
}
.manyangles-lockup {
position: absolute;
inset: 0;
display: flex;
align-items: flex-end;
gap: 10px;
padding: 6%;
color: white;
background: linear-gradient(transparent 45%, #101e33b3);
}
.manyangles-lockup svg {
width: 9%;
max-width: 42px;
flex-shrink: 0;
}
.manyangles-lockup span {
font-weight: 700;
font-variation-settings: "SHRP" 70;
font-size: clamp(24px, 3vw, 36px);
letter-spacing: -0.04em;
line-height: 1.1;
}