carlos / contra Public

Clone
git clone https://amadan.net/carlos/contra

Plain git — no account needed to clone.

Download

Download this file

1{{define "layout"}}<!doctype html>
2<html lang="{{block "lang" .}}en{{end}}" dir="{{block "dir" .}}ltr{{end}}">
3<head>
4<meta charset="utf-8">
5<meta name="viewport" content="width=device-width, initial-scale=1">
6<title>{{block "title" .}}Hello{{end}}</title>
7<link rel="stylesheet" href="{{asset "static/tokens.css"}}">
8<link rel="stylesheet" href="{{asset "static/theme.css"}}">
9{{/* CONTRA's own grammar, last of the three so it wins: theme.css is
10 the palette, this is the ticket the palette is printed on. */}}
11<link rel="stylesheet" href="{{asset "static/contra.css"}}">
12<script defer src="{{asset "static/rastrillo.js"}}"></script>
13{{iconAssets}}
14<script defer src="{{asset "static/select.js"}}"></script>
15<script defer src="{{asset "static/datetime.js"}}"></script>
16{{/* head is the app's own slot in <head>: a favicon, a meta tag, one
17 more stylesheet. Empty by default, last in the head so an app's
18 own CSS wins over the framework's, and the only block in the
19 four shells that is not chrome. */}}
20{{block "head" .}}{{end}}
21</head>
22<body>
23<!-- DIRECTION — CONTRA
24 THESIS: A cloakroom holds your bag, never learns what is in it, and
25 will not release it without the stub. Refuses this category's
26 navy-shield-and-rounded-cards arrangement.
27 OWN-WORLD: Eau-de-nil ticket stock, black ink, one vermilion spent
28 only on the numbering and on what cannot be undone; manila codes a
29 shared vault. Square-cut, no cards, condensed gothic caps, ruled
30 fields instead of boxes.
31 STORY: The visitor learns the server cannot help them, believes
32 that is the protection rather than a gap, and prints the kit.
33 FIRST VIEWPORT: Ticket stock; a condensed-gothic title in sentence
34 case over a 3px ink rule (capitals are for labels, not sentences);
35 ruled fields; and on the two screens with a real client/server
36 boundary — sign-in and the vault — one perforation with the sealed
37 half below it. The numbering lives where things are counted, which
38 is the vault and the device ladder; the public pages count nothing
39 and do not pretend to.
40 FORM: Left Luggage, 7th of 7 on the ordered list; seed bfd735a7.
41 FINISH: unreviewed and undocumented is unfinished; this build ends
42 with the finish review, the verdict, and DESIGN.md -->
43{{/* The console shell — a brand-and-account bar across the top AND a
44 navigation rail down the side, which is the shape most admin
45 consoles are and the one the other three shells could not make.
46 topbar is this without the rail; sidebar is this without the bar.
47
48 THE TWO CHROMES, AND WHY THERE IS STILL ONE CONTROL.
49
50 Every other shell has one thing to fold below 800px. This one has
51 two — a bar tail and a rail — and the wrong answer is two
52 disclosures a reader has to learn, one for "the account" and one
53 for "the navigation", stacked on top of each other on a phone.
54
55 So: ONE <details>, two reveals. The disclosure below gates its own
56 next sibling (the bar's tail) with the adjacent-sibling combinator
57 the topbar already uses, and gates the rail from the shell root
58 with :has(). One state, one control, one summary drawn at 390px.
59
60 The reveals are written as HIDE-WHEN-CLOSED rather than
61 show-when-open, and that is deliberate: :has() is the newer
62 selector of the two, and in an engine that does not support it the
63 rule that hides the rail simply never matches, so the rail renders
64 as a plain block of links under the bar. The page gets longer; the
65 navigation stays reachable. The show-when-open spelling fails the
66 other way, and the other way is a phone with no way to navigate.
67
68 NOTHING MOVES. The DOM order here — bar, then rail, then main — is
69 the order at 320px and the order at 1280px. Grid places the bar
70 across the top and the rail beside the page at width, and the
71 collapsed panel discloses the bar's tail above the rail's nav
72 because that is where they already are. So focus order matches
73 visual order at every width and in both directions of the
74 language, which is the thing an `order` or a `grid-row` shuffle
75 would have quietly cost. */}}
76<div rst-shell-console>
77<a rst-skip href="#main">{{T "rastrillo.ui.shell_skip"}}</a>
78<header rst-shell-bar>
79{{block "brand" .}}<a rst-shell-brand href="/">{{T "rastrillo.ui.shell_home"}}</a>{{end}}
80{{/* name="rst-shell-menu", and NOT rst-menus — the same group the
81 topbar's disclosure is in, for the same reason and with one more
82 element depending on it here. <details name> exclusivity is
83 DOCUMENT-WIDE rather than sibling-scoped: a disclosure in the
84 menus' group would be closed by the account menu it just revealed,
85 and in this shell that closes the rail too, because the rail is
86 gated on the very same [open]. One wrong attribute takes both
87 chromes away at once. Read this as the reason for the gates rather
88 than as advice to change the line below. */}}
89<details rst-shell-menu name="rst-shell-menu"><summary>{{icon "menu"}}{{T "rastrillo.ui.shell_menu"}}</summary></details>
90{{/* The tail is a SIBLING of the disclosure, not its content: a closed
91 <details> hides its own content and no CSS reliably un-hides it,
92 so the wide layout could not be recovered from inside one. Above
93 800px tokens.css gives it display: contents and the account and
94 locale menus become flex items of the bar itself.
95
96 account here is the DROPDOWN'S MENU BODY, exactly as in topbar:
97 the layout owns the details/summary and an override supplies only
98 the links inside [rst-dropdown-menu]. sidebar's account block is
99 the other shape — a bare slot in the rail — so a screen moving
100 between console and sidebar edits that one block, while console
101 and topbar are interchangeable. */}}
102<div rst-shell-tail>
103<details rst-dropdown rst-shell-account name="rst-menus">
104<summary>{{T "rastrillo.ui.shell_account"}}<span rst-caret aria-hidden="true">{{icon "chevron-down"}}</span></summary>
105<div rst-dropdown-menu>{{block "account" .}}{{end}}</div>
106</details>
107{{block "locale" .}}{{end}}
108</div>
109</header>
110{{/* The rail carries navigation and nothing else — the language
111 switcher and the person are up on the bar, which is what makes
112 this shell a console rather than a sidebar with a strip above it.
113 So it has no rail-foot, and the bug that cost this project a
114 release — a person pinned to the bottom of a rail that hung 32px
115 under the window — has nothing here to pin. What it has instead is
116 a nav that sticks: see the max-block-size in tokens.css, which is
117 measured against the viewport and gated against the viewport. */}}
118<aside rst-shell-rail>
119{{/* The default navigation, defined once here rather than per page: a
120 page that forgets to define it would render an empty rail, which is
121 what the canary shipped with until somebody looked at it. Any page
122 can still override the block. */}}
123<nav rst-shell-nav>{{block "nav" .}}<a href="/">Home</a>
124{{if .SignedIn}}<a href="/vault">Your vault</a>
125<a href="/signup">Add another account</a>{{else}}<a href="/signup">Create an account</a>
126<a href="/signin">Sign in</a>{{end}}{{end}}</nav>
127</aside>
128<main rst-shell-main id="main">
129<div rst-page>
130{{template "content" .}}
131</div>
132</main>
133{{/* A sibling of <main>, not a child of it: a <footer> whose nearest
134 sectioning ancestor is <body> is the contentinfo landmark, and one
135 inside <main> is a plain generic footer. Grid puts it under the
136 page column rather than under the rail. */}}
137{{/* The overprint that ends the ticket. Every printed ticket says which
138 office issued it and carries a serial; this one carries the build,
139 which is the same fact GET /api/version reports to the platform's
140 deploy check. It moved here from index.html so it appears on every
141 sheet rather than only the front page — the same words, one home. */}}
142<footer rst-shell-foot>{{block "foot" .}}<p data-overprint>Build <code>{{.Version}}</code>.</p>{{end}}</footer>
143</div>
144</body>
145</html>
146{{end}}
147