From 767c37ccac04339ae60e1a12370163b3e217fdf6 Mon Sep 17 00:00:00 2001 From: Antonino Cuzzola Date: Fri, 4 Sep 2026 18:52:30 +0200 Subject: [PATCH 1/3] Sei voci tipografiche in piu': come una riga arriva, e dove sta La famiglia passa da quattro a dieci, e le nuove si dividono in due gruppi. COME ARRIVA. La MASCHERA che scopre ogni parola da dietro il bordo della sua casella - la tecnica piu' usata nel kinetic tipografico, e il motivo e' che una maschera non muove il testo: la parola e' gia' al suo posto e viene solo scoperta, mentre una dissolvenza la fa comparire. Il GRANO dello scaglionamento, lettera per lettera contro parola per parola: 23 momenti d'ingresso contro 7 sulla stessa frase, che e' la differenza fra una riga che cola e una che atterra a blocchi. La SPAZIATURA che si chiude da 0,225em, l'unica entrata che non porta dentro niente da fuori campo - la frase e' gia' tutta li' e smette solo di trattenere il fiato. E il PESO che atterra da 300 a 800. DUE DI QUESTE HANNO CORRETTO LA VOCE CHE LE DESCRIVEVA, ed e' il senso di misurare. Quella sul peso sosteneva che il peso e' l'asse che cambia l'inchiostro di una parola senza cambiarne l'ingombro: il render l'ha smentita al primo fotogramma, perche' la frase lunga stava su una riga a 300 e andava a capo a 800. Adesso sono due parole, la crescita di larghezza e' dichiarata (12 per cento) invece che negata, e la lezione e' scritta: su una riga lunga o la si tiene corta o le si blocca la larghezza. La spaziatura aveva lo stesso difetto un piano sotto - larga spingeva la riga su tre righe e stretta su due, quindi la composizione si rifaceva a meta' animazione. La spaziatura E' una proprieta' di layout, che e' proprio quello che la voce dice. DOVE STA. La COMPAGNA SU UN ALTRO ASSE: la riga piccola ruotata che corre sul fianco in maiuscoletto spaziato, che e' quello che le reference mettono accanto a ogni frase principale. Non compete perche' non sta sull'asse che l'occhio sta seguendo; messa piatta sotto, le stesse parole diventano un sottotitolo, e un sottotitolo e' una seconda cosa da leggere. E la FRASE SUL PIANO, che e' la voce che lega questa famiglia al resto del repo: la tipografia vive nella prospettiva della lastra invece di stare appoggiata sul quadro. Se il film e' un oggetto inclinato ripreso da una camera, una frase piatta sul vetro viene da un altro film. Il banco lo misura nel modo piu' economico che c'e': sul piano i due capi della riga sono diversi (0,909), da piatta identici (1,000). Tre sonde sbagliate mie, tenute scritte perche' sono lo stesso errore. Per la maschera misuravo l'altezza della casella, che non cambia mai perche' e' il suo mestiere; poi lo scostamento assoluto, che ha un padding dentro. Quello che distingue lo scoprire dall'accendere e' l'ESCURSIONE: 83 px contro 0. Per la spaziatura misuravo il riquadro della riga, e con la riga che va a capo il riquadro misura la riga piu' larga, quindi chiudere la spaziatura risultava allargarla. Trentasei voci, trentadue con un banco che le boccia, trentasei cicli che si chiudono. Co-Authored-By: Claude Opus 5 --- README.md | 36 +++++- scripts/demo-check.py | 112 +++++++++++++++++ showcase/grammatica.html | 264 +++++++++++++++++++++++++++++++++++++-- 3 files changed, 402 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 3ee3c13..4b277ab 100644 --- a/README.md +++ b/README.md @@ -158,7 +158,7 @@ asserted. It is also closer to the references, where the enormous word is alone. ## Typography, where the sentence is the film -Four entries with no interface on screen at all. They come from three reference +Ten entries with no interface on screen at all. They come from three reference films the type direction was read off — a SaaS promo, a Spotify spot, an Apple Business Essentials piece — and their common DNA: the typography *is* the film, one phrase per beat, always centred, heavy sans with tight tracking, scale @@ -187,6 +187,40 @@ slowing the cut down, tighten the entry stagger and lengthen only the dwell. Widening the stagger looks like generosity and is theft, because every frame it takes comes out of the only stretch where anybody is reading. +Six more went in after the first four were looked at, and they split into how a +line *arrives* and where it *sits*. + +Arriving: a **mask** that uncovers each word from behind the edge of its own box +— the most common technique in the reference work, and the reason is that a mask +does not move the text, the word is already in place and only gets revealed. The +**grain of the stagger**, letter by letter against word by word: 23 entry moments +against 7 on the same sentence, which is the difference between a line that pours +and a line that lands in blocks. **Tracking** closing from 0.225em, the only +entrance that brings nothing in from off-frame — the sentence is all there and +only stops holding its breath. And **weight** landing from 300 to 800. + +Two of those corrected the entry that described them, which is the point of +measuring. The weight one claimed weight was the axis that changes a word's ink +without changing the room it takes; the render disagreed in the first frame, +because the long line fitted on one row at 300 and wrapped at 800. It is now two +words, the width growth is stated (12 per cent) rather than denied, and the +lesson is written down: on a long line you either keep it short or lock the +width. Tracking had the same shape of problem one floor down — wide spacing +pushed the line onto three rows and tight spacing onto two, so the composition +redid itself mid-animation. Tracking *is* a layout property, which is the entry; +the demo is two words so the effect does not eat the composition. + +Sitting: **the companion on another axis** — the small rotated line running up +the side in spaced capitals, which is what the references put next to every main +sentence. It does not compete because it is not on the axis the eye is following; +set flat underneath, the same words become a subtitle, and a subtitle is a second +thing to read. And **the sentence on the plane**, which is the entry that ties +this family to the rest of the repo: the type lives in the slab's own +perspective instead of sitting on the frame. If the film is an inclined object +seen by a camera, a sentence lying flat on the glass comes from a different film. +The bench measures it the cheapest way there is — on the plane the two ends of +the line differ (0.909), flat they are identical (1.000). + The page grew a second kind of stage for these. Type demos do not mount the slab — mounting it and then covering it would render a kanban behind a word for nothing, and the stage's perspective would falsify the type size. Same engine, diff --git a/scripts/demo-check.py b/scripts/demo-check.py index 5aca09b..1f23b57 100755 --- a/scripts/demo-check.py +++ b/scripts/demo-check.py @@ -385,6 +385,118 @@ def drift(code, lo, hi): check("TYP-04", w1 < 1.0 and w2 > 20, "spostamento delle tre parole in comune: %.2f px sostituendo una parola, %.0f px sostituendo la riga" % (w1, w2)) + # TYP-05 - scoprire non e' accendere. + # Il caso peggiore: se la parola comparisse invece di essere scoperta, la + # sua altezza resa sarebbe piena dal primo fotogramma e cambierebbe solo + # l'opacita'. + # La casella che ritaglia non cambia mai altezza - e' il suo mestiere - + # quindi non e' li' che si vede la differenza. Quello che si misura e' di + # quanto la parola sia SPOSTATA dentro la casella: scoprendola parte fuori + # e sale, in dissolvenza sta ferma al suo posto dal primo fotogramma. + def offset(code, lo, hi): + vals = [] + for f in range(lo, hi, 2): + vals.append(pg.evaluate("""([c,f])=>{ + const h=[...document.querySelectorAll('.mv')]; + const i=h.findIndex(m=>(m.querySelector('.code')||{}).textContent.trim()===c); + const st=document.querySelectorAll('.stage')[i], it=st.__it; + it.manual=f; it.last=-1; it.mv.draw(f,it.S); + const box=st.querySelectorAll('.tline > .tclip')[3]; + const inn=box.firstElementChild; + return inn.getBoundingClientRect().top - box.getBoundingClientRect().top; + }""", [code, f])) + return min(vals), max(vals) + o1 = offset("TYP-05", 8, 50) + o2 = offset("TYP-05", 123, 165) + # L'ESCURSIONE, non il valore: la casella ha un padding in cima, quindi la + # parola sta comunque una decina di pixel sotto il suo bordo anche da ferma. + # Quello che distingue lo scoprire dall'accendere e' di quanto si MUOVE. + r1, r2 = o1[1] - o1[0], o2[1] - o2[0] + check("TYP-05", r1 > 40 and r2 < 3, + "escursione della parola dentro la sua casella: %.0f px scoprendola, %.0f px in dissolvenza" + % (r1, r2)) + + # TYP-06 - il grano dello scaglionamento. + def moments(code, lo, hi): + seen, first = set(), {} + for f in range(lo, hi): + st = ty(code, f) + for j, sp in enumerate(st["spans"]): + pass + return seen, first + def entries(code, lo, hi): + # I fotogrammi distinti in cui QUALCOSA diventa visibile per la prima + # volta: e' il numero di momenti d'ingresso, che e' la tesi della voce. + prev = None + out = set() + for f in range(lo, hi): + cur = pg.evaluate("""([c,f])=>{ + const h=[...document.querySelectorAll('.mv')]; + const i=h.findIndex(m=>(m.querySelector('.code')||{}).textContent.trim()===c); + const st=document.querySelectorAll('.stage')[i], it=st.__it; + it.manual=f; it.last=-1; it.mv.draw(f,it.S); + return [...st.querySelectorAll('.tline .tw > span')].map(e=>parseFloat(getComputedStyle(e).opacity)>0.06?1:0); + }""", [code, f]) + if prev is not None and any(c and not p for c, p in zip(cur, prev)): + out.add(f) + prev = cur + return len(out) + e1, e2 = entries("TYP-06", 4, 60), entries("TYP-06", 129, 185) + check("TYP-06", e1 > e2 * 2.5, + "momenti d'ingresso distinti: %d lettera per lettera, %d parola per parola" % (e1, e2)) + + # TYP-07 - la spaziatura si chiude, quindi la riga si stringe. + # La larghezza di UNA parola, non il riquadro della riga: se la riga va a + # capo il riquadro misura la riga piu' larga e la spaziatura che si chiude + # risulta allargarla. La parola porta dentro la propria spaziatura e non ha + # quel problema. + def linew(code, f): + return ty(code, f)["spans"][1]["w"] + w_a, w_b = linew("TYP-07", 18), linew("TYP-07", 70) + w_c, w_d = linew("TYP-07", 128), linew("TYP-07", 180) + check("TYP-07", w_a > w_b * 1.12 and abs(w_c - w_d) < 2, + "larghezza della parola: da %.0f a %.0f px chiudendo la spaziatura, %.0f contro %.0f senza" + % (w_a, w_b, w_c, w_d)) + + # TYP-08 - il peso sale, e la larghezza con lui. + def wgt(code, f): + return pg.evaluate("""([c,f])=>{ + const h=[...document.querySelectorAll('.mv')]; + const i=h.findIndex(m=>(m.querySelector('.code')||{}).textContent.trim()===c); + const st=document.querySelectorAll('.stage')[i], it=st.__it; + it.manual=f; it.last=-1; it.mv.draw(f,it.S); + const l=st.querySelector('.tline'), s=st.querySelector('.tline > .tw'); + return [parseFloat(getComputedStyle(l).fontWeight), s.getBoundingClientRect().width]; + }""", [code, f]) + p_a, p_b = wgt("TYP-08", 16), wgt("TYP-08", 66) + p_c = wgt("TYP-08", 130) + check("TYP-08", p_a[0] < 380 and p_b[0] > 760 and p_b[1] > p_a[1] * 1.04 and p_c[0] > 760, + "peso da %.0f a %.0f, e la prima parola da %.0f a %.0f px; nella seconda meta' resta a %.0f" + % (p_a[0], p_b[0], p_a[1], p_b[1], p_c[0])) + + # TYP-09 - la compagna sta su un altro asse. + def side(code, f): + return pg.evaluate("""([c,f])=>{ + const h=[...document.querySelectorAll('.mv')]; + const i=h.findIndex(m=>(m.querySelector('.code')||{}).textContent.trim()===c); + const st=document.querySelectorAll('.stage')[i], it=st.__it, sr=st.getBoundingClientRect(); + it.manual=f; it.last=-1; it.mv.draw(f,it.S); + const e=st.querySelector('.tside'), r=e.getBoundingClientRect(); + return {tall: r.height > r.width, x:(r.left+r.width/2-sr.left)/sr.width}; + }""", [code, f]) + s1, s2 = side("TYP-09", 70), side("TYP-09", 185) + check("TYP-09", s1["tall"] and s1["x"] < 0.2 and (not s2["tall"]) and abs(s2["x"] - 0.5) < 0.1, + "compagna: verticale al %.0f%% della larghezza, contro orizzontale al %.0f%%" + % (s1["x"] * 100, s2["x"] * 100)) + + # TYP-10 - sul piano i due capi della riga non sono uguali. + def fore(code, f): + sp = ty(code, f)["spans"] + return sp[0]["h"] / sp[-1]["h"] + f1, f2 = fore("TYP-10", 90), fore("TYP-10", 215) + check("TYP-10", abs(f1 - 1) > 0.05 and abs(f2 - 1) < 0.01, + "rapporto fra il primo e l'ultimo capo della riga: %.3f sul piano, %.3f da piatta" % (f1, f2)) + br.close() print() diff --git a/showcase/grammatica.html b/showcase/grammatica.html index 051441b..8e74c2d 100644 --- a/showcase/grammatica.html +++ b/showcase/grammatica.html @@ -5,7 +5,7 @@ The grammar — scenes - +