@homepages/template-kit 0.8.1-dev-20260718020735 → 0.9.0-dev-20260719205406
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.
- package/CHANGELOG.md +81 -1
- package/README.md +2 -2
- package/dist/canvas-core/chrome-css.d.ts +24 -0
- package/dist/canvas-core/chrome-css.js +403 -0
- package/dist/canvas-core/chrome.d.ts +49 -0
- package/dist/canvas-core/chrome.js +147 -0
- package/dist/canvas-core/frame.d.ts +20 -0
- package/dist/canvas-core/frame.js +23 -0
- package/dist/canvas-core/hidden-tabs.d.ts +16 -0
- package/dist/canvas-core/hidden-tabs.js +84 -0
- package/dist/canvas-core/resolve-target.js +55 -0
- package/dist/canvas-core/scroll.d.ts +10 -0
- package/dist/canvas-core/scroll.js +15 -0
- package/dist/canvas-core/section-dom.d.ts +6 -0
- package/dist/canvas-core/section-dom.js +43 -0
- package/dist/canvas-core/srcdoc.d.ts +8 -0
- package/dist/canvas-core/srcdoc.js +77 -0
- package/dist/canvas-core/types.d.ts +76 -0
- package/dist/canvas-core.d.ts +9 -0
- package/dist/canvas-core.js +9 -0
- package/dist/cli/check/css.js +2 -2
- package/dist/cli/check/loader.js +10 -7
- package/dist/cli/check/stages/deps.js +1 -1
- package/dist/cli/check/stages/size.js +2 -2
- package/dist/cli/check/stages/tree.js +1 -1
- package/dist/cli/check/stages/validate/index.js +1 -1
- package/dist/cli/check/stages/validate/nav-contract.js +1 -1
- package/dist/cli/check/stages/validate/orchestrator.js +1 -1
- package/dist/cli/check/workspace.js +2 -2
- package/dist/cli/dev/build-css.js +12 -6
- package/dist/cli/dev/compose-template.js +3 -2
- package/dist/cli/dev/diagnostics.js +68 -0
- package/dist/cli/dev/discover.js +2 -2
- package/dist/cli/dev/index.js +12 -2
- package/dist/cli/dev/inspect.js +1 -1
- package/dist/cli/dev/island-bootstrap.js +1 -1
- package/dist/cli/dev/island-canvas-bootstrap.js +204 -0
- package/dist/cli/dev/section-page.js +39 -2
- package/dist/cli/dev/server.js +35 -5
- package/dist/cli/dev/tailwind.js +1 -1
- package/dist/cli/dev/tsconfig-paths-plugin.js +90 -0
- package/dist/cli/dev/vite-server.js +7 -1
- package/dist/cli/dev/workspace.js +2 -2
- package/dist/cli/link/index.js +1 -1
- package/dist/cli/link/overlay.js +1 -1
- package/dist/cli/new/emit.js +1 -1
- package/dist/cli/new/index.js +1 -1
- package/dist/cli/new/scaffold-assets.js +2 -2
- package/dist/cli/pack/collect.js +1 -1
- package/dist/cli/pack/guards.js +1 -1
- package/dist/cli/pack/index.js +1 -1
- package/dist/cli/theme/generate.js +1 -1
- package/dist/cli/theme/load-theme.js +1 -1
- package/dist/dev-client/assets/index-CJa9G1Na.css +1 -0
- package/dist/dev-client/assets/index-C_1atZs4.js +450 -0
- package/dist/dev-client/index.html +2 -2
- package/dist/islands/client-directive.d.ts +7 -0
- package/dist/{eslint/is-client-file.js → islands/client-directive.js} +6 -3
- package/dist/islands/detect.d.ts +1 -1
- package/dist/islands/detect.js +1 -1
- package/dist/islands/discover.js +1 -1
- package/dist/islands/esbuild-plugin.js +1 -1
- package/dist/islands/wrap.js +1 -1
- package/dist/package.js +1 -1
- package/dist/rules.d.ts +176 -0
- package/dist/rules.js +209 -0
- package/dist/ssr.d.ts +2 -2
- package/dist/ssr.js +2 -2
- package/dist/tokens.css +21 -0
- package/guide/INDEX.md +2 -2
- package/guide/check.md +5 -5
- package/guide/dev.md +82 -30
- package/guide/eslint.md +13 -10
- package/guide/llms.txt +4 -3
- package/guide/quickstart.md +1 -1
- package/guide/rules/INDEX.md +3 -2
- package/guide/upgrading.md +18 -2
- package/package.json +8 -4
- package/tsconfig.json +1 -1
- package/dist/dev-client/assets/index-DfEfrp6P.css +0 -1
- package/dist/dev-client/assets/index-bpMP0b0W.js +0 -50
- package/dist/eslint/is-client-file.d.ts +0 -4
- package/dist/eslint/rules/image-bare-needs-reason.js +0 -43
- package/dist/eslint/rules/no-client-directive-in-contract.js +0 -31
- package/dist/eslint/rules/no-client-runtime-in-server.js +0 -103
- package/dist/eslint/rules/no-css-import-from-render-path.js +0 -29
- package/dist/eslint/rules/no-hex.js +0 -188
- package/dist/eslint/rules/no-inline-style.js +0 -27
- package/dist/eslint/rules/no-nondeterminism.js +0 -48
- package/dist/eslint/rules/no-raw-element.js +0 -38
- package/dist/eslint/rules/props-from-schema.js +0 -37
- package/dist/eslint/rules/require-island-editor.js +0 -74
- package/dist/eslint/rules/serializable-island-props.js +0 -112
- package/dist/eslint/rules/slot-marker-literal.js +0 -38
- package/dist/eslint.d.ts +0 -6
- package/dist/eslint.js +0 -80
- package/dist/rules/registry.js +0 -16
package/CHANGELOG.md
CHANGED
|
@@ -1,14 +1,94 @@
|
|
|
1
1
|
# @homepages/template-kit
|
|
2
2
|
|
|
3
|
-
## 0.
|
|
3
|
+
## 0.9.0-dev-20260719205406
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 5030122: The authoring ESLint rules move out of `@homepages/template-kit` into the new
|
|
8
|
+
`@homepages/eslint-plugin-template`, which takes the kit as a peerDependency.
|
|
9
|
+
|
|
10
|
+
The kit's `./eslint` subpath is removed with no shim. Replace
|
|
11
|
+
|
|
12
|
+
import kit from "@homepages/template-kit/eslint";
|
|
13
|
+
|
|
14
|
+
with
|
|
15
|
+
|
|
16
|
+
import kit from "@homepages/eslint-plugin-template";
|
|
17
|
+
|
|
18
|
+
and add `@homepages/eslint-plugin-template` to devDependencies. Rule ids are
|
|
19
|
+
unchanged — the namespace is still `template-kit/`, so any rule-specific
|
|
20
|
+
`eslint-disable` comment and any `rules` override keep working.
|
|
21
|
+
|
|
22
|
+
The kit also gains a `./rules` subpath exposing the rule-id registry, and its
|
|
23
|
+
`./ssr` entry now exports the `"use client"` resolver (`isClientFile`,
|
|
24
|
+
`resolveImportedFile`, `resetClientFileCache`) that the plugin's rules consume
|
|
25
|
+
over the peer edge.
|
|
4
26
|
|
|
5
27
|
### Patch Changes
|
|
6
28
|
|
|
29
|
+
- 10f53a7: `template-kit dev`'s canvas hover/select ring no longer overflows the page's
|
|
30
|
+
x-axis when hovering a full-bleed section (e.g. a hero or header flush with
|
|
31
|
+
both page edges). The ring's geometry inflated the target's bounding rect by
|
|
32
|
+
a fixed offset on every side with no bound, so a rect already touching the
|
|
33
|
+
document edge pushed the ring 2px past it, growing the iframe's horizontal
|
|
34
|
+
scroll extent. The ring box is now clamped to `[0, containerWidth]`.
|
|
35
|
+
- f3f6d70: `template-kit check` no longer fails on a tsconfig `paths` alias (e.g.
|
|
36
|
+
`"@/platform/*": ["../platform/*"]`) or on a section whose contract files
|
|
37
|
+
share a module large enough for esbuild to code-split. Previously the
|
|
38
|
+
loader's css-stub plugin decided "external vs. bundle" from the specifier's
|
|
39
|
+
own text, so a `paths`-resolved alias got re-emitted as a literal bare
|
|
40
|
+
import Node couldn't resolve; separately, the loader's build applied
|
|
41
|
+
`publicPath` to code-split `chunk-*.js` cross-imports the same way it does
|
|
42
|
+
to real asset files, producing an import path Node couldn't resolve either.
|
|
43
|
+
Both are now decided from the actual resolved file, and `publicPath` is no
|
|
44
|
+
longer set on the loader's build.
|
|
45
|
+
- 1c2c2db: `template-kit dev`'s preview server no longer 500s on `/runtime/base.css`.
|
|
46
|
+
The route resolved the file at a path the build never emits to
|
|
47
|
+
(`dist/css/base.css`; the build copies it flat to `dist/base.css`), so every
|
|
48
|
+
section rendered unstyled — Tailwind-derived colors and layout were silently
|
|
49
|
+
missing everywhere, only arbitrary-value element geometry survived. The
|
|
50
|
+
route now checks the built (flat) path first and falls back to the source
|
|
51
|
+
(`src/css/`) layout, so it resolves correctly both from an installed kit and
|
|
52
|
+
when running the CLI unbuilt via `tsx` during kit development.
|
|
53
|
+
- 989d23e: `template-kit dev` gains an optional `--diagnostics-cmd` flag: point it at any
|
|
54
|
+
command that prints `{"violations": [...]}` JSON for a `--template`/`--section`
|
|
55
|
+
pair, and the standalone section preview
|
|
56
|
+
(`/sections/<template>/<section>/<fixture>`) shows a dismissible banner when it
|
|
57
|
+
reports violations. Unset by default — no behavior change for a workspace that
|
|
58
|
+
doesn't configure it.
|
|
59
|
+
- e1d5792: `template-kit dev`'s preview server now compiles a template's theme-color
|
|
60
|
+
Tailwind utilities (`bg-primary`, `text-ink`, `bg-background`, etc.) instead
|
|
61
|
+
of silently dropping them. `assembleTemplateCss` used to concatenate the
|
|
62
|
+
compiled theme (`compileThemeToCss`, the `@theme inline` alias layer plus
|
|
63
|
+
its `:root` custom props) onto Tailwind's CLI output _after_ the CLI had
|
|
64
|
+
already run, so the theme's color tokens were never part of the build graph
|
|
65
|
+
Tailwind scanned — `@theme` outside that graph is inert, and silently so, so
|
|
66
|
+
any utility built on a template color never got generated. No error, no
|
|
67
|
+
warning: every section rendered with correct layout but default
|
|
68
|
+
black-on-white text and backgrounds. The theme partial is now written to the
|
|
69
|
+
dev cache and `@import`ed into the Tailwind entry ahead of the section
|
|
70
|
+
`@source` scans, mirroring how the registry's own publish pipeline
|
|
71
|
+
(`ci/build-css.ts`) has always assembled it. `fontFaces` is still prepended
|
|
72
|
+
to the final bundle separately, since a nested `@import url(...)` doesn't
|
|
73
|
+
hoist out of an imported partial.
|
|
74
|
+
- 2e36277: Escape `>` and `&` as well as `<` in the dev server's island map, matching the escaping used by published pages.
|
|
7
75
|
- 1c4c7dd: Island hydration is now nesting-safe: `hydrateIslands` skips any island marker
|
|
8
76
|
nested inside another marker, so an island can render another island without the
|
|
9
77
|
child being hydrated twice (once by its parent root, once independently). Enables
|
|
10
78
|
interactive islands (accordion, card-slider, carousel) to wrap existing leaf
|
|
11
79
|
islands (expandable-text) on published pages.
|
|
80
|
+
- ff1d6de: `template-kit dev`'s canvas mirror now shows the Islands inspector panel for a
|
|
81
|
+
section-level selection, not just a slot selection. A `fill-parent`/`fill-self`
|
|
82
|
+
island with no owning slot of its own only ever gets its shield dropped by a
|
|
83
|
+
section-level click, so the panel previously could show it as `shielded` but
|
|
84
|
+
never `interactive`. Existing slot-selection behavior (edit panel + Islands
|
|
85
|
+
list) is unchanged.
|
|
86
|
+
- 41d0e88: `template-kit dev` now honors a workspace's tsconfig `paths` when resolving
|
|
87
|
+
imports, the same way `check` already does via esbuild's native tsconfig
|
|
88
|
+
lookup. Previously the dev server's Vite instance had no `paths` awareness at
|
|
89
|
+
all, so a workspace mapping (e.g. `"@/lib/*": ["../lib/*"]`) that made `check`
|
|
90
|
+
pass left `dev` 500ing with `Cannot find module`. A workspace with no `paths`
|
|
91
|
+
mapping is unaffected.
|
|
12
92
|
|
|
13
93
|
## 0.8.0
|
|
14
94
|
|
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@ The fastest way to start is the workspace scaffolder — it creates a workspace
|
|
|
17
17
|
working starter template and the full toolchain already wired:
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
|
-
npm create homepages
|
|
20
|
+
npm create @homepages/workspace <name>
|
|
21
21
|
cd <name>
|
|
22
22
|
npm install
|
|
23
23
|
npm run dev # preview the starter; the island hydrates
|
|
@@ -97,7 +97,7 @@ Full contract: **[guide/primitives.md](guide/primitives.md)**.
|
|
|
97
97
|
| `@homepages/template-kit/browser` | Browser helpers for published pages (site config, signed ingest) |
|
|
98
98
|
| `@homepages/template-kit/styles.css` | The global Tailwind preamble: the breakpoint ladder, the spacing base unit, the motion defaults, kit source registration |
|
|
99
99
|
| `@homepages/template-kit/base.css` | Cascade-layer order, the three reset gaps Tailwind's preflight leaves, `.tr-section`, and the motion tokens |
|
|
100
|
-
| `@homepages/template
|
|
100
|
+
| `@homepages/eslint-plugin-template` | ESLint flat-config preset (its own package) |
|
|
101
101
|
| `@homepages/template-kit/tsconfig` | tsconfig preset (`"extends"`) |
|
|
102
102
|
|
|
103
103
|
One specifier for everything you write a section against. The exceptions are
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { MarkerAttrs } from "./types.js";
|
|
2
|
+
//#region src/canvas-core/chrome-css.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* Custom properties the chrome CSS reads but deliberately does not declare —
|
|
5
|
+
* the host must set them on the iframe document's root element. This is the
|
|
6
|
+
* host's obligation, not a suggestion: an adapter that skips one leaves the
|
|
7
|
+
* declared inline fallback in force, which renders plausibly and silently
|
|
8
|
+
* wrong rather than failing.
|
|
9
|
+
*
|
|
10
|
+
* - `--we-panel-w`: the floating panel's measured width, mirrored onto the
|
|
11
|
+
* iframe root so the page card's right padding clears it. Unset, the card
|
|
12
|
+
* lays out against the 436px base-width fallback and drifts out of alignment
|
|
13
|
+
* whenever the real panel is any other width.
|
|
14
|
+
* - `--we-content`: the host's interior scaling unit, mirrored in so hidden-tab
|
|
15
|
+
* chrome scales with the rest of the UI. Unset, it pins to the 1px fallback
|
|
16
|
+
* and the tabs stay laptop-sized while every surrounding surface scales.
|
|
17
|
+
*
|
|
18
|
+
* Anything added here must actually be read by the kernel; the token test
|
|
19
|
+
* fails on a stale entry.
|
|
20
|
+
*/
|
|
21
|
+
declare const HOST_INJECTED_PROPERTIES: readonly ["--we-panel-w", "--we-content"];
|
|
22
|
+
declare function buildChromeCss(markers: MarkerAttrs): string;
|
|
23
|
+
//#endregion
|
|
24
|
+
export { HOST_INJECTED_PROPERTIES, buildChromeCss };
|
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
import { TAB_TUCK } from "./hidden-tabs.js";
|
|
2
|
+
|
|
3
|
+
//#region src/canvas-core/chrome-css.ts
|
|
4
|
+
/**
|
|
5
|
+
* Custom properties the chrome CSS reads but deliberately does not declare —
|
|
6
|
+
* the host must set them on the iframe document's root element. This is the
|
|
7
|
+
* host's obligation, not a suggestion: an adapter that skips one leaves the
|
|
8
|
+
* declared inline fallback in force, which renders plausibly and silently
|
|
9
|
+
* wrong rather than failing.
|
|
10
|
+
*
|
|
11
|
+
* - `--we-panel-w`: the floating panel's measured width, mirrored onto the
|
|
12
|
+
* iframe root so the page card's right padding clears it. Unset, the card
|
|
13
|
+
* lays out against the 436px base-width fallback and drifts out of alignment
|
|
14
|
+
* whenever the real panel is any other width.
|
|
15
|
+
* - `--we-content`: the host's interior scaling unit, mirrored in so hidden-tab
|
|
16
|
+
* chrome scales with the rest of the UI. Unset, it pins to the 1px fallback
|
|
17
|
+
* and the tabs stay laptop-sized while every surrounding surface scales.
|
|
18
|
+
*
|
|
19
|
+
* Anything added here must actually be read by the kernel; the token test
|
|
20
|
+
* fails on a stale entry.
|
|
21
|
+
*/
|
|
22
|
+
const HOST_INJECTED_PROPERTIES = ["--we-panel-w", "--we-content"];
|
|
23
|
+
function buildChromeCss(markers) {
|
|
24
|
+
return `
|
|
25
|
+
html, body { background: transparent; }
|
|
26
|
+
body {
|
|
27
|
+
--we-page-radius: 14px;
|
|
28
|
+
margin: 0;
|
|
29
|
+
/* Padding aligns the page card with the floating panel and centers
|
|
30
|
+
it in the visible canvas area. Top/bottom match the panel's
|
|
31
|
+
top-5/bottom-5 inset (20px). Left is 20px. The right constant
|
|
32
|
+
(40px) covers the panel's right-5 inset (20px) plus an equal
|
|
33
|
+
20px gap, giving symmetric breathing room on both sides of the
|
|
34
|
+
card between the canvas's left edge and the panel's left edge.
|
|
35
|
+
Fallback 436px matches the panel's base CSS width. */
|
|
36
|
+
padding: 20px calc(var(--we-panel-w, 436px) + 40px) 20px 20px;
|
|
37
|
+
/* Body is the container for the auto-fit calc on #we-zoom. With
|
|
38
|
+
container-type: inline-size, descendants resolve 100cqw to the
|
|
39
|
+
body's content-box width — exactly the width we want the card
|
|
40
|
+
to scale into. */
|
|
41
|
+
container-type: inline-size;
|
|
42
|
+
position: relative;
|
|
43
|
+
display: flex;
|
|
44
|
+
justify-content: center;
|
|
45
|
+
align-items: flex-start;
|
|
46
|
+
min-height: 100%;
|
|
47
|
+
overflow: auto;
|
|
48
|
+
color: #14180F;
|
|
49
|
+
font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
|
|
50
|
+
/* Dotted canvas surface. background-attachment: local makes the grid
|
|
51
|
+
scroll together with the page card so the canvas + website read as
|
|
52
|
+
one scene being panned, not a card sliding over a fixed grid. */
|
|
53
|
+
background-image: radial-gradient(rgba(20, 18, 13, 0.13) 1px, transparent 1.2px);
|
|
54
|
+
background-size: 20px 20px;
|
|
55
|
+
background-position: 10px 10px;
|
|
56
|
+
background-attachment: local;
|
|
57
|
+
}
|
|
58
|
+
/* Hide the native scrollbar on whichever element ends up being the
|
|
59
|
+
scroll container. Some browsers route the scroll to the root
|
|
60
|
+
element (html) rather than body, so cover both. Wheel / trackpad /
|
|
61
|
+
keyboard and programmatic scrollTo still work. */
|
|
62
|
+
html, body { scrollbar-width: none; -ms-overflow-style: none; }
|
|
63
|
+
html::-webkit-scrollbar,
|
|
64
|
+
body::-webkit-scrollbar { width: 0; height: 0; display: none; }
|
|
65
|
+
#we-zoom {
|
|
66
|
+
/* Auto-fit + manual zoom, declarative.
|
|
67
|
+
Two-slope formula: below ~1472px canvas (≤1536px viewport) slope 1
|
|
68
|
+
(0.8×) keeps the card at 80% of canvas width — the original
|
|
69
|
+
breathing-room factor. Above that breakpoint slope 2 (0.214×+0.600)
|
|
70
|
+
takes over, growing sub-linearly so the percentage of canvas the
|
|
71
|
+
card occupies shrinks on wider monitors while absolute pixel width
|
|
72
|
+
keeps growing. At a 1920px monitor this yields ~12.5% smaller than
|
|
73
|
+
the old native-1440px cap, restoring the birds-eye view. The outer
|
|
74
|
+
min(1,...) still prevents upscaling beyond native width.
|
|
75
|
+
|
|
76
|
+
--user-zoom is the manual multiplier the toolbar writes (1.0 = at
|
|
77
|
+
fit / "100%"). The browser recomputes on every resize — no host JS
|
|
78
|
+
needed.
|
|
79
|
+
|
|
80
|
+
Scale via the zoom property, NOT transform: scale(). A transform
|
|
81
|
+
only shrinks the PAINT — the layout box keeps the full unscaled
|
|
82
|
+
1440px footprint, so the body (scroll container) reserves space the
|
|
83
|
+
shrunken page no longer fills and the canvas scrolls far past the
|
|
84
|
+
bottom. zoom scales the layout box too, so scrollHeight tracks the
|
|
85
|
+
visible page exactly. getBoundingClientRect() reports visual coords
|
|
86
|
+
under either, so overlay-ring + scroll-into-view math in
|
|
87
|
+
iframe-canvas.tsx is unaffected; flex justify-content:center
|
|
88
|
+
re-centers the smaller box, so no transform-origin is needed. */
|
|
89
|
+
--user-zoom: 1;
|
|
90
|
+
--fit-zoom: min(0.9, min(
|
|
91
|
+
calc(0.72 * 100cqw / 1440px),
|
|
92
|
+
calc(0.1926 * 100cqw / 1440px + 0.540)
|
|
93
|
+
));
|
|
94
|
+
zoom: calc(var(--fit-zoom) * var(--user-zoom));
|
|
95
|
+
transition: zoom 150ms cubic-bezier(0.4, 0, 0.2, 1);
|
|
96
|
+
flex-shrink: 0;
|
|
97
|
+
/* Positioned + stacked above #we-htabs (z-index:1) so the page's
|
|
98
|
+
drop-shadow falls over the hidden-tab's inner edge, nestling it against
|
|
99
|
+
the frame. #we-chrome-overlay (z-index:100) stays above. */
|
|
100
|
+
position: relative;
|
|
101
|
+
z-index: 2;
|
|
102
|
+
}
|
|
103
|
+
#we-page {
|
|
104
|
+
width: 1440px;
|
|
105
|
+
overflow: hidden;
|
|
106
|
+
border-radius: var(--we-page-radius);
|
|
107
|
+
background: #f5f3ee;
|
|
108
|
+
padding: 0;
|
|
109
|
+
box-shadow:
|
|
110
|
+
0 1px 0 rgba(20,18,13,0.06),
|
|
111
|
+
0 2px 6px -2px rgba(20,18,13,0.10),
|
|
112
|
+
0 12px 28px -8px rgba(20,18,13,0.18),
|
|
113
|
+
0 32px 64px -20px rgba(20,18,13,0.28);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/* Section wrapper -- structure only. No selection chrome here;
|
|
117
|
+
rings are painted by #we-chrome-overlay (see below). Zero margin
|
|
118
|
+
+ padding means sections butt edge-to-edge, matching publisher. */
|
|
119
|
+
[${markers.sectionInstanceId}] {
|
|
120
|
+
position: relative;
|
|
121
|
+
margin: 0;
|
|
122
|
+
padding: 0;
|
|
123
|
+
cursor: pointer;
|
|
124
|
+
display: grid;
|
|
125
|
+
grid-template-rows: 1fr;
|
|
126
|
+
/* Reveal transition — governs the hidden→visible direction because
|
|
127
|
+
CSS uses the destination state's transition property. Spring easing
|
|
128
|
+
gives a snappy entrance. */
|
|
129
|
+
transition:
|
|
130
|
+
grid-template-rows 200ms cubic-bezier(0.16, 1, 0.3, 1),
|
|
131
|
+
opacity 180ms cubic-bezier(0.16, 1, 0.3, 1);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/* Section title pill -- lives in #we-chrome-overlay above the rings
|
|
135
|
+
so it straddles the top border without being covered. Sections
|
|
136
|
+
only; slots get the outline ring but no on-canvas label. */
|
|
137
|
+
.we-chrome-label {
|
|
138
|
+
position: absolute;
|
|
139
|
+
pointer-events: none;
|
|
140
|
+
z-index: 1;
|
|
141
|
+
background: rgba(229,184,50,0.95);
|
|
142
|
+
color: #282c32;
|
|
143
|
+
padding: 3px 8px;
|
|
144
|
+
border-radius: 4px;
|
|
145
|
+
font-size: 10.5px;
|
|
146
|
+
font-weight: 600;
|
|
147
|
+
line-height: 1;
|
|
148
|
+
letter-spacing: 0.02em;
|
|
149
|
+
text-transform: uppercase;
|
|
150
|
+
white-space: nowrap;
|
|
151
|
+
opacity: 0;
|
|
152
|
+
transform: translateY(calc(-50% - 4px)) scale(0.96);
|
|
153
|
+
transition:
|
|
154
|
+
opacity 60ms ease-in,
|
|
155
|
+
transform 60ms ease-in;
|
|
156
|
+
}
|
|
157
|
+
.we-chrome-label[data-active="true"] {
|
|
158
|
+
opacity: 1;
|
|
159
|
+
transform: translateY(-50%) scale(1);
|
|
160
|
+
transition:
|
|
161
|
+
opacity 130ms cubic-bezier(0.16, 1, 0.3, 1) 50ms,
|
|
162
|
+
transform 130ms cubic-bezier(0.16, 1, 0.3, 1) 50ms;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/* Hidden state -- collapse the section out of the page so it reflows shut.
|
|
166
|
+
The 0-height wrapper stays in flow as the anchor for the section's
|
|
167
|
+
floating edge tab (built in #we-htabs by iframe-canvas.tsx). See
|
|
168
|
+
docs/superpowers/specs/2026-06-02-hidden-section-edge-tabs-design.md. */
|
|
169
|
+
[${markers.sectionInstanceId}][data-hidden="true"] {
|
|
170
|
+
grid-template-rows: 0fr;
|
|
171
|
+
opacity: 0;
|
|
172
|
+
pointer-events: none;
|
|
173
|
+
/* Collapse transition — governs the visible→hidden direction.
|
|
174
|
+
Faster + accelerating so the hide feels decisive. */
|
|
175
|
+
transition:
|
|
176
|
+
grid-template-rows 150ms cubic-bezier(0.4, 0, 1, 1),
|
|
177
|
+
opacity 120ms cubic-bezier(0.4, 0, 1, 1);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/* Slot wrapper -- structure only. No outline here; the overlay
|
|
181
|
+
ring handles slot hover + selection too. */
|
|
182
|
+
[${markers.slotId}] {
|
|
183
|
+
position: relative;
|
|
184
|
+
cursor: pointer;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/* Slot-group container -- the gaps between grouped members (dividers,
|
|
188
|
+
padding) carry no slot id, so give the group container itself
|
|
189
|
+
the pointer cursor: the whole group reads as one clickable unit, and
|
|
190
|
+
the overlay ring outlines this container (see iframe-canvas.tsx). */
|
|
191
|
+
[${markers.slotGroup}],
|
|
192
|
+
[${markers.slotItem}] {
|
|
193
|
+
position: relative;
|
|
194
|
+
cursor: pointer;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/* Per-section body holds the API HTML.
|
|
198
|
+
1. position:relative gives the body a positioning context for any
|
|
199
|
+
body-scoped chrome or future body-only effects.
|
|
200
|
+
2. isolation:isolate SEALS the section's z-index hierarchy
|
|
201
|
+
inside this stacking context. Without it, any positioned
|
|
202
|
+
descendant with z-index > 0 (the header's sticky+z-50, a
|
|
203
|
+
dropdown's z-40, a sticky CTA) creates a stacking context
|
|
204
|
+
that escapes upward. Sections never need to know the
|
|
205
|
+
editor chrome exists -- internal z-index is unconstrained. */
|
|
206
|
+
/* overflow:hidden clips section content as the grid row collapses;
|
|
207
|
+
min-height:0 lets the grid child shrink below its content size.
|
|
208
|
+
Constraint: overflow:hidden is permanent (not just during animation), so
|
|
209
|
+
section content must not overflow its section-body boundary — sticky
|
|
210
|
+
elements, absolutely-positioned dropdowns, or shadows that bleed outside
|
|
211
|
+
the section edge will be silently clipped. Template sections are
|
|
212
|
+
self-contained blocks; this is safe in practice. */
|
|
213
|
+
.we-section-body { position: relative; isolation: isolate; overflow: hidden; min-height: 0; }
|
|
214
|
+
|
|
215
|
+
/* Chrome overlay -- sibling of #we-zoom inside the iframe body.
|
|
216
|
+
Sized to body's padding box; rings inside are absolutely
|
|
217
|
+
positioned by JS using getBoundingClientRect + window.scrollY/X.
|
|
218
|
+
Lives OUTSIDE #we-page so rings ride above the page's rounded
|
|
219
|
+
clip and any section's overflow:hidden. pointer-events:none so
|
|
220
|
+
clicks pass through to the section/slot underneath -- the
|
|
221
|
+
click resolver remains on doc.body. */
|
|
222
|
+
#we-chrome-overlay {
|
|
223
|
+
position: absolute;
|
|
224
|
+
inset: 0;
|
|
225
|
+
pointer-events: none;
|
|
226
|
+
z-index: 100;
|
|
227
|
+
}
|
|
228
|
+
.we-chrome-ring {
|
|
229
|
+
position: absolute;
|
|
230
|
+
pointer-events: none;
|
|
231
|
+
border: 2px solid transparent;
|
|
232
|
+
border-radius: 6px;
|
|
233
|
+
opacity: 0;
|
|
234
|
+
box-sizing: border-box;
|
|
235
|
+
}
|
|
236
|
+
/* Hover ring -- instant on cursor arrival, fades out on leave.
|
|
237
|
+
Position updates instantly (no top/left transition) so dragging
|
|
238
|
+
across elements never flickers. Ink stroke + white drop-shadow
|
|
239
|
+
halo (same trick as crop handles in globals.css) keeps the ring
|
|
240
|
+
readable on both light sections and dark ones like footer. */
|
|
241
|
+
.we-chrome-ring--hover {
|
|
242
|
+
border-color: rgba(15,42,34,0.85);
|
|
243
|
+
filter: drop-shadow(0 0 1px rgba(255,255,255,0.95));
|
|
244
|
+
transition: opacity 70ms ease-in;
|
|
245
|
+
}
|
|
246
|
+
.we-chrome-ring--hover[data-active="true"] {
|
|
247
|
+
opacity: 1;
|
|
248
|
+
transition: opacity 100ms cubic-bezier(0.16, 1, 0.3, 1);
|
|
249
|
+
}
|
|
250
|
+
/* Selection ring -- appears instantly (continuous with hover),
|
|
251
|
+
then morphs ink → gold. No position transition so resize
|
|
252
|
+
tracking stays tight. Same ink + white halo as hover. */
|
|
253
|
+
.we-chrome-ring--selection {
|
|
254
|
+
border-color: rgba(15,42,34,0.85);
|
|
255
|
+
filter: drop-shadow(0 0 1px rgba(255,255,255,0.95));
|
|
256
|
+
transform: scale(1);
|
|
257
|
+
box-shadow: none;
|
|
258
|
+
transition:
|
|
259
|
+
opacity 50ms ease-in,
|
|
260
|
+
border-color 160ms cubic-bezier(0.2, 0, 0, 1),
|
|
261
|
+
box-shadow 160ms cubic-bezier(0.2, 0, 0, 1);
|
|
262
|
+
}
|
|
263
|
+
.we-chrome-ring--selection[data-active="true"] {
|
|
264
|
+
opacity: 1;
|
|
265
|
+
/* Instant show on select; only border + glow animate */
|
|
266
|
+
transition:
|
|
267
|
+
opacity 0ms,
|
|
268
|
+
border-color 180ms cubic-bezier(0.2, 0, 0, 1),
|
|
269
|
+
box-shadow 180ms cubic-bezier(0.2, 0, 0, 1);
|
|
270
|
+
}
|
|
271
|
+
.we-chrome-ring--selection[data-kind="section"],
|
|
272
|
+
.we-chrome-ring--selection[data-kind="slot"] {
|
|
273
|
+
border-color: #E5B832;
|
|
274
|
+
filter: drop-shadow(0 0 1px rgba(255,255,255,0.95));
|
|
275
|
+
box-shadow: 0 0 0 1px rgba(229,184,50,0.25), 0 8px 24px -10px rgba(229,184,50,0.35);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
@media (prefers-reduced-motion: reduce) {
|
|
279
|
+
.we-chrome-ring,
|
|
280
|
+
.we-chrome-label,
|
|
281
|
+
[${markers.sectionInstanceId}] {
|
|
282
|
+
transition: none !important;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/* Hidden-section edge tabs -------------------------------------
|
|
287
|
+
One dim chip-tab per hidden section, pinned to the page's left edge at
|
|
288
|
+
the collapsed wrapper's reflow seam. Built + positioned by
|
|
289
|
+
iframe-canvas.tsx (positionHiddenTabs); de-collided via placeTabs. Lives
|
|
290
|
+
in its own layer (sibling of #we-zoom) so it rides with page scroll and
|
|
291
|
+
stays a constant size at any zoom — like the rings. Colors are literals
|
|
292
|
+
(the iframe doesn't inherit dashboard Tailwind tokens). */
|
|
293
|
+
#we-htabs {
|
|
294
|
+
position: absolute;
|
|
295
|
+
inset: 0;
|
|
296
|
+
pointer-events: none;
|
|
297
|
+
z-index: 1;
|
|
298
|
+
}
|
|
299
|
+
.we-htab {
|
|
300
|
+
position: absolute;
|
|
301
|
+
transform: translateY(-50%);
|
|
302
|
+
display: flex;
|
|
303
|
+
align-items: center;
|
|
304
|
+
gap: calc(9 * var(--we-content, 1px));
|
|
305
|
+
height: calc(34 * var(--we-content, 1px));
|
|
306
|
+
padding: 0 calc(${12 + 16} * var(--we-content, 1px)) 0 calc(13 * var(--we-content, 1px));
|
|
307
|
+
background: #ffffff;
|
|
308
|
+
border: 1px solid #ebebe6;
|
|
309
|
+
border-radius: calc(9 * var(--we-content, 1px)) 0 0 calc(9 * var(--we-content, 1px));
|
|
310
|
+
box-shadow: 0 10px 24px -12px rgba(20,18,13,0.34);
|
|
311
|
+
color: #4a4842;
|
|
312
|
+
font: inherit;
|
|
313
|
+
cursor: pointer;
|
|
314
|
+
opacity: 1;
|
|
315
|
+
pointer-events: auto;
|
|
316
|
+
transition: transform 0.2s cubic-bezier(0.2,0.7,0.3,1),
|
|
317
|
+
border-color 0.18s, box-shadow 0.18s;
|
|
318
|
+
}
|
|
319
|
+
.we-htab-eye {
|
|
320
|
+
display: grid;
|
|
321
|
+
place-items: center;
|
|
322
|
+
flex: 0 0 auto;
|
|
323
|
+
color: #8a877e;
|
|
324
|
+
transition: color 0.18s;
|
|
325
|
+
}
|
|
326
|
+
.we-htab-eye svg { width: calc(14 * var(--we-content, 1px)); height: calc(14 * var(--we-content, 1px)); }
|
|
327
|
+
.we-htab-name {
|
|
328
|
+
font-size: calc(13 * var(--we-content, 1px));
|
|
329
|
+
font-weight: 600;
|
|
330
|
+
letter-spacing: -0.01em;
|
|
331
|
+
color: #14180F;
|
|
332
|
+
white-space: nowrap;
|
|
333
|
+
}
|
|
334
|
+
.we-htab:hover {
|
|
335
|
+
transform: translateY(-50%) scale(1.07);
|
|
336
|
+
border-color: rgba(15,42,34,0.5);
|
|
337
|
+
box-shadow: 0 13px 30px -12px rgba(15,42,34,0.45);
|
|
338
|
+
}
|
|
339
|
+
.we-htab:hover .we-htab-eye { color: #0F2A22; }
|
|
340
|
+
.we-htab:focus-visible {
|
|
341
|
+
outline: none;
|
|
342
|
+
transform: translateY(-50%) scale(1.07);
|
|
343
|
+
border-color: #0F2A22;
|
|
344
|
+
box-shadow: 0 0 0 3px #F3F6EF, 0 13px 30px -12px rgba(15,42,34,0.45);
|
|
345
|
+
}
|
|
346
|
+
/* Hover/focus swaps the eye-off glyph to the eye glyph in place — no width
|
|
347
|
+
change, no sliding label. Only one glyph is in flow at a time, so the eye
|
|
348
|
+
slot's grid keeps the shown glyph centered. */
|
|
349
|
+
.we-htab-eye-on { display: none; }
|
|
350
|
+
.we-htab:hover .we-htab-eye-off,
|
|
351
|
+
.we-htab:focus-visible .we-htab-eye-off { display: none; }
|
|
352
|
+
.we-htab:hover .we-htab-eye-on,
|
|
353
|
+
.we-htab:focus-visible .we-htab-eye-on { display: block; }
|
|
354
|
+
|
|
355
|
+
/* Render-error placeholder ------------------------------------- */
|
|
356
|
+
.we-render-error {
|
|
357
|
+
margin: 8px 0;
|
|
358
|
+
padding: 24px 16px;
|
|
359
|
+
border-radius: 6px;
|
|
360
|
+
border: 1px dashed #ebebe6;
|
|
361
|
+
background: #ffffff;
|
|
362
|
+
color: #8a877e;
|
|
363
|
+
font-size: 12.5px;
|
|
364
|
+
}
|
|
365
|
+
.we-render-error__title {
|
|
366
|
+
margin: 0;
|
|
367
|
+
color: #4a4842;
|
|
368
|
+
font-weight: 500;
|
|
369
|
+
}
|
|
370
|
+
.we-render-error__detail {
|
|
371
|
+
margin: 4px 0 0 0;
|
|
372
|
+
color: #b9b6ab;
|
|
373
|
+
font-size: 11.5px;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/* Island interaction shield: a transparent click layer over an
|
|
377
|
+
island's live surface (editor.liveSurface) (e.g. the neighborhood map canvas). Present until
|
|
378
|
+
the surface's owner (slot/section) is the live selection; a click on it
|
|
379
|
+
falls through to the editor's resolver, which selects the owner. Once the
|
|
380
|
+
owner carries data-we-live the shield is removed, so the live surface (map
|
|
381
|
+
pan/zoom/markers) is interactive. z-index 1 sits on the map canvas's plane
|
|
382
|
+
(later in DOM than the canvas -> above it) but below the floating
|
|
383
|
+
.tr-nbhd__card (z-index 2), so the card's slots stay clickable. */
|
|
384
|
+
.we-enh-shield {
|
|
385
|
+
position: absolute;
|
|
386
|
+
inset: 0;
|
|
387
|
+
z-index: 1;
|
|
388
|
+
cursor: pointer;
|
|
389
|
+
background: transparent;
|
|
390
|
+
}
|
|
391
|
+
[data-we-live] .we-enh-shield { display: none; }
|
|
392
|
+
|
|
393
|
+
/* Image slider: until the slider's \`images\` slot is the live selection, its
|
|
394
|
+
z-index:2 nav arrows would paint above the z-index:1 shield and stay
|
|
395
|
+
clickable, escaping the select-first gate. Disable them so the shield
|
|
396
|
+
catches the first click; restored once .tr-slider carries data-we-live.
|
|
397
|
+
Editor-only — deliberately not in published tokens.css. */
|
|
398
|
+
.tr-slider:not([data-we-live]) .tr-slider__nav { pointer-events: none; }
|
|
399
|
+
`;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
//#endregion
|
|
403
|
+
export { HOST_INJECTED_PROPERTIES, buildChromeCss };
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
//#region src/canvas-core/chrome.d.ts
|
|
2
|
+
/** How far outside the target the ring sits, in CSS px. With a 2px border-box
|
|
3
|
+
* border, offset = 1 centers the stroke on the element's edge (1px outside,
|
|
4
|
+
* 1px overlapping inside) so the ring reads as flush. */
|
|
5
|
+
declare const RING_OFFSET = 1;
|
|
6
|
+
/** Inset of the section title pill from the target's left edge, in CSS px. */
|
|
7
|
+
declare const LABEL_LEFT_INDENT = 12;
|
|
8
|
+
interface RingRect {
|
|
9
|
+
top: number;
|
|
10
|
+
left: number;
|
|
11
|
+
width: number;
|
|
12
|
+
height: number;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Ring geometry in document space: shift by scroll, inflate by `offset`.
|
|
16
|
+
*
|
|
17
|
+
* `containerWidth` clamps the horizontal edges. A section flush with both page
|
|
18
|
+
* edges would otherwise inflate `offset` px past each one and grow the
|
|
19
|
+
* document's horizontal scroll extent. Omitting it means unclamped: the app's
|
|
20
|
+
* overlay is wider than its page card and never overflows, so clamping it would
|
|
21
|
+
* silently shift every full-bleed ring. A consumer whose overlay is page-width
|
|
22
|
+
* opts in by passing one.
|
|
23
|
+
*
|
|
24
|
+
* Absence — not a sentinel value — is what selects the unclamped branch. A
|
|
25
|
+
* numeric stand-in for "unbounded" would make NaN (plausible out of a detached
|
|
26
|
+
* document's clientWidth arithmetic) take the clamped branch and yield NaN
|
|
27
|
+
* geometry, and would let Number.MAX_SAFE_INTEGER quietly enable the left floor.
|
|
28
|
+
*/
|
|
29
|
+
declare function ringBox(rect: RingRect, scroll: {
|
|
30
|
+
x: number;
|
|
31
|
+
y: number;
|
|
32
|
+
}, offset: number, containerWidth?: number): RingRect;
|
|
33
|
+
/** Whether an element has a box a ring can actually wrap. */
|
|
34
|
+
declare function hasMeasurableRingBox(el: HTMLElement): boolean;
|
|
35
|
+
declare function measureRingRect(el: HTMLElement): DOMRect;
|
|
36
|
+
/**
|
|
37
|
+
* Move a ring in #we-chrome-overlay to sit `offset` px outside `target`.
|
|
38
|
+
*
|
|
39
|
+
* `kind` flips a data attribute so CSS can pick a color (gold for sections and
|
|
40
|
+
* slots). The hover ring leaves it unset and uses its own selector.
|
|
41
|
+
*/
|
|
42
|
+
declare function positionRing(ring: HTMLElement | null, target: HTMLElement | null, offset: number, kind?: "section" | "slot", containerWidth?: number): void;
|
|
43
|
+
declare function applySelectionKind(ring: HTMLElement, kind: "section" | "slot"): void;
|
|
44
|
+
declare function clearRing(ring: HTMLElement | null): void;
|
|
45
|
+
declare function positionLabel(label: HTMLElement | null, target: HTMLElement | null, text: string): void;
|
|
46
|
+
declare function clearLabel(label: HTMLElement | null): void;
|
|
47
|
+
declare function cssEscape(value: string): string;
|
|
48
|
+
//#endregion
|
|
49
|
+
export { LABEL_LEFT_INDENT, RING_OFFSET, RingRect, applySelectionKind, clearLabel, clearRing, cssEscape, hasMeasurableRingBox, measureRingRect, positionLabel, positionRing, ringBox };
|