feat: add the website
9c77724 parent: e80d1d3 modified
.gitignore +3 -0 | @@ -35,3 +35,6 @@ Thumbs.db | ||
| 35 | 35 | /docs/ |
| 36 | 36 | /backend/scripts/compare_stt.py |
| 37 | 37 | /editing-os-starter/ |
| 38 | + | |
| 39 | +# site construit (node site/build.mjs) | |
| 40 | +/site/dist/ | |
| @@ -35,3 +35,6 @@ Thumbs.db | |||
| 35 | /docs/ | 35 | /docs/ |
| 36 | /backend/scripts/compare_stt.py | 36 | /backend/scripts/compare_stt.py |
| 37 | /editing-os-starter/ | 37 | /editing-os-starter/ |
| 38 | + | ||
| 39 | +# site construit (node site/build.mjs) | ||
| 40 | +/site/dist/ | ||
added
site/build.mjs +15 -0 | new file mode 100644 | ||
| @@ -0,0 +1,15 @@ | ||
| 1 | +// Builds the website into site/dist: the static files of site/public, plus the screenshots and the logo, | |
| 2 | +// taken from the repository so they have a single source. | |
| 3 | +// No dependencies: `node site/build.mjs`, then open site/dist/index.html or serve the folder. | |
| 4 | +import { cpSync, rmSync } from 'node:fs' | |
| 5 | +import { fileURLToPath } from 'node:url' | |
| 6 | + | |
| 7 | +const root = (path) => fileURLToPath(new URL(`../${path}`, import.meta.url)) | |
| 8 | +const dist = root('site/dist') | |
| 9 | + | |
| 10 | +rmSync(dist, { recursive: true, force: true }) | |
| 11 | +cpSync(root('site/public'), dist, { recursive: true }) | |
| 12 | +cpSync(root('media/screenshots'), `${dist}/screenshots`, { recursive: true }) | |
| 13 | +cpSync(root('web/public/favicon.svg'), `${dist}/favicon.svg`) | |
| 14 | + | |
| 15 | +console.log(`Site built in ${dist}`) | |
| new file mode 100644 | |||
| @@ -0,0 +1,15 @@ | |||
| 1 | +// Builds the website into site/dist: the static files of site/public, plus the screenshots and the logo, | ||
| 2 | +// taken from the repository so they have a single source. | ||
| 3 | +// No dependencies: `node site/build.mjs`, then open site/dist/index.html or serve the folder. | ||
| 4 | +import { cpSync, rmSync } from 'node:fs' | ||
| 5 | +import { fileURLToPath } from 'node:url' | ||
| 6 | + | ||
| 7 | +const root = (path) => fileURLToPath(new URL(`../${path}`, import.meta.url)) | ||
| 8 | +const dist = root('site/dist') | ||
| 9 | + | ||
| 10 | +rmSync(dist, { recursive: true, force: true }) | ||
| 11 | +cpSync(root('site/public'), dist, { recursive: true }) | ||
| 12 | +cpSync(root('media/screenshots'), `${dist}/screenshots`, { recursive: true }) | ||
| 13 | +cpSync(root('web/public/favicon.svg'), `${dist}/favicon.svg`) | ||
| 14 | + | ||
| 15 | +console.log(`Site built in ${dist}`) | ||
added
site/public/fonts/OFL.txt +93 -0 | new file mode 100644 | ||
| @@ -0,0 +1,93 @@ | ||
| 1 | +Copyright 2019 IBM Corp. All rights reserved. IBMPlexSans-Italic[wdth,wght].ttf: Copyright 2019 IBM Corp. All rights reserved. | |
| 2 | + | |
| 3 | +This Font Software is licensed under the SIL Open Font License, Version 1.1. | |
| 4 | +This license is copied below, and is also available with a FAQ at: | |
| 5 | +http://scripts.sil.org/OFL | |
| 6 | + | |
| 7 | + | |
| 8 | +----------------------------------------------------------- | |
| 9 | +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 | |
| 10 | +----------------------------------------------------------- | |
| 11 | + | |
| 12 | +PREAMBLE | |
| 13 | +The goals of the Open Font License (OFL) are to stimulate worldwide | |
| 14 | +development of collaborative font projects, to support the font creation | |
| 15 | +efforts of academic and linguistic communities, and to provide a free and | |
| 16 | +open framework in which fonts may be shared and improved in partnership | |
| 17 | +with others. | |
| 18 | + | |
| 19 | +The OFL allows the licensed fonts to be used, studied, modified and | |
| 20 | +redistributed freely as long as they are not sold by themselves. The | |
| 21 | +fonts, including any derivative works, can be bundled, embedded, | |
| 22 | +redistributed and/or sold with any software provided that any reserved | |
| 23 | +names are not used by derivative works. The fonts and derivatives, | |
| 24 | +however, cannot be released under any other type of license. The | |
| 25 | +requirement for fonts to remain under this license does not apply | |
| 26 | +to any document created using the fonts or their derivatives. | |
| 27 | + | |
| 28 | +DEFINITIONS | |
| 29 | +"Font Software" refers to the set of files released by the Copyright | |
| 30 | +Holder(s) under this license and clearly marked as such. This may | |
| 31 | +include source files, build scripts and documentation. | |
| 32 | + | |
| 33 | +"Reserved Font Name" refers to any names specified as such after the | |
| 34 | +copyright statement(s). | |
| 35 | + | |
| 36 | +"Original Version" refers to the collection of Font Software components as | |
| 37 | +distributed by the Copyright Holder(s). | |
| 38 | + | |
| 39 | +"Modified Version" refers to any derivative made by adding to, deleting, | |
| 40 | +or substituting -- in part or in whole -- any of the components of the | |
| 41 | +Original Version, by changing formats or by porting the Font Software to a | |
| 42 | +new environment. | |
| 43 | + | |
| 44 | +"Author" refers to any designer, engineer, programmer, technical | |
| 45 | +writer or other person who contributed to the Font Software. | |
| 46 | + | |
| 47 | +PERMISSION & CONDITIONS | |
| 48 | +Permission is hereby granted, free of charge, to any person obtaining | |
| 49 | +a copy of the Font Software, to use, study, copy, merge, embed, modify, | |
| 50 | +redistribute, and sell modified and unmodified copies of the Font | |
| 51 | +Software, subject to the following conditions: | |
| 52 | + | |
| 53 | +1) Neither the Font Software nor any of its individual components, | |
| 54 | +in Original or Modified Versions, may be sold by itself. | |
| 55 | + | |
| 56 | +2) Original or Modified Versions of the Font Software may be bundled, | |
| 57 | +redistributed and/or sold with any software, provided that each copy | |
| 58 | +contains the above copyright notice and this license. These can be | |
| 59 | +included either as stand-alone text files, human-readable headers or | |
| 60 | +in the appropriate machine-readable metadata fields within text or | |
| 61 | +binary files as long as those fields can be easily viewed by the user. | |
| 62 | + | |
| 63 | +3) No Modified Version of the Font Software may use the Reserved Font | |
| 64 | +Name(s) unless explicit written permission is granted by the corresponding | |
| 65 | +Copyright Holder. This restriction only applies to the primary font name as | |
| 66 | +presented to the users. | |
| 67 | + | |
| 68 | +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font | |
| 69 | +Software shall not be used to promote, endorse or advertise any | |
| 70 | +Modified Version, except to acknowledge the contribution(s) of the | |
| 71 | +Copyright Holder(s) and the Author(s) or with their explicit written | |
| 72 | +permission. | |
| 73 | + | |
| 74 | +5) The Font Software, modified or unmodified, in part or in whole, | |
| 75 | +must be distributed entirely under this license, and must not be | |
| 76 | +distributed under any other license. The requirement for fonts to | |
| 77 | +remain under this license does not apply to any document created | |
| 78 | +using the Font Software. | |
| 79 | + | |
| 80 | +TERMINATION | |
| 81 | +This license becomes null and void if any of the above conditions are | |
| 82 | +not met. | |
| 83 | + | |
| 84 | +DISCLAIMER | |
| 85 | +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
| 86 | +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF | |
| 87 | +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT | |
| 88 | +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE | |
| 89 | +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, | |
| 90 | +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL | |
| 91 | +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | |
| 92 | +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM | |
| 93 | +OTHER DEALINGS IN THE FONT SOFTWARE. | |
| new file mode 100644 | |||
| @@ -0,0 +1,93 @@ | |||
| 1 | +Copyright 2019 IBM Corp. All rights reserved. IBMPlexSans-Italic[wdth,wght].ttf: Copyright 2019 IBM Corp. All rights reserved. | ||
| 2 | + | ||
| 3 | +This Font Software is licensed under the SIL Open Font License, Version 1.1. | ||
| 4 | +This license is copied below, and is also available with a FAQ at: | ||
| 5 | +http://scripts.sil.org/OFL | ||
| 6 | + | ||
| 7 | + | ||
| 8 | +----------------------------------------------------------- | ||
| 9 | +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 | ||
| 10 | +----------------------------------------------------------- | ||
| 11 | + | ||
| 12 | +PREAMBLE | ||
| 13 | +The goals of the Open Font License (OFL) are to stimulate worldwide | ||
| 14 | +development of collaborative font projects, to support the font creation | ||
| 15 | +efforts of academic and linguistic communities, and to provide a free and | ||
| 16 | +open framework in which fonts may be shared and improved in partnership | ||
| 17 | +with others. | ||
| 18 | + | ||
| 19 | +The OFL allows the licensed fonts to be used, studied, modified and | ||
| 20 | +redistributed freely as long as they are not sold by themselves. The | ||
| 21 | +fonts, including any derivative works, can be bundled, embedded, | ||
| 22 | +redistributed and/or sold with any software provided that any reserved | ||
| 23 | +names are not used by derivative works. The fonts and derivatives, | ||
| 24 | +however, cannot be released under any other type of license. The | ||
| 25 | +requirement for fonts to remain under this license does not apply | ||
| 26 | +to any document created using the fonts or their derivatives. | ||
| 27 | + | ||
| 28 | +DEFINITIONS | ||
| 29 | +"Font Software" refers to the set of files released by the Copyright | ||
| 30 | +Holder(s) under this license and clearly marked as such. This may | ||
| 31 | +include source files, build scripts and documentation. | ||
| 32 | + | ||
| 33 | +"Reserved Font Name" refers to any names specified as such after the | ||
| 34 | +copyright statement(s). | ||
| 35 | + | ||
| 36 | +"Original Version" refers to the collection of Font Software components as | ||
| 37 | +distributed by the Copyright Holder(s). | ||
| 38 | + | ||
| 39 | +"Modified Version" refers to any derivative made by adding to, deleting, | ||
| 40 | +or substituting -- in part or in whole -- any of the components of the | ||
| 41 | +Original Version, by changing formats or by porting the Font Software to a | ||
| 42 | +new environment. | ||
| 43 | + | ||
| 44 | +"Author" refers to any designer, engineer, programmer, technical | ||
| 45 | +writer or other person who contributed to the Font Software. | ||
| 46 | + | ||
| 47 | +PERMISSION & CONDITIONS | ||
| 48 | +Permission is hereby granted, free of charge, to any person obtaining | ||
| 49 | +a copy of the Font Software, to use, study, copy, merge, embed, modify, | ||
| 50 | +redistribute, and sell modified and unmodified copies of the Font | ||
| 51 | +Software, subject to the following conditions: | ||
| 52 | + | ||
| 53 | +1) Neither the Font Software nor any of its individual components, | ||
| 54 | +in Original or Modified Versions, may be sold by itself. | ||
| 55 | + | ||
| 56 | +2) Original or Modified Versions of the Font Software may be bundled, | ||
| 57 | +redistributed and/or sold with any software, provided that each copy | ||
| 58 | +contains the above copyright notice and this license. These can be | ||
| 59 | +included either as stand-alone text files, human-readable headers or | ||
| 60 | +in the appropriate machine-readable metadata fields within text or | ||
| 61 | +binary files as long as those fields can be easily viewed by the user. | ||
| 62 | + | ||
| 63 | +3) No Modified Version of the Font Software may use the Reserved Font | ||
| 64 | +Name(s) unless explicit written permission is granted by the corresponding | ||
| 65 | +Copyright Holder. This restriction only applies to the primary font name as | ||
| 66 | +presented to the users. | ||
| 67 | + | ||
| 68 | +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font | ||
| 69 | +Software shall not be used to promote, endorse or advertise any | ||
| 70 | +Modified Version, except to acknowledge the contribution(s) of the | ||
| 71 | +Copyright Holder(s) and the Author(s) or with their explicit written | ||
| 72 | +permission. | ||
| 73 | + | ||
| 74 | +5) The Font Software, modified or unmodified, in part or in whole, | ||
| 75 | +must be distributed entirely under this license, and must not be | ||
| 76 | +distributed under any other license. The requirement for fonts to | ||
| 77 | +remain under this license does not apply to any document created | ||
| 78 | +using the Font Software. | ||
| 79 | + | ||
| 80 | +TERMINATION | ||
| 81 | +This license becomes null and void if any of the above conditions are | ||
| 82 | +not met. | ||
| 83 | + | ||
| 84 | +DISCLAIMER | ||
| 85 | +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | ||
| 86 | +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF | ||
| 87 | +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT | ||
| 88 | +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE | ||
| 89 | +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, | ||
| 90 | +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL | ||
| 91 | +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | ||
| 92 | +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM | ||
| 93 | +OTHER DEALINGS IN THE FONT SOFTWARE. | ||
added
site/public/fonts/ibm-plex-mono-latin-400-normal.woff2 +0 -0 | new file mode 100644 | ||
| Binary files /dev/null and b/site/public/fonts/ibm-plex-mono-latin-400-normal.woff2 differ | ||
| new file mode 100644 | |||
| Binary files /dev/null and b/site/public/fonts/ibm-plex-mono-latin-400-normal.woff2 differ | Binary files /dev/null and b/site/public/fonts/ibm-plex-mono-latin-400-normal.woff2 differ | ||
added
site/public/fonts/ibm-plex-mono-latin-500-normal.woff2 +0 -0 | new file mode 100644 | ||
| Binary files /dev/null and b/site/public/fonts/ibm-plex-mono-latin-500-normal.woff2 differ | ||
| new file mode 100644 | |||
| Binary files /dev/null and b/site/public/fonts/ibm-plex-mono-latin-500-normal.woff2 differ | Binary files /dev/null and b/site/public/fonts/ibm-plex-mono-latin-500-normal.woff2 differ | ||
added
site/public/fonts/ibm-plex-sans-latin-400-normal.woff2 +0 -0 | new file mode 100644 | ||
| Binary files /dev/null and b/site/public/fonts/ibm-plex-sans-latin-400-normal.woff2 differ | ||
| new file mode 100644 | |||
| Binary files /dev/null and b/site/public/fonts/ibm-plex-sans-latin-400-normal.woff2 differ | Binary files /dev/null and b/site/public/fonts/ibm-plex-sans-latin-400-normal.woff2 differ | ||
added
site/public/fonts/ibm-plex-sans-latin-500-normal.woff2 +0 -0 | new file mode 100644 | ||
| Binary files /dev/null and b/site/public/fonts/ibm-plex-sans-latin-500-normal.woff2 differ | ||
| new file mode 100644 | |||
| Binary files /dev/null and b/site/public/fonts/ibm-plex-sans-latin-500-normal.woff2 differ | Binary files /dev/null and b/site/public/fonts/ibm-plex-sans-latin-500-normal.woff2 differ | ||
added
site/public/fonts/ibm-plex-sans-latin-600-normal.woff2 +0 -0 | new file mode 100644 | ||
| Binary files /dev/null and b/site/public/fonts/ibm-plex-sans-latin-600-normal.woff2 differ | ||
| new file mode 100644 | |||
| Binary files /dev/null and b/site/public/fonts/ibm-plex-sans-latin-600-normal.woff2 differ | Binary files /dev/null and b/site/public/fonts/ibm-plex-sans-latin-600-normal.woff2 differ | ||
added
site/public/index.html +233 -0 | new file mode 100644 | ||
| @@ -0,0 +1,233 @@ | ||
| 1 | +<!doctype html> | |
| 2 | +<html lang="en"> | |
| 3 | +<head> | |
| 4 | + <meta charset="utf-8"> | |
| 5 | + <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| 6 | + <title>Daryl: a local studio for YouTube videos, from the idea to the upload</title> | |
| 7 | + <meta name="description" content="Angle, titles, thumbnails, script, rough cut, motion design, editing, shorts and upload in one app that runs on your machine, with Claude Code as a co-author. Free and open source."> | |
| 8 | + <link rel="canonical" href="https://daryl.eventuallymaking.io/"> | |
| 9 | + <meta name="theme-color" content="#FAFAF8"> | |
| 10 | + | |
| 11 | + <meta property="og:type" content="website"> | |
| 12 | + <meta property="og:url" content="https://daryl.eventuallymaking.io/"> | |
| 13 | + <meta property="og:title" content="Daryl: a local studio for YouTube videos"> | |
| 14 | + <meta property="og:description" content="From the idea to the upload, in one app that runs on your machine, with Claude Code as a co-author. Free and open source."> | |
| 15 | + <meta property="og:image" content="https://daryl.eventuallymaking.io/screenshots/editing.png"> | |
| 16 | + <meta name="twitter:card" content="summary_large_image"> | |
| 17 | + | |
| 18 | + <link rel="icon" type="image/svg+xml" href="favicon.svg"> | |
| 19 | + <link rel="preload" href="fonts/ibm-plex-sans-latin-400-normal.woff2" as="font" type="font/woff2" crossorigin> | |
| 20 | + <link rel="preload" href="fonts/ibm-plex-sans-latin-600-normal.woff2" as="font" type="font/woff2" crossorigin> | |
| 21 | + <link rel="stylesheet" href="styles.css"> | |
| 22 | + <script>document.documentElement.classList.add('js')</script> | |
| 23 | + <script src="main.js" defer></script> | |
| 24 | +</head> | |
| 25 | +<body> | |
| 26 | + <a class="skip" href="#main">Skip to content</a> | |
| 27 | + | |
| 28 | + <header class="top"> | |
| 29 | + <a class="brand" href="./" aria-label="Daryl, home"> | |
| 30 | + <img src="favicon.svg" width="28" height="28" alt=""> | |
| 31 | + <span>daryl</span> | |
| 32 | + </a> | |
| 33 | + <nav class="top-nav" aria-label="Sections"> | |
| 34 | + <a href="#tour">Tour</a> | |
| 35 | + <a href="#how">How it works</a> | |
| 36 | + <a href="#try">Try it</a> | |
| 37 | + </nav> | |
| 38 | + <a class="btn btn-ink btn-sm" href="https://rickub.com/hlassiege/daryl"><svg class="btn-icon" viewBox="0 0 64 64" aria-hidden="true"><path d="M32 8 A24 24 0 1 1 12 44"/><path d="M32 18 A14 14 0 1 0 46 32"/><circle cx="32" cy="32" r="6.5"/></svg>Source code</a> | |
| 39 | + </header> | |
| 40 | + | |
| 41 | + <main id="main"> | |
| 42 | + <section class="hero"> | |
| 43 | + <div class="hero-copy"> | |
| 44 | + <h1>Your YouTube video, from the idea to the upload.</h1> | |
| 45 | + <p class="lead"> | |
| 46 | + Daryl is a studio that runs on your machine. It takes a video through every step, with Claude Code | |
| 47 | + as a co-author and editing assistant. Daryl proposes, you decide. | |
| 48 | + </p> | |
| 49 | + <div class="actions"> | |
| 50 | + <a class="btn btn-red" href="#try">Try it</a> | |
| 51 | + <a class="btn btn-line" href="https://rickub.com/hlassiege/daryl"><svg class="btn-icon" viewBox="0 0 64 64" aria-hidden="true"><path d="M32 8 A24 24 0 1 1 12 44"/><path d="M32 18 A14 14 0 1 0 46 32"/><circle cx="32" cy="32" r="6.5"/></svg>Source code</a> | |
| 52 | + </div> | |
| 53 | + <p class="perk"> | |
| 54 | + <svg viewBox="0 0 16 16" width="16" height="16" aria-hidden="true"><path d="M3.5 8.5l3 3 6-7"/></svg> | |
| 55 | + Free and open source, nothing to buy | |
| 56 | + </p> | |
| 57 | + </div> | |
| 58 | + </section> | |
| 59 | + | |
| 60 | + <!-- The reel: every step of a project laid out as an editing timeline. Static, it shows the finished | |
| 61 | + project; with JS, the playhead sweeps it once and each clip fills in as the playhead reaches it. | |
| 62 | + Positions are on a 30-unit timeline: --a is where a clip starts, --b how long it lasts. --> | |
| 63 | + <figure class="reel" data-reel aria-labelledby="reel-caption"> | |
| 64 | + <div class="reel-scroll"> | |
| 65 | + <div class="reel-inner"> | |
| 66 | + <ol class="ruler" aria-hidden="true"> | |
| 67 | + <li>00:00</li><li>00:05</li><li>00:10</li><li>00:15</li><li>00:20</li><li>00:25</li><li>00:30</li> | |
| 68 | + </ol> | |
| 69 | + <div class="lanes"> | |
| 70 | + <div class="lane" style="--c: var(--motion)"> | |
| 71 | + <span class="lane-name">Writing</span> | |
| 72 | + <ul> | |
| 73 | + <li style="--a:0; --b:2"><span>Angle</span></li> | |
| 74 | + <li style="--a:2; --b:1.5"><span>Title</span></li> | |
| 75 | + <li style="--a:3.5; --b:2.5"><span>Thumbnail</span></li> | |
| 76 | + <li style="--a:6; --b:2"><span>Script</span></li> | |
| 77 | + <li style="--a:8; --b:2"><span>Roast</span></li> | |
| 78 | + </ul> | |
| 79 | + </div> | |
| 80 | + <div class="lane" style="--c: var(--zoom)"> | |
| 81 | + <span class="lane-name">Shoot</span> | |
| 82 | + <ul> | |
| 83 | + <li style="--a:10; --b:3"><span>Teleprompter</span></li> | |
| 84 | + </ul> | |
| 85 | + </div> | |
| 86 | + <div class="lane" style="--c: var(--broll)"> | |
| 87 | + <span class="lane-name">Editing</span> | |
| 88 | + <ul> | |
| 89 | + <li style="--a:13; --b:2.5"><span>Rough cut</span></li> | |
| 90 | + <li style="--a:15.5; --b:2.75"><span>Storyboard</span></li> | |
| 91 | + <li style="--a:18.25; --b:3.25"><span>Editing</span></li> | |
| 92 | + <li style="--a:21.5; --b:2"><span>Shorts</span></li> | |
| 93 | + <li style="--a:23.5; --b:2"><span>Render</span></li> | |
| 94 | + </ul> | |
| 95 | + </div> | |
| 96 | + <div class="lane" style="--c: var(--music)"> | |
| 97 | + <span class="lane-name">Publishing</span> | |
| 98 | + <ul> | |
| 99 | + <li style="--a:25.5; --b:2.25"><span>Details</span></li> | |
| 100 | + <li style="--a:27.75; --b:2.25"><span>Upload</span></li> | |
| 101 | + </ul> | |
| 102 | + </div> | |
| 103 | + <div class="playhead" aria-hidden="true"></div> | |
| 104 | + </div> | |
| 105 | + </div> | |
| 106 | + </div> | |
| 107 | + <figcaption id="reel-caption">Every step of a video, one project folder on your disk.</figcaption> | |
| 108 | + </figure> | |
| 109 | + | |
| 110 | + <section class="tour" id="tour" aria-labelledby="tour-title"> | |
| 111 | + <h2 class="sr-only" id="tour-title">A tour of Daryl</h2> | |
| 112 | + <div class="tour-tabs" role="tablist" aria-label="Screens" aria-orientation="vertical"> | |
| 113 | + <button role="tab" id="tab-projects" aria-controls="shot-projects" aria-selected="true"> | |
| 114 | + <span class="tab-title">Every video, every step</span> | |
| 115 | + <span class="tab-desc">Each project keeps its brief, script, footage, edit and renders together, and shows where you left off.</span> | |
| 116 | + </button> | |
| 117 | + <button role="tab" id="tab-angle" aria-controls="shot-angle" aria-selected="false" tabindex="-1"> | |
| 118 | + <span class="tab-title">Find the angle</span> | |
| 119 | + <span class="tab-desc">Daryl researches the topic from your brief and notes, then suggests angles to keep or rework.</span> | |
| 120 | + </button> | |
| 121 | + <button role="tab" id="tab-titles" aria-controls="shot-titles" aria-selected="false" tabindex="-1"> | |
| 122 | + <span class="tab-title">Thirty titles, keep the best</span> | |
| 123 | + <span class="tab-desc">Daryl writes 30 titles from the angle. Keep two or three for the A/B test.</span> | |
| 124 | + </button> | |
| 125 | + <button role="tab" id="tab-thumbnail" aria-controls="shot-thumbnail" aria-selected="false" tabindex="-1"> | |
| 126 | + <span class="tab-title">A thumbnail per title</span> | |
| 127 | + <span class="tab-desc">Generate variants from your own photos and references, or import the ones you made elsewhere.</span> | |
| 128 | + </button> | |
| 129 | + <button role="tab" id="tab-script" aria-controls="shot-script" aria-selected="false" tabindex="-1"> | |
| 130 | + <span class="tab-title">Write the script</span> | |
| 131 | + <span class="tab-desc">Write it yourself or start from Daryl's draft, with versions, an outline and an assistant on the side.</span> | |
| 132 | + </button> | |
| 133 | + <button role="tab" id="tab-derush" aria-controls="shot-derush" aria-selected="false" tabindex="-1"> | |
| 134 | + <span class="tab-title">Rough cut in one pass</span> | |
| 135 | + <span class="tab-desc">Your footage is transcribed, then silences and bad takes are cut. Check every cut before you apply it.</span> | |
| 136 | + </button> | |
| 137 | + <button role="tab" id="tab-storyboard" aria-controls="shot-storyboard" aria-selected="false" tabindex="-1"> | |
| 138 | + <span class="tab-title">Plan the motion design</span> | |
| 139 | + <span class="tab-desc">Daryl reads what you say and places a motion beat on the right words. Review each one.</span> | |
| 140 | + </button> | |
| 141 | + <button role="tab" id="tab-editing" aria-controls="shot-editing" aria-selected="false" tabindex="-1"> | |
| 142 | + <span class="tab-title">Edit with Daryl</span> | |
| 143 | + <span class="tab-desc">Zooms, B-roll, motion design, sound and music on a real timeline. Ask Daryl or do it by hand, then export to DaVinci Resolve.</span> | |
| 144 | + </button> | |
| 145 | + <button role="tab" id="tab-details" aria-controls="shot-details" aria-selected="false" tabindex="-1"> | |
| 146 | + <span class="tab-title">Publish to YouTube</span> | |
| 147 | + <span class="tab-desc">Title, description, chapters and A/B variants, checked, then uploaded.</span> | |
| 148 | + </button> | |
| 149 | + </div> | |
| 150 | + <div class="tour-shots"> | |
| 151 | + <div class="frame" id="shot-projects" role="tabpanel" aria-labelledby="tab-projects"><img src="screenshots/projects.png" width="2880" height="1800" alt="The projects page, with the last opened project and the progress of each video" fetchpriority="high"></div> | |
| 152 | + <div class="frame" id="shot-angle" role="tabpanel" aria-labelledby="tab-angle"><img src="screenshots/angle.png" width="2880" height="1800" alt="The Angle step: the brief, the suggested angles and Daryl's chat" loading="lazy"></div> | |
| 153 | + <div class="frame" id="shot-titles" role="tabpanel" aria-labelledby="tab-titles"><img src="screenshots/titles.png" width="2880" height="1800" alt="The Title step: the titles kept for the A/B test and Daryl's suggestions with their score" loading="lazy"></div> | |
| 154 | + <div class="frame" id="shot-thumbnail" role="tabpanel" aria-labelledby="tab-thumbnail"><img src="screenshots/thumbnail.png" width="2880" height="1800" alt="The Thumbnail step: three variants and the brief of a touch-up" loading="lazy"></div> | |
| 155 | + <div class="frame" id="shot-script" role="tabpanel" aria-labelledby="tab-script"><img src="screenshots/script.png" width="2880" height="1800" alt="The Script step: the script, its sections and its estimated length" loading="lazy"></div> | |
| 156 | + <div class="frame" id="shot-derush" role="tabpanel" aria-labelledby="tab-derush"><img src="screenshots/derush.png" width="2880" height="1800" alt="The Rough cut step: preview, transcript and the timeline with its cuts" loading="lazy"></div> | |
| 157 | + <div class="frame" id="shot-storyboard" role="tabpanel" aria-labelledby="tab-storyboard"><img src="screenshots/storyboard.png" width="2880" height="1800" alt="The Storyboard step: motion beats anchored on the transcript, with their place on screen" loading="lazy"></div> | |
| 158 | + <div class="frame" id="shot-editing" role="tabpanel" aria-labelledby="tab-editing"><img src="screenshots/editing.png" width="2880" height="1800" alt="The Editing step: preview, timeline with chapters, motion, zooms, B-roll and music, and Daryl's chat" loading="lazy"></div> | |
| 159 | + <div class="frame" id="shot-details" role="tabpanel" aria-labelledby="tab-details"><img src="screenshots/details.png" width="2880" height="1800" alt="The Details step: A/B variants, description and chapters" loading="lazy"></div> | |
| 160 | + </div> | |
| 161 | + </section> | |
| 162 | + | |
| 163 | + <section class="how" id="how" aria-labelledby="how-title"> | |
| 164 | + <h2 id="how-title">A studio on your machine, an assistant that asks first</h2> | |
| 165 | + <dl class="facts"> | |
| 166 | + <div> | |
| 167 | + <dt>Your files stay with you</dt> | |
| 168 | + <dd>Projects, footage and renders live in a folder on your disk. Whisper, ffmpeg and the renderer run locally.</dd> | |
| 169 | + </div> | |
| 170 | + <div> | |
| 171 | + <dt>Runs on your Claude Code plan</dt> | |
| 172 | + <dd>Daryl drives Claude Code with your subscription. No API key to set up, no second bill.</dd> | |
| 173 | + </div> | |
| 174 | + <div> | |
| 175 | + <dt>Nothing changes without you</dt> | |
| 176 | + <dd>Cuts, titles, beats and edits are suggestions. They land in your project when you accept them.</dd> | |
| 177 | + </div> | |
| 178 | + </dl> | |
| 179 | + | |
| 180 | + <div class="skills"> | |
| 181 | + <div class="skills-copy"> | |
| 182 | + <h3>Editing jobs for Claude Code</h3> | |
| 183 | + <p>Some jobs work better from a Claude Code session opened in the repository. Daryl ships them as skills.</p> | |
| 184 | + </div> | |
| 185 | + <ul class="skills-list"> | |
| 186 | + <li><code>/music-bed</code><span>Scores the edit: one track per sequence, cut to cover it and ducked under the voice.</span></li> | |
| 187 | + <li><code>/replace-aroll-take</code><span>Swaps a stretch of A-roll for a re-recorded take and moves everything on top of it.</span></li> | |
| 188 | + <li><code>/snatch-cut</code><span>Proposes a burst of close-ups with snap zooms and punchy sounds, with a preview.</span></li> | |
| 189 | + </ul> | |
| 190 | + </div> | |
| 191 | + </section> | |
| 192 | + | |
| 193 | + <section class="honest" aria-labelledby="honest-title"> | |
| 194 | + <h2 id="honest-title">A personal tool, shared as is</h2> | |
| 195 | + <div class="honest-body"> | |
| 196 | + <p> | |
| 197 | + I make every video of <a href="https://www.youtube.com/@eventuallycoding">my channel</a> with Daryl. | |
| 198 | + I share it because people asked to see it, not to sell anything: there is no course, no paid plan, | |
| 199 | + no waiting list. | |
| 200 | + </p> | |
| 201 | + <p> | |
| 202 | + It follows how I work and evolves in that direction. It is built for one person on Windows, and | |
| 203 | + maintained on a best-effort basis. Fork it, take what you need, or use it for ideas. | |
| 204 | + </p> | |
| 205 | + </div> | |
| 206 | + </section> | |
| 207 | + | |
| 208 | + <section class="try" id="try" aria-labelledby="try-title"> | |
| 209 | + <h2 id="try-title">Try it</h2> | |
| 210 | + <p class="section-lead">You need Windows, Python 3.12, Node.js with pnpm, ffmpeg and Claude Code logged in.</p> | |
| 211 | + <div class="code"> | |
| 212 | + <pre><code id="install-cmd">git clone https://rickub.com/hlassiege/daryl.git | |
| 213 | +cd daryl | |
| 214 | +py -3.12 -m venv .venv | |
| 215 | +.venv\Scripts\pip install -r backend\requirements.txt | |
| 216 | +cd web; pnpm install; pnpm build; cd ..\backend | |
| 217 | +..\.venv\Scripts\python -m studio</code></pre> | |
| 218 | + <button class="btn btn-line btn-sm copy" type="button" data-copy="install-cmd">Copy</button> | |
| 219 | + </div> | |
| 220 | + <p class="note">Then open <code>http://localhost:4400</code>. <a href="https://rickub.com/hlassiege/daryl">Read the README</a> for the optional services: cloud transcription, thumbnails, YouTube upload, music.</p> | |
| 221 | + </section> | |
| 222 | + </main> | |
| 223 | + | |
| 224 | + <footer class="foot"> | |
| 225 | + <p>Open source under the AGPL-3.0. Made by <a href="https://eventuallymaking.io">Hugo Lassiège</a>.</p> | |
| 226 | + <nav aria-label="Elsewhere"> | |
| 227 | + <a href="https://rickub.com/hlassiege/daryl">Source code</a> | |
| 228 | + <a href="https://github.com/hlassiege/daryl">GitHub mirror</a> | |
| 229 | + <a href="https://panoplie.eventuallymaking.io">panoplie</a> | |
| 230 | + </nav> | |
| 231 | + </footer> | |
| 232 | +</body> | |
| 233 | +</html> | |
| new file mode 100644 | |||
| @@ -0,0 +1,233 @@ | |||
| 1 | +<!doctype html> | ||
| 2 | +<html lang="en"> | ||
| 3 | +<head> | ||
| 4 | + <meta charset="utf-8"> | ||
| 5 | + <meta name="viewport" content="width=device-width, initial-scale=1"> | ||
| 6 | + <title>Daryl: a local studio for YouTube videos, from the idea to the upload</title> | ||
| 7 | + <meta name="description" content="Angle, titles, thumbnails, script, rough cut, motion design, editing, shorts and upload in one app that runs on your machine, with Claude Code as a co-author. Free and open source."> | ||
| 8 | + <link rel="canonical" href="https://daryl.eventuallymaking.io/"> | ||
| 9 | + <meta name="theme-color" content="#FAFAF8"> | ||
| 10 | + | ||
| 11 | + <meta property="og:type" content="website"> | ||
| 12 | + <meta property="og:url" content="https://daryl.eventuallymaking.io/"> | ||
| 13 | + <meta property="og:title" content="Daryl: a local studio for YouTube videos"> | ||
| 14 | + <meta property="og:description" content="From the idea to the upload, in one app that runs on your machine, with Claude Code as a co-author. Free and open source."> | ||
| 15 | + <meta property="og:image" content="https://daryl.eventuallymaking.io/screenshots/editing.png"> | ||
| 16 | + <meta name="twitter:card" content="summary_large_image"> | ||
| 17 | + | ||
| 18 | + <link rel="icon" type="image/svg+xml" href="favicon.svg"> | ||
| 19 | + <link rel="preload" href="fonts/ibm-plex-sans-latin-400-normal.woff2" as="font" type="font/woff2" crossorigin> | ||
| 20 | + <link rel="preload" href="fonts/ibm-plex-sans-latin-600-normal.woff2" as="font" type="font/woff2" crossorigin> | ||
| 21 | + <link rel="stylesheet" href="styles.css"> | ||
| 22 | + <script>document.documentElement.classList.add('js')</script> | ||
| 23 | + <script src="main.js" defer></script> | ||
| 24 | +</head> | ||
| 25 | +<body> | ||
| 26 | + <a class="skip" href="#main">Skip to content</a> | ||
| 27 | + | ||
| 28 | + <header class="top"> | ||
| 29 | + <a class="brand" href="./" aria-label="Daryl, home"> | ||
| 30 | + <img src="favicon.svg" width="28" height="28" alt=""> | ||
| 31 | + <span>daryl</span> | ||
| 32 | + </a> | ||
| 33 | + <nav class="top-nav" aria-label="Sections"> | ||
| 34 | + <a href="#tour">Tour</a> | ||
| 35 | + <a href="#how">How it works</a> | ||
| 36 | + <a href="#try">Try it</a> | ||
| 37 | + </nav> | ||
| 38 | + <a class="btn btn-ink btn-sm" href="https://rickub.com/hlassiege/daryl"><svg class="btn-icon" viewBox="0 0 64 64" aria-hidden="true"><path d="M32 8 A24 24 0 1 1 12 44"/><path d="M32 18 A14 14 0 1 0 46 32"/><circle cx="32" cy="32" r="6.5"/></svg>Source code</a> | ||
| 39 | + </header> | ||
| 40 | + | ||
| 41 | + <main id="main"> | ||
| 42 | + <section class="hero"> | ||
| 43 | + <div class="hero-copy"> | ||
| 44 | + <h1>Your YouTube video, from the idea to the upload.</h1> | ||
| 45 | + <p class="lead"> | ||
| 46 | + Daryl is a studio that runs on your machine. It takes a video through every step, with Claude Code | ||
| 47 | + as a co-author and editing assistant. Daryl proposes, you decide. | ||
| 48 | + </p> | ||
| 49 | + <div class="actions"> | ||
| 50 | + <a class="btn btn-red" href="#try">Try it</a> | ||
| 51 | + <a class="btn btn-line" href="https://rickub.com/hlassiege/daryl"><svg class="btn-icon" viewBox="0 0 64 64" aria-hidden="true"><path d="M32 8 A24 24 0 1 1 12 44"/><path d="M32 18 A14 14 0 1 0 46 32"/><circle cx="32" cy="32" r="6.5"/></svg>Source code</a> | ||
| 52 | + </div> | ||
| 53 | + <p class="perk"> | ||
| 54 | + <svg viewBox="0 0 16 16" width="16" height="16" aria-hidden="true"><path d="M3.5 8.5l3 3 6-7"/></svg> | ||
| 55 | + Free and open source, nothing to buy | ||
| 56 | + </p> | ||
| 57 | + </div> | ||
| 58 | + </section> | ||
| 59 | + | ||
| 60 | + <!-- The reel: every step of a project laid out as an editing timeline. Static, it shows the finished | ||
| 61 | + project; with JS, the playhead sweeps it once and each clip fills in as the playhead reaches it. | ||
| 62 | + Positions are on a 30-unit timeline: --a is where a clip starts, --b how long it lasts. --> | ||
| 63 | + <figure class="reel" data-reel aria-labelledby="reel-caption"> | ||
| 64 | + <div class="reel-scroll"> | ||
| 65 | + <div class="reel-inner"> | ||
| 66 | + <ol class="ruler" aria-hidden="true"> | ||
| 67 | + <li>00:00</li><li>00:05</li><li>00:10</li><li>00:15</li><li>00:20</li><li>00:25</li><li>00:30</li> | ||
| 68 | + </ol> | ||
| 69 | + <div class="lanes"> | ||
| 70 | + <div class="lane" style="--c: var(--motion)"> | ||
| 71 | + <span class="lane-name">Writing</span> | ||
| 72 | + <ul> | ||
| 73 | + <li style="--a:0; --b:2"><span>Angle</span></li> | ||
| 74 | + <li style="--a:2; --b:1.5"><span>Title</span></li> | ||
| 75 | + <li style="--a:3.5; --b:2.5"><span>Thumbnail</span></li> | ||
| 76 | + <li style="--a:6; --b:2"><span>Script</span></li> | ||
| 77 | + <li style="--a:8; --b:2"><span>Roast</span></li> | ||
| 78 | + </ul> | ||
| 79 | + </div> | ||
| 80 | + <div class="lane" style="--c: var(--zoom)"> | ||
| 81 | + <span class="lane-name">Shoot</span> | ||
| 82 | + <ul> | ||
| 83 | + <li style="--a:10; --b:3"><span>Teleprompter</span></li> | ||
| 84 | + </ul> | ||
| 85 | + </div> | ||
| 86 | + <div class="lane" style="--c: var(--broll)"> | ||
| 87 | + <span class="lane-name">Editing</span> | ||
| 88 | + <ul> | ||
| 89 | + <li style="--a:13; --b:2.5"><span>Rough cut</span></li> | ||
| 90 | + <li style="--a:15.5; --b:2.75"><span>Storyboard</span></li> | ||
| 91 | + <li style="--a:18.25; --b:3.25"><span>Editing</span></li> | ||
| 92 | + <li style="--a:21.5; --b:2"><span>Shorts</span></li> | ||
| 93 | + <li style="--a:23.5; --b:2"><span>Render</span></li> | ||
| 94 | + </ul> | ||
| 95 | + </div> | ||
| 96 | + <div class="lane" style="--c: var(--music)"> | ||
| 97 | + <span class="lane-name">Publishing</span> | ||
| 98 | + <ul> | ||
| 99 | + <li style="--a:25.5; --b:2.25"><span>Details</span></li> | ||
| 100 | + <li style="--a:27.75; --b:2.25"><span>Upload</span></li> | ||
| 101 | + </ul> | ||
| 102 | + </div> | ||
| 103 | + <div class="playhead" aria-hidden="true"></div> | ||
| 104 | + </div> | ||
| 105 | + </div> | ||
| 106 | + </div> | ||
| 107 | + <figcaption id="reel-caption">Every step of a video, one project folder on your disk.</figcaption> | ||
| 108 | + </figure> | ||
| 109 | + | ||
| 110 | + <section class="tour" id="tour" aria-labelledby="tour-title"> | ||
| 111 | + <h2 class="sr-only" id="tour-title">A tour of Daryl</h2> | ||
| 112 | + <div class="tour-tabs" role="tablist" aria-label="Screens" aria-orientation="vertical"> | ||
| 113 | + <button role="tab" id="tab-projects" aria-controls="shot-projects" aria-selected="true"> | ||
| 114 | + <span class="tab-title">Every video, every step</span> | ||
| 115 | + <span class="tab-desc">Each project keeps its brief, script, footage, edit and renders together, and shows where you left off.</span> | ||
| 116 | + </button> | ||
| 117 | + <button role="tab" id="tab-angle" aria-controls="shot-angle" aria-selected="false" tabindex="-1"> | ||
| 118 | + <span class="tab-title">Find the angle</span> | ||
| 119 | + <span class="tab-desc">Daryl researches the topic from your brief and notes, then suggests angles to keep or rework.</span> | ||
| 120 | + </button> | ||
| 121 | + <button role="tab" id="tab-titles" aria-controls="shot-titles" aria-selected="false" tabindex="-1"> | ||
| 122 | + <span class="tab-title">Thirty titles, keep the best</span> | ||
| 123 | + <span class="tab-desc">Daryl writes 30 titles from the angle. Keep two or three for the A/B test.</span> | ||
| 124 | + </button> | ||
| 125 | + <button role="tab" id="tab-thumbnail" aria-controls="shot-thumbnail" aria-selected="false" tabindex="-1"> | ||
| 126 | + <span class="tab-title">A thumbnail per title</span> | ||
| 127 | + <span class="tab-desc">Generate variants from your own photos and references, or import the ones you made elsewhere.</span> | ||
| 128 | + </button> | ||
| 129 | + <button role="tab" id="tab-script" aria-controls="shot-script" aria-selected="false" tabindex="-1"> | ||
| 130 | + <span class="tab-title">Write the script</span> | ||
| 131 | + <span class="tab-desc">Write it yourself or start from Daryl's draft, with versions, an outline and an assistant on the side.</span> | ||
| 132 | + </button> | ||
| 133 | + <button role="tab" id="tab-derush" aria-controls="shot-derush" aria-selected="false" tabindex="-1"> | ||
| 134 | + <span class="tab-title">Rough cut in one pass</span> | ||
| 135 | + <span class="tab-desc">Your footage is transcribed, then silences and bad takes are cut. Check every cut before you apply it.</span> | ||
| 136 | + </button> | ||
| 137 | + <button role="tab" id="tab-storyboard" aria-controls="shot-storyboard" aria-selected="false" tabindex="-1"> | ||
| 138 | + <span class="tab-title">Plan the motion design</span> | ||
| 139 | + <span class="tab-desc">Daryl reads what you say and places a motion beat on the right words. Review each one.</span> | ||
| 140 | + </button> | ||
| 141 | + <button role="tab" id="tab-editing" aria-controls="shot-editing" aria-selected="false" tabindex="-1"> | ||
| 142 | + <span class="tab-title">Edit with Daryl</span> | ||
| 143 | + <span class="tab-desc">Zooms, B-roll, motion design, sound and music on a real timeline. Ask Daryl or do it by hand, then export to DaVinci Resolve.</span> | ||
| 144 | + </button> | ||
| 145 | + <button role="tab" id="tab-details" aria-controls="shot-details" aria-selected="false" tabindex="-1"> | ||
| 146 | + <span class="tab-title">Publish to YouTube</span> | ||
| 147 | + <span class="tab-desc">Title, description, chapters and A/B variants, checked, then uploaded.</span> | ||
| 148 | + </button> | ||
| 149 | + </div> | ||
| 150 | + <div class="tour-shots"> | ||
| 151 | + <div class="frame" id="shot-projects" role="tabpanel" aria-labelledby="tab-projects"><img src="screenshots/projects.png" width="2880" height="1800" alt="The projects page, with the last opened project and the progress of each video" fetchpriority="high"></div> | ||
| 152 | + <div class="frame" id="shot-angle" role="tabpanel" aria-labelledby="tab-angle"><img src="screenshots/angle.png" width="2880" height="1800" alt="The Angle step: the brief, the suggested angles and Daryl's chat" loading="lazy"></div> | ||
| 153 | + <div class="frame" id="shot-titles" role="tabpanel" aria-labelledby="tab-titles"><img src="screenshots/titles.png" width="2880" height="1800" alt="The Title step: the titles kept for the A/B test and Daryl's suggestions with their score" loading="lazy"></div> | ||
| 154 | + <div class="frame" id="shot-thumbnail" role="tabpanel" aria-labelledby="tab-thumbnail"><img src="screenshots/thumbnail.png" width="2880" height="1800" alt="The Thumbnail step: three variants and the brief of a touch-up" loading="lazy"></div> | ||
| 155 | + <div class="frame" id="shot-script" role="tabpanel" aria-labelledby="tab-script"><img src="screenshots/script.png" width="2880" height="1800" alt="The Script step: the script, its sections and its estimated length" loading="lazy"></div> | ||
| 156 | + <div class="frame" id="shot-derush" role="tabpanel" aria-labelledby="tab-derush"><img src="screenshots/derush.png" width="2880" height="1800" alt="The Rough cut step: preview, transcript and the timeline with its cuts" loading="lazy"></div> | ||
| 157 | + <div class="frame" id="shot-storyboard" role="tabpanel" aria-labelledby="tab-storyboard"><img src="screenshots/storyboard.png" width="2880" height="1800" alt="The Storyboard step: motion beats anchored on the transcript, with their place on screen" loading="lazy"></div> | ||
| 158 | + <div class="frame" id="shot-editing" role="tabpanel" aria-labelledby="tab-editing"><img src="screenshots/editing.png" width="2880" height="1800" alt="The Editing step: preview, timeline with chapters, motion, zooms, B-roll and music, and Daryl's chat" loading="lazy"></div> | ||
| 159 | + <div class="frame" id="shot-details" role="tabpanel" aria-labelledby="tab-details"><img src="screenshots/details.png" width="2880" height="1800" alt="The Details step: A/B variants, description and chapters" loading="lazy"></div> | ||
| 160 | + </div> | ||
| 161 | + </section> | ||
| 162 | + | ||
| 163 | + <section class="how" id="how" aria-labelledby="how-title"> | ||
| 164 | + <h2 id="how-title">A studio on your machine, an assistant that asks first</h2> | ||
| 165 | + <dl class="facts"> | ||
| 166 | + <div> | ||
| 167 | + <dt>Your files stay with you</dt> | ||
| 168 | + <dd>Projects, footage and renders live in a folder on your disk. Whisper, ffmpeg and the renderer run locally.</dd> | ||
| 169 | + </div> | ||
| 170 | + <div> | ||
| 171 | + <dt>Runs on your Claude Code plan</dt> | ||
| 172 | + <dd>Daryl drives Claude Code with your subscription. No API key to set up, no second bill.</dd> | ||
| 173 | + </div> | ||
| 174 | + <div> | ||
| 175 | + <dt>Nothing changes without you</dt> | ||
| 176 | + <dd>Cuts, titles, beats and edits are suggestions. They land in your project when you accept them.</dd> | ||
| 177 | + </div> | ||
| 178 | + </dl> | ||
| 179 | + | ||
| 180 | + <div class="skills"> | ||
| 181 | + <div class="skills-copy"> | ||
| 182 | + <h3>Editing jobs for Claude Code</h3> | ||
| 183 | + <p>Some jobs work better from a Claude Code session opened in the repository. Daryl ships them as skills.</p> | ||
| 184 | + </div> | ||
| 185 | + <ul class="skills-list"> | ||
| 186 | + <li><code>/music-bed</code><span>Scores the edit: one track per sequence, cut to cover it and ducked under the voice.</span></li> | ||
| 187 | + <li><code>/replace-aroll-take</code><span>Swaps a stretch of A-roll for a re-recorded take and moves everything on top of it.</span></li> | ||
| 188 | + <li><code>/snatch-cut</code><span>Proposes a burst of close-ups with snap zooms and punchy sounds, with a preview.</span></li> | ||
| 189 | + </ul> | ||
| 190 | + </div> | ||
| 191 | + </section> | ||
| 192 | + | ||
| 193 | + <section class="honest" aria-labelledby="honest-title"> | ||
| 194 | + <h2 id="honest-title">A personal tool, shared as is</h2> | ||
| 195 | + <div class="honest-body"> | ||
| 196 | + <p> | ||
| 197 | + I make every video of <a href="https://www.youtube.com/@eventuallycoding">my channel</a> with Daryl. | ||
| 198 | + I share it because people asked to see it, not to sell anything: there is no course, no paid plan, | ||
| 199 | + no waiting list. | ||
| 200 | + </p> | ||
| 201 | + <p> | ||
| 202 | + It follows how I work and evolves in that direction. It is built for one person on Windows, and | ||
| 203 | + maintained on a best-effort basis. Fork it, take what you need, or use it for ideas. | ||
| 204 | + </p> | ||
| 205 | + </div> | ||
| 206 | + </section> | ||
| 207 | + | ||
| 208 | + <section class="try" id="try" aria-labelledby="try-title"> | ||
| 209 | + <h2 id="try-title">Try it</h2> | ||
| 210 | + <p class="section-lead">You need Windows, Python 3.12, Node.js with pnpm, ffmpeg and Claude Code logged in.</p> | ||
| 211 | + <div class="code"> | ||
| 212 | + <pre><code id="install-cmd">git clone https://rickub.com/hlassiege/daryl.git | ||
| 213 | +cd daryl | ||
| 214 | +py -3.12 -m venv .venv | ||
| 215 | +.venv\Scripts\pip install -r backend\requirements.txt | ||
| 216 | +cd web; pnpm install; pnpm build; cd ..\backend | ||
| 217 | +..\.venv\Scripts\python -m studio</code></pre> | ||
| 218 | + <button class="btn btn-line btn-sm copy" type="button" data-copy="install-cmd">Copy</button> | ||
| 219 | + </div> | ||
| 220 | + <p class="note">Then open <code>http://localhost:4400</code>. <a href="https://rickub.com/hlassiege/daryl">Read the README</a> for the optional services: cloud transcription, thumbnails, YouTube upload, music.</p> | ||
| 221 | + </section> | ||
| 222 | + </main> | ||
| 223 | + | ||
| 224 | + <footer class="foot"> | ||
| 225 | + <p>Open source under the AGPL-3.0. Made by <a href="https://eventuallymaking.io">Hugo Lassiège</a>.</p> | ||
| 226 | + <nav aria-label="Elsewhere"> | ||
| 227 | + <a href="https://rickub.com/hlassiege/daryl">Source code</a> | ||
| 228 | + <a href="https://github.com/hlassiege/daryl">GitHub mirror</a> | ||
| 229 | + <a href="https://panoplie.eventuallymaking.io">panoplie</a> | ||
| 230 | + </nav> | ||
| 231 | + </footer> | ||
| 232 | +</body> | ||
| 233 | +</html> | ||
added
site/public/main.js +78 -0 | new file mode 100644 | ||
| @@ -0,0 +1,78 @@ | ||
| 1 | +// Progressive enhancement only: without this file every screenshot is listed and the reel shows the finished project. | |
| 2 | + | |
| 3 | +// Tour: screenshots as tabs (WAI-ARIA tabs pattern; up/down on the vertical list, left/right on mobile) | |
| 4 | +const tabs = [...document.querySelectorAll('.tour-tabs [role="tab"]')] | |
| 5 | + | |
| 6 | +function select(tab, focus) { | |
| 7 | + for (const t of tabs) { | |
| 8 | + const on = t === tab | |
| 9 | + t.setAttribute('aria-selected', String(on)) | |
| 10 | + t.tabIndex = on ? 0 : -1 | |
| 11 | + document.getElementById(t.getAttribute('aria-controls')).hidden = !on | |
| 12 | + } | |
| 13 | + if (focus) { | |
| 14 | + tab.focus() | |
| 15 | + tab.scrollIntoView({ block: 'nearest', inline: 'nearest' }) | |
| 16 | + } | |
| 17 | +} | |
| 18 | + | |
| 19 | +for (const tab of tabs) { | |
| 20 | + tab.addEventListener('click', () => select(tab, false)) | |
| 21 | + tab.addEventListener('keydown', (e) => { | |
| 22 | + const i = tabs.indexOf(tab) | |
| 23 | + const next = { ArrowDown: i + 1, ArrowRight: i + 1, ArrowUp: i - 1, ArrowLeft: i - 1, Home: 0, End: tabs.length - 1 }[e.key] | |
| 24 | + if (next === undefined) return | |
| 25 | + e.preventDefault() | |
| 26 | + select(tabs[(next + tabs.length) % tabs.length], true) | |
| 27 | + }) | |
| 28 | +} | |
| 29 | +if (tabs.length) select(tabs[0], false) | |
| 30 | + | |
| 31 | +// Copy buttons | |
| 32 | +for (const button of document.querySelectorAll('[data-copy]')) { | |
| 33 | + button.addEventListener('click', async () => { | |
| 34 | + const text = document.getElementById(button.dataset.copy).textContent | |
| 35 | + try { | |
| 36 | + await navigator.clipboard.writeText(text) | |
| 37 | + button.textContent = 'Copied' | |
| 38 | + } catch { | |
| 39 | + button.textContent = 'Copy failed' | |
| 40 | + } | |
| 41 | + setTimeout(() => { button.textContent = 'Copy' }, 1600) | |
| 42 | + }) | |
| 43 | +} | |
| 44 | + | |
| 45 | +// Reel: the playhead sweeps the project once, the first time it comes into view | |
| 46 | +const reel = document.querySelector('[data-reel]') | |
| 47 | +if (reel) { | |
| 48 | + const scroller = reel.querySelector('.reel-scroll') | |
| 49 | + const playhead = reel.querySelector('.playhead') | |
| 50 | + | |
| 51 | + // On narrow screens the reel scrolls: keep the playhead in view while it plays, like an editor does | |
| 52 | + function follow() { | |
| 53 | + if (scroller.scrollWidth <= scroller.clientWidth) return | |
| 54 | + const end = performance.now() + 5000 | |
| 55 | + const step = () => { | |
| 56 | + const x = playhead.getBoundingClientRect().left - scroller.getBoundingClientRect().left + scroller.scrollLeft | |
| 57 | + scroller.scrollLeft = x - scroller.clientWidth / 2 | |
| 58 | + if (performance.now() < end) requestAnimationFrame(step) | |
| 59 | + } | |
| 60 | + requestAnimationFrame(step) | |
| 61 | + } | |
| 62 | + | |
| 63 | + const run = () => setTimeout(() => { | |
| 64 | + reel.classList.add('run') | |
| 65 | + if (!matchMedia('(prefers-reduced-motion: reduce)').matches) follow() | |
| 66 | + }, 350) | |
| 67 | + if (!('IntersectionObserver' in window)) { | |
| 68 | + reel.classList.add('run') | |
| 69 | + } else { | |
| 70 | + const observer = new IntersectionObserver((entries) => { | |
| 71 | + if (entries.some((e) => e.isIntersecting)) { | |
| 72 | + observer.disconnect() | |
| 73 | + document.fonts.ready.then(run) | |
| 74 | + } | |
| 75 | + }, { threshold: 0.35 }) | |
| 76 | + observer.observe(reel) | |
| 77 | + } | |
| 78 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,78 @@ | |||
| 1 | +// Progressive enhancement only: without this file every screenshot is listed and the reel shows the finished project. | ||
| 2 | + | ||
| 3 | +// Tour: screenshots as tabs (WAI-ARIA tabs pattern; up/down on the vertical list, left/right on mobile) | ||
| 4 | +const tabs = [...document.querySelectorAll('.tour-tabs [role="tab"]')] | ||
| 5 | + | ||
| 6 | +function select(tab, focus) { | ||
| 7 | + for (const t of tabs) { | ||
| 8 | + const on = t === tab | ||
| 9 | + t.setAttribute('aria-selected', String(on)) | ||
| 10 | + t.tabIndex = on ? 0 : -1 | ||
| 11 | + document.getElementById(t.getAttribute('aria-controls')).hidden = !on | ||
| 12 | + } | ||
| 13 | + if (focus) { | ||
| 14 | + tab.focus() | ||
| 15 | + tab.scrollIntoView({ block: 'nearest', inline: 'nearest' }) | ||
| 16 | + } | ||
| 17 | +} | ||
| 18 | + | ||
| 19 | +for (const tab of tabs) { | ||
| 20 | + tab.addEventListener('click', () => select(tab, false)) | ||
| 21 | + tab.addEventListener('keydown', (e) => { | ||
| 22 | + const i = tabs.indexOf(tab) | ||
| 23 | + const next = { ArrowDown: i + 1, ArrowRight: i + 1, ArrowUp: i - 1, ArrowLeft: i - 1, Home: 0, End: tabs.length - 1 }[e.key] | ||
| 24 | + if (next === undefined) return | ||
| 25 | + e.preventDefault() | ||
| 26 | + select(tabs[(next + tabs.length) % tabs.length], true) | ||
| 27 | + }) | ||
| 28 | +} | ||
| 29 | +if (tabs.length) select(tabs[0], false) | ||
| 30 | + | ||
| 31 | +// Copy buttons | ||
| 32 | +for (const button of document.querySelectorAll('[data-copy]')) { | ||
| 33 | + button.addEventListener('click', async () => { | ||
| 34 | + const text = document.getElementById(button.dataset.copy).textContent | ||
| 35 | + try { | ||
| 36 | + await navigator.clipboard.writeText(text) | ||
| 37 | + button.textContent = 'Copied' | ||
| 38 | + } catch { | ||
| 39 | + button.textContent = 'Copy failed' | ||
| 40 | + } | ||
| 41 | + setTimeout(() => { button.textContent = 'Copy' }, 1600) | ||
| 42 | + }) | ||
| 43 | +} | ||
| 44 | + | ||
| 45 | +// Reel: the playhead sweeps the project once, the first time it comes into view | ||
| 46 | +const reel = document.querySelector('[data-reel]') | ||
| 47 | +if (reel) { | ||
| 48 | + const scroller = reel.querySelector('.reel-scroll') | ||
| 49 | + const playhead = reel.querySelector('.playhead') | ||
| 50 | + | ||
| 51 | + // On narrow screens the reel scrolls: keep the playhead in view while it plays, like an editor does | ||
| 52 | + function follow() { | ||
| 53 | + if (scroller.scrollWidth <= scroller.clientWidth) return | ||
| 54 | + const end = performance.now() + 5000 | ||
| 55 | + const step = () => { | ||
| 56 | + const x = playhead.getBoundingClientRect().left - scroller.getBoundingClientRect().left + scroller.scrollLeft | ||
| 57 | + scroller.scrollLeft = x - scroller.clientWidth / 2 | ||
| 58 | + if (performance.now() < end) requestAnimationFrame(step) | ||
| 59 | + } | ||
| 60 | + requestAnimationFrame(step) | ||
| 61 | + } | ||
| 62 | + | ||
| 63 | + const run = () => setTimeout(() => { | ||
| 64 | + reel.classList.add('run') | ||
| 65 | + if (!matchMedia('(prefers-reduced-motion: reduce)').matches) follow() | ||
| 66 | + }, 350) | ||
| 67 | + if (!('IntersectionObserver' in window)) { | ||
| 68 | + reel.classList.add('run') | ||
| 69 | + } else { | ||
| 70 | + const observer = new IntersectionObserver((entries) => { | ||
| 71 | + if (entries.some((e) => e.isIntersecting)) { | ||
| 72 | + observer.disconnect() | ||
| 73 | + document.fonts.ready.then(run) | ||
| 74 | + } | ||
| 75 | + }, { threshold: 0.35 }) | ||
| 76 | + observer.observe(reel) | ||
| 77 | + } | ||
| 78 | +} | ||
added
site/public/styles.css +415 -0 | new file mode 100644 | ||
| @@ -0,0 +1,415 @@ | ||
| 1 | +/* Daryl site. Colors and fonts come from the app (web/src/styles.css, montage/Timeline.vue), | |
| 2 | + so the screenshots sit at home. */ | |
| 3 | + | |
| 4 | +@font-face { font-family: 'IBM Plex Sans'; font-weight: 400; font-display: swap; src: url(fonts/ibm-plex-sans-latin-400-normal.woff2) format('woff2'); } | |
| 5 | +@font-face { font-family: 'IBM Plex Sans'; font-weight: 500; font-display: swap; src: url(fonts/ibm-plex-sans-latin-500-normal.woff2) format('woff2'); } | |
| 6 | +@font-face { font-family: 'IBM Plex Sans'; font-weight: 600; font-display: swap; src: url(fonts/ibm-plex-sans-latin-600-normal.woff2) format('woff2'); } | |
| 7 | +@font-face { font-family: 'IBM Plex Mono'; font-weight: 400; font-display: swap; src: url(fonts/ibm-plex-mono-latin-400-normal.woff2) format('woff2'); } | |
| 8 | +@font-face { font-family: 'IBM Plex Mono'; font-weight: 500; font-display: swap; src: url(fonts/ibm-plex-mono-latin-500-normal.woff2) format('woff2'); } | |
| 9 | + | |
| 10 | +:root { | |
| 11 | + --bg: #FAFAF8; | |
| 12 | + --panel: #FFFFFF; | |
| 13 | + --ink: #18191B; | |
| 14 | + --ink-2: #2B2C2E; | |
| 15 | + --muted: #5D5E5A; | |
| 16 | + --faint: #8B8C88; | |
| 17 | + --line: #E6E6E2; | |
| 18 | + --line-soft: #EFEFEC; | |
| 19 | + --hatch-a: #E8E8E4; | |
| 20 | + --hatch-b: #F2F2EF; | |
| 21 | + --red: #E5484D; | |
| 22 | + --red-ink: #C93A3F; | |
| 23 | + | |
| 24 | + /* the editing timeline lanes */ | |
| 25 | + --motion: #7C5CE0; | |
| 26 | + --zoom: #E8B02F; | |
| 27 | + --broll: #3B82C4; | |
| 28 | + --music: #2F9E83; | |
| 29 | + | |
| 30 | + --sans: 'IBM Plex Sans', ui-sans-serif, system-ui, sans-serif; | |
| 31 | + --mono: 'IBM Plex Mono', ui-monospace, Consolas, monospace; | |
| 32 | + --page: 1160px; | |
| 33 | + --gutter: clamp(20px, 4vw, 40px); | |
| 34 | +} | |
| 35 | + | |
| 36 | +/* ------------------------------------------------------------------ base */ | |
| 37 | +*, *::before, *::after { box-sizing: border-box; } | |
| 38 | + | |
| 39 | +html { scroll-behavior: smooth; scroll-padding-top: 24px; } | |
| 40 | + | |
| 41 | +body { | |
| 42 | + margin: 0; | |
| 43 | + font-family: var(--sans); | |
| 44 | + font-size: 1.0625rem; | |
| 45 | + line-height: 1.55; | |
| 46 | + color: var(--ink); | |
| 47 | + background: var(--bg); | |
| 48 | + -webkit-font-smoothing: antialiased; | |
| 49 | + text-rendering: optimizeLegibility; | |
| 50 | +} | |
| 51 | + | |
| 52 | +::selection { background: color-mix(in oklch, var(--red) 24%, transparent); } | |
| 53 | + | |
| 54 | +h1, h2, h3, p, dl, dd, ul, ol, figure { margin: 0; } | |
| 55 | +ul, ol { padding: 0; list-style: none; } | |
| 56 | + | |
| 57 | +a { color: inherit; text-underline-offset: 3px; text-decoration-thickness: 1px; } | |
| 58 | +a:hover { color: var(--red-ink); } | |
| 59 | + | |
| 60 | +code { | |
| 61 | + font-family: var(--mono); | |
| 62 | + font-size: 0.875em; | |
| 63 | + padding: 0.05em 0.35em; | |
| 64 | + border-radius: 4px; | |
| 65 | + background: var(--line-soft); | |
| 66 | +} | |
| 67 | + | |
| 68 | +:focus-visible { outline: 2px solid var(--red); outline-offset: 2px; border-radius: 4px; } | |
| 69 | + | |
| 70 | +[hidden] { display: none !important; } | |
| 71 | + | |
| 72 | +.sr-only { | |
| 73 | + position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; | |
| 74 | + overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; | |
| 75 | +} | |
| 76 | + | |
| 77 | +.skip { | |
| 78 | + position: absolute; left: 12px; top: -48px; z-index: 10; | |
| 79 | + padding: 8px 14px; border-radius: 6px; background: var(--ink); color: #fff; | |
| 80 | +} | |
| 81 | +.skip:focus { top: 12px; } | |
| 82 | + | |
| 83 | +main > section, | |
| 84 | +main > figure, | |
| 85 | +.top, | |
| 86 | +.foot { | |
| 87 | + width: min(100% - 2 * var(--gutter), var(--page)); | |
| 88 | + margin-inline: auto; | |
| 89 | +} | |
| 90 | + | |
| 91 | +main > section + section { margin-top: clamp(96px, 12vw, 152px); } | |
| 92 | + | |
| 93 | +h2 { | |
| 94 | + font-weight: 600; | |
| 95 | + font-size: clamp(1.625rem, 3vw, 2.25rem); | |
| 96 | + line-height: 1.15; | |
| 97 | + letter-spacing: -0.025em; | |
| 98 | + max-width: 24ch; | |
| 99 | + text-wrap: balance; | |
| 100 | +} | |
| 101 | + | |
| 102 | +h3 { font-size: 1.125rem; font-weight: 600; line-height: 1.35; } | |
| 103 | + | |
| 104 | +.section-lead { margin-top: 14px; max-width: 62ch; color: var(--muted); } | |
| 105 | + | |
| 106 | +.note { margin-top: 16px; color: var(--muted); font-size: 0.9375rem; max-width: 70ch; } | |
| 107 | + | |
| 108 | +/* ------------------------------------------------------------------ buttons */ | |
| 109 | +.btn { | |
| 110 | + display: inline-flex; align-items: center; justify-content: center; | |
| 111 | + height: 44px; padding: 0 20px; | |
| 112 | + border-radius: 6px; | |
| 113 | + font: 500 0.9375rem/1 var(--sans); | |
| 114 | + text-decoration: none; | |
| 115 | + white-space: nowrap; | |
| 116 | + cursor: pointer; | |
| 117 | + border: 0; | |
| 118 | +} | |
| 119 | +.btn-sm { height: 34px; padding: 0 14px; font-size: 0.875rem; } | |
| 120 | + | |
| 121 | +/* The rickub portal mark, drawn in the button's text color */ | |
| 122 | +.btn-icon { flex: none; width: 1.1em; height: 1.1em; margin-right: 0.5em; } | |
| 123 | +.btn-icon path { fill: none; stroke: currentColor; stroke-width: 7; stroke-linecap: round; } | |
| 124 | +.btn-icon circle { fill: currentColor; } | |
| 125 | + | |
| 126 | +.btn-ink { background: var(--ink); color: #fff; } | |
| 127 | +.btn-ink:hover { color: #fff; background: var(--ink-2); } | |
| 128 | +.btn-red { background: var(--red); color: #fff; } | |
| 129 | +.btn-red:hover { color: #fff; background: var(--red-ink); } | |
| 130 | +.btn-line { background: var(--panel); color: var(--ink); box-shadow: inset 0 0 0 1px var(--line); } | |
| 131 | +.btn-line:hover { color: var(--ink); box-shadow: inset 0 0 0 1px var(--faint); } | |
| 132 | + | |
| 133 | +/* ------------------------------------------------------------------ header */ | |
| 134 | +.top { | |
| 135 | + display: flex; align-items: center; gap: 24px; | |
| 136 | + padding-block: 20px; | |
| 137 | +} | |
| 138 | + | |
| 139 | +.brand { | |
| 140 | + display: inline-flex; align-items: center; gap: 10px; | |
| 141 | + font-weight: 600; font-size: 1.1875rem; letter-spacing: -0.01em; text-decoration: none; | |
| 142 | +} | |
| 143 | +.brand:hover { color: inherit; } | |
| 144 | +.brand img { display: block; border-radius: 7px; } | |
| 145 | + | |
| 146 | +.top-nav { display: flex; gap: 4px; margin-left: auto; } | |
| 147 | +.top-nav a { | |
| 148 | + padding: 6px 12px; border-radius: 6px; | |
| 149 | + color: var(--muted); text-decoration: none; font-size: 0.9375rem; | |
| 150 | +} | |
| 151 | +.top-nav a:hover { color: var(--ink); background: var(--line-soft); } | |
| 152 | + | |
| 153 | +@media (max-width: 720px) { | |
| 154 | + .top-nav { display: none; } | |
| 155 | + .top .btn { margin-left: auto; } | |
| 156 | +} | |
| 157 | + | |
| 158 | +/* ------------------------------------------------------------------ hero */ | |
| 159 | +.hero { padding-top: clamp(32px, 5vw, 64px); } | |
| 160 | + | |
| 161 | +.hero h1 { | |
| 162 | + font-weight: 600; | |
| 163 | + font-size: clamp(2.375rem, 5.2vw, 4.25rem); | |
| 164 | + line-height: 1.03; | |
| 165 | + letter-spacing: -0.04em; | |
| 166 | + max-width: 17ch; | |
| 167 | + text-wrap: balance; | |
| 168 | +} | |
| 169 | + | |
| 170 | +.lead { | |
| 171 | + margin-top: 24px; | |
| 172 | + max-width: 58ch; | |
| 173 | + font-size: clamp(1.125rem, 1.6vw, 1.25rem); | |
| 174 | + color: var(--muted); | |
| 175 | +} | |
| 176 | + | |
| 177 | +.actions { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 32px; } | |
| 178 | + | |
| 179 | +.perk { | |
| 180 | + display: flex; align-items: center; gap: 8px; | |
| 181 | + margin-top: 18px; | |
| 182 | + color: var(--muted); font-size: 0.9375rem; | |
| 183 | +} | |
| 184 | +.perk svg { flex: none; fill: none; stroke: var(--music); stroke-width: 1.75; stroke-linecap: round; stroke-linejoin: round; } | |
| 185 | + | |
| 186 | +/* ------------------------------------------------------------------ the reel: a project as an editing timeline */ | |
| 187 | +.reel { margin-top: clamp(40px, 5vw, 64px); } | |
| 188 | + | |
| 189 | +.reel-scroll { overflow-x: auto; scrollbar-width: thin; } | |
| 190 | + | |
| 191 | +.reel-inner { | |
| 192 | + --name: 96px; | |
| 193 | + --units: 30; | |
| 194 | + --dur: 4.5s; | |
| 195 | + min-width: 960px; | |
| 196 | + padding: 14px 18px 18px; | |
| 197 | + border-radius: 10px; | |
| 198 | + background: var(--panel); | |
| 199 | + box-shadow: 0 0 0 1px var(--line), 0 30px 60px -36px rgba(24, 25, 27, 0.35); | |
| 200 | +} | |
| 201 | + | |
| 202 | +.ruler { | |
| 203 | + display: flex; justify-content: space-between; | |
| 204 | + margin-left: var(--name); | |
| 205 | + padding-bottom: 8px; | |
| 206 | + border-bottom: 1px solid var(--line); | |
| 207 | + font-family: var(--mono); font-size: 0.75rem; color: var(--faint); | |
| 208 | +} | |
| 209 | +.ruler li { width: 0; display: flex; justify-content: center; } | |
| 210 | +.ruler li:first-child { justify-content: flex-start; } | |
| 211 | +.ruler li:last-child { justify-content: flex-end; } | |
| 212 | + | |
| 213 | +.lanes { position: relative; padding-top: 10px; } | |
| 214 | + | |
| 215 | +.lane { | |
| 216 | + display: grid; | |
| 217 | + grid-template-columns: var(--name) 1fr; | |
| 218 | + align-items: center; | |
| 219 | + height: 40px; | |
| 220 | +} | |
| 221 | +.lane + .lane { margin-top: 6px; } | |
| 222 | + | |
| 223 | +.lane-name { font-size: 0.8125rem; color: var(--muted); } | |
| 224 | + | |
| 225 | +.lane ul { position: relative; height: 100%; } | |
| 226 | + | |
| 227 | +.lane li { | |
| 228 | + position: absolute; top: 0; bottom: 0; | |
| 229 | + left: calc(var(--a) / var(--units) * 100%); | |
| 230 | + width: calc(var(--b) / var(--units) * 100% - 4px); | |
| 231 | + display: flex; align-items: center; | |
| 232 | + padding: 0 8px; | |
| 233 | + border-radius: 5px; | |
| 234 | + background: var(--c); | |
| 235 | + color: #fff; | |
| 236 | + font-size: 0.84375rem; font-weight: 500; | |
| 237 | + white-space: nowrap; overflow: hidden; | |
| 238 | +} | |
| 239 | +.lane[style*="--zoom"] li { color: var(--ink); } | |
| 240 | + | |
| 241 | +/* The playhead: a red line with the logo's head, over every lane */ | |
| 242 | +.playhead { | |
| 243 | + position: absolute; top: -6px; bottom: -6px; | |
| 244 | + left: calc(var(--name) + 100% - var(--name)); | |
| 245 | + width: 2px; margin-left: -1px; | |
| 246 | + background: var(--red); | |
| 247 | + pointer-events: none; | |
| 248 | +} | |
| 249 | +.playhead::before { | |
| 250 | + content: ""; | |
| 251 | + position: absolute; top: -10px; left: 50%; | |
| 252 | + width: 12px; height: 10px; margin-left: -6px; | |
| 253 | + border-radius: 3px; | |
| 254 | + background: var(--red); | |
| 255 | +} | |
| 256 | + | |
| 257 | +.reel figcaption { margin-top: 14px; color: var(--faint); font-size: 0.9375rem; } | |
| 258 | + | |
| 259 | +/* The one orchestrated moment. Before .run, the project is empty (hatched like an empty lane in the app); | |
| 260 | + then the playhead sweeps once and every clip fills in as the playhead reaches it. */ | |
| 261 | +.js .reel .lane li::before { | |
| 262 | + content: ""; | |
| 263 | + position: absolute; inset: 0; | |
| 264 | + background: repeating-linear-gradient(-45deg, var(--hatch-a) 0 6px, var(--hatch-b) 6px 12px); | |
| 265 | + opacity: 0; | |
| 266 | + transition: opacity 160ms ease-out; | |
| 267 | +} | |
| 268 | +.js .reel .lane li { transition: color 160ms ease-out; } | |
| 269 | +.js .reel .lane li span { position: relative; } | |
| 270 | +.js .reel:not(.run) .lane li { color: var(--faint); } | |
| 271 | +.js .reel:not(.run) .lane li::before { opacity: 1; } | |
| 272 | +.js .reel.run .lane li, | |
| 273 | +.js .reel.run .lane li::before { | |
| 274 | + transition-delay: calc(var(--a) / var(--units) * var(--dur) + 60ms); | |
| 275 | +} | |
| 276 | +.js .reel:not(.run) .playhead { left: var(--name); } | |
| 277 | +.js .reel.run .playhead { animation: sweep var(--dur) linear both; } | |
| 278 | + | |
| 279 | +@keyframes sweep { | |
| 280 | + from { left: var(--name); } | |
| 281 | + to { left: 100%; } | |
| 282 | +} | |
| 283 | + | |
| 284 | +/* ------------------------------------------------------------------ tour: tabs on the left, screenshot on the right */ | |
| 285 | +.reel + .tour { margin-top: clamp(96px, 12vw, 152px); } | |
| 286 | + | |
| 287 | +.tour { | |
| 288 | + display: grid; | |
| 289 | + grid-template-columns: minmax(0, 290px) minmax(0, 1fr); | |
| 290 | + gap: 40px; | |
| 291 | + align-items: start; | |
| 292 | +} | |
| 293 | + | |
| 294 | +.tour-tabs { display: flex; flex-direction: column; gap: 2px; } | |
| 295 | + | |
| 296 | +.tour-tabs button { | |
| 297 | + display: block; | |
| 298 | + width: 100%; | |
| 299 | + padding: 10px 14px 10px 16px; | |
| 300 | + border: 0; border-radius: 6px; | |
| 301 | + background: transparent; | |
| 302 | + color: var(--ink); | |
| 303 | + font: inherit; | |
| 304 | + text-align: left; | |
| 305 | + cursor: pointer; | |
| 306 | + position: relative; | |
| 307 | +} | |
| 308 | +.tab-title { display: block; font-weight: 500; line-height: 1.35; color: var(--muted); } | |
| 309 | +.tab-desc { display: none; margin-top: 4px; font-size: 0.9375rem; line-height: 1.45; color: var(--muted); } | |
| 310 | + | |
| 311 | +.tour-tabs button:hover .tab-title { color: var(--ink); } | |
| 312 | +.tour-tabs button[aria-selected="true"] { background: var(--panel); box-shadow: 0 0 0 1px var(--line); } | |
| 313 | +.tour-tabs button[aria-selected="true"]::before { | |
| 314 | + content: ""; position: absolute; left: 0; top: 10px; bottom: 10px; width: 3px; border-radius: 0 2px 2px 0; background: var(--red); | |
| 315 | +} | |
| 316 | +.tour-tabs button[aria-selected="true"] .tab-title { color: var(--ink); font-weight: 600; } | |
| 317 | +.tour-tabs button[aria-selected="true"] .tab-desc { display: block; } | |
| 318 | +/* without JS every screenshot is listed, so every description shows */ | |
| 319 | +html:not(.js) .tab-desc { display: block; } | |
| 320 | + | |
| 321 | +.tour-shots { display: grid; gap: 24px; } | |
| 322 | + | |
| 323 | +.frame { | |
| 324 | + aspect-ratio: 2880 / 1800; | |
| 325 | + border-radius: 10px; | |
| 326 | + overflow: hidden; | |
| 327 | + background: #fff; | |
| 328 | + box-shadow: 0 0 0 1px var(--line), 0 30px 60px -36px rgba(24, 25, 27, 0.35); | |
| 329 | +} | |
| 330 | +.frame img { display: block; width: 100%; height: 100%; object-fit: contain; object-position: top; } | |
| 331 | + | |
| 332 | +/* Small screens: titles become a scrolling row above the screenshot */ | |
| 333 | +@media (max-width: 900px) { | |
| 334 | + .tour { grid-template-columns: 1fr; gap: 16px; } | |
| 335 | + .tour-tabs { flex-direction: row; overflow-x: auto; scrollbar-width: none; margin-inline: calc(-1 * var(--gutter)); padding: 1px var(--gutter); } | |
| 336 | + .tour-tabs::-webkit-scrollbar { display: none; } | |
| 337 | + .tour-tabs button { flex: none; width: auto; padding: 8px 14px; white-space: nowrap; } | |
| 338 | + .tour-tabs button[aria-selected="true"]::before { display: none; } | |
| 339 | + .tour-tabs button[aria-selected="true"] .tab-desc { display: none; } | |
| 340 | + .frame { border-radius: 8px; } | |
| 341 | +} | |
| 342 | + | |
| 343 | +/* ------------------------------------------------------------------ how */ | |
| 344 | +.facts { | |
| 345 | + display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 40px; | |
| 346 | + margin-top: 48px; | |
| 347 | +} | |
| 348 | +.facts > div { padding-top: 18px; border-top: 2px solid var(--ink); } | |
| 349 | +.facts dt { font-weight: 600; font-size: 1.125rem; } | |
| 350 | +.facts dd { margin-top: 8px; color: var(--muted); } | |
| 351 | + | |
| 352 | +@media (max-width: 820px) { .facts { grid-template-columns: 1fr; gap: 28px; } } | |
| 353 | + | |
| 354 | +.skills { | |
| 355 | + display: grid; grid-template-columns: minmax(0, 0.8fr) minmax(0, 1.2fr); gap: 40px; | |
| 356 | + margin-top: 72px; | |
| 357 | + padding: clamp(24px, 4vw, 40px); | |
| 358 | + border-radius: 10px; | |
| 359 | + background: var(--ink); | |
| 360 | + color: #fff; | |
| 361 | +} | |
| 362 | +.skills-copy p { margin-top: 10px; color: #B5B6B2; max-width: 40ch; } | |
| 363 | + | |
| 364 | +.skills-list { display: grid; gap: 18px; } | |
| 365 | +.skills-list li { display: grid; gap: 4px; } | |
| 366 | +.skills-list code { | |
| 367 | + justify-self: start; | |
| 368 | + padding: 0; background: none; | |
| 369 | + color: #fff; font-size: 0.9375rem; font-weight: 500; | |
| 370 | +} | |
| 371 | +.skills-list span { color: #B5B6B2; font-size: 0.9375rem; } | |
| 372 | + | |
| 373 | +@media (max-width: 820px) { .skills { grid-template-columns: 1fr; gap: 24px; } } | |
| 374 | + | |
| 375 | +/* ------------------------------------------------------------------ a personal tool */ | |
| 376 | +.honest { | |
| 377 | + display: grid; grid-template-columns: minmax(0, 0.8fr) minmax(0, 1.2fr); gap: 40px; | |
| 378 | +} | |
| 379 | +.honest-body { display: grid; gap: 16px; max-width: 60ch; color: var(--muted); } | |
| 380 | +.honest-body p:first-child { color: var(--ink); font-size: 1.1875rem; } | |
| 381 | + | |
| 382 | +@media (max-width: 820px) { .honest { grid-template-columns: 1fr; gap: 20px; } } | |
| 383 | + | |
| 384 | +/* ------------------------------------------------------------------ try it */ | |
| 385 | +.code { | |
| 386 | + position: relative; | |
| 387 | + margin-top: 28px; | |
| 388 | + max-width: 760px; | |
| 389 | + border-radius: 10px; | |
| 390 | + background: var(--ink); | |
| 391 | + color: #fff; | |
| 392 | +} | |
| 393 | +.code pre { margin: 0; padding: 22px 24px; overflow-x: auto; } | |
| 394 | +.code code { | |
| 395 | + background: none; padding: 0; | |
| 396 | + font-size: 0.9375rem; line-height: 1.75; | |
| 397 | +} | |
| 398 | +.code .copy { position: absolute; top: 14px; right: 14px; } | |
| 399 | +@media (max-width: 720px) { .code pre { padding-top: 60px; } } | |
| 400 | + | |
| 401 | +/* ------------------------------------------------------------------ footer */ | |
| 402 | +.foot { | |
| 403 | + display: flex; flex-wrap: wrap; justify-content: space-between; gap: 12px 32px; | |
| 404 | + margin-top: clamp(96px, 12vw, 152px); | |
| 405 | + padding-block: 28px 40px; | |
| 406 | + border-top: 1px solid var(--line); | |
| 407 | + color: var(--muted); font-size: 0.9375rem; | |
| 408 | +} | |
| 409 | +.foot nav { display: flex; flex-wrap: wrap; gap: 8px 24px; } | |
| 410 | + | |
| 411 | +/* ------------------------------------------------------------------ motion */ | |
| 412 | +@media (prefers-reduced-motion: reduce) { | |
| 413 | + html { scroll-behavior: auto; } | |
| 414 | + *, *::before, *::after { transition: none !important; animation: none !important; } | |
| 415 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,415 @@ | |||
| 1 | +/* Daryl site. Colors and fonts come from the app (web/src/styles.css, montage/Timeline.vue), | ||
| 2 | + so the screenshots sit at home. */ | ||
| 3 | + | ||
| 4 | +@font-face { font-family: 'IBM Plex Sans'; font-weight: 400; font-display: swap; src: url(fonts/ibm-plex-sans-latin-400-normal.woff2) format('woff2'); } | ||
| 5 | +@font-face { font-family: 'IBM Plex Sans'; font-weight: 500; font-display: swap; src: url(fonts/ibm-plex-sans-latin-500-normal.woff2) format('woff2'); } | ||
| 6 | +@font-face { font-family: 'IBM Plex Sans'; font-weight: 600; font-display: swap; src: url(fonts/ibm-plex-sans-latin-600-normal.woff2) format('woff2'); } | ||
| 7 | +@font-face { font-family: 'IBM Plex Mono'; font-weight: 400; font-display: swap; src: url(fonts/ibm-plex-mono-latin-400-normal.woff2) format('woff2'); } | ||
| 8 | +@font-face { font-family: 'IBM Plex Mono'; font-weight: 500; font-display: swap; src: url(fonts/ibm-plex-mono-latin-500-normal.woff2) format('woff2'); } | ||
| 9 | + | ||
| 10 | +:root { | ||
| 11 | + --bg: #FAFAF8; | ||
| 12 | + --panel: #FFFFFF; | ||
| 13 | + --ink: #18191B; | ||
| 14 | + --ink-2: #2B2C2E; | ||
| 15 | + --muted: #5D5E5A; | ||
| 16 | + --faint: #8B8C88; | ||
| 17 | + --line: #E6E6E2; | ||
| 18 | + --line-soft: #EFEFEC; | ||
| 19 | + --hatch-a: #E8E8E4; | ||
| 20 | + --hatch-b: #F2F2EF; | ||
| 21 | + --red: #E5484D; | ||
| 22 | + --red-ink: #C93A3F; | ||
| 23 | + | ||
| 24 | + /* the editing timeline lanes */ | ||
| 25 | + --motion: #7C5CE0; | ||
| 26 | + --zoom: #E8B02F; | ||
| 27 | + --broll: #3B82C4; | ||
| 28 | + --music: #2F9E83; | ||
| 29 | + | ||
| 30 | + --sans: 'IBM Plex Sans', ui-sans-serif, system-ui, sans-serif; | ||
| 31 | + --mono: 'IBM Plex Mono', ui-monospace, Consolas, monospace; | ||
| 32 | + --page: 1160px; | ||
| 33 | + --gutter: clamp(20px, 4vw, 40px); | ||
| 34 | +} | ||
| 35 | + | ||
| 36 | +/* ------------------------------------------------------------------ base */ | ||
| 37 | +*, *::before, *::after { box-sizing: border-box; } | ||
| 38 | + | ||
| 39 | +html { scroll-behavior: smooth; scroll-padding-top: 24px; } | ||
| 40 | + | ||
| 41 | +body { | ||
| 42 | + margin: 0; | ||
| 43 | + font-family: var(--sans); | ||
| 44 | + font-size: 1.0625rem; | ||
| 45 | + line-height: 1.55; | ||
| 46 | + color: var(--ink); | ||
| 47 | + background: var(--bg); | ||
| 48 | + -webkit-font-smoothing: antialiased; | ||
| 49 | + text-rendering: optimizeLegibility; | ||
| 50 | +} | ||
| 51 | + | ||
| 52 | +::selection { background: color-mix(in oklch, var(--red) 24%, transparent); } | ||
| 53 | + | ||
| 54 | +h1, h2, h3, p, dl, dd, ul, ol, figure { margin: 0; } | ||
| 55 | +ul, ol { padding: 0; list-style: none; } | ||
| 56 | + | ||
| 57 | +a { color: inherit; text-underline-offset: 3px; text-decoration-thickness: 1px; } | ||
| 58 | +a:hover { color: var(--red-ink); } | ||
| 59 | + | ||
| 60 | +code { | ||
| 61 | + font-family: var(--mono); | ||
| 62 | + font-size: 0.875em; | ||
| 63 | + padding: 0.05em 0.35em; | ||
| 64 | + border-radius: 4px; | ||
| 65 | + background: var(--line-soft); | ||
| 66 | +} | ||
| 67 | + | ||
| 68 | +:focus-visible { outline: 2px solid var(--red); outline-offset: 2px; border-radius: 4px; } | ||
| 69 | + | ||
| 70 | +[hidden] { display: none !important; } | ||
| 71 | + | ||
| 72 | +.sr-only { | ||
| 73 | + position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; | ||
| 74 | + overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; | ||
| 75 | +} | ||
| 76 | + | ||
| 77 | +.skip { | ||
| 78 | + position: absolute; left: 12px; top: -48px; z-index: 10; | ||
| 79 | + padding: 8px 14px; border-radius: 6px; background: var(--ink); color: #fff; | ||
| 80 | +} | ||
| 81 | +.skip:focus { top: 12px; } | ||
| 82 | + | ||
| 83 | +main > section, | ||
| 84 | +main > figure, | ||
| 85 | +.top, | ||
| 86 | +.foot { | ||
| 87 | + width: min(100% - 2 * var(--gutter), var(--page)); | ||
| 88 | + margin-inline: auto; | ||
| 89 | +} | ||
| 90 | + | ||
| 91 | +main > section + section { margin-top: clamp(96px, 12vw, 152px); } | ||
| 92 | + | ||
| 93 | +h2 { | ||
| 94 | + font-weight: 600; | ||
| 95 | + font-size: clamp(1.625rem, 3vw, 2.25rem); | ||
| 96 | + line-height: 1.15; | ||
| 97 | + letter-spacing: -0.025em; | ||
| 98 | + max-width: 24ch; | ||
| 99 | + text-wrap: balance; | ||
| 100 | +} | ||
| 101 | + | ||
| 102 | +h3 { font-size: 1.125rem; font-weight: 600; line-height: 1.35; } | ||
| 103 | + | ||
| 104 | +.section-lead { margin-top: 14px; max-width: 62ch; color: var(--muted); } | ||
| 105 | + | ||
| 106 | +.note { margin-top: 16px; color: var(--muted); font-size: 0.9375rem; max-width: 70ch; } | ||
| 107 | + | ||
| 108 | +/* ------------------------------------------------------------------ buttons */ | ||
| 109 | +.btn { | ||
| 110 | + display: inline-flex; align-items: center; justify-content: center; | ||
| 111 | + height: 44px; padding: 0 20px; | ||
| 112 | + border-radius: 6px; | ||
| 113 | + font: 500 0.9375rem/1 var(--sans); | ||
| 114 | + text-decoration: none; | ||
| 115 | + white-space: nowrap; | ||
| 116 | + cursor: pointer; | ||
| 117 | + border: 0; | ||
| 118 | +} | ||
| 119 | +.btn-sm { height: 34px; padding: 0 14px; font-size: 0.875rem; } | ||
| 120 | + | ||
| 121 | +/* The rickub portal mark, drawn in the button's text color */ | ||
| 122 | +.btn-icon { flex: none; width: 1.1em; height: 1.1em; margin-right: 0.5em; } | ||
| 123 | +.btn-icon path { fill: none; stroke: currentColor; stroke-width: 7; stroke-linecap: round; } | ||
| 124 | +.btn-icon circle { fill: currentColor; } | ||
| 125 | + | ||
| 126 | +.btn-ink { background: var(--ink); color: #fff; } | ||
| 127 | +.btn-ink:hover { color: #fff; background: var(--ink-2); } | ||
| 128 | +.btn-red { background: var(--red); color: #fff; } | ||
| 129 | +.btn-red:hover { color: #fff; background: var(--red-ink); } | ||
| 130 | +.btn-line { background: var(--panel); color: var(--ink); box-shadow: inset 0 0 0 1px var(--line); } | ||
| 131 | +.btn-line:hover { color: var(--ink); box-shadow: inset 0 0 0 1px var(--faint); } | ||
| 132 | + | ||
| 133 | +/* ------------------------------------------------------------------ header */ | ||
| 134 | +.top { | ||
| 135 | + display: flex; align-items: center; gap: 24px; | ||
| 136 | + padding-block: 20px; | ||
| 137 | +} | ||
| 138 | + | ||
| 139 | +.brand { | ||
| 140 | + display: inline-flex; align-items: center; gap: 10px; | ||
| 141 | + font-weight: 600; font-size: 1.1875rem; letter-spacing: -0.01em; text-decoration: none; | ||
| 142 | +} | ||
| 143 | +.brand:hover { color: inherit; } | ||
| 144 | +.brand img { display: block; border-radius: 7px; } | ||
| 145 | + | ||
| 146 | +.top-nav { display: flex; gap: 4px; margin-left: auto; } | ||
| 147 | +.top-nav a { | ||
| 148 | + padding: 6px 12px; border-radius: 6px; | ||
| 149 | + color: var(--muted); text-decoration: none; font-size: 0.9375rem; | ||
| 150 | +} | ||
| 151 | +.top-nav a:hover { color: var(--ink); background: var(--line-soft); } | ||
| 152 | + | ||
| 153 | +@media (max-width: 720px) { | ||
| 154 | + .top-nav { display: none; } | ||
| 155 | + .top .btn { margin-left: auto; } | ||
| 156 | +} | ||
| 157 | + | ||
| 158 | +/* ------------------------------------------------------------------ hero */ | ||
| 159 | +.hero { padding-top: clamp(32px, 5vw, 64px); } | ||
| 160 | + | ||
| 161 | +.hero h1 { | ||
| 162 | + font-weight: 600; | ||
| 163 | + font-size: clamp(2.375rem, 5.2vw, 4.25rem); | ||
| 164 | + line-height: 1.03; | ||
| 165 | + letter-spacing: -0.04em; | ||
| 166 | + max-width: 17ch; | ||
| 167 | + text-wrap: balance; | ||
| 168 | +} | ||
| 169 | + | ||
| 170 | +.lead { | ||
| 171 | + margin-top: 24px; | ||
| 172 | + max-width: 58ch; | ||
| 173 | + font-size: clamp(1.125rem, 1.6vw, 1.25rem); | ||
| 174 | + color: var(--muted); | ||
| 175 | +} | ||
| 176 | + | ||
| 177 | +.actions { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 32px; } | ||
| 178 | + | ||
| 179 | +.perk { | ||
| 180 | + display: flex; align-items: center; gap: 8px; | ||
| 181 | + margin-top: 18px; | ||
| 182 | + color: var(--muted); font-size: 0.9375rem; | ||
| 183 | +} | ||
| 184 | +.perk svg { flex: none; fill: none; stroke: var(--music); stroke-width: 1.75; stroke-linecap: round; stroke-linejoin: round; } | ||
| 185 | + | ||
| 186 | +/* ------------------------------------------------------------------ the reel: a project as an editing timeline */ | ||
| 187 | +.reel { margin-top: clamp(40px, 5vw, 64px); } | ||
| 188 | + | ||
| 189 | +.reel-scroll { overflow-x: auto; scrollbar-width: thin; } | ||
| 190 | + | ||
| 191 | +.reel-inner { | ||
| 192 | + --name: 96px; | ||
| 193 | + --units: 30; | ||
| 194 | + --dur: 4.5s; | ||
| 195 | + min-width: 960px; | ||
| 196 | + padding: 14px 18px 18px; | ||
| 197 | + border-radius: 10px; | ||
| 198 | + background: var(--panel); | ||
| 199 | + box-shadow: 0 0 0 1px var(--line), 0 30px 60px -36px rgba(24, 25, 27, 0.35); | ||
| 200 | +} | ||
| 201 | + | ||
| 202 | +.ruler { | ||
| 203 | + display: flex; justify-content: space-between; | ||
| 204 | + margin-left: var(--name); | ||
| 205 | + padding-bottom: 8px; | ||
| 206 | + border-bottom: 1px solid var(--line); | ||
| 207 | + font-family: var(--mono); font-size: 0.75rem; color: var(--faint); | ||
| 208 | +} | ||
| 209 | +.ruler li { width: 0; display: flex; justify-content: center; } | ||
| 210 | +.ruler li:first-child { justify-content: flex-start; } | ||
| 211 | +.ruler li:last-child { justify-content: flex-end; } | ||
| 212 | + | ||
| 213 | +.lanes { position: relative; padding-top: 10px; } | ||
| 214 | + | ||
| 215 | +.lane { | ||
| 216 | + display: grid; | ||
| 217 | + grid-template-columns: var(--name) 1fr; | ||
| 218 | + align-items: center; | ||
| 219 | + height: 40px; | ||
| 220 | +} | ||
| 221 | +.lane + .lane { margin-top: 6px; } | ||
| 222 | + | ||
| 223 | +.lane-name { font-size: 0.8125rem; color: var(--muted); } | ||
| 224 | + | ||
| 225 | +.lane ul { position: relative; height: 100%; } | ||
| 226 | + | ||
| 227 | +.lane li { | ||
| 228 | + position: absolute; top: 0; bottom: 0; | ||
| 229 | + left: calc(var(--a) / var(--units) * 100%); | ||
| 230 | + width: calc(var(--b) / var(--units) * 100% - 4px); | ||
| 231 | + display: flex; align-items: center; | ||
| 232 | + padding: 0 8px; | ||
| 233 | + border-radius: 5px; | ||
| 234 | + background: var(--c); | ||
| 235 | + color: #fff; | ||
| 236 | + font-size: 0.84375rem; font-weight: 500; | ||
| 237 | + white-space: nowrap; overflow: hidden; | ||
| 238 | +} | ||
| 239 | +.lane[style*="--zoom"] li { color: var(--ink); } | ||
| 240 | + | ||
| 241 | +/* The playhead: a red line with the logo's head, over every lane */ | ||
| 242 | +.playhead { | ||
| 243 | + position: absolute; top: -6px; bottom: -6px; | ||
| 244 | + left: calc(var(--name) + 100% - var(--name)); | ||
| 245 | + width: 2px; margin-left: -1px; | ||
| 246 | + background: var(--red); | ||
| 247 | + pointer-events: none; | ||
| 248 | +} | ||
| 249 | +.playhead::before { | ||
| 250 | + content: ""; | ||
| 251 | + position: absolute; top: -10px; left: 50%; | ||
| 252 | + width: 12px; height: 10px; margin-left: -6px; | ||
| 253 | + border-radius: 3px; | ||
| 254 | + background: var(--red); | ||
| 255 | +} | ||
| 256 | + | ||
| 257 | +.reel figcaption { margin-top: 14px; color: var(--faint); font-size: 0.9375rem; } | ||
| 258 | + | ||
| 259 | +/* The one orchestrated moment. Before .run, the project is empty (hatched like an empty lane in the app); | ||
| 260 | + then the playhead sweeps once and every clip fills in as the playhead reaches it. */ | ||
| 261 | +.js .reel .lane li::before { | ||
| 262 | + content: ""; | ||
| 263 | + position: absolute; inset: 0; | ||
| 264 | + background: repeating-linear-gradient(-45deg, var(--hatch-a) 0 6px, var(--hatch-b) 6px 12px); | ||
| 265 | + opacity: 0; | ||
| 266 | + transition: opacity 160ms ease-out; | ||
| 267 | +} | ||
| 268 | +.js .reel .lane li { transition: color 160ms ease-out; } | ||
| 269 | +.js .reel .lane li span { position: relative; } | ||
| 270 | +.js .reel:not(.run) .lane li { color: var(--faint); } | ||
| 271 | +.js .reel:not(.run) .lane li::before { opacity: 1; } | ||
| 272 | +.js .reel.run .lane li, | ||
| 273 | +.js .reel.run .lane li::before { | ||
| 274 | + transition-delay: calc(var(--a) / var(--units) * var(--dur) + 60ms); | ||
| 275 | +} | ||
| 276 | +.js .reel:not(.run) .playhead { left: var(--name); } | ||
| 277 | +.js .reel.run .playhead { animation: sweep var(--dur) linear both; } | ||
| 278 | + | ||
| 279 | +@keyframes sweep { | ||
| 280 | + from { left: var(--name); } | ||
| 281 | + to { left: 100%; } | ||
| 282 | +} | ||
| 283 | + | ||
| 284 | +/* ------------------------------------------------------------------ tour: tabs on the left, screenshot on the right */ | ||
| 285 | +.reel + .tour { margin-top: clamp(96px, 12vw, 152px); } | ||
| 286 | + | ||
| 287 | +.tour { | ||
| 288 | + display: grid; | ||
| 289 | + grid-template-columns: minmax(0, 290px) minmax(0, 1fr); | ||
| 290 | + gap: 40px; | ||
| 291 | + align-items: start; | ||
| 292 | +} | ||
| 293 | + | ||
| 294 | +.tour-tabs { display: flex; flex-direction: column; gap: 2px; } | ||
| 295 | + | ||
| 296 | +.tour-tabs button { | ||
| 297 | + display: block; | ||
| 298 | + width: 100%; | ||
| 299 | + padding: 10px 14px 10px 16px; | ||
| 300 | + border: 0; border-radius: 6px; | ||
| 301 | + background: transparent; | ||
| 302 | + color: var(--ink); | ||
| 303 | + font: inherit; | ||
| 304 | + text-align: left; | ||
| 305 | + cursor: pointer; | ||
| 306 | + position: relative; | ||
| 307 | +} | ||
| 308 | +.tab-title { display: block; font-weight: 500; line-height: 1.35; color: var(--muted); } | ||
| 309 | +.tab-desc { display: none; margin-top: 4px; font-size: 0.9375rem; line-height: 1.45; color: var(--muted); } | ||
| 310 | + | ||
| 311 | +.tour-tabs button:hover .tab-title { color: var(--ink); } | ||
| 312 | +.tour-tabs button[aria-selected="true"] { background: var(--panel); box-shadow: 0 0 0 1px var(--line); } | ||
| 313 | +.tour-tabs button[aria-selected="true"]::before { | ||
| 314 | + content: ""; position: absolute; left: 0; top: 10px; bottom: 10px; width: 3px; border-radius: 0 2px 2px 0; background: var(--red); | ||
| 315 | +} | ||
| 316 | +.tour-tabs button[aria-selected="true"] .tab-title { color: var(--ink); font-weight: 600; } | ||
| 317 | +.tour-tabs button[aria-selected="true"] .tab-desc { display: block; } | ||
| 318 | +/* without JS every screenshot is listed, so every description shows */ | ||
| 319 | +html:not(.js) .tab-desc { display: block; } | ||
| 320 | + | ||
| 321 | +.tour-shots { display: grid; gap: 24px; } | ||
| 322 | + | ||
| 323 | +.frame { | ||
| 324 | + aspect-ratio: 2880 / 1800; | ||
| 325 | + border-radius: 10px; | ||
| 326 | + overflow: hidden; | ||
| 327 | + background: #fff; | ||
| 328 | + box-shadow: 0 0 0 1px var(--line), 0 30px 60px -36px rgba(24, 25, 27, 0.35); | ||
| 329 | +} | ||
| 330 | +.frame img { display: block; width: 100%; height: 100%; object-fit: contain; object-position: top; } | ||
| 331 | + | ||
| 332 | +/* Small screens: titles become a scrolling row above the screenshot */ | ||
| 333 | +@media (max-width: 900px) { | ||
| 334 | + .tour { grid-template-columns: 1fr; gap: 16px; } | ||
| 335 | + .tour-tabs { flex-direction: row; overflow-x: auto; scrollbar-width: none; margin-inline: calc(-1 * var(--gutter)); padding: 1px var(--gutter); } | ||
| 336 | + .tour-tabs::-webkit-scrollbar { display: none; } | ||
| 337 | + .tour-tabs button { flex: none; width: auto; padding: 8px 14px; white-space: nowrap; } | ||
| 338 | + .tour-tabs button[aria-selected="true"]::before { display: none; } | ||
| 339 | + .tour-tabs button[aria-selected="true"] .tab-desc { display: none; } | ||
| 340 | + .frame { border-radius: 8px; } | ||
| 341 | +} | ||
| 342 | + | ||
| 343 | +/* ------------------------------------------------------------------ how */ | ||
| 344 | +.facts { | ||
| 345 | + display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 40px; | ||
| 346 | + margin-top: 48px; | ||
| 347 | +} | ||
| 348 | +.facts > div { padding-top: 18px; border-top: 2px solid var(--ink); } | ||
| 349 | +.facts dt { font-weight: 600; font-size: 1.125rem; } | ||
| 350 | +.facts dd { margin-top: 8px; color: var(--muted); } | ||
| 351 | + | ||
| 352 | +@media (max-width: 820px) { .facts { grid-template-columns: 1fr; gap: 28px; } } | ||
| 353 | + | ||
| 354 | +.skills { | ||
| 355 | + display: grid; grid-template-columns: minmax(0, 0.8fr) minmax(0, 1.2fr); gap: 40px; | ||
| 356 | + margin-top: 72px; | ||
| 357 | + padding: clamp(24px, 4vw, 40px); | ||
| 358 | + border-radius: 10px; | ||
| 359 | + background: var(--ink); | ||
| 360 | + color: #fff; | ||
| 361 | +} | ||
| 362 | +.skills-copy p { margin-top: 10px; color: #B5B6B2; max-width: 40ch; } | ||
| 363 | + | ||
| 364 | +.skills-list { display: grid; gap: 18px; } | ||
| 365 | +.skills-list li { display: grid; gap: 4px; } | ||
| 366 | +.skills-list code { | ||
| 367 | + justify-self: start; | ||
| 368 | + padding: 0; background: none; | ||
| 369 | + color: #fff; font-size: 0.9375rem; font-weight: 500; | ||
| 370 | +} | ||
| 371 | +.skills-list span { color: #B5B6B2; font-size: 0.9375rem; } | ||
| 372 | + | ||
| 373 | +@media (max-width: 820px) { .skills { grid-template-columns: 1fr; gap: 24px; } } | ||
| 374 | + | ||
| 375 | +/* ------------------------------------------------------------------ a personal tool */ | ||
| 376 | +.honest { | ||
| 377 | + display: grid; grid-template-columns: minmax(0, 0.8fr) minmax(0, 1.2fr); gap: 40px; | ||
| 378 | +} | ||
| 379 | +.honest-body { display: grid; gap: 16px; max-width: 60ch; color: var(--muted); } | ||
| 380 | +.honest-body p:first-child { color: var(--ink); font-size: 1.1875rem; } | ||
| 381 | + | ||
| 382 | +@media (max-width: 820px) { .honest { grid-template-columns: 1fr; gap: 20px; } } | ||
| 383 | + | ||
| 384 | +/* ------------------------------------------------------------------ try it */ | ||
| 385 | +.code { | ||
| 386 | + position: relative; | ||
| 387 | + margin-top: 28px; | ||
| 388 | + max-width: 760px; | ||
| 389 | + border-radius: 10px; | ||
| 390 | + background: var(--ink); | ||
| 391 | + color: #fff; | ||
| 392 | +} | ||
| 393 | +.code pre { margin: 0; padding: 22px 24px; overflow-x: auto; } | ||
| 394 | +.code code { | ||
| 395 | + background: none; padding: 0; | ||
| 396 | + font-size: 0.9375rem; line-height: 1.75; | ||
| 397 | +} | ||
| 398 | +.code .copy { position: absolute; top: 14px; right: 14px; } | ||
| 399 | +@media (max-width: 720px) { .code pre { padding-top: 60px; } } | ||
| 400 | + | ||
| 401 | +/* ------------------------------------------------------------------ footer */ | ||
| 402 | +.foot { | ||
| 403 | + display: flex; flex-wrap: wrap; justify-content: space-between; gap: 12px 32px; | ||
| 404 | + margin-top: clamp(96px, 12vw, 152px); | ||
| 405 | + padding-block: 28px 40px; | ||
| 406 | + border-top: 1px solid var(--line); | ||
| 407 | + color: var(--muted); font-size: 0.9375rem; | ||
| 408 | +} | ||
| 409 | +.foot nav { display: flex; flex-wrap: wrap; gap: 8px 24px; } | ||
| 410 | + | ||
| 411 | +/* ------------------------------------------------------------------ motion */ | ||
| 412 | +@media (prefers-reduced-motion: reduce) { | ||
| 413 | + html { scroll-behavior: auto; } | ||
| 414 | + *, *::before, *::after { transition: none !important; animation: none !important; } | ||
| 415 | +} | ||