diff --git a/Features/Dashboard/Dashboard.razor.css b/Features/Dashboard/Dashboard.razor.css
new file mode 100644
index 0000000..ab19ff0
--- /dev/null
+++ b/Features/Dashboard/Dashboard.razor.css
@@ -0,0 +1,69 @@
+/*
+ * Dashboard.
+ *
+ * Bootstrap's grid already stacks `.col-md-6` on a phone, so the layout
+ * needs no help. What it does need is for the two headline figures to
+ * read as figures rather than as body copy, and for the disclosure rows
+ * at the bottom to look like the grouped-list rows they behave as.
+ */
+
+.row {
+ --bs-gutter-x: 0.75rem;
+}
+
+/* The numbers are the point of this page. Tight, heavy and tabular, so
+ the net worth and the salary range line up with each other. */
+.display-6 {
+ font-family: var(--font-numeric);
+ font-size: clamp(1.875rem, 8.5vw, 2.375rem);
+ font-weight: 700;
+ font-variant-numeric: tabular-nums;
+ letter-spacing: -0.03em;
+ line-height: 1.05;
+}
+
+/*
+ * `.card-title` is small uppercase everywhere else, which is right for
+ * the eyebrow over a figure but wrong for the two disclosure buttons -
+ * they carry `.h5` as well and are headings in their own right.
+ */
+.card-title.h5 {
+ color: var(--text);
+ font-size: 1.0625rem;
+ font-weight: 600;
+ letter-spacing: -0.01em;
+ text-transform: none;
+}
+
+.card-body .btn-link {
+ width: 100%;
+ justify-content: flex-start;
+ text-align: left;
+}
+
+.card-text {
+ color: var(--text-muted);
+ font-size: 0.9375rem;
+}
+
+/* Full-width actions on a phone; back to their own width once there is
+ room for them to sit side by side. */
+.card-body > .btn,
+.card-body ::deep > .btn {
+ width: 100%;
+}
+
+@media (min-width: 48rem) {
+ .row {
+ --bs-gutter-x: 1.5rem;
+ }
+
+ .display-6 {
+ font-size: clamp(2rem, 3vw, 2.5rem);
+ }
+
+ .card-body > .btn,
+ .card-body ::deep > .btn {
+ width: auto;
+ }
+}
diff --git a/Features/PhysicalAssets/Inventory.razor b/Features/PhysicalAssets/Inventory.razor
index 77c65dd..02242e3 100644
--- a/Features/PhysicalAssets/Inventory.razor
+++ b/Features/PhysicalAssets/Inventory.razor
@@ -12,7 +12,5 @@
+ Add item
-
+
diff --git a/Features/PhysicalAssets/InventoryList.razor b/Features/PhysicalAssets/InventoryList.razor
index c87666d..f88c99b 100644
--- a/Features/PhysicalAssets/InventoryList.razor
+++ b/Features/PhysicalAssets/InventoryList.razor
@@ -13,13 +13,14 @@
}
else
{
-
- Total value: $@_assets.Sum(a => a.CurrentValuation ?? 0).ToString("N2")
+
+ Total value
+ $@_assets.Sum(a => a.CurrentValuation ?? 0).ToString("N2")
@if (_assets.Any(a => a.CurrentValuation is null))
{
- (some items have no valuation yet)
+ (some items have no valuation yet)
}
-
+
@if (_assets.Count == 0)
{
diff --git a/Features/PhysicalAssets/InventoryList.razor.css b/Features/PhysicalAssets/InventoryList.razor.css
index 877fba9..5b9a364 100644
--- a/Features/PhysicalAssets/InventoryList.razor.css
+++ b/Features/PhysicalAssets/InventoryList.razor.css
@@ -1,79 +1,167 @@
+/*
+ * Inventory.
+ *
+ * Total at the top, items underneath: the running value is the thing you
+ * came to see, and the list is how you change it.
+ *
+ * The rows are an iOS grouped list - one glass pane, hairline separators,
+ * tap anywhere on a row to expand it. Mobile first, with a desktop
+ * breakpoint at 48rem to match the rest of the app.
+ */
+
+/* ===================================================================
+ * Running total
+ * =================================================================== */
+
+.asset-total {
+ margin-bottom: 1rem;
+ padding: 1rem 1.125rem;
+ border: 1px solid var(--glass-border);
+ border-radius: var(--radius-lg);
+ background-color: var(--glass-bg);
+ background-image: radial-gradient(
+ 16rem 9rem at 92% -30%,
+ color-mix(in srgb, var(--accent) 22%, transparent),
+ transparent 70%
+ );
+ backdrop-filter: var(--glass-blur);
+ -webkit-backdrop-filter: var(--glass-blur);
+ box-shadow: var(--shadow-2);
+}
+
+.asset-total-label {
+ display: block;
+ color: var(--text-muted);
+ font-size: 0.6875rem;
+ font-weight: 700;
+ letter-spacing: 0.12em;
+ text-transform: uppercase;
+}
+
+.asset-total-amount {
+ display: block;
+ margin-top: 0.15rem;
+ color: var(--text);
+ font-family: var(--font-numeric);
+ font-size: clamp(1.875rem, 9vw, 2.5rem);
+ font-weight: 700;
+ font-variant-numeric: tabular-nums;
+ letter-spacing: -0.03em;
+ line-height: 1.05;
+}
+
+.asset-total-note {
+ display: block;
+ margin-top: 0.3rem;
+ font-size: 0.8125rem;
+}
+
+/* ===================================================================
+ * The list
+ * =================================================================== */
+
.asset-list {
list-style: none;
margin: 0 0 1.5rem;
padding: 0;
- border: 1px solid var(--border);
- border-radius: 0.7rem;
overflow: hidden;
+ border: 1px solid var(--glass-border);
+ border-radius: var(--radius-lg);
+ background: var(--glass-bg);
+ backdrop-filter: var(--glass-blur);
+ -webkit-backdrop-filter: var(--glass-blur);
+ box-shadow: var(--shadow-2);
}
.asset-row {
display: grid;
- grid-template-columns: minmax(0, 1fr) auto;
+ grid-template-columns: minmax(0, 1fr);
align-items: start;
- border-bottom: 1px solid var(--border-subtle);
- background: var(--surface);
+ border-bottom: 1px solid var(--glass-hairline);
}
.asset-row:last-child {
border-bottom: none;
}
+/*
+ * Name and value on the first line, category and provenance on the
+ * second, with the caret spanning both. Laid out on a grid rather than
+ * left to wrap, so a long item name cannot push the value onto its own
+ * line on a narrow screen.
+ */
.asset-row-summary {
- display: flex;
- flex-wrap: wrap;
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) auto auto;
+ grid-template-areas:
+ "name value caret"
+ "meta meta caret";
align-items: center;
- min-width: 0;
+ gap: 0.15rem 0.75rem;
width: 100%;
- padding: 0.9rem 1rem;
- gap: 0.75rem;
+ min-width: 0;
+ padding: 0.875rem 1rem;
border: 0;
background: none;
color: inherit;
+ font: inherit;
text-align: left;
cursor: pointer;
+ transition: background-color var(--dur-fast) var(--ease-standard);
}
-.asset-row-summary:hover {
- background: var(--surface-muted);
+.asset-row-summary:active {
+ background: var(--nav-surface-pressed);
+}
+
+@media (hover: hover) {
+ .asset-row-summary:hover {
+ background: var(--surface-muted);
+ }
}
.asset-row-name {
- flex: 1 1 auto;
+ grid-area: name;
min-width: 0;
- font-weight: 600;
overflow: hidden;
+ font-size: 0.9375rem;
+ font-weight: 600;
text-overflow: ellipsis;
white-space: nowrap;
}
.asset-row-meta {
- flex: 0 1 auto;
+ grid-area: meta;
min-width: 0;
overflow-wrap: anywhere;
color: var(--text-muted);
- font-size: 0.8rem;
+ font-size: 0.75rem;
}
.asset-row-value {
- flex: 0 1 auto;
+ grid-area: value;
min-width: 0;
- overflow-wrap: anywhere;
- font-weight: 600;
+ font-size: 0.9375rem;
+ font-weight: 700;
+ font-variant-numeric: tabular-nums;
+ letter-spacing: -0.015em;
+ white-space: nowrap;
}
.asset-row-caret {
+ grid-area: caret;
+ align-self: center;
flex: 0 0 auto;
width: 0.7rem;
height: 0.7rem;
background-color: var(--text-muted);
- mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3E%3C/svg%3E");
+ transition: transform var(--dur) var(--ease-spring);
mask-repeat: no-repeat;
mask-position: center;
- mask-size: cover;
+ mask-size: contain;
-webkit-mask-repeat: no-repeat;
-webkit-mask-position: center;
- -webkit-mask-size: cover;
+ -webkit-mask-size: contain;
}
.asset-row-summary[aria-expanded="true"] .asset-row-caret {
@@ -83,21 +171,23 @@
.asset-row-quick-actions {
display: flex;
gap: 0.35rem;
- padding: 0.6rem 0.75rem 0.6rem 0;
+ justify-self: end;
+ padding: 0 1rem 0.6rem;
}
.asset-row-detail {
grid-column: 1 / -1;
min-width: 0;
- padding: 0 1.25rem 1.25rem;
- border-top: 1px solid var(--border-subtle);
+ padding: 0 1rem 1.125rem;
+ border-top: 1px solid var(--glass-hairline);
}
.asset-detail-image {
display: block;
max-width: 240px;
margin: 1rem 0;
- border-radius: 0.5rem;
+ border-radius: var(--radius-md);
+ box-shadow: var(--shadow-1);
}
.asset-detail-facts {
@@ -106,24 +196,32 @@
.asset-detail-facts dt {
color: var(--text-muted);
- font-size: 0.75rem;
+ font-size: 0.6875rem;
+ font-weight: 600;
+ letter-spacing: 0.06em;
text-transform: uppercase;
}
.asset-detail-facts dd {
- margin: 0 0 0.5rem;
+ margin: 0.15rem 0 0.5rem;
}
.asset-detail-revalue {
margin-bottom: 1rem;
}
+.asset-row-detail h3 {
+ font-family: var(--font-display);
+ font-size: 1.0625rem;
+ font-weight: 400;
+}
+
.asset-edit-form {
grid-column: 1 / -1;
min-width: 0;
display: grid;
gap: 0.5rem;
- padding: 0.9rem 1rem;
+ padding: 0.875rem 1rem;
}
.asset-row-actions {
@@ -131,20 +229,91 @@
gap: 0.5rem;
}
-@media (min-width: 641px) {
+.asset-row-actions .btn {
+ flex: 1;
+}
+
+/* ===================================================================
+ * Add item
+ * ===================================================================
+ * The form is built from Bootstrap `.col`s, which stay equal-width at
+ * every screen size - three text fields and a button at a quarter each
+ * is unusable on a phone. Stack them until there is room.
+ */
+
+#add-item {
+ margin-top: 1.5rem;
+}
+
+#add-item h2 {
+ font-family: var(--font-display);
+ font-size: 1.25rem;
+ font-weight: 400;
+}
+
+#add-item .col,
+#add-item .col-auto {
+ flex: 0 0 100%;
+}
+
+#add-item .col-auto .btn {
+ width: 100%;
+}
+
+/* ===================================================================
+ * Desktop
+ * =================================================================== */
+
+@media (min-width: 48rem) {
+ .asset-total {
+ padding: 1.25rem 1.5rem;
+ }
+
+ .asset-row {
+ grid-template-columns: minmax(0, 1fr) auto;
+ }
+
+ .asset-row-quick-actions {
+ justify-self: auto;
+ padding: 0.6rem 0.75rem 0.6rem 0;
+ }
+
+ .asset-row-summary {
+ grid-template-columns: minmax(0, 1fr) auto auto auto;
+ grid-template-areas: "name meta value caret";
+ gap: 0.75rem;
+ padding: 0.9rem 1.25rem;
+ }
+
+ .asset-row-detail {
+ padding: 0 1.25rem 1.25rem;
+ }
+
.asset-edit-form {
grid-template-columns: 2fr 1.5fr 1.5fr 1fr auto;
align-items: center;
+ padding: 0.9rem 1.25rem;
}
-}
-@media (max-width: 640px) {
- .asset-row {
- grid-template-columns: minmax(0, 1fr);
+ .asset-row-actions .btn {
+ flex: none;
}
- .asset-row-quick-actions {
- justify-self: end;
- padding: 0 1rem 0.6rem;
+ #add-item .col {
+ flex: 1 0 0%;
}
+
+ #add-item .col-auto {
+ flex: 0 0 auto;
+ }
+
+ #add-item .col-auto .btn {
+ width: auto;
+ }
+}
+
+/* The chevron, carried over unchanged. */
+.asset-row-caret {
+ mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3E%3C/svg%3E");
+ -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3E%3C/svg%3E");
}
diff --git a/Features/PhysicalAssets/LiveCameraScanner.razor.css b/Features/PhysicalAssets/LiveCameraScanner.razor.css
index 120c315..f60d1c8 100644
--- a/Features/PhysicalAssets/LiveCameraScanner.razor.css
+++ b/Features/PhysicalAssets/LiveCameraScanner.razor.css
@@ -10,13 +10,13 @@
max-width: 720px;
margin: 0 auto;
background-color: #0b0f19;
- border-radius: 0.75rem;
+ border-radius: var(--radius-lg);
overflow: hidden;
touch-action: none;
user-select: none;
-webkit-user-select: none;
overscroll-behavior: none;
- box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
+ box-shadow: var(--shadow-3);
cursor: crosshair;
}
@@ -93,11 +93,13 @@
left: 50%;
transform: translateX(-50%);
z-index: 3;
- background-color: rgba(15, 23, 42, 0.75);
- backdrop-filter: blur(4px);
+ background-color: rgba(15, 23, 42, 0.6);
+ backdrop-filter: saturate(180%) blur(12px);
+ -webkit-backdrop-filter: saturate(180%) blur(12px);
+ border: 1px solid rgba(255, 255, 255, 0.18);
color: #f8fafc;
- padding: 0.35rem 0.75rem;
- border-radius: 9999px;
+ padding: 0.4rem 0.85rem;
+ border-radius: var(--radius-pill);
font-size: 0.85rem;
pointer-events: none;
white-space: nowrap;
@@ -125,28 +127,37 @@
.mode-toggle {
display: inline-flex;
- border-radius: 0.375rem;
- overflow: hidden;
- border: 1px solid #cbd5e1;
+ gap: 0.1875rem;
+ padding: 0.1875rem;
+ border: 1px solid var(--glass-hairline);
+ border-radius: var(--radius-pill);
+ background: var(--surface-muted);
}
.mode-btn {
- padding: 0.25rem 0.75rem;
- font-size: 0.875rem;
+ min-height: 2.25rem;
+ padding: 0.25rem 0.85rem;
border: none;
- background-color: #f8fafc;
- color: #334155;
+ border-radius: var(--radius-pill);
+ background-color: transparent;
+ color: var(--text-muted);
+ font: inherit;
+ font-size: 0.875rem;
+ font-weight: 600;
cursor: pointer;
+ transition:
+ color var(--dur-fast) var(--ease-standard),
+ background-color var(--dur) var(--ease-spring);
}
.mode-btn.active.positive {
- background-color: #10b981;
- color: #ffffff;
- font-weight: 600;
+ background-color: var(--positive);
+ color: var(--text-inverse);
+ box-shadow: var(--shadow-1);
}
.mode-btn.active.negative {
- background-color: #ef4444;
- color: #ffffff;
- font-weight: 600;
+ background-color: var(--negative);
+ color: var(--text-inverse);
+ box-shadow: var(--shadow-1);
}
diff --git a/Features/PhysicalAssets/VideoScanPicker.razor.css b/Features/PhysicalAssets/VideoScanPicker.razor.css
index 40442eb..4b777fb 100644
--- a/Features/PhysicalAssets/VideoScanPicker.razor.css
+++ b/Features/PhysicalAssets/VideoScanPicker.razor.css
@@ -3,6 +3,7 @@
width: 100%;
max-height: 280px;
margin-top: 1rem;
+ border-radius: var(--radius-md);
background: #111;
}
@@ -16,6 +17,7 @@
display: block;
max-width: 100%;
max-height: 560px;
+ border-radius: var(--radius-md);
}
.mask-overlay {
position: absolute;
@@ -33,7 +35,9 @@
fill: rgb(255 193 7 / 20%);
stroke: #ffc107;
stroke-width: 0.5;
- transition: fill 0.15s, stroke 0.15s;
+ transition:
+ fill 0.15s,
+ stroke 0.15s;
}
.item-polygon:hover {
diff --git a/wwwroot/app.css b/wwwroot/app.css
index 7691318..232e6c4 100644
--- a/wwwroot/app.css
+++ b/wwwroot/app.css
@@ -1,6 +1,70 @@
+/*
+ * Global styles.
+ *
+ * Written mobile first: the rules outside a media query describe a phone,
+ * and `@media (min-width: 48rem)` widens things out for a desktop. That
+ * breakpoint is shared with the shell (MainLayout / NavMenu), which is
+ * what flips navigation between a bottom tab bar and a side rail.
+ *
+ * Tokens come from theme.css. Nothing here should carry a literal color.
+ */
+
+/* ===================================================================
+ * Base
+ * =================================================================== */
+
+*,
+*::before,
+*::after {
+ box-sizing: border-box;
+}
+
+html {
+ /* Stop iOS inflating type when the phone is turned sideways. */
+ -webkit-text-size-adjust: 100%;
+ scroll-behavior: smooth;
+}
+
html,
body {
- font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+ font-family: var(--font-ui);
+ color: var(--text);
+}
+
+body {
+ margin: 0;
+ min-height: 100dvh;
+ background-color: var(--app-bg);
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ /* No grey flash when a finger lands on a button. */
+ -webkit-tap-highlight-color: transparent;
+}
+
+/*
+ * The ambient wash. A backdrop blur only reads as glass when there is
+ * something behind it worth blurring, so the app sits on a soft field of
+ * brand color rather than flat white. Painted on a fixed pseudo-element
+ * rather than with `background-attachment: fixed`, which stutters badly
+ * while scrolling on iOS.
+ */
+body::before {
+ content: "";
+ position: fixed;
+ inset: 0;
+ z-index: -1;
+ pointer-events: none;
+ background-image:
+ radial-gradient(60rem 36rem at 6% -14%, var(--wash-royal), transparent 62%),
+ radial-gradient(46rem 30rem at 106% 2%, var(--wash-gold), transparent 58%),
+ radial-gradient(54rem 42rem at 48% 116%, var(--wash-sky), transparent 62%);
+}
+
+h1,
+h2,
+h3,
+h4 {
+ letter-spacing: -0.015em;
}
a,
@@ -8,69 +72,82 @@ a,
color: var(--link);
}
-.btn-primary {
- color: var(--text-inverse);
- background-color: var(--button-primary-bg);
- border-color: var(--button-primary-border);
+a {
+ text-underline-offset: 0.15em;
}
-.btn:focus,
-.btn:active:focus,
-.btn-link.nav-link:focus,
-.form-control:focus,
-.form-check-input:focus {
- box-shadow:
- 0 0 0 0.1rem var(--focus-ring-inner),
- 0 0 0 0.25rem var(--focus-ring-outer);
+/* Money, counts and dates: fixed-width digits, so a column does not
+ jitter as its values change. */
+.tabular,
+.headline-amount,
+.record-amount,
+.asset-row-value,
+.display-6 {
+ font-variant-numeric: tabular-nums;
}
+/* ===================================================================
+ * Shared page chrome
+ * ===================================================================
+ * The financials page established the visual language; these primitives
+ * keep the other pages consistent without coupling their markup to it.
+ * Horizontal gutters belong to
(see MainLayout), so the wrappers
+ * below only own their vertical rhythm.
+ */
+
.content {
- padding-top: 1.1rem;
+ padding-top: 0;
}
-/* Shared page chrome for feature pages. The financials page established the
- visual language; these primitives keep the other pages consistent without
- coupling their feature-specific markup to the financials component. */
.standard-page {
- max-width: 1100px;
+ max-width: var(--content-max);
margin: 0 auto;
- padding: 2.5rem 2rem 4rem;
+ padding: 0.25rem 0 1rem;
color: var(--text);
}
.page-header {
display: flex;
- align-items: flex-end;
- justify-content: space-between;
- gap: 1rem;
- margin-bottom: 2rem;
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 0.75rem;
+ margin-bottom: 1.25rem;
}
+/* An iOS large title: big, tight, hard against the left margin. */
.page-header h1 {
- margin: 0.5rem 0 0;
- font-family: Times New Roman, serif;
- font-size: clamp(2rem, 5vw, 3.5rem);
+ margin: 0;
+ font-family: var(--font-display);
+ font-size: clamp(2rem, 8vw, 2.5rem);
font-weight: 400;
+ line-height: 1.1;
}
.page-eyebrow {
color: var(--accent-text);
- font-size: 0.7rem;
+ font-size: 0.6875rem;
font-weight: 700;
letter-spacing: 0.12em;
+ text-transform: uppercase;
}
.page-subtitle {
- margin: 0.5rem 0 0;
+ margin: 0.35rem 0 0;
color: var(--text-muted);
+ font-size: 0.9375rem;
+ line-height: 1.45;
}
+/* The glass pane every grouped section sits on. */
.page-card {
- margin-bottom: 1.25rem;
- padding: 1.25rem;
- border: 1px solid var(--border);
- border-radius: 0.7rem;
- background: var(--surface);
+ margin-bottom: 1rem;
+ padding: 1.125rem;
+ border: 1px solid var(--glass-border);
+ border-radius: var(--radius-lg);
+ background: var(--glass-bg);
+ backdrop-filter: var(--glass-blur);
+ -webkit-backdrop-filter: var(--glass-blur);
+ box-shadow: var(--shadow-2);
}
.page-card > :last-child {
@@ -78,43 +155,316 @@ a,
}
.page-section-title {
- margin: 0 0 1rem;
- font-family: Times New Roman, serif;
+ margin: 0 0 0.75rem;
+ font-family: var(--font-display);
+ font-size: 1.25rem;
font-weight: 400;
}
+/* ===================================================================
+ * Controls
+ * ===================================================================
+ * Every tappable thing clears 44px, the smallest target iOS treats as
+ * reliable, and dips slightly when pressed instead of changing color.
+ */
+
.primary-button,
.secondary-button {
display: inline-flex;
align-items: center;
- gap: 0.35rem;
- padding: 0.7rem 1rem;
- border-radius: 0.5rem;
+ justify-content: center;
+ gap: 0.4rem;
+ min-height: 2.75rem;
+ padding: 0.625rem 1.125rem;
+ border-radius: var(--radius-pill);
+ font: inherit;
+ font-size: 0.9375rem;
font-weight: 600;
text-decoration: none;
+ cursor: pointer;
+ transition:
+ transform var(--dur-fast) var(--ease-spring),
+ background-color var(--dur-fast) var(--ease-standard),
+ box-shadow var(--dur-fast) var(--ease-standard);
}
.primary-button {
- border: 1px solid var(--brand);
+ border: 1px solid transparent;
color: var(--text-inverse);
background: var(--brand);
+ box-shadow: var(--shadow-1);
+}
+
+.primary-button:hover {
+ background: var(--brand-strong);
}
.secondary-button {
- border: 1px solid var(--border);
+ border: 1px solid var(--glass-border);
color: var(--brand);
- background: var(--surface);
+ background: var(--glass-bg-strong);
+ backdrop-filter: var(--glass-blur);
+ -webkit-backdrop-filter: var(--glass-blur);
}
-@media (max-width: 700px) {
- .standard-page {
- padding: 1.5rem 1rem 3rem;
- }
+.secondary-button:hover {
+ background: var(--brand-surface);
+}
- .page-header {
- align-items: flex-start;
- flex-direction: column;
- }
+.primary-button:active,
+.secondary-button:active {
+ transform: scale(0.97);
+}
+
+/* ===================================================================
+ * Bootstrap, restyled
+ * ===================================================================
+ * Overridden through Bootstrap's own --bs-* custom properties wherever
+ * possible, so hover, active and disabled states stay coherent instead
+ * of being flattened by one hardcoded declaration.
+ */
+
+.btn {
+ --bs-btn-border-radius: var(--radius-pill);
+ --bs-btn-font-size: 0.9375rem;
+ --bs-btn-font-weight: 600;
+ --bs-btn-padding-y: 0.55rem;
+ --bs-btn-padding-x: 1.125rem;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: 0.4rem;
+ min-height: 2.75rem;
+ transition:
+ transform var(--dur-fast) var(--ease-spring),
+ background-color var(--dur-fast) var(--ease-standard),
+ border-color var(--dur-fast) var(--ease-standard),
+ color var(--dur-fast) var(--ease-standard);
+}
+
+.btn:active {
+ transform: scale(0.97);
+}
+
+.btn-sm {
+ --bs-btn-font-size: 0.875rem;
+ --bs-btn-padding-y: 0.3rem;
+ --bs-btn-padding-x: 0.8rem;
+ min-height: 2.25rem;
+}
+
+.btn-primary {
+ --bs-btn-bg: var(--button-primary-bg);
+ --bs-btn-border-color: var(--button-primary-border);
+ --bs-btn-color: var(--text-inverse);
+ --bs-btn-hover-bg: var(--brand-strong);
+ --bs-btn-hover-border-color: var(--brand-strong);
+ --bs-btn-hover-color: var(--text-inverse);
+ --bs-btn-active-bg: var(--brand-strong);
+ --bs-btn-active-border-color: var(--brand-strong);
+ --bs-btn-disabled-bg: var(--button-primary-bg);
+ --bs-btn-disabled-border-color: var(--button-primary-border);
+}
+
+.btn-outline-primary,
+.btn-outline-secondary,
+.btn-outline-success,
+.btn-outline-danger {
+ --bs-btn-bg: var(--glass-bg-strong);
+ --bs-btn-hover-color: var(--text-inverse);
+ backdrop-filter: var(--glass-blur);
+ -webkit-backdrop-filter: var(--glass-blur);
+}
+
+.btn-outline-primary {
+ --bs-btn-color: var(--brand);
+ --bs-btn-border-color: var(--border);
+ --bs-btn-hover-bg: var(--brand);
+ --bs-btn-hover-border-color: var(--brand);
+ --bs-btn-active-bg: var(--brand-strong);
+ --bs-btn-active-border-color: var(--brand-strong);
+}
+
+.btn-outline-secondary {
+ --bs-btn-color: var(--text);
+ --bs-btn-border-color: var(--border);
+ --bs-btn-hover-bg: var(--brand-strong);
+ --bs-btn-hover-border-color: var(--brand-strong);
+ --bs-btn-active-bg: var(--brand-strong);
+ --bs-btn-active-border-color: var(--brand-strong);
+}
+
+.btn-outline-success {
+ --bs-btn-color: var(--positive-text);
+ --bs-btn-border-color: color-mix(in srgb, var(--positive) 40%, transparent);
+ --bs-btn-hover-bg: var(--positive-text);
+ --bs-btn-hover-border-color: var(--positive-text);
+ --bs-btn-active-bg: var(--positive-text);
+ --bs-btn-active-border-color: var(--positive-text);
+}
+
+.btn-outline-danger {
+ --bs-btn-color: var(--negative-text);
+ --bs-btn-border-color: color-mix(in srgb, var(--negative) 40%, transparent);
+ --bs-btn-hover-bg: var(--negative-text);
+ --bs-btn-hover-border-color: var(--negative-text);
+ --bs-btn-active-bg: var(--negative-text);
+ --bs-btn-active-border-color: var(--negative-text);
+}
+
+.btn-link {
+ --bs-btn-color: var(--link);
+ --bs-btn-hover-color: var(--brand-strong);
+ min-height: 0;
+}
+
+/* Grouped cards, the iOS Settings idiom. */
+.card {
+ --bs-card-border-radius: var(--radius-lg);
+ --bs-card-inner-border-radius: var(--radius-md);
+ --bs-card-border-color: var(--glass-border);
+ --bs-card-bg: var(--glass-bg);
+ --bs-card-spacer-y: 1.125rem;
+ --bs-card-spacer-x: 1.125rem;
+ backdrop-filter: var(--glass-blur);
+ -webkit-backdrop-filter: var(--glass-blur);
+ box-shadow: var(--shadow-2);
+}
+
+.card-title {
+ font-size: 0.8125rem;
+ font-weight: 600;
+ letter-spacing: 0.06em;
+ text-transform: uppercase;
+ color: var(--text-muted);
+}
+
+/* `.border-primary` and `.border-success` are set with !important by the
+ utility, and a saturated ring around a glass pane looks like a 2015
+ admin panel. Keep the hairline; move the color to a top sheen. */
+.card.border-primary,
+.card.border-success {
+ border-color: var(--glass-border) !important;
+}
+
+.card.border-primary {
+ box-shadow:
+ var(--shadow-2),
+ inset 0 2px 0 color-mix(in srgb, var(--brand) 55%, transparent);
+}
+
+.card.border-success {
+ box-shadow:
+ var(--shadow-2),
+ inset 0 2px 0 color-mix(in srgb, var(--positive) 60%, transparent);
+}
+
+.badge {
+ --bs-badge-border-radius: var(--radius-pill);
+ --bs-badge-font-weight: 600;
+ --bs-badge-padding-y: 0.3em;
+ --bs-badge-padding-x: 0.7em;
+ letter-spacing: 0.01em;
+ vertical-align: middle;
+}
+
+/* The utility sets background-color with !important, so matching it is
+ the only way to pull these off Bootstrap's grey. */
+.badge.bg-secondary {
+ color: var(--brand);
+ background-color: var(--brand-surface) !important;
+}
+
+.alert {
+ --bs-alert-border-radius: var(--radius-md);
+ border: 1px solid var(--glass-border);
+ backdrop-filter: var(--glass-blur);
+ -webkit-backdrop-filter: var(--glass-blur);
+ box-shadow: var(--shadow-1);
+ font-size: 0.9375rem;
+}
+
+.alert-info {
+ --bs-alert-color: var(--brand-strong);
+ --bs-alert-bg: color-mix(in srgb, var(--brand-surface) 70%, transparent);
+ --bs-alert-border-color: color-mix(in srgb, var(--brand) 18%, transparent);
+}
+
+.alert-success {
+ --bs-alert-color: var(--positive-text);
+ --bs-alert-bg: color-mix(in srgb, var(--positive-surface) 80%, transparent);
+ --bs-alert-border-color: color-mix(in srgb, var(--positive) 24%, transparent);
+}
+
+.alert-danger {
+ --bs-alert-color: var(--negative-text);
+ --bs-alert-bg: color-mix(in srgb, var(--negative-surface) 80%, transparent);
+ --bs-alert-border-color: color-mix(in srgb, var(--negative) 24%, transparent);
+}
+
+.text-muted {
+ color: var(--text-muted) !important;
+}
+
+/* ===================================================================
+ * Fields
+ * =================================================================== */
+
+/*
+ * 16px is not a taste call: Safari on iOS zooms the whole page in when a
+ * focused field's text is smaller than that, and does not zoom back out.
+ * So every input stays at 1rem until the desktop breakpoint.
+ */
+.form-control,
+.form-select,
+.form-control-sm,
+.form-select-sm,
+input[type="text"],
+input[type="number"],
+input[type="date"],
+input[type="email"],
+select,
+textarea {
+ font-size: 1rem;
+}
+
+.form-control,
+.form-select {
+ --bs-border-radius: var(--radius-sm);
+ min-height: 2.75rem;
+ border-color: var(--border);
+ background-color: var(--glass-bg-strong);
+ transition:
+ border-color var(--dur-fast) var(--ease-standard),
+ box-shadow var(--dur-fast) var(--ease-standard);
+}
+
+.form-control-sm,
+.form-select-sm {
+ --bs-border-radius: var(--radius-xs);
+ min-height: 2.5rem;
+}
+
+.form-control:focus,
+.form-select:focus {
+ border-color: var(--brand);
+ background-color: var(--surface);
+}
+
+/* Keyboard users get the ring; a mouse click does not light it up. */
+.btn:focus-visible,
+.btn-link.nav-link:focus-visible,
+.form-control:focus-visible,
+.form-select:focus-visible,
+.form-check-input:focus-visible,
+a:focus-visible,
+button:focus-visible,
+summary:focus-visible,
+[tabindex]:focus-visible {
+ outline: none;
+ box-shadow:
+ 0 0 0 0.1rem var(--focus-ring-inner),
+ 0 0 0 0.25rem var(--focus-ring-outer);
}
h1:focus {
@@ -133,19 +483,6 @@ h1:focus {
color: var(--input-invalid);
}
-.blazor-error-boundary {
- background:
- url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=)
- no-repeat 1rem/1.8rem,
- var(--error-boundary-bg);
- padding: 1rem 1rem 1rem 3.7rem;
- color: var(--error-boundary-text);
-}
-
-.blazor-error-boundary::after {
- content: "An error has occurred.";
-}
-
.darker-border-checkbox.form-check-input {
border-color: var(--checkbox-border);
}
@@ -170,22 +507,95 @@ input[type="file"] {
input[type="file"]::file-selector-button {
margin-right: 0.75rem;
- padding: 0.6rem 1.1rem;
- border: 1px solid var(--brand);
- border-radius: 0.5rem;
+ min-height: 2.75rem;
+ padding: 0.55rem 1.125rem;
+ border: 1px solid transparent;
+ border-radius: var(--radius-pill);
color: var(--text-inverse);
background: var(--brand);
font-weight: 600;
cursor: pointer;
- transition: background-color 0.15s ease, border-color 0.15s ease;
+ transition:
+ background-color var(--dur-fast) var(--ease-standard),
+ border-color var(--dur-fast) var(--ease-standard);
}
input[type="file"]::file-selector-button:hover {
background: var(--brand-strong);
- border-color: var(--brand-strong);
}
input[type="file"]:disabled::file-selector-button {
cursor: not-allowed;
opacity: 0.6;
}
+
+/* ===================================================================
+ * Blazor's built-in error UI
+ * =================================================================== */
+
+.blazor-error-boundary {
+ background:
+ url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=)
+ no-repeat 1rem/1.8rem,
+ var(--error-boundary-bg);
+ padding: 1rem 1rem 1rem 3.7rem;
+ border-radius: var(--radius-md);
+ color: var(--error-boundary-text);
+}
+
+.blazor-error-boundary::after {
+ content: "An error has occurred.";
+}
+
+/* ===================================================================
+ * Desktop
+ * =================================================================== */
+
+@media (min-width: 48rem) {
+ .page-header {
+ flex-direction: row;
+ align-items: flex-end;
+ justify-content: space-between;
+ gap: 1rem;
+ margin-bottom: 1.75rem;
+ }
+
+ .page-header h1 {
+ font-size: clamp(2.25rem, 4vw, 3.25rem);
+ }
+
+ .page-card {
+ margin-bottom: 1.25rem;
+ padding: 1.5rem;
+ }
+
+ .page-section-title {
+ font-size: 1.375rem;
+ }
+
+ /* The iOS zoom rule does not apply to a mouse, so the compact field
+ sizes can go back to being compact. */
+ .form-control-sm,
+ .form-select-sm {
+ font-size: 0.875rem;
+ min-height: 2.25rem;
+ }
+}
+
+/* ===================================================================
+ * Accessibility
+ * =================================================================== */
+
+@media (prefers-reduced-motion: reduce) {
+ html {
+ scroll-behavior: auto;
+ }
+
+ *,
+ *::before,
+ *::after {
+ animation-duration: 0.01ms !important;
+ animation-iteration-count: 1 !important;
+ transition-duration: 0.01ms !important;
+ }
+}
diff --git a/wwwroot/theme.css b/wwwroot/theme.css
index f7be3c9..e12f83f 100644
--- a/wwwroot/theme.css
+++ b/wwwroot/theme.css
@@ -128,3 +128,113 @@
--error-boundary-text: var(--pitt-white);
--error-toast-bg: var(--accent-surface);
}
+
+/*
+ * ===================================================================
+ * Surface system - iOS-style glass
+ * ===================================================================
+ * Added for the mobile-first reskin. Everything below is presentation
+ * only: tokens for type, radius, glass, elevation and motion, plus the
+ * handful of chrome tokens the nav needed once it became a frosted
+ * light surface instead of a solid navy one.
+ *
+ * The role tokens above are unchanged and still the thing components
+ * should reach for. These sit beside them.
+ */
+
+:root {
+ /* Type. The Apple system stack first so iOS and macOS get SF, with
+ * the app's previous Helvetica Neue fallbacks kept behind it. */
+ --font-ui:
+ -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI Variable Text", "Segoe UI",
+ Roboto, "Helvetica Neue", Helvetica, Arial, sans-serif;
+ /* The serif wordmark and page titles the brand already used. */
+ --font-display: "Times New Roman", Times, serif;
+ /* Tabular figures keep currency columns from shifting as they change. */
+ --font-numeric: ui-rounded, "SF Pro Rounded", var(--font-ui);
+
+ /* Corner radii. Continuous-ish curvature: containers round harder
+ * than the controls inside them. */
+ --radius-xs: 0.5rem;
+ --radius-sm: 0.75rem;
+ --radius-md: 1.125rem;
+ --radius-lg: 1.5rem;
+ --radius-pill: 999px;
+
+ /* Glass. A backdrop blur is only convincing over something worth
+ * blurring, which is what the ambient wash on is for. */
+ --glass-blur: saturate(180%) blur(20px);
+ --glass-blur-strong: saturate(190%) blur(30px);
+ --glass-bg: color-mix(in srgb, var(--pitt-white) 70%, transparent);
+ --glass-bg-strong: color-mix(in srgb, var(--pitt-white) 85%, transparent);
+ --glass-bg-solid: color-mix(in srgb, var(--pitt-white) 96%, var(--pitt-royal));
+ --glass-border: color-mix(in srgb, var(--pitt-white) 60%, transparent);
+ --glass-hairline: color-mix(in srgb, var(--pitt-royal) 14%, transparent);
+ /* The bright top edge that sells a pane as glass rather than paper. */
+ --glass-highlight: color-mix(in srgb, var(--pitt-white) 80%, transparent);
+
+ /* Elevation. Tinted toward Medium Blue rather than neutral black so
+ * shadows sit in the same color family as everything else. */
+ --shadow-1: 0 1px 2px color-mix(in srgb, var(--pitt-medium-blue) 8%, transparent);
+ --shadow-2:
+ 0 1px 2px color-mix(in srgb, var(--pitt-medium-blue) 6%, transparent),
+ 0 6px 20px color-mix(in srgb, var(--pitt-medium-blue) 10%, transparent);
+ --shadow-3:
+ 0 2px 4px color-mix(in srgb, var(--pitt-medium-blue) 6%, transparent),
+ 0 16px 48px color-mix(in srgb, var(--pitt-medium-blue) 16%, transparent);
+ --shadow-bar: 0 -8px 32px color-mix(in srgb, var(--pitt-medium-blue) 10%, transparent);
+
+ /* Motion. The spring curve is the one Apple uses for sheets and
+ * pushes: quick to leave, slow to settle. */
+ --ease-spring: cubic-bezier(0.32, 0.72, 0, 1);
+ --ease-standard: cubic-bezier(0.4, 0, 0.2, 1);
+ --dur-fast: 120ms;
+ --dur: 220ms;
+ --dur-slow: 360ms;
+
+ /* Shell metrics. The safe-area insets are what keep the tab bar off
+ * the home indicator on a notched phone. */
+ --tab-bar-height: 3.25rem;
+ --safe-top: env(safe-area-inset-top, 0px);
+ --safe-bottom: env(safe-area-inset-bottom, 0px);
+ --safe-left: env(safe-area-inset-left, 0px);
+ --safe-right: env(safe-area-inset-right, 0px);
+ --nav-rail-width: 15.5rem;
+ --content-max: 60rem;
+ --header-height: 3.25rem;
+
+ /* Ambient background. Drawn as a fixed layer behind everything so
+ * the glass has something to refract. */
+ --app-bg: color-mix(in srgb, var(--pitt-new-sky) 28%, var(--pitt-white));
+ --wash-royal: color-mix(in srgb, var(--pitt-royal) 20%, transparent);
+ --wash-gold: color-mix(in srgb, var(--pitt-gold) 20%, transparent);
+ --wash-sky: color-mix(in srgb, var(--pitt-light-blue) 22%, transparent);
+
+ /* ===============================================================
+ * Chrome overrides
+ * ===============================================================
+ * The nav used to be a navy gradient with white type. It is now a
+ * frosted light surface in both layouts (bottom tab bar on a phone,
+ * side rail on a desktop), so these are re-pointed at ink-on-light
+ * values. Nothing outside the two layout stylesheets reads them.
+ */
+ --nav-scrim: transparent;
+ --nav-link: var(--text-muted);
+ --nav-link-hover: var(--brand);
+ --nav-link-active: var(--brand);
+ --nav-surface-hover: color-mix(in srgb, var(--pitt-royal) 7%, transparent);
+ --nav-surface-pressed: color-mix(in srgb, var(--pitt-royal) 12%, transparent);
+ --nav-surface-active: color-mix(in srgb, var(--pitt-royal) 11%, transparent);
+ --nav-surface-active-hover: color-mix(in srgb, var(--pitt-royal) 15%, transparent);
+ --nav-surface-active-pressed: color-mix(in srgb, var(--pitt-royal) 19%, transparent);
+ /* The gold pip under a selected tab, the one spot of accent in the bar. */
+ --nav-indicator: var(--pitt-gold);
+
+ --topbar-bg: var(--glass-bg);
+ --topbar-border: var(--glass-hairline);
+
+ /* Drawers present as iOS sheets now: they rise from the bottom on a
+ * phone and slide in from the right on a desktop. */
+ --overlay: color-mix(in srgb, var(--pitt-medium-blue) 40%, transparent);
+ --shadow-drawer: 0 -16px 48px color-mix(in srgb, var(--pitt-medium-blue) 22%, transparent);
+}