/* ── 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
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;
padding: 52px 36px 32px;
position: relative;
/* overflow-x: clip (not hidden) — `clip` clips horizontal overflow
WITHOUT making this a scroll container, so `position: sticky` on
descendants (table
) 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. TOP NAVIGATION (Home / About links in the corner) ──────────── */
.utci-topnav {
position: absolute;
top: 0;
left: 0;
right: 0;
z-index: 100;
display: flex;
align-items: center;
justify-content: flex-end;
padding: 0px 32px;
gap: 28px;
}
.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 nav flex row */
.utci-topnav .nav-links {
display: contents; /* participates in nav'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);
}
.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,
above the nav. 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.
The nav is normally position:absolute and .utci-app reserves 52px of top
padding for it. When a note is present the nav joins the flow instead so
the note can sit above it, and that padding is no longer needed. On true
mobile (≤640px) the nav is position:fixed and stays that way — there the
note simply becomes the first thing under it, so no override applies. */
.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;
left: 0;
top: 0;
bottom: 0;
width: 3px;
opacity: 0.75;
}
/* 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;
align-items: baseline;
flex-wrap: wrap;
gap: 4px 10px;
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-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);
}
/* Above true-mobile: the nav drops into the flow beneath the note. Its own
32px side padding is dropped so the links line up with the note and the
content rather than sitting 32px inside them. */
@media (min-width: 641px) {
.utci-app.has-event-note {
padding-top: 18px;
}
/* `relative`, not `static` — the nav keeps its z-index:100 that way, so
the About dropdown still paints over .utci-shell (which is z-index:1). */
.utci-app.has-event-note .utci-topnav {
position: relative;
padding-left: 0;
padding-right: 0;
}
}
@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 */
}
}
/* Nav logo — hidden on desktop, shown on mobile */
.nav-logo {
display: none;
}
.nav-logo-wordmark {
display: flex;
align-items: center;
gap: 0.05em;
}
.nav-logo-sun,
.nav-logo-scope {
font-family: Manrope, sans-serif;
font-weight: 250;
font-size: 2.1rem;
color: #1e1208;
letter-spacing: -0.01em;
}
.nav-logo-icon {
width: 1.65rem;
height: 1.65rem;
color: #1e1208;
flex-shrink: 0;
}
.nav-logo-tag {
display: block;
font-family: Manrope, sans-serif;
font-style: normal;
font-weight: 600;
font-size: 8px;
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;
}
.utci-title,
.utci-tagline {
display: none;
}
.utci-topnav {
position: fixed;
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; }
/* 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;
}
}
|