@open-press/core 1.2.1 → 1.3.2
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 +2 -2
- package/engine/commands/typecheck.mjs +1 -1
- package/engine/document-export.mjs +1 -1
- package/engine/output/page-block.mjs +11 -2
- package/engine/output/public-assets.mjs +41 -6
- package/engine/output/static-server.mjs +68 -15
- package/engine/react/caption-numbering.mjs +2 -2
- package/engine/react/comment-marker.mjs +1 -2
- package/engine/react/document-entry.mjs +64 -11
- package/engine/react/document-export.d.mts +6 -0
- package/engine/react/document-export.mjs +158 -28
- package/engine/react/mdx-compile.mjs +4 -4
- package/engine/react/measurement-css.mjs +3 -3
- package/engine/react/page-folio.mjs +37 -0
- package/engine/react/pagination/allocator.mjs +4 -4
- package/engine/react/pipeline/frame-measurement.mjs +34 -16
- package/engine/react/press-tree-inspection.mjs +43 -13
- package/engine/react/project-asset-endpoint.mjs +45 -11
- package/engine/react/sources/heading-numbering.mjs +2 -2
- package/engine/react/sources/mdx-resolver.mjs +3 -3
- package/engine/react/style-discovery.mjs +60 -11
- package/engine/react/text-source-transform.mjs +18 -4
- package/engine/runtime/config.mjs +22 -22
- package/engine/runtime/file-utils.mjs +57 -13
- package/engine/runtime/inspection.mjs +40 -15
- package/engine/runtime/page-geometry.mjs +6 -6
- package/engine/runtime/source-text-tools.mjs +28 -4
- package/engine/runtime/source-workspace.mjs +6 -9
- package/engine/runtime/validation.mjs +42 -24
- package/package.json +1 -1
- package/src/openpress/app/OpenPressApp.tsx +20 -18
- package/src/openpress/app/OpenPressRuntime.tsx +3 -3
- package/src/openpress/app/WorkspaceGalleryPage.tsx +65 -39
- package/src/openpress/core/PageFolio.tsx +115 -0
- package/src/openpress/core/Press.tsx +5 -10
- package/src/openpress/core/Slide.tsx +11 -0
- package/src/openpress/core/index.tsx +4 -0
- package/src/openpress/core/types.ts +21 -13
- package/src/openpress/core/useSource.ts +1 -1
- package/src/openpress/document-model/workspaceManifestModel.ts +4 -9
- package/src/openpress/reader/SlidePresentationPage.tsx +7 -3
- package/src/openpress/workbench/shell/WorkbenchToolbarActions.tsx +46 -43
- package/src/styles/openpress/workbench-toolbar.css +33 -0
- package/src/styles/openpress/workspace-gallery.css +130 -47
- package/vite.config.ts +82 -16
|
@@ -14,17 +14,16 @@ export interface AllocationHints {
|
|
|
14
14
|
totalPagesPerChain: Record<string, number>;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
// Metadata read from <Press> props by the engine pipeline.
|
|
18
|
-
//
|
|
19
|
-
// instead and leaves these as undefined. The engine merges both sources
|
|
20
|
-
// (props override config) until v1.0 removes config support.
|
|
17
|
+
// Metadata read from <Press> props by the engine pipeline. Each
|
|
18
|
+
// press/<slug>/press.tsx entry declares its document metadata here.
|
|
21
19
|
export interface PressMetadata {
|
|
22
20
|
title?: string;
|
|
23
21
|
type?: PressProps["type"];
|
|
24
22
|
page?: PressProps["page"];
|
|
25
23
|
slug?: string;
|
|
26
24
|
theme?: string;
|
|
27
|
-
componentsDir?:
|
|
25
|
+
componentsDir?: PressProps["componentsDir"];
|
|
26
|
+
mediaDir?: PressProps["mediaDir"];
|
|
28
27
|
}
|
|
29
28
|
|
|
30
29
|
export interface PressContextValue {
|
|
@@ -36,9 +35,7 @@ export interface PressContextValue {
|
|
|
36
35
|
// the first measurement pass.
|
|
37
36
|
hints: AllocationHints | null;
|
|
38
37
|
toc: Record<string, TocEntry[]> | null;
|
|
39
|
-
// Metadata declared on <Press> props
|
|
40
|
-
// omit this on v0.x; consumers should treat undefined as "no metadata
|
|
41
|
-
// declared on Press — fall back to openpress.config.mjs values".
|
|
38
|
+
// Metadata declared on <Press> props.
|
|
42
39
|
metadata?: PressMetadata;
|
|
43
40
|
}
|
|
44
41
|
|
|
@@ -48,8 +45,6 @@ export function Press(props: PressProps) {
|
|
|
48
45
|
// Press is intentionally inert at render time — the engine reads its
|
|
49
46
|
// props and children through React.Children inspection during the
|
|
50
47
|
// export pipeline, then injects context above any nested helpers.
|
|
51
|
-
// For the v0.x shape (children-only usage), this still passes children
|
|
52
|
-
// through unchanged.
|
|
53
48
|
return <Fragment>{props.children}</Fragment>;
|
|
54
49
|
}
|
|
55
50
|
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Frame } from "./Frame";
|
|
2
|
+
import type { SlideProps } from "./types";
|
|
3
|
+
|
|
4
|
+
export function Slide({
|
|
5
|
+
id,
|
|
6
|
+
role = "canvas.slide",
|
|
7
|
+
chrome = false,
|
|
8
|
+
...rest
|
|
9
|
+
}: SlideProps) {
|
|
10
|
+
return <Frame {...rest} frameKey={id} role={role} chrome={chrome} />;
|
|
11
|
+
}
|
|
@@ -8,13 +8,16 @@
|
|
|
8
8
|
export { Press, PressContext, PRESS_MARKER } from "./Press";
|
|
9
9
|
export { Workspace, WorkspaceContext, WORKSPACE_MARKER } from "./Workspace";
|
|
10
10
|
export { Frame, FRAME_MARKER } from "./Frame";
|
|
11
|
+
export { Slide } from "./Slide";
|
|
11
12
|
export { FrameContext } from "./FrameContext";
|
|
13
|
+
export { PageFolio } from "./PageFolio";
|
|
12
14
|
export { MdxArea } from "./MdxArea";
|
|
13
15
|
export { useSource } from "./useSource";
|
|
14
16
|
export { ObjectEntity, Text, BaseFigure, BaseCallout, MediaFigure, ImageFigure } from "./primitives";
|
|
15
17
|
|
|
16
18
|
export type {
|
|
17
19
|
FrameProps,
|
|
20
|
+
SlideProps,
|
|
18
21
|
FrameRole,
|
|
19
22
|
MdxAreaProps,
|
|
20
23
|
MdxAreaOverflow,
|
|
@@ -45,3 +48,4 @@ export type {
|
|
|
45
48
|
export type { PressContextValue, AllocationHints, PressMetadata } from "./Press";
|
|
46
49
|
export type { WorkspaceContextValue } from "./Workspace";
|
|
47
50
|
export type { FrameContextValue } from "./FrameContext";
|
|
51
|
+
export type { PageFolioNumberFormat, PageFolioProps, PageFolioVariant } from "./PageFolio";
|
|
@@ -18,6 +18,12 @@ export type FrameProps = Omit<HTMLAttributes<HTMLElement>, "role" | "children">
|
|
|
18
18
|
children?: ReactNode;
|
|
19
19
|
};
|
|
20
20
|
|
|
21
|
+
export type SlideProps = Omit<FrameProps, "frameKey" | "role" | "chrome" | "title"> & {
|
|
22
|
+
id: string;
|
|
23
|
+
role?: FrameRole;
|
|
24
|
+
chrome?: boolean;
|
|
25
|
+
};
|
|
26
|
+
|
|
21
27
|
export type MdxAreaOverflow = "extend" | "truncate" | "error";
|
|
22
28
|
|
|
23
29
|
export type MdxAreaProps = Omit<HTMLAttributes<HTMLElement>, "children"> & {
|
|
@@ -47,9 +53,9 @@ export interface PressProps {
|
|
|
47
53
|
// Document tree — Frames, manuscript helpers, etc.
|
|
48
54
|
children: ReactNode;
|
|
49
55
|
// -------------------------------------------------------------------------
|
|
50
|
-
//
|
|
56
|
+
// Press metadata props.
|
|
51
57
|
// -------------------------------------------------------------------------
|
|
52
|
-
// Document title.
|
|
58
|
+
// Document title. Used for PDF metadata, HTML <title>,
|
|
53
59
|
// OG tags, and the Workspace gallery / tab-bar label.
|
|
54
60
|
title?: string;
|
|
55
61
|
// Creation mode. Pages are source-driven with MDX allocation; slides are
|
|
@@ -61,15 +67,17 @@ export interface PressProps {
|
|
|
61
67
|
// Array of source registrations from mdxSource(). Replaces the v0.x
|
|
62
68
|
// `export const sources` named export.
|
|
63
69
|
sources?: ReadonlyArray<PressSource>;
|
|
64
|
-
// URL / output slug for this Press
|
|
65
|
-
// "/" when only one Press exists in the Workspace; required when the
|
|
66
|
-
// Workspace has multiple Press children.
|
|
70
|
+
// URL / output slug for this Press. Must match the Press folder name.
|
|
67
71
|
slug?: string;
|
|
68
|
-
// Optional per-Press theme directory. Defaults
|
|
69
|
-
//
|
|
72
|
+
// Optional per-Press theme directory. Defaults include the folder-local
|
|
73
|
+
// "./theme"; shared CSS lives in press/shared/theme.
|
|
70
74
|
theme?: string;
|
|
71
|
-
// Optional per-Press components
|
|
72
|
-
|
|
75
|
+
// Optional per-Press components directories. Defaults include the
|
|
76
|
+
// folder-local "./components" and workspace shared components.
|
|
77
|
+
componentsDir?: string | string[];
|
|
78
|
+
// Optional per-Press media directories. Defaults include the folder-local
|
|
79
|
+
// "./media" and workspace shared media.
|
|
80
|
+
mediaDir?: string | string[];
|
|
73
81
|
// Optional caption numbering overrides. Engine defaults to
|
|
74
82
|
// { figure: "Figure", table: "Table", separator: " " }.
|
|
75
83
|
captionNumbering?: {
|
|
@@ -80,7 +88,8 @@ export interface PressProps {
|
|
|
80
88
|
}
|
|
81
89
|
|
|
82
90
|
// ---------------------------------------------------------------------------
|
|
83
|
-
// Workspace —
|
|
91
|
+
// Workspace — engine-owned grouping component holding one or more Press
|
|
92
|
+
// children from discovered press/*/press.tsx entries.
|
|
84
93
|
// ---------------------------------------------------------------------------
|
|
85
94
|
|
|
86
95
|
export interface WorkspaceProps {
|
|
@@ -90,10 +99,9 @@ export interface WorkspaceProps {
|
|
|
90
99
|
// Project label surfaced in the gallery header, tab bar, and PDF
|
|
91
100
|
// metadata. Optional.
|
|
92
101
|
name?: string;
|
|
93
|
-
//
|
|
94
|
-
// set their own `theme` prop inherit from this. Default "./theme".
|
|
102
|
+
// Reserved for future workspace-level shared theme overrides.
|
|
95
103
|
theme?: string;
|
|
96
|
-
//
|
|
104
|
+
// Reserved for future workspace-level shared media overrides.
|
|
97
105
|
media?: string;
|
|
98
106
|
}
|
|
99
107
|
|
|
@@ -21,7 +21,7 @@ export function useSource<T extends ResolvedSource = ResolvedSource>(id: string)
|
|
|
21
21
|
const knownText = known.length > 0 ? known.join(", ") : "(none)";
|
|
22
22
|
throw new Error(
|
|
23
23
|
`Unknown source "${id}". Available sources: ${knownText}. ` +
|
|
24
|
-
`Register it as a <Press sources={[mdxSource({ id: "${id}", ... })]}> entry in press
|
|
24
|
+
`Register it as a <Press sources={[mdxSource({ id: "${id}", ... })]}> entry in press/<slug>/press.tsx.`,
|
|
25
25
|
);
|
|
26
26
|
}
|
|
27
27
|
return source as T;
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
// Shape of /openpress/workspace.json — the reader fetches this on
|
|
2
2
|
// boot to decide between gallery routing (multi-Press) and direct
|
|
3
|
-
// load (single Press). One entry per
|
|
4
|
-
//
|
|
5
|
-
// Single-Press workspaces emit one entry with slug = "" and the
|
|
6
|
-
// legacy /openpress/document.json path. Multi-Press emits one entry
|
|
7
|
-
// per slug; each `documentUrl` resolves to /openpress/<slug>/document.json.
|
|
3
|
+
// load (single Press). One entry per discovered Press folder.
|
|
8
4
|
import type { PressType } from "./documentTypes";
|
|
9
5
|
|
|
10
6
|
export interface WorkspaceManifest {
|
|
@@ -16,13 +12,12 @@ export interface WorkspaceManifest {
|
|
|
16
12
|
}
|
|
17
13
|
|
|
18
14
|
export interface WorkspaceManifestPress {
|
|
19
|
-
// Slug for this Press.
|
|
20
|
-
// (legacy root); slug-shaped string for multi-Press.
|
|
15
|
+
// Slug for this Press. Matches the folder-convention Press slug.
|
|
21
16
|
slug: string;
|
|
22
17
|
// <Press title="..."> prop. Required in v1.0 contract.
|
|
23
18
|
title: string;
|
|
24
|
-
// Creation mode declared by <Press type>.
|
|
25
|
-
//
|
|
19
|
+
// Creation mode declared by <Press type>. The reader uses this for
|
|
20
|
+
// mode-specific navigation affordances.
|
|
26
21
|
type: PressType;
|
|
27
22
|
// Page geometry summary. Same shape as the reader's
|
|
28
23
|
// ReaderDocument.theme — readers can show a thumb in the gallery
|
|
@@ -29,9 +29,13 @@ export function SlidePresentationPage({
|
|
|
29
29
|
if (typeof window === "undefined") return 0;
|
|
30
30
|
return pageIndexFromHash(window.location.hash, normalizedPageCount) ?? 0;
|
|
31
31
|
});
|
|
32
|
-
const [uiMode, setUiMode] = useState<PresentationUiMode>(() =>
|
|
33
|
-
shouldStartImmersive()
|
|
34
|
-
|
|
32
|
+
const [uiMode, setUiMode] = useState<PresentationUiMode>(() => {
|
|
33
|
+
if (shouldStartImmersive()) return "immersive";
|
|
34
|
+
// Fullscreen may already be active if requestFullscreen() was called
|
|
35
|
+
// synchronously in the workbench click handler before in-place navigation.
|
|
36
|
+
if (typeof globalThis.document !== "undefined" && globalThis.document.fullscreenElement) return "immersive";
|
|
37
|
+
return "chrome";
|
|
38
|
+
});
|
|
35
39
|
const pageViewport = usePageViewportScale({
|
|
36
40
|
stageRef,
|
|
37
41
|
pageContainerRef: sourceContainerRef,
|
|
@@ -98,36 +98,9 @@ export function WorkbenchToolbarActions({
|
|
|
98
98
|
</button>
|
|
99
99
|
</div>
|
|
100
100
|
) : null}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
currentPageIndex={currentPageIndex}
|
|
105
|
-
pressTitle={pressTitle}
|
|
106
|
-
theme={theme}
|
|
107
|
-
onExportPdf={onExportPdf}
|
|
108
|
-
pdfDisabled={pdfDisabled}
|
|
109
|
-
pdfLabel={pdfLabel}
|
|
110
|
-
pdfStatusMessage={pdfStatusMessage}
|
|
111
|
-
pdfActionStatus={pdfActionStatus}
|
|
112
|
-
/>
|
|
113
|
-
</div>
|
|
114
|
-
<div className="openpress-workbench-toolbar__group openpress-workbench-toolbar__group--page" aria-label="頁面規格">
|
|
115
|
-
{isSlidePress && onOpenPresentation ? (
|
|
116
|
-
<button
|
|
117
|
-
type="button"
|
|
118
|
-
className="openpress-workbench-toolbar-action"
|
|
119
|
-
data-openpress-slide-present
|
|
120
|
-
data-openpress-toolbar-expanded="false"
|
|
121
|
-
data-openpress-toolbar-active="false"
|
|
122
|
-
aria-pressed="false"
|
|
123
|
-
title="進入放映模式"
|
|
124
|
-
aria-label="進入放映模式"
|
|
125
|
-
onClick={() => onOpenPresentation(currentPageIndex)}
|
|
126
|
-
>
|
|
127
|
-
<Play aria-hidden="true" />
|
|
128
|
-
<span className="openpress-workbench-toolbar-action__label">放映</span>
|
|
129
|
-
</button>
|
|
130
|
-
) : null}
|
|
101
|
+
|
|
102
|
+
{/* Center group: page geometry / zoom + workspace tools */}
|
|
103
|
+
<div className="openpress-workbench-toolbar__group openpress-workbench-toolbar__group--page" aria-label="頁面規格與工具">
|
|
131
104
|
<button
|
|
132
105
|
type="button"
|
|
133
106
|
className="openpress-workbench-page-geometry"
|
|
@@ -146,25 +119,15 @@ export function WorkbenchToolbarActions({
|
|
|
146
119
|
onScaleModeChange={onScaleModeChange}
|
|
147
120
|
onPageLayoutModeChange={onPageLayoutModeChange}
|
|
148
121
|
/>
|
|
149
|
-
|
|
150
|
-
|
|
122
|
+
{workspaceMode ? (
|
|
123
|
+
<span className="openpress-workbench-toolbar__sep" aria-hidden="true" />
|
|
124
|
+
) : null}
|
|
151
125
|
{workspaceMode ? (
|
|
152
126
|
<SearchControl
|
|
153
127
|
sourceBlocksByPath={sourceBlocksByPath}
|
|
154
128
|
onSelectPage={onSelectPage}
|
|
155
129
|
/>
|
|
156
130
|
) : null}
|
|
157
|
-
{workspaceMode && editStatusMessage ? (
|
|
158
|
-
<span
|
|
159
|
-
className="openpress-dev-edit-status openpress-dev-edit-status--toolbar"
|
|
160
|
-
data-openpress-edit-status={inlineEditStatus.state}
|
|
161
|
-
role="status"
|
|
162
|
-
aria-live="polite"
|
|
163
|
-
>
|
|
164
|
-
{inlineEditStatus.state === "saving" ? <span className="openpress-dev-edit-status__spinner" aria-hidden="true" /> : null}
|
|
165
|
-
<span>{editStatusMessage}</span>
|
|
166
|
-
</span>
|
|
167
|
-
) : null}
|
|
168
131
|
{workspaceMode ? (
|
|
169
132
|
<button
|
|
170
133
|
type="button"
|
|
@@ -183,6 +146,17 @@ export function WorkbenchToolbarActions({
|
|
|
183
146
|
<span className="openpress-dev-inspector-status">{inspectorSelectionLabel}</span>
|
|
184
147
|
</button>
|
|
185
148
|
) : null}
|
|
149
|
+
{workspaceMode && editStatusMessage ? (
|
|
150
|
+
<span
|
|
151
|
+
className="openpress-dev-edit-status openpress-dev-edit-status--toolbar"
|
|
152
|
+
data-openpress-edit-status={inlineEditStatus.state}
|
|
153
|
+
role="status"
|
|
154
|
+
aria-live="polite"
|
|
155
|
+
>
|
|
156
|
+
{inlineEditStatus.state === "saving" ? <span className="openpress-dev-edit-status__spinner" aria-hidden="true" /> : null}
|
|
157
|
+
<span>{editStatusMessage}</span>
|
|
158
|
+
</span>
|
|
159
|
+
) : null}
|
|
186
160
|
{workspaceMode && inspectorMode ? (
|
|
187
161
|
<span
|
|
188
162
|
className="openpress-dev-inspector-status"
|
|
@@ -193,6 +167,21 @@ export function WorkbenchToolbarActions({
|
|
|
193
167
|
{inspectorCommentStatusMessage}
|
|
194
168
|
</span>
|
|
195
169
|
) : null}
|
|
170
|
+
</div>
|
|
171
|
+
|
|
172
|
+
{/* Right group: export + deploy + present */}
|
|
173
|
+
<div className="openpress-workbench-toolbar__group openpress-workbench-toolbar__group--right" aria-label="匯出與發布">
|
|
174
|
+
<ExportControl
|
|
175
|
+
pages={pages}
|
|
176
|
+
currentPageIndex={currentPageIndex}
|
|
177
|
+
pressTitle={pressTitle}
|
|
178
|
+
theme={theme}
|
|
179
|
+
onExportPdf={onExportPdf}
|
|
180
|
+
pdfDisabled={pdfDisabled}
|
|
181
|
+
pdfLabel={pdfLabel}
|
|
182
|
+
pdfStatusMessage={pdfStatusMessage}
|
|
183
|
+
pdfActionStatus={pdfActionStatus}
|
|
184
|
+
/>
|
|
196
185
|
{localDeployEnabled ? (
|
|
197
186
|
<DeploymentControl
|
|
198
187
|
info={deploymentInfo}
|
|
@@ -200,6 +189,20 @@ export function WorkbenchToolbarActions({
|
|
|
200
189
|
onDeploy={onDeploy}
|
|
201
190
|
/>
|
|
202
191
|
) : null}
|
|
192
|
+
{isSlidePress && onOpenPresentation ? (
|
|
193
|
+
<button
|
|
194
|
+
type="button"
|
|
195
|
+
className="openpress-workbench-toolbar-action openpress-workbench-toolbar-action--primary"
|
|
196
|
+
data-openpress-slide-present
|
|
197
|
+
aria-pressed="false"
|
|
198
|
+
title="進入放映模式"
|
|
199
|
+
aria-label="進入放映模式"
|
|
200
|
+
onClick={() => onOpenPresentation(currentPageIndex)}
|
|
201
|
+
>
|
|
202
|
+
<Play aria-hidden="true" />
|
|
203
|
+
<span className="openpress-workbench-toolbar-action__label">放映</span>
|
|
204
|
+
</button>
|
|
205
|
+
) : null}
|
|
203
206
|
</div>
|
|
204
207
|
</>
|
|
205
208
|
);
|
|
@@ -235,6 +235,15 @@
|
|
|
235
235
|
height: 1px;
|
|
236
236
|
}
|
|
237
237
|
|
|
238
|
+
.openpress-workbench-toolbar__sep {
|
|
239
|
+
display: block;
|
|
240
|
+
width: 1px;
|
|
241
|
+
height: 16px;
|
|
242
|
+
flex: 0 0 auto;
|
|
243
|
+
background: rgb(255 255 255 / 10%);
|
|
244
|
+
border-radius: 1px;
|
|
245
|
+
}
|
|
246
|
+
|
|
238
247
|
.openpress-workbench-toolbar-action {
|
|
239
248
|
position: relative;
|
|
240
249
|
display: inline-flex;
|
|
@@ -279,6 +288,30 @@
|
|
|
279
288
|
opacity: 0.62;
|
|
280
289
|
}
|
|
281
290
|
|
|
291
|
+
.openpress-workbench-toolbar-action--primary {
|
|
292
|
+
width: auto;
|
|
293
|
+
min-width: 30px;
|
|
294
|
+
max-width: min(34vw, 300px);
|
|
295
|
+
gap: 7px;
|
|
296
|
+
padding: 0 12px;
|
|
297
|
+
background: var(--openpress-accent, #df4b21);
|
|
298
|
+
border-color: transparent;
|
|
299
|
+
color: #fff;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
.openpress-workbench-toolbar-action--primary .openpress-workbench-toolbar-action__label {
|
|
303
|
+
display: inline-flex;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
.openpress-workbench-toolbar-action--primary:hover:not(:disabled) {
|
|
307
|
+
background: color-mix(in srgb, var(--openpress-accent, #df4b21) 82%, #fff);
|
|
308
|
+
color: #fff;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
.openpress-workbench-toolbar-action--primary:active:not(:disabled) {
|
|
312
|
+
transform: translateY(1px);
|
|
313
|
+
}
|
|
314
|
+
|
|
282
315
|
.openpress-workbench-toolbar-action[data-openpress-toolbar-expanded="true"] {
|
|
283
316
|
width: auto;
|
|
284
317
|
min-width: 30px;
|
|
@@ -4,10 +4,8 @@
|
|
|
4
4
|
load the document directly, so this CSS is dormant until users
|
|
5
5
|
add a second <Press> to their <Workspace>.
|
|
6
6
|
|
|
7
|
-
Layout
|
|
8
|
-
|
|
9
|
-
loads its first page asynchronously and renders it scaled-down
|
|
10
|
-
inside the thumbnail slot. */
|
|
7
|
+
Layout: full-height dark page, header + two-column body
|
|
8
|
+
(narrow left sidebar for type filter, fluid right grid). */
|
|
11
9
|
|
|
12
10
|
.openpress-workspace-gallery {
|
|
13
11
|
--workspace-bg: #10110f;
|
|
@@ -20,10 +18,10 @@
|
|
|
20
18
|
--workspace-card-muted: #65635d;
|
|
21
19
|
--workspace-card-line: rgba(20, 20, 17, 0.1);
|
|
22
20
|
--workspace-card-stage: #e8e5dc;
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
--workspace-sidebar-w: 180px;
|
|
22
|
+
display: flex;
|
|
23
|
+
flex-direction: column;
|
|
25
24
|
min-height: 100vh;
|
|
26
|
-
max-width: none;
|
|
27
25
|
margin: 0;
|
|
28
26
|
padding: 3.6rem clamp(2rem, 4vw, 4.5rem) 6rem;
|
|
29
27
|
font-family: var(--openpress-font-body, system-ui, sans-serif);
|
|
@@ -31,11 +29,13 @@
|
|
|
31
29
|
background:
|
|
32
30
|
linear-gradient(180deg, var(--workspace-bg-soft), var(--workspace-bg) 42rem),
|
|
33
31
|
var(--workspace-bg);
|
|
32
|
+
gap: 2.25rem;
|
|
34
33
|
}
|
|
35
34
|
|
|
35
|
+
/* ── Header ──────────────────────────────────────────────── */
|
|
36
|
+
|
|
36
37
|
.openpress-workspace-gallery__header {
|
|
37
|
-
display:
|
|
38
|
-
grid-template-columns: minmax(0, 1fr) auto;
|
|
38
|
+
display: flex;
|
|
39
39
|
align-items: end;
|
|
40
40
|
justify-content: space-between;
|
|
41
41
|
gap: 2.5rem;
|
|
@@ -48,50 +48,114 @@
|
|
|
48
48
|
gap: 0.75rem;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
.openpress-workspace-
|
|
51
|
+
.openpress-workspace-gallery__brand {
|
|
52
|
+
display: flex;
|
|
53
|
+
align-items: center;
|
|
54
|
+
gap: 0.5rem;
|
|
52
55
|
margin: 0;
|
|
53
|
-
color: var(--workspace-muted);
|
|
54
56
|
font-family: var(--openpress-font-mono, ui-monospace, monospace);
|
|
55
57
|
font-size: 0.68rem;
|
|
56
58
|
font-weight: 600;
|
|
57
|
-
letter-spacing: 0.
|
|
59
|
+
letter-spacing: 0.12em;
|
|
58
60
|
text-transform: uppercase;
|
|
59
61
|
}
|
|
60
62
|
|
|
63
|
+
.openpress-workspace-gallery__brand-mark {
|
|
64
|
+
color: var(--workspace-ink);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.openpress-workspace-gallery__brand-sep {
|
|
68
|
+
color: var(--workspace-muted);
|
|
69
|
+
letter-spacing: 0;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.openpress-workspace-gallery__eyebrow {
|
|
73
|
+
color: var(--workspace-muted);
|
|
74
|
+
}
|
|
75
|
+
|
|
61
76
|
.openpress-workspace-gallery__header h1 {
|
|
62
77
|
margin: 0;
|
|
63
78
|
font-family: var(--openpress-font-display, var(--openpress-font-body, system-ui));
|
|
64
|
-
font-size: clamp(
|
|
65
|
-
font-weight:
|
|
66
|
-
line-height:
|
|
67
|
-
letter-spacing: -0.
|
|
79
|
+
font-size: clamp(1.4rem, 2.6vw, 2.2rem);
|
|
80
|
+
font-weight: 600;
|
|
81
|
+
line-height: 1.1;
|
|
82
|
+
letter-spacing: -0.02em;
|
|
68
83
|
color: var(--workspace-ink);
|
|
69
84
|
}
|
|
70
85
|
|
|
71
|
-
|
|
72
|
-
|
|
86
|
+
/* ── Body: sidebar + grid ────────────────────────────────── */
|
|
87
|
+
|
|
88
|
+
.openpress-workspace-gallery__body {
|
|
73
89
|
display: grid;
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
90
|
+
grid-template-columns: var(--workspace-sidebar-w) 1fr;
|
|
91
|
+
align-items: start;
|
|
92
|
+
gap: 2.5rem;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/* ── Left sidebar ────────────────────────────────────────── */
|
|
96
|
+
|
|
97
|
+
.openpress-workspace-gallery__sidebar {
|
|
98
|
+
display: flex;
|
|
99
|
+
flex-direction: column;
|
|
100
|
+
gap: 2px;
|
|
101
|
+
position: sticky;
|
|
102
|
+
top: 1.5rem;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.openpress-workspace-gallery__filter-btn {
|
|
106
|
+
display: flex;
|
|
107
|
+
align-items: center;
|
|
108
|
+
justify-content: space-between;
|
|
109
|
+
gap: 0.6rem;
|
|
110
|
+
width: 100%;
|
|
111
|
+
padding: 0.52rem 0.75rem;
|
|
112
|
+
border: 1px solid transparent;
|
|
113
|
+
border-radius: 7px;
|
|
114
|
+
background: transparent;
|
|
115
|
+
color: var(--workspace-muted);
|
|
116
|
+
font-family: var(--openpress-font-body, system-ui, sans-serif);
|
|
117
|
+
font-size: 0.82rem;
|
|
118
|
+
font-weight: 500;
|
|
119
|
+
text-align: left;
|
|
120
|
+
cursor: pointer;
|
|
121
|
+
transition:
|
|
122
|
+
background 140ms ease,
|
|
123
|
+
color 140ms ease,
|
|
124
|
+
border-color 140ms ease;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.openpress-workspace-gallery__filter-btn:hover {
|
|
128
|
+
background: rgba(244, 241, 232, 0.06);
|
|
77
129
|
color: var(--workspace-ink);
|
|
78
|
-
font-family: var(--openpress-font-mono, ui-monospace, monospace);
|
|
79
|
-
line-height: 1;
|
|
80
130
|
}
|
|
81
131
|
|
|
82
|
-
.openpress-workspace-
|
|
132
|
+
.openpress-workspace-gallery__filter-btn[data-active="true"] {
|
|
133
|
+
background: rgba(244, 241, 232, 0.1);
|
|
134
|
+
border-color: rgba(244, 241, 232, 0.14);
|
|
83
135
|
color: var(--workspace-ink);
|
|
84
|
-
font-size: 2rem;
|
|
85
|
-
font-weight: 500;
|
|
86
|
-
letter-spacing: -0.04em;
|
|
87
136
|
}
|
|
88
137
|
|
|
89
|
-
.openpress-workspace-
|
|
138
|
+
.openpress-workspace-gallery__filter-label {
|
|
139
|
+
flex: 1 1 auto;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.openpress-workspace-gallery__filter-count {
|
|
143
|
+
flex: 0 0 auto;
|
|
144
|
+
font-family: var(--openpress-font-mono, ui-monospace, monospace);
|
|
145
|
+
font-size: 0.72rem;
|
|
146
|
+
font-weight: 500;
|
|
90
147
|
color: var(--workspace-muted);
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
148
|
+
letter-spacing: 0.04em;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.openpress-workspace-gallery__filter-btn[data-active="true"] .openpress-workspace-gallery__filter-count {
|
|
152
|
+
color: var(--workspace-ink);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/* ── Main grid ───────────────────────────────────────────── */
|
|
156
|
+
|
|
157
|
+
.openpress-workspace-gallery__main {
|
|
158
|
+
min-width: 0;
|
|
95
159
|
}
|
|
96
160
|
|
|
97
161
|
.openpress-workspace-gallery__grid {
|
|
@@ -100,8 +164,6 @@
|
|
|
100
164
|
padding: 0;
|
|
101
165
|
display: grid;
|
|
102
166
|
align-items: start;
|
|
103
|
-
/* Uniform card size — Figma-style. Outer thumb is fixed 4:3, the
|
|
104
|
-
inner page letterboxes to its own geometry. */
|
|
105
167
|
grid-template-columns: repeat(auto-fill, minmax(20rem, 1fr));
|
|
106
168
|
gap: 1.5rem;
|
|
107
169
|
}
|
|
@@ -110,6 +172,15 @@
|
|
|
110
172
|
display: flex;
|
|
111
173
|
}
|
|
112
174
|
|
|
175
|
+
.openpress-workspace-gallery__empty {
|
|
176
|
+
margin: 0;
|
|
177
|
+
padding: 3rem 0;
|
|
178
|
+
color: var(--workspace-muted);
|
|
179
|
+
font-size: 0.88rem;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/* ── Card ────────────────────────────────────────────────── */
|
|
183
|
+
|
|
113
184
|
.openpress-workspace-gallery__card {
|
|
114
185
|
appearance: none;
|
|
115
186
|
display: grid;
|
|
@@ -138,13 +209,12 @@
|
|
|
138
209
|
outline: none;
|
|
139
210
|
}
|
|
140
211
|
|
|
212
|
+
/* ── Thumbnail ───────────────────────────────────────────── */
|
|
213
|
+
|
|
141
214
|
.openpress-workspace-gallery__thumb {
|
|
142
215
|
position: relative;
|
|
143
216
|
display: block;
|
|
144
217
|
width: 100%;
|
|
145
|
-
/* Uniform 4:3 outer slot across every card. The page itself
|
|
146
|
-
letterboxes inside via centered scale, so each Press shows at its
|
|
147
|
-
own true aspect against the gradient background. */
|
|
148
218
|
aspect-ratio: 4 / 3;
|
|
149
219
|
background:
|
|
150
220
|
linear-gradient(
|
|
@@ -220,7 +290,9 @@
|
|
|
220
290
|
50% { opacity: 0.55; }
|
|
221
291
|
}
|
|
222
292
|
|
|
223
|
-
|
|
293
|
+
/* ── Card body ───────────────────────────────────────────── */
|
|
294
|
+
|
|
295
|
+
.openpress-workspace-gallery__card-body {
|
|
224
296
|
display: grid;
|
|
225
297
|
align-content: space-between;
|
|
226
298
|
gap: 1.2rem;
|
|
@@ -262,10 +334,6 @@
|
|
|
262
334
|
white-space: nowrap;
|
|
263
335
|
}
|
|
264
336
|
|
|
265
|
-
.openpress-workspace-gallery__geom {
|
|
266
|
-
color: var(--workspace-card-muted);
|
|
267
|
-
}
|
|
268
|
-
|
|
269
337
|
.openpress-workspace-gallery__geom {
|
|
270
338
|
display: inline-flex;
|
|
271
339
|
align-items: center;
|
|
@@ -279,16 +347,31 @@
|
|
|
279
347
|
white-space: nowrap;
|
|
280
348
|
}
|
|
281
349
|
|
|
350
|
+
/* ── Responsive ──────────────────────────────────────────── */
|
|
351
|
+
|
|
352
|
+
@media (max-width: 860px) {
|
|
353
|
+
.openpress-workspace-gallery__body {
|
|
354
|
+
grid-template-columns: 1fr;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
.openpress-workspace-gallery__sidebar {
|
|
358
|
+
position: static;
|
|
359
|
+
flex-direction: row;
|
|
360
|
+
flex-wrap: wrap;
|
|
361
|
+
gap: 6px;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
.openpress-workspace-gallery__filter-btn {
|
|
365
|
+
width: auto;
|
|
366
|
+
flex: 0 0 auto;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
282
370
|
@media (max-width: 720px) {
|
|
283
371
|
.openpress-workspace-gallery {
|
|
284
372
|
padding: 2.25rem 1rem 4rem;
|
|
285
373
|
}
|
|
286
374
|
|
|
287
|
-
.openpress-workspace-gallery__header {
|
|
288
|
-
display: grid;
|
|
289
|
-
align-items: start;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
375
|
.openpress-workspace-gallery__grid {
|
|
293
376
|
grid-template-columns: 1fr;
|
|
294
377
|
}
|