tower / cascadia / files / src / style.css
Branch cascadia · default branch · all branches
style.css
| 1 | :root { |
| 2 | --bg: #0b0d12; --panel: #131722; --line: #232a3a; |
| 3 | --fg: #dde3ee; --dim: #8892a6; --accent: #7fd4ff; --danger: #e06c6c; |
| 4 | color-scheme: dark; |
| 5 | } |
| 6 | * { box-sizing: border-box; } |
| 7 | html, body { height: 100%; margin: 0; } |
| 8 | body { |
| 9 | background: var(--bg); color: var(--fg); |
| 10 | font: 13px/1.5 system-ui, -apple-system, "Segoe UI", sans-serif; |
| 11 | } |
| 12 | #app { display: grid; grid-template-columns: 1fr 340px; height: 100%; } |
| 13 | main { position: relative; overflow: hidden; } |
| 14 | canvas { position: absolute; inset: 0; width: 100%; height: 100%; display: block; |
| 15 | cursor: crosshair; touch-action: none; } |
| 16 | #exterior, #interior { cursor: grab; } |
| 17 | #exterior:active, #interior:active { cursor: grabbing; } |
| 18 | .views { |
| 19 | position: absolute; top: 12px; left: 12px; display: flex; gap: 0; |
| 20 | border: 1px solid var(--line); border-radius: 6px; overflow: hidden; |
| 21 | background: rgba(10,13,20,0.86); |
| 22 | } |
| 23 | .views button { |
| 24 | width: auto; margin: 0; border: none; border-radius: 0; padding: 6px 14px; |
| 25 | background: transparent; font-size: 12px; |
| 26 | } |
| 27 | .views button.active { background: #1d2942; color: var(--accent); } |
| 28 | #spin { left: auto; right: 12px; bottom: auto; top: 12px; } |
| 29 | .labels3d { position: absolute; inset: 0; pointer-events: none; overflow: hidden; } |
| 30 | .labels3d span { |
| 31 | position: absolute; transform: translate(-50%, -50%); white-space: nowrap; |
| 32 | font: 11px/1.4 ui-monospace, SFMono-Regular, Menlo, monospace; |
| 33 | color: rgba(226,236,250,0.9); text-shadow: 0 0 4px #05070d, 0 0 8px #05070d; |
| 34 | letter-spacing: 0.04em; |
| 35 | } |
| 36 | .labels3d span small { display: block; color: var(--dim); letter-spacing: 0; } |
| 37 | #int-labels span:hover { color: var(--accent); } |
| 38 | |
| 39 | .overlay { |
| 40 | position: absolute; bottom: 12px; left: 12px; |
| 41 | background: rgba(10,13,20,0.86); border: 1px solid var(--line); border-radius: 6px; |
| 42 | padding: 8px 10px; font: 11px/1.6 ui-monospace, SFMono-Regular, Menlo, monospace; |
| 43 | pointer-events: none; white-space: pre; |
| 44 | } |
| 45 | #sightline { left: auto; right: 12px; } |
| 46 | .hidden { display: none !important; } |
| 47 | |
| 48 | aside { |
| 49 | background: var(--panel); border-left: 1px solid var(--line); |
| 50 | overflow-y: auto; padding: 0 14px 24px; |
| 51 | } |
| 52 | header { display: flex; align-items: center; gap: 8px; padding: 14px 0 10px; } |
| 53 | h1 { font-size: 15px; margin: 0; letter-spacing: 0.08em; text-transform: uppercase; } |
| 54 | |
| 55 | /* One section open at a time; the summary is the whole affordance. */ |
| 56 | details.sec { border-top: 1px solid var(--line); } |
| 57 | details.sec[hidden] { display: none; } |
| 58 | details.sec > summary { |
| 59 | display: flex; align-items: center; gap: 8px; cursor: pointer; |
| 60 | padding: 12px 0; list-style: none; |
| 61 | font-size: 11px; text-transform: uppercase; letter-spacing: 0.08em; color: var(--dim); |
| 62 | } |
| 63 | details.sec > summary::-webkit-details-marker { display: none; } |
| 64 | details.sec > summary::after { |
| 65 | content: ''; margin-left: auto; width: 6px; height: 6px; |
| 66 | border-right: 1.5px solid currentColor; border-bottom: 1.5px solid currentColor; |
| 67 | transform: rotate(45deg) translate(-2px, -2px); transition: transform 0.12s ease; |
| 68 | } |
| 69 | details.sec[open] > summary { color: var(--fg); } |
| 70 | details.sec[open] > summary::after { transform: rotate(-135deg) translate(-2px, -2px); } |
| 71 | details.sec > summary:hover { color: var(--fg); } |
| 72 | details.sec > :not(summary):last-child { margin-bottom: 14px; } |
| 73 | .pill { font-size: 10px; padding: 2px 7px; border-radius: 999px; |
| 74 | background: #26314a; color: var(--accent); letter-spacing: 0.06em; } |
| 75 | .dim { color: var(--dim); font-weight: 400; } |
| 76 | .hint { color: var(--dim); font-size: 11px; margin: 8px 0 14px; } |
| 77 | code { background: #0b0d12; padding: 1px 4px; border-radius: 3px; } |
| 78 | |
| 79 | .tools { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; } |
| 80 | .tools button { text-align: center; } |
| 81 | button { |
| 82 | width: 100%; margin-bottom: 6px; padding: 7px 10px; border-radius: 6px; |
| 83 | border: 1px solid var(--line); background: #1b2130; color: var(--fg); |
| 84 | font: inherit; font-size: 12px; cursor: pointer; |
| 85 | } |
| 86 | button:hover { background: #222a3d; } |
| 87 | button:disabled { opacity: 0.45; cursor: default; } |
| 88 | /* The Light menu is disabled while the clock is driving the light; say so. */ |
| 89 | select:disabled, input:disabled { opacity: 0.45; cursor: default; } |
| 90 | button:disabled:hover { background: #1b2130; } |
| 91 | button.active { border-color: var(--accent); color: var(--accent); background: #16233a; } |
| 92 | button.danger { border-color: #4a2a2a; color: var(--danger); } |
| 93 | button.danger:hover { background: #2a1a1a; } |
| 94 | |
| 95 | .list { list-style: none; margin: 0; padding: 0; max-height: 260px; overflow-y: auto; } |
| 96 | .list li { display: flex; align-items: center; gap: 8px; padding: 5px 6px; |
| 97 | border-radius: 5px; cursor: pointer; } |
| 98 | .list li:hover { background: #1b2130; } |
| 99 | .list li.sel { background: #1d2942; } |
| 100 | .list .swatch { width: 9px; height: 9px; border-radius: 50%; flex: none; } |
| 101 | .list .nm { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } |
| 102 | .list .kd { color: var(--dim); font-size: 10px; } |
| 103 | .list li.private .nm { opacity: 0.6; font-style: italic; } |
| 104 | |
| 105 | #detail p { margin: 0 0 8px; } |
| 106 | #d-name { font-size: 14px; font-weight: 600; } |
| 107 | label { display: block; margin-bottom: 9px; color: var(--dim); font-size: 11px; } |
| 108 | label.row { display: flex; align-items: center; gap: 7px; color: var(--fg); font-size: 12px; } |
| 109 | input[type=text], select, textarea { |
| 110 | width: 100%; margin-top: 3px; padding: 6px 8px; border-radius: 5px; |
| 111 | border: 1px solid var(--line); background: #0e1219; color: var(--fg); |
| 112 | font: inherit; font-size: 12px; resize: vertical; |
| 113 | } |
| 114 | input[type=range] { width: 100%; margin-top: 6px; accent-color: var(--accent); } |
| 115 | .warn { color: #e8c46a; } |
| 116 | .links { display: flex; flex-wrap: wrap; gap: 4px 12px; margin: 0 0 14px; font-size: 11px; } |
| 117 | .links a { color: var(--accent); text-decoration: none; } |
| 118 | .links a:hover { text-decoration: underline; } |
| 119 | .save { font-size: 11px; color: var(--dim); margin-bottom: 10px; } |
| 120 | .save.error { color: var(--danger); } |
| 121 | |
| 122 | /** |
| 123 | * Narrow viewports: the sidebar becomes a drawer. |
| 124 | * |
| 125 | * The desktop layout is a fixed 340 px panel beside the world. On a 390 px phone |
| 126 | * that leaves the world about fifty pixels — the 3D views rendered into a black |
| 127 | * slot and there was no way to get the panel out of the way. Below 720 px the |
| 128 | * panel comes out of the grid entirely and slides in over the world instead, so |
| 129 | * the world always has the whole screen and the panel is one tap away. |
| 130 | */ |
| 131 | #panel-toggle, #panel-close, .scrim { display: none; } |
| 132 | |
| 133 | @media (max-width: 720px) { |
| 134 | #app { grid-template-columns: 1fr; } |
| 135 | |
| 136 | aside { |
| 137 | position: fixed; top: 0; right: 0; bottom: 0; z-index: 20; |
| 138 | /* Never the full width: the strip of world left showing is what tells you |
| 139 | the panel is a drawer over the map and not a page you navigated to. */ |
| 140 | width: min(340px, 86vw); |
| 141 | transform: translateX(100%); |
| 142 | transition: transform 0.18s ease; |
| 143 | box-shadow: -12px 0 28px rgba(0, 0, 0, 0.5); |
| 144 | } |
| 145 | #app.panel-open aside { transform: translateX(0); } |
| 146 | |
| 147 | /* Opens the drawer. Sits opposite the view switcher, and goes away once the |
| 148 | drawer is open — the Close button in the panel header takes over there, in |
| 149 | normal flow, so it cannot end up floating over scrolled content. */ |
| 150 | #panel-toggle { |
| 151 | display: block; position: fixed; top: 12px; right: 12px; z-index: 21; |
| 152 | width: auto; margin: 0; padding: 6px 14px; font-size: 12px; |
| 153 | background: rgba(10, 13, 20, 0.86); |
| 154 | } |
| 155 | #app.panel-open #panel-toggle { display: none; } |
| 156 | #panel-close { display: block; width: auto; margin: 0 0 0 auto; padding: 4px 12px; } |
| 157 | |
| 158 | /* That strip of world beside the drawer is still live canvas, and in writer |
| 159 | mode a stray tap on it drops a marker. The scrim eats those and closes. */ |
| 160 | #app.panel-open .scrim { |
| 161 | display: block; position: fixed; inset: 0; z-index: 19; |
| 162 | background: rgba(5, 7, 13, 0.55); |
| 163 | } |
| 164 | |
| 165 | /* The spin readout shares the top-right corner with the toggle. */ |
| 166 | #spin { top: 54px; } |
| 167 | |
| 168 | /* The standing readout is ~65 monospace characters wide, which overruns a |
| 169 | 390 px screen at 11 px. Shrink the type rather than wrap it: these are |
| 170 | aligned columns, and wrapping them destroys the thing they are for. */ |
| 171 | .overlay { font-size: 9px; padding: 6px 8px; } |
| 172 | } |