diff --git a/RECOMMENDED_FONTS.md b/RECOMMENDED_FONTS.md index 56eff17..7265416 100644 --- a/RECOMMENDED_FONTS.md +++ b/RECOMMENDED_FONTS.md @@ -1,56 +1,44 @@ -# Magazine Bold — Recommended fonts +# Magazine Bold — fonts -Per `themes/docs/FONTS.md` (wave-1 policy), this theme ships `fonts.json = []` -and provides CSS fallback stacks via `--font-heading`, `--font-body`, -`--font-mono`. The site admin assigns real fonts via the typography settings -panel; the recommendations below match the spec §5 intent. +This theme **bundles** a Latin subset of three open-licence (SIL OFL 1.1) +families, so it looks correct the moment it is activated — no admin setup +required. The bundled faces are declared in `fonts.json` and land in the +typography picker as `source=template` rows. -## Display (heading slot) +| Slot | Bundled family | Weights / styles | Role | +|---------|----------------|------------------------|---------------------------------------------------------------| +| Heading | Archivo Black | 400 normal | Massive condensed uppercase cover headlines and the wordmark | +| Body | Inter | 400 / 500 / 700 normal | Body copy, decks and UI text | +| Mono | IBM Plex Mono | 400 / 500 normal | Kickers, datelines, folio numbers, captions and button labels | -Spec calls for **PP Editorial New** (Regular Italic + Ultralight) at 180–280pt -for cover-story hits, with **Migra** as a secondary serif drop-in. Both are -commercial; the open-source picks below render the same italic-display energy. +The bundled woff2 files and their licence record live in `assets/fonts/` +(`LICENSES.md`). Every `font-family` in the theme is routed through +`var(--font-heading, ...)`, `var(--font-body, ...)` and `var(--font-mono, ...)` +with a fallback stack, so the layout is usable before any font is assigned and +the admin font picker stays in full control. -Open-source picks (assign one to **Heading**): +## Assigning the bundled faces -- `google:Playfair Display` — the closest open-source approximation of - PP Editorial New's contrast and italic axis. Open the typography panel, - pick "Playfair Display" from the Google Fonts tab, assign to Heading. -- `google:Fraunces` — a more contemporary alternative with optical sizing; - works well as the secondary serif (`font-serif-sub`). -- `google:Cormorant Garamond` — slightly higher-contrast classic serif if - you want a more traditional broadsheet feel. +1. In the BlockNinja admin, open **Settings → Theme → Typography**. +2. In the **Bundled** tab, assign **Archivo Black** to the *Heading* slot. +3. Assign **Inter** to the *Body* slot. +4. Assign **IBM Plex Mono** to the *Mono* slot. -Commercial picks (require admin licensing + upload): +## Optional swaps (Google Fonts tab) -- `upload:PP Editorial New` — purchase from pangrampangram.com, upload via the - font picker's "Upload" tab. -- `upload:Migra` — purchase from pangrampangram.com (Migra is a sibling - family), upload via the picker. +The identity is condensed-heavy display + neutral sans body + mono details. +Any of these drop in via the CSS variables with no template change: -## Body +- **Anton** (`google:Anton`) — a taller, more condensed display face for the + *Heading* slot; the loudest cover-headline option and the WO's alternate. +- **Archivo** (`google:Archivo`) — for the *Body* slot if you want the body to + echo the display family. +- **Space Grotesk** (`google:Space Grotesk`) — an alternative body / deck face. +- **Space Mono** (`google:Space Mono`) — a chunkier mono for the *Mono* slot. -Spec calls for **Inter** 16–18px with tight leading and generous tracking on -small-caps kickers. Inter is already in the curated Google Fonts list: +## Bundling a commercial display face (out of scope for Wave A) -- `google:Inter` — open the typography panel, pick "Inter" from the Google - Fonts tab, assign to Body. Aim for weights 400 + 600. - -## Mono (captions, folios, metadata only) - -Spec calls for **JetBrains Mono** at caption sizes for kickers, folios and -the colophon strip. Curated: - -- `google:JetBrains Mono` — open the typography panel, pick "JetBrains Mono" - from the Google Fonts tab, assign to Mono. Weight 400 is enough. - -Alternates: `google:Space Mono`, `google:IBM Plex Mono`. - -## How the theme degrades when nothing is picked - -The compiled CSS shipped with this `.so` uses the variables with sensible -fallbacks so a fresh install still reads like an editorial magazine: - -- `--font-heading` falls back to `"Playfair Display", "Cormorant Garamond", Georgia, "Times New Roman", serif` -- `--font-body` falls back to `"Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif` -- `--font-mono` falls back to `"JetBrains Mono", "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace` +If the brand locks in a licensed display face in place of Archivo Black, licence +and subset the woff2 set, drop it in `assets/fonts/`, and add the family + +variants to `fonts.json`. The CSS does not change — the same `var(--font-heading)` +consumer picks up whichever face an admin assigns. diff --git a/RECOMMENDED_ICONS.md b/RECOMMENDED_ICONS.md new file mode 100644 index 0000000..bea1c25 --- /dev/null +++ b/RECOMMENDED_ICONS.md @@ -0,0 +1,30 @@ +# Recommended Icons — Magazine Bold + +Magazine Bold is a typographic, rule-heavy theme. It leans on condensed +headlines, mono labels and heavy rules, not iconography, so it references only +two bundled packs. `plugin.mod` declares them in `required_icon_packs`; until +the loader auto-installs declared packs, an admin should confirm they are +enabled before activation. + +## Required packs + +- **lucide** — ships bundled with the CMS (v0.460). Enabled by default. Used + for chrome and UI glyphs (arrows, chevrons, close, check, play, map-pin, + navigation, mail, rss, star, flag). If icons render as empty boxes, open + `/admin/icons` and confirm **Lucide** is installed and enabled. +- **simple-icons** — ships bundled with the CMS (v14.0). Used only for brand + glyphs in `author-bio-hero`, `author-bio` and `social-links` (x, linkedin, + github). Confirm it is enabled at `/admin/icons` if brand marks are blank. + +## Icons used by the theme chrome and overrides + +All via the sprite pipeline (`` +or the `:::::` shorthand): + +`arrow-right`, `chevron-down`, `chevron-left`, `chevron-right`, `x`, `check`, +`play`, `download`, `map-pin`, `navigation`, `mail`, `rss`, `star`, `flag`, +`diamond`, `globe` (lucide); `x`, `linkedin`, `github` (simple-icons). + +Block content that stores an icon (feature-grid items, social links, contact +channels) may reference any installed pack as a `pack:name` string; the schemas +default to `lucide` so the theme renders without extra setup. diff --git a/assets/fonts/LICENSES.md b/assets/fonts/LICENSES.md new file mode 100644 index 0000000..966ce9d --- /dev/null +++ b/assets/fonts/LICENSES.md @@ -0,0 +1,17 @@ +# Bundled font licenses + +All bundled woff2 files in this directory are latin-subset builds redistributed +under the SIL Open Font License 1.1 (OFL). No modifications were made beyond +subsetting to the latin range. + +| Family | Files | License | Source | +|--------|-------|---------|--------| +| Archivo Black | archivo-black-400.woff2 | SIL OFL 1.1 | Omnibus-Type / Google Fonts | +| Inter | inter-400.woff2, inter-500.woff2, inter-700.woff2 | SIL OFL 1.1 | Rasmus Andersson / Google Fonts | +| IBM Plex Mono | ibm-plex-mono-400.woff2, ibm-plex-mono-500.woff2 | SIL OFL 1.1 | IBM / Google Fonts | + +Full OFL text: https://openfontlicense.org/. Font-family is still admin-controlled; +these bundled families appear as `source=template` rows in the fonts picker and +are consumed only via `var(--font-heading|body|mono, )`. Archivo Black +drives the condensed display headlines, Inter the body, IBM Plex Mono the +kickers, datelines and captions. diff --git a/assets/fonts/archivo-black-400.woff2 b/assets/fonts/archivo-black-400.woff2 new file mode 100644 index 0000000..a88b0b5 Binary files /dev/null and b/assets/fonts/archivo-black-400.woff2 differ diff --git a/assets/fonts/ibm-plex-mono-400.woff2 b/assets/fonts/ibm-plex-mono-400.woff2 new file mode 100644 index 0000000..0804aaf Binary files /dev/null and b/assets/fonts/ibm-plex-mono-400.woff2 differ diff --git a/assets/fonts/ibm-plex-mono-500.woff2 b/assets/fonts/ibm-plex-mono-500.woff2 new file mode 100644 index 0000000..090f82f Binary files /dev/null and b/assets/fonts/ibm-plex-mono-500.woff2 differ diff --git a/assets/fonts/inter-400.woff2 b/assets/fonts/inter-400.woff2 new file mode 100644 index 0000000..f15b025 Binary files /dev/null and b/assets/fonts/inter-400.woff2 differ diff --git a/assets/fonts/inter-500.woff2 b/assets/fonts/inter-500.woff2 new file mode 100644 index 0000000..54f0a59 Binary files /dev/null and b/assets/fonts/inter-500.woff2 differ diff --git a/assets/fonts/inter-700.woff2 b/assets/fonts/inter-700.woff2 new file mode 100644 index 0000000..a68fb10 Binary files /dev/null and b/assets/fonts/inter-700.woff2 differ diff --git a/blocks/blocks.yaml b/blocks/blocks.yaml index 30cc45b..a92cd75 100644 --- a/blocks/blocks.yaml +++ b/blocks/blocks.yaml @@ -1,42 +1,34 @@ -# Magazine Bold theme block definitions (codeless .bnp, WO-WZ-021). -# Keys are fully qualified (magazine-bold:) because the codeless loader -# registers definitions verbatim — there is no PluginBlockRegistry prefixing -# in the codeless path (unlike the wasm/templ path). The qualified keys match -# the hardcoded data-block attributes and the master_pages.json references. +# Magazine Bold theme block definitions (codeless .bnp, WO-TF-021). +# +# These are the theme's TRUE persona blocks — streetwear / fashion-drop +# furniture with no builtin equivalent. Everything the theme used to ship that +# duplicated a builtin (masthead, photo essay, pull quote, issue archive, +# colophon) is now a re-skin OVERRIDE of a builtin (navbar, gallery, quote, +# post-index, footer) under templates/overrides/magazine-bold/. Keys are fully +# qualified (magazine-bold:) because the codeless loader registers +# definitions verbatim. blocks: - - key: magazine-bold:masthead - title: Issue Masthead - description: Wordmark, issue number and tagline strip — sits at the top of every page. - category: layout - schema: masthead.schema.json - template: masthead.ninjatpl - key: magazine-bold:cover_story title: Cover Story - description: 12-column asymmetric hero with kicker, oversized display headline, deck and cover image. - category: content + description: Asymmetric magazine cover — mono folio kicker, massive condensed headline, deck and a duotone cover image with a shop/read CTA. + category: theme schema: cover_story.schema.json template: cover_story.ninjatpl - - key: magazine-bold:photo_essay - title: Photo Essay - description: Numbered photo-essay with asymmetric frame spans (half / full / tall) and mono captions. - category: content - schema: photo_essay.schema.json - template: photo_essay.ninjatpl - - key: magazine-bold:pull_quote - title: Pull Quote - description: Color-block pull quote with rotating accent (pink / blue / lime). - category: content - schema: pull_quote.schema.json - template: pull_quote.ninjatpl - - key: magazine-bold:issue_archive - title: Issue Archive - description: Numbered list of back-issues driven by a bucket query. - category: navigation - schema: issue_archive.schema.json - template: issue_archive.ninjatpl - - key: magazine-bold:colophon - title: Colophon - description: Mono caption strip with credits and an optional social row. - category: layout - schema: colophon.schema.json - template: colophon.ninjatpl + - key: magazine-bold:lookbook + title: Lookbook + description: Grid of duotone look frames with mono numbering, captions and optional shop links — the drop's lookbook spread. + category: theme + schema: lookbook.schema.json + template: lookbook.ninjatpl + - key: magazine-bold:drop_calendar + title: Drop Calendar + description: Dated list of upcoming and past drops — mono datelines, condensed names and a live/soon/sold status tag. + category: theme + schema: drop_calendar.schema.json + template: drop_calendar.ninjatpl + - key: magazine-bold:credits_ticker + title: Credits Ticker + description: Marquee strip of credits (photographer, stylist, cast) — MID-motion scroll, paused on hover, static under prefers-reduced-motion. + category: theme + schema: credits_ticker.schema.json + template: credits_ticker.ninjatpl diff --git a/blocks/colophon.ninjatpl b/blocks/colophon.ninjatpl deleted file mode 100644 index 7cec6be..0000000 --- a/blocks/colophon.ninjatpl +++ /dev/null @@ -1,4 +0,0 @@ -
- {% if credits %}
{{ credits|safe }}
{% endif %} - {% if showSocial == "true" or showSocial == true %}{% endif %} -
diff --git a/blocks/colophon.schema.json b/blocks/colophon.schema.json deleted file mode 100644 index 64f3583..0000000 --- a/blocks/colophon.schema.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Colophon", - "description": "Mono caption strip with credits and optional social row.", - "type": "object", - "properties": { - "credits": { - "type": "string", - "title": "Credits", - "description": "Mono-set credits block (rich text).", - "x-editor": "richtext" - }, - "showSocial": { - "type": "string", - "title": "Show Social Row", - "description": "Toggle the social row beneath the credits.", - "enum": ["true", "false"], - "default": "false", - "x-editor": "select" - } - } -} diff --git a/blocks/cover_story.ninjatpl b/blocks/cover_story.ninjatpl index 3c48542..bf9c791 100644 --- a/blocks/cover_story.ninjatpl +++ b/blocks/cover_story.ninjatpl @@ -1,11 +1,23 @@ -
-
-
-
{% if kicker %}
{{ kicker }}
{% endif %}
-

{% if headline %}{{ headline|safe }}{% else %}Cover story{% endif %}

-
{% if deck %}
{{ deck|safe }}
{% endif %}
-
- {% if image %}
{% endif %} - {% if link %}{% endif %} -
+{# cover_story (magazine-bold) — asymmetric cover: mono folio kicker, massive condensed headline, deck, duotone cover image, shop/read CTA. Fields: kicker, headline (richtext), deck (richtext), image (media), link, linkLabel. Image via {% img %} (media filter not registered in block render path). MID motion: mb-reveal + duotone hover, reduced-motion safe. #} +
+
+
+
{% if kicker %}
{{ kicker }}
{% endif %}
+

{% if headline %}{{ headline|safe }}{% else %}Cover story{% endif %}

+
{% if deck %}
{{ deck|safe }}
{% endif %}
+
+ {% if image %}
{% img image alt="" class="h-full w-full object-cover" layout="hero" %}
{% endif %} + {% if link %}{% endif %} +
+ diff --git a/blocks/cover_story.schema.json b/blocks/cover_story.schema.json index e25bf86..3e34821 100644 --- a/blocks/cover_story.schema.json +++ b/blocks/cover_story.schema.json @@ -1,19 +1,19 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Cover Story", - "description": "12-column asymmetric hero anchoring the issue landing page.", + "description": "Asymmetric magazine cover anchoring the drop / landing page.", "type": "object", "properties": { "kicker": { "type": "string", "title": "Kicker", - "description": "Small-caps label above the headline.", + "description": "Mono label above the headline (e.g. \"Drop 04 / Spring\").", "x-editor": "text" }, "headline": { "type": "string", "title": "Headline", - "description": "Display headline (rich text — allows italics).", + "description": "Massive display headline (rich text — allows italics).", "x-editor": "richtext" }, "deck": { @@ -25,14 +25,21 @@ "image": { "type": "string", "title": "Cover Image", - "description": "Hero image for the cover story.", + "description": "Duotone-treated cover image.", "x-editor": "media" }, "link": { "type": "string", - "title": "Read Story Link", - "description": "URL to the full feature.", + "title": "CTA Link", + "description": "URL to the full feature or the shop.", "x-editor": "link" + }, + "linkLabel": { + "type": "string", + "title": "CTA Label", + "description": "Button label.", + "x-editor": "text", + "default": "Read the story" } } } diff --git a/blocks/credits_ticker.ninjatpl b/blocks/credits_ticker.ninjatpl new file mode 100644 index 0000000..456a625 --- /dev/null +++ b/blocks/credits_ticker.ninjatpl @@ -0,0 +1,10 @@ +{# credits_ticker (magazine-bold) — marquee strip of credits. Fields: items[].label. MID motion: CSS marquee, paused on hover, static under prefers-reduced-motion. The track renders the item list TWICE so the -50% translate loops seamlessly (engine can't repeat N times). #} +{% if items %} +
+
+
+ {% for item in items %}{{ item.label }}{% endfor %}{% for item in items %}{{ item.label }}{% endfor %} +
+
+
+{% endif %} diff --git a/blocks/credits_ticker.schema.json b/blocks/credits_ticker.schema.json new file mode 100644 index 0000000..f01e9be --- /dev/null +++ b/blocks/credits_ticker.schema.json @@ -0,0 +1,21 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Credits Ticker", + "description": "Marquee strip of credits scrolling across a fluoro accent band.", + "type": "object", + "properties": { + "items": { + "type": "array", + "title": "Credits", + "description": "The credit lines shown in the ticker.", + "default": [], + "x-editor": "array", + "items": { + "type": "object", + "properties": { + "label": { "type": "string", "title": "Credit", "description": "One credit (e.g. \"Photography — Kit Sato\").", "x-editor": "text" } + } + } + } + } +} diff --git a/blocks/drop_calendar.ninjatpl b/blocks/drop_calendar.ninjatpl new file mode 100644 index 0000000..838a6c4 --- /dev/null +++ b/blocks/drop_calendar.ninjatpl @@ -0,0 +1,23 @@ +{# drop_calendar (magazine-bold) — dated list of drops: mono datelines, condensed names, a status tag. Fields: heading, intro, drops[].date/.name/.detail/.status(live|soon|sold)/.link/.linkLabel. Static, no motion. #} +
+
+ {% if heading %}

{{ heading }}

{% endif %} + {% if intro %}

{{ intro }}

{% endif %} + {% if drops %} +
    + {% for drop in drops %} +
  • +
    {{ drop.date }}
    +
    +

    {% if drop.link %}{{ drop.name }}{% else %}{{ drop.name }}{% endif %}

    + {% if drop.detail %}

    {{ drop.detail }}

    {% endif %} +
    + {% if drop.status == "live" %}Live + {% elif drop.status == "sold" %}Sold out + {% elif drop.status == "soon" %}Coming soon{% endif %} +
  • + {% endfor %} +
+ {% else %}
No drops scheduled.
{% endif %} +
+
diff --git a/blocks/drop_calendar.schema.json b/blocks/drop_calendar.schema.json new file mode 100644 index 0000000..0c4aa45 --- /dev/null +++ b/blocks/drop_calendar.schema.json @@ -0,0 +1,45 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Drop Calendar", + "description": "Dated list of upcoming and past drops with a status tag.", + "type": "object", + "properties": { + "heading": { + "type": "string", + "title": "Heading", + "description": "Section headline.", + "x-editor": "text" + }, + "intro": { + "type": "string", + "title": "Intro", + "description": "Optional standfirst line.", + "x-editor": "textarea" + }, + "drops": { + "type": "array", + "title": "Drops", + "description": "The scheduled drops.", + "default": [], + "x-editor": "array", + "items": { + "type": "object", + "properties": { + "date": { "type": "string", "title": "Date", "description": "Dateline (e.g. \"14 MAR\").", "x-editor": "text" }, + "name": { "type": "string", "title": "Name", "description": "Drop name.", "x-editor": "text" }, + "detail": { "type": "string", "title": "Detail", "description": "Optional one-line detail.", "x-editor": "text" }, + "status": { + "type": "string", + "title": "Status", + "description": "Availability tag.", + "enum": ["live", "soon", "sold"], + "default": "soon", + "x-editor": "select" + }, + "link": { "type": "string", "title": "Link", "description": "Optional link to the drop.", "x-editor": "link" }, + "linkLabel": { "type": "string", "title": "Link Label", "description": "Link label.", "x-editor": "text", "default": "View drop" } + } + } + } + } +} diff --git a/blocks/issue_archive.ninjatpl b/blocks/issue_archive.ninjatpl deleted file mode 100644 index 14f1bb4..0000000 --- a/blocks/issue_archive.ninjatpl +++ /dev/null @@ -1,4 +0,0 @@ - diff --git a/blocks/issue_archive.schema.json b/blocks/issue_archive.schema.json deleted file mode 100644 index 142cd3a..0000000 --- a/blocks/issue_archive.schema.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Issue Archive", - "description": "Numbered list of back-issues driven by a bucket query.", - "type": "object", - "properties": { - "bucketSlug": { - "type": "string", - "title": "Issues Bucket", - "description": "Bucket that holds the issue rows.", - "x-editor": "bucket-picker" - }, - "limit": { - "type": "integer", - "title": "Limit", - "description": "How many issues to render.", - "x-editor": "number", - "minimum": 1, - "maximum": 50, - "default": 12 - } - } -} diff --git a/blocks/lookbook.ninjatpl b/blocks/lookbook.ninjatpl new file mode 100644 index 0000000..6da4b68 --- /dev/null +++ b/blocks/lookbook.ninjatpl @@ -0,0 +1,24 @@ +{# lookbook (magazine-bold) — grid of duotone look frames with mono numbering, captions and optional shop links. Fields: heading, intro, looks[].image/.caption/.credit/.link/.linkLabel. Image via {% img %} (media filter not registered in block render path). MID motion: duotone hover + mb-reveal, reduced-motion safe. #} +
+
+ {% if heading %}

{{ heading }}

{% endif %} + {% if intro %}

{{ intro }}

{% endif %} + {% if looks %} +
+ {% for look in looks %} +
+ {% if look.image %}
{% img look.image alt=look.caption|default:"" class="h-full w-full object-cover" %}
{% endif %} +
+ {{ forloop.Counter|stringformat:"%02d" }} + + {% if look.caption %}{{ look.caption }}{% endif %} + {% if look.credit %}{{ look.credit }}{% endif %} + +
+ {% if look.link %}{{ look.linkLabel|default:"Shop the look" }}{% endif %} +
+ {% endfor %} +
+ {% else %}
No looks yet.
{% endif %} +
+
diff --git a/blocks/lookbook.schema.json b/blocks/lookbook.schema.json new file mode 100644 index 0000000..5ed660e --- /dev/null +++ b/blocks/lookbook.schema.json @@ -0,0 +1,37 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Lookbook", + "description": "Grid of duotone look frames for a drop — numbered, captioned, optionally shoppable.", + "type": "object", + "properties": { + "heading": { + "type": "string", + "title": "Heading", + "description": "Section headline.", + "x-editor": "text" + }, + "intro": { + "type": "string", + "title": "Intro", + "description": "Optional standfirst line.", + "x-editor": "textarea" + }, + "looks": { + "type": "array", + "title": "Looks", + "description": "The lookbook frames.", + "default": [], + "x-editor": "array", + "items": { + "type": "object", + "properties": { + "image": { "type": "string", "title": "Image", "description": "Look photo.", "x-editor": "media" }, + "caption": { "type": "string", "title": "Caption", "description": "Look name / description.", "x-editor": "text" }, + "credit": { "type": "string", "title": "Credit", "description": "Optional credit line (model, photographer).", "x-editor": "text" }, + "link": { "type": "string", "title": "Shop Link", "description": "Optional link to the product.", "x-editor": "link" }, + "linkLabel": { "type": "string", "title": "Link Label", "description": "Shop link label.", "x-editor": "text", "default": "Shop the look" } + } + } + } + } +} diff --git a/blocks/masthead.ninjatpl b/blocks/masthead.ninjatpl deleted file mode 100644 index c50be68..0000000 --- a/blocks/masthead.ninjatpl +++ /dev/null @@ -1,7 +0,0 @@ -
-
- {% if issueNo %}No. {{ issueNo }}{% endif %} - {% if tagline %}{{ tagline }}{% endif %} -
- {% if date %}{{ date }}{% endif %} -
diff --git a/blocks/masthead.schema.json b/blocks/masthead.schema.json deleted file mode 100644 index 446ccb3..0000000 --- a/blocks/masthead.schema.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Issue Masthead", - "description": "Wordmark + folio bar across the top of the page.", - "type": "object", - "properties": { - "issueNo": { - "type": "string", - "title": "Issue Number", - "description": "Folio number (e.g. \"01\").", - "x-editor": "text" - }, - "tagline": { - "type": "string", - "title": "Tagline", - "description": "Issue volume / tagline line (e.g. \"Vol. 1 / Spring\").", - "x-editor": "text" - }, - "date": { - "type": "string", - "title": "Date", - "description": "Issue date or month.", - "x-editor": "text" - } - } -} diff --git a/blocks/photo_essay.ninjatpl b/blocks/photo_essay.ninjatpl deleted file mode 100644 index f84e0b9..0000000 --- a/blocks/photo_essay.ninjatpl +++ /dev/null @@ -1,6 +0,0 @@ -
-
- {% if title %}

{{ title }}

{% endif %} - {% if frames %}
{% for frame in frames %}{% if frame.image %}
{{ frame.caption }}
{{ forloop.Counter|stringformat:"%02d" }}{% if frame.caption %}{{ frame.caption }}{% endif %}
{% endif %}{% endfor %}
{% else %}
No frames yet.
{% endif %} -
-
diff --git a/blocks/photo_essay.schema.json b/blocks/photo_essay.schema.json deleted file mode 100644 index 8fd54f3..0000000 --- a/blocks/photo_essay.schema.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Photo Essay", - "description": "Numbered photo-essay scaffolding with asymmetric frame spans.", - "type": "object", - "properties": { - "title": { - "type": "string", - "title": "Essay Title", - "description": "Essay heading shown above the frames.", - "x-editor": "text" - }, - "frames": { - "type": "array", - "title": "Frames", - "description": "Ordered list of photo frames in the essay.", - "default": [], - "x-editor": "collection", - "items": { - "type": "object", - "properties": { - "image": { - "type": "string", - "title": "Image", - "description": "Frame image.", - "x-editor": "media" - }, - "caption": { - "type": "string", - "title": "Caption", - "description": "Mono-set caption.", - "x-editor": "text" - }, - "span": { - "type": "string", - "title": "Span", - "description": "Frame layout span.", - "enum": ["half", "full", "tall"], - "default": "half", - "x-editor": "select" - } - }, - "required": ["image"] - } - } - } -} diff --git a/blocks/pull_quote.ninjatpl b/blocks/pull_quote.ninjatpl deleted file mode 100644 index 6be9546..0000000 --- a/blocks/pull_quote.ninjatpl +++ /dev/null @@ -1,7 +0,0 @@ - diff --git a/blocks/pull_quote.schema.json b/blocks/pull_quote.schema.json deleted file mode 100644 index b104c6a..0000000 --- a/blocks/pull_quote.schema.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Pull Quote", - "description": "Color-block pull quote that sits in the gutter or main column.", - "type": "object", - "properties": { - "quote": { - "type": "string", - "title": "Quote", - "description": "The quote body (rich text).", - "x-editor": "richtext" - }, - "attribution": { - "type": "string", - "title": "Attribution", - "description": "Who said it.", - "x-editor": "text" - }, - "accent": { - "type": "string", - "title": "Accent", - "description": "Which rotating accent to apply.", - "enum": ["pink", "blue", "lime"], - "default": "pink", - "x-editor": "select" - } - } -} diff --git a/fonts.json b/fonts.json index fe51488..7b6f547 100644 --- a/fonts.json +++ b/fonts.json @@ -1 +1,26 @@ -[] +[ + { + "name": "Archivo Black", + "family": "Archivo Black", + "variants": [ + { "weight": "400", "style": "normal", "file": "fonts/archivo-black-400.woff2" } + ] + }, + { + "name": "Inter", + "family": "Inter", + "variants": [ + { "weight": "400", "style": "normal", "file": "fonts/inter-400.woff2" }, + { "weight": "500", "style": "normal", "file": "fonts/inter-500.woff2" }, + { "weight": "700", "style": "normal", "file": "fonts/inter-700.woff2" } + ] + }, + { + "name": "IBM Plex Mono", + "family": "IBM Plex Mono", + "variants": [ + { "weight": "400", "style": "normal", "file": "fonts/ibm-plex-mono-400.woff2" }, + { "weight": "500", "style": "normal", "file": "fonts/ibm-plex-mono-500.woff2" } + ] + } +] diff --git a/manifest.yaml b/manifest.yaml index eefb0f5..8b67dbd 100644 --- a/manifest.yaml +++ b/manifest.yaml @@ -1,5 +1,14 @@ -# Magazine Bold theme — codeless .bnp manifest (WO-WZ-021 Phase B1). +# Magazine Bold theme — codeless .bnp manifest (Theme Fleet Wave A, WO-TF-021). # Synthesized into manifest.pb by `ninja plugin build` (no plugin.wasm). +# +# Persona: a streetwear label / fashion-drop publication. Print-cover energy +# translated to drops and lookbooks — massive condensed uppercase headlines +# (Archivo Black), duotone imagery, grid-breaking overlap, mono datelines and +# fluoro accent slabs. Newsprint-plus-voltage by day, ink-black-plus-fluoro by +# night. Colour ALWAYS via hsl(var(--token)); font families ALWAYS via +# var(--font-heading|body|mono, ). No literal colours, no hardcoded +# families. Motion tier MID: JS effects (marquee, scroll reveal, duotone hover), +# no canvas, prefers-reduced-motion honored with static fallbacks. theme_presets: presets.json bundled_fonts: fonts.json master_pages: master_pages.json @@ -7,212 +16,441 @@ master_pages: master_pages.json system_templates: - key: magazine-bold title: Magazine Bold - description: "Editorial-print theme with oversized display type, asymmetric grids and rotating color-block accents for fashion, streetwear and culture publications." + description: "A streetwear label's cover-story chrome: massive condensed uppercase headlines, duotone imagery, grid-breaking overlap and fluoro accent slabs. Built for fashion drops, lookbooks and culture publications." page_templates: - system: magazine-bold key: default title: Default - description: "Standard editorial page with masthead, asymmetric main, footer colophon." - slots: [masthead, main, colophon] - - system: magazine-bold - key: landing - title: Issue Landing - description: "Cover-story hero plus issue navigation strip." - slots: [cover, secondary, main, colophon] - - system: magazine-bold - key: article - title: Article / Feature - description: "Long-form feature with deck, byline rail and pull-quote gutter." - slots: [masthead, deck, main, colophon] + description: "Standard page: masthead, single reading column, colophon footer." + slots: [header, main, footer] - system: magazine-bold key: full-width title: Full Bleed - description: "Edge-to-edge photo-essay layout." - slots: [masthead, main, colophon] + description: "Edge-to-edge for lookbooks, cover images and photo essays." + slots: [header, main, footer] + - system: magazine-bold + key: landing + title: Cover / Drop + description: "Cover-story stack: masthead, lead drop, river of stories, colophon." + slots: [masthead, lead, river, footer] + - system: magazine-bold + key: article + title: Feature + description: "Long-form feature: byline, drop-cap opener, reading column with a marginalia rail." + slots: [header, byline, main, marginalia, footer] + - system: magazine-bold + key: blog-index + title: Archive + description: "Drop archive: masthead, featured drop, and a river of stories." + slots: [header, featured, main, footer] + - system: magazine-bold + key: contact + title: Studio + description: "Contact layout with an optional aside for stockists, hours and location." + slots: [header, main, aside, footer] + - system: magazine-bold + key: auth + title: Members + description: "Centred members shell with wordmark and a fluoro rule." + slots: [main, footer] template_overrides: + # Chrome (4) + - { template: magazine-bold, block: navbar } + - { template: magazine-bold, block: footer } + - { template: magazine-bold, block: utility-bar } + - { template: magazine-bold, block: announcement-bar } + # Marketing (12) + - { template: magazine-bold, block: hero } + - { template: magazine-bold, block: feature-grid } + - { template: magazine-bold, block: rich-section } + - { template: magazine-bold, block: stats } + - { template: magazine-bold, block: testimonial } + - { template: magazine-bold, block: logo-strip } + - { template: magazine-bold, block: pricing } + - { template: magazine-bold, block: team } + - { template: magazine-bold, block: faq } + - { template: magazine-bold, block: timeline } + - { template: magazine-bold, block: cta } + - { template: magazine-bold, block: countdown } + # Primitives (11) - { template: magazine-bold, block: heading } - { template: magazine-bold, block: text } + - { template: magazine-bold, block: rich-text } - { template: magazine-bold, block: image } + - { template: magazine-bold, block: quote } + - { template: magazine-bold, block: divider } - { template: magazine-bold, block: button } + - { template: magazine-bold, block: card } + - { template: magazine-bold, block: gallery } + - { template: magazine-bold, block: video-embed } + - { template: magazine-bold, block: audio-embed } + # Commerce / local (6) + - { template: magazine-bold, block: menu-list } + - { template: magazine-bold, block: event-list } + - { template: magazine-bold, block: contact-form } + - { template: magazine-bold, block: contact-card } + - { template: magazine-bold, block: hours-location } + - { template: magazine-bold, block: social-links } + # Blog family (12) + - { template: magazine-bold, block: blog-hero } + - { template: magazine-bold, block: featured-posts } + - { template: magazine-bold, block: related-posts } + - { template: magazine-bold, block: popular-posts } + - { template: magazine-bold, block: category-list } + - { template: magazine-bold, block: post-hero } + - { template: magazine-bold, block: post-metadata } + - { template: magazine-bold, block: author-bio } + - { template: magazine-bold, block: author-bio-hero } + - { template: magazine-bold, block: newsletter-signup } + - { template: magazine-bold, block: breadcrumbs } + - { template: magazine-bold, block: sidebar-nav } + # Auth surface (3) + - { template: magazine-bold, block: auth-form } + - { template: magazine-bold, block: password-reset-form } + - { template: magazine-bold, block: auth-status } + # System (1) + - { template: magazine-bold, block: page-suggestions } email_wrappers: - magazine-bold css: input_css_append: | - /* =========================================================== - * Magazine Bold — theme utilities - * Appended to the Tailwind input via CSSManifest.InputCSSAppend. - * =========================================================== */ - - /* --- Font-slot classes (consume CMS-managed font variables) --- */ - .font-display { - font-family: var(--font-heading, "Playfair Display", "Cormorant Garamond", Georgia, "Times New Roman", serif); - font-feature-settings: "ss01", "ss02", "kern", "liga"; - font-optical-sizing: auto; - } - .font-serif-sub { - font-family: var(--font-heading, "Fraunces", "Playfair Display", Georgia, "Times New Roman", serif); - } - .font-sans { - font-family: var(--font-body, "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif); - } - .font-mono { - font-family: var(--font-mono, "JetBrains Mono", "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace); - } - - /* --- Display-size utilities ------------------------------- */ - /* text-folio: 220pt-equivalent oversized folio number / cover hit. - The clamp resolves to ~220px (~165pt) at 1440px, scaling down to 80px on mobile. */ - .text-folio { - font-family: var(--font-heading, "Playfair Display", Georgia, serif); - font-size: clamp(80px, 18vw, 240px); - line-height: 0.85; - letter-spacing: -0.04em; - font-weight: 200; - font-style: italic; - } - .text-deck { - font-family: var(--font-body, "Inter", system-ui, sans-serif); - font-size: clamp(28px, 3.2vw, 48px); - line-height: 1.15; - letter-spacing: -0.01em; - font-weight: 400; - } - .text-kicker { - font-family: var(--font-mono, "JetBrains Mono", monospace); - font-size: 12px; - text-transform: uppercase; - letter-spacing: 0.18em; - font-weight: 600; - } - - /* --- Hairline rule motif ---------------------------------- */ - .mb-hairline { border-color: hsl(var(--border)); border-width: 1px; } - .mb-hairline-top { border-top: 1px solid hsl(var(--border)); } - .mb-hairline-bottom { border-bottom: 1px solid hsl(var(--border)); } - - /* --- Asymmetric editorial grid ---------------------------- */ - .mb-grid-12 { - display: grid; - grid-template-columns: repeat(12, minmax(0, 1fr)); - gap: 1.5rem; - } - .mb-grid-asym-cover { - display: grid; - grid-template-columns: repeat(12, minmax(0, 1fr)); - gap: 2rem; - align-items: end; - } - .mb-col-folio { grid-column: 1 / span 2; } - .mb-col-headline { grid-column: 3 / span 7; } - .mb-col-deck { grid-column: 10 / span 3; } - .mb-col-image-wide { grid-column: 1 / -1; } - - @media (max-width: 1024px) { - .mb-grid-asym-cover { grid-template-columns: 1fr; gap: 1.25rem; } - .mb-col-folio, .mb-col-headline, .mb-col-deck { grid-column: 1 / -1; } - } - - /* --- Photo essay frame spans ------------------------------ */ - .mb-photo-grid { - display: grid; - grid-template-columns: repeat(12, minmax(0, 1fr)); - gap: 1.25rem; - } - .mb-frame-half { grid-column: span 6 / span 6; } - .mb-frame-full { grid-column: 1 / -1; } - .mb-frame-tall { grid-column: span 5 / span 5; grid-row: span 2; } - - @media (max-width: 768px) { - .mb-photo-grid { grid-template-columns: 1fr; } - .mb-frame-half, .mb-frame-full, .mb-frame-tall { grid-column: 1 / -1; grid-row: auto; } - } - - /* --- Color-block accent panels ---------------------------- */ - .colorblock-accent { - background-color: hsl(var(--accent)); - color: hsl(var(--accent-foreground)); - } - - /* --- Per-accent pull-quote variants ----------------------- * - * These let a pull_quote override the preset accent on a per-instance basis. - * They use literal HSL triples that match the spec §4 accent values so a - * "pink" pull-quote always reads pink even on the ink-blue preset. + /* === Magazine Bold theme — utility layer === + * + * Streetwear / fashion-drop identity: massive condensed uppercase headlines, + * heavy 2px rules, hard square edges, mono datelines and fluoro accent slabs. + * Colour is ALWAYS consumed via hsl(var(--token)); font families ALWAYS via + * var(--font-heading|body|mono, ). No literal colours, no hardcoded + * families. */ - [data-mb-accent="pink"] { --mb-accent-h: 330; --mb-accent-s: 95%; --mb-accent-l: 58%; } - [data-mb-accent="blue"] { --mb-accent-h: 220; --mb-accent-s: 100%; --mb-accent-l: 56%; } - [data-mb-accent="lime"] { --mb-accent-h: 78; --mb-accent-s: 90%; --mb-accent-l: 50%; } - [data-mb-accent] .mb-quote-bar { - background-color: hsl(var(--mb-accent-h) var(--mb-accent-s) var(--mb-accent-l)); - } - [data-mb-accent] .mb-quote-text { color: hsl(var(--foreground)); } - /* --- Drop-cap (heading override) -------------------------- */ - .mb-dropcap::first-letter { - font-family: var(--font-heading, "Playfair Display", Georgia, serif); - font-style: italic; - font-weight: 200; - float: left; - font-size: 5.5em; - line-height: 0.85; - padding-right: 0.08em; - margin-top: 0.04em; - color: hsl(var(--foreground)); - } + @layer utilities { + :root { + --mb-measure: 68ch; + --mb-rule: 2px solid hsl(var(--foreground)); + } - /* --- Body text override (Inter + margin folios) ----------- */ - .mb-body { - font-family: var(--font-body, "Inter", system-ui, sans-serif); - font-size: clamp(16px, 1.05vw, 18px); - line-height: 1.55; - letter-spacing: 0.005em; - color: hsl(var(--foreground)); - } + /* --- Font slots (consume CMS-managed font variables) --- */ + .font-display { font-family: var(--font-heading, "Archivo Black", "Arial Black", sans-serif); } + .font-sans { font-family: var(--font-body, "Inter", system-ui, sans-serif); } + .font-mono { font-family: var(--font-mono, "IBM Plex Mono", ui-monospace, monospace); } - /* --- Squared ink button ----------------------------------- */ - .mb-button { - display: inline-flex; - align-items: center; - gap: 0.5rem; - background-color: hsl(var(--primary)); - color: hsl(var(--primary-foreground)); - font-family: var(--font-mono, "JetBrains Mono", monospace); - font-size: 12px; - letter-spacing: 0.14em; - text-transform: uppercase; - padding: 0.875rem 1.5rem; - border: 1px solid hsl(var(--primary)); - border-radius: 0; - transition: background-color 120ms ease, color 120ms ease, border-color 120ms ease; - } - .mb-button:hover { - background-color: hsl(var(--accent)); - color: hsl(var(--accent-foreground)); - border-color: hsl(var(--accent)); - } - .mb-button:focus-visible { - outline: 2px solid hsl(var(--ring)); - outline-offset: 2px; - } + /* --- Page widths / reading measures --- */ + .mb-column { + max-width: var(--mb-measure); + margin-left: auto; margin-right: auto; + padding-left: 1rem; padding-right: 1rem; + } + .mb-wide { + max-width: 80rem; + margin-left: auto; margin-right: auto; + padding-left: 1rem; padding-right: 1rem; + } + .mb-article-grid { + display: grid; + grid-template-columns: 1fr; + gap: 2rem; + max-width: 84rem; + margin-left: auto; margin-right: auto; + padding-left: 1rem; padding-right: 1rem; + } + @media (min-width: 1024px) { + .mb-article-grid { grid-template-columns: minmax(0, 68ch) 18rem; } + } - /* --- Caption (mono, used by image override + photo essay) -- */ - .mb-caption { - font-family: var(--font-mono, "JetBrains Mono", monospace); - font-size: 12px; - line-height: 1.45; - letter-spacing: 0.01em; - color: hsl(var(--muted-foreground)); - } + /* --- Prose reading column --- */ + .mb-prose { + max-width: var(--mb-measure); + margin-left: auto; margin-right: auto; + font-family: var(--font-body, "Inter", system-ui, sans-serif); + color: hsl(var(--foreground)); + line-height: 1.6; + } + .mb-prose p { margin: 0 0 1.15em 0; } + .mb-prose h1, .mb-prose h2, .mb-prose h3, .mb-prose h4 { + font-family: var(--font-heading, "Archivo Black", "Arial Black", sans-serif); + text-transform: uppercase; + letter-spacing: -0.01em; + line-height: 1.0; + color: hsl(var(--foreground)); + } + .mb-prose a { + color: hsl(var(--accent)); + text-decoration: underline; + text-decoration-thickness: 2px; + text-underline-offset: 2px; + } + .mb-prose blockquote { + font-weight: 500; + border-left: 4px solid hsl(var(--accent)); + padding-left: 1rem; margin-left: 0; + color: hsl(var(--foreground)); + } - /* --- Full-bleed image with numbered folio ----------------- */ - .mb-fullbleed { width: 100vw; max-width: 100vw; margin-left: 50%; transform: translateX(-50%); } - .mb-image-folio { - font-family: var(--font-mono, "JetBrains Mono", monospace); - font-size: 11px; - letter-spacing: 0.16em; - color: hsl(var(--muted-foreground)); - text-transform: uppercase; + /* --- Mono / tracked labels (kickers, eyebrows, meta) --- */ + .mb-caps { + font-family: var(--font-mono, "IBM Plex Mono", ui-monospace, monospace); + text-transform: uppercase; + letter-spacing: 0.14em; + font-weight: 500; + font-size: 0.72rem; + line-height: 1.35; + } + + /* --- Display heading (Archivo Black, massive condensed caps) --- */ + .mb-heading { + font-family: var(--font-heading, "Archivo Black", "Arial Black", sans-serif); + text-transform: uppercase; + letter-spacing: -0.02em; + line-height: 0.95; + color: hsl(var(--foreground)); + } + .mb-underline { position: relative; padding-bottom: 0.28em; } + .mb-underline::after { + content: ""; + position: absolute; left: 0; right: 0; bottom: 0; + height: 4px; + background: hsl(var(--accent)); + } + + /* --- Heavy rules (bars, not hairlines) --- */ + hr.mb-rule, .mb-rule, .mb-hairline { + border: 0; + border-top: var(--mb-rule); + margin: 0; + } + .mb-hairline-top { border-top: var(--mb-rule); } + .mb-hairline-bottom { border-bottom: var(--mb-rule); } + + /* --- Body text override --- */ + .mb-body { + font-family: var(--font-body, "Inter", system-ui, sans-serif); + color: hsl(var(--foreground)); + line-height: 1.6; + } + + /* --- Inline accent link --- */ + .mb-link { + color: hsl(var(--accent)); + text-decoration: underline; + text-decoration-thickness: 2px; + text-underline-offset: 3px; + transition: color 120ms ease, text-decoration-color 120ms ease; + } + .mb-link:hover, .mb-link:focus-visible { color: hsl(var(--foreground)); } + + /* --- Button — fluoro / ink slab, square, uppercase mono --- */ + .mb-button, .mb-btn { + display: inline-block; + padding: 0.7rem 1.4rem; + font-family: var(--font-mono, "IBM Plex Mono", ui-monospace, monospace); + font-size: 0.72rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.14em; + background-color: hsl(var(--foreground)); + color: hsl(var(--background)); + border: 2px solid hsl(var(--foreground)); + text-decoration: none; + cursor: pointer; + transition: background-color 120ms ease, color 120ms ease, border-color 120ms ease; + } + .mb-button:hover, .mb-button:focus, .mb-btn:hover, .mb-btn:focus { + background-color: hsl(var(--accent)); + color: hsl(var(--accent-foreground)); + border-color: hsl(var(--accent)); + } + .mb-button:focus-visible, .mb-btn:focus-visible { + outline: 2px solid hsl(var(--ring)); + outline-offset: 2px; + } + + /* --- Panel / card — heavy 2px frame, hard edges, no shadow --- */ + .mb-panel, .mb-card { + border: 2px solid hsl(var(--foreground)); + background-color: hsl(var(--card)); + transition: border-color 120ms ease, transform 120ms ease; + } + .mb-panel:hover, .mb-card:hover { border-color: hsl(var(--accent)); } + + /* --- Form field — heavy border, square, accent focus --- */ + .mb-input { + width: 100%; + border: 2px solid hsl(var(--input)); + background-color: hsl(var(--background)); + color: hsl(var(--foreground)); + padding: 0.6rem 0.8rem; + font-family: var(--font-body, "Inter", system-ui, sans-serif); + font-size: 0.95rem; + border-radius: 0; + } + .mb-input::placeholder { color: hsl(var(--muted-foreground)); } + .mb-input:focus { + outline: none; + border-color: hsl(var(--accent)); + box-shadow: 0 0 0 2px hsl(var(--accent)); + } + + /* --- Figure / caption --- */ + .mb-figure { margin: 1.5rem 0; } + .mb-figure img { display: block; width: 100%; height: auto; } + .mb-figure figcaption { + margin-top: 0.6rem; + font-family: var(--font-mono, "IBM Plex Mono", ui-monospace, monospace); + font-size: 0.72rem; + text-transform: uppercase; + letter-spacing: 0.08em; + color: hsl(var(--muted-foreground)); + } + .mb-caption { + font-family: var(--font-mono, "IBM Plex Mono", ui-monospace, monospace); + font-size: 0.72rem; + line-height: 1.4; + letter-spacing: 0.04em; + color: hsl(var(--muted-foreground)); + } + + /* --- Drop cap — oversized Archivo Black accent first letter --- */ + .mb-dropcap p:first-of-type::first-letter { + font-family: var(--font-heading, "Archivo Black", "Arial Black", sans-serif); + color: hsl(var(--accent)); + float: left; + font-size: 4.6em; + line-height: 0.78; + padding-right: 0.1em; + padding-top: 0.02em; + margin-bottom: -0.08em; + } + + /* --- Marginalia rail (mono side notes, MID motion reveal) --- */ + .mb-marginalia { + font-family: var(--font-mono, "IBM Plex Mono", ui-monospace, monospace); + font-size: 0.75rem; + line-height: 1.5; + color: hsl(var(--muted-foreground)); + } + .mb-marginalia aside { + margin: 0 0 1.5rem 0; + padding-left: 0.75rem; + border-left: var(--mb-rule); + opacity: 0; + animation: mb-fade-in 500ms ease forwards; + } + @keyframes mb-fade-in { to { opacity: 1; } } + @media (max-width: 768px) { + .mb-marginalia { + margin-top: 1.5rem; + border-top: var(--mb-rule); + padding-top: 1rem; + } + } + + /* --- Masthead wordmark --- */ + .mb-masthead-wordmark { + font-family: var(--font-heading, "Archivo Black", "Arial Black", sans-serif); + text-transform: uppercase; + letter-spacing: -0.03em; + color: hsl(var(--foreground)); + } + + /* --- Display size utilities (folio / deck / kicker) --- */ + .text-folio { + font-family: var(--font-heading, "Archivo Black", "Arial Black", sans-serif); + font-size: clamp(72px, 16vw, 220px); + line-height: 0.82; + letter-spacing: -0.04em; + text-transform: uppercase; + } + .text-deck { + font-family: var(--font-body, "Inter", system-ui, sans-serif); + font-size: clamp(24px, 3vw, 44px); + line-height: 1.12; + letter-spacing: -0.01em; + font-weight: 500; + } + .text-kicker { + font-family: var(--font-mono, "IBM Plex Mono", ui-monospace, monospace); + font-size: 12px; + text-transform: uppercase; + letter-spacing: 0.18em; + font-weight: 600; + } + + /* --- Asymmetric editorial grids (persona blocks) --- */ + .mb-grid-12 { display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); gap: 1.5rem; } + .mb-photo-grid { display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); gap: 1.25rem; } + .mb-frame-half { grid-column: span 6 / span 6; } + .mb-frame-full { grid-column: 1 / -1; } + .mb-frame-tall { grid-column: span 5 / span 5; grid-row: span 2; } + @media (max-width: 768px) { + .mb-photo-grid { grid-template-columns: 1fr; } + .mb-frame-half, .mb-frame-full, .mb-frame-tall { grid-column: 1 / -1; grid-row: auto; } + } + + /* --- Color-block accent panel --- */ + .colorblock-accent { background-color: hsl(var(--accent)); color: hsl(var(--accent-foreground)); } + + /* --- Full-bleed image --- */ + .mb-fullbleed { width: 100vw; max-width: 100vw; margin-left: 50%; transform: translateX(-50%); } + + /* --- Duotone photo treatment (signature — persona blocks) --- */ + .mb-duotone { position: relative; } + .mb-duotone img { filter: grayscale(1) contrast(1.15); display: block; width: 100%; height: 100%; object-fit: cover; } + .mb-duotone::after { + content: ""; position: absolute; inset: 0; + background: hsl(var(--accent)); + mix-blend-mode: multiply; + opacity: 0.55; + transition: opacity 200ms ease; + pointer-events: none; + } + .mb-duotone:hover::after { opacity: 0.2; } + @media (prefers-reduced-motion: reduce) { + .mb-duotone:hover::after { opacity: 0.55; } + } + + /* --- Scroll reveal (MID motion; static fallback) --- */ + .mb-reveal { opacity: 0; transform: translateY(16px); transition: opacity 500ms ease, transform 500ms ease; } + .mb-reveal.mb-in { opacity: 1; transform: none; } + @media (prefers-reduced-motion: reduce) { + .mb-reveal { opacity: 1; transform: none; transition: none; } + } + .no-js .mb-reveal { opacity: 1; transform: none; } + + /* --- Marquee ticker (MID motion; paused on hover; static fallback) --- */ + .mb-marquee { overflow: hidden; white-space: nowrap; } + .mb-marquee-track { + display: inline-block; + will-change: transform; + animation: mb-marquee 22s linear infinite; + } + .mb-marquee:hover .mb-marquee-track { animation-play-state: paused; } + @keyframes mb-marquee { from { transform: translateX(0); } to { transform: translateX(-50%); } } + @media (prefers-reduced-motion: reduce) { + .mb-marquee-track { animation: none; } + } + + /* --- Reading-progress bar (fluoro; scroll-timeline; reduced-motion guard) --- */ + .mb-progress { + position: sticky; top: 0; + height: 4px; + background: hsl(var(--accent)); + transform-origin: 0 50%; + animation: mb-progress linear both; + animation-timeline: scroll(root block); + z-index: 50; + } + @keyframes mb-progress { from { transform: scaleX(0); } to { transform: scaleX(1); } } + @media (prefers-reduced-motion: reduce) { + .mb-progress { animation: none; transform: scaleX(0); } + } + + /* --- Per-instance pull-quote accent override (persona quote) --- */ + [data-mb-accent="pink"] { --mb-accent-h: 330; --mb-accent-s: 95%; --mb-accent-l: 58%; } + [data-mb-accent="blue"] { --mb-accent-h: 220; --mb-accent-s: 100%; --mb-accent-l: 56%; } + [data-mb-accent="lime"] { --mb-accent-h: 78; --mb-accent-s: 90%; --mb-accent-l: 50%; } + [data-mb-accent] .mb-quote-bar { background-color: hsl(var(--mb-accent-h) var(--mb-accent-s) var(--mb-accent-l)); } } diff --git a/master_pages.json b/master_pages.json index a43d8cd..ce0c3f1 100644 --- a/master_pages.json +++ b/master_pages.json @@ -1,25 +1,37 @@ [ { "key": "magazine-bold:default-master", - "title": "Magazine Bold — Default", - "page_templates": ["default", "article"], + "title": "Magazine Bold Default Master", + "page_templates": ["default", "full-width", "blog-index", "contact"], "blocks": [ - { "block_key": "navbar", "title": "Masthead Nav", "content": { "menuName": "main" }, "slot": "masthead", "sort_order": 0 }, - { "block_key": "magazine-bold:masthead", "title": "Issue Masthead", "content": { "issueNo": "01", "tagline": "Vol. 1 / Spring" }, "slot": "masthead", "sort_order": 1 }, - { "block_key": "slot", "title": "Main Slot", "content": { "slotName": "main" }, "slot": "main", "sort_order": 0 }, - { "block_key": "magazine-bold:colophon", "title": "Colophon", "content": { "showSocial": "true" }, "slot": "colophon", "sort_order": 0 } + { "block_key": "navbar", "title": "Masthead Navigation", "content": { "menuName": "main", "logoType": "text", "logoText": { "text": "OFFCUT" } }, "slot": "header", "sort_order": 0 }, + { "block_key": "slot", "title": "Featured", "content": { "slotName": "featured" }, "slot": "featured", "sort_order": 0 }, + { "block_key": "slot", "title": "Main Content", "content": { "slotName": "main", "placeholder": "Page content" }, "slot": "main", "sort_order": 0 }, + { "block_key": "slot", "title": "Aside", "content": { "slotName": "aside" }, "slot": "aside", "sort_order": 0 }, + { "block_key": "footer", "title": "Colophon", "content": { "layout": "columns", "companyName": "OFFCUT", "tagline": "A streetwear label and its cover stories.", "showNewsletter": true, "newsletterTitle": "Drop alerts", "newsletterDescription": "Get the next drop before it sells.", "copyright": "OFFCUT" }, "slot": "footer", "sort_order": 0 } ] }, { "key": "magazine-bold:landing-master", - "title": "Magazine Bold — Issue Landing", - "page_templates": ["landing", "full-width"], + "title": "Magazine Bold Cover Master", + "page_templates": ["landing"], "blocks": [ - { "block_key": "navbar", "title": "Masthead Nav", "content": { "menuName": "main" }, "slot": "masthead", "sort_order": 0 }, - { "block_key": "magazine-bold:cover_story", "title": "Cover Story", "content": { "issueNo": "01" }, "slot": "cover", "sort_order": 0 }, - { "block_key": "magazine-bold:issue_archive", "title": "Issue Archive", "content": { "bucketSlug": "issues" }, "slot": "secondary", "sort_order": 0 }, - { "block_key": "slot", "title": "Main Slot", "content": { "slotName": "main" }, "slot": "main", "sort_order": 0 }, - { "block_key": "magazine-bold:colophon", "title": "Colophon", "content": { "showSocial": "true" }, "slot": "colophon", "sort_order": 0 } + { "block_key": "navbar", "title": "Masthead Navigation", "content": { "menuName": "main", "logoType": "text", "logoText": { "text": "OFFCUT" } }, "slot": "masthead", "sort_order": 0 }, + { "block_key": "slot", "title": "Lead", "content": { "slotName": "lead", "placeholder": "Cover story" }, "slot": "lead", "sort_order": 0 }, + { "block_key": "slot", "title": "River", "content": { "slotName": "river", "placeholder": "Recent stories" }, "slot": "river", "sort_order": 0 }, + { "block_key": "footer", "title": "Colophon", "content": { "layout": "columns", "companyName": "OFFCUT", "tagline": "A streetwear label and its cover stories.", "showNewsletter": true, "newsletterTitle": "Drop alerts", "newsletterDescription": "Get the next drop before it sells.", "copyright": "OFFCUT" }, "slot": "footer", "sort_order": 0 } + ] + }, + { + "key": "magazine-bold:article-master", + "title": "Magazine Bold Feature Master", + "page_templates": ["article"], + "blocks": [ + { "block_key": "navbar", "title": "Masthead Navigation", "content": { "menuName": "main", "logoType": "text", "logoText": { "text": "OFFCUT" } }, "slot": "header", "sort_order": 0 }, + { "block_key": "slot", "title": "Byline", "content": { "slotName": "byline" }, "slot": "byline", "sort_order": 0 }, + { "block_key": "slot", "title": "Feature Body", "content": { "slotName": "main", "placeholder": "Feature body" }, "slot": "main", "sort_order": 0 }, + { "block_key": "slot", "title": "Marginalia Rail", "content": { "slotName": "marginalia" }, "slot": "marginalia", "sort_order": 0 }, + { "block_key": "footer", "title": "Colophon", "content": { "layout": "columns", "companyName": "OFFCUT", "tagline": "A streetwear label and its cover stories.", "copyright": "OFFCUT" }, "slot": "footer", "sort_order": 0 } ] } ] diff --git a/plugin.mod b/plugin.mod index e3ffb2f..bb2375d 100644 --- a/plugin.mod +++ b/plugin.mod @@ -3,7 +3,8 @@ name = "magazine-bold" display_name = "Magazine Bold" scope = "@themes" version = "0.2.0" -description = "Editorial-print theme with oversized display type, asymmetric grids and rotating color-block accents for fashion, streetwear and culture publications." +description = "A streetwear label's cover-story chrome: massive condensed uppercase headlines, duotone imagery, grid-breaking overlap and fluoro accent slabs. Newsprint plus voltage by day, ink-black plus fluoro by night. For fashion drops, lookbooks and culture publications." kind = "theme" categories = ["templates"] -tags = ["fashion", "lifestyle", "streetwear", "magazine", "bold", "display", "music", "editorial", "art"] +tags = ["fashion", "lifestyle", "streetwear", "magazine", "bold", "display", "editorial", "drops", "lookbook"] +required_icon_packs = ["lucide", "simple-icons"] diff --git a/presets.json b/presets.json index 52e8cef..b6f53ee 100644 --- a/presets.json +++ b/presets.json @@ -1,8 +1,8 @@ [ { - "id": "paper-pink", - "name": "Paper & Hot Pink", - "description": "Warm paper-tone background with a hot-pink editorial accent (default).", + "id": "newsprint-voltage", + "name": "Newsprint & Voltage", + "description": "The default. Off-white newsprint ground and near-black ink by day, ink-black with a fluoro hot-magenta accent by night.", "theme": { "mode": "both", "lightColors": { @@ -18,41 +18,41 @@ "secondaryForeground": "0 0% 8%", "muted": "38 20% 92%", "mutedForeground": "0 0% 40%", - "accent": "330 95% 58%", + "accent": "330 95% 55%", "accentForeground": "0 0% 100%", "destructive": "0 84% 55%", "destructiveForeground": "0 0% 100%", - "border": "0 0% 88%", - "input": "0 0% 88%", - "ring": "330 95% 58%" + "border": "0 0% 86%", + "input": "0 0% 86%", + "ring": "330 95% 55%" }, "darkColors": { - "background": "0 0% 8%", + "background": "0 0% 7%", "foreground": "38 30% 96%", "card": "0 0% 11%", "cardForeground": "38 30% 96%", "popover": "0 0% 11%", "popoverForeground": "38 30% 96%", "primary": "38 30% 96%", - "primaryForeground": "0 0% 8%", + "primaryForeground": "0 0% 7%", "secondary": "0 0% 14%", "secondaryForeground": "38 30% 96%", "muted": "0 0% 14%", - "mutedForeground": "38 10% 60%", - "accent": "330 95% 62%", - "accentForeground": "0 0% 8%", + "mutedForeground": "38 10% 62%", + "accent": "330 100% 64%", + "accentForeground": "0 0% 7%", "destructive": "0 84% 60%", "destructiveForeground": "0 0% 100%", - "border": "0 0% 18%", - "input": "0 0% 18%", - "ring": "330 95% 62%" + "border": "0 0% 20%", + "input": "0 0% 20%", + "ring": "330 100% 64%" } } }, { "id": "ink-blue", "name": "Ink & Electric Blue", - "description": "Same paper/ink neutrals with an electric-blue accent.", + "description": "The same newsprint/ink neutrals with an electric-blue accent — cooler, sportier drop energy.", "theme": { "mode": "both", "lightColors": { @@ -68,41 +68,41 @@ "secondaryForeground": "0 0% 8%", "muted": "38 20% 92%", "mutedForeground": "0 0% 40%", - "accent": "220 100% 56%", + "accent": "220 100% 54%", "accentForeground": "0 0% 100%", "destructive": "0 84% 55%", "destructiveForeground": "0 0% 100%", - "border": "0 0% 88%", - "input": "0 0% 88%", - "ring": "220 100% 56%" + "border": "0 0% 86%", + "input": "0 0% 86%", + "ring": "220 100% 54%" }, "darkColors": { - "background": "0 0% 8%", + "background": "0 0% 7%", "foreground": "38 30% 96%", "card": "0 0% 11%", "cardForeground": "38 30% 96%", "popover": "0 0% 11%", "popoverForeground": "38 30% 96%", "primary": "38 30% 96%", - "primaryForeground": "0 0% 8%", + "primaryForeground": "0 0% 7%", "secondary": "0 0% 14%", "secondaryForeground": "38 30% 96%", "muted": "0 0% 14%", - "mutedForeground": "38 10% 60%", - "accent": "220 100% 62%", - "accentForeground": "0 0% 8%", + "mutedForeground": "38 10% 62%", + "accent": "216 100% 66%", + "accentForeground": "0 0% 7%", "destructive": "0 84% 60%", "destructiveForeground": "0 0% 100%", - "border": "0 0% 18%", - "input": "0 0% 18%", - "ring": "220 100% 62%" + "border": "0 0% 20%", + "input": "0 0% 20%", + "ring": "216 100% 66%" } } }, { "id": "chalk-lime", "name": "Chalk & Lime", - "description": "Cooler chalk background with a lime accent — fresh, contemporary edge.", + "description": "Cooler chalk ground and an acid-lime accent — a fresh, contemporary edge. Deeper ink-black night desk.", "theme": { "mode": "both", "lightColors": { @@ -118,13 +118,13 @@ "secondaryForeground": "0 0% 8%", "muted": "60 12% 93%", "mutedForeground": "0 0% 40%", - "accent": "78 90% 50%", + "accent": "78 90% 45%", "accentForeground": "0 0% 8%", "destructive": "0 84% 55%", "destructiveForeground": "0 0% 100%", - "border": "0 0% 88%", - "input": "0 0% 88%", - "ring": "78 90% 50%" + "border": "0 0% 86%", + "input": "0 0% 86%", + "ring": "78 90% 45%" }, "darkColors": { "background": "0 0% 6%", @@ -138,14 +138,114 @@ "secondary": "0 0% 13%", "secondaryForeground": "60 15% 95%", "muted": "0 0% 13%", - "mutedForeground": "60 8% 60%", - "accent": "78 90% 55%", + "mutedForeground": "60 8% 62%", + "accent": "76 92% 56%", "accentForeground": "0 0% 8%", "destructive": "0 84% 60%", "destructiveForeground": "0 0% 100%", - "border": "0 0% 17%", - "input": "0 0% 17%", - "ring": "78 90% 55%" + "border": "0 0% 18%", + "input": "0 0% 18%", + "ring": "76 92% 56%" + } + } + }, + { + "id": "flux-green", + "name": "Flux Green", + "description": "Newsprint ground with a fluorescent green accent — rave-flyer energy. Ink-black night with a glowing lime-green pop.", + "theme": { + "mode": "both", + "lightColors": { + "background": "38 28% 95%", + "foreground": "0 0% 8%", + "card": "0 0% 100%", + "cardForeground": "0 0% 8%", + "popover": "0 0% 100%", + "popoverForeground": "0 0% 8%", + "primary": "0 0% 8%", + "primaryForeground": "38 28% 95%", + "secondary": "38 18% 90%", + "secondaryForeground": "0 0% 8%", + "muted": "38 18% 92%", + "mutedForeground": "0 0% 40%", + "accent": "150 82% 38%", + "accentForeground": "0 0% 100%", + "destructive": "0 84% 55%", + "destructiveForeground": "0 0% 100%", + "border": "0 0% 86%", + "input": "0 0% 86%", + "ring": "150 82% 38%" + }, + "darkColors": { + "background": "0 0% 7%", + "foreground": "38 28% 95%", + "card": "0 0% 11%", + "cardForeground": "38 28% 95%", + "popover": "0 0% 11%", + "popoverForeground": "38 28% 95%", + "primary": "38 28% 95%", + "primaryForeground": "0 0% 7%", + "secondary": "0 0% 14%", + "secondaryForeground": "38 28% 95%", + "muted": "0 0% 14%", + "mutedForeground": "38 10% 62%", + "accent": "146 90% 52%", + "accentForeground": "0 0% 7%", + "destructive": "0 84% 60%", + "destructiveForeground": "0 0% 100%", + "border": "0 0% 20%", + "input": "0 0% 20%", + "ring": "146 90% 52%" + } + } + }, + { + "id": "signal-orange", + "name": "Signal Orange", + "description": "Warm newsprint and a hazard-orange accent — hi-vis workwear energy. Ink-black night with a glowing tangerine pop.", + "theme": { + "mode": "both", + "lightColors": { + "background": "38 32% 95%", + "foreground": "0 0% 8%", + "card": "0 0% 100%", + "cardForeground": "0 0% 8%", + "popover": "0 0% 100%", + "popoverForeground": "0 0% 8%", + "primary": "0 0% 8%", + "primaryForeground": "38 32% 95%", + "secondary": "38 20% 90%", + "secondaryForeground": "0 0% 8%", + "muted": "38 20% 92%", + "mutedForeground": "0 0% 40%", + "accent": "18 100% 50%", + "accentForeground": "0 0% 100%", + "destructive": "0 84% 55%", + "destructiveForeground": "0 0% 100%", + "border": "0 0% 86%", + "input": "0 0% 86%", + "ring": "18 100% 50%" + }, + "darkColors": { + "background": "0 0% 7%", + "foreground": "38 32% 95%", + "card": "0 0% 11%", + "cardForeground": "38 32% 95%", + "popover": "0 0% 11%", + "popoverForeground": "38 32% 95%", + "primary": "38 32% 95%", + "primaryForeground": "0 0% 7%", + "secondary": "0 0% 14%", + "secondaryForeground": "38 32% 95%", + "muted": "0 0% 14%", + "mutedForeground": "38 10% 62%", + "accent": "22 100% 58%", + "accentForeground": "0 0% 7%", + "destructive": "0 84% 60%", + "destructiveForeground": "0 0% 100%", + "border": "0 0% 20%", + "input": "0 0% 20%", + "ring": "22 100% 58%" } } } diff --git a/seed/seed.json b/seed/seed.json new file mode 100644 index 0000000..83de4ec --- /dev/null +++ b/seed/seed.json @@ -0,0 +1,229 @@ +{ + "demo": true, + "settings": { + "merge": { + "companyName": "OFFCUT", + "tagline": "A streetwear label and its cover stories." + } + }, + "data_tables": [ + { + "key": "contact_submissions", + "name": "Studio Inbox", + "description": "Seeded contact-form submissions for the OFFCUT demo. A submission becomes a row here and emails the site admins.", + "columns": [ + { "key": "name", "label": "Name", "type": "text", "required": true }, + { "key": "email", "label": "Email", "type": "email", "required": true }, + { "key": "subject", "label": "Subject", "type": "text", "required": false }, + { "key": "message", "label": "Message", "type": "text", "required": true } + ] + } + ], + "pages": [ + { + "slug": "/", + "title": "OFFCUT", + "template_key": "landing", + "blocks": [ + { + "block_key": "magazine-bold:cover_story", + "title": "Cover", + "slot": "lead", + "sort_order": 1, + "content": { + "kicker": "Drop 04 / Spring", + "headline": "Cut from the offcuts", + "deck": "One drop. Made from last season's leftover cloth. Nothing reordered.", + "link": "/drops", + "linkLabel": "See the drop" + } + }, + { + "block_key": "magazine-bold:credits_ticker", + "title": "Credits", + "slot": "lead", + "sort_order": 2, + "content": { "items": [ { "label": "Photography Kit Sato" }, { "label": "Styling Remi Vaughn" }, { "label": "Cast Ari, Dune, Lux" }, { "label": "Set The Old Print Works" } ] } + }, + { + "block_key": "heading", + "title": "River heading", + "slot": "river", + "sort_order": 1, + "content": { "text": "From the studio", "level": 2 } + }, + { + "block_key": "card", + "title": "Story: Offcut method", + "slot": "river", + "sort_order": 2, + "content": { "layout": "vertical", "title": "The offcut method", "text": "

How we build a full drop from cloth other people threw away.

", "link": "/blog/the-offcut-method", "linkLabel": "Read the story" } + }, + { + "block_key": "card", + "title": "Story: Casting the block", + "slot": "river", + "sort_order": 3, + "content": { "layout": "vertical", "title": "Casting off the block", "text": "

We cast the drop on the street it was cut on. Here is who showed up.

", "link": "/blog/casting-off-the-block", "linkLabel": "Read the story" } + }, + { + "block_key": "card", + "title": "Story: One colour", + "slot": "river", + "sort_order": 4, + "content": { "layout": "vertical", "title": "One colour, all season", "text": "

Why the whole drop runs a single fluoro accent, and nothing else.

", "link": "/blog/one-colour-all-season", "linkLabel": "Read the story" } + }, + { + "block_key": "magazine-bold:drop_calendar", + "title": "Drops", + "slot": "river", + "sort_order": 5, + "content": { + "heading": "Drop calendar", + "intro": "Every drop is one run. When it is gone it is gone.", + "drops": [ + { "date": "14 MAR", "name": "Offcut 04", "detail": "Spring drop. Reworked shell jackets and cargo sets.", "status": "live" }, + { "date": "02 MAY", "name": "Offcut 05", "detail": "Workwear capsule. Signed up members first.", "status": "soon" }, + { "date": "09 FEB", "name": "Offcut 03", "detail": "Winter drop. Fleece and heavy cotton.", "status": "sold" } + ] + } + }, + { + "block_key": "newsletter-signup", + "title": "Drop alerts", + "slot": "river", + "sort_order": 6, + "content": { "title": "Drop alerts", "description": "One email per drop. We tell you when, and nothing else.", "listSlug": "main", "source": "home", "submitText": "Get alerts", "successMessage": "You are on the list. Watch your inbox before the next drop.", "placeholder": "you@example.com", "layout": "card" } + } + ] + }, + { + "slug": "/about", + "title": "About", + "template_key": "default", + "blocks": [ + { "block_key": "heading", "title": "Heading", "slot": "main", "sort_order": 1, "content": { "text": "About OFFCUT", "level": 1 } }, + { "block_key": "rich-text", "title": "Bio", "slot": "main", "sort_order": 2, "content": { "html": "

OFFCUT is a streetwear label built on leftover cloth. We buy end-of-run rolls that mills cannot sell and cut them into short, numbered drops.

Every drop is one run. We do not reorder. When a size is gone it stays gone. That keeps the work honest and keeps the waste down.

The studio is in an old print works. The photography, the lookbook and the label all come out of the same room.

" } }, + { + "block_key": "magazine-bold:lookbook", + "title": "Lookbook", + "slot": "main", + "sort_order": 3, + "content": { + "heading": "Drop 04 lookbook", + "intro": "Shot on the block, styled in one colour.", + "looks": [ + { "caption": "Shell jacket, offcut nylon", "credit": "On Ari" }, + { "caption": "Cargo set, reworked cotton", "credit": "On Dune" }, + { "caption": "Fleece half-zip", "credit": "On Lux" } + ] + } + }, + { "block_key": "quote", "title": "Quote", "slot": "main", "sort_order": 4, "content": { "style": "block", "align": "center", "quote": "We only make what we can cut from what is already there.", "attribution": "OFFCUT studio" } } + ] + }, + { + "slug": "/blog", + "title": "Stories", + "template_key": "blog-index", + "blocks": [ + { "block_key": "card", "title": "Featured: Offcut method", "slot": "featured", "sort_order": 1, "content": { "layout": "horizontal", "title": "The offcut method", "text": "

How we build a full drop from cloth other people threw away. The lead story this season.

", "link": "/blog/the-offcut-method", "linkLabel": "Read the story" } }, + { "block_key": "card", "title": "Post: Casting the block", "slot": "main", "sort_order": 1, "content": { "layout": "vertical", "title": "Casting off the block", "text": "

We cast the drop on the street it was cut on.

", "link": "/blog/casting-off-the-block", "linkLabel": "Read on" } }, + { "block_key": "card", "title": "Post: One colour", "slot": "main", "sort_order": 2, "content": { "layout": "vertical", "title": "One colour, all season", "text": "

Why the whole drop runs a single fluoro accent.

", "link": "/blog/one-colour-all-season", "linkLabel": "Read on" } }, + { "block_key": "card", "title": "Post: The offcut method again", "slot": "main", "sort_order": 3, "content": { "layout": "vertical", "title": "Numbering the run", "text": "

Every piece is numbered by hand. Here is why that matters.

", "link": "/blog/the-offcut-method", "linkLabel": "Read on" } } + ] + }, + { + "slug": "/blog/the-offcut-method", + "title": "The offcut method", + "template_key": "article", + "parent_slug": "/blog", + "blocks": [ + { "block_key": "text", "title": "Byline", "slot": "byline", "sort_order": 1, "content": { "text": "

By the OFFCUT studio. Drop 04.

" } }, + { "block_key": "text", "title": "Lead", "slot": "main", "sort_order": 1, "content": { "text": "

Most labels start with a colour card and a factory minimum. We start with a phone call about a roll of cloth that nobody wants. That is the whole method, and it shapes everything after it.

" } }, + { "block_key": "rich-text", "title": "Body", "slot": "main", "sort_order": 2, "content": { "html": "

Mills end every season with rolls they cannot sell. Wrong shade, short length, last year's spec. We buy those rolls cheap and we buy them whole. Then we cut a drop around what we actually have.

That means the run is short. A roll makes forty jackets, not four hundred. When it is cut, the pattern retires. The next drop uses a different cloth and looks like a different label, because it is a different cloth.

People ask us to reorder. We cannot, and we would not. The limit is the point.

" } }, + { "block_key": "quote", "title": "Gutter quote", "slot": "marginalia", "sort_order": 1, "content": { "style": "pull", "align": "left", "quote": "A roll makes forty jackets, not four hundred.", "attribution": "Drop 04" } } + ] + }, + { + "slug": "/blog/casting-off-the-block", + "title": "Casting off the block", + "template_key": "article", + "parent_slug": "/blog", + "blocks": [ + { "block_key": "text", "title": "Byline", "slot": "byline", "sort_order": 1, "content": { "text": "

By the OFFCUT studio. Drop 04.

" } }, + { "block_key": "text", "title": "Lead", "slot": "main", "sort_order": 1, "content": { "text": "

We do not book models through an agency. We cast the drop on the street it was cut on, and we shoot the people who show up. Here is how the casting went.

" } }, + { "block_key": "rich-text", "title": "Body", "slot": "main", "sort_order": 2, "content": { "html": "

We put up one poster outside the print works. It gave a date, a time, and nothing else. On the day, thirty people came by. Some knew the label. Most did not.

We shot everyone in the same set, in the same light, in one colour. No hair, no retouching past the basics. The clothes had to carry the frame, so we let them.

The cast in the lookbook are the people who came. That is the only rule we kept.

" } }, + { "block_key": "quote", "title": "Gutter quote", "slot": "marginalia", "sort_order": 1, "content": { "style": "pull", "align": "left", "quote": "We shoot the people who show up.", "attribution": "Casting" } } + ] + }, + { + "slug": "/blog/one-colour-all-season", + "title": "One colour, all season", + "template_key": "article", + "parent_slug": "/blog", + "blocks": [ + { "block_key": "text", "title": "Byline", "slot": "byline", "sort_order": 1, "content": { "text": "

By the OFFCUT studio. Drop 04.

" } }, + { "block_key": "text", "title": "Lead", "slot": "main", "sort_order": 1, "content": { "text": "

The whole drop runs one accent colour. It is on the labels, the site, the poster and the tape on the boxes. There is nothing else, and that is on purpose.

" } }, + { "block_key": "rich-text", "title": "Body", "slot": "main", "sort_order": 2, "content": { "html": "

A single colour makes a small label look sure of itself. It also makes the work easy to spot in a feed full of noise. One fluoro hit, black ink, newsprint paper, and you know it is us before you read a word.

Next drop the colour changes. The system stays. One accent, held all the way through, then swapped clean for the next run.

" } }, + { "block_key": "quote", "title": "Gutter quote", "slot": "marginalia", "sort_order": 1, "content": { "style": "pull", "align": "left", "quote": "One fluoro hit, black ink, newsprint paper.", "attribution": "House style" } } + ] + }, + { + "slug": "/contact", + "title": "Studio", + "template_key": "contact", + "blocks": [ + { + "block_key": "contact-form", + "title": "Write to the studio", + "slot": "main", + "sort_order": 1, + "content": { + "heading": "Write to the studio", + "description": "Stockist requests, press, or a question about a drop. We read everything.", + "submitLabel": "Send", + "successMessage": "Thank you. Your note reached the studio. We will write back.", + "fields": [ + { "name": "name", "label": "Your name", "type": "text", "required": true, "width": "half" }, + { "name": "email", "label": "Email", "type": "email", "required": true, "width": "half" }, + { "name": "subject", "label": "Subject", "type": "text", "required": false, "width": "full" }, + { "name": "message", "label": "Your note", "type": "textarea", "required": true, "width": "full" } + ], + "formConfig": { "enabled": true, "captchaEnabled": false, "targetTable": "contact_submissions" } + } + }, + { + "block_key": "contact-card", + "title": "Other ways to reach us", + "slot": "aside", + "sort_order": 1, + "content": { + "heading": "Elsewhere", + "description": "The studio inbox is best. These work too.", + "columns": 1, + "channels": [ + { "icon": "lucide:mail", "label": "Email", "value": "studio@offcut.example", "link": "mailto:studio@offcut.example" }, + { "icon": "lucide:map-pin", "label": "Studio", "value": "The Old Print Works", "link": "" } + ] + } + } + ] + }, + { + "slug": "/login", + "title": "Members", + "template_key": "auth", + "blocks": [ + { "block_key": "auth-form", "title": "Sign in", "slot": "main", "sort_order": 1, "content": { "default_tab": "login", "show_social": false } }, + { "block_key": "text", "title": "Foot", "slot": "footer", "sort_order": 1, "content": { "text": "

Members get the next drop before it goes public.

" } } + ] + } + ], + "menu_items": [ + { "menu": "main", "label": "Home", "page_slug": "/", "sort_order": 1 }, + { "menu": "main", "label": "Stories", "page_slug": "/blog", "sort_order": 2 }, + { "menu": "main", "label": "About", "page_slug": "/about", "sort_order": 3 }, + { "menu": "main", "label": "Studio", "page_slug": "/contact", "sort_order": 4 } + ] +} diff --git a/seed/workflows.json b/seed/workflows.json new file mode 100644 index 0000000..4328c35 --- /dev/null +++ b/seed/workflows.json @@ -0,0 +1,19 @@ +{ + "workflows": [ + { + "key": "contact_notify_admins", + "name": "Studio note to admins", + "description": "On a contact-form submission, email the site admins.", + "enabled": true, + "trigger": { "type": "row_inserted", "table": "contact_submissions" }, + "steps": [ + { + "type": "email", + "name": "Email admins", + "template": "Form Submission Notification", + "recipients": { "mode": "admins", "scope": "all" } + } + ] + } + ] +} diff --git a/templates/email/magazine-bold.ninjatpl b/templates/email/magazine-bold.ninjatpl index 36858d1..fc6d741 100644 --- a/templates/email/magazine-bold.ninjatpl +++ b/templates/email/magazine-bold.ninjatpl @@ -5,44 +5,59 @@ + {{ site_name }} + body, table, td, p, a, li, blockquote { + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; + } + table, td { mso-table-lspace: 0pt; mso-table-rspace: 0pt; } + img { -ms-interpolation-mode: bicubic; border: 0; height: auto; line-height: 100%; outline: none; text-decoration: none; } + body { margin: 0 !important; padding: 0 !important; width: 100% !important; } + a[x-apple-data-detectors] { color: inherit !important; text-decoration: none !important; } + @media only screen and (max-width: 620px) { + .email-container { width: 100% !important; max-width: 100% !important; } + .content-padding { padding-left: 24px !important; padding-right: 24px !important; } + } + - - {% if preview_text %}
{{ preview_text }}
{% endif %} + + {% if preview_text %}
{{ preview_text }}
{% endif %} -
- - +