feat: add responsive professional information layer
This commit is contained in:
@@ -1,10 +1,17 @@
|
||||
<script setup lang="ts">
|
||||
import AppShell from './components/AppShell.vue';
|
||||
import type { ProductionInputs } from './content/types';
|
||||
import './styles/tokens.css';
|
||||
import './styles/base.css';
|
||||
import './styles/layout.css';
|
||||
import './styles/components.css';
|
||||
import './styles/print.css';
|
||||
|
||||
defineProps<{ inputs: Readonly<ProductionInputs> }>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AppShell :content="inputs.content" />
|
||||
<div class="portfolio-shell">
|
||||
<AppShell :content="inputs.content" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -31,13 +31,13 @@ const canonicalNavigation = computed(() => canonicalSectionIds.map((id) => ({ id
|
||||
|
||||
<template>
|
||||
<a class="skip-link" href="#portfolio-content">Skip to portfolio content</a>
|
||||
<header>
|
||||
<header class="site-header">
|
||||
<h1>{{ content.identity.name }}</h1>
|
||||
<p>{{ content.identity.role }}</p>
|
||||
<SiteNavigation :items="canonicalNavigation" />
|
||||
</header>
|
||||
|
||||
<main id="portfolio-content">
|
||||
<main id="portfolio-content" class="portfolio-main">
|
||||
<PortfolioSection id="start" :index="1" :eyebrow="sectionLabel('start').toUpperCase()" :heading="sectionLabel('start').toUpperCase()">
|
||||
<p>{{ content.identity.valueProposition }}</p>
|
||||
</PortfolioSection>
|
||||
|
||||
@@ -8,8 +8,8 @@ defineProps<{
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section :id="id" :aria-labelledby="`${id}-heading`" :data-section-index="index">
|
||||
<p>{{ String(index).padStart(2, '0') }} / {{ eyebrow }}</p>
|
||||
<section :id="id" class="portfolio-section" :aria-labelledby="`${id}-heading`" :data-section-index="index">
|
||||
<p class="route-label">{{ String(index).padStart(2, '0') }} / {{ eyebrow }}</p>
|
||||
<h2 :id="`${id}-heading`">{{ heading }}</h2>
|
||||
<slot />
|
||||
</section>
|
||||
|
||||
@@ -5,7 +5,7 @@ defineProps<{ items: ReadonlyArray<{ id: SectionId; label: string }> }>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<nav aria-label="Portfolio sections">
|
||||
<nav class="site-navigation" aria-label="Portfolio sections">
|
||||
<ul>
|
||||
<li v-for="item in items" :key="item.id">
|
||||
<a :href="`#${item.id}`">{{ item.label }}</a>
|
||||
|
||||
121
src/styles/base.css
Normal file
121
src/styles/base.css
Normal file
@@ -0,0 +1,121 @@
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
min-width: 0;
|
||||
background: var(--canvas);
|
||||
color: var(--text);
|
||||
font-family: var(--sans);
|
||||
font-size: 100%;
|
||||
line-height: 1.55;
|
||||
scroll-behavior: auto;
|
||||
text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
min-width: 0;
|
||||
min-height: 100vh;
|
||||
margin: 0;
|
||||
overflow-wrap: anywhere;
|
||||
background: var(--canvas);
|
||||
}
|
||||
|
||||
button,
|
||||
input,
|
||||
textarea,
|
||||
select {
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--cyan);
|
||||
overflow-wrap: anywhere;
|
||||
text-decoration-color: color-mix(in srgb, currentcolor 55%, transparent);
|
||||
text-underline-offset: 0.18em;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: var(--text);
|
||||
text-decoration-color: currentcolor;
|
||||
}
|
||||
|
||||
a:focus-visible,
|
||||
button:focus-visible,
|
||||
input:focus-visible,
|
||||
textarea:focus-visible,
|
||||
select:focus-visible,
|
||||
[tabindex]:focus-visible {
|
||||
outline: 3px solid var(--focus);
|
||||
outline-offset: 3px;
|
||||
}
|
||||
|
||||
::selection {
|
||||
background: var(--amber);
|
||||
color: #14100a;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
p,
|
||||
dl,
|
||||
ul,
|
||||
ol,
|
||||
figure {
|
||||
margin-block: 0;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3 {
|
||||
color: var(--text);
|
||||
font-weight: 680;
|
||||
letter-spacing: -0.025em;
|
||||
line-height: 1.08;
|
||||
text-wrap: balance;
|
||||
}
|
||||
|
||||
p,
|
||||
li,
|
||||
dd,
|
||||
dt,
|
||||
a {
|
||||
max-inline-size: 72ch;
|
||||
}
|
||||
|
||||
svg,
|
||||
img,
|
||||
video,
|
||||
canvas {
|
||||
display: block;
|
||||
max-inline-size: 100%;
|
||||
}
|
||||
|
||||
.skip-link {
|
||||
position: fixed;
|
||||
z-index: 10;
|
||||
inset: var(--space-2) auto auto var(--space-2);
|
||||
min-block-size: 44px;
|
||||
padding: var(--space-1) var(--space-2);
|
||||
transform: translateY(-160%);
|
||||
background: var(--amber);
|
||||
color: #171006;
|
||||
font-weight: 700;
|
||||
transition: transform var(--transition);
|
||||
}
|
||||
|
||||
.skip-link:focus {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
scroll-behavior: auto !important;
|
||||
transition-duration: 0.001s !important;
|
||||
animation-duration: 0.001s !important;
|
||||
animation-iteration-count: 1 !important;
|
||||
}
|
||||
}
|
||||
131
src/styles/components.css
Normal file
131
src/styles/components.css
Normal file
@@ -0,0 +1,131 @@
|
||||
.route-label {
|
||||
color: var(--cyan);
|
||||
font-family: var(--mono);
|
||||
font-size: var(--step--1);
|
||||
font-weight: 650;
|
||||
letter-spacing: 0.08em;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.portfolio-section > h2 {
|
||||
margin-block: var(--space-1) var(--space-2);
|
||||
font-size: var(--step-2);
|
||||
}
|
||||
|
||||
.portfolio-section > p:not(.route-label),
|
||||
.portfolio-section article > p,
|
||||
.portfolio-section dd {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.portfolio-section > * + *,
|
||||
.portfolio-section article > * + *,
|
||||
.portfolio-section li + li {
|
||||
margin-block-start: var(--space-2);
|
||||
}
|
||||
|
||||
.portfolio-section article {
|
||||
min-width: 0;
|
||||
margin-block-start: var(--space-3);
|
||||
padding-block-start: var(--space-3);
|
||||
border-block-start: 1px solid var(--line);
|
||||
}
|
||||
|
||||
.portfolio-section article h3,
|
||||
.portfolio-section section h3 {
|
||||
font-size: var(--step-1);
|
||||
}
|
||||
|
||||
.portfolio-section ul,
|
||||
.portfolio-section ol {
|
||||
padding-inline-start: 1.2rem;
|
||||
}
|
||||
|
||||
.portfolio-section dl {
|
||||
display: grid;
|
||||
gap: var(--space-1) var(--space-2);
|
||||
margin-block-start: var(--space-3);
|
||||
}
|
||||
|
||||
.portfolio-section dt {
|
||||
color: var(--cyan);
|
||||
font-family: var(--mono);
|
||||
font-size: var(--step--1);
|
||||
font-weight: 650;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
|
||||
.portfolio-section dd {
|
||||
min-width: 0;
|
||||
margin-inline-start: 0;
|
||||
}
|
||||
|
||||
.portfolio-section figure {
|
||||
min-width: 0;
|
||||
margin-block-start: var(--space-3);
|
||||
padding: var(--space-2);
|
||||
border: 1px solid var(--line);
|
||||
background: var(--surface-raised);
|
||||
}
|
||||
|
||||
.portfolio-section figcaption {
|
||||
margin-block-start: var(--space-1);
|
||||
color: var(--text-muted);
|
||||
font-size: var(--step--1);
|
||||
}
|
||||
|
||||
.portfolio-section svg {
|
||||
inline-size: 100%;
|
||||
block-size: auto;
|
||||
}
|
||||
|
||||
.portfolio-section svg rect {
|
||||
fill: #172432;
|
||||
stroke: var(--cyan);
|
||||
stroke-width: 2;
|
||||
}
|
||||
|
||||
.portfolio-section svg text {
|
||||
fill: var(--text);
|
||||
font-family: var(--sans);
|
||||
font-size: 19px;
|
||||
}
|
||||
|
||||
.portfolio-section > div {
|
||||
display: grid;
|
||||
gap: var(--space-3);
|
||||
margin-block-start: var(--space-3);
|
||||
}
|
||||
|
||||
.portfolio-section > div > section {
|
||||
min-width: 0;
|
||||
padding-block-start: var(--space-2);
|
||||
border-block-start: 1px solid var(--line);
|
||||
}
|
||||
|
||||
.portfolio-section > div > section ul {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--space-1);
|
||||
margin-block-start: var(--space-2);
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.portfolio-section > div > section li {
|
||||
display: inline-flex;
|
||||
gap: 0.45rem;
|
||||
align-items: center;
|
||||
min-block-size: 2rem;
|
||||
padding: 0.25rem 0.55rem;
|
||||
border: 1px solid var(--line);
|
||||
color: var(--text-muted);
|
||||
font-size: var(--step--1);
|
||||
}
|
||||
|
||||
.portfolio-section > div > section li span[aria-hidden='true'] {
|
||||
inline-size: 0.5rem;
|
||||
block-size: 0.5rem;
|
||||
border: 1px solid var(--cyan);
|
||||
border-radius: 50%;
|
||||
}
|
||||
127
src/styles/layout.css
Normal file
127
src/styles/layout.css
Normal file
@@ -0,0 +1,127 @@
|
||||
.portfolio-shell {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.site-header {
|
||||
display: grid;
|
||||
gap: var(--space-2);
|
||||
min-width: 0;
|
||||
padding: var(--space-4) 4vw var(--space-3);
|
||||
border-block-end: var(--panel-border);
|
||||
background: #0b111b;
|
||||
}
|
||||
|
||||
.site-header h1 {
|
||||
font-size: var(--step-3);
|
||||
}
|
||||
|
||||
.site-header > p:not(.route-label) {
|
||||
color: var(--text-muted);
|
||||
font-size: var(--step-1);
|
||||
}
|
||||
|
||||
.site-navigation {
|
||||
margin-block-start: var(--space-1);
|
||||
}
|
||||
|
||||
.site-navigation ul {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.25rem;
|
||||
max-inline-size: 78rem;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.site-navigation a {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
min-block-size: 44px;
|
||||
padding-inline: 0.7rem;
|
||||
color: var(--text-muted);
|
||||
font-family: var(--mono);
|
||||
font-size: var(--step--1);
|
||||
letter-spacing: 0.04em;
|
||||
text-decoration: none;
|
||||
transition: color var(--transition), background-color var(--transition);
|
||||
}
|
||||
|
||||
.site-navigation a:hover,
|
||||
.site-navigation a:focus-visible {
|
||||
background: var(--cyan-deep);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.portfolio-main {
|
||||
min-width: 0;
|
||||
padding-block: var(--space-4) var(--space-5);
|
||||
}
|
||||
|
||||
.portfolio-section {
|
||||
width: calc(100% - 2rem);
|
||||
min-width: 0;
|
||||
margin: 0 1rem var(--space-4);
|
||||
padding: var(--space-3);
|
||||
scroll-margin-block: var(--space-3);
|
||||
border: var(--panel-border);
|
||||
border-radius: 2px;
|
||||
background: var(--surface);
|
||||
box-shadow: var(--panel-shadow);
|
||||
transition: border-color var(--transition), background-color var(--transition);
|
||||
}
|
||||
|
||||
.portfolio-section:hover {
|
||||
border-color: var(--line-strong);
|
||||
}
|
||||
|
||||
.site-footer,
|
||||
.portfolio-section footer {
|
||||
display: grid;
|
||||
gap: var(--space-2);
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.site-footer {
|
||||
padding: var(--space-4) 4vw;
|
||||
border-block-start: var(--panel-border);
|
||||
background: #0b111b;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.site-header {
|
||||
padding-inline: 5vw;
|
||||
}
|
||||
|
||||
.portfolio-section {
|
||||
width: calc(50% - 3rem);
|
||||
margin-inline: 2rem 0;
|
||||
}
|
||||
|
||||
.portfolio-section:nth-child(even) {
|
||||
margin-inline-start: calc(50% + 1rem);
|
||||
}
|
||||
|
||||
.site-footer {
|
||||
padding-inline: 5vw;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1280px) {
|
||||
.site-header {
|
||||
padding-inline: 4vw;
|
||||
}
|
||||
|
||||
.portfolio-section {
|
||||
width: 42%;
|
||||
margin-inline: 4% 0;
|
||||
}
|
||||
|
||||
.portfolio-section:nth-child(even) {
|
||||
margin-inline-start: 54%;
|
||||
}
|
||||
|
||||
.site-footer {
|
||||
padding-inline: 4vw;
|
||||
}
|
||||
}
|
||||
61
src/styles/print.css
Normal file
61
src/styles/print.css
Normal file
@@ -0,0 +1,61 @@
|
||||
@media print {
|
||||
:root {
|
||||
color-scheme: light;
|
||||
--text: #121820;
|
||||
--text-muted: #374151;
|
||||
--cyan: #0e5c72;
|
||||
--surface: #fff;
|
||||
--line: #8b98a5;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
background: #fff;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.skip-link,
|
||||
.site-navigation,
|
||||
[data-decorative-world],
|
||||
[data-scene-control] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.portfolio-shell,
|
||||
.site-header,
|
||||
.portfolio-main,
|
||||
.site-footer {
|
||||
display: block;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.site-header {
|
||||
margin-block-end: 1.5rem;
|
||||
}
|
||||
|
||||
.portfolio-section {
|
||||
width: 100%;
|
||||
margin: 0 0 1.25rem;
|
||||
padding: 0 0 1.25rem;
|
||||
break-inside: avoid;
|
||||
border: 0;
|
||||
border-block-end: 1px solid var(--line);
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
a[href^='http']::after,
|
||||
a[href^='mailto:']::after {
|
||||
content: ' (' attr(href) ')';
|
||||
font-size: 0.85em;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
}
|
||||
29
src/styles/tokens.css
Normal file
29
src/styles/tokens.css
Normal file
@@ -0,0 +1,29 @@
|
||||
:root {
|
||||
color-scheme: dark;
|
||||
--canvas: #080d16;
|
||||
--surface: #101722;
|
||||
--surface-raised: #141e2b;
|
||||
--line: #314154;
|
||||
--line-strong: #4a6275;
|
||||
--text: #f2f4f5;
|
||||
--text-muted: #b5c0c9;
|
||||
--cyan: #80d9e6;
|
||||
--cyan-deep: #163b48;
|
||||
--amber: #f5b752;
|
||||
--focus: #ffd17a;
|
||||
--sans: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||
--mono: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
|
||||
--step--1: clamp(0.82rem, 0.78rem + 0.14vw, 0.92rem);
|
||||
--step-0: clamp(1rem, 0.94rem + 0.24vw, 1.15rem);
|
||||
--step-1: clamp(1.3rem, 1.14rem + 0.68vw, 1.75rem);
|
||||
--step-2: clamp(1.9rem, 1.5rem + 1.35vw, 2.85rem);
|
||||
--step-3: clamp(2.45rem, 1.82rem + 2.25vw, 4rem);
|
||||
--space-1: clamp(0.5rem, 0.45rem + 0.2vw, 0.68rem);
|
||||
--space-2: clamp(0.85rem, 0.75rem + 0.38vw, 1.15rem);
|
||||
--space-3: clamp(1.3rem, 1.1rem + 0.7vw, 1.9rem);
|
||||
--space-4: clamp(2rem, 1.65rem + 1.1vw, 3rem);
|
||||
--space-5: clamp(3.25rem, 2.5rem + 2.6vw, 6rem);
|
||||
--panel-border: 1px solid var(--line);
|
||||
--panel-shadow: 0 14px 34px rgb(0 0 0 / 18%);
|
||||
--transition: 160ms ease;
|
||||
}
|
||||
Reference in New Issue
Block a user