Compare commits

..
2 Commits
Author SHA1 Message Date
marvin 4d1362c257 Optimized content and fixed a few mobile issues. 2026-10-14 19:40:45 +02:00
marvin 6e60f1c117 New design. 2026-10-14 19:29:31 +02:00
66 changed files with 3332 additions and 2724 deletions
+2
View File
@@ -1 +1,3 @@
config.json config.json
.secrets.env
shifu.db
+764
View File
@@ -0,0 +1,764 @@
# AGENTS.md — Building and editing websites with Shifu
This file tells an AI agent how to create and maintain a website built with
[Shifu](https://github.com/emvi/shifu), a Git/file-based CMS that renders Go
`html/template` files from JSON content. Read it fully before generating or
changing anything. When the rules here conflict with habits from other
frameworks (React, Hugo, Next.js, Tailwind build pipelines, …), the rules here win.
The goal of every change: **a site that renders correctly AND stays fully editable
in Shifu's admin UI** (on-page editor at `/admin`). Code that renders but cannot be
edited in the admin is considered broken.
---
## 1. Mental model
| Concept | Where it lives | What it is |
|---|---|---|
| **Template** | `tpl/**/<name>.html` | Go `html/template` for one element type (section, component, layout part). |
| **Admin config** | `admin/tpl/**/<name>.json` | Makes the template editable: label, editable fields, child content areas. |
| **Page** | `content/pages/**/<file>.json` | JSON with `path` (routes per language) and a tree of elements. |
| **Reference** ("ref") | `content/refs/<name>.json` | A standalone, reusable element (header, footer, head, …) shared by many pages. |
| **Sass** | `assets/scss/` | Compiled by Shifu (`sass` CLI) into `static/`. |
| **JS/TS** | `assets/js/` | Bundled + minified by Shifu (embedded esbuild) into `static/`. |
| **Static files** | `static/` | Served as-is under `/static/`. Admin uploads go to `static/media/`. |
| **Config** | `config.json` (+ `.secrets.env`) | Server, Sass/JS build, admin UI, etc. |
Rendering: a request matches a page `path` → Shifu walks `content.content` (the
root list of elements) in order → each element renders `tpl/.../<tpl>.html` with
`.CMS`, `.Args`, `.Page`, `.Content` → elements render their own children via
`.CMS.Render`.
### The "one element type = matching files" rule
Every editable element type `<name>` consists of files that share the same base name:
```
tpl/sections/hero.html # required — markup
admin/tpl/sections/hero.json # required — makes it editable/addable in the admin
assets/scss/_hero.scss # optional — styles, scoped to .hero
assets/js/hero.js # optional — behaviour, bound via data-component="hero"
```
Names: lowercase `snake_case`, **globally unique across all subdirectories** (Shifu
identifies templates by file base name only, folders are just for humans).
---
## 2. Project structure
Use this layout for new sites (it matches `shifu init` and the admin UI's own paths):
```
.
├── config.json
├── .secrets.env # secrets, never commit (add to .gitignore)
├── .gitignore
├── admin/
│ └── tpl/
│ ├── layout/ # head.json, end.json, header.json, footer.json
│ ├── sections/ # hero.json, text.json, cards.json, …
│ └── components/ # card.json, button.json, nav_item.json, …
├── assets/
│ ├── scss/
│ │ ├── main.scss # Sass entrypoint (only file referenced in config)
│ │ ├── _variables.scss
│ │ ├── _base.scss
│ │ └── _hero.scss … # one partial per element type, FLAT (no subfolders)
│ └── js/
│ ├── main.js # JS entrypoint
│ └── hero.js … # one module per interactive element type, FLAT
├── content/
│ ├── pages/ # pages (admin creates pages here)
│ │ ├── home.json
│ │ └── not-found.json
│ └── refs/ # references (admin creates refs here)
│ ├── head.json
│ └── end.json
├── static/ # served under /static/
│ ├── media/ # admin media library (uploads)
│ ├── main.css # GENERATED by Shifu — never write by hand
│ └── main.js # GENERATED by Shifu — never write by hand
└── tpl/
├── layout/ # head.html, end.html, header.html, footer.html
├── sections/
├── components/
└── partials/ # non-editable helpers used via {{ template }}
```
If you are editing an **existing** site, keep its structure, naming and file
locations (check `config.json` for the real `sass`/`js` dirs, entrypoints and output
paths) instead of migrating it to this layout.
`shifu.db` (SQLite, created at runtime when the admin UI is enabled) stores admin
users, sessions and the list of references. Don't commit it.
---
## 3. config.json
Minimal configuration for local development:
```json
{
"dev": true,
"log_level": "info",
"server": {
"host": "localhost",
"port": 8080,
"hostname": "http://localhost:8080"
},
"content": {
"provider": "fs",
"not_found": {
"en": "/not-found",
"de": "/de/nicht-gefunden"
}
},
"sass": {
"dir": "assets/scss",
"entrypoint": "main.scss",
"out": "static/main.css",
"out_source_map": "static/main.css.map",
"watch": true
},
"js": {
"dir": "assets/js",
"entrypoint": "main.js",
"out": "static/main.js",
"source_map": true,
"watch": true
},
"ui": {
"path": "/admin",
"admin_password": "${ADMIN_PASSWORD}"
}
}
```
`.secrets.env` (one `KEY=value` per line, referenced as `${KEY}` in `config.json`):
```env
ADMIN_PASSWORD=admin
```
Notes:
* `dev: true` reloads templates and content on every request (local only). For
production set `dev: false`, set `server.host` to `""`, `server.hostname` to the
public origin (e.g. `https://example.com`, used for canonical links/sitemap), and
`secure_cookies: true`.
* Keys starting with `_` are treated as comments by convention (they are ignored).
* Optional sections (`api`, `remote`, `git`, `analytics`, `cors`) are documented in
the Shifu README — only add them when asked.
* Run with `shifu run .` (requires the `sass` CLI: `npm i -g sass`). JS needs no
Node.js, esbuild is built in.
---
## 4. Templates (`tpl/`)
Go `html/template` syntax plus Shifu functions and all
[Sprig](https://masterminds.github.io/sprig/) functions.
### 4.1 Variables available in every template
| Variable | Meaning |
|---|---|
| `.CMS` | CMS instance. `.CMS.Render`, `.CMS.LastUpdate` (cache-busting string). |
| `.Args` | `map[string]string` of route variables, e.g. `{{ index .Args "slug" }}`. |
| `.Page` | The page: `.Page.Language`, `.Page.Path` (map lang → path), `.Page.CanonicalLink`, `.Page.File`. |
| `.Content` | The element being rendered: `.Content.Position`, `.Content.Data`, `.Content.Copy`, `.Content.Content`. |
### 4.2 Shifu template functions
| Function | Use |
|---|---|
| `copy .Page .Content "key"` | Translated text for the page language; falls back to `en`. Returns `nil` if missing. |
| `get .Content "key"` | Language-independent value from `data`. Returns `nil` if missing. |
| `html <string>` | Output trusted HTML (needed for `html` type fields). |
| `htmlAttr <string>` | Output a trusted attribute. |
| `fmt "%s/%s" a b` | `fmt.Sprintf`. |
| `dict "k" v "k2" v2` | Build a map, e.g. to pass to partials. |
| `default a b c` | **Shifu's version**: returns the first non-nil argument (NOT Sprig's `default "fallback" .x`). |
| `int "12"`, `uint64`, `formatInt`, `formatFloat`, `formatDate`, `gtFloat`, `ltFloat`, `shuffle`, `year` | Helpers. |
| `markdown "static/x.md" .`, `markdownBlock "static/x.md" "block" .` | Render a Markdown file (path relative to project root). |
| `hostname`, `config` | `server.hostname`, full config. |
| `adminHead .Page`, `adminBody .Page`, `loggedIn .Page` | Admin UI hooks (see §4.4). |
### 4.3 Element template contract (REQUIRED for admin editing)
Every editable element template must:
1. Have **exactly one top-level HTML element** carrying
`data-shifu-element="{{ .Content.Position }}"`.
2. For each child content area `<area>` declared in its admin config, render the
children **and** place a slot directly after them:
```gotemplate
{{ .CMS.Render .Args .Page (fmt "%s.<area>" .Content.Position) (index .Content.Content "<area>") }}
<slot name="{{ .Content.Position }}/<area>"></slot>
```
The `<area>` string must be identical in all three places (position suffix,
`index` key, slot name) and in the admin config's `content` keys.
3. Read every editable value through `copy` (translatable text) or `get` (data), and
handle missing values — new elements added in the admin start **empty**.
Canonical section template — `tpl/sections/text.html`:
```gotemplate
{{- $headline := copy .Page .Content "headline" -}}
{{- $text := copy .Page .Content "text" -}}
{{- $background := get .Content "background" -}}
<section class="text{{ with $background }} text--{{ . }}{{ end }}" data-shifu-element="{{ .Content.Position }}">
<div class="text__inner">
{{ with $headline }}<h2 class="text__headline">{{ . }}</h2>{{ end }}
{{ with $text }}<div class="text__body">{{ html . }}</div>{{ end }}
</div>
</section>
```
Container with child elements — `tpl/sections/cards.html`:
```gotemplate
{{- $headline := copy .Page .Content "headline" -}}
<section class="cards" data-shifu-element="{{ .Content.Position }}">
{{ with $headline }}<h2 class="cards__headline">{{ . }}</h2>{{ end }}
<div class="cards__grid">
{{ .CMS.Render .Args .Page (fmt "%s.items" .Content.Position) (index .Content.Content "items") }}
<slot name="{{ .Content.Position }}/items"></slot>
</div>
</section>
```
Child element — `tpl/components/card.html`:
```gotemplate
{{- $title := copy .Page .Content "title" -}}
{{- $text := copy .Page .Content "text" -}}
{{- $href := copy .Page .Content "href" -}}
{{- $image := get .Content "image" -}}
<article class="card" data-shifu-element="{{ .Content.Position }}">
{{ with $image }}<img class="card__image" src="{{ . }}" alt="{{ copy $.Page $.Content "image_alt" }}" loading="lazy" />{{ end }}
{{ with $title }}<h3 class="card__title">{{ . }}</h3>{{ end }}
{{ with $text }}<div class="card__text">{{ html . }}</div>{{ end }}
{{ with $href }}<a class="card__link" href="{{ . }}">{{ default (copy $.Page $.Content "link_label") "Read more" }}</a>{{ end }}
</article>
```
Multiple content areas (e.g. two columns) — `tpl/sections/columns.html`:
```gotemplate
<section class="columns" data-shifu-element="{{ .Content.Position }}">
<div class="columns__col">
{{ .CMS.Render .Args .Page (fmt "%s.left" .Content.Position) (index .Content.Content "left") }}
<slot name="{{ .Content.Position }}/left"></slot>
</div>
<div class="columns__col">
{{ .CMS.Render .Args .Page (fmt "%s.right" .Content.Position) (index .Content.Content "right") }}
<slot name="{{ .Content.Position }}/right"></slot>
</div>
</section>
```
Pitfalls:
* Inside `with`/`range`, `.` changes — use `$.Page` / `$.Content`.
* `{{ html $x }}` with a missing value prints the literal text `<nil>`. Always wrap
it: `{{ with $x }}{{ html . }}{{ end }}`.
* Never hard-code visible text in element templates — put it in `copy` so it is
editable and translatable. (Fallback labels via `default` are fine.)
* Don't use Go's `{{ block }}`/`{{ define }}` inheritance for page layouts. Layout
in Shifu is composition: pages are lists of elements (`head`, `header`, sections,
`footer`, `end`).
* Template file base names must be unique across all of `tpl/`; `tpl/a/card.html`
and `tpl/b/card.html` would clash.
### 4.4 Layout templates (head / end)
Every page starts with a `head` element and ends with an `end` element (normally
used as references, see §6). They split the HTML document. The admin UI needs
`adminHead` inside `<head>` and `adminBody` before `</body>`. They output nothing for
regular visitors.
`tpl/layout/head.html`:
```gotemplate
{{- $title := copy .Page .Content "title" -}}
{{- $description := copy .Page .Content "meta_description" -}}
<!DOCTYPE html>
<html lang="{{ .Page.Language }}">
<head>
<base href="/" />
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{{ $title }}</title>
<meta name="description" content="{{ $description }}" />
<link rel="canonical" href="{{ .Page.CanonicalLink }}" />
{{- range $language, $path := .Page.Path }}
<link rel="alternate" hreflang="{{ $language }}" href="{{ hostname }}{{ $path }}" />
{{- end }}
<link rel="stylesheet" href="/static/main.css?v={{ .CMS.LastUpdate }}" />
<script defer src="/static/main.js?v={{ .CMS.LastUpdate }}"></script>
{{ adminHead .Page }}
</head>
<body>
```
`tpl/layout/end.html`:
```gotemplate
{{ adminBody .Page }}
</body>
</html>
```
The `/static/...` file names must match `sass.out` and `js.out` in `config.json`.
The head is a *layout* element and is not wrapped in `data-shifu-element`
(`<head>` content cannot be edited on-page; its fields are editable from the
admin's page tree). For per-page titles, override the head reference's copy on
each page (§6).
### 4.5 Partials (non-editable helpers)
Put reusable markup that is *not* an admin element in `tpl/partials/` and call it
by file name. Do **not** create an admin config for partials.
```gotemplate
{{/* tpl/partials/button.html */}}
<a class="button button--{{ default .Variant "primary" }}" href="{{ .Href }}">{{ .Label }}</a>
```
```gotemplate
{{ with copy .Page .Content "cta_label" }}
{{ template "button.html" (dict "Label" . "Href" (copy $.Page $.Content "cta_href") "Variant" (get $.Content "cta_variant")) }}
{{ end }}
```
---
## 5. Admin configuration (`admin/tpl/`)
One JSON file per element type, **same base name as the template**. Without it the
element renders but cannot be edited, and it cannot be added from the admin.
```json
{
"label": "Cards",
"layout": false,
"content": {
"items": {
"label": "Cards",
"tpl_filter": ["card"]
}
},
"copy": {
"headline": { "label": "Headline" }
},
"data": {
"background": {
"label": "Background",
"type": "select",
"options": {
"": "Default",
"light": "Light",
"dark": "Dark"
}
}
}
}
```
| Key | Meaning |
|---|---|
| `label` | Display name in the admin's element picker. |
| `layout` | `true` for structural elements (head, end, header, footer). Disables some actions; new elements are inserted *before* a trailing layout element. |
| `content` | Child content areas: key = area name used in the template, `label`, optional `tpl_filter` (list of template names allowed in that area; omit to allow all). |
| `copy` | Translatable fields (stored per language under `copy.<lang>`). |
| `data` | Language-independent fields (stored under `data`). |
Field `type` (for `copy` and `data`):
| Type | Editor | Template usage |
|---|---|---|
| *(omitted)* / `text` | single-line input | `{{ $x }}` |
| `html` | rich-text editor (Trix) | `{{ with $x }}{{ html . }}{{ end }}` |
| `img` | media library picker, stores a path like `/static/media/photo.jpg` | `<img src="{{ $x }}">` |
| `file` | media library picker, stores a path | `<a href="{{ $x }}">` |
| `boolean` | checkbox, stores `"on"` when checked, key removed when unchecked | `{{ if $x }}` |
| `select` | dropdown from `options` (`value: label`) | compare as string: `{{ if eq $x "dark" }}` |
| `color` | color picker, stores `#rrggbb` | `style="--accent: {{ $x }}"` |
Rules derived from how the admin saves (important):
* **When an editor saves an element, its `copy` and `data` are replaced by exactly
the configured fields.** Any key the template reads but the admin config does not
declare is deleted on the first save. ⇒ Declare *every* `copy`/`data` key the
template uses. Don't store arrays/objects in `data` — they cannot be edited and
are lost. Model repeatable things (cards, slides, nav items, FAQ entries, team
members) as **child elements** in a content area instead.
* All saved values are **strings**. Write data values as strings in JSON too
(`"columns": "3"`, not `3`), otherwise `select` comparisons fail. Use
`{{ int (get .Content "columns") }}` when you need a number.
* Copy is edited for the languages present in the page's `path`.
* Use `copy` for anything a translator would change (text, alt text, link URLs that
differ by language, button labels); use `data` for design choices (variant, color,
image, alignment, size).
* Choose `select` over free text for design options and keep option keys stable —
they are referenced by CSS modifier classes.
* Prefixing a `label` with `_` (e.g. `"_HTML Head"`) sorts it first in the picker;
the demo uses this for layout elements.
Layout configs:
```json
{ "label": "_HTML Head", "layout": true, "copy": { "title": { "label": "Title" }, "meta_description": { "label": "Meta Description" } } }
```
```json
{ "label": "_HTML End", "layout": true }
```
Admin config changes are picked up automatically for files directly inside
`admin/tpl/`; restart Shifu after adding files in its subdirectories.
---
## 6. Content (`content/`)
### 6.1 Pages — `content/pages/<name>.json`
A file with `path` and **no top-level `tpl`** is a page.
```json
{
"display_name": "Home",
"path": {
"en": "/",
"de": "/de"
},
"sitemap": { "priority": "1.0" },
"content": {
"content": [
{
"ref": "head",
"copy": {
"en": { "title": "Acme — Home", "meta_description": "Acme builds things." },
"de": { "title": "Acme — Start", "meta_description": "Acme baut Dinge." }
}
},
{ "ref": "header" },
{
"tpl": "hero",
"copy": {
"en": { "headline": "We build things", "text": "<div>Fast and reliable.</div>" },
"de": { "headline": "Wir bauen Dinge", "text": "<div>Schnell und zuverlässig.</div>" }
},
"data": { "image": "/static/media/hero.jpg", "align": "center" }
},
{
"tpl": "cards",
"copy": { "en": { "headline": "Services" }, "de": { "headline": "Leistungen" } },
"content": {
"items": [
{
"tpl": "card",
"copy": {
"en": { "title": "Design", "text": "<div>…</div>" },
"de": { "title": "Design", "text": "<div>…</div>" }
},
"content": {}
}
]
}
},
{ "ref": "footer" },
{ "ref": "end" }
]
}
}
```
Rules:
* The page root must use a **single** content area named `content`
(`"content": { "content": [ … ] }`). Elements are rendered in array order.
* Each element has either `tpl` (template name, lowercase, no extension) **or**
`ref` (reference name), plus optional `copy`, `data`, `content`, `display_name`.
* Every content area declared in an element's admin config should exist in the JSON
(use `[]` when empty), e.g. `"content": { "items": [] }`.
* `path` keys are language codes; the value is the exact route (no trailing slash,
must start with `/`). Every language in `path` should have `copy` for every
element; missing languages fall back to `en`.
* Dynamic routes: always give the variable a regular expression, e.g.
`"/blog/{slug:[a-z0-9-]+}"` or `"/p/{id:[0-9]+}"` (a bare `{slug}` does not match
in the current version). Each `{…}` matches exactly one path segment; read it with
`{{ index .Args "slug" }}`. Longer paths are matched first.
* 404: create a page whose path matches `content.not_found` in `config.json`
(default `/404`).
* Optional page keys: `disable_cache`, `header` (HTTP headers map), `handler` (Go
custom handler, only if the backend defines it), `analytics` (Pirsch tags / A/B
experiments — see the Shifu README).
* Rich-text (`html`) values are HTML strings. The Trix editor produces `<div>`,
`<br>`, `<strong>`, `<em>`, `<a>`, `<ul>/<ol>/<li>`, `<h1>`, `<blockquote>`,
`<pre>`. Stick to these tags so content stays editable.
* Image paths should point to `static/media/…` (the admin media library), e.g.
`/static/media/hero.jpg`, and the file must exist there.
* Media files go in `static/media/` only; don't put site assets (logos used by
templates, icons, fonts) there — use `static/img/`, `static/fonts/` instead.
### 6.2 References — `content/refs/<name>.json`
A content file with a **top-level `tpl`** is a reference (a standalone element).
Its name is the lowercase file name without `.json`.
```json
{
"tpl": "head",
"copy": {
"en": { "title": "Acme", "meta_description": "Acme builds things." },
"de": { "title": "Acme", "meta_description": "Acme baut Dinge." }
}
}
```
```json
{
"tpl": "header",
"data": { "logo": "/static/media/logo.svg" },
"copy": { "en": { "home_label": "Acme" }, "de": { "home_label": "Acme" } },
"content": {
"nav": [
{ "tpl": "nav_item", "copy": { "en": { "label": "About", "href": "/about" }, "de": { "label": "Über uns", "href": "/de/ueber-uns" } }, "content": {} }
]
}
}
```
* Use refs for anything shared across pages: `head`, `header`, `footer`, `end`,
newsletter box, CTA banners.
* Pages use them with `{ "ref": "<name>" }`. Non-empty `copy`/`data` on the page
element override the ref's values for that page only (typical: page title).
* Ref names must be unique across **all** files in `content/` — never give a page
file the same name as a ref (e.g. no `content/pages/header.json`).
* The admin's "add reference" picker lists refs from `shifu.db`, not from disk. Refs
created by hand still render and are editable where used, but to make them
insertable from the admin register them (with Shifu stopped, after it has run
once so `shifu.db` exists; needs the `sqlite3` CLI):
```sh
sqlite3 shifu.db "INSERT OR IGNORE INTO reference (name) VALUES ('head'), ('header'), ('footer'), ('end');"
```
---
## 7. Styles (`assets/scss/`)
Shifu compiles the Sass entrypoint itself (`sass --style=compressed`) on start and
on every save. **Never** pre-compile, never write or edit `static/main.css` by hand,
never add PostCSS/Tailwind/Vite/webpack or npm build scripts, never inline large
`<style>` blocks in templates.
`assets/scss/main.scss`:
```scss
@use "variables";
@use "base";
@use "layout";
// one partial per element type, same name as the template
@use "head";
@use "header";
@use "footer";
@use "hero";
@use "text";
@use "cards";
@use "card";
```
`assets/scss/_variables.scss`:
```scss
$color-text: #1d1d1f;
$color-accent: #0a64d6;
$max-width: 1120px;
$radius: 12px;
$breakpoint-md: 768px;
@mixin md {
@media (min-width: $breakpoint-md) { @content; }
}
```
`assets/scss/_card.scss`:
```scss
@use "variables" as *;
.card {
border-radius: $radius;
overflow: hidden;
&__image { display: block; width: 100%; height: auto; }
&__title { margin: 16px 0 8px; }
&__link { color: $color-accent; }
}
```
Conventions:
* Use the module system (`@use`/`@forward`), not deprecated `@import`.
* Scope styles to the element's root class (BEM: `.card`, `.card__title`,
`.card--dark`). Modifier names come from `select` option keys.
* Style rich-text output by element (`.text__body p, .text__body ul …`) since editors
can't add classes.
* Expose admin `color` fields as CSS custom properties
(`style="--accent: {{ $color }}"` + `color: var(--accent, #{$color-accent})`).
* Keep partials flat inside the Sass dir: Shifu only watches that directory itself
(not subfolders) for changes. Every subfolder is also a Sass load path, so names
must be unique.
* Fonts/images referenced from CSS go in `static/` and use absolute URLs
(`url("/static/fonts/inter.woff2")`).
* Requires the Dart Sass CLI (`sass`) on the machine running Shifu.
---
## 8. JavaScript (`assets/js/`)
Shifu bundles and minifies the JS entrypoint with esbuild on start and on save (ES
modules, TypeScript `.ts` also works). **Never** pre-bundle, never edit
`static/main.js`, never add a separate bundler. `import` between files in
`assets/js/` is fine. Avoid npm dependencies unless the user asks; if one is needed,
prefer copying a small ES-module library into `assets/js/` (e.g. `vendor_glide.js`).
Keep JS files flat in that directory: like Sass, only the directory itself is
watched for changes, not its subfolders.
Pattern: progressive enhancement, components bound by `data-component`, idempotent
initialisation that also handles elements the admin inserts or re-renders on the fly.
`assets/js/main.js`:
```js
import { initNav } from "./nav.js";
import { initSlider } from "./slider.js";
const components = {
nav: initNav,
slider: initSlider,
};
function init(root = document) {
root.querySelectorAll("[data-component]").forEach((el) => {
if (el.dataset.componentReady) {
return;
}
const fn = components[el.dataset.component];
if (fn) {
fn(el);
el.dataset.componentReady = "true";
}
});
}
document.addEventListener("DOMContentLoaded", () => {
init();
// the admin UI swaps element HTML in place after edits
new MutationObserver(() => init()).observe(document.body, { childList: true, subtree: true });
});
```
`assets/js/nav.js`:
```js
export function initNav(el) {
const toggle = el.querySelector(".header__toggle");
if (!toggle) {
return;
}
toggle.addEventListener("click", () => {
const open = el.classList.toggle("header--open");
toggle.setAttribute("aria-expanded", String(open));
});
}
```
Template side: `<header class="header" data-component="nav" data-shifu-element="{{ .Content.Position }}">`.
Rules:
* The site must be usable without JS; JS enhances.
* No inline `<script>` in element templates; no `onclick=` attributes.
* Pass configuration via `data-*` attributes rendered from `copy`/`data`.
* Don't rely on the DOM being static — the admin replaces element markup when
editors save. Never store state outside the element or bind listeners in a way
that breaks if an element is replaced.
* Load the bundle once, in `head.html`, with `defer`.
---
## 9. Workflows
### 9.1 Create a whole new site
1. Write `config.json`, `.secrets.env`, `.gitignore`
(`.secrets.env`, `shifu.db`, `static/main.css*`, `static/main.js*`).
2. Plan the element inventory from the design: layout parts (head, header, footer,
end), sections (hero, text, image, cards, columns, cta, faq, contact, …),
components (card, nav_item, faq_item, button…). Prefer a small set of flexible
sections with `select` options over many near-duplicates.
3. For each element type create: template, admin config, Sass partial (+ `@use` in
`main.scss`), and JS module (+ register in `main.js`) when interactive.
4. Create refs: `content/refs/head.json`, `header.json`, `footer.json`, `end.json`.
5. Create pages in `content/pages/`, each starting with `head` and ending with `end`,
with `copy` for every language in its `path`. Include a 404 page.
6. Put referenced images in `static/media/`.
7. Run `shifu run .`, open every page, check the log for errors.
### 9.2 Edit an existing site
1. Read `config.json`, list `tpl/`, `admin/tpl/`, `content/` and the Sass/JS
entrypoints first to learn the existing element inventory and conventions.
2. Reuse existing element types before creating new ones.
3. Edit content JSON surgically; keep keys you don't touch (`display_name`,
`analytics`, `header`, …). The admin writes compact single-line JSON — that's fine,
keep it valid; reformatting is allowed but not required.
4. Renaming a `copy`/`data` key or content area → update the template, the admin
config **and every page/ref JSON** that uses it (search all of `content/`).
5. Removing an element type → first remove all its uses from `content/`.
6. Adding a field → add it to template *and* admin config; existing content may lack
it, so templates must handle the missing value.
### 9.3 Add a new page
Create `content/pages/<slug>.json` (unique file name, not equal to any ref name),
copy the head/header/footer/end refs from an existing page, set `path` for all site
languages, override the head `title`/`meta_description`, and add nav entries in the
header ref if the page should be linked.
---
## 10. Verification checklist
Before finishing, confirm:
- [ ] Every element type has `tpl/**/<name>.html` and `admin/tpl/**/<name>.json` with the same unique base name (partials excepted).
- [ ] Every element template has one root element with `data-shifu-element="{{ .Content.Position }}"`.
- [ ] Every content area appears identically in the template (`fmt "%s.<area>"`, `index … "<area>"`, `<slot name=".../<area>">`), the admin config `content`, and the JSON.
- [ ] Every `copy`/`data` key read in a template is declared in the admin config with the right `type`.
- [ ] No arrays/objects in `data`; all `copy`/`data` values are strings.
- [ ] `html` values are wrapped in `{{ with }}`; no `<nil>` can appear.
- [ ] `head.html` contains `{{ adminHead .Page }}`, `end.html` contains `{{ adminBody .Page }}`, and the CSS/JS URLs match `sass.out`/`js.out`.
- [ ] Pages: root area named `content`, start with `head`, end with `end`, `copy` for all languages in `path`, paths unique and starting with `/`.
- [ ] Refs live in `content/refs/`, names don't collide with any other file name in `content/`.
- [ ] No hand-written files in `static/` for CSS/JS output; no extra build tooling.
- [ ] Every Sass partial is `@use`d by the entrypoint; every JS module is imported by the entrypoint.
- [ ] JSON files are valid (e.g. `python3 -m json.tool file.json`).
- [ ] `shifu run .` starts without "Error compiling sass", "Error compiling js", "Error rendering template" or "Error parsing website content file" in the log; no page shows `Reference "…" not found!` or `<nil>`.
- [ ] Logged in at `/admin`, each element can be selected, edited, and new children can be added to every content area.
+1
View File
@@ -0,0 +1 @@
{ "label": "_HTML End", "layout": true }
+10
View File
@@ -0,0 +1,10 @@
{
"label": "FAQ",
"content": {
"items": { "label": "Fragen", "tpl_filter": ["faq_item"] }
},
"copy": {
"headline": { "label": "Überschrift" },
"text": { "label": "Text", "type": "html" }
}
}
+7
View File
@@ -0,0 +1,7 @@
{
"label": "FAQ-Frage",
"copy": {
"question": { "label": "Frage" },
"answer": { "label": "Antwort", "type": "html" }
}
}
+15
View File
@@ -0,0 +1,15 @@
{
"label": "_Footer",
"layout": true,
"copy": {
"headline": { "label": "Überschrift" },
"text": { "label": "Text" },
"copyright": { "label": "Copyright" }
},
"data": {
"email": { "label": "E-Mail" },
"phone": { "label": "Telefon" },
"instagram": { "label": "Instagram URL" },
"vimeo": { "label": "Vimeo URL" }
}
}
+18
View File
@@ -0,0 +1,18 @@
{
"label": "_HTML Head",
"layout": true,
"copy": {
"title": { "label": "Title (max. ~60 Zeichen)" },
"meta_description": { "label": "Meta Description (max. ~155 Zeichen)" }
},
"data": {
"robots": {
"label": "Suchmaschinen",
"type": "select",
"options": {
"index,follow": "Indexieren",
"noindex,follow": "Nicht indexieren"
}
}
}
}
+10
View File
@@ -0,0 +1,10 @@
{
"label": "Instagram",
"copy": {
"headline": { "label": "Überschrift" }
},
"data": {
"widget": { "label": "LightWidget URL" },
"profile": { "label": "Profil URL" }
}
}
+16
View File
@@ -0,0 +1,16 @@
{
"label": "Intro (Hero-Video)",
"copy": {
"kicker": { "label": "Dachzeile" },
"headline": { "label": "Überschrift (H1)" },
"text": { "label": "Einleitungstext", "type": "html" },
"video_label": { "label": "Video-Beschreibung (Barrierefreiheit)" },
"cta_label": { "label": "Button-Text" },
"cta_href": { "label": "Button-Link" },
"ticker": { "label": "Laufband-Text" }
},
"data": {
"video": { "label": "Video", "type": "file" },
"poster": { "label": "Vorschaubild", "type": "img" }
}
}
+1
View File
@@ -0,0 +1 @@
{ "label": "Impressum & Datenschutz" }
+13
View File
@@ -0,0 +1,13 @@
{
"label": "_Navigation",
"layout": true,
"copy": {
"service_label": { "label": "Link: Leistungen" },
"faq_label": { "label": "Link: FAQ" },
"work_label": { "label": "Link: Referenzen" },
"contact_label": { "label": "Link: Kontakt" }
},
"data": {
"full": { "label": "Volle Navigation anzeigen", "type": "boolean" }
}
}
+10
View File
@@ -0,0 +1,10 @@
{
"label": "Ablauf",
"content": {
"steps": { "label": "Schritte", "tpl_filter": ["process_step"] }
},
"copy": {
"headline": { "label": "Überschrift" },
"text": { "label": "Text", "type": "html" }
}
}
+14
View File
@@ -0,0 +1,14 @@
{
"label": "Ablauf-Schritt",
"copy": {
"title": { "label": "Titel" },
"text": { "label": "Text" }
},
"data": {
"color": {
"label": "Farbe",
"type": "select",
"options": { "yellow": "Gelb", "orange": "Orange", "red": "Rot", "pink": "Pink", "purple": "Lila" }
}
}
}
+14
View File
@@ -0,0 +1,14 @@
{
"label": "Gewerke",
"content": {
"items": { "label": "Gewerke", "tpl_filter": ["service_item"] }
},
"copy": {
"headline": { "label": "Überschrift" },
"text": { "label": "Text", "type": "html" },
"image_caption": { "label": "Bildunterschrift" }
},
"data": {
"image": { "label": "Bild", "type": "img" }
}
}
+20
View File
@@ -0,0 +1,20 @@
{
"label": "Gewerk (Klappe)",
"copy": {
"title": { "label": "Titel" },
"text": { "label": "Text" }
},
"data": {
"color": {
"label": "Farbe",
"type": "select",
"options": {
"red": "Rot",
"orange": "Orange",
"yellow": "Gelb",
"pink": "Pink",
"purple": "Lila"
}
}
}
}
+1
View File
@@ -0,0 +1 @@
{ "label": "AGB" }
+10
View File
@@ -0,0 +1,10 @@
{
"label": "Referenzen",
"content": {
"content": { "label": "Referenzen", "tpl_filter": ["work_entry"] }
},
"copy": {
"headline": { "label": "Überschrift" },
"text": { "label": "Text", "type": "html" }
}
}
+10
View File
@@ -0,0 +1,10 @@
{
"label": "Referenz (Video)",
"copy": {
"h2": { "label": "Titel" },
"text": { "label": "Text" }
},
"data": {
"video": { "label": "Embed-URL (YouTube/Vimeo)" }
}
}
+38 -1
View File
@@ -1 +1,38 @@
console.log("Hi!"); import { initNav } from "./nav.js";
import { initReveal } from "./reveal.js";
import { initTimecode } from "./timecode.js";
document.documentElement.classList.add("js");
const components = {
nav: initNav,
reveal: initReveal,
timecode: initTimecode,
};
function init(root = document) {
root.querySelectorAll("[data-component]").forEach((el) => {
if (el.dataset.componentReady) {
return;
}
const fn = components[el.dataset.component];
if (fn) {
fn(el);
el.dataset.componentReady = "true";
}
});
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", start);
} else {
start();
}
function start() {
init();
// the admin UI swaps element HTML in place after edits
new MutationObserver(() => init()).observe(document.body, { childList: true, subtree: true });
}
+21
View File
@@ -0,0 +1,21 @@
export function initNav(el) {
const toggle = el.querySelector(".nav__toggle");
if (!toggle) {
return;
}
const setOpen = (open) => {
el.classList.toggle("nav--open", open);
toggle.setAttribute("aria-expanded", String(open));
};
toggle.addEventListener("click", () => setOpen(!el.classList.contains("nav--open")));
el.querySelectorAll(".nav__link").forEach((link) => link.addEventListener("click", () => setOpen(false)));
el.addEventListener("keydown", (e) => {
if (e.key === "Escape") {
setOpen(false);
toggle.focus();
}
});
}
+25
View File
@@ -0,0 +1,25 @@
let observer = null;
function getObserver() {
if (!observer) {
observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.classList.add("is-visible");
observer.unobserve(entry.target);
}
});
}, { rootMargin: "0px 0px -8% 0px", threshold: 0.05 });
}
return observer;
}
export function initReveal(el) {
if (!("IntersectionObserver" in window)) {
el.classList.add("is-visible");
return;
}
getObserver().observe(el);
}
+25
View File
@@ -0,0 +1,25 @@
const FPS = 25;
function pad(n) {
return String(n).padStart(2, "0");
}
export function initTimecode(el) {
const start = performance.now();
const tick = (now) => {
if (!el.isConnected) {
return;
}
const frames = Math.floor(((now - start) / 1000) * FPS);
const f = frames % FPS;
const s = Math.floor(frames / FPS) % 60;
const m = Math.floor(frames / (FPS * 60)) % 60;
const h = Math.floor(frames / (FPS * 3600));
el.textContent = `${pad(h)}:${pad(m)}:${pad(s)}:${pad(f)}`;
requestAnimationFrame(tick);
};
requestAnimationFrame(tick);
}
+212
View File
@@ -0,0 +1,212 @@
@use "variables" as *;
:root {
--beige: #{$beige};
--yellow: #{$yellow};
--orange: #{$orange};
--red: #{$red};
--pink: #{$pink};
--purple: #{$purple};
--ink: #{$ink};
}
*,
*::before,
*::after {
box-sizing: border-box;
}
html {
scroll-behavior: smooth;
scroll-padding-top: 88px;
}
body {
counter-reset: section;
min-height: 100vh;
color: $ink;
background: $beige;
font-family: "Manrope", -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
font-size: 18px;
line-height: 1.6;
font-weight: 500;
overflow-x: hidden;
overflow-x: clip;
// film grain
&::after {
content: "";
position: fixed;
inset: -50%;
z-index: 9999;
pointer-events: none;
opacity: 0.07;
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='160' height='160'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/></filter><rect width='100%' height='100%' filter='url(%23n)'/></svg>");
animation: grain 0.8s steps(4) infinite;
}
}
@keyframes grain {
0% { transform: translate(0, 0); }
25% { transform: translate(-3%, 2%); }
50% { transform: translate(2%, -3%); }
75% { transform: translate(-2%, -1%); }
100% { transform: translate(1%, 3%); }
}
img,
video,
iframe {
display: block;
max-width: 100%;
}
a {
color: inherit;
}
strong, b { font-weight: 700; }
em, i { font-style: italic; }
::selection {
background: $yellow;
color: $ink;
}
:focus-visible {
outline: 3px solid $orange;
outline-offset: 3px;
}
.visually-hidden {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip: rect(0 0 0 0);
white-space: nowrap;
}
// shared section title: "01 Gewerke"
.section-title {
display: flex;
align-items: flex-start;
gap: 16px;
font-size: clamp(36px, 6vw, 76px);
line-height: 1;
font-weight: 800;
letter-spacing: -0.03em;
counter-increment: section;
&__nr {
flex: none;
margin-top: 0.35em;
padding: 4px 10px;
border: 2px solid currentColor;
border-radius: 999px;
font-size: 14px;
line-height: 1;
font-weight: 700;
letter-spacing: 0.1em;
font-variant-numeric: tabular-nums;
&::before {
content: counter(section, decimal-leading-zero);
}
}
&--light {
color: $beige;
}
}
// buttons
.button {
display: inline-flex;
align-items: center;
gap: 12px;
padding: 16px 28px 16px 18px;
border: 3px solid $ink;
border-radius: 999px;
font-size: 18px;
font-weight: 700;
text-decoration: none;
cursor: pointer;
box-shadow: 4px 4px 0 $ink;
transition: transform 0.25s $ease, box-shadow 0.25s $ease;
&:hover {
transform: translate(-2px, -2px) rotate(-1.5deg);
box-shadow: 7px 7px 0 $ink;
}
&:active {
transform: translate(3px, 3px);
box-shadow: 0 0 0 $ink;
}
&--yellow {
background: $yellow;
color: $ink;
}
&--outline {
background: transparent;
padding: 12px 22px;
font-size: 16px;
}
&__play {
position: relative;
width: 32px;
height: 32px;
flex: none;
border-radius: 50%;
background: $ink;
transition: transform 0.4s $ease;
&::after {
content: "";
position: absolute;
top: 50%;
left: 55%;
transform: translate(-50%, -50%);
border-style: solid;
border-width: 6px 0 6px 10px;
border-color: transparent transparent transparent $yellow;
}
}
&:hover &__play {
transform: rotate(360deg) scale(1.1);
}
}
// scroll reveal (only hidden once JS is running)
.js [data-component~="reveal"] {
opacity: 0;
transform: translateY(40px) rotate(1deg);
transition: opacity 0.8s $ease-out, transform 0.9s $ease-out;
&.is-visible {
opacity: 1;
transform: none;
}
}
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
.js [data-component~="reveal"] {
opacity: 1;
transform: none;
}
}
+127
View File
@@ -0,0 +1,127 @@
@use "variables" as *;
.faq {
padding: 120px 0 0;
@include md {
padding-top: 160px;
}
&__inner {
@include inner;
display: grid;
gap: 48px;
@include md {
grid-template-columns: 5fr 7fr;
gap: 64px;
align-items: start;
}
}
&__head {
display: grid;
gap: 24px;
@include md {
position: sticky;
top: 120px;
}
}
&__text {
max-width: 40ch;
font-size: 20px;
a {
font-weight: 700;
text-decoration: underline wavy $red;
text-underline-offset: 4px;
}
}
&__list {
display: grid;
gap: 16px;
}
}
.faq-item {
border: 3px solid $ink;
border-radius: $radius;
background: $cream;
box-shadow: 5px 5px 0 $ink;
transition: box-shadow 0.3s $ease, translate 0.3s $ease;
&:hover {
translate: -2px -2px;
box-shadow: 8px 8px 0 $ink;
}
&[open] {
background: $yellow;
}
&__question {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
padding: 20px 24px;
cursor: pointer;
list-style: none;
&::-webkit-details-marker {
display: none;
}
}
&__title {
font-size: clamp(18px, 2vw, 22px);
line-height: 1.3;
font-weight: 800;
}
&__icon {
position: relative;
flex: none;
width: 36px;
height: 36px;
border: 3px solid $ink;
border-radius: 50%;
background: $cream;
transition: rotate 0.4s $ease;
&::before,
&::after {
content: "";
position: absolute;
top: 50%;
left: 50%;
width: 14px;
height: 3px;
background: $ink;
translate: -50% -50%;
}
&::after {
rotate: 90deg;
}
}
&[open] &__icon {
rotate: 135deg;
}
&__answer {
padding: 0 24px 24px;
font-size: 17px;
ul, ol {
margin: 8px 0 0 20px;
}
ul { list-style: disc; }
ol { list-style: decimal; }
}
}
+137
View File
@@ -0,0 +1,137 @@
@use "variables" as *;
.footer {
position: relative;
overflow: hidden;
padding: 120px 0 32px;
background: $red;
color: $cream;
border-top: 3px solid $ink;
@include md {
padding-top: 160px;
}
// big rotating "film reel" decoration
&::before {
content: "";
position: absolute;
right: -160px;
top: -160px;
width: 480px;
height: 480px;
border-radius: 50%;
background:
radial-gradient(circle at 50% 50%, $red 0 34px, transparent 35px),
radial-gradient(circle at 50% 22%, $red 0 46px, transparent 47px),
radial-gradient(circle at 50% 78%, $red 0 46px, transparent 47px),
radial-gradient(circle at 22% 50%, $red 0 46px, transparent 47px),
radial-gradient(circle at 78% 50%, $red 0 46px, transparent 47px),
$ink;
opacity: 0.25;
animation: reel 18s linear infinite;
pointer-events: none;
}
&__inner {
@include inner;
position: relative;
}
&__headline {
font-size: clamp(56px, 12vw, 180px);
line-height: 0.9;
font-weight: 800;
letter-spacing: -0.05em;
color: $cream;
}
&__text {
max-width: 32ch;
margin-top: 24px;
font-size: clamp(20px, 2.4vw, 28px);
font-weight: 600;
}
&__mail {
margin-top: 40px;
font-size: clamp(16px, 2vw, 22px);
}
&__bottom {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
gap: 16px;
margin-top: 96px;
padding-top: 24px;
border-top: 2px solid rgba($cream, 0.35);
font-size: 15px;
}
&__legal {
display: flex;
gap: 24px;
a {
text-decoration: none;
&:hover {
text-decoration: underline wavy;
text-underline-offset: 4px;
}
}
}
}
// end-credits style contact list
.credits {
display: grid;
gap: 4px;
max-width: 640px;
margin: 96px 0 0;
&__row {
display: flex;
align-items: baseline;
gap: 12px;
font-size: clamp(18px, 2vw, 22px);
&::before {
content: "";
order: 1;
flex: 1;
border-bottom: 2px dotted rgba($cream, 0.5);
transform: translateY(-5px);
}
dt {
order: 0;
font-size: 13px;
font-weight: 700;
letter-spacing: 0.14em;
text-transform: uppercase;
opacity: 0.8;
}
dd {
order: 2;
margin: 0;
font-weight: 700;
a {
text-decoration: none;
background: linear-gradient($yellow, $yellow) 0 100% / 0 3px no-repeat;
transition: background-size 0.3s $ease-out;
&:hover {
background-size: 100% 3px;
}
}
}
}
}
@keyframes reel {
to { transform: rotate(360deg); }
}
-966
View File
@@ -1,966 +0,0 @@
.container {
max-width: 1600px;
width: 100%;
margin-right: auto;
margin-left: auto;
position: relative;
box-sizing: border-box;
}
.row {
box-sizing: border-box;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-flex: 0;
-ms-flex: 0 1 auto;
flex: 0 1 auto;
-webkit-box-orient: horizontal;
-webkit-box-direction: normal;
-ms-flex-direction: row;
flex-direction: row;
-ms-flex-wrap: wrap;
flex-wrap: wrap;
}
.row.reverse {
-webkit-box-orient: horizontal;
-webkit-box-direction: reverse;
-ms-flex-direction: row-reverse;
flex-direction: row-reverse;
}
.col.reverse {
-webkit-box-orient: vertical;
-webkit-box-direction: reverse;
-ms-flex-direction: column-reverse;
flex-direction: column-reverse;
}
.col-xs,
.col-xs-1,
.col-xs-2,
.col-xs-3,
.col-xs-4,
.col-xs-5,
.col-xs-6,
.col-xs-7,
.col-xs-8,
.col-xs-9,
.col-xs-10,
.col-xs-11,
.col-xs-12,
.col-xs-offset-0,
.col-xs-offset-1,
.col-xs-offset-2,
.col-xs-offset-3,
.col-xs-offset-4,
.col-xs-offset-5,
.col-xs-offset-6,
.col-xs-offset-7,
.col-xs-offset-8,
.col-xs-offset-9,
.col-xs-offset-10,
.col-xs-offset-11,
.col-xs-offset-12 {
box-sizing: border-box;
-webkit-box-flex: 0;
-ms-flex: 0 0 auto;
flex: 0 0 auto;
}
.col-xs {
-webkit-box-flex: 1;
-ms-flex-positive: 1;
flex-grow: 1;
-ms-flex-preferred-size: 0;
flex-basis: 0;
max-width: 100%;
}
.col-xs-1 {
-ms-flex-preferred-size: 8.33333333%;
flex-basis: 8.33333333%;
max-width: 8.33333333%;
}
.col-xs-2 {
-ms-flex-preferred-size: 16.66666667%;
flex-basis: 16.66666667%;
max-width: 16.66666667%;
}
.col-xs-3 {
-ms-flex-preferred-size: 25%;
flex-basis: 25%;
max-width: 25%;
}
.col-xs-4 {
-ms-flex-preferred-size: 33.33333333%;
flex-basis: 33.33333333%;
max-width: 33.33333333%;
}
.col-xs-5 {
-ms-flex-preferred-size: 41.66666667%;
flex-basis: 41.66666667%;
max-width: 41.66666667%;
}
.col-xs-6 {
-ms-flex-preferred-size: 50%;
flex-basis: 50%;
max-width: 50%;
}
.col-xs-7 {
-ms-flex-preferred-size: 58.33333333%;
flex-basis: 58.33333333%;
max-width: 58.33333333%;
}
.col-xs-8 {
-ms-flex-preferred-size: 66.66666667%;
flex-basis: 66.66666667%;
max-width: 66.66666667%;
}
.col-xs-9 {
-ms-flex-preferred-size: 75%;
flex-basis: 75%;
max-width: 75%;
}
.col-xs-10 {
-ms-flex-preferred-size: 83.33333333%;
flex-basis: 83.33333333%;
max-width: 83.33333333%;
}
.col-xs-11 {
-ms-flex-preferred-size: 91.66666667%;
flex-basis: 91.66666667%;
max-width: 91.66666667%;
}
.col-xs-12 {
-ms-flex-preferred-size: 100%;
flex-basis: 100%;
max-width: 100%;
}
.col-xs-offset-0 {
margin-left: 0;
}
.col-xs-offset-1 {
margin-left: 8.33333333%;
}
.col-xs-offset-2 {
margin-left: 16.66666667%;
}
.col-xs-offset-3 {
margin-left: 25%;
}
.col-xs-offset-4 {
margin-left: 33.33333333%;
}
.col-xs-offset-5 {
margin-left: 41.66666667%;
}
.col-xs-offset-6 {
margin-left: 50%;
}
.col-xs-offset-7 {
margin-left: 58.33333333%;
}
.col-xs-offset-8 {
margin-left: 66.66666667%;
}
.col-xs-offset-9 {
margin-left: 75%;
}
.col-xs-offset-10 {
margin-left: 83.33333333%;
}
.col-xs-offset-11 {
margin-left: 91.66666667%;
}
.start-xs {
-webkit-box-pack: start;
-ms-flex-pack: start;
justify-content: flex-start;
text-align: start;
}
.center-xs {
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
text-align: center;
}
.end-xs {
-webkit-box-pack: end;
-ms-flex-pack: end;
justify-content: flex-end;
text-align: end;
}
.top-xs {
-webkit-box-align: start;
-ms-flex-align: start;
align-items: flex-start;
}
.middle-xs {
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
}
.bottom-xs {
-webkit-box-align: end;
-ms-flex-align: end;
align-items: flex-end;
}
.around-xs {
-ms-flex-pack: distribute;
justify-content: space-around;
}
.between-xs {
-webkit-box-pack: justify;
-ms-flex-pack: justify;
justify-content: space-between;
}
.first-xs {
-webkit-box-ordinal-group: 0;
-ms-flex-order: -1;
order: -1;
}
.last-xs {
-webkit-box-ordinal-group: 2;
-ms-flex-order: 1;
order: 1;
}
@media only screen and (max-width: 511px) {
.hide-xs {
display: none !important;
}
}
@media only screen and (min-width: 512px) {
.col-sm,
.col-sm-1,
.col-sm-2,
.col-sm-3,
.col-sm-4,
.col-sm-5,
.col-sm-6,
.col-sm-7,
.col-sm-8,
.col-sm-9,
.col-sm-10,
.col-sm-11,
.col-sm-12,
.col-sm-offset-0,
.col-sm-offset-1,
.col-sm-offset-2,
.col-sm-offset-3,
.col-sm-offset-4,
.col-sm-offset-5,
.col-sm-offset-6,
.col-sm-offset-7,
.col-sm-offset-8,
.col-sm-offset-9,
.col-sm-offset-10,
.col-sm-offset-11,
.col-sm-offset-12 {
box-sizing: border-box;
-webkit-box-flex: 0;
-ms-flex: 0 0 auto;
flex: 0 0 auto;
}
.col-sm {
-webkit-box-flex: 1;
-ms-flex-positive: 1;
flex-grow: 1;
-ms-flex-preferred-size: 0;
flex-basis: 0;
max-width: 100%;
}
.col-sm-1 {
-ms-flex-preferred-size: 8.33333333%;
flex-basis: 8.33333333%;
max-width: 8.33333333%;
}
.col-sm-2 {
-ms-flex-preferred-size: 16.66666667%;
flex-basis: 16.66666667%;
max-width: 16.66666667%;
}
.col-sm-3 {
-ms-flex-preferred-size: 25%;
flex-basis: 25%;
max-width: 25%;
}
.col-sm-4 {
-ms-flex-preferred-size: 33.33333333%;
flex-basis: 33.33333333%;
max-width: 33.33333333%;
}
.col-sm-5 {
-ms-flex-preferred-size: 41.66666667%;
flex-basis: 41.66666667%;
max-width: 41.66666667%;
}
.col-sm-6 {
-ms-flex-preferred-size: 50%;
flex-basis: 50%;
max-width: 50%;
}
.col-sm-7 {
-ms-flex-preferred-size: 58.33333333%;
flex-basis: 58.33333333%;
max-width: 58.33333333%;
}
.col-sm-8 {
-ms-flex-preferred-size: 66.66666667%;
flex-basis: 66.66666667%;
max-width: 66.66666667%;
}
.col-sm-9 {
-ms-flex-preferred-size: 75%;
flex-basis: 75%;
max-width: 75%;
}
.col-sm-10 {
-ms-flex-preferred-size: 83.33333333%;
flex-basis: 83.33333333%;
max-width: 83.33333333%;
}
.col-sm-11 {
-ms-flex-preferred-size: 91.66666667%;
flex-basis: 91.66666667%;
max-width: 91.66666667%;
}
.col-sm-12 {
-ms-flex-preferred-size: 100%;
flex-basis: 100%;
max-width: 100%;
}
.col-sm-offset-0 {
margin-left: 0;
}
.col-sm-offset-1 {
margin-left: 8.33333333%;
}
.col-sm-offset-2 {
margin-left: 16.66666667%;
}
.col-sm-offset-3 {
margin-left: 25%;
}
.col-sm-offset-4 {
margin-left: 33.33333333%;
}
.col-sm-offset-5 {
margin-left: 41.66666667%;
}
.col-sm-offset-6 {
margin-left: 50%;
}
.col-sm-offset-7 {
margin-left: 58.33333333%;
}
.col-sm-offset-8 {
margin-left: 66.66666667%;
}
.col-sm-offset-9 {
margin-left: 75%;
}
.col-sm-offset-10 {
margin-left: 83.33333333%;
}
.col-sm-offset-11 {
margin-left: 91.66666667%;
}
.start-sm {
-webkit-box-pack: start;
-ms-flex-pack: start;
justify-content: flex-start;
text-align: start;
}
.center-sm {
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
text-align: center;
}
.end-sm {
-webkit-box-pack: end;
-ms-flex-pack: end;
justify-content: flex-end;
text-align: end;
}
.top-sm {
-webkit-box-align: start;
-ms-flex-align: start;
align-items: flex-start;
}
.middle-sm {
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
}
.bottom-sm {
-webkit-box-align: end;
-ms-flex-align: end;
align-items: flex-end;
}
.around-sm {
-ms-flex-pack: distribute;
justify-content: space-around;
}
.between-sm {
-webkit-box-pack: justify;
-ms-flex-pack: justify;
justify-content: space-between;
}
.first-sm {
-webkit-box-ordinal-group: 0;
-ms-flex-order: -1;
order: -1;
}
.last-sm {
-webkit-box-ordinal-group: 2;
-ms-flex-order: 1;
order: 1;
}
.show-xs {
display: none !important;
}
}
@media only screen and (max-width: 767px) {
.hide-sm {
display: none !important;
}
}
@media only screen and (min-width: 768px) {
.col-md,
.col-md-1,
.col-md-2,
.col-md-3,
.col-md-4,
.col-md-5,
.col-md-6,
.col-md-7,
.col-md-8,
.col-md-9,
.col-md-10,
.col-md-11,
.col-md-12,
.col-md-offset-0,
.col-md-offset-1,
.col-md-offset-2,
.col-md-offset-3,
.col-md-offset-4,
.col-md-offset-5,
.col-md-offset-6,
.col-md-offset-7,
.col-md-offset-8,
.col-md-offset-9,
.col-md-offset-10,
.col-md-offset-11,
.col-md-offset-12 {
box-sizing: border-box;
-webkit-box-flex: 0;
-ms-flex: 0 0 auto;
flex: 0 0 auto;
}
.col-md {
-webkit-box-flex: 1;
-ms-flex-positive: 1;
flex-grow: 1;
-ms-flex-preferred-size: 0;
flex-basis: 0;
max-width: 100%;
}
.col-md-1 {
-ms-flex-preferred-size: 8.33333333%;
flex-basis: 8.33333333%;
max-width: 8.33333333%;
}
.col-md-2 {
-ms-flex-preferred-size: 16.66666667%;
flex-basis: 16.66666667%;
max-width: 16.66666667%;
}
.col-md-3 {
-ms-flex-preferred-size: 25%;
flex-basis: 25%;
max-width: 25%;
}
.col-md-4 {
-ms-flex-preferred-size: 33.33333333%;
flex-basis: 33.33333333%;
max-width: 33.33333333%;
}
.col-md-5 {
-ms-flex-preferred-size: 41.66666667%;
flex-basis: 41.66666667%;
max-width: 41.66666667%;
}
.col-md-6 {
-ms-flex-preferred-size: 50%;
flex-basis: 50%;
max-width: 50%;
}
.col-md-7 {
-ms-flex-preferred-size: 58.33333333%;
flex-basis: 58.33333333%;
max-width: 58.33333333%;
}
.col-md-8 {
-ms-flex-preferred-size: 66.66666667%;
flex-basis: 66.66666667%;
max-width: 66.66666667%;
}
.col-md-9 {
-ms-flex-preferred-size: 75%;
flex-basis: 75%;
max-width: 75%;
}
.col-md-10 {
-ms-flex-preferred-size: 83.33333333%;
flex-basis: 83.33333333%;
max-width: 83.33333333%;
}
.col-md-11 {
-ms-flex-preferred-size: 91.66666667%;
flex-basis: 91.66666667%;
max-width: 91.66666667%;
}
.col-md-12 {
-ms-flex-preferred-size: 100%;
flex-basis: 100%;
max-width: 100%;
}
.col-md-offset-0 {
margin-left: 0;
}
.col-md-offset-1 {
margin-left: 8.33333333%;
}
.col-md-offset-2 {
margin-left: 16.66666667%;
}
.col-md-offset-3 {
margin-left: 25%;
}
.col-md-offset-4 {
margin-left: 33.33333333%;
}
.col-md-offset-5 {
margin-left: 41.66666667%;
}
.col-md-offset-6 {
margin-left: 50%;
}
.col-md-offset-7 {
margin-left: 58.33333333%;
}
.col-md-offset-8 {
margin-left: 66.66666667%;
}
.col-md-offset-9 {
margin-left: 75%;
}
.col-md-offset-10 {
margin-left: 83.33333333%;
}
.col-md-offset-11 {
margin-left: 91.66666667%;
}
.start-md {
-webkit-box-pack: start;
-ms-flex-pack: start;
justify-content: flex-start;
text-align: start;
}
.center-md {
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
text-align: center;
}
.end-md {
-webkit-box-pack: end;
-ms-flex-pack: end;
justify-content: flex-end;
text-align: end;
}
.top-md {
-webkit-box-align: start;
-ms-flex-align: start;
align-items: flex-start;
}
.middle-md {
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
}
.bottom-md {
-webkit-box-align: end;
-ms-flex-align: end;
align-items: flex-end;
}
.around-md {
-ms-flex-pack: distribute;
justify-content: space-around;
}
.between-md {
-webkit-box-pack: justify;
-ms-flex-pack: justify;
justify-content: space-between;
}
.first-md {
-webkit-box-ordinal-group: 0;
-ms-flex-order: -1;
order: -1;
}
.last-md {
-webkit-box-ordinal-group: 2;
-ms-flex-order: 1;
order: 1;
}
.show-sm {
display: none !important;
}
}
@media only screen and (max-width: 1023px) {
.hide-md {
display: none !important;
}
}
@media only screen and (min-width: 1024px) {
.col-lg,
.col-lg-1,
.col-lg-2,
.col-lg-3,
.col-lg-4,
.col-lg-5,
.col-lg-6,
.col-lg-7,
.col-lg-8,
.col-lg-9,
.col-lg-10,
.col-lg-11,
.col-lg-12,
.col-lg-offset-0,
.col-lg-offset-1,
.col-lg-offset-2,
.col-lg-offset-3,
.col-lg-offset-4,
.col-lg-offset-5,
.col-lg-offset-6,
.col-lg-offset-7,
.col-lg-offset-8,
.col-lg-offset-9,
.col-lg-offset-10,
.col-lg-offset-11,
.col-lg-offset-12 {
box-sizing: border-box;
-webkit-box-flex: 0;
-ms-flex: 0 0 auto;
flex: 0 0 auto;
}
.col-lg {
-webkit-box-flex: 1;
-ms-flex-positive: 1;
flex-grow: 1;
-ms-flex-preferred-size: 0;
flex-basis: 0;
max-width: 100%;
}
.col-lg-1 {
-ms-flex-preferred-size: 8.33333333%;
flex-basis: 8.33333333%;
max-width: 8.33333333%;
}
.col-lg-2 {
-ms-flex-preferred-size: 16.66666667%;
flex-basis: 16.66666667%;
max-width: 16.66666667%;
}
.col-lg-3 {
-ms-flex-preferred-size: 25%;
flex-basis: 25%;
max-width: 25%;
}
.col-lg-4 {
-ms-flex-preferred-size: 33.33333333%;
flex-basis: 33.33333333%;
max-width: 33.33333333%;
}
.col-lg-5 {
-ms-flex-preferred-size: 41.66666667%;
flex-basis: 41.66666667%;
max-width: 41.66666667%;
}
.col-lg-6 {
-ms-flex-preferred-size: 50%;
flex-basis: 50%;
max-width: 50%;
}
.col-lg-7 {
-ms-flex-preferred-size: 58.33333333%;
flex-basis: 58.33333333%;
max-width: 58.33333333%;
}
.col-lg-8 {
-ms-flex-preferred-size: 66.66666667%;
flex-basis: 66.66666667%;
max-width: 66.66666667%;
}
.col-lg-9 {
-ms-flex-preferred-size: 75%;
flex-basis: 75%;
max-width: 75%;
}
.col-lg-10 {
-ms-flex-preferred-size: 83.33333333%;
flex-basis: 83.33333333%;
max-width: 83.33333333%;
}
.col-lg-11 {
-ms-flex-preferred-size: 91.66666667%;
flex-basis: 91.66666667%;
max-width: 91.66666667%;
}
.col-lg-12 {
-ms-flex-preferred-size: 100%;
flex-basis: 100%;
max-width: 100%;
}
.col-lg-offset-0 {
margin-left: 0;
}
.col-lg-offset-1 {
margin-left: 8.33333333%;
}
.col-lg-offset-2 {
margin-left: 16.66666667%;
}
.col-lg-offset-3 {
margin-left: 25%;
}
.col-lg-offset-4 {
margin-left: 33.33333333%;
}
.col-lg-offset-5 {
margin-left: 41.66666667%;
}
.col-lg-offset-6 {
margin-left: 50%;
}
.col-lg-offset-7 {
margin-left: 58.33333333%;
}
.col-lg-offset-8 {
margin-left: 66.66666667%;
}
.col-lg-offset-9 {
margin-left: 75%;
}
.col-lg-offset-10 {
margin-left: 83.33333333%;
}
.col-lg-offset-11 {
margin-left: 91.66666667%;
}
.start-lg {
-webkit-box-pack: start;
-ms-flex-pack: start;
justify-content: flex-start;
text-align: start;
}
.center-lg {
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
text-align: center;
}
.end-lg {
-webkit-box-pack: end;
-ms-flex-pack: end;
justify-content: flex-end;
text-align: end;
}
.top-lg {
-webkit-box-align: start;
-ms-flex-align: start;
align-items: flex-start;
}
.middle-lg {
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
}
.bottom-lg {
-webkit-box-align: end;
-ms-flex-align: end;
align-items: flex-end;
}
.around-lg {
-ms-flex-pack: distribute;
justify-content: space-around;
}
.between-lg {
-webkit-box-pack: justify;
-ms-flex-pack: justify;
justify-content: space-between;
}
.first-lg {
-webkit-box-ordinal-group: 0;
-ms-flex-order: -1;
order: -1;
}
.last-lg {
-webkit-box-ordinal-group: 2;
-ms-flex-order: 1;
order: 1;
}
.show-md {
display: none !important;
}
}
+38
View File
@@ -0,0 +1,38 @@
@use "variables" as *;
.insta {
padding: 120px 0;
@include md {
padding: 160px 0;
}
&__inner {
@include inner;
}
&__head {
display: flex;
flex-wrap: wrap;
align-items: flex-end;
justify-content: space-between;
gap: 24px;
margin-bottom: 56px;
}
&__frame {
padding: 16px;
border: 3px solid $ink;
border-radius: $radius-lg;
background: $cream;
box-shadow: 10px 10px 0 $pink;
transform: rotate(-0.75deg);
}
&__widget {
width: 100%;
border: 0;
overflow: hidden;
border-radius: $radius - 6px;
}
}
+284
View File
@@ -0,0 +1,284 @@
@use "variables" as *;
.intro {
position: relative;
// the rotated, wider-than-viewport ticker must not create horizontal scrolling
overflow-x: clip;
padding: 48px 0 0;
@include md {
padding-top: 72px;
}
&__inner {
@include inner;
position: relative;
}
&__kicker {
display: inline-flex;
align-items: center;
gap: 10px;
margin-bottom: 20px;
padding: 6px 14px;
border-radius: 999px;
background: $ink;
color: $beige;
font-size: 14px;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
rotate: -2deg;
}
&__kicker-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: $red;
animation: blink 1s steps(1) infinite;
}
&__headline {
max-width: 16ch;
font-size: clamp(34px, 8.6vw, 150px);
hyphens: auto;
line-height: 0.92;
// background-clip: text only paints inside the box; keep descenders (g, p, …) visible
padding-bottom: 0.3em;
margin-bottom: -0.3em;
font-weight: 800;
letter-spacing: -0.045em;
background: linear-gradient(100deg, $ink 0 48%, $red 68%, $orange 88%);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
-webkit-text-fill-color: transparent;
}
&__text {
max-width: 52ch;
margin-top: 28px;
font-size: clamp(18px, 1.6vw, 22px);
line-height: 1.55;
strong { font-weight: 800; }
}
&__cta {
margin-top: 32px;
@include md {
position: absolute;
top: 0;
right: 48px;
margin: 0;
rotate: 4deg;
transition: rotate 0.4s $ease, opacity 0.8s $ease-out, transform 0.9s $ease-out;
&:hover {
rotate: -2deg;
}
}
}
}
@keyframes blink {
50% { opacity: 0; }
}
.viewfinder {
position: relative;
margin-top: 48px;
overflow: hidden;
border: 3px solid $ink;
border-radius: $radius-lg;
background: $ink;
box-shadow: 10px 10px 0 $orange;
// no min-height here: with aspect-ratio it is transferred to a min-width and overflows on small screens
width: 100%;
aspect-ratio: 4 / 3;
@include sm {
aspect-ratio: 16 / 9;
}
@include md {
margin-top: 64px;
box-shadow: 16px 16px 0 $orange;
}
&__video {
width: 100%;
height: 100%;
object-fit: cover;
}
&__overlay {
position: absolute;
inset: 0;
pointer-events: none;
color: #fff;
font-family: ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace;
font-size: 13px;
font-weight: 600;
letter-spacing: 0.08em;
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.6);
@include md {
font-size: 16px;
}
}
&__corner {
position: absolute;
width: 36px;
height: 36px;
border: 0 solid rgba(#fff, 0.9);
&--tl { top: 20px; left: 20px; border-top-width: 3px; border-left-width: 3px; }
&--tr { top: 20px; right: 20px; border-top-width: 3px; border-right-width: 3px; }
&--bl { bottom: 20px; left: 20px; border-bottom-width: 3px; border-left-width: 3px; }
&--br { bottom: 20px; right: 20px; border-bottom-width: 3px; border-right-width: 3px; }
}
&__rec {
position: absolute;
top: 30px;
left: 70px;
display: flex;
align-items: center;
gap: 8px;
&::before {
content: "";
width: 12px;
height: 12px;
border-radius: 50%;
background: $red;
animation: blink 1s steps(1) infinite;
}
}
&__timecode {
position: absolute;
top: 30px;
right: 70px;
font-variant-numeric: tabular-nums;
}
&__meta {
position: absolute;
bottom: 30px;
left: 70px;
opacity: 0.85;
}
&__battery {
position: absolute;
right: 74px;
bottom: 32px;
width: 30px;
height: 14px;
border: 2px solid #fff;
border-radius: 3px;
&::before {
content: "";
position: absolute;
inset: 2px 30% 2px 2px;
background: #fff;
}
&::after {
content: "";
position: absolute;
top: 3px;
right: -5px;
width: 3px;
height: 4px;
background: #fff;
}
}
&__focus {
position: absolute;
top: 50%;
left: 50%;
width: 64px;
height: 64px;
transform: translate(-50%, -50%);
border: 2px solid rgba($yellow, 0.9);
animation: focus-hunt 5s $ease infinite;
&::before,
&::after {
content: "";
position: absolute;
background: rgba($yellow, 0.9);
}
&::before { top: 50%; left: 50%; width: 12px; height: 2px; transform: translate(-50%, -50%); }
&::after { top: 50%; left: 50%; width: 2px; height: 12px; transform: translate(-50%, -50%); }
}
@media (max-width: 639px) {
&__meta,
&__battery {
display: none;
}
&__rec { left: 32px; top: 28px; }
&__timecode { right: 32px; top: 28px; }
&__corner { width: 22px; height: 22px; }
}
}
@keyframes focus-hunt {
0%, 100% { transform: translate(-50%, -50%) scale(1); }
10% { transform: translate(-50%, -50%) scale(1.25); }
18% { transform: translate(-50%, -50%) scale(0.9); }
24% { transform: translate(-50%, -50%) scale(1); }
60% { transform: translate(-30%, -60%) scale(1); }
70% { transform: translate(-30%, -60%) scale(1.2); }
76% { transform: translate(-30%, -60%) scale(1); }
}
.ticker {
position: relative;
z-index: 2;
margin: 72px -5vw 0;
padding: 18px 0;
overflow: hidden;
border-block: 3px solid $ink;
background: $yellow;
transform: rotate(-2deg);
@include md {
margin-top: 96px;
}
&__track {
display: flex;
width: max-content;
animation: ticker 40s linear infinite;
span {
padding-right: 0.5em;
white-space: nowrap;
font-size: clamp(24px, 4vw, 44px);
line-height: 1;
font-weight: 800;
letter-spacing: -0.02em;
text-transform: uppercase;
}
}
&:hover &__track {
animation-play-state: paused;
}
}
@keyframes ticker {
to { transform: translateX(-50%); }
}
+167
View File
@@ -0,0 +1,167 @@
@use "variables" as *;
.nav {
position: sticky;
top: 0;
z-index: 100;
padding: 12px 0;
transition: padding 0.3s $ease-out;
&__inner {
@include inner;
display: flex;
align-items: center;
justify-content: space-between;
gap: 24px;
}
&__brand {
display: inline-flex;
align-items: center;
gap: 10px;
padding: 10px 18px;
border: 3px solid $ink;
border-radius: 999px;
background: $cream;
box-shadow: 4px 4px 0 $ink;
text-decoration: none;
font-size: 22px;
font-weight: 800;
letter-spacing: -0.02em;
transition: transform 0.3s $ease;
&:hover {
transform: rotate(-3deg) scale(1.04);
}
}
&__rec {
width: 12px;
height: 12px;
border-radius: 50%;
background: $red;
box-shadow: 0 0 0 0 rgba($red, 0.6);
animation: rec-pulse 1.6s infinite;
}
&__word {
display: inline-flex;
align-items: center;
}
&__cut {
display: inline-block;
margin: 0 1px;
color: $orange;
font-size: 0.75em;
transform: rotate(-90deg);
transition: transform 0.5s $ease;
}
&__brand:hover &__cut {
transform: rotate(-90deg) translateY(-3px) scaleX(0.6);
}
&__toggle {
display: inline-flex;
flex-direction: column;
justify-content: center;
gap: 6px;
width: 52px;
height: 52px;
padding: 0 14px;
border: 3px solid $ink;
border-radius: 50%;
background: $yellow;
box-shadow: 4px 4px 0 $ink;
cursor: pointer;
@include md {
display: none;
}
}
&__toggle-bar {
display: block;
height: 3px;
border-radius: 3px;
background: $ink;
transition: transform 0.3s $ease;
}
&--open &__toggle-bar:first-child { transform: translateY(4.5px) rotate(45deg); }
&--open &__toggle-bar:nth-child(2) { transform: translateY(-4.5px) rotate(-45deg); }
&__links {
display: none;
gap: 8px;
padding: 6px;
border: 3px solid $ink;
border-radius: 999px;
background: $cream;
box-shadow: 4px 4px 0 $ink;
@include md {
display: flex;
}
}
&__link {
position: relative;
padding: 8px 18px;
border-radius: 999px;
font-size: 16px;
font-weight: 700;
text-decoration: none;
transition: background 0.2s, transform 0.3s $ease;
&:hover {
background: $ink;
color: $beige;
transform: rotate(-2deg);
}
&--cta {
background: $red;
color: $cream;
}
}
// mobile overlay menu
&--open &__links {
position: fixed;
inset: 84px 16px auto;
display: flex;
flex-direction: column;
gap: 4px;
padding: 16px;
border-radius: $radius-lg;
animation: menu-pop 0.4s $ease;
.nav__link {
padding: 16px 20px;
font-size: 28px;
font-weight: 800;
&::before {
content: attr(data-nr);
margin-right: 12px;
font-size: 14px;
vertical-align: middle;
opacity: 0.5;
}
}
}
}
@keyframes rec-pulse {
0% { box-shadow: 0 0 0 0 rgba($red, 0.6); opacity: 1; }
50% { opacity: 0.35; }
70% { box-shadow: 0 0 0 10px rgba($red, 0); }
100% { box-shadow: 0 0 0 0 rgba($red, 0); opacity: 1; }
}
@keyframes menu-pop {
from { opacity: 0; transform: scale(0.9) rotate(-2deg); }
to { opacity: 1; transform: none; }
}
+114
View File
@@ -0,0 +1,114 @@
@use "variables" as *;
.process {
padding: 0 0 120px;
@include md {
padding-bottom: 160px;
}
&__inner {
@include inner;
}
&__head {
display: grid;
gap: 24px;
margin-bottom: 64px;
@include md {
grid-template-columns: 1fr 1fr;
align-items: end;
}
}
&__text {
max-width: 46ch;
font-size: 20px;
}
&__steps {
counter-reset: frame;
display: grid;
gap: 28px;
margin: 0;
padding: 0;
list-style: none;
@include sm {
grid-template-columns: repeat(2, 1fr);
}
@include lg {
grid-template-columns: repeat(4, 1fr);
}
}
}
// storyboard frame
.frame {
--frame-color: #{$yellow};
counter-increment: frame;
position: relative;
padding: 28px 24px 32px;
border: 3px solid $ink;
border-radius: $radius;
background: $cream;
box-shadow: 8px 8px 0 var(--frame-color);
transition: rotate 0.4s $ease, translate 0.4s $ease;
@each $name, $color in $colors {
&--#{$name} {
--frame-color: #{$color};
}
}
&:nth-child(odd) { rotate: -1.2deg; }
&:nth-child(even) { rotate: 1.2deg; }
&:hover {
rotate: 0deg;
translate: 0 -6px;
}
// dashed arrow to the next frame
@include lg {
&:not(:last-child)::after {
content: "";
position: absolute;
top: 50%;
right: -26px;
width: 22px;
border-top: 3px dashed $ink;
}
}
&__nr {
display: grid;
place-items: center;
width: 56px;
height: 56px;
margin-bottom: 20px;
border: 3px solid $ink;
border-radius: 50%;
background: var(--frame-color);
font-size: 22px;
font-weight: 800;
&::before {
content: counter(frame);
}
}
&__title {
font-size: 24px;
line-height: 1.2;
font-weight: 800;
letter-spacing: -0.02em;
}
&__text {
margin-top: 10px;
font-size: 17px;
}
}
-2
View File
@@ -14,7 +14,6 @@ time, mark, audio, video {
margin: 0; margin: 0;
padding: 0; padding: 0;
border: 0; border: 0;
outline: none;
font: inherit; font: inherit;
text-decoration: none; text-decoration: none;
vertical-align: baseline; vertical-align: baseline;
@@ -54,5 +53,4 @@ table {
} }
textarea:focus, input:focus, button:focus { textarea:focus, input:focus, button:focus {
outline: none;
} }
+99
View File
@@ -0,0 +1,99 @@
@use "variables" as *;
.service {
padding: 120px 0;
@include md {
padding: 160px 0;
}
&__inner {
@include inner;
}
&__head {
display: grid;
gap: 24px;
margin-bottom: 64px;
@include md {
grid-template-columns: 1fr 1fr;
align-items: end;
}
}
&__text {
max-width: 46ch;
font-size: 20px;
}
&__grid {
display: grid;
gap: 56px;
@include md {
grid-template-columns: 5fr 7fr;
align-items: start;
gap: 64px;
}
}
&__items {
display: grid;
gap: 32px;
}
}
.polaroid {
position: relative;
margin: 0;
padding: 16px 16px 72px;
border: 3px solid $ink;
background: $cream;
box-shadow: 10px 10px 0 $ink;
rotate: 2.5deg;
transition: rotate 0.5s $ease, scale 0.5s $ease, opacity 0.8s $ease-out, transform 0.9s $ease-out;
@include md {
position: sticky;
top: 120px;
}
&:hover {
rotate: -1deg;
scale: 1.02;
}
// tape
&::before {
content: "";
position: absolute;
top: -18px;
left: 50%;
width: 140px;
height: 36px;
transform: translateX(-50%) rotate(-4deg);
background: rgba($yellow, 0.85);
border: 2px dashed rgba($ink, 0.25);
}
&__image {
width: 100%;
aspect-ratio: 4 / 3;
object-fit: cover;
object-position: center 33%;
border: 3px solid $ink;
}
&__caption {
position: absolute;
right: 16px;
bottom: 18px;
left: 16px;
font-size: 18px;
font-weight: 700;
font-style: italic;
text-align: center;
transform: rotate(-1deg);
}
}
+125
View File
@@ -0,0 +1,125 @@
@use "variables" as *;
.slate {
--slate-color: #{$red};
position: relative;
counter-increment: slate;
padding-top: 30px;
@each $name, $color in $colors {
&--#{$name} {
--slate-color: #{$color};
}
}
&__clapper {
position: absolute;
top: 0;
left: 0;
right: 0;
height: 34px;
border: 3px solid $ink;
border-radius: 10px 10px 0 0;
background: repeating-linear-gradient(
-55deg,
var(--slate-color) 0 20px,
$ink 20px 40px
);
transform-origin: 6px 100%;
transform: rotate(-9deg);
transition: transform 0.35s $ease;
// hinge
&::before {
content: "";
position: absolute;
left: 2px;
bottom: -9px;
width: 12px;
height: 12px;
border: 3px solid $ink;
border-radius: 50%;
background: $cream;
}
}
&:hover &__clapper {
animation: clap 0.45s $ease;
transform: rotate(0);
}
&__body {
position: relative;
padding: 28px 28px 28px;
border: 3px solid $ink;
border-radius: 0 0 $radius $radius;
background: $ink;
color: $beige;
box-shadow: 8px 8px 0 var(--slate-color);
transition: transform 0.35s $ease, box-shadow 0.35s $ease;
// top stripe bar of the slate body
&::before {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
height: 14px;
background: repeating-linear-gradient(
-55deg,
var(--slate-color) 0 20px,
$ink 20px 40px
);
border-bottom: 3px solid $ink;
}
// "SCENE 01"
&::after {
content: "Szene 0" counter(slate);
position: absolute;
top: 30px;
right: 24px;
font-family: ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace;
font-size: 12px;
letter-spacing: 0.12em;
text-transform: uppercase;
opacity: 0.6;
}
}
&:hover &__body {
transform: translate(-3px, -3px);
box-shadow: 12px 12px 0 var(--slate-color);
}
&__title {
margin-top: 12px;
font-size: clamp(32px, 4vw, 48px);
line-height: 1.1;
font-weight: 800;
letter-spacing: -0.03em;
color: var(--slate-color);
}
&__text {
margin-top: 8px;
font-size: 18px;
color: rgba($beige, 0.85);
}
// playful alternating tilt
&:nth-child(odd) { rotate: -1deg; }
&:nth-child(even) { rotate: 1deg; }
}
.service__items {
counter-reset: slate;
}
@keyframes clap {
0% { transform: rotate(-9deg); }
40% { transform: rotate(-22deg); }
70% { transform: rotate(1deg); }
100% { transform: rotate(0); }
}
+42
View File
@@ -0,0 +1,42 @@
@use "variables" as *;
.text {
padding: 72px 0 120px;
&__inner {
@include inner;
max-width: 880px;
}
h1 {
margin-bottom: 40px;
font-size: clamp(40px, 6vw, 72px);
line-height: 1;
font-weight: 800;
letter-spacing: -0.03em;
}
h2 {
display: inline-block;
margin: 40px 0 12px;
padding: 4px 12px;
background: $yellow;
border: 2px solid $ink;
border-radius: 8px;
font-size: 18px;
font-weight: 800;
transform: rotate(-1deg);
}
.text__date {
margin-top: 40px;
font-weight: 700;
}
p {
margin-bottom: 16px;
font-size: 17px;
line-height: 1.7;
hyphens: auto;
}
}
+55
View File
@@ -0,0 +1,55 @@
$beige: #f7eee0;
$cream: #fff8ec;
$yellow: #febb28;
$orange: #fe6732;
$red: #f5303c;
$pink: #ce2867;
$purple: #571b47;
$ink: #0e0c0b;
$grey: #3d3d3d;
$max-width: 1440px;
$radius: 20px;
$radius-lg: 32px;
$ease: cubic-bezier(0.34, 1.56, 0.64, 1); // springy
$ease-out: cubic-bezier(0.22, 1, 0.36, 1);
$shadow: 6px 6px 0 $ink;
$colors: (
"red": $red,
"orange": $orange,
"yellow": $yellow,
"pink": $pink,
"purple": $purple
);
@mixin sm {
@media (min-width: 640px) { @content; }
}
@mixin md {
@media (min-width: 900px) { @content; }
}
@mixin lg {
@media (min-width: 1200px) { @content; }
}
@mixin inner {
width: 100%;
max-width: $max-width;
margin: 0 auto;
padding: 0 20px;
@include md {
padding: 0 48px;
}
}
@mixin stripes($color, $size: 22px) {
background: repeating-linear-gradient(
-45deg,
$color 0 $size,
$ink $size ($size * 2)
);
}
+61
View File
@@ -0,0 +1,61 @@
@use "variables" as *;
.work {
position: relative;
padding: 0;
background: $ink;
color: $beige;
counter-reset: take;
&__inner {
@include inner;
padding-top: 120px;
padding-bottom: 120px;
@include md {
padding-top: 160px;
padding-bottom: 160px;
}
}
&__head {
display: grid;
gap: 24px;
margin-bottom: 80px;
@include md {
grid-template-columns: 1fr 1fr;
align-items: end;
}
}
&__text {
max-width: 46ch;
font-size: 20px;
color: rgba($beige, 0.8);
}
&__list {
display: grid;
gap: 96px;
@include md {
gap: 140px;
}
}
}
// sprocket holes
.filmstrip {
height: 44px;
background-color: $ink;
background-image: radial-gradient(ellipse 9px 8px at center, $beige 96%, transparent 100%);
background-size: 40px 20px;
background-position: 0 12px;
background-repeat: repeat-x;
animation: filmstrip 2s linear infinite;
}
@keyframes filmstrip {
to { background-position: 40px 12px; }
}
+95
View File
@@ -0,0 +1,95 @@
@use "variables" as *;
.take {
counter-increment: take;
display: grid;
gap: 32px;
align-items: center;
@include md {
grid-template-columns: 4fr 8fr;
gap: 64px;
&:nth-child(even) {
grid-template-columns: 8fr 4fr;
.take__info { order: 2; }
.take__screen { order: 1; transform: rotate(1.5deg); }
}
}
&__info {
position: relative;
}
&__label {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 6px 14px;
border: 2px solid $beige;
border-radius: 8px;
font-family: ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace;
font-size: 13px;
letter-spacing: 0.12em;
text-transform: uppercase;
&::after {
content: counter(take, decimal-leading-zero);
padding-left: 8px;
border-left: 2px solid $beige;
color: $yellow;
}
}
&__title {
margin-top: 20px;
font-size: clamp(32px, 4.5vw, 56px);
line-height: 1.05;
font-weight: 800;
letter-spacing: -0.03em;
}
&__text {
margin-top: 16px;
color: rgba($beige, 0.75);
}
&__screen {
position: relative;
aspect-ratio: 16 / 9;
border: 3px solid $beige;
border-radius: $radius;
overflow: hidden;
background: #000;
transform: rotate(-1.5deg);
transition: transform 0.5s $ease, box-shadow 0.5s $ease;
box-shadow: 10px 10px 0 var(--take-color, #{$red});
}
&:hover &__screen {
transform: rotate(0) scale(1.02);
box-shadow: 16px 16px 0 var(--take-color, #{$red});
}
&:nth-child(4n+1) { --take-color: #{$pink}; }
&:nth-child(4n+2) { --take-color: #{$red}; }
&:nth-child(4n+3) { --take-color: #{$orange}; }
&:nth-child(4n+4) { --take-color: #{$yellow}; }
&:hover &__title {
color: var(--take-color);
}
&__title {
transition: color 0.3s;
}
&__video {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
border: 0;
}
}
+13 -833
View File
@@ -1,833 +1,13 @@
@import "_reset.scss"; @use "reset";
@import "_grid.scss"; @use "base";
@use "nav";
* { @use "intro";
color: var(--black); @use "service";
box-sizing: border-box; @use "service_item";
} @use "process";
@use "work";
html { @use "work_entry";
color: var(--black); @use "faq";
font-family: "Manrope", -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; @use "insta";
@use "footer";
--white: #fff; @use "text";
--beige: #f7eee0;
--yellow: #febb28;
--orange: #fe6732;
--red: #f5303c;
--pink: #ce2867;
--purple: #571b47;
--grey: #3d3d3d;
--black: #000;
}
body {
background-color: var(--beige);
padding: 0 16px;
overflow-x: hidden;
}
section {
width: 100%;
}
h1, h2, h3, h4, p {
margin-block-start: 0;
margin-block-end: 0;
line-height: 1.5;
}
h1, h2, h3 {
font-weight: 500;
}
h1 {
font-size: 96px;
}
h2 {
font-size: 48px;
}
h3 {
font-size: 32px;
}
h4 {
font-size: 20px;
font-weight: 600;
}
p {
font-size: 18px;
font-weight: 500;
a {
color: var(--red);
&:hover {
text-decoration: underline;
}
}
}
button, a.button {
font-size: 18px;
font-weight: 600;
padding: 16px 24px;
cursor: pointer;
}
[data-aos="fade-in"] {
opacity: 0;
transition-property: opacity;
&.aos-animate {
opacity: 1;
}
}
.header {
position: sticky;
top: 0;
left: 0;
height: 64px;
width: 100%;
background-color: var(--beige);
z-index: 100;
-webkit-backface-visibility: hidden; /* Correct fixed positioning on mobile */
.container {
height: 100%;
display: flex;
align-items: center;
.wordmark {
font-size: 24px;
font-weight: 700;
cursor: pointer;
}
ul {
display: flex;
flex: 1;
justify-content: flex-end;
text-align: right;
li {
position: relative;
display: inline-flex;
margin-left: 48px;
font-size: 18px;
line-height: 1.5;
font-weight: 500;
color: inherit;
cursor: pointer;
&:nth-of-type(1):hover::before {
background-color: var(--pink);
}
&:nth-of-type(2):hover::before {
background-color: var(--red);
}
&:nth-of-type(3):hover::before {
background-color: var(--orange);
}
&:nth-of-type(4):hover::before {
background-color: var(--yellow);
}
&::before {
content: "";
position: absolute;
right: 0;
bottom: 0;
left: 0;
height: 2px;
z-index: -1;
}
}
}
}
}
.mobile-menu {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
display: flex;
justify-content: center;
align-items: center;
background-color: rgba(0, 0, 0, 0.8);
z-index: 1000;
animation: fade-in 0.2s ease-in-out;
-webkit-backface-visibility: hidden; /* Correct fixed positioning on mobile */
@keyframes fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
li {
color: white;
font-size: 24px;
line-height: 64px;
font-weight: 600;
text-align: center;
cursor: pointer;
}
}
.section-start {
padding: 128px 0;
.headline {
display: flex;
margin-bottom: 96px;
h1 {
flex: 1;
a {
color: var(--red);
}
}
img {
cursor: pointer;
}
}
.hero {
width: 100%;
max-width: 100%;
overflow: hidden;
z-index: 1;
video {
height: 100%;
min-height: 512px;
width: 100%;
object-fit: cover;
transform: scale(1.004);
}
}
}
.section-service {
padding: 128px 0;
h2 {
margin: 0 64px 24px 0;
padding-bottom: 12px;
border-bottom: 8px solid;
&:nth-of-type(1) {
border-color: var(--red);
}
&:nth-of-type(2) {
border-color: var(--orange);
}
&:nth-of-type(3) {
border-color: var(--yellow);
}
}
p {
margin: 0 64px 64px 0;
&:last-of-type {
margin-bottom: 16px;
}
}
.img {
min-height: 512px;
background-image: url("1.jpg");
background-position: center 33%;
background-size: cover;
overflow: hidden;
}
}
.section-work {
padding: 128px 0;
.video {
margin-bottom: 128px;
&:nth-of-type(4n+1) h2 {
border-color: var(--pink);
}
&:nth-of-type(4n+2) h2 {
border-color: var(--red);
}
&:nth-of-type(4n+3) h2 {
border-color: var(--orange);
}
&:nth-of-type(4n+4) h2 {
border-color: var(--yellow);
}
&:last-of-type {
margin-bottom: 0;
}
}
h2 {
margin: 0 0 24px 64px;
padding-bottom: 12px;
border-bottom: 8px solid;
}
p {
margin-left: 64px;
}
}
.section-rental {
padding: 128px 0;
.square {
position: sticky;
top: 128px;
padding: 32px 0;
h2 {
margin-bottom: 24px;
padding-bottom: 12px;
border-bottom: 8px solid var(--red);
}
button, a.button {
display: inline-flex;
color: var(--black);
margin: 48px 12px 0 0;
&:hover {
box-shadow: inset 0 0 0 2px var(--black);
}
&.primary {
background-color: var(--yellow);
}
&.secondary {
background-color: var(--orange);
}
}
}
.parts {
position: relative;
max-width: 720px;
min-height: 256px;
margin: 0 auto;
img:not(#part0) {
position: absolute;
filter: contrast(1.3) saturate(0.6);
}
#part0 {
width: 100%;
}
#part1 {
width: 29%;
left: 34%;
}
#part2 {
width: 8.5%;
left: 24%;
margin-top: 2%;
}
#part3 {
width: 19%;
left: 0;
margin-top: 2%;
}
#part4 {
width: 17%;
left: 2%;
margin-top: 15%;
}
#part5 {
width: 5.5%;
left: 70%;
margin-top: 9%;
}
#part6 {
width: 22%;
left: 64%;
margin-top: 1%;
}
#part7 {
width: 12.5%;
left: 87.5%;
margin-top: 1%;
}
#part8 {
width: 37%;
left: 30%;
margin-top: 28%;
}
#part9 {
width: 22.5%;
left: 0;
margin-top: 28%;
}
#part10 {
width: 49%;
left: 20%;
margin-top: 40%;
}
#part11 {
width: 14%;
left: 2%;
margin-top: 36%;
}
#part12 {
width: 27%;
left: 72%;
margin-top: 31%;
}
#part13 {
width: 24%;
left: 48%;
margin-top: 48%;
}
#part14 {
width: 19%;
left: 24%;
margin-top: 48%;
}
#part15 {
width: 4.5%;
left: 15%;
margin-top: 47%;
}
#part16 {
width: 24%;
left: 75%;
margin-top: 48%;
}
#part17 {
width: 7.5%;
left: 2%;
margin-top: 44%;
}
#part18-1 {
width: 14%;
left: 46%;
margin-top: 72%;
}
#part18-2 {
width: 14%;
left: 46%;
margin-top: 86%;
}
#part19-1 {
width: 17%;
left: 22%;
margin-top: 70%;
}
#part19-2 {
width: 17%;
left: 22%;
margin-top: 84%;
}
#part20 {
width: 13.5%;
left: 66%;
margin-top: 73%;
}
#part21 {
width: 11.5%;
left: 6%;
margin-top: 73%;
}
#part22 {
width: 13.5%;
left: 84%;
margin-top: 73%;
}
#part23 {
width: 28%;
left: 22%;
margin-top: 102%;
}
#part24 {
width: 37.5%;
left: 60%;
margin-top: 100%;
}
#part25 {
width: 20%;
left: 14%;
margin-top: 105%;
}
#part26 {
width: 40%;
left: 43%;
margin-top: 106%;
}
#part27 {
width: 22.5%;
left: 20%;
margin-top: 115%;
}
#part28 {
width: 22%;
left: 47%;
margin-top: 130%;
}
#part29-1 {
width: 3%;
left: 4%;
margin-top: 106%;
}
#part29-2 {
width: 3%;
left: 8%;
margin-top: 106%;
}
#part30 {
width: 19%;
left: 70%;
margin-top: 116%;
}
#part31-1 {
width: 2.5%;
left: 13%;
margin-top: 125.5%;
}
#part31-2 {
width: 2.5%;
left: 16%;
margin-top: 125.5%;
}
#part32-1 {
width: 2.75%;
left: 20%;
margin-top: 126%;
}
#part32-2 {
width: 2.75%;
left: 24%;
margin-top: 126%;
}
}
}
.section-instagram {
padding: 128px 0;
}
.section-text {
padding: 128px 0;
h3 {
margin-bottom: 32px;
}
h4 {
margin: 16px 0 12px;
}
p {
margin-bottom: 12px;
}
}
footer {
padding: 128px 0;
ul {
display: flex;
justify-content: space-between;
li {
font-size: 18px;
line-height: 1.5;
font-weight: 500;
&:first-of-type {
flex: 1;
}
&:not(:first-of-type) {
margin-left: 48px;
}
&:nth-of-type(4n+1):hover a::before {
background-color: var(--pink);
}
&:nth-of-type(4n+2):hover a::before {
background-color: var(--red);
}
&:nth-of-type(4n+3):hover a::before {
background-color: var(--orange);
}
&:nth-of-type(4n+4):hover a::before {
background-color: var(--yellow);
}
a {
position: relative;
&::before {
content: "";
position: absolute;
right: 0;
bottom: 0;
left: 0;
height: 2px;
z-index: -1;
}
}
}
}
}
@media only screen and (max-width: 1023px) {
h1 {
font-size: 64px;
}
h2 {
font-size: 36px;
}
h3 {
font-size: 24px;
}
h4 {
font-size: 20px;
}
.section-start {
padding: 64px 0;
.headline {
margin-bottom: 64px;
}
}
.section-service {
padding: 64px 0;
h2 {
margin: 0 32px 24px 0;
}
p {
margin: 0 32px 32px 0;
}
.img {
margin-top: 64px;
}
}
.section-work {
padding: 64px 0;
h2 {
margin: 0 0 24px 0;
}
p {
margin: 0 0 32px 0;
}
.video {
margin-bottom: 64px;
}
}
.section-rental {
padding: 64px 0;
overflow-x: hidden;
.square {
padding: 0;
h2 {
margin: 0 0 24px 0;
}
}
.parts {
margin: 64px 0 0;
overflow: hidden;
}
}
.section-instagram {
padding: 64px 0;
}
.section-text {
padding: 64px 0;
}
footer {
padding: 64px 0;
ul {
display: block;
li {
margin: 12px 0 !important;
}
}
}
}
@media only screen and (max-width: 767px) {
.section-service {
h2 {
margin: 0 0 16px 0;
padding-bottom: 8px;
}
p {
margin: 0 0 24px 0;
}
}
.section-work {
h2 {
margin: 0 0 16px 0;
padding-bottom: 8px;
}
p {
margin: 0 0 24px 0;
}
}
.section-rental {
.square h2 {
margin: 0 0 16px 0;
padding-bottom: 8px;
}
}
}
@media only screen and (max-width: 511px) {
h1 {
font-size: 40px;
}
h2 {
font-size: 32px;
}
.section-start {
padding: 32px 0 0;
.headline {
margin-bottom: 32px;
}
}
.section-service {
padding: 64px 0 0;
h2 {
border-bottom: 4px solid;
}
.img {
margin-top: 32px;
}
}
.section-work {
padding: 64px 0 0;
.video {
margin-bottom: 32px;
}
h2 {
border-bottom: 4px solid;
}
}
.section-rental {
padding: 64px 0 0;
.square h2 {
border-bottom: 4px solid var(--red);
}
}
.section-instagram {
padding: 32px 0;
}
.section-text {
padding: 32px 0;
}
footer {
padding: 32px 0;
}
}
+24 -6
View File
@@ -7,11 +7,29 @@
}, },
"content": { "content": {
"content": [ "content": [
{"ref": "head"}, {
{"ref": "nav"}, "ref": "head",
{"tpl": "terms"}, "copy": {
{"ref": "footer"}, "de": {
{"ref": "end"} "title": "AGB | Schnittfest GmbH – Videoproduktion & Verleih",
"meta_description": "Allgemeine Geschäftsbedingungen der Schnittfest GmbH für Videoproduktion und Geräteverleih (Rentalfest)."
}
}
},
{
"ref": "nav"
},
{
"tpl": "terms",
"content": {}
},
{
"ref": "footer"
},
{
"ref": "end"
}
] ]
} },
"display_name": "AGB"
} }
+287 -57
View File
@@ -1,4 +1,5 @@
{ {
"display_name": "Startseite",
"path": { "path": {
"de": "/" "de": "/"
}, },
@@ -7,127 +8,356 @@
}, },
"content": { "content": {
"content": [ "content": [
{"ref": "head"},
{ {
"ref": "nav", "ref": "head",
"data": { "copy": {
"full": true "de": {
"title": "Videoproduktion in OWL | Imagefilm & Animation | Schnittfest",
"meta_description": "Schnittfest ist eure Videoproduktion aus Rheda-Wiedenbrück bei Bielefeld: Imagefilme, Recruiting-Videos, Werbefilme, Animation und Sounddesign – vom Konzept bis zum fertigen Film."
}
} }
}, },
{ {
"tpl": "intro" "ref": "nav",
"data": {
"full": "on"
}
}, },
{ {
"tpl": "service" "tpl": "intro",
"copy": {
"de": {
"kicker": "Videoproduktion aus Ostwestfalen",
"headline": "Videoproduktion, auf den Frame genau.",
"text": "<div>Wir sind <strong>Schnittfest</strong> – eine Filmproduktion aus Rheda-Wiedenbrück bei Bielefeld. Wir produzieren <strong>Imagefilme, Werbefilme, Recruiting-Videos und Animationen</strong> für Unternehmen in OWL und ganz Deutschland.</div>",
"cta_label": "Projekt anfragen",
"cta_href": "mailto:hi@schnittfest.gmbh",
"ticker": "Imagefilm ✦ Werbefilm ✦ Recruiting-Video ✦ Animation ✦ Sounddesign ✦ Schnitt ✦ Color Grading ✦ Eventfilm ✦",
"video_label": "Showreel der Videoproduktion Schnittfest"
}
},
"data": {
"video": "/static/hero.mp4",
"poster": "/static/1.jpg"
},
"content": {}
},
{
"tpl": "service",
"copy": {
"de": {
"headline": "Film, Animation & Sound",
"text": "<div>Von der ersten Idee bis zum finalen Frame: Wir konzipieren, drehen, animieren und vertonen – alles aus einer Hand. So entsteht ein Video, das deine Marke, deine Produkte oder dein Team genau richtig in Szene setzt.</div>",
"image_caption": "Am Set, irgendwo zwischen Take 7 und 8."
}
},
"data": {
"image": "/static/1.jpg"
},
"content": {
"items": [
{
"tpl": "service_item",
"copy": {
"de": {
"title": "Film",
"text": "sleep.shoot.eat.repeat. Imagefilme, Unternehmensfilme, Werbespots und Eventfilme – mit Kamera, Licht und Crew, die wissen, was sie tun."
}
},
"data": {
"color": "red"
},
"content": {}
},
{
"tpl": "service_item",
"copy": {
"de": {
"title": "Animation",
"text": "Herausstechen und Welten erschaffen: Motion Design, 2D/3D-Animation und Erklärvideos, die Komplexes einfach machen."
}
},
"data": {
"color": "orange"
},
"content": {}
},
{
"tpl": "service_item",
"copy": {
"de": {
"title": "Sound",
"text": "Keine Musik wäre wie Butter ohne Brot. Sounddesign, Musik, Sprachaufnahmen und Mischung für den perfekten Ton."
}
},
"data": {
"color": "yellow"
},
"content": {}
}
]
}
},
{
"tpl": "process",
"copy": {
"de": {
"headline": "So entsteht dein Film",
"text": "<div>Klarer Ablauf, feste Ansprechpartner, keine Überraschungen – in vier Schritten zur fertigen Videoproduktion.</div>"
}
},
"content": {
"steps": [
{
"tpl": "process_step",
"copy": {
"de": {
"title": "Idee & Konzept",
"text": "Wir hören zu, entwickeln die Story und planen Drehbuch, Storyboard und Budget – damit dein Video ein klares Ziel hat."
}
},
"data": {
"color": "yellow"
},
"content": {}
},
{
"tpl": "process_step",
"copy": {
"de": {
"title": "Dreh & Produktion",
"text": "Kamera, Licht, Ton und Crew: Wir drehen im Studio, im Unternehmen oder on location – in OWL und überall sonst."
}
},
"data": {
"color": "orange"
},
"content": {}
},
{
"tpl": "process_step",
"copy": {
"de": {
"title": "Postproduktion",
"text": "Schnitt, Color Grading, Motion Design und Sounddesign machen aus dem Material einen Film, der hängen bleibt."
}
},
"data": {
"color": "red"
},
"content": {}
},
{
"tpl": "process_step",
"copy": {
"de": {
"title": "Auslieferung",
"text": "Du bekommst deinen Film in allen Formaten, die du brauchst – für Website, Social Media, Messe oder Kino."
}
},
"data": {
"color": "pink"
},
"content": {}
}
]
}
}, },
{ {
"tpl": "work", "tpl": "work",
"copy": {
"de": {
"headline": "Referenzen",
"text": "<div>Popcorn bereit? Eine Auswahl unserer Imagefilme, Commercials und Recruiting-Videos für Kunden wie Schüco, GOLDBECK und DansArt.</div>"
}
},
"content": { "content": {
"content": [ "content": [
{ {
"tpl": "work_entry", "tpl": "work_entry",
"copy": {
"de": {
"h2": "Imagefilm für Schüco: Bauprojekt in Schweden",
"text": "Für die Reihe „Schüco Stories“ haben wir ein Bauprojekt auf den Schären vor Stockholm filmisch begleitet – von der ersten Planung bis zum fertigen Gebäude. Eine Langzeit-Dokumentation als Imagefilm."
}
},
"data": { "data": {
"video": "https://www.youtube.com/embed/6z9AUO9lqBs" "video": "https://www.youtube.com/embed/6z9AUO9lqBs"
}, },
"copy": { "content": {}
"de": {
"h2": "Imagefilm",
"text": "Schüco Stories auf den schwedischen Inseln in der Nähe von Stockholm. Bei diesem Bauprojekt wurde das komplette vorhaben von der Entstehung bis zum fertigen Objekt filmisch begleitet."
}
}
}, },
{ {
"tpl": "work_entry", "tpl": "work_entry",
"copy": {
"de": {
"h2": "Kulturfilm für DansArt Bielefeld",
"text": "Seit 25 Jahren bringt DansArt Tanz und Kultur nach Bielefeld: vier Tanzstudios, ein eigenes Theater, Ausbildung, Festivals und Konzerte. Unser Artwork-Film fängt Bewegung, Musik und Atmosphäre ein."
}
},
"data": { "data": {
"video": "https://www.youtube.com/embed/AnKUnt_qr0I" "video": "https://www.youtube.com/embed/AnKUnt_qr0I"
}, },
"copy": { "content": {}
"de": {
"h2": "Artwork Film",
"text": "Seit 25 Jahren bringt DansArt Tanz und Kultur nach Bielefeld. In vier Tanzstudios und einem eigenen Theater finden regelmäßig Tanzkurse, Ausbildungen für angehende Profitänzer *innen, Festivals, Workshops, Performances und Konzerte statt."
}
}
}, },
{ {
"tpl": "work_entry", "tpl": "work_entry",
"copy": {
"de": {
"h2": "Unternehmensfilm für H.O. Schlüter Metallbau",
"text": "Neubau, Erweiterung und Renovation: Im Unternehmensfilm stellt sich der Metallbau H.O. Schlüter aus Lübz vor – Partner der Schüco International KG."
}
},
"data": { "data": {
"video": "https://www.youtube.com/embed/ZlRCoYe9G20" "video": "https://www.youtube.com/embed/ZlRCoYe9G20"
}, },
"copy": { "content": {}
"de": {
"h2": "Imagefilm",
"text": "Neubau, Erweiterung und Renovation – das sind die Geschäftsfelder von H.O. Schlüter. Die H.O. Schlüter GmbH arbeitet als Partner zusammen mit Schüco International KG. Der Metallbau Lübz stellt sich in diesem Unternehmensfilm vor."
}
}
}, },
{ {
"tpl": "work_entry", "tpl": "work_entry",
"copy": {
"de": {
"h2": "Commercial für ESDI EV Technologies",
"text": "ESDI EV baut Klassiker wie VW Käfer, Porsche und Mini Cooper mit Tesla- und Nissan-Leaf-Technik zu Elektrofahrzeugen um. Unser Werbefilm zeigt Handwerk, Technik und Fahrspaß."
}
},
"data": { "data": {
"video": "https://www.youtube.com/embed/nCNS4veOjBY" "video": "https://www.youtube.com/embed/nCNS4veOjBY"
}, },
"copy": { "content": {}
"de": {
"h2": "Commercial",
"text": "Umbauten von Verbrennern zu Elektrofahrzeugen. ESDI EV Technologies ist eine Firma, die sich auf dem Schwerpunkt Elektroumbauten fokussiert hat. Mit Tesla-Technik, Nissan Leaf oder vielen weiteren baut ESDI EV VW Käfer, Porsche, Mini Cooper und mehr zu Elektrofahrzeugen."
}
}
}, },
{ {
"tpl": "work_entry", "tpl": "work_entry",
"copy": {
"de": {
"h2": "Produktfilm für GOLDBECK & Schüco",
"text": "Wie entsteht ein Fenstersystem? Der Produktfilm zeigt Entwicklung und Fertigung des Schüco AWE80 für GOLDBECK in Bielefeld."
}
},
"data": { "data": {
"video": "https://www.youtube.com/embed/sFjOfG-5cIE" "video": "https://www.youtube.com/embed/sFjOfG-5cIE"
}, },
"copy": { "content": {}
"de": {
"h2": "Imagefilm",
"text": "GOLDBECK und Schüco, das Fenstersystem AWE80 in der Entwicklung und Produktion in Bielefeld."
}
}
}, },
{ {
"tpl": "work_entry", "tpl": "work_entry",
"copy": {
"de": {
"h2": "Porträtfilm: Digitalisierung im Metallbau",
"text": "Industrie 4.0 zum Anfassen: Ein Porträtfilm darüber, wie digitale Prozesse den Alltag im Metallbau verändern."
}
},
"data": { "data": {
"video": "https://www.youtube.com/embed/ktjlpv6j-Fw" "video": "https://www.youtube.com/embed/ktjlpv6j-Fw"
}, },
"copy": { "content": {}
"de": {
"h2": "Porträtfilm",
"text": "Digitalisierung im Metallbau - Die berühmt-berüchtigte Industrie 4.0."
}
}
}, },
{ {
"tpl": "work_entry", "tpl": "work_entry",
"copy": {
"de": {
"h2": "Recruiting-Video für Azubis im Metallbau",
"text": "Kampagne zur Ausbildungsplatz-Suche: Unser Recruitingfilm stellt die Berufe Metallbauer*in, Metalltechnik, Technische*r Systemplaner*in und Büromanagement vor – authentisch und auf Augenhöhe."
}
},
"data": { "data": {
"video": "https://www.youtube.com/embed/ji1ux1Y4Y_U" "video": "https://www.youtube.com/embed/ji1ux1Y4Y_U"
}, },
"copy": { "content": {}
"de": {
"h2": "Kampagne",
"text": "Azubi Recruiting Film für die Berufe Metallbauer, Metalltechnik, Technischer Systemplaner, Büromanagement."
}
}
}, },
{ {
"tpl": "work_entry", "tpl": "work_entry",
"copy": {
"de": {
"h2": "Eventfilm: Karrieretag Familienunternehmen",
"text": "50 führende Familienunternehmen wie Miele, Hilti und Haribo trafen sich bei Schüco in Bielefeld. Unser Eventfilm fasst den Karrieretag kompakt zusammen."
}
},
"data": { "data": {
"video": "https://www.youtube.com/embed/b8ASQboLCO8" "video": "https://www.youtube.com/embed/b8ASQboLCO8"
}, },
"copy": { "content": {}
"de": {
"h2": "Eventfilm",
"text": "50 führende Familienunternehmen aus ganz Deutschland zum Karrieretag Familienunternehmen in Bielefeld bei Schüco - Miele, Hilti, Haribo und viele mehr."
}
}
} }
] ]
} }
}, },
{ {
"tpl": "insta" "tpl": "faq",
"copy": {
"de": {
"headline": "Häufige Fragen",
"text": "<div>Deine Frage ist nicht dabei? Schreib uns an <a href=\"mailto:hi@schnittfest.gmbh\">hi@schnittfest.gmbh</a>.</div>"
}
}, },
{"ref": "footer"}, "content": {
{"ref": "end"} "items": [
{
"tpl": "faq_item",
"copy": {
"de": {
"question": "Was kostet ein Imagefilm?",
"answer": "<div>Das hängt von Umfang, Drehtagen, Crew und Postproduktion ab. Nach einem kurzen Gespräch bekommst du von uns ein transparentes, unverbindliches Angebot, das zu deinem Budget passt.</div>"
}
},
"content": {}
},
{
"tpl": "faq_item",
"copy": {
"de": {
"question": "Wie lange dauert eine Videoproduktion?",
"answer": "<div>Von der ersten Idee bis zum fertigen Film vergehen meist einige Wochen. Kleinere Projekte wie Social-Media-Clips oder Eventfilme sind oft deutlich schneller fertig. Den genauen Zeitplan legen wir gemeinsam fest.</div>"
}
},
"content": {}
},
{
"tpl": "faq_item",
"copy": {
"de": {
"question": "Wo dreht ihr?",
"answer": "<div>Unser Sitz ist in Rheda-Wiedenbrück zwischen Gütersloh und Bielefeld. Wir drehen in ganz Ostwestfalen-Lippe, deutschlandweit und auch international.</div>"
}
},
"content": {}
},
{
"tpl": "faq_item",
"copy": {
"de": {
"question": "Welche Videoformate produziert ihr?",
"answer": "<div>Imagefilme, Unternehmens- und Produktfilme, Recruiting-Videos, Werbefilme und Commercials, Eventfilme sowie 2D/3D-Animation und Erklärvideos – inklusive Musik und Sounddesign.</div>"
}
},
"content": {}
},
{
"tpl": "faq_item",
"copy": {
"de": {
"question": "Bekomme ich auch Schnittfassungen für Social Media?",
"answer": "<div>Auf Wunsch liefern wir neben dem Hauptfilm kurze Cut-downs in Hoch- und Querformat für Instagram, LinkedIn, TikTok oder YouTube.</div>"
}
},
"content": {}
}
]
}
},
{
"tpl": "insta",
"copy": {
"de": {
"headline": "Hinter den Kulissen"
}
},
"content": {}
},
{
"ref": "footer"
},
{
"ref": "end"
}
] ]
} }
} }
+24 -6
View File
@@ -7,11 +7,29 @@
}, },
"content": { "content": {
"content": [ "content": [
{"ref": "head"}, {
{"ref": "nav"}, "ref": "head",
{"tpl": "legal"}, "copy": {
{"ref": "footer"}, "de": {
{"ref": "end"} "title": "Impressum & Datenschutz | Schnittfest GmbH",
"meta_description": "Impressum und Datenschutzerklärung der Schnittfest GmbH, Videoproduktion aus Rheda-Wiedenbrück."
}
}
},
{
"ref": "nav"
},
{
"tpl": "legal",
"content": {}
},
{
"ref": "footer"
},
{
"ref": "end"
}
] ]
} },
"display_name": "Impressum"
} }
+15 -1
View File
@@ -1,3 +1,17 @@
{ {
"tpl": "footer" "tpl": "footer",
"copy": {
"de": {
"headline": "Und… Action?",
"text": "Du planst einen Imagefilm, ein Recruiting-Video oder eine Kampagne? Erzähl uns von deiner Idee – wir machen einen Film daraus.",
"copyright": "Schnittfest GmbH · Videoproduktion in Rheda-Wiedenbrück"
}
},
"data": {
"email": "hi@schnittfest.gmbh",
"phone": "+49 176 93124984",
"instagram": "https://instagram.com/schnittfest.gmbh",
"vimeo": "https://vimeo.com/schnittfest"
},
"content": {}
} }
+11 -1
View File
@@ -1,3 +1,13 @@
{ {
"tpl": "head" "tpl": "head",
"copy": {
"de": {
"title": "Videoproduktion in OWL | Imagefilm & Animation | Schnittfest",
"meta_description": "Schnittfest ist eure Videoproduktion aus Rheda-Wiedenbrück bei Bielefeld: Imagefilme, Recruiting-Videos, Werbefilme, Animation und Sounddesign."
}
},
"data": {
"robots": "index,follow"
},
"content": {}
} }
+10 -1
View File
@@ -1,3 +1,12 @@
{ {
"tpl": "nav" "tpl": "nav",
"copy": {
"de": {
"service_label": "Leistungen",
"work_label": "Referenzen",
"faq_label": "FAQ",
"contact_label": "Kontakt"
}
},
"content": {}
} }
+5 -1
View File
@@ -6,7 +6,7 @@
"shutdown_time": 30, "shutdown_time": 30,
"write_timeout": 5, "write_timeout": 5,
"read_timeout": 5, "read_timeout": 5,
"hostname": "schnittfest.gmbh", "hostname": "https://schnittfest.gmbh",
"secure_cookies": false, "secure_cookies": false,
"cookie_domain_name": "schnittfest.gmbh" "cookie_domain_name": "schnittfest.gmbh"
}, },
@@ -34,5 +34,9 @@
"analytics": { "analytics": {
"provider": "pirsch", "provider": "pirsch",
"client_secret": "" "client_secret": ""
},
"ui": {
"path": "/admin",
"admin_password": "${ADMIN_PASSWORD}"
} }
} }
-51
View File
@@ -1,51 +0,0 @@
var controller = new ScrollMagic.Controller();
var backgroundColor = new TweenMax.to(".bg-beige-to-black", 1, {
backgroundColor: "#000",
ease: Power1.easeOut
});
var backgroundColorScene = new ScrollMagic.Scene({
triggerElement: "#work",
duration: 256
})
.setTween(backgroundColor)
.addTo(controller);
var textColor = new TweenMax.to(".black-to-beige", 1, {
color: "#f7eee0",
ease: Power1.easeOut
});
var textColorScene = new ScrollMagic.Scene({
triggerElement: "#work",
duration: 256
})
.setTween(textColor)
.addTo(controller);
var reverseBackgroundColor = new TweenMax.to(".bg-beige-to-black", 1, {
backgroundColor: "#f7eee0",
ease: Power1.easeOut
});
var reverseBackgroundColorScene = new ScrollMagic.Scene({
triggerElement: "#rental",
duration: 256
})
.setTween(reverseBackgroundColor)
.addTo(controller);
var reverseTextColor = new TweenMax.to(".black-to-beige", 1, {
color: "#000",
ease: Power1.easeOut
});
var reverseTextColorScene = new ScrollMagic.Scene({
triggerElement: "#rental",
duration: 256
})
.setTween(reverseTextColor)
.addTo(controller);
+1 -1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1 +1 @@
(()=>{console.log("Hi!");})(); (()=>{function a(t){let e=t.querySelector(".nav__toggle");if(!e)return;let n=o=>{t.classList.toggle("nav--open",o),e.setAttribute("aria-expanded",String(o))};e.addEventListener("click",()=>n(!t.classList.contains("nav--open"))),t.querySelectorAll(".nav__link").forEach(o=>o.addEventListener("click",()=>n(!1))),t.addEventListener("keydown",o=>{o.key==="Escape"&&(n(!1),e.focus())})}var i=null;function g(){return i||(i=new IntersectionObserver(t=>{t.forEach(e=>{e.isIntersecting&&(e.target.classList.add("is-visible"),i.unobserve(e.target))})},{rootMargin:"0px 0px -8% 0px",threshold:.05})),i}function c(t){if(!("IntersectionObserver"in window)){t.classList.add("is-visible");return}g().observe(t)}function s(t){return String(t).padStart(2,"0")}function d(t){let e=performance.now(),n=o=>{if(!t.isConnected)return;let r=Math.floor((o-e)/1e3*25),l=r%25,m=Math.floor(r/25)%60,v=Math.floor(r/1500)%60,p=Math.floor(r/(25*3600));t.textContent=`${s(p)}:${s(v)}:${s(m)}:${s(l)}`,requestAnimationFrame(n)};requestAnimationFrame(n)}document.documentElement.classList.add("js");var b={nav:a,reveal:c,timecode:d};function f(t=document){t.querySelectorAll("[data-component]").forEach(e=>{if(e.dataset.componentReady)return;let n=b[e.dataset.component];n&&(n(e),e.dataset.componentReady="true")})}document.readyState==="loading"?document.addEventListener("DOMContentLoaded",u):u();function u(){f(),new MutationObserver(()=>f()).observe(document.body,{childList:!0,subtree:!0})}})();
-532
View File
@@ -1,532 +0,0 @@
var offset = window.screen.width > 512 ? 20 : 10;
var controller = new ScrollMagic.Controller();
var part2 = new TweenMax.from("#part2", 1, {
x: "-100vw",
ease: Power2.easeOut
});
var part2Scene = new ScrollMagic.Scene({
triggerElement: "#parts",
duration: 250,
})
.setTween(part2)
.addTo(controller);
var part3 = new TweenMax.from("#part3", 1, {
x: "-100vw",
ease: Power2.easeOut
});
var part3Scene = new ScrollMagic.Scene({
triggerElement: "#parts",
duration: 250,
offset: this.offset
})
.setTween(part3)
.addTo(controller);
var part4 = new TweenMax.from("#part4", 1, {
x: "-100vw",
y: "100%",
rotation: 2,
ease: Power2.easeOut
});
var part4Scene = new ScrollMagic.Scene({
triggerElement: "#parts",
duration: 250,
offset: 2*this.offset
})
.setTween(part4)
.addTo(controller);
var part5 = new TweenMax.from("#part5", 1, {
x: "100vw",
ease: Power2.easeOut
});
var part5Scene = new ScrollMagic.Scene({
triggerElement: "#parts",
duration: 250
})
.setTween(part5)
.addTo(controller);
var part6 = new TweenMax.from("#part6", 1, {
x: "100vw",
ease: Power2.easeOut
});
var part6Scene = new ScrollMagic.Scene({
triggerElement: "#parts",
duration: 250,
offset: 3*this.offset
})
.setTween(part6)
.addTo(controller);
var part7 = new TweenMax.from("#part7", 1, {
x: "100vw",
ease: Power2.easeOut
});
var part7Scene = new ScrollMagic.Scene({
triggerElement: "#parts",
duration: 250,
offset: 4*this.offset
})
.setTween(part7)
.addTo(controller);
var part8 = new TweenMax.from("#part8", 1, {
x: "-100vw",
y: "200%",
rotation: 4,
ease: Power2.easeOut
});
var part8Scene = new ScrollMagic.Scene({
triggerElement: "#parts",
duration: 250,
offset: 5*this.offset
})
.setTween(part8)
.addTo(controller);
var part9 = new TweenMax.from("#part9", 1, {
x: "-100vw",
y: "300%",
rotation: 4,
ease: Power2.easeOut
});
var part9Scene = new ScrollMagic.Scene({
triggerElement: "#parts",
duration: 250,
offset: 5*this.offset
})
.setTween(part9)
.addTo(controller);
var part10 = new TweenMax.from("#part10", 1, {
x: "100vw",
y: "600%",
rotation: -12,
ease: Power2.easeOut
});
var part10Scene = new ScrollMagic.Scene({
triggerElement: "#parts",
duration: 250,
offset: 6*this.offset
})
.setTween(part10)
.addTo(controller);
var part11 = new TweenMax.from("#part11", 1, {
x: "-100vw",
y: "800%",
rotation: 12,
ease: Power2.easeOut
});
var part11Scene = new ScrollMagic.Scene({
triggerElement: "#parts",
duration: 250,
offset: 7*this.offset
})
.setTween(part11)
.addTo(controller);
var part12 = new TweenMax.from("#part12", 1, {
x: "100vw",
y: "500%",
rotation: -16,
ease: Power2.easeOut
});
var part12Scene = new ScrollMagic.Scene({
triggerElement: "#parts",
duration: 250,
offset: 8*this.offset
})
.setTween(part12)
.addTo(controller);
var part13 = new TweenMax.from("#part13", 1, {
x: "100vw",
y: "400%",
rotation: -24,
ease: Power2.easeOut
});
var part13Scene = new ScrollMagic.Scene({
triggerElement: "#parts",
duration: 250,
offset: 9*this.offset
})
.setTween(part13)
.addTo(controller);
var part14 = new TweenMax.from("#part14", 1, {
x: "-100vw",
y: "400%",
rotation: 24,
ease: Power2.easeOut
});
var part14Scene = new ScrollMagic.Scene({
triggerElement: "#parts",
duration: 250,
offset: 10*this.offset
})
.setTween(part14)
.addTo(controller);
var part15 = new TweenMax.from("#part15", 1, {
x: "-100vw",
y: "400%",
rotation: 24,
ease: Power2.easeOut
});
var part15Scene = new ScrollMagic.Scene({
triggerElement: "#parts",
duration: 250,
offset: 11*this.offset
})
.setTween(part15)
.addTo(controller);
var part16 = new TweenMax.from("#part16", 1, {
x: "100vw",
y: "400%",
rotation: 24,
ease: Power2.easeOut
});
var part16Scene = new ScrollMagic.Scene({
triggerElement: "#parts",
duration: 250,
offset: 12*this.offset
})
.setTween(part16)
.addTo(controller);
var part17 = new TweenMax.from("#part17", 1, {
x: "-100vw",
y: "400%",
rotation: 24,
ease: Power2.easeOut
});
var part17Scene = new ScrollMagic.Scene({
triggerElement: "#parts",
duration: 250,
offset: 13*this.offset
})
.setTween(part17)
.addTo(controller);
var part181 = new TweenMax.from("#part18-1", 1, {
x: "100vw",
y: "800%",
rotation: -32,
ease: Power2.easeOut
});
var part181Scene = new ScrollMagic.Scene({
triggerElement: "#parts",
duration: 250,
offset: 14*this.offset
})
.setTween(part181)
.addTo(controller);
var part182 = new TweenMax.from("#part18-2", 1, {
x: "100vw",
y: "800%",
rotation: -32,
ease: Power2.easeOut
});
var part182Scene = new ScrollMagic.Scene({
triggerElement: "#parts",
duration: 250,
offset: 15*this.offset
})
.setTween(part182)
.addTo(controller);
var part191 = new TweenMax.from("#part19-1", 1, {
x: "-100vw",
y: "800%",
rotation: 32,
ease: Power2.easeOut
});
var part191Scene = new ScrollMagic.Scene({
triggerElement: "#parts",
duration: 250,
offset: 16*this.offset
})
.setTween(part191)
.addTo(controller);
var part192 = new TweenMax.from("#part19-2", 1, {
x: "-100vw",
y: "800%",
rotation: 32,
ease: Power2.easeOut
});
var part192Scene = new ScrollMagic.Scene({
triggerElement: "#parts",
duration: 250,
offset: 18*this.offset
})
.setTween(part192)
.addTo(controller);
var part20 = new TweenMax.from("#part20", 1, {
x: "100vw",
y: "800%",
rotation: -32,
ease: Power2.easeOut
});
var part20Scene = new ScrollMagic.Scene({
triggerElement: "#parts",
duration: 250,
offset: 19*this.offset
})
.setTween(part20)
.addTo(controller);
var part21 = new TweenMax.from("#part21", 1, {
x: "-100vw",
y: "800%",
rotation: 32,
ease: Power2.easeOut
});
var part21Scene = new ScrollMagic.Scene({
triggerElement: "#parts",
duration: 250,
offset: 20*this.offset
})
.setTween(part21)
.addTo(controller);
var part22 = new TweenMax.from("#part22", 1, {
x: "100vw",
y: "800%",
rotation: -32,
ease: Power2.easeOut
});
var part22Scene = new ScrollMagic.Scene({
triggerElement: "#parts",
duration: 250,
offset: 21*this.offset
})
.setTween(part22)
.addTo(controller);
var part23 = new TweenMax.from("#part23", 1, {
x: "-100vw",
y: "2400%",
rotation: 32,
ease: Power2.easeOut
});
var part23Scene = new ScrollMagic.Scene({
triggerElement: "#parts",
duration: 250,
offset: 22*this.offset
})
.setTween(part23)
.addTo(controller);
var part24 = new TweenMax.from("#part24", 1, {
x: "100vw",
y: "1000%",
rotation: -32,
ease: Power2.easeOut
});
var part24Scene = new ScrollMagic.Scene({
triggerElement: "#parts",
duration: 250,
offset: 23*this.offset
})
.setTween(part24)
.addTo(controller);
var part25 = new TweenMax.from("#part25", 1, {
x: "-100vw",
y: "1000%",
rotation: 32,
ease: Power2.easeOut
});
var part25Scene = new ScrollMagic.Scene({
triggerElement: "#parts",
duration: 250,
offset: 24*this.offset
})
.setTween(part25)
.addTo(controller);
var part26 = new TweenMax.from("#part26", 1, {
x: "100vw",
y: "1000%",
rotation: -32,
ease: Power2.easeOut
});
var part26Scene = new ScrollMagic.Scene({
triggerElement: "#parts",
duration: 250,
offset: 25*this.offset
})
.setTween(part26)
.addTo(controller);
var part27 = new TweenMax.from("#part27", 1, {
x: "-100vw",
y: "1600%",
rotation: 32,
ease: Power2.easeOut
});
var part27Scene = new ScrollMagic.Scene({
triggerElement: "#parts",
duration: 250,
offset: 26*this.offset
})
.setTween(part27)
.addTo(controller);
var part28 = new TweenMax.from("#part28", 1, {
x: "100vw",
y: "1600%",
rotation: -32,
ease: Power2.easeOut
});
var part28Scene = new ScrollMagic.Scene({
triggerElement: "#parts",
duration: 250,
offset: 33*this.offset
})
.setTween(part28)
.addTo(controller);
var part291 = new TweenMax.from("#part29-1", 1, {
x: "-100vw",
y: "1600%",
rotation: 32,
ease: Power2.easeOut
});
var part291Scene = new ScrollMagic.Scene({
triggerElement: "#parts",
duration: 250,
offset: 32*this.offset
})
.setTween(part291)
.addTo(controller);
var part292 = new TweenMax.from("#part29-2", 1, {
x: "-100vw",
y: "1600%",
rotation: 32,
ease: Power2.easeOut
});
var part292Scene = new ScrollMagic.Scene({
triggerElement: "#parts",
duration: 250,
offset: 31*this.offset
})
.setTween(part292)
.addTo(controller);
var part30 = new TweenMax.from("#part30", 1, {
x: "100vw",
y: "1600%",
rotation: -32,
ease: Power2.easeOut
});
var part30Scene = new ScrollMagic.Scene({
triggerElement: "#parts",
duration: 250,
offset: 34*this.offset
})
.setTween(part30)
.addTo(controller);
var part311 = new TweenMax.from("#part31-1", 1, {
x: "-100vw",
y: "3200%",
rotation: 32,
ease: Power2.easeOut
});
var part311Scene = new ScrollMagic.Scene({
triggerElement: "#parts",
duration: 250,
offset: 30*this.offset
})
.setTween(part311)
.addTo(controller);
var part312 = new TweenMax.from("#part31-2", 1, {
x: "-100vw",
y: "3200%",
rotation: 32,
ease: Power2.easeOut
});
var part312Scene = new ScrollMagic.Scene({
triggerElement: "#parts",
duration: 250,
offset: 29*this.offset
})
.setTween(part312)
.addTo(controller);
var part321 = new TweenMax.from("#part32-1", 1, {
x: "-100vw",
y: "3200%",
rotation: 32,
ease: Power2.easeOut
});
var part321Scene = new ScrollMagic.Scene({
triggerElement: "#parts",
duration: 250,
offset: 28*this.offset
})
.setTween(part321)
.addTo(controller);
var part322 = new TweenMax.from("#part32-2", 1, {
x: "-100vw",
y: "3200%",
rotation: 32,
ease: Power2.easeOut
});
var part322Scene = new ScrollMagic.Scene({
triggerElement: "#parts",
duration: 250,
offset: 27*this.offset
})
.setTween(part322)
.addTo(controller);
+1
View File
@@ -1,2 +1,3 @@
{{ adminBody .Page }}
</body> </body>
</html> </html>
+15
View File
@@ -0,0 +1,15 @@
{{- $headline := default (copy .Page .Content "headline") "Häufige Fragen" -}}
{{- $text := copy .Page .Content "text" -}}
<section class="faq" id="faq" itemscope itemtype="https://schema.org/FAQPage" data-shifu-element="{{ .Content.Position }}">
<div class="faq__inner">
<div class="faq__head" data-component="reveal">
<h2 class="section-title"><span class="section-title__nr" aria-hidden="true"></span>{{ $headline }}</h2>
{{ with $text }}<div class="faq__text">{{ html . }}</div>{{ end }}
</div>
<div class="faq__list">
{{ .CMS.Render .Args .Page (fmt "%s.items" .Content.Position) (index .Content.Content "items") }}
<slot name="{{ .Content.Position }}/items"></slot>
</div>
</div>
</section>
+12
View File
@@ -0,0 +1,12 @@
{{- $question := copy .Page .Content "question" -}}
{{- $answer := copy .Page .Content "answer" -}}
<details class="faq-item" itemscope itemprop="mainEntity" itemtype="https://schema.org/Question" data-shifu-element="{{ .Content.Position }}">
<summary class="faq-item__question">
<h3 class="faq-item__title" itemprop="name">{{ default $question "Frage" }}</h3>
<span class="faq-item__icon" aria-hidden="true"></span>
</summary>
<div class="faq-item__answer" itemscope itemprop="acceptedAnswer" itemtype="https://schema.org/Answer">
{{ with $answer }}<div itemprop="text">{{ html . }}</div>{{ end }}
</div>
</details>
+32 -109
View File
@@ -1,111 +1,34 @@
<footer id="footer"> {{- $headline := default (copy .Page .Content "headline") "Und… Action?" -}}
<div class="container"> {{- $text := default (copy .Page .Content "text") "Erzähl uns von deiner Idee – wir machen einen Film daraus." -}}
<ul> {{- $email := default (get .Content "email") "hi@schnittfest.gmbh" -}}
<li>© 2024 Schnittfest GmbH</li> {{- $phone := default (get .Content "phone") "+49 176 93124984" -}}
<li> {{- $instagram := default (get .Content "instagram") "https://instagram.com/schnittfest.gmbh" -}}
<a href="tel:+49017693124984">+49 176 93124984</a> {{- $vimeo := default (get .Content "vimeo") "https://vimeo.com/schnittfest" -}}
</li> {{- $copyright := default (copy .Page .Content "copyright") "Schnittfest GmbH" -}}
<li>
<a href="mailto:hi@schnitffest.gmbh">hi@schnittfest.gmbh</a> <footer class="footer" id="footer" data-shifu-element="{{ .Content.Position }}">
</li> <div class="footer__inner">
<li> <div class="footer__cta" data-component="reveal">
<a href="https://instagram.com/schnittfest.gmbh">Instagram</a> <h2 class="footer__headline">{{ $headline }}</h2>
</li> <p class="footer__text">{{ $text }}</p>
<li> <a class="button button--yellow footer__mail" href="mailto:{{ $email }}">
<a href="https://vimeo.com/schnittfest">Vimeo</a> <span class="button__play" aria-hidden="true"></span>{{ $email }}
</li> </a>
<li> </div>
<a href="/agb" target="_blank">AGB</a>
</li> <dl class="credits" data-component="reveal">
<li> <div class="credits__row"><dt>Telefon</dt><dd><a href="tel:{{ $phone | replace " " "" }}">{{ $phone }}</a></dd></div>
<a href="/impressum" target="_blank">Impressum</a> <div class="credits__row"><dt>E-Mail</dt><dd><a href="mailto:{{ $email }}">{{ $email }}</a></dd></div>
</li> <div class="credits__row"><dt>Instagram</dt><dd><a href="{{ $instagram }}" target="_blank" rel="noopener">schnittfest.gmbh</a></dd></div>
</ul> <div class="credits__row"><dt>Vimeo</dt><dd><a href="{{ $vimeo }}" target="_blank" rel="noopener">schnittfest</a></dd></div>
</dl>
<div class="footer__bottom">
<span>© {{ year }} {{ $copyright }}</span>
<span class="footer__legal">
<a href="/agb">AGB</a>
<a href="/impressum">Impressum</a>
</span>
</div>
</div> </div>
</footer> </footer>
<script type="text/javascript" src="https://player.vimeo.com/api/player.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.0.4/gsap.min.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/ScrollMagic/2.0.7/ScrollMagic.min.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/ScrollMagic/2.0.7/plugins/animation.gsap.min.js"></script>
<script type="text/javascript" src="https://unpkg.com/aos@2.3.1/dist/aos.js"></script>
<script type="text/javascript" src="https://cdn.lightwidget.com/widgets/lightwidget.js"></script>
<script type="text/javascript" src="static/js/color.js"></script>
<script type="text/javascript" src="static/js/parts.js"></script>
<script>
AOS.init({
duration: 1000,
easing: "cubic-bezier(0.4, 0.0, 0.2, 1)",
once: true,
offset: 32
});
// navbar mobile menu
let mobileMenu = document.getElementById("mobileMenu");
let mobileMenuOpen = document.getElementById("mobileMenuOpen");
let mobileMenuClose = document.getElementById("mobileMenuClose");
function toggleMobileMenu() {
let hidden = mobileMenu.style.display;
if(hidden) {
mobileMenu.style.display = "";
} else {
mobileMenu.style.display = "none"
}
}
mobileMenuOpen.addEventListener("click", e => {
e.preventDefault();
e.stopPropagation();
toggleMobileMenu();
});
// scroll to from navigation
let scrollToService = document.getElementsByClassName("scrollToService");
let scrollToWork = document.getElementsByClassName("scrollToWork");
let scrollToRental = document.getElementsByClassName("scrollToRental");
let scrollToFooter = document.getElementsByClassName("scrollToFooter");
function addScrollTo(nodes, anchor) {
for (let node of nodes) {
node.addEventListener("click", () => {
toggleMobileMenu();
let section = document.getElementById(anchor);
if (window.location.pathname === "/") {
window.scrollTo({top: section.offsetTop, behavior: "smooth"});
} else {
window.location = `/#${anchor}`;
}
});
}
}
document.addEventListener("click", e => {
if(!mobileMenu.style.display) {
mobileMenu.style.display = "none"
}
});
addScrollTo(scrollToService, "service");
addScrollTo(scrollToWork, "work");
addScrollTo(scrollToRental, "rental");
addScrollTo(scrollToFooter, "footer");
// click on logo navigation
let homeLink = document.getElementById("homeLink");
homeLink.addEventListener("click", e => {
e.preventDefault();
e.stopPropagation();
if(window.location.pathname === "/") {
window.scrollTo({top: 0, behavior: "smooth"});
}
else {
window.location = "/";
}
});
</script>
+54 -25
View File
@@ -1,37 +1,66 @@
{{- $title := default (copy .Page .Content "title") "Videoproduktion in OWL | Imagefilm, Animation & Sound | Schnittfest" -}}
{{- $description := default (copy .Page .Content "meta_description") "Schnittfest ist eure Videoproduktion aus Rheda-Wiedenbrück bei Bielefeld: Imagefilme, Recruiting-Videos, Commercials, Animation und Sounddesign – vom Konzept bis zum fertigen Film." -}}
{{- $robots := default (get .Content "robots") "index,follow" -}}
<!DOCTYPE html> <!DOCTYPE html>
<html lang="de"> <html lang="{{ .Page.Language }}">
<head> <head>
<base href="/" /> <base href="/" />
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{{ $title }}</title>
<meta name="description" content="{{ $description }}" />
<meta name="robots" content="{{ $robots }}" />
<meta name="copyright" content="Schnittfest GmbH" /> <meta name="copyright" content="Schnittfest GmbH" />
<meta name="author" content="Emvi Software GmbH" /> <meta name="author" content="Emvi Software GmbH" />
<meta name="title" content="Schnittfest" /> <meta name="theme-color" content="#0e0c0b" />
<meta name="description" content="Film, auf den Frame genau." /> <meta name="msapplication-TileColor" content="#0e0c0b" />
<meta name="msapplication-TileColor" content="#000000" /> <meta name="apple-mobile-web-app-title" content="Schnittfest" />
<meta name="theme-color" content="#000000" /> <meta name="application-name" content="Schnittfest" />
<meta name="twitter:card" content="product" /> <meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="Schnittfest" /> <meta name="twitter:title" content="{{ $title }}" />
<meta name="twitter:description" content="Film, auf den Frame genau." /> <meta name="twitter:description" content="{{ $description }}" />
<meta name="twitter:image" content="/static/1.jpg" /> <meta name="twitter:image" content="{{ hostname }}/static/1.jpg" />
<meta property="og:url" content="https://schnittfest.gmbh/" /> <meta property="og:type" content="website" />
<meta property="og:title" content="Schnittfest" /> <meta property="og:locale" content="de_DE" />
<meta property="og:description" content="Film, auf den Frame genau." /> <meta property="og:site_name" content="Schnittfest" />
<meta property="og:image" content="/static/1.jpg" /> <meta property="og:url" content="{{ .Page.CanonicalLink }}" />
<meta property="og:title" content="{{ $title }}" />
<link rel="stylesheet" type="text/css" href="/static/web/main.css" /> <meta property="og:description" content="{{ $description }}" />
<link rel="stylesheet" type="text/css" href="/static/web/manrope.css" /> <meta property="og:image" content="{{ hostname }}/static/1.jpg" />
<link href="https://unpkg.com/aos@2.3.1/dist/aos.css" rel="stylesheet" /> <link rel="canonical" href="{{ .Page.CanonicalLink }}" />
<link rel="apple-touch-icon" sizes="180x180" href="/static/favicon/apple-touch-icon.png" /> <link rel="apple-touch-icon" sizes="180x180" href="/static/favicon/apple-touch-icon.png" />
<link rel="icon" type="image/png" sizes="32x32" href="/static/favicon/favicon-32x32.png" /> <link rel="icon" type="image/png" sizes="32x32" href="/static/favicon/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="/static/favicon/favicon-16x16.png" /> <link rel="icon" type="image/png" sizes="16x16" href="/static/favicon/favicon-16x16.png" />
<link rel="mask-icon" href="/static/favicon/safari-pinned-tab.svg" color="#000000" /> <link rel="mask-icon" href="/static/favicon/safari-pinned-tab.svg" color="#0e0c0b" />
<link rel="shortcut icon" href="/static/favicon/favicon.ico" /> <link rel="shortcut icon" href="/static/favicon/favicon.ico" />
<meta name="apple-mobile-web-app-title" content="Schnittfest" /> <link rel="preload" href="/static/fonts/Manrope-ExtraBold.woff2" as="font" type="font/woff2" crossorigin />
<meta name="application-name" content="Schnittfest" /> <link rel="stylesheet" href="/static/web/manrope.css" />
<meta name="msapplication-TileColor" content="#000000" /> <link rel="stylesheet" href="/static/web/main.css?v={{ .CMS.LastUpdate }}" />
<meta name="theme-color" content="#000000" /> <script defer src="/static/web/main.min.js?v={{ .CMS.LastUpdate }}"></script>
<script type="application/ld+json">
<title>Schnittfest</title> {
"@context": "https://schema.org",
"@type": "LocalBusiness",
"@id": "{{ hostname }}/#organization",
"name": "Schnittfest GmbH",
"description": "Videoproduktion für Imagefilme, Commercials, Animation und Sound in Ostwestfalen-Lippe.",
"url": "{{ hostname }}/",
"logo": "{{ hostname }}/static/favicon/favicon512x512.png",
"image": "{{ hostname }}/static/1.jpg",
"email": "hi@schnittfest.gmbh",
"telephone": "+49 5242 9079902",
"address": {
"@type": "PostalAddress",
"streetAddress": "Schulbusch 29",
"postalCode": "33378",
"addressLocality": "Rheda-Wiedenbrück",
"addressRegion": "NRW",
"addressCountry": "DE"
},
"areaServed": ["Rheda-Wiedenbrück", "Gütersloh", "Bielefeld", "Ostwestfalen-Lippe", "Deutschland"],
"sameAs": ["https://instagram.com/schnittfest.gmbh", "https://vimeo.com/schnittfest"]
}
</script>
{{ adminHead .Page }}
</head> </head>
<body class="bg-beige-to-black"> <body>
+13 -3
View File
@@ -1,6 +1,16 @@
<section id="instagram" class="section-instagram"> {{- $headline := default (copy .Page .Content "headline") "Hinter den Kulissen" -}}
<div class="container"> {{- $widget := default (get .Content "widget") "https://cdn.lightwidget.com/widgets/ea2fc8dd2d55541dbafdc312dbe28aee.html" -}}
{{- $profile := default (get .Content "profile") "https://instagram.com/schnittfest.gmbh" -}}
<section class="insta" id="instagram" data-shifu-element="{{ .Content.Position }}">
<div class="insta__inner">
<div class="insta__head" data-component="reveal">
<h2 class="section-title"><span class="section-title__nr" aria-hidden="true"></span>{{ $headline }}</h2>
<a class="button button--outline" href="{{ $profile }}" target="_blank" rel="noopener">@schnittfest.gmbh</a>
</div>
<div class="insta__frame" data-component="reveal">
<script src="https://cdn.lightwidget.com/widgets/lightwidget.js"></script> <script src="https://cdn.lightwidget.com/widgets/lightwidget.js"></script>
<iframe src="https://cdn.lightwidget.com/widgets/ea2fc8dd2d55541dbafdc312dbe28aee.html" scrolling="no" allowtransparency="true" class="lightwidget-widget" style="width:100%;border:0;overflow:hidden;"></iframe> <iframe src="{{ $widget }}" scrolling="no" allowtransparency="true" class="lightwidget-widget insta__widget" title="Instagram"></iframe>
</div>
</div> </div>
</section> </section>
+36 -9
View File
@@ -1,13 +1,40 @@
<section id="start" class="section-start"> {{- $headline := default (copy .Page .Content "headline") "Film, auf den Frame genau." -}}
<div class="container" data-aos="fade-in"> {{- $kicker := default (copy .Page .Content "kicker") "Videoproduktion aus Ostwestfalen" -}}
<div class="headline"> {{- $text := copy .Page .Content "text" -}}
<h1 data-aos="fade-in"> {{- $ctaLabel := default (copy .Page .Content "cta_label") "Projekt anfragen" -}}
Film, auf den Frame genau. {{- $ctaHref := default (copy .Page .Content "cta_href") "mailto:hi@schnittfest.gmbh" -}}
<a href="mailto:hi@schnittfest.gmbh">&#10230;</a> {{- $ticker := default (copy .Page .Content "ticker") "Film ✦ Animation ✦ Sound ✦ Schnitt ✦ Color Grading ✦ Drohne ✦ Imagefilm ✦ Commercial ✦" -}}
</h1> {{- $video := default (get .Content "video") "/static/hero.mp4" -}}
{{- $poster := default (get .Content "poster") "/static/1.jpg" -}}
<section class="intro" id="start" data-shifu-element="{{ .Content.Position }}">
<div class="intro__inner">
<p class="intro__kicker" data-component="reveal"><span class="intro__kicker-dot"></span>{{ $kicker }}</p>
<h1 class="intro__headline" data-component="reveal">{{ $headline }}</h1>
{{ with $text }}<div class="intro__text" data-component="reveal">{{ html . }}</div>{{ end }}
<div class="intro__cta" data-component="reveal">
<a class="button button--yellow" href="{{ $ctaHref }}"><span class="button__play" aria-hidden="true"></span>{{ $ctaLabel }}</a>
</div> </div>
<div id="hero" class="hero" data-aos="fade-in" data-aos-delay="200">
<video src="/static/hero.mp4" autoplay loop muted playsinline /> <div class="viewfinder" data-component="reveal">
<video class="viewfinder__video" aria-label="{{ default (copy .Page .Content "video_label") "Showreel von Schnittfest" }}" src="{{ $video }}" poster="{{ $poster }}" autoplay loop muted playsinline></video>
<div class="viewfinder__overlay" aria-hidden="true">
<span class="viewfinder__corner viewfinder__corner--tl"></span>
<span class="viewfinder__corner viewfinder__corner--tr"></span>
<span class="viewfinder__corner viewfinder__corner--bl"></span>
<span class="viewfinder__corner viewfinder__corner--br"></span>
<span class="viewfinder__rec">REC</span>
<span class="viewfinder__timecode" data-component="timecode">00:00:00:00</span>
<span class="viewfinder__meta">4K · 25 FPS · ISO 800</span>
<span class="viewfinder__battery"></span>
<span class="viewfinder__focus"></span>
</div>
</div>
</div>
<div class="ticker" aria-hidden="true">
<div class="ticker__track">
<span>{{ $ticker }}</span><span>{{ $ticker }}</span><span>{{ $ticker }}</span><span>{{ $ticker }}</span>
</div> </div>
</div> </div>
</section> </section>
+8 -8
View File
@@ -1,6 +1,6 @@
<section id="text" class="section-text"> <section class="text" id="text" data-shifu-element="{{ .Content.Position }}">
<div class="container"> <div class="text__inner">
<h3>Impressum</h3> <h1>Impressum &amp; Datenschutz</h1>
<p> <p>
Schnittfest GmbH<br> Schnittfest GmbH<br>
Schulbusch 29<br> Schulbusch 29<br>
@@ -16,22 +16,22 @@
Verantwortlich für den Inhalt nach § 55 Abs. 2 RStV ist:<br> Verantwortlich für den Inhalt nach § 55 Abs. 2 RStV ist:<br>
Geschäftsführer: Malte Blum Geschäftsführer: Malte Blum
</p> </p>
<h4>Datenschutz</h4> <h2>Datenschutz</h2>
<p> <p>
Die Betreiber dieser Seiten nehmen den Schutz Ihrer persönlichen Daten sehr ernst. Wir behandeln Ihre personenbezogenen Daten vertraulich und entsprechend der gesetzlichen Datenschutzvorschriften sowie dieser Datenschutzerklärung.Die Nutzung un serer Webseite ist in der Regel ohne Angabe personenbezogener Daten möglich. Soweit auf unseren Seiten personenbezogene Daten (beispielsweise Name, Anschrift oder E-Mail-Adressen) erhoben werden, erfolgt dies, soweit möglich, stets auf freiwilliger Basis. Diese Daten werden ohne Ihre ausdrückliche Zustimmung nicht an Dritte weitergegeben.Wir weisen darauf hin, dass die Datenübertragung im Internet (z.B. bei der Kommunikation per E-Mail) Sicherheitslücken aufweisen kann. Ein lückenloser Schutz der Daten vor dem Zugriff durch Dritte ist nicht möglich. Die Betreiber dieser Seiten nehmen den Schutz Ihrer persönlichen Daten sehr ernst. Wir behandeln Ihre personenbezogenen Daten vertraulich und entsprechend der gesetzlichen Datenschutzvorschriften sowie dieser Datenschutzerklärung.Die Nutzung un serer Webseite ist in der Regel ohne Angabe personenbezogener Daten möglich. Soweit auf unseren Seiten personenbezogene Daten (beispielsweise Name, Anschrift oder E-Mail-Adressen) erhoben werden, erfolgt dies, soweit möglich, stets auf freiwilliger Basis. Diese Daten werden ohne Ihre ausdrückliche Zustimmung nicht an Dritte weitergegeben.Wir weisen darauf hin, dass die Datenübertragung im Internet (z.B. bei der Kommunikation per E-Mail) Sicherheitslücken aufweisen kann. Ein lückenloser Schutz der Daten vor dem Zugriff durch Dritte ist nicht möglich.
</p> </p>
<h4>Cookies</h4> <h2>Cookies</h2>
<p> <p>
Die Internetseiten verwenden teilweise so genannte Cookies. Cookies richten auf Ihrem Rechner keinen Schaden an und enthalten keine Viren. Cookies dienen dazu, unser Angebot nutzerfreundlicher, effektiver und sicherer zu machen. Cookies sind kleine Textdateien, die auf Ihrem Rechner abgelegt werden und die Ihr Browser speichert. Die meisten der von uns verwendeten Cookies sind so genannte „Session-Cookies“. Sie werden nach Ende Ihres Besuchs automatisch ge löscht. Andere Cookies bleiben auf Ihrem Endgerät gespeichert, bis Sie diese löschen. Diese Cookies ermöglichen es uns, Ihren Browser beim nächsten Besuch wiederzuerkennen.Sie können Ihren Browser so einstellen, dass Sie über das Setzen von Cookies informiert werden und Cookies nur im Einzelfall erlauben, die Annahme von Cookies für bestimmte Fälle oder generell ausschließen sowie das automatische Löschen der Cookies beim Schließen des Browser aktivieren. Bei der Deaktivierung von Cookies kann die Funktionalität dieser Website eingeschränkt sein. Die Internetseiten verwenden teilweise so genannte Cookies. Cookies richten auf Ihrem Rechner keinen Schaden an und enthalten keine Viren. Cookies dienen dazu, unser Angebot nutzerfreundlicher, effektiver und sicherer zu machen. Cookies sind kleine Textdateien, die auf Ihrem Rechner abgelegt werden und die Ihr Browser speichert. Die meisten der von uns verwendeten Cookies sind so genannte „Session-Cookies“. Sie werden nach Ende Ihres Besuchs automatisch ge löscht. Andere Cookies bleiben auf Ihrem Endgerät gespeichert, bis Sie diese löschen. Diese Cookies ermöglichen es uns, Ihren Browser beim nächsten Besuch wiederzuerkennen.Sie können Ihren Browser so einstellen, dass Sie über das Setzen von Cookies informiert werden und Cookies nur im Einzelfall erlauben, die Annahme von Cookies für bestimmte Fälle oder generell ausschließen sowie das automatische Löschen der Cookies beim Schließen des Browser aktivieren. Bei der Deaktivierung von Cookies kann die Funktionalität dieser Website eingeschränkt sein.
</p> </p>
<h4>Kontaktformular</h4> <h2>Kontaktformular</h2>
<p> <p>
Wenn Sie uns per Kontaktformular Anfragen zukommen lassen, werden Ihre Angaben aus dem Anfrageformular inklusive der von Ihnen dort angegebenen Kontaktdaten zwecks Bearbeitung der Anfrage und für den Fall von Anschlussfragen bei uns gespeichert. Diese Daten geben wir nicht ohne Ihre Einwilligung weiter. Wenn Sie uns per Kontaktformular Anfragen zukommen lassen, werden Ihre Angaben aus dem Anfrageformular inklusive der von Ihnen dort angegebenen Kontaktdaten zwecks Bearbeitung der Anfrage und für den Fall von Anschlussfragen bei uns gespeichert. Diese Daten geben wir nicht ohne Ihre Einwilligung weiter.
</p> </p>
<h4>SSL-Verschlüsselung</h4> <h2>SSL-Verschlüsselung</h2>
<p> <p>
Diese Seite nutzt aus Gründen der Sicherheit und zum Schutz der Übertragung vertraulicher Inhalte, wie zum Beispiel der Anfragen, die Sie an uns als Seitenbetreiber senden, eine SSL-Verschlüsselung. Eine verschlüsselte Verbindung erkennen Sie daran, dass die Adresszeile des Browsers von "http://" auf "https://" wechselt und an dem Schloss-Symbol in Ihrer Browserzeile. Wenn die SSL-Verschlüsselung aktiviert ist, können die Daten, die Sie an uns übermitteln, nicht von Dritten mitgelesen werden. Diese Seite nutzt aus Gründen der Sicherheit und zum Schutz der Übertragung vertraulicher Inhalte, wie zum Beispiel der Anfragen, die Sie an uns als Seitenbetreiber senden, eine SSL-Verschlüsselung. Eine verschlüsselte Verbindung erkennen Sie daran, dass die Adresszeile des Browsers von "http://" auf "https://" wechselt und an dem Schloss-Symbol in Ihrer Browserzeile. Wenn die SSL-Verschlüsselung aktiviert ist, können die Daten, die Sie an uns übermitteln, nicht von Dritten mitgelesen werden.
</p> </p>
<h4>Stand: 24.05.2020</h4> <p class="text__date">Stand: 24.05.2020</p>
</div> </div>
</section> </section>
+25 -26
View File
@@ -1,28 +1,27 @@
{{if get .Content "full"}} {{- $full := get .Content "full" -}}
<nav class="header bg-beige-to-black"> {{- $service := default (copy .Page .Content "service_label") "Leistungen" -}}
<div class="container" data-aos="fade-in" data-aos-delay="50"> {{- $work := default (copy .Page .Content "work_label") "Referenzen" -}}
<div id="homeLink" class="wordmark black-to-beige">Schnittfest</div> {{- $faq := default (copy .Page .Content "faq_label") "FAQ" -}}
<ul class="links hide-sm black-to-beige"> {{- $contact := default (copy .Page .Content "contact_label") "Kontakt" -}}
<li class="scrollToService">Gewerke</li>
<li class="scrollToWork">Referenzen</li> <header class="nav{{ if $full }} nav--full{{ end }}" data-component="nav" data-shifu-element="{{ .Content.Position }}">
<li class="scrollToFooter">Kontakt</li> <div class="nav__inner">
</ul> <a class="nav__brand" href="/" aria-label="Schnittfest – Startseite">
<ul class="show-sm black-to-beige"> <span class="nav__rec" aria-hidden="true"></span>
<li id="mobileMenuOpen">Menü</li> <span class="nav__word">Schnitt<span class="nav__cut" aria-hidden="true">✂</span>fest</span>
</ul> </a>
</div> {{ if $full }}
</nav> <button class="nav__toggle" type="button" aria-expanded="false" aria-controls="nav-links">
<nav id="mobileMenu" class="mobile-menu show-sm" style="display:none"> <span class="nav__toggle-bar"></span>
<ul> <span class="nav__toggle-bar"></span>
<li class="scrollToService">Gewerke</li> <span class="visually-hidden">Menü</span>
<li class="scrollToWork">Referenzen</li> </button>
<li class="scrollToFooter">Kontakt</li> <nav class="nav__links" id="nav-links">
</ul> <a class="nav__link" href="/#service" data-nr="01">{{ $service }}</a>
</nav> <a class="nav__link" href="/#work" data-nr="02">{{ $work }}</a>
{{else}} <a class="nav__link" href="/#faq" data-nr="03">{{ $faq }}</a>
<nav class="header bg-beige"> <a class="nav__link nav__link--cta" href="/#footer" data-nr="04">{{ $contact }}</a>
<div class="container">
<div id="homeLink" class="wordmark">Schnittfest</div>
</div>
</nav> </nav>
{{ end }} {{ end }}
</div>
</header>
+15
View File
@@ -0,0 +1,15 @@
{{- $headline := default (copy .Page .Content "headline") "Ablauf" -}}
{{- $text := copy .Page .Content "text" -}}
<section class="process" id="ablauf" data-shifu-element="{{ .Content.Position }}">
<div class="process__inner">
<div class="process__head" data-component="reveal">
<h2 class="section-title"><span class="section-title__nr" aria-hidden="true"></span>{{ $headline }}</h2>
{{ with $text }}<div class="process__text">{{ html . }}</div>{{ end }}
</div>
<ol class="process__steps">
{{ .CMS.Render .Args .Page (fmt "%s.steps" .Content.Position) (index .Content.Content "steps") }}
<slot name="{{ .Content.Position }}/steps"></slot>
</ol>
</div>
</section>
+9
View File
@@ -0,0 +1,9 @@
{{- $title := copy .Page .Content "title" -}}
{{- $text := copy .Page .Content "text" -}}
{{- $color := default (get .Content "color") "yellow" -}}
<li class="frame frame--{{ $color }}" data-component="reveal" data-shifu-element="{{ .Content.Position }}">
<span class="frame__nr" aria-hidden="true"></span>
{{ with $title }}<h3 class="frame__title">{{ . }}</h3>{{ end }}
{{ with $text }}<p class="frame__text">{{ . }}</p>{{ end }}
</li>
+19 -11
View File
@@ -1,15 +1,23 @@
<section id="service" class="section-service bg-beige"> {{- $headline := default (copy .Page .Content "headline") "Gewerke" -}}
<div class="container"> {{- $text := copy .Page .Content "text" -}}
<div class="services row"> {{- $image := default (get .Content "image") "/static/1.jpg" -}}
<div class="col-lg-4 col-md-4 col-sm-12 col-xs-12"> {{- $caption := copy .Page .Content "image_caption" -}}
<h2 class="black-to-beige" data-aos="fade-in" data-aos-delay="100">Film</h2>
<p class="black-to-beige" data-aos="fade-in" data-aos-delay="100">sleep.shoot.eat.repeat. </p> <section class="service" id="service" data-shifu-element="{{ .Content.Position }}">
<h2 class="black-to-beige" data-aos="fade-in" data-aos-delay="200">Animation</h2> <div class="service__inner">
<p class="black-to-beige" data-aos="fade-in" data-aos-delay="200">Herausstechen und Welten erschaffen.</p> <div class="service__head" data-component="reveal">
<h2 class="black-to-beige" data-aos="fade-in" data-aos-delay="300">Sound</h2> <h2 class="section-title"><span class="section-title__nr" aria-hidden="true"></span>{{ $headline }}</h2>
<p class="black-to-beige" data-aos="fade-in" data-aos-delay="300">Keine Musik wäre wie Butter ohne Brot.</p> {{ with $text }}<div class="service__text">{{ html . }}</div>{{ end }}
</div> </div>
<div class="col-lg-8 col-md-8 col-sm-12 col-xs-12 img" data-aos="fade-in"></div> <div class="service__grid">
<div class="service__items">
{{ .CMS.Render .Args .Page (fmt "%s.items" .Content.Position) (index .Content.Content "items") }}
<slot name="{{ .Content.Position }}/items"></slot>
</div>
<figure class="polaroid" data-component="reveal">
<img class="polaroid__image" src="{{ $image }}" alt="{{ default $caption "Schnittfest am Set" }}" loading="lazy" />
{{ with $caption }}<figcaption class="polaroid__caption">{{ . }}</figcaption>{{ end }}
</figure>
</div> </div>
</div> </div>
</section> </section>
+11
View File
@@ -0,0 +1,11 @@
{{- $title := copy .Page .Content "title" -}}
{{- $text := copy .Page .Content "text" -}}
{{- $color := default (get .Content "color") "red" -}}
<article class="slate slate--{{ $color }}" data-component="reveal" data-shifu-element="{{ .Content.Position }}">
<div class="slate__clapper" aria-hidden="true"></div>
<div class="slate__body">
{{ with $title }}<h3 class="slate__title">{{ . }}</h3>{{ end }}
{{ with $text }}<p class="slate__text">{{ . }}</p>{{ end }}
</div>
</article>
+18 -18
View File
@@ -1,6 +1,6 @@
<section id="text" class="section-text"> <section class="text" id="text" data-shifu-element="{{ .Content.Position }}">
<div class="container"> <div class="text__inner">
<h3>Allgemeine Geschäftsbedingungen</h3> <h1>Allgemeine Geschäftsbedingungen</h1>
<p> <p>
Alle Preise verstehen sich zzgl. 3% Versicherungsgebühr und 19% MwSt. Alle Preise verstehen sich zzgl. 3% Versicherungsgebühr und 19% MwSt.
</p> </p>
@@ -10,30 +10,30 @@
<p> <p>
Alle Lieferungen und Leistungen der Schnittfest GmbH erfolgen ausschließlich zu den nachstehenden Bedingungen. Dies gilt insbesondere auch im Falle entgegenstehender Bedingungen des Auftraggebers. Abweichungen sind nur gültig, wenn sie von uns schriftlich bestätigt werden. Alle Lieferungen und Leistungen der Schnittfest GmbH erfolgen ausschließlich zu den nachstehenden Bedingungen. Dies gilt insbesondere auch im Falle entgegenstehender Bedingungen des Auftraggebers. Abweichungen sind nur gültig, wenn sie von uns schriftlich bestätigt werden.
</p> </p>
<h4>1. Vertragsangebot, Vertragsschluss, Vertragsgegenstand</h4> <h2>1. Vertragsangebot, Vertragsschluss, Vertragsgegenstand</h2>
<p> <p>
Unsere Angebote sind grundsätzlich freibleibend, sofern nicht eine bestimmte Bindungsdauer zugesichert wird. Der Vertrag kommt erst zustande, wenn er durch uns schriftlich bestätigt oder mit der Erfüllung des Auftrags begonnen wurde. Es bleibt ausdrücklich vorbehalten, den Vertragsgegenstand durch einen solchen zu ersetzen, der Mietsache weitestgehend gleichwertig ist. Geringfügige Abweichungen bleiben vorbehalten Unsere Angebote sind grundsätzlich freibleibend, sofern nicht eine bestimmte Bindungsdauer zugesichert wird. Der Vertrag kommt erst zustande, wenn er durch uns schriftlich bestätigt oder mit der Erfüllung des Auftrags begonnen wurde. Es bleibt ausdrücklich vorbehalten, den Vertragsgegenstand durch einen solchen zu ersetzen, der Mietsache weitestgehend gleichwertig ist. Geringfügige Abweichungen bleiben vorbehalten
</p> </p>
<h4>2. Entgelt</h4> <h2>2. Entgelt</h2>
<p> <p>
Der Mietzins für die Mietsache neben Zubehör richtet sich nach der bei Vertragsabschluss gültigen Preisliste zuzüglich Umsatzsteuer und Versicherungsentgelt für die film- und/oder lichttechnische Ausrüstung in der jeweils geltenden Höhe. Einzel- und Pauschalpreise gelten pro Tag. Bei Gerätschaften mit Zubehör gilt der vollständige Listenpreis oder Pauschalpreis auch dann, wenn auf Wunsch des Mieters einzelne Zubehörstücke nicht mitgeliefert wurden. Der Mietzins für die Mietsache neben Zubehör richtet sich nach der bei Vertragsabschluss gültigen Preisliste zuzüglich Umsatzsteuer und Versicherungsentgelt für die film- und/oder lichttechnische Ausrüstung in der jeweils geltenden Höhe. Einzel- und Pauschalpreise gelten pro Tag. Bei Gerätschaften mit Zubehör gilt der vollständige Listenpreis oder Pauschalpreis auch dann, wenn auf Wunsch des Mieters einzelne Zubehörstücke nicht mitgeliefert wurden.
</p> </p>
<h4>3. Mietdauer</h4> <h2>3. Mietdauer</h2>
<p> <p>
Die Nutzungsbefugnis des Mieters erstreckt sich nur auf den vertraglich vereinbarten Zeitraum. Der Mietzins wird für den vollständigen Zeitraum der Mietzeit einschließlich An- und Ablieferung berechnet, spätestens aber ab dem Zeitpunkt der Bereitstellung im Lager Bielefeld oder der Versendung der Waren an den Mieter bis zur Rückgabe. Der Mietzins wird nach vollen Tagessätzen berechnet. Samstage, Sonntage und Feiertage werden voll berechnet. Eine Rückgabe der Geräte an diesen Tagen ist nach Vereinbarung möglich. Die Nutzungsbefugnis des Mieters erstreckt sich nur auf den vertraglich vereinbarten Zeitraum. Der Mietzins wird für den vollständigen Zeitraum der Mietzeit einschließlich An- und Ablieferung berechnet, spätestens aber ab dem Zeitpunkt der Bereitstellung im Lager Bielefeld oder der Versendung der Waren an den Mieter bis zur Rückgabe. Der Mietzins wird nach vollen Tagessätzen berechnet. Samstage, Sonntage und Feiertage werden voll berechnet. Eine Rückgabe der Geräte an diesen Tagen ist nach Vereinbarung möglich.
</p> </p>
<p> <p>
Wird die Mietsache nicht bis 10.00 Uhr am Rückgabetag an uns ausgehändigt, wird für den Tag der volle Mietzins berechnet, es sei denn, es ist eine spätere Rückgabe vereinbar oder der Mieter weist nach, dass kein oder ein geringerer Schaden entstanden ist. Nimmt der Mieter die Mietsache nicht ab, und teilt er uns dies nicht mindestens 2 Tage vor dem vereinbarten Vertragsbeginn mit, so hat er 50 % des auf die vertragliche Laufzeit entfallenden Mietzinses als Schadenersatz an uns zu zahlen, es sei denn, der Mieter weist nach, dass kein oder ein geringerer Schaden entstanden ist. Sollte uns die vertragsgemäße Übergabe der Geräte an den Kunden dadurch unmöglich werden, dass wir die Geräte ohne unser Verschulden von einem anderen Kunden verspätet oder beschädigt zurückerhalten, so werden wir von unserer Leistung frei und haften für Folgeschäden nur, soweit wir bei dem anderen Kunden deswegen evtl. Schadensersatzansprüche tatsächlich realisieren. Wird die Mietsache nicht bis 10.00 Uhr am Rückgabetag an uns ausgehändigt, wird für den Tag der volle Mietzins berechnet, es sei denn, es ist eine spätere Rückgabe vereinbar oder der Mieter weist nach, dass kein oder ein geringerer Schaden entstanden ist. Nimmt der Mieter die Mietsache nicht ab, und teilt er uns dies nicht mindestens 2 Tage vor dem vereinbarten Vertragsbeginn mit, so hat er 50 % des auf die vertragliche Laufzeit entfallenden Mietzinses als Schadenersatz an uns zu zahlen, es sei denn, der Mieter weist nach, dass kein oder ein geringerer Schaden entstanden ist. Sollte uns die vertragsgemäße Übergabe der Geräte an den Kunden dadurch unmöglich werden, dass wir die Geräte ohne unser Verschulden von einem anderen Kunden verspätet oder beschädigt zurückerhalten, so werden wir von unserer Leistung frei und haften für Folgeschäden nur, soweit wir bei dem anderen Kunden deswegen evtl. Schadensersatzansprüche tatsächlich realisieren.
</p> </p>
<h4>4. Ort</h4> <h2>4. Ort</h2>
<p> <p>
Der Mieter ist berechtigt, die Mietsache im Gebiet der Bundesrepublik Deutschland zu nutzen. Will er die Mietsache an einen außerhalb der Bundesr epublik Deutschland gelegenen Ort verbringen, so ist hierzu unsere schriftliche Einwilligung erforderlich. In Ländern, in denen Krieg, Bürgerkrieg, Unruhen oder dergleichen herrschen, oder bei sorgfältiger Prüfung zu erwarten ist, dass innerhalb der Mietze it derartiges auftritt, ist eine Verbringung nur dann möglich, wenn dies von uns schriftlich gestattet und von dem Mieter für den Fall eines Totalverlustes der Mietsache zuvor ausreichend Sicherheit bereitgestellt wird. In jedem sonstigen Falle haftet der Mieter in diesen Ländern für entstandene Schäden in voller Höhe. Der Mieter ist berechtigt, die Mietsache im Gebiet der Bundesrepublik Deutschland zu nutzen. Will er die Mietsache an einen außerhalb der Bundesr epublik Deutschland gelegenen Ort verbringen, so ist hierzu unsere schriftliche Einwilligung erforderlich. In Ländern, in denen Krieg, Bürgerkrieg, Unruhen oder dergleichen herrschen, oder bei sorgfältiger Prüfung zu erwarten ist, dass innerhalb der Mietze it derartiges auftritt, ist eine Verbringung nur dann möglich, wenn dies von uns schriftlich gestattet und von dem Mieter für den Fall eines Totalverlustes der Mietsache zuvor ausreichend Sicherheit bereitgestellt wird. In jedem sonstigen Falle haftet der Mieter in diesen Ländern für entstandene Schäden in voller Höhe.
</p> </p>
<h4>5. Transportgefahr</h4> <h2>5. Transportgefahr</h2>
<p> <p>
Der Mieter trägt grundsätzlich die Transportgefahr. Das gilt auch dann, wenn die Versendung durch uns oder von uns beauftragten Dritten vorgenommen wird. Die Kosten der Ve rsendung und der Rücksendung an unsere gewerbliche Niederlassung einschließlich der Kosten einer ordnungsgemäßen Verpackung trägt der Mieter. Wird die Mietsache vom Mieter ins Ausland verbracht oder auf dessen Wunsch von uns dorthin versandt, verpflichtet sich der Mieter zur ordnungsgemäßen Abwicklung des Zollverfahrens. Die daraus resultierenden Kosten trägt der Mieter. Der Mieter trägt grundsätzlich die Transportgefahr. Das gilt auch dann, wenn die Versendung durch uns oder von uns beauftragten Dritten vorgenommen wird. Die Kosten der Ve rsendung und der Rücksendung an unsere gewerbliche Niederlassung einschließlich der Kosten einer ordnungsgemäßen Verpackung trägt der Mieter. Wird die Mietsache vom Mieter ins Ausland verbracht oder auf dessen Wunsch von uns dorthin versandt, verpflichtet sich der Mieter zur ordnungsgemäßen Abwicklung des Zollverfahrens. Die daraus resultierenden Kosten trägt der Mieter.
</p> </p>
<h4>6. Pflichten des Mieters</h4> <h2>6. Pflichten des Mieters</h2>
<p> <p>
Der Mieter hat uns darüber zu informieren, für welchen Zweck er die Mietsache verwendet. Über Umstände, die unsere Interessen berühren, hat uns der Mieter unaufgefordert zu unterrichten. Der Mieter verpflichtet sich, die Mietsachen pfleglich und sachkundig zu behandeln. Vorbehaltlich des sachgerechten Austauschs von Leuchtmitteln ist der Mieter nicht berechtigt, an den Mietsachen Instandsetzungsarbeiten vorzunehmen. Der Mieter verpflichtet sich, unverzüglich nach Erhalt der Mietsachen zu prüfen, ob diese funktionstüchtig sind und der Bestellung entsprechen. Abweichungen hinsichtlich der Zahl, Art und Güte von der Bestellung, dem Lieferschein und / oder der Rechnung sind unverzüglich zu rügen. Transportschäden sind uns sofort, spätestens aber am 1. Werktag nach der Ablieferung an den Mieter anzuzeigen, da andernfalls der Verlust des Versicherungsschutzes droht. Der Mieter hat uns darüber zu informieren, für welchen Zweck er die Mietsache verwendet. Über Umstände, die unsere Interessen berühren, hat uns der Mieter unaufgefordert zu unterrichten. Der Mieter verpflichtet sich, die Mietsachen pfleglich und sachkundig zu behandeln. Vorbehaltlich des sachgerechten Austauschs von Leuchtmitteln ist der Mieter nicht berechtigt, an den Mietsachen Instandsetzungsarbeiten vorzunehmen. Der Mieter verpflichtet sich, unverzüglich nach Erhalt der Mietsachen zu prüfen, ob diese funktionstüchtig sind und der Bestellung entsprechen. Abweichungen hinsichtlich der Zahl, Art und Güte von der Bestellung, dem Lieferschein und / oder der Rechnung sind unverzüglich zu rügen. Transportschäden sind uns sofort, spätestens aber am 1. Werktag nach der Ablieferung an den Mieter anzuzeigen, da andernfalls der Verlust des Versicherungsschutzes droht.
</p> </p>
@@ -43,14 +43,14 @@
<p> <p>
Zusätzlich zum Mietpreis trägt der Kunde gemäß unserer Preisliste anteilig die Kosten der obligatorischen Sachversicherung für die Geräte. Der Mietpreis einschl. Versicherungspauschale erhöht sich um den jeweils gültigen Mehrwertsteuersatz. Erteilte Rechnungen sind ohne Abzug sofort fällig. Der Kunde gerät auch ohne weitere Mahnung spätestens 14 Tage nach dem Rechnungsdatum in Verzug . Wir sind berechtigt, die Übergabe der Geräte oder Waren von einer vollständigen Vorauszahlung abhängig zu machen. Zusätzlich zum Mietpreis trägt der Kunde gemäß unserer Preisliste anteilig die Kosten der obligatorischen Sachversicherung für die Geräte. Der Mietpreis einschl. Versicherungspauschale erhöht sich um den jeweils gültigen Mehrwertsteuersatz. Erteilte Rechnungen sind ohne Abzug sofort fällig. Der Kunde gerät auch ohne weitere Mahnung spätestens 14 Tage nach dem Rechnungsdatum in Verzug . Wir sind berechtigt, die Übergabe der Geräte oder Waren von einer vollständigen Vorauszahlung abhängig zu machen.
</p> </p>
<h4>8. Gebrauchsüberlassung an Dritte / Kündigungsrecht</h4> <h2>8. Gebrauchsüberlassung an Dritte / Kündigungsrecht</h2>
<p> <p>
Die Mietsache steht in unserem Alleineigentum. Es ist dem Mieter grundsätzlich untersagt, die Mietsachen entgeltlich oder unentgeltlich an Dritte zum Gebrauch zu überlassen, wenn nicht unsere schriftliche Einwilligung erteilt wurde. Wir sind berechtigt, den Vertrag sofort zu kündigen, wenn der Mieter die Mietsache unbefugt einem Drit ten überlässt. Gleichzeitig sind wir in diesem Fall zur sofortigen Inbesitznahme der Mietsache berechtigt. Unbeschadet der vorstehenden Regelung ist der Mieter im Falle der entgeltlichen Weitervermietung verpflichtet, die gesamten davon betroffenen Geräte seinem eigenen Versicherungsschutz zu unterstellen und die Schadensregulierung über seine eigene Versicherung vorzunehmen. In derartigen Fällen ist eine Inanspruchnahme unserer Versicherung ausgeschlossen. Die Mietsache steht in unserem Alleineigentum. Es ist dem Mieter grundsätzlich untersagt, die Mietsachen entgeltlich oder unentgeltlich an Dritte zum Gebrauch zu überlassen, wenn nicht unsere schriftliche Einwilligung erteilt wurde. Wir sind berechtigt, den Vertrag sofort zu kündigen, wenn der Mieter die Mietsache unbefugt einem Drit ten überlässt. Gleichzeitig sind wir in diesem Fall zur sofortigen Inbesitznahme der Mietsache berechtigt. Unbeschadet der vorstehenden Regelung ist der Mieter im Falle der entgeltlichen Weitervermietung verpflichtet, die gesamten davon betroffenen Geräte seinem eigenen Versicherungsschutz zu unterstellen und die Schadensregulierung über seine eigene Versicherung vorzunehmen. In derartigen Fällen ist eine Inanspruchnahme unserer Versicherung ausgeschlossen.
</p> </p>
<p> <p>
Der Mieter ist verpflichtet, bei Vollstreckungsmaß nahmen in die Mietsachen und bei Pfändungen auf die Eigentumsverhältnisse hinzuweisen. Er hat uns in diesen Fällen unverzüglich zu unterrichten. Hält der Mieter das vereinbarte Zahlungsziel nicht ein, sind wir berechtigt, den Vertrag fristlos zu kündigen u nd die sofortige Herausgabe der Mietsache zu verlangen. Für den Fall der Durchsetzung des Herausgabeanspruchs ermächtigt der Mieter unwiderruflich uns oder von uns beauftragte Dritte, jeden Raum zu betreten, in dem sich die Mietsache oder Teile davon dann befinden. Der Mieter verzichtet auf die Geltendmachung eines Zurückbehaltungsrechts, gleich aus welchem Rechtsgrunde. Der Mieter ist verpflichtet, bei Vollstreckungsmaß nahmen in die Mietsachen und bei Pfändungen auf die Eigentumsverhältnisse hinzuweisen. Er hat uns in diesen Fällen unverzüglich zu unterrichten. Hält der Mieter das vereinbarte Zahlungsziel nicht ein, sind wir berechtigt, den Vertrag fristlos zu kündigen u nd die sofortige Herausgabe der Mietsache zu verlangen. Für den Fall der Durchsetzung des Herausgabeanspruchs ermächtigt der Mieter unwiderruflich uns oder von uns beauftragte Dritte, jeden Raum zu betreten, in dem sich die Mietsache oder Teile davon dann befinden. Der Mieter verzichtet auf die Geltendmachung eines Zurückbehaltungsrechts, gleich aus welchem Rechtsgrunde.
</p> </p>
<h4>9. Haftung des Vermieters</h4> <h2>9. Haftung des Vermieters</h2>
<p> <p>
Für entstandene Schäden – gleich aus welchem Rechtsgrund – haften wir grundsätzlich nur bei Vorsatz oder grober Fahrlässigkeit. Bei der Verletzung von wesentlichen Vertragspflichten, bei Fehlen zugesicherter Eigenschaften sowie bei der Verletzung von Leben, Körper oder Gesundheit haften wir auch dann, wenn der Schaden leicht fahrlässig verursacht wurde. Für entstandene Schäden – gleich aus welchem Rechtsgrund – haften wir grundsätzlich nur bei Vorsatz oder grober Fahrlässigkeit. Bei der Verletzung von wesentlichen Vertragspflichten, bei Fehlen zugesicherter Eigenschaften sowie bei der Verletzung von Leben, Körper oder Gesundheit haften wir auch dann, wenn der Schaden leicht fahrlässig verursacht wurde.
</p> </p>
@@ -60,21 +60,21 @@
<p> <p>
Soweit die Haftung nach den vorbezeichneten Bestimmungen beschränkt oder ausgeschlossen ist, gilt dies auch für unsere Vertreter, Arbeitnehmer, Mitarbeiter und Erfüllungsgehilfen. Eine etwaige Haftung nach dem Produkthaftungsgesetz bleibt unberührt. Soweit die Haftung nach den vorbezeichneten Bestimmungen beschränkt oder ausgeschlossen ist, gilt dies auch für unsere Vertreter, Arbeitnehmer, Mitarbeiter und Erfüllungsgehilfen. Eine etwaige Haftung nach dem Produkthaftungsgesetz bleibt unberührt.
</p> </p>
<h4>10. Versicherung</h4> <h2>10. Versicherung</h2>
<p> <p>
Die Mietsachen sind nach den allgemeinen Bedingungen für die Elektronikversicherung (AEB) versichert. Der Versicherungsvertrag beinhaltet einen verschuldensunabhängigen Selbstbehalt in Höhe von 500,00 € pro Schadenfall. Die Versicherungsbedingungen liegen bei uns zur Einsicht bereit. Es wird ausdrücklich darauf hingewiesen, dass der Versicherungsvertrag nur in Europa gilt. Will der Mieter die Gerätschaften an einen außereuropäischen Ort verbringen, so hat er dies vor Vertragsschluss mitzuteilen, damit für uns ausreichend Zeit zur Verfügung steht, die Mietsache g egen die Gefahrenerhöhung zu versichern. Bei gefährlichen Einsätzen, wie sie z.B. bei Fahr - , Hubschrauber - , Unterwasser - , Hochsee - (außerhalb der 3 Meilenzone), Hochgebirgsaufnahmen und dergleichen vorliegen, sind zwecks Abschluss einer Zusatzversicherung entsprechende Hinweise an uns bei Vertragschluss zu machen. Die Kosten für eine erforderliche Zusatzversicherung trägt in jedem Falle der Mieter. Die Mietsachen sind nach den allgemeinen Bedingungen für die Elektronikversicherung (AEB) versichert. Der Versicherungsvertrag beinhaltet einen verschuldensunabhängigen Selbstbehalt in Höhe von 500,00 € pro Schadenfall. Die Versicherungsbedingungen liegen bei uns zur Einsicht bereit. Es wird ausdrücklich darauf hingewiesen, dass der Versicherungsvertrag nur in Europa gilt. Will der Mieter die Gerätschaften an einen außereuropäischen Ort verbringen, so hat er dies vor Vertragsschluss mitzuteilen, damit für uns ausreichend Zeit zur Verfügung steht, die Mietsache g egen die Gefahrenerhöhung zu versichern. Bei gefährlichen Einsätzen, wie sie z.B. bei Fahr - , Hubschrauber - , Unterwasser - , Hochsee - (außerhalb der 3 Meilenzone), Hochgebirgsaufnahmen und dergleichen vorliegen, sind zwecks Abschluss einer Zusatzversicherung entsprechende Hinweise an uns bei Vertragschluss zu machen. Die Kosten für eine erforderliche Zusatzversicherung trägt in jedem Falle der Mieter.
</p> </p>
<p> <p>
Der Mieter ist bei Diebstahl, Unterschlagung, Raub oder Veruntreuung durch Dritte oder sonstigem Abhandenkomme n der Mietsache verpflichtet, dieses Ereignis unverzüglich polizeilich anzuzeigen und einen ausführlichen Schadensbericht anzufertigen. Das Nichtbefolgen dieser Pflicht führt zur vollständigen Haftung des Mieters. Nach den Versicherungsbedingungen sind Sch äden an Leuchtmitteln und Kathodenstrahlröhren nicht versichert. Der Mieter ist bei Diebstahl, Unterschlagung, Raub oder Veruntreuung durch Dritte oder sonstigem Abhandenkomme n der Mietsache verpflichtet, dieses Ereignis unverzüglich polizeilich anzuzeigen und einen ausführlichen Schadensbericht anzufertigen. Das Nichtbefolgen dieser Pflicht führt zur vollständigen Haftung des Mieters. Nach den Versicherungsbedingungen sind Sch äden an Leuchtmitteln und Kathodenstrahlröhren nicht versichert.
</p> </p>
<h4>11. Haftung des Mieters / Ausfallschäden</h4> <h2>11. Haftung des Mieters / Ausfallschäden</h2>
<p> <p>
Während der gesamten Mietdauer übernimmt der Mieter für die gemieteten Gegenstände samt Zubehör die Haftung, auch für Zufall. Entstehen beim Mieter od er Dritten beim Gebrauch der Mietsache Schäden, die auf eine unqualifizierte oder unsachgemäße Ingebrauchnahme der Mietsache oder des Zubehörs zurückzuführen sind, so haftet der Mieter hierfür uneingeschränkt und hat uns auf Verlangen von der Haftung gegen über Dritten freizustellen. Besteht eine Versicherung für eine Gefahr und wird ein Schaden nicht in der entstandenen Höhe von dieser Versicherung getragen, so haftet der Mieter für den Differenzbetrag. Kathodenstrahlröhren nicht versichert. Während der gesamten Mietdauer übernimmt der Mieter für die gemieteten Gegenstände samt Zubehör die Haftung, auch für Zufall. Entstehen beim Mieter od er Dritten beim Gebrauch der Mietsache Schäden, die auf eine unqualifizierte oder unsachgemäße Ingebrauchnahme der Mietsache oder des Zubehörs zurückzuführen sind, so haftet der Mieter hierfür uneingeschränkt und hat uns auf Verlangen von der Haftung gegen über Dritten freizustellen. Besteht eine Versicherung für eine Gefahr und wird ein Schaden nicht in der entstandenen Höhe von dieser Versicherung getragen, so haftet der Mieter für den Differenzbetrag. Kathodenstrahlröhren nicht versichert.
</p> </p>
<p> <p>
Gibt der Mieter die Mietsache mangelhaft oder nicht zu rück, so hat er während der Reparatur oder Wiederbeschaffungszeit Nutzungsausfall in Höhe des Mietzinses zu zahlen, es sei denn, der Mieter weist nach, dass kein oder ein geringerer Schaden entstanden ist Gibt der Mieter die Mietsache mangelhaft oder nicht zu rück, so hat er während der Reparatur oder Wiederbeschaffungszeit Nutzungsausfall in Höhe des Mietzinses zu zahlen, es sei denn, der Mieter weist nach, dass kein oder ein geringerer Schaden entstanden ist
</p> </p>
<h4>12. Zahlungsbedingungen</h4> <h2>12. Zahlungsbedingungen</h2>
<p> <p>
Es ist grundsätzlich Zahlu ng bei Auslieferung bzw. Übergabe vereinbart. Abweichende Zahlungsziele müssen schriftlich vereinbart werden. Eine Aufrechnung gegen unsere Forderungen ist ausgeschlossen, es sei denn, dass die Gegenforderung unbestritten oder rechtskräftig festgestellt is t. Gleiches gilt für die Geltendmachung eines Zurückbehaltungsrechts. Es ist grundsätzlich Zahlu ng bei Auslieferung bzw. Übergabe vereinbart. Abweichende Zahlungsziele müssen schriftlich vereinbart werden. Eine Aufrechnung gegen unsere Forderungen ist ausgeschlossen, es sei denn, dass die Gegenforderung unbestritten oder rechtskräftig festgestellt is t. Gleiches gilt für die Geltendmachung eines Zurückbehaltungsrechts.
</p> </p>
@@ -84,18 +84,18 @@
<p> <p>
Vereinbarte Preisnachlässe geraten bei Zahlungsverzug des Mieters sowie bei einer ge richtlichen Geltendmachung unserer Forderungen in Wegfall. Vereinbarte Preisnachlässe geraten bei Zahlungsverzug des Mieters sowie bei einer ge richtlichen Geltendmachung unserer Forderungen in Wegfall.
</p> </p>
<h4>14. Erfüllungsort, Rechtswahl und Gerichtsstand</h4> <h2>14. Erfüllungsort, Rechtswahl und Gerichtsstand</h2>
<p> <p>
Erfüllungsort für Lieferung und Leistungen aus diesem Vertrag ist Bielefeld . Für die auf Grundlage dieser Bedingungen abgeschlossenen Verträge und der aus Ihnen folgenden Ansprüche gilt deutsches Recht. Für sämtliche gegenwärtigen und zukünftigen Ansprüche aus der Geschäftsbeziehung einschließlich Wechsel- und Scheckforderungen ist, wenn der Vertragspartner Unternehmer, eine juristische Person des öffentlichen Rechts oder ein öffentlich-rechtliches Sondervermögen ist, ausschließlicher Gerichtsstand Bielefeld . Erfüllungsort für Lieferung und Leistungen aus diesem Vertrag ist Bielefeld . Für die auf Grundlage dieser Bedingungen abgeschlossenen Verträge und der aus Ihnen folgenden Ansprüche gilt deutsches Recht. Für sämtliche gegenwärtigen und zukünftigen Ansprüche aus der Geschäftsbeziehung einschließlich Wechsel- und Scheckforderungen ist, wenn der Vertragspartner Unternehmer, eine juristische Person des öffentlichen Rechts oder ein öffentlich-rechtliches Sondervermögen ist, ausschließlicher Gerichtsstand Bielefeld .
</p> </p>
<h4>15. Schriftform</h4> <h2>15. Schriftform</h2>
<p> <p>
Mündliche Nebenabreden sind zu diesem Vertrag nicht getroffen worden. Änderungen und Ergänzungen sowie die teilweise oder gesamte Aufhebung des Vertrages bedürfen der Schriftform. Dies gilt auch für die Aufhebung oder Änderung des Schriftformerfordernisses. Mündliche Nebenabreden sind zu diesem Vertrag nicht getroffen worden. Änderungen und Ergänzungen sowie die teilweise oder gesamte Aufhebung des Vertrages bedürfen der Schriftform. Dies gilt auch für die Aufhebung oder Änderung des Schriftformerfordernisses.
</p> </p>
<h4>16. Salvatorische Klausel</h4> <h2>16. Salvatorische Klausel</h2>
<p> <p>
Sollte eine Bestimmung oder mehrere Bestimmungen dieser Vereinbarung unwirksam sein oder werden, so bl eiben die übrigen Bestimmungen wirksam. Die Parteien verpflichten sich, die unwirksame Bestimmung durch eine wirksame Bestimmung zu ersetzen, die dem ursprünglichen wirtschaftlichen Inhalt der unwirksamen Bestimmung weitestgehend entspricht . Sollte eine Bestimmung oder mehrere Bestimmungen dieser Vereinbarung unwirksam sein oder werden, so bl eiben die übrigen Bestimmungen wirksam. Die Parteien verpflichten sich, die unwirksame Bestimmung durch eine wirksame Bestimmung zu ersetzen, die dem ursprünglichen wirtschaftlichen Inhalt der unwirksamen Bestimmung weitestgehend entspricht .
</p> </p>
<h4>Stand: 24.05.2020</h4> <p class="text__date">Stand: 24.05.2020</p>
</div> </div>
</section> </section>
+15 -3
View File
@@ -1,5 +1,17 @@
<section id="work" class="section-work"> {{- $headline := default (copy .Page .Content "headline") "Referenzen" -}}
<div class="container"> {{- $text := copy .Page .Content "text" -}}
{{.CMS.Render .Args .Page (fmt "%s.content" .Content.Position) (index .Content.Content "content")}}
<section class="work" id="work" data-shifu-element="{{ .Content.Position }}">
<div class="filmstrip" aria-hidden="true"></div>
<div class="work__inner">
<div class="work__head" data-component="reveal">
<h2 class="section-title section-title--light"><span class="section-title__nr" aria-hidden="true"></span>{{ $headline }}</h2>
{{ with $text }}<div class="work__text">{{ html . }}</div>{{ end }}
</div> </div>
<div class="work__list">
{{ .CMS.Render .Args .Page (fmt "%s.content" .Content.Position) (index .Content.Content "content") }}
<slot name="{{ .Content.Position }}/content"></slot>
</div>
</div>
<div class="filmstrip filmstrip--bottom" aria-hidden="true"></div>
</section> </section>
+14 -9
View File
@@ -1,11 +1,16 @@
<div class="video row reverse"> {{- $title := copy .Page .Content "h2" -}}
<div class="col-lg-4 col-md-12 col-sm-12 col-xs-12"> {{- $text := copy .Page .Content "text" -}}
<h2 class="black-to-beige" data-aos="fade-in" data-aos-delay="100">{{copy .Page .Content "h2"}}</h2> {{- $video := get .Content "video" -}}
<p class="black-to-beige" data-aos="fade-in" data-aos-delay="200">{{copy .Page .Content "text"}}</p>
</div> <article class="take" data-component="reveal" data-shifu-element="{{ .Content.Position }}">
<div class="col-lg-8 col-md-12 col-sm-12 col-xs-12"> <div class="take__info">
<div style="padding:56.25% 0 0 0;position:relative;" data-aos="fade-in"> <span class="take__label" aria-hidden="true">Take</span>
<iframe src="{{get .Content "video"}}" style="position:absolute;top:0;left:0;width:100%;height:100%;" frameborder="0" allowfullscreen></iframe> {{ with $title }}<h3 class="take__title">{{ . }}</h3>{{ end }}
</div> {{ with $text }}<p class="take__text">{{ . }}</p>{{ end }}
</div> </div>
<div class="take__screen">
{{ with $video }}
<iframe class="take__video" src="{{ . }}" title="{{ default $title "Video" }}" loading="lazy" allow="autoplay; fullscreen; picture-in-picture" allowfullscreen></iframe>
{{ end }}
</div> </div>
</article>