@struq/skill 0.5.1 → 0.5.3
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/README.md +137 -100
- package/package.json +5 -3
- package/skills/struq/SKILL.md +168 -165
- package/skills/struq/blocks/CATALOG.md +49 -46
- package/skills/struq/blocks/backgrounds/background-aurora.tsx +2 -1
- package/skills/struq/blocks/backgrounds/background-facets.tsx +5 -3
- package/skills/struq/blocks/backgrounds/background-silk.tsx +6 -2
- package/skills/struq/blocks/catalog.json +50 -5
- package/skills/struq/blocks/film.json +11 -7
- package/skills/struq/blocks/footers/footer-contact.tsx +8 -78
- package/skills/struq/blocks/headers/header-bar.tsx +144 -29
- package/skills/struq/blocks/headers/menu-curtain.tsx +36 -19
- package/skills/struq/blocks/heroes/hero-poster.tsx +36 -10
- package/skills/struq/blocks/kit/HeroCanvas.tsx +6 -2
- package/skills/struq/blocks/kit/SplitHeadline.tsx +2 -1
- package/skills/struq/blocks/kit/accent.ts +29 -3
- package/skills/struq/blocks/kit/blocks.css +530 -6
- package/skills/struq/blocks/kit/feel.ts +183 -0
- package/skills/struq/blocks/kit/gsap.ts +9 -6
- package/skills/struq/blocks/kit/middle/PlaceHours.tsx +254 -0
- package/skills/struq/blocks/kit/middle/ProcessStrip.tsx +428 -0
- package/skills/struq/blocks/kit/middle/ServicesList.tsx +496 -0
- package/skills/struq/blocks/kit/middle/entrance.ts +98 -0
- package/skills/struq/blocks/kit/middle/hours.ts +154 -0
- package/skills/struq/blocks/kit/middle/index.ts +14 -0
- package/skills/struq/blocks/kit/motion/HoverLink.tsx +33 -0
- package/skills/struq/blocks/kit/motion/ImageCurtain.tsx +79 -0
- package/skills/struq/blocks/kit/motion/MaskWipe.tsx +91 -0
- package/skills/struq/blocks/kit/motion/Pressable.tsx +58 -0
- package/skills/struq/blocks/kit/motion/RevealText.tsx +103 -0
- package/skills/struq/blocks/kit/motion/Rise.tsx +77 -0
- package/skills/struq/blocks/kit/motion/ScrubLine.tsx +108 -0
- package/skills/struq/blocks/kit/motion/index.ts +11 -0
- package/skills/struq/blocks/kit/quiet/Close.tsx +178 -52
- package/skills/struq/blocks/kit/quiet/Statement.tsx +3 -2
- package/skills/struq/blocks/kit/subject/AppScreen.tsx +388 -0
- package/skills/struq/blocks/kit/subject/Document.tsx +742 -0
- package/skills/struq/blocks/kit/subject/Mark.tsx +44 -0
- package/skills/struq/blocks/kit/subject/PatternPaper.tsx +410 -0
- package/skills/struq/blocks/kit/subject/SectionDrawing.tsx +349 -0
- package/skills/struq/blocks/kit/subject/Ticket.tsx +354 -0
- package/skills/struq/blocks/kit/subject/build.ts +66 -0
- package/skills/struq/blocks/kit/subject/draw.ts +196 -0
- package/skills/struq/blocks/kit/subject/drawings.ts +373 -0
- package/skills/struq/blocks/kit/subject/format.ts +122 -0
- package/skills/struq/blocks/kit/subject/index.ts +11 -0
- package/skills/struq/blocks/kit/subject/patterns.ts +204 -0
- package/skills/struq/blocks/preloaders/preloader-stack.tsx +75 -22
- package/skills/struq/blocks/showpieces/hero-opener.tsx +55 -7
- package/skills/struq/blocks/showpieces/image-to-fullbleed.tsx +64 -6
- package/skills/struq/blocks/showpieces/manifesto-scrub.tsx +56 -3
- package/skills/struq/blocks/showpieces/panel-wipe.tsx +93 -11
- package/skills/struq/blocks/showpieces/wordmark-finale.tsx +38 -5
- package/skills/struq/canon/canon.json +1 -1
- package/skills/struq/references/art-direction.md +51 -0
- package/skills/struq/scripts/blocks.mjs +56 -20
- package/skills/struq/scripts/canon.mjs +36 -7
- package/skills/struq/scripts/check.mjs +417 -11
- package/skills/struq/scripts/draw.mjs +49 -0
- package/skills/struq/scripts/film-check.mjs +28 -4
- package/skills/struq/scripts/gate.mjs +13 -3
- package/skills/struq/scripts/hand.mjs +222 -0
- package/skills/struq/scripts/images.mjs +12 -3
- package/skills/struq/scripts/look.mjs +1184 -0
- package/skills/struq/scripts/stamp.mjs +47 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
// components/struq/kit/motion/ScrubLine.tsx (Struq recipe)
|
|
3
|
+
// A rule or a drawn path that draws itself as the reader scrolls and undraws going back. Use it for a divider that
|
|
4
|
+
// grows into a section, a timeline's spine, a route on a map, an underline under a statement. Extracted from
|
|
5
|
+
// sections/approach's thread (a line drawn by scroll progress, linear, with the in-flow smoothing).
|
|
6
|
+
//
|
|
7
|
+
// <ScrubLine className="my-16" />
|
|
8
|
+
// <ScrubLine path="M2 40 C 120 0, 240 80, 398 30" viewBox="0 0 400 80" className="h-20 w-full text-accent" />
|
|
9
|
+
//
|
|
10
|
+
// Props
|
|
11
|
+
// - path: an SVG path's d. Without it, a straight rule.
|
|
12
|
+
// - viewBox: the path's viewBox (default "0 0 100 100"). strokeWidth: in the viewBox's units (default 1.5).
|
|
13
|
+
// - axis: the rule's direction, "x" (default, drawn left to right) or "y" (top to bottom; give it a height).
|
|
14
|
+
// - thickness: the rule's weight in px (default 1).
|
|
15
|
+
// - start, end: ScrollTrigger positions for the line's own top (default "top 85%" to "top 35%").
|
|
16
|
+
// - className: on the rule or the svg. It draws in currentColor.
|
|
17
|
+
//
|
|
18
|
+
// Motion: progress follows the scroll, linear, with the feel's scrub.flow smoothing; a path draws along its length
|
|
19
|
+
// (one dash as long as the measured path, so any path draws at an even pace). Reduced motion, no scripts or the server render: fully drawn.
|
|
20
|
+
import { useRef } from "react";
|
|
21
|
+
import { feel, prefersReduced } from "../feel";
|
|
22
|
+
import { gsap, useGSAP } from "../gsap";
|
|
23
|
+
|
|
24
|
+
type Props = {
|
|
25
|
+
path?: string;
|
|
26
|
+
viewBox?: string;
|
|
27
|
+
strokeWidth?: number;
|
|
28
|
+
axis?: "x" | "y";
|
|
29
|
+
thickness?: number;
|
|
30
|
+
start?: string;
|
|
31
|
+
end?: string;
|
|
32
|
+
className?: string;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export function ScrubLine({
|
|
36
|
+
path,
|
|
37
|
+
viewBox = "0 0 100 100",
|
|
38
|
+
strokeWidth = 1.5,
|
|
39
|
+
axis = "x",
|
|
40
|
+
thickness = 1,
|
|
41
|
+
start = "top 85%",
|
|
42
|
+
end = "top 35%",
|
|
43
|
+
className = "",
|
|
44
|
+
}: Props) {
|
|
45
|
+
const ref = useRef<HTMLSpanElement & SVGSVGElement>(null);
|
|
46
|
+
|
|
47
|
+
useGSAP(
|
|
48
|
+
() => {
|
|
49
|
+
const el = ref.current;
|
|
50
|
+
if (!el) return;
|
|
51
|
+
if (prefersReduced()) {
|
|
52
|
+
el.dataset.ready = "1";
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const f = feel(el);
|
|
56
|
+
const scrollTrigger = { trigger: el, start, end, scrub: f.scrub.flow };
|
|
57
|
+
const line = path ? el.querySelector("path") : el;
|
|
58
|
+
if (!line) return;
|
|
59
|
+
if (path) {
|
|
60
|
+
// The path's own measured length: one dash as long as the path, offset by all of it, then by none.
|
|
61
|
+
const length = (line as SVGPathElement).getTotalLength();
|
|
62
|
+
gsap.set(line, { attr: { "stroke-dasharray": `${length} ${length}` } });
|
|
63
|
+
gsap.fromTo(
|
|
64
|
+
line,
|
|
65
|
+
{ attr: { "stroke-dashoffset": length } },
|
|
66
|
+
{ attr: { "stroke-dashoffset": 0 }, ease: f.ease.scrub, scrollTrigger },
|
|
67
|
+
);
|
|
68
|
+
} else {
|
|
69
|
+
gsap.fromTo(
|
|
70
|
+
line,
|
|
71
|
+
axis === "x" ? { scaleX: 0 } : { scaleY: 0 },
|
|
72
|
+
{ ...(axis === "x" ? { scaleX: 1 } : { scaleY: 1 }), ease: f.ease.scrub, scrollTrigger },
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
el.dataset.ready = "1";
|
|
76
|
+
},
|
|
77
|
+
{ scope: ref, dependencies: [path, axis, start, end] },
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
if (path) {
|
|
81
|
+
return (
|
|
82
|
+
<svg
|
|
83
|
+
ref={ref}
|
|
84
|
+
data-scrub-line="path"
|
|
85
|
+
viewBox={viewBox}
|
|
86
|
+
fill="none"
|
|
87
|
+
aria-hidden
|
|
88
|
+
className={`block ${className}`}
|
|
89
|
+
>
|
|
90
|
+
<path
|
|
91
|
+
d={path}
|
|
92
|
+
stroke="currentColor"
|
|
93
|
+
strokeWidth={strokeWidth}
|
|
94
|
+
strokeLinecap="butt"
|
|
95
|
+
/>
|
|
96
|
+
</svg>
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
return (
|
|
100
|
+
<span
|
|
101
|
+
ref={ref}
|
|
102
|
+
aria-hidden
|
|
103
|
+
data-scrub-line={axis}
|
|
104
|
+
className={`block bg-current ${className}`}
|
|
105
|
+
style={axis === "x" ? { height: thickness } : { width: thickness }}
|
|
106
|
+
/>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// components/struq/kit/motion/index.ts (Struq recipe)
|
|
2
|
+
// The motion primitives: the house moves with the craft built in (reduced motion, cleanup, descender-safe masks, no
|
|
3
|
+
// layout shift), timed by the site's temperament (../feel, data-temperament on <body>). Use these instead of writing
|
|
4
|
+
// GSAP for a reveal, a block rising, a wipe, a curtain, a link hover, a button press or a scroll-drawn line.
|
|
5
|
+
export { HoverLink } from "./HoverLink";
|
|
6
|
+
export { ImageCurtain } from "./ImageCurtain";
|
|
7
|
+
export { MaskWipe } from "./MaskWipe";
|
|
8
|
+
export { Pressable } from "./Pressable";
|
|
9
|
+
export { RevealText } from "./RevealText";
|
|
10
|
+
export { Rise, riseFrom, riseTo } from "./Rise";
|
|
11
|
+
export { ScrubLine } from "./ScrubLine";
|
|
@@ -1,72 +1,198 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
// components/struq/kit/quiet/Close.tsx (Struq recipe)
|
|
3
|
-
// The page's last
|
|
4
|
-
//
|
|
5
|
-
// hairline frame around the line is the frame motif's last echo; an optional caption sits at the start of the link's
|
|
6
|
-
// line, a label in the margin columns beside the line.
|
|
3
|
+
// The page's last scene before the footer, built from the motion primitives (../motion) and timed by the site's
|
|
4
|
+
// temperament. Three shapes; none of them is the site's name at full width, which is never a close.
|
|
7
5
|
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
6
|
+
// - "idea": the idea's last line rises out of its masks (RevealText), a ScrubLine draws the motif's line under it as
|
|
7
|
+
// the reader arrives, and the doors sit below. For a site whose last word is the idea itself.
|
|
8
|
+
// - "echo": the opening comes back smaller. Its first line wipes in (MaskWipe) or its photo is uncovered
|
|
9
|
+
// (ImageCurtain), at a fraction of its first size, with the doors beside or under it. For a film that ends where it
|
|
10
|
+
// began.
|
|
11
|
+
// - "door": the brief gave real ways in (an address, a phone number, an email, a booking or shop URL) and they are the
|
|
12
|
+
// close: set large, each a HoverLink (or a Pressable for a booking or a shop), with the idea in one line above. For
|
|
13
|
+
// a place people visit, call or book.
|
|
14
|
+
//
|
|
15
|
+
// <Close shape="idea" line="Grown slowly, poured by hand." accent="slowly" motif={{ path: "M2 40 C 120 0, 240 80, 398 30", viewBox: "0 0 400 80" }}
|
|
16
|
+
// doors={[{ kind: "email", href: "mailto:hello@kiln.studio", label: "hello@kiln.studio" }]} />
|
|
17
|
+
// <Close shape="echo" echo={{ media: <img src="/hero.jpg" alt="The kiln at dawn" className="h-full w-full object-cover" /> }} line="Back to the kiln." />
|
|
18
|
+
// <Close shape="door" line="Bread from four in the morning." doors={[
|
|
19
|
+
// { kind: "address", href: "https://maps.app.goo.gl/...", label: "Oudegracht 12, Utrecht" },
|
|
20
|
+
// { kind: "phone", href: "tel:+31301234567", label: "030 123 45 67" },
|
|
21
|
+
// { kind: "booking", href: "https://book.example/table", label: "Book a table" },
|
|
22
|
+
// ]} credit="Bakkerij Oud, since 1962" />
|
|
10
23
|
//
|
|
11
24
|
// Props
|
|
12
|
-
// -
|
|
13
|
-
// -
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
// -
|
|
18
|
-
//
|
|
19
|
-
//
|
|
25
|
+
// - shape: "idea" (default), "echo" or "door".
|
|
26
|
+
// - line: the idea's line, plain text. idea: the line itself (short, two or three lines at h2). door: one line above
|
|
27
|
+
// the doors. echo: optional, a short lead beside the echo.
|
|
28
|
+
// - accent: the site's one accent word, again: set in the pairing's italic accent face where it first stands in line
|
|
29
|
+
// or echo.line (../accent). A word the line does not hold changes nothing.
|
|
30
|
+
// - echo (shape "echo"): { line?: string; media?: ReactNode; reveal?: "wipe" | "curtain"; className?: string }. line is
|
|
31
|
+
// the opening's first line, set at h2 (not the opening's poster size); media is the opening's photo (an img, a
|
|
32
|
+
// next/image with fill), shown at about half the page's width, sized by className (default aspect-[4/3]). reveal:
|
|
33
|
+
// "curtain" (default for media) or "wipe" (always for a line).
|
|
34
|
+
// - motif (shape "idea"): { path?: string; viewBox?: string }, the ScrubLine under the line. A path echoes a drawn
|
|
35
|
+
// motif; without one it is a straight rule.
|
|
36
|
+
// - doors: the real ways in, from the brief only: { label, href?, kind? } with kind "address" | "phone" | "email" |
|
|
37
|
+
// "booking" | "shop" | "link". With an href a door is a HoverLink, or a Pressable when it is a booking or a shop;
|
|
38
|
+
// an address without an href is plain text. With no doors given nothing is rendered for them: never a placeholder,
|
|
39
|
+
// never an invitation without a destination.
|
|
40
|
+
// - credit: a small line at the foot (credit, legal, year). wordmark: the name at text size in the foot, if at all.
|
|
41
|
+
// - look: the HoverLink look, the site's one ("draw" default, "slide", "color").
|
|
20
42
|
// - className: on the root, for example "scene-inverse" or "scene-field".
|
|
21
43
|
//
|
|
22
|
-
// Mounting: the last section before the footer. The root carries data-scene="close"
|
|
23
|
-
// Motion: it enters once
|
|
24
|
-
//
|
|
25
|
-
//
|
|
44
|
+
// Mounting: the last section before the footer. The root carries data-scene="close", data-energy="1" and
|
|
45
|
+
// data-close-shape. Motion: it enters once, from the primitives: RevealText on the line, ScrubLine scrubbed under it,
|
|
46
|
+
// MaskWipe or ImageCurtain on the echo; doors answer hover and press by the feel. No pin. Reduced motion, no scripts
|
|
47
|
+
// or the server render: every primitive shows its final state. Phones: one column, the doors stacked.
|
|
48
|
+
import type { ReactNode } from "react";
|
|
26
49
|
import { withAccent } from "../accent";
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
50
|
+
import { HoverLink } from "../motion/HoverLink";
|
|
51
|
+
import { ImageCurtain } from "../motion/ImageCurtain";
|
|
52
|
+
import { MaskWipe } from "../motion/MaskWipe";
|
|
53
|
+
import { Pressable } from "../motion/Pressable";
|
|
54
|
+
import { RevealText } from "../motion/RevealText";
|
|
55
|
+
import { ScrubLine } from "../motion/ScrubLine";
|
|
29
56
|
import { LABEL, SCENE_SPACE, SHEET_GRID } from "./sheet";
|
|
30
57
|
|
|
58
|
+
export type CloseShape = "idea" | "echo" | "door";
|
|
59
|
+
|
|
60
|
+
export type Door = {
|
|
61
|
+
label: string;
|
|
62
|
+
href?: string;
|
|
63
|
+
kind?: "address" | "phone" | "email" | "booking" | "shop" | "link";
|
|
64
|
+
};
|
|
65
|
+
|
|
31
66
|
type Props = {
|
|
32
|
-
|
|
67
|
+
shape?: CloseShape;
|
|
68
|
+
line?: string;
|
|
33
69
|
accent?: string;
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
70
|
+
echo?: { line?: string; media?: ReactNode; reveal?: "wipe" | "curtain"; className?: string };
|
|
71
|
+
motif?: { path?: string; viewBox?: string };
|
|
72
|
+
doors?: readonly Door[];
|
|
73
|
+
credit?: string;
|
|
74
|
+
wordmark?: string;
|
|
75
|
+
look?: "draw" | "slide" | "color";
|
|
38
76
|
className?: string;
|
|
39
77
|
};
|
|
40
78
|
|
|
41
|
-
|
|
79
|
+
/** One door: a Pressable for a booking or a shop, a HoverLink for any other link, plain text without an href. */
|
|
80
|
+
function DoorItem({ door, size, look }: { door: Door; size: "large" | "small"; look: Props["look"] }) {
|
|
81
|
+
const type = size === "large" ? "font-display text-h2" : "font-body text-h3 font-medium";
|
|
82
|
+
if (!door.href) return <p className={`m-0 text-ink ${type}`}>{door.label}</p>;
|
|
83
|
+
if (door.kind === "booking" || door.kind === "shop") {
|
|
84
|
+
return (
|
|
85
|
+
<Pressable
|
|
86
|
+
href={door.href}
|
|
87
|
+
press="fill"
|
|
88
|
+
className={`w-fit px-8 py-4 text-ink shadow-[inset_0_0_0_1px_var(--line)] ${size === "large" ? "font-body text-h3 font-medium" : "font-body text-lead font-medium"}`}
|
|
89
|
+
>
|
|
90
|
+
{door.label}
|
|
91
|
+
</Pressable>
|
|
92
|
+
);
|
|
93
|
+
}
|
|
42
94
|
return (
|
|
43
|
-
<
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
95
|
+
<HoverLink href={door.href} look={look} className={`w-fit text-ink ${type}`}>
|
|
96
|
+
{door.label}
|
|
97
|
+
</HoverLink>
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function Doors({ doors, size, look, className = "" }: { doors?: readonly Door[]; size: "large" | "small"; look: Props["look"]; className?: string }) {
|
|
102
|
+
if (!doors || doors.length === 0) return null;
|
|
103
|
+
return (
|
|
104
|
+
<ul className={`m-0 flex list-none flex-col items-start p-0 ${size === "large" ? "gap-6 md:gap-8" : "gap-4"} ${className}`}>
|
|
105
|
+
{doors.map((door) => (
|
|
106
|
+
<li key={`${door.kind ?? "link"}:${door.label}`}>
|
|
107
|
+
<DoorItem door={door} size={size} look={look} />
|
|
108
|
+
</li>
|
|
109
|
+
))}
|
|
110
|
+
</ul>
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function Foot({ credit, wordmark }: { credit?: string; wordmark?: string }) {
|
|
115
|
+
if (!credit && !wordmark) return null;
|
|
116
|
+
return (
|
|
117
|
+
<div className="col-span-12 flex flex-col gap-1 border-t border-line pt-5 md:flex-row md:items-baseline md:justify-between">
|
|
118
|
+
{wordmark ? <p className="m-0 font-body text-body font-medium text-ink">{wordmark}</p> : null}
|
|
119
|
+
{credit ? <p className={LABEL}>{credit}</p> : null}
|
|
120
|
+
</div>
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function Close({
|
|
125
|
+
shape = "idea",
|
|
126
|
+
line,
|
|
127
|
+
accent,
|
|
128
|
+
echo,
|
|
129
|
+
motif,
|
|
130
|
+
doors,
|
|
131
|
+
credit,
|
|
132
|
+
wordmark,
|
|
133
|
+
look = "draw",
|
|
134
|
+
className = "",
|
|
135
|
+
}: Props) {
|
|
136
|
+
return (
|
|
137
|
+
<section data-scene="close" data-energy="1" data-close-shape={shape} className={`text-ink ${SCENE_SPACE} ${className}`}>
|
|
138
|
+
<div className={`items-start gap-y-12 md:gap-y-20 ${SHEET_GRID}`}>
|
|
139
|
+
{shape === "idea" ? (
|
|
140
|
+
<>
|
|
141
|
+
{line ? (
|
|
142
|
+
<RevealText as="h2" by="lines" className="col-span-12 m-0 max-w-[20ch] font-display text-h2 md:col-span-9">
|
|
143
|
+
{withAccent(line, accent)}
|
|
144
|
+
</RevealText>
|
|
145
|
+
) : null}
|
|
146
|
+
<div className="col-span-12 text-accent">
|
|
147
|
+
{motif?.path ? (
|
|
148
|
+
<ScrubLine path={motif.path} viewBox={motif.viewBox ?? "0 0 400 80"} className="h-16 w-full md:h-24" />
|
|
149
|
+
) : (
|
|
150
|
+
<ScrubLine />
|
|
151
|
+
)}
|
|
152
|
+
</div>
|
|
153
|
+
<Doors doors={doors} size="small" look={look} className="col-span-12 md:col-span-6 md:col-start-7" />
|
|
154
|
+
</>
|
|
56
155
|
) : null}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
156
|
+
|
|
157
|
+
{shape === "echo" ? (
|
|
158
|
+
<>
|
|
159
|
+
{echo?.media ? (
|
|
160
|
+
(echo.reveal ?? "curtain") === "curtain" ? (
|
|
161
|
+
<ImageCurtain className={`col-span-12 md:col-span-6 ${echo.className ?? "aspect-[4/3]"}`}>{echo.media}</ImageCurtain>
|
|
162
|
+
) : (
|
|
163
|
+
<MaskWipe from="left" settle className={`relative col-span-12 overflow-hidden md:col-span-6 ${echo.className ?? "aspect-[4/3]"}`}>
|
|
164
|
+
{echo.media}
|
|
165
|
+
</MaskWipe>
|
|
166
|
+
)
|
|
167
|
+
) : null}
|
|
168
|
+
{echo?.line ? (
|
|
169
|
+
<MaskWipe from="left" className={`col-span-12 ${echo.media ? "md:col-span-6" : "md:col-span-9"}`}>
|
|
170
|
+
<p className="m-0 max-w-[20ch] font-display text-h2">{withAccent(echo.line, accent)}</p>
|
|
171
|
+
</MaskWipe>
|
|
172
|
+
) : null}
|
|
173
|
+
<div className={`col-span-12 flex flex-col gap-8 ${echo?.media && !echo.line ? "md:col-span-5 md:col-start-8 md:self-end" : "md:col-span-6 md:col-start-7"}`}>
|
|
174
|
+
{line ? (
|
|
175
|
+
<RevealText as="p" by="lines" className="m-0 max-w-[32ch] font-body text-lead text-ink-muted">
|
|
176
|
+
{withAccent(line, accent)}
|
|
177
|
+
</RevealText>
|
|
178
|
+
) : null}
|
|
179
|
+
<Doors doors={doors} size="small" look={look} />
|
|
180
|
+
</div>
|
|
181
|
+
</>
|
|
182
|
+
) : null}
|
|
183
|
+
|
|
184
|
+
{shape === "door" ? (
|
|
185
|
+
<>
|
|
186
|
+
{line ? (
|
|
187
|
+
<RevealText as="p" by="lines" className="col-span-12 m-0 max-w-[40ch] font-body text-lead text-ink-muted md:col-span-8">
|
|
188
|
+
{withAccent(line, accent)}
|
|
189
|
+
</RevealText>
|
|
190
|
+
) : null}
|
|
191
|
+
<Doors doors={doors} size="large" look={look} className="col-span-12" />
|
|
192
|
+
</>
|
|
193
|
+
) : null}
|
|
194
|
+
|
|
195
|
+
<Foot credit={credit} wordmark={wordmark} />
|
|
70
196
|
</div>
|
|
71
197
|
</section>
|
|
72
198
|
);
|
|
@@ -12,8 +12,9 @@
|
|
|
12
12
|
// Props
|
|
13
13
|
// - children: the sentence, plain text. Two to five lines at moment size on a wide screen.
|
|
14
14
|
// - accent: one word of the sentence, set in the pairing's italic accent face (--face-accent) where it first stands
|
|
15
|
-
// (whole word, any case; punctuation stays outside
|
|
16
|
-
// too (../accent). A word the sentence does
|
|
15
|
+
// (whole word, any case; punctuation stays outside the mark but on its line, so a phone never sets the full stop
|
|
16
|
+
// alone). The site's one accent word: the opening and the Close carry it too (../accent). A word the sentence does
|
|
17
|
+
// not hold changes nothing.
|
|
17
18
|
// - label: small, sentence case, body face. Beside the first line (wide screens), under the sentence (phones).
|
|
18
19
|
// - image: a StruqImage, shown upright (4/5) and small: two columns wide, five on a phone.
|
|
19
20
|
// - as: "h2" (default) or "p" when the page already has its heading here.
|