| 1 | /* ------------------------------------------------------------------ */ |
| 2 | /* Route boundaries and the connection banner */ |
| 3 | /* ------------------------------------------------------------------ */ |
| 4 | /* |
| 5 | * The empty / loading / error grammar itself lives in primitives.css |
| 6 | * (.empty-state, .state-block, .skeleton-bar); components/surface-state.tsx |
| 7 | * renders it. |
| 8 | * This file keeps the two containers around it. |
| 9 | */ |
| 10 | |
| 11 | /* Route-level boundaries (loading.tsx / error.tsx / not-found.tsx) sit in |
| 12 | the site container with the page rhythm, so a boundary never looks like a |
| 13 | different site. */ |
| 14 | .route-state { |
| 15 | width: var(--container); |
| 16 | margin-inline: auto; |
| 17 | padding-block: var(--space-6) var(--space-8); |
| 18 | } |
| 19 | |
| 20 | /* ---------- not found: the Codwhale poster ---------- */ |
| 21 | .not-found { |
| 22 | display: grid; |
| 23 | grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); |
| 24 | align-items: center; |
| 25 | gap: clamp(2rem, 6vw, 5.5rem); |
| 26 | max-width: 70rem; |
| 27 | margin-inline: auto; |
| 28 | padding-block: clamp(1rem, 4vw, 3rem); |
| 29 | } |
| 30 | .not-found-copy { display: grid; gap: var(--space-5); min-width: 0; } |
| 31 | .not-found-title { |
| 32 | max-width: 11ch; |
| 33 | font-size: var(--text-display-lg); |
| 34 | font-weight: 600; |
| 35 | line-height: 1.04; |
| 36 | letter-spacing: -0.03em; |
| 37 | text-wrap: balance; |
| 38 | } |
| 39 | .not-found-code { display: block; margin-bottom: 0.2em; color: var(--accent); } |
| 40 | .not-found-body { |
| 41 | color: var(--muted); |
| 42 | font-size: clamp(1.0625rem, 1rem + 0.3vw, 1.25rem); |
| 43 | line-height: 1.6; |
| 44 | white-space: pre-line; |
| 45 | } |
| 46 | /* The poster sits in the deep water: a navy plate with a hairline edge. */ |
| 47 | .not-found-frame { |
| 48 | justify-self: end; |
| 49 | width: 100%; |
| 50 | max-width: 30rem; |
| 51 | padding: clamp(0.75rem, 2vw, 1.25rem); |
| 52 | background: var(--ocean-floor); |
| 53 | border: 1px solid var(--line); |
| 54 | border-radius: var(--radius-sheet); |
| 55 | box-shadow: 0 30px 70px -30px rgb(3 10 28 / 0.7); |
| 56 | } |
| 57 | .not-found-poster { display: block; width: 100%; height: auto; border-radius: var(--radius-surface); } |
| 58 | @media (max-width: 760px) { |
| 59 | .not-found { grid-template-columns: minmax(0, 1fr); } |
| 60 | .not-found-title { max-width: 15ch; } |
| 61 | .not-found-frame { justify-self: center; } |
| 62 | } |
| 63 | |
| 64 | /* ------------------------------------------------------------------ */ |
| 65 | /* Connection banner — offline / reconnecting / degraded / restored */ |
| 66 | /* ------------------------------------------------------------------ */ |
| 67 | /* A raised sheet with a dot and a word. The dot's hue is the state |
| 68 | vocabulary (danger offline, accent reconnecting, attention degraded, live |
| 69 | restored) and the title always says it in words. No blur, no pulse. */ |
| 70 | .connection-banner { |
| 71 | position: sticky; |
| 72 | top: 0; |
| 73 | z-index: 35; |
| 74 | display: grid; |
| 75 | grid-template-columns: auto minmax(0, 1fr) auto; |
| 76 | gap: var(--space-3); |
| 77 | align-items: center; |
| 78 | width: var(--container); |
| 79 | margin: var(--space-3) auto 0; |
| 80 | padding: var(--space-3) var(--space-4); |
| 81 | border: 1px solid var(--line); |
| 82 | border-radius: var(--radius-surface); |
| 83 | background: var(--panel); |
| 84 | box-shadow: var(--shadow-popover); |
| 85 | color: var(--text); |
| 86 | } |
| 87 | |
| 88 | .connection-mark { |
| 89 | width: 0.5rem; |
| 90 | height: 0.5rem; |
| 91 | border-radius: var(--radius-pill); |
| 92 | background: currentColor; |
| 93 | color: var(--muted); |
| 94 | } |
| 95 | |
| 96 | .connection-banner-offline .connection-mark { color: var(--danger); } |
| 97 | .connection-banner-reconnecting .connection-mark { color: var(--accent); box-shadow: var(--glow); } |
| 98 | .connection-banner-degraded .connection-mark { color: var(--attention); box-shadow: var(--glow); } |
| 99 | .connection-banner-restored .connection-mark { color: var(--live); } |
| 100 | |
| 101 | @media (prefers-reduced-motion: no-preference) { |
| 102 | .connection-banner { |
| 103 | animation: mm-in var(--dur-arrive) var(--ease-out) both; |
| 104 | } |
| 105 | } |
| 106 | |
| 107 | .connection-copy { min-width: 0; } |
| 108 | .connection-title { font-size: var(--text-ui); font-weight: 500; } |
| 109 | .connection-body { margin-top: 0.125rem; color: var(--muted); font-size: var(--text-caption); line-height: 1.5; } |
| 110 | .connection-checked { color: var(--muted); } |
| 111 | |
| 112 | .connection-actions { display: flex; gap: var(--space-2); } |
| 113 | |
| 114 | /* The banner's buttons take the .btn secondary and primary paint under |
| 115 | the class names the component emits. */ |
| 116 | .connection-button { |
| 117 | display: inline-flex; |
| 118 | min-height: 2rem; |
| 119 | align-items: center; |
| 120 | padding-inline: var(--space-3); |
| 121 | border: 1px solid var(--line); |
| 122 | border-radius: var(--radius-control); |
| 123 | background: var(--panel); |
| 124 | color: var(--text); |
| 125 | font-family: var(--font-body); |
| 126 | font-size: var(--text-ui); |
| 127 | font-weight: 500; |
| 128 | transition-property: background-color, border-color, color; |
| 129 | transition-duration: var(--dur-state); |
| 130 | transition-timing-function: var(--ease-out); |
| 131 | } |
| 132 | |
| 133 | .connection-button:hover { background: var(--hover); } |
| 134 | .connection-button-primary { border-color: transparent; background: var(--brand-fill); color: var(--on-brand); font-weight: 600; } |
| 135 | .connection-button-primary:hover { background: var(--brand-fill-hover); color: var(--on-brand); } |
| 136 | .connection-button:disabled { opacity: 0.5; cursor: not-allowed; } |
| 137 | .connection-button[aria-busy="true"] { cursor: progress; } |
| 138 | |
| 139 | @media (pointer: coarse) { |
| 140 | .connection-button { min-height: 2.75rem; } |
| 141 | } |
| 142 | |
| 143 | @media (max-width: 560px) { |
| 144 | .connection-banner { |
| 145 | grid-template-columns: auto minmax(0, 1fr); |
| 146 | } |
| 147 | .connection-actions { |
| 148 | grid-column: 2; |
| 149 | } |
| 150 | } |
| 151 |