carlos / movement Public

Round three: the regressions undone, and the tab says where it goes

The second verdict scored four resolved, four partial, and three
regressions my own fix batch introduced. The regressions were the worse
half: min-height on mobile opened a field of blank stock bigger than the
content and pushed the tabs further from the fold than before I "fixed"
them; the receipt's stamp was a content-free cyan bar floating below the
rule it was meant to strike; and the textarea, reduced to a bare
underline, read as absent until you found the rule.

The full-height sheet is now a desktop idea only. The stamp strikes the
rule it sits on. The textarea carries a faint tint of the ink so its extent
is visible without becoming the only boxed control on the sheet.

One partial was a plain CSS bug of exactly the kind that is easy to ship:
.receipt p (0,1,1) was beating .receipt__mark (0,1,0) on source order, so
the confirmation screen had no headline at all. The reply's 68ch cap was
similarly defeated — by a grid column that resolved narrower than the cap,
so the measure it named never applied.

The primary action stops being a default button: the second ink is struck
across it, which is what a stamp is.

On ornament I took the date mark and declined the tab numbering, on a named
ground rather than taste: numbering three positions implies a rank, and
these three are equal by design — the same objection that ruled out the
cracktro direction. What a tear-off tab actually carries is the address you
walk away holding, so each tab carries its own. That also replaced the
"take" label, which was saying the same thing twice, and dropped the
unreviewed copy from three strings to one.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Paul Campbell efa02f4e08f10c6cbf4b1d9396d025054c464058 parent 88cb9fc
4 files changed, +79 −34
  • .impeccable/hook.cache.json +1 −1
  • internal/movement/copy.go +0 −1
  • internal/movement/static/bill.css +61 −22
  • internal/movement/templates/index.html +17 −10
diff --git a/.impeccable/hook.cache.json b/.impeccable/hook.cache.json
index e18c086..b3df96e 100644
--- a/.impeccable/hook.cache.json
+++ b/.impeccable/hook.cache.json
@@ -1 +1 @@
-{"version":1,"sessions":{"91e646a9-0b65-4ca7-83c7-59b544490463":{"updatedAt":1788369082741,"files":{"/home/paulca/amadan.net/carlos/movement/.claude/worktrees/design/internal/movement/static/theme.css":{"editCount":1,"findings":[],"cleanAcked":true},"/home/paulca/amadan.net/carlos/movement/.claude/worktrees/design/internal/movement/static/bill.css":{"editCount":1,"findings":[],"cleanAcked":true},"/home/paulca/amadan.net/carlos/movement/.claude/worktrees/design/internal/movement/templates/layout.html":{"editCount":1,"findings":[],"cleanAcked":true},"/home/paulca/amadan.net/carlos/movement/.claude/worktrees/design/internal/movement/templates/index.html":{"editCount":3,"findings":[],"cleanAcked":true},"/home/paulca/amadan.net/carlos/movement/.claude/worktrees/design/internal/movement/templates/stance.html":{"editCount":2,"findings":[],"cleanAcked":true},"/home/paulca/amadan.net/carlos/movement/.claude/worktrees/design/internal/movement/templates/sent.html":{"editCount":1,"findings":[],"cleanAcked":true},"/home/paulca/amadan.net/carlos/movement/.claude/worktrees/design/internal/movement/templates/confirm.html":{"editCount":1,"findings":[],"cleanAcked":true},"/home/paulca/amadan.net/carlos/movement/.claude/worktrees/design/internal/movement/templates/signin.html":{"editCount":1,"findings":[],"cleanAcked":true},"/home/paulca/amadan.net/carlos/movement/.claude/worktrees/design/internal/movement/templates/admin.html":{"editCount":1,"findings":[],"cleanAcked":true}}}}}
\ No newline at end of file
+{"version":1,"sessions":{"91e646a9-0b65-4ca7-83c7-59b544490463":{"updatedAt":1788370973442,"files":{"/home/paulca/amadan.net/carlos/movement/.claude/worktrees/design/internal/movement/static/theme.css":{"editCount":1,"findings":[],"cleanAcked":true},"/home/paulca/amadan.net/carlos/movement/.claude/worktrees/design/internal/movement/static/bill.css":{"editCount":1,"findings":[],"cleanAcked":true},"/home/paulca/amadan.net/carlos/movement/.claude/worktrees/design/internal/movement/templates/layout.html":{"editCount":1,"findings":[],"cleanAcked":true},"/home/paulca/amadan.net/carlos/movement/.claude/worktrees/design/internal/movement/templates/index.html":{"editCount":4,"findings":[],"cleanAcked":true},"/home/paulca/amadan.net/carlos/movement/.claude/worktrees/design/internal/movement/templates/stance.html":{"editCount":2,"findings":[],"cleanAcked":true},"/home/paulca/amadan.net/carlos/movement/.claude/worktrees/design/internal/movement/templates/sent.html":{"editCount":1,"findings":[],"cleanAcked":true},"/home/paulca/amadan.net/carlos/movement/.claude/worktrees/design/internal/movement/templates/confirm.html":{"editCount":1,"findings":[],"cleanAcked":true},"/home/paulca/amadan.net/carlos/movement/.claude/worktrees/design/internal/movement/templates/signin.html":{"editCount":1,"findings":[],"cleanAcked":true},"/home/paulca/amadan.net/carlos/movement/.claude/worktrees/design/internal/movement/templates/admin.html":{"editCount":1,"findings":[],"cleanAcked":true}}}}}
\ No newline at end of file
diff --git a/internal/movement/copy.go b/internal/movement/copy.go
index 2db0847..3445966 100644
--- a/internal/movement/copy.go
+++ b/internal/movement/copy.go
@@ -46,7 +46,6 @@ var table = map[string]entry{
// round never saw, so they ship as placeholders and the canonical host
// refuses to serve them.
"tear.instruction": {"PLACEHOLDER take the one that is yours", true},
- "tab.take": {"PLACEHOLDER take", true},
// The form.
"form.email": {"Your email address", false},
diff --git a/internal/movement/static/bill.css b/internal/movement/static/bill.css
index 13cca63..d27c396 100644
--- a/internal/movement/static/bill.css
+++ b/internal/movement/static/bill.css
@@ -32,7 +32,7 @@ body {
.bill {
max-width: 78rem;
- min-height: 100svh;
+ box-sizing: border-box;
margin: 0 auto;
display: flex;
flex-direction: column;
@@ -40,7 +40,18 @@ body {
clamp(1.5rem, 3vw, 2.25rem);
}
.bill__top {
- flex: 1 1 auto;
+ flex: 0 0 auto;
+}
+
+/* A bill fills the wall it is pasted on — but only where the wall is big
+ enough that the strip still lands near the fold. */
+@media (min-width: 46rem) and (min-height: 40rem) {
+ .bill {
+ min-height: 100svh;
+ }
+ .bill__top {
+ flex: 1 1 auto;
+ }
}
/* The printer's slug. A real bill carries who printed it and when; this
@@ -48,7 +59,8 @@ body {
monospace here would be a costume for "technical" on a page that is
not about machines. */
.bill__slug {
- margin: clamp(1.25rem, 3vw, 2rem) 0 0;
+ margin: 0;
+ font-size: 0.875rem;
}
/* ─────────────────────────────────────────────────────────────────────
@@ -129,13 +141,21 @@ body {
───────────────────────────────────────────────────────────────────── */
.tear {
- margin-top: clamp(2.25rem, 5vw, 4rem);
+ margin-top: clamp(1.75rem, 4vw, 3rem);
}
+.tear__head {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: baseline;
+ justify-content: space-between;
+ gap: 0.5rem 1.5rem;
+ margin: 0 0 0.6rem;
+}
.tear__instruction {
font-weight: 700;
font-size: clamp(1rem, 1.4vw, 1.25rem);
- margin: 0 0 0.6rem;
+ margin: 0;
}
/* The perforation. A dashed rule the tabs hang from, drawn as a real
@@ -160,9 +180,9 @@ body {
display: flex;
flex-direction: column;
justify-content: flex-start;
- gap: 0.75rem;
- min-height: clamp(6.5rem, 10vw, 8.5rem);
- padding: 1.1rem 1.1rem 1.25rem;
+ gap: 0.5rem;
+ min-height: clamp(5rem, 7vw, 6.5rem);
+ padding: 0.9rem 1.1rem 1rem;
background: var(--bill-stock);
color: var(--bill-ink);
text-decoration: none;
@@ -182,16 +202,12 @@ body {
z-index: 1;
}
-.tab__take {
- font-size: 0.6875rem;
+.tab__addr {
+ margin-top: 0.15rem;
+ font-size: 0.8125rem;
font-weight: 700;
- letter-spacing: 0.18em;
- text-transform: uppercase;
- color: var(--bill-dim);
-}
-.tab:hover .tab__take,
-.tab:focus-visible .tab__take {
color: var(--bill-blue);
+ word-break: break-all;
}
.tab__word {
@@ -200,6 +216,7 @@ body {
text-transform: uppercase;
font-size: clamp(1.6rem, 3.2vw, 2.75rem);
line-height: 0.92;
+ min-height: 1.84em;
letter-spacing: -0.01em;
text-wrap: balance;
}
@@ -219,7 +236,7 @@ body {
sets its argument in columns — not stacked down a centred strip. */
.sheet__split {
display: grid;
- grid-template-columns: 1.15fr 1fr;
+ grid-template-columns: minmax(0, 1.55fr) minmax(19rem, 1fr);
gap: clamp(2rem, 5vw, 4.5rem);
align-items: start;
border-top: 2px solid var(--bill-ink);
@@ -317,6 +334,8 @@ body {
resize: vertical;
min-height: 7.5rem;
line-height: 1.5;
+ padding: 0.55rem 0.7rem;
+ background: rgba(23, 20, 15, 0.05);
}
.field input[type="email"]::placeholder,
.field textarea::placeholder {
@@ -324,6 +343,8 @@ body {
}
.stamp {
+ position: relative;
+ overflow: hidden;
font: inherit;
font-weight: 700;
letter-spacing: 0.14em;
@@ -337,9 +358,25 @@ body {
cursor: pointer;
transition: background 160ms ease-out;
}
+.stamp::after {
+ content: "";
+ position: absolute;
+ left: 0;
+ right: 0;
+ top: 58%;
+ height: 7px;
+ background: var(--bill-cyan);
+ mix-blend-mode: screen;
+ opacity: 0.55;
+ transform: translateY(1px);
+ pointer-events: none;
+}
.stamp:hover:not(:disabled) {
background: var(--rst-accent-strong);
}
+.stamp:disabled::after {
+ display: none;
+}
.stamp:disabled {
background: transparent;
color: var(--bill-dim-bone);
@@ -387,18 +424,20 @@ body {
border-top: 2px solid var(--bill-ink);
border-bottom: 2px solid var(--bill-ink);
margin-top: clamp(2rem, 12vh, 7rem);
+ position: relative;
}
/* The stamp mark: the second ink, struck across the top rule. */
.receipt::before {
content: "";
- display: block;
- width: clamp(4rem, 12vw, 7rem);
- height: 0.6rem;
+ position: absolute;
+ top: -3px;
+ left: clamp(1.25rem, 5vw, 2.5rem);
+ width: clamp(5rem, 14vw, 9rem);
+ height: 8px;
background: var(--bill-cyan);
mix-blend-mode: multiply;
- margin: -1.6rem 0 1.75rem;
}
-.receipt__mark {
+.receipt .receipt__mark {
font-family: var(--bill-display);
font-weight: 800;
text-transform: uppercase;
diff --git a/internal/movement/templates/index.html b/internal/movement/templates/index.html
index 648e5db..5a9f9eb 100644
--- a/internal/movement/templates/index.html
+++ b/internal/movement/templates/index.html
@@ -17,26 +17,33 @@
</div>
{{/* The strip sits at the foot of the sheet, which is where a bill's
- tear-offs are. Taking a tab IS the primary action — there is no button
- on this page, because the sort happens before anyone is asked for an
- address. */}}
+ tear-offs are, and the imprint shares its head-rule so nothing hangs
+ below it. Taking a tab IS the primary action — there is no button on
+ this page, because the sort happens before anyone is asked for an
+ address.
+
+ Each tab carries the address it leads to and nothing else, because
+ that is what a tear-off tab is FOR: the thing you walk away holding. A
+ separate "take" label said the same thing twice. Deliberately not
+ numbered — numbering three positions implies a rank, and these three
+ are equal by design. */}}
<section class="tear">
- <p class="tear__instruction">{{say "tear.instruction"}}</p>
+ <div class="tear__head">
+ <p class="tear__instruction">{{say "tear.instruction"}}</p>
+ <p class="bill__slug">
+ <a class="bill__out" href="https://carlosframework.com">{{say "index.link"}}</a>
+ </p>
+ </div>
<div class="tear__perf"></div>
<div class="tear__tabs">
{{range .Choices}}
<a class="tab" href="/{{.Slug}}">
<span class="tab__word">{{.Label}}</span>
- <span class="tab__take">{{say "tab.take"}}</span>
+ <span class="tab__addr">movement.bdj.oncarlos.com/{{.Slug}}</span>
</a>
{{end}}
</div>
</section>
- {{/* The bill's bottom edge. A printed sheet ends somewhere. */}}
- <p class="bill__slug">
- <a class="bill__out" href="https://carlosframework.com">{{say "index.link"}}</a>
- </p>
-
</div>
{{end}}