├── .github
└── workflows
│ └── website.yml
├── .gitignore
├── .prettierrc
├── Hammer.ts
├── README.md
├── assets
├── hammer.png
├── vike-hammer_favicon.svg
└── vike-hammer_with-text.svg
├── editor.html
├── editor
├── index.css
└── index.ts
├── embed.html
├── embed
├── index.ts
└── style.css
├── fonts
├── Inter-Var.ttf
└── Mitr-Light.ttf
├── index.html
├── index
├── anchor-scrolling.css
├── anchor-scrolling.ts
└── css
│ ├── blockquote.css
│ ├── code.css
│ ├── copy.css
│ ├── fonts.css
│ └── index.css
├── package.json
├── pnpm-lock.yaml
├── pricing.html
├── pricing
├── Pricing.css
├── Pricing.tsx
├── PricingDesc.tsx
├── index.css
└── index.tsx
├── tsconfig.json
└── vite.config.ts
/.github/workflows/website.yml:
--------------------------------------------------------------------------------
1 | name: Website
2 | on:
3 | push:
4 | branches:
5 | - main
6 | permissions:
7 | contents: write
8 | jobs:
9 | website:
10 | runs-on: ubuntu-latest
11 | steps:
12 | - uses: actions/checkout@v4
13 | - uses: pnpm/action-setup@v4
14 | with:
15 | version: 9.10.0
16 | - run: pnpm install
17 | - run: pnpm build
18 | - uses: JamesIves/github-pages-deploy-action@4.1.4
19 | with:
20 | branch: gh-pages
21 | folder: dist/
22 | # Remove previous build
23 | clean: true
24 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules/
2 | /dist/
3 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | semi: false
2 | tabWidth: 2
3 | singleQuote: true
4 | printWidth: 120
5 | trailingComma: all
6 |
--------------------------------------------------------------------------------
/Hammer.ts:
--------------------------------------------------------------------------------
1 | export { Hammer }
2 | export { toHumanReadable }
3 | export { fromHumanReadable }
4 | export { fromHumanReadableAxis }
5 | export { colorsDefault }
6 | export type { IlloElement }
7 | export type { Colors }
8 | export type { Perspective }
9 | export type { PerspectiveUserControlable }
10 |
11 | import * as Zdog from 'zdog'
12 | const { TAU } = Zdog
13 |
14 | /*******************************/
15 | /********** DEFAULTS ***********/
16 | /*******************************/
17 |
18 | const headLength = 35
19 |
20 | const handleDiameterDefault = 8.6
21 | const handleLengthDefault = 16.7
22 |
23 | const STROKE = 0
24 | const slopeSize = 3
25 | const sideLength = 8
26 |
27 | // perspectiveDefault is meant to be overriden by setting hammer.perspective = /* ... */
28 | const perspectiveDefault: Perspective = {
29 | rotate: fromHumanReadable({ x: 0, y: 0, z: 0 }),
30 | translate: { x: 0, y: 0, z: 0 },
31 | }
32 |
33 | const colorsDefault: Colors = {
34 | metal1: '#ababab',
35 | metal2: '#949494',
36 | metal3: '#757575',
37 | metal4: '#6e6e6e',
38 | metal5: '#7a7a7a',
39 | metal6: '#828282',
40 | wood: '#91512b',
41 | lightningBolt: '#fbbf28',
42 | }
43 |
44 | /******************************/
45 | /*********** LOGIC ************/
46 | /******************************/
47 |
48 | // default to flat, filled shapes
49 | ;[Zdog.Shape, Zdog.Rect, Zdog.Ellipse].forEach(function (ItemClass) {
50 | //@ts-ignore
51 | ItemClass.defaults.fill = true
52 | //@ts-ignore
53 | ItemClass.defaults.stroke = false
54 | })
55 |
56 | type IlloElement = SVGSVGElement | HTMLCanvasElement
57 |
58 | type Perspective = {
59 | rotate: {
60 | x: number
61 | y: number
62 | z: number
63 | }
64 | translate: {
65 | x: number
66 | y: number
67 | z: number
68 | }
69 | }
70 | type PerspectiveUserControlable = {
71 | x: number
72 | y: number
73 | z: number
74 | }
75 |
76 | type Colors = {
77 | metal1: string
78 | metal2: string
79 | metal3: string
80 | metal4: string
81 | metal5: string
82 | metal6: string
83 | wood: string
84 | lightningBolt: string
85 | }
86 |
87 | class Hammer {
88 | constructor(outerElem: HTMLElement) {
89 | if (!outerElem) throw new Error('Missing `outerElem` argument')
90 | const illoElem = renderOuterHtml(outerElem)
91 | this.illoElem = illoElem
92 | this.colors = colorsDefault
93 | this.perspective = perspectiveDefault
94 | this.handleDiameterDefault = handleDiameterDefault
95 | this.handleDiameter = handleDiameterDefault
96 | this.handleLength = handleLengthDefault
97 | this.handleLengthDefault = handleLengthDefault
98 | this.hideBackLightningBolt = false
99 | }
100 | illo: Zdog.Illustration | undefined = undefined
101 | illoElem: IlloElement
102 | perspective: Perspective
103 | dragRotate: boolean = false
104 | onDragStart: (() => void) | undefined = undefined
105 | handleDiameter: number
106 | handleDiameterDefault: number
107 | handleLength: number
108 | handleLengthDefault: number
109 | hideBackLightningBolt: boolean
110 | reset() {
111 | this.init()
112 | }
113 | updatePerspective() {
114 | if (this.illo) {
115 | Object.assign(this.illo.rotate, this.perspective.rotate)
116 | Object.assign(this.illo.translate, this.perspective.translate)
117 | this.illo.updateRenderGraph()
118 | }
119 | }
120 | colors: Colors
121 | init() {
122 | this.illo = render(this)
123 | this.illo.updateRenderGraph()
124 | }
125 | update() {
126 | if (this.illo) {
127 | this.illo.updateRenderGraph()
128 | }
129 | }
130 | }
131 |
132 | function renderOuterHtml(outerElem: HTMLElement) {
133 | outerElem.style.position = 'relative'
134 | outerElem.innerHTML = `
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
155 | `
156 | const illoElem: SVGSVGElement = outerElem.querySelector('svg.hammer')!
157 | illoElem.style.height = '100%'
158 | illoElem.style.width = '100%'
159 | return illoElem
160 | }
161 |
162 | type Options = {
163 | colors: Colors
164 | hideBackLightningBolt: boolean
165 | }
166 |
167 | function render(hammer: Hammer) {
168 | const { illoElem, perspective, dragRotate } = hammer
169 | illoElem.setAttribute('width', '100')
170 | illoElem.setAttribute('height', '100')
171 | const illo = new Zdog.Illustration({
172 | element: illoElem,
173 | dragRotate,
174 | onDragStart() {
175 | hammer.onDragStart?.()
176 | },
177 | onDragMove() {
178 | Object.assign(perspective.rotate, illo.rotate)
179 | Object.assign(perspective.translate, illo.translate)
180 | },
181 | //zoom: 4.3,
182 | rotate: perspective.rotate,
183 | translate: perspective.translate,
184 | })
185 |
186 | const { colors, hideBackLightningBolt } = hammer
187 | const options = { colors, hideBackLightningBolt }
188 |
189 | // anchor
190 | var hammerGroup = new Zdog.Group({
191 | addTo: illo,
192 | //translate: { y: -20, x: -20 },
193 | updateSort: true,
194 | })
195 | var handle = new Zdog.Anchor({
196 | addTo: hammerGroup,
197 | //*
198 | rotate: { x: TAU / 4 },
199 | //*/
200 | })
201 | var head = new Zdog.Anchor({
202 | addTo: hammerGroup,
203 | //*
204 | rotate: { z: (-1 * TAU) / 4 },
205 | translate: { x: -1 * (headLength / 2), y: -10 },
206 | //*/
207 | })
208 |
209 | genHead(head, options)
210 |
211 | {
212 | const { handleDiameter, handleLength } = hammer
213 | genHandle(handle, colors, handleDiameter, handleLength)
214 | }
215 |
216 | return illo
217 | }
218 |
219 | function genHandle(handle: Zdog.Anchor, colors: Colors, handleDiameter: number, handleLength: number) {
220 | const handleStick = colors.wood
221 | const mountColor1 = colors.metal4
222 | const mountColor2 = colors.metal5
223 | const mountColor3 = colors.metal6
224 |
225 | let zOffset = 0
226 | const mount = (color: string, stroke: number, length: number = 0) => {
227 | stroke = stroke / 2
228 | const zOffsetAddendum = stroke + length
229 | /*
230 | if( zOffset === 0 ) {
231 | } else {
232 | zOffset += zOffsetAddendum
233 | }
234 | */
235 | zOffset += zOffsetAddendum / 2
236 | new Zdog.Cylinder({
237 | addTo: handle,
238 | diameter: handleDiameter,
239 | stroke: stroke,
240 | length,
241 | fill: true,
242 | color,
243 | translate: { x: 0, y: 0, z: 0 - 1 - zOffset },
244 | })
245 | zOffset += zOffsetAddendum / 2
246 | }
247 |
248 | mount(mountColor1, 1, 3)
249 | mount(handleStick, 0, handleLength)
250 | mount(mountColor2, 1, 1)
251 | mount(mountColor3, 2, 3)
252 | }
253 |
254 | function genHead(head: Zdog.Anchor, options: Options) {
255 | genHeadSides(head, options.colors)
256 | genHeadFaces(head, options)
257 | }
258 |
259 | function genHeadSides(head: Zdog.Anchor, colors: Colors) {
260 | const headSide = genHeadSide(head, colors)
261 |
262 | headSide.copyGraph({
263 | addTo: head,
264 | rotate: { x: TAU / 2 },
265 | translate: { y: headLength },
266 | })
267 | }
268 |
269 | function genHeadFaces(head: Zdog.Anchor, options: Options) {
270 | genFaces(head, options)
271 | genFaceSlopes(head, options.colors)
272 | }
273 | function genFaces(head: Zdog.Anchor, options: Options) {
274 | const { colors } = options
275 |
276 | const shape = (props: Zdog.ShapeOptions) =>
277 | new Zdog.Shape({
278 | stroke: STROKE,
279 | ...props,
280 | })
281 |
282 | // Bottom face
283 | const face = shape({
284 | path: [
285 | { x: -1, y: 0, z: 0.5 },
286 | { x: -1, y: 0, z: -0.5 },
287 | { x: -1, y: 1, z: -0.5 },
288 | { x: -1, y: 1, z: 0.5 },
289 | ],
290 | translate: { y: slopeSize },
291 | scale: { x: sideLength + slopeSize, y: headLength - 2 * slopeSize, z: 2 * sideLength },
292 | color: colors.metal3,
293 | addTo: head,
294 | })
295 |
296 | // Upper face
297 | const opposite = 2 * (sideLength + slopeSize)
298 | const face2 = face.copy({
299 | translate: { x: opposite, y: slopeSize },
300 | addTo: head,
301 | })
302 |
303 | // Front face
304 | var frontFaceGroup = new Zdog.Group({
305 | addTo: head,
306 | })
307 | face2.copy({
308 | rotate: { y: (-1 * TAU) / 4 },
309 | translate: { x: 0, y: slopeSize },
310 | addTo: frontFaceGroup,
311 | })
312 | const viteLogo = genViteLogo(frontFaceGroup, colors)
313 |
314 | // Back face
315 | frontFaceGroup.copyGraph({
316 | rotate: { x: (-1 * TAU) / 2 },
317 | translate: { x: 0, y: headLength, z: 0 },
318 | addTo: head,
319 | })
320 |
321 | if (options.hideBackLightningBolt) {
322 | viteLogo.remove()
323 | }
324 | }
325 |
326 | function genFaceSlopes(head: Zdog.Anchor, colors: Colors) {
327 | const shape = (props: Zdog.ShapeOptions) =>
328 | new Zdog.Shape({
329 | stroke: STROKE,
330 | addTo: head,
331 | ...props,
332 | })
333 |
334 | const x = -1 * sideLength
335 | const y = headLength - 2 * slopeSize
336 | const z1 = sideLength + slopeSize
337 | const z2 = sideLength
338 | const faceSlope = shape({
339 | path: [
340 | { x, y: 0, z: z1 },
341 | { x: x - slopeSize, y: 0, z: z2 },
342 | { x: x - slopeSize, y, z: z2 },
343 | { x, y, z: z1 },
344 | ],
345 | translate: { y: slopeSize },
346 | color: colors.metal2,
347 | })
348 |
349 | const opposite = 2 * sideLength + slopeSize
350 | faceSlope.copy({
351 | translate: { x: opposite, y: slopeSize, z: -1 * opposite },
352 | })
353 | faceSlope.copy({
354 | rotate: { x: TAU / 2 },
355 | translate: { y: headLength - slopeSize },
356 | })
357 | faceSlope.copy({
358 | rotate: { x: TAU / 2 },
359 | translate: { x: opposite, y: headLength - slopeSize, z: 1 * opposite },
360 | })
361 | }
362 |
363 | function genHeadSide(head: Zdog.Anchor, colors: Colors) {
364 | const headSide = new Zdog.Anchor({
365 | addTo: head,
366 | })
367 |
368 | const shape = (props: Zdog.ShapeOptions) =>
369 | new Zdog.Shape({
370 | stroke: STROKE,
371 | addTo: headSide,
372 | ...props,
373 | })
374 |
375 | const colorEdge = colors.metal2
376 | const colorCorner = colors.metal1
377 |
378 | // east slope
379 | var EWSlope = shape({
380 | path: [
381 | { x: 0, y: 0, z: 1 },
382 | { x: 0, y: 0, z: -1 },
383 | { x: 1, y: 1, z: -1 },
384 | { x: 1, y: 1, z: 1 },
385 | ],
386 | translate: { x: sideLength },
387 | scale: { x: slopeSize, y: slopeSize, z: sideLength },
388 | color: colorEdge,
389 | })
390 |
391 | // south slope
392 | var NSSLope = shape({
393 | path: [
394 | { z: 0, y: 0, x: 1 },
395 | { z: 0, y: 0, x: -1 },
396 | { z: 1, y: 1, x: -1 },
397 | { z: 1, y: 1, x: 1 },
398 | ],
399 | translate: { z: sideLength },
400 | scale: { x: sideLength, y: slopeSize, z: slopeSize },
401 | color: colorEdge,
402 | })
403 |
404 | // south east corner
405 | shape({
406 | path: [
407 | { x: 0, y: 0, z: 0 },
408 | { x: slopeSize, y: slopeSize, z: 0 },
409 | { x: 0, y: slopeSize, z: slopeSize },
410 | ],
411 | translate: { x: sideLength, z: sideLength },
412 | color: colorCorner,
413 | })
414 |
415 | // north slope
416 | NSSLope.copy({
417 | scale: { x: sideLength, y: slopeSize, z: -1 * slopeSize },
418 | translate: { z: -1 * sideLength },
419 | color: colorEdge,
420 | })
421 |
422 | // north east corner
423 | shape({
424 | path: [
425 | { x: 0, y: 0, z: 0 },
426 | { x: slopeSize, y: slopeSize, z: 0 },
427 | { x: 0, y: slopeSize, z: -1 * slopeSize },
428 | ],
429 | translate: { x: sideLength, z: -1 * sideLength },
430 | color: colorCorner,
431 | })
432 |
433 | // west slope
434 | EWSlope.copy({
435 | scale: { x: -1 * slopeSize, y: slopeSize, z: sideLength },
436 | translate: { x: -1 * sideLength },
437 | color: colorEdge,
438 | })
439 |
440 | // north west corner
441 | shape({
442 | path: [
443 | { x: 0, y: 0, z: 0 },
444 | { x: -slopeSize, y: slopeSize, z: 0 },
445 | { x: 0, y: slopeSize, z: -1 * slopeSize },
446 | ],
447 | translate: { x: -1 * sideLength, z: -1 * sideLength },
448 | color: colorCorner,
449 | })
450 |
451 | // south west corner
452 | shape({
453 | path: [
454 | { x: 0, y: 0, z: 0 },
455 | { x: -1 * slopeSize, y: slopeSize, z: 0 },
456 | { x: 0, y: slopeSize, z: slopeSize },
457 | ],
458 | translate: { x: -1 * sideLength, z: sideLength },
459 | color: colorCorner,
460 | })
461 |
462 | /* Failed attempt to remove aliasing issues
463 | const y = 0.02
464 | */
465 | const y = 0
466 |
467 | // cover
468 | shape({
469 | path: [
470 | { x: -1, y, z: 1 },
471 | { x: -1, y, z: -1 },
472 | { x: 1, y, z: -1 },
473 | { x: 1, y, z: 1 },
474 | ],
475 | scale: { x: sideLength, y: sideLength, z: sideLength },
476 | color: colors.metal3,
477 | })
478 |
479 | return headSide
480 | }
481 |
482 | function genViteLogo(group: Zdog.Group, colors: Colors) {
483 | const scale = 0.04
484 | //const width = 12
485 |
486 | //const translate = {x: 0, y: 0, z: 0}
487 | /*/
488 | const addTo = illo
489 | const translate = { x: -1 * (headLength / 2) + width / 2 , y: -2 * sideLength, z: sideLength + slopeSize + 2 }
490 | const rotate = undefined;
491 | /*/
492 | const addTo = group
493 | const translate = { x: 6.5, y: 8.2, z: sideLength + slopeSize }
494 | const rotate = { z: (1 * TAU) / 4 }
495 | //*/
496 | const stroke = 0.6
497 | // Distance from the hammer => apparent thickness of the Vite logo
498 | const thikness = stroke / 4
499 | const shape = new Zdog.Shape({
500 | addTo,
501 | rotate,
502 | path: [
503 | { x: 292.965, y: 1.5744 },
504 | {
505 | bezier: [
506 | { x: 292.965, y: 1.5744 },
507 | { x: 156.801, y: 28.2552 },
508 | { x: 156.801, y: 28.2552 },
509 | ],
510 | },
511 | {
512 | bezier: [
513 | { x: 154.563, y: 28.6937 },
514 | { x: 152.906, y: 30.5903 },
515 | { x: 152.771, y: 32.8664 },
516 | ],
517 | },
518 | {
519 | bezier: [
520 | { x: 152.771, y: 32.8664 },
521 | { x: 144.395, y: 174.33 },
522 | { x: 144.395, y: 174.33 },
523 | ],
524 | },
525 | {
526 | bezier: [
527 | { x: 144.198, y: 177.662 },
528 | { x: 147.258, y: 180.248 },
529 | { x: 150.51, y: 179.498 },
530 | ],
531 | },
532 | {
533 | bezier: [
534 | { x: 150.51, y: 179.498 },
535 | { x: 188.42, y: 170.749 },
536 | { x: 188.42, y: 170.749 },
537 | ],
538 | },
539 | {
540 | bezier: [
541 | { x: 191.967, y: 169.931 },
542 | { x: 195.172, y: 173.055 },
543 | { x: 194.443, y: 176.622 },
544 | ],
545 | },
546 | {
547 | bezier: [
548 | { x: 194.443, y: 176.622 },
549 | { x: 183.18, y: 231.775 },
550 | { x: 183.18, y: 231.775 },
551 | ],
552 | },
553 | {
554 | bezier: [
555 | { x: 182.422, y: 235.487 },
556 | { x: 185.907, y: 238.661 },
557 | { x: 189.532, y: 237.56 },
558 | ],
559 | },
560 | {
561 | bezier: [
562 | { x: 189.532, y: 237.56 },
563 | { x: 212.947, y: 230.446 },
564 | { x: 212.947, y: 230.446 },
565 | ],
566 | },
567 | {
568 | bezier: [
569 | { x: 216.577, y: 229.344 },
570 | { x: 220.065, y: 232.527 },
571 | { x: 219.297, y: 236.242 },
572 | ],
573 | },
574 | {
575 | bezier: [
576 | { x: 219.297, y: 236.242 },
577 | { x: 201.398, y: 322.875 },
578 | { x: 201.398, y: 322.875 },
579 | ],
580 | },
581 | {
582 | bezier: [
583 | { x: 200.278, y: 328.294 },
584 | { x: 207.486, y: 331.249 },
585 | { x: 210.492, y: 326.603 },
586 | ],
587 | },
588 | {
589 | bezier: [
590 | { x: 210.492, y: 326.603 },
591 | { x: 212.5, y: 323.5 },
592 | { x: 212.5, y: 323.5 },
593 | ],
594 | },
595 | {
596 | bezier: [
597 | { x: 212.5, y: 323.5 },
598 | { x: 323.454, y: 102.072 },
599 | { x: 323.454, y: 102.072 },
600 | ],
601 | },
602 | {
603 | bezier: [
604 | { x: 325.312, y: 98.3645 },
605 | { x: 322.108, y: 94.137 },
606 | { x: 318.036, y: 94.9228 },
607 | ],
608 | },
609 | {
610 | bezier: [
611 | { x: 318.036, y: 94.9228 },
612 | { x: 279.014, y: 102.454 },
613 | { x: 279.014, y: 102.454 },
614 | ],
615 | },
616 | {
617 | bezier: [
618 | { x: 275.347, y: 103.161 },
619 | { x: 272.227, y: 99.746 },
620 | { x: 273.262, y: 96.1583 },
621 | ],
622 | },
623 | {
624 | bezier: [
625 | { x: 273.262, y: 96.1583 },
626 | { x: 298.731, y: 7.86689 },
627 | { x: 298.731, y: 7.86689 },
628 | ],
629 | },
630 | {
631 | bezier: [
632 | { x: 299.767, y: 4.27314 },
633 | { x: 296.636, y: 0.855181 },
634 | { x: 292.965, y: 1.5744 },
635 | ],
636 | },
637 | ],
638 | closed: true,
639 | stroke,
640 | fill: true,
641 | color: colors.lightningBolt,
642 | translate: { x: translate.x, y: translate.y, z: translate.z + thikness },
643 | scale: { x: scale, y: scale, z: scale },
644 | })
645 | return shape
646 | }
647 |
648 | function toHumanReadable({ x, y, z }: { x: number; y: number; z: number }) {
649 | const f = (v: number) => {
650 | v = (v * 16) / TAU
651 | v = Math.floor(v * 100) / 100
652 | return v
653 | }
654 | x = f(x)
655 | y = f(y)
656 | z = f(z)
657 | return { x, y, z }
658 | }
659 | function fromHumanReadableAxis(n: number) {
660 | return TAU * (n / 16)
661 | }
662 | function fromHumanReadable({ x, y, z }: { x: number; y: number; z: number }) {
663 | const f = fromHumanReadableAxis
664 | x = f(x)
665 | y = f(y)
666 | z = f(z)
667 | return { x, y, z }
668 | }
669 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Build Your Own Framework
2 |
3 | See [vike.land.dev](https://land.vike.dev).
4 |
--------------------------------------------------------------------------------
/assets/hammer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brillout/vike-land/31dd0acf9b5e3e127c550f73627871d3df55b255/assets/hammer.png
--------------------------------------------------------------------------------
/assets/vike-hammer_favicon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/assets/vike-hammer_with-text.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
--------------------------------------------------------------------------------
/editor.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
11 |
12 |
reset to original values (settings are persisted to localStorage
)
13 |
14 |
download file
vike-generated.svg
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
Drag'n'drop to change perspective.
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/editor/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: sans-serif;
4 | --logo-size-large: 400px;
5 | --logo-size-small: 40px;
6 | --logo-size: var(--logo-size-large);
7 | }
8 | body.faviconSize {
9 | --logo-size: var(--logo-size-small);
10 | }
11 |
12 | .hammer {
13 | cursor: move;
14 | }
15 | #logo {
16 | width: var(--logo-size);
17 | height: var(--logo-size);
18 | }
19 | #wrapper {
20 | float: left;
21 | width: var(--logo-size-large);
22 | height: var(--logo-size-large);
23 | }
24 | #colors {
25 | display: flex;
26 | flex-direction: column;
27 | }
28 | label {
29 | display: inline-block;
30 | }
31 |
32 | body.zdogView .circle-square {
33 | display: none;
34 | }
35 | #logo:hover .hammer {
36 | outline: 1px solid black;
37 | }
38 |
39 | code {
40 | font-family: monospace;
41 | background-color: #eaeaea;
42 | padding: 3px 5px;
43 | border-radius: 4px;
44 | }
45 |
--------------------------------------------------------------------------------
/editor/index.ts:
--------------------------------------------------------------------------------
1 | import {
2 | colorsDefault,
3 | fromHumanReadable,
4 | fromHumanReadableAxis,
5 | Hammer,
6 | toHumanReadable,
7 | type Colors,
8 | type PerspectiveUserControlable,
9 | } from '../Hammer'
10 |
11 | const presetsColor: Record
= {
12 | oldest: {
13 | metal1: '#949494',
14 | metal2: '#828282',
15 | metal3: '#696969',
16 | metal4: '#707070',
17 | metal5: '#707070',
18 | metal6: '#696969',
19 | wood: '#96511d',
20 | lightningBolt: '#ecb018',
21 | },
22 | ['too-bright']: {
23 | metal1: '#bdbdbd',
24 | metal2: '#b3b3b3',
25 | metal3: '#a6a6a6',
26 | metal4: '#9e9e9e',
27 | metal5: '#9e9e9e',
28 | metal6: '#a6a6a6',
29 | wood: '#a56c4a',
30 | lightningBolt: '#fbcc56',
31 | },
32 | brighter: {
33 | metal1: '#b5b5b5',
34 | metal2: '#949494',
35 | metal3: '#7a7a7a',
36 | metal4: '#787878',
37 | metal5: '#787878',
38 | metal6: '#7a7a7a',
39 | wood: '#91512b',
40 | lightningBolt: '#f7bc26',
41 | },
42 | older: {
43 | metal1: '#b5b5b5',
44 | metal2: '#949494',
45 | metal3: '#757575',
46 | metal4: '#787878',
47 | metal5: '#787878',
48 | metal6: '#707070',
49 | wood: '#91512b',
50 | lightningBolt: '#f7bc26',
51 | },
52 | older2: {
53 | metal1: '#b0b0b0',
54 | metal2: '#949494',
55 | metal3: '#757575',
56 | metal4: '#878787',
57 | metal5: '#878787',
58 | metal6: '#808080',
59 | wood: '#91512b',
60 | lightningBolt: '#f7bc26',
61 | },
62 | darker: {
63 | metal1: '#a3a3a3',
64 | metal2: '#8f8f8f',
65 | metal3: '#757575',
66 | metal4: '#878787',
67 | metal5: '#878787',
68 | metal6: '#808080',
69 | wood: '#91512b',
70 | lightningBolt: '#f7bc26',
71 | },
72 | previous: {
73 | metal1: '#b5b5b5',
74 | metal2: '#949494',
75 | metal3: '#757575',
76 | metal4: '#6e6e6e',
77 | metal5: '#767676',
78 | metal6: '#828282',
79 | wood: '#91512b',
80 | lightningBolt: '#f7bc26',
81 | },
82 | default: { ...colorsDefault },
83 | }
84 | const presets: Record = {
85 | maud: {
86 | rotation2D: 0,
87 | handleLength: 10,
88 | rotate: { x: -0.1, y: -7, z: -2 },
89 | },
90 | classic: {
91 | rotation2D: 0,
92 | rotate: { x: -0.13, y: -6.63, z: -1.2 },
93 | // translate: { x: -2.6, y: 7, z: 0 },
94 | },
95 | ['classic-long-handle']: {
96 | rotation2D: 0,
97 | handleLength: 20.5,
98 | rotate: { x: -0.13, y: -6.63, z: -1.2 },
99 | // translate: { x: -2.6, y: 7, z: 0 },
100 | },
101 | ['oldest-mirrored']: {
102 | rotation2D: -21,
103 | rotate: { x: -0.7, y: 7.1, z: 0 },
104 | },
105 | previous: {
106 | rotation2D: 11,
107 | rotate: { x: -0.3, y: -6.63, z: 0 },
108 | },
109 | new1: {
110 | rotation2D: -23,
111 | rotate: { x: -0.4, y: 152.8, z: 0 },
112 | },
113 | new2: {
114 | rotation2D: -23,
115 | rotate: { x: -0.6, y: 159.19, z: 0 },
116 | },
117 | new3: {
118 | rotation2D: -23,
119 | rotate: { x: -0.4, y: 159.3, z: 0 },
120 | },
121 | favicon: {
122 | rotation2D: 29,
123 | rotate: { x: -0.4, y: 0.6, z: 0 },
124 | },
125 | latest: {
126 | rotation2D: -30,
127 | rotate: { x: -0.5, y: 23.2, z: 0 },
128 | },
129 | latest2: {
130 | rotation2D: 13,
131 | rotate: { x: -0.4, y: 23.5, z: 0 },
132 | },
133 | ['vertical-mirrored']: {
134 | handleDiameter: 8.2,
135 | handleLength: 21.8,
136 | rotation2D: 0,
137 | rotate: { x: -0.4, y: -56.41, z: 0 },
138 | },
139 | vertical: {
140 | handleDiameter: 8.2,
141 | handleLength: 21.8,
142 | rotation2D: 0,
143 | rotate: { x: -0.4, y: -55.6, z: 0 },
144 | },
145 | laying: {
146 | handleDiameter: 8.5,
147 | handleLength: 20.9,
148 | rotation2D: 66.3,
149 | rotate: { x: -0.4, y: -56.41, z: 0 },
150 | },
151 | }
152 | type Preset = {
153 | rotation2D: number
154 | rotate: { x: number; y: number; z: number }
155 | handleDiameter?: number
156 | handleLength?: number
157 | }
158 |
159 | const perspectiveDefault = presets.latest2
160 |
161 | let changeRotation2D: (n: number) => void
162 | let getRotation2D: () => number
163 | let changeHandleDiameter: (n: number) => void
164 | let changeHandleLength: (n: number) => void
165 | let updateColorInputs: () => void
166 | main()
167 |
168 | function getElements() {
169 | return {
170 | presets: document.getElementById('presets')!,
171 | presetsColor: document.getElementById('presetsColor')!,
172 | colorPicker: document.getElementById('colorPicker')!,
173 | handleDiameterPicker: document.getElementById('handleDiameterPicker')!,
174 | handleLengthPicker: document.getElementById('handleLengthPicker')!,
175 | zdogView: document.getElementById('zdogView')!,
176 | faviconSize: document.getElementById('faviconSize')!,
177 | autoSpinning: document.getElementById('autoSpinning')!,
178 | reset: document.querySelector('button#reset') as HTMLButtonElement,
179 | download: document.querySelector('button#download') as HTMLButtonElement,
180 | hideBackLightningBolt: document.getElementById('hideBackLightningBolt')!,
181 | rotateX: document.getElementById('rotate-x')!,
182 | rotateY: document.getElementById('rotate-y')!,
183 | rotateZ: document.getElementById('rotate-z')!,
184 | rotate2D: document.getElementById('rotate2D')!,
185 | translateX: document.getElementById('translate-x')!,
186 | translateY: document.getElementById('translate-y')!,
187 | translateZ: document.getElementById('translate-z')!,
188 | }
189 | }
190 |
191 | function main() {
192 | const elements = getElements()
193 |
194 | const hammer = new Hammer(document.querySelector('#logo')!)
195 | hammer.dragRotate = true
196 |
197 | zdogViewInit(elements.zdogView)
198 |
199 | initPresets(elements.presets, hammer)
200 | initPresetsColor(elements.presetsColor, hammer)
201 | initColorInputs(elements.colorPicker, hammer)
202 | changeHandleDiameter = initHandlePicker(hammer, elements.handleDiameterPicker, 'handleDiameter').changeVal
203 | changeHandleLength = initHandlePicker(hammer, elements.handleLengthPicker, 'handleLength').changeVal
204 | initFaviconSize(elements.faviconSize)
205 | initAutoSpinning(elements.autoSpinning)
206 | initReset(elements.reset)
207 | initDownload(elements.download)
208 | initHighBackLightningBold(elements.hideBackLightningBolt, hammer)
209 | initPerspectiveControlers(hammer, elements)
210 | initRotate2D(elements.rotate2D)
211 |
212 | animate(hammer)
213 |
214 | hammer.init()
215 | }
216 |
217 | function initPerspectiveControlers(
218 | hammer: Hammer,
219 | {
220 | rotateX,
221 | rotateY,
222 | rotateZ,
223 | }: /*
224 | translateX,
225 | translateY,
226 | translateZ,
227 | */
228 | {
229 | rotateX: HTMLElement
230 | rotateY: HTMLElement
231 | rotateZ: HTMLElement
232 | translateX: HTMLElement
233 | translateY: HTMLElement
234 | translateZ: HTMLElement
235 | },
236 | ) {
237 | ;(
238 | [
239 | {
240 | elem: rotateX,
241 | type: 'rotate' as const,
242 | axis: 'x',
243 | },
244 | {
245 | elem: rotateY,
246 | type: 'rotate' as const,
247 | axis: 'y',
248 | },
249 | {
250 | elem: rotateZ,
251 | type: 'rotate' as const,
252 | axis: 'z',
253 | },
254 | /*
255 | {
256 | elem: translateX,
257 | type: 'translate' as const,
258 | axis: 'x',
259 | },
260 | {
261 | elem: translateY,
262 | type: 'translate' as const,
263 | axis: 'y',
264 | },
265 | {
266 | elem: translateZ,
267 | type: 'translate' as const,
268 | axis: 'z',
269 | },
270 | */
271 | ] as const
272 | ).forEach(({ elem, axis, type }) => {
273 | const getCoordinate = () => hammer.perspective[type]
274 | const { changeVal } = createNumberInput({
275 | elem,
276 | labelText: `${axis}
(${type})`,
277 | getValue() {
278 | const n = toHumanReadable(getCoordinate())[axis]
279 | return n
280 | },
281 | setValue(n: number) {
282 | getCoordinate()[axis] = fromHumanReadableAxis(n)
283 | },
284 | defaultValue: perspectiveDefault.rotate[axis],
285 | hammer,
286 | })
287 | if (type === 'rotate') {
288 | if (!onPerspectiveChange) onPerspectiveChange = []
289 | onPerspectiveChange.push((perspectiveUserControlable) => {
290 | const n = perspectiveUserControlable[axis]
291 | changeVal(n, true)
292 | })
293 | }
294 | })
295 | }
296 |
297 | function initRotate2D(elemRotate2D: HTMLElement) {
298 | const elemLogo = document.getElementById('logo')!
299 |
300 | const toVal = (n: number) => `rotate(${n}deg)`
301 | const fromVal = (val: string) => parseInt(val.split('(')[1]!.split('deg)')[0]!, 10)
302 |
303 | const controls = createNumberInput({
304 | elem: elemRotate2D,
305 | labelText: `degree
(2D rotation)`,
306 | defaultValue: perspectiveDefault.rotation2D,
307 | getValue() {
308 | const val = elemLogo.style.transform
309 | return fromVal(val)
310 | },
311 | setValue(n: number) {
312 | const val = toVal(n)
313 | elemLogo.style.transform = val
314 | },
315 | step: 1,
316 | })
317 | changeRotation2D = controls.changeVal
318 | getRotation2D = controls.getValue
319 | }
320 |
321 | function initHandlePicker(hammer: Hammer, handlePicker: Element, handleProp: 'handleDiameter' | 'handleLength') {
322 | return createNumberInput({
323 | elem: handlePicker,
324 | labelText: `${handleProp}
`,
325 | getValue() {
326 | return hammer[handleProp]
327 | },
328 | setValue(n: number) {
329 | hammer[handleProp] = n
330 | },
331 | hammer,
332 | })
333 | }
334 |
335 | function createNumberInput({
336 | elem,
337 | labelText,
338 | getValue,
339 | setValue,
340 | defaultValue,
341 | step = 0.1,
342 | hammer,
343 | }: {
344 | elem: Element
345 | labelText: string
346 | getValue: () => number
347 | setValue: (n: number) => void
348 | step?: number
349 | defaultValue?: number
350 | hammer?: Hammer
351 | }) {
352 | const storeKey = elem.id!
353 |
354 | {
355 | const val = toFloat(getStoreValue(storeKey))
356 | if (val !== null) setValue(val)
357 | else if (defaultValue !== undefined) setValue(defaultValue)
358 | }
359 |
360 | //
361 | elem.innerHTML = ''
362 | const parentEl = document.createElement('div')
363 | elem.appendChild(parentEl)
364 | const labelEl = document.createElement('label')
365 | parentEl.appendChild(labelEl)
366 | const inputEl = document.createElement('input')
367 | inputEl.setAttribute('type', 'number')
368 | inputEl.setAttribute('step', step.toString())
369 | inputEl.style.width = '40px'
370 | inputEl.style.padding = '4px'
371 | labelEl.appendChild(inputEl)
372 | const valEl = document.createElement('span')
373 | valEl.innerHTML = ' ' + labelText
374 | parentEl.appendChild(valEl)
375 |
376 | const val = getValue()
377 | inputEl.value = String(val)
378 |
379 | const apply = (n: number) => {
380 | setValue(n)
381 | hammer?.reset()
382 | }
383 |
384 | inputEl.oninput = (ev: any) => {
385 | const val: string = ev.target!.value
386 | const n = toFloat(val)
387 | apply(n)
388 | setStoreValue(storeKey, val)
389 | }
390 |
391 | const changeVal = (n: number, alreadyApplied?: true) => {
392 | const val = String(n)
393 | inputEl.value = val
394 | if (!alreadyApplied) apply(n)
395 | setStoreValue(storeKey, val)
396 | }
397 | const controls = { changeVal, getValue }
398 | return controls
399 | }
400 |
401 | function zdogViewInit(zdogView: Element) {
402 | createCheckboxInput({
403 | elem: zdogView,
404 | labelText: 'Icon view',
405 | onToggle(isChecked: boolean) {
406 | document.body.classList[!isChecked ? 'add' : 'remove']('zdogView')
407 | },
408 | })
409 | }
410 |
411 | function initFaviconSize(faviconSize: Element) {
412 | createCheckboxInput({
413 | elem: faviconSize,
414 | labelText: 'Favicon size',
415 | onToggle(isChecked: boolean) {
416 | document.body.classList[isChecked ? 'add' : 'remove']('faviconSize')
417 | },
418 | })
419 | }
420 |
421 | function initAutoSpinning(autoSpinning: Element) {
422 | createCheckboxInput({
423 | elem: autoSpinning,
424 | labelText: 'Auto spinning',
425 | onToggle(isChecked: boolean) {
426 | isSpinning = isChecked
427 | },
428 | })
429 | }
430 |
431 | function initHighBackLightningBold(hideBackLightningBolt: HTMLElement, hammer: Hammer) {
432 | createCheckboxInput({
433 | elem: hideBackLightningBolt,
434 | labelText: 'Hide back lightning bolt',
435 | onToggle(isChecked: boolean) {
436 | hammer.hideBackLightningBolt = isChecked
437 | },
438 | applyValue() {
439 | hammer.reset()
440 | },
441 | })
442 | }
443 |
444 | function createCheckboxInput({
445 | elem,
446 | labelText,
447 | onToggle,
448 | applyValue,
449 | }: {
450 | elem: Element
451 | labelText: string
452 | onToggle: (isChecked: boolean) => void
453 | applyValue?: (isChecked: boolean) => void
454 | }) {
455 | elem.innerHTML = ''
456 | const labelEl = document.createElement('label')
457 | elem.appendChild(labelEl)
458 | const inputEl = document.createElement('input')
459 | inputEl.setAttribute('type', 'checkbox')
460 | labelEl.appendChild(inputEl)
461 | labelEl.appendChild(document.createTextNode(labelText))
462 |
463 | const { id } = elem
464 | assert(id)
465 | const storeGet = () => (JSON.parse(getStoreValue(id) ?? '"{}"').isChecked as undefined | boolean) ?? false
466 | const storeToggle = () => {
467 | let isChecked = storeGet()
468 | isChecked = !isChecked
469 | setStoreValue(id, JSON.stringify({ isChecked }))
470 | }
471 | const updateUI = (isInit?: true) => {
472 | const isChecked = storeGet()
473 | onToggle(isChecked)
474 | inputEl.checked = isChecked
475 | if (!isInit) {
476 | applyValue?.(isChecked)
477 | }
478 | }
479 | inputEl.oninput = (ev) => {
480 | ev.preventDefault()
481 | storeToggle()
482 | updateUI()
483 | }
484 | updateUI(true)
485 | }
486 |
487 | function initReset(reset: HTMLButtonElement) {
488 | reset.onclick = () => {
489 | isSpinning = false
490 | clearStore()
491 | // @ts-ignore
492 | window.navigation.reload()
493 | }
494 | }
495 | function initDownload(download: HTMLButtonElement) {
496 | download.onclick = () => {
497 | const hammerSvg = document.querySelector('.hammer')!
498 | let content = hammerSvg.outerHTML
499 | content = content.replace('', ' ')
503 | downloadFile(content, 'image/svg+xml', 'vike-generated.svg')
504 | }
505 | }
506 |
507 | function downloadFile(content: string, mimeType: string, filename: string) {
508 | const a = document.createElement('a')
509 | const blob = new Blob([content], { type: mimeType })
510 | const url = URL.createObjectURL(blob)
511 | a.setAttribute('href', url)
512 | a.setAttribute('download', filename)
513 | a.click()
514 | }
515 |
516 | function initPresets(presetsEl: Element, hammer: Hammer) {
517 | addHeading('Settings', presetsEl)
518 | Object.entries(presets).forEach(([name, preset]) => {
519 | genPresetBtn(name, presetsEl, () => {
520 | changeHandleDiameter(preset.handleDiameter || hammer.handleDiameterDefault)
521 | changeHandleLength(preset.handleLength || hammer.handleLengthDefault)
522 | hammer.perspective.rotate = fromHumanReadable(preset.rotate)
523 | changeRotation2D(preset.rotation2D)
524 | hammer.reset()
525 | })
526 | })
527 | }
528 | function initPresetsColor(presetsColorEl: Element, hammer: Hammer) {
529 | addHeading('Colors', presetsColorEl)
530 | Object.entries(presetsColor).forEach(([name, colors]) => {
531 | genPresetBtn(name, presetsColorEl, () => {
532 | hammer.colors = colors
533 | hammer.reset()
534 | updateColorInputs()
535 | })
536 | })
537 | }
538 |
539 | function genPresetBtn(name: string, parentEl: Element, onclick: () => void) {
540 | const btnEl = document.createElement('button')
541 | btnEl.style.marginRight = '10px'
542 | btnEl.style.marginBottom = '7px'
543 | btnEl.innerHTML = ` ${name} `
544 | btnEl.onclick = onclick
545 | parentEl.appendChild(btnEl)
546 | }
547 |
548 | function addHeading(heading: string, el: Element) {
549 | const headingEl = document.createElement('h3')
550 | headingEl.innerHTML = heading
551 | el.prepend(headingEl)
552 | }
553 |
554 | function initColorInputs(colorPicker: Element, hammer: Hammer) {
555 | colorPicker.innerHTML = ''
556 |
557 | const updateInputs: (() => void)[] = []
558 | updateColorInputs = () => updateInputs.forEach((f) => f())
559 |
560 | objectKeys(hammer.colors).forEach((key) => {
561 | {
562 | const val = getStoreValue(key)
563 | if (val) hammer.colors[key] = val
564 | }
565 |
566 | //
567 | const parentEl = document.createElement('div')
568 | colorPicker.appendChild(parentEl)
569 | const labelEl = document.createElement('label')
570 | parentEl.appendChild(labelEl)
571 | const inputEl = document.createElement('input')
572 | inputEl.setAttribute('type', 'color')
573 | labelEl.appendChild(inputEl)
574 | const valEl = document.createElement('span')
575 | parentEl.appendChild(valEl)
576 |
577 | const updateInput = () => {
578 | const val = hammer.colors[key]
579 | inputEl.value = val
580 | valEl.innerHTML = ` ${val} ${key}
`
581 | }
582 | const updateStore = () => {
583 | const val = hammer.colors[key]
584 | setStoreValue(key, val)
585 | }
586 | updateInput()
587 | updateInputs.push(() => {
588 | updateInput()
589 | updateStore()
590 | })
591 |
592 | inputEl.oninput = (ev: any) => {
593 | const val: string = ev.target!.value
594 | hammer.colors[key] = val
595 | updateInput()
596 | hammer.reset()
597 | updateStore()
598 | }
599 | })
600 | }
601 |
602 | var isSpinning: boolean
603 | var isMouseover: boolean
604 | var onPerspectiveChange: ((perspective: PerspectiveUserControlable) => void | undefined)[]
605 | function animate(hammer: Hammer) {
606 | const logoElem = document.getElementById('logo')!
607 | logoElem.onmouseleave = () => {
608 | isMouseover = false
609 | }
610 | logoElem.onmouseenter = () => {
611 | isMouseover = true
612 | }
613 | /*
614 | hammer.onDragStart = () => {
615 | isMouseover = true
616 | }
617 | */
618 | render(hammer)
619 | }
620 |
621 | function render(hammer: Hammer) {
622 | requestAnimationFrame(() => {
623 | if (isSpinning && !isMouseover) {
624 | hammer.perspective.rotate.y += 0.015
625 | hammer.updatePerspective()
626 | }
627 | hammer.update()
628 | callOnPerspectiveChange(hammer)
629 | render(hammer)
630 | })
631 | }
632 |
633 | var rotateValue: string
634 | function callOnPerspectiveChange(hammer: Hammer) {
635 | if (!hammer.illo) return
636 | const { x, y, z } = toHumanReadable(hammer.illo.rotate)
637 | const rotateValueNew = JSON.stringify({ x, y, z }, null, 2)
638 | const hasChanged = rotateValue !== rotateValueNew
639 | rotateValue = rotateValueNew
640 | if (!hasChanged) return
641 | const perspectiveUserControlable: PerspectiveUserControlable = { x, y, z }
642 | onPerspectiveChange.forEach((fn) => fn(perspectiveUserControlable))
643 | }
644 |
645 | function assert(condition: unknown): asserts condition {
646 | if (!condition) throw new Error('Assertion failed.')
647 | }
648 |
649 | function getStoreValue(key: string): null | string {
650 | return window.localStorage[`__vike_logo__input_${key}`] ?? null
651 | }
652 | function setStoreValue(key: string, val: string): void | undefined {
653 | window.localStorage[`__vike_logo__input_${key}`] = val
654 | }
655 | function clearStore() {
656 | window.localStorage.clear()
657 | }
658 |
659 | function toFloat(val: string): number
660 | function toFloat(val: string | null): number | null
661 | function toFloat(val: string | null): number | null {
662 | if (val === null) return null
663 | return parseFloat(val)
664 | }
665 |
666 | /** Same as Object.keys() but with type inference */
667 | export function objectKeys>(obj: T): Array {
668 | return Object.keys(obj)
669 | }
670 |
--------------------------------------------------------------------------------
/embed.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
22 |
23 |
27 |
28 |
32 |
33 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
46 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/embed/index.ts:
--------------------------------------------------------------------------------
1 | import { fromHumanReadable, Hammer, type Perspective } from '../Hammer'
2 |
3 | const perspective: Perspective = {
4 | rotate: fromHumanReadable({ x: -0.13, y: -6.63, z: -1.2 }),
5 | translate: { x: -2.6, y: 7, z: 0 },
6 | }
7 |
8 | Array.from(document.querySelectorAll('.logo')).forEach((logoEl) => {
9 | const hammer = new Hammer(logoEl as HTMLElement)
10 | hammer.perspective = perspective
11 | hammer.init()
12 | })
13 |
--------------------------------------------------------------------------------
/embed/style.css:
--------------------------------------------------------------------------------
1 | .header {
2 | text-align: center;
3 | }
4 | .header > * {
5 | display: inline-block;
6 | vertical-align: middle;
7 | }
8 | .header > .logo {
9 | height: 200px;
10 | width: 200px;
11 | position: relative;
12 | top: -13px;
13 | right: -25px;
14 | }
15 |
16 | body {
17 | font-family: 'Inter';
18 | }
19 | button {
20 | font-family: inherit;
21 | }
22 | body {
23 | line-height: 1.5;
24 | }
25 |
26 | @font-face {
27 | font-family: 'Inter';
28 | font-weight: 100 900;
29 | font-stretch: 75% 125%;
30 | font-style: oblique 0deg 12deg;
31 | src: url('/fonts/Inter-Var.ttf') format('truetype-variations');
32 | }
33 |
34 | @font-face {
35 | font-family: 'Mitr';
36 | font-weight: 100 900;
37 | src: url('/fonts/Mitr-Light.ttf') format('truetype-variations');
38 | }
39 |
40 | #header-0 {
41 | position: relative;
42 | left: -76px;
43 | }
44 | #header-0 #logo-png {
45 | width: 85px;
46 | margin-right: 27px;
47 | }
48 |
49 | #header-2 {
50 | color: white;
51 | --color-border: black;
52 | -webkit-text-stroke: 3px black;
53 | }
54 |
55 | /* Adapted from https://markdotto.com/playground/3d-text/ */
56 | #header-5 {
57 | background-color: #0e8cbb;
58 | }
59 | #header-5 > .title {
60 | color: white;
61 | /* prettier-ignore */
62 | text-shadow:
63 | 0 1px 0 #ccc,
64 | 0 2px 0 #c9c9c9,
65 | 0 3px 0 #bbb,
66 | 0 4px 0 #b9b9b9,
67 | 0 5px 0 #aaa,
68 | 0 6px 1px rgba(0,0,0,.1),
69 | 0 0 5px rgba(0,0,0,.1),
70 | 0 1px 3px rgba(0,0,0,.3),
71 | 0 3px 5px rgba(0,0,0,.2),
72 | 0 5px 10px rgba(0,0,0,.25),
73 | 0 10px 10px rgba(0,0,0,.2),
74 | 0 20px 20px rgba(0,0,0,.15);
75 | }
76 |
77 | #header-3 > .title {
78 | color: #ddd;
79 | --step: 20;
80 | --color-side: black;
81 | --color-border: black;
82 | -webkit-text-stroke: 2px black;
83 | /* prettier-ignore */
84 | text-shadow:
85 | 0 1px 0 var(--color-side),
86 | 0 2px 0 var(--color-side),
87 | 0 3px 0 var(--color-side),
88 | 0 4px 0 var(--color-side),
89 | 0 5px 0 var(--color-side),
90 | 0 6px 0 var(--color-side);
91 | }
92 | #header-4 > .title {
93 | color: black;
94 | --step: 100;
95 | --color-side: rgba(var(--step), var(--step), var(--step), 0.3);
96 | /* prettier-ignore */
97 | text-shadow:
98 | 0 1px 0 var(--color-side),
99 | 0 2px 0 var(--color-side),
100 | 0 3px 0 var(--color-side),
101 | 0 4px 0 var(--color-side),
102 | 0 5px 0 var(--color-side),
103 | 0 6px 0 var(--color-side);
104 | }
105 | .header > .title {
106 | font-size: 5em;
107 | font: bold 100px/1 'Helvetica Neue', Helvetica, Arial, sans-serif;
108 | }
109 | .header {
110 | position: relative;
111 | --shift: 120px;
112 | width: calc(100vw + var(--shift));
113 | left: calc(-1 * var(--shift));
114 | }
115 |
116 | body {
117 | margin: 0;
118 | padding-bottom: 100px;
119 | }
120 |
121 | .circle-square {
122 | display: none;
123 | }
124 |
--------------------------------------------------------------------------------
/fonts/Inter-Var.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brillout/vike-land/31dd0acf9b5e3e127c550f73627871d3df55b255/fonts/Inter-Var.ttf
--------------------------------------------------------------------------------
/fonts/Mitr-Light.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brillout/vike-land/31dd0acf9b5e3e127c550f73627871d3df55b255/fonts/Mitr-Light.ttf
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Vike: Build Your Own Framework
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
19 |
20 |
21 | Build Your Own Framework
22 |
23 |
24 |
25 |
26 |
27 |
34 |
35 |
VIDEO
36 |
37 |
It has never been easier to build your own framework
38 |
39 | Building a framework like Next.js used to be hard. These days are over: with modern tools, such as Vite and its
40 | ecosystem, you can build your own Next.js with only hundreds of lines of code.
41 |
42 |
43 | Don't let a framework dictate your architecture, keep full control, and use any tool and technology you want.
44 |
45 |
46 | Build internal company frameworks to scale your teams, or enhance your product with a bespoke framework to
47 | delight your users, or just keep architectural control.
48 |
49 |
Build Your Own Framework.
50 |
51 |
All-included frameworks
52 |
53 | While frameworks like Next.js only take care of the frontend, you can build a framework that includes
54 | everything: the server (Express.js, Koa, ...), authentication, API (GraphQL, RPC, ...), ORM (Prisma, MikroORM,
55 | ...), logging (e.g. Prometheus), error tracking (Sentry, Bugsnag, ...), emailing (e.g. Mailgun + Nodemailer),
56 | deployment (AWS, Cloudflare Workers, ...), etc.
57 |
58 |
59 | All your framework's users have left to do is: define UI components, and define data models (if your framework
60 | has a database).
61 |
62 |
63 |
Build company internal frameworks, to scale your teams
64 |
65 | Give senior employees the power of fully owning the architecture while providing junior employees an
66 | all-included framework that takes care of everything.
67 |
68 |
Keep full control and leverage your company's favorite tools without limitations.
69 |
70 | You can then share (some of) your internal frameworks with the world, fostering open source values within your
71 | company while stimulating and attracting engineering talent. For example, Facebook's decision to open source and
72 | share React with the world has been a strong force within Facebook for engineering excellence and has enabled
73 | Facebook to hire highly skilled Software Engineers.
74 |
75 |
76 |
Build a framework tailored to your product, to delight your users
77 |
Delight your users with a bespoke framework that deeply integrates with your product.
78 |
79 | For example, if you are a headless CMS provider (Sanity, Contentful, Tina, ...), build a CMS on top. Or, if you
80 | are a full-stack deployment provider (PlanetScale, Cloudflare D1, Fly, ...), build a little full-stack framework
81 | to showcase the capabalities of your deployment platform and for the community to fork. Or, if you are an
82 | e-commerce platform, provide a framework like Shopify's
83 | Hydrogen .
84 |
85 |
Build a "Framework as a Product".
86 |
87 |
Getting Started
88 |
Vike is just a website (this website) recommending you to build your own framework.
89 |
We recommend following tools.
90 |
136 |
137 |
138 |
139 |
153 |
154 |
155 |
156 |
157 |
158 |
--------------------------------------------------------------------------------
/index/anchor-scrolling.css:
--------------------------------------------------------------------------------
1 | h1,
2 | h2[id] {
3 | cursor: pointer;
4 | }
5 | h2[id] {
6 | --anchor-icon-width: 1em;
7 | position: relative;
8 | margin-left: calc(-1 * var(--anchor-icon-width));
9 | padding-left: var(--anchor-icon-width);
10 | }
11 | h2[id]:hover:before {
12 | content: '#';
13 | position: absolute;
14 | left: 0px;
15 | color: #aaa;
16 | }
17 |
--------------------------------------------------------------------------------
/index/anchor-scrolling.ts:
--------------------------------------------------------------------------------
1 | import './anchor-scrolling.css'
2 |
3 | anchorScrolling()
4 |
5 | // Copied & adapted from https://stackoverflow.com/questions/7717527/smooth-scrolling-when-clicking-an-anchor-link/49910424#49910424
6 | function anchorScrolling() {
7 | document.querySelectorAll('a[href^="#"]').forEach((anchor) => {
8 | anchor.addEventListener('click', function (ev) {
9 | const hash = anchor.getAttribute('href')!
10 | scrollTo(hash)
11 | ev.preventDefault()
12 | })
13 | })
14 |
15 | document.querySelectorAll('h2[id]').forEach((heading) => {
16 | heading.addEventListener('click', function (ev) {
17 | const hash = heading.getAttribute('id')!
18 | scrollTo(`#${hash}`)
19 | ev.preventDefault()
20 | })
21 | })
22 |
23 | document.querySelector('h1')?.addEventListener('click', () => {
24 | document.documentElement.scrollIntoView({
25 | behavior: 'smooth',
26 | })
27 | if (window.location.hash === '' && window.scrollY === 0) {
28 | window.location.reload()
29 | } else {
30 | history.pushState(null, '', '/')
31 | }
32 | })
33 | }
34 |
35 | function scrollTo(hash: string) {
36 | const el = document.querySelector(hash)!
37 | el.scrollIntoView({
38 | behavior: 'smooth',
39 | })
40 | history.pushState(null, '', hash)
41 | }
42 |
--------------------------------------------------------------------------------
/index/css/blockquote.css:
--------------------------------------------------------------------------------
1 | blockquote {
2 | border-left: 5px solid #ccc;
3 | border-radius: 5px;
4 | margin: 0;
5 | padding: 12px 18px;
6 | background-color: #f0f0f0;
7 | margin: 6px 0;
8 | }
9 |
10 | blockquote p:first-of-type {
11 | margin-top: 0px;
12 | }
13 | blockquote p:last-of-type {
14 | margin-bottom: 0px;
15 | }
16 |
17 | /*
18 | blockquote.info::before {
19 | content: '\2139\FE0F';
20 | font-size: 1.1em;
21 | font-family: emoji;
22 | }
23 | */
24 |
--------------------------------------------------------------------------------
/index/css/code.css:
--------------------------------------------------------------------------------
1 | code {
2 | font-family: monospace;
3 | /*
4 | background-color: #f3f3f3;
5 | */
6 | background-color: #ddd;
7 | padding-left: 6px;
8 | padding-right: 5px;
9 | padding-top: 2px;
10 | padding-bottom: 3px;
11 | font-size: 1.1em;
12 | border-radius: 5px;
13 | }
14 |
--------------------------------------------------------------------------------
/index/css/copy.css:
--------------------------------------------------------------------------------
1 | #copy {
2 | max-width: 770px;
3 | width: 100vw;
4 | padding: 0 10px;
5 | margin: auto;
6 | box-sizing: border-box;
7 | }
8 | #copy h2 {
9 | font-weight: 500;
10 | margin-top: 50px;
11 | }
12 |
--------------------------------------------------------------------------------
/index/css/fonts.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: 'Inter';
3 | }
4 | button {
5 | font-family: inherit;
6 | }
7 | body {
8 | line-height: 1.5;
9 | }
10 |
11 | @font-face {
12 | font-family: 'Inter';
13 | font-weight: 100 900;
14 | font-stretch: 75% 125%;
15 | font-style: oblique 0deg 12deg;
16 | src: url('/fonts/Inter-Var.ttf') format('truetype-variations');
17 | }
18 |
--------------------------------------------------------------------------------
/index/css/index.css:
--------------------------------------------------------------------------------
1 | @import './fonts.css';
2 | @import './code.css';
3 | @import './blockquote.css';
4 | @import './copy.css';
5 |
6 | body {
7 | margin: 0;
8 | }
9 |
10 | #toc {
11 | margin-top: 20px;
12 | }
13 | #toc a {
14 | margin-bottom: 2px;
15 | display: block;
16 | font-weight: 500;
17 | }
18 |
19 | a {
20 | color: #999;
21 | text-decoration: none;
22 | }
23 |
24 | #tool-list li {
25 | margin: 25px 0px;
26 | }
27 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "scripts": {
3 | "dev": "vite",
4 | "build": "vite build"
5 | },
6 | "dependencies": {
7 | "@types/node": "^17.0.42",
8 | "@types/react": "^18.2.11",
9 | "@types/react-dom": "^18.2.4",
10 | "@types/zdog": "^1.1.2",
11 | "react": "^18.2.0",
12 | "react-dom": "^18.2.0",
13 | "vite": "^4.3.9",
14 | "zdog": "1.1.3"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/pnpm-lock.yaml:
--------------------------------------------------------------------------------
1 | lockfileVersion: '9.0'
2 |
3 | settings:
4 | autoInstallPeers: true
5 | excludeLinksFromLockfile: false
6 |
7 | importers:
8 |
9 | .:
10 | dependencies:
11 | '@types/node':
12 | specifier: ^17.0.42
13 | version: 17.0.42
14 | '@types/react':
15 | specifier: ^18.2.11
16 | version: 18.2.11
17 | '@types/react-dom':
18 | specifier: ^18.2.4
19 | version: 18.2.4
20 | '@types/zdog':
21 | specifier: ^1.1.2
22 | version: 1.1.2
23 | react:
24 | specifier: ^18.2.0
25 | version: 18.2.0
26 | react-dom:
27 | specifier: ^18.2.0
28 | version: 18.2.0(react@18.2.0)
29 | vite:
30 | specifier: ^4.3.9
31 | version: 4.3.9(@types/node@17.0.42)
32 | zdog:
33 | specifier: 1.1.3
34 | version: 1.1.3
35 |
36 | packages:
37 |
38 | '@esbuild/android-arm64@0.17.19':
39 | resolution: {integrity: sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==}
40 | engines: {node: '>=12'}
41 | cpu: [arm64]
42 | os: [android]
43 |
44 | '@esbuild/android-arm@0.17.19':
45 | resolution: {integrity: sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==}
46 | engines: {node: '>=12'}
47 | cpu: [arm]
48 | os: [android]
49 |
50 | '@esbuild/android-x64@0.17.19':
51 | resolution: {integrity: sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==}
52 | engines: {node: '>=12'}
53 | cpu: [x64]
54 | os: [android]
55 |
56 | '@esbuild/darwin-arm64@0.17.19':
57 | resolution: {integrity: sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==}
58 | engines: {node: '>=12'}
59 | cpu: [arm64]
60 | os: [darwin]
61 |
62 | '@esbuild/darwin-x64@0.17.19':
63 | resolution: {integrity: sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==}
64 | engines: {node: '>=12'}
65 | cpu: [x64]
66 | os: [darwin]
67 |
68 | '@esbuild/freebsd-arm64@0.17.19':
69 | resolution: {integrity: sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==}
70 | engines: {node: '>=12'}
71 | cpu: [arm64]
72 | os: [freebsd]
73 |
74 | '@esbuild/freebsd-x64@0.17.19':
75 | resolution: {integrity: sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==}
76 | engines: {node: '>=12'}
77 | cpu: [x64]
78 | os: [freebsd]
79 |
80 | '@esbuild/linux-arm64@0.17.19':
81 | resolution: {integrity: sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==}
82 | engines: {node: '>=12'}
83 | cpu: [arm64]
84 | os: [linux]
85 |
86 | '@esbuild/linux-arm@0.17.19':
87 | resolution: {integrity: sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==}
88 | engines: {node: '>=12'}
89 | cpu: [arm]
90 | os: [linux]
91 |
92 | '@esbuild/linux-ia32@0.17.19':
93 | resolution: {integrity: sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==}
94 | engines: {node: '>=12'}
95 | cpu: [ia32]
96 | os: [linux]
97 |
98 | '@esbuild/linux-loong64@0.17.19':
99 | resolution: {integrity: sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==}
100 | engines: {node: '>=12'}
101 | cpu: [loong64]
102 | os: [linux]
103 |
104 | '@esbuild/linux-mips64el@0.17.19':
105 | resolution: {integrity: sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==}
106 | engines: {node: '>=12'}
107 | cpu: [mips64el]
108 | os: [linux]
109 |
110 | '@esbuild/linux-ppc64@0.17.19':
111 | resolution: {integrity: sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==}
112 | engines: {node: '>=12'}
113 | cpu: [ppc64]
114 | os: [linux]
115 |
116 | '@esbuild/linux-riscv64@0.17.19':
117 | resolution: {integrity: sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==}
118 | engines: {node: '>=12'}
119 | cpu: [riscv64]
120 | os: [linux]
121 |
122 | '@esbuild/linux-s390x@0.17.19':
123 | resolution: {integrity: sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==}
124 | engines: {node: '>=12'}
125 | cpu: [s390x]
126 | os: [linux]
127 |
128 | '@esbuild/linux-x64@0.17.19':
129 | resolution: {integrity: sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==}
130 | engines: {node: '>=12'}
131 | cpu: [x64]
132 | os: [linux]
133 |
134 | '@esbuild/netbsd-x64@0.17.19':
135 | resolution: {integrity: sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==}
136 | engines: {node: '>=12'}
137 | cpu: [x64]
138 | os: [netbsd]
139 |
140 | '@esbuild/openbsd-x64@0.17.19':
141 | resolution: {integrity: sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==}
142 | engines: {node: '>=12'}
143 | cpu: [x64]
144 | os: [openbsd]
145 |
146 | '@esbuild/sunos-x64@0.17.19':
147 | resolution: {integrity: sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==}
148 | engines: {node: '>=12'}
149 | cpu: [x64]
150 | os: [sunos]
151 |
152 | '@esbuild/win32-arm64@0.17.19':
153 | resolution: {integrity: sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==}
154 | engines: {node: '>=12'}
155 | cpu: [arm64]
156 | os: [win32]
157 |
158 | '@esbuild/win32-ia32@0.17.19':
159 | resolution: {integrity: sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==}
160 | engines: {node: '>=12'}
161 | cpu: [ia32]
162 | os: [win32]
163 |
164 | '@esbuild/win32-x64@0.17.19':
165 | resolution: {integrity: sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==}
166 | engines: {node: '>=12'}
167 | cpu: [x64]
168 | os: [win32]
169 |
170 | '@types/node@17.0.42':
171 | resolution: {integrity: sha512-Q5BPGyGKcvQgAMbsr7qEGN/kIPN6zZecYYABeTDBizOsau+2NMdSVTar9UQw21A2+JyA2KRNDYaYrPB0Rpk2oQ==}
172 |
173 | '@types/prop-types@15.7.5':
174 | resolution: {integrity: sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==}
175 |
176 | '@types/react-dom@18.2.4':
177 | resolution: {integrity: sha512-G2mHoTMTL4yoydITgOGwWdWMVd8sNgyEP85xVmMKAPUBwQWm9wBPQUmvbeF4V3WBY1P7mmL4BkjQ0SqUpf1snw==}
178 |
179 | '@types/react@18.2.11':
180 | resolution: {integrity: sha512-+hsJr9hmwyDecSMQAmX7drgbDpyE+EgSF6t7+5QEBAn1tQK7kl1vWZ4iRf6SjQ8lk7dyEULxUmZOIpN0W5baZA==}
181 |
182 | '@types/scheduler@0.16.3':
183 | resolution: {integrity: sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==}
184 |
185 | '@types/zdog@1.1.2':
186 | resolution: {integrity: sha512-E1nINLaVvxu7N66gANMjhlxMdGL9KqsePa7NyO4p96xeQ7YiAjydVH+eE4W2aLdGyIcLqt+pNMK8l7+gD8c+Wg==}
187 |
188 | csstype@3.1.2:
189 | resolution: {integrity: sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==}
190 |
191 | esbuild@0.17.19:
192 | resolution: {integrity: sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==}
193 | engines: {node: '>=12'}
194 | hasBin: true
195 |
196 | fsevents@2.3.2:
197 | resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
198 | engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
199 | os: [darwin]
200 |
201 | js-tokens@4.0.0:
202 | resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
203 |
204 | loose-envify@1.4.0:
205 | resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
206 | hasBin: true
207 |
208 | nanoid@3.3.6:
209 | resolution: {integrity: sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==}
210 | engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
211 | hasBin: true
212 |
213 | picocolors@1.0.0:
214 | resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
215 |
216 | postcss@8.4.24:
217 | resolution: {integrity: sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg==}
218 | engines: {node: ^10 || ^12 || >=14}
219 |
220 | react-dom@18.2.0:
221 | resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==}
222 | peerDependencies:
223 | react: ^18.2.0
224 |
225 | react@18.2.0:
226 | resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==}
227 | engines: {node: '>=0.10.0'}
228 |
229 | rollup@3.25.1:
230 | resolution: {integrity: sha512-tywOR+rwIt5m2ZAWSe5AIJcTat8vGlnPFAv15ycCrw33t6iFsXZ6mzHVFh2psSjxQPmI+xgzMZZizUAukBI4aQ==}
231 | engines: {node: '>=14.18.0', npm: '>=8.0.0'}
232 | hasBin: true
233 |
234 | scheduler@0.23.0:
235 | resolution: {integrity: sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==}
236 |
237 | source-map-js@1.0.2:
238 | resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==}
239 | engines: {node: '>=0.10.0'}
240 |
241 | vite@4.3.9:
242 | resolution: {integrity: sha512-qsTNZjO9NoJNW7KnOrgYwczm0WctJ8m/yqYAMAK9Lxt4SoySUfS5S8ia9K7JHpa3KEeMfyF8LoJ3c5NeBJy6pg==}
243 | engines: {node: ^14.18.0 || >=16.0.0}
244 | hasBin: true
245 | peerDependencies:
246 | '@types/node': '>= 14'
247 | less: '*'
248 | sass: '*'
249 | stylus: '*'
250 | sugarss: '*'
251 | terser: ^5.4.0
252 | peerDependenciesMeta:
253 | '@types/node':
254 | optional: true
255 | less:
256 | optional: true
257 | sass:
258 | optional: true
259 | stylus:
260 | optional: true
261 | sugarss:
262 | optional: true
263 | terser:
264 | optional: true
265 |
266 | zdog@1.1.3:
267 | resolution: {integrity: sha512-raRj6r0gPzopFm5XWBJZr/NuV4EEnT4iE+U3dp5FV5pCb588Gmm3zLIp/j9yqqcMiHH8VNQlerLTgOqL7krh6w==}
268 |
269 | snapshots:
270 |
271 | '@esbuild/android-arm64@0.17.19':
272 | optional: true
273 |
274 | '@esbuild/android-arm@0.17.19':
275 | optional: true
276 |
277 | '@esbuild/android-x64@0.17.19':
278 | optional: true
279 |
280 | '@esbuild/darwin-arm64@0.17.19':
281 | optional: true
282 |
283 | '@esbuild/darwin-x64@0.17.19':
284 | optional: true
285 |
286 | '@esbuild/freebsd-arm64@0.17.19':
287 | optional: true
288 |
289 | '@esbuild/freebsd-x64@0.17.19':
290 | optional: true
291 |
292 | '@esbuild/linux-arm64@0.17.19':
293 | optional: true
294 |
295 | '@esbuild/linux-arm@0.17.19':
296 | optional: true
297 |
298 | '@esbuild/linux-ia32@0.17.19':
299 | optional: true
300 |
301 | '@esbuild/linux-loong64@0.17.19':
302 | optional: true
303 |
304 | '@esbuild/linux-mips64el@0.17.19':
305 | optional: true
306 |
307 | '@esbuild/linux-ppc64@0.17.19':
308 | optional: true
309 |
310 | '@esbuild/linux-riscv64@0.17.19':
311 | optional: true
312 |
313 | '@esbuild/linux-s390x@0.17.19':
314 | optional: true
315 |
316 | '@esbuild/linux-x64@0.17.19':
317 | optional: true
318 |
319 | '@esbuild/netbsd-x64@0.17.19':
320 | optional: true
321 |
322 | '@esbuild/openbsd-x64@0.17.19':
323 | optional: true
324 |
325 | '@esbuild/sunos-x64@0.17.19':
326 | optional: true
327 |
328 | '@esbuild/win32-arm64@0.17.19':
329 | optional: true
330 |
331 | '@esbuild/win32-ia32@0.17.19':
332 | optional: true
333 |
334 | '@esbuild/win32-x64@0.17.19':
335 | optional: true
336 |
337 | '@types/node@17.0.42': {}
338 |
339 | '@types/prop-types@15.7.5': {}
340 |
341 | '@types/react-dom@18.2.4':
342 | dependencies:
343 | '@types/react': 18.2.11
344 |
345 | '@types/react@18.2.11':
346 | dependencies:
347 | '@types/prop-types': 15.7.5
348 | '@types/scheduler': 0.16.3
349 | csstype: 3.1.2
350 |
351 | '@types/scheduler@0.16.3': {}
352 |
353 | '@types/zdog@1.1.2': {}
354 |
355 | csstype@3.1.2: {}
356 |
357 | esbuild@0.17.19:
358 | optionalDependencies:
359 | '@esbuild/android-arm': 0.17.19
360 | '@esbuild/android-arm64': 0.17.19
361 | '@esbuild/android-x64': 0.17.19
362 | '@esbuild/darwin-arm64': 0.17.19
363 | '@esbuild/darwin-x64': 0.17.19
364 | '@esbuild/freebsd-arm64': 0.17.19
365 | '@esbuild/freebsd-x64': 0.17.19
366 | '@esbuild/linux-arm': 0.17.19
367 | '@esbuild/linux-arm64': 0.17.19
368 | '@esbuild/linux-ia32': 0.17.19
369 | '@esbuild/linux-loong64': 0.17.19
370 | '@esbuild/linux-mips64el': 0.17.19
371 | '@esbuild/linux-ppc64': 0.17.19
372 | '@esbuild/linux-riscv64': 0.17.19
373 | '@esbuild/linux-s390x': 0.17.19
374 | '@esbuild/linux-x64': 0.17.19
375 | '@esbuild/netbsd-x64': 0.17.19
376 | '@esbuild/openbsd-x64': 0.17.19
377 | '@esbuild/sunos-x64': 0.17.19
378 | '@esbuild/win32-arm64': 0.17.19
379 | '@esbuild/win32-ia32': 0.17.19
380 | '@esbuild/win32-x64': 0.17.19
381 |
382 | fsevents@2.3.2:
383 | optional: true
384 |
385 | js-tokens@4.0.0: {}
386 |
387 | loose-envify@1.4.0:
388 | dependencies:
389 | js-tokens: 4.0.0
390 |
391 | nanoid@3.3.6: {}
392 |
393 | picocolors@1.0.0: {}
394 |
395 | postcss@8.4.24:
396 | dependencies:
397 | nanoid: 3.3.6
398 | picocolors: 1.0.0
399 | source-map-js: 1.0.2
400 |
401 | react-dom@18.2.0(react@18.2.0):
402 | dependencies:
403 | loose-envify: 1.4.0
404 | react: 18.2.0
405 | scheduler: 0.23.0
406 |
407 | react@18.2.0:
408 | dependencies:
409 | loose-envify: 1.4.0
410 |
411 | rollup@3.25.1:
412 | optionalDependencies:
413 | fsevents: 2.3.2
414 |
415 | scheduler@0.23.0:
416 | dependencies:
417 | loose-envify: 1.4.0
418 |
419 | source-map-js@1.0.2: {}
420 |
421 | vite@4.3.9(@types/node@17.0.42):
422 | dependencies:
423 | '@types/node': 17.0.42
424 | esbuild: 0.17.19
425 | postcss: 8.4.24
426 | rollup: 3.25.1
427 | optionalDependencies:
428 | fsevents: 2.3.2
429 |
430 | zdog@1.1.3: {}
431 |
--------------------------------------------------------------------------------
/pricing.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/pricing/Pricing.css:
--------------------------------------------------------------------------------
1 | #pricing-page {
2 | width: 1100px;
3 | margin: auto;
4 | }
5 |
6 | #pricing-container {
7 | display: flex;
8 | }
9 |
10 | .pricing-table {
11 | display: table;
12 | font-size: 1.07em;
13 | border-spacing: 40px 8px;
14 | margin: auto;
15 | }
16 | .pricing-row {
17 | display: table-row;
18 | }
19 | .pricing-cell {
20 | display: table-cell;
21 | /*
22 | padding: 20px;
23 | */
24 | }
25 |
26 | .pricing-header {
27 | font-size: 1.45em;
28 | margin: 7px 30px;
29 | margin-bottom: 20px;
30 | font-weight: 500;
31 | text-align: center;
32 | }
33 |
34 | .pricing-price,
35 | .pricing-free {
36 | font-size: 1.3rem;
37 | font-weight: 600;
38 | }
39 | .pricing-price {
40 | color: blue;
41 | }
42 | .pricing-free {
43 | color: green;
44 | }
45 |
46 | .pricing-price-container {
47 | color: #777;
48 | line-height: 1.4em;
49 | }
50 | .pricing-price-container > * {
51 | display: inline;
52 | vertical-align: middle;
53 | }
54 | .pricing-price-container {
55 | font-size: 0.99rem;
56 | }
57 | .pricing-price-note {
58 | /*
59 | line-height: 0;
60 | */
61 | }
62 |
63 | .pricing-box {
64 | border-radius: 10px;
65 | border: 1px solid #ddd;
66 | padding: 20px;
67 | margin: 10px;
68 | }
69 | /*
70 | #pricing-individual.pricing-box {
71 | width: 400px;
72 | }
73 | #pricing-company.pricing-box {
74 | width: 400px;
75 | }
76 | */
77 |
78 | /*
79 | h2 {
80 | font-weight: 500;
81 | font-size: 1.3em;
82 | }
83 | */
84 |
--------------------------------------------------------------------------------
/pricing/Pricing.tsx:
--------------------------------------------------------------------------------
1 | export { Pricing }
2 |
3 | import './Pricing.css'
4 | import React from 'react'
5 |
6 | function Pricing() {
7 | return (
8 |
12 | )
13 | }
14 |
15 | function PricingIndividual() {
16 | return (
17 | <>
18 |
19 |
Individual
20 |
21 |
22 |
Always
23 |
24 |
25 |
26 |
Free
27 |
28 |
29 |
30 |
31 | >
32 | )
33 | }
34 | function PricingCompany() {
35 | return (
36 | <>
37 |
38 |
Company
39 |
40 |
41 |
Prototyping
42 | Development
43 | Finished
44 |
45 |
46 |
47 |
Free
48 |
49 |
50 |
51 |
$49
/ month
52 |
53 | per paid dev
54 |
55 |
56 |
57 |
Free
58 |
59 |
60 |
61 |
62 | >
63 | )
64 | }
65 |
66 | function P({ c, ...props }: { c: string } & Record) {
67 | return
68 | }
69 |
--------------------------------------------------------------------------------
/pricing/PricingDesc.tsx:
--------------------------------------------------------------------------------
1 | export { PricingDesc }
2 |
3 | import React from 'react'
4 |
5 | function PricingDesc() {
6 | return (
7 |
8 | {/*
9 |
Accessible to everyone
10 | */}
11 |
12 | )
13 | }
14 |
--------------------------------------------------------------------------------
/pricing/index.css:
--------------------------------------------------------------------------------
1 | @import '../index/css/fonts.css';
2 |
3 | body {
4 | margin: 0;
5 | }
6 |
--------------------------------------------------------------------------------
/pricing/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom/client'
3 |
4 | import { Pricing } from './Pricing'
5 | import { PricingDesc } from './PricingDesc'
6 |
7 | const container = document.getElementById('react-root')
8 | const root = ReactDOM.createRoot(container!)
9 | root.render(
10 |
11 |
Pricing
12 |
13 |
14 |
,
15 | )
16 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "strict": true,
4 | "module": "ES2020",
5 | "moduleResolution": "Node",
6 | "target": "ES2020",
7 | "lib": ["DOM", "DOM.Iterable", "ESNext"],
8 | "types": ["vite/client"],
9 | "jsx": "react",
10 | "skipLibCheck": true,
11 | "esModuleInterop": true
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | // vite.config.js
2 | import path from 'path'
3 | import type { UserConfig } from 'vite'
4 |
5 | const config: UserConfig = {
6 | server: {
7 | host: true,
8 | port: 3000
9 | },
10 | build: {
11 | rollupOptions: {
12 | input: {
13 | index: path.resolve(__dirname, 'index.html'),
14 | editor: path.resolve(__dirname, 'editor.html'),
15 | embed: path.resolve(__dirname, 'embed.html')
16 | }
17 | }
18 | }
19 | }
20 | export default config
21 |
--------------------------------------------------------------------------------