774 lines
25 KiB
CSS
774 lines
25 KiB
CSS
/* ── SELF-HOSTED FONTS ───────────────────────────────────────────────── */
|
||
@font-face { font-family: 'Manrope'; font-style: normal; font-weight: 200 800; font-display: swap; src: url('../fonts/manrope-variable.woff2') format('woff2'); }
|
||
@font-face { font-family: 'Fraunces'; font-style: normal; font-weight: 700; font-display: swap; src: url('../fonts/fraunces-700-normal.woff2') format('woff2'); }
|
||
@font-face { font-family: 'Fraunces'; font-style: normal; font-weight: 900; font-display: swap; src: url('../fonts/fraunces-900-normal.woff2') format('woff2'); }
|
||
@font-face { font-family: 'Fraunces'; font-style: italic; font-weight: 300; font-display: swap; src: url('../fonts/fraunces-300-italic.woff2') format('woff2'); }
|
||
@font-face { font-family: 'Fraunces'; font-style: italic; font-weight: 700; font-display: swap; src: url('../fonts/fraunces-700-italic.woff2') format('woff2'); }
|
||
@font-face { font-family: 'JetBrains Mono'; font-style: normal; font-weight: 400; font-display: swap; src: url('../fonts/jetbrains-mono-400.woff2') format('woff2'); }
|
||
@font-face { font-family: 'JetBrains Mono'; font-style: normal; font-weight: 600; font-display: swap; src: url('../fonts/jetbrains-mono-600.woff2') format('woff2'); }
|
||
|
||
/* ════════════════════════════════════════════════════════════════════════
|
||
base.css — Fonts, reset, page shell, and top navigation.
|
||
════════════════════════════════════════════════════════════════════════
|
||
|
||
COLOUR PALETTE (the "warm parchment & brass" theme)
|
||
──────────────────────────────────────────────────────────────────────
|
||
#f5edd6 parchment page background
|
||
#fffcf2 soft cream cards, search box, table
|
||
#fdf8ee warm cream toolbars, legend, about block
|
||
#f0e4c4 pale honey table heading row
|
||
#fef3dc warm hover buttons on hover
|
||
#fef8ea row hover table row on hover
|
||
#fff8e4 "now" row the currently-active hour
|
||
#1e1208 espresso headings + emphasis
|
||
#4a3420 warm dark body text in the table
|
||
#6b4f2a muted bronze subtle emphasis
|
||
#7a5c2a tagline brown italic intro line
|
||
#9a7d5a soft brown column toggle text
|
||
#b09870 warm grey muted labels
|
||
#c0a880 pale dust night-row text, footer
|
||
#c8922a BRASS brand accent (links, dial, focus)
|
||
#a06e18 deep brass hover state for some links
|
||
#c9b08a warm border panel borders
|
||
#d4c0a0 soft border toolbar/legend borders
|
||
#ede4cc line table row dividers
|
||
|
||
FONTS
|
||
──────────────────────────────────────────────────────────────────────
|
||
Fraunces serif display titles, day labels, hour cell
|
||
Manrope body sans-serif paragraphs, small labels
|
||
JetBrains Mono numeric monospace table data
|
||
════════════════════════════════════════════════════════════════════════ */
|
||
|
||
/* ── 1. FONTS + RESET ───────────────────────────────────────────────── */
|
||
|
||
/* Font loading is handled in index.html via a non-blocking <link rel="preload">
|
||
to avoid render-blocking. The @import has been intentionally removed. */
|
||
|
||
*,
|
||
*:before,
|
||
*:after {
|
||
box-sizing: border-box;
|
||
margin: 0;
|
||
padding: 0;
|
||
}
|
||
|
||
|
||
/* ── 2. PAGE BACKGROUND & SHELL ─────────────────────────────────────── */
|
||
|
||
body {
|
||
background: #f5edd6; /* parchment */
|
||
}
|
||
|
||
.utci-app {
|
||
font-family: Manrope, sans-serif;
|
||
color: #1e1208;
|
||
background: #f5edd6;
|
||
min-height: 100vh;
|
||
/* The side padding, as a variable: the header and the event note both
|
||
full-bleed by cancelling it, and would otherwise each need the number
|
||
restating at every breakpoint that changes it. */
|
||
--app-pad: 36px;
|
||
/* No top padding: .utci-topnav is a real header at the very top of the flow
|
||
now, and supplies its own. Was 52px, reserved for the nav when it was
|
||
absolutely positioned over this space. */
|
||
padding: 0 var(--app-pad) 32px;
|
||
position: relative;
|
||
/* overflow-x: clip (not hidden) — `clip` clips horizontal overflow
|
||
WITHOUT making this a scroll container, so `position: sticky` on
|
||
descendants (table <th>) can still stick relative to the viewport.
|
||
`hidden` would create a containing block that breaks sticky. */
|
||
overflow-x: clip;
|
||
}
|
||
|
||
|
||
/* The centred container that holds everything. */
|
||
.utci-shell {
|
||
position: relative;
|
||
z-index: 1;
|
||
margin: 0 auto;
|
||
}
|
||
|
||
|
||
/* ── 3. SITE HEADER (wordmark left, nav links right) ───────────────── */
|
||
|
||
/* Rendered as <header id="site-nav"> — the same element the static pages
|
||
(about.html et al) use — and it now carries the wordmark and tagline that
|
||
used to be repeated inside .utci-header. It sits FIRST in the flow, at the
|
||
very top of the page, with the event banner beneath it.
|
||
Full-bleed: .utci-app pads its content in by --app-pad a side, so the header
|
||
cancels that with a matching negative margin and pads itself instead — the
|
||
bar's rule reaches both page edges while its contents still line up with
|
||
the content below. */
|
||
.utci-topnav {
|
||
position: relative; /* keeps z-index working without leaving the flow */
|
||
z-index: 100;
|
||
margin: 0 calc(var(--app-pad) * -1) 18px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: flex-end; /* .nav-logo's margin-right:auto splits the row */
|
||
padding: 18px var(--app-pad);
|
||
gap: 28px;
|
||
background: #f5edd6;
|
||
border-bottom: 1px solid #e0d0a8;
|
||
}
|
||
|
||
.utci-topnav a {
|
||
font-family: Manrope, sans-serif;
|
||
font-size: 16px;
|
||
font-weight: 700;
|
||
letter-spacing: 0.06em;
|
||
text-transform: uppercase;
|
||
color: #b09870;
|
||
text-decoration: none;
|
||
border-bottom: 1px solid transparent;
|
||
padding-bottom: 2px;
|
||
transition: color 0.2s, border-color 0.2s;
|
||
}
|
||
|
||
.utci-topnav a:hover {
|
||
color: #1e1208;
|
||
border-bottom-color: #1e1208;
|
||
}
|
||
|
||
/* Desktop: nav-links renders inline as part of the header's flex row */
|
||
.utci-topnav .nav-links {
|
||
display: contents; /* participates in the header's flex layout transparently */
|
||
}
|
||
|
||
/* ── About dropdown ─────────────────────────────────────────────────── */
|
||
.utci-topnav .nav-dropdown {
|
||
position: relative;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
}
|
||
/* Invisible bridge so hover doesn't break between toggle and menu */
|
||
.utci-topnav .nav-dropdown::after {
|
||
content: '';
|
||
position: absolute;
|
||
left: 0;
|
||
right: 0;
|
||
top: 100%;
|
||
height: 4px;
|
||
}
|
||
.utci-topnav .nav-dropdown-toggle .caret {
|
||
display: inline-block;
|
||
margin-left: 4px;
|
||
font-size: 0.7em;
|
||
transition: transform 0.2s;
|
||
}
|
||
.utci-topnav .nav-dropdown:hover .nav-dropdown-toggle .caret,
|
||
.utci-topnav .nav-dropdown:focus-within .nav-dropdown-toggle .caret {
|
||
transform: rotate(180deg);
|
||
}
|
||
.utci-topnav .nav-dropdown-menu {
|
||
position: absolute;
|
||
top: calc(100% - 1px);
|
||
left: 50%;
|
||
margin-top: 0;
|
||
background: #f5edd6;
|
||
border: 1px solid #e0d0a8;
|
||
border-radius: 8px;
|
||
box-shadow: 0 8px 24px rgba(100, 70, 20, 0.12);
|
||
display: flex;
|
||
flex-direction: column;
|
||
min-width: 210px;
|
||
padding: 6px;
|
||
opacity: 0;
|
||
visibility: hidden;
|
||
transform: translateX(-50%) translateY(-4px);
|
||
transition: opacity 0.18s ease, transform 0.18s ease, visibility 0.18s ease;
|
||
z-index: 110;
|
||
}
|
||
.utci-topnav .nav-dropdown:hover .nav-dropdown-menu,
|
||
.utci-topnav .nav-dropdown:focus-within .nav-dropdown-menu {
|
||
opacity: 1;
|
||
visibility: visible;
|
||
transform: translateX(-50%) translateY(0);
|
||
}
|
||
/* The Account dropdown sits at the far right of the nav row, so a menu
|
||
centred under it (like About's) would spill past the viewport edge on
|
||
most screen widths. Right-align it to the toggle instead, growing left. */
|
||
.utci-topnav .nav-dropdown--right .nav-dropdown-menu {
|
||
left: auto;
|
||
right: 0;
|
||
transform: translateY(-4px);
|
||
}
|
||
.utci-topnav .nav-dropdown--right.nav-dropdown:hover .nav-dropdown-menu,
|
||
.utci-topnav .nav-dropdown--right.nav-dropdown:focus-within .nav-dropdown-menu {
|
||
transform: translateY(0);
|
||
}
|
||
/* Labels align to the same right edge the menu itself hangs from. */
|
||
.utci-topnav .nav-dropdown--right .nav-dropdown-menu a {
|
||
text-align: right;
|
||
}
|
||
.utci-topnav .nav-dropdown-menu a {
|
||
text-transform: none;
|
||
letter-spacing: normal;
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
color: #5a3e1a;
|
||
padding: 8px 12px;
|
||
border-radius: 6px;
|
||
border-bottom: none;
|
||
white-space: nowrap;
|
||
}
|
||
.utci-topnav .nav-dropdown-menu a:hover {
|
||
background: #ede4cc;
|
||
color: #1e1208;
|
||
border-bottom: none;
|
||
}
|
||
|
||
/* ── Event note ─────────────────────────────────────────────────────── */
|
||
/* Active cosmic/weather events shown as a strip across the content width,
|
||
directly under the site header. It is IN FLOW — it pushes the page down
|
||
instead of covering anything, which is the whole point of it not being a
|
||
flyout. One event at a time with its full message, cycling with a fade.
|
||
|
||
It used to render ABOVE the nav, which meant repositioning the nav out of
|
||
its absolute placement whenever a note was present. The header owns the top
|
||
of the page now, so the note is simply the first thing under it — on desktop
|
||
and on mobile (where the header is fixed and .utci-app pads clear of it)
|
||
alike, with no override either way. */
|
||
.event-note {
|
||
display: flex;
|
||
align-items: baseline;
|
||
gap: 11px;
|
||
padding: 10px 15px;
|
||
margin-bottom: 14px;
|
||
background: #fdf8ee;
|
||
border: 1.5px solid #c9b08a;
|
||
border-radius: 4px;
|
||
position: relative;
|
||
overflow: hidden;
|
||
animation: event-note-in 0.35s cubic-bezier(0.34, 1.25, 0.64, 1) both;
|
||
}
|
||
|
||
@keyframes event-note-in {
|
||
from { opacity: 0; transform: translateY(-5px); }
|
||
to { opacity: 1; transform: translateY(0); }
|
||
}
|
||
|
||
/* Per-event colour, kept to a hairline so the strip stays parchment —
|
||
same device as .almanac-entry-accent. */
|
||
.event-note-accent {
|
||
position: absolute;
|
||
inset: 0;
|
||
opacity: 0.75;
|
||
pointer-events: none;
|
||
/* One element, drawn as an L: a 1px strip down the left that turns and runs
|
||
along the bottom. The note's own overflow: hidden rounds the corner off
|
||
with the rest of the box. */
|
||
clip-path: polygon(
|
||
0 0,
|
||
1px 0,
|
||
1px calc(100% - 1px),
|
||
100% calc(100% - 1px),
|
||
100% 100%,
|
||
0 100%
|
||
);
|
||
}
|
||
|
||
/* Safety-relevant weather (heat, frost, storm, wind, wet) — brass emphasis.
|
||
This is the fallback: when the event carries a weather tint, app.js
|
||
overrides background/border inline with the matching day-tab colour and
|
||
adds .is-tinted (below). */
|
||
.event-note.is-priority {
|
||
background: #f0e4c4;
|
||
border-color: #c8922a;
|
||
}
|
||
|
||
/* Tinted warning banner — the hue now carries the "this is a warning" signal,
|
||
so the copy drops the brass accents for neutral dark tones that stay
|
||
readable on any of the palette's hues (orange, blue, grey). */
|
||
.event-note.is-tinted .event-note-msg {
|
||
color: #3f3222;
|
||
}
|
||
.event-note.is-tinted.is-priority .event-note-dates {
|
||
color: #56452c;
|
||
}
|
||
.event-note.is-tinted .event-note-dot {
|
||
background: rgba(0, 0, 0, 0.18);
|
||
}
|
||
.event-note.is-tinted .event-note-dot:hover {
|
||
background: rgba(0, 0, 0, 0.34);
|
||
}
|
||
.event-note.is-tinted .event-note-dot.active {
|
||
background: rgba(0, 0, 0, 0.55);
|
||
}
|
||
|
||
/* Slide stack. Every active event is rendered and every slide is placed in the
|
||
same single grid cell, so the strip's height is set by the TALLEST slide and
|
||
stays put as the rotation cycles — a one-line event followed by a message
|
||
that wraps to two lines no longer grows the banner and shunts the page. */
|
||
.event-note-stack {
|
||
display: grid;
|
||
grid-template-columns: minmax(0, 1fr);
|
||
align-items: start;
|
||
flex: 1;
|
||
min-width: 0;
|
||
}
|
||
|
||
.event-note-slide {
|
||
grid-area: 1 / 1; /* stack all slides on top of each other */
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 2px;
|
||
min-width: 0;
|
||
opacity: 0; /* only .is-active is visible */
|
||
pointer-events: none;
|
||
user-select: none; /* keeps hidden copy out of a drag-selection */
|
||
transition: opacity 0.35s ease;
|
||
}
|
||
|
||
.event-note-header {
|
||
display: flex;
|
||
align-items: baseline;
|
||
gap: 6px;
|
||
}
|
||
|
||
.event-note-body {
|
||
display: flex;
|
||
align-items: baseline;
|
||
gap: 10px;
|
||
min-width: 0;
|
||
}
|
||
.event-note-slide.is-active {
|
||
opacity: 1;
|
||
pointer-events: auto;
|
||
user-select: auto;
|
||
}
|
||
|
||
.event-note-emoji {
|
||
font-size: 16px;
|
||
line-height: 1;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.event-note-title {
|
||
font-family: Fraunces, serif;
|
||
font-style: italic;
|
||
font-weight: 700;
|
||
font-size: 14px;
|
||
color: #2a1a08;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.event-note-msg {
|
||
font-family: Manrope, sans-serif;
|
||
font-size: 12.5px;
|
||
font-weight: 500;
|
||
line-height: 1.45;
|
||
color: #6b4f2a;
|
||
min-width: 0;
|
||
}
|
||
|
||
.event-note-dates {
|
||
font-family: Manrope, sans-serif;
|
||
font-size: 10px;
|
||
font-weight: 700;
|
||
letter-spacing: 0.06em;
|
||
text-transform: uppercase;
|
||
color: #9a7d5a;
|
||
white-space: nowrap;
|
||
margin-left: auto; /* push the dates to the right-hand end */
|
||
flex-shrink: 0;
|
||
}
|
||
.event-note.is-priority .event-note-dates {
|
||
color: #a06e18;
|
||
}
|
||
|
||
.event-note-dots {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
flex-shrink: 0;
|
||
align-self: center;
|
||
}
|
||
.event-note-dot {
|
||
width: 6px;
|
||
height: 6px;
|
||
padding: 0;
|
||
border: none;
|
||
border-radius: 50%;
|
||
background: #d9c39b;
|
||
cursor: pointer;
|
||
transition: background 0.2s, transform 0.2s;
|
||
}
|
||
.event-note-dot:hover {
|
||
background: #c0a880;
|
||
}
|
||
.event-note-dot.active {
|
||
background: #c8922a;
|
||
transform: scale(1.3);
|
||
}
|
||
|
||
/* Close control — a small brass "Close ×" pill in the top-right corner. It
|
||
sits at the end of the strip's flex row rather than being absolutely
|
||
positioned, so the dates line can never run underneath it. */
|
||
.event-note-close {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
flex-shrink: 0;
|
||
align-self: flex-start;
|
||
margin: -2px -6px 0 2px; /* pull into the padding so it hugs the corner */
|
||
padding: 3px 7px;
|
||
border: 1px solid transparent;
|
||
border-radius: 999px;
|
||
background: transparent;
|
||
font-family: Manrope, sans-serif;
|
||
font-size: 10px;
|
||
font-weight: 700;
|
||
letter-spacing: 0.06em;
|
||
text-transform: uppercase;
|
||
color: #9a7d5a;
|
||
line-height: 1;
|
||
cursor: pointer;
|
||
transition: background 0.2s, color 0.2s, border-color 0.2s;
|
||
}
|
||
.event-note-close:hover {
|
||
background: rgba(200, 146, 42, 0.14);
|
||
border-color: rgba(200, 146, 42, 0.45);
|
||
color: #7a5a1e;
|
||
}
|
||
.event-note-close:focus-visible {
|
||
outline: 2px solid #c8922a;
|
||
outline-offset: 1px;
|
||
}
|
||
.event-note.is-priority .event-note-close {
|
||
color: #a06e18;
|
||
}
|
||
.event-note.is-tinted .event-note-close {
|
||
color: #56452c;
|
||
}
|
||
.event-note.is-tinted .event-note-close:hover {
|
||
background: rgba(0, 0, 0, 0.09);
|
||
border-color: rgba(0, 0, 0, 0.22);
|
||
color: #2a1a08;
|
||
}
|
||
|
||
/* Closing: the slot's height runs down to zero (measured and driven inline by
|
||
noteClose in app.js) and the note is anchored to its BOTTOM edge, so the
|
||
banner rides up behind the header's seam while everything below it rises by
|
||
the same amount at the same rate. One animated property for both movements —
|
||
a transform on the note would slide it independently of the page, and a
|
||
transform on the content below would break the fixed-position controls
|
||
living inside it. */
|
||
.event-note-slot {
|
||
/* The gap under the banner lives here rather than on the note, and is
|
||
deliberately equal to what the header gets back when .has-event-note is
|
||
dropped at the end of the close: the header's own margin-bottom (18px,
|
||
zeroed while a note is attached) plus, on a phone, the 8px .utci-app
|
||
stops padding once the note is gone. Without that, the space swapped in
|
||
at unmount showed up as a jump. */
|
||
margin-bottom: 18px;
|
||
display: grid;
|
||
grid-template-rows: 1fr; /* the row fills the slot, whatever its height */
|
||
align-items: end; /* ...and the note hangs from its bottom edge */
|
||
overflow: hidden;
|
||
}
|
||
.event-note-slot > .event-note {
|
||
min-height: 0; /* grid items floor at min-content without this */
|
||
}
|
||
/* The entry animation is fill-mode: both, so its final transform would stay
|
||
applied — cleared so it cannot offset the note against the slot. */
|
||
.event-note.is-closing {
|
||
animation: none;
|
||
pointer-events: none;
|
||
}
|
||
|
||
/* The note follows the header in the flow now, so it needs no special
|
||
positioning — only the gap the header's own margin-bottom would otherwise
|
||
have given the content it sits in front of. */
|
||
|
||
@media (max-width: 640px) {
|
||
.event-note {
|
||
align-items: flex-start;
|
||
gap: 9px;
|
||
padding: 10px 12px;
|
||
}
|
||
.event-note-dates {
|
||
margin-left: 0; /* no room to right-align on a phone */
|
||
}
|
||
/* Phone: the cross alone, pushed to the right-hand end of the row and
|
||
padded out to a ~26px touch target. */
|
||
.event-note-close {
|
||
margin: -4px -6px 0 auto;
|
||
padding: 8px;
|
||
}
|
||
.event-note-close-label {
|
||
display: none;
|
||
}
|
||
}
|
||
|
||
/* ── Note attached to the header ─────────────────────────────────────
|
||
When a note is present it hangs off the header's bottom edge rather than
|
||
floating below it as a separate panel: the header drops the gap it would
|
||
otherwise leave, and the note keeps a square top with no top border, inset
|
||
to the content edge so there is a gap either side. The header's own bottom
|
||
rule becomes the seam the strip hangs from.
|
||
|
||
Placed after the ≤640px block above deliberately — these override the
|
||
note's base padding and border at every width, mobile included. */
|
||
.has-event-note .utci-topnav {
|
||
margin-bottom: 0;
|
||
}
|
||
.has-event-note .event-note {
|
||
/* Inset to the content edge rather than full-bleed, so the strip keeps a
|
||
gap either side of it. The gap below is the slot's — see .event-note-slot. */
|
||
margin: 0;
|
||
padding: 10px 15px;
|
||
/* No top border and square top corners: the header's own bottom rule is the
|
||
seam it hangs from. A tinted note's inline borderColor still tints what is
|
||
left, since that sets the colour only and this sets style and width. */
|
||
border: 1.5px solid #c9b08a;
|
||
border-top: none;
|
||
border-radius: 0 0 4px 4px;
|
||
}
|
||
@media (max-width: 640px) {
|
||
/* Matches the two-class specificity above so the phone's tighter padding
|
||
survives the override. */
|
||
.has-event-note .event-note {
|
||
padding: 10px 12px;
|
||
}
|
||
|
||
/* 18px + the 8px of .utci-app padding-top below — see .event-note-slot. */
|
||
.event-note-slot {
|
||
margin-bottom: 26px;
|
||
}
|
||
|
||
/* The bar is position:fixed and 72px tall on a phone, and .utci-app pads 80px
|
||
clear of it — the spare 8px would show as a gap above the note. Two classes
|
||
so it outranks the single-class padding-top in ui.css whatever the order. */
|
||
.utci-app.has-event-note {
|
||
padding-top: 72px;
|
||
}
|
||
}
|
||
|
||
/* The site identity, in the header on every viewport. Was display:none here
|
||
and unhidden only on mobile, back when the big .utci-title wordmark carried
|
||
the desktop identity instead. */
|
||
.nav-logo {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: flex-start;
|
||
justify-content: center;
|
||
flex-shrink: 0;
|
||
/* Same device about.css uses on .nav-logo: one auto margin pushes everything
|
||
after it to the right-hand end, which works with .nav-links staying
|
||
display:contents. */
|
||
margin-right: auto;
|
||
}
|
||
.nav-logo-wordmark {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.05em;
|
||
/* It is the page's <h1> now, so the browser's own h1 margin and 2em size are
|
||
cleared here — the wordmark's size and weight come from .nav-logo-sun and
|
||
.nav-logo-scope, and the icon is sized in rem. */
|
||
margin: 0;
|
||
font-size: 1rem;
|
||
font-weight: inherit;
|
||
}
|
||
.nav-logo-sun,
|
||
.nav-logo-scope {
|
||
font-family: Manrope, sans-serif;
|
||
font-weight: 250;
|
||
/* 1.6rem matches .nav-logo-sun in about.css, so the wordmark is the same size on the
|
||
forecast and on every static page. The phone bar scales it back up (see the
|
||
≤640px block) where it is the centred focal point rather than a corner mark. */
|
||
font-size: 1.6rem;
|
||
color: #1e1208;
|
||
letter-spacing: -0.01em;
|
||
}
|
||
.nav-logo-icon {
|
||
width: 1.26rem; /* same 0.79 ratio to the wordmark as .nav-logo-icon */
|
||
height: 1.26rem;
|
||
color: #1e1208;
|
||
flex-shrink: 0;
|
||
}
|
||
.nav-logo-tag {
|
||
display: block;
|
||
font-family: Manrope, sans-serif;
|
||
font-style: normal;
|
||
font-weight: 600;
|
||
font-size: 10px;
|
||
letter-spacing: 0.12em;
|
||
text-transform: uppercase;
|
||
color: #7a5c2a;
|
||
margin-top: 3px;
|
||
}
|
||
|
||
/* Desktop: overlay hidden */
|
||
.utci-topnav .nav-overlay {
|
||
display: none;
|
||
}
|
||
|
||
/* ── Burger button (hidden on desktop) ─────────────────────────────── */
|
||
.utci-burger {
|
||
display: none;
|
||
flex-direction: column;
|
||
justify-content: center;
|
||
align-items: center;
|
||
gap: 5px;
|
||
width: 36px;
|
||
height: 36px;
|
||
background: none;
|
||
border: none;
|
||
cursor: pointer;
|
||
padding: 4px;
|
||
z-index: 110;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.utci-burger span {
|
||
display: block;
|
||
width: 22px;
|
||
height: 2px;
|
||
background: #7a5c2a;
|
||
border-radius: 2px;
|
||
transition: transform 0.25s ease, opacity 0.2s ease;
|
||
transform-origin: center;
|
||
}
|
||
|
||
/* ── Mobile nav drawer ──────────────────────────────────────────────── */
|
||
@media (max-width: 640px) {
|
||
.utci-app {
|
||
padding: 96px 16px 24px; /* 96px = 72px nav height + 24px breathing room */
|
||
}
|
||
|
||
/* Show wordmark in nav bar, hide it from main content */
|
||
.nav-logo {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
position: absolute;
|
||
left: 0;
|
||
right: 0;
|
||
top: 50%;
|
||
transform: translateY(-50%);
|
||
text-align: center;
|
||
pointer-events: none;
|
||
}
|
||
|
||
.nav-logo-wordmark {
|
||
display: block;
|
||
line-height: 1;
|
||
}
|
||
.nav-logo-sun,
|
||
.nav-logo-scope {
|
||
font-size: 2.1rem;
|
||
}
|
||
.nav-logo-icon {
|
||
width: 1.65rem;
|
||
height: 1.65rem;
|
||
}
|
||
|
||
/* Phone keeps the bar pinned to the viewport, with the wordmark centred in
|
||
it and the links behind the burger. The offsets are set here rather than
|
||
inherited: the desktop rule is in-flow now and carries no top/left/right,
|
||
and its full-bleed negative margin has nothing to cancel once the bar is
|
||
fixed and already edge-to-edge. */
|
||
.utci-topnav {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
margin: 0;
|
||
padding: 10px 20px;
|
||
height: 72px;
|
||
background: #f5edd6;
|
||
border-bottom: 1px solid #e0d0a8;
|
||
}
|
||
|
||
.utci-burger {
|
||
display: flex;
|
||
}
|
||
|
||
/* Hide the links by default on mobile — shown in drawer */
|
||
.utci-topnav .nav-links {
|
||
position: fixed;
|
||
top: 0;
|
||
right: -100%;
|
||
width: 220px;
|
||
height: 100vh;
|
||
background: #f5edd6;
|
||
border-left: 1px solid #e0d0a8;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: flex-start;
|
||
justify-content: flex-start;
|
||
padding: 80px 32px 32px;
|
||
gap: 28px;
|
||
transition: right 0.28s cubic-bezier(0.4, 0, 0.2, 1);
|
||
box-shadow: -8px 0 24px rgba(100,70,20,0.08);
|
||
z-index: 105;
|
||
}
|
||
|
||
.utci-topnav .nav-links.is-open {
|
||
right: 0;
|
||
}
|
||
|
||
.utci-topnav .nav-links a {
|
||
font-size: 15px;
|
||
}
|
||
|
||
/* Dropdown collapses to an indented static list inside the drawer */
|
||
.utci-topnav .nav-dropdown {
|
||
flex-direction: column;
|
||
align-items: flex-start;
|
||
width: 100%;
|
||
gap: 18px;
|
||
}
|
||
.utci-topnav .nav-dropdown-toggle .caret { display: none; }
|
||
.utci-topnav .nav-dropdown-menu {
|
||
position: static;
|
||
opacity: 1;
|
||
visibility: visible;
|
||
transform: none;
|
||
box-shadow: none;
|
||
border: none;
|
||
background: none;
|
||
padding: 0 0 0 14px;
|
||
margin-top: 0;
|
||
min-width: 0;
|
||
gap: 18px;
|
||
}
|
||
.utci-topnav .nav-dropdown-menu a { padding: 0; font-size: 14px; }
|
||
/* Right-aligned text is a desktop-popup touch (see the --right rule
|
||
above) - on mobile everything is one left-indented list, so match
|
||
About's list instead of standing out. */
|
||
.utci-topnav .nav-dropdown--right .nav-dropdown-menu a { text-align: left; }
|
||
|
||
/* Burger → X animation */
|
||
.utci-topnav.nav-open .utci-burger span:nth-child(1) {
|
||
transform: translateY(7px) rotate(45deg);
|
||
}
|
||
.utci-topnav.nav-open .utci-burger span:nth-child(2) {
|
||
opacity: 0;
|
||
transform: scaleX(0);
|
||
}
|
||
.utci-topnav.nav-open .utci-burger span:nth-child(3) {
|
||
transform: translateY(-7px) rotate(-45deg);
|
||
}
|
||
|
||
/* Overlay behind drawer */
|
||
.utci-topnav .nav-overlay {
|
||
display: block;
|
||
position: fixed;
|
||
inset: 0;
|
||
background: rgba(30,18,8,0.35);
|
||
opacity: 0;
|
||
pointer-events: none;
|
||
transition: opacity 0.25s ease;
|
||
z-index: 104;
|
||
}
|
||
|
||
.utci-topnav.nav-open .nav-overlay {
|
||
opacity: 1;
|
||
pointer-events: auto;
|
||
}
|
||
}
|