studio-engine 0.62.5 → 0.62.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 629e819d3b8b6a9c92140a69829e75a3d6d6c3c8e9ff4faeede098ffe7394a1e
4
- data.tar.gz: 562d9d5caffa68f7c282cc91cb91369fe132a8e90c63257b97f43bf4d328669b
3
+ metadata.gz: fa64bc8c9da93f3beaede057f50b905755a4d62e549aa83d32d154fd6f2dbf4c
4
+ data.tar.gz: 9e3fc2011c4cada24b7d93f08a3e4e54fad76df242a8e3986b83bb0e8a742126
5
5
  SHA512:
6
- metadata.gz: 3bc2df617a2f90f9c46efde0d73f7f39f7b2f063147971303d004b080c80af4710cb4cce605cbb7a608e87c37bcdd58dceaa4a2d0088c791b9024625c3da1fb3
7
- data.tar.gz: 5a69fabcce40e7e93c5b8d0a3b88431a74d4047e100854393be1fb5ead4b4bacb0229b7182953e148655fe64a1faa60eacbb6aca426531fb20e34bff30116686
6
+ metadata.gz: 842b7b8f102010ef77ee303a763c42dfceb047ccb3d8cdce2b06e44a95d97fc8ca91a03207eecb6b0c9340f9434baf071ad6914e6cc7f6910a8889f8300b2a8e
7
+ data.tar.gz: dea3a8a030028885d614787882af0ceaec4bda5a50a175e863f5a2144d44b3f958a9d9d2594c7dee592365af0636e748239ed1b11579bf92bc91ae28da0d637d
data/CHANGELOG.md CHANGED
@@ -40,6 +40,30 @@ The format is [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). This pro
40
40
 
41
41
  ### Added
42
42
 
43
+ - **`--nav-p` and `navCollapse()` — the navbar collapse becomes a primitive.**
44
+ `layouts/studio/_head` now publishes a third header property beside `--nav-h`
45
+ and `--nav-bottom`. The first two are measurements OF the header; `--nav-p` is
46
+ an input TO it: collapse progress, `0` expanded to `1` collapsed, written on
47
+ the `<header>` once per animation frame from `window.scrollY`, and registered
48
+ with `@property` as a `<number>` so it is legal inside `calc()`, real before
49
+ the first scroll, and interpolable.
50
+
51
+ **It ships no sizing opinion.** A host adopts it by putting `nav-shell` and
52
+ `x-data="navCollapse()"` on its header, giving each breakpoint band a
53
+ `--nav-ramp`, and writing its collapsing dimensions as `calc()`s off
54
+ `--nav-p`. An app whose navbar collapses to different endpoints than this
55
+ gem's — `mcritchie-studio` goes `w-8 → w-5`, not `w-12 → w-8` — can adopt the
56
+ mechanism without changing its endpoints.
57
+
58
+ **NO SHIPPING CONSUMER EXERCISES THIS YET, and the adoptions are follow-ons.**
59
+ `turf-monster` overrides `layouts/_navbar.html.erb` AND redefines
60
+ `window.navCollapse` in `shared/_alpine_factories.html.erb`, rendered after the
61
+ engine head, so the engine's copy is shadowed. `mcritchie-studio` never renders
62
+ `layouts/navbar` at all — its inline header still carries `@scroll.window` and
63
+ `transition-all duration-300`. Said plainly because it also means the green
64
+ consumer CI on this change is **not evidence about this code path**: both apps
65
+ compile and pass against a primitive neither of them runs.
66
+
43
67
  - **`studio/modals/blocks/_age_gate` — the refusal card.** Where the birthday card
44
68
  hands off when the date is under the app's bar. Headed "Easy, Young'un" under a
45
69
  teddy bear rather than a red X, because a person who will be welcome later is
@@ -49,6 +73,45 @@ The format is [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). This pro
49
73
  absent rather than rendered dead) and a back link that returns to the birthday
50
74
  card. Displays `min_age` / `state`; computes no eligibility of its own.
51
75
 
76
+ ### Fixed
77
+
78
+ - **The engine navbar's collapse tracks the finger instead of a clock.**
79
+ `layouts/_navbar` flipped `scrolled` at `scrollY > 60` and fed that step into
80
+ `transition-all duration-300` on padding, logo `width`/`height` and two font
81
+ sizes. The finger set the step; an ease curve owned everything after it.
82
+ Measured in `turf-monster` at 390×844 on the same code before the change: the
83
+ header ran 178px → 139px and **34 of those 39px of document reflow landed
84
+ AFTER the scroll had stopped, over 232ms**, at up to 3px per frame — plus a
85
+ 1px REVERSE lurch in the frame the class flipped, where a discrete
86
+ `text-3xl → text-xl` swap collided with the stylesheet's own
87
+ `transition: font-size`.
88
+
89
+ Every collapsing dimension is now a `calc()` off `--nav-p`, and nothing on the
90
+ path carries a time-based transition. **Both endpoints are unchanged at every
91
+ breakpoint** — this is the same navbar, re-plumbed, so a consumer sees no
92
+ visual difference at rest.
93
+
94
+ Content still accelerates *during* the collapse: a sticky header is in flow,
95
+ so shrinking it adds to the scroll and content necessarily outruns the finger.
96
+ That is the point of a collapse, not a defect. What is tunable is the shape,
97
+ and `--nav-ramp` is 3× the band's collapse total with a smoothstep ease, whose
98
+ slope is zero at both ends — so the burst leaves 1×, peaks near 1.5×, and
99
+ returns to 1× with no velocity step. A linear ramp equal to the collapse hits
100
+ 2× and steps straight back.
101
+
102
+ Also included: a passive, rAF-coalesced listener that writes to the header
103
+ rather than `:root` (keeping each frame's style recalc inside the navbar
104
+ subtree); a short-page guard, because collapsing shortens the document and on
105
+ a barely-scrollable page that deletes the very scroll room that triggered it,
106
+ clamps `scrollY` to 0, and flaps forever; and a `prefers-reduced-motion`
107
+ branch that snaps `--nav-p` to `0`/`1`, since resizing type under a moving
108
+ finger is itself motion worth dropping.
109
+
110
+ `/navbar`'s Scrolled toggle drops **twelve `!important` rules** that restated
111
+ every collapsed value — it sets `--nav-p: 0|1` and transitions the registered
112
+ property, so the preview exercises the shipped `calc()`s instead of a parallel
113
+ copy of them.
114
+
52
115
  ### Added
53
116
 
54
117
  - **A Geo row in the shared admin dropdown, with signage when it is off.** Every
@@ -37,7 +37,16 @@
37
37
  logo_path = Studio.logo_for("Navbar Logo")
38
38
  %>
39
39
 
40
- <header x-data="{ scrolled: false }" <%= '@scroll.window="scrolled = scrolled ? (window.scrollY > 5) : (window.scrollY > 60)"'.html_safe unless is_preview %>
40
+ <%# nav-shell is the --nav-p scope: navCollapse() (layouts/studio/_head) writes
41
+ the collapse progress, 0 expanded .. 1 collapsed, on THIS element once per
42
+ animation frame, and the band table below derives the row padding, logo size
43
+ and title sizes from it with calc(). Nothing on the collapse path carries a
44
+ time-based transition — the head's comment carries the measurements that
45
+ bought that. transition-shadow stays: box-shadow paints, it never reflows,
46
+ so it cannot move content under the reader.
47
+ Preview keeps nav-shell (the calc()s have to resolve) but no x-data — the
48
+ /navbar review page drives --nav-p from its own Scrolled toggle. %>
49
+ <header <%= 'x-data="navCollapse()"'.html_safe unless is_preview %>
41
50
  <%# top-0, a STATIC value, and never a custom property. Any bars render as
42
51
  this header's sibling in normal flow (studio/banners/_stack), so they
43
52
  already occupy their own height above it and there is nothing to
@@ -46,7 +55,7 @@
46
55
  measurement landed, moved again on a webfont swap, and drew twice
47
56
  during a view transition composited at two different tops. Only CSS
48
57
  can set this top now, so none of that is reachable. %>
49
- class="<%= is_preview ? 'bg-page' : "#{'vt-pinned-header ' if pin_header}sticky top-0 z-50 bg-page transition-shadow duration-300" %>"
58
+ class="nav-shell <%= is_preview ? 'bg-page' : "#{'vt-pinned-header ' if pin_header}sticky top-0 z-50 bg-page transition-shadow duration-300" %>"
50
59
  :class="scrolled && 'shadow-lg border-b border-subtle is-scrolled'">
51
60
  <style>
52
61
  .user-nav-col { width: 14rem; }
@@ -56,37 +65,94 @@
56
65
  long nowrap username sizes the column past a narrow viewport. */
57
66
  .user-nav-fit { max-width: 14rem; }
58
67
  .nav-title { display: flex; gap: 0.25em; align-items: baseline; }
68
+
69
+ /* THE COLLAPSE BAND TABLE. Each band is the two ENDPOINTS of its collapse
70
+ written once, not two rule sets that have to be kept in step — the old
71
+ shape, where a base rule and an `.is-scrolled` override each carried half
72
+ the answer and `transition: font-size` guessed the middle.
73
+
74
+ --nav-ramp is the scroll distance the collapse is spread over, and it is
75
+ 3x that band's collapse TOTAL (mobile: 8px of logo + 32px of padding =
76
+ 40px, ramp 120px; desktop: 16px + 32px = 48px, ramp 144px). That ratio is
77
+ what puts the peak content speed near 1.5x rather than 2x — see the
78
+ navCollapse comment in layouts/studio/_head. Retune one and retune the
79
+ other, or the easing stops matching the geometry.
80
+
81
+ Endpoints below are UNCHANGED from the threshold build. This is the same
82
+ navbar; only the path between the ends is new.
83
+
84
+ EVERY var() CARRIES A FALLBACK OF 0, and it is inert where @property works.
85
+ navCollapse only WRITES --nav-p when progress changes, so on an unscrolled
86
+ page it is never written at all and the registration is doing 100% of the work
87
+ on first paint. In a browser that ignores @property, an unregistered custom
88
+ property is the guaranteed-invalid token and every calc() here goes
89
+ invalid-at-computed-value-time — measured: padding-block 0 and the logo at its
90
+ intrinsic size. `var(--nav-p, 0)` degrades to "always expanded" instead, which
91
+ is the pre-collapse navbar rather than a broken one. It costs nothing where
92
+ @property is honoured, which is why it is written unconditionally. (Not a NEW
93
+ exposure: the built Tailwind v4 already ships 59 @property registrations, so
94
+ that browser band is unsupported engine-wide.) */
95
+ .nav-shell {
96
+ --nav-ramp: 144px;
97
+ --nav-pad: calc(1.5rem - 1rem * var(--nav-p, 0)); /* py-6 -> py-2 */
98
+ --nav-logo-size: calc(3rem - 1rem * var(--nav-p, 0)); /* 48px -> 32px */
99
+ --nav-title-size: calc(1.875rem - 0.625rem * var(--nav-p, 0)); /* 3xl -> xl */
100
+ --nav-title-lead-size: var(--nav-title-size); /* one line here */
101
+ /* THE LINE BOX, and the reason this variable exists at all. `text-3xl` and
102
+ `text-xl` are FONT-SIZE AND LINE-HEIGHT PAIRS (1.875rem/2.25rem and
103
+ 1.25rem/1.75rem). Carrying only the font-size across dropped the
104
+ line-height to whatever the h1 inherited, and the header grew 9px at every
105
+ width >= 768px — expanded 84px -> 93px, measured. Collapsed was unaffected
106
+ because the 32px logo, not the title, sets the row height there, which is
107
+ exactly why a one-endpoint check would have missed this. Mobile was clean
108
+ only because its band already declared line-height: 1.15 explicitly.
109
+ Interpolate the PAIR or the endpoints move. */
110
+ --nav-title-lead: calc(2.25rem - 0.5rem * var(--nav-p, 0)); /* 36px -> 28px */
111
+ }
112
+ .nav-row { padding-block: var(--nav-pad); }
113
+ .nav-shell .nav-logo { width: var(--nav-logo-size); height: var(--nav-logo-size); }
114
+ .nav-shell .nav-title { font-size: var(--nav-title-size); line-height: var(--nav-title-lead); }
115
+ .nav-shell .nav-title span:last-child { font-size: var(--nav-title-lead-size); }
116
+
59
117
  @media (min-width: 400px) { .user-nav-col { width: 15rem; } .user-nav-fit { max-width: 15rem; } }
60
118
  @media (min-width: 768px) { .user-nav-col { width: 20rem; } .user-nav-fit { max-width: 20rem; } }
61
119
  @media (max-width: 767px) {
62
- .nav-title { font-size: 1.25rem !important; flex-direction: column; gap: 0; line-height: 1.15; transition: font-size 0.3s; }
120
+ .nav-title { flex-direction: column; gap: 0; }
63
121
  .nav-title span:first-child { margin-bottom: -4px; }
64
- .nav-title span:last-child { font-size: 1.5rem; transition: font-size 0.3s; }
65
122
  .nav-logo-link { gap: 0.5rem !important; }
66
- .is-scrolled .nav-title { font-size: 1rem !important; }
67
- .is-scrolled .nav-title span:last-child { font-size: 1.15rem !important; }
68
- .is-scrolled .nav-logo { width: 2.5rem !important; height: 2.5rem !important; }
123
+ .nav-shell {
124
+ --nav-ramp: 120px;
125
+ --nav-logo-size: calc(3rem - 0.5rem * var(--nav-p, 0)); /* 48px -> 40px */
126
+ --nav-title-size: calc(1.25rem - 0.25rem * var(--nav-p, 0)); /* 20px -> 16px */
127
+ --nav-title-lead-size: calc(1.5rem - 0.35rem * var(--nav-p, 0));/* 24px -> 18.4px */
128
+ /* Mobile stacks the two words, so its line box is a RATIO, not the desktop
129
+ pair. Declared here rather than as a `line-height` on .nav-title, because
130
+ `.nav-shell .nav-title` outranks a bare `.nav-title` on specificity no
131
+ matter which comes later in the sheet — one declaration site, like every
132
+ other endpoint in this table. */
133
+ --nav-title-lead: 1.15;
134
+ }
69
135
  }
70
136
  @media (max-width: 399px) {
71
137
  .nav-logo-link { gap: 0.25rem !important; }
72
- .nav-title { font-size: 1.1rem !important; transition: font-size 0.3s; }
73
- .nav-title span:last-child { font-size: 1.3rem; transition: font-size 0.3s; }
74
- .is-scrolled .nav-title { font-size: 0.9rem !important; }
75
- .is-scrolled .nav-title span:last-child { font-size: 1rem !important; }
138
+ .nav-shell {
139
+ --nav-title-size: calc(1.1rem - 0.2rem * var(--nav-p, 0)); /* 17.6px -> 14.4px */
140
+ --nav-title-lead-size: calc(1.3rem - 0.3rem * var(--nav-p, 0)); /* 20.8px -> 16px */
141
+ }
76
142
  }
77
143
  </style>
78
- <div class="flex items-center <%= is_preview ? 'py-6' : 'transition-all duration-300' %>"
79
- <%= ":class=\"scrolled ? 'py-2' : 'py-6'\"".html_safe unless is_preview %>>
144
+ <div class="nav-row flex items-center">
80
145
  <%# Left side: logo + nav %>
81
146
  <div class="flex-1 px-4">
82
147
  <div class="flex items-center gap-6">
83
148
  <%= link_to root_path, class: "nav-logo-link inline-flex items-center gap-3 group" do %>
84
149
  <% if logo_path %>
85
- <img src="<%= logo_path %>" alt="<%= Studio.app_name %>" class="nav-logo rounded-full shadow-lg <%= is_preview ? 'w-12 h-12' : 'group-hover:scale-105 transition-all duration-300' %>"
86
- <%= "x-bind:class=\"scrolled ? 'w-8 h-8' : 'w-12 h-12'\"".html_safe unless is_preview %>>
150
+ <%# No w-*/h-* utility here: .nav-shell .nav-logo sizes it off --nav-p.
151
+ transition-transform, NOT transition-all the hover lift still
152
+ eases, while width/height follow the finger with no clock. %>
153
+ <img src="<%= logo_path %>" alt="<%= Studio.app_name %>" class="nav-logo rounded-full shadow-lg <%= "group-hover:scale-105 transition-transform duration-300" unless is_preview %>">
87
154
  <% end %>
88
- <h1 class="nav-title font-extrabold text-heading tracking-tight <%= is_preview ? 'text-3xl' : 'transition-all duration-300' %>"
89
- <%= "x-bind:class=\"scrolled ? 'text-xl' : 'text-3xl'\"".html_safe unless is_preview %>><span><%= prefix %></span><span class="text-primary"><%= last_word %></span></h1>
155
+ <h1 class="nav-title font-extrabold text-heading tracking-tight"><span><%= prefix %></span><span class="text-primary"><%= last_word %></span></h1>
90
156
  <% end %>
91
157
  <%# Desktop nav — apps should override this partial to add links here %>
92
158
  <nav class="hidden md:flex items-center gap-4">
@@ -244,6 +244,161 @@
244
244
  })();
245
245
  </script>
246
246
 
247
+ <style>
248
+ /* studio-engine: --nav-p, the sticky header's COLLAPSE PROGRESS, 0 (expanded)
249
+ to 1 (collapsed). Third of the header properties published from this file,
250
+ and the only one that is an INPUT to the header rather than a measurement
251
+ of it — --nav-h and --nav-bottom above say how big it is, this says how far
252
+ through its collapse it has got.
253
+
254
+ REGISTERED, not merely declared. Untyped, `calc(3rem - 1rem * var(--nav-p))`
255
+ is invalid at computed-value time on a page that has never scrolled and
256
+ every collapsing dimension drops to its unstyled default; the <number> type
257
+ is what makes the arithmetic legal and the initial value real. Registration
258
+ also makes it INTERPOLABLE, which is how /navbar's Scrolled toggle animates
259
+ the whole collapse by transitioning one property. */
260
+ @property --nav-p {
261
+ syntax: "<number>";
262
+ inherits: true;
263
+ initial-value: 0;
264
+ }
265
+ </style>
266
+ <script>
267
+ // studio-engine: navCollapse — the scroll-linked navbar collapse.
268
+ //
269
+ // Publishes --nav-p on the <header> once per animation frame from
270
+ // window.scrollY. The navbar's own stylesheet derives every collapsing
271
+ // dimension from it with calc(), so the header moves only in a frame the
272
+ // finger moved it, and stops the instant the finger does.
273
+ //
274
+ // It replaces `@scroll.window="scrolled = scrolled ? (scrollY > 5) : (scrollY
275
+ // > 60)"` plus `transition-all duration-300`. That pair let the FINGER set a
276
+ // step and an ease curve own everything after it. Measured in turf-monster at
277
+ // 390x844 before the change: the header ran 178px -> 139px and 34 of those
278
+ // 39px of document reflow landed AFTER the scroll had stopped, over 232ms, at
279
+ // up to 3px per frame of content nobody asked to move -- plus a 1px REVERSE
280
+ // lurch in the frame the class flipped, where a discrete text-3xl -> text-xl
281
+ // swap collided with the stylesheet's own `transition: font-size`.
282
+ //
283
+ // ADOPTING IT: put `nav-shell` and `x-data="navCollapse()"` on the header,
284
+ // give each breakpoint band a `--nav-ramp`, and write the collapsing
285
+ // dimensions as calc()s off --nav-p. This file ships NO sizing opinion, so an
286
+ // app whose navbar collapses to different endpoints than the engine's adopts
287
+ // the mechanism without touching its markup.
288
+ //
289
+ // Four details are load-bearing:
290
+ //
291
+ // passive + rAF — the listener never blocks the compositor and coalesces a
292
+ // burst of scroll events (iOS momentum fires far above 60Hz) into one write
293
+ // per frame. The write lands on the HEADER, not :root: an inherited custom
294
+ // property written on :root dirties style for the whole document every
295
+ // frame, and it would leak the live page's scroll progress into the preview
296
+ // headers on /navbar.
297
+ //
298
+ // the smoothstep — collapsing a sticky, IN-FLOW header pulls the page up,
299
+ // so during the collapse content moves by the scroll AND by the shrink:
300
+ // faster than the finger, always. That is inherent; reclaiming the vertical
301
+ // space is the point. What is tunable is the shape of the burst. --nav-ramp
302
+ // is sized at 3x the band's collapse total and the ramp is smoothstepped,
303
+ // whose slope is zero at both ends, so content speed LEAVES 1x, peaks near
304
+ // 1.5x mid-ramp, and returns to 1x with no velocity step. A linear ramp
305
+ // equal to the collapse hits 2x and steps straight back to 1x.
306
+ //
307
+ // the short-page guard — collapsing shortens the document by the collapse
308
+ // total. On a page with barely more than that to scroll, the collapse
309
+ // deletes the very scroll room that triggered it, the browser clamps
310
+ // scrollY to 0, and the navbar flaps open and shut forever. roomExpanded
311
+ // adds back the shrink ALREADY applied, so the measurement cannot chase
312
+ // itself as it collapses.
313
+ //
314
+ // reduced motion — scroll-linked motion has no clock left to slow down, but
315
+ // resizing type under a moving finger is itself the motion some readers are
316
+ // asking us to drop. Under the query --nav-p snaps 0/1 on the old
317
+ // 60/5 hysteresis instead of interpolating.
318
+ window.navCollapse = function () {
319
+ return {
320
+ scrolled: false,
321
+ p: 0,
322
+ _ramp: 144,
323
+ _reduce: null,
324
+ _onScroll: null,
325
+ _onResize: null,
326
+ _onReduce: null,
327
+ init: function () {
328
+ var self = this;
329
+ var el = this.$el;
330
+ var queued = false;
331
+
332
+ this._reduce = window.matchMedia('(prefers-reduced-motion: reduce)');
333
+
334
+ function readRamp() {
335
+ var raw = parseFloat(getComputedStyle(el).getPropertyValue('--nav-ramp'));
336
+ self._ramp = raw > 0 ? raw : 144;
337
+ }
338
+
339
+ function apply() {
340
+ queued = false;
341
+ var ramp = self._ramp;
342
+ // Clamped: rubber-band overscroll reports a NEGATIVE scrollY, and a
343
+ // negative progress inflates the navbar past its expanded size.
344
+ var y = Math.max(0, window.scrollY);
345
+
346
+ // The height the document WOULD have with the navbar expanded. The
347
+ // add-back is the whole trick — see the guard note above.
348
+ var roomExpanded = document.documentElement.scrollHeight
349
+ - window.innerHeight
350
+ + ramp * self.p;
351
+
352
+ var p;
353
+ if (roomExpanded < ramp + 24) {
354
+ p = 0;
355
+ } else if (self._reduce.matches) {
356
+ p = (self.p > 0 ? y > 5 : y > 60) ? 1 : 0;
357
+ } else {
358
+ var t = Math.min(1, y / ramp);
359
+ p = t * t * (3 - 2 * t);
360
+ }
361
+
362
+ if (p !== self.p) {
363
+ self.p = p;
364
+ el.style.setProperty('--nav-p', p.toFixed(4));
365
+ }
366
+
367
+ // The shadow is the one thing still on a clock, and it may stay
368
+ // there: box-shadow paints, it never reflows, so it cannot move
369
+ // content. Hysteresis keeps it from strobing at the boundary.
370
+ var lit = self.scrolled ? y > 5 : y > 60;
371
+ if (lit !== self.scrolled) self.scrolled = lit;
372
+ }
373
+
374
+ this._onScroll = function () {
375
+ if (queued) return;
376
+ queued = true;
377
+ requestAnimationFrame(apply);
378
+ };
379
+ this._onResize = function () { readRamp(); self._onScroll(); };
380
+ this._onReduce = apply;
381
+
382
+ readRamp();
383
+ apply();
384
+
385
+ window.addEventListener('scroll', this._onScroll, { passive: true });
386
+ window.addEventListener('resize', this._onResize, { passive: true });
387
+ if (this._reduce.addEventListener) this._reduce.addEventListener('change', this._onReduce);
388
+ },
389
+ destroy: function () {
390
+ // A Turbo visit tears the header down and builds a new one; without
391
+ // this every visit would stack another listener on window.
392
+ if (this._onScroll) window.removeEventListener('scroll', this._onScroll);
393
+ if (this._onResize) window.removeEventListener('resize', this._onResize);
394
+ if (this._reduce && this._reduce.removeEventListener && this._onReduce) {
395
+ this._reduce.removeEventListener('change', this._onReduce);
396
+ }
397
+ }
398
+ };
399
+ };
400
+ </script>
401
+
247
402
  <%= stylesheet_link_tag "tailwind", "data-turbo-track": "reload" %>
248
403
  <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
249
404
  <% if Studio.sticky_table_headers %>
@@ -7,7 +7,11 @@
7
7
  border: 2px solid var(--color-border-strong);
8
8
  border-radius: 0.5rem;
9
9
  pointer-events: none;
10
- transition: width 0.15s ease;
10
+ /* --nav-p is a registered <number>, so the Scrolled toggle can TRANSITION
11
+ it directly — one interpolating property in place of the five
12
+ per-element font-size/width/padding transitions this block used to
13
+ carry. */
14
+ transition: width 0.15s ease, --nav-p 0.3s ease;
11
15
  }
12
16
  .navbar-preview header { position: static !important; }
13
17
 
@@ -18,19 +22,32 @@
18
22
  .navbar-preview.is-mobile .user-nav-col, .navbar-preview.is-mobile .user-nav-fit { padding-left: 0 !important; padding-right: 1rem !important; }
19
23
  .navbar-preview.is-mobile .nav-title { flex-direction: column !important; gap: 0 !important; line-height: 1.15 !important; }
20
24
  .navbar-preview.is-mobile .nav-title span:first-child { margin-bottom: -4px !important; }
21
- .navbar-preview.is-mobile .nav-title span:last-child { font-size: 1.5rem !important; }
22
25
  .navbar-preview.is-mobile .nav-logo-link { gap: 0.5rem !important; }
23
26
 
27
+ /* Breakpoint simulation for the --nav-p sizes. The live navbar picks these
28
+ up from media queries in layouts/_navbar, which cannot fire for a
29
+ CONTAINER at a fixed viewport width — so the same calc()s are re-keyed on
30
+ the wrapper. They are the same expressions, not a second set of numbers:
31
+ retune the collapse and retune both, or the preview starts lying. */
32
+ .navbar-preview.is-mobile .nav-shell {
33
+ --nav-logo-size: calc(3rem - 0.5rem * var(--nav-p));
34
+ }
35
+ .navbar-preview.bp-tiny .nav-shell {
36
+ --nav-title-size: calc(1.1rem - 0.2rem * var(--nav-p));
37
+ --nav-title-lead-size: calc(1.3rem - 0.3rem * var(--nav-p));
38
+ }
39
+ .navbar-preview.bp-small .nav-shell {
40
+ --nav-title-size: calc(1.25rem - 0.25rem * var(--nav-p));
41
+ --nav-title-lead-size: calc(1.5rem - 0.35rem * var(--nav-p));
42
+ }
43
+
24
44
  /* Tiny (< 400px) */
25
- .navbar-preview.bp-tiny .nav-title { font-size: 1.1rem !important; }
26
- .navbar-preview.bp-tiny .nav-title span:last-child { font-size: 1.3rem !important; }
27
45
  .navbar-preview.bp-tiny .nav-logo-link { gap: 0.25rem !important; }
28
46
  .navbar-preview.bp-tiny .user-nav-col { width: 14rem !important; }
29
47
  .navbar-preview.bp-tiny .user-nav-fit { max-width: 14rem !important; }
30
48
  .navbar-preview.bp-tiny .username-cap { max-width: 5rem !important; }
31
49
 
32
50
  /* Small (400-767px) */
33
- .navbar-preview.bp-small .nav-title { font-size: 1.25rem !important; }
34
51
  .navbar-preview.bp-small .user-nav-col { width: 15rem !important; }
35
52
  .navbar-preview.bp-small .user-nav-fit { max-width: 15rem !important; }
36
53
  .navbar-preview.bp-small .username-cap { max-width: 6rem !important; }
@@ -40,27 +57,14 @@
40
57
  .navbar-preview.is-desktop .user-nav-fit { max-width: 20rem !important; }
41
58
  .navbar-preview.is-desktop .username-cap { max-width: 7rem !important; }
42
59
 
43
- /* Transitions for scrolled toggle */
44
- .navbar-preview .nav-logo { transition: width 0.3s, height 0.3s; }
45
- .navbar-preview .nav-title,
46
- .navbar-preview .nav-title span:last-child { transition: font-size 0.3s; }
47
- .navbar-preview .py-6 { transition: padding 0.3s; }
60
+ /* Scrolled state. The size half of this used to be TWELVE !important rules
61
+ restating every collapsed value; the toggle now just sets --nav-p: 1 on
62
+ the wrapper (below) and the shipped calc()s do the rest, so the preview
63
+ exercises the real rules instead of a parallel copy of them. What is left
64
+ is the shadow, which the live header gets from .is-scrolled and a preview
65
+ header (no x-data, so no `scrolled`) cannot. */
48
66
  .navbar-preview header { transition: box-shadow 0.3s, border-color 0.3s; }
49
- .navbar-preview [data-balance-display] { transition: font-size 0.3s; }
50
-
51
- /* Scrolled state: base (all breakpoints) */
52
67
  .navbar-preview.is-scrolled-preview header { box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1); border-bottom: 1px solid var(--color-border-subtle); }
53
- .navbar-preview.is-scrolled-preview .py-6 { padding-top: 0.5rem !important; padding-bottom: 0.5rem !important; }
54
- .navbar-preview.is-scrolled-preview .nav-logo { width: 2rem !important; height: 2rem !important; }
55
- .navbar-preview.is-scrolled-preview .nav-title { font-size: 1.25rem !important; }
56
- .navbar-preview.is-scrolled-preview [data-balance-display] { font-size: 1.125rem !important; }
57
-
58
- /* Scrolled state: mobile overrides */
59
- .navbar-preview.is-mobile.is-scrolled-preview .nav-logo { width: 2.5rem !important; height: 2.5rem !important; }
60
- .navbar-preview.bp-tiny.is-scrolled-preview .nav-title { font-size: 0.9rem !important; }
61
- .navbar-preview.bp-tiny.is-scrolled-preview .nav-title span:last-child { font-size: 1rem !important; }
62
- .navbar-preview.bp-small.is-scrolled-preview .nav-title { font-size: 1rem !important; }
63
- .navbar-preview.bp-small.is-scrolled-preview .nav-title span:last-child { font-size: 1.15rem !important; }
64
68
 
65
69
  /* Slider with device marker */
66
70
  .slider-wrap { position: relative; }
@@ -139,7 +143,7 @@
139
143
  Scrolled
140
144
  </button>
141
145
  </div>
142
- <div class="navbar-preview <%= bp[:classes] %>" :class="scrolled && 'is-scrolled-preview'" :style="'width: ' + w + 'px'">
146
+ <div class="navbar-preview <%= bp[:classes] %>" :class="scrolled && 'is-scrolled-preview'" :style="'width: ' + w + 'px; --nav-p: ' + (scrolled ? 1 : 0)">
143
147
  <%= render "layouts/navbar", preview: true, show_logged_in: section[:show_logged_in] %>
144
148
  </div>
145
149
  </div>
@@ -1,3 +1,3 @@
1
1
  module Studio
2
- VERSION = "0.62.5"
2
+ VERSION = "0.62.6"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: studio-engine
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.62.5
4
+ version: 0.62.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alex McRitchie